From f4c58102a1e5d755da356874b1055850fb77169e Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Wed, 20 Nov 2024 15:36:53 -0500
Subject: [PATCH 01/34] Initial uwb work (#9)

* Initial uwb work

* Update short address config

* Add anchor calibration setup

* DW1000 library changes - script trilateration

* Add anchor positioning to plot script
---
 libraries/dw1000.tar.gz     | Bin 0 -> 204800 bytes
 uwb/anchor/anchor.ino       | 130 ++++++++++++++++++
 uwb/setup/anchor/anchor.ino | 135 +++++++++++++++++++
 uwb/setup/tag/tag.ino       |  63 +++++++++
 uwb/tag/link.cpp            | 172 ++++++++++++++++++++++++
 uwb/tag/link.h              |  18 +++
 uwb/tag/tag.ino             | 219 ++++++++++++++++++++++++++++++
 uwb/uwb_position2.0.py      | 259 ++++++++++++++++++++++++++++++++++++
 8 files changed, 996 insertions(+)
 create mode 100644 libraries/dw1000.tar.gz
 create mode 100644 uwb/anchor/anchor.ino
 create mode 100644 uwb/setup/anchor/anchor.ino
 create mode 100644 uwb/setup/tag/tag.ino
 create mode 100644 uwb/tag/link.cpp
 create mode 100644 uwb/tag/link.h
 create mode 100644 uwb/tag/tag.ino
 create mode 100644 uwb/uwb_position2.0.py

diff --git a/libraries/dw1000.tar.gz b/libraries/dw1000.tar.gz
new file mode 100644
index 0000000000000000000000000000000000000000..53d2db5bbd2ac9f2271d7a053286cffcb60ae0c6
GIT binary patch
literal 204800
zcmeFa`&wH^(mtAh;VM>~XA)s^ki^j>&WsSq<`)c%gk#V6%cDm~3s5X1MH0rI%<S9Q
z_pz^Rzi(Ba*3!}ej5FW-#?FiltGlbKtE;Q4tE#K_Ul;QE{MuKa^A9@6Z*OjXg?|d$
zoB8lx7V*zlg^lgP_Qra#xKaEnUnmqew!extKPOTa_RVnA7(~%mzxD>LUe|sd|94jT
zc#HptFZ(+G@8XZQAi!{S^kMYicyofcHa5a}w|u~&LUDWRtHSzbKEJ*JK0&?0`qozd
zt0@2Am}CI_FQ5N!=KHhwO;r2X9W_2g<zA;ZXm>B8myLc@=?x;~pvC#%e6u)r#c!gc
z_W7VO_!#AWI{4e`lj?q9Y3|00iq(Q`X?{OzU&sF2+|a&>UdE%VUTYXNx~-^k(`}C0
zz3$K<R=fjZ=kaB`YjG$p#@&|xWf*tj=E(ovjc?1n?nV3ZX3$`dzNFc^zHW~u6r$PL
zpwS(ohHpVJHyZzpy4AQDx8FOu6c?rH_}6%}A73<XI-{Y7`b~6pvVRh-uCCr?Vu3gM
z=#D_aqr<a<YU$`MQycV|v7Wztx)TMA{2vaQpHBy%ke_q^&lk6#ClreLjl!4x|4tKm
z&&t94x8nXFZZ<}7YxU}$(6XCx{-^E#g|YpAV?7W3@5}z5P0xq?y7u_-V)PihdjI2~
zeR(yCa?Pcvm@jNa=O3e^o7QdnGI~B}cREq-_V#vl&>Q{Z{HC$mikH}|*1qm{;>dGO
z1TFGiW6-<7K6wG<pfqURw7b3NaQ}ey@lRA(-CEs=ZiaDm*=$A|`1$s#-Morgy{Ow8
zMZ=qZzc&~~%_mO^8`0ah9`-N45X2D}bP&Cd2SeF{hgZFuPAl1JIT~HXUFkxh3`KQ8
z>$Syi+85ncd=c#*oK_FYrL%+m`isSH@TJ|2lOI`?HS6`O#<1S(_v?+(XwW{t8O1Eh
z`kwR1LL(RhnjXz~jrz;l^W63Daw$<&zF_p<Kv_V?YiqOnjypqOIgUZy529Yrh4^M6
z?9rLnPH<a7<7v1-H*m!X!apP@e}Q!$3Iy_Be_REp-v`i?HdEz)VY>heggyV~i|YlR
z|JFCL|9_GHQ;7ONMQhpOf0N?>?5cO&7)ED<-meh3Ki~v}U;i?aUst>F=ts)qeW(n#
zjrS0qVo8WP=Fmpz-%#nlih3ft+s(K;j9U?Q2LObSj!OMT6aO4($#V3w%JrzYnvZg<
z@W>QBTKb+KKK5>+>&8dfDY%7<uG+)sqK!Q${?Ls3qp01Da5n09+Kp~Aj&9qdE9u8T
zAtuq^42<6SsDbJYH0XbfdKY2Es4+4~NBDC!8ufSA*0=#S1mV@*;Bu{_NDtSJ4$B9}
zwSyHPZ5qAmcH-eM8pOZdv<Db4h}q}^sb=FGsCF8+5vg-Ih*37`5$W4OdxTT<ay0B+
zjBXo)n3%TOL$l+AG&5s~F!HcM4@2ojk4m-Zu=Xh0E7cBb%LMZJ@a)COtF!2Jsah=^
zpB*05qLXS=J~`e$JUcu&#&4CVbo{sIr^Dm@<tPTlFgNiB=z<sl(Bu~1hE7OWwU{J#
z^Ceq6e;@YaX8WSu#L&8zH;qf|?LBPKUD^ry@!+~W)ZH4#XM*XpaWs_^?J(elV2)Vn
zFEXuODDj(BCt3p>SO<>Ra>L%u0MpGDOAe_tXTEy97mau>mgDXqIxKTcYuKFH-Dc;e
z1!gn7T|HVf27_{^0V?R{?-v)>9!GQ9Ip(Rx5wTuWBQcuMb$oq}QN3>r+T@er9s9E2
z=IMH79TEnLM!(%1E<%GF?bJtx3__CK-TH9&{e+Uia5Sl?J@}1eT#F8?e~p@rPUpPQ
zd^cQt-)py`++(G)?cfUB(ur$vceIpag{A0Ol>dHO-GjlPH<-7wF>ti*SijN$lijru
z?M}U$xyNBpV=LH6uKT6;=#(sxz?Ebiz?w9KLOfhN|2VQT*b;E~aoD`Le0y|oT(AAD
zRxekc|MLB$dZR(dS3Ij8O{@uoMmIxOw{})KdsWM>J^FCbv~bVf)GOs_aIv+#b5LKF
zYCmPeY=SEs(DIAYaeXpWY*9Rpl)9}_t3`1p@J~y}rTr?PlYZB|ZhIv5%q+sZzm6Nj
z_4n(_co`HPu|jdZtQ82*NG7Zph`&ZBBDvx$*zt!EbmF*G88oirqquvCo$6Ussg_<I
z)G=ev&tBAzPpU6VN10QGU8vUnM~oJyrE<M?`1gbQPIko`oCGS|h~jSZBkE%!PG23>
z4(gTa!C&izt(PzUp4qU~7&WTEolUB5>Qx+->RUVc{7-wQwaf<CFdEn3Q8yO$R6QuY
z+&hvH)eFT5D4K@l9z-w(WZRTa_74=n`bK7t<b`hBK^ro{@xf7j^ZUTio03<sH^$v=
zV?XXRJ_1~?*NL%TCPj{^Q*MYB14{^CUo=3$NezbAu&SQ+ZXvc^%?YqCIML;+xcLrM
zM}wP*Fu7qix)6It40y=dg18mGhbHn;sdu;!YQF^KzUMhnjI)Ed-)UfXgc1^WZ>|?7
z;Ma=JacpnK<xa2p?x0I?6AWJ$OADJ46r~<el<k{er4Q<21P;YP8Gl(+w5C|Ep$85c
zA6Zw_wN~8hG_*Z+m@bqcRfDb@@8V$hYc!i!vu)r(3jzKf+;m3}iP6QN=R1P-)`}O=
z5yT*r7K}122T6_k(aG`i{nA+pjC}l@zrH>_d3{iQ3pp^eZSBQL^-LO9O0~1QwRQd8
zy=ki3Qn~!9S}Ol-E|inq-mj@rDr}3xf)MM@L<S50?xYCLdlVweS)xckS_#0N3#Q<m
zf0;>|)NhJXesUB2TIn@s#}HSu`b|<f==PKP%@;Rjb(|WopQE`t-r9iBAE)%42OM<!
zN&U_-U`OdRQzkl|z*!T<YdA|4U7QPWYB_J?ygO3mI|qk!^zy|&cshG~ef^90{+<Tk
ziu}03^V>z62T%M!;WJ#CDpB?hOSL*cV`rPSl3mrUjk&4<xp|?oszRNzrd|I;m4q%l
z-C2$bJKOkc`zij~TE|}-{Hw@sd3@X1SdKRF?|NZ5%HyA{<!EbDKR=Z^_<(8RU&Se0
z?z$$D(zUe70~D-9%h6MGx>E#fbhU}D*grlO@#8w`Y@lcZMO*lH8{f9k>kj_i*_zd-
zrJ?k>PK=9yQDDbA7(Dyiz}F&vH~nqkvrt2j6GE^?VT*%g{lYe>_H=umuHA42#5ze^
z0I;V3vyCn{*(ZMHKOpo5K5-6Yc&x^b3Z$?gy-v(GNM_~n+Ua35j7PLH46Swhpmw@b
z+}aY=RlU2B`GjrOYK_>Xcd!<&{z1$74b2Yk+tAgZP;77qFUmSGTLU_&nLbj=)kR}C
zA_~u<piZtx>mdFdEk`eFdzHg#?QEG9M2X46>hq1*vUT4H8qNhAlQ0W7P3kjIA9i}T
z6H%Xt`8dw06K?ZnFo2$?kiV@P0)0L^!yyhkBF1p?xTA5w84MoW%Ag1OgdPs`a!ZT9
zFD`&GtMbeDi~m~8sHg!GIja{BA$!pZM?DFo$l>?z7Z<2^B8r4+E$vZF{s8-4bb53|
z!xePTobe)^!<E&8AE?p6k)-u;S=z$k6a9)PAqsj_7~Txv`&x)aK^9#(S9*1JQZFB!
zlz(D(Egmp(o2F;t0%z7$yC3C@ZH*Q_8rH609t9+LbaHi=Oro!0-uQ5Ffln9%v2NWm
zHZS5yEE-<Lfb$+E#+~T@`+qm#Ln}mm8rq7G1vJ57-E?WLK~3?9(U->Y&{EcouHf5?
zIFe3`&M!JWsPEm&RrG@<JUR#p%hBQS>8mrM{x2otC}+k2R}w=%psmZ9TDYjoBS*Wn
zxIp7&{Bxso6QA|=xCQ3wV`py&tq+yTazvd@kKlwXEsquEVGAeqeL2jS^Ln~-7&J!l
zH8lPpCT4K~W;4(@S66d~2*<$W!Cp|j-@Cy<V(;eSB4+ebPEK{E?2w1`!A&0rIUG#d
zqmMZr&m4L+>9rhoS~X^uG=g&MCnOlDhwql7_G-L}gAkPi?P0jM(8RfO;JO?N2qA;^
zbAag@y+rGDADFXNbOZ<Lb-Ov}t;n$_!G>?n4l}tBhe<FBT1r;|d(OkdvIMvqgvmht
zzD)i5b1Wc!=3b%)vXiq@)WK&QhH+Mln}{dG+G^cMv{--8Yu!lLR0RGQ3D_a5$y=-I
z;AFPNH1@DeuN(cv1v$i>-(1vtqyF2CUoeI2OIQn4bNLVXJhnPhVM&g<Vupq(2VJhO
zu17yx>=qa5ff>ib%xA*H&N>MujXY+&fEd_)LWr3%%1NV?l!5^jTOqBeehm(X%Mon#
zW(~I5moSreUUPMUy;1h!@c9eyWoADTSms0_46VzN?Ge&N1S4e1W!#P7o*#lG;c$R~
zivO<N;^e(@(_d-B;0kX(1Xx;*S@u`^KdrZvHPDttVys40>*}E22UCyv7oAwy-l`p9
zo3|>58@Ax;BoB~;(F_p)5dOLY=*YMpQDz<Z4hp`>K5;VyFS>P=GsHV>ch7qME4o@z
zexZDf;Y9cto%bM!T?7G56mWyAUane2NV~;01Vw=u$iT6Io;QZxXb%x{1I86W{wxv;
z_(C1MVT$oLP_Iug(+EgJ7QheY#YFJY<4$nPNwr5!<gUuH7{Z<(m=)1ONJZO9o+Rlp
z46)mct_Ho^2$Mud&xH()CK=GC5j4e!7Z(&q*VhCB6Un7nSXK{fhsV!nVwuV2fi&Y@
zkAa9!2Vxz93A;STJi5Ui{+`<r#I1-Dd_BWQvHwDV)T^-OWJv(%)TXK;Cp0o)6uMJe
zNcmr6zxJE8lJiDfUJYxnUpBr)I;)^Qz_`0BVJXDn2yv6=ZAe0FwH!r^7#j*P(G^fx
z<UKz)nXe+>PTc_V5^h&fXI2JSvou$-N|ZZ&Su2ZOe2FVmnFJgj_i1*EJ6PO8mHzdx
zNn3he?c%nP$VRCE>FaFmMA?|y?T+i(yFJ%+OvT++Gf-S53yD>nSQng^BM+cnd$qS5
z1zm{Z;BZVfCdfl57Z$WOb{(^+En-6l^)056T35c1_*yVu*YVZTz}AEZ%6&8ScKA)U
zaM__|52Z*Xf(A3tm+Ha3J&ryR@Lz$}w=$|?vKtb2Dv8-q=AsKV1>p?gI@wcZa*|Qc
z9D0rq{9Gp|b0(VOoIo8~_4;SeqCg#2Rk}XuoCWRIEdiNd-xQP!3N1w|2l$Tl$|EV|
zrSH@$U)g<{6J!!B`jm;Pl*hjR^I7CTPU;f}7^CoI_vy});ZUac=|K8E-El-D9N17?
z@6hMq1c(r+K#3<r2^f0j6wctZ{m>VoN5TIUe1=_quRY2+t}(|MCzY0?=coJSgZkbf
zj2H#7_3Rcuz4&|i=qE@h-{>xs9A6C3G%`Io>T{a1zK`P8UZ>sVDLF-RxPqE~lQ=rq
zFLU<{RyHdn7d9(RT-Pl00$v-tf!LM-=`w08M|(%dAe*6cHp%AcCzBxP`}yhNiJXkB
z7LB;fzSpU*vD111c-e3F_(h1smSVmNh-51`BDqRQBFSg&vt%U$Mk(bXkW#LY&8c(e
zny@ECZEk1Z<DVa*BK~>u#3(3+mJ?tsM;u%9Bx)~5ISwQG@yDpW6n(2a41pJr_dXbA
z(zq4J{aOcq1<cmyb?dh_XrXj+Tz{=m3zpYxxg(>klcXZPE=RUV$>&>=e720u3~jP2
zK&Lo<4PC9Q%hMVL=4!K_Pc{mOcozf%ZLLmqtl(E78Kn?#83mKpaRhR^tcatD`c}a&
zM+?9$Na-b@RM<<h5{Q2Grgn6y;|^^sR%~ViN4si9yMSP+9c3PL4c$1e;3!pcIq%>o
zQE`p<vgM9qw$22WVsp<@gsi(j*eqa=@e0cb#rx@?j^k~TJ?3e2dT92E3`U&TQ~CHT
zdqVus?V8}=V17S3I5@4_Ey;Ej%!Xs%IfxNXgN+6fT4^*;xU$LJ$!S2HNz;I$U>c;Q
z8--~WQgp1sa`fsH`cvik<XOmQQ!D*!w)d%InT=egNF4UtuVIS0F(Pr?00bX1>7j=K
zJr_(e5~*Q1OBXPELdxV$uV+f{LQnt7J`jjmTjjR1Uqkf{?ANq%S84nftE17Goi6!r
zFl~Fg+Cq}9FxFrND`7ol89p;7mb);RT(`S0rfufCFdi94LE{3(GKRFku8E)8*YRX?
zyXm)pbF+4QJ!bkZtQOVczv=s1$HqOqKa2v}0iu6k2!b_;_C49)bBzw-xo_YVfJ?}`
zKaA%iYFxlz4TC4<58SfAc4i*4`PNT1jWRQJ^@-b%k*pIHY%;$qOq|{W>6F?l?gn$+
zoc-^5rcZ2yV*b!Y$|=plIeBC^y;eTG*9j92>y??q%n8&3{!6HSIrUn~6pBXsi)guE
zgnbhcC>kPY&vD`NX(GZ<3k$9gGwp){wXn#-Oe|=)Q=x)U!1RSfwhM4Y5E-M&aW*mQ
zi<Mhz))y4R4J9}?E}JY-C8_khTt9ntd>}efSX>kx$`%)?LWRZaiQ;uv+!}l!@cg-y
z?;|df(DUc%3WY|ZLZLBMAqk-{20;<DJ$M8S1{*ID9c;XC9dr<0cX6r1hvkqPoFBaw
zDr{5eL8B0%f_lzlVfEltj(I|u(T6^x9HmzVb~1R*^*Oj0eBj=FCB@ZBz50eaqep6R
zF_H?l{7lMS`%$xhDXT@nm+Pm`_YaOre{&TshW$<l!;<n!tqwB<#+5AQ#02G>n4rQ3
zI8sKo_{~}ANbJ90Gs^=7<B9Dj*g3+cmJu;)c2JQy3BRZ1K4>u?c6PR{YG~PzOC~fQ
z)o1l4p-Xf2_7wI~(SR~~a~rQ1ag6as%>MBG=sgWF6I)wOK$xOA{bh|40Veqm#m#)N
z^fbSXBWu{bXeR-j^(k=H2?zN<5YG~VSSP&z!Y+gSd4zq7n+NXe^s%EN1iBR=P86<2
zBN)!8#xNoUVX6p99l)R1y=innLeW!vRC5s4Y{JGSlG&|##gYLw#L{CmHm#Z6>}*Qh
z<GkJE@7awFbDX;$#6?XU&OP|XlBLjikQHT!UuY=t8GIIETWE|C|I^k~x<A~Bn%Li`
zQ0LrIC$U3Xc@sv!7B<(nqJRD~s;49DRWyZF8PbTV(AegvypeypWte21wINX{Y6wb*
z0!y%SouDoF&=LeY=LAveiPaqc{Eq)~RnH@e*e)qBljK6oO!}I^*2c_1nUyF+&0MO%
z8k%$148!>>!&V-fJVAFym64PWj%pyKv0(_h?v6@$NhO?{SqoJzr=Y=%DUeNQy-+CS
z^TmC$30WZ|n~-BZaoNnh=g!yd`W}*7>`m>~j^N%rZ!h}<ge)YM?t@Ztos`@xP94%`
zpyWC!xpy$N+s{GCb!2ansaB?)Q~jHZIW|ejr=_Vw`V5rZ1SJcbe>^3VlJ`M*avPK^
z6#nqly$wnhpKeW^!29#$C#Vk_FRYMF?uX{M66yqQR>n^&X6xQ~VHDfc#<0OyGrRpN
zvNK^65tkQkE)E8SpFbEH<l(ZC3w+qc?G3(-=-Y3jdPc#vS*7C`xYH*`v2XhKea$h3
z1cW`rNN@yW5I6!H_~q!D8UV;{2Y^gwA{(;s)I+cm8zQ#6!KioBm%<H@Rl#5>5%gf?
z)yy6?Hp``Z^{_tg7<RVG<$LvDb+mcLuyd~-Hs>5e;b6UTaIYSwab!5tg@f(=sXg37
z4xBj~$$dD{*;~kpWkkJ_2R+D7W5OhGC}(#EKAHcLz>T@!ZkSJIy#(;5_X0kd>k`1r
zM$+C5^T`aC0M5Hn6KQ@AUUT*~iTu{zXkEJx=UGFZ$Uxk+$|K{ek%6uovR}qIQ<~Dq
z$F_w79izu)E~b?eUxrAX%o7QOcQzok^E>xJxM-|7cSCscLP+BUchS@P;I(-#yf!Bh
zax#l15$4Q0b?#0xzc+2>@4?;HMxJGzG`4gyoG}C0+T=Fq1J;IamcqO$FD)SBJD)Mm
z3wv+njh)N(Fvi}q`kHPz4*JSZ>+7BbrR%`b#3-m`h801J$x81~5O%WWt!Xu_?lajQ
z;*GhJj%dcuflw!0B-woWbcfMS>x_2d4W&v2;p-Hiq&&wV;wWvt%A7rVPdIyonGGY;
zlr?R<>vL0P$LKo`N14Jw>A@)jm=8o5E3zMKyc@-xgNGh3VLs4!t!D9X<J~Mg{CENL
zfySG#Z)F;3@3_*72goP-z$=|FA85RG=X|jFMXsLwLywm*KY6^eGu59yw7e?rR33hQ
z0rP>zTgcBL%9S*Q2j_+L_4VRI^8#T$6m6{0<iVJKy>#&4%ukpPHQ)!=W7hY#AAZ1q
z`A`F<The<lvsE6${SPh~);G%!&;5Y;K(s05A6gV{Y?mH>YX{7!1HQ*m!3Gi`HG;`E
zCi&F#Mki8)MiO(x`~FBe>tZ-E&hL7F$S6pxvsp)`q`y&Qk<agjIWmsOjH8>0j*_F>
z=KAL5=JuvJjN2i~DMB2P)(x)5lj*>=0f_%i1ZK}A_wgd#bAb8n;`YY&mNnaa+5ooz
zi2uzT;QhTU6Yc2MxFU4;LwF-!DONTrTa}5Uy$f$_0TBP22<+d$8^yhiy{)|+>kj`k
z-q-;k{x@@gpMf_Lr^`QtHwu+PWxcXFc{=aH8=C;c|7Mcz50o40rOncI$$7&+jW?bG
z5dWJwz|X)NiIeyr!W;R6{7!La!y2Bal5TAc`_%MZWdor2-x%?b>U?c2m1@ssHH`9a
zHg=vBict$;rW^R1`{DF{ia?tG-L;=0`q@nMLs9nc5NDekn_HVZlegcytjQe!;(ue*
zAR)s6eg>9J94P+~mR&Dy7PpH}Z4}W|mYvB+PXUPk%_Q9)D9$$VaK&C>zc3M}yYR+7
z0P(+xz%29MpEvHIo|gy6e1%(0$kWb89ryyBPxX|8+T`+_KslCM0yY^^X38d-23>k3
z^$ytjOum5IW9A$L$I3CXxD49Yn#INU<GRN^Y+MexZnrdVrzCq}$TvKLdC+LwJby8i
z8vttbhb@N2{z7Fb`X~O~${UkE98q#LAaJ%Q*`~q^1~Cs*j84GuSmU4eLpC8~rNb31
zrhiG?x3(7)a7We#_M3Z;=BA6D%3L9lmk#zM6Px|+n->$!%_-JalYq&o&DRsnjj!1@
zmx%qKxsAg_Mj&@tM%$a%qddZf>22}FDg14LhfruFATTmKgrE`OQ*kJqCn2P8C`ici
zR0xKKW)Oyk7S%Kj?RP(fjTZ?77=RTHL4&3O1n7%mejDQrG`F9fd(yhAc-JRIzEn$t
zw{!mmEz?+sE$IhSjGgn$wzMv?09z~Um!snoZRO6#Y-Nj6m?GW^kCI@JKJZE#2hmw<
z?u(}Y&}b^#-bUk?=04OaiFBFjqz}n3sN1_qIz_%X)jMQ|rfu*ZegzQ3EwF!IJRJZp
zCQAw=?>P6(Q8=Zw$S~X3h&|KRiZ@)p`%G_{xCCLEcZ%0?T<*~MfA}M^jHZ&Oxr>Kz
zSR;{^re>A*8;~szPm!2BTG9J9UJP<Cmq-SyAai3)p2tY!ZlryoJ(N_Tr-L4@Ctc^3
zB2ZuMKM-&aR~-jKytpE<kBZiTq-0}CP_rrK9^)N4K8PYEOZHZ^0=MtF?jw6CfHQ<r
zEA#b~Fj1L<qR*<3i8LXlg`oH+p-+5{B+=ExTABS%tgRzcq$XCHf~CICl7*R;OvA>$
z?c&K|FbMiHcN<b~G|)Q8sE!5yp03bvVPVKzh6ap&f-Z;uxfD|EVK7WQ5|f2GUxGo=
zG~7*<4D$UU$d7TJ8B}W;&Ui-Wk03QPE=jtIyuSJ%3l_9wDn+V8$%lK>)zyMK#8R=;
z>3E`jK2do(K%(rAQ6C9XnY&LDMB<`8F6>{cwmpx){3Ar=C9ZiBOU)6%=SB`&kSuue
zD;{X#AtVsmljOQ@^}e_q2k5q!JGc7>Ki3cUc^#L+RTWeH=5+*CHfoU6CT9$VHWz$k
zQlT$LkN%V|zCqf3-c9f9Mt^E8+s8olmjMz8^RY4h_6QlA7ZxNZYGJF6=a+JV5iZ;R
z@I$mi<zIfsh4f|psqhO92=Wal62uB@XjD)hT8aHi6+d4Aa+x>SLqZ*x7k*xK+jxEF
zZUj4cb(o|UK4g&!pQq7jm&BU>m(oZ^w~f)u4_o$!P5Z-!{bAkyP_#dgW`;QPP$f+E
zT)V<cuQt~+yf?V5YbQFBjoi~s0UPPJEZX=i(^+hUQm_cfgLF4UBQkjC$GdRO7=IyK
zV1mDpDq$YVXzFXt!H`7@h%wSxva^bgBJbd+)c9J9AkgF^9@rTo_bFOX$yi*lDa`tV
z3#=;s#`O^4?cQ$qduw2R<7;ottKgS@6Uz<1-^o|<J60chL`t&47TSiJ-rSe)Jd7<o
z0%c+7sa#<r-5JIyciE*}Q8ZD9_A#m{*F;GKpcsNTW~7-g@M4P5&I}NF;>sd9FO=e@
zjde0m@WBstUMQOd>+3X7@Zs)I3V4epH6(nw2OQj4PQk&KyTjpy<|G_`xjUTTuCpNz
zzj!!?p_COeIf_fKD(|69%pB0-BVdS1^I&!IGMgr|0Z3DPnyYDW<@a|iF=@+hzOfER
z3etl?n(1eeO%uLkZ(5<LEb&BH7?6t!SyFVL0*Oh5AvSNKcjs$JM|4`P$ZvQ%2fvs_
zBwvOfug`m(A#xQm82Fn%-C*A>L-oQVZoB;T5q6RRlKu`Z@E7I+f3qgCC|{`n{Y+<8
z&4lH0%}Ap6HL(rc<?Q-LC}NcRQ{N-d_b;rDWuATXyQ6y~f%X7XI%#hb0?h0zFrj=>
zZ+>L*0)pz_{v16%E`512IW!!$@HnMvk;F#pOd24&4Al|cfUQ0vX^&V%)z!3~k@dm_
ziR!eDO#M)(HKWgfq7Tz)inPhcCB_NM$2XH?)yFI>%dmVBNdunAc*uf%1*78(eM)B>
zmk}Rj4H25eFT9*-%yZTxDUweU4su|w#?+Bb>n#|V0~U}G>a9Fns?6zXOzMtfvo;xd
zB6P+GVN{j(Rf8VQ^Q*>gY04+{C)m&KFc{?Bdi=bS+dW7bUEm2zI;!sUc=8`1xsfT&
zt$TN3pnpr`%(ygpTVx`-9M92s^%q_^9z1b51sicjWyv3BDobq~UU9&6Z8C)sbGjv-
z+i?ejDQMAEH#b_6Ab&bq_GpsGM%itj!%kYkJD*r_#=_7PLQ%VKMn67_zRQ0{J7+Wc
z0Y5+e&M1knsN4+j1Uw7`r~q2w(BYdjf+iPPO;F(p9(zl-D+TQ;S?y54w=*~tH}M*6
zCf(uq!-EhbeJe@LCw%9<d-)beX)&BU=HEjZu>2n1wCnsqj48%8fJ4u%Jb8{cT=3|Q
zed!)@1}|ao!PVetby}3@9(`0Q?N%zgh4Sty{ybXL%+YSzgY40iiRdWIA?=W83k#&?
z$c&(kH_g|7S>gd0#*-(}lv*U2<O#$aAs276|4}#v+qdmWMVHz*GARjj%Nd>|!n;vF
z<3XaFylWMG%U6n)^^vRSTfS5zgao}VdL2CECa-KD1zI!4+eGFy*&&{*p?iC4b%S)`
zvm39NJ?y1q4>R6vJU-Pgfcuf!jVuz#Lg%}JQyJ;BzLlCLGr3G4KNO{DzBIWh{D{&N
z|B4^aHI)~rVRo9`6#m#`(+%yF4_pKYLwJj~%kGBKEv`Bw+iq&x)L=8);<j|qwlS!8
zNO-nK$6kxFZDB(Lrr!>2#j)1%($tjJz*Sr8WV3Ivcfd2#yy06Y(feL!#0S#oVPPdc
zS&aE!Trh~`T;~uL?u{sTX7FI+`fC0H?76<WPJ4;LMH|R3Xked!S^~otRtp=fUu3%p
ziyp!jjI662{2ki2EW3xRJ1HYj!><Z%0S20FIcZln)vOFLovP%3p%h*xeb1?(@1w8$
zXgJ-J0pYhhv_o1UJ=8bHc>tH3^Y(inI9Mf=8jo`x7)XSy^3S5jIUtT-1l$U;)vqY?
zKoA5xir&38<AJ1v0tO@cQB-Mnkh7NlMm(edE4nOVb0l#`yUaHPM*=|G=j_Aab_)av
znY%pe;Itf7DiD+MZUd`Np824Iz4)r}J}&npWnlvkmff|-J(G%l?jGUI=-uwc4Y1T*
z`zr0-adgrbjRJS>Y5zVwm74Fmy<5BvaT#C3A93eSOZVyYWvP7Uj$YoUBc{(DypLOV
z?yWjcZ|N5i@5K1~hc$-m820#jIxo7A0QbJ)o%(u#*XEFL_Gq3#8L#7@bTjHzW4%>!
zH}X~A;1QtN1f<JO-i$oy(v?1~kMYwp-`=iNYcmHFbYk79ckQV9X1|gcjeYKd0xb)(
z>YGa0q{N>xtK*m%zx$M#P4;HZHwZI2sLqL@Z9^PQ7JkKX@}PW^(-(JcQmxcxbKN*8
zj4S@`Q&p|tA=z2mNp^p*d_fPk@6O$|YfQra$#H@{nfi9nH&+<F)XhK!pAFiVm+%PB
z%>-)wDV5Co6jK-u+i(d>uG?+w%lo?6%<yEcJGr1GUTmd(son^xt)dxcL#V@Zu|Df+
z>y%AkE1d7o5N~<|4;O~*fF_QlIBFyikj~6ek7ma<9$vu%*FV_SKjv&Ng77MjDh`v0
z{=l)vVL2r8P>El$=2oV)pG{IcG~;^;LgI}LQIC?3Wr(8V9=@;@AhD`%PAV1UPW$x5
z<Tot?HO_;uhmGtmCXFJDsNq;_9{Z3i69*;qG_tcM#$ky#Ng|HO)R!mwc-k$K=P|iK
z01q(;Rl*EP)hgwhVd$^x7lX!iT)v8%?=ZeuEvs)1j;+dNY5cI1v=%<|bccP`A>gp-
zq_piP(h>)}S+uk;6bBif{UmyFIc>|GO}P7$9M2V<Rn2UD8p(}sd^|7=95WZMrk;p(
zT$vF=W?bp&=1Vg3;r`KqnF1KUGC3M63N-ey=P733Y2BGBVOmMr2+78*G1W0->P#}1
zo8p<X?)%AbCQMka&E7R`4rh#yr(V9KEKy+CKK%HHF8E%9Da<K!It>KYEFwsyDO8w`
z`Gc-9>A*M-CPqtU!cPnlMhsLC*@wy!dD^5<l3)@%dX8N?!o%)jedOEFqZnW8n;5}{
zx<|X@TiqDBo$c<zQ26}pMg8pX<pFf9m!~urNC-w^A5QLma@j+oR86IDBYqa?yJ3d8
z6JQeCd0}gPXM^nM_WTfx+0PgL#psZ7@4AmI`n=u2>+WhR?R8o<aBR4mRvQxNh8W2B
z(PzR?0hw1iK0}5uyTOgMa6TB;Bg!(R0xXw^vwy=18oE6J2&?0aqHr>o_idv+s`Lgp
zJ@%m_#nZGg@Bi|<^y|{$*+v!5YtPg~hc|tM2n>fRwaHFneFgg;R4kqoCoEfB=c!#|
zqdny-BMwY>{r-1MlK&mh{4XGAxF=s+D!pY)mFKv2fzxX3?2Q@G*S;a90wnb8oP1py
zFhgZCsP-*l4;vpv1r%Oaxxvsalfa*Qpm9lrm}O`SXK(h8;0G10-)=xgyqo9|anV@V
zW>=UGHV%K2VC(9_(tx3i8r5>tzU<<iDy$}>)cWCy*Z@vqMwF4O7}(@m_toHgqk)xT
z0pGENR62UC*Ozk%_}`)@&-Bsx#Mh+8?7L-K|C@OOD)}w(TyL<{_L069a>_4M4@Wu1
zRA3h%O{4Ye4GtOchd#UmI3tWOG8G<Hw(|86m5w@qa8${&hwo{8?1_T{x;7Kw2{d8N
zkUvdSl6X!e5J%4?G=S?H(0s$XZd}1{En#-jU-8_qb<;7Lhwhurz<o$+3_*o9{E!H6
zdUxJOpkb$hcYn$L(gOGQa73tAOJ@glpPB1-@Kd+|#+GLF%}dtK<bUPS`J21<Cc&#f
zxK`QMx6Z@^@Uf4F(Yw9wik&;gGB5LebgTe<H3o|+b2zDZD~k|3ZCM_Dh@02wT1Vti
za#Wbu>g>37pJvspW}yHXo1J9j*<D6s<<s2;Gk>daEVh*yN|F~Q5-}jX1#mZH?=WE@
zwyv2Klf#Ra#-3vN&Bau<J~lQu$I>B=vnDao+B&d##l+ApQNCdu+<|0#Rjby)eTany
z=NgqxZl8VY24eP9S57%r_o0m<d?pL3)5N<9Dy}XD@oyro_(PVsLI{L^AKeKteD&f=
zoBGfQ%NY!x(6%D#!q2{OPFh3O6+>A!7MqNi>i9Pqn(TnO<CPg=y}OHTE;Y2!Fq8r{
zg*-Uw1n=ZYKMcP_5Tw8}KN}*^4;+$123k(Y^aPP|wt0G}<V;&e0gi{=l1~RPYuA(t
zJ^;GzUVDj7OSmg~`1gZ)nu(_XLdf%su6%rRR##&I=2Q9#Vw|%;tp{P#x_Yu|*>Lf(
zca|)<Cd@VTTzfA>)MgbDPT{2mlqRNnY%Y%kl^_`;wCB%JrCNG<V3+B^o3n#s*l(lV
z<oDx~>dTV6o-6h?Jf7#4M49{p`#)dQQ%1?^Z^8%$aWihek58?kql2)GUau23x;~l-
z&UU=J2ET|gy#aMm{Y4bY80EH{s-G~M#*u4*Ex9x7!C|Y;8~h;M4WeEbub5sm+VJ+N
zQej{xeLYU<2Z!czc-Z$Lf*`^K0}1xTvW6wAprZ6ZjIzv(`Pm2;64J>Q4UM9UUtYEN
zr@YB7KT0%ALdMui&k)-z)d7h*ON?6?MixktJSZpUpbsvIUf3rlR-<7Yvss^Sv=;KK
zh`o@TS<x0^Pt2RO;4}O7oo+xj(wheqMjsv@9MuzP7w?u91SC2Q@XR^6QO3!qE}7Ko
z$;X#4X}{zD+>WQTG&um}(NqR+g_=y(5I!M^*K|a52wU9ch=k4N@fn7&A*m4^L-^_W
zgsd&|jBV%sV{=Ut(=#szk%sdiD8?PrgVX=!<ekYYI|0CLw7h5Fc3QcfAe(AIwpz{&
zO(jl(u8)1DQXyNZw8eVg8e8rHA$(rgOAZa+%mok-ziE_`{5CHtCSb2GC4l#Xg$PN@
zUx~#8`3=hN#}|#8&S+>1zfb}aHe^u*yagjM$4N^^!WuE9o-;}tyTycE&}q>NNpq0@
zo?&wGg^-_&O1kXy&T&q?pywsJuu(zEbF!V9NS~nL<A~<KmLZFt@f(N;e9!Pe4+e2c
z=eV*9$qFJG&^$gelImkLY!2FeL}KDHe;j@6-S8Sor}HuD#t`4_(KrHbuUcCp-qfW{
zcey?y6><9of7g*0W*N$L_Y$X7&31!4^SH2Rf72~-Y05}_P)@2xzVFQd6P*1!?e06k
z2nLHtsxg!Wk_$@-pn)C%X{8#&WTw|JF-g-a1@2`D@o}{uuK~TlAuVATt~4kxdP2)%
zGhS$-7p63KW-RYy_Y4#OoaPsI;6bkG+6!3OaHE<qz*Jq$hROi9o4y3MX{sQx&5YW(
z+73fir`>Fiz;na6qc$rt7Q-Q7-oNSUlm>5obCK64Bz(i}00ILM%~R1rP7I^i(4Hb<
z4(W<`4I7|wTMPG1r`N`yN5EicOlY(h<0=?+89h;j@AZmW<FuM3PBogB6@i@9Up8e2
z;~^&F7-lT7r>~A`2lYzz;IFEcKmyI9hv@dN?ga?8Pul)7t@Id<#>v5>qW7l}{B45=
zB0cqSWPt_U2+uC<rA4{*>RmSFYI&uw<>KAD(T|c*2#16EtJixNKTZra13>MI0zl?A
zd=A=WZP`^}u!ihb7Zi91>l}mbA=8=jb=D!`A-V()jv)Dl%m^$(k_?EG@#W|$;Yq>q
zb?+5IGOK}s7czAj)Pxr24$R>VhOWCJK{ruBqwf*1dWjffEF9?rfvPBIBZ}O^u!htF
zno=BOd<~2Xm;-kSBag&b?4bywati_%;9z*Xz>-Bw3qP3QOl1{VE@6(Y$yI^qTT((a
z7SA-;{P}fNfgzCyFb963<UD4{9^H35N9w1gYiv-xW=`lMEwPTIjwC^lpA7XZyE-W5
zJ4_?7uM9e41XgAYVg$Lujpz_eTTb?Y(vi?uH4#@40|1t!tt^40(qTdj3}<oL@=8w}
zk<Q+jJq)yB&S)R_yRe`@p=7!1H%sBnL1&Yc5E-Soz%P7~U)KvbQ#74~MnriSAXlA&
zoJ`2zgKVOQ$N(a--~d)RJg%J3&k_XD+s~fOgNACRvsFYuW(+)NK&Qa?*ux4L!%s;j
zE`RnyrByX`<HO*`6$mR49sb({)fC)EmQd~_r}09JV+t=5yS(&c6C)XEW_qyKH9>&M
zvLJJn+0G}guQrC^u>pgcCP#*aWrcTduFoMa!Hb+yY<6o%Wr*t}6x9d}f*FH5CF$o!
zLCK(r8jSBp=_NcdbKP3JNw09l0;XoY(VIDNnxVq#AinOsk8^Uq<_C=8(Ia$++Kql6
zvWPhdZ3`+7xgfT`8wU1EYwSdFrD4hS{<ckS#R|!i4DWmGR?^1`>}?o`)39=ZPw`ti
z-9*d@HAWv;ONJ>c1hT+I9YEbScXjbq{d~4qJ?cSYT_v`dtAza|U3Nirm1~*U8$E6w
z<OeC_!J`?-<EtWXixv5xv0LKOIve?*IxCPFdV>LULh)iErtP|qsa=g%nTB%MhL|>o
zi}Q8^7ta<<Z9gp1N6BEA73v@ZNbH46Sf1L{6kzm=Ms&8lZ}*^h%2iApnr>mw)7_Pu
zH}vI<n;2t#rujh77G?krG>b(zwm^H@yI#hztBuqKNPx0JD;U!hUxSb1KCY9Eddy;4
z*iq%7-$U<EJX^h6+!5(TD^Hng_!?u@>tllt5`(Zu@Xw+Zs+MMsH8z2}>Q(_4eR%T+
z#y4E};W0!KsswG1aM28l!;~xqC0_AT3X)oxAYQaDnXDmbN}$}+nWs67;xQl~R8o~g
zIqAoxxN&=32<n^ejC084Crj7B-AngCO;DyV3)|U1<vWA|(AJ}g;kHiicI}G8gi-hg
z(o(b6kW(4F(&Ax9Oq&6HcoN<NlM>yK^!7Ut;b*GO_P`RDJZBo)fFa(cjF7y-o1V|X
zv$yRQ)lCIFucb>*8ggp4D3d7yvI)_Dz?G#^t&V<iMydb(pn4MhIa&$6y-l_F1;+kq
z#a!~3TT2q{1iV0PF!-)>7NWy1cftk@rXa)86fV+(Ma7=1Op=>~k8u3_CbnU$)4Nl3
zYh#?MTN_iUno*n@$-SvM8=Ev$lgP|VRZA%=D>Bb{!Vq$6n!JGt!T-GD7)>|-O{)R@
zj;#j$HgVo26sP8m9GF~$!jrn7=Ib?_pYcxJ{QME@&F<3-rQfEKDalb}<*bD|l}xjH
zO_M3v=@^;r&9C56DgrpMW4nz9Fq?|vAxb!Q3bVPm4c>}H?ydG`AcLJX6oojq1X35}
z-0u<V6l_1NVt;1^tRSEwpKu~Y$8WE%f1!>+<w<oe*vpVLv8njUrbh*gH*ujUC8Fsx
z`iwSDDq#Sf{E*aDou1(l1h!s(+TBGA<XPz$*QQP?2-4a=JgFZZ|126zK3}-tg%ooJ
zfc4NQVuF!JedU>ni1Rp2I!L7>|2!hNolixQK5kLy;f3b#yhhKUP3pfqMR<+ge)UJ*
zu%QV}F)cY1c9Nt}SRXYeVLpBs5j1(cF}J5ZQWK6DtUGqTnAG3c@nLdz6AlKxJ9}W5
zGQ9M}Ozv%*;=UtWrX5-DzfQ7=i)jxnB{efFC)}L>p-|f!)mRf5`I>_3kyCp})6QGw
zg}>(SF1Y?SPltX)DKiLk6AbeX<8+rT(JeRL`B8TFI_E*ycsF#_nOF@Zaz`d<IFUZU
zbZmZlffIB*Fu&}_wDIE?pr(}}FXxy$5Yt@f<e)70`P#aRo!ZplGjD5i$5UIo0nFRl
zUiV~k&a4?VLZI9z<Y$kT{ObBiPCuPJiCGnQku{LYl`y0shOq?>kVf~TO*=BWf<;v}
z3?_V5J8?IH^wPD0)Gl#!#S>Gm`iA#Kqny|bqt}S~x6P1NulnYcW!Bl9C_#)L%WAyX
zX3V7sPj17!j?nu*FS)v_ixZZ1u<#^db+EX3T%NVM6>$d0(z88R0}SR)^<{|C0+R{Z
zJ7FNnr0lXOndQ;!yz6vyC0xi4m`eL?K7J}6{d9VCRLB1~P%Z@pG+*m#CE1w#==vwy
zDX_U&Zrpa2&UDSuLwh-Dj9`|q9tv=th-ha=7yhH)7`%y_G`LX3GkQUkoU8acaaq+$
z_xy(oBy?m(A#NN{L(X2+NRS}@)ED?2o)Kym#8~6Z{WQGoNv#5{gG<phbN`bA1^%<R
zG}shiLc(JH>?nr_VkXpj3sqL;1I_~x25Phu(ka=c8u(z2A-PS%4`_~7nS_|Hox9m@
z={H9+NG^L*|4yx3N`O1IGRY3^)XMG@o{pb~m#q#pl{4V;B4^CS0n9sLl6Kr-z_>bP
zvsK)!)r-`{ra7iR$c$n8`b65QSvR4bV$U&q*k5mu17P$~{g0vzcYEz&cpkxhyyKI@
znjA=^!bQJy-R~6l<SHZuQ{}Xdi;qV|9$CD9h+vR~jJ_{_WPplZ`RgmbGgGs0)x7U}
zHImX6%g*6E8@|2mRGWu5DF9o}_5vLHj(2VP?s$O;;boac)-wnY7tCJfD~0`yK}Zw^
zFpPGqY1)<maShi`K_%ItvuCN&9t=mPjnS2{y~`HOhzaeZFg6JVPQvVI3Tmv6!B}@N
zLN6$1n&h_*SvS)c@Hbo_W!nWZnjMo=ogOlum5{X<cI--{iBAazQ16bJAM=z`IGn`%
zNm8{$bHhJ&e&?D0DecW`UfREsBGJD*=#^|w0{C4zAdxn-G$P|&FkN1nYs{pj`7R`Z
z^GOrN190926X>P!FcY7RhbauW_n07pbrKAmaiRXT2*d~^S5#P2-=2`TA~`(H>q+<>
z=gJZ=C=}LLx5dd~xC`HktLy9QL6UYog-Y#WVRf4&;?Dyw62fX>Yl{$Y-c(kFo>b|d
z-b(iJ{AwOXp-%6zkjG%9d<nA%%a{8ZW-{}0G5B7j`OJJ@55BK6qn?Fy><f+&!@3eN
z86xT6P=IG(Wyhv`Hv+>JD$WVxG<?>B-iP+Jv-efnm#a~+$f(R91YY7oTj$pa%hrO0
z%Xlh}!IKF615dwt{lVO}m?E*vzLJ(#&GrBk_M)bISgoJFuFfrK){A(O0Wkyux3JB5
zTrc1$ZqURDqVjz5#|RLEfy$2nJzd47bbR-@!|E>sOTXxXF2;6@u<XBO$yGm0S#o8+
zQ2eCNyOI?0gq0+PpJ63wr)DLYU#71lg9%P@pD8)?i@1YpMJ__&PAQ@N%S#WR9D40B
zVb*YjW?+yy0T3_Lpc+rXg_~5Gz}E#4$!k@#{McX(+<_7i)kWgk8;MhASj7<OByn^X
zC;H3U9uMV5wY^nV{Y$@r*lvk{;I9h{XXx6*D#1}9`jB)Q*6xl~br=CnB0tsyL-39I
zy&>!aNUMuxtj+NOr}LQc^I=Dz0a__i)>NZ(an+55A-Y6RJ|al~$wDWfwTy5QbJ&of
zsr^0-CJTNBFyviM-C=W5wcYw)ge+gh8D6z7MvRQcXNWiAu@>>7BX===LwE!N)dD-P
z5BJFQzwSn~^O3H-n{ErIIP5lp@?)6iXd9!p`%J?<-=YKV7=nc9{ZFoU5X^K8tiWBl
z0K&2&PH_(SehWp-GOq3oCf1eJ$r%-c<$o{3!UofP7q^txEU%=`lzT`NI~Ti&CH}!U
zOcUnZm(LPxChKGpm+>4U>D9qCRUAokzNzCc7npQA=rMSQkzm@2HbwGs8$rxytSm40
zMzfu9a_aw?VL_TGeV8OIJiVe5f$M{%tTCdVvf%fbL5zr4q@rTe<|E+oF(QHe4J$XK
zb70K@A;t{x@RwbT=mxQK@&nd3U-snka!ZCZk};dOCW=61nbqhx6(-t>CHNAu62a$f
zP{>A~hhnmMEn;8gq^oC=H|tf3vbh$z&5gu&7%Ia{AxKWR6TSS!Y&|M>dGG_EQ7>%L
zb@kJ+u0B(W2>F3t-8G8_7EQTr(C#8d7w(Jj+C#6IA-DZ$Q}_jg4koyFaRIq*(&Udz
zb?;cd0_ySLt(@h_4;0?|4dJ?&Z-zp7KEVgXy@>(*{HBe&V71djQ(Z;7_RE+gP4Z+U
zt?5Os6yWCkJ`R4sH!Wq;wWxEunzC~k@IOR)qh2GNOfx9NU{BIwNvi?;!h##ilPCNg
zQay1;C2U)L%?|6h3&L}0HXt5hZ7(^44UV+i+%baA^y-QBhb}>-)pPJ`R}7WBF^3$}
zkL%{~T$HkTt9{u<0Oo5ZUqc>k1Y{nayk<dqWx64SVaDuYgpi6MVoP&oRBgnna8(W&
zudLtnI6Es|S(kbO<o|hfjMr=kO_*J@4&8+%WzWlYbI^m5g<!;CPVO6x59h_<^A{Ws
z;R2(O7WhnYfZ*!RZR6uG+E97mu1MToeM}Yo8IKD?zro_L;k1yg@Cf!-qs<+2BMNs4
zdZT`Xtc^X~JG^f6r>&xsvr~5n<LwhTZ!iV2v~>OWHDgNmz-mJYuSVN?X@8%`R9PGS
ztvqDk)+!R?N0e^#{WOdQD_SQS7_@XP5fUZ6fy^KlK|s5ZWs@_X+)bQRC1rnC%ipQd
zyBNX-Bi}(_EGG7RHQkOvkGdfSde*h&oI`@cTN(@{hS*b{;2pQXTa^pp^389B%O?$#
zkx*XUKRCu><4=$Zb2Wygk6dUP3nrc;*UoWI=N)Ko87MF*hUirHkxN$+w{UL#@c7*f
zk84H%&pj+&7EmrWCn^vILKrhY1Os%q!RwZl!G>-+boZVi%!L(QlVMJ(t2DxU4>}}T
zMBNrELdEQ{z{+oUt5HpHBcl)Pnj&aixZ+vD;*97pJm@{VIbSi1fo9=u2NbCOKIERO
z19H2lk+woq-r97i3063_AzGcHlE?$O<p@86+43Akt36~ME5%R;!S$UQ0}1E4<1dL#
z-Vh}mH0#vm%AY-zrMG(Y4H;$W2~F@F4yQb!c6)NhyX+;;H4p-dB&W#IUNn(4CkzcC
zz?{4`eJ;7AF{KoTn8ZI%<1V8d&S0;^bs{%LFaON;a(A6!+BHfm-crXrEcJV98n3H|
zX9rG!NF3<Q5Ja*{HA(|l^^2|nH4pEN{NPo=htf41J52=08c;w~%yZ=!+s{L58{?rK
zFe4xk2yczcwwpXhHB(Ow2fB9pFtr^03dI4N<T+HuNw0ybYB9unpRNDt?(Ig~(N?sH
zZEGEOatcxYf4X?&6ZcKsp=w2rRO8BE;;tFje*~?P_@i$_Nno-t?3yRSzx7W?pFeqm
zdxP-EJ>nDKyupS$GZNvtkgP<Fm*dC7;94ipLB^|ox622`GQLOhU9{O>0TLYcD>!Ti
z|2%nubAl@K<2~o>fS}Ukwz0SWY7U+G!V>O(d{_7mw><O}vjthWgvy*S2I53a<S-zo
z0l8N}cIPcjX8W1k^nbcC3g32WWqDgoPaP0H_}|#@zWX26!A3nFJ7!Dndod?@<S{iI
zJQ>d!7Up@Jxa%yXsF%_(Oe1r(c@mdb<I7!az<4|S!#vC`BTI37|05L2^zmmJ#~aM_
za^)AM=S9P}T;`bH?{xWj#{IW5y<UAaQ|5H%pJDjm<w)=}t`H2V^;DqAN%Cz??SW((
zH%P^sv0|iBz0cgp^>!ClM$uUSffkJQA{8n<yWj_qq;)&0v-|ZTEOq#ldwz;|e>tig
zO8RNEWX*p<SV57@7dG&h2Vg(r8j1Z(h_Yp6f<zQ?d})psu4Z8iovTVm6i#2(%JoBJ
zn3Nw7<#Tv^P(xA<JStH;I2-$n>k6g4qXPgn=5WAxyCLULA*?K3H>~9dPhz0PG9Ddd
z3s%OtQ3fK}F8M(--C@8q{Uko%p~m2dHuU7mf-9<(=>Qi<6tMoF$(;aQNTr)ChYK(t
zidzOlW<}Hf1ycS)nWT3B?Jr`FvA<*i#_a=n!tbnHKYhM`a8&x69{d1FU(-u;Qs6aJ
z-^mB}yY}OM*RJ&UU>NA_^yD>DYlM{k*Wy=S{`^N%K<&|M!$EV+cr%z}1WB!jt5**-
zFp80_jg7DHPhopAAO6cv8=J-LuL|psHtQSt?M>7xY-|_Tzl!n?g}^j8$ncGOW6@W?
zB3DVTJGthp^2sg#SC^<Sy4>r3#M@0*Bk03RQ88cG#F0BXyTS`k!{}_#`xWkpA4c-)
zUyxP>zpi%U(T_aQ<Mu=27B3pwBu<_7`Je%-B(<BypoI-eoCmCW)W!zLT$XTj0w65`
zC8UkTKc-|k`dKf+A*3lE<yhg7DS8B{2@p)Hc8#ZPX=Ols4`ATNKXSaq$fVPZk$nwb
z4{<NFk+IJtYbO{3MQj{@GcbDRqXxb0XwY}pFHFU#K{qyg<e#h2sK2|mc6)of+7N_S
zdxOihjt*zIc63-iIIbP60BO_cRTufNhS(f_yJ^!S4`L!x2@Pnh!IpnZ8&2aAH$5cZ
z2C$&0730pZcQLw!A%%Uzx`Sjt=Qm6~?`dXH#K^-6a2>%?@~Bjc4r`C1y;AM4woD+e
z56@nlygG|sm#S4f8*zA0i%zOh`Q&*2@a*v97{67b((&J-pAL_)JI8oO7;^(p4(&)p
zvkhlWTRoqMSs=+BIc1XhyX1z)wMGoBdwIiGS1*xeWWa=DlG(KllN96%5ENsY+%++|
zA;!kVIU?SFk!c+hN-QcVUq!6}f&rjH8J08d0pSPavIFkKEzO`vTBYfvOkUZ;GJ9NG
zT>Pee0lvJ5>ZVW71~(?pYV{YmDp@{yg&ez!-=G3hPE4yq&5rjEd36F=Hstt$JUGl&
zWt+=&2ELXOZHjjwhIviQdGLx2uGs;?{eeG1yCuOIEa>KG8VkW?R1^))0Y>m8bGq?k
z!F|NuZ9f*JJFFZ;i2y!47GTmw3pY8CrUw2SL_-TJLO-=V(Lth}a9~}Se!Z<!fIzEI
zhoz_suATbH(6UmIPIUlyY=SzIG;me-4}OMfm>da7WWH}Gjclqc<gxqh27crgv}xR>
z1p|x$#M7}!5)c|c?g*&U(s9t7zH8)Dv@V2kXsBM=uM*(8Lzw{0cSX+(4~`=Lu+TUt
z-g7Beo)gwa0+ucCU+vqnQQlRoRP^!J$j@!1q7Q~e#h_M6ULS}&wx}gfokk(}k~~TZ
z4zx3kTJjKMwB<g9?|i~HnfPvBR6%AH_dR^8GkWSi`$sjSqW^mOqAH9jCQ2l)vv>$?
z!!qA*@LU9<JVU+$;gbo!EHt4kyfmb_U%>3cm3XSShr|M^`zOKzy8CBa0?PY0@&Xbj
z-qVS;-6;KIZ{SLctbefu)Z==?9Vomw%?zVBIzph}x@<OA>9r~jvf`lGL7Fl@)8*5n
zld?Rz5e&&Z!x*rQyDbv%jk^mHFb=-yO^;yM?jA=l<lrU|@}fC3-Ho4+!`xjZ1iA(_
z&Eup|!OxkyG#VBB2zkd)AMcC;&hj@8k;5i{YOWVXMZd7BZ`>Wis2K33yWJYHeRx|h
zDtgoscL1ZJ3j4&A@=;Vjp_uUFle2?e>NdC>iroumXl4TCp<i}T>;z<6Go!ilxP!z6
z>&UuhIf$ivR+F4?;J#W;kP;PG1`XM%nutBa&f}^R2(Kv+NHBJ@W~4+QatjkS#dbwN
zy5o(BW9&)>S};QdU<(qnsY$U!D4=`KCgF_<BIYO1fC5plqk6<FN%+M!#%krV0=}B-
z_BXx%<3`Tg3%sC%4gzs!I?xioS`&A8h|(CcMLP^H!VD&I@q%pvZqb-<+ayJJdm$*0
zH^IP)*`Hx`cSB=s6V`300QF+ZteNx%q(qX`wjfx-_$1U<+!I~eqF`c-hD7bq@(S(_
zKp&Bi3+N`*H*05aJWrcvnMkct0=6yS(4>EvTnQI($wD=OscQywG%+Bd`Mrn`d=a0B
za4lFAWI8PrlhjD2ScP0Gt|wWnjdYF<82O7K^rWii7IxuEba|TKDa~1y8Uw1jF%-PW
zGEhvBj6o>P=oym3Y>^HZjJHCgASvOQ+E7AERASL&iT4l20F?2LfS46In&h_Qi##FS
z<2<48_0JyR-04vBZ|hJda}<z>gB8`j5uH`>JGptf1}Fs5TjosPw23x6h0xAEn?biI
z#_hXg6JL&fk-Z|e!j4)w6kAwy&6ps3M&@H&^^zc%Ublni;H<#Whzw%n(d0LV(%9AQ
z$-)vFMnaVQx@U=v=wL~b`7OlM61U(oJ`)zUat^HYyj(wfb$n1*E9$@o@rq`MMuy68
zEVd#?<2PdaRj#iQH9s6%<V0WD(Zv>7*_V>Awz!Z&-F`0yY6xe`ey5P*nD8LIjA_P*
zY&DL^FJ=2Re{N^UOHNx>XxK78Tvz7W`T4VDjud{C*`kdX24tvXU~INLf1V<SEhsc5
z6-;W8CZC10@xss#yv@T&VLC&w)?tOj3)BnK(voK<U}DzS2P;i!VJF}g)2F2n$~^g`
z<-sXl6Oy%BOtb@yOxr@B!;&p5$P|4nw5-qao~EG!q@q~5r&Gbzg5(S6fbLz1F0^W;
z4qHoQIy74eMD;9!kJUF643<Umdu}D6>`clm#r+~934GHOZCpDe27oM_2-A@>b%2|f
zKS`$p1%Stm2>9{<0eq~Ls%6&pVpl)P9;Ewb1t(-~N}>=<l1jD`kn$blBa&u-gDp|~
z>eeC(JX@A%_U5c~Byn@ZEjgVIXiE5ah`|UsbQB0Ud=^1_a_nZUbaKpf<L3$=>aPQY
zJy{6JmIfSqa{L<nU1my=;H;%z_w({O5UK<a?krxSS@1$>zdz#H*RzxAfxz*&)Ji|=
zCtvZ^=@CVRRD|%g8=rJod7igK4%V_64;tkIO4g1J4o>SR<A8jRuS-82)K3rfv+q?t
z!)9f=wY|#c^0B<)jngZsPOqE8iobyF>mD2NL2~R4Q{T5M%h=nb?C1a!U`3MagcNm6
z!yzUL3c=W20RaZzpP%j{O&bXk7*>9I@pl|14WWXKc<<;KUuoB})F{DTaU@UO;<B*+
ziu0`fs=FnYHD3{qO5p>hL)X9-L%w;DM3ilTLZJ)RwQ>Gfa=E9re3qvsGL<e@3>r94
zFKf?F^8t(V_d;MO;qM42^3%o->!AUGA2vce0Y7Yp)&PFk3W>}Q+o2lD4?ALLvefm+
z`mQm5YGB2R{|4c;?Th~$x9FJv?~IxAn*II*daY*o|F+k+w$lE;Vt(U`|L*~(U?Qls
z$Kq`H1P9<)(=EEYj&6`K`7(Mw;KMXIdd>#D(Lc^_8mo9`>w6sgFW_yE+p0}Iq)D$%
z+EHWOI$u1AUp$Eq=1H{TZHgPwo5XGhd=mk0APjpq1HL{9M{|Z_QJD920b(wi;#}-D
zJ2$WxJW`1(cw9Y#vZ4#{kOp47(=-f{w(3Jk^eL|eZ`31|tMo+(c;e-EzCYtkuX2q|
zZb!(no($DBA@b&OIKKr3L0p58TQGPc3<3EF-i5<i^KGoc(%$Eqoro90(vTh)Hvs@r
zaKwF)GmB+TzM5Mc_rR0kzX6_!>+92y5>Kqe4&yx+&@F)zzaz$ABJGI1c^D4C2lSEi
zA}gZ|0t#v*LArDzm0*y=gFb#bzlBk$|8X2Q`8pSFYML_l)dIvw@mKIDa1e+#7_T<!
zweXl%<0CG0;CZ=D1KbWNZeF{yZ#>BOd5=Nzyi6mtRFLjo4a4!s)Z^1!gAr_**Fg7*
zU^sG1PrwHswH8KsjkgWkeOw{YFB%DekxShN9;n1$g3$#d<baagPZpEJ$ZZYT_fjM{
zxbBZWj?we~@tnbh?pd?O)DakvxLX1WC|M9uZ_AWnf#cLQ97JfJq2=ywVqw^&EWl+%
z33(%*!|9xa+{{%w9gXzjO(4NINvH{O&yg2>gEwn%WO;t}Li|tSA<*j?T>hclM5HM0
z0I1NCw`_P*6t5!X+K+J+9MH|RZjn}r5o)%L=@P+83iCE3{d#_i5PWch3&&V^Smp?{
z5jL1kiz#v=K^Dr5Rb2kytO~E>=398<;z6^A5b4h%MJ5eb6G{oOj2uaf&&`+-E05`A
ztiy=G+~yeS+FCfrf5N+_EI;}ApjyEdWVwUF?3u_<f-oX1-P=I?bCPBKk$Ad}IDCdM
z{jZQm6G=8(BH_7F5mTe#&rGj?fN@#GkFD4OyRWVJ!V82T<;<^0%^i}ED`RcVhznbX
zAMK|k-~{(Q4~1BSO`Kxt5>99;Bqq@WDXu={METmH6-r)0?(QZToo*X!uD>(tS&~+G
z&IzT}7#1RDB&|&#SVeZdN1UiW?kd0fb<4OX+$zmpxCyKhLd+qEd)%OxyrQ?v@anY4
zB0=vi-H4>#ch@Wbxtyl}v(>3+3vN7xBCe`5Lu^<_R$sQ#5X@3`1!T2=9}{XV;ujIE
z30jnfFX=t2nH%*>LNsN^)LfQ^6>=$dK1ZmvHPfT8q{%ktN0nwyCk{YA(=Y>rkQ*Lk
z*lUXu#*-YmAC|!_n4do-S6MI+uLKxeRVZmFsKmTdy<-?59^`t~d@R|RBuW<AJLcfx
zrX%`Lyox+|(iRaV7ahj_h6|5@r3q-N?N1sQ`@?I}en@wnsj=57{FF^0OGYbCpRzow
zfxFY)_!6<Eh)@<`1WG~VS%J^o1bC4TVU2vomQG!LBu%OcLE;Yx;_Sq`{N-f>sRkLT
zNwLJ(PSBLcJ_##6CWzyLB&8TePoF-eOtUqS@P)T@nr#GfNf@lDW&<9$tKfbR)*C}J
zrIiMc;`L{Nz8`lG_ALD&UNL-8<kqSWz~m_|q(MDTj1XVh4!JUrvfF|?M$}NV(P`qD
zQ{0w2JE`DC8|tkH-blgt^eGZO{3v18mLx<<y5^}E7mz@Y*Z9eSL3q-xHpFd9o`sQP
zMSz3=qL9h(b9glRHaH3G?rJZT-|dTl1#p)!FG^^*vm@(VV!?oUa3#?!tH<R2aFJ^!
zE9C3)WCndO;|NWqt?6dS0A@M;sz|c2x8cTweBiuiCas|MH#!h+_}%du;5Z)3>A9+L
zfmITcONAekNhL<!=C<5!o!Pxl5BXL|<oWicmw9}7Gx!Z<LW!59H)gX%>JbEAUv>iv
zLXyxuSU4Im#XZZZob_V(ThmnNjad*ef($s{Dny-zB$Y|`03Ey-Bb|>ap&63{L|d(y
zSU$-_v(-u;Ou!NVH%GsIxQR)yRC$s`6?W$~B|`uY2f5V1ad;+BP*Bc8Am`0F;*y%9
znIl}u8X;PzM~LTm@b%M2xRNnK>2CH26$Lv&d+eS$!pF~yUatzL3~XE{9oycGeu~At
zwwB;AcMLE}DryAfkDnQR9iNlTioL?mTITFOP9Zv*BtdTp@Y!n)lo(%h0adbw@#!=E
zcuJ>t9U*+Jt@eAw;s>P69_w1>tS3j9<GY$`h5*TFT>FG+T)Xo$2EENOjTqXz)93(y
z`ZTUh>2&rqIz;U9<+Ul^J4z-u<Sw+kYo+0{tbsPK`{Uv;m{;r=s4_NQ&~;i@U(fE!
zDVblZ$YpjmA|nTT<jlH;?uUC~=IF?0)|~_h(EEBKdbkk8`#?rU@Z9^Nep)>|KI6?~
zYq+mS*y{NWlAz(<tt6ue6bl@GkaokOfYYt&73N|bwX#85a-T+jNu8ByQnD%lbG`Fl
zkr+tzg|UObRuL(hXr$W^R#Eu1(@oldP6s$j2M9wScGfC?=2oob%n$-oEzo!m`a1Zu
zVcdvZCKIBvEyzN}Wo8FGQ?LXkJ1>cMccs!<53d&ixo@M8z3jnX?&aA8woo<wgkr!h
zE2HF(K=AC_qPz=W^8jk&w*G_0G#@6*ESg#dAtxVjQo?MT$%`8A5wP4778~C5`@BLV
z*r}3?SDJV$l4lC5XNxLp5-EXIjUL-KbF7(2kne-XeY;cfsyb9EC|{<XQ-4#<R<xOg
z9<At4vJ|eFoJq6gzDM4P1v5BF90=feIk^*C_&-{-ETMk`eH7MOfpl6RIoq0gLBFs3
z_E<|?=f5e|mWWV<+W0~f29xig3;eUP!gM6bQRD>n3NwK7?0_67`R|^%Z%Bqx^1rSW
z@?w~EOWO2Cai&2SOfloRDzSDay2JnL3Y0*>{qOzwJ?^zWXavag_>cVd1}=N2?tgD=
zZ+(gX_#>F{lkR_uG5jd*;lWkJ<Tr3Rdl>!DiF-r&@h_Lxjdo`hdc~KE;=K3%<)ZlK
zzbNjd7Y$AJp7>>_cMe0g4Jb;6bKLd@?|2hQ3_f(tsYtZ735UDBl%#SUWJi0bjZ6H@
zF4M7?PHd!;+%->zgBZLK5b_PofMKA>DuYDe$tu&GF}PvSlq|az93%RxIe94v8y%v*
z8PgH0i@oa_FFBVq{i@Lcb)=aM)l(sRfoa3!iB@5k3)3fE4efw(@^#_^&gZvA{b@uy
zaseJTPwg3dK71V(jaJx!>OGmbQU_wvdU4x^(4}7sJIWQcGiny-B{AZ$uEgDOcM34O
znDpw%V(b@FhCFS=G8$A61~d5E0f`?iF^aHfHSi@zU_u6h*~c+UJan|s?}n+R?6jlr
zDk45U@}%bz1v3cbqZpm}U7W8Ga!MsW)}R`uJ`KN*&|z>y33{=UE=qY~I`Fb`^t)Mi
z*f01T_DGYy=Mwdnqk8YQYvACRH?u2pG16!xxQsjmk6W|<F&99Cw<N5!j12H{FwP!I
zVotJ_itvQ(C7iUeewYpl<Cs9RDszPy>xH~4PD0h?Cla4eM(B&*P{EO=cQQqA$C}y0
z3@G!P05d;3aOU?}04t{<K=YdbG(S6l=J#0u?Mr;>V>0)01)%xa0W`mRfQvfM$)x~S
z-yWvlavdPKqe3w6YW;Y;SVN)}Yyx8-T=U--d0Z6UWm1+d@`-IC9T`8`RG9jmX(#i=
zH@ma{dW8He!JhytJdl*&btE%O{$5P|zMlGhBlY`c>i4bG@7qXAD#)1Qx{@QOBO-^w
zQ!|aqi3O1$b;$g)h@FHoPVudo1W%JR>|KeP_)?fP>_v{7rh&qivr+7eppbZjB*Gzw
zrMh`s+}wInPkhW{FkJX{PffC3duTZCfZU!73#~v4$xKmm-4_qd%8zNzw=yTI1W%pO
z6d|Zg!XzB!{m}`b<nThZ%3Ft1y0{o1mT;&C<X7oP8DZE~WJgZr;}Yx@c+KuYgktEp
z4gnacI1a)}$T@&mNjMI~O2s*V{MEigIyM+1?GnSu+AXjtjA?n~$TZhcj7rrULQTMy
zSly{Ql5sy>Cws4jeKjuO*@yS?hN^T2AD8@!ey$Px-$q=(bVfhyN)r>zytW3t5_VCj
z0rEynTT207%wQJeO5OGIV{~|2DxV$xe0cUZiSv_%vEVC&h&am{AD6|*p;%hpp%0gs
z{t?IS|22K6U<vF$FB{E=%lxOqoMHdjMAE;M{bzk+WAlss=Z|2LY4)FaEIV=*{gNmU
zMgso%k|^-soG4JF*EG|QzK>vT@;t2R2l+A)M<hlb_<l5fQ~zcF$BW!NfG6lO?lRT}
zULly7uJIZ#V!AZol_>v3OhyP08+?Shh~7kRHDUx8^Ku|tC0-gE_4o`K+R&0@G7?tG
zb)?t-5F|U{m-^n(p``CC`_x$YUPR3ue?gjG>a`aqRoub;uoK)RMus~i<`ckWfBD~h
zC6N6CkAVO{?x`|UtOUv5j!urByU$1fg%-(7i)=DE$6~6~8_0ez=DpRcAN@yjy-IaF
z*v_KGMV*mQgGK`8@FSQ8noIxY90@vi8C^2P*0!K<&?Kdk(|6t-QJL~J_?xlFumOMN
zf6gAWxFF^QSYY;H&4Z@H9AEatX>Awj0z#SGOw?!z_<gKqs&U96fSO!vOLnQ3-ORU%
zmB%_tfbv77?G*lj4uVpL8dw)gEby;Lyo)CxILg2{h`93}-U@qGFePTFcH!)G+TC{*
zJY*VcR)5LiC5A~c%Y}nUeGQKnFgM9epfjw)p(KAKB;W-tocD%+b2T3Jm!k?M1@8;W
z@AU9;@+{J<1?rk<iwwbJLkz1ZU2HJ{=n$Xk*i$1EJi+oa2ALd=K0|;LmE)CUc{tn-
zpJf!f=`dknqTz!H=7^g$*P6jurnKnKJ32o1qg-mI?g;&U`nU{{{R|@78Ht+_bO7<%
z!l8j-yqHuSAdWfFkY0FYX*-Co4Qg6{I!`s!Z0)%zu%as#vKAH8%*k`m+_KV!vxt*u
zXRw!W;cLj8TKwCwye3Hjv$zO9_<QUz5M(x_p+2-Va3-S<Ur-)!ry0hj!JzR`SJSBH
zhRMg=_=~lCFi1nV=wIx@Bsuu+Iw}1T+V~&e$JqJL?wk*Z%B=HWacj)~U)cDP|NQ}H
zU^1v_=f9c9I@t~8IVDoP-A_4Btv8lCjR~pZO$Syr8P3C`=U)=VbBFtqD4v_UVGqkK
zVWM~|y~stAy#Jr^2*_^ve?7`g^!~d;Tgq@L_h!b9M>?h{C#3NTW9K0{aT!UFjxNBl
zL;?&_$Dz~#Nju2=c-MoIf|8?9cp_5;#Hl$(p&gDXThrU%CJk@%N;6uK88Qx|E)|!Q
zW8CQfIC-gG+?}ZAafMqA3av{fo<jVB4t=NH1vT_IXR3r1LRCFQ2GO`YYGc0O01@M_
zF#cV6DJty5`Y6;7GFEK84@uvv5Xl}jg)lB4zy}J9z*+QsWCaJXjP4hlc`cYjgkgW+
zg$=o;&tIf*n?GT`<X_WdZ4HhrPEYVqH@^583LGwDy&D)x^|V7w24-+Tnc@^eOQNC9
zP`SP8!)F!}pc9iw!aJWK;Exrqt$C1Rg$R_D+u0nd%s9dMPu61<ezdcjsA=bvAMu8k
zSsU~C716*~5AmDg{F6>_iRY#(kAM$xQ@V{ul;OqUBP(hQ!;R#4H_G9TBOh(RcGkyT
zMHtG|^0LI^r;kKO6F$-l$L2XqWX7mMF(y?`VNs~z?1OGqBqU0z=HdD5(o*x?<fbT`
z+*;-%yR8%jEe*%RCY*GUC1`u-JYUI!+uB-7e9X}yo{lD;iE){Kg)ss<a3H5jhoQhM
zvK(B#UWDsf{y3Ky3LJw9*i;x#O<Qt}nwDloV1{_50vCyKmqMV6V_ZG1+DJbbc@%y!
zTpE5dzD<0xhtdouCQHL(=VWE}OUa!nFn0bUBf}u{kOAP+&wtz7n>_z*Y!?s+j-QM2
z{#W5k{P&&b@;(i|od3Q&6!w47Lt$2WT}?Xx8f&m-^PAxVl;a#t6g+sQO&Hnn-*Nyv
zJJ~<s4=dDRkO_b;gjS4eg7Tgh0frapw5HV4kUP<SE|3}XO4Xp&4HB}civr0Bw7R`}
z*0_j~@j+gfgQmbcb}$q0WCJlSi8WVDVmD1vrFRMOmJp-IZ}g$L6>-V%6*8vb!HYu;
ziM_$?MR*&VaNGc2sB+8+GSXXnJf$WB)g9@_Pnmk?lY^%2H>PfxsXtB3rTIifXl+d~
zRV4_jB(g3I_@o>i70?QCuD-@2V9YC{QTwWEvJM`>gA)j87)G4e-9%`7vLoqxceie;
zDvHCmNSwfD$MB+G4eC=u_ZmkX&GCje{eI>g_?9}^+>Juj8n4sN(-H)Oy}yg-=&{D{
zd&uR%mk_PlKNH{NaMf=hUIF(YyXcUJf5yv0e`cN~(dR@9%mhe`_9a^koS3{i#;SBP
zWCK(f^tj?rCP2Ncff`_iwKXi48;ptm?kERQ4F#7=Y1Ft}ngcmD^N5hY|NLh{b<Rkf
zt8V34Exx*J=x66I6FWSNh?}@^>*etBuq7BWnhyCwAWd*q4WO6<S0)tb&*b;GB8{w_
z;`?-@X}C<lYtoT6L52`X+*RPy#ld)n;0+{6lBW&f`@(x+;lQS$#FjYxa9TV*o+i_p
z+Wy$wm|PSq2~}t!y-7S6VA2#Fd$g3E_*W){Y|{#^km0&;0xZo6fOXwZo)HsA9fh6+
znyvBRtNSA^u>_w_ZfoAjs`LipH3V7#pTl*^B_s188NzUL74MWM<w53XB~=%5tK)r^
z6663T6t8dMP6*uH;C*EK!RFJvYWIaf^i-BLw0gC6rc5-YP7Q}Uba2}kHLqH|OR?c=
zZ%72w?zN;3Ju_zZ!3A$aKDDzcl?Cvrz%|2SUQU@nWYvWqpA!2V;yxN8hXkH-C7XP_
zzFw781$n>Z*F+}i#tlChoKCUiR+`mZs)|`l@zc`}witO3Ey^+duLh&e#AJBW<W^}H
zEg=Yl%pxe4HAY%y10|ITPz>Zn%smYo8XjQ6%kVITi5)EmZ;IpqwtaCQg@Djn$MG${
z$4fa@Mi@1HzH_t_mR1?Qlnic}9|rpVL<rU<G7SuJFGSxw+#jk_gcdPfvaAOr@xN~e
z7TANJClOvUby@Sxn2oa2g%CRA@}FLRgd|vYn!xqPS}pwTu=-b6JH_5MLC!2tlsFM7
zKOl;lP+^U!*J{Zkuo$~wQLx~(&a-I$pi+8;=T(rG^Wdz0dQd$)*+*3oa(9*n>|yK@
zn(yKC=x<Qpj!GH`F1<i|q$uSo3gv>P2pY)JH>2^WGKW@G0Raa8Q~Ix-<LTON?)oDr
zxUBS`dDRpS;|_EKl-6qan!41LT&hr}<(b&{ST$8|Pj4l!p*b_1QQQt&%c_Y>^pLy`
zEVt)`o*HW-@3i?xXdQap_!fa}z94YItb#)ONXd`H$r7&bM`V;b>*q*dml*oUjO4JD
zbEpOt;x3=Qeu?;;98W+&3AK6haT+rca3brQk=F2bJ2OQ7F2iq(MtDt}`8GOCbSIlh
z&PZkCJ;bYK-~<R|h6P!j#cG0ZGMHIZC{(gvsL;)+Sml=hZLMZ4zR9ZgE31RejoJ#8
zWq@>qIGkzcJk5|6%Ml(>m)E}$-Dx(1M97?SAg<Z&AMCw)E~6z_`3(>t1$w-YhD)#E
znG%^^Ofck%aj)|Y^#!)1;6U0LrevXJq0eZeNdK1xVtG}f6IWLq6z!jC2>@Q;`Dh`l
zKJ5&nKCV?D^Nq>M0m$MayB=P?eaf3p3zpn)bDT2G3)w?u{-Jx=Ifv?kbBGc~WsT#z
zks=coW7xKpnb5F@1M`UVD|thDW=~oTX85e0TtDHZKweS9<zpH4g}FZ$-Uxhzdwk~1
zm^i`7g}<x<xuTYw+1w*_ab_Wf;j0Bf=$1`pw^B>pR)~ZfP)N_*BU2qzK(bgvdm|4f
zU@&C@w7trKX_L!^F&(<N6~W9u!w$h!jah)w<JWPgF}RHD;_9rE;Z6AMgRDm*Jq7|3
z5UI9av<HZ2<rd=>@x>s<OXc0>N180)?2;A4O>$i03-2gR-8n>xl!hfa3QNu)dt03G
zdRVJKLQb%xVDJeCIO3JMB4$LV1W8g982_{rhJY7b#x|v%g$*01oRjC@MWC2^)a}J=
zrf*}&0md$SY&dw2J!fhXK$7WNy2eep;e@aX7(Hl=)SJ8qoi#cLJg5NOn0ZwhBZ{|_
zF-}7Z6a8zu!Fn-JP(9Z`>~HU~3S(GKa}6y_Sh~=Ev{`VaF1v2#)w)!61KPV_Ag~N|
z+cQDZ*f@YEP8fPhqN~r(S9W*#Ux1oLx@1CGRs7nop#Z=&<Ba-}I?JxM|AS~-Sm0!>
z$>wG^Zrw`E)-M=d!^opn5!sy9*34yo8-}}V&2*3~NTF)i%94Op*<ICz!khp}btx13
z{4aXVHd~A$wKlFRluOXti~{0xD>iU+2q@sbf#|qBJPkS+cm~RePR5bpsIC4cYa9W*
zNi7Fmu#$2xn3*Oqc+XY4fK-tW&!W<C88<m#fJv<={hnfY-9S_8DuwBb1CvX3qBdq^
z(pls3i$DUaP?~)aNE4Q5u)H2ZAVF4I;UIg>!w8@mOL@Z3rpbq_1wXdD#}_y3+<s-B
zm7a?XNG;b#G+m&K77OabP#>&~H8Bq3@xUHpG&<GAhDlowc1T+BQ@MW3REfG^fk)hZ
z6b2^M+^3e!7FT-oz-?zkVM12%vVpj8uHxo9N;Aw~tJnPwJ6Q+G&s!fgYa;KWK&Nu{
zj5a!8(m><|OgU`SgAqLHf@4yu3LZgNO@%!vK1)oVt*MK`@$rZx$0DPG7{uZa@Nz)C
zTv!N)Edhp@6?d=PeZY>XIg2J?6z(n>Srwo)3?g~-#P@Jm$*p&aC~8nTf!c-kzw#rL
zGw!@G)E*8E3a&|I^KF{6so!-rpoc0XWWvN5vxkGLXn>|GfoTA`8GgL<K(Og{x@dpE
z0UC!&7;l>}S7Qt*MBk=<H;B^?qS1hv<$`%;iR99>nFM@lf$`)553n>7i_p#BfAA7a
zQB>FK#MSYKtp=`J)v2sfwFKtkQLhW9VJ9Aahy1;?>8KOBKp6~9`yKyJvzxM)2r-mW
zGW^%_|3oEH<0MjBll99<_@T`F+8R_V>>83?S1yqjBG_Gd5l%!}ikU3s@fd|N&V+LM
zNZdILGcO%7HBM&z6#%n{z|Dl`Pel2kYs@1<M#yfx67<Ddx@qDFIh5x(_}U^jATunm
z7p$6$ZqOGbSj0C=K!jVwW9X@tIIIE`cTGn$Fr10ooVatY6ztyV{PpVj2NTc1qix9g
zF~Qw_!gN*HqJM&TP7{hUz{%pa$X%|QY2<|3^O@X2H4Q@-1Lt$|^WEJ$5APWA_sAzM
zD(>W;FS(yNo*Ttd`odr8!<3y}Ji~!Y2uqL`1zaUCXz-h4YD$FcWcxXXabwWDN(7zg
z#fea9a$XTRAxLU_Syc9FHa1Rljo>1_aw<y-fAOw?)jK8bWnRT|Z1F&ZaQPr7SBMlW
zXGSGfU1&%?hkbevG2Y>Z$NX3(0hZhDtjxP$(m!!$sgf1pI<wP0$Je6Rqp^1IKc|g!
z;SA}I269vgRkLhz1HUA#jT{C-C=%KkOl-_QLZXI!=5I2g3-0Q_+zGga(L($%a1^Og
z4sRc-p$Na?)lpm%w}vekgB;dlW2=wF9&xbZc>7F};8b!%KpP%i^;*OK02gtS=d4O^
zQ01CaM;WbNvMyofc_|6HunKKjEzl2~`!#NiYcvoKNLa8MPlWg$<wmI30+=rzA^iap
zelf2+S~{33GC4L*1UcWsWp8*|mcI4(iz!`HBhAO<!?v1Da_=;6pdF=!Wfqr?*RE%|
za3m_&C!Hg0AGswn3XIwWUf3e-KElHiCdL24Lhf;9ou%9uQ@15Kg(``v7}!-3C>mzH
z@s_@<KRH*!U`RQ@DAKeRB^xMK66veY{H@{9ul*A<jV1#cm0cH9mt{I=LCI*2UP5dl
z2=xrLRqQf+X=vmvsG+~o^!zOHIcStxrYFft6DUC$t#~$9NHj_dBuGT3Es6Tqh$Nc@
z<%K4Eu!2T%qSC)g#}8zPJ((%MFd+>k28bfbh3CXVGv$oqB7$Dh`lOIy`jcgG3c;)b
zEf3Lx2x5TwemROq&DGU01QVHb42Fhs)Yotb!2uW1!Izg1n5IBN?53`gPOpb|+tgRi
zO_8EO-z<yqGzw+4EJ*Grc0n1j0U9}}Ar7uUV7Q%6V|KfY>B0-dJva$E$b3Ywk0kEm
zk%$)lxFWw(CddyGf}!Np0vMA}3!N^=?`Q4iJCP2ug>ktV>7;G!Z&=#S_JXF6QOe<v
z9=ru*9sOimR2^q!@`kJv*5~f+xST8b^yKKMUMl}Yw#+EX_*^X=KR@8q8s_78@V~XS
zQUw1fRCnD4SEJW4@<8F<oT?$@4|1*zfP|8N6~psmdvy&J&9M{e@9Z&!KbDWhUukB&
z?<%PtBa$Vt!-GCqI+loZhCw8wk5#yEZ0tBf{7V9u?a_MvpfjBDsDq%7GfXp1&`^?6
zZB~Bd%=3~T_8j(T`Tz&0WaJNa%{mg`{B1hR9>2vzMH@R8Mhor|0=gDbDI3SqA{AgH
zr$DNYa`rQF8LJB{iY0~bBs{Zsoez4A7L8FNra40s$O%W2q-6b2sgSEC)l#XLM=d)n
z-7TCe7f%x+L>RjtOsjgHRP2VTFzQj!xxnUCbY+&u4q{YeL*jse!e7byX5!4o=Ciru
zyBvR&;X-W^F^#=u8xhPcucQl=g|<e2SI9{vHpNW$;^s_Zin)Ol<?&mosr^r5`-K!b
zp0~+HGbtr3Z&^K*JXq8!Vq{aajO)0XXS0|+DDh{pwX1Gc+84ft6GV<@WMoGjGhOcY
zFPshe-8Dgi%Gr)KbI^*bhYZgDn<MJlnkpt-W{6mXPGT-I7~WSSpZ)5NB;g?i9Vayb
z$YVN}RJc%Cg}P`oXT$>FehFSi5RHN7S<{bLdyv!*$s6t3;-;{V=H{ht8?f(`hC3=5
zBKEu)(+&w+y(Vq6h$yaf1ZM2(T9x}m+EguyfJ6crQ7M;`KydjMS|#M}5en+=>dmUA
zB@0rcQ9DAQq8d$L+zZ`PvIt}}P04QAkcqQ<q1sn?V+R8;+v~o%)~-J?WMB$Vw!lt_
z8mSY_x6;%e!DKf@ND4yagCO?A0nDGkf*Lq}DI8tKB5GCeOOP>b5!}M@ZmXy(qPyEu
zh2;~XxL|9L_fg@H;bsZYV@UwaK3GrOCj#SAT_85ZWs1~4L`u!W<HIx25bVr*{f9^;
zre1OZWXy0P@myk#bsv~9t1wAR92)xz_h2ZLCP2+iE}dcula(h;s<uMrQ`WYM4Rs}*
zU3a};!rZ!YnOOy9HZ+-B`HTIJ&xa$ptQ)^J8zy3g(M*!A)f_d&>NXdR#jxj6A7&7l
z2-*Kj#}q?kJiCNUDvbv<t0gCF-XnmSPCt=mGSfbH?{<L+<e&c>I}}jEHC9MB%`$Wi
z2GFMyK><Ben_hXhH6RP(;b3Cz4UiaKI4TO`qwysjCUK0!6=FQqpkdU&<##qs)XY7)
z>Ar)5509C<dLbk$uH-cd4UNixv76b}$1KW&9zknl@+B1E&)~eMA6UfyOISjeejmA&
zYtHOqPjIJ2*5i7yGC`xhf|V4ikT};S1(2<WuA-Z+l}%RELF|jWlMkCmjZ5TpK{A&Q
z&A5+;FE3mG2PN2&jAo&3(Q>!=jBDHt6{v;?nS*M8hHTxU0bpO0vy>W{aiVI-nTkg$
zi=g%@d}NJgmxIQ+2~R)oA>9lkOZdn7$hwj_Q}?4G<S*TT{9}Ay-zLft*MM#CNy0qy
zq_7OvqG_dVt;I3zK}f)|ZZ~bByN*R#;%7io$^We9IE<GX(T!*-L{J3<N5>^W=@hJz
znD-!5dbkD?w|zg_a97V9Yw=0@@djNJX1v0d78#FqdfhU4HLl+n1kH#s^<jVJ*gGM#
ziu+umwjO^oYa?Rb9EmJu!UQCfZ0l6$)u-x+YEbe?aik*8Qg)z@AzaC10zD~)B+{9(
zL27!P&jE4Zp}&q@YZ|X*@x<~(Y_%>2+>0K>D@(F8u|oMEtur1$4{mpO#~+m7pYa9h
z6lE#5_55kEs_HjMF~d@0YPKA27@o!pA(4BlLk`bi1?5dOg`nrLBl7tEEcgl2!=<fv
z|MjN-hmk!aSTP_gg@w@DlD0HEzNFN{vX_&zn+&VY83R<>I^{<=0ZrSDnr1Cn749m^
zt*AE(NE&GQ6s7OL@wL#w92g)61$&cm>r(|IEkbmaB!txi2}Y%h6^|*0Orpd8Sm{{U
z4%fb61evb?3&i7H#e$du#^u6<RWr6I?O7-xny_trof2biS)ig_dgCI6V&9s28C&8-
zTE?cn@uaZ*SmREl_sQU3fv_W-3H9k*P=#u=+|7hn7(>$yA3KC>3wrTLuO!KJ0VGCQ
zxkV7Jm5q=W(7D|XVUBSm4i0f=ClxEbaDh+mFsUu9#7p`y-ob08g(KpDq#Yh31P-{0
zP{U&o6i_&_Mx~?YC)LBV7cU*2?g!0q*pf^lNFu@;#ps<Tm(uVh{Eh>?n5whkr<s=5
zdNQ3x$PU<pYVUACQ7>n4%zmWMlgG{_@*J5=06b-M;Nk(X1iXa+LycLGR2(_`nYV%y
z?vD^6!aoFsAcu$FOWc-#A=<5C8b5=DZPL%Nb*WD?%KPYsU}IRZImzKL4={0)lGVy4
zoAjMlc=PSFF}j+sTbr2D127B77KC^^R78e~<zEyzIrYG|s4<s1NV{#zxT@k|O><|~
zAO*6VIB_+sJ-ouX$|?`MT;MM5iKotC(CpdcI4weIo<N>T^GCck2|f^xPJ9v0IFx*-
z0a`q~Ib(;6Bx&nl2QHcb_717Cq|p=F`8e~qFQQ!h?yyV{OwVYO3Fd&<=JwxvXgLDQ
zYC7($>g`upwh~)pO`tICMb;L(!1HZ*X#C_;tPW&t4f~SZ69fbNS-&NQmbpLCCu~Gy
zSXqgcLNvn-8@L<IBJkVN*y6K0Sg_?-#t|OozB)tI9)p>=7h8hhfX~H-Beur3+UdZe
zfM<~=bS{*@^Jx@%jyppN8eXUEClv;FET?cr)mr8pBev+c$RTlhQe_Sb`Lni0#;{y0
zxWZ`R%mH#G6nRjr-Dr?L4nzo*^^Vyk1J%Nlm6fwrD$}P<tp05oIAlhQEyUCfNMY+H
zhaf3|Z9rO6gZ*p{mW8C9VL95clkK5sGT;n2T3{e;PK2abP2EE$ZlHJHJ?#J?3d60_
ze!lnC`Tw_f?q87{N50pu;;$&r9{U0s8k*NUWO>a1BXP|PEHIYjIXbAJ8)yq?wi|?4
zAK$<Ie!dZzSy|oH4I_<o?R(I!CAumrACVD}@yZC_4Gv9TPRY;R5ut6jI23b@o87iX
z<4osaoKA%H2qtF-ui84OV{4y6oXFQAo=eL}VWbj{iRghZXZn#&o}YQ_+<WFsIB*_J
z5U<y^wl_BtK~&f+9yttsA*M@gj@VSA@SLZ9I>HmAAe0Q)W}<)TfA@t9Rz=3?c&E+N
zl#n#(A6&buwgGFyTZ@~W7;-I)%Y?i#y9*volNW6OQBZuq&9)HUHX<T2@FL*&r`QeC
z^7H|D_cVyzsNJs`0PDqtu(WsttoZL&=6LJ7fif~rQbmLV&5drASlN#s3O_zmjFX(y
zsTliadFXDa)W<6r1BNt&y6Yk&iecaNlQR~uy19B%=&5%fHNf#h^rIWQ940;*8fyGf
zs%N!O6fx=M3iUNR_C-BrsWV&P^;wJSNxEZcdAsp~V;Fv7aef}APgI^UjTEVyh4xL1
zCL9Xh_1S>Y-4qWju2ScQs3l_%7r%!Y`aP=#L|k3(WU4c!;gN!xipH)++PjEB(BXn?
z$-{~Kr0O|q%4b~)q_uNStVb6yAOx$;Xfa-vzK0bh$FK|!6;Xoa@&8eR7@iJN#Kh@D
zDl=bp89ANDPQ-nPZUDJxKm@x+WVeO!Cb)BmdB%9pY6P=u+)Q(5dUn|nI1N1pwu@ob
z!*ATL;+$zL<2>$N-R);_1(jkL%~YB!co7yu!0~MO-3&>};%~9c+32imeuilIMUj3l
z^4sv~SGp&F!NM*G$lN_Dbruz7IQ}Nl$7hpcVIL97A{UGhf_Bcqa=;1{t!a1`9wP(g
z`NH6k<Jng1do)-)>nw)V44xKwTup2tzhgd+4<GPpNSSyb%a*uBzDipT-u-EN0p`SU
z#|Hew1~n7A7s!EX#it@{9PSL9#o%~)fNKwFd2K~6J$N8`z1CthPJED4j|9@#y9F#c
z!rwbNX(lm*#TV($(UAxNgI^pW&EG`YIP>J1^niF#Lz&?WTT9Z^YS&#8n^L#XdbQyd
zH>v`BxTygwY+@n%kCfV2sf44TjZILHeP6j40KW)kON+Fj3o*hW7Bwb1cl@04E%J%|
zylt`r<^KICJ5M`ghvSe^lZRsnEgf3X9SwDu_F`wOMe9q?&w}>>RZPK{WlsDoiM=!R
zhw<#vZ2SzpzLL3dzDdytZ5u$(0@hx|AZkL^81x?se^zFV*RTJnwl&0K@E!`jsmG#$
zj-E7tjcrI#@%DemDN_b%8BQTA5)L7hau1fQ4Z-!!lt#PYax7icN+)5xV;p4lpA_BC
zK6A;1P0RiMQJrdcm4#RmuKAOh8;s64pzaF~U#SfjZ?T@9--%UDPcP1LpLb^LP8pya
zU&;hv2FvL07eHhu%6z`T<RRcxZ4;j@ezrW@{jh72f|}68CxJL%n~|L7dXvK3)6>x{
z2v;%7mHWyjO*KsR52@GW`(se#Ce7;METaYiOCj+ebY<w)=dzBaFmsSv&Q2%AL*H3(
z*>Er3A#RjW(k?5HRF@HJK)qi?K|@T%?AzkQMuQb*EWnIXMwPfm-#g_rHPzY;ts8%6
zJ16WWj;m}R!|nAr4o8UGSyrJPkD1jOHV?ot8NWYppX;Vs4<=BkWZ>B$e!r|0BNy&J
zAx2K{-v|Xpw^wX2v*-Zsz^Y)amMXk;VL7!i7Dbubf{J~OVpjU#x@CWX<d$>Bc-TmA
zO)d<+eEDW;eGfO~`p(-8CGPzIl70AAMlgri@ibA!n`B;ypGhj~RMP0w%VUHH25H@3
zROUTwk(-=2y>tu7Vq`AJ_uIXqyh4oPjUQ*i!lStGi^T<V2#9O_O7cRY6j>qUjg&AF
zeKoSs#WH$Dis(GKQRx>qeCcQ~ixD#{i6)~ZY1S2bis#3$)Ha!{^3FA;;q<-)li0Ze
z9yTPNkSQ_{vK_)q?B~lw7(G6YV4{eSpDCHJ0N?!ICbD}-QjE9ra)4JhK)6{a2&z#E
znPKV&5mStd4M}c!EXtS(gi|S6OB?rfna9hF=`yY9-p(q)GA#Jx%V)pQ$yqBoWg(2e
zHam#aDr}hkz@zmyq=J#Nxq|Zd@|@bwClJGsyVm&D*gi53ehuEPRw-+ISCf#xSvR4F
zD<dOQhgK&DsXtRpwFMxeqj^6-xEYa7ej|db7gcjNKMWMA^J50(tYxXTE%X>S2}0IX
zDBv4?%XXmFf!F|IhSp&xKL}g<K~@iVe*SAjxuE}APj-nBX~5$+f~$J!(_9N0LKC#0
zILQ!kkT)KQB(TOY*uVl5ehn0h8y|${Qb_qA_VXd4b_j;@kv_-(`Y(W!?r8@ne*;zK
z9-x*kJ((39oDF;mK?IoKQV>CLnZbOQ$`2cjkr0$!LXwOdd*}Mg<qcU5xCs`92ygIl
z@(L029MgWdfEZd>FWEHYQTUC#L6xXNBhg?=poGt0taIq&dxOTC5ypi2P9%q1_?L1@
z^sd<@*~eCj7A}UvkEsTO9+E{_;y}LcTn@;T!?o-|s2s;Vvdhj)++7JCMPF&qnsCL@
zP%|C$L!#!~2=9!Iq=3vcZu>2A<3KXLL?1ym(0fPF_v3pH2-Y4mg4VD&b`9Zl^!xAQ
z$3U34XA$=dF?k7@tdgyQLqt1lxq<~PQy{0OeOG4fKZ|o_PixNFe->xv!`sjN?Pvbh
z|Fh^mn%Uu`^>LT7N$6VU*xk(9HLM1UZEEHIre!0Fg51Q;1edYB1pH$%l}fH$@tjst
zsu_P@mF{ej6PBm}C(bBr_1H;8a7DD6*ja2ewm07(^)Y6NMj#JdVg;GU&a2XD?8M?S
z131R^7MQ^O`?xq^kw&8*vdD?MXbYS;p=@z+hwfWoY&RBQPJt5^shwP`S)>Zkg_H61
z&YqW?(oGH?kM$(~*d1^fllXfB_s(F{>t89CWW`9vmrHv2`-R1Ce(}GaJe7gNT@EVb
z;c>A8yM!&_et<hMK3htEEvLU$(qF6TuV?A6=jpE(>91xsh?3#@D9v)1W;sr?9H_Zs
z+5d8Wl^1l<PW)Oz$~$TYj~7112dPxwe0(9r^&mf%MLgIyL%!KOhS*!#RcT7eocD-&
zqX5~D-QLZ>;w`=Up<T#!FYV6BrYDUan2-_`GzI-Fa_6Vu`qZ@bGc(-2_^fE^RuIC1
zN2eCkHZDaZNsswibzB}!t2HU{esEEFYU-1JrGFf-^Mn5Jy#M*8b=)|<y84)N#Ff9W
zy1e`e|7kv3S;+sZN6n?BXP-2eRu&ePmKUC_(5|`k?Ahuk#lpv&#JIk0qzDwnCx2IA
zqswuvKDqb(_%HvZ-;e?W99<`_rQbb0yDp}WXNcl!t|<4-!CCJDXWK!)_je*3{zyn0
zKmUAfKT+bY|75z$cBgmMJ8u{Jtvjf#_~Ut}H?Tkde0ss{b80tT{7GYMbdITW_+uwH
z=BRCa)Nl3g%n=m|6gFt5rrYG+LIr4(gQ6W`E7q=vT<yd+Pm8~DArN&*#9B;C7km;v
zQsND|U@oe9Z&9x*re&!V;23084;d>6%ja&_c^BW8(gB1l(4iO2r1&=U(L1_Uu8S5Q
zT;0XXB%xu^x(<_;m*njF`syWKp4;0-%O>3D^-t%|eK~{qZOY2O-QS#J(&3|bm!{>Y
z_Ei_pzM$T^Vp7K{hse#ETNOKRo%TbbWM&~F6FgASy(ibVgmY@9x_#L19^EKcUJ0`}
zMP2dC8UQu!Bs^K$FShod6tC9yxAvc^ldrj$x%2Lz_<C&*+t$I>=6=DEFR$p<!Pd@O
ze!V7Q^xNVuTW_h?)d6Dc4IU*la`HR$i>{#AX&dPFJAPv*PC-El+NA;2)4L~KrP8@P
zy&;QiajI-0m-1R&p{#cY+Eo>`YHLd8UE&U{YTjUUT@hRUY};DlUix;e-^P32D?F)H
zLbvHNGJ!F1eKb?t_WD0)?6m*hO>C`x;?#%n_ubd8{OXYhEf-ts4Jbyz0bGb05Pd^+
za#nnG)4w7Qs(RwO;z(qir4-Uj{qN5fU(f*{ci^iw+0fQIr03ox#j`ES_`0XaYQCAD
zn)(k4AfDf}A%PMGXHSZe=i&5hoIO#Aa_Y8$GdIW74mC2Suw3{#6;iNs61;qwy7bU?
zV&nfiL{eK!uOyPx<mJo%lm52my!C<^Fo^Tyk;J4UinJJ&p=|6lXJxJPFPh;+E@j$$
zA^<Ih(t{$82r==bOescqKyb`)jQ<;Ynn$CgB=Oc0Z1589c23~12P}%D^Bl$;`3m*6
znvOBtOHszw+Cz*>09j)K6pGbl6^2*&%xOx*Gg;8vBR6M?xjBqj?6<Pa**YpS-}6_V
zxGl6>QwgQ%x@KYt*|MJ&&n2S-^Q}*j#3YqAVSvz7tJ8!RE0ZMFFPm8oy*NEfE><(W
znznKL{8b2=s!X)GP}ed|*Amc#m`<=?{%zghh`342LVUO%pUDtl_-JfMv3`a#prTn0
zjm-B*TmQ6pEd%~jakM@2On{UTO~nf~Is+P9>+lINO-4U?lYQHV_&Oo=3*Y^*IBh^l
zv5E$C+qQ`aOrb8}zsNUCD~Ih6f|G?t2oAE1U^u8!L1Z#_FM6iyI;=;kZ8nx>R7&yY
zyjLu)td^gva6vId2fJ%;A%L<CpTa+WT3^eCUG`@in>^G1sTx{ZR3w>VIsh7L;G~6*
z$IYzM98w9l#=Nh?(iIfDa*DORB}{d?8@mjDUDJgpXZ*O-MUIlO<Artl2EeKlC4B&S
zSypyup`w^@@X!_6x8~;~6AZ)@(KeFHL#e5`GM11kk>fv`jskW6G7_pIo{|j4lS(n|
z)k;D0NS620%ovIZ0Oz!kJEe4_*tuguiowW=2#iuy07E3G=gLQ4LC1^)s%<OO5nk1u
zN>wr%?!!(oJ9wR|wnE{SVMZ_(qkjzwYA!v5ge>0c!w6^ur$;ie*5GphNqJ6Wx2O{O
zvh^u9xDjXsWXP&GAb1S9823xhaKeL!8<~iL7SX5v11Dd=l86SrBQB<dW4!uG<$Wm(
zpu}jlo$;;7GGQK+MsRUwEnaI0J8ugP?{Ws|`&u0@e;)^xEMaUo_U{r@2BS1|^>clM
z$dDO2O$7BFv8Iy_>$E9mV9FN}7Faj`w04JAOeR&CdFVJzI(=h{G@MHb?=sp<M^98}
zE>xdc+alt!`DLvubDW<KP#$d%feZ~#1lN5mIV>t*>(7Ihh;nqJe^YQ7cYGMQgxyk~
zhS4kWi%fKt^Bh0wWe;~YZtg-lGuBD011E(-)Snlvn`^8y*BB1;9+kXvjsDr=wk!8O
zEn(;4%8GS@m4Ih+D3#K}2A$3Yg(zBF5uvgK4s_jrO@g%U1@Rq+kZwg+G(Wq$T6wN(
zp*7@E!Cl+f>t5niN+O~ktig=43cELY0ZA)$>_YWdz0Y#P%}!tUKI{6`PIdK}-p_SO
zB*G!3>!$zGwzq$U$+^Ow*?-qDieVbrxe}irG|oQ4Bq!T{7Zw+nn?v^Bm1hghNBi$b
z0R6pP%+J;vfLCRT+`ehwc2A2h`;-YQre)Ra_pblv=%z(}_!$|jhHYYxHeG=7Xw&_;
zHr-*4B*goQ8;zFBV4ze~J!e)r@AT&dhk#$v(&YyedaW6<jwzQM6zU^dPfw70Pl8us
zedo<CX=-+M&EkFd)zp84+jh<;M_%KNHTv1@bAp-8-Wwh$M5%Z5cf@68Lf!D9rS#J;
z30t~&SuDR;aI4Md+|uFqifd?^lv$8{J*pZcWle4Yd?z}s<*|S{tE%;-xi->rZ!US?
zwpEAf7Trti^H|SuZ?sds77ej4kad2d>=M2QnYr4XqnlF)m<Ex}+1hdK*U|$^gpfZa
zBoQ$M5`(6I>xtTX8IT9~J=`!|1)c619)5MWySMfBVC~iRrsY<_c&#fdxLfDPZNR-8
z?6JWwfxyW5FU<WYr+zuB$>%@*-)9R$=l{~`(xd;k1mLgu*SPclVGdxii4ouN{h|YG
zuX939a6&&}S#p=Dqeo%l(LwyHJBXdV3EiT9xJ^nkPShy6B0gZ!=WxW+jw_xwRgZDB
zo+NBF)U~_DMU6{XZ}q=FA5d;pf6NsvY5enEsMTYY<bm!nlzY1It^agI`~%Zca<U7=
z2kP@4_j^|LgQ9p;sgAuWMV5Xb7{~=SK#?wp-ZAesNl02haKbmvih;}qEJa85Dg9dU
z469^rwt*kJ6xuQYMSZn?By7WEnLNwekF9=J4+nC)o7Xv)oBVVil9s~5(h>c~0Q1J#
zpR$L8Yc8!`SD*1f4sP_%tYz?T56gp3%D$eoJA@+~t7f3w+@)tc1Xvsb8yr{ifd2<l
zN08NqB$Fxkw`YRfOT73;1Z5PZnD-AP7xM9qw$+QONF$WXC7&tp48JN;k6y}L(>uR0
zmm>RTdp$Sk;H;y@w5+pgb3_tW-Q+zbk^_J7yv3Zc8R}$HS>5DgwkVYngDnl0=j9`6
z0BNvCYGW$3T!BeHHHzj+V|C?<lIP$%mLrgekN^dAga|s@zdgc0+z(DJY=vvBtP-iU
ztcb4W!qN$LmV3S?d(K~R_N^{GfAOrbst;_=`&Wu-VK)6%yURTfB7+9S`-}O(cc5Uo
zgbXzA|4*3kclyk?XYA-3Tl)v3H`_eid40IIwz0L7En<1~Mf2I}(u-%$SDPy@CM;p3
zldzV>=2D}%^kR9Xx%k3BU@IyoZC0-H&gCgVMRUb6!ajR5_oqUKD)c%EF|*-Obb-`Y
zUmkpQD8MuQH@m=XB?3&{_vb@qwbBAO?cEc`$N<a9Fc|mPf=P<zv&JH(KEtS*;i0zG
zWrnv6-}&q2-s|n1ugyWLG(6!SCBSGdym+zFY_2XoBS3F^`@XK;tbJo(x2{goKlzH#
zFeC>Il&$_8EQIHw#l627P{c*77CWZGopc+$bqB)ORWOUVaq>|SYbh<4a7v5UteKO?
zgN6hTaYD_`>Ui9Jv$egw6+if*Xm*w+wJozpuu!EZ8AB0o*WSi76N)QocIMQrt;0yq
zqjB;(C_`6LWD&KE5-!NZS@Nhd%o-DV^^Nr%c^w}r`{C8yP$P$t`h7PTdc~ktFWxv#
zjIQ-<^R^-C=YWrG!nPu9T!)0{u&4H|(5NJ8nR|)ZP*!OO`LkwVNW**yX2(TJO=;>=
zGm_J##<0^lfx>2`8ILwGGPKK^29nVR;~``7PUDNO-oPjG9y~hOrsq(oyu24?HUM{O
z4MuTWEBE1NoFB)Pfwz^{`QlPbFk*9eZ*zU^U~}W}&Hk4ZGXuzcM-2r@5c|#ore~gD
z()TU5m#-BuO~{r3gb~>^o-?<ndW3l7_2{%027S?}1<nwa*S!WZSzpzGvMvokHry)(
zK~*p|G?N`bf>J0>etULD!7kZBFlBHfj6e9PU{krS6@df(&|`@eg364tx91ZI)g#hW
za=Q70iFrBNo^#-`N?e@1-Z?}5mAee>as;880SK+7dx|Htq`^M~A+Fc|g4~##r4^#{
z_WGPf;*_fY^0V4c+g4Sn94QU-S=~UiPHSbX!Oam|^OzNt9h6g@s~eC{s!SjaI9D^E
zTIU0Xv@$r87(M>*5KG?{H)c^D-X)e}ZXCCc?}%gyX0F)k{P>15K}ISCK3a_C;*!o%
zdAbxbSGS<qF9K;giyE^If-3BmKxmXtpO{gy<=B)8&2j87qkDYMkYhi{hi`X(>?$K6
zu=78EGsw+92y*kk6mkqR77$QJxu*%~=Er}KLr&IF1w<R=cCe7TP6avEjd*-<Rbp+-
z{~f>h-F;t3e<;nyuIZG<P1`-Ye<)5lK=S4!lQ?6hR@PX<rSnTa({4I8Hf?OPyvqn$
zsH2;Kf~SonI*rpt<$~Tc%w3L9Bs%-<pf_!b%O8qz#rG;HpHA`Zk5HfMDJAPy&%_N-
z{2`p{aOLF%s8agoPu+`~i<k&8mloakERKl~7wKF^<&YoWDcu)TV!-DZz)xK&v~wQ4
z6}vz2)Pt_#<2(JnlwlQfG_lC?;Q0aN(aE2W8koh&vFrbeCbfU7t{v9q{~gkiKi)E=
zK#aft!#kY4)%!oot1HhQ{l6b?2gdg_&i;G95m%<7gR_3`HeS-lpmO}#XU(Shtth24
zxVchdmyT|7%!^-|pKWjRzus-_Z62=g?lzm5gKhY^8E6hM*qq-C^gk6y;I7!h(l!Pb
z(S7m6d}YyYC$ALj;hMAostDUE)@?326LiN%ZaDXyiA~)dWxljl|MbS<$HiCut1xw~
z#IB^sK1XPryTK$<N8{nv#wHo~3Af_<1)M&LrGNUO*;s9?Vu3PSP|k&h*ExP`ev>yD
z{rjIE+4rMGOs4;prRK`YC(Y%><)y`?Rl6V9Tv}LKdZho~0s(%}{XemQWCwTjNp^G*
zQ_HVl#fw_uGFW~^?eSWl*aqf>$7_A@ZfC@n{90e*OsE!_?vTgk9{)_h%SpZ&UAxl&
zb9RUEC6^O1eZ2H12j9HfdHs4{dvx&4;p_E-y=@b~dnYH@ql%*&W!HAIBzG0VH+v(^
zPtIGXmJm#I(f51o@kX4vbLNWmwYU4P*KMTzZ}$&vB#;9E`M87jph(?d7t!&AVO$mv
zAMmUFy;r*$GykwHyT?WMQg=9r-N1u^7r~~B2+qZwAjNw<^|+3ntZsf{rz88&L5L4y
z>+SmXyN%6_<mjn+#b@C@;vePN=~Y}fdc=u6{EX<C)9SNYPtpGFR;5*P2w`jCh{s!$
z`+R)XlK0KwlY4d6?TY3<GEdB`*W@Ua`SyY3IjWft@?Vm7K1H%Brao1FU9(|WERs@J
z$@w<$-J|T4E*_KINb-fHLh@NRiY+`NvX2l0Bw0!+`1^$^LEM`<IeDs1xB)ERkwSf^
zyNymmG7kB!FfDN#`1BN;8I~YvOo%L$r@{H>)-+5}NIc4uj=Y4q!}@@83sp2FzQeJ^
z?Ej2D-MV<+z5K4|_Wv7kPq+kD^>4@}toHL);FfiRN`FbTlV5s`yI1K!%TW*IX!f-T
z;*66bt^||b*1Ec3ZlP20NL|~D-4@v7ZaM}BXo%`087P>zpbtXGrDe+V5l*v9D@I@?
zr~Ciy*&y^obJ9h$6U*liH*LRjZc72g@xF54BFAScCd-TkX>6ak8X-b<3%6^qXY&!*
z46Ws#044T-eX$F|IAh(U<w7f~LmpKXUY>FporN=fY_2!lMznBk7D##yuF$fFE!-t{
zjbaCY=uS0e4lbtCBc1n(?*&{<etPD6Ub361&ISHICQh(7U+5;5OcJ2Z&6SL(g6?JS
z7O$f|1~s(M##CkhxYVreGZ1*YNhomLn>*@M`*UPxsHsomPL6I?n11w#4a^%6H)eWI
zmF|Co-3esp;dh_=W;vLJi{FPy0%N=8ZTlDC3+#qBF>aQ&KW*1gKh?cz_b_^j?n9?z
z_iVa#N@{`#6UHXxA3LW_hTx6N&1TM|Lv=k^NLO)!mnYp*Y@FtF#u7@D;s2BEPWW2C
z;Rl^_veF1UhYo#X53sHZ9SWwcNO!j)80aeHR%l(`iNnGmOmW1a9&St6P4p9TEDQ=x
z7z3R~j0t2gJsbao)ydyJ%w5p%qWi!&{^q5{PGIRHa}?CPe%?F&0gfXU%>0h^(^f<^
z#Db`TOC=zv>sH-tgkG!@4P%rr^=TO7oIYb0#Yv~7q~7DY+U~UN(mbPO9qn_le7TEz
zTmr%9r!WY;eZ$MdKraTT@TIt4`cCt5ziLsKK_UD|py*r`gYypmwozhJAQ}J6*>#(S
z`)X~|j4kqhzb39_4#wqHNcHQz6S98YI~DKEnH2k$#u+o@;Hs-WZ+u7{-BX|L`N1k)
z9gZQ9wx_YcJD`dxV1{!7-elaqQLJSrn%Jv#BD%y@IS^tYGjhmsh@X>H<>vIPa+*hm
zTr-N!2zu!J)H$|fADAQ{0$s>trwSd$Vn|0FGQNhRi>XcDDg*3|j-}#zKMXm=XTFyo
zmVl3zg<+I0^q99ZHMnlWRIoijN%T@P-3%zKx`&+v`6OOan*On?lqz`{?I6Dl>c*C|
zW22?<gUDvKZ7l0Ql4hmo3aIe{Vdwg|@jziN6WPA(Tq+a`ZqO?B5u|_``O~y?LcEP%
zh66JV$qSO2a2$shc6f4Fa*)UL0#lz#4ilKp&9OQlTL*sHCxzLzc>Za$9f@;uXR2%d
zO;0}MQ9uH5^;9OJ{JR2jeC0~W0WHFWOK~0!NU9St3KKQWArKgp1ME^#fr~L*=i2S}
z<bEoei{aLA$U!myP4T<Z+knBk$6eN3gCIC#Z9ANIX@ISEgwC`jeoNE!IE8q!KN#rL
zcZTD#9HcK2aj@aB|6+Rc-Bv30{BH%S&9JC<wx*8_GWHH(GEjvbZ=p114!lIY)IzfY
zLv53Dg|VPMVz9;L{98fwZHH{L{U5g4YnSc(X79&AM3P%Y4<jmo3)2c!suC+)fZG(x
zN1_xxBw`}nAo~9`(F|8N1Q3d~bgq%c6~81=rE@7yIP9i%A%$JsK!s{AF#*8hZGcQr
z`*+{~COnLz{lLRL#dg9`^c>uqGRhNg)ICSz96W%JXFNP83LdsngY9ng5OSvjT&P`?
zY!2bMcP!1_8D}GGWY_~8!sNSxuH;%Ug^(Z_VFXvg(=vSOc7WVi<ZMNsLJ}|5(BAs^
zPAwA}%pOniF+NYj+l)E`r&Z)hE-5jYgyX5$o{R(vdWf900*Y{CMPA@mg9?A}uscB}
zafB+2eoIJHr(1L000BpxH9zx5pn$vF0*Y)~prwFKHIU@+aK_NN>H66;1Zp*~sH@aK
zAPxxZ=ht}@L#p~!ZX25PcZR@($0|`890TEv-Eg@xm!}+6k#?j3a!TLyVMnO;m|zHj
z?hr|-WDahYFl~umrar~+C7Y3oK^e;-)EmvOK(8>RC`0d1>>Mq!zzT%<=|p-j;I@);
z)CT@u4lyB0*d^z4U@dG*a(>39WIz(Sf3GC$7$$n5l={=+d%Iy~kH}GHf=Qaa?zEFb
z+9y)K`FmldYc$;pEmJY~AwnoYs!5l2ERvHE+kTxg`(;2jz^pwS00MYgzsb?TR+h$q
zZvaF=0Ey7g)H4z<j)#?aOV+TQH<fZD@Uk{a3Xqmor>-*kRy5)5hLztBv4n&~vjpQa
zM-#~#GpjU~xB>d7Fczd@nO=;F`TLHF4-!A9z!gml183|?nh<Kt;+&C}6{E&LDITjE
z7$)rjFzs6S9yydXmt*LZGz*H%AD!bL;@9U?5Re%q)xAO@Pv|v3bZiuFxk16Vyf7^(
zPDzu^76-6py$K;F1yR+oeU4>tz5nTS>-t*RDG5tNqxs%{1<c%(tNJT%2+5FfP-Y3u
z2~j#(Y3UqV3NvfFkihDt9ARm7)6DA<)WC(s{|o}H;OBUp1K1G`IS|BNLY5c-yaLD)
z8GC@Ke@l2?9mNLRv9+vgr|w5RLEWULPzA)?9wtIXb*n<KJF=oWUxNN`ItX8L#rFq$
z-*64!VDpd&g1@}l-Tyuq%=uaTa5O(4Vk&R-e5KNgpLMNPpD$FJtu~$)Z@jb2Ea(cA
zS=W>mbC`z;lHdDQnxF(*HF9pZO(=wEc1sOSJCGzuF_K|<P>fd6rCDn0p~m&1OVzJL
znZsNHaJl$H)O{5CIk`CZ<V>lx25lg}?lFL$gc>TBHL5IjyntIw@9t3@f_)m9V{nOR
zV!p49ViO;ULdms5-yH6~+uq+id`%q1VRQA(SAR=exmvvW>VLvUR+_CYXU)jQ=*`M4
zIzA&o=cUM@WCj|)X+1_TiyfJV=Bjtg(b|bx@YBHW^o4hxz6hpA|8DA2`_L`=EpaC9
zH0YG(L^xOOU93q*W*dm!M91`C0=6`@F0X+ERkNr3iLNTZq*9D_3}T%rrpfXGG~ZYl
z{yV}Ei#Jv<g&bDW>*((V$^7iF`zR>N=0+HRMirq~I*uVHZGIUh?n-@n9aSdBNA)gz
z43e82Co)M?n`SLy(Ee`Fx3=xlj&~*<j7vN<t=C;4^JYOit7#THU6=LL=Ate|d&7+}
z4)0$eWbF#I5=-RPUU;PTCTs>C=$G#U1D1KRH0C!<hztO53X%&HGpY%1Dz0cG9;i$2
z{vb?j*^Q7~<c*fsFwAaNODBfQ1_`U~D~$%P@O6vImF7oAV^6|941qXU{LJU*)-2}7
z_v5}`e}B$E@v|{Qq?u}Bp>05p2@^Sh5(k6<2&w?(;wn@%b0}Z)bi&7bF3bL`Z};yo
z<X-%&Pe<o$sOnqxA=7=>XTvic`pU38BKCi)js${vb^=z=J6-Y!pmXxc=A0V5?&9g)
zZC#&@ZxZ_WFCVisGAC(T-q&E2sLaB6NNTlnxsPm^Xt;(#vZi6o_J=hO#mOcR;ph-T
z4U9z}Hc-N+wRk1?f!|9VGebWWT5v0)VxD#mJS1VL2ixy8m3+EvIUABQTdCpgTpD(^
z!5%{64XYFDY$6e>NqpJ<rx#Fg<vZHg$a0(?wQ$G<S$^UkyI^0D=xy((XT#_AYGzsw
zXDo}^r-$CJy&%va`vph0^4>Y`^Z|rFUj>b!+P~h^b+6~Ccl{XOdBnK-0pi=bLE^hK
zP<(LjV2-a7NAxBWhV=%+WBb#4ClHW-;tWEY2~!9yhUX9-KWGwmCM06IcRtnT_e`zY
zxNer!7IDIuuz~@H_h=5oTn-DGnKbNe60Mx+q5eijsT+gxo0+$?6h{=vl3jVW&#3>m
zo{KEjh1$h{9Z9#{>(Mrl?7XR+ZoI1RI-uo9r?l?lKs3u(Iq8a0S>ItiAnSX~-`f2C
zimhMtD7g?zhdq0Z5d<R>Lb~Yy=n*%7t_n<he`xOy6`vF57%hxDC<4wDWn4p=Q>2b$
z_emDT;diwbMrkhAC;C`QLy^2cQ45oHYQBxKiCxC&cjkibsB)7RM<ENjHI)$YOT8Ty
zHKUtM8W$bu6iZMKevV3LQF6LOG*ImcZU~~cno28MzNNpE|A#xQXT6?zzgxaeG&!`f
z=!N(6n4mo;|Bx)rA#nh3PAFf{yEXfj0U@rN)5kC!U0Q0q1#p?^fh$mU9RWiMW**ls
zxkrTp`(|fj^Kg6T?Ux&C2Wyxl-hQc{U+?aGy}5@bTLz_=Ig5!#3q`5-KaGm1)GUo_
z*?gs1$YT^t88*)TS37$LHqh&}{e#KlMB~MTaokT(H&9y8M;Yg#>sec0f48@`{%tMX
z>eds~^9Z+xuIHDIGj=@*Y>o#L$Em}Eq4f-pliM>Qjv2zlf#9VCq8Oow2L$Vbv#4m_
zIHuwVI0k`X$O6$T3^TJ$OnnOXFpJ6G>;s$I-wMIH^dCc`W{db9Ic``fSHOvr8|<V-
z(d1}CaO*6jkX&^buGB-t6)`9#k=BqE!nmC%dxY)%0-Ho;#B;IKR2z+uLLKH623G8S
zGk@?+jL$hXXvMR0Xz83D`MFqGGv~L7u%5jSsw86$ToTbAfQKwGqk0OAbJv=_D(~PQ
zUtoVvqOu3Ji)m69D~s01nvB>)>Uwnr^8S8wKmsoeVXDZ%wNe`x&S_;+h-^M1S794(
z7&(lv?ge;gx(eXE>>k1NUh~~L^K}Q_ai#&7z!ADX=xNd3W?+FmF%R)<flu#H`=w9q
z$i^mkJ?C+<@&UtL$x_5MF!+1t!j58m9kzEzi0xGDA=dbu>M28~I*>V_{n};wue9^4
z(|@oo@NDY_`X7oPd69<A$%`CO(NCvK?=rTg^&5s+t{%g(Vf(i7i!A)Ku)t~pz{Y(L
zOx@vw+UzCO5u(-GM_7#T$|oK|8&W)j0aVkG4OPZiH-c(;(M=g__$lyJ{s%>^u<q&K
z{pWv{8$Y~`ilx^K!alJHE2(^9#^Is0#3PPaEz99Ey}L<azk_df_xF^_M@Q@!sM7V`
z=Gq(bTON|j=MW}%KVXNxQRJ1+N2z}vHV*Yy!Z$A-{0+&A!Z#Za{-(LGxLm^OVRLJ)
zEUo%CwF!3|xrSh{xUl@(zfpmhhmEtm@FI+pvg+&-i=pf46*f48K(smB-}>9;p+Wum
z*x^z;&Ny=Sj9*J?xTLbnOCpf^1*C>x3Hh;>X=n^xii5&^a%p_h3f8|`d;4~C`%tB-
z$76kLrAdvSQ*5=QsgIp`Y^8Z?+V$&;tyX9?9+hLOXS8}2L68Vg#`3|W#V2xGmtbx5
z4<P7kJS6HqF;W;0mby=tG|G4w)qMgtm;|u8PgeBFc<9xAVys{SentkBLF}1E5%lYK
zvTl_7$fYuhG1<cx8f83#s*d8H;GShG#~)+0-xwi0bpOLRB8%oj5g>dclx;o~YT+BP
zwdMpaQv<f<3w`rpa9b4I76mtBDIUoXPBWxPcN^!Pq9LyMdT)(%%^@A#+nv2PYuoW8
z8^0Nqk2b$K*nG=XLFU29A6W9N0vn^bz=ZE1i^BAhrO5M~g(KF3Bv6>#Wh%T!N&3q0
zU}pB>p%qQi4&HyqEdMbqLp{}n&tY$~Ka9JiGhS!aKgW{JBt3j+*eF;%a9resdxlNw
zWkoAz)XHi<bXY`yd&V6yJ49{sfy*^Q83pr)E;k$XLCYOC?ETA4V6JNML(2_;C&QaN
zX4rsKR7*pni&=O`^J+1wX8BY3A1K)<A-Q{W1EhLfMm_0pFeq^}J;W(i^OUQek#R)-
zNVU`GmB_st!8%=bH`FNeU5srLth4R=`U>qTPJ{b8t=jh=(wegn#VvZi$~hTFjVd<Y
zDk8tC;4zW*aBw#`J~@3a7GbxtUw^5}Y2kn^n_TzL%QjptuWO`_ZU(6tsX-6k?T<9P
z{^{g6_7<<Ik2GRd6mk9`_4Q_L|1TqrB!Q-`B%s-4bTzZJXYK;>vRw2qR`=|hOvZhE
zfj=rHaOuZPO7iE|dcL@{9=vd>4k;s3B@ns7%>A<v7>zO=!hLNSe7&}@mu6?gpm_YV
z&N+IzTaQj}2=*gJ)(Q>F)@2tN^5Y_UQ#s}i%fO4F9pk0m$2%w%#^zB0^$nvOs|b+#
z7D3pre;GfYI7B73rM@2>q<dHo>fchZVIL)_e3?a!9AZ(IYu3E2Ws4kGp~ZUe+IHGB
zGc+052wgO%kk}EMaz>acCZ1%}C&IP5-{f9Y2Dd|Y5X(INltA;Nb2v2N#)1ib<oOgb
zz4whS>|txe?BFr9i2xG|bYzxu!;rE3I9=fM8=IqDL?9f-mUc;V)2@F41cO1i=Uj&5
zCP#BdE5<1yIb1?pF*LXI{;bLOVAl#<iYqS_IK*OvyG2Xm%8vf_v{dV!qr$NDwcVx|
za>7ThRMYZRNB4%;d%Zs9FI_!<_@_4UR|<H#9R6s2-efza_}1Ro-B?1WMVQo`Z6Yx)
zHz&>Cy50b*hdU1JwPqpqNfwTv%OxN9HFpSxznGuj;}q`8Fdo;T=uIueF5AX{<$D(s
zyBQt;EK=P!((qZ(umY?Vc7NKFG1A>j@_pG|8785@L4c9rHZ1*z-xK7^;ZY(Ev4*TS
zx6%bq7V%7cSnZg7i#OLYR``)RzioA|@tXpeD?(BUpbIP^MAR601%^(fqv5APL<aW?
zFEM1GWpY(l9NwH@%FQyUI}5slz;z@%RK<bZ860QU9YkmF2Cl&R7NNs?8@OyUv5z3p
z)VDES#AQy5;}_PVWXYDiLxj?CW+Y4+=0?YG-+p37T=ppWu7T6uLsI)Dnbf}yOMO6-
zQSeUsC;~ZaQ!Xw)_&t}+yizOSFSiJ}Nv;u@_&qvpQ{nyI{a$dTj4ZKiy`IN1jBRhT
z@a`Y>fd)voyAXLc?1FQ==QO+_*8Rhqm$SFEzCk2>$jt9=td8--$biW*Rcw56zYlDy
zZsWcit9el8_kLk`<LCQu*vlY`M{&=dkyMEJRT78bvzaZnNdo9a8)U8=382Y)@=^Gp
zq{-{ZTkN`r$8?PHFS{pyJRz`@QAlVTWiP=?bS<emq!x>Y6}i~-n8PDDUEr8pPaGXn
zoxHlwO=yz2PV>p}y{WIAgWd9yUbu(ILBlW>c`$nO&M;fk2%xWZR;KT3=|W$JeB<@L
z&h+`{A`C?$2;&V+i!(I8xxTDwf5sne(tp+c6L&gRvYp$-u$WSR*sWU2%5<#D1(-r_
zFq=^*1Sfgd?o3r@5YHogkFSU$8nd`;V7eZL8)^<B<AXnE2WyV!NcxhENGPfl!Lpee
zaGvty3$7U*&9&NCG8CTkv{;#w-LfBeuBW>q5!GyQ(7QH`4o5I`YPk<m{SqzwQjs=I
zDr|JCn#8dsVmBCky|;C+X&M}ZEd2DGnz9y8c1o6Udet$J<eTBo4)@=^lHF|Kr*;3#
zny0D6E5CK$V=Ub8QSE!2viYzgjB#K2p!U^r{EBQHas3kq{aPO%Vuj7ux;Ovz$k$ws
zL&dXPa>4&3=7z!)Egu!ekili`-NDY``u5KHU*fvz*7423R%qm{hVAlB`n%iPl~%IE
z8zm?q4`UK$RmE|i2XFU-HM=-G;c6=nN=c=r%>Z3$a+}%WFkF20b~X#YU~|;vF055~
zBr1U-IP)*?Xmgo}+B}y;P&8NP%YdAjr^VcAQ%rX5#Y)kBbukW#ueR3q59tiw)#2YZ
z_jZ8Nh`+;Olv<MOI<y>iBxDVlTh`^Umtv@4*<*0Q5oT$3iiVeL%SBUre{u1hogxtg
zdbX&Cb6WF>^317_ys^ibDqfUP3u#5oRkxNlUpxnn&nbJ}eD*>RrJ{FpS^rwpvjv_#
zUxrQa-=*f$g2L`gt1uFZkL&RZYr_L<t@_vEKZkIkX5`i7FtUBJKu=tfqASLHPPO_~
zMp|Jc4bS66{#>HXGOw0-waR~=@$4CcKIgyBSMM3pcO?wDr1>t=MN{KGXXzSlnWu~V
z4#O?;*kECEGcc${b5%=J`{pyj?8UPOjGPv0JuC^XP5OF4FV7fpMMLtp{sTCdd87@n
z#i^ObX$rhH=Os;i*<d`y*i&L7TtRo4!V@g|-@FTHkbjGWwvS_c5CnXSvije9o}2Vz
zrh%TE|C#uZXRE{c-<Osi^S^)4KK|Us^RvnzJ5s~*VdX#V1laEM$ZpUs_FF_^lcxQ=
z(;L{Ie?BEe&Uu5F@IPsc$3)SO<vq)w*K^p+4bhTVGKu^&w~S2vUAyy)@JSL|aORv^
z-qOR7;mP+U*<Y5`kVm{h3pM&P7V&slJaI3*h{pHR^WG8KL#z}~%@?g30@9RCs8;Vm
zDGsgGPqgR`V{m;+X`516?K9N*M1iwFU0Els!@S&!KAFRj#x;D8OEuLJv*h4R(iE8j
zZ%A!N{s%1OiZHe2p&Iv~b%N%6Ly-fOy;RVcvH+sHS&T>M*$N(5Vuo0hoN;cBEnTE_
zrm*A=+Aa3qG9@7HCmb}#C}+&xglT*|N>**<KSPq|kb`+r>gALWI<$357GD%)uITXU
zP3zc_HKUisR3R1bfTU9G7W2C99rw;tBI@0p?d@Wr^7C*F&7_$gnRmWeto}aS+uY@j
zM6opV`1Kl9;5U%NR?>@C+goq{QY@$6>Tm1qm*m9WIw)3<ar_pHYB(P1&PDNJVG95K
zo7PX^eifMwxc_rwHLrwd!JFyOP)d(}>#q2n7xQ!^^jFfJpDh=!WNBb~ihW{pfDX9`
z$iL$D{ze*|l8c8Mn}6L}r)=l4#t%XA#V%i}wHte0rVlnYU$4E}J~-Utg2?XH+v3H@
z8}y*V{e64cv?awZ%?iR0^n5WG`HaG$n{>Ikx3vQx7Z$uBW%MGT(#V^;+ut(j?X_>M
z@@4T1fk9Ir{6GRnC_rAKX*|-Tv9ko6t<#y*$-&x}*|t;hlPfQ5I$y_7dfC@ReB5wP
zU&WzSw`tFZE}q+%9!)PQP3g}!HebE_GW(QYRIOC;*I3=7QUx}!pwSfLlxz(pbkQj(
z?xnj?o~JSCN2RGIV$pZ52m@ow*i4Efal`b18JjRJ==FM@lDf>+ddJS9RK_KTeRk`Q
zBIqyf_V=^*<!<Lqw)XyuHj6b@B3I6FO9kU|Y20h)2J6h)6!3lvmDx3PTeUzwYW!nq
z;c5it_`ctX%k8PW=Vzb#hHHi&9^4<yiZbSnjF29wx*zPM-MiRVe#Br{XO+rPRkP4A
z&Oz(+R~RH(&zO0DV_*v*fq?&tj;qqzW)Tx%>e-w>th?;Ph);fM4NV1&-jX+TIf;ML
zN5W&BG~6MoVk8guI^4*UW2Zz2j+65{izLu<ubP=}rhUb~cCAveeP>ou%!Bb^92L`M
zWmKWitnxIhduHAM5=vVBgX7b3qeZh$c=dT9g*i=oxPu%g>UzoF4|Nqo3z$t6fNyUU
z`%vjE&_dX3ZxFkipO2?q%;8=_Ic|k&JRD!Y@vK@0Fv`-}I}kMu71Ru%5Jycbw=F;K
z^(^;sB@zIkK+LQ&6f@x75UWTD8+)g`)=@$lT=bAK%<*u3N=(r8*#%iyf5L%_Lrn?i
z-;iCF<9iL64?%{2P3{7lnH*y_iRm!m{s}hIKw0oBU{rjrPE<Hj&dEM45iE8VvE(br
zn*?10u3qMN=GW}sc5baFyRKiPJN)r+dE2@OVQ%paEW(2|;)A$hqhnYMfN$>5Fxi`#
zDvc|1KpZk_l}62;jUSk))ef7UPZ%_{NkgV5;|JWj^Z=XMQL8T}3|(!Q#;-n(lgOwy
z7nh6;Y;@U-%a9u~_uW=o4)AgxpH&;%X_WKT^7h_qWDAo9B=}WvihRvV9P&RMMWtlk
z9;3pksY)?L7sT6kJeow8NrS1^e4@o+l~26Sq&x`-3GpagBV8zRprOwKdr9ph6EWw+
zp)x~OAW?Q~AhA~bYlx4&f1b^twqedH-!G1Bt??Bkde9sDfx`qHL325DwT@rqL|KuY
z7&ccE{3`G4^s;xMjQZ<d-^htpI?KlJ3wXm23n+s@6X+?bP`1lxbQs&q2^r%pzez;)
z;4i9VQS+Id&$$rI{X|L7E=!kY`3fQ!Y;{8D@K|ux&S$XFqG`YUYm;HKTFv?*me!Ld
zNe$*wiPKfb+mTcd0b3)|5LT%))1CI4!ReNw2(#}8AckFs<h~$rq6%I~Xw1D!NBb!C
z9I7>;UOA|ToAmweVcVNAQq(BDsOZoTv1h{_15j+9GLX=P$thM7MOwRvT2ZEHo{=i;
zd&KZ^dX+c(`@Zzs7Rpb%=aBcVv5<6orMFO@MRYgvT~k6)5++r9PE9g7%7-w4%<Hmq
zFI)Ac@G2j{p13uqWDh#l2X`0vu7V^|qKw8)<a9VB+4%}w@jB>|UB|Vr$fv5`TC*5m
zDkugOKaglK&8o3yAjr4J4L`U!mX$)sInu>CbvK5>+Di~*O<c@0NW?KcwleM5C}vef
zqAHvdIBbK}WssaqO0jVc&7T%xIdVPjSeLB}dyOW({l59^yHa*5O-KwcvS>b=UHoE+
z`_Z{cAk8}NjQ7R`eEfj-b~vW5d|v7a57MNmIUBtoIY+Nu3aiHGTx1f1&yVBmRR|aJ
zVk$XXRrF)T?2&H`rz0bbK{fNi;+N#+Apwwx{o^0t{d-a@M!Jk|W3MjK)WpE`uI}=q
z%0+K9IOa5H#;fJgNxxW%5hAOIagmQ;;c(+ciQ@*FYZ@w}+*Zs8lS3xH91{8yCRBQ;
zG<uq)|1$<KtOLfb?IarkQGC1S<Ybe}1(sQ?3KdymT=!%kK8FQYCE$mCtkFB@nP~67
zpo0);`9aeUrw6(mWG#vuv!z(4#_XpS{tG63|EHQ&AVlNiLO2d2is0V3MZJn8{=)UW
zHSzoo2@;Mc50fL2cKl%HwZo%=qcXqg5J<3khm2Y>2T#%GjMvpUfAXV(r$UYd-5UI{
zi+9Ny<fbIksCP~_Jpfv@!qDoguIPZHxkoxY`uwp9_$Q9`(KFS!X#ZSC;)L%e`+q6=
zyF~rpC1PNfmKUC_EPt}lTzstmJprB{^HsF~Z>p1(1l{tFzhX#Hy0OIu@ge_P*|P9I
zVAo$B`_b9;_0{0z{QT|hZKK64qq9z<*FT-dQ$@U4XMh)p-VZkS-s~4^Z#RndowplX
z2U|OD_X{c7@AfyJ7Px2jb~fIv>+w@-wXwB-u($Q<ot{_+O|ur6eH=F-iGF;_PlA{5
z3Af5xq+C~>NR!F28(I-WD}v`qwq4RixYDIcPGsC|va&x7T}kz6V^HXKDaI%e5C(>K
zf%2D@Lwnz2c1hJm|9z|G^}_PRsQhBPcWlMQhv(Jnk4)?sd4e37XM}m)l1WdB8q$*T
zpK`Sa6E(&KjTa_m>c!9(md2hzsk6n0*xA%5c}o*uI;U9THm&QCNpM4s1uDx8pUR{|
zFL4%t0Z4{!+=yYXRHed(Mn+e}H#I4^r4oLq^3>7YQ&T&6>Rwz<8?Ft8o<hUufh7~a
z57;OW(QF=9H~lM8blY8V%Zp~vxBTdFkDi2Hp4djK9_oyLs*K7{**=bJE1`<<+Ww|u
zhHC4w>1r3I;;#$>c*;_`SHS}v<19=}8Na<I2Z^X`TmZ!abI2$GzAbvX%S>!T2fDmV
zPbb|IWoqj5ElnVSgukajsQ&7Jj~(4ybCP-$1&|oAGWJN9M`T-69WJ)A(t9?~66e0{
z-8?C#8RQ9n^`Fe-Gyz!%CRF`W{yW-iXZR6jLcO{EF%`+=dCk1^hG$L$wk;#5w$EtC
zohK4NF7h#jT%74%1<&6~g1Ts#D;`%6rBA%H>L6~gS5VRnR1sAL6f8J^(b-4UhPxHg
z4QxO*r495W?YYa$*@8C!LqTS?E=yFYD1wZA>>T|aj2WG}RlU%1*bc>CGa<`bb<sgr
zQbE{AASNSg;WoSgYfof|a0;WkoX=K`6cerzQDK1r?nBFzkj5;@G-#6x%X9~sl2la|
z903<rip5>*+ae2`cBueNxeD2BvSoiwIK&83P>h5y*ZGj}xFi8Nq<tBQ0339-t+EIa
z@Q`AON255{3fVy07QSHov=tU#@<YI&+J2F}g<@J<xr0+-&5G4Gg3(2@4P8uw+++{a
z1vz=qVeKkzXCDd$*Q~lo_a}%6`;_v}g|lM=LkqX=F*X$f$e;odVSvV{he6@14?X$T
z#)*^NQJZ&Zq)0T28MA~v&Z{((A*N^{Rj3L*v01uh4T;9$fN%ru*$@~ngyIJt?xUWk
z2z5pgwmGM!(3ws-bYGR%0{Sfm<GQW71D;)MD2$>NR`-0AABWzIX#w6q{-^~*;0vTL
zYz}*8-G@^H^9B`+LSKW9N=~ctnc!ml4*j7+;y_hOHNcT!)bQjIgB5^tOB+TH>KnH(
z12O4{`r6|J)R9T?w%_TUl)JvJ<13u;wL4ygM@F(?@)|DzFc6%+smw#@Q{ixi!=c;<
z(KYsut%{S-W)C)uo4l$s(hT4W037M6M0Xx#1a4PgIuK`|Ki?ht5_Z`(m^cRwrRjx&
zASL11$r5*&D=5kZ$+zQ9i(F7)L?7O4irgYTC%NoNTpoZL8f6r_9C9ozJMD`nX-!W;
zUlQyTFlNCp7y8KZWAsT&aQw7j7l{2bJkpH~V8p;#>255Gm)CJtj!Fqof|FI#3dCXz
z0n(~)>Y<}3bX6S#iZAB`N9=;jry%s;n%=S_ODB>9sBl>^oqb~AM8;9=k|X5U&Pcz`
z7$`_5pGzxA)?&z`2y`JylyK7Ggaz98AX^D;$hgNhb`uA1GNPsY)J!9S*oEM*!p=|e
zxT>x-;v-9;s_yX(T06u`C5s=#`jBer%*q3Wey}rlPq|s{Tv>Z$N1&Ic8Nv}Ut0!;a
zA}u0TOImF_NsK!^9+I!qvb8Yb%6KFW^U47z{>Y`d%AojwL!sU1N)UPE<Ajbt*n!n`
zRDl;zr-v-7RE~a+CnG;+L^!{{l=!X<Sx@r4_sbo<{Tn~}1KX=rW->C%xiUBL&&nmR
z72<!)*`zp=3lI)jB@!MbDG>%E8|5fCZ{Koos!G@8obCL8vr9kcWFbD~&wlTYV#arK
z<Y?kom#V%$_KwBK0~pG!Ja_$1JSl2hd;yKY`3b)qK08Wwesy!iw?Gj@y&{}TB{ahm
z=Ilo;&<_3UILq|5T+B~rf(gqbBjzGaIL=L&@eNwaHi(K6u#b4~{TuJjG@D9EKoOXX
zcTS8Z(}xN{GZX=|wPw4AxX#8|U3p1)5nW7=CX1}1bvXp6;I2AD`ZpW}Tdi+Q;sY)g
zTkCLK$BxjlB+^Uqilb5w?p*3-?-DdH8bB|MQ#(Fyby4lTZH9W_xOI{-2Ur@%rts|&
zM<6xn`dy>W6XmgC^?U-K>PD3=eaZPcJ(xiawp`zKBEtS+&T~Jp_^c!|{~*vniZM0>
zWcn`TRVNpQxYbs~^V<@gL!F@?#prggDF7Z<PL-7IsVf#%4w6tBF*ykYsG8H|ft6(I
zes3_CGk}RH2Va~22K`%PtG^xGa4l3CKi4ts5CCHFBnT7X(;==niNWc33lry1H!ZyE
zhj%V~DXcZ%8g*Z6KfDkuV>2_BRmH?8GDT$+u!@hxaPg7@n&4ymZs2#bkQgZPF79L(
zynuQT*oe<e$d{l?Z_*Q0&5icUjbabEytE88Z0HN(coSzE=0fm6Ltv8%!=N@FU^&8x
zbXFpwa%|YbrLo;06dNAVUjL=2OX#H{R63E3<78sDK@~E9l&E#lQFTfPvCMhT&IB{r
z%XrRCw`P2$3felAnxuVkeS_n$%MytckB|(@3)2*wM;59=@K>P%XSrja3wUYFqijq!
z+-Oi=H6&~LyLiGek<`kB-D6|Lon3p3XrkfGKM_${$RgEUWN!<X9iTe$aW*m`bXIQ_
z(5iK&!fUyj(eUE3bw^<91aoYMfyz3WPDgge_$)G8AD$|?$+j}<1bsJZDRFIXf~c8~
z9tr%BrE2x1<OcS@Jp+7L4g?-<6>y$fHDkc(6lVosvq`Aq;e}R#)}ARS@fjx&gl1TG
z3lVPTfB^L;q{04)oZrA(=T0m#i^!dglK=%<G&$BOVR&etytXoZfJh8>W?eZKWGslY
z)ixcoL^>YVfK^P1-R^K1CH*6!WAlw^`$B99=-){q9t+p3n|cTvyoMJijf3ftjatQf
zIeD^f0<$Wq6~TxD?WOSseO81ymar}%lrJmWf%?4->(<K((#JDW0IC|Gl&C2286BDV
zEt?@R?{ki!97)4y)P!<bFNA?hGV!x{1p9!J=r3QB2@q(2zFBNrzda{3yGuBKDf=7>
zvlW_cQBdT7<!RT&zed5F?Y9XJu1ZVtw8Iy(NolH<gi@VOpe^HAuHS6CC|n*JL=T9u
zq9)yH%M+nzQ;FmnAT*3Db(A0;1N0TtlXaxSTZ*$H6ezMw!Ck0b+grgHr3=wR<05H0
zvu=!S7&Xw3+2jF3HuVH+ax^e^#rOhX-9M_36;pA*M1?cLCh~s~w@%gyh!Wp)u~mYC
zR$(BloQK&~sFQ$}>MTwfaA0;u4pkpf-mKHF!rHa>qb?&!Q;ERfSvnL(XCr$=){R)K
zL2zU<wL#H3mq^L_)6k)dC~L_2V~54a4B`fs+BbWERn+Q>!_PuTYF3{1!<s;4Lsm@L
z3HSB1$aRP&9!sQZcz4MuIxr!Dus*=qmHKPCMLCJM0Ifg&9)2eRWI0BgNemuux(W+`
zH3&@t9{YsZ5Eu18yeJf+4Ayx8$0*LCG{h`W$gnRWjCVozCCIpvX#c$JUF)Ody%Auo
z%&t^s9UrHreCrr7=Oon*Fwb|el+&G)LAE7fAeS!OK#FJ@QM7f6#%+jy7bwIuGF&#t
zXb6!VYC5HLf($DyCG<OWw98>1d;zPl!YF{Sod@z|1_Xf1bBv3WP2|ozLIQknsJZwO
z79mS5maZhx$m&JXJlXUm;P8`WgWmGH7S=e5j&Tbkrj%-GsIBXF4>-^VAdAe#Qu?uC
zss12Rpk@P<F)|&4oPo83S-GV)j$}frK91BB`4GEnif=e+vm?NjfM@uhF`P;Fl+*U4
z_*yslF0bzr5u~Yto~FQ>T|_EY$p&l}%}&d7gB9#)xnY6!W$z0nZCo4eLMENCnzV-w
zO8Gf33L}K47myRGD=Z0?|72~y*xG+myjt7e+K<5fdh6h;op%St*K2!RZamoH3L$>@
z%q_q38W;Sx#b37GZXoV}Q7l+FHALx&p(%l2R~6lp*69k1D5$*8D?X)yT?Q&sh;llL
zJ9JVUY#nTGKIKSyJGb@r^&WBen{PJX9y~3$xVKJb+Jm)MTiaU)-x><N-a2@@$!#`;
z53GegcGvb0Hs5Wp?G?N4_I7vnH~qAg?mxim0as%FN-@|quLI!Ir5b64RdiwJC_#n?
z25hhepMYIz!WZ1ojRJi{n}km-47WtqvfMDEes`esI^3$odmHV5en<pAt6$^I9IcUW
zl|<;c_q+j$#4FOmz3p!62<x(tZ|f2o4VCJJE8Yi>(C0a!**rAuxb@MY4f5e-qUK84
zJPahKl&7X<c!!)eA_h#**l<{NGU3EtZk*{)!1rkv6lBxqg0%|TImMpJ9+CVOaY$J1
zG1XQyRy}<<_pBTrv^|Pa&N<M4CAdIfhex}0!M!6GPPsmP9YX?S6;_}C{E1L>oNaMs
zG>4#22HceZ7=le#4!O{&;IGbP5oO-c2394*5S`+2^UZ7QOsCvUgC_J5c#OCf>%cDI
zo0;*|(2ZeNwn;TU@i;5VDd#{}ouBr47;-Vig>G);<bzjUT>%yCuA&2(_z5lwWg6vE
z#N!x7Yn!eW3Y|EaLnp5G%L||&fgBz_FeT~W7Nk^^fNuMrSW;pd$(Y`1bCU)kKR|-8
z59rb`T954OurQ~97mZ?#;BN{-LWz;{YTj$5WS0@e*Jsi;tK=G%kvJ<KkH}$Sbr@ov
zns^}r(n<;%YMG@o0#uN3i#T%I302pDKCI4W$h`?8>`>YWt{4sqVI!(O5x$8YnS+B>
zC>)lLVcTtd658AIG_h!*frxSd`;pvg$2<Z?)Lp`n-6*~y4hBs~Lxt%GU<OZegvO4`
z^;tv<DoottOJ`70OM+iXOXV;zVN{H?WTQ4;lXTR;o5y~fM0{--8o1bMR$Y)pGMo{-
z5I?}?B*6xEAnJ;Lf#2Fs!Sa{`E)d{_iJ!uKA#8$Tm<Hsmd!or(7A(i`Wb7V14n|bi
zfQ--CJf(cPuu(1uv5Qd>5^6mAEN-u`G1D{BTpWQZl2LH3)`h|S7U8KO{xf8|X#5>9
z>SKl%T3?gnKNl8OR+qE*&sE|-msXn#pA-upeI_*>A}Z?Y#V3C!gYcxa-}k<z>wkww
z{{WAlNaTL<vIsdio`{(}`Jr>i>ChhN`7MW{=*doq3qk}vHp?5jY;6^J)piYHqk1>8
zw;2YEJ7P4yp`d{2b8<)oH6i9x`Co&Wbj9knHoRZtaEi7fjoi_#rpcrftfWCsyVur;
zM>qX*J&CcVyg9o$QqrM$ls25{^C&u8IO@%{Z^`Vo(9oxJ_D@@6h|y373yl{(uAA{R
z%zGacTj3Rl;;SA##ZfU__FDsW^qqBNso8a!8oa`g-X8|JYxvNGy_tP7_0QjetRIYe
zf&P`F^3CS26aM|lWcqKeJZp}`f3GYpKGOe#H}MxV{11fGoIa+ee)s+jCU2w($t&MY
zN5bk}oHmI3>zua-jc#vVAzPu%{AF)`aiRI5G5C?hEWdldN3;VBqg|*J!5ozy#&&Yl
zYM*w<pzrN}zqnvu=#x2gtrIwov5Yx7i=Ag;9jAPoGgg9!cW}=*<)@4kKc;X0c#fb9
zd_ZxW<Ig^8Ht*?h{r8Kn`2V?PbMDAQF<D|Ry@&U|doP|zz~5kOFV3sD)@tp>@GF4&
zpCmfsqVg9D3$50|!c5uUeatR)O~K0Fy+@&qdrd+2@jL6AvgTN^wXr$(`=!m_FKsj%
ztBvNu9HQdcHS4@f%lxu?{ngD;G)I+_8$uZGHW;nu{MrHgusC<bJs<0={IuV?6`zKY
zqY3p9ZzbD~=jk9L(r-Vz#4<BAReugyeeDlAjpi}OksX`lO|qZ`uuNGM<4791-#~%d
z)-9DXY?i^4HJESTUg*%7^F9<{y4&w5uGlT1Q*-%0Q&Y3E+%+T=^-AWvm$S1|2NnT=
zo;W*;;*Lx%S1XAFVw>67=nb(*y<*nSyjcb^i(Nd5=^-Sw#oRG1Vx6%rO~Iy5o*F@$
zuWMGHuU+2VqUOk?sNBlO2)zn7xWQK`ilG_;)}<4Bf5LrRL}uc$%@z`-Cn;LlJ<x(E
zUeqUaiN$D`JM6G3&EpD=%#E|N2Rj=(11)&Ac#V1rtbgdfEFygGMRPUhQ|fb=YHEkE
zoN^!qS`Nx*zi(Or*+BSB%l-3C=W5;yUH1@OF1r8YQMA0<BlXBJVn^}4pN%9K_`c@x
z9^8V?>R$PUQN4M0Wl;nn1ese2W_8EBgb38`DQFW=;O14)4uv>_x3jZf%MUA{Z^;{*
zG&;#u|0x*lTMA+io5>9DDPJ08yl>${#r17Zwq!$I5WrPw<#}f#&>2G>I<`x8*icD>
zc9LSG8)CG$wWuA!E@)8tM(7iAKpRil$ZfXr>&TyciAIjVN47e&-?#@Dlp2iuBLU~y
zB}iH#k%o!Wp0NOJoO4(ZK*y{MNpaV~Afw}^ufXJhPbs><YNAQeyVR0Uv}BI71qdr|
zu6uKYOva)(W+!Lraa5bROxbb{V0Q?XZh?!bXpyM#H^N^s{KEyV@xFSbDm^kO-d8(E
zH_uKa^H|?`h!jFEgDBh-BH;Fvc^#QzK5gJ?#(QrI+*wkJRu6M#W_J{hW^H^7r&0J4
zje=KHWvc~Vg^`l_#;EH{43>Q$=Q!qMCLE}EYadQ+*PL>Z<+>a^Pfyyuw65|>f+p!S
zFZ(a>6hSl~jSb~VIa9QWngp}BRD>{12V{A10Gf#WV1(Zm)8P~c?XbCc{&<gs7th_o
z+X%Rg!YeFS)VAm6KMYbQKAOALot~ZLwwWJ+1!thL66pY3jqklRee<~ZQi~}5wzccA
z3@q{*==z~lpbL?iOf~>LWD+0|GqfY$mOkLrz!H7Y@o+Z)U<QQ?O#4J0;87wXbmt0s
zw#WIT1h`Z3AQp%FmgM8Gm@f{uLSDvg*TU=>l26gl{KCUVqj5M@5XiXCJUEOrvo<G)
z&<a!<UWxO;sMk#??CD$11#;xvV10oQz-l;%mKRh%nUU=$Yt<|bsbwO;49R@;F7d?B
zmVv_+kPvYwWKl!FKQ;B1902?R31}X)wy+~K8-WZDjIv3=6(-^;srZljj8Kg8_26ky
zi)=lR=pf+<TRdC5FOThzHuJmbMq{4;*U>W{-p<LXZAmp}?4#Fy@|n3#E-2!hjg6US
z6R|MsAvX0755SQl9iV`2&9zU`a<uyG$~U3a9IfU;t2t}sorFoDw7&tgEjg1ef2fWc
zoA?K>;5TRHkE;w*KDK6SB;<$O&A<p?zB+ibT?%f;?aKzj8wd9N(pt$n=FhG#&dG2{
zR=I}$WSqi)zVZ4OckQ@)7&iQn*&j0d##Q@-v-X{3Q#BcEYASL`OR*Y}uZsHOaqSLK
z?G}cjJ420P*C{ztK*TH~^LX`0Y@Ea2EhXC6fb;LKk~H`*w3;y|2AMxOt*+)c+DyC<
znxwNBK5rN6?9jHgGz!Eyr-KXFj3OkAHk~ANRv`94mxv2?8SJHy7g6+UiLN>=3M38{
zAcKqIPJtsG@I60}So{<-!Z!CAy$1u2xfV=#G3pG02&_yY7LWwRJ^I}a#vtfd;z0NG
z#yK`i(J|mu<AiCUA;>W<HTxS0!?a>?{9ifW1H7=ca~5GsybYIX<#IHBLgXJg;-Ue{
z+&<hrR89=Z5SE3&{NG%csvPa&W2_$L2Vc|F;ZKYWST3%7hWbfq2p!JuSaA{abAcZp
z3JIe?gO+l1XrwDR9Kd7y@TcmoB?o~v_E<Nk1ygVZ=o8-zz!>GS4L>MIw4`#15~f;`
z$S)IX)3>JW_!faNw|!1o8vErzzw^AhJV(J4(*2z3GP7i?Q(Ayk;bP$DNRaSw58TG7
zjbvuCzb5pXsnIL5VFZ<35k6|o#*pL>HHU}!sZGQb+a_+5%?=D4*}t-PL8Q*Nk40bi
zgq1W6J7~j2dm=LGS(oG0cY-ZZOuFH>w0VPT$P|p&Rvpcy^oI;a7;=p8f{HDUEr^*o
z2$Vat=v<u<f*~0zy>Rk55Dow$tcCb!gt0S)B0&^QVo^G}=@CS5AeE=MmG4A^YI&JL
z{<k53p_+`Fzo~z?H8^l#T7=i)_)1<ipPK`q@J2$H1jH!5+4z#zgETKCiry-p>(U0w
zIk27j{`>C(`z!N0|Ir&a&i*uo)InNakPL(~#$Y17Rii^Kb3d@tzM-r&(Je)V4G{Af
zIyzD``ZOhq56N@=o_i;QYyCy8OLfjY0|_*(GSRrH2Rio+23N5~-lcyueLPS{-VZC4
z6E=*gS*YLGM6^F(ejpxyL9dpa1lsUsB6ubK3JZocL&|ZNtGo{Lklo6St3zQ1%}Zve
zq$w;begrniFD(Yu(qE7~8z)y_@JHesYjG=i5c>mq8{v_<3hS|_qDcfpX-m^9m3JX)
z=~jEXx!~iw%4hu2F5@9CibaU)k)p*60++|!$01*HgG^#bgHcSFiDP=!&^MffA*oDb
zQbl$j762L#ba&z4`sU<>Jn1A}Fa#mzxzr$bKxE<vOd}2_ceGL>55Y2QiI7Snw{g6_
zp<t|Z%|#}XJP_d#dm@w(QG6dH{|Z>6Q%&p3kPxtc7&%c1LH}ScB$$gIwa~t>=G1*x
z#S_^~j9oB)5X$f)&V_de;v~HK^)Q2-^(AVL)3S_{Sspk0i-gi9ALs{YLQ0VG_sf@W
zwzjvo_BRzev`-K3%XdMW)@OE9lH>Ec94pUTcgRWnTP+@f1@r;~76t^MRRCshbC5*@
z;CNTjFWC(g4}RPs0RpFRfcH~a4(G_ASEzlMAHWZrI!sDVb74pLh&=E368{&mY#n$3
z2nvy<Y5HKCvdA`W1+a^G*Q_^=6q@W30c5q0&+n!~QfhbsGnzmYJ`Scx4x7xn5_`7S
z*($U=*!I-aMh7lMgv;?=(tPITc$^~JUb;omw0xQHTF_3iX6Y(x5{2E&z?Pt`8Id6}
zhrWY?DB(*%6tlBIZ=ao|KmFpo56M?}WRhXbiJ}84SdX95G{%##@yJrhA<9Z{1++0+
z={i>b?svaKbj8pxI~xwA#+e>Svv^HV^UEYDNxz?`t{2xBnQ-8*)-FU(yey{uZTe=W
z`10*L5@je}0QKL^E4y~}5H-uv+n}~L>BJ^LPWAtHgAcBeFRv$$zwU`86f6P0PJ%=-
z13GBcdGF)*9pFaqXz0IU_c9NE{O+fw0uDq{nyO}5QVJzMCEssz59Tc)i5IB$(Y$r9
zx%-HECeZ*_b}%#Za~#Xg+sMge+F%3v)5R1B^cVOPhd-U{|6OP{mzqQIA1e!skN)3b
zF#amP75edoBf(Tq%f28KV#-s!d~tBr!;VQPI9{uM@kiWC{QNV<WqvkLO8zvJ{6_r;
zG?w*ShKVogy>n5PYAJ$M=cdWv!Lv3LB@+E|jVUw`g2h@_XJ72Q@fOYAg(MRNbc{rE
zKlb<}c$`U^(-}D}At0)YcFb^b-7L{e*F6ckbsX{$aU&KUncNeyB5>NLizCSnBZ1Ux
z3{i`~2ukA~)4>4ZPMBzZjJ823@W4kl*w~QKi1HrXNx2p@OlMM_6SdILCRHlilo=V$
zJ3ra{qQc<ouwH4AmMo2wZ-Zbn#nL*Y>%aSgbD#|GnIg32i-$6$DJ4>;fU2242Vczo
z9A5ORAoydaj}Ul-z#{}6A@B%+M+iJZ;1L3k5O{>ZBLp5H@CboN2s}dI5dx18c!a<s
z1Rf#q2!TfkJVM|R0*?@Qguo*N9wG1ufky~DLf{bsj}Ul-z#{}6A@B%+M+iJZ;1L3k
K5cvNH0{<8Ou{=xw

literal 0
HcmV?d00001

diff --git a/uwb/anchor/anchor.ino b/uwb/anchor/anchor.ino
new file mode 100644
index 0000000..43d17e1
--- /dev/null
+++ b/uwb/anchor/anchor.ino
@@ -0,0 +1,130 @@
+#include "DW1000.h"
+#include "DW1000Ranging.h"
+#include <SPI.h>
+
+#include <Adafruit_GFX.h>
+#include <Adafruit_SSD1306.h>
+#include <Wire.h>
+
+#define SPI_SCK 18
+#define SPI_MISO 19
+#define SPI_MOSI 23
+
+#define UWB_RST 27 // Reset pin
+#define UWB_IRQ 34 // IRQ pin
+#define UWB_SS 21  // SPI select pin for UWB module
+
+#define I2C_SDA 4 // I2C SDA pin
+#define I2C_SCL 5 // I2C SCL pin
+
+/*
+  Short address and MAC address for the anchors:
+    "50:00:22:EA:82:60:3B:9A", // 80
+    "51:00:22:EA:82:60:3B:9A", // 81
+    "52:00:22:EA:82:60:3B:9A"  // 82
+*/
+uint16_t shortAddress = 80; // 0x50
+char macAddress[] = "50:00:22:EA:82:60:3B:9A";
+
+// Calibrated Antenna Delay setting for this anchor
+uint16_t antennaDelay = 16641;
+
+// Previously determined calibration results for antenna delay
+// #1 (80) 16641
+// #2 (81) 16446
+// #3 (82) 16561
+
+// Calibration distance
+float dist_m = 3.078; // meters
+
+Adafruit_SSD1306 display(128, 64, &Wire, -1);
+
+void setup() {
+  Serial.begin(115200);
+
+  // Initialize I2C for the display
+  Wire.begin(I2C_SDA, I2C_SCL);
+  delay(1000);
+
+  Serial.println("Anchor config and start");
+  Serial.print("Antenna delay ");
+  Serial.println(antennaDelay);
+  Serial.print("Calibration distance ");
+  Serial.println(dist_m);
+
+  // Initialize the SSD1306 display
+  // Address 0x3C for 128x32
+  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
+    Serial.println(F("SSD1306 allocation failed"));
+    for (;;)
+      ; // Don't proceed, loop forever
+  }
+  display.clearDisplay();
+  initDisplay();
+
+  // Initialize SPI communication with UWB module
+  SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
+
+  // Reset, CS, IRQ pin
+  DW1000Ranging.initCommunication(UWB_RST, UWB_SS, UWB_IRQ);
+
+  // Set device configuration
+  DW1000.setDeviceAddress(shortAddress);
+  DW1000.setNetworkId(10);
+
+  // set antenna delay for anchors only. Tag is default (16384)
+  DW1000.setAntennaDelay(antennaDelay);
+
+  // Same mode as the tag
+  DW1000.enableMode(DW1000.MODE_LONGDATA_RANGE_LOWPOWER);
+  DW1000.commitConfiguration();
+
+  // Attach callbacks
+  DW1000Ranging.attachNewRange(newRange);
+  DW1000Ranging.attachBlinkDevice(newBlink);
+  DW1000Ranging.attachInactiveDevice(inactiveDevice);
+
+  // Start as anchor with long data range accuracy and static short address
+  DW1000Ranging.startAsAnchor(macAddress, DW1000.MODE_LONGDATA_RANGE_LOWPOWER,
+                              false);
+}
+
+void loop() { DW1000Ranging.loop(); }
+
+void newRange() {
+  Serial.print("from: ");
+  Serial.print(DW1000Ranging.getDistantDevice()->getShortAddress(), HEX);
+  Serial.print("\t Range: ");
+  Serial.print(DW1000Ranging.getDistantDevice()->getRange());
+  Serial.print(" m");
+  Serial.print("\t RX power: ");
+  Serial.print(DW1000Ranging.getDistantDevice()->getRXPower());
+  Serial.println(" dBm");
+}
+
+void newBlink(DW1000Device *device) {
+  Serial.print("blink; 1 device added ! -> ");
+  Serial.print(" short:");
+  Serial.println(device->getShortAddress(), HEX);
+}
+
+void inactiveDevice(DW1000Device *device) {
+  Serial.print("delete inactive device: ");
+  Serial.println(device->getShortAddress(), HEX);
+}
+
+void initDisplay() {
+  display.clearDisplay();
+
+  display.setTextSize(1);
+  display.setTextColor(SSD1306_WHITE);
+  display.setCursor(0, 0);
+  display.println(F("UWB Anchor"));
+
+  display.setCursor(0, 20);
+  display.print(F("Device Address: "));
+  display.print(shortAddress, HEX);
+  display.setCursor(0, 40);
+  display.print(macAddress);
+  display.display();
+}
\ No newline at end of file
diff --git a/uwb/setup/anchor/anchor.ino b/uwb/setup/anchor/anchor.ino
new file mode 100644
index 0000000..2d185d0
--- /dev/null
+++ b/uwb/setup/anchor/anchor.ino
@@ -0,0 +1,135 @@
+#include <SPI.h>
+#include "DW1000Ranging.h"
+#include "DW1000.h"
+
+#include <Adafruit_GFX.h>
+#include <Adafruit_SSD1306.h>
+#include <Wire.h>
+
+#define SPI_SCK 18
+#define SPI_MISO 19
+#define SPI_MOSI 23
+
+#define I2C_SDA 4 // I2C SDA pin
+#define I2C_SCL 5 // I2C SCL pin
+
+// Connection pins
+const uint8_t PIN_RST = 27; // Reset pin
+const uint8_t PIN_IRQ = 34; // IRQ pin
+const uint8_t PIN_SS = 21;  // SPI select pin
+
+char anchorAddress[] = "80:00:22:EA:82:60:3B:9A";
+float targetDistance = 3.078; // Measured distance to anchor in meters
+
+uint16_t antennaDelay = 16600; // Starting value
+uint16_t delayDelta = 100;     // Initial binary search step size
+
+Adafruit_SSD1306 display(128, 64, &Wire, -1);
+
+void setup() {
+  Serial.begin(115200);
+  Wire.begin(I2C_SDA, I2C_SCL);
+  delay(1000);
+
+  // Initialize the SSD1306 display
+  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
+    Serial.println(F("SSD1306 allocation failed"));
+    for (;;)
+      ; // Don't proceed, loop forever
+  }
+  display.clearDisplay();
+
+  // Initialize the DW1000 module
+  SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
+  DW1000Ranging.initCommunication(PIN_RST, PIN_SS, PIN_IRQ);
+
+  Serial.print("Starting Antenna Delay: ");
+  Serial.println(antennaDelay);
+  Serial.print("Measured Distance: ");
+  Serial.println(targetDistance);
+
+  // Update display with initial values
+  updateDisplay("Starting Antenna Delay:", antennaDelay);
+
+  DW1000.setAntennaDelay(antennaDelay);
+
+  DW1000Ranging.attachNewRange(newRange);
+  DW1000Ranging.attachNewDevice(newDevice);
+  DW1000Ranging.attachInactiveDevice(inactiveDevice);
+
+  // Start as anchor
+  DW1000Ranging.startAsAnchor(anchorAddress, DW1000.MODE_LONGDATA_RANGE_LOWPOWER, false);
+}
+
+void loop() {
+  DW1000Ranging.loop();
+}
+
+void newRange() {
+  static float lastDelta = 0.0;
+
+  float distance = DW1000Ranging.getDistantDevice()->getRange();
+  Serial.print("Distance: ");
+  Serial.println(distance);
+
+  // Update display with current distance
+  updateDisplay("Distance:", distance);
+
+  if (delayDelta < 3) {
+    Serial.print("Final Antenna Delay: ");
+    Serial.println(antennaDelay);
+
+    updateDisplay("Final Antenna Delay:", antennaDelay);
+
+    while (1)
+      ; // Calibration complete
+  }
+
+  float currentDelta = distance - targetDistance;
+
+  if (currentDelta * lastDelta < 0.0)
+    delayDelta /= 2; // Reduce step size if sign changes
+
+  lastDelta = currentDelta;
+
+  if (currentDelta > 0.0)
+    antennaDelay += delayDelta;
+  else
+    antennaDelay -= delayDelta;
+
+  Serial.print("Updated Antenna Delay: ");
+  Serial.println(antennaDelay);
+
+  // Update display with updated antenna delay
+  updateDisplay("Updated Antenna Delay:", antennaDelay);
+
+  DW1000.setAntennaDelay(antennaDelay);
+}
+
+void newDevice(DW1000Device *device) {
+  Serial.print("Device added: ");
+  Serial.println(device->getShortAddress(), HEX);
+
+  // Update display with new device info
+  updateDisplay("Device added:", device->getShortAddress());
+}
+
+void inactiveDevice(DW1000Device *device) {
+  Serial.print("Inactive device removed: ");
+  Serial.println(device->getShortAddress(), HEX);
+
+  // Update display with inactive device info
+  updateDisplay("Device removed:", device->getShortAddress());
+}
+
+void updateDisplay(const char *label, float value) {
+  display.clearDisplay();
+  display.setTextSize(1);
+  display.setTextColor(SSD1306_WHITE);
+  display.setCursor(0, 0);
+  display.println("Anchor Calibration");
+  display.setCursor(0, 20);
+  display.print(label);
+  display.println(value);
+  display.display();
+}
diff --git a/uwb/setup/tag/tag.ino b/uwb/setup/tag/tag.ino
new file mode 100644
index 0000000..fefd02a
--- /dev/null
+++ b/uwb/setup/tag/tag.ino
@@ -0,0 +1,63 @@
+// currently tag is module #1
+// The purpose of this code is to set the tag address and antenna delay to default.
+// this tag will be used for calibrating the anchors.
+
+#include <SPI.h>
+#include "DW1000Ranging.h"
+#include "DW1000.h"
+
+#define SPI_SCK 18
+#define SPI_MISO 19
+#define SPI_MOSI 23
+#define DW_CS 4
+
+// connection pins
+const uint8_t PIN_RST = 27; // reset pin
+const uint8_t PIN_IRQ = 34; // irq pin
+const uint8_t PIN_SS = 21;   // spi select pin
+
+// TAG antenna delay defaults to 16384
+// leftmost two bytes below will become the "short address"
+char tag_addr[] = "7D:00:22:EA:82:60:3B:9C";
+
+void setup()
+{
+  Serial.begin(115200);
+  delay(1000);
+
+  //init the configuration
+  SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
+  DW1000Ranging.initCommunication(PIN_RST, PIN_SS, PIN_IRQ); //Reset, CS, IRQ pin
+
+  DW1000Ranging.attachNewRange(newRange);
+  DW1000Ranging.attachNewDevice(newDevice);
+  DW1000Ranging.attachInactiveDevice(inactiveDevice);
+
+// start as tag, do not assign random short address
+
+  DW1000Ranging.startAsTag(tag_addr, DW1000.MODE_LONGDATA_RANGE_LOWPOWER, false);
+}
+
+void loop()
+{
+  DW1000Ranging.loop();
+}
+
+void newRange()
+{
+  Serial.print(DW1000Ranging.getDistantDevice()->getShortAddress(), HEX);
+  Serial.print(",");
+  Serial.println(DW1000Ranging.getDistantDevice()->getRange());
+}
+
+void newDevice(DW1000Device *device)
+{
+  Serial.print("Device added: ");
+  Serial.println(device->getShortAddress(), HEX);
+}
+
+void inactiveDevice(DW1000Device *device)
+{
+  Serial.print("delete inactive device: ");
+  Serial.println(device->getShortAddress(), HEX);
+}
diff --git a/uwb/tag/link.cpp b/uwb/tag/link.cpp
new file mode 100644
index 0000000..396f208
--- /dev/null
+++ b/uwb/tag/link.cpp
@@ -0,0 +1,172 @@
+#include "link.h"
+
+// #define SERIAL_DEBUG
+
+struct MyLink *init_link() {
+#ifdef SERIAL_DEBUG
+  Serial.println("init_link");
+#endif
+  struct MyLink *p = (struct MyLink *)malloc(sizeof(struct MyLink));
+  p->next = NULL;
+  p->anchor_addr = 0;
+  p->range[0] = 0.0;
+  p->range[1] = 0.0;
+  p->range[2] = 0.0;
+
+  return p;
+}
+
+void add_link(struct MyLink *p, uint16_t addr) {
+#ifdef SERIAL_DEBUG
+  Serial.println("add_link");
+#endif
+  struct MyLink *temp = p;
+  // Find struct MyLink end
+  while (temp->next != NULL) {
+    temp = temp->next;
+  }
+
+  Serial.println("add_link:find struct MyLink end");
+  // Create a anchor
+  struct MyLink *a = (struct MyLink *)malloc(sizeof(struct MyLink));
+  a->anchor_addr = addr;
+  a->range[0] = 0.0;
+  a->range[1] = 0.0;
+  a->range[2] = 0.0;
+  a->dbm = 0.0;
+  a->next = NULL;
+
+  // Add anchor to end of struct MyLink
+  temp->next = a;
+
+  return;
+}
+
+struct MyLink *find_link(struct MyLink *p, uint16_t addr) {
+#ifdef SERIAL_DEBUG
+  Serial.println("find_link");
+#endif
+  if (addr == 0) {
+    Serial.println("find_link:Input addr is 0");
+    return NULL;
+  }
+
+  if (p->next == NULL) {
+    Serial.println("find_link:Link is empty");
+    return NULL;
+  }
+
+  struct MyLink *temp = p;
+  // Find target struct MyLink or struct MyLink end
+  while (temp->next != NULL) {
+    temp = temp->next;
+    if (temp->anchor_addr == addr) {
+      // Serial.println("find_link:Find addr");
+      return temp;
+    }
+  }
+
+  Serial.println("find_link:Can't find addr");
+  return NULL;
+}
+
+void fresh_link(struct MyLink *p, uint16_t addr, float range, float dbm) {
+#ifdef SERIAL_DEBUG
+  Serial.println("fresh_link");
+#endif
+  struct MyLink *temp = find_link(p, addr);
+  if (temp != NULL) {
+    // Shift previous ranges
+    temp->range[2] = temp->range[1];
+    temp->range[1] = temp->range[0];
+    temp->range[0] = range;
+
+    // Compute average only if previous ranges are valid
+    int validRanges = 0;
+    float sumRanges = 0.0;
+
+    if (temp->range[0] > 0) {
+      sumRanges += temp->range[0];
+      validRanges++;
+    }
+    if (temp->range[1] > 0) {
+      sumRanges += temp->range[1];
+      validRanges++;
+    }
+    if (temp->range[2] > 0) {
+      sumRanges += temp->range[2];
+      validRanges++;
+    }
+
+    if (validRanges > 0) {
+      temp->averageRange = sumRanges / validRanges;
+    } else {
+      temp->averageRange = range;
+    }
+
+    temp->dbm = dbm;
+    return;
+  } else {
+    Serial.println("fresh_link:Fresh fail");
+    return;
+  }
+}
+
+void print_link(struct MyLink *p) {
+#ifdef SERIAL_DEBUG
+  Serial.println("print_link");
+#endif
+  struct MyLink *temp = p;
+
+  while (temp->next != NULL) {
+    // Serial.println("Dev %d:%d m", temp->next->anchor_addr,
+    // temp->next->range);
+    Serial.println(temp->next->anchor_addr, HEX);
+    Serial.println(temp->next->range[0]);
+    Serial.println(temp->next->dbm);
+    temp = temp->next;
+  }
+
+  return;
+}
+
+void delete_link(struct MyLink *p, uint16_t addr) {
+#ifdef SERIAL_DEBUG
+  Serial.println("delete_link");
+#endif
+  if (addr == 0)
+    return;
+
+  struct MyLink *temp = p;
+  while (temp->next != NULL) {
+    if (temp->next->anchor_addr == addr) {
+      struct MyLink *del = temp->next;
+      temp->next = del->next;
+      free(del);
+      return;
+    }
+    temp = temp->next;
+  }
+  return;
+}
+
+void make_link_json(struct MyLink *p, String *s) {
+#ifdef SERIAL_DEBUG
+  Serial.println("make_link_json");
+#endif
+  *s = "{\"links\":[";
+  struct MyLink *temp = p;
+
+  while (temp->next != NULL) {
+    temp = temp->next;
+    char link_json[50];
+    sprintf(link_json, "{\"A\":\"%X\",\"R\":\"%.1f\"}", temp->anchor_addr,
+            temp->averageRange);
+    *s += link_json;
+    if (temp->next != NULL) {
+      *s += ",";
+    }
+  }
+  *s += "]}";
+  Serial.println(*s);
+}
\ No newline at end of file
diff --git a/uwb/tag/link.h b/uwb/tag/link.h
new file mode 100644
index 0000000..fc6d434
--- /dev/null
+++ b/uwb/tag/link.h
@@ -0,0 +1,18 @@
+#include <Arduino.h>
+
+struct MyLink
+{
+    uint16_t anchor_addr;
+    float range[3];
+    float averageRange;
+    float dbm;
+    struct MyLink *next;
+};
+
+struct MyLink *init_link();
+void add_link(struct MyLink *p, uint16_t addr);
+struct MyLink *find_link(struct MyLink *p, uint16_t addr);
+void fresh_link(struct MyLink *p, uint16_t addr, float range, float dbm);
+void print_link(struct MyLink *p);
+void delete_link(struct MyLink *p, uint16_t addr);
+void make_link_json(struct MyLink *p,String *s);
\ No newline at end of file
diff --git a/uwb/tag/tag.ino b/uwb/tag/tag.ino
new file mode 100644
index 0000000..fcbe700
--- /dev/null
+++ b/uwb/tag/tag.ino
@@ -0,0 +1,219 @@
+#include "link.h"
+#include <Adafruit_GFX.h>
+#include <Adafruit_SSD1306.h>
+#include <DW1000.h>
+#include <DW1000Ranging.h>
+#include <SPI.h>
+#include <WiFi.h>
+#include <Wire.h>
+
+#define SPI_SCK 18
+#define SPI_MISO 19
+#define SPI_MOSI 23
+
+#define UWB_RST 27 // Reset pin
+#define UWB_SS 21  // SPI select pin for UWB module
+#define UWB_IRQ 34 // IRQ pin
+
+#define I2C_SDA 4
+#define I2C_SCL 5
+
+uint16_t shortAddress = 200;             // 0xC8
+char macAddress[] = "5B:D5:A9:9A:E3:00"; // 0xC8 == :00
+
+Adafruit_SSD1306 display(128, 64, &Wire, -1);
+
+const char *ssid = "";     // WiFi SSID
+const char *password = ""; // WiFi Password
+const char *host = "";     // Server IP
+WiFiClient client;
+
+struct MyLink *uwb_data;
+int index_num = 0;
+long runtime = 0;
+String all_json = "";
+
+void setup() {
+  Serial.begin(115200);
+
+  WiFi.mode(WIFI_STA);
+  WiFi.setSleep(false);
+  WiFi.begin(ssid, password);
+
+  while (WiFi.status() != WL_CONNECTED) {
+    delay(500);
+    Serial.print(".");
+  }
+
+  Serial.println("Connected");
+  Serial.print("IP Address:");
+  Serial.print(WiFi.localIP());
+
+  // Initialize I2C communication
+  Wire.begin(I2C_SDA, I2C_SCL);
+  delay(1000);
+
+  // Initialize the SSD1306 display
+  // Address 0x3C for 128x32
+  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
+    Serial.println(F("SSD1306 allocation failed"));
+    for (;;)
+      ; // Don't proceed, loop forever
+  }
+
+  display.clearDisplay();
+  initDisplay();
+
+  // Initialize UWB
+  SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
+  // Reset, CS, IRQ pin
+  DW1000Ranging.initCommunication(UWB_RST, UWB_SS, UWB_IRQ);
+
+  // Set device configuration
+  DW1000.setDeviceAddress(shortAddress);
+  DW1000.setNetworkId(10);
+  DW1000.enableMode(DW1000.MODE_LONGDATA_RANGE_LOWPOWER);
+
+  DW1000Ranging.attachNewRange(newRange);
+  DW1000Ranging.attachNewDevice(newDevice);
+  DW1000Ranging.attachInactiveDevice(inactiveDevice);
+
+  // Enable the filter to smooth the distance
+  DW1000Ranging.useRangeFilter(true);
+
+  DW1000.commitConfiguration();
+
+  // Start as tag with long data range accuracy and static short address
+  DW1000Ranging.startAsTag(macAddress, DW1000.MODE_LONGDATA_RANGE_LOWPOWER,
+                           false);
+
+  uwb_data = init_link();
+}
+
+void loop() {
+  DW1000Ranging.loop();
+
+  if ((millis() - runtime) > 1000) {
+    make_link_json(uwb_data, &all_json);
+    send_udp(&all_json);
+    display_uwb(uwb_data); // Update the display
+    printAllRanges();
+    runtime = millis();
+  }
+}
+
+
+void newDevice(DW1000Device *device) {
+  Serial.print("New device detected: ");
+  Serial.print("Short Address: ");
+  Serial.println(device->getShortAddress(), HEX);
+
+  add_link(uwb_data, device->getShortAddress());
+}
+
+void newRange() {
+  // Update the linked list with the new range data
+  uint16_t anchorAddress = DW1000Ranging.getDistantDevice()->getShortAddress();
+  float range = DW1000Ranging.getDistantDevice()->getRange();
+  float rxPower = DW1000Ranging.getDistantDevice()->getRXPower();
+
+  fresh_link(uwb_data, anchorAddress, range, rxPower);
+
+  Serial.print("Updated range for anchor: ");
+  Serial.println(anchorAddress, HEX);
+}
+
+void inactiveDevice(DW1000Device *device) {
+  Serial.print("delete inactive device: ");
+  Serial.println(device->getShortAddress(), HEX);
+
+  delete_link(uwb_data, device->getShortAddress());
+}
+
+void send_udp(String *msg_json) {
+  if (!client.connected()) {
+    Serial.println("Client disconnected, attempting to reconnect...");
+    if (client.connect(host, 8080)) {
+      Serial.println("Connected to server");
+    } else {
+      Serial.println("Failed to connect to server");
+      return;
+    }
+  }
+  client.print(*msg_json + "\n");
+  Serial.println("Data sent: " + *msg_json);
+}
+
+void initDisplay(void) {
+  display.clearDisplay();
+
+  display.setTextSize(2);
+  display.setTextColor(SSD1306_WHITE);
+  display.setCursor(0, 0);
+  display.println(F("UWB Tag"));
+
+  display.setTextSize(1);
+  display.setCursor(0, 20);
+  display.print(F("Device Address: "));
+  display.print(shortAddress, HEX);
+  display.setCursor(0, 40);
+  display.println(macAddress);
+  display.display();
+  delay(2000);
+}
+
+void display_uwb(struct MyLink *p) {
+  struct MyLink *temp = p->next; // Start from the first actual node
+  int row = 0;
+
+  display.clearDisplay();
+  display.setTextColor(SSD1306_WHITE);
+
+  if (temp == NULL) {
+    display.setTextSize(2);
+    display.setCursor(0, 0);
+    display.println("No Anchor");
+    display.display();
+    return;
+  }
+
+  while (temp != NULL) {
+    char c[30];
+    sprintf(c, "A%X: %.1f m", temp->anchor_addr, temp->averageRange);
+    display.setTextSize(1);
+    display.setCursor(0, row * 16);
+    display.println(c);
+
+    sprintf(c, "RX: %.2f dBm", temp->dbm);
+    display.setCursor(0, row * 16 + 8);
+    display.println(c);
+
+    row++;
+
+    if (row >= 4) {
+      break;
+    }
+
+    temp = temp->next;
+  }
+
+  display.display();
+}
+
+void printAllRanges() {
+  struct MyLink *temp = uwb_data->next; // Skip the head node
+
+  Serial.println("Current Ranges to Anchors:");
+
+  while (temp != NULL) {
+    Serial.print("Anchor ");
+    Serial.print(temp->anchor_addr, HEX);
+    Serial.print(": Range = ");
+    Serial.print(temp->averageRange, 2);
+    Serial.print(" m, RX Power = ");
+    Serial.print(temp->dbm, 2);
+    Serial.println(" dBm");
+    temp = temp->next;
+  }
+  Serial.println("------------------------------");
+}
diff --git a/uwb/uwb_position2.0.py b/uwb/uwb_position2.0.py
new file mode 100644
index 0000000..2658dc7
--- /dev/null
+++ b/uwb/uwb_position2.0.py
@@ -0,0 +1,259 @@
+import time
+import turtle
+import cmath
+import socket
+import json
+import threading
+import numpy as np
+import math
+
+# Constants
+METER_TO_PIXEL = 22
+
+ANCHOR_IDS = ["50", "51", "52"]
+
+# Shared data between threads
+uwb_list = []
+data_lock = threading.Lock()
+
+def calculate_anchor_positions():
+    d_50_51 = 10.0 # Distance between anchor 50 and 51 in meters
+    d_50_52 = 8.0 # Distance between anchor 50 and 52 in meters
+    d_51_52 = 6.0 # Distance between anchor 51 and 52 in meters
+
+    anchor_positions = {}
+    anchor_positions['50'] = (0.0, 0.0)
+    anchor_positions['51'] = (d_50_51, 0.0)
+
+    x = (d_50_51**2 + d_50_52**2 - d_51_52**2) / (2 * d_50_51)
+    y = math.sqrt(d_50_52**2 - x**2)
+
+    anchor_positions['52'] = (x, y)
+
+    return anchor_positions
+
+anchor_positions_dict = calculate_anchor_positions()
+
+# Calculate maximum x and y values from anchor positions
+all_x = [pos[0] for pos in anchor_positions_dict.values()]
+all_y = [pos[1] for pos in anchor_positions_dict.values()]
+max_x = max(all_x)
+max_y = max(all_y)
+
+def screen_init(width=1200, height=800, t=turtle):
+    t.setup(width, height)
+    t.tracer(False)
+    t.hideturtle()
+    t.speed(0)
+    # Adjust world coordinates based on anchor positions
+    t.setworldcoordinates(
+        -5 * METER_TO_PIXEL,
+        -5 * METER_TO_PIXEL,
+        (max_x + 5) * METER_TO_PIXEL,
+        (max_y + 5) * METER_TO_PIXEL,
+    )
+
+def turtle_init(t=turtle):
+    t.hideturtle()
+    t.speed(0)
+
+def draw_line(x0, y0, x1, y1, color="black", t=turtle):
+    t.pencolor(color)
+    t.up()
+    t.goto(x0, y0)
+    t.down()
+    t.goto(x1, y1)
+    t.up()
+
+def draw_cycle(x, y, r, color="black", t=turtle):
+    t.pencolor(color)
+    t.up()
+    t.goto(x, y - r)
+    t.setheading(0)
+    t.down()
+    t.circle(r)
+    t.up()
+
+def fill_cycle(x, y, r, color="black", t=turtle):
+    t.up()
+    t.goto(x, y)
+    t.down()
+    t.dot(r, color)
+    t.up()
+
+def write_txt(x, y, txt, color="black", t=turtle, f=("Arial", 12, "normal")):
+    t.pencolor(color)
+    t.up()
+    t.goto(x, y)
+    t.down()
+    t.write(txt, move=False, align="left", font=f)
+    t.up()
+
+def clean(t=turtle):
+    t.clear()
+
+def draw_uwb_anchor(x_m, y_m, txt, range, t):
+    x = x_m * METER_TO_PIXEL
+    y = y_m * METER_TO_PIXEL
+    r = 10
+    fill_cycle(x, y, r, "green", t)
+    write_txt(
+        x + r, y, txt + ": " + str(range) + "m", "black", t, f=("Arial", 12, "normal")
+    )
+
+def draw_uwb_tag(x_m, y_m, txt, t):
+    x = x_m * METER_TO_PIXEL
+    y = y_m * METER_TO_PIXEL
+    r = 10
+    fill_cycle(x, y, r, "blue", t)
+    write_txt(
+        x + r,
+        y,
+        txt + ": (" + str(round(x_m, 2)) + ", " + str(round(y_m, 2)) + ")",
+        "black",
+        t,
+        f=("Arial", 12, "normal"),
+    )
+
+def tag_pos(positions, distances):
+    if len(positions) < 3:
+        print("Error: At least three anchors are required.")
+        return -1, -1
+    try:
+        x1, y1 = positions[0]
+        x2, y2 = positions[1]
+        x3, y3 = positions[2]
+        r1 = distances[0]
+        r2 = distances[1]
+        r3 = distances[2]
+
+        # Formulate matrices
+        A = np.array([
+            [2*(x2 - x1), 2*(y2 - y1)],
+            [2*(x3 - x1), 2*(y3 - y1)]
+        ])
+        B = np.array([
+            r1**2 - r2**2 - x1**2 + x2**2 - y1**2 + y2**2,
+            r1**2 - r3**2 - x1**2 + x3**2 - y1**2 + y3**2
+        ])
+
+        # Solve for x and y
+        position = np.linalg.lstsq(A, B, rcond=None)[0]
+
+        return round(position[0], 2), round(position[1], 2)
+    except Exception as e:
+        print(f"Error calculating position data: {e}")
+        return -1, -1
+
+def uwb_range_offset(uwb_range):
+    return uwb_range
+
+def socket_listener():
+    global uwb_list
+
+    TCP_PORT = 8080
+    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    sock.bind(("0.0.0.0", TCP_PORT))
+    sock.listen(1)
+
+    print(f"Listening on port {TCP_PORT} for incoming connections...")
+
+    conn, addr = sock.accept()
+    print(f"Connected by {addr}")
+    buffer = ''
+
+    while True:
+        try:
+            data = conn.recv(1024).decode("UTF-8")
+            if not data:
+                print("Connection closed by client")
+                break  # Connection closed
+
+            buffer += data
+            while '\n' in buffer:
+                line, buffer = buffer.split('\n', 1)
+                if line:
+                    uwb_data = json.loads(line)
+                    with data_lock:
+                        uwb_list = uwb_data.get("links", [])
+                    for uwb_anchor in uwb_list:
+                        print(f"anchor: {uwb_anchor}")
+        except json.JSONDecodeError:
+            print("Received invalid JSON data.")
+        except Exception as e:
+            print(f"Error reading data: {e}")
+            break
+    conn.close()
+
+def main():
+    # Start the socket listener in a separate thread
+    threading.Thread(target=socket_listener, daemon=True).start()
+
+    # Calculate anchor positions
+    # anchor_positions_dict is already calculated above
+
+    # Initialize the Turtle screen
+    screen_init()
+
+    # Turtle instances
+    t_anchors = [turtle.Turtle() for _ in ANCHOR_IDS]
+    t_tag = turtle.Turtle()
+    for t in t_anchors:
+        turtle_init(t)
+    turtle_init(t_tag)
+
+    # Anchor positions in meters
+    anchor_positions = [
+        anchor_positions_dict['50'],
+        anchor_positions_dict['51'],
+        anchor_positions_dict['52'],
+    ]
+    anchor_ranges = [0.0] * len(ANCHOR_IDS)
+
+    def update():
+        nonlocal anchor_ranges, anchor_positions
+        node_count = 0
+        with data_lock:
+            local_uwb_list = uwb_list.copy()
+
+        for one in local_uwb_list:
+            if one.get("A") in ANCHOR_IDS:
+                index = ANCHOR_IDS.index(one.get("A"))
+                clean(t_anchors[index])
+                anchor_ranges[index] = uwb_range_offset(float(one.get("R", 0)))
+                draw_uwb_anchor(
+                    anchor_positions[index][0],
+                    anchor_positions[index][1],
+                    f"A{ANCHOR_IDS[index]}",
+                    anchor_ranges[index],
+                    t_anchors[index],
+                )
+                node_count += 1
+
+        if node_count >= 3:
+            # Build positions and distances for anchors with valid ranges
+            valid_positions = []
+            valid_distances = []
+            for i in range(len(anchor_ranges)):
+                if anchor_ranges[i] > 0:
+                    valid_positions.append(anchor_positions[i])
+                    valid_distances.append(anchor_ranges[i])
+
+            if len(valid_positions) >= 3:
+                x, y = tag_pos(valid_positions, valid_distances)
+                if x != -1:
+                    print(f"Tag Position: x={x}, y={y}")
+                    clean(t_tag)
+                    draw_uwb_tag(x, y, "TAG", t_tag)
+
+        turtle.update()
+        # Schedule the next update after 100 milliseconds
+        turtle.ontimer(update, 100)
+
+    # Start the update loop
+    update()
+
+    turtle.mainloop()
+
+if __name__ == "__main__":
+    main()

From b08540eee70f4fc0fd923c262351a2844bcfa11d Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Thu, 21 Nov 2024 05:20:01 -0500
Subject: [PATCH 02/34] Add display to calibration tag - lint (#10)

---
 uwb/setup/anchor/anchor.ino |  11 +-
 uwb/setup/tag/tag.ino       | 194 +++++++++++++++++++++++++++++++-----
 uwb/tag/link.h              |  15 ++-
 uwb/tag/tag.ino             |   1 -
 uwb/uwb_position2.0.py      |  57 +++++++----
 5 files changed, 214 insertions(+), 64 deletions(-)

diff --git a/uwb/setup/anchor/anchor.ino b/uwb/setup/anchor/anchor.ino
index 2d185d0..3795e74 100644
--- a/uwb/setup/anchor/anchor.ino
+++ b/uwb/setup/anchor/anchor.ino
@@ -1,6 +1,6 @@
-#include <SPI.h>
-#include "DW1000Ranging.h"
 #include "DW1000.h"
+#include "DW1000Ranging.h"
+#include <SPI.h>
 
 #include <Adafruit_GFX.h>
 #include <Adafruit_SSD1306.h>
@@ -58,12 +58,11 @@ void setup() {
   DW1000Ranging.attachInactiveDevice(inactiveDevice);
 
   // Start as anchor
-  DW1000Ranging.startAsAnchor(anchorAddress, DW1000.MODE_LONGDATA_RANGE_LOWPOWER, false);
+  DW1000Ranging.startAsAnchor(anchorAddress,
+                              DW1000.MODE_LONGDATA_RANGE_LOWPOWER, false);
 }
 
-void loop() {
-  DW1000Ranging.loop();
-}
+void loop() { DW1000Ranging.loop(); }
 
 void newRange() {
   static float lastDelta = 0.0;
diff --git a/uwb/setup/tag/tag.ino b/uwb/setup/tag/tag.ino
index fefd02a..a074a3f 100644
--- a/uwb/setup/tag/tag.ino
+++ b/uwb/setup/tag/tag.ino
@@ -1,63 +1,203 @@
-// currently tag is module #1
-// The purpose of this code is to set the tag address and antenna delay to default.
-// this tag will be used for calibrating the anchors.
+// The purpose of this code is to set the tag address and antenna delay to the
+// default values, this tag will be used for calibrating the anchors.
 
-#include <SPI.h>
-#include "DW1000Ranging.h"
 #include "DW1000.h"
+#include "DW1000Ranging.h"
+#include <Adafruit_GFX.h>
+#include <Adafruit_SSD1306.h>
+#include <SPI.h>
+#include <Wire.h>
 
 #define SPI_SCK 18
 #define SPI_MISO 19
 #define SPI_MOSI 23
 #define DW_CS 4
 
-// connection pins
-const uint8_t PIN_RST = 27; // reset pin
-const uint8_t PIN_IRQ = 34; // irq pin
-const uint8_t PIN_SS = 21;   // spi select pin
+// Connection pins
+const uint8_t PIN_RST = 27; // Reset pin
+const uint8_t PIN_IRQ = 34; // IRQ pin
+const uint8_t PIN_SS = 21;  // SPI select pin
+
+// OLED display settings
+#define I2C_SDA 4
+#define I2C_SCL 5
+Adafruit_SSD1306 display(128, 64, &Wire, -1);
 
 // TAG antenna delay defaults to 16384
-// leftmost two bytes below will become the "short address"
+// Leftmost two bytes below will become the "short address" (0x7D00)
 char tag_addr[] = "7D:00:22:EA:82:60:3B:9C";
 
-void setup()
-{
+// Maximum number of devices to track
+#define MAX_DEVICES 10
+
+// Structure to hold device data
+struct DeviceData {
+  uint16_t shortAddress;
+  float range;
+  float rxPower;
+  unsigned long lastUpdated;
+  bool active;
+};
+
+// Array to store connected devices
+DeviceData devices[MAX_DEVICES];
+
+void setup() {
   Serial.begin(115200);
   delay(1000);
 
-  //init the configuration
+  // Initialize the devices array
+  for (int i = 0; i < MAX_DEVICES; i++) {
+    devices[i].active = false;
+  }
+
+  // Initialize I2C communication for the OLED
+  Wire.begin(I2C_SDA, I2C_SCL);
+  delay(1000);
+
+  // Initialize the SSD1306 display
+  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
+    Serial.println(F("SSD1306 allocation failed"));
+    for (;;)
+      ; // Don't proceed, loop forever
+  }
+
+  display.clearDisplay();
+  initDisplay();
+
+  // Initialize the DW1000 module
   SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
-  DW1000Ranging.initCommunication(PIN_RST, PIN_SS, PIN_IRQ); //Reset, CS, IRQ pin
+  DW1000Ranging.initCommunication(PIN_RST, PIN_SS,
+                                  PIN_IRQ); // Reset, CS, IRQ pin
 
   DW1000Ranging.attachNewRange(newRange);
   DW1000Ranging.attachNewDevice(newDevice);
   DW1000Ranging.attachInactiveDevice(inactiveDevice);
 
-// start as tag, do not assign random short address
-
-  DW1000Ranging.startAsTag(tag_addr, DW1000.MODE_LONGDATA_RANGE_LOWPOWER, false);
+  // Start as tag, do not assign random short address
+  DW1000Ranging.startAsTag(tag_addr, DW1000.MODE_LONGDATA_RANGE_LOWPOWER,
+                           false);
 }
 
-void loop()
-{
+void loop() {
   DW1000Ranging.loop();
+
+  static unsigned long runtime = 0;
+
+  if ((millis() - runtime) > 1000) {
+    displayDevices();
+    runtime = millis();
+  }
 }
 
-void newRange()
-{
-  Serial.print(DW1000Ranging.getDistantDevice()->getShortAddress(), HEX);
+void newRange() {
+  uint16_t shortAddr = DW1000Ranging.getDistantDevice()->getShortAddress();
+  float range = DW1000Ranging.getDistantDevice()->getRange();
+  float rxPower = DW1000Ranging.getDistantDevice()->getRXPower();
+
+  Serial.print(shortAddr, HEX);
   Serial.print(",");
-  Serial.println(DW1000Ranging.getDistantDevice()->getRange());
+  Serial.println(range);
+
+  // Update device data
+  for (int i = 0; i < MAX_DEVICES; i++) {
+    if (devices[i].active && devices[i].shortAddress == shortAddr) {
+      devices[i].range = range;
+      devices[i].rxPower = rxPower;
+      devices[i].lastUpdated = millis();
+      break;
+    }
+  }
 }
 
-void newDevice(DW1000Device *device)
-{
+void newDevice(DW1000Device *device) {
   Serial.print("Device added: ");
   Serial.println(device->getShortAddress(), HEX);
+
+  // Add device to the devices array
+  uint16_t shortAddr = device->getShortAddress();
+
+  int index = -1;
+  for (int i = 0; i < MAX_DEVICES; i++) {
+    if (!devices[i].active) {
+      index = i;
+      break;
+    }
+  }
+
+  if (index != -1) {
+    devices[index].shortAddress = shortAddr;
+    devices[index].active = true;
+    devices[index].lastUpdated = millis();
+  } else {
+    Serial.println("Device list full, cannot add new device.");
+  }
 }
 
-void inactiveDevice(DW1000Device *device)
-{
+void inactiveDevice(DW1000Device *device) {
   Serial.print("delete inactive device: ");
   Serial.println(device->getShortAddress(), HEX);
+
+  uint16_t shortAddr = device->getShortAddress();
+
+  // Mark device as inactive
+  for (int i = 0; i < MAX_DEVICES; i++) {
+    if (devices[i].active && devices[i].shortAddress == shortAddr) {
+      devices[i].active = false;
+      break;
+    }
+  }
+}
+
+void initDisplay(void) {
+  display.clearDisplay();
+
+  display.setTextSize(2);
+  display.setTextColor(SSD1306_WHITE);
+  display.setCursor(0, 0);
+  display.println(F("UWB Tag"));
+
+  display.setTextSize(1);
+  display.setCursor(0, 20);
+  display.print(F("Device Address: "));
+  display.println(tag_addr);
+  display.display();
+  delay(2000);
+}
+
+void displayDevices() {
+  display.clearDisplay();
+  display.setTextColor(SSD1306_WHITE);
+  int row = 0;
+
+  bool anyDevice = false;
+
+  for (int i = 0; i < MAX_DEVICES; i++) {
+    if (devices[i].active) {
+      anyDevice = true;
+      char c[30];
+      sprintf(c, "A%X: %.1f m", devices[i].shortAddress, devices[i].range);
+      display.setTextSize(1);
+      display.setCursor(0, row * 16);
+      display.println(c);
+
+      sprintf(c, "RX: %.2f dBm", devices[i].rxPower);
+      display.setCursor(0, row * 16 + 8);
+      display.println(c);
+
+      row++;
+
+      if (row >= 4) {
+        break;
+      }
+    }
+  }
+
+  if (!anyDevice) {
+    display.setTextSize(2);
+    display.setCursor(0, 0);
+    display.println("No Anchor");
+  }
+
+  display.display();
 }
diff --git a/uwb/tag/link.h b/uwb/tag/link.h
index fc6d434..32fa58b 100644
--- a/uwb/tag/link.h
+++ b/uwb/tag/link.h
@@ -1,12 +1,11 @@
 #include <Arduino.h>
 
-struct MyLink
-{
-    uint16_t anchor_addr;
-    float range[3];
-    float averageRange;
-    float dbm;
-    struct MyLink *next;
+struct MyLink {
+  uint16_t anchor_addr;
+  float range[3];
+  float averageRange;
+  float dbm;
+  struct MyLink *next;
 };
 
 struct MyLink *init_link();
@@ -15,4 +14,4 @@ struct MyLink *find_link(struct MyLink *p, uint16_t addr);
 void fresh_link(struct MyLink *p, uint16_t addr, float range, float dbm);
 void print_link(struct MyLink *p);
 void delete_link(struct MyLink *p, uint16_t addr);
-void make_link_json(struct MyLink *p,String *s);
\ No newline at end of file
+void make_link_json(struct MyLink *p, String *s);
\ No newline at end of file
diff --git a/uwb/tag/tag.ino b/uwb/tag/tag.ino
index fcbe700..4434997 100644
--- a/uwb/tag/tag.ino
+++ b/uwb/tag/tag.ino
@@ -102,7 +102,6 @@ void loop() {
   }
 }
 
-
 void newDevice(DW1000Device *device) {
   Serial.print("New device detected: ");
   Serial.print("Short Address: ");
diff --git a/uwb/uwb_position2.0.py b/uwb/uwb_position2.0.py
index 2658dc7..b88f721 100644
--- a/uwb/uwb_position2.0.py
+++ b/uwb/uwb_position2.0.py
@@ -1,6 +1,4 @@
-import time
 import turtle
-import cmath
 import socket
 import json
 import threading
@@ -16,22 +14,24 @@
 uwb_list = []
 data_lock = threading.Lock()
 
+
 def calculate_anchor_positions():
-    d_50_51 = 10.0 # Distance between anchor 50 and 51 in meters
-    d_50_52 = 8.0 # Distance between anchor 50 and 52 in meters
-    d_51_52 = 6.0 # Distance between anchor 51 and 52 in meters
+    d_50_51 = 10.0  # Distance between anchor 50 and 51 in meters
+    d_50_52 = 8.0  # Distance between anchor 50 and 52 in meters
+    d_51_52 = 6.0  # Distance between anchor 51 and 52 in meters
 
     anchor_positions = {}
-    anchor_positions['50'] = (0.0, 0.0)
-    anchor_positions['51'] = (d_50_51, 0.0)
+    anchor_positions["50"] = (0.0, 0.0)
+    anchor_positions["51"] = (d_50_51, 0.0)
 
     x = (d_50_51**2 + d_50_52**2 - d_51_52**2) / (2 * d_50_51)
     y = math.sqrt(d_50_52**2 - x**2)
 
-    anchor_positions['52'] = (x, y)
+    anchor_positions["52"] = (x, y)
 
     return anchor_positions
 
+
 anchor_positions_dict = calculate_anchor_positions()
 
 # Calculate maximum x and y values from anchor positions
@@ -40,6 +40,7 @@ def calculate_anchor_positions():
 max_x = max(all_x)
 max_y = max(all_y)
 
+
 def screen_init(width=1200, height=800, t=turtle):
     t.setup(width, height)
     t.tracer(False)
@@ -53,10 +54,12 @@ def screen_init(width=1200, height=800, t=turtle):
         (max_y + 5) * METER_TO_PIXEL,
     )
 
+
 def turtle_init(t=turtle):
     t.hideturtle()
     t.speed(0)
 
+
 def draw_line(x0, y0, x1, y1, color="black", t=turtle):
     t.pencolor(color)
     t.up()
@@ -65,6 +68,7 @@ def draw_line(x0, y0, x1, y1, color="black", t=turtle):
     t.goto(x1, y1)
     t.up()
 
+
 def draw_cycle(x, y, r, color="black", t=turtle):
     t.pencolor(color)
     t.up()
@@ -74,6 +78,7 @@ def draw_cycle(x, y, r, color="black", t=turtle):
     t.circle(r)
     t.up()
 
+
 def fill_cycle(x, y, r, color="black", t=turtle):
     t.up()
     t.goto(x, y)
@@ -81,6 +86,7 @@ def fill_cycle(x, y, r, color="black", t=turtle):
     t.dot(r, color)
     t.up()
 
+
 def write_txt(x, y, txt, color="black", t=turtle, f=("Arial", 12, "normal")):
     t.pencolor(color)
     t.up()
@@ -89,9 +95,11 @@ def write_txt(x, y, txt, color="black", t=turtle, f=("Arial", 12, "normal")):
     t.write(txt, move=False, align="left", font=f)
     t.up()
 
+
 def clean(t=turtle):
     t.clear()
 
+
 def draw_uwb_anchor(x_m, y_m, txt, range, t):
     x = x_m * METER_TO_PIXEL
     y = y_m * METER_TO_PIXEL
@@ -101,6 +109,7 @@ def draw_uwb_anchor(x_m, y_m, txt, range, t):
         x + r, y, txt + ": " + str(range) + "m", "black", t, f=("Arial", 12, "normal")
     )
 
+
 def draw_uwb_tag(x_m, y_m, txt, t):
     x = x_m * METER_TO_PIXEL
     y = y_m * METER_TO_PIXEL
@@ -115,6 +124,7 @@ def draw_uwb_tag(x_m, y_m, txt, t):
         f=("Arial", 12, "normal"),
     )
 
+
 def tag_pos(positions, distances):
     if len(positions) < 3:
         print("Error: At least three anchors are required.")
@@ -128,14 +138,13 @@ def tag_pos(positions, distances):
         r3 = distances[2]
 
         # Formulate matrices
-        A = np.array([
-            [2*(x2 - x1), 2*(y2 - y1)],
-            [2*(x3 - x1), 2*(y3 - y1)]
-        ])
-        B = np.array([
-            r1**2 - r2**2 - x1**2 + x2**2 - y1**2 + y2**2,
-            r1**2 - r3**2 - x1**2 + x3**2 - y1**2 + y3**2
-        ])
+        A = np.array([[2 * (x2 - x1), 2 * (y2 - y1)], [2 * (x3 - x1), 2 * (y3 - y1)]])
+        B = np.array(
+            [
+                r1**2 - r2**2 - x1**2 + x2**2 - y1**2 + y2**2,
+                r1**2 - r3**2 - x1**2 + x3**2 - y1**2 + y3**2,
+            ]
+        )
 
         # Solve for x and y
         position = np.linalg.lstsq(A, B, rcond=None)[0]
@@ -145,9 +154,11 @@ def tag_pos(positions, distances):
         print(f"Error calculating position data: {e}")
         return -1, -1
 
+
 def uwb_range_offset(uwb_range):
     return uwb_range
 
+
 def socket_listener():
     global uwb_list
 
@@ -160,7 +171,7 @@ def socket_listener():
 
     conn, addr = sock.accept()
     print(f"Connected by {addr}")
-    buffer = ''
+    buffer = ""
 
     while True:
         try:
@@ -170,8 +181,8 @@ def socket_listener():
                 break  # Connection closed
 
             buffer += data
-            while '\n' in buffer:
-                line, buffer = buffer.split('\n', 1)
+            while "\n" in buffer:
+                line, buffer = buffer.split("\n", 1)
                 if line:
                     uwb_data = json.loads(line)
                     with data_lock:
@@ -185,6 +196,7 @@ def socket_listener():
             break
     conn.close()
 
+
 def main():
     # Start the socket listener in a separate thread
     threading.Thread(target=socket_listener, daemon=True).start()
@@ -204,9 +216,9 @@ def main():
 
     # Anchor positions in meters
     anchor_positions = [
-        anchor_positions_dict['50'],
-        anchor_positions_dict['51'],
-        anchor_positions_dict['52'],
+        anchor_positions_dict["50"],
+        anchor_positions_dict["51"],
+        anchor_positions_dict["52"],
     ]
     anchor_ranges = [0.0] * len(ANCHOR_IDS)
 
@@ -255,5 +267,6 @@ def update():
 
     turtle.mainloop()
 
+
 if __name__ == "__main__":
     main()

From 45b0b5a62c3e9c6847c29f61146843660ec330d9 Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Thu, 21 Nov 2024 12:58:02 -0500
Subject: [PATCH 03/34] Initial Pi UI and Server (#11)

* Add pi UI and server

* Update workflow
---
 ...{compile-lawndon.yml => build-lawndon.yml} |   32 +-
 .gitignore                                    |    2 +
 pi/config/anchorPositions.json                |    8 +
 pi/package.json                               |   13 +
 pi/package.sh                                 |   41 +
 pi/pnpm-lock.yaml                             |    9 +
 pi/server/package.json                        |   26 +
 pi/server/pnpm-lock.yaml                      |  994 +++++
 pi/server/src/server.ts                       |   65 +
 pi/server/src/utils/position.ts               |   40 +
 pi/server/tsconfig.json                       |   16 +
 pi/ui/.editorconfig                           |    6 +
 pi/ui/.gitignore                              |   30 +
 pi/ui/.prettierrc.json                        |    7 +
 pi/ui/.vscode/extensions.json                 |    8 +
 pi/ui/README.md                               |   39 +
 pi/ui/env.d.ts                                |    1 +
 pi/ui/eslint.config.js                        |   25 +
 pi/ui/index.html                              |   13 +
 pi/ui/package.json                            |   40 +
 pi/ui/pnpm-lock.yaml                          | 3696 +++++++++++++++++
 pi/ui/public/favicon.ico                      |  Bin 0 -> 4286 bytes
 pi/ui/src/App.vue                             |    7 +
 pi/ui/src/assets/main.css                     |    6 +
 pi/ui/src/components/UWBVisualization.vue     |  228 +
 pi/ui/src/main.ts                             |   21 +
 pi/ui/src/router/index.ts                     |   15 +
 pi/ui/src/views/HomeView.vue                  |    9 +
 pi/ui/tsconfig.app.json                       |   14 +
 pi/ui/tsconfig.json                           |   11 +
 pi/ui/tsconfig.node.json                      |   19 +
 pi/ui/vite.config.ts                          |   18 +
 uwb/uwb_position2.0.py                        |  272 --
 33 files changed, 5457 insertions(+), 274 deletions(-)
 rename .github/workflows/{compile-lawndon.yml => build-lawndon.yml} (63%)
 create mode 100644 .gitignore
 create mode 100644 pi/config/anchorPositions.json
 create mode 100644 pi/package.json
 create mode 100755 pi/package.sh
 create mode 100644 pi/pnpm-lock.yaml
 create mode 100644 pi/server/package.json
 create mode 100644 pi/server/pnpm-lock.yaml
 create mode 100644 pi/server/src/server.ts
 create mode 100644 pi/server/src/utils/position.ts
 create mode 100644 pi/server/tsconfig.json
 create mode 100644 pi/ui/.editorconfig
 create mode 100644 pi/ui/.gitignore
 create mode 100644 pi/ui/.prettierrc.json
 create mode 100644 pi/ui/.vscode/extensions.json
 create mode 100644 pi/ui/README.md
 create mode 100644 pi/ui/env.d.ts
 create mode 100644 pi/ui/eslint.config.js
 create mode 100644 pi/ui/index.html
 create mode 100644 pi/ui/package.json
 create mode 100644 pi/ui/pnpm-lock.yaml
 create mode 100644 pi/ui/public/favicon.ico
 create mode 100644 pi/ui/src/App.vue
 create mode 100644 pi/ui/src/assets/main.css
 create mode 100644 pi/ui/src/components/UWBVisualization.vue
 create mode 100644 pi/ui/src/main.ts
 create mode 100644 pi/ui/src/router/index.ts
 create mode 100644 pi/ui/src/views/HomeView.vue
 create mode 100644 pi/ui/tsconfig.app.json
 create mode 100644 pi/ui/tsconfig.json
 create mode 100644 pi/ui/tsconfig.node.json
 create mode 100644 pi/ui/vite.config.ts
 delete mode 100644 uwb/uwb_position2.0.py

diff --git a/.github/workflows/compile-lawndon.yml b/.github/workflows/build-lawndon.yml
similarity index 63%
rename from .github/workflows/compile-lawndon.yml
rename to .github/workflows/build-lawndon.yml
index 1947a1c..efe9388 100644
--- a/.github/workflows/compile-lawndon.yml
+++ b/.github/workflows/build-lawndon.yml
@@ -1,4 +1,4 @@
-name: Compile Lawndon Lite
+name: Build Lawndon
 
 on:
   workflow_dispatch:
@@ -6,7 +6,7 @@ on:
     types: [released]
 
 jobs:
-  build:
+  build-lawndon:
     runs-on: ubuntu-latest
     permissions:
       contents: write
@@ -51,3 +51,31 @@ jobs:
         with:
           files: |
             lawndon/build/*.tar.gz
+
+  build-lawndon-pi:
+    runs-on: ubuntu-latest
+    needs: build-lawndon-lite
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v3
+        with:
+          node-version: '22'
+          cache: 'pnpm'
+
+      - name: Setup pnpm
+        run: npm install -g pnpm
+
+      - name: Package application
+        working-directory: ./pi
+        run: |
+          bash package.sh
+
+      - name: Upload release assets
+        uses: softprops/action-gh-release@v2
+        if: startsWith(github.ref, 'refs/tags/')
+        with:
+          files: |
+            ./pi/lawndon-pi.tar.gz
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b0ff562
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+**/*/node_modules
+**/*/dist
\ No newline at end of file
diff --git a/pi/config/anchorPositions.json b/pi/config/anchorPositions.json
new file mode 100644
index 0000000..9dbf566
--- /dev/null
+++ b/pi/config/anchorPositions.json
@@ -0,0 +1,8 @@
+{
+  "anchors": ["50", "51", "52"],
+  "distances": {
+    "50-51": 2.4,
+    "50-52": 2.93,
+    "51-52": 1.18
+  }
+}
diff --git a/pi/package.json b/pi/package.json
new file mode 100644
index 0000000..5a565db
--- /dev/null
+++ b/pi/package.json
@@ -0,0 +1,13 @@
+{
+  "name": "lawndon-pi",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "install:ui": "pnpm install --prefix ui",
+    "install:server": "pnpm install --prefix server",
+    "build:ui": "cd ui && pnpm build",
+    "build:server": "cd server && pnpm build",
+    "start": "node server/dist/server.js"
+  },
+  "packageManager": "pnpm@9.12.0+sha512.4abf725084d7bcbafbd728bfc7bee61f2f791f977fd87542b3579dcb23504d170d46337945e4c66485cd12d588a0c0e570ed9c477e7ccdd8507cf05f3f92eaca"
+}
diff --git a/pi/package.sh b/pi/package.sh
new file mode 100755
index 0000000..3964a74
--- /dev/null
+++ b/pi/package.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+set -e
+
+PROJECT_NAME="lawndon-pi"
+TARGET_ARCHIVE="$PROJECT_NAME.tar.gz"
+PI_USER="pi"
+PI_HOST="192.168.12.1"
+PI_PATH="/home/pi"
+
+echo "Starting the build and packaging process..."
+
+echo "Cleaning up old builds..."
+rm -rf $PROJECT_NAME $TARGET_ARCHIVE
+
+echo "Building UI..."
+pnpm install:ui
+pnpm build:ui
+
+echo "Building server..."
+pnpm install:server
+pnpm build:server
+
+echo "Preparing the package directory..."
+mkdir $PROJECT_NAME
+cp -r server/dist $PROJECT_NAME/server
+cp -r ui/dist $PROJECT_NAME/ui
+cp -r config $PROJECT_NAME/
+cp package.json $PROJECT_NAME/
+
+echo "Creating the tar.gz archive..."
+tar -czf $TARGET_ARCHIVE $PROJECT_NAME
+
+rm -rf $PROJECT_NAME
+
+echo "Build and transfer complete"
+echo "To run the app on the Raspberry Pi:"
+echo "1. SSH into your Raspberry Pi: ssh $PI_USER@$PI_HOST"
+echo "2. Unpack the archive: tar -xzf $TARGET_ARCHIVE"
+echo "3. Navigate to the project: cd $PROJECT_NAME"
+echo "4. Start the backend server: node server/server.js"
diff --git a/pi/pnpm-lock.yaml b/pi/pnpm-lock.yaml
new file mode 100644
index 0000000..9b60ae1
--- /dev/null
+++ b/pi/pnpm-lock.yaml
@@ -0,0 +1,9 @@
+lockfileVersion: '9.0'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+importers:
+
+  .: {}
diff --git a/pi/server/package.json b/pi/server/package.json
new file mode 100644
index 0000000..488a0d4
--- /dev/null
+++ b/pi/server/package.json
@@ -0,0 +1,26 @@
+{
+  "name": "uwb-server",
+  "version": "0.1.0",
+  "description": "",
+  "main": "index.js",
+  "type": "module",
+  "scripts": {
+    "start": "node dist/server.js",
+    "build": "tsc --outDir dist"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "packageManager": "pnpm@9.12.0+sha512.4abf725084d7bcbafbd728bfc7bee61f2f791f977fd87542b3579dcb23504d170d46337945e4c66485cd12d588a0c0e570ed9c477e7ccdd8507cf05f3f92eaca",
+  "dependencies": {
+    "express": "^4.21.1",
+    "mathjs": "^14.0.0",
+    "socket.io": "^4.8.1"
+  },
+  "devDependencies": {
+    "@types/express": "^5.0.0",
+    "@types/node": "^22.9.1",
+    "ts-node": "^10.9.2",
+    "typescript": "^5.6.3"
+  }
+}
diff --git a/pi/server/pnpm-lock.yaml b/pi/server/pnpm-lock.yaml
new file mode 100644
index 0000000..58d78b0
--- /dev/null
+++ b/pi/server/pnpm-lock.yaml
@@ -0,0 +1,994 @@
+lockfileVersion: '9.0'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+importers:
+
+  .:
+    dependencies:
+      express:
+        specifier: ^4.21.1
+        version: 4.21.1
+      mathjs:
+        specifier: ^14.0.0
+        version: 14.0.0
+      socket.io:
+        specifier: ^4.8.1
+        version: 4.8.1
+    devDependencies:
+      '@types/express':
+        specifier: ^5.0.0
+        version: 5.0.0
+      '@types/node':
+        specifier: ^22.9.1
+        version: 22.9.1
+      ts-node:
+        specifier: ^10.9.2
+        version: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
+      typescript:
+        specifier: ^5.6.3
+        version: 5.6.3
+
+packages:
+
+  '@babel/runtime@7.26.0':
+    resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==}
+    engines: {node: '>=6.9.0'}
+
+  '@cspotcode/source-map-support@0.8.1':
+    resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
+    engines: {node: '>=12'}
+
+  '@jridgewell/resolve-uri@3.1.2':
+    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/sourcemap-codec@1.5.0':
+    resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
+
+  '@jridgewell/trace-mapping@0.3.9':
+    resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+
+  '@socket.io/component-emitter@3.1.2':
+    resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
+
+  '@tsconfig/node10@1.0.11':
+    resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
+
+  '@tsconfig/node12@1.0.11':
+    resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
+
+  '@tsconfig/node14@1.0.3':
+    resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
+
+  '@tsconfig/node16@1.0.4':
+    resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+
+  '@types/body-parser@1.19.5':
+    resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
+
+  '@types/connect@3.4.38':
+    resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
+
+  '@types/cookie@0.4.1':
+    resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
+
+  '@types/cors@2.8.17':
+    resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
+
+  '@types/express-serve-static-core@5.0.1':
+    resolution: {integrity: sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==}
+
+  '@types/express@5.0.0':
+    resolution: {integrity: sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==}
+
+  '@types/http-errors@2.0.4':
+    resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
+
+  '@types/mime@1.3.5':
+    resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
+
+  '@types/node@22.9.1':
+    resolution: {integrity: sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==}
+
+  '@types/qs@6.9.17':
+    resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==}
+
+  '@types/range-parser@1.2.7':
+    resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
+
+  '@types/send@0.17.4':
+    resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
+
+  '@types/serve-static@1.15.7':
+    resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==}
+
+  accepts@1.3.8:
+    resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
+    engines: {node: '>= 0.6'}
+
+  acorn-walk@8.3.4:
+    resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
+    engines: {node: '>=0.4.0'}
+
+  acorn@8.14.0:
+    resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+
+  arg@4.1.3:
+    resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
+
+  array-flatten@1.1.1:
+    resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
+
+  base64id@2.0.0:
+    resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
+    engines: {node: ^4.5.0 || >= 5.9}
+
+  body-parser@1.20.3:
+    resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
+    engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+
+  bytes@3.1.2:
+    resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+    engines: {node: '>= 0.8'}
+
+  call-bind@1.0.7:
+    resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
+    engines: {node: '>= 0.4'}
+
+  complex.js@2.4.2:
+    resolution: {integrity: sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g==}
+
+  content-disposition@0.5.4:
+    resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
+    engines: {node: '>= 0.6'}
+
+  content-type@1.0.5:
+    resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
+    engines: {node: '>= 0.6'}
+
+  cookie-signature@1.0.6:
+    resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
+
+  cookie@0.7.1:
+    resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
+    engines: {node: '>= 0.6'}
+
+  cookie@0.7.2:
+    resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
+    engines: {node: '>= 0.6'}
+
+  cors@2.8.5:
+    resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
+    engines: {node: '>= 0.10'}
+
+  create-require@1.1.1:
+    resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+
+  debug@2.6.9:
+    resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
+  debug@4.3.7:
+    resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
+    engines: {node: '>=6.0'}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
+  decimal.js@10.4.3:
+    resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
+
+  define-data-property@1.1.4:
+    resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+    engines: {node: '>= 0.4'}
+
+  depd@2.0.0:
+    resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+    engines: {node: '>= 0.8'}
+
+  destroy@1.2.0:
+    resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
+    engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+
+  diff@4.0.2:
+    resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
+    engines: {node: '>=0.3.1'}
+
+  ee-first@1.1.1:
+    resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
+
+  encodeurl@1.0.2:
+    resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
+    engines: {node: '>= 0.8'}
+
+  encodeurl@2.0.0:
+    resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
+    engines: {node: '>= 0.8'}
+
+  engine.io-parser@5.2.3:
+    resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
+    engines: {node: '>=10.0.0'}
+
+  engine.io@6.6.2:
+    resolution: {integrity: sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==}
+    engines: {node: '>=10.2.0'}
+
+  es-define-property@1.0.0:
+    resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
+    engines: {node: '>= 0.4'}
+
+  es-errors@1.3.0:
+    resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+    engines: {node: '>= 0.4'}
+
+  escape-html@1.0.3:
+    resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+
+  escape-latex@1.2.0:
+    resolution: {integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==}
+
+  etag@1.8.1:
+    resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+    engines: {node: '>= 0.6'}
+
+  express@4.21.1:
+    resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==}
+    engines: {node: '>= 0.10.0'}
+
+  finalhandler@1.3.1:
+    resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
+    engines: {node: '>= 0.8'}
+
+  forwarded@0.2.0:
+    resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
+    engines: {node: '>= 0.6'}
+
+  fraction.js@5.2.1:
+    resolution: {integrity: sha512-Ah6t/7YCYjrPUFUFsOsRLMXAdnYM+aQwmojD2Ayb/Ezr82SwES0vuyQ8qZ3QO8n9j7W14VJuVZZet8U3bhSdQQ==}
+    engines: {node: '>= 12'}
+
+  fresh@0.5.2:
+    resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
+    engines: {node: '>= 0.6'}
+
+  function-bind@1.1.2:
+    resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+  get-intrinsic@1.2.4:
+    resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
+    engines: {node: '>= 0.4'}
+
+  gopd@1.0.1:
+    resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
+
+  has-property-descriptors@1.0.2:
+    resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+  has-proto@1.0.3:
+    resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==}
+    engines: {node: '>= 0.4'}
+
+  has-symbols@1.0.3:
+    resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
+    engines: {node: '>= 0.4'}
+
+  hasown@2.0.2:
+    resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+    engines: {node: '>= 0.4'}
+
+  http-errors@2.0.0:
+    resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
+    engines: {node: '>= 0.8'}
+
+  iconv-lite@0.4.24:
+    resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
+    engines: {node: '>=0.10.0'}
+
+  inherits@2.0.4:
+    resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+  ipaddr.js@1.9.1:
+    resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
+    engines: {node: '>= 0.10'}
+
+  javascript-natural-sort@0.7.1:
+    resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
+
+  make-error@1.3.6:
+    resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
+
+  mathjs@14.0.0:
+    resolution: {integrity: sha512-MR3me92c6pKBqzUXosqL5KMIZDrb1x0MGOy+Ss6fQllD1zhAFloG6DJnG6X5b0VYAMA9sgGfAR2tYi5HPNNQBQ==}
+    engines: {node: '>= 18'}
+    hasBin: true
+
+  media-typer@0.3.0:
+    resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
+    engines: {node: '>= 0.6'}
+
+  merge-descriptors@1.0.3:
+    resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
+
+  methods@1.1.2:
+    resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
+    engines: {node: '>= 0.6'}
+
+  mime-db@1.52.0:
+    resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+    engines: {node: '>= 0.6'}
+
+  mime-types@2.1.35:
+    resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+    engines: {node: '>= 0.6'}
+
+  mime@1.6.0:
+    resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
+    engines: {node: '>=4'}
+    hasBin: true
+
+  ms@2.0.0:
+    resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+
+  ms@2.1.3:
+    resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+  negotiator@0.6.3:
+    resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
+    engines: {node: '>= 0.6'}
+
+  object-assign@4.1.1:
+    resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+    engines: {node: '>=0.10.0'}
+
+  object-inspect@1.13.3:
+    resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==}
+    engines: {node: '>= 0.4'}
+
+  on-finished@2.4.1:
+    resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
+    engines: {node: '>= 0.8'}
+
+  parseurl@1.3.3:
+    resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
+    engines: {node: '>= 0.8'}
+
+  path-to-regexp@0.1.10:
+    resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==}
+
+  proxy-addr@2.0.7:
+    resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
+    engines: {node: '>= 0.10'}
+
+  qs@6.13.0:
+    resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
+    engines: {node: '>=0.6'}
+
+  range-parser@1.2.1:
+    resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
+    engines: {node: '>= 0.6'}
+
+  raw-body@2.5.2:
+    resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
+    engines: {node: '>= 0.8'}
+
+  regenerator-runtime@0.14.1:
+    resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
+
+  safe-buffer@5.2.1:
+    resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+  safer-buffer@2.1.2:
+    resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+  seedrandom@3.0.5:
+    resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
+
+  send@0.19.0:
+    resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
+    engines: {node: '>= 0.8.0'}
+
+  serve-static@1.16.2:
+    resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
+    engines: {node: '>= 0.8.0'}
+
+  set-function-length@1.2.2:
+    resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+    engines: {node: '>= 0.4'}
+
+  setprototypeof@1.2.0:
+    resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+
+  side-channel@1.0.6:
+    resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
+    engines: {node: '>= 0.4'}
+
+  socket.io-adapter@2.5.5:
+    resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==}
+
+  socket.io-parser@4.2.4:
+    resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
+    engines: {node: '>=10.0.0'}
+
+  socket.io@4.8.1:
+    resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==}
+    engines: {node: '>=10.2.0'}
+
+  statuses@2.0.1:
+    resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
+    engines: {node: '>= 0.8'}
+
+  tiny-emitter@2.1.0:
+    resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
+
+  toidentifier@1.0.1:
+    resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+    engines: {node: '>=0.6'}
+
+  ts-node@10.9.2:
+    resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
+    hasBin: true
+    peerDependencies:
+      '@swc/core': '>=1.2.50'
+      '@swc/wasm': '>=1.2.50'
+      '@types/node': '*'
+      typescript: '>=2.7'
+    peerDependenciesMeta:
+      '@swc/core':
+        optional: true
+      '@swc/wasm':
+        optional: true
+
+  type-is@1.6.18:
+    resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
+    engines: {node: '>= 0.6'}
+
+  typed-function@4.2.1:
+    resolution: {integrity: sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==}
+    engines: {node: '>= 18'}
+
+  typescript@5.6.3:
+    resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
+    engines: {node: '>=14.17'}
+    hasBin: true
+
+  undici-types@6.19.8:
+    resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+
+  unpipe@1.0.0:
+    resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+    engines: {node: '>= 0.8'}
+
+  utils-merge@1.0.1:
+    resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
+    engines: {node: '>= 0.4.0'}
+
+  v8-compile-cache-lib@3.0.1:
+    resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
+
+  vary@1.1.2:
+    resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+    engines: {node: '>= 0.8'}
+
+  ws@8.17.1:
+    resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
+    engines: {node: '>=10.0.0'}
+    peerDependencies:
+      bufferutil: ^4.0.1
+      utf-8-validate: '>=5.0.2'
+    peerDependenciesMeta:
+      bufferutil:
+        optional: true
+      utf-8-validate:
+        optional: true
+
+  yn@3.1.1:
+    resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
+    engines: {node: '>=6'}
+
+snapshots:
+
+  '@babel/runtime@7.26.0':
+    dependencies:
+      regenerator-runtime: 0.14.1
+
+  '@cspotcode/source-map-support@0.8.1':
+    dependencies:
+      '@jridgewell/trace-mapping': 0.3.9
+
+  '@jridgewell/resolve-uri@3.1.2': {}
+
+  '@jridgewell/sourcemap-codec@1.5.0': {}
+
+  '@jridgewell/trace-mapping@0.3.9':
+    dependencies:
+      '@jridgewell/resolve-uri': 3.1.2
+      '@jridgewell/sourcemap-codec': 1.5.0
+
+  '@socket.io/component-emitter@3.1.2': {}
+
+  '@tsconfig/node10@1.0.11': {}
+
+  '@tsconfig/node12@1.0.11': {}
+
+  '@tsconfig/node14@1.0.3': {}
+
+  '@tsconfig/node16@1.0.4': {}
+
+  '@types/body-parser@1.19.5':
+    dependencies:
+      '@types/connect': 3.4.38
+      '@types/node': 22.9.1
+
+  '@types/connect@3.4.38':
+    dependencies:
+      '@types/node': 22.9.1
+
+  '@types/cookie@0.4.1': {}
+
+  '@types/cors@2.8.17':
+    dependencies:
+      '@types/node': 22.9.1
+
+  '@types/express-serve-static-core@5.0.1':
+    dependencies:
+      '@types/node': 22.9.1
+      '@types/qs': 6.9.17
+      '@types/range-parser': 1.2.7
+      '@types/send': 0.17.4
+
+  '@types/express@5.0.0':
+    dependencies:
+      '@types/body-parser': 1.19.5
+      '@types/express-serve-static-core': 5.0.1
+      '@types/qs': 6.9.17
+      '@types/serve-static': 1.15.7
+
+  '@types/http-errors@2.0.4': {}
+
+  '@types/mime@1.3.5': {}
+
+  '@types/node@22.9.1':
+    dependencies:
+      undici-types: 6.19.8
+
+  '@types/qs@6.9.17': {}
+
+  '@types/range-parser@1.2.7': {}
+
+  '@types/send@0.17.4':
+    dependencies:
+      '@types/mime': 1.3.5
+      '@types/node': 22.9.1
+
+  '@types/serve-static@1.15.7':
+    dependencies:
+      '@types/http-errors': 2.0.4
+      '@types/node': 22.9.1
+      '@types/send': 0.17.4
+
+  accepts@1.3.8:
+    dependencies:
+      mime-types: 2.1.35
+      negotiator: 0.6.3
+
+  acorn-walk@8.3.4:
+    dependencies:
+      acorn: 8.14.0
+
+  acorn@8.14.0: {}
+
+  arg@4.1.3: {}
+
+  array-flatten@1.1.1: {}
+
+  base64id@2.0.0: {}
+
+  body-parser@1.20.3:
+    dependencies:
+      bytes: 3.1.2
+      content-type: 1.0.5
+      debug: 2.6.9
+      depd: 2.0.0
+      destroy: 1.2.0
+      http-errors: 2.0.0
+      iconv-lite: 0.4.24
+      on-finished: 2.4.1
+      qs: 6.13.0
+      raw-body: 2.5.2
+      type-is: 1.6.18
+      unpipe: 1.0.0
+    transitivePeerDependencies:
+      - supports-color
+
+  bytes@3.1.2: {}
+
+  call-bind@1.0.7:
+    dependencies:
+      es-define-property: 1.0.0
+      es-errors: 1.3.0
+      function-bind: 1.1.2
+      get-intrinsic: 1.2.4
+      set-function-length: 1.2.2
+
+  complex.js@2.4.2: {}
+
+  content-disposition@0.5.4:
+    dependencies:
+      safe-buffer: 5.2.1
+
+  content-type@1.0.5: {}
+
+  cookie-signature@1.0.6: {}
+
+  cookie@0.7.1: {}
+
+  cookie@0.7.2: {}
+
+  cors@2.8.5:
+    dependencies:
+      object-assign: 4.1.1
+      vary: 1.1.2
+
+  create-require@1.1.1: {}
+
+  debug@2.6.9:
+    dependencies:
+      ms: 2.0.0
+
+  debug@4.3.7:
+    dependencies:
+      ms: 2.1.3
+
+  decimal.js@10.4.3: {}
+
+  define-data-property@1.1.4:
+    dependencies:
+      es-define-property: 1.0.0
+      es-errors: 1.3.0
+      gopd: 1.0.1
+
+  depd@2.0.0: {}
+
+  destroy@1.2.0: {}
+
+  diff@4.0.2: {}
+
+  ee-first@1.1.1: {}
+
+  encodeurl@1.0.2: {}
+
+  encodeurl@2.0.0: {}
+
+  engine.io-parser@5.2.3: {}
+
+  engine.io@6.6.2:
+    dependencies:
+      '@types/cookie': 0.4.1
+      '@types/cors': 2.8.17
+      '@types/node': 22.9.1
+      accepts: 1.3.8
+      base64id: 2.0.0
+      cookie: 0.7.2
+      cors: 2.8.5
+      debug: 4.3.7
+      engine.io-parser: 5.2.3
+      ws: 8.17.1
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+
+  es-define-property@1.0.0:
+    dependencies:
+      get-intrinsic: 1.2.4
+
+  es-errors@1.3.0: {}
+
+  escape-html@1.0.3: {}
+
+  escape-latex@1.2.0: {}
+
+  etag@1.8.1: {}
+
+  express@4.21.1:
+    dependencies:
+      accepts: 1.3.8
+      array-flatten: 1.1.1
+      body-parser: 1.20.3
+      content-disposition: 0.5.4
+      content-type: 1.0.5
+      cookie: 0.7.1
+      cookie-signature: 1.0.6
+      debug: 2.6.9
+      depd: 2.0.0
+      encodeurl: 2.0.0
+      escape-html: 1.0.3
+      etag: 1.8.1
+      finalhandler: 1.3.1
+      fresh: 0.5.2
+      http-errors: 2.0.0
+      merge-descriptors: 1.0.3
+      methods: 1.1.2
+      on-finished: 2.4.1
+      parseurl: 1.3.3
+      path-to-regexp: 0.1.10
+      proxy-addr: 2.0.7
+      qs: 6.13.0
+      range-parser: 1.2.1
+      safe-buffer: 5.2.1
+      send: 0.19.0
+      serve-static: 1.16.2
+      setprototypeof: 1.2.0
+      statuses: 2.0.1
+      type-is: 1.6.18
+      utils-merge: 1.0.1
+      vary: 1.1.2
+    transitivePeerDependencies:
+      - supports-color
+
+  finalhandler@1.3.1:
+    dependencies:
+      debug: 2.6.9
+      encodeurl: 2.0.0
+      escape-html: 1.0.3
+      on-finished: 2.4.1
+      parseurl: 1.3.3
+      statuses: 2.0.1
+      unpipe: 1.0.0
+    transitivePeerDependencies:
+      - supports-color
+
+  forwarded@0.2.0: {}
+
+  fraction.js@5.2.1: {}
+
+  fresh@0.5.2: {}
+
+  function-bind@1.1.2: {}
+
+  get-intrinsic@1.2.4:
+    dependencies:
+      es-errors: 1.3.0
+      function-bind: 1.1.2
+      has-proto: 1.0.3
+      has-symbols: 1.0.3
+      hasown: 2.0.2
+
+  gopd@1.0.1:
+    dependencies:
+      get-intrinsic: 1.2.4
+
+  has-property-descriptors@1.0.2:
+    dependencies:
+      es-define-property: 1.0.0
+
+  has-proto@1.0.3: {}
+
+  has-symbols@1.0.3: {}
+
+  hasown@2.0.2:
+    dependencies:
+      function-bind: 1.1.2
+
+  http-errors@2.0.0:
+    dependencies:
+      depd: 2.0.0
+      inherits: 2.0.4
+      setprototypeof: 1.2.0
+      statuses: 2.0.1
+      toidentifier: 1.0.1
+
+  iconv-lite@0.4.24:
+    dependencies:
+      safer-buffer: 2.1.2
+
+  inherits@2.0.4: {}
+
+  ipaddr.js@1.9.1: {}
+
+  javascript-natural-sort@0.7.1: {}
+
+  make-error@1.3.6: {}
+
+  mathjs@14.0.0:
+    dependencies:
+      '@babel/runtime': 7.26.0
+      complex.js: 2.4.2
+      decimal.js: 10.4.3
+      escape-latex: 1.2.0
+      fraction.js: 5.2.1
+      javascript-natural-sort: 0.7.1
+      seedrandom: 3.0.5
+      tiny-emitter: 2.1.0
+      typed-function: 4.2.1
+
+  media-typer@0.3.0: {}
+
+  merge-descriptors@1.0.3: {}
+
+  methods@1.1.2: {}
+
+  mime-db@1.52.0: {}
+
+  mime-types@2.1.35:
+    dependencies:
+      mime-db: 1.52.0
+
+  mime@1.6.0: {}
+
+  ms@2.0.0: {}
+
+  ms@2.1.3: {}
+
+  negotiator@0.6.3: {}
+
+  object-assign@4.1.1: {}
+
+  object-inspect@1.13.3: {}
+
+  on-finished@2.4.1:
+    dependencies:
+      ee-first: 1.1.1
+
+  parseurl@1.3.3: {}
+
+  path-to-regexp@0.1.10: {}
+
+  proxy-addr@2.0.7:
+    dependencies:
+      forwarded: 0.2.0
+      ipaddr.js: 1.9.1
+
+  qs@6.13.0:
+    dependencies:
+      side-channel: 1.0.6
+
+  range-parser@1.2.1: {}
+
+  raw-body@2.5.2:
+    dependencies:
+      bytes: 3.1.2
+      http-errors: 2.0.0
+      iconv-lite: 0.4.24
+      unpipe: 1.0.0
+
+  regenerator-runtime@0.14.1: {}
+
+  safe-buffer@5.2.1: {}
+
+  safer-buffer@2.1.2: {}
+
+  seedrandom@3.0.5: {}
+
+  send@0.19.0:
+    dependencies:
+      debug: 2.6.9
+      depd: 2.0.0
+      destroy: 1.2.0
+      encodeurl: 1.0.2
+      escape-html: 1.0.3
+      etag: 1.8.1
+      fresh: 0.5.2
+      http-errors: 2.0.0
+      mime: 1.6.0
+      ms: 2.1.3
+      on-finished: 2.4.1
+      range-parser: 1.2.1
+      statuses: 2.0.1
+    transitivePeerDependencies:
+      - supports-color
+
+  serve-static@1.16.2:
+    dependencies:
+      encodeurl: 2.0.0
+      escape-html: 1.0.3
+      parseurl: 1.3.3
+      send: 0.19.0
+    transitivePeerDependencies:
+      - supports-color
+
+  set-function-length@1.2.2:
+    dependencies:
+      define-data-property: 1.1.4
+      es-errors: 1.3.0
+      function-bind: 1.1.2
+      get-intrinsic: 1.2.4
+      gopd: 1.0.1
+      has-property-descriptors: 1.0.2
+
+  setprototypeof@1.2.0: {}
+
+  side-channel@1.0.6:
+    dependencies:
+      call-bind: 1.0.7
+      es-errors: 1.3.0
+      get-intrinsic: 1.2.4
+      object-inspect: 1.13.3
+
+  socket.io-adapter@2.5.5:
+    dependencies:
+      debug: 4.3.7
+      ws: 8.17.1
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+
+  socket.io-parser@4.2.4:
+    dependencies:
+      '@socket.io/component-emitter': 3.1.2
+      debug: 4.3.7
+    transitivePeerDependencies:
+      - supports-color
+
+  socket.io@4.8.1:
+    dependencies:
+      accepts: 1.3.8
+      base64id: 2.0.0
+      cors: 2.8.5
+      debug: 4.3.7
+      engine.io: 6.6.2
+      socket.io-adapter: 2.5.5
+      socket.io-parser: 4.2.4
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+
+  statuses@2.0.1: {}
+
+  tiny-emitter@2.1.0: {}
+
+  toidentifier@1.0.1: {}
+
+  ts-node@10.9.2(@types/node@22.9.1)(typescript@5.6.3):
+    dependencies:
+      '@cspotcode/source-map-support': 0.8.1
+      '@tsconfig/node10': 1.0.11
+      '@tsconfig/node12': 1.0.11
+      '@tsconfig/node14': 1.0.3
+      '@tsconfig/node16': 1.0.4
+      '@types/node': 22.9.1
+      acorn: 8.14.0
+      acorn-walk: 8.3.4
+      arg: 4.1.3
+      create-require: 1.1.1
+      diff: 4.0.2
+      make-error: 1.3.6
+      typescript: 5.6.3
+      v8-compile-cache-lib: 3.0.1
+      yn: 3.1.1
+
+  type-is@1.6.18:
+    dependencies:
+      media-typer: 0.3.0
+      mime-types: 2.1.35
+
+  typed-function@4.2.1: {}
+
+  typescript@5.6.3: {}
+
+  undici-types@6.19.8: {}
+
+  unpipe@1.0.0: {}
+
+  utils-merge@1.0.1: {}
+
+  v8-compile-cache-lib@3.0.1: {}
+
+  vary@1.1.2: {}
+
+  ws@8.17.1: {}
+
+  yn@3.1.1: {}
diff --git a/pi/server/src/server.ts b/pi/server/src/server.ts
new file mode 100644
index 0000000..76ba808
--- /dev/null
+++ b/pi/server/src/server.ts
@@ -0,0 +1,65 @@
+import express from 'express';
+import { createServer } from 'http';
+import { Server } from 'socket.io';
+import net from 'net';
+import path from 'path';
+
+const app = express();
+const httpServer = createServer(app);
+const io = new Server(httpServer, {
+  cors: { origin: '*' },
+});
+
+const PORT = 5000;
+
+app.use(express.static('static'));
+
+app.get('/', (req, res) => {
+  res.sendFile(__dirname + '../../ui');
+});
+
+app.get('/api/config', (req, res) => {
+  res.sendFile(path.resolve(__dirname, '../../config/anchorPositions.json'));
+});
+
+app.get('*', (req, res) => {
+  res.sendFile(path.resolve(__dirname, '../../ui'));
+});
+
+io.on('connection', (socket) => {
+  console.log('A client connected');
+});
+
+const TCP_PORT = 8080;
+const tcpServer = net.createServer((socket) => {
+  console.log('UWB data source connected');
+
+  socket.on('data', (data) => {
+    const messages = data.toString().split('\n');
+
+    messages.forEach((line) => {
+      if (line.trim()) {
+        try {
+          const uwbData = JSON.parse(line);
+          const uwbList = uwbData.links || [];
+
+          io.emit('uwb_data', uwbList);
+        } catch (error) {
+          console.error('Invalid JSON data:', error);
+        }
+      }
+    });
+  });
+
+  socket.on('end', () => {
+    console.log('UWB data source disconnected');
+  });
+});
+
+tcpServer.listen(TCP_PORT, () => {
+  console.log(`TCP server listening on port ${TCP_PORT}`);
+});
+
+httpServer.listen(PORT, () => {
+  console.log(`Server is running on port ${PORT}`);
+});
diff --git a/pi/server/src/utils/position.ts b/pi/server/src/utils/position.ts
new file mode 100644
index 0000000..8c35055
--- /dev/null
+++ b/pi/server/src/utils/position.ts
@@ -0,0 +1,40 @@
+import * as math from 'mathjs';
+
+export function calculateTagPosition(
+  positions: [number, number][],
+  distances: number[]
+): [number, number] {
+  if (positions.length < 3) {
+    console.error('At least three anchors are required.');
+    return [-1, -1];
+  }
+
+  try {
+    const [x1, y1] = positions[0];
+    const [x2, y2] = positions[1];
+    const [x3, y3] = positions[2];
+    const r1 = distances[0];
+    const r2 = distances[1];
+    const r3 = distances[2];
+
+    // Formulate matrices and solve for position
+    const A = [
+      [2 * (x2 - x1), 2 * (y2 - y1)],
+      [2 * (x3 - x1), 2 * (y3 - y1)],
+    ];
+    const B = [
+      r1 ** 2 - r2 ** 2 - x1 ** 2 + x2 ** 2 - y1 ** 2 + y2 ** 2,
+      r1 ** 2 - r3 ** 2 - x1 ** 2 + x3 ** 2 - y1 ** 2 + y3 ** 2,
+    ];
+
+    const position = math.lusolve(A, B) as number[][];
+
+    const x = position[0][0];
+    const y = position[1][0];
+
+    return [x, y];
+  } catch (error) {
+    console.error('Error calculating position:', error);
+    return [-1, -1];
+  }
+}
diff --git a/pi/server/tsconfig.json b/pi/server/tsconfig.json
new file mode 100644
index 0000000..f566996
--- /dev/null
+++ b/pi/server/tsconfig.json
@@ -0,0 +1,16 @@
+{
+  "compilerOptions": {
+    "module": "CommonJS",
+    "target": "ESNext",
+    "outDir": "./dist",
+    "rootDir": "./src",
+    "allowSyntheticDefaultImports": true,
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true,
+    "strict": true,
+    "noImplicitAny": true,
+    "skipLibCheck": true
+  },
+  "include": ["src/**/*"],
+  "exclude": ["node_modules", "dist"]
+}
diff --git a/pi/ui/.editorconfig b/pi/ui/.editorconfig
new file mode 100644
index 0000000..ecea360
--- /dev/null
+++ b/pi/ui/.editorconfig
@@ -0,0 +1,6 @@
+[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]
+charset = utf-8
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/pi/ui/.gitignore b/pi/ui/.gitignore
new file mode 100644
index 0000000..8ee54e8
--- /dev/null
+++ b/pi/ui/.gitignore
@@ -0,0 +1,30 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+.DS_Store
+dist
+dist-ssr
+coverage
+*.local
+
+/cypress/videos/
+/cypress/screenshots/
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+*.tsbuildinfo
diff --git a/pi/ui/.prettierrc.json b/pi/ui/.prettierrc.json
new file mode 100644
index 0000000..17a23d0
--- /dev/null
+++ b/pi/ui/.prettierrc.json
@@ -0,0 +1,7 @@
+
+{
+  "$schema": "https://json.schemastore.org/prettierrc",
+  "semi": false,
+  "singleQuote": true,
+  "printWidth": 100
+}
diff --git a/pi/ui/.vscode/extensions.json b/pi/ui/.vscode/extensions.json
new file mode 100644
index 0000000..c92168f
--- /dev/null
+++ b/pi/ui/.vscode/extensions.json
@@ -0,0 +1,8 @@
+{
+  "recommendations": [
+    "Vue.volar",
+    "dbaeumer.vscode-eslint",
+    "EditorConfig.EditorConfig",
+    "esbenp.prettier-vscode"
+  ]
+}
diff --git a/pi/ui/README.md b/pi/ui/README.md
new file mode 100644
index 0000000..ada244c
--- /dev/null
+++ b/pi/ui/README.md
@@ -0,0 +1,39 @@
+# uwb-ui
+
+This template should help get you started developing with Vue 3 in Vite.
+
+## Recommended IDE Setup
+
+[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
+
+## Type Support for `.vue` Imports in TS
+
+TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
+
+## Customize configuration
+
+See [Vite Configuration Reference](https://vite.dev/config/).
+
+## Project Setup
+
+```sh
+pnpm install
+```
+
+### Compile and Hot-Reload for Development
+
+```sh
+pnpm dev
+```
+
+### Type-Check, Compile and Minify for Production
+
+```sh
+pnpm build
+```
+
+### Lint with [ESLint](https://eslint.org/)
+
+```sh
+pnpm lint
+```
diff --git a/pi/ui/env.d.ts b/pi/ui/env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/pi/ui/env.d.ts
@@ -0,0 +1 @@
+/// <reference types="vite/client" />
diff --git a/pi/ui/eslint.config.js b/pi/ui/eslint.config.js
new file mode 100644
index 0000000..b115712
--- /dev/null
+++ b/pi/ui/eslint.config.js
@@ -0,0 +1,25 @@
+import pluginVue from 'eslint-plugin-vue'
+import vueTsEslintConfig from '@vue/eslint-config-typescript'
+import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
+
+export default [
+  {
+    name: 'app/files-to-lint',
+    files: ['**/*.{ts,mts,tsx,vue}'],
+  },
+
+  {
+    name: 'app/files-to-ignore',
+    ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
+  },
+
+  ...pluginVue.configs['flat/essential'],
+  ...vueTsEslintConfig(),
+  skipFormatting,
+
+  {
+    rules: {
+      'vue/no-multiple-template-root': 'off',
+    }
+  }
+]
diff --git a/pi/ui/index.html b/pi/ui/index.html
new file mode 100644
index 0000000..6c0b0c7
--- /dev/null
+++ b/pi/ui/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="">
+  <head>
+    <meta charset="UTF-8">
+    <link rel="icon" href="/favicon.ico">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Lawndon UI</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.ts"></script>
+  </body>
+</html>
diff --git a/pi/ui/package.json b/pi/ui/package.json
new file mode 100644
index 0000000..ae28a45
--- /dev/null
+++ b/pi/ui/package.json
@@ -0,0 +1,40 @@
+{
+  "name": "uwb-ui",
+  "version": "0.1.0",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "run-p type-check \"build-only {@}\" --",
+    "preview": "vite preview",
+    "build-only": "vite build",
+    "type-check": "vue-tsc --build --force",
+    "lint": "eslint . --fix",
+    "format": "prettier --write src/"
+  },
+  "dependencies": {
+    "d3": "^7.9.0",
+    "pinia": "^2.2.6",
+    "socket.io-client": "^4.8.1",
+    "vue": "^3.5.12",
+    "vue-router": "^4.4.5"
+  },
+  "devDependencies": {
+    "@tsconfig/node22": "^22.0.0",
+    "@types/d3": "^7.4.3",
+    "@types/node": "^22.9.1",
+    "@vitejs/plugin-vue": "^5.1.4",
+    "@vue/eslint-config-prettier": "^10.1.0",
+    "@vue/eslint-config-typescript": "^14.1.3",
+    "@vue/tsconfig": "^0.5.1",
+    "eslint": "^9.14.0",
+    "eslint-plugin-vue": "^9.30.0",
+    "npm-run-all2": "^7.0.1",
+    "prettier": "^3.3.3",
+    "typescript": "~5.6.3",
+    "vite": "^5.4.10",
+    "vite-plugin-vue-devtools": "^7.5.4",
+    "vue-tsc": "^2.1.10"
+  },
+  "packageManager": "pnpm@9.12.0+sha512.4abf725084d7bcbafbd728bfc7bee61f2f791f977fd87542b3579dcb23504d170d46337945e4c66485cd12d588a0c0e570ed9c477e7ccdd8507cf05f3f92eaca"
+}
diff --git a/pi/ui/pnpm-lock.yaml b/pi/ui/pnpm-lock.yaml
new file mode 100644
index 0000000..7addf84
--- /dev/null
+++ b/pi/ui/pnpm-lock.yaml
@@ -0,0 +1,3696 @@
+lockfileVersion: '9.0'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+importers:
+
+  .:
+    dependencies:
+      d3:
+        specifier: ^7.9.0
+        version: 7.9.0
+      pinia:
+        specifier: ^2.2.6
+        version: 2.2.6(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3))
+      socket.io-client:
+        specifier: ^4.8.1
+        version: 4.8.1
+      vue:
+        specifier: ^3.5.12
+        version: 3.5.13(typescript@5.6.3)
+      vue-router:
+        specifier: ^4.4.5
+        version: 4.4.5(vue@3.5.13(typescript@5.6.3))
+    devDependencies:
+      '@tsconfig/node22':
+        specifier: ^22.0.0
+        version: 22.0.0
+      '@types/d3':
+        specifier: ^7.4.3
+        version: 7.4.3
+      '@types/node':
+        specifier: ^22.9.1
+        version: 22.9.1
+      '@vitejs/plugin-vue':
+        specifier: ^5.1.4
+        version: 5.2.0(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3))
+      '@vue/eslint-config-prettier':
+        specifier: ^10.1.0
+        version: 10.1.0(eslint@9.15.0)(prettier@3.3.3)
+      '@vue/eslint-config-typescript':
+        specifier: ^14.1.3
+        version: 14.1.3(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint-plugin-vue@9.31.0(eslint@9.15.0))(eslint@9.15.0)(typescript@5.6.3)
+      '@vue/tsconfig':
+        specifier: ^0.5.1
+        version: 0.5.1
+      eslint:
+        specifier: ^9.14.0
+        version: 9.15.0
+      eslint-plugin-vue:
+        specifier: ^9.30.0
+        version: 9.31.0(eslint@9.15.0)
+      npm-run-all2:
+        specifier: ^7.0.1
+        version: 7.0.1
+      prettier:
+        specifier: ^3.3.3
+        version: 3.3.3
+      typescript:
+        specifier: ~5.6.3
+        version: 5.6.3
+      vite:
+        specifier: ^5.4.10
+        version: 5.4.11(@types/node@22.9.1)
+      vite-plugin-vue-devtools:
+        specifier: ^7.5.4
+        version: 7.6.4(rollup@4.27.3)(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3))
+      vue-tsc:
+        specifier: ^2.1.10
+        version: 2.1.10(typescript@5.6.3)
+
+packages:
+
+  '@ampproject/remapping@2.3.0':
+    resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
+    engines: {node: '>=6.0.0'}
+
+  '@antfu/utils@0.7.10':
+    resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==}
+
+  '@babel/code-frame@7.26.2':
+    resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/compat-data@7.26.2':
+    resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/core@7.26.0':
+    resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/generator@7.26.2':
+    resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-annotate-as-pure@7.25.9':
+    resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-compilation-targets@7.25.9':
+    resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-create-class-features-plugin@7.25.9':
+    resolution: {integrity: sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+
+  '@babel/helper-member-expression-to-functions@7.25.9':
+    resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-module-imports@7.25.9':
+    resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-module-transforms@7.26.0':
+    resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+
+  '@babel/helper-optimise-call-expression@7.25.9':
+    resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-plugin-utils@7.25.9':
+    resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-replace-supers@7.25.9':
+    resolution: {integrity: sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+
+  '@babel/helper-skip-transparent-expression-wrappers@7.25.9':
+    resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-string-parser@7.25.9':
+    resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-validator-identifier@7.25.9':
+    resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-validator-option@7.25.9':
+    resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helpers@7.26.0':
+    resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/parser@7.26.2':
+    resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+
+  '@babel/plugin-proposal-decorators@7.25.9':
+    resolution: {integrity: sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-decorators@7.25.9':
+    resolution: {integrity: sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-import-attributes@7.26.0':
+    resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-import-meta@7.10.4':
+    resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-jsx@7.25.9':
+    resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-typescript@7.25.9':
+    resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-transform-typescript@7.25.9':
+    resolution: {integrity: sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/template@7.25.9':
+    resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/traverse@7.25.9':
+    resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/types@7.26.0':
+    resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==}
+    engines: {node: '>=6.9.0'}
+
+  '@esbuild/aix-ppc64@0.21.5':
+    resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
+    engines: {node: '>=12'}
+    cpu: [ppc64]
+    os: [aix]
+
+  '@esbuild/android-arm64@0.21.5':
+    resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [android]
+
+  '@esbuild/android-arm@0.21.5':
+    resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
+    engines: {node: '>=12'}
+    cpu: [arm]
+    os: [android]
+
+  '@esbuild/android-x64@0.21.5':
+    resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [android]
+
+  '@esbuild/darwin-arm64@0.21.5':
+    resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@esbuild/darwin-x64@0.21.5':
+    resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [darwin]
+
+  '@esbuild/freebsd-arm64@0.21.5':
+    resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [freebsd]
+
+  '@esbuild/freebsd-x64@0.21.5':
+    resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@esbuild/linux-arm64@0.21.5':
+    resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [linux]
+
+  '@esbuild/linux-arm@0.21.5':
+    resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
+    engines: {node: '>=12'}
+    cpu: [arm]
+    os: [linux]
+
+  '@esbuild/linux-ia32@0.21.5':
+    resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
+    engines: {node: '>=12'}
+    cpu: [ia32]
+    os: [linux]
+
+  '@esbuild/linux-loong64@0.21.5':
+    resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
+    engines: {node: '>=12'}
+    cpu: [loong64]
+    os: [linux]
+
+  '@esbuild/linux-mips64el@0.21.5':
+    resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
+    engines: {node: '>=12'}
+    cpu: [mips64el]
+    os: [linux]
+
+  '@esbuild/linux-ppc64@0.21.5':
+    resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
+    engines: {node: '>=12'}
+    cpu: [ppc64]
+    os: [linux]
+
+  '@esbuild/linux-riscv64@0.21.5':
+    resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
+    engines: {node: '>=12'}
+    cpu: [riscv64]
+    os: [linux]
+
+  '@esbuild/linux-s390x@0.21.5':
+    resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
+    engines: {node: '>=12'}
+    cpu: [s390x]
+    os: [linux]
+
+  '@esbuild/linux-x64@0.21.5':
+    resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [linux]
+
+  '@esbuild/netbsd-x64@0.21.5':
+    resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [netbsd]
+
+  '@esbuild/openbsd-x64@0.21.5':
+    resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [openbsd]
+
+  '@esbuild/sunos-x64@0.21.5':
+    resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [sunos]
+
+  '@esbuild/win32-arm64@0.21.5':
+    resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [win32]
+
+  '@esbuild/win32-ia32@0.21.5':
+    resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
+    engines: {node: '>=12'}
+    cpu: [ia32]
+    os: [win32]
+
+  '@esbuild/win32-x64@0.21.5':
+    resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
+    engines: {node: '>=12'}
+    cpu: [x64]
+    os: [win32]
+
+  '@eslint-community/eslint-utils@4.4.1':
+    resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+  '@eslint-community/regexpp@4.12.1':
+    resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
+    engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+  '@eslint/config-array@0.19.0':
+    resolution: {integrity: sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/core@0.9.0':
+    resolution: {integrity: sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/eslintrc@3.2.0':
+    resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/js@9.15.0':
+    resolution: {integrity: sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/object-schema@2.1.4':
+    resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/plugin-kit@0.2.3':
+    resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@humanfs/core@0.19.1':
+    resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+    engines: {node: '>=18.18.0'}
+
+  '@humanfs/node@0.16.6':
+    resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
+    engines: {node: '>=18.18.0'}
+
+  '@humanwhocodes/module-importer@1.0.1':
+    resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+    engines: {node: '>=12.22'}
+
+  '@humanwhocodes/retry@0.3.1':
+    resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
+    engines: {node: '>=18.18'}
+
+  '@humanwhocodes/retry@0.4.1':
+    resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==}
+    engines: {node: '>=18.18'}
+
+  '@jridgewell/gen-mapping@0.3.5':
+    resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/resolve-uri@3.1.2':
+    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/set-array@1.2.1':
+    resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/sourcemap-codec@1.5.0':
+    resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
+
+  '@jridgewell/trace-mapping@0.3.25':
+    resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+
+  '@nodelib/fs.scandir@2.1.5':
+    resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+    engines: {node: '>= 8'}
+
+  '@nodelib/fs.stat@2.0.5':
+    resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+    engines: {node: '>= 8'}
+
+  '@nodelib/fs.walk@1.2.8':
+    resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+    engines: {node: '>= 8'}
+
+  '@pkgr/core@0.1.1':
+    resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==}
+    engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+
+  '@polka/url@1.0.0-next.28':
+    resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==}
+
+  '@rollup/pluginutils@5.1.3':
+    resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==}
+    engines: {node: '>=14.0.0'}
+    peerDependencies:
+      rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
+    peerDependenciesMeta:
+      rollup:
+        optional: true
+
+  '@rollup/rollup-android-arm-eabi@4.27.3':
+    resolution: {integrity: sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ==}
+    cpu: [arm]
+    os: [android]
+
+  '@rollup/rollup-android-arm64@4.27.3':
+    resolution: {integrity: sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw==}
+    cpu: [arm64]
+    os: [android]
+
+  '@rollup/rollup-darwin-arm64@4.27.3':
+    resolution: {integrity: sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA==}
+    cpu: [arm64]
+    os: [darwin]
+
+  '@rollup/rollup-darwin-x64@4.27.3':
+    resolution: {integrity: sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg==}
+    cpu: [x64]
+    os: [darwin]
+
+  '@rollup/rollup-freebsd-arm64@4.27.3':
+    resolution: {integrity: sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw==}
+    cpu: [arm64]
+    os: [freebsd]
+
+  '@rollup/rollup-freebsd-x64@4.27.3':
+    resolution: {integrity: sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA==}
+    cpu: [x64]
+    os: [freebsd]
+
+  '@rollup/rollup-linux-arm-gnueabihf@4.27.3':
+    resolution: {integrity: sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q==}
+    cpu: [arm]
+    os: [linux]
+
+  '@rollup/rollup-linux-arm-musleabihf@4.27.3':
+    resolution: {integrity: sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg==}
+    cpu: [arm]
+    os: [linux]
+
+  '@rollup/rollup-linux-arm64-gnu@4.27.3':
+    resolution: {integrity: sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ==}
+    cpu: [arm64]
+    os: [linux]
+
+  '@rollup/rollup-linux-arm64-musl@4.27.3':
+    resolution: {integrity: sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g==}
+    cpu: [arm64]
+    os: [linux]
+
+  '@rollup/rollup-linux-powerpc64le-gnu@4.27.3':
+    resolution: {integrity: sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw==}
+    cpu: [ppc64]
+    os: [linux]
+
+  '@rollup/rollup-linux-riscv64-gnu@4.27.3':
+    resolution: {integrity: sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A==}
+    cpu: [riscv64]
+    os: [linux]
+
+  '@rollup/rollup-linux-s390x-gnu@4.27.3':
+    resolution: {integrity: sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA==}
+    cpu: [s390x]
+    os: [linux]
+
+  '@rollup/rollup-linux-x64-gnu@4.27.3':
+    resolution: {integrity: sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA==}
+    cpu: [x64]
+    os: [linux]
+
+  '@rollup/rollup-linux-x64-musl@4.27.3':
+    resolution: {integrity: sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ==}
+    cpu: [x64]
+    os: [linux]
+
+  '@rollup/rollup-win32-arm64-msvc@4.27.3':
+    resolution: {integrity: sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw==}
+    cpu: [arm64]
+    os: [win32]
+
+  '@rollup/rollup-win32-ia32-msvc@4.27.3':
+    resolution: {integrity: sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w==}
+    cpu: [ia32]
+    os: [win32]
+
+  '@rollup/rollup-win32-x64-msvc@4.27.3':
+    resolution: {integrity: sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg==}
+    cpu: [x64]
+    os: [win32]
+
+  '@socket.io/component-emitter@3.1.2':
+    resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
+
+  '@tsconfig/node22@22.0.0':
+    resolution: {integrity: sha512-twLQ77zevtxobBOD4ToAtVmuYrpeYUh3qh+TEp+08IWhpsrIflVHqQ1F1CiPxQGL7doCdBIOOCF+1Tm833faNg==}
+
+  '@types/d3-array@3.2.1':
+    resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
+
+  '@types/d3-axis@3.0.6':
+    resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==}
+
+  '@types/d3-brush@3.0.6':
+    resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==}
+
+  '@types/d3-chord@3.0.6':
+    resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==}
+
+  '@types/d3-color@3.1.3':
+    resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+  '@types/d3-contour@3.0.6':
+    resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}
+
+  '@types/d3-delaunay@6.0.4':
+    resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==}
+
+  '@types/d3-dispatch@3.0.6':
+    resolution: {integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==}
+
+  '@types/d3-drag@3.0.7':
+    resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
+
+  '@types/d3-dsv@3.0.7':
+    resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==}
+
+  '@types/d3-ease@3.0.2':
+    resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
+
+  '@types/d3-fetch@3.0.7':
+    resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==}
+
+  '@types/d3-force@3.0.10':
+    resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==}
+
+  '@types/d3-format@3.0.4':
+    resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==}
+
+  '@types/d3-geo@3.1.0':
+    resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==}
+
+  '@types/d3-hierarchy@3.1.7':
+    resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==}
+
+  '@types/d3-interpolate@3.0.4':
+    resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+  '@types/d3-path@3.1.0':
+    resolution: {integrity: sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==}
+
+  '@types/d3-polygon@3.0.2':
+    resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==}
+
+  '@types/d3-quadtree@3.0.6':
+    resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==}
+
+  '@types/d3-random@3.0.3':
+    resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==}
+
+  '@types/d3-scale-chromatic@3.0.3':
+    resolution: {integrity: sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==}
+
+  '@types/d3-scale@4.0.8':
+    resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==}
+
+  '@types/d3-selection@3.0.11':
+    resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
+
+  '@types/d3-shape@3.1.6':
+    resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==}
+
+  '@types/d3-time-format@4.0.3':
+    resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
+
+  '@types/d3-time@3.0.3':
+    resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==}
+
+  '@types/d3-timer@3.0.2':
+    resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
+
+  '@types/d3-transition@3.0.9':
+    resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
+
+  '@types/d3-zoom@3.0.8':
+    resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
+
+  '@types/d3@7.4.3':
+    resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==}
+
+  '@types/estree@1.0.6':
+    resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
+
+  '@types/geojson@7946.0.14':
+    resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==}
+
+  '@types/json-schema@7.0.15':
+    resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+  '@types/node@22.9.1':
+    resolution: {integrity: sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==}
+
+  '@typescript-eslint/eslint-plugin@8.15.0':
+    resolution: {integrity: sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
+      eslint: ^8.57.0 || ^9.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  '@typescript-eslint/parser@8.15.0':
+    resolution: {integrity: sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      eslint: ^8.57.0 || ^9.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  '@typescript-eslint/scope-manager@8.15.0':
+    resolution: {integrity: sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@typescript-eslint/type-utils@8.15.0':
+    resolution: {integrity: sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      eslint: ^8.57.0 || ^9.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  '@typescript-eslint/types@8.15.0':
+    resolution: {integrity: sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@typescript-eslint/typescript-estree@8.15.0':
+    resolution: {integrity: sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  '@typescript-eslint/utils@8.15.0':
+    resolution: {integrity: sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      eslint: ^8.57.0 || ^9.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  '@typescript-eslint/visitor-keys@8.15.0':
+    resolution: {integrity: sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@vitejs/plugin-vue@5.2.0':
+    resolution: {integrity: sha512-7n7KdUEtx/7Yl7I/WVAMZ1bEb0eVvXF3ummWTeLcs/9gvo9pJhuLdouSXGjdZ/MKD1acf1I272+X0RMua4/R3g==}
+    engines: {node: ^18.0.0 || >=20.0.0}
+    peerDependencies:
+      vite: ^5.0.0
+      vue: ^3.2.25
+
+  '@volar/language-core@2.4.10':
+    resolution: {integrity: sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==}
+
+  '@volar/source-map@2.4.10':
+    resolution: {integrity: sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA==}
+
+  '@volar/typescript@2.4.10':
+    resolution: {integrity: sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==}
+
+  '@vue/babel-helper-vue-transform-on@1.2.5':
+    resolution: {integrity: sha512-lOz4t39ZdmU4DJAa2hwPYmKc8EsuGa2U0L9KaZaOJUt0UwQNjNA3AZTq6uEivhOKhhG1Wvy96SvYBoFmCg3uuw==}
+
+  '@vue/babel-plugin-jsx@1.2.5':
+    resolution: {integrity: sha512-zTrNmOd4939H9KsRIGmmzn3q2zvv1mjxkYZHgqHZgDrXz5B1Q3WyGEjO2f+JrmKghvl1JIRcvo63LgM1kH5zFg==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    peerDependenciesMeta:
+      '@babel/core':
+        optional: true
+
+  '@vue/babel-plugin-resolve-type@1.2.5':
+    resolution: {integrity: sha512-U/ibkQrf5sx0XXRnUZD1mo5F7PkpKyTbfXM3a3rC4YnUz6crHEz9Jg09jzzL6QYlXNto/9CePdOg/c87O4Nlfg==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@vue/compiler-core@3.5.13':
+    resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==}
+
+  '@vue/compiler-dom@3.5.13':
+    resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==}
+
+  '@vue/compiler-sfc@3.5.13':
+    resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==}
+
+  '@vue/compiler-ssr@3.5.13':
+    resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==}
+
+  '@vue/compiler-vue2@2.7.16':
+    resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==}
+
+  '@vue/devtools-api@6.6.4':
+    resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
+
+  '@vue/devtools-core@7.6.4':
+    resolution: {integrity: sha512-blSwGVYpb7b5TALMjjoBiAl5imuBF7WEOAtaJaBMNikR8SQkm6mkUt4YlIKh9874/qoimwmpDOm+GHBZ4Y5m+g==}
+    peerDependencies:
+      vue: ^3.0.0
+
+  '@vue/devtools-kit@7.6.4':
+    resolution: {integrity: sha512-Zs86qIXXM9icU0PiGY09PQCle4TI750IPLmAJzW5Kf9n9t5HzSYf6Rz6fyzSwmfMPiR51SUKJh9sXVZu78h2QA==}
+
+  '@vue/devtools-shared@7.6.4':
+    resolution: {integrity: sha512-nD6CUvBEel+y7zpyorjiUocy0nh77DThZJ0k1GRnJeOmY3ATq2fWijEp7wk37gb023Cb0R396uYh5qMSBQ5WFg==}
+
+  '@vue/eslint-config-prettier@10.1.0':
+    resolution: {integrity: sha512-J6wV91y2pXc0Phha01k0WOHBTPsoSTf4xlmMjoKaeSxBpAdsgTppGF5RZRdOHM7OA74zAXD+VLANrtYXpiPKkQ==}
+    peerDependencies:
+      eslint: '>= 8.21.0'
+      prettier: '>= 3.0.0'
+
+  '@vue/eslint-config-typescript@14.1.3':
+    resolution: {integrity: sha512-L4NUJQz/0We2QYtrNwRAGRy4KfpOagl5V3MpZZ+rQ51a+bKjlKYYrugi7lp7PIX8LolRgu06ZwDoswnSGWnAmA==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      eslint: ^9.10.0
+      eslint-plugin-vue: ^9.28.0
+      typescript: '>=4.8.4'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  '@vue/language-core@2.1.10':
+    resolution: {integrity: sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  '@vue/reactivity@3.5.13':
+    resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==}
+
+  '@vue/runtime-core@3.5.13':
+    resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==}
+
+  '@vue/runtime-dom@3.5.13':
+    resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==}
+
+  '@vue/server-renderer@3.5.13':
+    resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==}
+    peerDependencies:
+      vue: 3.5.13
+
+  '@vue/shared@3.5.13':
+    resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
+
+  '@vue/tsconfig@0.5.1':
+    resolution: {integrity: sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==}
+
+  acorn-jsx@5.3.2:
+    resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+    peerDependencies:
+      acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+  acorn@8.14.0:
+    resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+
+  ajv@6.12.6:
+    resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+  alien-signals@0.2.2:
+    resolution: {integrity: sha512-cZIRkbERILsBOXTQmMrxc9hgpxglstn69zm+F1ARf4aPAzdAFYd6sBq87ErO0Fj3DV94tglcyHG5kQz9nDC/8A==}
+
+  ansi-styles@4.3.0:
+    resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+    engines: {node: '>=8'}
+
+  ansi-styles@6.2.1:
+    resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
+    engines: {node: '>=12'}
+
+  argparse@2.0.1:
+    resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+  balanced-match@1.0.2:
+    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+  birpc@0.2.19:
+    resolution: {integrity: sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==}
+
+  boolbase@1.0.0:
+    resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
+
+  brace-expansion@1.1.11:
+    resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+
+  brace-expansion@2.0.1:
+    resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+
+  braces@3.0.3:
+    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+    engines: {node: '>=8'}
+
+  browserslist@4.24.2:
+    resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==}
+    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+    hasBin: true
+
+  bundle-name@4.1.0:
+    resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
+    engines: {node: '>=18'}
+
+  callsites@3.1.0:
+    resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+    engines: {node: '>=6'}
+
+  caniuse-lite@1.0.30001683:
+    resolution: {integrity: sha512-iqmNnThZ0n70mNwvxpEC2nBJ037ZHZUoBI5Gorh1Mw6IlEAZujEoU1tXA628iZfzm7R9FvFzxbfdgml82a3k8Q==}
+
+  chalk@4.1.2:
+    resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+    engines: {node: '>=10'}
+
+  color-convert@2.0.1:
+    resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+    engines: {node: '>=7.0.0'}
+
+  color-name@1.1.4:
+    resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+  commander@7.2.0:
+    resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
+    engines: {node: '>= 10'}
+
+  concat-map@0.0.1:
+    resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+  convert-source-map@2.0.0:
+    resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+  copy-anything@3.0.5:
+    resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
+    engines: {node: '>=12.13'}
+
+  cross-spawn@7.0.6:
+    resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+    engines: {node: '>= 8'}
+
+  cssesc@3.0.0:
+    resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+    engines: {node: '>=4'}
+    hasBin: true
+
+  csstype@3.1.3:
+    resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+
+  d3-array@3.2.4:
+    resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+    engines: {node: '>=12'}
+
+  d3-axis@3.0.0:
+    resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==}
+    engines: {node: '>=12'}
+
+  d3-brush@3.0.0:
+    resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==}
+    engines: {node: '>=12'}
+
+  d3-chord@3.0.1:
+    resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
+    engines: {node: '>=12'}
+
+  d3-color@3.1.0:
+    resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+    engines: {node: '>=12'}
+
+  d3-contour@4.0.2:
+    resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
+    engines: {node: '>=12'}
+
+  d3-delaunay@6.0.4:
+    resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
+    engines: {node: '>=12'}
+
+  d3-dispatch@3.0.1:
+    resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+    engines: {node: '>=12'}
+
+  d3-drag@3.0.0:
+    resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
+    engines: {node: '>=12'}
+
+  d3-dsv@3.0.1:
+    resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
+    engines: {node: '>=12'}
+    hasBin: true
+
+  d3-ease@3.0.1:
+    resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+    engines: {node: '>=12'}
+
+  d3-fetch@3.0.1:
+    resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==}
+    engines: {node: '>=12'}
+
+  d3-force@3.0.0:
+    resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
+    engines: {node: '>=12'}
+
+  d3-format@3.1.0:
+    resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
+    engines: {node: '>=12'}
+
+  d3-geo@3.1.1:
+    resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
+    engines: {node: '>=12'}
+
+  d3-hierarchy@3.1.2:
+    resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
+    engines: {node: '>=12'}
+
+  d3-interpolate@3.0.1:
+    resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+    engines: {node: '>=12'}
+
+  d3-path@3.1.0:
+    resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+    engines: {node: '>=12'}
+
+  d3-polygon@3.0.1:
+    resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
+    engines: {node: '>=12'}
+
+  d3-quadtree@3.0.1:
+    resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
+    engines: {node: '>=12'}
+
+  d3-random@3.0.1:
+    resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
+    engines: {node: '>=12'}
+
+  d3-scale-chromatic@3.1.0:
+    resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
+    engines: {node: '>=12'}
+
+  d3-scale@4.0.2:
+    resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+    engines: {node: '>=12'}
+
+  d3-selection@3.0.0:
+    resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+    engines: {node: '>=12'}
+
+  d3-shape@3.2.0:
+    resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+    engines: {node: '>=12'}
+
+  d3-time-format@4.1.0:
+    resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+    engines: {node: '>=12'}
+
+  d3-time@3.1.0:
+    resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+    engines: {node: '>=12'}
+
+  d3-timer@3.0.1:
+    resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+    engines: {node: '>=12'}
+
+  d3-transition@3.0.1:
+    resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
+    engines: {node: '>=12'}
+    peerDependencies:
+      d3-selection: 2 - 3
+
+  d3-zoom@3.0.0:
+    resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
+    engines: {node: '>=12'}
+
+  d3@7.9.0:
+    resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==}
+    engines: {node: '>=12'}
+
+  de-indent@1.0.2:
+    resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
+
+  debug@4.3.7:
+    resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
+    engines: {node: '>=6.0'}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
+  deep-is@0.1.4:
+    resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+  default-browser-id@5.0.0:
+    resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==}
+    engines: {node: '>=18'}
+
+  default-browser@5.2.1:
+    resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==}
+    engines: {node: '>=18'}
+
+  define-lazy-prop@3.0.0:
+    resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
+    engines: {node: '>=12'}
+
+  delaunator@5.0.1:
+    resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
+
+  electron-to-chromium@1.5.63:
+    resolution: {integrity: sha512-ddeXKuY9BHo/mw145axlyWjlJ1UBt4WK3AlvkT7W2AbqfRQoacVoRUCF6wL3uIx/8wT9oLKXzI+rFqHHscByaA==}
+
+  engine.io-client@6.6.2:
+    resolution: {integrity: sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==}
+
+  engine.io-parser@5.2.3:
+    resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
+    engines: {node: '>=10.0.0'}
+
+  entities@4.5.0:
+    resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+    engines: {node: '>=0.12'}
+
+  error-stack-parser-es@0.1.5:
+    resolution: {integrity: sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==}
+
+  esbuild@0.21.5:
+    resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
+    engines: {node: '>=12'}
+    hasBin: true
+
+  escalade@3.2.0:
+    resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+    engines: {node: '>=6'}
+
+  escape-string-regexp@4.0.0:
+    resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+    engines: {node: '>=10'}
+
+  eslint-config-prettier@9.1.0:
+    resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==}
+    hasBin: true
+    peerDependencies:
+      eslint: '>=7.0.0'
+
+  eslint-plugin-prettier@5.2.1:
+    resolution: {integrity: sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==}
+    engines: {node: ^14.18.0 || >=16.0.0}
+    peerDependencies:
+      '@types/eslint': '>=8.0.0'
+      eslint: '>=8.0.0'
+      eslint-config-prettier: '*'
+      prettier: '>=3.0.0'
+    peerDependenciesMeta:
+      '@types/eslint':
+        optional: true
+      eslint-config-prettier:
+        optional: true
+
+  eslint-plugin-vue@9.31.0:
+    resolution: {integrity: sha512-aYMUCgivhz1o4tLkRHj5oq9YgYPM4/EJc0M7TAKRLCUA5OYxRLAhYEVD2nLtTwLyixEFI+/QXSvKU9ESZFgqjQ==}
+    engines: {node: ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
+
+  eslint-scope@7.2.2:
+    resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+  eslint-scope@8.2.0:
+    resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  eslint-visitor-keys@3.4.3:
+    resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+  eslint-visitor-keys@4.2.0:
+    resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  eslint@9.15.0:
+    resolution: {integrity: sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    hasBin: true
+    peerDependencies:
+      jiti: '*'
+    peerDependenciesMeta:
+      jiti:
+        optional: true
+
+  espree@10.3.0:
+    resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  espree@9.6.1:
+    resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+  esquery@1.6.0:
+    resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
+    engines: {node: '>=0.10'}
+
+  esrecurse@4.3.0:
+    resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+    engines: {node: '>=4.0'}
+
+  estraverse@5.3.0:
+    resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+    engines: {node: '>=4.0'}
+
+  estree-walker@2.0.2:
+    resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+
+  esutils@2.0.3:
+    resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+    engines: {node: '>=0.10.0'}
+
+  execa@8.0.1:
+    resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
+    engines: {node: '>=16.17'}
+
+  fast-deep-equal@3.1.3:
+    resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+  fast-diff@1.3.0:
+    resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
+
+  fast-glob@3.3.2:
+    resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
+    engines: {node: '>=8.6.0'}
+
+  fast-json-stable-stringify@2.1.0:
+    resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+  fast-levenshtein@2.0.6:
+    resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+  fastq@1.17.1:
+    resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
+
+  file-entry-cache@8.0.0:
+    resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+    engines: {node: '>=16.0.0'}
+
+  fill-range@7.1.1:
+    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+    engines: {node: '>=8'}
+
+  find-up@5.0.0:
+    resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+    engines: {node: '>=10'}
+
+  flat-cache@4.0.1:
+    resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+    engines: {node: '>=16'}
+
+  flatted@3.3.2:
+    resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
+
+  fs-extra@11.2.0:
+    resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
+    engines: {node: '>=14.14'}
+
+  fsevents@2.3.3:
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+
+  gensync@1.0.0-beta.2:
+    resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+    engines: {node: '>=6.9.0'}
+
+  get-stream@8.0.1:
+    resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
+    engines: {node: '>=16'}
+
+  glob-parent@5.1.2:
+    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+    engines: {node: '>= 6'}
+
+  glob-parent@6.0.2:
+    resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+    engines: {node: '>=10.13.0'}
+
+  globals@11.12.0:
+    resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
+    engines: {node: '>=4'}
+
+  globals@13.24.0:
+    resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
+    engines: {node: '>=8'}
+
+  globals@14.0.0:
+    resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+    engines: {node: '>=18'}
+
+  graceful-fs@4.2.11:
+    resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+  graphemer@1.4.0:
+    resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+
+  has-flag@4.0.0:
+    resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+    engines: {node: '>=8'}
+
+  he@1.2.0:
+    resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
+    hasBin: true
+
+  hookable@5.5.3:
+    resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
+
+  html-tags@3.3.1:
+    resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
+    engines: {node: '>=8'}
+
+  human-signals@5.0.0:
+    resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
+    engines: {node: '>=16.17.0'}
+
+  iconv-lite@0.6.3:
+    resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+    engines: {node: '>=0.10.0'}
+
+  ignore@5.3.2:
+    resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+    engines: {node: '>= 4'}
+
+  import-fresh@3.3.0:
+    resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
+    engines: {node: '>=6'}
+
+  imurmurhash@0.1.4:
+    resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+    engines: {node: '>=0.8.19'}
+
+  internmap@2.0.3:
+    resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+    engines: {node: '>=12'}
+
+  is-docker@3.0.0:
+    resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    hasBin: true
+
+  is-extglob@2.1.1:
+    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+    engines: {node: '>=0.10.0'}
+
+  is-glob@4.0.3:
+    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+    engines: {node: '>=0.10.0'}
+
+  is-inside-container@1.0.0:
+    resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
+    engines: {node: '>=14.16'}
+    hasBin: true
+
+  is-number@7.0.0:
+    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+    engines: {node: '>=0.12.0'}
+
+  is-stream@3.0.0:
+    resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  is-what@4.1.16:
+    resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
+    engines: {node: '>=12.13'}
+
+  is-wsl@3.1.0:
+    resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
+    engines: {node: '>=16'}
+
+  isexe@2.0.0:
+    resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+  isexe@3.1.1:
+    resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
+    engines: {node: '>=16'}
+
+  js-tokens@4.0.0:
+    resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+  js-yaml@4.1.0:
+    resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+    hasBin: true
+
+  jsesc@3.0.2:
+    resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==}
+    engines: {node: '>=6'}
+    hasBin: true
+
+  json-buffer@3.0.1:
+    resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+  json-parse-even-better-errors@4.0.0:
+    resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==}
+    engines: {node: ^18.17.0 || >=20.5.0}
+
+  json-schema-traverse@0.4.1:
+    resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+  json-stable-stringify-without-jsonify@1.0.1:
+    resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+  json5@2.2.3:
+    resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+    engines: {node: '>=6'}
+    hasBin: true
+
+  jsonfile@6.1.0:
+    resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
+
+  keyv@4.5.4:
+    resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+  kolorist@1.8.0:
+    resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
+
+  levn@0.4.1:
+    resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+    engines: {node: '>= 0.8.0'}
+
+  locate-path@6.0.0:
+    resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+    engines: {node: '>=10'}
+
+  lodash.merge@4.6.2:
+    resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+  lodash@4.17.21:
+    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+  lru-cache@5.1.1:
+    resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+  magic-string@0.30.13:
+    resolution: {integrity: sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==}
+
+  memorystream@0.3.1:
+    resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
+    engines: {node: '>= 0.10.0'}
+
+  merge-stream@2.0.0:
+    resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+
+  merge2@1.4.1:
+    resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+    engines: {node: '>= 8'}
+
+  micromatch@4.0.8:
+    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+    engines: {node: '>=8.6'}
+
+  mimic-fn@4.0.0:
+    resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
+    engines: {node: '>=12'}
+
+  minimatch@3.1.2:
+    resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+  minimatch@9.0.5:
+    resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
+    engines: {node: '>=16 || 14 >=14.17'}
+
+  mitt@3.0.1:
+    resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
+
+  mrmime@2.0.0:
+    resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==}
+    engines: {node: '>=10'}
+
+  ms@2.1.3:
+    resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+  muggle-string@0.4.1:
+    resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
+
+  nanoid@3.3.7:
+    resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
+    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+    hasBin: true
+
+  natural-compare@1.4.0:
+    resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+  node-releases@2.0.18:
+    resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
+
+  npm-normalize-package-bin@4.0.0:
+    resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==}
+    engines: {node: ^18.17.0 || >=20.5.0}
+
+  npm-run-all2@7.0.1:
+    resolution: {integrity: sha512-Adbv+bJQ8UTAM03rRODqrO5cx0YU5KCG2CvHtSURiadvdTjjgGJXdbc1oQ9CXBh9dnGfHSoSB1Web/0Dzp6kOQ==}
+    engines: {node: ^18.17.0 || >=20.5.0, npm: '>= 9'}
+    hasBin: true
+
+  npm-run-path@5.3.0:
+    resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
+  nth-check@2.1.1:
+    resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
+
+  onetime@6.0.0:
+    resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
+    engines: {node: '>=12'}
+
+  open@10.1.0:
+    resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==}
+    engines: {node: '>=18'}
+
+  optionator@0.9.4:
+    resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+    engines: {node: '>= 0.8.0'}
+
+  p-limit@3.1.0:
+    resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+    engines: {node: '>=10'}
+
+  p-locate@5.0.0:
+    resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+    engines: {node: '>=10'}
+
+  parent-module@1.0.1:
+    resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+    engines: {node: '>=6'}
+
+  path-browserify@1.0.1:
+    resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
+
+  path-exists@4.0.0:
+    resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+    engines: {node: '>=8'}
+
+  path-key@3.1.1:
+    resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+    engines: {node: '>=8'}
+
+  path-key@4.0.0:
+    resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
+    engines: {node: '>=12'}
+
+  pathe@1.1.2:
+    resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
+
+  perfect-debounce@1.0.0:
+    resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
+
+  picocolors@1.1.1:
+    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+  picomatch@2.3.1:
+    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+    engines: {node: '>=8.6'}
+
+  picomatch@4.0.2:
+    resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
+    engines: {node: '>=12'}
+
+  pidtree@0.6.0:
+    resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
+    engines: {node: '>=0.10'}
+    hasBin: true
+
+  pinia@2.2.6:
+    resolution: {integrity: sha512-vIsR8JkDN5Ga2vAxqOE2cJj4VtsHnzpR1Fz30kClxlh0yCHfec6uoMeM3e/ddqmwFUejK3NlrcQa/shnpyT4hA==}
+    peerDependencies:
+      '@vue/composition-api': ^1.4.0
+      typescript: '>=4.4.4'
+      vue: ^2.6.14 || ^3.5.11
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+      typescript:
+        optional: true
+
+  postcss-selector-parser@6.1.2:
+    resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
+    engines: {node: '>=4'}
+
+  postcss@8.4.49:
+    resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==}
+    engines: {node: ^10 || ^12 || >=14}
+
+  prelude-ls@1.2.1:
+    resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+    engines: {node: '>= 0.8.0'}
+
+  prettier-linter-helpers@1.0.0:
+    resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
+    engines: {node: '>=6.0.0'}
+
+  prettier@3.3.3:
+    resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==}
+    engines: {node: '>=14'}
+    hasBin: true
+
+  punycode@2.3.1:
+    resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+    engines: {node: '>=6'}
+
+  queue-microtask@1.2.3:
+    resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+  read-package-json-fast@4.0.0:
+    resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==}
+    engines: {node: ^18.17.0 || >=20.5.0}
+
+  resolve-from@4.0.0:
+    resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+    engines: {node: '>=4'}
+
+  reusify@1.0.4:
+    resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+    engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+  rfdc@1.4.1:
+    resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
+
+  robust-predicates@3.0.2:
+    resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
+
+  rollup@4.27.3:
+    resolution: {integrity: sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==}
+    engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+    hasBin: true
+
+  run-applescript@7.0.0:
+    resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==}
+    engines: {node: '>=18'}
+
+  run-parallel@1.2.0:
+    resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+  rw@1.3.3:
+    resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
+
+  safer-buffer@2.1.2:
+    resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+  semver@6.3.1:
+    resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+    hasBin: true
+
+  semver@7.6.3:
+    resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
+    engines: {node: '>=10'}
+    hasBin: true
+
+  shebang-command@2.0.0:
+    resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+    engines: {node: '>=8'}
+
+  shebang-regex@3.0.0:
+    resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+    engines: {node: '>=8'}
+
+  shell-quote@1.8.1:
+    resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
+
+  signal-exit@4.1.0:
+    resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+    engines: {node: '>=14'}
+
+  sirv@3.0.0:
+    resolution: {integrity: sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==}
+    engines: {node: '>=18'}
+
+  socket.io-client@4.8.1:
+    resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==}
+    engines: {node: '>=10.0.0'}
+
+  socket.io-parser@4.2.4:
+    resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
+    engines: {node: '>=10.0.0'}
+
+  source-map-js@1.2.1:
+    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+    engines: {node: '>=0.10.0'}
+
+  speakingurl@14.0.1:
+    resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
+    engines: {node: '>=0.10.0'}
+
+  strip-final-newline@3.0.0:
+    resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
+    engines: {node: '>=12'}
+
+  strip-json-comments@3.1.1:
+    resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+    engines: {node: '>=8'}
+
+  superjson@2.2.1:
+    resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==}
+    engines: {node: '>=16'}
+
+  supports-color@7.2.0:
+    resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+    engines: {node: '>=8'}
+
+  svg-tags@1.0.0:
+    resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
+
+  synckit@0.9.2:
+    resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==}
+    engines: {node: ^14.18.0 || >=16.0.0}
+
+  to-regex-range@5.0.1:
+    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+    engines: {node: '>=8.0'}
+
+  totalist@3.0.1:
+    resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
+    engines: {node: '>=6'}
+
+  ts-api-utils@1.4.0:
+    resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==}
+    engines: {node: '>=16'}
+    peerDependencies:
+      typescript: '>=4.2.0'
+
+  tslib@2.8.1:
+    resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+  type-check@0.4.0:
+    resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+    engines: {node: '>= 0.8.0'}
+
+  type-fest@0.20.2:
+    resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
+    engines: {node: '>=10'}
+
+  typescript-eslint@8.15.0:
+    resolution: {integrity: sha512-wY4FRGl0ZI+ZU4Jo/yjdBu0lVTSML58pu6PgGtJmCufvzfV565pUF6iACQt092uFOd49iLOTX/sEVmHtbSrS+w==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    peerDependencies:
+      eslint: ^8.57.0 || ^9.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  typescript@5.6.3:
+    resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
+    engines: {node: '>=14.17'}
+    hasBin: true
+
+  undici-types@6.19.8:
+    resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+
+  universalify@2.0.1:
+    resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
+    engines: {node: '>= 10.0.0'}
+
+  update-browserslist-db@1.1.1:
+    resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==}
+    hasBin: true
+    peerDependencies:
+      browserslist: '>= 4.21.0'
+
+  uri-js@4.4.1:
+    resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+  util-deprecate@1.0.2:
+    resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+  vite-hot-client@0.2.3:
+    resolution: {integrity: sha512-rOGAV7rUlUHX89fP2p2v0A2WWvV3QMX2UYq0fRqsWSvFvev4atHWqjwGoKaZT1VTKyLGk533ecu3eyd0o59CAg==}
+    peerDependencies:
+      vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0
+
+  vite-plugin-inspect@0.8.8:
+    resolution: {integrity: sha512-aZlBuXsWUPJFmMK92GIv6lH7LrwG2POu4KJ+aEdcqnu92OAf+rhBnfMDQvxIJPEB7hE2t5EyY/PMgf5aDLT8EA==}
+    engines: {node: '>=14'}
+    peerDependencies:
+      '@nuxt/kit': '*'
+      vite: ^3.1.0 || ^4.0.0 || ^5.0.0-0
+    peerDependenciesMeta:
+      '@nuxt/kit':
+        optional: true
+
+  vite-plugin-vue-devtools@7.6.4:
+    resolution: {integrity: sha512-jxSsLyuETfmZ1OSrmnDp28BG6rmURrP7lkeyHW2gBFDyo+4dUcqVeQNMhbV7uKZn80mDdv06Mysw/5AdGxDvJQ==}
+    engines: {node: '>=v14.21.3'}
+    peerDependencies:
+      vite: ^3.1.0 || ^4.0.0-0 || ^5.0.0-0
+
+  vite-plugin-vue-inspector@5.2.0:
+    resolution: {integrity: sha512-wWxyb9XAtaIvV/Lr7cqB1HIzmHZFVUJsTNm3yAxkS87dgh/Ky4qr2wDEWNxF23fdhVa3jQ8MZREpr4XyiuaRqA==}
+    peerDependencies:
+      vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0
+
+  vite@5.4.11:
+    resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==}
+    engines: {node: ^18.0.0 || >=20.0.0}
+    hasBin: true
+    peerDependencies:
+      '@types/node': ^18.0.0 || >=20.0.0
+      less: '*'
+      lightningcss: ^1.21.0
+      sass: '*'
+      sass-embedded: '*'
+      stylus: '*'
+      sugarss: '*'
+      terser: ^5.4.0
+    peerDependenciesMeta:
+      '@types/node':
+        optional: true
+      less:
+        optional: true
+      lightningcss:
+        optional: true
+      sass:
+        optional: true
+      sass-embedded:
+        optional: true
+      stylus:
+        optional: true
+      sugarss:
+        optional: true
+      terser:
+        optional: true
+
+  vscode-uri@3.0.8:
+    resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
+
+  vue-demi@0.14.10:
+    resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
+    engines: {node: '>=12'}
+    hasBin: true
+    peerDependencies:
+      '@vue/composition-api': ^1.0.0-rc.1
+      vue: ^3.0.0-0 || ^2.6.0
+    peerDependenciesMeta:
+      '@vue/composition-api':
+        optional: true
+
+  vue-eslint-parser@9.4.3:
+    resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==}
+    engines: {node: ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: '>=6.0.0'
+
+  vue-router@4.4.5:
+    resolution: {integrity: sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==}
+    peerDependencies:
+      vue: ^3.2.0
+
+  vue-tsc@2.1.10:
+    resolution: {integrity: sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==}
+    hasBin: true
+    peerDependencies:
+      typescript: '>=5.0.0'
+
+  vue@3.5.13:
+    resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  which@2.0.2:
+    resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+    engines: {node: '>= 8'}
+    hasBin: true
+
+  which@5.0.0:
+    resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==}
+    engines: {node: ^18.17.0 || >=20.5.0}
+    hasBin: true
+
+  word-wrap@1.2.5:
+    resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+    engines: {node: '>=0.10.0'}
+
+  ws@8.17.1:
+    resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
+    engines: {node: '>=10.0.0'}
+    peerDependencies:
+      bufferutil: ^4.0.1
+      utf-8-validate: '>=5.0.2'
+    peerDependenciesMeta:
+      bufferutil:
+        optional: true
+      utf-8-validate:
+        optional: true
+
+  xml-name-validator@4.0.0:
+    resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
+    engines: {node: '>=12'}
+
+  xmlhttprequest-ssl@2.1.2:
+    resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
+    engines: {node: '>=0.4.0'}
+
+  yallist@3.1.1:
+    resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+  yocto-queue@0.1.0:
+    resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+    engines: {node: '>=10'}
+
+snapshots:
+
+  '@ampproject/remapping@2.3.0':
+    dependencies:
+      '@jridgewell/gen-mapping': 0.3.5
+      '@jridgewell/trace-mapping': 0.3.25
+
+  '@antfu/utils@0.7.10': {}
+
+  '@babel/code-frame@7.26.2':
+    dependencies:
+      '@babel/helper-validator-identifier': 7.25.9
+      js-tokens: 4.0.0
+      picocolors: 1.1.1
+
+  '@babel/compat-data@7.26.2': {}
+
+  '@babel/core@7.26.0':
+    dependencies:
+      '@ampproject/remapping': 2.3.0
+      '@babel/code-frame': 7.26.2
+      '@babel/generator': 7.26.2
+      '@babel/helper-compilation-targets': 7.25.9
+      '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0)
+      '@babel/helpers': 7.26.0
+      '@babel/parser': 7.26.2
+      '@babel/template': 7.25.9
+      '@babel/traverse': 7.25.9
+      '@babel/types': 7.26.0
+      convert-source-map: 2.0.0
+      debug: 4.3.7
+      gensync: 1.0.0-beta.2
+      json5: 2.2.3
+      semver: 6.3.1
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/generator@7.26.2':
+    dependencies:
+      '@babel/parser': 7.26.2
+      '@babel/types': 7.26.0
+      '@jridgewell/gen-mapping': 0.3.5
+      '@jridgewell/trace-mapping': 0.3.25
+      jsesc: 3.0.2
+
+  '@babel/helper-annotate-as-pure@7.25.9':
+    dependencies:
+      '@babel/types': 7.26.0
+
+  '@babel/helper-compilation-targets@7.25.9':
+    dependencies:
+      '@babel/compat-data': 7.26.2
+      '@babel/helper-validator-option': 7.25.9
+      browserslist: 4.24.2
+      lru-cache: 5.1.1
+      semver: 6.3.1
+
+  '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.26.0)':
+    dependencies:
+      '@babel/core': 7.26.0
+      '@babel/helper-annotate-as-pure': 7.25.9
+      '@babel/helper-member-expression-to-functions': 7.25.9
+      '@babel/helper-optimise-call-expression': 7.25.9
+      '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0)
+      '@babel/helper-skip-transparent-expression-wrappers': 7.25.9
+      '@babel/traverse': 7.25.9
+      semver: 6.3.1
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-member-expression-to-functions@7.25.9':
+    dependencies:
+      '@babel/traverse': 7.25.9
+      '@babel/types': 7.26.0
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-module-imports@7.25.9':
+    dependencies:
+      '@babel/traverse': 7.25.9
+      '@babel/types': 7.26.0
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)':
+    dependencies:
+      '@babel/core': 7.26.0
+      '@babel/helper-module-imports': 7.25.9
+      '@babel/helper-validator-identifier': 7.25.9
+      '@babel/traverse': 7.25.9
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-optimise-call-expression@7.25.9':
+    dependencies:
+      '@babel/types': 7.26.0
+
+  '@babel/helper-plugin-utils@7.25.9': {}
+
+  '@babel/helper-replace-supers@7.25.9(@babel/core@7.26.0)':
+    dependencies:
+      '@babel/core': 7.26.0
+      '@babel/helper-member-expression-to-functions': 7.25.9
+      '@babel/helper-optimise-call-expression': 7.25.9
+      '@babel/traverse': 7.25.9
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-skip-transparent-expression-wrappers@7.25.9':
+    dependencies:
+      '@babel/traverse': 7.25.9
+      '@babel/types': 7.26.0
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-string-parser@7.25.9': {}
+
+  '@babel/helper-validator-identifier@7.25.9': {}
+
+  '@babel/helper-validator-option@7.25.9': {}
+
+  '@babel/helpers@7.26.0':
+    dependencies:
+      '@babel/template': 7.25.9
+      '@babel/types': 7.26.0
+
+  '@babel/parser@7.26.2':
+    dependencies:
+      '@babel/types': 7.26.0
+
+  '@babel/plugin-proposal-decorators@7.25.9(@babel/core@7.26.0)':
+    dependencies:
+      '@babel/core': 7.26.0
+      '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0)
+      '@babel/helper-plugin-utils': 7.25.9
+      '@babel/plugin-syntax-decorators': 7.25.9(@babel/core@7.26.0)
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/plugin-syntax-decorators@7.25.9(@babel/core@7.26.0)':
+    dependencies:
+      '@babel/core': 7.26.0
+      '@babel/helper-plugin-utils': 7.25.9
+
+  '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)':
+    dependencies:
+      '@babel/core': 7.26.0
+      '@babel/helper-plugin-utils': 7.25.9
+
+  '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)':
+    dependencies:
+      '@babel/core': 7.26.0
+      '@babel/helper-plugin-utils': 7.25.9
+
+  '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)':
+    dependencies:
+      '@babel/core': 7.26.0
+      '@babel/helper-plugin-utils': 7.25.9
+
+  '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)':
+    dependencies:
+      '@babel/core': 7.26.0
+      '@babel/helper-plugin-utils': 7.25.9
+
+  '@babel/plugin-transform-typescript@7.25.9(@babel/core@7.26.0)':
+    dependencies:
+      '@babel/core': 7.26.0
+      '@babel/helper-annotate-as-pure': 7.25.9
+      '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0)
+      '@babel/helper-plugin-utils': 7.25.9
+      '@babel/helper-skip-transparent-expression-wrappers': 7.25.9
+      '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0)
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/template@7.25.9':
+    dependencies:
+      '@babel/code-frame': 7.26.2
+      '@babel/parser': 7.26.2
+      '@babel/types': 7.26.0
+
+  '@babel/traverse@7.25.9':
+    dependencies:
+      '@babel/code-frame': 7.26.2
+      '@babel/generator': 7.26.2
+      '@babel/parser': 7.26.2
+      '@babel/template': 7.25.9
+      '@babel/types': 7.26.0
+      debug: 4.3.7
+      globals: 11.12.0
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/types@7.26.0':
+    dependencies:
+      '@babel/helper-string-parser': 7.25.9
+      '@babel/helper-validator-identifier': 7.25.9
+
+  '@esbuild/aix-ppc64@0.21.5':
+    optional: true
+
+  '@esbuild/android-arm64@0.21.5':
+    optional: true
+
+  '@esbuild/android-arm@0.21.5':
+    optional: true
+
+  '@esbuild/android-x64@0.21.5':
+    optional: true
+
+  '@esbuild/darwin-arm64@0.21.5':
+    optional: true
+
+  '@esbuild/darwin-x64@0.21.5':
+    optional: true
+
+  '@esbuild/freebsd-arm64@0.21.5':
+    optional: true
+
+  '@esbuild/freebsd-x64@0.21.5':
+    optional: true
+
+  '@esbuild/linux-arm64@0.21.5':
+    optional: true
+
+  '@esbuild/linux-arm@0.21.5':
+    optional: true
+
+  '@esbuild/linux-ia32@0.21.5':
+    optional: true
+
+  '@esbuild/linux-loong64@0.21.5':
+    optional: true
+
+  '@esbuild/linux-mips64el@0.21.5':
+    optional: true
+
+  '@esbuild/linux-ppc64@0.21.5':
+    optional: true
+
+  '@esbuild/linux-riscv64@0.21.5':
+    optional: true
+
+  '@esbuild/linux-s390x@0.21.5':
+    optional: true
+
+  '@esbuild/linux-x64@0.21.5':
+    optional: true
+
+  '@esbuild/netbsd-x64@0.21.5':
+    optional: true
+
+  '@esbuild/openbsd-x64@0.21.5':
+    optional: true
+
+  '@esbuild/sunos-x64@0.21.5':
+    optional: true
+
+  '@esbuild/win32-arm64@0.21.5':
+    optional: true
+
+  '@esbuild/win32-ia32@0.21.5':
+    optional: true
+
+  '@esbuild/win32-x64@0.21.5':
+    optional: true
+
+  '@eslint-community/eslint-utils@4.4.1(eslint@9.15.0)':
+    dependencies:
+      eslint: 9.15.0
+      eslint-visitor-keys: 3.4.3
+
+  '@eslint-community/regexpp@4.12.1': {}
+
+  '@eslint/config-array@0.19.0':
+    dependencies:
+      '@eslint/object-schema': 2.1.4
+      debug: 4.3.7
+      minimatch: 3.1.2
+    transitivePeerDependencies:
+      - supports-color
+
+  '@eslint/core@0.9.0': {}
+
+  '@eslint/eslintrc@3.2.0':
+    dependencies:
+      ajv: 6.12.6
+      debug: 4.3.7
+      espree: 10.3.0
+      globals: 14.0.0
+      ignore: 5.3.2
+      import-fresh: 3.3.0
+      js-yaml: 4.1.0
+      minimatch: 3.1.2
+      strip-json-comments: 3.1.1
+    transitivePeerDependencies:
+      - supports-color
+
+  '@eslint/js@9.15.0': {}
+
+  '@eslint/object-schema@2.1.4': {}
+
+  '@eslint/plugin-kit@0.2.3':
+    dependencies:
+      levn: 0.4.1
+
+  '@humanfs/core@0.19.1': {}
+
+  '@humanfs/node@0.16.6':
+    dependencies:
+      '@humanfs/core': 0.19.1
+      '@humanwhocodes/retry': 0.3.1
+
+  '@humanwhocodes/module-importer@1.0.1': {}
+
+  '@humanwhocodes/retry@0.3.1': {}
+
+  '@humanwhocodes/retry@0.4.1': {}
+
+  '@jridgewell/gen-mapping@0.3.5':
+    dependencies:
+      '@jridgewell/set-array': 1.2.1
+      '@jridgewell/sourcemap-codec': 1.5.0
+      '@jridgewell/trace-mapping': 0.3.25
+
+  '@jridgewell/resolve-uri@3.1.2': {}
+
+  '@jridgewell/set-array@1.2.1': {}
+
+  '@jridgewell/sourcemap-codec@1.5.0': {}
+
+  '@jridgewell/trace-mapping@0.3.25':
+    dependencies:
+      '@jridgewell/resolve-uri': 3.1.2
+      '@jridgewell/sourcemap-codec': 1.5.0
+
+  '@nodelib/fs.scandir@2.1.5':
+    dependencies:
+      '@nodelib/fs.stat': 2.0.5
+      run-parallel: 1.2.0
+
+  '@nodelib/fs.stat@2.0.5': {}
+
+  '@nodelib/fs.walk@1.2.8':
+    dependencies:
+      '@nodelib/fs.scandir': 2.1.5
+      fastq: 1.17.1
+
+  '@pkgr/core@0.1.1': {}
+
+  '@polka/url@1.0.0-next.28': {}
+
+  '@rollup/pluginutils@5.1.3(rollup@4.27.3)':
+    dependencies:
+      '@types/estree': 1.0.6
+      estree-walker: 2.0.2
+      picomatch: 4.0.2
+    optionalDependencies:
+      rollup: 4.27.3
+
+  '@rollup/rollup-android-arm-eabi@4.27.3':
+    optional: true
+
+  '@rollup/rollup-android-arm64@4.27.3':
+    optional: true
+
+  '@rollup/rollup-darwin-arm64@4.27.3':
+    optional: true
+
+  '@rollup/rollup-darwin-x64@4.27.3':
+    optional: true
+
+  '@rollup/rollup-freebsd-arm64@4.27.3':
+    optional: true
+
+  '@rollup/rollup-freebsd-x64@4.27.3':
+    optional: true
+
+  '@rollup/rollup-linux-arm-gnueabihf@4.27.3':
+    optional: true
+
+  '@rollup/rollup-linux-arm-musleabihf@4.27.3':
+    optional: true
+
+  '@rollup/rollup-linux-arm64-gnu@4.27.3':
+    optional: true
+
+  '@rollup/rollup-linux-arm64-musl@4.27.3':
+    optional: true
+
+  '@rollup/rollup-linux-powerpc64le-gnu@4.27.3':
+    optional: true
+
+  '@rollup/rollup-linux-riscv64-gnu@4.27.3':
+    optional: true
+
+  '@rollup/rollup-linux-s390x-gnu@4.27.3':
+    optional: true
+
+  '@rollup/rollup-linux-x64-gnu@4.27.3':
+    optional: true
+
+  '@rollup/rollup-linux-x64-musl@4.27.3':
+    optional: true
+
+  '@rollup/rollup-win32-arm64-msvc@4.27.3':
+    optional: true
+
+  '@rollup/rollup-win32-ia32-msvc@4.27.3':
+    optional: true
+
+  '@rollup/rollup-win32-x64-msvc@4.27.3':
+    optional: true
+
+  '@socket.io/component-emitter@3.1.2': {}
+
+  '@tsconfig/node22@22.0.0': {}
+
+  '@types/d3-array@3.2.1': {}
+
+  '@types/d3-axis@3.0.6':
+    dependencies:
+      '@types/d3-selection': 3.0.11
+
+  '@types/d3-brush@3.0.6':
+    dependencies:
+      '@types/d3-selection': 3.0.11
+
+  '@types/d3-chord@3.0.6': {}
+
+  '@types/d3-color@3.1.3': {}
+
+  '@types/d3-contour@3.0.6':
+    dependencies:
+      '@types/d3-array': 3.2.1
+      '@types/geojson': 7946.0.14
+
+  '@types/d3-delaunay@6.0.4': {}
+
+  '@types/d3-dispatch@3.0.6': {}
+
+  '@types/d3-drag@3.0.7':
+    dependencies:
+      '@types/d3-selection': 3.0.11
+
+  '@types/d3-dsv@3.0.7': {}
+
+  '@types/d3-ease@3.0.2': {}
+
+  '@types/d3-fetch@3.0.7':
+    dependencies:
+      '@types/d3-dsv': 3.0.7
+
+  '@types/d3-force@3.0.10': {}
+
+  '@types/d3-format@3.0.4': {}
+
+  '@types/d3-geo@3.1.0':
+    dependencies:
+      '@types/geojson': 7946.0.14
+
+  '@types/d3-hierarchy@3.1.7': {}
+
+  '@types/d3-interpolate@3.0.4':
+    dependencies:
+      '@types/d3-color': 3.1.3
+
+  '@types/d3-path@3.1.0': {}
+
+  '@types/d3-polygon@3.0.2': {}
+
+  '@types/d3-quadtree@3.0.6': {}
+
+  '@types/d3-random@3.0.3': {}
+
+  '@types/d3-scale-chromatic@3.0.3': {}
+
+  '@types/d3-scale@4.0.8':
+    dependencies:
+      '@types/d3-time': 3.0.3
+
+  '@types/d3-selection@3.0.11': {}
+
+  '@types/d3-shape@3.1.6':
+    dependencies:
+      '@types/d3-path': 3.1.0
+
+  '@types/d3-time-format@4.0.3': {}
+
+  '@types/d3-time@3.0.3': {}
+
+  '@types/d3-timer@3.0.2': {}
+
+  '@types/d3-transition@3.0.9':
+    dependencies:
+      '@types/d3-selection': 3.0.11
+
+  '@types/d3-zoom@3.0.8':
+    dependencies:
+      '@types/d3-interpolate': 3.0.4
+      '@types/d3-selection': 3.0.11
+
+  '@types/d3@7.4.3':
+    dependencies:
+      '@types/d3-array': 3.2.1
+      '@types/d3-axis': 3.0.6
+      '@types/d3-brush': 3.0.6
+      '@types/d3-chord': 3.0.6
+      '@types/d3-color': 3.1.3
+      '@types/d3-contour': 3.0.6
+      '@types/d3-delaunay': 6.0.4
+      '@types/d3-dispatch': 3.0.6
+      '@types/d3-drag': 3.0.7
+      '@types/d3-dsv': 3.0.7
+      '@types/d3-ease': 3.0.2
+      '@types/d3-fetch': 3.0.7
+      '@types/d3-force': 3.0.10
+      '@types/d3-format': 3.0.4
+      '@types/d3-geo': 3.1.0
+      '@types/d3-hierarchy': 3.1.7
+      '@types/d3-interpolate': 3.0.4
+      '@types/d3-path': 3.1.0
+      '@types/d3-polygon': 3.0.2
+      '@types/d3-quadtree': 3.0.6
+      '@types/d3-random': 3.0.3
+      '@types/d3-scale': 4.0.8
+      '@types/d3-scale-chromatic': 3.0.3
+      '@types/d3-selection': 3.0.11
+      '@types/d3-shape': 3.1.6
+      '@types/d3-time': 3.0.3
+      '@types/d3-time-format': 4.0.3
+      '@types/d3-timer': 3.0.2
+      '@types/d3-transition': 3.0.9
+      '@types/d3-zoom': 3.0.8
+
+  '@types/estree@1.0.6': {}
+
+  '@types/geojson@7946.0.14': {}
+
+  '@types/json-schema@7.0.15': {}
+
+  '@types/node@22.9.1':
+    dependencies:
+      undici-types: 6.19.8
+
+  '@typescript-eslint/eslint-plugin@8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3)':
+    dependencies:
+      '@eslint-community/regexpp': 4.12.1
+      '@typescript-eslint/parser': 8.15.0(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/scope-manager': 8.15.0
+      '@typescript-eslint/type-utils': 8.15.0(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/utils': 8.15.0(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/visitor-keys': 8.15.0
+      eslint: 9.15.0
+      graphemer: 1.4.0
+      ignore: 5.3.2
+      natural-compare: 1.4.0
+      ts-api-utils: 1.4.0(typescript@5.6.3)
+    optionalDependencies:
+      typescript: 5.6.3
+    transitivePeerDependencies:
+      - supports-color
+
+  '@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3)':
+    dependencies:
+      '@typescript-eslint/scope-manager': 8.15.0
+      '@typescript-eslint/types': 8.15.0
+      '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3)
+      '@typescript-eslint/visitor-keys': 8.15.0
+      debug: 4.3.7
+      eslint: 9.15.0
+    optionalDependencies:
+      typescript: 5.6.3
+    transitivePeerDependencies:
+      - supports-color
+
+  '@typescript-eslint/scope-manager@8.15.0':
+    dependencies:
+      '@typescript-eslint/types': 8.15.0
+      '@typescript-eslint/visitor-keys': 8.15.0
+
+  '@typescript-eslint/type-utils@8.15.0(eslint@9.15.0)(typescript@5.6.3)':
+    dependencies:
+      '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3)
+      '@typescript-eslint/utils': 8.15.0(eslint@9.15.0)(typescript@5.6.3)
+      debug: 4.3.7
+      eslint: 9.15.0
+      ts-api-utils: 1.4.0(typescript@5.6.3)
+    optionalDependencies:
+      typescript: 5.6.3
+    transitivePeerDependencies:
+      - supports-color
+
+  '@typescript-eslint/types@8.15.0': {}
+
+  '@typescript-eslint/typescript-estree@8.15.0(typescript@5.6.3)':
+    dependencies:
+      '@typescript-eslint/types': 8.15.0
+      '@typescript-eslint/visitor-keys': 8.15.0
+      debug: 4.3.7
+      fast-glob: 3.3.2
+      is-glob: 4.0.3
+      minimatch: 9.0.5
+      semver: 7.6.3
+      ts-api-utils: 1.4.0(typescript@5.6.3)
+    optionalDependencies:
+      typescript: 5.6.3
+    transitivePeerDependencies:
+      - supports-color
+
+  '@typescript-eslint/utils@8.15.0(eslint@9.15.0)(typescript@5.6.3)':
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0)
+      '@typescript-eslint/scope-manager': 8.15.0
+      '@typescript-eslint/types': 8.15.0
+      '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3)
+      eslint: 9.15.0
+    optionalDependencies:
+      typescript: 5.6.3
+    transitivePeerDependencies:
+      - supports-color
+
+  '@typescript-eslint/visitor-keys@8.15.0':
+    dependencies:
+      '@typescript-eslint/types': 8.15.0
+      eslint-visitor-keys: 4.2.0
+
+  '@vitejs/plugin-vue@5.2.0(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3))':
+    dependencies:
+      vite: 5.4.11(@types/node@22.9.1)
+      vue: 3.5.13(typescript@5.6.3)
+
+  '@volar/language-core@2.4.10':
+    dependencies:
+      '@volar/source-map': 2.4.10
+
+  '@volar/source-map@2.4.10': {}
+
+  '@volar/typescript@2.4.10':
+    dependencies:
+      '@volar/language-core': 2.4.10
+      path-browserify: 1.0.1
+      vscode-uri: 3.0.8
+
+  '@vue/babel-helper-vue-transform-on@1.2.5': {}
+
+  '@vue/babel-plugin-jsx@1.2.5(@babel/core@7.26.0)':
+    dependencies:
+      '@babel/helper-module-imports': 7.25.9
+      '@babel/helper-plugin-utils': 7.25.9
+      '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0)
+      '@babel/template': 7.25.9
+      '@babel/traverse': 7.25.9
+      '@babel/types': 7.26.0
+      '@vue/babel-helper-vue-transform-on': 1.2.5
+      '@vue/babel-plugin-resolve-type': 1.2.5(@babel/core@7.26.0)
+      html-tags: 3.3.1
+      svg-tags: 1.0.0
+    optionalDependencies:
+      '@babel/core': 7.26.0
+    transitivePeerDependencies:
+      - supports-color
+
+  '@vue/babel-plugin-resolve-type@1.2.5(@babel/core@7.26.0)':
+    dependencies:
+      '@babel/code-frame': 7.26.2
+      '@babel/core': 7.26.0
+      '@babel/helper-module-imports': 7.25.9
+      '@babel/helper-plugin-utils': 7.25.9
+      '@babel/parser': 7.26.2
+      '@vue/compiler-sfc': 3.5.13
+    transitivePeerDependencies:
+      - supports-color
+
+  '@vue/compiler-core@3.5.13':
+    dependencies:
+      '@babel/parser': 7.26.2
+      '@vue/shared': 3.5.13
+      entities: 4.5.0
+      estree-walker: 2.0.2
+      source-map-js: 1.2.1
+
+  '@vue/compiler-dom@3.5.13':
+    dependencies:
+      '@vue/compiler-core': 3.5.13
+      '@vue/shared': 3.5.13
+
+  '@vue/compiler-sfc@3.5.13':
+    dependencies:
+      '@babel/parser': 7.26.2
+      '@vue/compiler-core': 3.5.13
+      '@vue/compiler-dom': 3.5.13
+      '@vue/compiler-ssr': 3.5.13
+      '@vue/shared': 3.5.13
+      estree-walker: 2.0.2
+      magic-string: 0.30.13
+      postcss: 8.4.49
+      source-map-js: 1.2.1
+
+  '@vue/compiler-ssr@3.5.13':
+    dependencies:
+      '@vue/compiler-dom': 3.5.13
+      '@vue/shared': 3.5.13
+
+  '@vue/compiler-vue2@2.7.16':
+    dependencies:
+      de-indent: 1.0.2
+      he: 1.2.0
+
+  '@vue/devtools-api@6.6.4': {}
+
+  '@vue/devtools-core@7.6.4(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3))':
+    dependencies:
+      '@vue/devtools-kit': 7.6.4
+      '@vue/devtools-shared': 7.6.4
+      mitt: 3.0.1
+      nanoid: 3.3.7
+      pathe: 1.1.2
+      vite-hot-client: 0.2.3(vite@5.4.11(@types/node@22.9.1))
+      vue: 3.5.13(typescript@5.6.3)
+    transitivePeerDependencies:
+      - vite
+
+  '@vue/devtools-kit@7.6.4':
+    dependencies:
+      '@vue/devtools-shared': 7.6.4
+      birpc: 0.2.19
+      hookable: 5.5.3
+      mitt: 3.0.1
+      perfect-debounce: 1.0.0
+      speakingurl: 14.0.1
+      superjson: 2.2.1
+
+  '@vue/devtools-shared@7.6.4':
+    dependencies:
+      rfdc: 1.4.1
+
+  '@vue/eslint-config-prettier@10.1.0(eslint@9.15.0)(prettier@3.3.3)':
+    dependencies:
+      eslint: 9.15.0
+      eslint-config-prettier: 9.1.0(eslint@9.15.0)
+      eslint-plugin-prettier: 5.2.1(eslint-config-prettier@9.1.0(eslint@9.15.0))(eslint@9.15.0)(prettier@3.3.3)
+      prettier: 3.3.3
+    transitivePeerDependencies:
+      - '@types/eslint'
+
+  '@vue/eslint-config-typescript@14.1.3(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint-plugin-vue@9.31.0(eslint@9.15.0))(eslint@9.15.0)(typescript@5.6.3)':
+    dependencies:
+      '@typescript-eslint/eslint-plugin': 8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3)
+      eslint: 9.15.0
+      eslint-plugin-vue: 9.31.0(eslint@9.15.0)
+      fast-glob: 3.3.2
+      typescript-eslint: 8.15.0(eslint@9.15.0)(typescript@5.6.3)
+      vue-eslint-parser: 9.4.3(eslint@9.15.0)
+    optionalDependencies:
+      typescript: 5.6.3
+    transitivePeerDependencies:
+      - '@typescript-eslint/parser'
+      - supports-color
+
+  '@vue/language-core@2.1.10(typescript@5.6.3)':
+    dependencies:
+      '@volar/language-core': 2.4.10
+      '@vue/compiler-dom': 3.5.13
+      '@vue/compiler-vue2': 2.7.16
+      '@vue/shared': 3.5.13
+      alien-signals: 0.2.2
+      minimatch: 9.0.5
+      muggle-string: 0.4.1
+      path-browserify: 1.0.1
+    optionalDependencies:
+      typescript: 5.6.3
+
+  '@vue/reactivity@3.5.13':
+    dependencies:
+      '@vue/shared': 3.5.13
+
+  '@vue/runtime-core@3.5.13':
+    dependencies:
+      '@vue/reactivity': 3.5.13
+      '@vue/shared': 3.5.13
+
+  '@vue/runtime-dom@3.5.13':
+    dependencies:
+      '@vue/reactivity': 3.5.13
+      '@vue/runtime-core': 3.5.13
+      '@vue/shared': 3.5.13
+      csstype: 3.1.3
+
+  '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.6.3))':
+    dependencies:
+      '@vue/compiler-ssr': 3.5.13
+      '@vue/shared': 3.5.13
+      vue: 3.5.13(typescript@5.6.3)
+
+  '@vue/shared@3.5.13': {}
+
+  '@vue/tsconfig@0.5.1': {}
+
+  acorn-jsx@5.3.2(acorn@8.14.0):
+    dependencies:
+      acorn: 8.14.0
+
+  acorn@8.14.0: {}
+
+  ajv@6.12.6:
+    dependencies:
+      fast-deep-equal: 3.1.3
+      fast-json-stable-stringify: 2.1.0
+      json-schema-traverse: 0.4.1
+      uri-js: 4.4.1
+
+  alien-signals@0.2.2: {}
+
+  ansi-styles@4.3.0:
+    dependencies:
+      color-convert: 2.0.1
+
+  ansi-styles@6.2.1: {}
+
+  argparse@2.0.1: {}
+
+  balanced-match@1.0.2: {}
+
+  birpc@0.2.19: {}
+
+  boolbase@1.0.0: {}
+
+  brace-expansion@1.1.11:
+    dependencies:
+      balanced-match: 1.0.2
+      concat-map: 0.0.1
+
+  brace-expansion@2.0.1:
+    dependencies:
+      balanced-match: 1.0.2
+
+  braces@3.0.3:
+    dependencies:
+      fill-range: 7.1.1
+
+  browserslist@4.24.2:
+    dependencies:
+      caniuse-lite: 1.0.30001683
+      electron-to-chromium: 1.5.63
+      node-releases: 2.0.18
+      update-browserslist-db: 1.1.1(browserslist@4.24.2)
+
+  bundle-name@4.1.0:
+    dependencies:
+      run-applescript: 7.0.0
+
+  callsites@3.1.0: {}
+
+  caniuse-lite@1.0.30001683: {}
+
+  chalk@4.1.2:
+    dependencies:
+      ansi-styles: 4.3.0
+      supports-color: 7.2.0
+
+  color-convert@2.0.1:
+    dependencies:
+      color-name: 1.1.4
+
+  color-name@1.1.4: {}
+
+  commander@7.2.0: {}
+
+  concat-map@0.0.1: {}
+
+  convert-source-map@2.0.0: {}
+
+  copy-anything@3.0.5:
+    dependencies:
+      is-what: 4.1.16
+
+  cross-spawn@7.0.6:
+    dependencies:
+      path-key: 3.1.1
+      shebang-command: 2.0.0
+      which: 2.0.2
+
+  cssesc@3.0.0: {}
+
+  csstype@3.1.3: {}
+
+  d3-array@3.2.4:
+    dependencies:
+      internmap: 2.0.3
+
+  d3-axis@3.0.0: {}
+
+  d3-brush@3.0.0:
+    dependencies:
+      d3-dispatch: 3.0.1
+      d3-drag: 3.0.0
+      d3-interpolate: 3.0.1
+      d3-selection: 3.0.0
+      d3-transition: 3.0.1(d3-selection@3.0.0)
+
+  d3-chord@3.0.1:
+    dependencies:
+      d3-path: 3.1.0
+
+  d3-color@3.1.0: {}
+
+  d3-contour@4.0.2:
+    dependencies:
+      d3-array: 3.2.4
+
+  d3-delaunay@6.0.4:
+    dependencies:
+      delaunator: 5.0.1
+
+  d3-dispatch@3.0.1: {}
+
+  d3-drag@3.0.0:
+    dependencies:
+      d3-dispatch: 3.0.1
+      d3-selection: 3.0.0
+
+  d3-dsv@3.0.1:
+    dependencies:
+      commander: 7.2.0
+      iconv-lite: 0.6.3
+      rw: 1.3.3
+
+  d3-ease@3.0.1: {}
+
+  d3-fetch@3.0.1:
+    dependencies:
+      d3-dsv: 3.0.1
+
+  d3-force@3.0.0:
+    dependencies:
+      d3-dispatch: 3.0.1
+      d3-quadtree: 3.0.1
+      d3-timer: 3.0.1
+
+  d3-format@3.1.0: {}
+
+  d3-geo@3.1.1:
+    dependencies:
+      d3-array: 3.2.4
+
+  d3-hierarchy@3.1.2: {}
+
+  d3-interpolate@3.0.1:
+    dependencies:
+      d3-color: 3.1.0
+
+  d3-path@3.1.0: {}
+
+  d3-polygon@3.0.1: {}
+
+  d3-quadtree@3.0.1: {}
+
+  d3-random@3.0.1: {}
+
+  d3-scale-chromatic@3.1.0:
+    dependencies:
+      d3-color: 3.1.0
+      d3-interpolate: 3.0.1
+
+  d3-scale@4.0.2:
+    dependencies:
+      d3-array: 3.2.4
+      d3-format: 3.1.0
+      d3-interpolate: 3.0.1
+      d3-time: 3.1.0
+      d3-time-format: 4.1.0
+
+  d3-selection@3.0.0: {}
+
+  d3-shape@3.2.0:
+    dependencies:
+      d3-path: 3.1.0
+
+  d3-time-format@4.1.0:
+    dependencies:
+      d3-time: 3.1.0
+
+  d3-time@3.1.0:
+    dependencies:
+      d3-array: 3.2.4
+
+  d3-timer@3.0.1: {}
+
+  d3-transition@3.0.1(d3-selection@3.0.0):
+    dependencies:
+      d3-color: 3.1.0
+      d3-dispatch: 3.0.1
+      d3-ease: 3.0.1
+      d3-interpolate: 3.0.1
+      d3-selection: 3.0.0
+      d3-timer: 3.0.1
+
+  d3-zoom@3.0.0:
+    dependencies:
+      d3-dispatch: 3.0.1
+      d3-drag: 3.0.0
+      d3-interpolate: 3.0.1
+      d3-selection: 3.0.0
+      d3-transition: 3.0.1(d3-selection@3.0.0)
+
+  d3@7.9.0:
+    dependencies:
+      d3-array: 3.2.4
+      d3-axis: 3.0.0
+      d3-brush: 3.0.0
+      d3-chord: 3.0.1
+      d3-color: 3.1.0
+      d3-contour: 4.0.2
+      d3-delaunay: 6.0.4
+      d3-dispatch: 3.0.1
+      d3-drag: 3.0.0
+      d3-dsv: 3.0.1
+      d3-ease: 3.0.1
+      d3-fetch: 3.0.1
+      d3-force: 3.0.0
+      d3-format: 3.1.0
+      d3-geo: 3.1.1
+      d3-hierarchy: 3.1.2
+      d3-interpolate: 3.0.1
+      d3-path: 3.1.0
+      d3-polygon: 3.0.1
+      d3-quadtree: 3.0.1
+      d3-random: 3.0.1
+      d3-scale: 4.0.2
+      d3-scale-chromatic: 3.1.0
+      d3-selection: 3.0.0
+      d3-shape: 3.2.0
+      d3-time: 3.1.0
+      d3-time-format: 4.1.0
+      d3-timer: 3.0.1
+      d3-transition: 3.0.1(d3-selection@3.0.0)
+      d3-zoom: 3.0.0
+
+  de-indent@1.0.2: {}
+
+  debug@4.3.7:
+    dependencies:
+      ms: 2.1.3
+
+  deep-is@0.1.4: {}
+
+  default-browser-id@5.0.0: {}
+
+  default-browser@5.2.1:
+    dependencies:
+      bundle-name: 4.1.0
+      default-browser-id: 5.0.0
+
+  define-lazy-prop@3.0.0: {}
+
+  delaunator@5.0.1:
+    dependencies:
+      robust-predicates: 3.0.2
+
+  electron-to-chromium@1.5.63: {}
+
+  engine.io-client@6.6.2:
+    dependencies:
+      '@socket.io/component-emitter': 3.1.2
+      debug: 4.3.7
+      engine.io-parser: 5.2.3
+      ws: 8.17.1
+      xmlhttprequest-ssl: 2.1.2
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+
+  engine.io-parser@5.2.3: {}
+
+  entities@4.5.0: {}
+
+  error-stack-parser-es@0.1.5: {}
+
+  esbuild@0.21.5:
+    optionalDependencies:
+      '@esbuild/aix-ppc64': 0.21.5
+      '@esbuild/android-arm': 0.21.5
+      '@esbuild/android-arm64': 0.21.5
+      '@esbuild/android-x64': 0.21.5
+      '@esbuild/darwin-arm64': 0.21.5
+      '@esbuild/darwin-x64': 0.21.5
+      '@esbuild/freebsd-arm64': 0.21.5
+      '@esbuild/freebsd-x64': 0.21.5
+      '@esbuild/linux-arm': 0.21.5
+      '@esbuild/linux-arm64': 0.21.5
+      '@esbuild/linux-ia32': 0.21.5
+      '@esbuild/linux-loong64': 0.21.5
+      '@esbuild/linux-mips64el': 0.21.5
+      '@esbuild/linux-ppc64': 0.21.5
+      '@esbuild/linux-riscv64': 0.21.5
+      '@esbuild/linux-s390x': 0.21.5
+      '@esbuild/linux-x64': 0.21.5
+      '@esbuild/netbsd-x64': 0.21.5
+      '@esbuild/openbsd-x64': 0.21.5
+      '@esbuild/sunos-x64': 0.21.5
+      '@esbuild/win32-arm64': 0.21.5
+      '@esbuild/win32-ia32': 0.21.5
+      '@esbuild/win32-x64': 0.21.5
+
+  escalade@3.2.0: {}
+
+  escape-string-regexp@4.0.0: {}
+
+  eslint-config-prettier@9.1.0(eslint@9.15.0):
+    dependencies:
+      eslint: 9.15.0
+
+  eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.15.0))(eslint@9.15.0)(prettier@3.3.3):
+    dependencies:
+      eslint: 9.15.0
+      prettier: 3.3.3
+      prettier-linter-helpers: 1.0.0
+      synckit: 0.9.2
+    optionalDependencies:
+      eslint-config-prettier: 9.1.0(eslint@9.15.0)
+
+  eslint-plugin-vue@9.31.0(eslint@9.15.0):
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0)
+      eslint: 9.15.0
+      globals: 13.24.0
+      natural-compare: 1.4.0
+      nth-check: 2.1.1
+      postcss-selector-parser: 6.1.2
+      semver: 7.6.3
+      vue-eslint-parser: 9.4.3(eslint@9.15.0)
+      xml-name-validator: 4.0.0
+    transitivePeerDependencies:
+      - supports-color
+
+  eslint-scope@7.2.2:
+    dependencies:
+      esrecurse: 4.3.0
+      estraverse: 5.3.0
+
+  eslint-scope@8.2.0:
+    dependencies:
+      esrecurse: 4.3.0
+      estraverse: 5.3.0
+
+  eslint-visitor-keys@3.4.3: {}
+
+  eslint-visitor-keys@4.2.0: {}
+
+  eslint@9.15.0:
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0)
+      '@eslint-community/regexpp': 4.12.1
+      '@eslint/config-array': 0.19.0
+      '@eslint/core': 0.9.0
+      '@eslint/eslintrc': 3.2.0
+      '@eslint/js': 9.15.0
+      '@eslint/plugin-kit': 0.2.3
+      '@humanfs/node': 0.16.6
+      '@humanwhocodes/module-importer': 1.0.1
+      '@humanwhocodes/retry': 0.4.1
+      '@types/estree': 1.0.6
+      '@types/json-schema': 7.0.15
+      ajv: 6.12.6
+      chalk: 4.1.2
+      cross-spawn: 7.0.6
+      debug: 4.3.7
+      escape-string-regexp: 4.0.0
+      eslint-scope: 8.2.0
+      eslint-visitor-keys: 4.2.0
+      espree: 10.3.0
+      esquery: 1.6.0
+      esutils: 2.0.3
+      fast-deep-equal: 3.1.3
+      file-entry-cache: 8.0.0
+      find-up: 5.0.0
+      glob-parent: 6.0.2
+      ignore: 5.3.2
+      imurmurhash: 0.1.4
+      is-glob: 4.0.3
+      json-stable-stringify-without-jsonify: 1.0.1
+      lodash.merge: 4.6.2
+      minimatch: 3.1.2
+      natural-compare: 1.4.0
+      optionator: 0.9.4
+    transitivePeerDependencies:
+      - supports-color
+
+  espree@10.3.0:
+    dependencies:
+      acorn: 8.14.0
+      acorn-jsx: 5.3.2(acorn@8.14.0)
+      eslint-visitor-keys: 4.2.0
+
+  espree@9.6.1:
+    dependencies:
+      acorn: 8.14.0
+      acorn-jsx: 5.3.2(acorn@8.14.0)
+      eslint-visitor-keys: 3.4.3
+
+  esquery@1.6.0:
+    dependencies:
+      estraverse: 5.3.0
+
+  esrecurse@4.3.0:
+    dependencies:
+      estraverse: 5.3.0
+
+  estraverse@5.3.0: {}
+
+  estree-walker@2.0.2: {}
+
+  esutils@2.0.3: {}
+
+  execa@8.0.1:
+    dependencies:
+      cross-spawn: 7.0.6
+      get-stream: 8.0.1
+      human-signals: 5.0.0
+      is-stream: 3.0.0
+      merge-stream: 2.0.0
+      npm-run-path: 5.3.0
+      onetime: 6.0.0
+      signal-exit: 4.1.0
+      strip-final-newline: 3.0.0
+
+  fast-deep-equal@3.1.3: {}
+
+  fast-diff@1.3.0: {}
+
+  fast-glob@3.3.2:
+    dependencies:
+      '@nodelib/fs.stat': 2.0.5
+      '@nodelib/fs.walk': 1.2.8
+      glob-parent: 5.1.2
+      merge2: 1.4.1
+      micromatch: 4.0.8
+
+  fast-json-stable-stringify@2.1.0: {}
+
+  fast-levenshtein@2.0.6: {}
+
+  fastq@1.17.1:
+    dependencies:
+      reusify: 1.0.4
+
+  file-entry-cache@8.0.0:
+    dependencies:
+      flat-cache: 4.0.1
+
+  fill-range@7.1.1:
+    dependencies:
+      to-regex-range: 5.0.1
+
+  find-up@5.0.0:
+    dependencies:
+      locate-path: 6.0.0
+      path-exists: 4.0.0
+
+  flat-cache@4.0.1:
+    dependencies:
+      flatted: 3.3.2
+      keyv: 4.5.4
+
+  flatted@3.3.2: {}
+
+  fs-extra@11.2.0:
+    dependencies:
+      graceful-fs: 4.2.11
+      jsonfile: 6.1.0
+      universalify: 2.0.1
+
+  fsevents@2.3.3:
+    optional: true
+
+  gensync@1.0.0-beta.2: {}
+
+  get-stream@8.0.1: {}
+
+  glob-parent@5.1.2:
+    dependencies:
+      is-glob: 4.0.3
+
+  glob-parent@6.0.2:
+    dependencies:
+      is-glob: 4.0.3
+
+  globals@11.12.0: {}
+
+  globals@13.24.0:
+    dependencies:
+      type-fest: 0.20.2
+
+  globals@14.0.0: {}
+
+  graceful-fs@4.2.11: {}
+
+  graphemer@1.4.0: {}
+
+  has-flag@4.0.0: {}
+
+  he@1.2.0: {}
+
+  hookable@5.5.3: {}
+
+  html-tags@3.3.1: {}
+
+  human-signals@5.0.0: {}
+
+  iconv-lite@0.6.3:
+    dependencies:
+      safer-buffer: 2.1.2
+
+  ignore@5.3.2: {}
+
+  import-fresh@3.3.0:
+    dependencies:
+      parent-module: 1.0.1
+      resolve-from: 4.0.0
+
+  imurmurhash@0.1.4: {}
+
+  internmap@2.0.3: {}
+
+  is-docker@3.0.0: {}
+
+  is-extglob@2.1.1: {}
+
+  is-glob@4.0.3:
+    dependencies:
+      is-extglob: 2.1.1
+
+  is-inside-container@1.0.0:
+    dependencies:
+      is-docker: 3.0.0
+
+  is-number@7.0.0: {}
+
+  is-stream@3.0.0: {}
+
+  is-what@4.1.16: {}
+
+  is-wsl@3.1.0:
+    dependencies:
+      is-inside-container: 1.0.0
+
+  isexe@2.0.0: {}
+
+  isexe@3.1.1: {}
+
+  js-tokens@4.0.0: {}
+
+  js-yaml@4.1.0:
+    dependencies:
+      argparse: 2.0.1
+
+  jsesc@3.0.2: {}
+
+  json-buffer@3.0.1: {}
+
+  json-parse-even-better-errors@4.0.0: {}
+
+  json-schema-traverse@0.4.1: {}
+
+  json-stable-stringify-without-jsonify@1.0.1: {}
+
+  json5@2.2.3: {}
+
+  jsonfile@6.1.0:
+    dependencies:
+      universalify: 2.0.1
+    optionalDependencies:
+      graceful-fs: 4.2.11
+
+  keyv@4.5.4:
+    dependencies:
+      json-buffer: 3.0.1
+
+  kolorist@1.8.0: {}
+
+  levn@0.4.1:
+    dependencies:
+      prelude-ls: 1.2.1
+      type-check: 0.4.0
+
+  locate-path@6.0.0:
+    dependencies:
+      p-locate: 5.0.0
+
+  lodash.merge@4.6.2: {}
+
+  lodash@4.17.21: {}
+
+  lru-cache@5.1.1:
+    dependencies:
+      yallist: 3.1.1
+
+  magic-string@0.30.13:
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.5.0
+
+  memorystream@0.3.1: {}
+
+  merge-stream@2.0.0: {}
+
+  merge2@1.4.1: {}
+
+  micromatch@4.0.8:
+    dependencies:
+      braces: 3.0.3
+      picomatch: 2.3.1
+
+  mimic-fn@4.0.0: {}
+
+  minimatch@3.1.2:
+    dependencies:
+      brace-expansion: 1.1.11
+
+  minimatch@9.0.5:
+    dependencies:
+      brace-expansion: 2.0.1
+
+  mitt@3.0.1: {}
+
+  mrmime@2.0.0: {}
+
+  ms@2.1.3: {}
+
+  muggle-string@0.4.1: {}
+
+  nanoid@3.3.7: {}
+
+  natural-compare@1.4.0: {}
+
+  node-releases@2.0.18: {}
+
+  npm-normalize-package-bin@4.0.0: {}
+
+  npm-run-all2@7.0.1:
+    dependencies:
+      ansi-styles: 6.2.1
+      cross-spawn: 7.0.6
+      memorystream: 0.3.1
+      minimatch: 9.0.5
+      pidtree: 0.6.0
+      read-package-json-fast: 4.0.0
+      shell-quote: 1.8.1
+      which: 5.0.0
+
+  npm-run-path@5.3.0:
+    dependencies:
+      path-key: 4.0.0
+
+  nth-check@2.1.1:
+    dependencies:
+      boolbase: 1.0.0
+
+  onetime@6.0.0:
+    dependencies:
+      mimic-fn: 4.0.0
+
+  open@10.1.0:
+    dependencies:
+      default-browser: 5.2.1
+      define-lazy-prop: 3.0.0
+      is-inside-container: 1.0.0
+      is-wsl: 3.1.0
+
+  optionator@0.9.4:
+    dependencies:
+      deep-is: 0.1.4
+      fast-levenshtein: 2.0.6
+      levn: 0.4.1
+      prelude-ls: 1.2.1
+      type-check: 0.4.0
+      word-wrap: 1.2.5
+
+  p-limit@3.1.0:
+    dependencies:
+      yocto-queue: 0.1.0
+
+  p-locate@5.0.0:
+    dependencies:
+      p-limit: 3.1.0
+
+  parent-module@1.0.1:
+    dependencies:
+      callsites: 3.1.0
+
+  path-browserify@1.0.1: {}
+
+  path-exists@4.0.0: {}
+
+  path-key@3.1.1: {}
+
+  path-key@4.0.0: {}
+
+  pathe@1.1.2: {}
+
+  perfect-debounce@1.0.0: {}
+
+  picocolors@1.1.1: {}
+
+  picomatch@2.3.1: {}
+
+  picomatch@4.0.2: {}
+
+  pidtree@0.6.0: {}
+
+  pinia@2.2.6(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3)):
+    dependencies:
+      '@vue/devtools-api': 6.6.4
+      vue: 3.5.13(typescript@5.6.3)
+      vue-demi: 0.14.10(vue@3.5.13(typescript@5.6.3))
+    optionalDependencies:
+      typescript: 5.6.3
+
+  postcss-selector-parser@6.1.2:
+    dependencies:
+      cssesc: 3.0.0
+      util-deprecate: 1.0.2
+
+  postcss@8.4.49:
+    dependencies:
+      nanoid: 3.3.7
+      picocolors: 1.1.1
+      source-map-js: 1.2.1
+
+  prelude-ls@1.2.1: {}
+
+  prettier-linter-helpers@1.0.0:
+    dependencies:
+      fast-diff: 1.3.0
+
+  prettier@3.3.3: {}
+
+  punycode@2.3.1: {}
+
+  queue-microtask@1.2.3: {}
+
+  read-package-json-fast@4.0.0:
+    dependencies:
+      json-parse-even-better-errors: 4.0.0
+      npm-normalize-package-bin: 4.0.0
+
+  resolve-from@4.0.0: {}
+
+  reusify@1.0.4: {}
+
+  rfdc@1.4.1: {}
+
+  robust-predicates@3.0.2: {}
+
+  rollup@4.27.3:
+    dependencies:
+      '@types/estree': 1.0.6
+    optionalDependencies:
+      '@rollup/rollup-android-arm-eabi': 4.27.3
+      '@rollup/rollup-android-arm64': 4.27.3
+      '@rollup/rollup-darwin-arm64': 4.27.3
+      '@rollup/rollup-darwin-x64': 4.27.3
+      '@rollup/rollup-freebsd-arm64': 4.27.3
+      '@rollup/rollup-freebsd-x64': 4.27.3
+      '@rollup/rollup-linux-arm-gnueabihf': 4.27.3
+      '@rollup/rollup-linux-arm-musleabihf': 4.27.3
+      '@rollup/rollup-linux-arm64-gnu': 4.27.3
+      '@rollup/rollup-linux-arm64-musl': 4.27.3
+      '@rollup/rollup-linux-powerpc64le-gnu': 4.27.3
+      '@rollup/rollup-linux-riscv64-gnu': 4.27.3
+      '@rollup/rollup-linux-s390x-gnu': 4.27.3
+      '@rollup/rollup-linux-x64-gnu': 4.27.3
+      '@rollup/rollup-linux-x64-musl': 4.27.3
+      '@rollup/rollup-win32-arm64-msvc': 4.27.3
+      '@rollup/rollup-win32-ia32-msvc': 4.27.3
+      '@rollup/rollup-win32-x64-msvc': 4.27.3
+      fsevents: 2.3.3
+
+  run-applescript@7.0.0: {}
+
+  run-parallel@1.2.0:
+    dependencies:
+      queue-microtask: 1.2.3
+
+  rw@1.3.3: {}
+
+  safer-buffer@2.1.2: {}
+
+  semver@6.3.1: {}
+
+  semver@7.6.3: {}
+
+  shebang-command@2.0.0:
+    dependencies:
+      shebang-regex: 3.0.0
+
+  shebang-regex@3.0.0: {}
+
+  shell-quote@1.8.1: {}
+
+  signal-exit@4.1.0: {}
+
+  sirv@3.0.0:
+    dependencies:
+      '@polka/url': 1.0.0-next.28
+      mrmime: 2.0.0
+      totalist: 3.0.1
+
+  socket.io-client@4.8.1:
+    dependencies:
+      '@socket.io/component-emitter': 3.1.2
+      debug: 4.3.7
+      engine.io-client: 6.6.2
+      socket.io-parser: 4.2.4
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+
+  socket.io-parser@4.2.4:
+    dependencies:
+      '@socket.io/component-emitter': 3.1.2
+      debug: 4.3.7
+    transitivePeerDependencies:
+      - supports-color
+
+  source-map-js@1.2.1: {}
+
+  speakingurl@14.0.1: {}
+
+  strip-final-newline@3.0.0: {}
+
+  strip-json-comments@3.1.1: {}
+
+  superjson@2.2.1:
+    dependencies:
+      copy-anything: 3.0.5
+
+  supports-color@7.2.0:
+    dependencies:
+      has-flag: 4.0.0
+
+  svg-tags@1.0.0: {}
+
+  synckit@0.9.2:
+    dependencies:
+      '@pkgr/core': 0.1.1
+      tslib: 2.8.1
+
+  to-regex-range@5.0.1:
+    dependencies:
+      is-number: 7.0.0
+
+  totalist@3.0.1: {}
+
+  ts-api-utils@1.4.0(typescript@5.6.3):
+    dependencies:
+      typescript: 5.6.3
+
+  tslib@2.8.1: {}
+
+  type-check@0.4.0:
+    dependencies:
+      prelude-ls: 1.2.1
+
+  type-fest@0.20.2: {}
+
+  typescript-eslint@8.15.0(eslint@9.15.0)(typescript@5.6.3):
+    dependencies:
+      '@typescript-eslint/eslint-plugin': 8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/parser': 8.15.0(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/utils': 8.15.0(eslint@9.15.0)(typescript@5.6.3)
+      eslint: 9.15.0
+    optionalDependencies:
+      typescript: 5.6.3
+    transitivePeerDependencies:
+      - supports-color
+
+  typescript@5.6.3: {}
+
+  undici-types@6.19.8: {}
+
+  universalify@2.0.1: {}
+
+  update-browserslist-db@1.1.1(browserslist@4.24.2):
+    dependencies:
+      browserslist: 4.24.2
+      escalade: 3.2.0
+      picocolors: 1.1.1
+
+  uri-js@4.4.1:
+    dependencies:
+      punycode: 2.3.1
+
+  util-deprecate@1.0.2: {}
+
+  vite-hot-client@0.2.3(vite@5.4.11(@types/node@22.9.1)):
+    dependencies:
+      vite: 5.4.11(@types/node@22.9.1)
+
+  vite-plugin-inspect@0.8.8(rollup@4.27.3)(vite@5.4.11(@types/node@22.9.1)):
+    dependencies:
+      '@antfu/utils': 0.7.10
+      '@rollup/pluginutils': 5.1.3(rollup@4.27.3)
+      debug: 4.3.7
+      error-stack-parser-es: 0.1.5
+      fs-extra: 11.2.0
+      open: 10.1.0
+      perfect-debounce: 1.0.0
+      picocolors: 1.1.1
+      sirv: 3.0.0
+      vite: 5.4.11(@types/node@22.9.1)
+    transitivePeerDependencies:
+      - rollup
+      - supports-color
+
+  vite-plugin-vue-devtools@7.6.4(rollup@4.27.3)(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3)):
+    dependencies:
+      '@vue/devtools-core': 7.6.4(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3))
+      '@vue/devtools-kit': 7.6.4
+      '@vue/devtools-shared': 7.6.4
+      execa: 8.0.1
+      sirv: 3.0.0
+      vite: 5.4.11(@types/node@22.9.1)
+      vite-plugin-inspect: 0.8.8(rollup@4.27.3)(vite@5.4.11(@types/node@22.9.1))
+      vite-plugin-vue-inspector: 5.2.0(vite@5.4.11(@types/node@22.9.1))
+    transitivePeerDependencies:
+      - '@nuxt/kit'
+      - rollup
+      - supports-color
+      - vue
+
+  vite-plugin-vue-inspector@5.2.0(vite@5.4.11(@types/node@22.9.1)):
+    dependencies:
+      '@babel/core': 7.26.0
+      '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.0)
+      '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0)
+      '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.0)
+      '@babel/plugin-transform-typescript': 7.25.9(@babel/core@7.26.0)
+      '@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.26.0)
+      '@vue/compiler-dom': 3.5.13
+      kolorist: 1.8.0
+      magic-string: 0.30.13
+      vite: 5.4.11(@types/node@22.9.1)
+    transitivePeerDependencies:
+      - supports-color
+
+  vite@5.4.11(@types/node@22.9.1):
+    dependencies:
+      esbuild: 0.21.5
+      postcss: 8.4.49
+      rollup: 4.27.3
+    optionalDependencies:
+      '@types/node': 22.9.1
+      fsevents: 2.3.3
+
+  vscode-uri@3.0.8: {}
+
+  vue-demi@0.14.10(vue@3.5.13(typescript@5.6.3)):
+    dependencies:
+      vue: 3.5.13(typescript@5.6.3)
+
+  vue-eslint-parser@9.4.3(eslint@9.15.0):
+    dependencies:
+      debug: 4.3.7
+      eslint: 9.15.0
+      eslint-scope: 7.2.2
+      eslint-visitor-keys: 3.4.3
+      espree: 9.6.1
+      esquery: 1.6.0
+      lodash: 4.17.21
+      semver: 7.6.3
+    transitivePeerDependencies:
+      - supports-color
+
+  vue-router@4.4.5(vue@3.5.13(typescript@5.6.3)):
+    dependencies:
+      '@vue/devtools-api': 6.6.4
+      vue: 3.5.13(typescript@5.6.3)
+
+  vue-tsc@2.1.10(typescript@5.6.3):
+    dependencies:
+      '@volar/typescript': 2.4.10
+      '@vue/language-core': 2.1.10(typescript@5.6.3)
+      semver: 7.6.3
+      typescript: 5.6.3
+
+  vue@3.5.13(typescript@5.6.3):
+    dependencies:
+      '@vue/compiler-dom': 3.5.13
+      '@vue/compiler-sfc': 3.5.13
+      '@vue/runtime-dom': 3.5.13
+      '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.6.3))
+      '@vue/shared': 3.5.13
+    optionalDependencies:
+      typescript: 5.6.3
+
+  which@2.0.2:
+    dependencies:
+      isexe: 2.0.0
+
+  which@5.0.0:
+    dependencies:
+      isexe: 3.1.1
+
+  word-wrap@1.2.5: {}
+
+  ws@8.17.1: {}
+
+  xml-name-validator@4.0.0: {}
+
+  xmlhttprequest-ssl@2.1.2: {}
+
+  yallist@3.1.1: {}
+
+  yocto-queue@0.1.0: {}
diff --git a/pi/ui/public/favicon.ico b/pi/ui/public/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..df36fcfb72584e00488330b560ebcf34a41c64c2
GIT binary patch
literal 4286
zcmds*O-Phc6o&64GDVCEQHxsW(p4>LW*W<827=Unuo8sGpRux(DN@jWP-e29Wl%wj
zY84_aq9}^Am9-cWTD5GGEo#+5Fi2wX_P*bo+xO!)p*7B;iKlbFd(U~_d(U?#hLj56
zPhFkj-|A6~Qk#@g^#D^U0XT1cu=c-vu1+SElX9NR;kzAUV(q0|dl0|%h|dI$%VICy
zJnu2^L*Te9JrJMGh%-P79CL0}dq92RGU6gI{v2~|)p}sG5x0U*z<8U;Ij*hB9z?ei
z@g6Xq-pDoPl=MANPiR7%172VA%r)kev<ISBgE$F{SFy+(=9Z)f)De0Se}ZDZW}Z3B
zElCeVrw;K0Fdl_Cg=gZOFXXc3pL)Q05CAuT+XucQ<8g~3dteP~|7s7c6QYP;fy;mF
zMN;>tV-_5H*QJKFmd;8yA$98zCxBZYXTNZ#QFk2(TX0;Y2dt&WitL#$96|gJY=3xX
zpCoi|YNzgO3R`f@IiEeSmKrPSf#h#Qd<$%Ej^RIeeYfsxhPMOG`S`Pz8q``=511zm
zAm)MX5AV^5xIWPyEu7u>qYs?pn$I4nL9J!=K=SGlKLXpE<5x+2cDTXq?brj?n6sp=
zphe9;_JHf40^9~}9i08r{XM$7HB!`{Ys~TK0kx<}ZQng`UPvH*11|q7&l9?@FQz;8
zx!=3<4seY*%=OlbCbcae?<QnEgvj4i?s}Yk=qA2z`-^*<eK3c)MS4JOdbsTQEOa0)
z0NWqlna2rzs>5^V_}*K>Uo6ZWV8mTyE^B=DKy7-sdLYkR5Z?paTgK-zyIkKjIcpyO
z{+uIt&YSa_$QnN_@t~L014dyK(fOOo+W*MIxbA6Ndgr=Y!f#Tokqv}n<7-9qfHkc3
z=>a|HWqcX8fzQCT=dqVbogRq!-S>H%yA{1w#2Pn;=e>JiEj7Hl;zdt-2f+j2%DeVD
zsW0Ab)ZK@0cIW%W7<X*Er!BfRbvU93$DH%#v6dRt^6HBxz1xBNHx=$&_Gv<&J}Ljk
zJN<Fzx(`Oe@KgQ0F$<14=XV#WK`o#6Ku>z}H{&~yGhn~D;aiP4=;m-HCo`BEI+Kd6
z={Xwx{T<?%b6i9IjI)Ls)S{-*mq<@~R{?$}ZKjf;^k75i_}(2MXt}^SEBVg7AI@28
zo_uPg2V)_e-`2Ois=PYoe%9u*n9({PFR)OnHJPi{dNx>Kx<YG`4QQ>D#iCLfl2<BD
h7L=-;Q>vQGDitKtN>z|-AdCN|$jTFDg0m3O`WLD4_s#$S

literal 0
HcmV?d00001

diff --git a/pi/ui/src/App.vue b/pi/ui/src/App.vue
new file mode 100644
index 0000000..a78fb6f
--- /dev/null
+++ b/pi/ui/src/App.vue
@@ -0,0 +1,7 @@
+<script setup lang="ts">
+import { RouterView } from 'vue-router'
+</script>
+
+<template>
+  <RouterView />
+</template>
diff --git a/pi/ui/src/assets/main.css b/pi/ui/src/assets/main.css
new file mode 100644
index 0000000..d6f2ad0
--- /dev/null
+++ b/pi/ui/src/assets/main.css
@@ -0,0 +1,6 @@
+#app {
+  max-width: 1280px;
+  margin: 0 auto;
+  padding: 2rem;
+  font-weight: normal;
+}
diff --git a/pi/ui/src/components/UWBVisualization.vue b/pi/ui/src/components/UWBVisualization.vue
new file mode 100644
index 0000000..b378711
--- /dev/null
+++ b/pi/ui/src/components/UWBVisualization.vue
@@ -0,0 +1,228 @@
+<script lang="ts">
+import { defineComponent, onMounted, inject, ref } from 'vue';
+import * as d3 from 'd3';
+import { Socket } from 'socket.io-client';
+
+interface AnchorData {
+  A: string; // Anchor ID
+  R: string; // Range
+}
+
+interface AnchorConfig {
+  anchors: string[];
+  distances: { [key: string]: number };
+}
+
+export default defineComponent({
+  name: 'UWBVisualization',
+  setup() {
+    const socket = inject('socket') as Socket;
+
+    const uwbData = ref<AnchorData[]>([]);
+    const anchorPositions = ref<{ [key: string]: [number, number] }>({});
+    const anchorDistances = ref<{ [key: string]: number }>({});
+    const anchorIDs = ref<string[]>([]);
+
+    // D3 variables
+    let svg: d3.Selection<SVGSVGElement, unknown, HTMLElement, any>; // eslint-disable-line
+    const width = 600;
+    const height = 400;
+
+    // Scales
+    let xScale: d3.ScaleLinear<number, number>;
+    let yScale: d3.ScaleLinear<number, number>;
+
+    onMounted(() => {
+      // Fetch the configuration file from the backend
+      fetch('/api/config')
+        .then((response) => response.json())
+        .then((configData: AnchorConfig) => {
+          parseConfiguration(configData);
+          initVisualization();
+
+          // Listen for UWB data
+          socket.on('uwb_data', (data: AnchorData[]) => {
+            uwbData.value = data;
+            updateVisualization();
+          });
+        })
+        .catch((error) => {
+          console.error('Failed to load configuration:', error);
+        });
+    });
+
+    function parseConfiguration(config: AnchorConfig) {
+      anchorIDs.value = config.anchors;
+      anchorDistances.value = config.distances;
+      computeAnchorPositions();
+    }
+
+    function computeAnchorPositions() {
+      const positions: { [key: string]: [number, number] } = {};
+      const ids = anchorIDs.value;
+
+      if (ids.length < 3) {
+        console.error('At least three anchors are required.');
+        return;
+      }
+
+      positions[ids[0]] = [0, 0]; // First anchor at (0, 0)
+
+      const d1 = getDistance(ids[0], ids[1]);
+      positions[ids[1]] = [d1, 0]; // Second anchor on x-axis
+
+      const d2 = getDistance(ids[0], ids[2]);
+      const d3 = getDistance(ids[1], ids[2]);
+
+      const x = (d1 ** 2 + d2 ** 2 - d3 ** 2) / (2 * d1);
+      const y = Math.sqrt(d2 ** 2 - x ** 2);
+
+      positions[ids[2]] = [x, y];
+
+      anchorPositions.value = positions;
+      updateScales();
+    }
+
+    function getDistance(id1: string, id2: string): number {
+      return (
+        anchorDistances.value[`${id1}-${id2}`] ||
+        anchorDistances.value[`${id2}-${id1}`] ||
+        0
+      );
+    }
+
+    function updateScales() {
+      const allX = Object.values(anchorPositions.value).map((pos) => pos[0]);
+      const allY = Object.values(anchorPositions.value).map((pos) => pos[1]);
+
+      const minX = Math.min(...allX) - 5;
+      const maxX = Math.max(...allX) + 5;
+      const minY = Math.min(...allY) - 5;
+      const maxY = Math.max(...allY) + 5;
+
+      xScale = d3.scaleLinear().domain([minX, maxX]).range([0, width]);
+      yScale = d3.scaleLinear().domain([minY, maxY]).range([height, 0]);
+    }
+
+    function initVisualization() {
+      d3.select('#visualization').select('svg').remove();
+
+      svg = d3.select('#visualization').append('svg').attr('width', width).attr('height', height);
+
+      drawAnchors();
+    }
+
+    function drawAnchors() {
+      const anchors = Object.entries(anchorPositions.value);
+
+      svg
+        .selectAll('.anchor')
+        .data(anchors)
+        .enter()
+        .append('circle')
+        .attr('class', 'anchor')
+        .attr('cx', (d) => xScale(d[1][0]))
+        .attr('cy', (d) => yScale(d[1][1]))
+        .attr('r', 5)
+        .attr('fill', 'green');
+
+      svg
+        .selectAll('.anchor-label')
+        .data(anchors)
+        .enter()
+        .append('text')
+        .attr('class', 'anchor-label')
+        .attr('x', (d) => xScale(d[1][0]) + 5)
+        .attr('y', (d) => yScale(d[1][1]) - 5)
+        .text((d) => `A${d[0]}`)
+        .attr('font-size', '12px')
+        .attr('fill', 'black');
+    }
+
+    function updateVisualization() {
+      const positions: [number, number][] = [];
+      const distances: number[] = [];
+
+      uwbData.value.forEach((anchor) => {
+        const anchorID = anchor.A;
+        const range = parseFloat(anchor.R);
+        if (anchorPositions.value[anchorID]) {
+          positions.push(anchorPositions.value[anchorID]);
+          distances.push(range);
+        }
+      });
+
+      if (positions.length >= 3) {
+        const [x, y] = calculateTagPosition(positions, distances);
+        drawTag(x, y);
+      }
+    }
+
+    function calculateTagPosition(
+      positions: [number, number][],
+      distances: number[]
+    ): [number, number] {
+      if (positions.length < 3) {
+        console.error('At least three anchors are required.');
+        return [-1, -1];
+      }
+
+      try {
+        const [x1, y1] = positions[0];
+        const [x2, y2] = positions[1];
+        const [x3, y3] = positions[2];
+        const r1 = distances[0];
+        const r2 = distances[1];
+        const r3 = distances[2];
+
+        const A = 2 * (x2 - x1);
+        const B = 2 * (y2 - y1);
+        const C = r1 ** 2 - r2 ** 2 - x1 ** 2 + x2 ** 2 - y1 ** 2 + y2 ** 2;
+        const D = 2 * (x3 - x1);
+        const E = 2 * (y3 - y1);
+        const F = r1 ** 2 - r3 ** 2 - x1 ** 2 + x3 ** 2 - y1 ** 2 + y3 ** 2;
+
+        const denominator = A * E - B * D;
+        if (denominator === 0) {
+          console.error('Cannot solve, determinant is zero.');
+          return [-1, -1];
+        }
+
+        const x = (C * E - B * F) / denominator;
+        const y = (A * F - C * D) / denominator;
+
+        return [x, y];
+      } catch (error) {
+        console.error('Error calculating position:', error);
+        return [-1, -1];
+      }
+    }
+
+    function drawTag(x: number, y: number) {
+      svg.selectAll('.tag').remove();
+
+      if (x !== -1 && y !== -1) {
+        svg
+          .append('circle')
+          .attr('class', 'tag')
+          .attr('cx', xScale(x))
+          .attr('cy', yScale(y))
+          .attr('r', 5)
+          .attr('fill', 'blue');
+      }
+    }
+
+    return {};
+  },
+});
+</script>
+
+<template>
+  <div id="visualization"></div>
+</template>
+
+<style scoped>
+#visualization {
+  border: 1px solid #ccc;
+}
+</style>
diff --git a/pi/ui/src/main.ts b/pi/ui/src/main.ts
new file mode 100644
index 0000000..72ab836
--- /dev/null
+++ b/pi/ui/src/main.ts
@@ -0,0 +1,21 @@
+import './assets/main.css'
+
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+import { io, Socket } from 'socket.io-client'
+
+import App from './App.vue'
+import router from './router'
+
+const app = createApp(App)
+
+const socket: Socket = io('http://0.0.0.0:8080', {
+  transports: ['websocket'],
+})
+
+app.provide('socket', socket)
+
+app.use(createPinia())
+app.use(router)
+
+app.mount('#app')
diff --git a/pi/ui/src/router/index.ts b/pi/ui/src/router/index.ts
new file mode 100644
index 0000000..29788a1
--- /dev/null
+++ b/pi/ui/src/router/index.ts
@@ -0,0 +1,15 @@
+import { createRouter, createWebHistory } from 'vue-router'
+import HomeView from '../views/HomeView.vue'
+
+const router = createRouter({
+  history: createWebHistory(import.meta.env.BASE_URL),
+  routes: [
+    {
+      path: '/',
+      name: 'home',
+      component: HomeView,
+    },
+  ],
+})
+
+export default router
diff --git a/pi/ui/src/views/HomeView.vue b/pi/ui/src/views/HomeView.vue
new file mode 100644
index 0000000..4e24910
--- /dev/null
+++ b/pi/ui/src/views/HomeView.vue
@@ -0,0 +1,9 @@
+<script setup lang="ts">
+import UWBVisualization from '../components/UWBVisualization.vue'
+</script>
+
+<template>
+  <main>
+    <UWBVisualization />
+  </main>
+</template>
diff --git a/pi/ui/tsconfig.app.json b/pi/ui/tsconfig.app.json
new file mode 100644
index 0000000..e14c754
--- /dev/null
+++ b/pi/ui/tsconfig.app.json
@@ -0,0 +1,14 @@
+{
+  "extends": "@vue/tsconfig/tsconfig.dom.json",
+  "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
+  "exclude": ["src/**/__tests__/*"],
+  "compilerOptions": {
+    "composite": true,
+    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  }
+}
diff --git a/pi/ui/tsconfig.json b/pi/ui/tsconfig.json
new file mode 100644
index 0000000..66b5e57
--- /dev/null
+++ b/pi/ui/tsconfig.json
@@ -0,0 +1,11 @@
+{
+  "files": [],
+  "references": [
+    {
+      "path": "./tsconfig.node.json"
+    },
+    {
+      "path": "./tsconfig.app.json"
+    }
+  ]
+}
diff --git a/pi/ui/tsconfig.node.json b/pi/ui/tsconfig.node.json
new file mode 100644
index 0000000..5a0c6a5
--- /dev/null
+++ b/pi/ui/tsconfig.node.json
@@ -0,0 +1,19 @@
+{
+  "extends": "@tsconfig/node22/tsconfig.json",
+  "include": [
+    "vite.config.*",
+    "vitest.config.*",
+    "cypress.config.*",
+    "nightwatch.conf.*",
+    "playwright.config.*"
+  ],
+  "compilerOptions": {
+    "composite": true,
+    "noEmit": true,
+    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+
+    "module": "ESNext",
+    "moduleResolution": "Bundler",
+    "types": ["node"]
+  }
+}
diff --git a/pi/ui/vite.config.ts b/pi/ui/vite.config.ts
new file mode 100644
index 0000000..4dbb315
--- /dev/null
+++ b/pi/ui/vite.config.ts
@@ -0,0 +1,18 @@
+import { fileURLToPath, URL } from 'node:url'
+
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import vueDevTools from 'vite-plugin-vue-devtools'
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [
+    vue(),
+    vueDevTools(),
+  ],
+  resolve: {
+    alias: {
+      '@': fileURLToPath(new URL('./src', import.meta.url))
+    },
+  }
+})
diff --git a/uwb/uwb_position2.0.py b/uwb/uwb_position2.0.py
deleted file mode 100644
index b88f721..0000000
--- a/uwb/uwb_position2.0.py
+++ /dev/null
@@ -1,272 +0,0 @@
-import turtle
-import socket
-import json
-import threading
-import numpy as np
-import math
-
-# Constants
-METER_TO_PIXEL = 22
-
-ANCHOR_IDS = ["50", "51", "52"]
-
-# Shared data between threads
-uwb_list = []
-data_lock = threading.Lock()
-
-
-def calculate_anchor_positions():
-    d_50_51 = 10.0  # Distance between anchor 50 and 51 in meters
-    d_50_52 = 8.0  # Distance between anchor 50 and 52 in meters
-    d_51_52 = 6.0  # Distance between anchor 51 and 52 in meters
-
-    anchor_positions = {}
-    anchor_positions["50"] = (0.0, 0.0)
-    anchor_positions["51"] = (d_50_51, 0.0)
-
-    x = (d_50_51**2 + d_50_52**2 - d_51_52**2) / (2 * d_50_51)
-    y = math.sqrt(d_50_52**2 - x**2)
-
-    anchor_positions["52"] = (x, y)
-
-    return anchor_positions
-
-
-anchor_positions_dict = calculate_anchor_positions()
-
-# Calculate maximum x and y values from anchor positions
-all_x = [pos[0] for pos in anchor_positions_dict.values()]
-all_y = [pos[1] for pos in anchor_positions_dict.values()]
-max_x = max(all_x)
-max_y = max(all_y)
-
-
-def screen_init(width=1200, height=800, t=turtle):
-    t.setup(width, height)
-    t.tracer(False)
-    t.hideturtle()
-    t.speed(0)
-    # Adjust world coordinates based on anchor positions
-    t.setworldcoordinates(
-        -5 * METER_TO_PIXEL,
-        -5 * METER_TO_PIXEL,
-        (max_x + 5) * METER_TO_PIXEL,
-        (max_y + 5) * METER_TO_PIXEL,
-    )
-
-
-def turtle_init(t=turtle):
-    t.hideturtle()
-    t.speed(0)
-
-
-def draw_line(x0, y0, x1, y1, color="black", t=turtle):
-    t.pencolor(color)
-    t.up()
-    t.goto(x0, y0)
-    t.down()
-    t.goto(x1, y1)
-    t.up()
-
-
-def draw_cycle(x, y, r, color="black", t=turtle):
-    t.pencolor(color)
-    t.up()
-    t.goto(x, y - r)
-    t.setheading(0)
-    t.down()
-    t.circle(r)
-    t.up()
-
-
-def fill_cycle(x, y, r, color="black", t=turtle):
-    t.up()
-    t.goto(x, y)
-    t.down()
-    t.dot(r, color)
-    t.up()
-
-
-def write_txt(x, y, txt, color="black", t=turtle, f=("Arial", 12, "normal")):
-    t.pencolor(color)
-    t.up()
-    t.goto(x, y)
-    t.down()
-    t.write(txt, move=False, align="left", font=f)
-    t.up()
-
-
-def clean(t=turtle):
-    t.clear()
-
-
-def draw_uwb_anchor(x_m, y_m, txt, range, t):
-    x = x_m * METER_TO_PIXEL
-    y = y_m * METER_TO_PIXEL
-    r = 10
-    fill_cycle(x, y, r, "green", t)
-    write_txt(
-        x + r, y, txt + ": " + str(range) + "m", "black", t, f=("Arial", 12, "normal")
-    )
-
-
-def draw_uwb_tag(x_m, y_m, txt, t):
-    x = x_m * METER_TO_PIXEL
-    y = y_m * METER_TO_PIXEL
-    r = 10
-    fill_cycle(x, y, r, "blue", t)
-    write_txt(
-        x + r,
-        y,
-        txt + ": (" + str(round(x_m, 2)) + ", " + str(round(y_m, 2)) + ")",
-        "black",
-        t,
-        f=("Arial", 12, "normal"),
-    )
-
-
-def tag_pos(positions, distances):
-    if len(positions) < 3:
-        print("Error: At least three anchors are required.")
-        return -1, -1
-    try:
-        x1, y1 = positions[0]
-        x2, y2 = positions[1]
-        x3, y3 = positions[2]
-        r1 = distances[0]
-        r2 = distances[1]
-        r3 = distances[2]
-
-        # Formulate matrices
-        A = np.array([[2 * (x2 - x1), 2 * (y2 - y1)], [2 * (x3 - x1), 2 * (y3 - y1)]])
-        B = np.array(
-            [
-                r1**2 - r2**2 - x1**2 + x2**2 - y1**2 + y2**2,
-                r1**2 - r3**2 - x1**2 + x3**2 - y1**2 + y3**2,
-            ]
-        )
-
-        # Solve for x and y
-        position = np.linalg.lstsq(A, B, rcond=None)[0]
-
-        return round(position[0], 2), round(position[1], 2)
-    except Exception as e:
-        print(f"Error calculating position data: {e}")
-        return -1, -1
-
-
-def uwb_range_offset(uwb_range):
-    return uwb_range
-
-
-def socket_listener():
-    global uwb_list
-
-    TCP_PORT = 8080
-    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-    sock.bind(("0.0.0.0", TCP_PORT))
-    sock.listen(1)
-
-    print(f"Listening on port {TCP_PORT} for incoming connections...")
-
-    conn, addr = sock.accept()
-    print(f"Connected by {addr}")
-    buffer = ""
-
-    while True:
-        try:
-            data = conn.recv(1024).decode("UTF-8")
-            if not data:
-                print("Connection closed by client")
-                break  # Connection closed
-
-            buffer += data
-            while "\n" in buffer:
-                line, buffer = buffer.split("\n", 1)
-                if line:
-                    uwb_data = json.loads(line)
-                    with data_lock:
-                        uwb_list = uwb_data.get("links", [])
-                    for uwb_anchor in uwb_list:
-                        print(f"anchor: {uwb_anchor}")
-        except json.JSONDecodeError:
-            print("Received invalid JSON data.")
-        except Exception as e:
-            print(f"Error reading data: {e}")
-            break
-    conn.close()
-
-
-def main():
-    # Start the socket listener in a separate thread
-    threading.Thread(target=socket_listener, daemon=True).start()
-
-    # Calculate anchor positions
-    # anchor_positions_dict is already calculated above
-
-    # Initialize the Turtle screen
-    screen_init()
-
-    # Turtle instances
-    t_anchors = [turtle.Turtle() for _ in ANCHOR_IDS]
-    t_tag = turtle.Turtle()
-    for t in t_anchors:
-        turtle_init(t)
-    turtle_init(t_tag)
-
-    # Anchor positions in meters
-    anchor_positions = [
-        anchor_positions_dict["50"],
-        anchor_positions_dict["51"],
-        anchor_positions_dict["52"],
-    ]
-    anchor_ranges = [0.0] * len(ANCHOR_IDS)
-
-    def update():
-        nonlocal anchor_ranges, anchor_positions
-        node_count = 0
-        with data_lock:
-            local_uwb_list = uwb_list.copy()
-
-        for one in local_uwb_list:
-            if one.get("A") in ANCHOR_IDS:
-                index = ANCHOR_IDS.index(one.get("A"))
-                clean(t_anchors[index])
-                anchor_ranges[index] = uwb_range_offset(float(one.get("R", 0)))
-                draw_uwb_anchor(
-                    anchor_positions[index][0],
-                    anchor_positions[index][1],
-                    f"A{ANCHOR_IDS[index]}",
-                    anchor_ranges[index],
-                    t_anchors[index],
-                )
-                node_count += 1
-
-        if node_count >= 3:
-            # Build positions and distances for anchors with valid ranges
-            valid_positions = []
-            valid_distances = []
-            for i in range(len(anchor_ranges)):
-                if anchor_ranges[i] > 0:
-                    valid_positions.append(anchor_positions[i])
-                    valid_distances.append(anchor_ranges[i])
-
-            if len(valid_positions) >= 3:
-                x, y = tag_pos(valid_positions, valid_distances)
-                if x != -1:
-                    print(f"Tag Position: x={x}, y={y}")
-                    clean(t_tag)
-                    draw_uwb_tag(x, y, "TAG", t_tag)
-
-        turtle.update()
-        # Schedule the next update after 100 milliseconds
-        turtle.ontimer(update, 100)
-
-    # Start the update loop
-    update()
-
-    turtle.mainloop()
-
-
-if __name__ == "__main__":
-    main()

From 1632f91440d4b804ab509927b77c46cad6597d09 Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Thu, 21 Nov 2024 12:59:39 -0500
Subject: [PATCH 04/34] Fix typo

---
 .github/workflows/build-lawndon.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build-lawndon.yml b/.github/workflows/build-lawndon.yml
index efe9388..28c4ce3 100644
--- a/.github/workflows/build-lawndon.yml
+++ b/.github/workflows/build-lawndon.yml
@@ -54,7 +54,7 @@ jobs:
 
   build-lawndon-pi:
     runs-on: ubuntu-latest
-    needs: build-lawndon-lite
+    needs: build-lawndon
     steps:
       - name: Checkout repository
         uses: actions/checkout@v4

From ca411579ba9950f4719797ff92cdd4cb7eb8e064 Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Thu, 21 Nov 2024 13:09:33 -0500
Subject: [PATCH 05/34] Fix pnpm setup

---
 .github/workflows/build-lawndon.yml | 11 ++++++-----
 pi/package.json                     |  2 +-
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/build-lawndon.yml b/.github/workflows/build-lawndon.yml
index 28c4ce3..e1844b9 100644
--- a/.github/workflows/build-lawndon.yml
+++ b/.github/workflows/build-lawndon.yml
@@ -59,14 +59,15 @@ jobs:
       - name: Checkout repository
         uses: actions/checkout@v4
 
+      - name: Install pnpm
+        uses: pnpm/action-setup@v4
+        with:
+          version: 9
+
       - name: Setup Node.js
-        uses: actions/setup-node@v3
+        uses: actions/setup-node@v4
         with:
           node-version: '22'
-          cache: 'pnpm'
-
-      - name: Setup pnpm
-        run: npm install -g pnpm
 
       - name: Package application
         working-directory: ./pi
diff --git a/pi/package.json b/pi/package.json
index 5a565db..f1d6c24 100644
--- a/pi/package.json
+++ b/pi/package.json
@@ -9,5 +9,5 @@
     "build:server": "cd server && pnpm build",
     "start": "node server/dist/server.js"
   },
-  "packageManager": "pnpm@9.12.0+sha512.4abf725084d7bcbafbd728bfc7bee61f2f791f977fd87542b3579dcb23504d170d46337945e4c66485cd12d588a0c0e570ed9c477e7ccdd8507cf05f3f92eaca"
+  "packageManager": "pnpm@9.14.2"
 }

From 596a2402aee0474b9acfda5cd2ff9d81f0df7d5c Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Thu, 21 Nov 2024 16:03:13 -0500
Subject: [PATCH 06/34] Fix package build - fix ui destinations (#15)

---
 pi/package.sh           |  1 +
 pi/server/package.json  |  2 +-
 pi/server/src/server.ts | 10 +++-------
 pi/ui/src/main.ts       |  2 +-
 4 files changed, 6 insertions(+), 9 deletions(-)

diff --git a/pi/package.sh b/pi/package.sh
index 3964a74..5c03627 100755
--- a/pi/package.sh
+++ b/pi/package.sh
@@ -24,6 +24,7 @@ pnpm build:server
 echo "Preparing the package directory..."
 mkdir $PROJECT_NAME
 cp -r server/dist $PROJECT_NAME/server
+cp -r server/node_modules $PROJECT_NAME/server
 cp -r ui/dist $PROJECT_NAME/ui
 cp -r config $PROJECT_NAME/
 cp package.json $PROJECT_NAME/
diff --git a/pi/server/package.json b/pi/server/package.json
index 488a0d4..aa89611 100644
--- a/pi/server/package.json
+++ b/pi/server/package.json
@@ -11,7 +11,7 @@
   "keywords": [],
   "author": "",
   "license": "ISC",
-  "packageManager": "pnpm@9.12.0+sha512.4abf725084d7bcbafbd728bfc7bee61f2f791f977fd87542b3579dcb23504d170d46337945e4c66485cd12d588a0c0e570ed9c477e7ccdd8507cf05f3f92eaca",
+  "packageManager": "pnpm@9.14.2",
   "dependencies": {
     "express": "^4.21.1",
     "mathjs": "^14.0.0",
diff --git a/pi/server/src/server.ts b/pi/server/src/server.ts
index 76ba808..a2e0293 100644
--- a/pi/server/src/server.ts
+++ b/pi/server/src/server.ts
@@ -12,18 +12,14 @@ const io = new Server(httpServer, {
 
 const PORT = 5000;
 
-app.use(express.static('static'));
+app.use(express.static(path.join(__dirname + '../../ui/index.html')));
 
 app.get('/', (req, res) => {
-  res.sendFile(__dirname + '../../ui');
+  res.sendFile(path.join(__dirname + '../../ui/index.html'));
 });
 
 app.get('/api/config', (req, res) => {
-  res.sendFile(path.resolve(__dirname, '../../config/anchorPositions.json'));
-});
-
-app.get('*', (req, res) => {
-  res.sendFile(path.resolve(__dirname, '../../ui'));
+  res.sendFile(path.join(__dirname, '../../config/anchorPositions.json'));
 });
 
 io.on('connection', (socket) => {
diff --git a/pi/ui/src/main.ts b/pi/ui/src/main.ts
index 72ab836..0d51d91 100644
--- a/pi/ui/src/main.ts
+++ b/pi/ui/src/main.ts
@@ -9,7 +9,7 @@ import router from './router'
 
 const app = createApp(App)
 
-const socket: Socket = io('http://0.0.0.0:8080', {
+const socket: Socket = io('http://0.0.0.0:5000', {
   transports: ['websocket'],
 })
 

From 4d296e7a0d51aa182c4cccdadcaee7e16c208310 Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Fri, 22 Nov 2024 05:38:55 -0500
Subject: [PATCH 07/34] Fix UI static location - start dev loading
 functionality (#16)

---
 pi/package.json                           |  11 +-
 pi/package.sh                             |  14 +-
 pi/pnpm-lock.yaml                         | 702 +++++++++++++++++++++-
 pi/server/package.json                    |   7 +-
 pi/server/pnpm-lock.yaml                  | 416 +++++++++++++
 pi/server/src/server.ts                   |   2 +-
 pi/ui/package.json                        |   4 +-
 pi/ui/pnpm-lock.yaml                      |  21 +-
 pi/ui/src/components/UWBVisualization.vue |  28 +-
 pi/ui/vite.config.ts                      |   7 +-
 10 files changed, 1178 insertions(+), 34 deletions(-)

diff --git a/pi/package.json b/pi/package.json
index f1d6c24..455b171 100644
--- a/pi/package.json
+++ b/pi/package.json
@@ -7,7 +7,14 @@
     "install:server": "pnpm install --prefix server",
     "build:ui": "cd ui && pnpm build",
     "build:server": "cd server && pnpm build",
-    "start": "node server/dist/server.js"
+    "start": "node server/dist/server.js",
+    "dev": "concurrently -k -n SERVER,UI \"pnpm dev:server\" \"pnpm dev:ui\"",
+    "dev:ui": "pnpm --prefix ui dev",
+    "dev:server": "NODE_ENV=development pnpm --prefix server dev"
   },
-  "packageManager": "pnpm@9.14.2"
+  "packageManager": "pnpm@9.14.2",
+  "devDependencies": {
+    "concurrently": "^9.1.0",
+    "ts-node-dev": "^2.0.0"
+  }
 }
diff --git a/pi/package.sh b/pi/package.sh
index 5c03627..721a66b 100755
--- a/pi/package.sh
+++ b/pi/package.sh
@@ -8,20 +8,20 @@ PI_USER="pi"
 PI_HOST="192.168.12.1"
 PI_PATH="/home/pi"
 
-echo "Starting the build and packaging process..."
+echo "Starting the build and packaging process"
 
-echo "Cleaning up old builds..."
+echo "Cleaning up old builds"
 rm -rf $PROJECT_NAME $TARGET_ARCHIVE
 
-echo "Building UI..."
+echo "Building UI"
 pnpm install:ui
 pnpm build:ui
 
-echo "Building server..."
+echo "Building server"
 pnpm install:server
 pnpm build:server
 
-echo "Preparing the package directory..."
+echo "Preparing the package directory"
 mkdir $PROJECT_NAME
 cp -r server/dist $PROJECT_NAME/server
 cp -r server/node_modules $PROJECT_NAME/server
@@ -29,10 +29,10 @@ cp -r ui/dist $PROJECT_NAME/ui
 cp -r config $PROJECT_NAME/
 cp package.json $PROJECT_NAME/
 
-echo "Creating the tar.gz archive..."
+echo "Creating the tar.gz archive"
 tar -czf $TARGET_ARCHIVE $PROJECT_NAME
 
-rm -rf $PROJECT_NAME
+# rm -rf $PROJECT_NAME
 
 echo "Build and transfer complete"
 echo "To run the app on the Raspberry Pi:"
diff --git a/pi/pnpm-lock.yaml b/pi/pnpm-lock.yaml
index 9b60ae1..fad7d35 100644
--- a/pi/pnpm-lock.yaml
+++ b/pi/pnpm-lock.yaml
@@ -6,4 +6,704 @@ settings:
 
 importers:
 
-  .: {}
+  .:
+    devDependencies:
+      concurrently:
+        specifier: ^9.1.0
+        version: 9.1.0
+      ts-node-dev:
+        specifier: ^2.0.0
+        version: 2.0.0(@types/node@22.9.1)(typescript@5.6.3)
+
+packages:
+
+  '@cspotcode/source-map-support@0.8.1':
+    resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
+    engines: {node: '>=12'}
+
+  '@jridgewell/resolve-uri@3.1.2':
+    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/sourcemap-codec@1.5.0':
+    resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
+
+  '@jridgewell/trace-mapping@0.3.9':
+    resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+
+  '@tsconfig/node10@1.0.11':
+    resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
+
+  '@tsconfig/node12@1.0.11':
+    resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
+
+  '@tsconfig/node14@1.0.3':
+    resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
+
+  '@tsconfig/node16@1.0.4':
+    resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+
+  '@types/node@22.9.1':
+    resolution: {integrity: sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==}
+
+  '@types/strip-bom@3.0.0':
+    resolution: {integrity: sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==}
+
+  '@types/strip-json-comments@0.0.30':
+    resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==}
+
+  acorn-walk@8.3.4:
+    resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
+    engines: {node: '>=0.4.0'}
+
+  acorn@8.14.0:
+    resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+
+  ansi-regex@5.0.1:
+    resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+    engines: {node: '>=8'}
+
+  ansi-styles@4.3.0:
+    resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+    engines: {node: '>=8'}
+
+  anymatch@3.1.3:
+    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+    engines: {node: '>= 8'}
+
+  arg@4.1.3:
+    resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
+
+  balanced-match@1.0.2:
+    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+  binary-extensions@2.3.0:
+    resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
+    engines: {node: '>=8'}
+
+  brace-expansion@1.1.11:
+    resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+
+  braces@3.0.3:
+    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+    engines: {node: '>=8'}
+
+  buffer-from@1.1.2:
+    resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+
+  chalk@4.1.2:
+    resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+    engines: {node: '>=10'}
+
+  chokidar@3.6.0:
+    resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
+    engines: {node: '>= 8.10.0'}
+
+  cliui@8.0.1:
+    resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+    engines: {node: '>=12'}
+
+  color-convert@2.0.1:
+    resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+    engines: {node: '>=7.0.0'}
+
+  color-name@1.1.4:
+    resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+  concat-map@0.0.1:
+    resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+  concurrently@9.1.0:
+    resolution: {integrity: sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==}
+    engines: {node: '>=18'}
+    hasBin: true
+
+  create-require@1.1.1:
+    resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+
+  diff@4.0.2:
+    resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
+    engines: {node: '>=0.3.1'}
+
+  dynamic-dedupe@0.3.0:
+    resolution: {integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==}
+
+  emoji-regex@8.0.0:
+    resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+  escalade@3.2.0:
+    resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+    engines: {node: '>=6'}
+
+  fill-range@7.1.1:
+    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+    engines: {node: '>=8'}
+
+  fs.realpath@1.0.0:
+    resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+  fsevents@2.3.3:
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+
+  function-bind@1.1.2:
+    resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+  get-caller-file@2.0.5:
+    resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+    engines: {node: 6.* || 8.* || >= 10.*}
+
+  glob-parent@5.1.2:
+    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+    engines: {node: '>= 6'}
+
+  glob@7.2.3:
+    resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+    deprecated: Glob versions prior to v9 are no longer supported
+
+  has-flag@4.0.0:
+    resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+    engines: {node: '>=8'}
+
+  hasown@2.0.2:
+    resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+    engines: {node: '>= 0.4'}
+
+  inflight@1.0.6:
+    resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+    deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
+
+  inherits@2.0.4:
+    resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+  is-binary-path@2.1.0:
+    resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+    engines: {node: '>=8'}
+
+  is-core-module@2.15.1:
+    resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
+    engines: {node: '>= 0.4'}
+
+  is-extglob@2.1.1:
+    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+    engines: {node: '>=0.10.0'}
+
+  is-fullwidth-code-point@3.0.0:
+    resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+    engines: {node: '>=8'}
+
+  is-glob@4.0.3:
+    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+    engines: {node: '>=0.10.0'}
+
+  is-number@7.0.0:
+    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+    engines: {node: '>=0.12.0'}
+
+  lodash@4.17.21:
+    resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+  make-error@1.3.6:
+    resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
+
+  minimatch@3.1.2:
+    resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+  minimist@1.2.8:
+    resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+  mkdirp@1.0.4:
+    resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
+    engines: {node: '>=10'}
+    hasBin: true
+
+  normalize-path@3.0.0:
+    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+    engines: {node: '>=0.10.0'}
+
+  once@1.4.0:
+    resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+  path-is-absolute@1.0.1:
+    resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+    engines: {node: '>=0.10.0'}
+
+  path-parse@1.0.7:
+    resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+  picomatch@2.3.1:
+    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+    engines: {node: '>=8.6'}
+
+  readdirp@3.6.0:
+    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+    engines: {node: '>=8.10.0'}
+
+  require-directory@2.1.1:
+    resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+    engines: {node: '>=0.10.0'}
+
+  resolve@1.22.8:
+    resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
+    hasBin: true
+
+  rimraf@2.7.1:
+    resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
+    deprecated: Rimraf versions prior to v4 are no longer supported
+    hasBin: true
+
+  rxjs@7.8.1:
+    resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
+
+  shell-quote@1.8.1:
+    resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
+
+  source-map-support@0.5.21:
+    resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
+
+  source-map@0.6.1:
+    resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+    engines: {node: '>=0.10.0'}
+
+  string-width@4.2.3:
+    resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+    engines: {node: '>=8'}
+
+  strip-ansi@6.0.1:
+    resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+    engines: {node: '>=8'}
+
+  strip-bom@3.0.0:
+    resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
+    engines: {node: '>=4'}
+
+  strip-json-comments@2.0.1:
+    resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
+    engines: {node: '>=0.10.0'}
+
+  supports-color@7.2.0:
+    resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+    engines: {node: '>=8'}
+
+  supports-color@8.1.1:
+    resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
+    engines: {node: '>=10'}
+
+  supports-preserve-symlinks-flag@1.0.0:
+    resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+    engines: {node: '>= 0.4'}
+
+  to-regex-range@5.0.1:
+    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+    engines: {node: '>=8.0'}
+
+  tree-kill@1.2.2:
+    resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
+    hasBin: true
+
+  ts-node-dev@2.0.0:
+    resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==}
+    engines: {node: '>=0.8.0'}
+    hasBin: true
+    peerDependencies:
+      node-notifier: '*'
+      typescript: '*'
+    peerDependenciesMeta:
+      node-notifier:
+        optional: true
+
+  ts-node@10.9.2:
+    resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
+    hasBin: true
+    peerDependencies:
+      '@swc/core': '>=1.2.50'
+      '@swc/wasm': '>=1.2.50'
+      '@types/node': '*'
+      typescript: '>=2.7'
+    peerDependenciesMeta:
+      '@swc/core':
+        optional: true
+      '@swc/wasm':
+        optional: true
+
+  tsconfig@7.0.0:
+    resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==}
+
+  tslib@2.8.1:
+    resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+  typescript@5.6.3:
+    resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
+    engines: {node: '>=14.17'}
+    hasBin: true
+
+  undici-types@6.19.8:
+    resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+
+  v8-compile-cache-lib@3.0.1:
+    resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
+
+  wrap-ansi@7.0.0:
+    resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+    engines: {node: '>=10'}
+
+  wrappy@1.0.2:
+    resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+  xtend@4.0.2:
+    resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
+    engines: {node: '>=0.4'}
+
+  y18n@5.0.8:
+    resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+    engines: {node: '>=10'}
+
+  yargs-parser@21.1.1:
+    resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+    engines: {node: '>=12'}
+
+  yargs@17.7.2:
+    resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+    engines: {node: '>=12'}
+
+  yn@3.1.1:
+    resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
+    engines: {node: '>=6'}
+
+snapshots:
+
+  '@cspotcode/source-map-support@0.8.1':
+    dependencies:
+      '@jridgewell/trace-mapping': 0.3.9
+
+  '@jridgewell/resolve-uri@3.1.2': {}
+
+  '@jridgewell/sourcemap-codec@1.5.0': {}
+
+  '@jridgewell/trace-mapping@0.3.9':
+    dependencies:
+      '@jridgewell/resolve-uri': 3.1.2
+      '@jridgewell/sourcemap-codec': 1.5.0
+
+  '@tsconfig/node10@1.0.11': {}
+
+  '@tsconfig/node12@1.0.11': {}
+
+  '@tsconfig/node14@1.0.3': {}
+
+  '@tsconfig/node16@1.0.4': {}
+
+  '@types/node@22.9.1':
+    dependencies:
+      undici-types: 6.19.8
+
+  '@types/strip-bom@3.0.0': {}
+
+  '@types/strip-json-comments@0.0.30': {}
+
+  acorn-walk@8.3.4:
+    dependencies:
+      acorn: 8.14.0
+
+  acorn@8.14.0: {}
+
+  ansi-regex@5.0.1: {}
+
+  ansi-styles@4.3.0:
+    dependencies:
+      color-convert: 2.0.1
+
+  anymatch@3.1.3:
+    dependencies:
+      normalize-path: 3.0.0
+      picomatch: 2.3.1
+
+  arg@4.1.3: {}
+
+  balanced-match@1.0.2: {}
+
+  binary-extensions@2.3.0: {}
+
+  brace-expansion@1.1.11:
+    dependencies:
+      balanced-match: 1.0.2
+      concat-map: 0.0.1
+
+  braces@3.0.3:
+    dependencies:
+      fill-range: 7.1.1
+
+  buffer-from@1.1.2: {}
+
+  chalk@4.1.2:
+    dependencies:
+      ansi-styles: 4.3.0
+      supports-color: 7.2.0
+
+  chokidar@3.6.0:
+    dependencies:
+      anymatch: 3.1.3
+      braces: 3.0.3
+      glob-parent: 5.1.2
+      is-binary-path: 2.1.0
+      is-glob: 4.0.3
+      normalize-path: 3.0.0
+      readdirp: 3.6.0
+    optionalDependencies:
+      fsevents: 2.3.3
+
+  cliui@8.0.1:
+    dependencies:
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+      wrap-ansi: 7.0.0
+
+  color-convert@2.0.1:
+    dependencies:
+      color-name: 1.1.4
+
+  color-name@1.1.4: {}
+
+  concat-map@0.0.1: {}
+
+  concurrently@9.1.0:
+    dependencies:
+      chalk: 4.1.2
+      lodash: 4.17.21
+      rxjs: 7.8.1
+      shell-quote: 1.8.1
+      supports-color: 8.1.1
+      tree-kill: 1.2.2
+      yargs: 17.7.2
+
+  create-require@1.1.1: {}
+
+  diff@4.0.2: {}
+
+  dynamic-dedupe@0.3.0:
+    dependencies:
+      xtend: 4.0.2
+
+  emoji-regex@8.0.0: {}
+
+  escalade@3.2.0: {}
+
+  fill-range@7.1.1:
+    dependencies:
+      to-regex-range: 5.0.1
+
+  fs.realpath@1.0.0: {}
+
+  fsevents@2.3.3:
+    optional: true
+
+  function-bind@1.1.2: {}
+
+  get-caller-file@2.0.5: {}
+
+  glob-parent@5.1.2:
+    dependencies:
+      is-glob: 4.0.3
+
+  glob@7.2.3:
+    dependencies:
+      fs.realpath: 1.0.0
+      inflight: 1.0.6
+      inherits: 2.0.4
+      minimatch: 3.1.2
+      once: 1.4.0
+      path-is-absolute: 1.0.1
+
+  has-flag@4.0.0: {}
+
+  hasown@2.0.2:
+    dependencies:
+      function-bind: 1.1.2
+
+  inflight@1.0.6:
+    dependencies:
+      once: 1.4.0
+      wrappy: 1.0.2
+
+  inherits@2.0.4: {}
+
+  is-binary-path@2.1.0:
+    dependencies:
+      binary-extensions: 2.3.0
+
+  is-core-module@2.15.1:
+    dependencies:
+      hasown: 2.0.2
+
+  is-extglob@2.1.1: {}
+
+  is-fullwidth-code-point@3.0.0: {}
+
+  is-glob@4.0.3:
+    dependencies:
+      is-extglob: 2.1.1
+
+  is-number@7.0.0: {}
+
+  lodash@4.17.21: {}
+
+  make-error@1.3.6: {}
+
+  minimatch@3.1.2:
+    dependencies:
+      brace-expansion: 1.1.11
+
+  minimist@1.2.8: {}
+
+  mkdirp@1.0.4: {}
+
+  normalize-path@3.0.0: {}
+
+  once@1.4.0:
+    dependencies:
+      wrappy: 1.0.2
+
+  path-is-absolute@1.0.1: {}
+
+  path-parse@1.0.7: {}
+
+  picomatch@2.3.1: {}
+
+  readdirp@3.6.0:
+    dependencies:
+      picomatch: 2.3.1
+
+  require-directory@2.1.1: {}
+
+  resolve@1.22.8:
+    dependencies:
+      is-core-module: 2.15.1
+      path-parse: 1.0.7
+      supports-preserve-symlinks-flag: 1.0.0
+
+  rimraf@2.7.1:
+    dependencies:
+      glob: 7.2.3
+
+  rxjs@7.8.1:
+    dependencies:
+      tslib: 2.8.1
+
+  shell-quote@1.8.1: {}
+
+  source-map-support@0.5.21:
+    dependencies:
+      buffer-from: 1.1.2
+      source-map: 0.6.1
+
+  source-map@0.6.1: {}
+
+  string-width@4.2.3:
+    dependencies:
+      emoji-regex: 8.0.0
+      is-fullwidth-code-point: 3.0.0
+      strip-ansi: 6.0.1
+
+  strip-ansi@6.0.1:
+    dependencies:
+      ansi-regex: 5.0.1
+
+  strip-bom@3.0.0: {}
+
+  strip-json-comments@2.0.1: {}
+
+  supports-color@7.2.0:
+    dependencies:
+      has-flag: 4.0.0
+
+  supports-color@8.1.1:
+    dependencies:
+      has-flag: 4.0.0
+
+  supports-preserve-symlinks-flag@1.0.0: {}
+
+  to-regex-range@5.0.1:
+    dependencies:
+      is-number: 7.0.0
+
+  tree-kill@1.2.2: {}
+
+  ts-node-dev@2.0.0(@types/node@22.9.1)(typescript@5.6.3):
+    dependencies:
+      chokidar: 3.6.0
+      dynamic-dedupe: 0.3.0
+      minimist: 1.2.8
+      mkdirp: 1.0.4
+      resolve: 1.22.8
+      rimraf: 2.7.1
+      source-map-support: 0.5.21
+      tree-kill: 1.2.2
+      ts-node: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
+      tsconfig: 7.0.0
+      typescript: 5.6.3
+    transitivePeerDependencies:
+      - '@swc/core'
+      - '@swc/wasm'
+      - '@types/node'
+
+  ts-node@10.9.2(@types/node@22.9.1)(typescript@5.6.3):
+    dependencies:
+      '@cspotcode/source-map-support': 0.8.1
+      '@tsconfig/node10': 1.0.11
+      '@tsconfig/node12': 1.0.11
+      '@tsconfig/node14': 1.0.3
+      '@tsconfig/node16': 1.0.4
+      '@types/node': 22.9.1
+      acorn: 8.14.0
+      acorn-walk: 8.3.4
+      arg: 4.1.3
+      create-require: 1.1.1
+      diff: 4.0.2
+      make-error: 1.3.6
+      typescript: 5.6.3
+      v8-compile-cache-lib: 3.0.1
+      yn: 3.1.1
+
+  tsconfig@7.0.0:
+    dependencies:
+      '@types/strip-bom': 3.0.0
+      '@types/strip-json-comments': 0.0.30
+      strip-bom: 3.0.0
+      strip-json-comments: 2.0.1
+
+  tslib@2.8.1: {}
+
+  typescript@5.6.3: {}
+
+  undici-types@6.19.8: {}
+
+  v8-compile-cache-lib@3.0.1: {}
+
+  wrap-ansi@7.0.0:
+    dependencies:
+      ansi-styles: 4.3.0
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+
+  wrappy@1.0.2: {}
+
+  xtend@4.0.2: {}
+
+  y18n@5.0.8: {}
+
+  yargs-parser@21.1.1: {}
+
+  yargs@17.7.2:
+    dependencies:
+      cliui: 8.0.1
+      escalade: 3.2.0
+      get-caller-file: 2.0.5
+      require-directory: 2.1.1
+      string-width: 4.2.3
+      y18n: 5.0.8
+      yargs-parser: 21.1.1
+
+  yn@3.1.1: {}
diff --git a/pi/server/package.json b/pi/server/package.json
index aa89611..d911d0b 100644
--- a/pi/server/package.json
+++ b/pi/server/package.json
@@ -2,11 +2,12 @@
   "name": "uwb-server",
   "version": "0.1.0",
   "description": "",
-  "main": "index.js",
+  "main": "src/server.js",
   "type": "module",
   "scripts": {
     "start": "node dist/server.js",
-    "build": "tsc --outDir dist"
+    "build": "tsc --outDir dist",
+    "dev": "node --experimental-loader ts-node/esm --no-warnings --watch ./src/server.ts"
   },
   "keywords": [],
   "author": "",
@@ -20,7 +21,9 @@
   "devDependencies": {
     "@types/express": "^5.0.0",
     "@types/node": "^22.9.1",
+    "http-proxy-middleware": "^3.0.3",
     "ts-node": "^10.9.2",
+    "ts-node-dev": "^2.0.0",
     "typescript": "^5.6.3"
   }
 }
diff --git a/pi/server/pnpm-lock.yaml b/pi/server/pnpm-lock.yaml
index 58d78b0..7c963b5 100644
--- a/pi/server/pnpm-lock.yaml
+++ b/pi/server/pnpm-lock.yaml
@@ -24,9 +24,15 @@ importers:
       '@types/node':
         specifier: ^22.9.1
         version: 22.9.1
+      http-proxy-middleware:
+        specifier: ^3.0.3
+        version: 3.0.3
       ts-node:
         specifier: ^10.9.2
         version: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
+      ts-node-dev:
+        specifier: ^2.0.0
+        version: 2.0.0(@types/node@22.9.1)(typescript@5.6.3)
       typescript:
         specifier: ^5.6.3
         version: 5.6.3
@@ -87,6 +93,9 @@ packages:
   '@types/http-errors@2.0.4':
     resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
 
+  '@types/http-proxy@1.17.15':
+    resolution: {integrity: sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==}
+
   '@types/mime@1.3.5':
     resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
 
@@ -105,6 +114,12 @@ packages:
   '@types/serve-static@1.15.7':
     resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==}
 
+  '@types/strip-bom@3.0.0':
+    resolution: {integrity: sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==}
+
+  '@types/strip-json-comments@0.0.30':
+    resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==}
+
   accepts@1.3.8:
     resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
     engines: {node: '>= 0.6'}
@@ -118,20 +133,41 @@ packages:
     engines: {node: '>=0.4.0'}
     hasBin: true
 
+  anymatch@3.1.3:
+    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+    engines: {node: '>= 8'}
+
   arg@4.1.3:
     resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
 
   array-flatten@1.1.1:
     resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
 
+  balanced-match@1.0.2:
+    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
   base64id@2.0.0:
     resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
     engines: {node: ^4.5.0 || >= 5.9}
 
+  binary-extensions@2.3.0:
+    resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
+    engines: {node: '>=8'}
+
   body-parser@1.20.3:
     resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
     engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
 
+  brace-expansion@1.1.11:
+    resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+
+  braces@3.0.3:
+    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+    engines: {node: '>=8'}
+
+  buffer-from@1.1.2:
+    resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+
   bytes@3.1.2:
     resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
     engines: {node: '>= 0.8'}
@@ -140,9 +176,16 @@ packages:
     resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
     engines: {node: '>= 0.4'}
 
+  chokidar@3.6.0:
+    resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
+    engines: {node: '>= 8.10.0'}
+
   complex.js@2.4.2:
     resolution: {integrity: sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g==}
 
+  concat-map@0.0.1:
+    resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
   content-disposition@0.5.4:
     resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
     engines: {node: '>= 0.6'}
@@ -205,6 +248,9 @@ packages:
     resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
     engines: {node: '>=0.3.1'}
 
+  dynamic-dedupe@0.3.0:
+    resolution: {integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==}
+
   ee-first@1.1.1:
     resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
 
@@ -242,14 +288,30 @@ packages:
     resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
     engines: {node: '>= 0.6'}
 
+  eventemitter3@4.0.7:
+    resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
+
   express@4.21.1:
     resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==}
     engines: {node: '>= 0.10.0'}
 
+  fill-range@7.1.1:
+    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+    engines: {node: '>=8'}
+
   finalhandler@1.3.1:
     resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
     engines: {node: '>= 0.8'}
 
+  follow-redirects@1.15.9:
+    resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
+    engines: {node: '>=4.0'}
+    peerDependencies:
+      debug: '*'
+    peerDependenciesMeta:
+      debug:
+        optional: true
+
   forwarded@0.2.0:
     resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
     engines: {node: '>= 0.6'}
@@ -262,6 +324,14 @@ packages:
     resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
     engines: {node: '>= 0.6'}
 
+  fs.realpath@1.0.0:
+    resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+  fsevents@2.3.3:
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+
   function-bind@1.1.2:
     resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
 
@@ -269,6 +339,14 @@ packages:
     resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
     engines: {node: '>= 0.4'}
 
+  glob-parent@5.1.2:
+    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+    engines: {node: '>= 6'}
+
+  glob@7.2.3:
+    resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+    deprecated: Glob versions prior to v9 are no longer supported
+
   gopd@1.0.1:
     resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
 
@@ -291,10 +369,22 @@ packages:
     resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
     engines: {node: '>= 0.8'}
 
+  http-proxy-middleware@3.0.3:
+    resolution: {integrity: sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==}
+    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+  http-proxy@1.18.1:
+    resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
+    engines: {node: '>=8.0.0'}
+
   iconv-lite@0.4.24:
     resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
     engines: {node: '>=0.10.0'}
 
+  inflight@1.0.6:
+    resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+    deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
+
   inherits@2.0.4:
     resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
 
@@ -302,6 +392,30 @@ packages:
     resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
     engines: {node: '>= 0.10'}
 
+  is-binary-path@2.1.0:
+    resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+    engines: {node: '>=8'}
+
+  is-core-module@2.15.1:
+    resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
+    engines: {node: '>= 0.4'}
+
+  is-extglob@2.1.1:
+    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+    engines: {node: '>=0.10.0'}
+
+  is-glob@4.0.3:
+    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+    engines: {node: '>=0.10.0'}
+
+  is-number@7.0.0:
+    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+    engines: {node: '>=0.12.0'}
+
+  is-plain-object@5.0.0:
+    resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
+    engines: {node: '>=0.10.0'}
+
   javascript-natural-sort@0.7.1:
     resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
 
@@ -324,6 +438,10 @@ packages:
     resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
     engines: {node: '>= 0.6'}
 
+  micromatch@4.0.8:
+    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+    engines: {node: '>=8.6'}
+
   mime-db@1.52.0:
     resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
     engines: {node: '>= 0.6'}
@@ -337,6 +455,17 @@ packages:
     engines: {node: '>=4'}
     hasBin: true
 
+  minimatch@3.1.2:
+    resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+  minimist@1.2.8:
+    resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+  mkdirp@1.0.4:
+    resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
+    engines: {node: '>=10'}
+    hasBin: true
+
   ms@2.0.0:
     resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
 
@@ -347,6 +476,10 @@ packages:
     resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
     engines: {node: '>= 0.6'}
 
+  normalize-path@3.0.0:
+    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+    engines: {node: '>=0.10.0'}
+
   object-assign@4.1.1:
     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
     engines: {node: '>=0.10.0'}
@@ -359,13 +492,27 @@ packages:
     resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
     engines: {node: '>= 0.8'}
 
+  once@1.4.0:
+    resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
   parseurl@1.3.3:
     resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
     engines: {node: '>= 0.8'}
 
+  path-is-absolute@1.0.1:
+    resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+    engines: {node: '>=0.10.0'}
+
+  path-parse@1.0.7:
+    resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
   path-to-regexp@0.1.10:
     resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==}
 
+  picomatch@2.3.1:
+    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+    engines: {node: '>=8.6'}
+
   proxy-addr@2.0.7:
     resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
     engines: {node: '>= 0.10'}
@@ -382,9 +529,25 @@ packages:
     resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
     engines: {node: '>= 0.8'}
 
+  readdirp@3.6.0:
+    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+    engines: {node: '>=8.10.0'}
+
   regenerator-runtime@0.14.1:
     resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
 
+  requires-port@1.0.0:
+    resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
+
+  resolve@1.22.8:
+    resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
+    hasBin: true
+
+  rimraf@2.7.1:
+    resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
+    deprecated: Rimraf versions prior to v4 are no longer supported
+    hasBin: true
+
   safe-buffer@5.2.1:
     resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
 
@@ -424,17 +587,55 @@ packages:
     resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==}
     engines: {node: '>=10.2.0'}
 
+  source-map-support@0.5.21:
+    resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
+
+  source-map@0.6.1:
+    resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+    engines: {node: '>=0.10.0'}
+
   statuses@2.0.1:
     resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
     engines: {node: '>= 0.8'}
 
+  strip-bom@3.0.0:
+    resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
+    engines: {node: '>=4'}
+
+  strip-json-comments@2.0.1:
+    resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
+    engines: {node: '>=0.10.0'}
+
+  supports-preserve-symlinks-flag@1.0.0:
+    resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+    engines: {node: '>= 0.4'}
+
   tiny-emitter@2.1.0:
     resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
 
+  to-regex-range@5.0.1:
+    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+    engines: {node: '>=8.0'}
+
   toidentifier@1.0.1:
     resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
     engines: {node: '>=0.6'}
 
+  tree-kill@1.2.2:
+    resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
+    hasBin: true
+
+  ts-node-dev@2.0.0:
+    resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==}
+    engines: {node: '>=0.8.0'}
+    hasBin: true
+    peerDependencies:
+      node-notifier: '*'
+      typescript: '*'
+    peerDependenciesMeta:
+      node-notifier:
+        optional: true
+
   ts-node@10.9.2:
     resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
     hasBin: true
@@ -449,6 +650,9 @@ packages:
       '@swc/wasm':
         optional: true
 
+  tsconfig@7.0.0:
+    resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==}
+
   type-is@1.6.18:
     resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
     engines: {node: '>= 0.6'}
@@ -480,6 +684,9 @@ packages:
     resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
     engines: {node: '>= 0.8'}
 
+  wrappy@1.0.2:
+    resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
   ws@8.17.1:
     resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
     engines: {node: '>=10.0.0'}
@@ -492,6 +699,10 @@ packages:
       utf-8-validate:
         optional: true
 
+  xtend@4.0.2:
+    resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
+    engines: {node: '>=0.4'}
+
   yn@3.1.1:
     resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
     engines: {node: '>=6'}
@@ -556,6 +767,10 @@ snapshots:
 
   '@types/http-errors@2.0.4': {}
 
+  '@types/http-proxy@1.17.15':
+    dependencies:
+      '@types/node': 22.9.1
+
   '@types/mime@1.3.5': {}
 
   '@types/node@22.9.1':
@@ -577,6 +792,10 @@ snapshots:
       '@types/node': 22.9.1
       '@types/send': 0.17.4
 
+  '@types/strip-bom@3.0.0': {}
+
+  '@types/strip-json-comments@0.0.30': {}
+
   accepts@1.3.8:
     dependencies:
       mime-types: 2.1.35
@@ -588,12 +807,21 @@ snapshots:
 
   acorn@8.14.0: {}
 
+  anymatch@3.1.3:
+    dependencies:
+      normalize-path: 3.0.0
+      picomatch: 2.3.1
+
   arg@4.1.3: {}
 
   array-flatten@1.1.1: {}
 
+  balanced-match@1.0.2: {}
+
   base64id@2.0.0: {}
 
+  binary-extensions@2.3.0: {}
+
   body-parser@1.20.3:
     dependencies:
       bytes: 3.1.2
@@ -611,6 +839,17 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  brace-expansion@1.1.11:
+    dependencies:
+      balanced-match: 1.0.2
+      concat-map: 0.0.1
+
+  braces@3.0.3:
+    dependencies:
+      fill-range: 7.1.1
+
+  buffer-from@1.1.2: {}
+
   bytes@3.1.2: {}
 
   call-bind@1.0.7:
@@ -621,8 +860,22 @@ snapshots:
       get-intrinsic: 1.2.4
       set-function-length: 1.2.2
 
+  chokidar@3.6.0:
+    dependencies:
+      anymatch: 3.1.3
+      braces: 3.0.3
+      glob-parent: 5.1.2
+      is-binary-path: 2.1.0
+      is-glob: 4.0.3
+      normalize-path: 3.0.0
+      readdirp: 3.6.0
+    optionalDependencies:
+      fsevents: 2.3.3
+
   complex.js@2.4.2: {}
 
+  concat-map@0.0.1: {}
+
   content-disposition@0.5.4:
     dependencies:
       safe-buffer: 5.2.1
@@ -664,6 +917,10 @@ snapshots:
 
   diff@4.0.2: {}
 
+  dynamic-dedupe@0.3.0:
+    dependencies:
+      xtend: 4.0.2
+
   ee-first@1.1.1: {}
 
   encodeurl@1.0.2: {}
@@ -701,6 +958,8 @@ snapshots:
 
   etag@1.8.1: {}
 
+  eventemitter3@4.0.7: {}
+
   express@4.21.1:
     dependencies:
       accepts: 1.3.8
@@ -737,6 +996,10 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  fill-range@7.1.1:
+    dependencies:
+      to-regex-range: 5.0.1
+
   finalhandler@1.3.1:
     dependencies:
       debug: 2.6.9
@@ -749,12 +1012,21 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  follow-redirects@1.15.9(debug@4.3.7):
+    optionalDependencies:
+      debug: 4.3.7
+
   forwarded@0.2.0: {}
 
   fraction.js@5.2.1: {}
 
   fresh@0.5.2: {}
 
+  fs.realpath@1.0.0: {}
+
+  fsevents@2.3.3:
+    optional: true
+
   function-bind@1.1.2: {}
 
   get-intrinsic@1.2.4:
@@ -765,6 +1037,19 @@ snapshots:
       has-symbols: 1.0.3
       hasown: 2.0.2
 
+  glob-parent@5.1.2:
+    dependencies:
+      is-glob: 4.0.3
+
+  glob@7.2.3:
+    dependencies:
+      fs.realpath: 1.0.0
+      inflight: 1.0.6
+      inherits: 2.0.4
+      minimatch: 3.1.2
+      once: 1.4.0
+      path-is-absolute: 1.0.1
+
   gopd@1.0.1:
     dependencies:
       get-intrinsic: 1.2.4
@@ -789,14 +1074,56 @@ snapshots:
       statuses: 2.0.1
       toidentifier: 1.0.1
 
+  http-proxy-middleware@3.0.3:
+    dependencies:
+      '@types/http-proxy': 1.17.15
+      debug: 4.3.7
+      http-proxy: 1.18.1(debug@4.3.7)
+      is-glob: 4.0.3
+      is-plain-object: 5.0.0
+      micromatch: 4.0.8
+    transitivePeerDependencies:
+      - supports-color
+
+  http-proxy@1.18.1(debug@4.3.7):
+    dependencies:
+      eventemitter3: 4.0.7
+      follow-redirects: 1.15.9(debug@4.3.7)
+      requires-port: 1.0.0
+    transitivePeerDependencies:
+      - debug
+
   iconv-lite@0.4.24:
     dependencies:
       safer-buffer: 2.1.2
 
+  inflight@1.0.6:
+    dependencies:
+      once: 1.4.0
+      wrappy: 1.0.2
+
   inherits@2.0.4: {}
 
   ipaddr.js@1.9.1: {}
 
+  is-binary-path@2.1.0:
+    dependencies:
+      binary-extensions: 2.3.0
+
+  is-core-module@2.15.1:
+    dependencies:
+      hasown: 2.0.2
+
+  is-extglob@2.1.1: {}
+
+  is-glob@4.0.3:
+    dependencies:
+      is-extglob: 2.1.1
+
+  is-number@7.0.0: {}
+
+  is-plain-object@5.0.0: {}
+
   javascript-natural-sort@0.7.1: {}
 
   make-error@1.3.6: {}
@@ -819,6 +1146,11 @@ snapshots:
 
   methods@1.1.2: {}
 
+  micromatch@4.0.8:
+    dependencies:
+      braces: 3.0.3
+      picomatch: 2.3.1
+
   mime-db@1.52.0: {}
 
   mime-types@2.1.35:
@@ -827,12 +1159,22 @@ snapshots:
 
   mime@1.6.0: {}
 
+  minimatch@3.1.2:
+    dependencies:
+      brace-expansion: 1.1.11
+
+  minimist@1.2.8: {}
+
+  mkdirp@1.0.4: {}
+
   ms@2.0.0: {}
 
   ms@2.1.3: {}
 
   negotiator@0.6.3: {}
 
+  normalize-path@3.0.0: {}
+
   object-assign@4.1.1: {}
 
   object-inspect@1.13.3: {}
@@ -841,10 +1183,20 @@ snapshots:
     dependencies:
       ee-first: 1.1.1
 
+  once@1.4.0:
+    dependencies:
+      wrappy: 1.0.2
+
   parseurl@1.3.3: {}
 
+  path-is-absolute@1.0.1: {}
+
+  path-parse@1.0.7: {}
+
   path-to-regexp@0.1.10: {}
 
+  picomatch@2.3.1: {}
+
   proxy-addr@2.0.7:
     dependencies:
       forwarded: 0.2.0
@@ -863,8 +1215,24 @@ snapshots:
       iconv-lite: 0.4.24
       unpipe: 1.0.0
 
+  readdirp@3.6.0:
+    dependencies:
+      picomatch: 2.3.1
+
   regenerator-runtime@0.14.1: {}
 
+  requires-port@1.0.0: {}
+
+  resolve@1.22.8:
+    dependencies:
+      is-core-module: 2.15.1
+      path-parse: 1.0.7
+      supports-preserve-symlinks-flag: 1.0.0
+
+  rimraf@2.7.1:
+    dependencies:
+      glob: 7.2.3
+
   safe-buffer@5.2.1: {}
 
   safer-buffer@2.1.2: {}
@@ -946,12 +1314,49 @@ snapshots:
       - supports-color
       - utf-8-validate
 
+  source-map-support@0.5.21:
+    dependencies:
+      buffer-from: 1.1.2
+      source-map: 0.6.1
+
+  source-map@0.6.1: {}
+
   statuses@2.0.1: {}
 
+  strip-bom@3.0.0: {}
+
+  strip-json-comments@2.0.1: {}
+
+  supports-preserve-symlinks-flag@1.0.0: {}
+
   tiny-emitter@2.1.0: {}
 
+  to-regex-range@5.0.1:
+    dependencies:
+      is-number: 7.0.0
+
   toidentifier@1.0.1: {}
 
+  tree-kill@1.2.2: {}
+
+  ts-node-dev@2.0.0(@types/node@22.9.1)(typescript@5.6.3):
+    dependencies:
+      chokidar: 3.6.0
+      dynamic-dedupe: 0.3.0
+      minimist: 1.2.8
+      mkdirp: 1.0.4
+      resolve: 1.22.8
+      rimraf: 2.7.1
+      source-map-support: 0.5.21
+      tree-kill: 1.2.2
+      ts-node: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
+      tsconfig: 7.0.0
+      typescript: 5.6.3
+    transitivePeerDependencies:
+      - '@swc/core'
+      - '@swc/wasm'
+      - '@types/node'
+
   ts-node@10.9.2(@types/node@22.9.1)(typescript@5.6.3):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
@@ -970,6 +1375,13 @@ snapshots:
       v8-compile-cache-lib: 3.0.1
       yn: 3.1.1
 
+  tsconfig@7.0.0:
+    dependencies:
+      '@types/strip-bom': 3.0.0
+      '@types/strip-json-comments': 0.0.30
+      strip-bom: 3.0.0
+      strip-json-comments: 2.0.1
+
   type-is@1.6.18:
     dependencies:
       media-typer: 0.3.0
@@ -989,6 +1401,10 @@ snapshots:
 
   vary@1.1.2: {}
 
+  wrappy@1.0.2: {}
+
   ws@8.17.1: {}
 
+  xtend@4.0.2: {}
+
   yn@3.1.1: {}
diff --git a/pi/server/src/server.ts b/pi/server/src/server.ts
index a2e0293..b3fdc76 100644
--- a/pi/server/src/server.ts
+++ b/pi/server/src/server.ts
@@ -12,7 +12,7 @@ const io = new Server(httpServer, {
 
 const PORT = 5000;
 
-app.use(express.static(path.join(__dirname + '../../ui/index.html')));
+app.use(express.static(path.join(__dirname + '../../ui')));
 
 app.get('/', (req, res) => {
   res.sendFile(path.join(__dirname + '../../ui/index.html'));
diff --git a/pi/ui/package.json b/pi/ui/package.json
index ae28a45..1e48912 100644
--- a/pi/ui/package.json
+++ b/pi/ui/package.json
@@ -26,7 +26,7 @@
     "@vitejs/plugin-vue": "^5.1.4",
     "@vue/eslint-config-prettier": "^10.1.0",
     "@vue/eslint-config-typescript": "^14.1.3",
-    "@vue/tsconfig": "^0.5.1",
+    "@vue/tsconfig": "^0.6.0",
     "eslint": "^9.14.0",
     "eslint-plugin-vue": "^9.30.0",
     "npm-run-all2": "^7.0.1",
@@ -36,5 +36,5 @@
     "vite-plugin-vue-devtools": "^7.5.4",
     "vue-tsc": "^2.1.10"
   },
-  "packageManager": "pnpm@9.12.0+sha512.4abf725084d7bcbafbd728bfc7bee61f2f791f977fd87542b3579dcb23504d170d46337945e4c66485cd12d588a0c0e570ed9c477e7ccdd8507cf05f3f92eaca"
+  "packageManager": "pnpm@9.14.2"
 }
diff --git a/pi/ui/pnpm-lock.yaml b/pi/ui/pnpm-lock.yaml
index 7addf84..9845b25 100644
--- a/pi/ui/pnpm-lock.yaml
+++ b/pi/ui/pnpm-lock.yaml
@@ -43,8 +43,8 @@ importers:
         specifier: ^14.1.3
         version: 14.1.3(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint-plugin-vue@9.31.0(eslint@9.15.0))(eslint@9.15.0)(typescript@5.6.3)
       '@vue/tsconfig':
-        specifier: ^0.5.1
-        version: 0.5.1
+        specifier: ^0.6.0
+        version: 0.6.0(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3))
       eslint:
         specifier: ^9.14.0
         version: 9.15.0
@@ -819,8 +819,16 @@ packages:
   '@vue/shared@3.5.13':
     resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
 
-  '@vue/tsconfig@0.5.1':
-    resolution: {integrity: sha512-VcZK7MvpjuTPx2w6blwnwZAu5/LgBUtejFOi3pPGQFXQN5Ela03FUtd2Qtg4yWGGissVL0dr6Ro1LfOFh+PCuQ==}
+  '@vue/tsconfig@0.6.0':
+    resolution: {integrity: sha512-MHXNd6lzugsEHvuA6l1GqrF5jROqUon8sP/HInLPnthJiYvB0VvpHMywg7em1dBZfFZNBSkR68qH37zOdRHmCw==}
+    peerDependencies:
+      typescript: 5.x
+      vue: ^3.3.0
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+      vue:
+        optional: true
 
   acorn-jsx@5.3.2:
     resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
@@ -2674,7 +2682,10 @@ snapshots:
 
   '@vue/shared@3.5.13': {}
 
-  '@vue/tsconfig@0.5.1': {}
+  '@vue/tsconfig@0.6.0(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3))':
+    optionalDependencies:
+      typescript: 5.6.3
+      vue: 3.5.13(typescript@5.6.3)
 
   acorn-jsx@5.3.2(acorn@8.14.0):
     dependencies:
diff --git a/pi/ui/src/components/UWBVisualization.vue b/pi/ui/src/components/UWBVisualization.vue
index b378711..2916911 100644
--- a/pi/ui/src/components/UWBVisualization.vue
+++ b/pi/ui/src/components/UWBVisualization.vue
@@ -25,22 +25,21 @@ export default defineComponent({
 
     // D3 variables
     let svg: d3.Selection<SVGSVGElement, unknown, HTMLElement, any>; // eslint-disable-line
-    const width = 600;
-    const height = 400;
+    const width = 1000;
+    const height = 800;
+    const padding = 20; // Padding around the visualization
 
     // Scales
     let xScale: d3.ScaleLinear<number, number>;
     let yScale: d3.ScaleLinear<number, number>;
 
     onMounted(() => {
-      // Fetch the configuration file from the backend
       fetch('/api/config')
         .then((response) => response.json())
         .then((configData: AnchorConfig) => {
           parseConfiguration(configData);
           initVisualization();
 
-          // Listen for UWB data
           socket.on('uwb_data', (data: AnchorData[]) => {
             uwbData.value = data;
             updateVisualization();
@@ -66,16 +65,18 @@ export default defineComponent({
         return;
       }
 
+      const scalingFactor = 50; // Adjust to control visualization size
+
       positions[ids[0]] = [0, 0]; // First anchor at (0, 0)
 
-      const d1 = getDistance(ids[0], ids[1]);
+      const d1 = getDistance(ids[0], ids[1]) * scalingFactor;
       positions[ids[1]] = [d1, 0]; // Second anchor on x-axis
 
-      const d2 = getDistance(ids[0], ids[2]);
-      const d3 = getDistance(ids[1], ids[2]);
+      const d2 = getDistance(ids[0], ids[2]) * scalingFactor;
+      const d3 = getDistance(ids[1], ids[2]) * scalingFactor;
 
       const x = (d1 ** 2 + d2 ** 2 - d3 ** 2) / (2 * d1);
-      const y = Math.sqrt(d2 ** 2 - x ** 2);
+      const y = Math.sqrt(Math.max(0, d2 ** 2 - x ** 2)); // Ensure no NaN from negative sqrt
 
       positions[ids[2]] = [x, y];
 
@@ -95,13 +96,13 @@ export default defineComponent({
       const allX = Object.values(anchorPositions.value).map((pos) => pos[0]);
       const allY = Object.values(anchorPositions.value).map((pos) => pos[1]);
 
-      const minX = Math.min(...allX) - 5;
-      const maxX = Math.max(...allX) + 5;
-      const minY = Math.min(...allY) - 5;
-      const maxY = Math.max(...allY) + 5;
+      const minX = Math.min(...allX) - padding;
+      const maxX = Math.max(...allX) + padding;
+      const minY = Math.min(...allY) - padding;
+      const maxY = Math.max(...allY) + padding;
 
       xScale = d3.scaleLinear().domain([minX, maxX]).range([0, width]);
-      yScale = d3.scaleLinear().domain([minY, maxY]).range([height, 0]);
+      yScale = d3.scaleLinear().domain([minY, maxY]).range([0, height]);
     }
 
     function initVisualization() {
@@ -183,6 +184,7 @@ export default defineComponent({
         const F = r1 ** 2 - r3 ** 2 - x1 ** 2 + x3 ** 2 - y1 ** 2 + y3 ** 2;
 
         const denominator = A * E - B * D;
+
         if (denominator === 0) {
           console.error('Cannot solve, determinant is zero.');
           return [-1, -1];
diff --git a/pi/ui/vite.config.ts b/pi/ui/vite.config.ts
index 4dbb315..1798a4c 100644
--- a/pi/ui/vite.config.ts
+++ b/pi/ui/vite.config.ts
@@ -14,5 +14,10 @@ export default defineConfig({
     alias: {
       '@': fileURLToPath(new URL('./src', import.meta.url))
     },
-  }
+  },
+  server: {
+    proxy: {
+      '/api': 'http://localhost:5000',
+    },
+  },
 })

From e94f543e7866e2bf20051828cfd1911fd5bf69ae Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Fri, 22 Nov 2024 13:34:15 -0500
Subject: [PATCH 08/34] Dynamic scaling for UI based on anchor distances (#17)

* Add dev env

* Change ui window size

* Update scales when updating vis

* Fix distances

* Refactor tag draw

* Fix scaling factoring
---
 pi/config/anchorPositions.json            |   6 +-
 pi/server/package.json                    |   5 +-
 pi/server/pnpm-lock.yaml                  |  93 +++++++++++++++++--
 pi/server/src/server.ts                   |  34 +++++--
 pi/server/tsconfig.json                   |   3 +-
 pi/ui/src/components/UWBVisualization.vue | 106 +++++++++++++++++-----
 6 files changed, 206 insertions(+), 41 deletions(-)

diff --git a/pi/config/anchorPositions.json b/pi/config/anchorPositions.json
index 9dbf566..e4a8e52 100644
--- a/pi/config/anchorPositions.json
+++ b/pi/config/anchorPositions.json
@@ -1,8 +1,8 @@
 {
   "anchors": ["50", "51", "52"],
   "distances": {
-    "50-51": 2.4,
-    "50-52": 2.93,
-    "51-52": 1.18
+    "50-51": 5.42,
+    "50-52": 1.92,
+    "51-52": 6.21
   }
 }
diff --git a/pi/server/package.json b/pi/server/package.json
index d911d0b..2cbbbda 100644
--- a/pi/server/package.json
+++ b/pi/server/package.json
@@ -7,21 +7,24 @@
   "scripts": {
     "start": "node dist/server.js",
     "build": "tsc --outDir dist",
-    "dev": "node --experimental-loader ts-node/esm --no-warnings --watch ./src/server.ts"
+    "dev": "nodemon --watch src --ext ts --exec 'node --loader ts-node/esm ./src/server.ts'"
   },
   "keywords": [],
   "author": "",
   "license": "ISC",
   "packageManager": "pnpm@9.14.2",
   "dependencies": {
+    "cors": "^2.8.5",
     "express": "^4.21.1",
     "mathjs": "^14.0.0",
     "socket.io": "^4.8.1"
   },
   "devDependencies": {
+    "@types/cors": "^2.8.17",
     "@types/express": "^5.0.0",
     "@types/node": "^22.9.1",
     "http-proxy-middleware": "^3.0.3",
+    "nodemon": "^3.1.7",
     "ts-node": "^10.9.2",
     "ts-node-dev": "^2.0.0",
     "typescript": "^5.6.3"
diff --git a/pi/server/pnpm-lock.yaml b/pi/server/pnpm-lock.yaml
index 7c963b5..ad21140 100644
--- a/pi/server/pnpm-lock.yaml
+++ b/pi/server/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
 
   .:
     dependencies:
+      cors:
+        specifier: ^2.8.5
+        version: 2.8.5
       express:
         specifier: ^4.21.1
         version: 4.21.1
@@ -18,6 +21,9 @@ importers:
         specifier: ^4.8.1
         version: 4.8.1
     devDependencies:
+      '@types/cors':
+        specifier: ^2.8.17
+        version: 2.8.17
       '@types/express':
         specifier: ^5.0.0
         version: 5.0.0
@@ -27,6 +33,9 @@ importers:
       http-proxy-middleware:
         specifier: ^3.0.3
         version: 3.0.3
+      nodemon:
+        specifier: ^3.1.7
+        version: 3.1.7
       ts-node:
         specifier: ^10.9.2
         version: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
@@ -350,6 +359,10 @@ packages:
   gopd@1.0.1:
     resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
 
+  has-flag@3.0.0:
+    resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
+    engines: {node: '>=4'}
+
   has-property-descriptors@1.0.2:
     resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
 
@@ -381,6 +394,9 @@ packages:
     resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
     engines: {node: '>=0.10.0'}
 
+  ignore-by-default@1.0.1:
+    resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
+
   inflight@1.0.6:
     resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
     deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
@@ -476,6 +492,11 @@ packages:
     resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
     engines: {node: '>= 0.6'}
 
+  nodemon@3.1.7:
+    resolution: {integrity: sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==}
+    engines: {node: '>=10'}
+    hasBin: true
+
   normalize-path@3.0.0:
     resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
     engines: {node: '>=0.10.0'}
@@ -517,6 +538,9 @@ packages:
     resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
     engines: {node: '>= 0.10'}
 
+  pstree.remy@1.1.8:
+    resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
+
   qs@6.13.0:
     resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
     engines: {node: '>=0.6'}
@@ -557,6 +581,11 @@ packages:
   seedrandom@3.0.5:
     resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
 
+  semver@7.6.3:
+    resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
+    engines: {node: '>=10'}
+    hasBin: true
+
   send@0.19.0:
     resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
     engines: {node: '>= 0.8.0'}
@@ -576,6 +605,10 @@ packages:
     resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
     engines: {node: '>= 0.4'}
 
+  simple-update-notifier@2.0.0:
+    resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
+    engines: {node: '>=10'}
+
   socket.io-adapter@2.5.5:
     resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==}
 
@@ -606,6 +639,10 @@ packages:
     resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
     engines: {node: '>=0.10.0'}
 
+  supports-color@5.5.0:
+    resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
+    engines: {node: '>=4'}
+
   supports-preserve-symlinks-flag@1.0.0:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
     engines: {node: '>= 0.4'}
@@ -621,6 +658,10 @@ packages:
     resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
     engines: {node: '>=0.6'}
 
+  touch@3.1.1:
+    resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==}
+    hasBin: true
+
   tree-kill@1.2.2:
     resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
     hasBin: true
@@ -666,6 +707,9 @@ packages:
     engines: {node: '>=14.17'}
     hasBin: true
 
+  undefsafe@2.0.5:
+    resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
+
   undici-types@6.19.8:
     resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
 
@@ -899,9 +943,11 @@ snapshots:
     dependencies:
       ms: 2.0.0
 
-  debug@4.3.7:
+  debug@4.3.7(supports-color@5.5.0):
     dependencies:
       ms: 2.1.3
+    optionalDependencies:
+      supports-color: 5.5.0
 
   decimal.js@10.4.3: {}
 
@@ -938,7 +984,7 @@ snapshots:
       base64id: 2.0.0
       cookie: 0.7.2
       cors: 2.8.5
-      debug: 4.3.7
+      debug: 4.3.7(supports-color@5.5.0)
       engine.io-parser: 5.2.3
       ws: 8.17.1
     transitivePeerDependencies:
@@ -1014,7 +1060,7 @@ snapshots:
 
   follow-redirects@1.15.9(debug@4.3.7):
     optionalDependencies:
-      debug: 4.3.7
+      debug: 4.3.7(supports-color@5.5.0)
 
   forwarded@0.2.0: {}
 
@@ -1054,6 +1100,8 @@ snapshots:
     dependencies:
       get-intrinsic: 1.2.4
 
+  has-flag@3.0.0: {}
+
   has-property-descriptors@1.0.2:
     dependencies:
       es-define-property: 1.0.0
@@ -1077,7 +1125,7 @@ snapshots:
   http-proxy-middleware@3.0.3:
     dependencies:
       '@types/http-proxy': 1.17.15
-      debug: 4.3.7
+      debug: 4.3.7(supports-color@5.5.0)
       http-proxy: 1.18.1(debug@4.3.7)
       is-glob: 4.0.3
       is-plain-object: 5.0.0
@@ -1097,6 +1145,8 @@ snapshots:
     dependencies:
       safer-buffer: 2.1.2
 
+  ignore-by-default@1.0.1: {}
+
   inflight@1.0.6:
     dependencies:
       once: 1.4.0
@@ -1173,6 +1223,19 @@ snapshots:
 
   negotiator@0.6.3: {}
 
+  nodemon@3.1.7:
+    dependencies:
+      chokidar: 3.6.0
+      debug: 4.3.7(supports-color@5.5.0)
+      ignore-by-default: 1.0.1
+      minimatch: 3.1.2
+      pstree.remy: 1.1.8
+      semver: 7.6.3
+      simple-update-notifier: 2.0.0
+      supports-color: 5.5.0
+      touch: 3.1.1
+      undefsafe: 2.0.5
+
   normalize-path@3.0.0: {}
 
   object-assign@4.1.1: {}
@@ -1202,6 +1265,8 @@ snapshots:
       forwarded: 0.2.0
       ipaddr.js: 1.9.1
 
+  pstree.remy@1.1.8: {}
+
   qs@6.13.0:
     dependencies:
       side-channel: 1.0.6
@@ -1239,6 +1304,8 @@ snapshots:
 
   seedrandom@3.0.5: {}
 
+  semver@7.6.3: {}
+
   send@0.19.0:
     dependencies:
       debug: 2.6.9
@@ -1284,9 +1351,13 @@ snapshots:
       get-intrinsic: 1.2.4
       object-inspect: 1.13.3
 
+  simple-update-notifier@2.0.0:
+    dependencies:
+      semver: 7.6.3
+
   socket.io-adapter@2.5.5:
     dependencies:
-      debug: 4.3.7
+      debug: 4.3.7(supports-color@5.5.0)
       ws: 8.17.1
     transitivePeerDependencies:
       - bufferutil
@@ -1296,7 +1367,7 @@ snapshots:
   socket.io-parser@4.2.4:
     dependencies:
       '@socket.io/component-emitter': 3.1.2
-      debug: 4.3.7
+      debug: 4.3.7(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -1305,7 +1376,7 @@ snapshots:
       accepts: 1.3.8
       base64id: 2.0.0
       cors: 2.8.5
-      debug: 4.3.7
+      debug: 4.3.7(supports-color@5.5.0)
       engine.io: 6.6.2
       socket.io-adapter: 2.5.5
       socket.io-parser: 4.2.4
@@ -1327,6 +1398,10 @@ snapshots:
 
   strip-json-comments@2.0.1: {}
 
+  supports-color@5.5.0:
+    dependencies:
+      has-flag: 3.0.0
+
   supports-preserve-symlinks-flag@1.0.0: {}
 
   tiny-emitter@2.1.0: {}
@@ -1337,6 +1412,8 @@ snapshots:
 
   toidentifier@1.0.1: {}
 
+  touch@3.1.1: {}
+
   tree-kill@1.2.2: {}
 
   ts-node-dev@2.0.0(@types/node@22.9.1)(typescript@5.6.3):
@@ -1391,6 +1468,8 @@ snapshots:
 
   typescript@5.6.3: {}
 
+  undefsafe@2.0.5: {}
+
   undici-types@6.19.8: {}
 
   unpipe@1.0.0: {}
diff --git a/pi/server/src/server.ts b/pi/server/src/server.ts
index b3fdc76..b41691e 100644
--- a/pi/server/src/server.ts
+++ b/pi/server/src/server.ts
@@ -2,7 +2,14 @@ import express from 'express';
 import { createServer } from 'http';
 import { Server } from 'socket.io';
 import net from 'net';
+import { fileURLToPath } from 'url';
+import { dirname } from 'path';
 import path from 'path';
+import cors from 'cors';
+
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
 
 const app = express();
 const httpServer = createServer(app);
@@ -11,17 +18,32 @@ const io = new Server(httpServer, {
 });
 
 const PORT = 5000;
-
-app.use(express.static(path.join(__dirname + '../../ui')));
-
-app.get('/', (req, res) => {
-  res.sendFile(path.join(__dirname + '../../ui/index.html'));
-});
+app.use(cors()); // Allow all origins
 
 app.get('/api/config', (req, res) => {
   res.sendFile(path.join(__dirname, '../../config/anchorPositions.json'));
 });
 
+// Proxy to Vite dev server in development
+if (process.env.NODE_ENV === 'development') {
+  const { createProxyMiddleware } = await import('http-proxy-middleware');
+
+  app.use(
+    '/',
+    createProxyMiddleware({
+      target: 'http://localhost:5173',
+      changeOrigin: true,
+      ws: true,
+    })
+  );
+} else {
+  // Serve static files in production
+  app.use(express.static(path.join(__dirname, '../../ui')));
+  app.get('/', (req, res) => {
+    res.sendFile(path.join(__dirname, '../../ui/index.html'));
+  });
+}
+
 io.on('connection', (socket) => {
   console.log('A client connected');
 });
diff --git a/pi/server/tsconfig.json b/pi/server/tsconfig.json
index f566996..8edc27d 100644
--- a/pi/server/tsconfig.json
+++ b/pi/server/tsconfig.json
@@ -1,6 +1,7 @@
 {
   "compilerOptions": {
-    "module": "CommonJS",
+    "module": "NodeNext",
+    "moduleResolution": "nodenext",
     "target": "ESNext",
     "outDir": "./dist",
     "rootDir": "./src",
diff --git a/pi/ui/src/components/UWBVisualization.vue b/pi/ui/src/components/UWBVisualization.vue
index 2916911..b20ca2f 100644
--- a/pi/ui/src/components/UWBVisualization.vue
+++ b/pi/ui/src/components/UWBVisualization.vue
@@ -25,24 +25,31 @@ export default defineComponent({
 
     // D3 variables
     let svg: d3.Selection<SVGSVGElement, unknown, HTMLElement, any>; // eslint-disable-line
-    const width = 1000;
-    const height = 800;
+    const width = 600;
+    const height = 400;
     const padding = 20; // Padding around the visualization
 
     // Scales
     let xScale: d3.ScaleLinear<number, number>;
     let yScale: d3.ScaleLinear<number, number>;
 
+    const scalingFactor = 50; // Adjust to control visualization size
+
     onMounted(() => {
       fetch('/api/config')
         .then((response) => response.json())
         .then((configData: AnchorConfig) => {
+          console.log('## configData:', configData);
           parseConfiguration(configData);
           initVisualization();
 
           socket.on('uwb_data', (data: AnchorData[]) => {
             uwbData.value = data;
             updateVisualization();
+            if (uwbData?.value.length === 3) {
+              console.log('Incoming UWB data:', JSON.parse(JSON.stringify(uwbData.value)));
+            }
+
           });
         })
         .catch((error) => {
@@ -65,7 +72,6 @@ export default defineComponent({
         return;
       }
 
-      const scalingFactor = 50; // Adjust to control visualization size
 
       positions[ids[0]] = [0, 0]; // First anchor at (0, 0)
 
@@ -92,17 +98,22 @@ export default defineComponent({
       );
     }
 
-    function updateScales() {
+    function updateScales(tagPosition?: [number, number]) {
       const allX = Object.values(anchorPositions.value).map((pos) => pos[0]);
       const allY = Object.values(anchorPositions.value).map((pos) => pos[1]);
 
+      if (tagPosition) {
+        allX.push(tagPosition[0]);
+        allY.push(tagPosition[1]);
+      }
+
       const minX = Math.min(...allX) - padding;
       const maxX = Math.max(...allX) + padding;
       const minY = Math.min(...allY) - padding;
       const maxY = Math.max(...allY) + padding;
 
       xScale = d3.scaleLinear().domain([minX, maxX]).range([0, width]);
-      yScale = d3.scaleLinear().domain([minY, maxY]).range([0, height]);
+      yScale = d3.scaleLinear().domain([minY, maxY]).range([height, 0]); // Invert y-axis for SVG coordinates
     }
 
     function initVisualization() {
@@ -116,37 +127,54 @@ export default defineComponent({
     function drawAnchors() {
       const anchors = Object.entries(anchorPositions.value);
 
-      svg
-        .selectAll('.anchor')
-        .data(anchors)
-        .enter()
+      // Bind data to anchor circles
+      const anchorCircles = svg.selectAll('.anchor')
+        .data(anchors, (d) => d[0]);
+
+      // Enter selection
+      anchorCircles.enter()
         .append('circle')
         .attr('class', 'anchor')
-        .attr('cx', (d) => xScale(d[1][0]))
-        .attr('cy', (d) => yScale(d[1][1]))
         .attr('r', 5)
-        .attr('fill', 'green');
+        .attr('fill', 'green')
+        .merge(anchorCircles as any) // eslint-disable-line
+        .attr('cx', (d) => xScale(d[1][0]))
+        .attr('cy', (d) => yScale(d[1][1]));
 
-      svg
-        .selectAll('.anchor-label')
-        .data(anchors)
-        .enter()
+      // Exit selection
+      anchorCircles.exit().remove();
+
+      // Bind data to anchor labels
+      const anchorLabels = svg.selectAll('.anchor-label')
+        .data(anchors, (d) => d[0]);
+
+      // Enter selection
+      anchorLabels.enter()
         .append('text')
         .attr('class', 'anchor-label')
+        .attr('font-size', '12px')
+        .attr('fill', 'black')
+        .merge(anchorLabels as any) // eslint-disable-line
         .attr('x', (d) => xScale(d[1][0]) + 5)
         .attr('y', (d) => yScale(d[1][1]) - 5)
-        .text((d) => `A${d[0]}`)
-        .attr('font-size', '12px')
-        .attr('fill', 'black');
+        .text((d) => `A${d[0]}`);
+
+      // Exit selection
+      anchorLabels.exit().remove();
     }
 
+
     function updateVisualization() {
       const positions: [number, number][] = [];
       const distances: number[] = [];
 
       uwbData.value.forEach((anchor) => {
         const anchorID = anchor.A;
-        const range = parseFloat(anchor.R);
+        const range = parseFloat(anchor.R) * scalingFactor; // Scale the range
+        if (range <= 0) {
+          console.warn(`Invalid distance for anchor ${anchorID}: ${range}. Skipping.`);
+          return; // Skip invalid distances
+        }
         if (anchorPositions.value[anchorID]) {
           positions.push(anchorPositions.value[anchorID]);
           distances.push(range);
@@ -155,10 +183,35 @@ export default defineComponent({
 
       if (positions.length >= 3) {
         const [x, y] = calculateTagPosition(positions, distances);
-        drawTag(x, y);
+        if (x !== -1 && y !== -1) {
+          // Update scales to include tag position
+          updateScales([x, y]);
+          // Redraw anchors and tag
+          drawVisualization(x, y);
+        } else {
+          console.error('Invalid tag position calculated.');
+          svg.selectAll('.tag').remove(); // Remove tag if position is invalid
+        }
+      } else {
+        console.error('Not enough valid anchors with positive distances.');
+        svg.selectAll('.tag').remove(); // Remove tag if not enough data
       }
     }
 
+    function drawVisualization(tagX: number, tagY: number) {
+      // Clear existing anchors and tag
+      svg.selectAll('.anchor').remove();
+      svg.selectAll('.anchor-label').remove();
+      svg.selectAll('.tag').remove();
+
+      // Redraw anchors
+      drawAnchors();
+
+      // Draw tag
+      drawTag(tagX, tagY);
+    }
+
+
     function calculateTagPosition(
       positions: [number, number][],
       distances: number[]
@@ -204,16 +257,23 @@ export default defineComponent({
       svg.selectAll('.tag').remove();
 
       if (x !== -1 && y !== -1) {
+        const scaledX = xScale(x);
+        const scaledY = yScale(y);
+
+        console.log('Tag position:', x, y);
+        console.log('Scaled tag position:', scaledX, scaledY);
+
         svg
           .append('circle')
           .attr('class', 'tag')
-          .attr('cx', xScale(x))
-          .attr('cy', yScale(y))
+          .attr('cx', scaledX)
+          .attr('cy', scaledY)
           .attr('r', 5)
           .attr('fill', 'blue');
       }
     }
 
+
     return {};
   },
 });

From 215c7a510113f92cf058a8795aae0242f4499fcd Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Fri, 22 Nov 2024 13:49:53 -0500
Subject: [PATCH 09/34] Add lawndon-pi to tests workflow - lint UI

---
 .github/workflows/tests.yml               |  50 ++-
 pi/package.json                           |   3 +-
 pi/pnpm-lock.yaml                         | 498 ----------------------
 pi/ui/env.d.ts                            |   2 +-
 pi/ui/eslint.config.js                    | 144 ++++++-
 pi/ui/src/App.vue                         |   2 +-
 pi/ui/src/components/UWBVisualization.vue |  24 +-
 pi/ui/src/main.ts                         |  26 +-
 pi/ui/src/router/index.ts                 |  14 +-
 pi/ui/src/views/HomeView.vue              |   2 +-
 pi/ui/vite.config.ts                      |  22 +-
 11 files changed, 231 insertions(+), 556 deletions(-)

diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 187ed35..6792013 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -8,7 +8,7 @@ on:
     - cron: "0 4 * * 4"
 
 jobs:
-  build:
+  build-lawndon:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
@@ -40,3 +40,51 @@ jobs:
           verbose: false
           cli-compile-flags: |
             - --export-binaries
+
+  package-lawndon-pi:
+    runs-on: ubuntu-latest
+    with:
+      env:
+        WORKING_DIR: ./pi
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Install pnpm
+        uses: pnpm/action-setup@v4
+        with:
+          version: 9
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: '22'
+
+      - name: Install dependencies for UI
+        working-directory: ${{ env.WORKING_DIR }}
+        run: |
+          pnpm install:ui
+
+      - name: Build UI
+        working-directory: ${{ env.WORKING_DIR }}
+        run: |
+          pnpm build:ui
+
+      - name: Install dependencies for Server
+        working-directory: ${{ env.WORKING_DIR }}
+        run: |
+          pnpm install:server
+
+      - name: Build Server
+        working-directory: ${{ env.WORKING_DIR }}
+        run: |
+          pnpm build:server
+
+      - name: Run package script
+        working-directory: ${{ env.WORKING_DIR }}
+        run: |
+          ./package.sh
+
+      - name: Verify package contents
+        run: |
+          tar -tf lawndon-pi.tar.gz
diff --git a/pi/package.json b/pi/package.json
index 455b171..cd43688 100644
--- a/pi/package.json
+++ b/pi/package.json
@@ -14,7 +14,6 @@
   },
   "packageManager": "pnpm@9.14.2",
   "devDependencies": {
-    "concurrently": "^9.1.0",
-    "ts-node-dev": "^2.0.0"
+    "concurrently": "^9.1.0"
   }
 }
diff --git a/pi/pnpm-lock.yaml b/pi/pnpm-lock.yaml
index fad7d35..ca37c77 100644
--- a/pi/pnpm-lock.yaml
+++ b/pi/pnpm-lock.yaml
@@ -11,56 +11,9 @@ importers:
       concurrently:
         specifier: ^9.1.0
         version: 9.1.0
-      ts-node-dev:
-        specifier: ^2.0.0
-        version: 2.0.0(@types/node@22.9.1)(typescript@5.6.3)
 
 packages:
 
-  '@cspotcode/source-map-support@0.8.1':
-    resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
-    engines: {node: '>=12'}
-
-  '@jridgewell/resolve-uri@3.1.2':
-    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
-    engines: {node: '>=6.0.0'}
-
-  '@jridgewell/sourcemap-codec@1.5.0':
-    resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
-
-  '@jridgewell/trace-mapping@0.3.9':
-    resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
-
-  '@tsconfig/node10@1.0.11':
-    resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
-
-  '@tsconfig/node12@1.0.11':
-    resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
-
-  '@tsconfig/node14@1.0.3':
-    resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
-
-  '@tsconfig/node16@1.0.4':
-    resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
-
-  '@types/node@22.9.1':
-    resolution: {integrity: sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==}
-
-  '@types/strip-bom@3.0.0':
-    resolution: {integrity: sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==}
-
-  '@types/strip-json-comments@0.0.30':
-    resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==}
-
-  acorn-walk@8.3.4:
-    resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
-    engines: {node: '>=0.4.0'}
-
-  acorn@8.14.0:
-    resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
-    engines: {node: '>=0.4.0'}
-    hasBin: true
-
   ansi-regex@5.0.1:
     resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
     engines: {node: '>=8'}
@@ -69,38 +22,10 @@ packages:
     resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
     engines: {node: '>=8'}
 
-  anymatch@3.1.3:
-    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
-    engines: {node: '>= 8'}
-
-  arg@4.1.3:
-    resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
-
-  balanced-match@1.0.2:
-    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
-
-  binary-extensions@2.3.0:
-    resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
-    engines: {node: '>=8'}
-
-  brace-expansion@1.1.11:
-    resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
-
-  braces@3.0.3:
-    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
-    engines: {node: '>=8'}
-
-  buffer-from@1.1.2:
-    resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
-
   chalk@4.1.2:
     resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
     engines: {node: '>=10'}
 
-  chokidar@3.6.0:
-    resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
-    engines: {node: '>= 8.10.0'}
-
   cliui@8.0.1:
     resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
     engines: {node: '>=12'}
@@ -112,24 +37,11 @@ packages:
   color-name@1.1.4:
     resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
 
-  concat-map@0.0.1:
-    resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
-
   concurrently@9.1.0:
     resolution: {integrity: sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==}
     engines: {node: '>=18'}
     hasBin: true
 
-  create-require@1.1.1:
-    resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
-
-  diff@4.0.2:
-    resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
-    engines: {node: '>=0.3.1'}
-
-  dynamic-dedupe@0.3.0:
-    resolution: {integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==}
-
   emoji-regex@8.0.0:
     resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
 
@@ -137,137 +49,31 @@ packages:
     resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
     engines: {node: '>=6'}
 
-  fill-range@7.1.1:
-    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
-    engines: {node: '>=8'}
-
-  fs.realpath@1.0.0:
-    resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
-
-  fsevents@2.3.3:
-    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
-    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
-    os: [darwin]
-
-  function-bind@1.1.2:
-    resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
-
   get-caller-file@2.0.5:
     resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
     engines: {node: 6.* || 8.* || >= 10.*}
 
-  glob-parent@5.1.2:
-    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
-    engines: {node: '>= 6'}
-
-  glob@7.2.3:
-    resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
-    deprecated: Glob versions prior to v9 are no longer supported
-
   has-flag@4.0.0:
     resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
     engines: {node: '>=8'}
 
-  hasown@2.0.2:
-    resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
-    engines: {node: '>= 0.4'}
-
-  inflight@1.0.6:
-    resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
-    deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
-
-  inherits@2.0.4:
-    resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
-
-  is-binary-path@2.1.0:
-    resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
-    engines: {node: '>=8'}
-
-  is-core-module@2.15.1:
-    resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
-    engines: {node: '>= 0.4'}
-
-  is-extglob@2.1.1:
-    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
-    engines: {node: '>=0.10.0'}
-
   is-fullwidth-code-point@3.0.0:
     resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
     engines: {node: '>=8'}
 
-  is-glob@4.0.3:
-    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
-    engines: {node: '>=0.10.0'}
-
-  is-number@7.0.0:
-    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
-    engines: {node: '>=0.12.0'}
-
   lodash@4.17.21:
     resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
 
-  make-error@1.3.6:
-    resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
-
-  minimatch@3.1.2:
-    resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
-
-  minimist@1.2.8:
-    resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
-
-  mkdirp@1.0.4:
-    resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
-    engines: {node: '>=10'}
-    hasBin: true
-
-  normalize-path@3.0.0:
-    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
-    engines: {node: '>=0.10.0'}
-
-  once@1.4.0:
-    resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
-
-  path-is-absolute@1.0.1:
-    resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
-    engines: {node: '>=0.10.0'}
-
-  path-parse@1.0.7:
-    resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
-
-  picomatch@2.3.1:
-    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
-    engines: {node: '>=8.6'}
-
-  readdirp@3.6.0:
-    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
-    engines: {node: '>=8.10.0'}
-
   require-directory@2.1.1:
     resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
     engines: {node: '>=0.10.0'}
 
-  resolve@1.22.8:
-    resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
-    hasBin: true
-
-  rimraf@2.7.1:
-    resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
-    deprecated: Rimraf versions prior to v4 are no longer supported
-    hasBin: true
-
   rxjs@7.8.1:
     resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
 
   shell-quote@1.8.1:
     resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
 
-  source-map-support@0.5.21:
-    resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
-
-  source-map@0.6.1:
-    resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
-    engines: {node: '>=0.10.0'}
-
   string-width@4.2.3:
     resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
     engines: {node: '>=8'}
@@ -276,14 +82,6 @@ packages:
     resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
     engines: {node: '>=8'}
 
-  strip-bom@3.0.0:
-    resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
-    engines: {node: '>=4'}
-
-  strip-json-comments@2.0.1:
-    resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
-    engines: {node: '>=0.10.0'}
-
   supports-color@7.2.0:
     resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
     engines: {node: '>=8'}
@@ -292,71 +90,17 @@ packages:
     resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
     engines: {node: '>=10'}
 
-  supports-preserve-symlinks-flag@1.0.0:
-    resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
-    engines: {node: '>= 0.4'}
-
-  to-regex-range@5.0.1:
-    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
-    engines: {node: '>=8.0'}
-
   tree-kill@1.2.2:
     resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
     hasBin: true
 
-  ts-node-dev@2.0.0:
-    resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==}
-    engines: {node: '>=0.8.0'}
-    hasBin: true
-    peerDependencies:
-      node-notifier: '*'
-      typescript: '*'
-    peerDependenciesMeta:
-      node-notifier:
-        optional: true
-
-  ts-node@10.9.2:
-    resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
-    hasBin: true
-    peerDependencies:
-      '@swc/core': '>=1.2.50'
-      '@swc/wasm': '>=1.2.50'
-      '@types/node': '*'
-      typescript: '>=2.7'
-    peerDependenciesMeta:
-      '@swc/core':
-        optional: true
-      '@swc/wasm':
-        optional: true
-
-  tsconfig@7.0.0:
-    resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==}
-
   tslib@2.8.1:
     resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
 
-  typescript@5.6.3:
-    resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
-    engines: {node: '>=14.17'}
-    hasBin: true
-
-  undici-types@6.19.8:
-    resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
-
-  v8-compile-cache-lib@3.0.1:
-    resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
-
   wrap-ansi@7.0.0:
     resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
     engines: {node: '>=10'}
 
-  wrappy@1.0.2:
-    resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
-
-  xtend@4.0.2:
-    resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
-    engines: {node: '>=0.4'}
-
   y18n@5.0.8:
     resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
     engines: {node: '>=10'}
@@ -369,92 +113,19 @@ packages:
     resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
     engines: {node: '>=12'}
 
-  yn@3.1.1:
-    resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
-    engines: {node: '>=6'}
-
 snapshots:
 
-  '@cspotcode/source-map-support@0.8.1':
-    dependencies:
-      '@jridgewell/trace-mapping': 0.3.9
-
-  '@jridgewell/resolve-uri@3.1.2': {}
-
-  '@jridgewell/sourcemap-codec@1.5.0': {}
-
-  '@jridgewell/trace-mapping@0.3.9':
-    dependencies:
-      '@jridgewell/resolve-uri': 3.1.2
-      '@jridgewell/sourcemap-codec': 1.5.0
-
-  '@tsconfig/node10@1.0.11': {}
-
-  '@tsconfig/node12@1.0.11': {}
-
-  '@tsconfig/node14@1.0.3': {}
-
-  '@tsconfig/node16@1.0.4': {}
-
-  '@types/node@22.9.1':
-    dependencies:
-      undici-types: 6.19.8
-
-  '@types/strip-bom@3.0.0': {}
-
-  '@types/strip-json-comments@0.0.30': {}
-
-  acorn-walk@8.3.4:
-    dependencies:
-      acorn: 8.14.0
-
-  acorn@8.14.0: {}
-
   ansi-regex@5.0.1: {}
 
   ansi-styles@4.3.0:
     dependencies:
       color-convert: 2.0.1
 
-  anymatch@3.1.3:
-    dependencies:
-      normalize-path: 3.0.0
-      picomatch: 2.3.1
-
-  arg@4.1.3: {}
-
-  balanced-match@1.0.2: {}
-
-  binary-extensions@2.3.0: {}
-
-  brace-expansion@1.1.11:
-    dependencies:
-      balanced-match: 1.0.2
-      concat-map: 0.0.1
-
-  braces@3.0.3:
-    dependencies:
-      fill-range: 7.1.1
-
-  buffer-from@1.1.2: {}
-
   chalk@4.1.2:
     dependencies:
       ansi-styles: 4.3.0
       supports-color: 7.2.0
 
-  chokidar@3.6.0:
-    dependencies:
-      anymatch: 3.1.3
-      braces: 3.0.3
-      glob-parent: 5.1.2
-      is-binary-path: 2.1.0
-      is-glob: 4.0.3
-      normalize-path: 3.0.0
-      readdirp: 3.6.0
-    optionalDependencies:
-      fsevents: 2.3.3
-
   cliui@8.0.1:
     dependencies:
       string-width: 4.2.3
@@ -467,8 +138,6 @@ snapshots:
 
   color-name@1.1.4: {}
 
-  concat-map@0.0.1: {}
-
   concurrently@9.1.0:
     dependencies:
       chalk: 4.1.2
@@ -479,128 +148,26 @@ snapshots:
       tree-kill: 1.2.2
       yargs: 17.7.2
 
-  create-require@1.1.1: {}
-
-  diff@4.0.2: {}
-
-  dynamic-dedupe@0.3.0:
-    dependencies:
-      xtend: 4.0.2
-
   emoji-regex@8.0.0: {}
 
   escalade@3.2.0: {}
 
-  fill-range@7.1.1:
-    dependencies:
-      to-regex-range: 5.0.1
-
-  fs.realpath@1.0.0: {}
-
-  fsevents@2.3.3:
-    optional: true
-
-  function-bind@1.1.2: {}
-
   get-caller-file@2.0.5: {}
 
-  glob-parent@5.1.2:
-    dependencies:
-      is-glob: 4.0.3
-
-  glob@7.2.3:
-    dependencies:
-      fs.realpath: 1.0.0
-      inflight: 1.0.6
-      inherits: 2.0.4
-      minimatch: 3.1.2
-      once: 1.4.0
-      path-is-absolute: 1.0.1
-
   has-flag@4.0.0: {}
 
-  hasown@2.0.2:
-    dependencies:
-      function-bind: 1.1.2
-
-  inflight@1.0.6:
-    dependencies:
-      once: 1.4.0
-      wrappy: 1.0.2
-
-  inherits@2.0.4: {}
-
-  is-binary-path@2.1.0:
-    dependencies:
-      binary-extensions: 2.3.0
-
-  is-core-module@2.15.1:
-    dependencies:
-      hasown: 2.0.2
-
-  is-extglob@2.1.1: {}
-
   is-fullwidth-code-point@3.0.0: {}
 
-  is-glob@4.0.3:
-    dependencies:
-      is-extglob: 2.1.1
-
-  is-number@7.0.0: {}
-
   lodash@4.17.21: {}
 
-  make-error@1.3.6: {}
-
-  minimatch@3.1.2:
-    dependencies:
-      brace-expansion: 1.1.11
-
-  minimist@1.2.8: {}
-
-  mkdirp@1.0.4: {}
-
-  normalize-path@3.0.0: {}
-
-  once@1.4.0:
-    dependencies:
-      wrappy: 1.0.2
-
-  path-is-absolute@1.0.1: {}
-
-  path-parse@1.0.7: {}
-
-  picomatch@2.3.1: {}
-
-  readdirp@3.6.0:
-    dependencies:
-      picomatch: 2.3.1
-
   require-directory@2.1.1: {}
 
-  resolve@1.22.8:
-    dependencies:
-      is-core-module: 2.15.1
-      path-parse: 1.0.7
-      supports-preserve-symlinks-flag: 1.0.0
-
-  rimraf@2.7.1:
-    dependencies:
-      glob: 7.2.3
-
   rxjs@7.8.1:
     dependencies:
       tslib: 2.8.1
 
   shell-quote@1.8.1: {}
 
-  source-map-support@0.5.21:
-    dependencies:
-      buffer-from: 1.1.2
-      source-map: 0.6.1
-
-  source-map@0.6.1: {}
-
   string-width@4.2.3:
     dependencies:
       emoji-regex: 8.0.0
@@ -611,10 +178,6 @@ snapshots:
     dependencies:
       ansi-regex: 5.0.1
 
-  strip-bom@3.0.0: {}
-
-  strip-json-comments@2.0.1: {}
-
   supports-color@7.2.0:
     dependencies:
       has-flag: 4.0.0
@@ -623,75 +186,16 @@ snapshots:
     dependencies:
       has-flag: 4.0.0
 
-  supports-preserve-symlinks-flag@1.0.0: {}
-
-  to-regex-range@5.0.1:
-    dependencies:
-      is-number: 7.0.0
-
   tree-kill@1.2.2: {}
 
-  ts-node-dev@2.0.0(@types/node@22.9.1)(typescript@5.6.3):
-    dependencies:
-      chokidar: 3.6.0
-      dynamic-dedupe: 0.3.0
-      minimist: 1.2.8
-      mkdirp: 1.0.4
-      resolve: 1.22.8
-      rimraf: 2.7.1
-      source-map-support: 0.5.21
-      tree-kill: 1.2.2
-      ts-node: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
-      tsconfig: 7.0.0
-      typescript: 5.6.3
-    transitivePeerDependencies:
-      - '@swc/core'
-      - '@swc/wasm'
-      - '@types/node'
-
-  ts-node@10.9.2(@types/node@22.9.1)(typescript@5.6.3):
-    dependencies:
-      '@cspotcode/source-map-support': 0.8.1
-      '@tsconfig/node10': 1.0.11
-      '@tsconfig/node12': 1.0.11
-      '@tsconfig/node14': 1.0.3
-      '@tsconfig/node16': 1.0.4
-      '@types/node': 22.9.1
-      acorn: 8.14.0
-      acorn-walk: 8.3.4
-      arg: 4.1.3
-      create-require: 1.1.1
-      diff: 4.0.2
-      make-error: 1.3.6
-      typescript: 5.6.3
-      v8-compile-cache-lib: 3.0.1
-      yn: 3.1.1
-
-  tsconfig@7.0.0:
-    dependencies:
-      '@types/strip-bom': 3.0.0
-      '@types/strip-json-comments': 0.0.30
-      strip-bom: 3.0.0
-      strip-json-comments: 2.0.1
-
   tslib@2.8.1: {}
 
-  typescript@5.6.3: {}
-
-  undici-types@6.19.8: {}
-
-  v8-compile-cache-lib@3.0.1: {}
-
   wrap-ansi@7.0.0:
     dependencies:
       ansi-styles: 4.3.0
       string-width: 4.2.3
       strip-ansi: 6.0.1
 
-  wrappy@1.0.2: {}
-
-  xtend@4.0.2: {}
-
   y18n@5.0.8: {}
 
   yargs-parser@21.1.1: {}
@@ -705,5 +209,3 @@ snapshots:
       string-width: 4.2.3
       y18n: 5.0.8
       yargs-parser: 21.1.1
-
-  yn@3.1.1: {}
diff --git a/pi/ui/env.d.ts b/pi/ui/env.d.ts
index 11f02fe..14fa749 100644
--- a/pi/ui/env.d.ts
+++ b/pi/ui/env.d.ts
@@ -1 +1 @@
-/// <reference types="vite/client" />
+// / <reference types="vite/client" />
diff --git a/pi/ui/eslint.config.js b/pi/ui/eslint.config.js
index b115712..a0f49f0 100644
--- a/pi/ui/eslint.config.js
+++ b/pi/ui/eslint.config.js
@@ -1,15 +1,15 @@
-import pluginVue from 'eslint-plugin-vue'
-import vueTsEslintConfig from '@vue/eslint-config-typescript'
-import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
+import pluginVue from 'eslint-plugin-vue';
+import vueTsEslintConfig from '@vue/eslint-config-typescript';
+import skipFormatting from '@vue/eslint-config-prettier/skip-formatting';
 
 export default [
   {
-    name: 'app/files-to-lint',
+    name:  'app/files-to-lint',
     files: ['**/*.{ts,mts,tsx,vue}'],
   },
 
   {
-    name: 'app/files-to-ignore',
+    name:    'app/files-to-ignore',
     ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
   },
 
@@ -19,7 +19,137 @@ export default [
 
   {
     rules: {
-      'vue/no-multiple-template-root': 'off',
+      'vue/no-multiple-template-root':  'off',
+      'dot-notation':                   'off',
+      'guard-for-in':                   'off',
+      'new-cap':                        'off',
+      'no-empty':                       'off',
+      'no-extra-boolean-cast':          'off',
+      'no-new':                         'off',
+      'no-plusplus':                    'off',
+      'no-useless-escape':              'off',
+      'strict':                         'off',
+      'vue/html-self-closing':          'off',
+      'vue/no-v-html':                  'off',
+      'vue/multi-word-component-names': 'off',
+
+      'array-bracket-spacing':             'warn',
+      'arrow-parens':                      'warn',
+      'arrow-spacing':                     ['warn', {
+        'before': true,
+        'after':  true
+      }],
+      'block-spacing':                     ['warn', 'always'],
+      'brace-style':                       ['warn', '1tbs'],
+      'comma-dangle':                      ['warn', 'only-multiline'],
+      'comma-spacing':                     'warn',
+      'curly':                             'warn',
+      'eqeqeq':                            'warn',
+      'func-call-spacing':                 ['warn', 'never'],
+      'implicit-arrow-linebreak':          'warn',
+      'indent':                            ['warn', 2],
+      'keyword-spacing':                   'warn',
+      'lines-between-class-members':       ['warn', 'always', { 'exceptAfterSingleLine': true }],
+      'multiline-ternary':                 ['warn', 'never'],
+      'newline-per-chained-call':          ['warn', { 'ignoreChainWithDepth': 4 }],
+      'no-caller':                         'warn',
+      'no-cond-assign':                    ['warn', 'except-parens'],
+      'no-console':                        'off',
+      'no-debugger':                       'warn',
+      'no-eq-null':                        'warn',
+      'no-eval':                           'warn',
+      'no-trailing-spaces':                'warn',
+      'no-undef':                          'warn',
+      'no-unused-vars':                    'warn',
+      'no-whitespace-before-property':     'warn',
+      'object-curly-spacing':              ['warn', 'always'],
+      'object-property-newline':           'warn',
+      'object-shorthand':                  'warn',
+      'padded-blocks':                     ['warn', 'never'],
+      'prefer-arrow-callback':             'warn',
+      'prefer-template':                   'warn',
+      'rest-spread-spacing':               'warn',
+      'semi':                              ['warn', 'always'],
+      'space-before-function-paren':       ['warn', 'never'],
+      'space-infix-ops':                   'warn',
+      'spaced-comment':                    'warn',
+      'switch-colon-spacing':              'warn',
+      'template-curly-spacing':            ['warn', 'always'],
+      'yield-star-spacing':                ['warn', 'both'],
+
+      'key-spacing':              ['warn', {
+        'align': {
+          'beforeColon': false,
+          'afterColon':  true,
+          'on':          'value',
+          'mode':        'minimum'
+        },
+        'multiLine': {
+          'beforeColon': false,
+          'afterColon':  true
+        },
+      }],
+
+      'object-curly-newline':          ['warn', {
+        'ObjectExpression':  {
+          'multiline':     true,
+          'minProperties': 3
+        },
+        'ObjectPattern':     {
+          'multiline':     true,
+          'minProperties': 4
+        },
+        'ImportDeclaration': {
+          'multiline':     true,
+          'minProperties': 5
+        },
+        'ExportDeclaration': {
+          'multiline':     true,
+          'minProperties': 3
+        }
+      }],
+
+      'padding-line-between-statements': [
+        'warn',
+        {
+          'blankLine': 'always',
+          'prev':      '*',
+          'next':      'return',
+        },
+        {
+          'blankLine': 'always',
+          'prev':      'function',
+          'next':      'function',
+        },
+        // This configuration would require blank lines after every sequence of variable declarations
+        {
+          'blankLine': 'always',
+          'prev':      ['const', 'let', 'var'],
+          'next':      '*'
+        },
+        {
+          'blankLine': 'any',
+          'prev':      ['const', 'let', 'var'],
+          'next':      ['const', 'let', 'var']
+        }
+      ],
+
+      'quotes': [
+        'warn',
+        'single',
+        {
+          'avoidEscape':           true,
+          'allowTemplateLiterals': true
+        },
+      ],
+
+      'space-unary-ops': [
+        'warn',
+        {
+          'words':    true,
+          'nonwords': false,
+        }
+      ]
     }
   }
-]
+];
diff --git a/pi/ui/src/App.vue b/pi/ui/src/App.vue
index a78fb6f..69dd87a 100644
--- a/pi/ui/src/App.vue
+++ b/pi/ui/src/App.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { RouterView } from 'vue-router'
+import { RouterView } from 'vue-router';
 </script>
 
 <template>
diff --git a/pi/ui/src/components/UWBVisualization.vue b/pi/ui/src/components/UWBVisualization.vue
index b20ca2f..c6ffc0b 100644
--- a/pi/ui/src/components/UWBVisualization.vue
+++ b/pi/ui/src/components/UWBVisualization.vue
@@ -49,7 +49,6 @@ export default defineComponent({
             if (uwbData?.value.length === 3) {
               console.log('Incoming UWB data:', JSON.parse(JSON.stringify(uwbData.value)));
             }
-
           });
         })
         .catch((error) => {
@@ -69,6 +68,7 @@ export default defineComponent({
 
       if (ids.length < 3) {
         console.error('At least three anchors are required.');
+
         return;
       }
 
@@ -76,6 +76,7 @@ export default defineComponent({
       positions[ids[0]] = [0, 0]; // First anchor at (0, 0)
 
       const d1 = getDistance(ids[0], ids[1]) * scalingFactor;
+
       positions[ids[1]] = [d1, 0]; // Second anchor on x-axis
 
       const d2 = getDistance(ids[0], ids[2]) * scalingFactor;
@@ -92,8 +93,8 @@ export default defineComponent({
 
     function getDistance(id1: string, id2: string): number {
       return (
-        anchorDistances.value[`${id1}-${id2}`] ||
-        anchorDistances.value[`${id2}-${id1}`] ||
+        anchorDistances.value[`${ id1 }-${ id2 }`] ||
+        anchorDistances.value[`${ id2 }-${ id1 }`] ||
         0
       );
     }
@@ -125,10 +126,10 @@ export default defineComponent({
     }
 
     function drawAnchors() {
-      const anchors = Object.entries(anchorPositions.value);
+      const anchors = Object.entries(anchorPositions.value) as [string, [number, number]][];
 
       // Bind data to anchor circles
-      const anchorCircles = svg.selectAll('.anchor')
+      const anchorCircles = svg.selectAll<SVGCircleElement, [string, [number, number]]>('.anchor')
         .data(anchors, (d) => d[0]);
 
       // Enter selection
@@ -145,7 +146,7 @@ export default defineComponent({
       anchorCircles.exit().remove();
 
       // Bind data to anchor labels
-      const anchorLabels = svg.selectAll('.anchor-label')
+      const anchorLabels = svg.selectAll<SVGTextElement, [string, [number, number]]>('.anchor-label')
         .data(anchors, (d) => d[0]);
 
       // Enter selection
@@ -157,13 +158,12 @@ export default defineComponent({
         .merge(anchorLabels as any) // eslint-disable-line
         .attr('x', (d) => xScale(d[1][0]) + 5)
         .attr('y', (d) => yScale(d[1][1]) - 5)
-        .text((d) => `A${d[0]}`);
+        .text((d) => `A${ d[0] }`);
 
       // Exit selection
       anchorLabels.exit().remove();
     }
 
-
     function updateVisualization() {
       const positions: [number, number][] = [];
       const distances: number[] = [];
@@ -171,8 +171,10 @@ export default defineComponent({
       uwbData.value.forEach((anchor) => {
         const anchorID = anchor.A;
         const range = parseFloat(anchor.R) * scalingFactor; // Scale the range
+
         if (range <= 0) {
-          console.warn(`Invalid distance for anchor ${anchorID}: ${range}. Skipping.`);
+          console.warn(`Invalid distance for anchor ${ anchorID }: ${ range }. Skipping.`);
+
           return; // Skip invalid distances
         }
         if (anchorPositions.value[anchorID]) {
@@ -183,6 +185,7 @@ export default defineComponent({
 
       if (positions.length >= 3) {
         const [x, y] = calculateTagPosition(positions, distances);
+
         if (x !== -1 && y !== -1) {
           // Update scales to include tag position
           updateScales([x, y]);
@@ -218,6 +221,7 @@ export default defineComponent({
     ): [number, number] {
       if (positions.length < 3) {
         console.error('At least three anchors are required.');
+
         return [-1, -1];
       }
 
@@ -240,6 +244,7 @@ export default defineComponent({
 
         if (denominator === 0) {
           console.error('Cannot solve, determinant is zero.');
+
           return [-1, -1];
         }
 
@@ -249,6 +254,7 @@ export default defineComponent({
         return [x, y];
       } catch (error) {
         console.error('Error calculating position:', error);
+
         return [-1, -1];
       }
     }
diff --git a/pi/ui/src/main.ts b/pi/ui/src/main.ts
index 0d51d91..ef2b5f2 100644
--- a/pi/ui/src/main.ts
+++ b/pi/ui/src/main.ts
@@ -1,21 +1,19 @@
-import './assets/main.css'
+import './assets/main.css';
 
-import { createApp } from 'vue'
-import { createPinia } from 'pinia'
-import { io, Socket } from 'socket.io-client'
+import { createApp } from 'vue';
+import { createPinia } from 'pinia';
+import { io, Socket } from 'socket.io-client';
 
-import App from './App.vue'
-import router from './router'
+import App from './App.vue';
+import router from './router';
 
-const app = createApp(App)
+const app = createApp(App);
 
-const socket: Socket = io('http://0.0.0.0:5000', {
-  transports: ['websocket'],
-})
+const socket: Socket = io('http://0.0.0.0:5000', { transports: ['websocket'] });
 
-app.provide('socket', socket)
+app.provide('socket', socket);
 
-app.use(createPinia())
-app.use(router)
+app.use(createPinia());
+app.use(router);
 
-app.mount('#app')
+app.mount('#app');
diff --git a/pi/ui/src/router/index.ts b/pi/ui/src/router/index.ts
index 29788a1..4155626 100644
--- a/pi/ui/src/router/index.ts
+++ b/pi/ui/src/router/index.ts
@@ -1,15 +1,15 @@
-import { createRouter, createWebHistory } from 'vue-router'
-import HomeView from '../views/HomeView.vue'
+import { createRouter, createWebHistory } from 'vue-router';
+import HomeView from '../views/HomeView.vue';
 
 const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
-  routes: [
+  routes:  [
     {
-      path: '/',
-      name: 'home',
+      path:      '/',
+      name:      'home',
       component: HomeView,
     },
   ],
-})
+});
 
-export default router
+export default router;
diff --git a/pi/ui/src/views/HomeView.vue b/pi/ui/src/views/HomeView.vue
index 4e24910..73b2247 100644
--- a/pi/ui/src/views/HomeView.vue
+++ b/pi/ui/src/views/HomeView.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import UWBVisualization from '../components/UWBVisualization.vue'
+import UWBVisualization from '../components/UWBVisualization.vue';
 </script>
 
 <template>
diff --git a/pi/ui/vite.config.ts b/pi/ui/vite.config.ts
index 1798a4c..6c1b071 100644
--- a/pi/ui/vite.config.ts
+++ b/pi/ui/vite.config.ts
@@ -1,8 +1,8 @@
-import { fileURLToPath, URL } from 'node:url'
+import { fileURLToPath, URL } from 'node:url';
 
-import { defineConfig } from 'vite'
-import vue from '@vitejs/plugin-vue'
-import vueDevTools from 'vite-plugin-vue-devtools'
+import { defineConfig } from 'vite';
+import vue from '@vitejs/plugin-vue';
+import vueDevTools from 'vite-plugin-vue-devtools';
 
 // https://vite.dev/config/
 export default defineConfig({
@@ -10,14 +10,6 @@ export default defineConfig({
     vue(),
     vueDevTools(),
   ],
-  resolve: {
-    alias: {
-      '@': fileURLToPath(new URL('./src', import.meta.url))
-    },
-  },
-  server: {
-    proxy: {
-      '/api': 'http://localhost:5000',
-    },
-  },
-})
+  resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } },
+  server:  { proxy: { '/api': 'http://localhost:5000' } },
+});

From c7da0a7382bb3c38345df9579d039871c37eb9da Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Fri, 22 Nov 2024 14:01:30 -0500
Subject: [PATCH 10/34] Add eslint to server - update deps - fix ui build error

---
 pi/package.sh              |   2 +-
 pi/server/eslint.config.js | 138 +++++++++
 pi/server/package.json     |   8 +-
 pi/server/pnpm-lock.yaml   | 621 ++++++++++++++++++++++++++++++++++++-
 pi/ui/env.d.ts             |   9 +
 5 files changed, 763 insertions(+), 15 deletions(-)
 create mode 100644 pi/server/eslint.config.js

diff --git a/pi/package.sh b/pi/package.sh
index 721a66b..9083206 100755
--- a/pi/package.sh
+++ b/pi/package.sh
@@ -32,7 +32,7 @@ cp package.json $PROJECT_NAME/
 echo "Creating the tar.gz archive"
 tar -czf $TARGET_ARCHIVE $PROJECT_NAME
 
-# rm -rf $PROJECT_NAME
+rm -rf $PROJECT_NAME
 
 echo "Build and transfer complete"
 echo "To run the app on the Raspberry Pi:"
diff --git a/pi/server/eslint.config.js b/pi/server/eslint.config.js
new file mode 100644
index 0000000..8d54878
--- /dev/null
+++ b/pi/server/eslint.config.js
@@ -0,0 +1,138 @@
+import globals from 'globals';
+
+export default [
+  { languageOptions: { globals: globals.node } },
+  { ignores: ['dist/*', 'node_modules/*'] },
+  {
+    'rules':       {
+      'dot-notation':                   'off',
+      'guard-for-in':                   'off',
+      'new-cap':                        'off',
+      'no-empty':                       'off',
+      'no-extra-boolean-cast':          'off',
+      'no-new':                         'off',
+      'no-plusplus':                    'off',
+      'no-useless-escape':              'off',
+      'strict':                         'off',
+
+      'array-bracket-spacing':             'warn',
+      'arrow-parens':                      'warn',
+      'arrow-spacing':                     ['warn', {
+        'before': true,
+        'after':  true
+      }],
+      'block-spacing':                     ['warn', 'always'],
+      'brace-style':                       ['warn', '1tbs'],
+      'comma-dangle':                      ['warn', 'only-multiline'],
+      'comma-spacing':                     'warn',
+      'curly':                             'warn',
+      'eqeqeq':                            'warn',
+      'func-call-spacing':                 ['warn', 'never'],
+      'implicit-arrow-linebreak':          'warn',
+      'indent':                            ['warn', 2],
+      'keyword-spacing':                   'warn',
+      'lines-between-class-members':       ['warn', 'always', { 'exceptAfterSingleLine': true }],
+      'multiline-ternary':                 ['warn', 'never'],
+      'newline-per-chained-call':          ['warn', { 'ignoreChainWithDepth': 4 }],
+      'no-caller':                         'warn',
+      'no-cond-assign':                    ['warn', 'except-parens'],
+      'no-console':                        'warn',
+      'no-debugger':                       'warn',
+      'no-eq-null':                        'warn',
+      'no-eval':                           'warn',
+      'no-trailing-spaces':                'warn',
+      'no-undef':                          'warn',
+      'no-unused-vars':                    'warn',
+      'no-whitespace-before-property':     'warn',
+      'object-curly-spacing':              ['warn', 'always'],
+      'object-property-newline':           'warn',
+      'object-shorthand':                  'warn',
+      'padded-blocks':                     ['warn', 'never'],
+      'prefer-arrow-callback':             'warn',
+      'prefer-template':                   'warn',
+      'rest-spread-spacing':               'warn',
+      'semi':                              ['warn', 'always'],
+      'space-before-function-paren':       ['warn', 'never'],
+      'space-infix-ops':                   'warn',
+      'spaced-comment':                    'warn',
+      'switch-colon-spacing':              'warn',
+      'template-curly-spacing':            ['warn', 'always'],
+      'yield-star-spacing':                ['warn', 'both'],
+
+      'key-spacing':              ['warn', {
+        'align': {
+          'beforeColon': false,
+          'afterColon':  true,
+          'on':          'value',
+          'mode':        'minimum'
+        },
+        'multiLine': {
+          'beforeColon': false,
+          'afterColon':  true
+        },
+      }],
+
+      'object-curly-newline':          ['warn', {
+        'ObjectExpression':  {
+          'multiline':     true,
+          'minProperties': 3
+        },
+        'ObjectPattern':     {
+          'multiline':     true,
+          'minProperties': 4
+        },
+        'ImportDeclaration': {
+          'multiline':     true,
+          'minProperties': 5
+        },
+        'ExportDeclaration': {
+          'multiline':     true,
+          'minProperties': 3
+        }
+      }],
+
+      'padding-line-between-statements': [
+        'warn',
+        {
+          'blankLine': 'always',
+          'prev':      '*',
+          'next':      'return',
+        },
+        {
+          'blankLine': 'always',
+          'prev':      'function',
+          'next':      'function',
+        },
+        // This configuration would require blank lines after every sequence of variable declarations
+        {
+          'blankLine': 'always',
+          'prev':      ['const', 'let', 'var'],
+          'next':      '*'
+        },
+        {
+          'blankLine': 'any',
+          'prev':      ['const', 'let', 'var'],
+          'next':      ['const', 'let', 'var']
+        }
+      ],
+
+      'quotes': [
+        'warn',
+        'single',
+        {
+          'avoidEscape':           true,
+          'allowTemplateLiterals': true
+        },
+      ],
+
+      'space-unary-ops': [
+        'warn',
+        {
+          'words':    true,
+          'nonwords': false,
+        }
+      ]
+    }
+  }
+];
+
diff --git a/pi/server/package.json b/pi/server/package.json
index 2cbbbda..7e3f98a 100644
--- a/pi/server/package.json
+++ b/pi/server/package.json
@@ -7,7 +7,9 @@
   "scripts": {
     "start": "node dist/server.js",
     "build": "tsc --outDir dist",
-    "dev": "nodemon --watch src --ext ts --exec 'node --loader ts-node/esm ./src/server.ts'"
+    "dev": "nodemon --watch src --ext ts --exec 'node --loader ts-node/esm ./src/server.ts'",
+    "lint": "npx eslint .",
+    "format": "npx eslint . --fix"
   },
   "keywords": [],
   "author": "",
@@ -23,10 +25,12 @@
     "@types/cors": "^2.8.17",
     "@types/express": "^5.0.0",
     "@types/node": "^22.9.1",
+    "eslint": "^9.15.0",
+    "globals": "^15.12.0",
     "http-proxy-middleware": "^3.0.3",
     "nodemon": "^3.1.7",
     "ts-node": "^10.9.2",
     "ts-node-dev": "^2.0.0",
-    "typescript": "^5.6.3"
+    "typescript": "^5.7.2"
   }
 }
diff --git a/pi/server/pnpm-lock.yaml b/pi/server/pnpm-lock.yaml
index ad21140..145967a 100644
--- a/pi/server/pnpm-lock.yaml
+++ b/pi/server/pnpm-lock.yaml
@@ -30,6 +30,12 @@ importers:
       '@types/node':
         specifier: ^22.9.1
         version: 22.9.1
+      eslint:
+        specifier: ^9.15.0
+        version: 9.15.0
+      globals:
+        specifier: ^15.12.0
+        version: 15.12.0
       http-proxy-middleware:
         specifier: ^3.0.3
         version: 3.0.3
@@ -38,13 +44,13 @@ importers:
         version: 3.1.7
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
+        version: 10.9.2(@types/node@22.9.1)(typescript@5.7.2)
       ts-node-dev:
         specifier: ^2.0.0
-        version: 2.0.0(@types/node@22.9.1)(typescript@5.6.3)
+        version: 2.0.0(@types/node@22.9.1)(typescript@5.7.2)
       typescript:
-        specifier: ^5.6.3
-        version: 5.6.3
+        specifier: ^5.7.2
+        version: 5.7.2
 
 packages:
 
@@ -56,6 +62,60 @@ packages:
     resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
     engines: {node: '>=12'}
 
+  '@eslint-community/eslint-utils@4.4.1':
+    resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+  '@eslint-community/regexpp@4.12.1':
+    resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
+    engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+  '@eslint/config-array@0.19.0':
+    resolution: {integrity: sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/core@0.9.0':
+    resolution: {integrity: sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/eslintrc@3.2.0':
+    resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/js@9.15.0':
+    resolution: {integrity: sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/object-schema@2.1.4':
+    resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/plugin-kit@0.2.3':
+    resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@humanfs/core@0.19.1':
+    resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+    engines: {node: '>=18.18.0'}
+
+  '@humanfs/node@0.16.6':
+    resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
+    engines: {node: '>=18.18.0'}
+
+  '@humanwhocodes/module-importer@1.0.1':
+    resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+    engines: {node: '>=12.22'}
+
+  '@humanwhocodes/retry@0.3.1':
+    resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
+    engines: {node: '>=18.18'}
+
+  '@humanwhocodes/retry@0.4.1':
+    resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==}
+    engines: {node: '>=18.18'}
+
   '@jridgewell/resolve-uri@3.1.2':
     resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
     engines: {node: '>=6.0.0'}
@@ -93,6 +153,9 @@ packages:
   '@types/cors@2.8.17':
     resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
 
+  '@types/estree@1.0.6':
+    resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
+
   '@types/express-serve-static-core@5.0.1':
     resolution: {integrity: sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==}
 
@@ -105,6 +168,9 @@ packages:
   '@types/http-proxy@1.17.15':
     resolution: {integrity: sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==}
 
+  '@types/json-schema@7.0.15':
+    resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
   '@types/mime@1.3.5':
     resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
 
@@ -133,6 +199,11 @@ packages:
     resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
     engines: {node: '>= 0.6'}
 
+  acorn-jsx@5.3.2:
+    resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+    peerDependencies:
+      acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
   acorn-walk@8.3.4:
     resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
     engines: {node: '>=0.4.0'}
@@ -142,6 +213,13 @@ packages:
     engines: {node: '>=0.4.0'}
     hasBin: true
 
+  ajv@6.12.6:
+    resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+  ansi-styles@4.3.0:
+    resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+    engines: {node: '>=8'}
+
   anymatch@3.1.3:
     resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
     engines: {node: '>= 8'}
@@ -149,6 +227,9 @@ packages:
   arg@4.1.3:
     resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
 
+  argparse@2.0.1:
+    resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
   array-flatten@1.1.1:
     resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
 
@@ -185,10 +266,25 @@ packages:
     resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
     engines: {node: '>= 0.4'}
 
+  callsites@3.1.0:
+    resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+    engines: {node: '>=6'}
+
+  chalk@4.1.2:
+    resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+    engines: {node: '>=10'}
+
   chokidar@3.6.0:
     resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
     engines: {node: '>= 8.10.0'}
 
+  color-convert@2.0.1:
+    resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+    engines: {node: '>=7.0.0'}
+
+  color-name@1.1.4:
+    resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
   complex.js@2.4.2:
     resolution: {integrity: sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g==}
 
@@ -221,6 +317,10 @@ packages:
   create-require@1.1.1:
     resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
 
+  cross-spawn@7.0.6:
+    resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+    engines: {node: '>= 8'}
+
   debug@2.6.9:
     resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
     peerDependencies:
@@ -241,6 +341,9 @@ packages:
   decimal.js@10.4.3:
     resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
 
+  deep-is@0.1.4:
+    resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
   define-data-property@1.1.4:
     resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
     engines: {node: '>= 0.4'}
@@ -293,6 +396,52 @@ packages:
   escape-latex@1.2.0:
     resolution: {integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==}
 
+  escape-string-regexp@4.0.0:
+    resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+    engines: {node: '>=10'}
+
+  eslint-scope@8.2.0:
+    resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  eslint-visitor-keys@3.4.3:
+    resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+  eslint-visitor-keys@4.2.0:
+    resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  eslint@9.15.0:
+    resolution: {integrity: sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    hasBin: true
+    peerDependencies:
+      jiti: '*'
+    peerDependenciesMeta:
+      jiti:
+        optional: true
+
+  espree@10.3.0:
+    resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  esquery@1.6.0:
+    resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
+    engines: {node: '>=0.10'}
+
+  esrecurse@4.3.0:
+    resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+    engines: {node: '>=4.0'}
+
+  estraverse@5.3.0:
+    resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+    engines: {node: '>=4.0'}
+
+  esutils@2.0.3:
+    resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+    engines: {node: '>=0.10.0'}
+
   etag@1.8.1:
     resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
     engines: {node: '>= 0.6'}
@@ -304,6 +453,19 @@ packages:
     resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==}
     engines: {node: '>= 0.10.0'}
 
+  fast-deep-equal@3.1.3:
+    resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+  fast-json-stable-stringify@2.1.0:
+    resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+  fast-levenshtein@2.0.6:
+    resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+  file-entry-cache@8.0.0:
+    resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+    engines: {node: '>=16.0.0'}
+
   fill-range@7.1.1:
     resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
     engines: {node: '>=8'}
@@ -312,6 +474,17 @@ packages:
     resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
     engines: {node: '>= 0.8'}
 
+  find-up@5.0.0:
+    resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+    engines: {node: '>=10'}
+
+  flat-cache@4.0.1:
+    resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+    engines: {node: '>=16'}
+
+  flatted@3.3.2:
+    resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
+
   follow-redirects@1.15.9:
     resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
     engines: {node: '>=4.0'}
@@ -352,10 +525,22 @@ packages:
     resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
     engines: {node: '>= 6'}
 
+  glob-parent@6.0.2:
+    resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+    engines: {node: '>=10.13.0'}
+
   glob@7.2.3:
     resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
     deprecated: Glob versions prior to v9 are no longer supported
 
+  globals@14.0.0:
+    resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+    engines: {node: '>=18'}
+
+  globals@15.12.0:
+    resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==}
+    engines: {node: '>=18'}
+
   gopd@1.0.1:
     resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
 
@@ -363,6 +548,10 @@ packages:
     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
     engines: {node: '>=4'}
 
+  has-flag@4.0.0:
+    resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+    engines: {node: '>=8'}
+
   has-property-descriptors@1.0.2:
     resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
 
@@ -397,6 +586,18 @@ packages:
   ignore-by-default@1.0.1:
     resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
 
+  ignore@5.3.2:
+    resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+    engines: {node: '>= 4'}
+
+  import-fresh@3.3.0:
+    resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
+    engines: {node: '>=6'}
+
+  imurmurhash@0.1.4:
+    resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+    engines: {node: '>=0.8.19'}
+
   inflight@1.0.6:
     resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
     deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
@@ -432,9 +633,39 @@ packages:
     resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
     engines: {node: '>=0.10.0'}
 
+  isexe@2.0.0:
+    resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
   javascript-natural-sort@0.7.1:
     resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
 
+  js-yaml@4.1.0:
+    resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+    hasBin: true
+
+  json-buffer@3.0.1:
+    resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+  json-schema-traverse@0.4.1:
+    resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+  json-stable-stringify-without-jsonify@1.0.1:
+    resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+  keyv@4.5.4:
+    resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+  levn@0.4.1:
+    resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+    engines: {node: '>= 0.8.0'}
+
+  locate-path@6.0.0:
+    resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+    engines: {node: '>=10'}
+
+  lodash.merge@4.6.2:
+    resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
   make-error@1.3.6:
     resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
 
@@ -488,6 +719,9 @@ packages:
   ms@2.1.3:
     resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
 
+  natural-compare@1.4.0:
+    resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
   negotiator@0.6.3:
     resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
     engines: {node: '>= 0.6'}
@@ -516,14 +750,38 @@ packages:
   once@1.4.0:
     resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
 
+  optionator@0.9.4:
+    resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+    engines: {node: '>= 0.8.0'}
+
+  p-limit@3.1.0:
+    resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+    engines: {node: '>=10'}
+
+  p-locate@5.0.0:
+    resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+    engines: {node: '>=10'}
+
+  parent-module@1.0.1:
+    resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+    engines: {node: '>=6'}
+
   parseurl@1.3.3:
     resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
     engines: {node: '>= 0.8'}
 
+  path-exists@4.0.0:
+    resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+    engines: {node: '>=8'}
+
   path-is-absolute@1.0.1:
     resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
     engines: {node: '>=0.10.0'}
 
+  path-key@3.1.1:
+    resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+    engines: {node: '>=8'}
+
   path-parse@1.0.7:
     resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
 
@@ -534,6 +792,10 @@ packages:
     resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
     engines: {node: '>=8.6'}
 
+  prelude-ls@1.2.1:
+    resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+    engines: {node: '>= 0.8.0'}
+
   proxy-addr@2.0.7:
     resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
     engines: {node: '>= 0.10'}
@@ -541,6 +803,10 @@ packages:
   pstree.remy@1.1.8:
     resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
 
+  punycode@2.3.1:
+    resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+    engines: {node: '>=6'}
+
   qs@6.13.0:
     resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
     engines: {node: '>=0.6'}
@@ -563,6 +829,10 @@ packages:
   requires-port@1.0.0:
     resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
 
+  resolve-from@4.0.0:
+    resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+    engines: {node: '>=4'}
+
   resolve@1.22.8:
     resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
     hasBin: true
@@ -601,6 +871,14 @@ packages:
   setprototypeof@1.2.0:
     resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
 
+  shebang-command@2.0.0:
+    resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+    engines: {node: '>=8'}
+
+  shebang-regex@3.0.0:
+    resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+    engines: {node: '>=8'}
+
   side-channel@1.0.6:
     resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
     engines: {node: '>= 0.4'}
@@ -639,10 +917,18 @@ packages:
     resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
     engines: {node: '>=0.10.0'}
 
+  strip-json-comments@3.1.1:
+    resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+    engines: {node: '>=8'}
+
   supports-color@5.5.0:
     resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
     engines: {node: '>=4'}
 
+  supports-color@7.2.0:
+    resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+    engines: {node: '>=8'}
+
   supports-preserve-symlinks-flag@1.0.0:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
     engines: {node: '>= 0.4'}
@@ -694,6 +980,10 @@ packages:
   tsconfig@7.0.0:
     resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==}
 
+  type-check@0.4.0:
+    resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+    engines: {node: '>= 0.8.0'}
+
   type-is@1.6.18:
     resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
     engines: {node: '>= 0.6'}
@@ -702,8 +992,8 @@ packages:
     resolution: {integrity: sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==}
     engines: {node: '>= 18'}
 
-  typescript@5.6.3:
-    resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
+  typescript@5.7.2:
+    resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==}
     engines: {node: '>=14.17'}
     hasBin: true
 
@@ -717,6 +1007,9 @@ packages:
     resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
     engines: {node: '>= 0.8'}
 
+  uri-js@4.4.1:
+    resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
   utils-merge@1.0.1:
     resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
     engines: {node: '>= 0.4.0'}
@@ -728,6 +1021,15 @@ packages:
     resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
     engines: {node: '>= 0.8'}
 
+  which@2.0.2:
+    resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+    engines: {node: '>= 8'}
+    hasBin: true
+
+  word-wrap@1.2.5:
+    resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+    engines: {node: '>=0.10.0'}
+
   wrappy@1.0.2:
     resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
 
@@ -751,6 +1053,10 @@ packages:
     resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
     engines: {node: '>=6'}
 
+  yocto-queue@0.1.0:
+    resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+    engines: {node: '>=10'}
+
 snapshots:
 
   '@babel/runtime@7.26.0':
@@ -761,6 +1067,58 @@ snapshots:
     dependencies:
       '@jridgewell/trace-mapping': 0.3.9
 
+  '@eslint-community/eslint-utils@4.4.1(eslint@9.15.0)':
+    dependencies:
+      eslint: 9.15.0
+      eslint-visitor-keys: 3.4.3
+
+  '@eslint-community/regexpp@4.12.1': {}
+
+  '@eslint/config-array@0.19.0':
+    dependencies:
+      '@eslint/object-schema': 2.1.4
+      debug: 4.3.7(supports-color@5.5.0)
+      minimatch: 3.1.2
+    transitivePeerDependencies:
+      - supports-color
+
+  '@eslint/core@0.9.0': {}
+
+  '@eslint/eslintrc@3.2.0':
+    dependencies:
+      ajv: 6.12.6
+      debug: 4.3.7(supports-color@5.5.0)
+      espree: 10.3.0
+      globals: 14.0.0
+      ignore: 5.3.2
+      import-fresh: 3.3.0
+      js-yaml: 4.1.0
+      minimatch: 3.1.2
+      strip-json-comments: 3.1.1
+    transitivePeerDependencies:
+      - supports-color
+
+  '@eslint/js@9.15.0': {}
+
+  '@eslint/object-schema@2.1.4': {}
+
+  '@eslint/plugin-kit@0.2.3':
+    dependencies:
+      levn: 0.4.1
+
+  '@humanfs/core@0.19.1': {}
+
+  '@humanfs/node@0.16.6':
+    dependencies:
+      '@humanfs/core': 0.19.1
+      '@humanwhocodes/retry': 0.3.1
+
+  '@humanwhocodes/module-importer@1.0.1': {}
+
+  '@humanwhocodes/retry@0.3.1': {}
+
+  '@humanwhocodes/retry@0.4.1': {}
+
   '@jridgewell/resolve-uri@3.1.2': {}
 
   '@jridgewell/sourcemap-codec@1.5.0': {}
@@ -795,6 +1153,8 @@ snapshots:
     dependencies:
       '@types/node': 22.9.1
 
+  '@types/estree@1.0.6': {}
+
   '@types/express-serve-static-core@5.0.1':
     dependencies:
       '@types/node': 22.9.1
@@ -815,6 +1175,8 @@ snapshots:
     dependencies:
       '@types/node': 22.9.1
 
+  '@types/json-schema@7.0.15': {}
+
   '@types/mime@1.3.5': {}
 
   '@types/node@22.9.1':
@@ -845,12 +1207,27 @@ snapshots:
       mime-types: 2.1.35
       negotiator: 0.6.3
 
+  acorn-jsx@5.3.2(acorn@8.14.0):
+    dependencies:
+      acorn: 8.14.0
+
   acorn-walk@8.3.4:
     dependencies:
       acorn: 8.14.0
 
   acorn@8.14.0: {}
 
+  ajv@6.12.6:
+    dependencies:
+      fast-deep-equal: 3.1.3
+      fast-json-stable-stringify: 2.1.0
+      json-schema-traverse: 0.4.1
+      uri-js: 4.4.1
+
+  ansi-styles@4.3.0:
+    dependencies:
+      color-convert: 2.0.1
+
   anymatch@3.1.3:
     dependencies:
       normalize-path: 3.0.0
@@ -858,6 +1235,8 @@ snapshots:
 
   arg@4.1.3: {}
 
+  argparse@2.0.1: {}
+
   array-flatten@1.1.1: {}
 
   balanced-match@1.0.2: {}
@@ -904,6 +1283,13 @@ snapshots:
       get-intrinsic: 1.2.4
       set-function-length: 1.2.2
 
+  callsites@3.1.0: {}
+
+  chalk@4.1.2:
+    dependencies:
+      ansi-styles: 4.3.0
+      supports-color: 7.2.0
+
   chokidar@3.6.0:
     dependencies:
       anymatch: 3.1.3
@@ -916,6 +1302,12 @@ snapshots:
     optionalDependencies:
       fsevents: 2.3.3
 
+  color-convert@2.0.1:
+    dependencies:
+      color-name: 1.1.4
+
+  color-name@1.1.4: {}
+
   complex.js@2.4.2: {}
 
   concat-map@0.0.1: {}
@@ -939,6 +1331,12 @@ snapshots:
 
   create-require@1.1.1: {}
 
+  cross-spawn@7.0.6:
+    dependencies:
+      path-key: 3.1.1
+      shebang-command: 2.0.0
+      which: 2.0.2
+
   debug@2.6.9:
     dependencies:
       ms: 2.0.0
@@ -951,6 +1349,8 @@ snapshots:
 
   decimal.js@10.4.3: {}
 
+  deep-is@0.1.4: {}
+
   define-data-property@1.1.4:
     dependencies:
       es-define-property: 1.0.0
@@ -1002,6 +1402,74 @@ snapshots:
 
   escape-latex@1.2.0: {}
 
+  escape-string-regexp@4.0.0: {}
+
+  eslint-scope@8.2.0:
+    dependencies:
+      esrecurse: 4.3.0
+      estraverse: 5.3.0
+
+  eslint-visitor-keys@3.4.3: {}
+
+  eslint-visitor-keys@4.2.0: {}
+
+  eslint@9.15.0:
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0)
+      '@eslint-community/regexpp': 4.12.1
+      '@eslint/config-array': 0.19.0
+      '@eslint/core': 0.9.0
+      '@eslint/eslintrc': 3.2.0
+      '@eslint/js': 9.15.0
+      '@eslint/plugin-kit': 0.2.3
+      '@humanfs/node': 0.16.6
+      '@humanwhocodes/module-importer': 1.0.1
+      '@humanwhocodes/retry': 0.4.1
+      '@types/estree': 1.0.6
+      '@types/json-schema': 7.0.15
+      ajv: 6.12.6
+      chalk: 4.1.2
+      cross-spawn: 7.0.6
+      debug: 4.3.7(supports-color@5.5.0)
+      escape-string-regexp: 4.0.0
+      eslint-scope: 8.2.0
+      eslint-visitor-keys: 4.2.0
+      espree: 10.3.0
+      esquery: 1.6.0
+      esutils: 2.0.3
+      fast-deep-equal: 3.1.3
+      file-entry-cache: 8.0.0
+      find-up: 5.0.0
+      glob-parent: 6.0.2
+      ignore: 5.3.2
+      imurmurhash: 0.1.4
+      is-glob: 4.0.3
+      json-stable-stringify-without-jsonify: 1.0.1
+      lodash.merge: 4.6.2
+      minimatch: 3.1.2
+      natural-compare: 1.4.0
+      optionator: 0.9.4
+    transitivePeerDependencies:
+      - supports-color
+
+  espree@10.3.0:
+    dependencies:
+      acorn: 8.14.0
+      acorn-jsx: 5.3.2(acorn@8.14.0)
+      eslint-visitor-keys: 4.2.0
+
+  esquery@1.6.0:
+    dependencies:
+      estraverse: 5.3.0
+
+  esrecurse@4.3.0:
+    dependencies:
+      estraverse: 5.3.0
+
+  estraverse@5.3.0: {}
+
+  esutils@2.0.3: {}
+
   etag@1.8.1: {}
 
   eventemitter3@4.0.7: {}
@@ -1042,6 +1510,16 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  fast-deep-equal@3.1.3: {}
+
+  fast-json-stable-stringify@2.1.0: {}
+
+  fast-levenshtein@2.0.6: {}
+
+  file-entry-cache@8.0.0:
+    dependencies:
+      flat-cache: 4.0.1
+
   fill-range@7.1.1:
     dependencies:
       to-regex-range: 5.0.1
@@ -1058,6 +1536,18 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  find-up@5.0.0:
+    dependencies:
+      locate-path: 6.0.0
+      path-exists: 4.0.0
+
+  flat-cache@4.0.1:
+    dependencies:
+      flatted: 3.3.2
+      keyv: 4.5.4
+
+  flatted@3.3.2: {}
+
   follow-redirects@1.15.9(debug@4.3.7):
     optionalDependencies:
       debug: 4.3.7(supports-color@5.5.0)
@@ -1087,6 +1577,10 @@ snapshots:
     dependencies:
       is-glob: 4.0.3
 
+  glob-parent@6.0.2:
+    dependencies:
+      is-glob: 4.0.3
+
   glob@7.2.3:
     dependencies:
       fs.realpath: 1.0.0
@@ -1096,12 +1590,18 @@ snapshots:
       once: 1.4.0
       path-is-absolute: 1.0.1
 
+  globals@14.0.0: {}
+
+  globals@15.12.0: {}
+
   gopd@1.0.1:
     dependencies:
       get-intrinsic: 1.2.4
 
   has-flag@3.0.0: {}
 
+  has-flag@4.0.0: {}
+
   has-property-descriptors@1.0.2:
     dependencies:
       es-define-property: 1.0.0
@@ -1147,6 +1647,15 @@ snapshots:
 
   ignore-by-default@1.0.1: {}
 
+  ignore@5.3.2: {}
+
+  import-fresh@3.3.0:
+    dependencies:
+      parent-module: 1.0.1
+      resolve-from: 4.0.0
+
+  imurmurhash@0.1.4: {}
+
   inflight@1.0.6:
     dependencies:
       once: 1.4.0
@@ -1174,8 +1683,35 @@ snapshots:
 
   is-plain-object@5.0.0: {}
 
+  isexe@2.0.0: {}
+
   javascript-natural-sort@0.7.1: {}
 
+  js-yaml@4.1.0:
+    dependencies:
+      argparse: 2.0.1
+
+  json-buffer@3.0.1: {}
+
+  json-schema-traverse@0.4.1: {}
+
+  json-stable-stringify-without-jsonify@1.0.1: {}
+
+  keyv@4.5.4:
+    dependencies:
+      json-buffer: 3.0.1
+
+  levn@0.4.1:
+    dependencies:
+      prelude-ls: 1.2.1
+      type-check: 0.4.0
+
+  locate-path@6.0.0:
+    dependencies:
+      p-locate: 5.0.0
+
+  lodash.merge@4.6.2: {}
+
   make-error@1.3.6: {}
 
   mathjs@14.0.0:
@@ -1221,6 +1757,8 @@ snapshots:
 
   ms@2.1.3: {}
 
+  natural-compare@1.4.0: {}
+
   negotiator@0.6.3: {}
 
   nodemon@3.1.7:
@@ -1250,16 +1788,43 @@ snapshots:
     dependencies:
       wrappy: 1.0.2
 
+  optionator@0.9.4:
+    dependencies:
+      deep-is: 0.1.4
+      fast-levenshtein: 2.0.6
+      levn: 0.4.1
+      prelude-ls: 1.2.1
+      type-check: 0.4.0
+      word-wrap: 1.2.5
+
+  p-limit@3.1.0:
+    dependencies:
+      yocto-queue: 0.1.0
+
+  p-locate@5.0.0:
+    dependencies:
+      p-limit: 3.1.0
+
+  parent-module@1.0.1:
+    dependencies:
+      callsites: 3.1.0
+
   parseurl@1.3.3: {}
 
+  path-exists@4.0.0: {}
+
   path-is-absolute@1.0.1: {}
 
+  path-key@3.1.1: {}
+
   path-parse@1.0.7: {}
 
   path-to-regexp@0.1.10: {}
 
   picomatch@2.3.1: {}
 
+  prelude-ls@1.2.1: {}
+
   proxy-addr@2.0.7:
     dependencies:
       forwarded: 0.2.0
@@ -1267,6 +1832,8 @@ snapshots:
 
   pstree.remy@1.1.8: {}
 
+  punycode@2.3.1: {}
+
   qs@6.13.0:
     dependencies:
       side-channel: 1.0.6
@@ -1288,6 +1855,8 @@ snapshots:
 
   requires-port@1.0.0: {}
 
+  resolve-from@4.0.0: {}
+
   resolve@1.22.8:
     dependencies:
       is-core-module: 2.15.1
@@ -1344,6 +1913,12 @@ snapshots:
 
   setprototypeof@1.2.0: {}
 
+  shebang-command@2.0.0:
+    dependencies:
+      shebang-regex: 3.0.0
+
+  shebang-regex@3.0.0: {}
+
   side-channel@1.0.6:
     dependencies:
       call-bind: 1.0.7
@@ -1398,10 +1973,16 @@ snapshots:
 
   strip-json-comments@2.0.1: {}
 
+  strip-json-comments@3.1.1: {}
+
   supports-color@5.5.0:
     dependencies:
       has-flag: 3.0.0
 
+  supports-color@7.2.0:
+    dependencies:
+      has-flag: 4.0.0
+
   supports-preserve-symlinks-flag@1.0.0: {}
 
   tiny-emitter@2.1.0: {}
@@ -1416,7 +1997,7 @@ snapshots:
 
   tree-kill@1.2.2: {}
 
-  ts-node-dev@2.0.0(@types/node@22.9.1)(typescript@5.6.3):
+  ts-node-dev@2.0.0(@types/node@22.9.1)(typescript@5.7.2):
     dependencies:
       chokidar: 3.6.0
       dynamic-dedupe: 0.3.0
@@ -1426,15 +2007,15 @@ snapshots:
       rimraf: 2.7.1
       source-map-support: 0.5.21
       tree-kill: 1.2.2
-      ts-node: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
+      ts-node: 10.9.2(@types/node@22.9.1)(typescript@5.7.2)
       tsconfig: 7.0.0
-      typescript: 5.6.3
+      typescript: 5.7.2
     transitivePeerDependencies:
       - '@swc/core'
       - '@swc/wasm'
       - '@types/node'
 
-  ts-node@10.9.2(@types/node@22.9.1)(typescript@5.6.3):
+  ts-node@10.9.2(@types/node@22.9.1)(typescript@5.7.2):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
       '@tsconfig/node10': 1.0.11
@@ -1448,7 +2029,7 @@ snapshots:
       create-require: 1.1.1
       diff: 4.0.2
       make-error: 1.3.6
-      typescript: 5.6.3
+      typescript: 5.7.2
       v8-compile-cache-lib: 3.0.1
       yn: 3.1.1
 
@@ -1459,6 +2040,10 @@ snapshots:
       strip-bom: 3.0.0
       strip-json-comments: 2.0.1
 
+  type-check@0.4.0:
+    dependencies:
+      prelude-ls: 1.2.1
+
   type-is@1.6.18:
     dependencies:
       media-typer: 0.3.0
@@ -1466,7 +2051,7 @@ snapshots:
 
   typed-function@4.2.1: {}
 
-  typescript@5.6.3: {}
+  typescript@5.7.2: {}
 
   undefsafe@2.0.5: {}
 
@@ -1474,12 +2059,22 @@ snapshots:
 
   unpipe@1.0.0: {}
 
+  uri-js@4.4.1:
+    dependencies:
+      punycode: 2.3.1
+
   utils-merge@1.0.1: {}
 
   v8-compile-cache-lib@3.0.1: {}
 
   vary@1.1.2: {}
 
+  which@2.0.2:
+    dependencies:
+      isexe: 2.0.0
+
+  word-wrap@1.2.5: {}
+
   wrappy@1.0.2: {}
 
   ws@8.17.1: {}
@@ -1487,3 +2082,5 @@ snapshots:
   xtend@4.0.2: {}
 
   yn@3.1.1: {}
+
+  yocto-queue@0.1.0: {}
diff --git a/pi/ui/env.d.ts b/pi/ui/env.d.ts
index 14fa749..81486fe 100644
--- a/pi/ui/env.d.ts
+++ b/pi/ui/env.d.ts
@@ -1 +1,10 @@
 // / <reference types="vite/client" />
+
+interface ImportMetaEnv {
+  readonly BASE_URL: string;
+  readonly PROD: boolean
+}
+
+interface ImportMeta {
+  readonly env: ImportMetaEnv;
+}
\ No newline at end of file

From 0e375e648254f561c8994915d8f99ebedc06f8ef Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Fri, 22 Nov 2024 14:02:06 -0500
Subject: [PATCH 11/34] Revert "Add eslint to server - update deps - fix ui
 build error"

This reverts commit c7da0a7382bb3c38345df9579d039871c37eb9da.
---
 pi/package.sh              |   2 +-
 pi/server/eslint.config.js | 138 ---------
 pi/server/package.json     |   8 +-
 pi/server/pnpm-lock.yaml   | 621 +------------------------------------
 pi/ui/env.d.ts             |   9 -
 5 files changed, 15 insertions(+), 763 deletions(-)
 delete mode 100644 pi/server/eslint.config.js

diff --git a/pi/package.sh b/pi/package.sh
index 9083206..721a66b 100755
--- a/pi/package.sh
+++ b/pi/package.sh
@@ -32,7 +32,7 @@ cp package.json $PROJECT_NAME/
 echo "Creating the tar.gz archive"
 tar -czf $TARGET_ARCHIVE $PROJECT_NAME
 
-rm -rf $PROJECT_NAME
+# rm -rf $PROJECT_NAME
 
 echo "Build and transfer complete"
 echo "To run the app on the Raspberry Pi:"
diff --git a/pi/server/eslint.config.js b/pi/server/eslint.config.js
deleted file mode 100644
index 8d54878..0000000
--- a/pi/server/eslint.config.js
+++ /dev/null
@@ -1,138 +0,0 @@
-import globals from 'globals';
-
-export default [
-  { languageOptions: { globals: globals.node } },
-  { ignores: ['dist/*', 'node_modules/*'] },
-  {
-    'rules':       {
-      'dot-notation':                   'off',
-      'guard-for-in':                   'off',
-      'new-cap':                        'off',
-      'no-empty':                       'off',
-      'no-extra-boolean-cast':          'off',
-      'no-new':                         'off',
-      'no-plusplus':                    'off',
-      'no-useless-escape':              'off',
-      'strict':                         'off',
-
-      'array-bracket-spacing':             'warn',
-      'arrow-parens':                      'warn',
-      'arrow-spacing':                     ['warn', {
-        'before': true,
-        'after':  true
-      }],
-      'block-spacing':                     ['warn', 'always'],
-      'brace-style':                       ['warn', '1tbs'],
-      'comma-dangle':                      ['warn', 'only-multiline'],
-      'comma-spacing':                     'warn',
-      'curly':                             'warn',
-      'eqeqeq':                            'warn',
-      'func-call-spacing':                 ['warn', 'never'],
-      'implicit-arrow-linebreak':          'warn',
-      'indent':                            ['warn', 2],
-      'keyword-spacing':                   'warn',
-      'lines-between-class-members':       ['warn', 'always', { 'exceptAfterSingleLine': true }],
-      'multiline-ternary':                 ['warn', 'never'],
-      'newline-per-chained-call':          ['warn', { 'ignoreChainWithDepth': 4 }],
-      'no-caller':                         'warn',
-      'no-cond-assign':                    ['warn', 'except-parens'],
-      'no-console':                        'warn',
-      'no-debugger':                       'warn',
-      'no-eq-null':                        'warn',
-      'no-eval':                           'warn',
-      'no-trailing-spaces':                'warn',
-      'no-undef':                          'warn',
-      'no-unused-vars':                    'warn',
-      'no-whitespace-before-property':     'warn',
-      'object-curly-spacing':              ['warn', 'always'],
-      'object-property-newline':           'warn',
-      'object-shorthand':                  'warn',
-      'padded-blocks':                     ['warn', 'never'],
-      'prefer-arrow-callback':             'warn',
-      'prefer-template':                   'warn',
-      'rest-spread-spacing':               'warn',
-      'semi':                              ['warn', 'always'],
-      'space-before-function-paren':       ['warn', 'never'],
-      'space-infix-ops':                   'warn',
-      'spaced-comment':                    'warn',
-      'switch-colon-spacing':              'warn',
-      'template-curly-spacing':            ['warn', 'always'],
-      'yield-star-spacing':                ['warn', 'both'],
-
-      'key-spacing':              ['warn', {
-        'align': {
-          'beforeColon': false,
-          'afterColon':  true,
-          'on':          'value',
-          'mode':        'minimum'
-        },
-        'multiLine': {
-          'beforeColon': false,
-          'afterColon':  true
-        },
-      }],
-
-      'object-curly-newline':          ['warn', {
-        'ObjectExpression':  {
-          'multiline':     true,
-          'minProperties': 3
-        },
-        'ObjectPattern':     {
-          'multiline':     true,
-          'minProperties': 4
-        },
-        'ImportDeclaration': {
-          'multiline':     true,
-          'minProperties': 5
-        },
-        'ExportDeclaration': {
-          'multiline':     true,
-          'minProperties': 3
-        }
-      }],
-
-      'padding-line-between-statements': [
-        'warn',
-        {
-          'blankLine': 'always',
-          'prev':      '*',
-          'next':      'return',
-        },
-        {
-          'blankLine': 'always',
-          'prev':      'function',
-          'next':      'function',
-        },
-        // This configuration would require blank lines after every sequence of variable declarations
-        {
-          'blankLine': 'always',
-          'prev':      ['const', 'let', 'var'],
-          'next':      '*'
-        },
-        {
-          'blankLine': 'any',
-          'prev':      ['const', 'let', 'var'],
-          'next':      ['const', 'let', 'var']
-        }
-      ],
-
-      'quotes': [
-        'warn',
-        'single',
-        {
-          'avoidEscape':           true,
-          'allowTemplateLiterals': true
-        },
-      ],
-
-      'space-unary-ops': [
-        'warn',
-        {
-          'words':    true,
-          'nonwords': false,
-        }
-      ]
-    }
-  }
-];
-
diff --git a/pi/server/package.json b/pi/server/package.json
index 7e3f98a..2cbbbda 100644
--- a/pi/server/package.json
+++ b/pi/server/package.json
@@ -7,9 +7,7 @@
   "scripts": {
     "start": "node dist/server.js",
     "build": "tsc --outDir dist",
-    "dev": "nodemon --watch src --ext ts --exec 'node --loader ts-node/esm ./src/server.ts'",
-    "lint": "npx eslint .",
-    "format": "npx eslint . --fix"
+    "dev": "nodemon --watch src --ext ts --exec 'node --loader ts-node/esm ./src/server.ts'"
   },
   "keywords": [],
   "author": "",
@@ -25,12 +23,10 @@
     "@types/cors": "^2.8.17",
     "@types/express": "^5.0.0",
     "@types/node": "^22.9.1",
-    "eslint": "^9.15.0",
-    "globals": "^15.12.0",
     "http-proxy-middleware": "^3.0.3",
     "nodemon": "^3.1.7",
     "ts-node": "^10.9.2",
     "ts-node-dev": "^2.0.0",
-    "typescript": "^5.7.2"
+    "typescript": "^5.6.3"
   }
 }
diff --git a/pi/server/pnpm-lock.yaml b/pi/server/pnpm-lock.yaml
index 145967a..ad21140 100644
--- a/pi/server/pnpm-lock.yaml
+++ b/pi/server/pnpm-lock.yaml
@@ -30,12 +30,6 @@ importers:
       '@types/node':
         specifier: ^22.9.1
         version: 22.9.1
-      eslint:
-        specifier: ^9.15.0
-        version: 9.15.0
-      globals:
-        specifier: ^15.12.0
-        version: 15.12.0
       http-proxy-middleware:
         specifier: ^3.0.3
         version: 3.0.3
@@ -44,13 +38,13 @@ importers:
         version: 3.1.7
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.9.1)(typescript@5.7.2)
+        version: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
       ts-node-dev:
         specifier: ^2.0.0
-        version: 2.0.0(@types/node@22.9.1)(typescript@5.7.2)
+        version: 2.0.0(@types/node@22.9.1)(typescript@5.6.3)
       typescript:
-        specifier: ^5.7.2
-        version: 5.7.2
+        specifier: ^5.6.3
+        version: 5.6.3
 
 packages:
 
@@ -62,60 +56,6 @@ packages:
     resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
     engines: {node: '>=12'}
 
-  '@eslint-community/eslint-utils@4.4.1':
-    resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    peerDependencies:
-      eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
-
-  '@eslint-community/regexpp@4.12.1':
-    resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
-    engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
-
-  '@eslint/config-array@0.19.0':
-    resolution: {integrity: sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==}
-    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
-  '@eslint/core@0.9.0':
-    resolution: {integrity: sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==}
-    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
-  '@eslint/eslintrc@3.2.0':
-    resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==}
-    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
-  '@eslint/js@9.15.0':
-    resolution: {integrity: sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==}
-    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
-  '@eslint/object-schema@2.1.4':
-    resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==}
-    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
-  '@eslint/plugin-kit@0.2.3':
-    resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==}
-    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
-  '@humanfs/core@0.19.1':
-    resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
-    engines: {node: '>=18.18.0'}
-
-  '@humanfs/node@0.16.6':
-    resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
-    engines: {node: '>=18.18.0'}
-
-  '@humanwhocodes/module-importer@1.0.1':
-    resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
-    engines: {node: '>=12.22'}
-
-  '@humanwhocodes/retry@0.3.1':
-    resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
-    engines: {node: '>=18.18'}
-
-  '@humanwhocodes/retry@0.4.1':
-    resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==}
-    engines: {node: '>=18.18'}
-
   '@jridgewell/resolve-uri@3.1.2':
     resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
     engines: {node: '>=6.0.0'}
@@ -153,9 +93,6 @@ packages:
   '@types/cors@2.8.17':
     resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
 
-  '@types/estree@1.0.6':
-    resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
-
   '@types/express-serve-static-core@5.0.1':
     resolution: {integrity: sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==}
 
@@ -168,9 +105,6 @@ packages:
   '@types/http-proxy@1.17.15':
     resolution: {integrity: sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==}
 
-  '@types/json-schema@7.0.15':
-    resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
-
   '@types/mime@1.3.5':
     resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
 
@@ -199,11 +133,6 @@ packages:
     resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
     engines: {node: '>= 0.6'}
 
-  acorn-jsx@5.3.2:
-    resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
-    peerDependencies:
-      acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
-
   acorn-walk@8.3.4:
     resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
     engines: {node: '>=0.4.0'}
@@ -213,13 +142,6 @@ packages:
     engines: {node: '>=0.4.0'}
     hasBin: true
 
-  ajv@6.12.6:
-    resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
-
-  ansi-styles@4.3.0:
-    resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
-    engines: {node: '>=8'}
-
   anymatch@3.1.3:
     resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
     engines: {node: '>= 8'}
@@ -227,9 +149,6 @@ packages:
   arg@4.1.3:
     resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
 
-  argparse@2.0.1:
-    resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
-
   array-flatten@1.1.1:
     resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
 
@@ -266,25 +185,10 @@ packages:
     resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
     engines: {node: '>= 0.4'}
 
-  callsites@3.1.0:
-    resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
-    engines: {node: '>=6'}
-
-  chalk@4.1.2:
-    resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
-    engines: {node: '>=10'}
-
   chokidar@3.6.0:
     resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
     engines: {node: '>= 8.10.0'}
 
-  color-convert@2.0.1:
-    resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
-    engines: {node: '>=7.0.0'}
-
-  color-name@1.1.4:
-    resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
-
   complex.js@2.4.2:
     resolution: {integrity: sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g==}
 
@@ -317,10 +221,6 @@ packages:
   create-require@1.1.1:
     resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
 
-  cross-spawn@7.0.6:
-    resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
-    engines: {node: '>= 8'}
-
   debug@2.6.9:
     resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
     peerDependencies:
@@ -341,9 +241,6 @@ packages:
   decimal.js@10.4.3:
     resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
 
-  deep-is@0.1.4:
-    resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
-
   define-data-property@1.1.4:
     resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
     engines: {node: '>= 0.4'}
@@ -396,52 +293,6 @@ packages:
   escape-latex@1.2.0:
     resolution: {integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==}
 
-  escape-string-regexp@4.0.0:
-    resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
-    engines: {node: '>=10'}
-
-  eslint-scope@8.2.0:
-    resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==}
-    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
-  eslint-visitor-keys@3.4.3:
-    resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-
-  eslint-visitor-keys@4.2.0:
-    resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
-    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
-  eslint@9.15.0:
-    resolution: {integrity: sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==}
-    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-    hasBin: true
-    peerDependencies:
-      jiti: '*'
-    peerDependenciesMeta:
-      jiti:
-        optional: true
-
-  espree@10.3.0:
-    resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
-    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
-  esquery@1.6.0:
-    resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
-    engines: {node: '>=0.10'}
-
-  esrecurse@4.3.0:
-    resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
-    engines: {node: '>=4.0'}
-
-  estraverse@5.3.0:
-    resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
-    engines: {node: '>=4.0'}
-
-  esutils@2.0.3:
-    resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
-    engines: {node: '>=0.10.0'}
-
   etag@1.8.1:
     resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
     engines: {node: '>= 0.6'}
@@ -453,19 +304,6 @@ packages:
     resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==}
     engines: {node: '>= 0.10.0'}
 
-  fast-deep-equal@3.1.3:
-    resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
-
-  fast-json-stable-stringify@2.1.0:
-    resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
-
-  fast-levenshtein@2.0.6:
-    resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
-
-  file-entry-cache@8.0.0:
-    resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
-    engines: {node: '>=16.0.0'}
-
   fill-range@7.1.1:
     resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
     engines: {node: '>=8'}
@@ -474,17 +312,6 @@ packages:
     resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
     engines: {node: '>= 0.8'}
 
-  find-up@5.0.0:
-    resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
-    engines: {node: '>=10'}
-
-  flat-cache@4.0.1:
-    resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
-    engines: {node: '>=16'}
-
-  flatted@3.3.2:
-    resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
-
   follow-redirects@1.15.9:
     resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
     engines: {node: '>=4.0'}
@@ -525,22 +352,10 @@ packages:
     resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
     engines: {node: '>= 6'}
 
-  glob-parent@6.0.2:
-    resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
-    engines: {node: '>=10.13.0'}
-
   glob@7.2.3:
     resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
     deprecated: Glob versions prior to v9 are no longer supported
 
-  globals@14.0.0:
-    resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
-    engines: {node: '>=18'}
-
-  globals@15.12.0:
-    resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==}
-    engines: {node: '>=18'}
-
   gopd@1.0.1:
     resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
 
@@ -548,10 +363,6 @@ packages:
     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
     engines: {node: '>=4'}
 
-  has-flag@4.0.0:
-    resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
-    engines: {node: '>=8'}
-
   has-property-descriptors@1.0.2:
     resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
 
@@ -586,18 +397,6 @@ packages:
   ignore-by-default@1.0.1:
     resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
 
-  ignore@5.3.2:
-    resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
-    engines: {node: '>= 4'}
-
-  import-fresh@3.3.0:
-    resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
-    engines: {node: '>=6'}
-
-  imurmurhash@0.1.4:
-    resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
-    engines: {node: '>=0.8.19'}
-
   inflight@1.0.6:
     resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
     deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
@@ -633,39 +432,9 @@ packages:
     resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
     engines: {node: '>=0.10.0'}
 
-  isexe@2.0.0:
-    resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
-
   javascript-natural-sort@0.7.1:
     resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
 
-  js-yaml@4.1.0:
-    resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
-    hasBin: true
-
-  json-buffer@3.0.1:
-    resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
-
-  json-schema-traverse@0.4.1:
-    resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
-
-  json-stable-stringify-without-jsonify@1.0.1:
-    resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
-
-  keyv@4.5.4:
-    resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
-
-  levn@0.4.1:
-    resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
-    engines: {node: '>= 0.8.0'}
-
-  locate-path@6.0.0:
-    resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
-    engines: {node: '>=10'}
-
-  lodash.merge@4.6.2:
-    resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
-
   make-error@1.3.6:
     resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
 
@@ -719,9 +488,6 @@ packages:
   ms@2.1.3:
     resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
 
-  natural-compare@1.4.0:
-    resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
-
   negotiator@0.6.3:
     resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
     engines: {node: '>= 0.6'}
@@ -750,38 +516,14 @@ packages:
   once@1.4.0:
     resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
 
-  optionator@0.9.4:
-    resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
-    engines: {node: '>= 0.8.0'}
-
-  p-limit@3.1.0:
-    resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
-    engines: {node: '>=10'}
-
-  p-locate@5.0.0:
-    resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
-    engines: {node: '>=10'}
-
-  parent-module@1.0.1:
-    resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
-    engines: {node: '>=6'}
-
   parseurl@1.3.3:
     resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
     engines: {node: '>= 0.8'}
 
-  path-exists@4.0.0:
-    resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
-    engines: {node: '>=8'}
-
   path-is-absolute@1.0.1:
     resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
     engines: {node: '>=0.10.0'}
 
-  path-key@3.1.1:
-    resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
-    engines: {node: '>=8'}
-
   path-parse@1.0.7:
     resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
 
@@ -792,10 +534,6 @@ packages:
     resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
     engines: {node: '>=8.6'}
 
-  prelude-ls@1.2.1:
-    resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
-    engines: {node: '>= 0.8.0'}
-
   proxy-addr@2.0.7:
     resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
     engines: {node: '>= 0.10'}
@@ -803,10 +541,6 @@ packages:
   pstree.remy@1.1.8:
     resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
 
-  punycode@2.3.1:
-    resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
-    engines: {node: '>=6'}
-
   qs@6.13.0:
     resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
     engines: {node: '>=0.6'}
@@ -829,10 +563,6 @@ packages:
   requires-port@1.0.0:
     resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
 
-  resolve-from@4.0.0:
-    resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
-    engines: {node: '>=4'}
-
   resolve@1.22.8:
     resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
     hasBin: true
@@ -871,14 +601,6 @@ packages:
   setprototypeof@1.2.0:
     resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
 
-  shebang-command@2.0.0:
-    resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
-    engines: {node: '>=8'}
-
-  shebang-regex@3.0.0:
-    resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
-    engines: {node: '>=8'}
-
   side-channel@1.0.6:
     resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
     engines: {node: '>= 0.4'}
@@ -917,18 +639,10 @@ packages:
     resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
     engines: {node: '>=0.10.0'}
 
-  strip-json-comments@3.1.1:
-    resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
-    engines: {node: '>=8'}
-
   supports-color@5.5.0:
     resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
     engines: {node: '>=4'}
 
-  supports-color@7.2.0:
-    resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
-    engines: {node: '>=8'}
-
   supports-preserve-symlinks-flag@1.0.0:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
     engines: {node: '>= 0.4'}
@@ -980,10 +694,6 @@ packages:
   tsconfig@7.0.0:
     resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==}
 
-  type-check@0.4.0:
-    resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
-    engines: {node: '>= 0.8.0'}
-
   type-is@1.6.18:
     resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
     engines: {node: '>= 0.6'}
@@ -992,8 +702,8 @@ packages:
     resolution: {integrity: sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==}
     engines: {node: '>= 18'}
 
-  typescript@5.7.2:
-    resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==}
+  typescript@5.6.3:
+    resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
     engines: {node: '>=14.17'}
     hasBin: true
 
@@ -1007,9 +717,6 @@ packages:
     resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
     engines: {node: '>= 0.8'}
 
-  uri-js@4.4.1:
-    resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
-
   utils-merge@1.0.1:
     resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
     engines: {node: '>= 0.4.0'}
@@ -1021,15 +728,6 @@ packages:
     resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
     engines: {node: '>= 0.8'}
 
-  which@2.0.2:
-    resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
-    engines: {node: '>= 8'}
-    hasBin: true
-
-  word-wrap@1.2.5:
-    resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
-    engines: {node: '>=0.10.0'}
-
   wrappy@1.0.2:
     resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
 
@@ -1053,10 +751,6 @@ packages:
     resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
     engines: {node: '>=6'}
 
-  yocto-queue@0.1.0:
-    resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
-    engines: {node: '>=10'}
-
 snapshots:
 
   '@babel/runtime@7.26.0':
@@ -1067,58 +761,6 @@ snapshots:
     dependencies:
       '@jridgewell/trace-mapping': 0.3.9
 
-  '@eslint-community/eslint-utils@4.4.1(eslint@9.15.0)':
-    dependencies:
-      eslint: 9.15.0
-      eslint-visitor-keys: 3.4.3
-
-  '@eslint-community/regexpp@4.12.1': {}
-
-  '@eslint/config-array@0.19.0':
-    dependencies:
-      '@eslint/object-schema': 2.1.4
-      debug: 4.3.7(supports-color@5.5.0)
-      minimatch: 3.1.2
-    transitivePeerDependencies:
-      - supports-color
-
-  '@eslint/core@0.9.0': {}
-
-  '@eslint/eslintrc@3.2.0':
-    dependencies:
-      ajv: 6.12.6
-      debug: 4.3.7(supports-color@5.5.0)
-      espree: 10.3.0
-      globals: 14.0.0
-      ignore: 5.3.2
-      import-fresh: 3.3.0
-      js-yaml: 4.1.0
-      minimatch: 3.1.2
-      strip-json-comments: 3.1.1
-    transitivePeerDependencies:
-      - supports-color
-
-  '@eslint/js@9.15.0': {}
-
-  '@eslint/object-schema@2.1.4': {}
-
-  '@eslint/plugin-kit@0.2.3':
-    dependencies:
-      levn: 0.4.1
-
-  '@humanfs/core@0.19.1': {}
-
-  '@humanfs/node@0.16.6':
-    dependencies:
-      '@humanfs/core': 0.19.1
-      '@humanwhocodes/retry': 0.3.1
-
-  '@humanwhocodes/module-importer@1.0.1': {}
-
-  '@humanwhocodes/retry@0.3.1': {}
-
-  '@humanwhocodes/retry@0.4.1': {}
-
   '@jridgewell/resolve-uri@3.1.2': {}
 
   '@jridgewell/sourcemap-codec@1.5.0': {}
@@ -1153,8 +795,6 @@ snapshots:
     dependencies:
       '@types/node': 22.9.1
 
-  '@types/estree@1.0.6': {}
-
   '@types/express-serve-static-core@5.0.1':
     dependencies:
       '@types/node': 22.9.1
@@ -1175,8 +815,6 @@ snapshots:
     dependencies:
       '@types/node': 22.9.1
 
-  '@types/json-schema@7.0.15': {}
-
   '@types/mime@1.3.5': {}
 
   '@types/node@22.9.1':
@@ -1207,27 +845,12 @@ snapshots:
       mime-types: 2.1.35
       negotiator: 0.6.3
 
-  acorn-jsx@5.3.2(acorn@8.14.0):
-    dependencies:
-      acorn: 8.14.0
-
   acorn-walk@8.3.4:
     dependencies:
       acorn: 8.14.0
 
   acorn@8.14.0: {}
 
-  ajv@6.12.6:
-    dependencies:
-      fast-deep-equal: 3.1.3
-      fast-json-stable-stringify: 2.1.0
-      json-schema-traverse: 0.4.1
-      uri-js: 4.4.1
-
-  ansi-styles@4.3.0:
-    dependencies:
-      color-convert: 2.0.1
-
   anymatch@3.1.3:
     dependencies:
       normalize-path: 3.0.0
@@ -1235,8 +858,6 @@ snapshots:
 
   arg@4.1.3: {}
 
-  argparse@2.0.1: {}
-
   array-flatten@1.1.1: {}
 
   balanced-match@1.0.2: {}
@@ -1283,13 +904,6 @@ snapshots:
       get-intrinsic: 1.2.4
       set-function-length: 1.2.2
 
-  callsites@3.1.0: {}
-
-  chalk@4.1.2:
-    dependencies:
-      ansi-styles: 4.3.0
-      supports-color: 7.2.0
-
   chokidar@3.6.0:
     dependencies:
       anymatch: 3.1.3
@@ -1302,12 +916,6 @@ snapshots:
     optionalDependencies:
       fsevents: 2.3.3
 
-  color-convert@2.0.1:
-    dependencies:
-      color-name: 1.1.4
-
-  color-name@1.1.4: {}
-
   complex.js@2.4.2: {}
 
   concat-map@0.0.1: {}
@@ -1331,12 +939,6 @@ snapshots:
 
   create-require@1.1.1: {}
 
-  cross-spawn@7.0.6:
-    dependencies:
-      path-key: 3.1.1
-      shebang-command: 2.0.0
-      which: 2.0.2
-
   debug@2.6.9:
     dependencies:
       ms: 2.0.0
@@ -1349,8 +951,6 @@ snapshots:
 
   decimal.js@10.4.3: {}
 
-  deep-is@0.1.4: {}
-
   define-data-property@1.1.4:
     dependencies:
       es-define-property: 1.0.0
@@ -1402,74 +1002,6 @@ snapshots:
 
   escape-latex@1.2.0: {}
 
-  escape-string-regexp@4.0.0: {}
-
-  eslint-scope@8.2.0:
-    dependencies:
-      esrecurse: 4.3.0
-      estraverse: 5.3.0
-
-  eslint-visitor-keys@3.4.3: {}
-
-  eslint-visitor-keys@4.2.0: {}
-
-  eslint@9.15.0:
-    dependencies:
-      '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0)
-      '@eslint-community/regexpp': 4.12.1
-      '@eslint/config-array': 0.19.0
-      '@eslint/core': 0.9.0
-      '@eslint/eslintrc': 3.2.0
-      '@eslint/js': 9.15.0
-      '@eslint/plugin-kit': 0.2.3
-      '@humanfs/node': 0.16.6
-      '@humanwhocodes/module-importer': 1.0.1
-      '@humanwhocodes/retry': 0.4.1
-      '@types/estree': 1.0.6
-      '@types/json-schema': 7.0.15
-      ajv: 6.12.6
-      chalk: 4.1.2
-      cross-spawn: 7.0.6
-      debug: 4.3.7(supports-color@5.5.0)
-      escape-string-regexp: 4.0.0
-      eslint-scope: 8.2.0
-      eslint-visitor-keys: 4.2.0
-      espree: 10.3.0
-      esquery: 1.6.0
-      esutils: 2.0.3
-      fast-deep-equal: 3.1.3
-      file-entry-cache: 8.0.0
-      find-up: 5.0.0
-      glob-parent: 6.0.2
-      ignore: 5.3.2
-      imurmurhash: 0.1.4
-      is-glob: 4.0.3
-      json-stable-stringify-without-jsonify: 1.0.1
-      lodash.merge: 4.6.2
-      minimatch: 3.1.2
-      natural-compare: 1.4.0
-      optionator: 0.9.4
-    transitivePeerDependencies:
-      - supports-color
-
-  espree@10.3.0:
-    dependencies:
-      acorn: 8.14.0
-      acorn-jsx: 5.3.2(acorn@8.14.0)
-      eslint-visitor-keys: 4.2.0
-
-  esquery@1.6.0:
-    dependencies:
-      estraverse: 5.3.0
-
-  esrecurse@4.3.0:
-    dependencies:
-      estraverse: 5.3.0
-
-  estraverse@5.3.0: {}
-
-  esutils@2.0.3: {}
-
   etag@1.8.1: {}
 
   eventemitter3@4.0.7: {}
@@ -1510,16 +1042,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  fast-deep-equal@3.1.3: {}
-
-  fast-json-stable-stringify@2.1.0: {}
-
-  fast-levenshtein@2.0.6: {}
-
-  file-entry-cache@8.0.0:
-    dependencies:
-      flat-cache: 4.0.1
-
   fill-range@7.1.1:
     dependencies:
       to-regex-range: 5.0.1
@@ -1536,18 +1058,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  find-up@5.0.0:
-    dependencies:
-      locate-path: 6.0.0
-      path-exists: 4.0.0
-
-  flat-cache@4.0.1:
-    dependencies:
-      flatted: 3.3.2
-      keyv: 4.5.4
-
-  flatted@3.3.2: {}
-
   follow-redirects@1.15.9(debug@4.3.7):
     optionalDependencies:
       debug: 4.3.7(supports-color@5.5.0)
@@ -1577,10 +1087,6 @@ snapshots:
     dependencies:
       is-glob: 4.0.3
 
-  glob-parent@6.0.2:
-    dependencies:
-      is-glob: 4.0.3
-
   glob@7.2.3:
     dependencies:
       fs.realpath: 1.0.0
@@ -1590,18 +1096,12 @@ snapshots:
       once: 1.4.0
       path-is-absolute: 1.0.1
 
-  globals@14.0.0: {}
-
-  globals@15.12.0: {}
-
   gopd@1.0.1:
     dependencies:
       get-intrinsic: 1.2.4
 
   has-flag@3.0.0: {}
 
-  has-flag@4.0.0: {}
-
   has-property-descriptors@1.0.2:
     dependencies:
       es-define-property: 1.0.0
@@ -1647,15 +1147,6 @@ snapshots:
 
   ignore-by-default@1.0.1: {}
 
-  ignore@5.3.2: {}
-
-  import-fresh@3.3.0:
-    dependencies:
-      parent-module: 1.0.1
-      resolve-from: 4.0.0
-
-  imurmurhash@0.1.4: {}
-
   inflight@1.0.6:
     dependencies:
       once: 1.4.0
@@ -1683,35 +1174,8 @@ snapshots:
 
   is-plain-object@5.0.0: {}
 
-  isexe@2.0.0: {}
-
   javascript-natural-sort@0.7.1: {}
 
-  js-yaml@4.1.0:
-    dependencies:
-      argparse: 2.0.1
-
-  json-buffer@3.0.1: {}
-
-  json-schema-traverse@0.4.1: {}
-
-  json-stable-stringify-without-jsonify@1.0.1: {}
-
-  keyv@4.5.4:
-    dependencies:
-      json-buffer: 3.0.1
-
-  levn@0.4.1:
-    dependencies:
-      prelude-ls: 1.2.1
-      type-check: 0.4.0
-
-  locate-path@6.0.0:
-    dependencies:
-      p-locate: 5.0.0
-
-  lodash.merge@4.6.2: {}
-
   make-error@1.3.6: {}
 
   mathjs@14.0.0:
@@ -1757,8 +1221,6 @@ snapshots:
 
   ms@2.1.3: {}
 
-  natural-compare@1.4.0: {}
-
   negotiator@0.6.3: {}
 
   nodemon@3.1.7:
@@ -1788,43 +1250,16 @@ snapshots:
     dependencies:
       wrappy: 1.0.2
 
-  optionator@0.9.4:
-    dependencies:
-      deep-is: 0.1.4
-      fast-levenshtein: 2.0.6
-      levn: 0.4.1
-      prelude-ls: 1.2.1
-      type-check: 0.4.0
-      word-wrap: 1.2.5
-
-  p-limit@3.1.0:
-    dependencies:
-      yocto-queue: 0.1.0
-
-  p-locate@5.0.0:
-    dependencies:
-      p-limit: 3.1.0
-
-  parent-module@1.0.1:
-    dependencies:
-      callsites: 3.1.0
-
   parseurl@1.3.3: {}
 
-  path-exists@4.0.0: {}
-
   path-is-absolute@1.0.1: {}
 
-  path-key@3.1.1: {}
-
   path-parse@1.0.7: {}
 
   path-to-regexp@0.1.10: {}
 
   picomatch@2.3.1: {}
 
-  prelude-ls@1.2.1: {}
-
   proxy-addr@2.0.7:
     dependencies:
       forwarded: 0.2.0
@@ -1832,8 +1267,6 @@ snapshots:
 
   pstree.remy@1.1.8: {}
 
-  punycode@2.3.1: {}
-
   qs@6.13.0:
     dependencies:
       side-channel: 1.0.6
@@ -1855,8 +1288,6 @@ snapshots:
 
   requires-port@1.0.0: {}
 
-  resolve-from@4.0.0: {}
-
   resolve@1.22.8:
     dependencies:
       is-core-module: 2.15.1
@@ -1913,12 +1344,6 @@ snapshots:
 
   setprototypeof@1.2.0: {}
 
-  shebang-command@2.0.0:
-    dependencies:
-      shebang-regex: 3.0.0
-
-  shebang-regex@3.0.0: {}
-
   side-channel@1.0.6:
     dependencies:
       call-bind: 1.0.7
@@ -1973,16 +1398,10 @@ snapshots:
 
   strip-json-comments@2.0.1: {}
 
-  strip-json-comments@3.1.1: {}
-
   supports-color@5.5.0:
     dependencies:
       has-flag: 3.0.0
 
-  supports-color@7.2.0:
-    dependencies:
-      has-flag: 4.0.0
-
   supports-preserve-symlinks-flag@1.0.0: {}
 
   tiny-emitter@2.1.0: {}
@@ -1997,7 +1416,7 @@ snapshots:
 
   tree-kill@1.2.2: {}
 
-  ts-node-dev@2.0.0(@types/node@22.9.1)(typescript@5.7.2):
+  ts-node-dev@2.0.0(@types/node@22.9.1)(typescript@5.6.3):
     dependencies:
       chokidar: 3.6.0
       dynamic-dedupe: 0.3.0
@@ -2007,15 +1426,15 @@ snapshots:
       rimraf: 2.7.1
       source-map-support: 0.5.21
       tree-kill: 1.2.2
-      ts-node: 10.9.2(@types/node@22.9.1)(typescript@5.7.2)
+      ts-node: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
       tsconfig: 7.0.0
-      typescript: 5.7.2
+      typescript: 5.6.3
     transitivePeerDependencies:
       - '@swc/core'
       - '@swc/wasm'
       - '@types/node'
 
-  ts-node@10.9.2(@types/node@22.9.1)(typescript@5.7.2):
+  ts-node@10.9.2(@types/node@22.9.1)(typescript@5.6.3):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
       '@tsconfig/node10': 1.0.11
@@ -2029,7 +1448,7 @@ snapshots:
       create-require: 1.1.1
       diff: 4.0.2
       make-error: 1.3.6
-      typescript: 5.7.2
+      typescript: 5.6.3
       v8-compile-cache-lib: 3.0.1
       yn: 3.1.1
 
@@ -2040,10 +1459,6 @@ snapshots:
       strip-bom: 3.0.0
       strip-json-comments: 2.0.1
 
-  type-check@0.4.0:
-    dependencies:
-      prelude-ls: 1.2.1
-
   type-is@1.6.18:
     dependencies:
       media-typer: 0.3.0
@@ -2051,7 +1466,7 @@ snapshots:
 
   typed-function@4.2.1: {}
 
-  typescript@5.7.2: {}
+  typescript@5.6.3: {}
 
   undefsafe@2.0.5: {}
 
@@ -2059,22 +1474,12 @@ snapshots:
 
   unpipe@1.0.0: {}
 
-  uri-js@4.4.1:
-    dependencies:
-      punycode: 2.3.1
-
   utils-merge@1.0.1: {}
 
   v8-compile-cache-lib@3.0.1: {}
 
   vary@1.1.2: {}
 
-  which@2.0.2:
-    dependencies:
-      isexe: 2.0.0
-
-  word-wrap@1.2.5: {}
-
   wrappy@1.0.2: {}
 
   ws@8.17.1: {}
@@ -2082,5 +1487,3 @@ snapshots:
   xtend@4.0.2: {}
 
   yn@3.1.1: {}
-
-  yocto-queue@0.1.0: {}
diff --git a/pi/ui/env.d.ts b/pi/ui/env.d.ts
index 81486fe..14fa749 100644
--- a/pi/ui/env.d.ts
+++ b/pi/ui/env.d.ts
@@ -1,10 +1 @@
 // / <reference types="vite/client" />
-
-interface ImportMetaEnv {
-  readonly BASE_URL: string;
-  readonly PROD: boolean
-}
-
-interface ImportMeta {
-  readonly env: ImportMetaEnv;
-}
\ No newline at end of file

From db2bb164dedecb6c93dc7820c0cc33fc76004d89 Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Fri, 22 Nov 2024 14:02:10 -0500
Subject: [PATCH 12/34] Revert "Add lawndon-pi to tests workflow - lint UI"

This reverts commit 215c7a510113f92cf058a8795aae0242f4499fcd.
---
 .github/workflows/tests.yml               |  50 +--
 pi/package.json                           |   3 +-
 pi/pnpm-lock.yaml                         | 498 ++++++++++++++++++++++
 pi/ui/env.d.ts                            |   2 +-
 pi/ui/eslint.config.js                    | 144 +------
 pi/ui/src/App.vue                         |   2 +-
 pi/ui/src/components/UWBVisualization.vue |  24 +-
 pi/ui/src/main.ts                         |  26 +-
 pi/ui/src/router/index.ts                 |  14 +-
 pi/ui/src/views/HomeView.vue              |   2 +-
 pi/ui/vite.config.ts                      |  22 +-
 11 files changed, 556 insertions(+), 231 deletions(-)

diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 6792013..187ed35 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -8,7 +8,7 @@ on:
     - cron: "0 4 * * 4"
 
 jobs:
-  build-lawndon:
+  build:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
@@ -40,51 +40,3 @@ jobs:
           verbose: false
           cli-compile-flags: |
             - --export-binaries
-
-  package-lawndon-pi:
-    runs-on: ubuntu-latest
-    with:
-      env:
-        WORKING_DIR: ./pi
-    steps:
-      - name: Checkout repository
-        uses: actions/checkout@v4
-
-      - name: Install pnpm
-        uses: pnpm/action-setup@v4
-        with:
-          version: 9
-
-      - name: Setup Node.js
-        uses: actions/setup-node@v4
-        with:
-          node-version: '22'
-
-      - name: Install dependencies for UI
-        working-directory: ${{ env.WORKING_DIR }}
-        run: |
-          pnpm install:ui
-
-      - name: Build UI
-        working-directory: ${{ env.WORKING_DIR }}
-        run: |
-          pnpm build:ui
-
-      - name: Install dependencies for Server
-        working-directory: ${{ env.WORKING_DIR }}
-        run: |
-          pnpm install:server
-
-      - name: Build Server
-        working-directory: ${{ env.WORKING_DIR }}
-        run: |
-          pnpm build:server
-
-      - name: Run package script
-        working-directory: ${{ env.WORKING_DIR }}
-        run: |
-          ./package.sh
-
-      - name: Verify package contents
-        run: |
-          tar -tf lawndon-pi.tar.gz
diff --git a/pi/package.json b/pi/package.json
index cd43688..455b171 100644
--- a/pi/package.json
+++ b/pi/package.json
@@ -14,6 +14,7 @@
   },
   "packageManager": "pnpm@9.14.2",
   "devDependencies": {
-    "concurrently": "^9.1.0"
+    "concurrently": "^9.1.0",
+    "ts-node-dev": "^2.0.0"
   }
 }
diff --git a/pi/pnpm-lock.yaml b/pi/pnpm-lock.yaml
index ca37c77..fad7d35 100644
--- a/pi/pnpm-lock.yaml
+++ b/pi/pnpm-lock.yaml
@@ -11,9 +11,56 @@ importers:
       concurrently:
         specifier: ^9.1.0
         version: 9.1.0
+      ts-node-dev:
+        specifier: ^2.0.0
+        version: 2.0.0(@types/node@22.9.1)(typescript@5.6.3)
 
 packages:
 
+  '@cspotcode/source-map-support@0.8.1':
+    resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
+    engines: {node: '>=12'}
+
+  '@jridgewell/resolve-uri@3.1.2':
+    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/sourcemap-codec@1.5.0':
+    resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
+
+  '@jridgewell/trace-mapping@0.3.9':
+    resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+
+  '@tsconfig/node10@1.0.11':
+    resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
+
+  '@tsconfig/node12@1.0.11':
+    resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
+
+  '@tsconfig/node14@1.0.3':
+    resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
+
+  '@tsconfig/node16@1.0.4':
+    resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+
+  '@types/node@22.9.1':
+    resolution: {integrity: sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==}
+
+  '@types/strip-bom@3.0.0':
+    resolution: {integrity: sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==}
+
+  '@types/strip-json-comments@0.0.30':
+    resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==}
+
+  acorn-walk@8.3.4:
+    resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
+    engines: {node: '>=0.4.0'}
+
+  acorn@8.14.0:
+    resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+
   ansi-regex@5.0.1:
     resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
     engines: {node: '>=8'}
@@ -22,10 +69,38 @@ packages:
     resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
     engines: {node: '>=8'}
 
+  anymatch@3.1.3:
+    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+    engines: {node: '>= 8'}
+
+  arg@4.1.3:
+    resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
+
+  balanced-match@1.0.2:
+    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+  binary-extensions@2.3.0:
+    resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
+    engines: {node: '>=8'}
+
+  brace-expansion@1.1.11:
+    resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+
+  braces@3.0.3:
+    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+    engines: {node: '>=8'}
+
+  buffer-from@1.1.2:
+    resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+
   chalk@4.1.2:
     resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
     engines: {node: '>=10'}
 
+  chokidar@3.6.0:
+    resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
+    engines: {node: '>= 8.10.0'}
+
   cliui@8.0.1:
     resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
     engines: {node: '>=12'}
@@ -37,11 +112,24 @@ packages:
   color-name@1.1.4:
     resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
 
+  concat-map@0.0.1:
+    resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
   concurrently@9.1.0:
     resolution: {integrity: sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==}
     engines: {node: '>=18'}
     hasBin: true
 
+  create-require@1.1.1:
+    resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+
+  diff@4.0.2:
+    resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
+    engines: {node: '>=0.3.1'}
+
+  dynamic-dedupe@0.3.0:
+    resolution: {integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==}
+
   emoji-regex@8.0.0:
     resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
 
@@ -49,31 +137,137 @@ packages:
     resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
     engines: {node: '>=6'}
 
+  fill-range@7.1.1:
+    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+    engines: {node: '>=8'}
+
+  fs.realpath@1.0.0:
+    resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+  fsevents@2.3.3:
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+
+  function-bind@1.1.2:
+    resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
   get-caller-file@2.0.5:
     resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
     engines: {node: 6.* || 8.* || >= 10.*}
 
+  glob-parent@5.1.2:
+    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+    engines: {node: '>= 6'}
+
+  glob@7.2.3:
+    resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+    deprecated: Glob versions prior to v9 are no longer supported
+
   has-flag@4.0.0:
     resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
     engines: {node: '>=8'}
 
+  hasown@2.0.2:
+    resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+    engines: {node: '>= 0.4'}
+
+  inflight@1.0.6:
+    resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+    deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
+
+  inherits@2.0.4:
+    resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+  is-binary-path@2.1.0:
+    resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+    engines: {node: '>=8'}
+
+  is-core-module@2.15.1:
+    resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
+    engines: {node: '>= 0.4'}
+
+  is-extglob@2.1.1:
+    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+    engines: {node: '>=0.10.0'}
+
   is-fullwidth-code-point@3.0.0:
     resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
     engines: {node: '>=8'}
 
+  is-glob@4.0.3:
+    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+    engines: {node: '>=0.10.0'}
+
+  is-number@7.0.0:
+    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+    engines: {node: '>=0.12.0'}
+
   lodash@4.17.21:
     resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
 
+  make-error@1.3.6:
+    resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
+
+  minimatch@3.1.2:
+    resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+  minimist@1.2.8:
+    resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+  mkdirp@1.0.4:
+    resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
+    engines: {node: '>=10'}
+    hasBin: true
+
+  normalize-path@3.0.0:
+    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+    engines: {node: '>=0.10.0'}
+
+  once@1.4.0:
+    resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+  path-is-absolute@1.0.1:
+    resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+    engines: {node: '>=0.10.0'}
+
+  path-parse@1.0.7:
+    resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+  picomatch@2.3.1:
+    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+    engines: {node: '>=8.6'}
+
+  readdirp@3.6.0:
+    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+    engines: {node: '>=8.10.0'}
+
   require-directory@2.1.1:
     resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
     engines: {node: '>=0.10.0'}
 
+  resolve@1.22.8:
+    resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
+    hasBin: true
+
+  rimraf@2.7.1:
+    resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
+    deprecated: Rimraf versions prior to v4 are no longer supported
+    hasBin: true
+
   rxjs@7.8.1:
     resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
 
   shell-quote@1.8.1:
     resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
 
+  source-map-support@0.5.21:
+    resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
+
+  source-map@0.6.1:
+    resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+    engines: {node: '>=0.10.0'}
+
   string-width@4.2.3:
     resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
     engines: {node: '>=8'}
@@ -82,6 +276,14 @@ packages:
     resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
     engines: {node: '>=8'}
 
+  strip-bom@3.0.0:
+    resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
+    engines: {node: '>=4'}
+
+  strip-json-comments@2.0.1:
+    resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
+    engines: {node: '>=0.10.0'}
+
   supports-color@7.2.0:
     resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
     engines: {node: '>=8'}
@@ -90,17 +292,71 @@ packages:
     resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
     engines: {node: '>=10'}
 
+  supports-preserve-symlinks-flag@1.0.0:
+    resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+    engines: {node: '>= 0.4'}
+
+  to-regex-range@5.0.1:
+    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+    engines: {node: '>=8.0'}
+
   tree-kill@1.2.2:
     resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
     hasBin: true
 
+  ts-node-dev@2.0.0:
+    resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==}
+    engines: {node: '>=0.8.0'}
+    hasBin: true
+    peerDependencies:
+      node-notifier: '*'
+      typescript: '*'
+    peerDependenciesMeta:
+      node-notifier:
+        optional: true
+
+  ts-node@10.9.2:
+    resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
+    hasBin: true
+    peerDependencies:
+      '@swc/core': '>=1.2.50'
+      '@swc/wasm': '>=1.2.50'
+      '@types/node': '*'
+      typescript: '>=2.7'
+    peerDependenciesMeta:
+      '@swc/core':
+        optional: true
+      '@swc/wasm':
+        optional: true
+
+  tsconfig@7.0.0:
+    resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==}
+
   tslib@2.8.1:
     resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
 
+  typescript@5.6.3:
+    resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
+    engines: {node: '>=14.17'}
+    hasBin: true
+
+  undici-types@6.19.8:
+    resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+
+  v8-compile-cache-lib@3.0.1:
+    resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
+
   wrap-ansi@7.0.0:
     resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
     engines: {node: '>=10'}
 
+  wrappy@1.0.2:
+    resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+  xtend@4.0.2:
+    resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
+    engines: {node: '>=0.4'}
+
   y18n@5.0.8:
     resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
     engines: {node: '>=10'}
@@ -113,19 +369,92 @@ packages:
     resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
     engines: {node: '>=12'}
 
+  yn@3.1.1:
+    resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
+    engines: {node: '>=6'}
+
 snapshots:
 
+  '@cspotcode/source-map-support@0.8.1':
+    dependencies:
+      '@jridgewell/trace-mapping': 0.3.9
+
+  '@jridgewell/resolve-uri@3.1.2': {}
+
+  '@jridgewell/sourcemap-codec@1.5.0': {}
+
+  '@jridgewell/trace-mapping@0.3.9':
+    dependencies:
+      '@jridgewell/resolve-uri': 3.1.2
+      '@jridgewell/sourcemap-codec': 1.5.0
+
+  '@tsconfig/node10@1.0.11': {}
+
+  '@tsconfig/node12@1.0.11': {}
+
+  '@tsconfig/node14@1.0.3': {}
+
+  '@tsconfig/node16@1.0.4': {}
+
+  '@types/node@22.9.1':
+    dependencies:
+      undici-types: 6.19.8
+
+  '@types/strip-bom@3.0.0': {}
+
+  '@types/strip-json-comments@0.0.30': {}
+
+  acorn-walk@8.3.4:
+    dependencies:
+      acorn: 8.14.0
+
+  acorn@8.14.0: {}
+
   ansi-regex@5.0.1: {}
 
   ansi-styles@4.3.0:
     dependencies:
       color-convert: 2.0.1
 
+  anymatch@3.1.3:
+    dependencies:
+      normalize-path: 3.0.0
+      picomatch: 2.3.1
+
+  arg@4.1.3: {}
+
+  balanced-match@1.0.2: {}
+
+  binary-extensions@2.3.0: {}
+
+  brace-expansion@1.1.11:
+    dependencies:
+      balanced-match: 1.0.2
+      concat-map: 0.0.1
+
+  braces@3.0.3:
+    dependencies:
+      fill-range: 7.1.1
+
+  buffer-from@1.1.2: {}
+
   chalk@4.1.2:
     dependencies:
       ansi-styles: 4.3.0
       supports-color: 7.2.0
 
+  chokidar@3.6.0:
+    dependencies:
+      anymatch: 3.1.3
+      braces: 3.0.3
+      glob-parent: 5.1.2
+      is-binary-path: 2.1.0
+      is-glob: 4.0.3
+      normalize-path: 3.0.0
+      readdirp: 3.6.0
+    optionalDependencies:
+      fsevents: 2.3.3
+
   cliui@8.0.1:
     dependencies:
       string-width: 4.2.3
@@ -138,6 +467,8 @@ snapshots:
 
   color-name@1.1.4: {}
 
+  concat-map@0.0.1: {}
+
   concurrently@9.1.0:
     dependencies:
       chalk: 4.1.2
@@ -148,26 +479,128 @@ snapshots:
       tree-kill: 1.2.2
       yargs: 17.7.2
 
+  create-require@1.1.1: {}
+
+  diff@4.0.2: {}
+
+  dynamic-dedupe@0.3.0:
+    dependencies:
+      xtend: 4.0.2
+
   emoji-regex@8.0.0: {}
 
   escalade@3.2.0: {}
 
+  fill-range@7.1.1:
+    dependencies:
+      to-regex-range: 5.0.1
+
+  fs.realpath@1.0.0: {}
+
+  fsevents@2.3.3:
+    optional: true
+
+  function-bind@1.1.2: {}
+
   get-caller-file@2.0.5: {}
 
+  glob-parent@5.1.2:
+    dependencies:
+      is-glob: 4.0.3
+
+  glob@7.2.3:
+    dependencies:
+      fs.realpath: 1.0.0
+      inflight: 1.0.6
+      inherits: 2.0.4
+      minimatch: 3.1.2
+      once: 1.4.0
+      path-is-absolute: 1.0.1
+
   has-flag@4.0.0: {}
 
+  hasown@2.0.2:
+    dependencies:
+      function-bind: 1.1.2
+
+  inflight@1.0.6:
+    dependencies:
+      once: 1.4.0
+      wrappy: 1.0.2
+
+  inherits@2.0.4: {}
+
+  is-binary-path@2.1.0:
+    dependencies:
+      binary-extensions: 2.3.0
+
+  is-core-module@2.15.1:
+    dependencies:
+      hasown: 2.0.2
+
+  is-extglob@2.1.1: {}
+
   is-fullwidth-code-point@3.0.0: {}
 
+  is-glob@4.0.3:
+    dependencies:
+      is-extglob: 2.1.1
+
+  is-number@7.0.0: {}
+
   lodash@4.17.21: {}
 
+  make-error@1.3.6: {}
+
+  minimatch@3.1.2:
+    dependencies:
+      brace-expansion: 1.1.11
+
+  minimist@1.2.8: {}
+
+  mkdirp@1.0.4: {}
+
+  normalize-path@3.0.0: {}
+
+  once@1.4.0:
+    dependencies:
+      wrappy: 1.0.2
+
+  path-is-absolute@1.0.1: {}
+
+  path-parse@1.0.7: {}
+
+  picomatch@2.3.1: {}
+
+  readdirp@3.6.0:
+    dependencies:
+      picomatch: 2.3.1
+
   require-directory@2.1.1: {}
 
+  resolve@1.22.8:
+    dependencies:
+      is-core-module: 2.15.1
+      path-parse: 1.0.7
+      supports-preserve-symlinks-flag: 1.0.0
+
+  rimraf@2.7.1:
+    dependencies:
+      glob: 7.2.3
+
   rxjs@7.8.1:
     dependencies:
       tslib: 2.8.1
 
   shell-quote@1.8.1: {}
 
+  source-map-support@0.5.21:
+    dependencies:
+      buffer-from: 1.1.2
+      source-map: 0.6.1
+
+  source-map@0.6.1: {}
+
   string-width@4.2.3:
     dependencies:
       emoji-regex: 8.0.0
@@ -178,6 +611,10 @@ snapshots:
     dependencies:
       ansi-regex: 5.0.1
 
+  strip-bom@3.0.0: {}
+
+  strip-json-comments@2.0.1: {}
+
   supports-color@7.2.0:
     dependencies:
       has-flag: 4.0.0
@@ -186,16 +623,75 @@ snapshots:
     dependencies:
       has-flag: 4.0.0
 
+  supports-preserve-symlinks-flag@1.0.0: {}
+
+  to-regex-range@5.0.1:
+    dependencies:
+      is-number: 7.0.0
+
   tree-kill@1.2.2: {}
 
+  ts-node-dev@2.0.0(@types/node@22.9.1)(typescript@5.6.3):
+    dependencies:
+      chokidar: 3.6.0
+      dynamic-dedupe: 0.3.0
+      minimist: 1.2.8
+      mkdirp: 1.0.4
+      resolve: 1.22.8
+      rimraf: 2.7.1
+      source-map-support: 0.5.21
+      tree-kill: 1.2.2
+      ts-node: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
+      tsconfig: 7.0.0
+      typescript: 5.6.3
+    transitivePeerDependencies:
+      - '@swc/core'
+      - '@swc/wasm'
+      - '@types/node'
+
+  ts-node@10.9.2(@types/node@22.9.1)(typescript@5.6.3):
+    dependencies:
+      '@cspotcode/source-map-support': 0.8.1
+      '@tsconfig/node10': 1.0.11
+      '@tsconfig/node12': 1.0.11
+      '@tsconfig/node14': 1.0.3
+      '@tsconfig/node16': 1.0.4
+      '@types/node': 22.9.1
+      acorn: 8.14.0
+      acorn-walk: 8.3.4
+      arg: 4.1.3
+      create-require: 1.1.1
+      diff: 4.0.2
+      make-error: 1.3.6
+      typescript: 5.6.3
+      v8-compile-cache-lib: 3.0.1
+      yn: 3.1.1
+
+  tsconfig@7.0.0:
+    dependencies:
+      '@types/strip-bom': 3.0.0
+      '@types/strip-json-comments': 0.0.30
+      strip-bom: 3.0.0
+      strip-json-comments: 2.0.1
+
   tslib@2.8.1: {}
 
+  typescript@5.6.3: {}
+
+  undici-types@6.19.8: {}
+
+  v8-compile-cache-lib@3.0.1: {}
+
   wrap-ansi@7.0.0:
     dependencies:
       ansi-styles: 4.3.0
       string-width: 4.2.3
       strip-ansi: 6.0.1
 
+  wrappy@1.0.2: {}
+
+  xtend@4.0.2: {}
+
   y18n@5.0.8: {}
 
   yargs-parser@21.1.1: {}
@@ -209,3 +705,5 @@ snapshots:
       string-width: 4.2.3
       y18n: 5.0.8
       yargs-parser: 21.1.1
+
+  yn@3.1.1: {}
diff --git a/pi/ui/env.d.ts b/pi/ui/env.d.ts
index 14fa749..11f02fe 100644
--- a/pi/ui/env.d.ts
+++ b/pi/ui/env.d.ts
@@ -1 +1 @@
-// / <reference types="vite/client" />
+/// <reference types="vite/client" />
diff --git a/pi/ui/eslint.config.js b/pi/ui/eslint.config.js
index a0f49f0..b115712 100644
--- a/pi/ui/eslint.config.js
+++ b/pi/ui/eslint.config.js
@@ -1,15 +1,15 @@
-import pluginVue from 'eslint-plugin-vue';
-import vueTsEslintConfig from '@vue/eslint-config-typescript';
-import skipFormatting from '@vue/eslint-config-prettier/skip-formatting';
+import pluginVue from 'eslint-plugin-vue'
+import vueTsEslintConfig from '@vue/eslint-config-typescript'
+import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
 
 export default [
   {
-    name:  'app/files-to-lint',
+    name: 'app/files-to-lint',
     files: ['**/*.{ts,mts,tsx,vue}'],
   },
 
   {
-    name:    'app/files-to-ignore',
+    name: 'app/files-to-ignore',
     ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
   },
 
@@ -19,137 +19,7 @@ export default [
 
   {
     rules: {
-      'vue/no-multiple-template-root':  'off',
-      'dot-notation':                   'off',
-      'guard-for-in':                   'off',
-      'new-cap':                        'off',
-      'no-empty':                       'off',
-      'no-extra-boolean-cast':          'off',
-      'no-new':                         'off',
-      'no-plusplus':                    'off',
-      'no-useless-escape':              'off',
-      'strict':                         'off',
-      'vue/html-self-closing':          'off',
-      'vue/no-v-html':                  'off',
-      'vue/multi-word-component-names': 'off',
-
-      'array-bracket-spacing':             'warn',
-      'arrow-parens':                      'warn',
-      'arrow-spacing':                     ['warn', {
-        'before': true,
-        'after':  true
-      }],
-      'block-spacing':                     ['warn', 'always'],
-      'brace-style':                       ['warn', '1tbs'],
-      'comma-dangle':                      ['warn', 'only-multiline'],
-      'comma-spacing':                     'warn',
-      'curly':                             'warn',
-      'eqeqeq':                            'warn',
-      'func-call-spacing':                 ['warn', 'never'],
-      'implicit-arrow-linebreak':          'warn',
-      'indent':                            ['warn', 2],
-      'keyword-spacing':                   'warn',
-      'lines-between-class-members':       ['warn', 'always', { 'exceptAfterSingleLine': true }],
-      'multiline-ternary':                 ['warn', 'never'],
-      'newline-per-chained-call':          ['warn', { 'ignoreChainWithDepth': 4 }],
-      'no-caller':                         'warn',
-      'no-cond-assign':                    ['warn', 'except-parens'],
-      'no-console':                        'off',
-      'no-debugger':                       'warn',
-      'no-eq-null':                        'warn',
-      'no-eval':                           'warn',
-      'no-trailing-spaces':                'warn',
-      'no-undef':                          'warn',
-      'no-unused-vars':                    'warn',
-      'no-whitespace-before-property':     'warn',
-      'object-curly-spacing':              ['warn', 'always'],
-      'object-property-newline':           'warn',
-      'object-shorthand':                  'warn',
-      'padded-blocks':                     ['warn', 'never'],
-      'prefer-arrow-callback':             'warn',
-      'prefer-template':                   'warn',
-      'rest-spread-spacing':               'warn',
-      'semi':                              ['warn', 'always'],
-      'space-before-function-paren':       ['warn', 'never'],
-      'space-infix-ops':                   'warn',
-      'spaced-comment':                    'warn',
-      'switch-colon-spacing':              'warn',
-      'template-curly-spacing':            ['warn', 'always'],
-      'yield-star-spacing':                ['warn', 'both'],
-
-      'key-spacing':              ['warn', {
-        'align': {
-          'beforeColon': false,
-          'afterColon':  true,
-          'on':          'value',
-          'mode':        'minimum'
-        },
-        'multiLine': {
-          'beforeColon': false,
-          'afterColon':  true
-        },
-      }],
-
-      'object-curly-newline':          ['warn', {
-        'ObjectExpression':  {
-          'multiline':     true,
-          'minProperties': 3
-        },
-        'ObjectPattern':     {
-          'multiline':     true,
-          'minProperties': 4
-        },
-        'ImportDeclaration': {
-          'multiline':     true,
-          'minProperties': 5
-        },
-        'ExportDeclaration': {
-          'multiline':     true,
-          'minProperties': 3
-        }
-      }],
-
-      'padding-line-between-statements': [
-        'warn',
-        {
-          'blankLine': 'always',
-          'prev':      '*',
-          'next':      'return',
-        },
-        {
-          'blankLine': 'always',
-          'prev':      'function',
-          'next':      'function',
-        },
-        // This configuration would require blank lines after every sequence of variable declarations
-        {
-          'blankLine': 'always',
-          'prev':      ['const', 'let', 'var'],
-          'next':      '*'
-        },
-        {
-          'blankLine': 'any',
-          'prev':      ['const', 'let', 'var'],
-          'next':      ['const', 'let', 'var']
-        }
-      ],
-
-      'quotes': [
-        'warn',
-        'single',
-        {
-          'avoidEscape':           true,
-          'allowTemplateLiterals': true
-        },
-      ],
-
-      'space-unary-ops': [
-        'warn',
-        {
-          'words':    true,
-          'nonwords': false,
-        }
-      ]
+      'vue/no-multiple-template-root': 'off',
     }
   }
-];
+]
diff --git a/pi/ui/src/App.vue b/pi/ui/src/App.vue
index 69dd87a..a78fb6f 100644
--- a/pi/ui/src/App.vue
+++ b/pi/ui/src/App.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { RouterView } from 'vue-router';
+import { RouterView } from 'vue-router'
 </script>
 
 <template>
diff --git a/pi/ui/src/components/UWBVisualization.vue b/pi/ui/src/components/UWBVisualization.vue
index c6ffc0b..b20ca2f 100644
--- a/pi/ui/src/components/UWBVisualization.vue
+++ b/pi/ui/src/components/UWBVisualization.vue
@@ -49,6 +49,7 @@ export default defineComponent({
             if (uwbData?.value.length === 3) {
               console.log('Incoming UWB data:', JSON.parse(JSON.stringify(uwbData.value)));
             }
+
           });
         })
         .catch((error) => {
@@ -68,7 +69,6 @@ export default defineComponent({
 
       if (ids.length < 3) {
         console.error('At least three anchors are required.');
-
         return;
       }
 
@@ -76,7 +76,6 @@ export default defineComponent({
       positions[ids[0]] = [0, 0]; // First anchor at (0, 0)
 
       const d1 = getDistance(ids[0], ids[1]) * scalingFactor;
-
       positions[ids[1]] = [d1, 0]; // Second anchor on x-axis
 
       const d2 = getDistance(ids[0], ids[2]) * scalingFactor;
@@ -93,8 +92,8 @@ export default defineComponent({
 
     function getDistance(id1: string, id2: string): number {
       return (
-        anchorDistances.value[`${ id1 }-${ id2 }`] ||
-        anchorDistances.value[`${ id2 }-${ id1 }`] ||
+        anchorDistances.value[`${id1}-${id2}`] ||
+        anchorDistances.value[`${id2}-${id1}`] ||
         0
       );
     }
@@ -126,10 +125,10 @@ export default defineComponent({
     }
 
     function drawAnchors() {
-      const anchors = Object.entries(anchorPositions.value) as [string, [number, number]][];
+      const anchors = Object.entries(anchorPositions.value);
 
       // Bind data to anchor circles
-      const anchorCircles = svg.selectAll<SVGCircleElement, [string, [number, number]]>('.anchor')
+      const anchorCircles = svg.selectAll('.anchor')
         .data(anchors, (d) => d[0]);
 
       // Enter selection
@@ -146,7 +145,7 @@ export default defineComponent({
       anchorCircles.exit().remove();
 
       // Bind data to anchor labels
-      const anchorLabels = svg.selectAll<SVGTextElement, [string, [number, number]]>('.anchor-label')
+      const anchorLabels = svg.selectAll('.anchor-label')
         .data(anchors, (d) => d[0]);
 
       // Enter selection
@@ -158,12 +157,13 @@ export default defineComponent({
         .merge(anchorLabels as any) // eslint-disable-line
         .attr('x', (d) => xScale(d[1][0]) + 5)
         .attr('y', (d) => yScale(d[1][1]) - 5)
-        .text((d) => `A${ d[0] }`);
+        .text((d) => `A${d[0]}`);
 
       // Exit selection
       anchorLabels.exit().remove();
     }
 
+
     function updateVisualization() {
       const positions: [number, number][] = [];
       const distances: number[] = [];
@@ -171,10 +171,8 @@ export default defineComponent({
       uwbData.value.forEach((anchor) => {
         const anchorID = anchor.A;
         const range = parseFloat(anchor.R) * scalingFactor; // Scale the range
-
         if (range <= 0) {
-          console.warn(`Invalid distance for anchor ${ anchorID }: ${ range }. Skipping.`);
-
+          console.warn(`Invalid distance for anchor ${anchorID}: ${range}. Skipping.`);
           return; // Skip invalid distances
         }
         if (anchorPositions.value[anchorID]) {
@@ -185,7 +183,6 @@ export default defineComponent({
 
       if (positions.length >= 3) {
         const [x, y] = calculateTagPosition(positions, distances);
-
         if (x !== -1 && y !== -1) {
           // Update scales to include tag position
           updateScales([x, y]);
@@ -221,7 +218,6 @@ export default defineComponent({
     ): [number, number] {
       if (positions.length < 3) {
         console.error('At least three anchors are required.');
-
         return [-1, -1];
       }
 
@@ -244,7 +240,6 @@ export default defineComponent({
 
         if (denominator === 0) {
           console.error('Cannot solve, determinant is zero.');
-
           return [-1, -1];
         }
 
@@ -254,7 +249,6 @@ export default defineComponent({
         return [x, y];
       } catch (error) {
         console.error('Error calculating position:', error);
-
         return [-1, -1];
       }
     }
diff --git a/pi/ui/src/main.ts b/pi/ui/src/main.ts
index ef2b5f2..0d51d91 100644
--- a/pi/ui/src/main.ts
+++ b/pi/ui/src/main.ts
@@ -1,19 +1,21 @@
-import './assets/main.css';
+import './assets/main.css'
 
-import { createApp } from 'vue';
-import { createPinia } from 'pinia';
-import { io, Socket } from 'socket.io-client';
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+import { io, Socket } from 'socket.io-client'
 
-import App from './App.vue';
-import router from './router';
+import App from './App.vue'
+import router from './router'
 
-const app = createApp(App);
+const app = createApp(App)
 
-const socket: Socket = io('http://0.0.0.0:5000', { transports: ['websocket'] });
+const socket: Socket = io('http://0.0.0.0:5000', {
+  transports: ['websocket'],
+})
 
-app.provide('socket', socket);
+app.provide('socket', socket)
 
-app.use(createPinia());
-app.use(router);
+app.use(createPinia())
+app.use(router)
 
-app.mount('#app');
+app.mount('#app')
diff --git a/pi/ui/src/router/index.ts b/pi/ui/src/router/index.ts
index 4155626..29788a1 100644
--- a/pi/ui/src/router/index.ts
+++ b/pi/ui/src/router/index.ts
@@ -1,15 +1,15 @@
-import { createRouter, createWebHistory } from 'vue-router';
-import HomeView from '../views/HomeView.vue';
+import { createRouter, createWebHistory } from 'vue-router'
+import HomeView from '../views/HomeView.vue'
 
 const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
-  routes:  [
+  routes: [
     {
-      path:      '/',
-      name:      'home',
+      path: '/',
+      name: 'home',
       component: HomeView,
     },
   ],
-});
+})
 
-export default router;
+export default router
diff --git a/pi/ui/src/views/HomeView.vue b/pi/ui/src/views/HomeView.vue
index 73b2247..4e24910 100644
--- a/pi/ui/src/views/HomeView.vue
+++ b/pi/ui/src/views/HomeView.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import UWBVisualization from '../components/UWBVisualization.vue';
+import UWBVisualization from '../components/UWBVisualization.vue'
 </script>
 
 <template>
diff --git a/pi/ui/vite.config.ts b/pi/ui/vite.config.ts
index 6c1b071..1798a4c 100644
--- a/pi/ui/vite.config.ts
+++ b/pi/ui/vite.config.ts
@@ -1,8 +1,8 @@
-import { fileURLToPath, URL } from 'node:url';
+import { fileURLToPath, URL } from 'node:url'
 
-import { defineConfig } from 'vite';
-import vue from '@vitejs/plugin-vue';
-import vueDevTools from 'vite-plugin-vue-devtools';
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import vueDevTools from 'vite-plugin-vue-devtools'
 
 // https://vite.dev/config/
 export default defineConfig({
@@ -10,6 +10,14 @@ export default defineConfig({
     vue(),
     vueDevTools(),
   ],
-  resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } },
-  server:  { proxy: { '/api': 'http://localhost:5000' } },
-});
+  resolve: {
+    alias: {
+      '@': fileURLToPath(new URL('./src', import.meta.url))
+    },
+  },
+  server: {
+    proxy: {
+      '/api': 'http://localhost:5000',
+    },
+  },
+})

From d2beec601182ed7703d20d8228718f075e83ed65 Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Fri, 22 Nov 2024 14:19:14 -0500
Subject: [PATCH 13/34] Add lawndon-pi build and package tests to test workflow
 - fix UI build (#20)

* Add lawndon-pi to tests workflow - lint UI

* Add eslint to server - update deps - fix ui build error

* Fix workflow invalid property

* Add working directory to verify package step

* Verify package contents by checking diff
---
 .github/workflows/tests.yml               |  50 +-
 pi/package.json                           |   3 +-
 pi/package.sh                             |   2 -
 pi/pnpm-lock.yaml                         | 498 -----------------
 pi/server/eslint.config.js                | 138 +++++
 pi/server/package.json                    |   8 +-
 pi/server/pnpm-lock.yaml                  | 621 +++++++++++++++++++++-
 pi/ui/env.d.ts                            |  11 +-
 pi/ui/eslint.config.js                    | 144 ++++-
 pi/ui/src/App.vue                         |   2 +-
 pi/ui/src/components/UWBVisualization.vue |  24 +-
 pi/ui/src/main.ts                         |  26 +-
 pi/ui/src/router/index.ts                 |  14 +-
 pi/ui/src/views/HomeView.vue              |   2 +-
 pi/ui/vite.config.ts                      |  22 +-
 15 files changed, 993 insertions(+), 572 deletions(-)
 create mode 100644 pi/server/eslint.config.js

diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 187ed35..087b8f0 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -8,7 +8,7 @@ on:
     - cron: "0 4 * * 4"
 
 jobs:
-  build:
+  build-lawndon:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
@@ -40,3 +40,51 @@ jobs:
           verbose: false
           cli-compile-flags: |
             - --export-binaries
+
+  package-lawndon-pi:
+    runs-on: ubuntu-latest
+    env:
+      WORKING_DIR: ./pi
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Install pnpm
+        uses: pnpm/action-setup@v4
+        with:
+          version: 9
+
+      - name: Setup Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: '22'
+
+      - name: Install dependencies for UI
+        working-directory: ${{ env.WORKING_DIR }}
+        run: |
+          pnpm install:ui
+
+      - name: Build UI
+        working-directory: ${{ env.WORKING_DIR }}
+        run: |
+          pnpm build:ui
+
+      - name: Install dependencies for Server
+        working-directory: ${{ env.WORKING_DIR }}
+        run: |
+          pnpm install:server
+
+      - name: Build Server
+        working-directory: ${{ env.WORKING_DIR }}
+        run: |
+          pnpm build:server
+
+      - name: Run package script
+        working-directory: ${{ env.WORKING_DIR }}
+        run: |
+          ./package.sh
+
+      - name: Verify package contents
+        working-directory: ${{ env.WORKING_DIR }}
+        run: |
+          tar -df lawndon-pi.tar.gz lawndon-pi
diff --git a/pi/package.json b/pi/package.json
index 455b171..cd43688 100644
--- a/pi/package.json
+++ b/pi/package.json
@@ -14,7 +14,6 @@
   },
   "packageManager": "pnpm@9.14.2",
   "devDependencies": {
-    "concurrently": "^9.1.0",
-    "ts-node-dev": "^2.0.0"
+    "concurrently": "^9.1.0"
   }
 }
diff --git a/pi/package.sh b/pi/package.sh
index 721a66b..0f33b8b 100755
--- a/pi/package.sh
+++ b/pi/package.sh
@@ -32,8 +32,6 @@ cp package.json $PROJECT_NAME/
 echo "Creating the tar.gz archive"
 tar -czf $TARGET_ARCHIVE $PROJECT_NAME
 
-# rm -rf $PROJECT_NAME
-
 echo "Build and transfer complete"
 echo "To run the app on the Raspberry Pi:"
 echo "1. SSH into your Raspberry Pi: ssh $PI_USER@$PI_HOST"
diff --git a/pi/pnpm-lock.yaml b/pi/pnpm-lock.yaml
index fad7d35..ca37c77 100644
--- a/pi/pnpm-lock.yaml
+++ b/pi/pnpm-lock.yaml
@@ -11,56 +11,9 @@ importers:
       concurrently:
         specifier: ^9.1.0
         version: 9.1.0
-      ts-node-dev:
-        specifier: ^2.0.0
-        version: 2.0.0(@types/node@22.9.1)(typescript@5.6.3)
 
 packages:
 
-  '@cspotcode/source-map-support@0.8.1':
-    resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
-    engines: {node: '>=12'}
-
-  '@jridgewell/resolve-uri@3.1.2':
-    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
-    engines: {node: '>=6.0.0'}
-
-  '@jridgewell/sourcemap-codec@1.5.0':
-    resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
-
-  '@jridgewell/trace-mapping@0.3.9':
-    resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
-
-  '@tsconfig/node10@1.0.11':
-    resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
-
-  '@tsconfig/node12@1.0.11':
-    resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
-
-  '@tsconfig/node14@1.0.3':
-    resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
-
-  '@tsconfig/node16@1.0.4':
-    resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
-
-  '@types/node@22.9.1':
-    resolution: {integrity: sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==}
-
-  '@types/strip-bom@3.0.0':
-    resolution: {integrity: sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==}
-
-  '@types/strip-json-comments@0.0.30':
-    resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==}
-
-  acorn-walk@8.3.4:
-    resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
-    engines: {node: '>=0.4.0'}
-
-  acorn@8.14.0:
-    resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
-    engines: {node: '>=0.4.0'}
-    hasBin: true
-
   ansi-regex@5.0.1:
     resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
     engines: {node: '>=8'}
@@ -69,38 +22,10 @@ packages:
     resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
     engines: {node: '>=8'}
 
-  anymatch@3.1.3:
-    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
-    engines: {node: '>= 8'}
-
-  arg@4.1.3:
-    resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
-
-  balanced-match@1.0.2:
-    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
-
-  binary-extensions@2.3.0:
-    resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
-    engines: {node: '>=8'}
-
-  brace-expansion@1.1.11:
-    resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
-
-  braces@3.0.3:
-    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
-    engines: {node: '>=8'}
-
-  buffer-from@1.1.2:
-    resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
-
   chalk@4.1.2:
     resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
     engines: {node: '>=10'}
 
-  chokidar@3.6.0:
-    resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
-    engines: {node: '>= 8.10.0'}
-
   cliui@8.0.1:
     resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
     engines: {node: '>=12'}
@@ -112,24 +37,11 @@ packages:
   color-name@1.1.4:
     resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
 
-  concat-map@0.0.1:
-    resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
-
   concurrently@9.1.0:
     resolution: {integrity: sha512-VxkzwMAn4LP7WyMnJNbHN5mKV9L2IbyDjpzemKr99sXNR3GqRNMMHdm7prV1ws9wg7ETj6WUkNOigZVsptwbgg==}
     engines: {node: '>=18'}
     hasBin: true
 
-  create-require@1.1.1:
-    resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
-
-  diff@4.0.2:
-    resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
-    engines: {node: '>=0.3.1'}
-
-  dynamic-dedupe@0.3.0:
-    resolution: {integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==}
-
   emoji-regex@8.0.0:
     resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
 
@@ -137,137 +49,31 @@ packages:
     resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
     engines: {node: '>=6'}
 
-  fill-range@7.1.1:
-    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
-    engines: {node: '>=8'}
-
-  fs.realpath@1.0.0:
-    resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
-
-  fsevents@2.3.3:
-    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
-    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
-    os: [darwin]
-
-  function-bind@1.1.2:
-    resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
-
   get-caller-file@2.0.5:
     resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
     engines: {node: 6.* || 8.* || >= 10.*}
 
-  glob-parent@5.1.2:
-    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
-    engines: {node: '>= 6'}
-
-  glob@7.2.3:
-    resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
-    deprecated: Glob versions prior to v9 are no longer supported
-
   has-flag@4.0.0:
     resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
     engines: {node: '>=8'}
 
-  hasown@2.0.2:
-    resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
-    engines: {node: '>= 0.4'}
-
-  inflight@1.0.6:
-    resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
-    deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
-
-  inherits@2.0.4:
-    resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
-
-  is-binary-path@2.1.0:
-    resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
-    engines: {node: '>=8'}
-
-  is-core-module@2.15.1:
-    resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
-    engines: {node: '>= 0.4'}
-
-  is-extglob@2.1.1:
-    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
-    engines: {node: '>=0.10.0'}
-
   is-fullwidth-code-point@3.0.0:
     resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
     engines: {node: '>=8'}
 
-  is-glob@4.0.3:
-    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
-    engines: {node: '>=0.10.0'}
-
-  is-number@7.0.0:
-    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
-    engines: {node: '>=0.12.0'}
-
   lodash@4.17.21:
     resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
 
-  make-error@1.3.6:
-    resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
-
-  minimatch@3.1.2:
-    resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
-
-  minimist@1.2.8:
-    resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
-
-  mkdirp@1.0.4:
-    resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
-    engines: {node: '>=10'}
-    hasBin: true
-
-  normalize-path@3.0.0:
-    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
-    engines: {node: '>=0.10.0'}
-
-  once@1.4.0:
-    resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
-
-  path-is-absolute@1.0.1:
-    resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
-    engines: {node: '>=0.10.0'}
-
-  path-parse@1.0.7:
-    resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
-
-  picomatch@2.3.1:
-    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
-    engines: {node: '>=8.6'}
-
-  readdirp@3.6.0:
-    resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
-    engines: {node: '>=8.10.0'}
-
   require-directory@2.1.1:
     resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
     engines: {node: '>=0.10.0'}
 
-  resolve@1.22.8:
-    resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
-    hasBin: true
-
-  rimraf@2.7.1:
-    resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
-    deprecated: Rimraf versions prior to v4 are no longer supported
-    hasBin: true
-
   rxjs@7.8.1:
     resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
 
   shell-quote@1.8.1:
     resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
 
-  source-map-support@0.5.21:
-    resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
-
-  source-map@0.6.1:
-    resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
-    engines: {node: '>=0.10.0'}
-
   string-width@4.2.3:
     resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
     engines: {node: '>=8'}
@@ -276,14 +82,6 @@ packages:
     resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
     engines: {node: '>=8'}
 
-  strip-bom@3.0.0:
-    resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
-    engines: {node: '>=4'}
-
-  strip-json-comments@2.0.1:
-    resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
-    engines: {node: '>=0.10.0'}
-
   supports-color@7.2.0:
     resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
     engines: {node: '>=8'}
@@ -292,71 +90,17 @@ packages:
     resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
     engines: {node: '>=10'}
 
-  supports-preserve-symlinks-flag@1.0.0:
-    resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
-    engines: {node: '>= 0.4'}
-
-  to-regex-range@5.0.1:
-    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
-    engines: {node: '>=8.0'}
-
   tree-kill@1.2.2:
     resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
     hasBin: true
 
-  ts-node-dev@2.0.0:
-    resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==}
-    engines: {node: '>=0.8.0'}
-    hasBin: true
-    peerDependencies:
-      node-notifier: '*'
-      typescript: '*'
-    peerDependenciesMeta:
-      node-notifier:
-        optional: true
-
-  ts-node@10.9.2:
-    resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
-    hasBin: true
-    peerDependencies:
-      '@swc/core': '>=1.2.50'
-      '@swc/wasm': '>=1.2.50'
-      '@types/node': '*'
-      typescript: '>=2.7'
-    peerDependenciesMeta:
-      '@swc/core':
-        optional: true
-      '@swc/wasm':
-        optional: true
-
-  tsconfig@7.0.0:
-    resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==}
-
   tslib@2.8.1:
     resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
 
-  typescript@5.6.3:
-    resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
-    engines: {node: '>=14.17'}
-    hasBin: true
-
-  undici-types@6.19.8:
-    resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
-
-  v8-compile-cache-lib@3.0.1:
-    resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
-
   wrap-ansi@7.0.0:
     resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
     engines: {node: '>=10'}
 
-  wrappy@1.0.2:
-    resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
-
-  xtend@4.0.2:
-    resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
-    engines: {node: '>=0.4'}
-
   y18n@5.0.8:
     resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
     engines: {node: '>=10'}
@@ -369,92 +113,19 @@ packages:
     resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
     engines: {node: '>=12'}
 
-  yn@3.1.1:
-    resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
-    engines: {node: '>=6'}
-
 snapshots:
 
-  '@cspotcode/source-map-support@0.8.1':
-    dependencies:
-      '@jridgewell/trace-mapping': 0.3.9
-
-  '@jridgewell/resolve-uri@3.1.2': {}
-
-  '@jridgewell/sourcemap-codec@1.5.0': {}
-
-  '@jridgewell/trace-mapping@0.3.9':
-    dependencies:
-      '@jridgewell/resolve-uri': 3.1.2
-      '@jridgewell/sourcemap-codec': 1.5.0
-
-  '@tsconfig/node10@1.0.11': {}
-
-  '@tsconfig/node12@1.0.11': {}
-
-  '@tsconfig/node14@1.0.3': {}
-
-  '@tsconfig/node16@1.0.4': {}
-
-  '@types/node@22.9.1':
-    dependencies:
-      undici-types: 6.19.8
-
-  '@types/strip-bom@3.0.0': {}
-
-  '@types/strip-json-comments@0.0.30': {}
-
-  acorn-walk@8.3.4:
-    dependencies:
-      acorn: 8.14.0
-
-  acorn@8.14.0: {}
-
   ansi-regex@5.0.1: {}
 
   ansi-styles@4.3.0:
     dependencies:
       color-convert: 2.0.1
 
-  anymatch@3.1.3:
-    dependencies:
-      normalize-path: 3.0.0
-      picomatch: 2.3.1
-
-  arg@4.1.3: {}
-
-  balanced-match@1.0.2: {}
-
-  binary-extensions@2.3.0: {}
-
-  brace-expansion@1.1.11:
-    dependencies:
-      balanced-match: 1.0.2
-      concat-map: 0.0.1
-
-  braces@3.0.3:
-    dependencies:
-      fill-range: 7.1.1
-
-  buffer-from@1.1.2: {}
-
   chalk@4.1.2:
     dependencies:
       ansi-styles: 4.3.0
       supports-color: 7.2.0
 
-  chokidar@3.6.0:
-    dependencies:
-      anymatch: 3.1.3
-      braces: 3.0.3
-      glob-parent: 5.1.2
-      is-binary-path: 2.1.0
-      is-glob: 4.0.3
-      normalize-path: 3.0.0
-      readdirp: 3.6.0
-    optionalDependencies:
-      fsevents: 2.3.3
-
   cliui@8.0.1:
     dependencies:
       string-width: 4.2.3
@@ -467,8 +138,6 @@ snapshots:
 
   color-name@1.1.4: {}
 
-  concat-map@0.0.1: {}
-
   concurrently@9.1.0:
     dependencies:
       chalk: 4.1.2
@@ -479,128 +148,26 @@ snapshots:
       tree-kill: 1.2.2
       yargs: 17.7.2
 
-  create-require@1.1.1: {}
-
-  diff@4.0.2: {}
-
-  dynamic-dedupe@0.3.0:
-    dependencies:
-      xtend: 4.0.2
-
   emoji-regex@8.0.0: {}
 
   escalade@3.2.0: {}
 
-  fill-range@7.1.1:
-    dependencies:
-      to-regex-range: 5.0.1
-
-  fs.realpath@1.0.0: {}
-
-  fsevents@2.3.3:
-    optional: true
-
-  function-bind@1.1.2: {}
-
   get-caller-file@2.0.5: {}
 
-  glob-parent@5.1.2:
-    dependencies:
-      is-glob: 4.0.3
-
-  glob@7.2.3:
-    dependencies:
-      fs.realpath: 1.0.0
-      inflight: 1.0.6
-      inherits: 2.0.4
-      minimatch: 3.1.2
-      once: 1.4.0
-      path-is-absolute: 1.0.1
-
   has-flag@4.0.0: {}
 
-  hasown@2.0.2:
-    dependencies:
-      function-bind: 1.1.2
-
-  inflight@1.0.6:
-    dependencies:
-      once: 1.4.0
-      wrappy: 1.0.2
-
-  inherits@2.0.4: {}
-
-  is-binary-path@2.1.0:
-    dependencies:
-      binary-extensions: 2.3.0
-
-  is-core-module@2.15.1:
-    dependencies:
-      hasown: 2.0.2
-
-  is-extglob@2.1.1: {}
-
   is-fullwidth-code-point@3.0.0: {}
 
-  is-glob@4.0.3:
-    dependencies:
-      is-extglob: 2.1.1
-
-  is-number@7.0.0: {}
-
   lodash@4.17.21: {}
 
-  make-error@1.3.6: {}
-
-  minimatch@3.1.2:
-    dependencies:
-      brace-expansion: 1.1.11
-
-  minimist@1.2.8: {}
-
-  mkdirp@1.0.4: {}
-
-  normalize-path@3.0.0: {}
-
-  once@1.4.0:
-    dependencies:
-      wrappy: 1.0.2
-
-  path-is-absolute@1.0.1: {}
-
-  path-parse@1.0.7: {}
-
-  picomatch@2.3.1: {}
-
-  readdirp@3.6.0:
-    dependencies:
-      picomatch: 2.3.1
-
   require-directory@2.1.1: {}
 
-  resolve@1.22.8:
-    dependencies:
-      is-core-module: 2.15.1
-      path-parse: 1.0.7
-      supports-preserve-symlinks-flag: 1.0.0
-
-  rimraf@2.7.1:
-    dependencies:
-      glob: 7.2.3
-
   rxjs@7.8.1:
     dependencies:
       tslib: 2.8.1
 
   shell-quote@1.8.1: {}
 
-  source-map-support@0.5.21:
-    dependencies:
-      buffer-from: 1.1.2
-      source-map: 0.6.1
-
-  source-map@0.6.1: {}
-
   string-width@4.2.3:
     dependencies:
       emoji-regex: 8.0.0
@@ -611,10 +178,6 @@ snapshots:
     dependencies:
       ansi-regex: 5.0.1
 
-  strip-bom@3.0.0: {}
-
-  strip-json-comments@2.0.1: {}
-
   supports-color@7.2.0:
     dependencies:
       has-flag: 4.0.0
@@ -623,75 +186,16 @@ snapshots:
     dependencies:
       has-flag: 4.0.0
 
-  supports-preserve-symlinks-flag@1.0.0: {}
-
-  to-regex-range@5.0.1:
-    dependencies:
-      is-number: 7.0.0
-
   tree-kill@1.2.2: {}
 
-  ts-node-dev@2.0.0(@types/node@22.9.1)(typescript@5.6.3):
-    dependencies:
-      chokidar: 3.6.0
-      dynamic-dedupe: 0.3.0
-      minimist: 1.2.8
-      mkdirp: 1.0.4
-      resolve: 1.22.8
-      rimraf: 2.7.1
-      source-map-support: 0.5.21
-      tree-kill: 1.2.2
-      ts-node: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
-      tsconfig: 7.0.0
-      typescript: 5.6.3
-    transitivePeerDependencies:
-      - '@swc/core'
-      - '@swc/wasm'
-      - '@types/node'
-
-  ts-node@10.9.2(@types/node@22.9.1)(typescript@5.6.3):
-    dependencies:
-      '@cspotcode/source-map-support': 0.8.1
-      '@tsconfig/node10': 1.0.11
-      '@tsconfig/node12': 1.0.11
-      '@tsconfig/node14': 1.0.3
-      '@tsconfig/node16': 1.0.4
-      '@types/node': 22.9.1
-      acorn: 8.14.0
-      acorn-walk: 8.3.4
-      arg: 4.1.3
-      create-require: 1.1.1
-      diff: 4.0.2
-      make-error: 1.3.6
-      typescript: 5.6.3
-      v8-compile-cache-lib: 3.0.1
-      yn: 3.1.1
-
-  tsconfig@7.0.0:
-    dependencies:
-      '@types/strip-bom': 3.0.0
-      '@types/strip-json-comments': 0.0.30
-      strip-bom: 3.0.0
-      strip-json-comments: 2.0.1
-
   tslib@2.8.1: {}
 
-  typescript@5.6.3: {}
-
-  undici-types@6.19.8: {}
-
-  v8-compile-cache-lib@3.0.1: {}
-
   wrap-ansi@7.0.0:
     dependencies:
       ansi-styles: 4.3.0
       string-width: 4.2.3
       strip-ansi: 6.0.1
 
-  wrappy@1.0.2: {}
-
-  xtend@4.0.2: {}
-
   y18n@5.0.8: {}
 
   yargs-parser@21.1.1: {}
@@ -705,5 +209,3 @@ snapshots:
       string-width: 4.2.3
       y18n: 5.0.8
       yargs-parser: 21.1.1
-
-  yn@3.1.1: {}
diff --git a/pi/server/eslint.config.js b/pi/server/eslint.config.js
new file mode 100644
index 0000000..8d54878
--- /dev/null
+++ b/pi/server/eslint.config.js
@@ -0,0 +1,138 @@
+import globals from 'globals';
+
+export default [
+  { languageOptions: { globals: globals.node } },
+  { ignores: ['dist/*', 'node_modules/*'] },
+  {
+    'rules':       {
+      'dot-notation':                   'off',
+      'guard-for-in':                   'off',
+      'new-cap':                        'off',
+      'no-empty':                       'off',
+      'no-extra-boolean-cast':          'off',
+      'no-new':                         'off',
+      'no-plusplus':                    'off',
+      'no-useless-escape':              'off',
+      'strict':                         'off',
+
+      'array-bracket-spacing':             'warn',
+      'arrow-parens':                      'warn',
+      'arrow-spacing':                     ['warn', {
+        'before': true,
+        'after':  true
+      }],
+      'block-spacing':                     ['warn', 'always'],
+      'brace-style':                       ['warn', '1tbs'],
+      'comma-dangle':                      ['warn', 'only-multiline'],
+      'comma-spacing':                     'warn',
+      'curly':                             'warn',
+      'eqeqeq':                            'warn',
+      'func-call-spacing':                 ['warn', 'never'],
+      'implicit-arrow-linebreak':          'warn',
+      'indent':                            ['warn', 2],
+      'keyword-spacing':                   'warn',
+      'lines-between-class-members':       ['warn', 'always', { 'exceptAfterSingleLine': true }],
+      'multiline-ternary':                 ['warn', 'never'],
+      'newline-per-chained-call':          ['warn', { 'ignoreChainWithDepth': 4 }],
+      'no-caller':                         'warn',
+      'no-cond-assign':                    ['warn', 'except-parens'],
+      'no-console':                        'warn',
+      'no-debugger':                       'warn',
+      'no-eq-null':                        'warn',
+      'no-eval':                           'warn',
+      'no-trailing-spaces':                'warn',
+      'no-undef':                          'warn',
+      'no-unused-vars':                    'warn',
+      'no-whitespace-before-property':     'warn',
+      'object-curly-spacing':              ['warn', 'always'],
+      'object-property-newline':           'warn',
+      'object-shorthand':                  'warn',
+      'padded-blocks':                     ['warn', 'never'],
+      'prefer-arrow-callback':             'warn',
+      'prefer-template':                   'warn',
+      'rest-spread-spacing':               'warn',
+      'semi':                              ['warn', 'always'],
+      'space-before-function-paren':       ['warn', 'never'],
+      'space-infix-ops':                   'warn',
+      'spaced-comment':                    'warn',
+      'switch-colon-spacing':              'warn',
+      'template-curly-spacing':            ['warn', 'always'],
+      'yield-star-spacing':                ['warn', 'both'],
+
+      'key-spacing':              ['warn', {
+        'align': {
+          'beforeColon': false,
+          'afterColon':  true,
+          'on':          'value',
+          'mode':        'minimum'
+        },
+        'multiLine': {
+          'beforeColon': false,
+          'afterColon':  true
+        },
+      }],
+
+      'object-curly-newline':          ['warn', {
+        'ObjectExpression':  {
+          'multiline':     true,
+          'minProperties': 3
+        },
+        'ObjectPattern':     {
+          'multiline':     true,
+          'minProperties': 4
+        },
+        'ImportDeclaration': {
+          'multiline':     true,
+          'minProperties': 5
+        },
+        'ExportDeclaration': {
+          'multiline':     true,
+          'minProperties': 3
+        }
+      }],
+
+      'padding-line-between-statements': [
+        'warn',
+        {
+          'blankLine': 'always',
+          'prev':      '*',
+          'next':      'return',
+        },
+        {
+          'blankLine': 'always',
+          'prev':      'function',
+          'next':      'function',
+        },
+        // This configuration would require blank lines after every sequence of variable declarations
+        {
+          'blankLine': 'always',
+          'prev':      ['const', 'let', 'var'],
+          'next':      '*'
+        },
+        {
+          'blankLine': 'any',
+          'prev':      ['const', 'let', 'var'],
+          'next':      ['const', 'let', 'var']
+        }
+      ],
+
+      'quotes': [
+        'warn',
+        'single',
+        {
+          'avoidEscape':           true,
+          'allowTemplateLiterals': true
+        },
+      ],
+
+      'space-unary-ops': [
+        'warn',
+        {
+          'words':    true,
+          'nonwords': false,
+        }
+      ]
+    }
+  }
+];
+
diff --git a/pi/server/package.json b/pi/server/package.json
index 2cbbbda..7e3f98a 100644
--- a/pi/server/package.json
+++ b/pi/server/package.json
@@ -7,7 +7,9 @@
   "scripts": {
     "start": "node dist/server.js",
     "build": "tsc --outDir dist",
-    "dev": "nodemon --watch src --ext ts --exec 'node --loader ts-node/esm ./src/server.ts'"
+    "dev": "nodemon --watch src --ext ts --exec 'node --loader ts-node/esm ./src/server.ts'",
+    "lint": "npx eslint .",
+    "format": "npx eslint . --fix"
   },
   "keywords": [],
   "author": "",
@@ -23,10 +25,12 @@
     "@types/cors": "^2.8.17",
     "@types/express": "^5.0.0",
     "@types/node": "^22.9.1",
+    "eslint": "^9.15.0",
+    "globals": "^15.12.0",
     "http-proxy-middleware": "^3.0.3",
     "nodemon": "^3.1.7",
     "ts-node": "^10.9.2",
     "ts-node-dev": "^2.0.0",
-    "typescript": "^5.6.3"
+    "typescript": "^5.7.2"
   }
 }
diff --git a/pi/server/pnpm-lock.yaml b/pi/server/pnpm-lock.yaml
index ad21140..145967a 100644
--- a/pi/server/pnpm-lock.yaml
+++ b/pi/server/pnpm-lock.yaml
@@ -30,6 +30,12 @@ importers:
       '@types/node':
         specifier: ^22.9.1
         version: 22.9.1
+      eslint:
+        specifier: ^9.15.0
+        version: 9.15.0
+      globals:
+        specifier: ^15.12.0
+        version: 15.12.0
       http-proxy-middleware:
         specifier: ^3.0.3
         version: 3.0.3
@@ -38,13 +44,13 @@ importers:
         version: 3.1.7
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
+        version: 10.9.2(@types/node@22.9.1)(typescript@5.7.2)
       ts-node-dev:
         specifier: ^2.0.0
-        version: 2.0.0(@types/node@22.9.1)(typescript@5.6.3)
+        version: 2.0.0(@types/node@22.9.1)(typescript@5.7.2)
       typescript:
-        specifier: ^5.6.3
-        version: 5.6.3
+        specifier: ^5.7.2
+        version: 5.7.2
 
 packages:
 
@@ -56,6 +62,60 @@ packages:
     resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
     engines: {node: '>=12'}
 
+  '@eslint-community/eslint-utils@4.4.1':
+    resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+  '@eslint-community/regexpp@4.12.1':
+    resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
+    engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+  '@eslint/config-array@0.19.0':
+    resolution: {integrity: sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/core@0.9.0':
+    resolution: {integrity: sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/eslintrc@3.2.0':
+    resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/js@9.15.0':
+    resolution: {integrity: sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/object-schema@2.1.4':
+    resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@eslint/plugin-kit@0.2.3':
+    resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  '@humanfs/core@0.19.1':
+    resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+    engines: {node: '>=18.18.0'}
+
+  '@humanfs/node@0.16.6':
+    resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
+    engines: {node: '>=18.18.0'}
+
+  '@humanwhocodes/module-importer@1.0.1':
+    resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+    engines: {node: '>=12.22'}
+
+  '@humanwhocodes/retry@0.3.1':
+    resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
+    engines: {node: '>=18.18'}
+
+  '@humanwhocodes/retry@0.4.1':
+    resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==}
+    engines: {node: '>=18.18'}
+
   '@jridgewell/resolve-uri@3.1.2':
     resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
     engines: {node: '>=6.0.0'}
@@ -93,6 +153,9 @@ packages:
   '@types/cors@2.8.17':
     resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
 
+  '@types/estree@1.0.6':
+    resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
+
   '@types/express-serve-static-core@5.0.1':
     resolution: {integrity: sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==}
 
@@ -105,6 +168,9 @@ packages:
   '@types/http-proxy@1.17.15':
     resolution: {integrity: sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==}
 
+  '@types/json-schema@7.0.15':
+    resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
   '@types/mime@1.3.5':
     resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
 
@@ -133,6 +199,11 @@ packages:
     resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
     engines: {node: '>= 0.6'}
 
+  acorn-jsx@5.3.2:
+    resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+    peerDependencies:
+      acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
   acorn-walk@8.3.4:
     resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
     engines: {node: '>=0.4.0'}
@@ -142,6 +213,13 @@ packages:
     engines: {node: '>=0.4.0'}
     hasBin: true
 
+  ajv@6.12.6:
+    resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+  ansi-styles@4.3.0:
+    resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+    engines: {node: '>=8'}
+
   anymatch@3.1.3:
     resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
     engines: {node: '>= 8'}
@@ -149,6 +227,9 @@ packages:
   arg@4.1.3:
     resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
 
+  argparse@2.0.1:
+    resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
   array-flatten@1.1.1:
     resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
 
@@ -185,10 +266,25 @@ packages:
     resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
     engines: {node: '>= 0.4'}
 
+  callsites@3.1.0:
+    resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+    engines: {node: '>=6'}
+
+  chalk@4.1.2:
+    resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+    engines: {node: '>=10'}
+
   chokidar@3.6.0:
     resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
     engines: {node: '>= 8.10.0'}
 
+  color-convert@2.0.1:
+    resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+    engines: {node: '>=7.0.0'}
+
+  color-name@1.1.4:
+    resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
   complex.js@2.4.2:
     resolution: {integrity: sha512-qtx7HRhPGSCBtGiST4/WGHuW+zeaND/6Ld+db6PbrulIB1i2Ev/2UPiqcmpQNPSyfBKraC0EOvOKCB5dGZKt3g==}
 
@@ -221,6 +317,10 @@ packages:
   create-require@1.1.1:
     resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
 
+  cross-spawn@7.0.6:
+    resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+    engines: {node: '>= 8'}
+
   debug@2.6.9:
     resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
     peerDependencies:
@@ -241,6 +341,9 @@ packages:
   decimal.js@10.4.3:
     resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
 
+  deep-is@0.1.4:
+    resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
   define-data-property@1.1.4:
     resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
     engines: {node: '>= 0.4'}
@@ -293,6 +396,52 @@ packages:
   escape-latex@1.2.0:
     resolution: {integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==}
 
+  escape-string-regexp@4.0.0:
+    resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+    engines: {node: '>=10'}
+
+  eslint-scope@8.2.0:
+    resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  eslint-visitor-keys@3.4.3:
+    resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+  eslint-visitor-keys@4.2.0:
+    resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  eslint@9.15.0:
+    resolution: {integrity: sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    hasBin: true
+    peerDependencies:
+      jiti: '*'
+    peerDependenciesMeta:
+      jiti:
+        optional: true
+
+  espree@10.3.0:
+    resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+  esquery@1.6.0:
+    resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
+    engines: {node: '>=0.10'}
+
+  esrecurse@4.3.0:
+    resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+    engines: {node: '>=4.0'}
+
+  estraverse@5.3.0:
+    resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+    engines: {node: '>=4.0'}
+
+  esutils@2.0.3:
+    resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+    engines: {node: '>=0.10.0'}
+
   etag@1.8.1:
     resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
     engines: {node: '>= 0.6'}
@@ -304,6 +453,19 @@ packages:
     resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==}
     engines: {node: '>= 0.10.0'}
 
+  fast-deep-equal@3.1.3:
+    resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+  fast-json-stable-stringify@2.1.0:
+    resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+  fast-levenshtein@2.0.6:
+    resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+  file-entry-cache@8.0.0:
+    resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+    engines: {node: '>=16.0.0'}
+
   fill-range@7.1.1:
     resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
     engines: {node: '>=8'}
@@ -312,6 +474,17 @@ packages:
     resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
     engines: {node: '>= 0.8'}
 
+  find-up@5.0.0:
+    resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+    engines: {node: '>=10'}
+
+  flat-cache@4.0.1:
+    resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+    engines: {node: '>=16'}
+
+  flatted@3.3.2:
+    resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==}
+
   follow-redirects@1.15.9:
     resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
     engines: {node: '>=4.0'}
@@ -352,10 +525,22 @@ packages:
     resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
     engines: {node: '>= 6'}
 
+  glob-parent@6.0.2:
+    resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+    engines: {node: '>=10.13.0'}
+
   glob@7.2.3:
     resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
     deprecated: Glob versions prior to v9 are no longer supported
 
+  globals@14.0.0:
+    resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+    engines: {node: '>=18'}
+
+  globals@15.12.0:
+    resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==}
+    engines: {node: '>=18'}
+
   gopd@1.0.1:
     resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
 
@@ -363,6 +548,10 @@ packages:
     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
     engines: {node: '>=4'}
 
+  has-flag@4.0.0:
+    resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+    engines: {node: '>=8'}
+
   has-property-descriptors@1.0.2:
     resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
 
@@ -397,6 +586,18 @@ packages:
   ignore-by-default@1.0.1:
     resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
 
+  ignore@5.3.2:
+    resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+    engines: {node: '>= 4'}
+
+  import-fresh@3.3.0:
+    resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
+    engines: {node: '>=6'}
+
+  imurmurhash@0.1.4:
+    resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+    engines: {node: '>=0.8.19'}
+
   inflight@1.0.6:
     resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
     deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
@@ -432,9 +633,39 @@ packages:
     resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
     engines: {node: '>=0.10.0'}
 
+  isexe@2.0.0:
+    resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
   javascript-natural-sort@0.7.1:
     resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
 
+  js-yaml@4.1.0:
+    resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+    hasBin: true
+
+  json-buffer@3.0.1:
+    resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+  json-schema-traverse@0.4.1:
+    resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+  json-stable-stringify-without-jsonify@1.0.1:
+    resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+  keyv@4.5.4:
+    resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+  levn@0.4.1:
+    resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+    engines: {node: '>= 0.8.0'}
+
+  locate-path@6.0.0:
+    resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+    engines: {node: '>=10'}
+
+  lodash.merge@4.6.2:
+    resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
   make-error@1.3.6:
     resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
 
@@ -488,6 +719,9 @@ packages:
   ms@2.1.3:
     resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
 
+  natural-compare@1.4.0:
+    resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
   negotiator@0.6.3:
     resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
     engines: {node: '>= 0.6'}
@@ -516,14 +750,38 @@ packages:
   once@1.4.0:
     resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
 
+  optionator@0.9.4:
+    resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+    engines: {node: '>= 0.8.0'}
+
+  p-limit@3.1.0:
+    resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+    engines: {node: '>=10'}
+
+  p-locate@5.0.0:
+    resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+    engines: {node: '>=10'}
+
+  parent-module@1.0.1:
+    resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+    engines: {node: '>=6'}
+
   parseurl@1.3.3:
     resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
     engines: {node: '>= 0.8'}
 
+  path-exists@4.0.0:
+    resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+    engines: {node: '>=8'}
+
   path-is-absolute@1.0.1:
     resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
     engines: {node: '>=0.10.0'}
 
+  path-key@3.1.1:
+    resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+    engines: {node: '>=8'}
+
   path-parse@1.0.7:
     resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
 
@@ -534,6 +792,10 @@ packages:
     resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
     engines: {node: '>=8.6'}
 
+  prelude-ls@1.2.1:
+    resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+    engines: {node: '>= 0.8.0'}
+
   proxy-addr@2.0.7:
     resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
     engines: {node: '>= 0.10'}
@@ -541,6 +803,10 @@ packages:
   pstree.remy@1.1.8:
     resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
 
+  punycode@2.3.1:
+    resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+    engines: {node: '>=6'}
+
   qs@6.13.0:
     resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
     engines: {node: '>=0.6'}
@@ -563,6 +829,10 @@ packages:
   requires-port@1.0.0:
     resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
 
+  resolve-from@4.0.0:
+    resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+    engines: {node: '>=4'}
+
   resolve@1.22.8:
     resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
     hasBin: true
@@ -601,6 +871,14 @@ packages:
   setprototypeof@1.2.0:
     resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
 
+  shebang-command@2.0.0:
+    resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+    engines: {node: '>=8'}
+
+  shebang-regex@3.0.0:
+    resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+    engines: {node: '>=8'}
+
   side-channel@1.0.6:
     resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
     engines: {node: '>= 0.4'}
@@ -639,10 +917,18 @@ packages:
     resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
     engines: {node: '>=0.10.0'}
 
+  strip-json-comments@3.1.1:
+    resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+    engines: {node: '>=8'}
+
   supports-color@5.5.0:
     resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
     engines: {node: '>=4'}
 
+  supports-color@7.2.0:
+    resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+    engines: {node: '>=8'}
+
   supports-preserve-symlinks-flag@1.0.0:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
     engines: {node: '>= 0.4'}
@@ -694,6 +980,10 @@ packages:
   tsconfig@7.0.0:
     resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==}
 
+  type-check@0.4.0:
+    resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+    engines: {node: '>= 0.8.0'}
+
   type-is@1.6.18:
     resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
     engines: {node: '>= 0.6'}
@@ -702,8 +992,8 @@ packages:
     resolution: {integrity: sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA==}
     engines: {node: '>= 18'}
 
-  typescript@5.6.3:
-    resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==}
+  typescript@5.7.2:
+    resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==}
     engines: {node: '>=14.17'}
     hasBin: true
 
@@ -717,6 +1007,9 @@ packages:
     resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
     engines: {node: '>= 0.8'}
 
+  uri-js@4.4.1:
+    resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
   utils-merge@1.0.1:
     resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
     engines: {node: '>= 0.4.0'}
@@ -728,6 +1021,15 @@ packages:
     resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
     engines: {node: '>= 0.8'}
 
+  which@2.0.2:
+    resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+    engines: {node: '>= 8'}
+    hasBin: true
+
+  word-wrap@1.2.5:
+    resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+    engines: {node: '>=0.10.0'}
+
   wrappy@1.0.2:
     resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
 
@@ -751,6 +1053,10 @@ packages:
     resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
     engines: {node: '>=6'}
 
+  yocto-queue@0.1.0:
+    resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+    engines: {node: '>=10'}
+
 snapshots:
 
   '@babel/runtime@7.26.0':
@@ -761,6 +1067,58 @@ snapshots:
     dependencies:
       '@jridgewell/trace-mapping': 0.3.9
 
+  '@eslint-community/eslint-utils@4.4.1(eslint@9.15.0)':
+    dependencies:
+      eslint: 9.15.0
+      eslint-visitor-keys: 3.4.3
+
+  '@eslint-community/regexpp@4.12.1': {}
+
+  '@eslint/config-array@0.19.0':
+    dependencies:
+      '@eslint/object-schema': 2.1.4
+      debug: 4.3.7(supports-color@5.5.0)
+      minimatch: 3.1.2
+    transitivePeerDependencies:
+      - supports-color
+
+  '@eslint/core@0.9.0': {}
+
+  '@eslint/eslintrc@3.2.0':
+    dependencies:
+      ajv: 6.12.6
+      debug: 4.3.7(supports-color@5.5.0)
+      espree: 10.3.0
+      globals: 14.0.0
+      ignore: 5.3.2
+      import-fresh: 3.3.0
+      js-yaml: 4.1.0
+      minimatch: 3.1.2
+      strip-json-comments: 3.1.1
+    transitivePeerDependencies:
+      - supports-color
+
+  '@eslint/js@9.15.0': {}
+
+  '@eslint/object-schema@2.1.4': {}
+
+  '@eslint/plugin-kit@0.2.3':
+    dependencies:
+      levn: 0.4.1
+
+  '@humanfs/core@0.19.1': {}
+
+  '@humanfs/node@0.16.6':
+    dependencies:
+      '@humanfs/core': 0.19.1
+      '@humanwhocodes/retry': 0.3.1
+
+  '@humanwhocodes/module-importer@1.0.1': {}
+
+  '@humanwhocodes/retry@0.3.1': {}
+
+  '@humanwhocodes/retry@0.4.1': {}
+
   '@jridgewell/resolve-uri@3.1.2': {}
 
   '@jridgewell/sourcemap-codec@1.5.0': {}
@@ -795,6 +1153,8 @@ snapshots:
     dependencies:
       '@types/node': 22.9.1
 
+  '@types/estree@1.0.6': {}
+
   '@types/express-serve-static-core@5.0.1':
     dependencies:
       '@types/node': 22.9.1
@@ -815,6 +1175,8 @@ snapshots:
     dependencies:
       '@types/node': 22.9.1
 
+  '@types/json-schema@7.0.15': {}
+
   '@types/mime@1.3.5': {}
 
   '@types/node@22.9.1':
@@ -845,12 +1207,27 @@ snapshots:
       mime-types: 2.1.35
       negotiator: 0.6.3
 
+  acorn-jsx@5.3.2(acorn@8.14.0):
+    dependencies:
+      acorn: 8.14.0
+
   acorn-walk@8.3.4:
     dependencies:
       acorn: 8.14.0
 
   acorn@8.14.0: {}
 
+  ajv@6.12.6:
+    dependencies:
+      fast-deep-equal: 3.1.3
+      fast-json-stable-stringify: 2.1.0
+      json-schema-traverse: 0.4.1
+      uri-js: 4.4.1
+
+  ansi-styles@4.3.0:
+    dependencies:
+      color-convert: 2.0.1
+
   anymatch@3.1.3:
     dependencies:
       normalize-path: 3.0.0
@@ -858,6 +1235,8 @@ snapshots:
 
   arg@4.1.3: {}
 
+  argparse@2.0.1: {}
+
   array-flatten@1.1.1: {}
 
   balanced-match@1.0.2: {}
@@ -904,6 +1283,13 @@ snapshots:
       get-intrinsic: 1.2.4
       set-function-length: 1.2.2
 
+  callsites@3.1.0: {}
+
+  chalk@4.1.2:
+    dependencies:
+      ansi-styles: 4.3.0
+      supports-color: 7.2.0
+
   chokidar@3.6.0:
     dependencies:
       anymatch: 3.1.3
@@ -916,6 +1302,12 @@ snapshots:
     optionalDependencies:
       fsevents: 2.3.3
 
+  color-convert@2.0.1:
+    dependencies:
+      color-name: 1.1.4
+
+  color-name@1.1.4: {}
+
   complex.js@2.4.2: {}
 
   concat-map@0.0.1: {}
@@ -939,6 +1331,12 @@ snapshots:
 
   create-require@1.1.1: {}
 
+  cross-spawn@7.0.6:
+    dependencies:
+      path-key: 3.1.1
+      shebang-command: 2.0.0
+      which: 2.0.2
+
   debug@2.6.9:
     dependencies:
       ms: 2.0.0
@@ -951,6 +1349,8 @@ snapshots:
 
   decimal.js@10.4.3: {}
 
+  deep-is@0.1.4: {}
+
   define-data-property@1.1.4:
     dependencies:
       es-define-property: 1.0.0
@@ -1002,6 +1402,74 @@ snapshots:
 
   escape-latex@1.2.0: {}
 
+  escape-string-regexp@4.0.0: {}
+
+  eslint-scope@8.2.0:
+    dependencies:
+      esrecurse: 4.3.0
+      estraverse: 5.3.0
+
+  eslint-visitor-keys@3.4.3: {}
+
+  eslint-visitor-keys@4.2.0: {}
+
+  eslint@9.15.0:
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0)
+      '@eslint-community/regexpp': 4.12.1
+      '@eslint/config-array': 0.19.0
+      '@eslint/core': 0.9.0
+      '@eslint/eslintrc': 3.2.0
+      '@eslint/js': 9.15.0
+      '@eslint/plugin-kit': 0.2.3
+      '@humanfs/node': 0.16.6
+      '@humanwhocodes/module-importer': 1.0.1
+      '@humanwhocodes/retry': 0.4.1
+      '@types/estree': 1.0.6
+      '@types/json-schema': 7.0.15
+      ajv: 6.12.6
+      chalk: 4.1.2
+      cross-spawn: 7.0.6
+      debug: 4.3.7(supports-color@5.5.0)
+      escape-string-regexp: 4.0.0
+      eslint-scope: 8.2.0
+      eslint-visitor-keys: 4.2.0
+      espree: 10.3.0
+      esquery: 1.6.0
+      esutils: 2.0.3
+      fast-deep-equal: 3.1.3
+      file-entry-cache: 8.0.0
+      find-up: 5.0.0
+      glob-parent: 6.0.2
+      ignore: 5.3.2
+      imurmurhash: 0.1.4
+      is-glob: 4.0.3
+      json-stable-stringify-without-jsonify: 1.0.1
+      lodash.merge: 4.6.2
+      minimatch: 3.1.2
+      natural-compare: 1.4.0
+      optionator: 0.9.4
+    transitivePeerDependencies:
+      - supports-color
+
+  espree@10.3.0:
+    dependencies:
+      acorn: 8.14.0
+      acorn-jsx: 5.3.2(acorn@8.14.0)
+      eslint-visitor-keys: 4.2.0
+
+  esquery@1.6.0:
+    dependencies:
+      estraverse: 5.3.0
+
+  esrecurse@4.3.0:
+    dependencies:
+      estraverse: 5.3.0
+
+  estraverse@5.3.0: {}
+
+  esutils@2.0.3: {}
+
   etag@1.8.1: {}
 
   eventemitter3@4.0.7: {}
@@ -1042,6 +1510,16 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  fast-deep-equal@3.1.3: {}
+
+  fast-json-stable-stringify@2.1.0: {}
+
+  fast-levenshtein@2.0.6: {}
+
+  file-entry-cache@8.0.0:
+    dependencies:
+      flat-cache: 4.0.1
+
   fill-range@7.1.1:
     dependencies:
       to-regex-range: 5.0.1
@@ -1058,6 +1536,18 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  find-up@5.0.0:
+    dependencies:
+      locate-path: 6.0.0
+      path-exists: 4.0.0
+
+  flat-cache@4.0.1:
+    dependencies:
+      flatted: 3.3.2
+      keyv: 4.5.4
+
+  flatted@3.3.2: {}
+
   follow-redirects@1.15.9(debug@4.3.7):
     optionalDependencies:
       debug: 4.3.7(supports-color@5.5.0)
@@ -1087,6 +1577,10 @@ snapshots:
     dependencies:
       is-glob: 4.0.3
 
+  glob-parent@6.0.2:
+    dependencies:
+      is-glob: 4.0.3
+
   glob@7.2.3:
     dependencies:
       fs.realpath: 1.0.0
@@ -1096,12 +1590,18 @@ snapshots:
       once: 1.4.0
       path-is-absolute: 1.0.1
 
+  globals@14.0.0: {}
+
+  globals@15.12.0: {}
+
   gopd@1.0.1:
     dependencies:
       get-intrinsic: 1.2.4
 
   has-flag@3.0.0: {}
 
+  has-flag@4.0.0: {}
+
   has-property-descriptors@1.0.2:
     dependencies:
       es-define-property: 1.0.0
@@ -1147,6 +1647,15 @@ snapshots:
 
   ignore-by-default@1.0.1: {}
 
+  ignore@5.3.2: {}
+
+  import-fresh@3.3.0:
+    dependencies:
+      parent-module: 1.0.1
+      resolve-from: 4.0.0
+
+  imurmurhash@0.1.4: {}
+
   inflight@1.0.6:
     dependencies:
       once: 1.4.0
@@ -1174,8 +1683,35 @@ snapshots:
 
   is-plain-object@5.0.0: {}
 
+  isexe@2.0.0: {}
+
   javascript-natural-sort@0.7.1: {}
 
+  js-yaml@4.1.0:
+    dependencies:
+      argparse: 2.0.1
+
+  json-buffer@3.0.1: {}
+
+  json-schema-traverse@0.4.1: {}
+
+  json-stable-stringify-without-jsonify@1.0.1: {}
+
+  keyv@4.5.4:
+    dependencies:
+      json-buffer: 3.0.1
+
+  levn@0.4.1:
+    dependencies:
+      prelude-ls: 1.2.1
+      type-check: 0.4.0
+
+  locate-path@6.0.0:
+    dependencies:
+      p-locate: 5.0.0
+
+  lodash.merge@4.6.2: {}
+
   make-error@1.3.6: {}
 
   mathjs@14.0.0:
@@ -1221,6 +1757,8 @@ snapshots:
 
   ms@2.1.3: {}
 
+  natural-compare@1.4.0: {}
+
   negotiator@0.6.3: {}
 
   nodemon@3.1.7:
@@ -1250,16 +1788,43 @@ snapshots:
     dependencies:
       wrappy: 1.0.2
 
+  optionator@0.9.4:
+    dependencies:
+      deep-is: 0.1.4
+      fast-levenshtein: 2.0.6
+      levn: 0.4.1
+      prelude-ls: 1.2.1
+      type-check: 0.4.0
+      word-wrap: 1.2.5
+
+  p-limit@3.1.0:
+    dependencies:
+      yocto-queue: 0.1.0
+
+  p-locate@5.0.0:
+    dependencies:
+      p-limit: 3.1.0
+
+  parent-module@1.0.1:
+    dependencies:
+      callsites: 3.1.0
+
   parseurl@1.3.3: {}
 
+  path-exists@4.0.0: {}
+
   path-is-absolute@1.0.1: {}
 
+  path-key@3.1.1: {}
+
   path-parse@1.0.7: {}
 
   path-to-regexp@0.1.10: {}
 
   picomatch@2.3.1: {}
 
+  prelude-ls@1.2.1: {}
+
   proxy-addr@2.0.7:
     dependencies:
       forwarded: 0.2.0
@@ -1267,6 +1832,8 @@ snapshots:
 
   pstree.remy@1.1.8: {}
 
+  punycode@2.3.1: {}
+
   qs@6.13.0:
     dependencies:
       side-channel: 1.0.6
@@ -1288,6 +1855,8 @@ snapshots:
 
   requires-port@1.0.0: {}
 
+  resolve-from@4.0.0: {}
+
   resolve@1.22.8:
     dependencies:
       is-core-module: 2.15.1
@@ -1344,6 +1913,12 @@ snapshots:
 
   setprototypeof@1.2.0: {}
 
+  shebang-command@2.0.0:
+    dependencies:
+      shebang-regex: 3.0.0
+
+  shebang-regex@3.0.0: {}
+
   side-channel@1.0.6:
     dependencies:
       call-bind: 1.0.7
@@ -1398,10 +1973,16 @@ snapshots:
 
   strip-json-comments@2.0.1: {}
 
+  strip-json-comments@3.1.1: {}
+
   supports-color@5.5.0:
     dependencies:
       has-flag: 3.0.0
 
+  supports-color@7.2.0:
+    dependencies:
+      has-flag: 4.0.0
+
   supports-preserve-symlinks-flag@1.0.0: {}
 
   tiny-emitter@2.1.0: {}
@@ -1416,7 +1997,7 @@ snapshots:
 
   tree-kill@1.2.2: {}
 
-  ts-node-dev@2.0.0(@types/node@22.9.1)(typescript@5.6.3):
+  ts-node-dev@2.0.0(@types/node@22.9.1)(typescript@5.7.2):
     dependencies:
       chokidar: 3.6.0
       dynamic-dedupe: 0.3.0
@@ -1426,15 +2007,15 @@ snapshots:
       rimraf: 2.7.1
       source-map-support: 0.5.21
       tree-kill: 1.2.2
-      ts-node: 10.9.2(@types/node@22.9.1)(typescript@5.6.3)
+      ts-node: 10.9.2(@types/node@22.9.1)(typescript@5.7.2)
       tsconfig: 7.0.0
-      typescript: 5.6.3
+      typescript: 5.7.2
     transitivePeerDependencies:
       - '@swc/core'
       - '@swc/wasm'
       - '@types/node'
 
-  ts-node@10.9.2(@types/node@22.9.1)(typescript@5.6.3):
+  ts-node@10.9.2(@types/node@22.9.1)(typescript@5.7.2):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
       '@tsconfig/node10': 1.0.11
@@ -1448,7 +2029,7 @@ snapshots:
       create-require: 1.1.1
       diff: 4.0.2
       make-error: 1.3.6
-      typescript: 5.6.3
+      typescript: 5.7.2
       v8-compile-cache-lib: 3.0.1
       yn: 3.1.1
 
@@ -1459,6 +2040,10 @@ snapshots:
       strip-bom: 3.0.0
       strip-json-comments: 2.0.1
 
+  type-check@0.4.0:
+    dependencies:
+      prelude-ls: 1.2.1
+
   type-is@1.6.18:
     dependencies:
       media-typer: 0.3.0
@@ -1466,7 +2051,7 @@ snapshots:
 
   typed-function@4.2.1: {}
 
-  typescript@5.6.3: {}
+  typescript@5.7.2: {}
 
   undefsafe@2.0.5: {}
 
@@ -1474,12 +2059,22 @@ snapshots:
 
   unpipe@1.0.0: {}
 
+  uri-js@4.4.1:
+    dependencies:
+      punycode: 2.3.1
+
   utils-merge@1.0.1: {}
 
   v8-compile-cache-lib@3.0.1: {}
 
   vary@1.1.2: {}
 
+  which@2.0.2:
+    dependencies:
+      isexe: 2.0.0
+
+  word-wrap@1.2.5: {}
+
   wrappy@1.0.2: {}
 
   ws@8.17.1: {}
@@ -1487,3 +2082,5 @@ snapshots:
   xtend@4.0.2: {}
 
   yn@3.1.1: {}
+
+  yocto-queue@0.1.0: {}
diff --git a/pi/ui/env.d.ts b/pi/ui/env.d.ts
index 11f02fe..81486fe 100644
--- a/pi/ui/env.d.ts
+++ b/pi/ui/env.d.ts
@@ -1 +1,10 @@
-/// <reference types="vite/client" />
+// / <reference types="vite/client" />
+
+interface ImportMetaEnv {
+  readonly BASE_URL: string;
+  readonly PROD: boolean
+}
+
+interface ImportMeta {
+  readonly env: ImportMetaEnv;
+}
\ No newline at end of file
diff --git a/pi/ui/eslint.config.js b/pi/ui/eslint.config.js
index b115712..a0f49f0 100644
--- a/pi/ui/eslint.config.js
+++ b/pi/ui/eslint.config.js
@@ -1,15 +1,15 @@
-import pluginVue from 'eslint-plugin-vue'
-import vueTsEslintConfig from '@vue/eslint-config-typescript'
-import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'
+import pluginVue from 'eslint-plugin-vue';
+import vueTsEslintConfig from '@vue/eslint-config-typescript';
+import skipFormatting from '@vue/eslint-config-prettier/skip-formatting';
 
 export default [
   {
-    name: 'app/files-to-lint',
+    name:  'app/files-to-lint',
     files: ['**/*.{ts,mts,tsx,vue}'],
   },
 
   {
-    name: 'app/files-to-ignore',
+    name:    'app/files-to-ignore',
     ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'],
   },
 
@@ -19,7 +19,137 @@ export default [
 
   {
     rules: {
-      'vue/no-multiple-template-root': 'off',
+      'vue/no-multiple-template-root':  'off',
+      'dot-notation':                   'off',
+      'guard-for-in':                   'off',
+      'new-cap':                        'off',
+      'no-empty':                       'off',
+      'no-extra-boolean-cast':          'off',
+      'no-new':                         'off',
+      'no-plusplus':                    'off',
+      'no-useless-escape':              'off',
+      'strict':                         'off',
+      'vue/html-self-closing':          'off',
+      'vue/no-v-html':                  'off',
+      'vue/multi-word-component-names': 'off',
+
+      'array-bracket-spacing':             'warn',
+      'arrow-parens':                      'warn',
+      'arrow-spacing':                     ['warn', {
+        'before': true,
+        'after':  true
+      }],
+      'block-spacing':                     ['warn', 'always'],
+      'brace-style':                       ['warn', '1tbs'],
+      'comma-dangle':                      ['warn', 'only-multiline'],
+      'comma-spacing':                     'warn',
+      'curly':                             'warn',
+      'eqeqeq':                            'warn',
+      'func-call-spacing':                 ['warn', 'never'],
+      'implicit-arrow-linebreak':          'warn',
+      'indent':                            ['warn', 2],
+      'keyword-spacing':                   'warn',
+      'lines-between-class-members':       ['warn', 'always', { 'exceptAfterSingleLine': true }],
+      'multiline-ternary':                 ['warn', 'never'],
+      'newline-per-chained-call':          ['warn', { 'ignoreChainWithDepth': 4 }],
+      'no-caller':                         'warn',
+      'no-cond-assign':                    ['warn', 'except-parens'],
+      'no-console':                        'off',
+      'no-debugger':                       'warn',
+      'no-eq-null':                        'warn',
+      'no-eval':                           'warn',
+      'no-trailing-spaces':                'warn',
+      'no-undef':                          'warn',
+      'no-unused-vars':                    'warn',
+      'no-whitespace-before-property':     'warn',
+      'object-curly-spacing':              ['warn', 'always'],
+      'object-property-newline':           'warn',
+      'object-shorthand':                  'warn',
+      'padded-blocks':                     ['warn', 'never'],
+      'prefer-arrow-callback':             'warn',
+      'prefer-template':                   'warn',
+      'rest-spread-spacing':               'warn',
+      'semi':                              ['warn', 'always'],
+      'space-before-function-paren':       ['warn', 'never'],
+      'space-infix-ops':                   'warn',
+      'spaced-comment':                    'warn',
+      'switch-colon-spacing':              'warn',
+      'template-curly-spacing':            ['warn', 'always'],
+      'yield-star-spacing':                ['warn', 'both'],
+
+      'key-spacing':              ['warn', {
+        'align': {
+          'beforeColon': false,
+          'afterColon':  true,
+          'on':          'value',
+          'mode':        'minimum'
+        },
+        'multiLine': {
+          'beforeColon': false,
+          'afterColon':  true
+        },
+      }],
+
+      'object-curly-newline':          ['warn', {
+        'ObjectExpression':  {
+          'multiline':     true,
+          'minProperties': 3
+        },
+        'ObjectPattern':     {
+          'multiline':     true,
+          'minProperties': 4
+        },
+        'ImportDeclaration': {
+          'multiline':     true,
+          'minProperties': 5
+        },
+        'ExportDeclaration': {
+          'multiline':     true,
+          'minProperties': 3
+        }
+      }],
+
+      'padding-line-between-statements': [
+        'warn',
+        {
+          'blankLine': 'always',
+          'prev':      '*',
+          'next':      'return',
+        },
+        {
+          'blankLine': 'always',
+          'prev':      'function',
+          'next':      'function',
+        },
+        // This configuration would require blank lines after every sequence of variable declarations
+        {
+          'blankLine': 'always',
+          'prev':      ['const', 'let', 'var'],
+          'next':      '*'
+        },
+        {
+          'blankLine': 'any',
+          'prev':      ['const', 'let', 'var'],
+          'next':      ['const', 'let', 'var']
+        }
+      ],
+
+      'quotes': [
+        'warn',
+        'single',
+        {
+          'avoidEscape':           true,
+          'allowTemplateLiterals': true
+        },
+      ],
+
+      'space-unary-ops': [
+        'warn',
+        {
+          'words':    true,
+          'nonwords': false,
+        }
+      ]
     }
   }
-]
+];
diff --git a/pi/ui/src/App.vue b/pi/ui/src/App.vue
index a78fb6f..69dd87a 100644
--- a/pi/ui/src/App.vue
+++ b/pi/ui/src/App.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { RouterView } from 'vue-router'
+import { RouterView } from 'vue-router';
 </script>
 
 <template>
diff --git a/pi/ui/src/components/UWBVisualization.vue b/pi/ui/src/components/UWBVisualization.vue
index b20ca2f..c6ffc0b 100644
--- a/pi/ui/src/components/UWBVisualization.vue
+++ b/pi/ui/src/components/UWBVisualization.vue
@@ -49,7 +49,6 @@ export default defineComponent({
             if (uwbData?.value.length === 3) {
               console.log('Incoming UWB data:', JSON.parse(JSON.stringify(uwbData.value)));
             }
-
           });
         })
         .catch((error) => {
@@ -69,6 +68,7 @@ export default defineComponent({
 
       if (ids.length < 3) {
         console.error('At least three anchors are required.');
+
         return;
       }
 
@@ -76,6 +76,7 @@ export default defineComponent({
       positions[ids[0]] = [0, 0]; // First anchor at (0, 0)
 
       const d1 = getDistance(ids[0], ids[1]) * scalingFactor;
+
       positions[ids[1]] = [d1, 0]; // Second anchor on x-axis
 
       const d2 = getDistance(ids[0], ids[2]) * scalingFactor;
@@ -92,8 +93,8 @@ export default defineComponent({
 
     function getDistance(id1: string, id2: string): number {
       return (
-        anchorDistances.value[`${id1}-${id2}`] ||
-        anchorDistances.value[`${id2}-${id1}`] ||
+        anchorDistances.value[`${ id1 }-${ id2 }`] ||
+        anchorDistances.value[`${ id2 }-${ id1 }`] ||
         0
       );
     }
@@ -125,10 +126,10 @@ export default defineComponent({
     }
 
     function drawAnchors() {
-      const anchors = Object.entries(anchorPositions.value);
+      const anchors = Object.entries(anchorPositions.value) as [string, [number, number]][];
 
       // Bind data to anchor circles
-      const anchorCircles = svg.selectAll('.anchor')
+      const anchorCircles = svg.selectAll<SVGCircleElement, [string, [number, number]]>('.anchor')
         .data(anchors, (d) => d[0]);
 
       // Enter selection
@@ -145,7 +146,7 @@ export default defineComponent({
       anchorCircles.exit().remove();
 
       // Bind data to anchor labels
-      const anchorLabels = svg.selectAll('.anchor-label')
+      const anchorLabels = svg.selectAll<SVGTextElement, [string, [number, number]]>('.anchor-label')
         .data(anchors, (d) => d[0]);
 
       // Enter selection
@@ -157,13 +158,12 @@ export default defineComponent({
         .merge(anchorLabels as any) // eslint-disable-line
         .attr('x', (d) => xScale(d[1][0]) + 5)
         .attr('y', (d) => yScale(d[1][1]) - 5)
-        .text((d) => `A${d[0]}`);
+        .text((d) => `A${ d[0] }`);
 
       // Exit selection
       anchorLabels.exit().remove();
     }
 
-
     function updateVisualization() {
       const positions: [number, number][] = [];
       const distances: number[] = [];
@@ -171,8 +171,10 @@ export default defineComponent({
       uwbData.value.forEach((anchor) => {
         const anchorID = anchor.A;
         const range = parseFloat(anchor.R) * scalingFactor; // Scale the range
+
         if (range <= 0) {
-          console.warn(`Invalid distance for anchor ${anchorID}: ${range}. Skipping.`);
+          console.warn(`Invalid distance for anchor ${ anchorID }: ${ range }. Skipping.`);
+
           return; // Skip invalid distances
         }
         if (anchorPositions.value[anchorID]) {
@@ -183,6 +185,7 @@ export default defineComponent({
 
       if (positions.length >= 3) {
         const [x, y] = calculateTagPosition(positions, distances);
+
         if (x !== -1 && y !== -1) {
           // Update scales to include tag position
           updateScales([x, y]);
@@ -218,6 +221,7 @@ export default defineComponent({
     ): [number, number] {
       if (positions.length < 3) {
         console.error('At least three anchors are required.');
+
         return [-1, -1];
       }
 
@@ -240,6 +244,7 @@ export default defineComponent({
 
         if (denominator === 0) {
           console.error('Cannot solve, determinant is zero.');
+
           return [-1, -1];
         }
 
@@ -249,6 +254,7 @@ export default defineComponent({
         return [x, y];
       } catch (error) {
         console.error('Error calculating position:', error);
+
         return [-1, -1];
       }
     }
diff --git a/pi/ui/src/main.ts b/pi/ui/src/main.ts
index 0d51d91..ef2b5f2 100644
--- a/pi/ui/src/main.ts
+++ b/pi/ui/src/main.ts
@@ -1,21 +1,19 @@
-import './assets/main.css'
+import './assets/main.css';
 
-import { createApp } from 'vue'
-import { createPinia } from 'pinia'
-import { io, Socket } from 'socket.io-client'
+import { createApp } from 'vue';
+import { createPinia } from 'pinia';
+import { io, Socket } from 'socket.io-client';
 
-import App from './App.vue'
-import router from './router'
+import App from './App.vue';
+import router from './router';
 
-const app = createApp(App)
+const app = createApp(App);
 
-const socket: Socket = io('http://0.0.0.0:5000', {
-  transports: ['websocket'],
-})
+const socket: Socket = io('http://0.0.0.0:5000', { transports: ['websocket'] });
 
-app.provide('socket', socket)
+app.provide('socket', socket);
 
-app.use(createPinia())
-app.use(router)
+app.use(createPinia());
+app.use(router);
 
-app.mount('#app')
+app.mount('#app');
diff --git a/pi/ui/src/router/index.ts b/pi/ui/src/router/index.ts
index 29788a1..4155626 100644
--- a/pi/ui/src/router/index.ts
+++ b/pi/ui/src/router/index.ts
@@ -1,15 +1,15 @@
-import { createRouter, createWebHistory } from 'vue-router'
-import HomeView from '../views/HomeView.vue'
+import { createRouter, createWebHistory } from 'vue-router';
+import HomeView from '../views/HomeView.vue';
 
 const router = createRouter({
   history: createWebHistory(import.meta.env.BASE_URL),
-  routes: [
+  routes:  [
     {
-      path: '/',
-      name: 'home',
+      path:      '/',
+      name:      'home',
       component: HomeView,
     },
   ],
-})
+});
 
-export default router
+export default router;
diff --git a/pi/ui/src/views/HomeView.vue b/pi/ui/src/views/HomeView.vue
index 4e24910..73b2247 100644
--- a/pi/ui/src/views/HomeView.vue
+++ b/pi/ui/src/views/HomeView.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import UWBVisualization from '../components/UWBVisualization.vue'
+import UWBVisualization from '../components/UWBVisualization.vue';
 </script>
 
 <template>
diff --git a/pi/ui/vite.config.ts b/pi/ui/vite.config.ts
index 1798a4c..6c1b071 100644
--- a/pi/ui/vite.config.ts
+++ b/pi/ui/vite.config.ts
@@ -1,8 +1,8 @@
-import { fileURLToPath, URL } from 'node:url'
+import { fileURLToPath, URL } from 'node:url';
 
-import { defineConfig } from 'vite'
-import vue from '@vitejs/plugin-vue'
-import vueDevTools from 'vite-plugin-vue-devtools'
+import { defineConfig } from 'vite';
+import vue from '@vitejs/plugin-vue';
+import vueDevTools from 'vite-plugin-vue-devtools';
 
 // https://vite.dev/config/
 export default defineConfig({
@@ -10,14 +10,6 @@ export default defineConfig({
     vue(),
     vueDevTools(),
   ],
-  resolve: {
-    alias: {
-      '@': fileURLToPath(new URL('./src', import.meta.url))
-    },
-  },
-  server: {
-    proxy: {
-      '/api': 'http://localhost:5000',
-    },
-  },
-})
+  resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } },
+  server:  { proxy: { '/api': 'http://localhost:5000' } },
+});

From 14e0b361032b50cd84c5a8081ebb56c843ea5bb2 Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Sat, 23 Nov 2024 10:07:15 -0500
Subject: [PATCH 14/34] Refactor lawndon-pi build and deployment (#22)

* Refactor builds

* Refactor lawndon-pi build and deployment
---
 .github/workflows/build-lawndon.yaml        | 108 ++++++++++
 .github/workflows/build-lawndon.yml         |  82 -------
 .github/workflows/{tests.yml => tests.yaml} |  12 +-
 pi/Dockerfile.server                        |  27 +++
 pi/Dockerfile.ui                            |  22 ++
 pi/docker-compose.yaml                      |  28 +++
 pi/package.json                             |   3 +-
 pi/server/.env.dev                          |   2 +
 pi/server/.env.prod                         |   2 +
 pi/server/eslint.config.js                  |   1 -
 pi/server/package.json                      |   8 +-
 pi/server/pnpm-lock.yaml                    |  58 +++--
 pi/server/src/server.ts                     |  38 ++--
 pi/server/tsconfig.json                     |   7 +-
 pi/ui/env.d.ts                              |   2 +
 pi/ui/nginx.conf                            |  63 ++++++
 pi/ui/package.json                          |  16 +-
 pi/ui/pnpm-lock.yaml                        | 228 ++++++++++----------
 pi/ui/src/components/UWBVisualization.vue   |  36 ++--
 pi/ui/src/main.ts                           |   3 +-
 20 files changed, 465 insertions(+), 281 deletions(-)
 create mode 100644 .github/workflows/build-lawndon.yaml
 delete mode 100644 .github/workflows/build-lawndon.yml
 rename .github/workflows/{tests.yml => tests.yaml} (86%)
 create mode 100644 pi/Dockerfile.server
 create mode 100644 pi/Dockerfile.ui
 create mode 100644 pi/docker-compose.yaml
 create mode 100644 pi/server/.env.dev
 create mode 100644 pi/server/.env.prod
 create mode 100644 pi/ui/nginx.conf

diff --git a/.github/workflows/build-lawndon.yaml b/.github/workflows/build-lawndon.yaml
new file mode 100644
index 0000000..4aa849b
--- /dev/null
+++ b/.github/workflows/build-lawndon.yaml
@@ -0,0 +1,108 @@
+name: Build Lawndon
+
+on:
+  workflow_dispatch:
+  release:
+    types: [released]
+
+jobs:
+  build-lawndon:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: write
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Install Arduino cli
+        uses: arduino/setup-arduino-cli@v2
+
+      - name: Install libraries
+        run: |
+          arduino-cli lib install IBusBM Servo
+
+      - name: Compile Lawndon
+        uses: arduino/compile-sketches@v1
+        with:
+          fqbn: "arduino:avr:mega"
+          libraries: |
+            - name: IBusBM
+            - name: Servo
+          sketch-paths: |
+            - ./lawndon
+          enable-warnings-report: true
+          verbose: false
+          cli-compile-flags: |
+            - --export-binaries
+
+      - name: Package build
+        if: startsWith(github.ref, 'refs/tags/')
+        run: |
+          for dir in ./lawndon/build/*; do
+            if [ -d "${dir}" ]; then
+              tar -czvf "${dir}.tar.gz" -C "${dir}" .
+            fi
+          done
+
+      - name: Release build
+        uses: softprops/action-gh-release@v2
+        if: startsWith(github.ref, 'refs/tags/')
+        with:
+          files: |
+            lawndon/build/*.tar.gz
+
+  build-lawndon-pi:
+    runs-on: ubuntu-latest
+    needs: build-lawndon
+    env:
+      WORKING_DIR: ./pi
+    permissions:
+      contents: read
+      packages: write
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+      
+      - name: Set up QEMU for multi-platform builds
+        uses: docker/setup-qemu-action@v3
+        with:
+          platforms: linux/amd64,linux/arm64
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+        with:
+          buildkitd-flags: --allow-insecure-entitlement security.insecure
+          install: true
+
+      - name: Log in to GitHub Container Registry
+        uses: docker/login-action@v2
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Build and push server image
+        uses: docker/build-push-action@v4
+        working-directory: ${{ env.WORKING_DIR }}
+        with:
+          context: .
+          file: ./Dockerfile.server
+          push: true
+          platforms: linux/amd64,linux/arm64
+          tags: |
+            ghcr.io/${{ github.repository_owner }}/lawndon-pi-server:${{ github.ref_name }}
+            ghcr.io/${{ github.repository_owner }}/lawndon-pi-server:${{ github.sha }}
+
+      - name: Build and push UI image
+        uses: docker/build-push-action@v4
+        working-directory: ${{ env.WORKING_DIR }}
+        with:
+          context: .
+          file: ./Dockerfile.ui
+          push: true
+          platforms: linux/amd64,linux/arm64
+          tags: |
+            ghcr.io/${{ github.repository_owner }}/lawndon-pi-ui:${{ github.ref_name }}
+            ghcr.io/${{ github.repository_owner }}/lawndon-pi-ui:${{ github.sha }}
diff --git a/.github/workflows/build-lawndon.yml b/.github/workflows/build-lawndon.yml
deleted file mode 100644
index e1844b9..0000000
--- a/.github/workflows/build-lawndon.yml
+++ /dev/null
@@ -1,82 +0,0 @@
-name: Build Lawndon
-
-on:
-  workflow_dispatch:
-  release:
-    types: [released]
-
-jobs:
-  build-lawndon:
-    runs-on: ubuntu-latest
-    permissions:
-      contents: write
-
-    steps:
-      - name: Checkout repository
-        uses: actions/checkout@v4
-
-      - name: Install Arduino cli
-        uses: arduino/setup-arduino-cli@v2
-
-      - name: Install libraries
-        run: |
-          arduino-cli lib install IBusBM Servo
-
-      - name: Compile Lawndon
-        uses: arduino/compile-sketches@v1
-        with:
-          fqbn: "arduino:avr:mega"
-          libraries: |
-            - name: IBusBM
-            - name: Servo
-          sketch-paths: |
-            - ./lawndon
-          enable-warnings-report: true
-          verbose: false
-          cli-compile-flags: |
-            - --export-binaries
-
-      - name: Package build
-        if: startsWith(github.ref, 'refs/tags/')
-        run: |
-          for dir in ./lawndon/build/*; do
-            if [ -d "${dir}" ]; then
-              tar -czvf "${dir}.tar.gz" -C "${dir}" .
-            fi
-          done
-
-      - name: Release build
-        uses: softprops/action-gh-release@v2
-        if: startsWith(github.ref, 'refs/tags/')
-        with:
-          files: |
-            lawndon/build/*.tar.gz
-
-  build-lawndon-pi:
-    runs-on: ubuntu-latest
-    needs: build-lawndon
-    steps:
-      - name: Checkout repository
-        uses: actions/checkout@v4
-
-      - name: Install pnpm
-        uses: pnpm/action-setup@v4
-        with:
-          version: 9
-
-      - name: Setup Node.js
-        uses: actions/setup-node@v4
-        with:
-          node-version: '22'
-
-      - name: Package application
-        working-directory: ./pi
-        run: |
-          bash package.sh
-
-      - name: Upload release assets
-        uses: softprops/action-gh-release@v2
-        if: startsWith(github.ref, 'refs/tags/')
-        with:
-          files: |
-            ./pi/lawndon-pi.tar.gz
\ No newline at end of file
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yaml
similarity index 86%
rename from .github/workflows/tests.yml
rename to .github/workflows/tests.yaml
index 087b8f0..5a08d4b 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yaml
@@ -8,7 +8,7 @@ on:
     - cron: "0 4 * * 4"
 
 jobs:
-  build-lawndon:
+  build-lawndon-test:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout repository
@@ -41,7 +41,7 @@ jobs:
           cli-compile-flags: |
             - --export-binaries
 
-  package-lawndon-pi:
+  build-lawndon-pi-test:
     runs-on: ubuntu-latest
     env:
       WORKING_DIR: ./pi
@@ -79,12 +79,12 @@ jobs:
         run: |
           pnpm build:server
 
-      - name: Run package script
+      - name: Test Docker build for Server
         working-directory: ${{ env.WORKING_DIR }}
         run: |
-          ./package.sh
+          docker build --no-cache --file ./Dockerfile.server --tag lawndon-pi-server-test .
 
-      - name: Verify package contents
+      - name: Test Docker build for UI
         working-directory: ${{ env.WORKING_DIR }}
         run: |
-          tar -df lawndon-pi.tar.gz lawndon-pi
+          docker build --no-cache --file ./Dockerfile.ui --tag lawndon-pi-ui-test .
diff --git a/pi/Dockerfile.server b/pi/Dockerfile.server
new file mode 100644
index 0000000..02becf6
--- /dev/null
+++ b/pi/Dockerfile.server
@@ -0,0 +1,27 @@
+# Build stage
+FROM node:22-alpine AS build
+WORKDIR /usr/src/app
+
+ENV PNPM_HOME="/pnpm"
+ENV PATH="$PNPM_HOME:$PATH"
+ENV NODE_ENV=production
+ENV CONFIG_PATH=/usr/src/app/config
+
+RUN corepack enable
+COPY server/package*.json ./
+RUN pnpm install
+
+COPY server ./server
+WORKDIR /usr/src/app/server
+RUN pnpm build
+
+# Runtime stage
+FROM node:22-alpine
+WORKDIR /usr/src/app
+
+COPY --from=build /usr/src/app/server/dist ./dist
+COPY --from=build /usr/src/app/server/node_modules ./node_modules
+COPY config ./config
+
+EXPOSE 5000 8080
+CMD ["node", "dist/server.js"]
diff --git a/pi/Dockerfile.ui b/pi/Dockerfile.ui
new file mode 100644
index 0000000..c05bd32
--- /dev/null
+++ b/pi/Dockerfile.ui
@@ -0,0 +1,22 @@
+# Build stage
+FROM node:22-alpine AS build
+WORKDIR /usr/src/app
+
+ENV PNPM_HOME="/pnpm"
+ENV PATH="$PNPM_HOME:$PATH"
+ENV VITE_NODE_ENV=production
+
+RUN corepack enable
+COPY ui/package*.json ./
+RUN pnpm install
+
+COPY ui ./ui
+WORKDIR /usr/src/app/ui
+RUN pnpm build
+
+# Runtime stage
+FROM nginx:alpine
+COPY --from=build /usr/src/app/ui/dist /usr/share/nginx/html
+
+EXPOSE 80
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/pi/docker-compose.yaml b/pi/docker-compose.yaml
new file mode 100644
index 0000000..e92adc4
--- /dev/null
+++ b/pi/docker-compose.yaml
@@ -0,0 +1,28 @@
+services:
+  server:
+    image: ghcr.io/jordojordo/lawndon-pi-server:latest
+    environment:
+      NODE_ENV: production
+      CONFIG_PATH: /usr/src/app/config
+    ports:
+      - "5000:5000" # WebSocket and API
+      - "8080:8080" # ESP32 module communication
+    volumes:
+      - ./config:/usr/src/app/config:ro # Mount config for anchor positions
+    networks:
+      - app_network
+
+  ui:
+    image: ghcr.io/jordojordo/uwb-ui:latest
+    environment:
+      VITE_NODE_ENV: production
+    ports:
+      - "80:80" # Expose UI
+    depends_on:
+      - server
+    networks:
+      - app_network
+
+networks:
+  app_network:
+    driver: bridge
diff --git a/pi/package.json b/pi/package.json
index cd43688..6fd29c9 100644
--- a/pi/package.json
+++ b/pi/package.json
@@ -2,6 +2,7 @@
   "name": "lawndon-pi",
   "version": "0.1.0",
   "private": true,
+  "type": "module",
   "scripts": {
     "install:ui": "pnpm install --prefix ui",
     "install:server": "pnpm install --prefix server",
@@ -10,7 +11,7 @@
     "start": "node server/dist/server.js",
     "dev": "concurrently -k -n SERVER,UI \"pnpm dev:server\" \"pnpm dev:ui\"",
     "dev:ui": "pnpm --prefix ui dev",
-    "dev:server": "NODE_ENV=development pnpm --prefix server dev"
+    "dev:server": "pnpm --prefix server dev"
   },
   "packageManager": "pnpm@9.14.2",
   "devDependencies": {
diff --git a/pi/server/.env.dev b/pi/server/.env.dev
new file mode 100644
index 0000000..999e070
--- /dev/null
+++ b/pi/server/.env.dev
@@ -0,0 +1,2 @@
+NODE_ENV="development"
+CONFIG_PATH="../../config"
diff --git a/pi/server/.env.prod b/pi/server/.env.prod
new file mode 100644
index 0000000..3ede221
--- /dev/null
+++ b/pi/server/.env.prod
@@ -0,0 +1,2 @@
+NODE_ENV="production"
+CONFIG_PATH="../config"
diff --git a/pi/server/eslint.config.js b/pi/server/eslint.config.js
index 8d54878..ebb590c 100644
--- a/pi/server/eslint.config.js
+++ b/pi/server/eslint.config.js
@@ -36,7 +36,6 @@ export default [
       'newline-per-chained-call':          ['warn', { 'ignoreChainWithDepth': 4 }],
       'no-caller':                         'warn',
       'no-cond-assign':                    ['warn', 'except-parens'],
-      'no-console':                        'warn',
       'no-debugger':                       'warn',
       'no-eq-null':                        'warn',
       'no-eval':                           'warn',
diff --git a/pi/server/package.json b/pi/server/package.json
index 7e3f98a..baef2a1 100644
--- a/pi/server/package.json
+++ b/pi/server/package.json
@@ -2,12 +2,12 @@
   "name": "uwb-server",
   "version": "0.1.0",
   "description": "",
-  "main": "src/server.js",
+  "main": "dist/server.js",
   "type": "module",
   "scripts": {
     "start": "node dist/server.js",
     "build": "tsc --outDir dist",
-    "dev": "nodemon --watch src --ext ts --exec 'node --loader ts-node/esm ./src/server.ts'",
+    "dev": "NODE_ENV=development nodemon --watch src --ext ts --exec 'node --loader ts-node/esm ./src/server.ts'",
     "lint": "npx eslint .",
     "format": "npx eslint . --fix"
   },
@@ -17,14 +17,16 @@
   "packageManager": "pnpm@9.14.2",
   "dependencies": {
     "cors": "^2.8.5",
+    "dotenv": "^16.4.5",
     "express": "^4.21.1",
     "mathjs": "^14.0.0",
     "socket.io": "^4.8.1"
   },
   "devDependencies": {
     "@types/cors": "^2.8.17",
+    "@types/dotenv": "^8.2.3",
     "@types/express": "^5.0.0",
-    "@types/node": "^22.9.1",
+    "@types/node": "^22.9.3",
     "eslint": "^9.15.0",
     "globals": "^15.12.0",
     "http-proxy-middleware": "^3.0.3",
diff --git a/pi/server/pnpm-lock.yaml b/pi/server/pnpm-lock.yaml
index 145967a..c33915a 100644
--- a/pi/server/pnpm-lock.yaml
+++ b/pi/server/pnpm-lock.yaml
@@ -11,6 +11,9 @@ importers:
       cors:
         specifier: ^2.8.5
         version: 2.8.5
+      dotenv:
+        specifier: ^16.4.5
+        version: 16.4.5
       express:
         specifier: ^4.21.1
         version: 4.21.1
@@ -24,12 +27,15 @@ importers:
       '@types/cors':
         specifier: ^2.8.17
         version: 2.8.17
+      '@types/dotenv':
+        specifier: ^8.2.3
+        version: 8.2.3
       '@types/express':
         specifier: ^5.0.0
         version: 5.0.0
       '@types/node':
-        specifier: ^22.9.1
-        version: 22.9.1
+        specifier: ^22.9.3
+        version: 22.9.3
       eslint:
         specifier: ^9.15.0
         version: 9.15.0
@@ -44,10 +50,10 @@ importers:
         version: 3.1.7
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.9.1)(typescript@5.7.2)
+        version: 10.9.2(@types/node@22.9.3)(typescript@5.7.2)
       ts-node-dev:
         specifier: ^2.0.0
-        version: 2.0.0(@types/node@22.9.1)(typescript@5.7.2)
+        version: 2.0.0(@types/node@22.9.3)(typescript@5.7.2)
       typescript:
         specifier: ^5.7.2
         version: 5.7.2
@@ -153,6 +159,10 @@ packages:
   '@types/cors@2.8.17':
     resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
 
+  '@types/dotenv@8.2.3':
+    resolution: {integrity: sha512-g2FXjlDX/cYuc5CiQvyU/6kkbP1JtmGzh0obW50zD7OKeILVL0NSpPWLXVfqoAGQjom2/SLLx9zHq0KXvD6mbw==}
+    deprecated: This is a stub types definition. dotenv provides its own type definitions, so you do not need this installed.
+
   '@types/estree@1.0.6':
     resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
 
@@ -174,8 +184,8 @@ packages:
   '@types/mime@1.3.5':
     resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
 
-  '@types/node@22.9.1':
-    resolution: {integrity: sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==}
+  '@types/node@22.9.3':
+    resolution: {integrity: sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==}
 
   '@types/qs@6.9.17':
     resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==}
@@ -360,6 +370,10 @@ packages:
     resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
     engines: {node: '>=0.3.1'}
 
+  dotenv@16.4.5:
+    resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
+    engines: {node: '>=12'}
+
   dynamic-dedupe@0.3.0:
     resolution: {integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==}
 
@@ -1141,23 +1155,27 @@ snapshots:
   '@types/body-parser@1.19.5':
     dependencies:
       '@types/connect': 3.4.38
-      '@types/node': 22.9.1
+      '@types/node': 22.9.3
 
   '@types/connect@3.4.38':
     dependencies:
-      '@types/node': 22.9.1
+      '@types/node': 22.9.3
 
   '@types/cookie@0.4.1': {}
 
   '@types/cors@2.8.17':
     dependencies:
-      '@types/node': 22.9.1
+      '@types/node': 22.9.3
+
+  '@types/dotenv@8.2.3':
+    dependencies:
+      dotenv: 16.4.5
 
   '@types/estree@1.0.6': {}
 
   '@types/express-serve-static-core@5.0.1':
     dependencies:
-      '@types/node': 22.9.1
+      '@types/node': 22.9.3
       '@types/qs': 6.9.17
       '@types/range-parser': 1.2.7
       '@types/send': 0.17.4
@@ -1173,13 +1191,13 @@ snapshots:
 
   '@types/http-proxy@1.17.15':
     dependencies:
-      '@types/node': 22.9.1
+      '@types/node': 22.9.3
 
   '@types/json-schema@7.0.15': {}
 
   '@types/mime@1.3.5': {}
 
-  '@types/node@22.9.1':
+  '@types/node@22.9.3':
     dependencies:
       undici-types: 6.19.8
 
@@ -1190,12 +1208,12 @@ snapshots:
   '@types/send@0.17.4':
     dependencies:
       '@types/mime': 1.3.5
-      '@types/node': 22.9.1
+      '@types/node': 22.9.3
 
   '@types/serve-static@1.15.7':
     dependencies:
       '@types/http-errors': 2.0.4
-      '@types/node': 22.9.1
+      '@types/node': 22.9.3
       '@types/send': 0.17.4
 
   '@types/strip-bom@3.0.0': {}
@@ -1363,6 +1381,8 @@ snapshots:
 
   diff@4.0.2: {}
 
+  dotenv@16.4.5: {}
+
   dynamic-dedupe@0.3.0:
     dependencies:
       xtend: 4.0.2
@@ -1379,7 +1399,7 @@ snapshots:
     dependencies:
       '@types/cookie': 0.4.1
       '@types/cors': 2.8.17
-      '@types/node': 22.9.1
+      '@types/node': 22.9.3
       accepts: 1.3.8
       base64id: 2.0.0
       cookie: 0.7.2
@@ -1997,7 +2017,7 @@ snapshots:
 
   tree-kill@1.2.2: {}
 
-  ts-node-dev@2.0.0(@types/node@22.9.1)(typescript@5.7.2):
+  ts-node-dev@2.0.0(@types/node@22.9.3)(typescript@5.7.2):
     dependencies:
       chokidar: 3.6.0
       dynamic-dedupe: 0.3.0
@@ -2007,7 +2027,7 @@ snapshots:
       rimraf: 2.7.1
       source-map-support: 0.5.21
       tree-kill: 1.2.2
-      ts-node: 10.9.2(@types/node@22.9.1)(typescript@5.7.2)
+      ts-node: 10.9.2(@types/node@22.9.3)(typescript@5.7.2)
       tsconfig: 7.0.0
       typescript: 5.7.2
     transitivePeerDependencies:
@@ -2015,14 +2035,14 @@ snapshots:
       - '@swc/wasm'
       - '@types/node'
 
-  ts-node@10.9.2(@types/node@22.9.1)(typescript@5.7.2):
+  ts-node@10.9.2(@types/node@22.9.3)(typescript@5.7.2):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
       '@tsconfig/node10': 1.0.11
       '@tsconfig/node12': 1.0.11
       '@tsconfig/node14': 1.0.3
       '@tsconfig/node16': 1.0.4
-      '@types/node': 22.9.1
+      '@types/node': 22.9.3
       acorn: 8.14.0
       acorn-walk: 8.3.4
       arg: 4.1.3
diff --git a/pi/server/src/server.ts b/pi/server/src/server.ts
index b41691e..377773f 100644
--- a/pi/server/src/server.ts
+++ b/pi/server/src/server.ts
@@ -3,11 +3,9 @@ import { createServer } from 'http';
 import { Server } from 'socket.io';
 import net from 'net';
 import { fileURLToPath } from 'url';
-import { dirname } from 'path';
-import path from 'path';
+import { resolve, join, dirname } from 'path';
 import cors from 'cors';
 
-
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = dirname(__filename);
 
@@ -17,38 +15,28 @@ const io = new Server(httpServer, {
   cors: { origin: '*' },
 });
 
-const PORT = 5000;
+const PORT = process.env.PORT || 5000;
+const TCP_PORT = process.env.TCP_PORT || 8080;
+
+const CONFIG_PATH = process.env.CONFIG_PATH || resolve(__dirname, '../../config');
+
 app.use(cors()); // Allow all origins
 
 app.get('/api/config', (req, res) => {
-  res.sendFile(path.join(__dirname, '../../config/anchorPositions.json'));
-});
-
-// Proxy to Vite dev server in development
-if (process.env.NODE_ENV === 'development') {
-  const { createProxyMiddleware } = await import('http-proxy-middleware');
+  const configPath = join(CONFIG_PATH, 'anchorPositions.json');
 
-  app.use(
-    '/',
-    createProxyMiddleware({
-      target: 'http://localhost:5173',
-      changeOrigin: true,
-      ws: true,
-    })
-  );
-} else {
-  // Serve static files in production
-  app.use(express.static(path.join(__dirname, '../../ui')));
-  app.get('/', (req, res) => {
-    res.sendFile(path.join(__dirname, '../../ui/index.html'));
+  res.sendFile(configPath, (err) => {
+    if (err) {
+      console.error('Error sending file:', err);
+      res.status(500).send('Error loading configuration file.');
+    }
   });
-}
+});
 
 io.on('connection', (socket) => {
   console.log('A client connected');
 });
 
-const TCP_PORT = 8080;
 const tcpServer = net.createServer((socket) => {
   console.log('UWB data source connected');
 
diff --git a/pi/server/tsconfig.json b/pi/server/tsconfig.json
index 8edc27d..a8c0293 100644
--- a/pi/server/tsconfig.json
+++ b/pi/server/tsconfig.json
@@ -10,8 +10,11 @@
     "forceConsistentCasingInFileNames": true,
     "strict": true,
     "noImplicitAny": true,
-    "skipLibCheck": true
+    "skipLibCheck": true,
+    "paths": {
+      "@src/*": ["./src/*"],
+    }
   },
   "include": ["src/**/*"],
   "exclude": ["node_modules", "dist"]
-}
+}
\ No newline at end of file
diff --git a/pi/ui/env.d.ts b/pi/ui/env.d.ts
index 81486fe..27abeaf 100644
--- a/pi/ui/env.d.ts
+++ b/pi/ui/env.d.ts
@@ -1,6 +1,8 @@
 // / <reference types="vite/client" />
 
 interface ImportMetaEnv {
+  readonly VITE_NODE_ENV: string;
+  readonly VITE_BACKEND_URL: string;
   readonly BASE_URL: string;
   readonly PROD: boolean
 }
diff --git a/pi/ui/nginx.conf b/pi/ui/nginx.conf
new file mode 100644
index 0000000..dbd9288
--- /dev/null
+++ b/pi/ui/nginx.conf
@@ -0,0 +1,63 @@
+user  nginx;
+worker_processes  1;
+error_log  /var/log/nginx/error.log warn;
+pid        /var/run/nginx.pid;
+
+events {
+  worker_connections  1024;
+}
+
+http {
+  include       /etc/nginx/mime.types;
+  default_type  application/octet-stream;
+
+  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+                    '$status $body_bytes_sent "$http_referer" '
+                    '"$http_user_agent" "$http_x_forwarded_for"';
+
+  access_log  /var/log/nginx/access.log  main;
+
+  sendfile        on;
+  keepalive_timeout  65;
+
+  server {
+    listen       80;
+    server_name  localhost;
+
+    location / {
+      root   /app;
+      index  index.html;
+      try_files $uri $uri/ /index.html;
+    }
+
+    location /api {
+      proxy_pass http://server:5000;
+      proxy_http_version 1.1;
+      proxy_set_header Upgrade $http_upgrade;
+      proxy_set_header Connection 'upgrade';
+      proxy_set_header Host $host;
+      proxy_cache_bypass $http_upgrade;
+      proxy_set_header X-Real-IP $remote_addr;
+      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+      proxy_set_header X-Forwarded-Proto $scheme;
+      access_log /var/log/nginx/api_access.log main;
+    }
+
+    location /ws/ {
+      proxy_pass http://server:5000;
+      proxy_http_version 1.1;
+      proxy_set_header Upgrade $http_upgrade;
+      proxy_set_header Connection "upgrade";
+      proxy_set_header Host $host;
+      proxy_set_header X-Real-IP $remote_addr;
+      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+      proxy_set_header X-Forwarded-Proto $scheme;
+      access_log /var/log/nginx/websocket_access.log main;
+    }
+
+    error_page   500 502 503 504  /50x.html;
+    location = /50x.html {
+      root   /usr/share/nginx/html;
+    }
+  }
+}
\ No newline at end of file
diff --git a/pi/ui/package.json b/pi/ui/package.json
index 1e48912..1d55e56 100644
--- a/pi/ui/package.json
+++ b/pi/ui/package.json
@@ -4,7 +4,7 @@
   "private": true,
   "type": "module",
   "scripts": {
-    "dev": "vite",
+    "dev": "NODE_ENV=development vite",
     "build": "run-p type-check \"build-only {@}\" --",
     "preview": "vite preview",
     "build-only": "vite build",
@@ -16,24 +16,24 @@
     "d3": "^7.9.0",
     "pinia": "^2.2.6",
     "socket.io-client": "^4.8.1",
-    "vue": "^3.5.12",
+    "vue": "^3.5.13",
     "vue-router": "^4.4.5"
   },
   "devDependencies": {
     "@tsconfig/node22": "^22.0.0",
     "@types/d3": "^7.4.3",
-    "@types/node": "^22.9.1",
-    "@vitejs/plugin-vue": "^5.1.4",
+    "@types/node": "^22.9.3",
+    "@vitejs/plugin-vue": "^5.2.0",
     "@vue/eslint-config-prettier": "^10.1.0",
     "@vue/eslint-config-typescript": "^14.1.3",
     "@vue/tsconfig": "^0.6.0",
-    "eslint": "^9.14.0",
-    "eslint-plugin-vue": "^9.30.0",
+    "eslint": "^9.15.0",
+    "eslint-plugin-vue": "^9.31.0",
     "npm-run-all2": "^7.0.1",
     "prettier": "^3.3.3",
     "typescript": "~5.6.3",
-    "vite": "^5.4.10",
-    "vite-plugin-vue-devtools": "^7.5.4",
+    "vite": "^5.4.11",
+    "vite-plugin-vue-devtools": "^7.6.4",
     "vue-tsc": "^2.1.10"
   },
   "packageManager": "pnpm@9.14.2"
diff --git a/pi/ui/pnpm-lock.yaml b/pi/ui/pnpm-lock.yaml
index 9845b25..2634cac 100644
--- a/pi/ui/pnpm-lock.yaml
+++ b/pi/ui/pnpm-lock.yaml
@@ -18,7 +18,7 @@ importers:
         specifier: ^4.8.1
         version: 4.8.1
       vue:
-        specifier: ^3.5.12
+        specifier: ^3.5.13
         version: 3.5.13(typescript@5.6.3)
       vue-router:
         specifier: ^4.4.5
@@ -31,11 +31,11 @@ importers:
         specifier: ^7.4.3
         version: 7.4.3
       '@types/node':
-        specifier: ^22.9.1
-        version: 22.9.1
+        specifier: ^22.9.3
+        version: 22.9.3
       '@vitejs/plugin-vue':
-        specifier: ^5.1.4
-        version: 5.2.0(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3))
+        specifier: ^5.2.0
+        version: 5.2.0(vite@5.4.11(@types/node@22.9.3))(vue@3.5.13(typescript@5.6.3))
       '@vue/eslint-config-prettier':
         specifier: ^10.1.0
         version: 10.1.0(eslint@9.15.0)(prettier@3.3.3)
@@ -46,10 +46,10 @@ importers:
         specifier: ^0.6.0
         version: 0.6.0(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3))
       eslint:
-        specifier: ^9.14.0
+        specifier: ^9.15.0
         version: 9.15.0
       eslint-plugin-vue:
-        specifier: ^9.30.0
+        specifier: ^9.31.0
         version: 9.31.0(eslint@9.15.0)
       npm-run-all2:
         specifier: ^7.0.1
@@ -61,11 +61,11 @@ importers:
         specifier: ~5.6.3
         version: 5.6.3
       vite:
-        specifier: ^5.4.10
-        version: 5.4.11(@types/node@22.9.1)
+        specifier: ^5.4.11
+        version: 5.4.11(@types/node@22.9.3)
       vite-plugin-vue-devtools:
-        specifier: ^7.5.4
-        version: 7.6.4(rollup@4.27.3)(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3))
+        specifier: ^7.6.4
+        version: 7.6.4(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.3))(vue@3.5.13(typescript@5.6.3))
       vue-tsc:
         specifier: ^2.1.10
         version: 2.1.10(typescript@5.6.3)
@@ -453,93 +453,93 @@ packages:
       rollup:
         optional: true
 
-  '@rollup/rollup-android-arm-eabi@4.27.3':
-    resolution: {integrity: sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ==}
+  '@rollup/rollup-android-arm-eabi@4.27.4':
+    resolution: {integrity: sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==}
     cpu: [arm]
     os: [android]
 
-  '@rollup/rollup-android-arm64@4.27.3':
-    resolution: {integrity: sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw==}
+  '@rollup/rollup-android-arm64@4.27.4':
+    resolution: {integrity: sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==}
     cpu: [arm64]
     os: [android]
 
-  '@rollup/rollup-darwin-arm64@4.27.3':
-    resolution: {integrity: sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA==}
+  '@rollup/rollup-darwin-arm64@4.27.4':
+    resolution: {integrity: sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==}
     cpu: [arm64]
     os: [darwin]
 
-  '@rollup/rollup-darwin-x64@4.27.3':
-    resolution: {integrity: sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg==}
+  '@rollup/rollup-darwin-x64@4.27.4':
+    resolution: {integrity: sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==}
     cpu: [x64]
     os: [darwin]
 
-  '@rollup/rollup-freebsd-arm64@4.27.3':
-    resolution: {integrity: sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw==}
+  '@rollup/rollup-freebsd-arm64@4.27.4':
+    resolution: {integrity: sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==}
     cpu: [arm64]
     os: [freebsd]
 
-  '@rollup/rollup-freebsd-x64@4.27.3':
-    resolution: {integrity: sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA==}
+  '@rollup/rollup-freebsd-x64@4.27.4':
+    resolution: {integrity: sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==}
     cpu: [x64]
     os: [freebsd]
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.27.3':
-    resolution: {integrity: sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q==}
+  '@rollup/rollup-linux-arm-gnueabihf@4.27.4':
+    resolution: {integrity: sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm-musleabihf@4.27.3':
-    resolution: {integrity: sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg==}
+  '@rollup/rollup-linux-arm-musleabihf@4.27.4':
+    resolution: {integrity: sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-gnu@4.27.3':
-    resolution: {integrity: sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ==}
+  '@rollup/rollup-linux-arm64-gnu@4.27.4':
+    resolution: {integrity: sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-musl@4.27.3':
-    resolution: {integrity: sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g==}
+  '@rollup/rollup-linux-arm64-musl@4.27.4':
+    resolution: {integrity: sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.27.3':
-    resolution: {integrity: sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw==}
+  '@rollup/rollup-linux-powerpc64le-gnu@4.27.4':
+    resolution: {integrity: sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==}
     cpu: [ppc64]
     os: [linux]
 
-  '@rollup/rollup-linux-riscv64-gnu@4.27.3':
-    resolution: {integrity: sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A==}
+  '@rollup/rollup-linux-riscv64-gnu@4.27.4':
+    resolution: {integrity: sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==}
     cpu: [riscv64]
     os: [linux]
 
-  '@rollup/rollup-linux-s390x-gnu@4.27.3':
-    resolution: {integrity: sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA==}
+  '@rollup/rollup-linux-s390x-gnu@4.27.4':
+    resolution: {integrity: sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==}
     cpu: [s390x]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-gnu@4.27.3':
-    resolution: {integrity: sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA==}
+  '@rollup/rollup-linux-x64-gnu@4.27.4':
+    resolution: {integrity: sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-musl@4.27.3':
-    resolution: {integrity: sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ==}
+  '@rollup/rollup-linux-x64-musl@4.27.4':
+    resolution: {integrity: sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-win32-arm64-msvc@4.27.3':
-    resolution: {integrity: sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw==}
+  '@rollup/rollup-win32-arm64-msvc@4.27.4':
+    resolution: {integrity: sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==}
     cpu: [arm64]
     os: [win32]
 
-  '@rollup/rollup-win32-ia32-msvc@4.27.3':
-    resolution: {integrity: sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w==}
+  '@rollup/rollup-win32-ia32-msvc@4.27.4':
+    resolution: {integrity: sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==}
     cpu: [ia32]
     os: [win32]
 
-  '@rollup/rollup-win32-x64-msvc@4.27.3':
-    resolution: {integrity: sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg==}
+  '@rollup/rollup-win32-x64-msvc@4.27.4':
+    resolution: {integrity: sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==}
     cpu: [x64]
     os: [win32]
 
@@ -651,8 +651,8 @@ packages:
   '@types/json-schema@7.0.15':
     resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
 
-  '@types/node@22.9.1':
-    resolution: {integrity: sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==}
+  '@types/node@22.9.3':
+    resolution: {integrity: sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==}
 
   '@typescript-eslint/eslint-plugin@8.15.0':
     resolution: {integrity: sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==}
@@ -1086,8 +1086,8 @@ packages:
   delaunator@5.0.1:
     resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
 
-  electron-to-chromium@1.5.63:
-    resolution: {integrity: sha512-ddeXKuY9BHo/mw145axlyWjlJ1UBt4WK3AlvkT7W2AbqfRQoacVoRUCF6wL3uIx/8wT9oLKXzI+rFqHHscByaA==}
+  electron-to-chromium@1.5.64:
+    resolution: {integrity: sha512-IXEuxU+5ClW2IGEYFC2T7szbyVgehupCWQe5GNh+H065CD6U6IFN0s4KeAMFGNmQolRU4IV7zGBWSYMmZ8uuqQ==}
 
   engine.io-client@6.6.2:
     resolution: {integrity: sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==}
@@ -1604,8 +1604,8 @@ packages:
   robust-predicates@3.0.2:
     resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
 
-  rollup@4.27.3:
-    resolution: {integrity: sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==}
+  rollup@4.27.4:
+    resolution: {integrity: sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
     hasBin: true
 
@@ -2237,66 +2237,66 @@ snapshots:
 
   '@polka/url@1.0.0-next.28': {}
 
-  '@rollup/pluginutils@5.1.3(rollup@4.27.3)':
+  '@rollup/pluginutils@5.1.3(rollup@4.27.4)':
     dependencies:
       '@types/estree': 1.0.6
       estree-walker: 2.0.2
       picomatch: 4.0.2
     optionalDependencies:
-      rollup: 4.27.3
+      rollup: 4.27.4
 
-  '@rollup/rollup-android-arm-eabi@4.27.3':
+  '@rollup/rollup-android-arm-eabi@4.27.4':
     optional: true
 
-  '@rollup/rollup-android-arm64@4.27.3':
+  '@rollup/rollup-android-arm64@4.27.4':
     optional: true
 
-  '@rollup/rollup-darwin-arm64@4.27.3':
+  '@rollup/rollup-darwin-arm64@4.27.4':
     optional: true
 
-  '@rollup/rollup-darwin-x64@4.27.3':
+  '@rollup/rollup-darwin-x64@4.27.4':
     optional: true
 
-  '@rollup/rollup-freebsd-arm64@4.27.3':
+  '@rollup/rollup-freebsd-arm64@4.27.4':
     optional: true
 
-  '@rollup/rollup-freebsd-x64@4.27.3':
+  '@rollup/rollup-freebsd-x64@4.27.4':
     optional: true
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.27.3':
+  '@rollup/rollup-linux-arm-gnueabihf@4.27.4':
     optional: true
 
-  '@rollup/rollup-linux-arm-musleabihf@4.27.3':
+  '@rollup/rollup-linux-arm-musleabihf@4.27.4':
     optional: true
 
-  '@rollup/rollup-linux-arm64-gnu@4.27.3':
+  '@rollup/rollup-linux-arm64-gnu@4.27.4':
     optional: true
 
-  '@rollup/rollup-linux-arm64-musl@4.27.3':
+  '@rollup/rollup-linux-arm64-musl@4.27.4':
     optional: true
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.27.3':
+  '@rollup/rollup-linux-powerpc64le-gnu@4.27.4':
     optional: true
 
-  '@rollup/rollup-linux-riscv64-gnu@4.27.3':
+  '@rollup/rollup-linux-riscv64-gnu@4.27.4':
     optional: true
 
-  '@rollup/rollup-linux-s390x-gnu@4.27.3':
+  '@rollup/rollup-linux-s390x-gnu@4.27.4':
     optional: true
 
-  '@rollup/rollup-linux-x64-gnu@4.27.3':
+  '@rollup/rollup-linux-x64-gnu@4.27.4':
     optional: true
 
-  '@rollup/rollup-linux-x64-musl@4.27.3':
+  '@rollup/rollup-linux-x64-musl@4.27.4':
     optional: true
 
-  '@rollup/rollup-win32-arm64-msvc@4.27.3':
+  '@rollup/rollup-win32-arm64-msvc@4.27.4':
     optional: true
 
-  '@rollup/rollup-win32-ia32-msvc@4.27.3':
+  '@rollup/rollup-win32-ia32-msvc@4.27.4':
     optional: true
 
-  '@rollup/rollup-win32-x64-msvc@4.27.3':
+  '@rollup/rollup-win32-x64-msvc@4.27.4':
     optional: true
 
   '@socket.io/component-emitter@3.1.2': {}
@@ -2426,7 +2426,7 @@ snapshots:
 
   '@types/json-schema@7.0.15': {}
 
-  '@types/node@22.9.1':
+  '@types/node@22.9.3':
     dependencies:
       undici-types: 6.19.8
 
@@ -2512,9 +2512,9 @@ snapshots:
       '@typescript-eslint/types': 8.15.0
       eslint-visitor-keys: 4.2.0
 
-  '@vitejs/plugin-vue@5.2.0(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3))':
+  '@vitejs/plugin-vue@5.2.0(vite@5.4.11(@types/node@22.9.3))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
-      vite: 5.4.11(@types/node@22.9.1)
+      vite: 5.4.11(@types/node@22.9.3)
       vue: 3.5.13(typescript@5.6.3)
 
   '@volar/language-core@2.4.10':
@@ -2596,14 +2596,14 @@ snapshots:
 
   '@vue/devtools-api@6.6.4': {}
 
-  '@vue/devtools-core@7.6.4(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3))':
+  '@vue/devtools-core@7.6.4(vite@5.4.11(@types/node@22.9.3))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
       '@vue/devtools-kit': 7.6.4
       '@vue/devtools-shared': 7.6.4
       mitt: 3.0.1
       nanoid: 3.3.7
       pathe: 1.1.2
-      vite-hot-client: 0.2.3(vite@5.4.11(@types/node@22.9.1))
+      vite-hot-client: 0.2.3(vite@5.4.11(@types/node@22.9.3))
       vue: 3.5.13(typescript@5.6.3)
     transitivePeerDependencies:
       - vite
@@ -2732,7 +2732,7 @@ snapshots:
   browserslist@4.24.2:
     dependencies:
       caniuse-lite: 1.0.30001683
-      electron-to-chromium: 1.5.63
+      electron-to-chromium: 1.5.64
       node-releases: 2.0.18
       update-browserslist-db: 1.1.1(browserslist@4.24.2)
 
@@ -2948,7 +2948,7 @@ snapshots:
     dependencies:
       robust-predicates: 3.0.2
 
-  electron-to-chromium@1.5.63: {}
+  electron-to-chromium@1.5.64: {}
 
   engine.io-client@6.6.2:
     dependencies:
@@ -3443,28 +3443,28 @@ snapshots:
 
   robust-predicates@3.0.2: {}
 
-  rollup@4.27.3:
+  rollup@4.27.4:
     dependencies:
       '@types/estree': 1.0.6
     optionalDependencies:
-      '@rollup/rollup-android-arm-eabi': 4.27.3
-      '@rollup/rollup-android-arm64': 4.27.3
-      '@rollup/rollup-darwin-arm64': 4.27.3
-      '@rollup/rollup-darwin-x64': 4.27.3
-      '@rollup/rollup-freebsd-arm64': 4.27.3
-      '@rollup/rollup-freebsd-x64': 4.27.3
-      '@rollup/rollup-linux-arm-gnueabihf': 4.27.3
-      '@rollup/rollup-linux-arm-musleabihf': 4.27.3
-      '@rollup/rollup-linux-arm64-gnu': 4.27.3
-      '@rollup/rollup-linux-arm64-musl': 4.27.3
-      '@rollup/rollup-linux-powerpc64le-gnu': 4.27.3
-      '@rollup/rollup-linux-riscv64-gnu': 4.27.3
-      '@rollup/rollup-linux-s390x-gnu': 4.27.3
-      '@rollup/rollup-linux-x64-gnu': 4.27.3
-      '@rollup/rollup-linux-x64-musl': 4.27.3
-      '@rollup/rollup-win32-arm64-msvc': 4.27.3
-      '@rollup/rollup-win32-ia32-msvc': 4.27.3
-      '@rollup/rollup-win32-x64-msvc': 4.27.3
+      '@rollup/rollup-android-arm-eabi': 4.27.4
+      '@rollup/rollup-android-arm64': 4.27.4
+      '@rollup/rollup-darwin-arm64': 4.27.4
+      '@rollup/rollup-darwin-x64': 4.27.4
+      '@rollup/rollup-freebsd-arm64': 4.27.4
+      '@rollup/rollup-freebsd-x64': 4.27.4
+      '@rollup/rollup-linux-arm-gnueabihf': 4.27.4
+      '@rollup/rollup-linux-arm-musleabihf': 4.27.4
+      '@rollup/rollup-linux-arm64-gnu': 4.27.4
+      '@rollup/rollup-linux-arm64-musl': 4.27.4
+      '@rollup/rollup-linux-powerpc64le-gnu': 4.27.4
+      '@rollup/rollup-linux-riscv64-gnu': 4.27.4
+      '@rollup/rollup-linux-s390x-gnu': 4.27.4
+      '@rollup/rollup-linux-x64-gnu': 4.27.4
+      '@rollup/rollup-linux-x64-musl': 4.27.4
+      '@rollup/rollup-win32-arm64-msvc': 4.27.4
+      '@rollup/rollup-win32-ia32-msvc': 4.27.4
+      '@rollup/rollup-win32-x64-msvc': 4.27.4
       fsevents: 2.3.3
 
   run-applescript@7.0.0: {}
@@ -3585,14 +3585,14 @@ snapshots:
 
   util-deprecate@1.0.2: {}
 
-  vite-hot-client@0.2.3(vite@5.4.11(@types/node@22.9.1)):
+  vite-hot-client@0.2.3(vite@5.4.11(@types/node@22.9.3)):
     dependencies:
-      vite: 5.4.11(@types/node@22.9.1)
+      vite: 5.4.11(@types/node@22.9.3)
 
-  vite-plugin-inspect@0.8.8(rollup@4.27.3)(vite@5.4.11(@types/node@22.9.1)):
+  vite-plugin-inspect@0.8.8(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.3)):
     dependencies:
       '@antfu/utils': 0.7.10
-      '@rollup/pluginutils': 5.1.3(rollup@4.27.3)
+      '@rollup/pluginutils': 5.1.3(rollup@4.27.4)
       debug: 4.3.7
       error-stack-parser-es: 0.1.5
       fs-extra: 11.2.0
@@ -3600,28 +3600,28 @@ snapshots:
       perfect-debounce: 1.0.0
       picocolors: 1.1.1
       sirv: 3.0.0
-      vite: 5.4.11(@types/node@22.9.1)
+      vite: 5.4.11(@types/node@22.9.3)
     transitivePeerDependencies:
       - rollup
       - supports-color
 
-  vite-plugin-vue-devtools@7.6.4(rollup@4.27.3)(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3)):
+  vite-plugin-vue-devtools@7.6.4(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.3))(vue@3.5.13(typescript@5.6.3)):
     dependencies:
-      '@vue/devtools-core': 7.6.4(vite@5.4.11(@types/node@22.9.1))(vue@3.5.13(typescript@5.6.3))
+      '@vue/devtools-core': 7.6.4(vite@5.4.11(@types/node@22.9.3))(vue@3.5.13(typescript@5.6.3))
       '@vue/devtools-kit': 7.6.4
       '@vue/devtools-shared': 7.6.4
       execa: 8.0.1
       sirv: 3.0.0
-      vite: 5.4.11(@types/node@22.9.1)
-      vite-plugin-inspect: 0.8.8(rollup@4.27.3)(vite@5.4.11(@types/node@22.9.1))
-      vite-plugin-vue-inspector: 5.2.0(vite@5.4.11(@types/node@22.9.1))
+      vite: 5.4.11(@types/node@22.9.3)
+      vite-plugin-inspect: 0.8.8(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.3))
+      vite-plugin-vue-inspector: 5.2.0(vite@5.4.11(@types/node@22.9.3))
     transitivePeerDependencies:
       - '@nuxt/kit'
       - rollup
       - supports-color
       - vue
 
-  vite-plugin-vue-inspector@5.2.0(vite@5.4.11(@types/node@22.9.1)):
+  vite-plugin-vue-inspector@5.2.0(vite@5.4.11(@types/node@22.9.3)):
     dependencies:
       '@babel/core': 7.26.0
       '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.0)
@@ -3632,17 +3632,17 @@ snapshots:
       '@vue/compiler-dom': 3.5.13
       kolorist: 1.8.0
       magic-string: 0.30.13
-      vite: 5.4.11(@types/node@22.9.1)
+      vite: 5.4.11(@types/node@22.9.3)
     transitivePeerDependencies:
       - supports-color
 
-  vite@5.4.11(@types/node@22.9.1):
+  vite@5.4.11(@types/node@22.9.3):
     dependencies:
       esbuild: 0.21.5
       postcss: 8.4.49
-      rollup: 4.27.3
+      rollup: 4.27.4
     optionalDependencies:
-      '@types/node': 22.9.1
+      '@types/node': 22.9.3
       fsevents: 2.3.3
 
   vscode-uri@3.0.8: {}
diff --git a/pi/ui/src/components/UWBVisualization.vue b/pi/ui/src/components/UWBVisualization.vue
index c6ffc0b..217ec07 100644
--- a/pi/ui/src/components/UWBVisualization.vue
+++ b/pi/ui/src/components/UWBVisualization.vue
@@ -4,18 +4,19 @@ import * as d3 from 'd3';
 import { Socket } from 'socket.io-client';
 
 interface AnchorData {
-  A: string; // Anchor ID
-  R: string; // Range
+  A: string // Anchor ID
+  R: string // Range
 }
 
 interface AnchorConfig {
-  anchors: string[];
-  distances: { [key: string]: number };
+  anchors: string[]
+  distances: { [key: string]: number }
 }
 
 export default defineComponent({
   name: 'UWBVisualization',
   setup() {
+    const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000';
     const socket = inject('socket') as Socket;
 
     const uwbData = ref<AnchorData[]>([]);
@@ -24,7 +25,7 @@ export default defineComponent({
     const anchorIDs = ref<string[]>([]);
 
     // D3 variables
-    let svg: d3.Selection<SVGSVGElement, unknown, HTMLElement, any>; // eslint-disable-line
+    let svg: d3.Selection<SVGSVGElement, unknown, HTMLElement, any> // eslint-disable-line
     const width = 600;
     const height = 400;
     const padding = 20; // Padding around the visualization
@@ -36,7 +37,7 @@ export default defineComponent({
     const scalingFactor = 50; // Adjust to control visualization size
 
     onMounted(() => {
-      fetch('/api/config')
+      fetch(`${ backendUrl }/api/config`)
         .then((response) => response.json())
         .then((configData: AnchorConfig) => {
           console.log('## configData:', configData);
@@ -72,7 +73,6 @@ export default defineComponent({
         return;
       }
 
-
       positions[ids[0]] = [0, 0]; // First anchor at (0, 0)
 
       const d1 = getDistance(ids[0], ids[1]) * scalingFactor;
@@ -92,11 +92,7 @@ export default defineComponent({
     }
 
     function getDistance(id1: string, id2: string): number {
-      return (
-        anchorDistances.value[`${ id1 }-${ id2 }`] ||
-        anchorDistances.value[`${ id2 }-${ id1 }`] ||
-        0
-      );
+      return anchorDistances.value[`${ id1 }-${ id2 }`] || anchorDistances.value[`${ id2 }-${ id1 }`] || 0;
     }
 
     function updateScales(tagPosition?: [number, number]) {
@@ -129,11 +125,13 @@ export default defineComponent({
       const anchors = Object.entries(anchorPositions.value) as [string, [number, number]][];
 
       // Bind data to anchor circles
-      const anchorCircles = svg.selectAll<SVGCircleElement, [string, [number, number]]>('.anchor')
+      const anchorCircles = svg
+        .selectAll<SVGCircleElement, [string, [number, number]]>('.anchor')
         .data(anchors, (d) => d[0]);
 
       // Enter selection
-      anchorCircles.enter()
+      anchorCircles
+        .enter()
         .append('circle')
         .attr('class', 'anchor')
         .attr('r', 5)
@@ -146,11 +144,13 @@ export default defineComponent({
       anchorCircles.exit().remove();
 
       // Bind data to anchor labels
-      const anchorLabels = svg.selectAll<SVGTextElement, [string, [number, number]]>('.anchor-label')
+      const anchorLabels = svg
+        .selectAll<SVGTextElement, [string, [number, number]]>('.anchor-label')
         .data(anchors, (d) => d[0]);
 
       // Enter selection
-      anchorLabels.enter()
+      anchorLabels
+        .enter()
         .append('text')
         .attr('class', 'anchor-label')
         .attr('font-size', '12px')
@@ -214,10 +214,9 @@ export default defineComponent({
       drawTag(tagX, tagY);
     }
 
-
     function calculateTagPosition(
       positions: [number, number][],
-      distances: number[]
+      distances: number[],
     ): [number, number] {
       if (positions.length < 3) {
         console.error('At least three anchors are required.');
@@ -279,7 +278,6 @@ export default defineComponent({
       }
     }
 
-
     return {};
   },
 });
diff --git a/pi/ui/src/main.ts b/pi/ui/src/main.ts
index ef2b5f2..57cfd44 100644
--- a/pi/ui/src/main.ts
+++ b/pi/ui/src/main.ts
@@ -9,7 +9,8 @@ import router from './router';
 
 const app = createApp(App);
 
-const socket: Socket = io('http://0.0.0.0:5000', { transports: ['websocket'] });
+const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000';
+const socket: Socket = io(backendUrl, { transports: ['websocket'] });
 
 app.provide('socket', socket);
 

From 365f81c015fc1444baef61c53fc814e6b0c781f0 Mon Sep 17 00:00:00 2001
From: jordojordo <jordonleach@gmail.com>
Date: Sat, 23 Nov 2024 10:10:44 -0500
Subject: [PATCH 15/34] Fix docker build context

---
 .github/workflows/build-lawndon.yaml | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/.github/workflows/build-lawndon.yaml b/.github/workflows/build-lawndon.yaml
index 4aa849b..fbe9f26 100644
--- a/.github/workflows/build-lawndon.yaml
+++ b/.github/workflows/build-lawndon.yaml
@@ -85,10 +85,9 @@ jobs:
 
       - name: Build and push server image
         uses: docker/build-push-action@v4
-        working-directory: ${{ env.WORKING_DIR }}
         with:
-          context: .
-          file: ./Dockerfile.server
+          context: ./pi
+          file: ./pi/Dockerfile.server
           push: true
           platforms: linux/amd64,linux/arm64
           tags: |
@@ -97,10 +96,9 @@ jobs:
 
       - name: Build and push UI image
         uses: docker/build-push-action@v4
-        working-directory: ${{ env.WORKING_DIR }}
         with:
-          context: .
-          file: ./Dockerfile.ui
+          context: ./pi
+          file: ./pi/Dockerfile.ui
           push: true
           platforms: linux/amd64,linux/arm64
           tags: |

From 007908a6845b1f70d10da0d335e2ffcaf5aa3333 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 23 Nov 2024 11:10:10 -0500
Subject: [PATCH 16/34] Update docker/build-push-action action to v6 (#23)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .github/workflows/build-lawndon.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/build-lawndon.yaml b/.github/workflows/build-lawndon.yaml
index fbe9f26..3cbe099 100644
--- a/.github/workflows/build-lawndon.yaml
+++ b/.github/workflows/build-lawndon.yaml
@@ -84,7 +84,7 @@ jobs:
           password: ${{ secrets.GITHUB_TOKEN }}
 
       - name: Build and push server image
-        uses: docker/build-push-action@v4
+        uses: docker/build-push-action@v6
         with:
           context: ./pi
           file: ./pi/Dockerfile.server
@@ -95,7 +95,7 @@ jobs:
             ghcr.io/${{ github.repository_owner }}/lawndon-pi-server:${{ github.sha }}
 
       - name: Build and push UI image
-        uses: docker/build-push-action@v4
+        uses: docker/build-push-action@v6
         with:
           context: ./pi
           file: ./pi/Dockerfile.ui

From 31b13ef0071cd0e156197eb737d3384ee57a2fcf Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sat, 23 Nov 2024 11:10:21 -0500
Subject: [PATCH 17/34] Update docker/login-action action to v3 (#24)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .github/workflows/build-lawndon.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/build-lawndon.yaml b/.github/workflows/build-lawndon.yaml
index 3cbe099..c66e320 100644
--- a/.github/workflows/build-lawndon.yaml
+++ b/.github/workflows/build-lawndon.yaml
@@ -77,7 +77,7 @@ jobs:
           install: true
 
       - name: Log in to GitHub Container Registry
-        uses: docker/login-action@v2
+        uses: docker/login-action@v3
         with:
           registry: ghcr.io
           username: ${{ github.actor }}

From 916203cb283aa693a597b888fe4f3fafa6d0da0b Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Sun, 24 Nov 2024 05:32:33 -0500
Subject: [PATCH 18/34] Move dependencies - update test badge (#25)

* Move dependencies - update test badge

* Switch test step to use docker/build-push-action

* Fix image tags

* Replace dependencies - Enable node_modules path for docker build

* Fix working directory
---
 .github/workflows/tests.yaml | 28 ++++++++++++++++++++++------
 README.md                    |  2 +-
 pi/Dockerfile.server         |  3 +--
 pi/Dockerfile.ui             |  9 ++++-----
 pi/server/package.json       |  1 -
 pi/server/pnpm-lock.yaml     | 11 -----------
 pi/ui/package.json           |  4 +---
 7 files changed, 29 insertions(+), 29 deletions(-)

diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 5a08d4b..2b55f66 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -79,12 +79,28 @@ jobs:
         run: |
           pnpm build:server
 
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+        with:
+          buildkitd-flags: --allow-insecure-entitlement security.insecure
+          install: true
+
       - name: Test Docker build for Server
-        working-directory: ${{ env.WORKING_DIR }}
-        run: |
-          docker build --no-cache --file ./Dockerfile.server --tag lawndon-pi-server-test .
+        uses: docker/build-push-action@v4
+        with:
+          context: ./pi
+          file: ./pi/Dockerfile.server
+          push: false
+          platforms: linux/arm64
+          tags: |
+            ghcr.io/${{ github.repository_owner }}/lawndon-pi-server:test
 
       - name: Test Docker build for UI
-        working-directory: ${{ env.WORKING_DIR }}
-        run: |
-          docker build --no-cache --file ./Dockerfile.ui --tag lawndon-pi-ui-test .
+        uses: docker/build-push-action@v4
+        with:
+          context: ./pi
+          file: ./pi/Dockerfile.ui
+          push: false
+          platforms: linux/arm64
+          tags: |
+            ghcr.io/${{ github.repository_owner }}/lawndon-pi-ui:test
diff --git a/README.md b/README.md
index fc38b52..03693fa 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[![Build Tests](https://github.com/jordojordo/lawndon-lite/actions/workflows/tests.yml/badge.svg?event=schedule)](https://github.com/jordojordo/lawndon-lite/actions/workflows/tests.yml)
+[![Build Tests](https://github.com/jordojordo/lawndon/actions/workflows/tests.yaml/badge.svg?event=schedule)](https://github.com/jordojordo/lawndon/actions/workflows/tests.yaml)
 
 # Lawndon Lite
 
diff --git a/pi/Dockerfile.server b/pi/Dockerfile.server
index 02becf6..21c8986 100644
--- a/pi/Dockerfile.server
+++ b/pi/Dockerfile.server
@@ -4,12 +4,11 @@ WORKDIR /usr/src/app
 
 ENV PNPM_HOME="/pnpm"
 ENV PATH="$PNPM_HOME:$PATH"
-ENV NODE_ENV=production
 ENV CONFIG_PATH=/usr/src/app/config
 
 RUN corepack enable
 COPY server/package*.json ./
-RUN pnpm install
+RUN pnpm install --prod=false
 
 COPY server ./server
 WORKDIR /usr/src/app/server
diff --git a/pi/Dockerfile.ui b/pi/Dockerfile.ui
index c05bd32..5e9ccbd 100644
--- a/pi/Dockerfile.ui
+++ b/pi/Dockerfile.ui
@@ -4,14 +4,13 @@ WORKDIR /usr/src/app
 
 ENV PNPM_HOME="/pnpm"
 ENV PATH="$PNPM_HOME:$PATH"
-ENV VITE_NODE_ENV=production
 
 RUN corepack enable
-COPY ui/package*.json ./
-RUN pnpm install
-
-COPY ui ./ui
+COPY ui/package.json ui/pnpm-lock.yaml ./ui/
 WORKDIR /usr/src/app/ui
+RUN pnpm install --prod=false
+
+COPY ui ./
 RUN pnpm build
 
 # Runtime stage
diff --git a/pi/server/package.json b/pi/server/package.json
index baef2a1..561ecd2 100644
--- a/pi/server/package.json
+++ b/pi/server/package.json
@@ -24,7 +24,6 @@
   },
   "devDependencies": {
     "@types/cors": "^2.8.17",
-    "@types/dotenv": "^8.2.3",
     "@types/express": "^5.0.0",
     "@types/node": "^22.9.3",
     "eslint": "^9.15.0",
diff --git a/pi/server/pnpm-lock.yaml b/pi/server/pnpm-lock.yaml
index c33915a..ec55e31 100644
--- a/pi/server/pnpm-lock.yaml
+++ b/pi/server/pnpm-lock.yaml
@@ -27,9 +27,6 @@ importers:
       '@types/cors':
         specifier: ^2.8.17
         version: 2.8.17
-      '@types/dotenv':
-        specifier: ^8.2.3
-        version: 8.2.3
       '@types/express':
         specifier: ^5.0.0
         version: 5.0.0
@@ -159,10 +156,6 @@ packages:
   '@types/cors@2.8.17':
     resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
 
-  '@types/dotenv@8.2.3':
-    resolution: {integrity: sha512-g2FXjlDX/cYuc5CiQvyU/6kkbP1JtmGzh0obW50zD7OKeILVL0NSpPWLXVfqoAGQjom2/SLLx9zHq0KXvD6mbw==}
-    deprecated: This is a stub types definition. dotenv provides its own type definitions, so you do not need this installed.
-
   '@types/estree@1.0.6':
     resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
 
@@ -1167,10 +1160,6 @@ snapshots:
     dependencies:
       '@types/node': 22.9.3
 
-  '@types/dotenv@8.2.3':
-    dependencies:
-      dotenv: 16.4.5
-
   '@types/estree@1.0.6': {}
 
   '@types/express-serve-static-core@5.0.1':
diff --git a/pi/ui/package.json b/pi/ui/package.json
index 1d55e56..6b89254 100644
--- a/pi/ui/package.json
+++ b/pi/ui/package.json
@@ -5,10 +5,8 @@
   "type": "module",
   "scripts": {
     "dev": "NODE_ENV=development vite",
-    "build": "run-p type-check \"build-only {@}\" --",
+    "build": "vue-tsc --build --force && vite build",
     "preview": "vite preview",
-    "build-only": "vite build",
-    "type-check": "vue-tsc --build --force",
     "lint": "eslint . --fix",
     "format": "prettier --write src/"
   },

From 6fdc1b9f76620f3cca14b260f3c9e38c0a34b4a4 Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Sun, 24 Nov 2024 05:46:45 -0500
Subject: [PATCH 19/34] Bump buildx action - fix server dockerfile (#27)

---
 .github/workflows/tests.yaml | 4 ++--
 pi/Dockerfile.server         | 6 +++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 2b55f66..1e74692 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -86,7 +86,7 @@ jobs:
           install: true
 
       - name: Test Docker build for Server
-        uses: docker/build-push-action@v4
+        uses: docker/build-push-action@v6
         with:
           context: ./pi
           file: ./pi/Dockerfile.server
@@ -96,7 +96,7 @@ jobs:
             ghcr.io/${{ github.repository_owner }}/lawndon-pi-server:test
 
       - name: Test Docker build for UI
-        uses: docker/build-push-action@v4
+        uses: docker/build-push-action@v6
         with:
           context: ./pi
           file: ./pi/Dockerfile.ui
diff --git a/pi/Dockerfile.server b/pi/Dockerfile.server
index 21c8986..ca61270 100644
--- a/pi/Dockerfile.server
+++ b/pi/Dockerfile.server
@@ -7,11 +7,11 @@ ENV PATH="$PNPM_HOME:$PATH"
 ENV CONFIG_PATH=/usr/src/app/config
 
 RUN corepack enable
-COPY server/package*.json ./
+COPY server/package.json server/pnpm-lock.yaml ./server/
+WORKDIR /usr/src/app/server
 RUN pnpm install --prod=false
 
-COPY server ./server
-WORKDIR /usr/src/app/server
+COPY server ./
 RUN pnpm build
 
 # Runtime stage

From cdeb29bf6a7ec9272779ceff8161f7e8593b21fd Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 25 Nov 2024 10:18:07 -0500
Subject: [PATCH 20/34] Update dependency @vue/eslint-config-typescript to
 v14.1.4 (#28)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 pi/ui/pnpm-lock.yaml | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/pi/ui/pnpm-lock.yaml b/pi/ui/pnpm-lock.yaml
index 2634cac..72c03da 100644
--- a/pi/ui/pnpm-lock.yaml
+++ b/pi/ui/pnpm-lock.yaml
@@ -41,7 +41,7 @@ importers:
         version: 10.1.0(eslint@9.15.0)(prettier@3.3.3)
       '@vue/eslint-config-typescript':
         specifier: ^14.1.3
-        version: 14.1.3(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint-plugin-vue@9.31.0(eslint@9.15.0))(eslint@9.15.0)(typescript@5.6.3)
+        version: 14.1.4(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint-plugin-vue@9.31.0(eslint@9.15.0))(eslint@9.15.0)(typescript@5.6.3)
       '@vue/tsconfig':
         specifier: ^0.6.0
         version: 0.6.0(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3))
@@ -783,8 +783,8 @@ packages:
       eslint: '>= 8.21.0'
       prettier: '>= 3.0.0'
 
-  '@vue/eslint-config-typescript@14.1.3':
-    resolution: {integrity: sha512-L4NUJQz/0We2QYtrNwRAGRy4KfpOagl5V3MpZZ+rQ51a+bKjlKYYrugi7lp7PIX8LolRgu06ZwDoswnSGWnAmA==}
+  '@vue/eslint-config-typescript@14.1.4':
+    resolution: {integrity: sha512-NcG1adLFde+t+TCaXlL38PHuZlBEuwDahgrPVyB052m9QeHOswVIAplMD2cXgH8vXieAVNF1+mXvyilpIO3+kg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^9.10.0
@@ -1697,8 +1697,8 @@ packages:
     resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
     engines: {node: '>=6'}
 
-  ts-api-utils@1.4.0:
-    resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==}
+  ts-api-utils@1.4.1:
+    resolution: {integrity: sha512-5RU2/lxTA3YUZxju61HO2U6EoZLvBLtmV2mbTvqyu4a/7s7RmJPT+1YekhMVsQhznRWk/czIwDUg+V8Q9ZuG4w==}
     engines: {node: '>=16'}
     peerDependencies:
       typescript: '>=4.2.0'
@@ -2442,7 +2442,7 @@ snapshots:
       graphemer: 1.4.0
       ignore: 5.3.2
       natural-compare: 1.4.0
-      ts-api-utils: 1.4.0(typescript@5.6.3)
+      ts-api-utils: 1.4.1(typescript@5.6.3)
     optionalDependencies:
       typescript: 5.6.3
     transitivePeerDependencies:
@@ -2472,7 +2472,7 @@ snapshots:
       '@typescript-eslint/utils': 8.15.0(eslint@9.15.0)(typescript@5.6.3)
       debug: 4.3.7
       eslint: 9.15.0
-      ts-api-utils: 1.4.0(typescript@5.6.3)
+      ts-api-utils: 1.4.1(typescript@5.6.3)
     optionalDependencies:
       typescript: 5.6.3
     transitivePeerDependencies:
@@ -2489,7 +2489,7 @@ snapshots:
       is-glob: 4.0.3
       minimatch: 9.0.5
       semver: 7.6.3
-      ts-api-utils: 1.4.0(typescript@5.6.3)
+      ts-api-utils: 1.4.1(typescript@5.6.3)
     optionalDependencies:
       typescript: 5.6.3
     transitivePeerDependencies:
@@ -2631,7 +2631,7 @@ snapshots:
     transitivePeerDependencies:
       - '@types/eslint'
 
-  '@vue/eslint-config-typescript@14.1.3(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint-plugin-vue@9.31.0(eslint@9.15.0))(eslint@9.15.0)(typescript@5.6.3)':
+  '@vue/eslint-config-typescript@14.1.4(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint-plugin-vue@9.31.0(eslint@9.15.0))(eslint@9.15.0)(typescript@5.6.3)':
     dependencies:
       '@typescript-eslint/eslint-plugin': 8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3)
       eslint: 9.15.0
@@ -3544,7 +3544,7 @@ snapshots:
 
   totalist@3.0.1: {}
 
-  ts-api-utils@1.4.0(typescript@5.6.3):
+  ts-api-utils@1.4.1(typescript@5.6.3):
     dependencies:
       typescript: 5.6.3
 

From 5560406000d1c2fa1f68a70c13d218e90e46de2c Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Mon, 25 Nov 2024 10:22:55 -0500
Subject: [PATCH 21/34] Update dependency vue-router to v4.5.0 (#29)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 pi/ui/pnpm-lock.yaml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pi/ui/pnpm-lock.yaml b/pi/ui/pnpm-lock.yaml
index 72c03da..96b52a8 100644
--- a/pi/ui/pnpm-lock.yaml
+++ b/pi/ui/pnpm-lock.yaml
@@ -22,7 +22,7 @@ importers:
         version: 3.5.13(typescript@5.6.3)
       vue-router:
         specifier: ^4.4.5
-        version: 4.4.5(vue@3.5.13(typescript@5.6.3))
+        version: 4.5.0(vue@3.5.13(typescript@5.6.3))
     devDependencies:
       '@tsconfig/node22':
         specifier: ^22.0.0
@@ -1825,8 +1825,8 @@ packages:
     peerDependencies:
       eslint: '>=6.0.0'
 
-  vue-router@4.4.5:
-    resolution: {integrity: sha512-4fKZygS8cH1yCyuabAXGUAsyi1b2/o/OKgu/RUb+znIYOxPRxdkytJEx+0wGcpBE1pX6vUgh5jwWOKRGvuA/7Q==}
+  vue-router@4.5.0:
+    resolution: {integrity: sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==}
     peerDependencies:
       vue: ^3.2.0
 
@@ -3664,7 +3664,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  vue-router@4.4.5(vue@3.5.13(typescript@5.6.3)):
+  vue-router@4.5.0(vue@3.5.13(typescript@5.6.3)):
     dependencies:
       '@vue/devtools-api': 6.6.4
       vue: 3.5.13(typescript@5.6.3)

From c2eed370a9289a87c86525d509485c62e1e10ebb Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 26 Nov 2024 04:35:48 -0500
Subject: [PATCH 22/34] Update dependency @types/node to v22.9.4 (#21)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 pi/server/pnpm-lock.yaml | 36 +++++++++++++++---------------
 pi/ui/pnpm-lock.yaml     | 48 ++++++++++++++++++++--------------------
 2 files changed, 42 insertions(+), 42 deletions(-)

diff --git a/pi/server/pnpm-lock.yaml b/pi/server/pnpm-lock.yaml
index ec55e31..3ccd431 100644
--- a/pi/server/pnpm-lock.yaml
+++ b/pi/server/pnpm-lock.yaml
@@ -32,7 +32,7 @@ importers:
         version: 5.0.0
       '@types/node':
         specifier: ^22.9.3
-        version: 22.9.3
+        version: 22.9.4
       eslint:
         specifier: ^9.15.0
         version: 9.15.0
@@ -47,10 +47,10 @@ importers:
         version: 3.1.7
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.9.3)(typescript@5.7.2)
+        version: 10.9.2(@types/node@22.9.4)(typescript@5.7.2)
       ts-node-dev:
         specifier: ^2.0.0
-        version: 2.0.0(@types/node@22.9.3)(typescript@5.7.2)
+        version: 2.0.0(@types/node@22.9.4)(typescript@5.7.2)
       typescript:
         specifier: ^5.7.2
         version: 5.7.2
@@ -177,8 +177,8 @@ packages:
   '@types/mime@1.3.5':
     resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
 
-  '@types/node@22.9.3':
-    resolution: {integrity: sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==}
+  '@types/node@22.9.4':
+    resolution: {integrity: sha512-d9RWfoR7JC/87vj7n+PVTzGg9hDyuFjir3RxUHbjFSKNd9mpxbxwMEyaCim/ddCmy4IuW7HjTzF3g9p3EtWEOg==}
 
   '@types/qs@6.9.17':
     resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==}
@@ -1148,23 +1148,23 @@ snapshots:
   '@types/body-parser@1.19.5':
     dependencies:
       '@types/connect': 3.4.38
-      '@types/node': 22.9.3
+      '@types/node': 22.9.4
 
   '@types/connect@3.4.38':
     dependencies:
-      '@types/node': 22.9.3
+      '@types/node': 22.9.4
 
   '@types/cookie@0.4.1': {}
 
   '@types/cors@2.8.17':
     dependencies:
-      '@types/node': 22.9.3
+      '@types/node': 22.9.4
 
   '@types/estree@1.0.6': {}
 
   '@types/express-serve-static-core@5.0.1':
     dependencies:
-      '@types/node': 22.9.3
+      '@types/node': 22.9.4
       '@types/qs': 6.9.17
       '@types/range-parser': 1.2.7
       '@types/send': 0.17.4
@@ -1180,13 +1180,13 @@ snapshots:
 
   '@types/http-proxy@1.17.15':
     dependencies:
-      '@types/node': 22.9.3
+      '@types/node': 22.9.4
 
   '@types/json-schema@7.0.15': {}
 
   '@types/mime@1.3.5': {}
 
-  '@types/node@22.9.3':
+  '@types/node@22.9.4':
     dependencies:
       undici-types: 6.19.8
 
@@ -1197,12 +1197,12 @@ snapshots:
   '@types/send@0.17.4':
     dependencies:
       '@types/mime': 1.3.5
-      '@types/node': 22.9.3
+      '@types/node': 22.9.4
 
   '@types/serve-static@1.15.7':
     dependencies:
       '@types/http-errors': 2.0.4
-      '@types/node': 22.9.3
+      '@types/node': 22.9.4
       '@types/send': 0.17.4
 
   '@types/strip-bom@3.0.0': {}
@@ -1388,7 +1388,7 @@ snapshots:
     dependencies:
       '@types/cookie': 0.4.1
       '@types/cors': 2.8.17
-      '@types/node': 22.9.3
+      '@types/node': 22.9.4
       accepts: 1.3.8
       base64id: 2.0.0
       cookie: 0.7.2
@@ -2006,7 +2006,7 @@ snapshots:
 
   tree-kill@1.2.2: {}
 
-  ts-node-dev@2.0.0(@types/node@22.9.3)(typescript@5.7.2):
+  ts-node-dev@2.0.0(@types/node@22.9.4)(typescript@5.7.2):
     dependencies:
       chokidar: 3.6.0
       dynamic-dedupe: 0.3.0
@@ -2016,7 +2016,7 @@ snapshots:
       rimraf: 2.7.1
       source-map-support: 0.5.21
       tree-kill: 1.2.2
-      ts-node: 10.9.2(@types/node@22.9.3)(typescript@5.7.2)
+      ts-node: 10.9.2(@types/node@22.9.4)(typescript@5.7.2)
       tsconfig: 7.0.0
       typescript: 5.7.2
     transitivePeerDependencies:
@@ -2024,14 +2024,14 @@ snapshots:
       - '@swc/wasm'
       - '@types/node'
 
-  ts-node@10.9.2(@types/node@22.9.3)(typescript@5.7.2):
+  ts-node@10.9.2(@types/node@22.9.4)(typescript@5.7.2):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
       '@tsconfig/node10': 1.0.11
       '@tsconfig/node12': 1.0.11
       '@tsconfig/node14': 1.0.3
       '@tsconfig/node16': 1.0.4
-      '@types/node': 22.9.3
+      '@types/node': 22.9.4
       acorn: 8.14.0
       acorn-walk: 8.3.4
       arg: 4.1.3
diff --git a/pi/ui/pnpm-lock.yaml b/pi/ui/pnpm-lock.yaml
index 96b52a8..8db4a18 100644
--- a/pi/ui/pnpm-lock.yaml
+++ b/pi/ui/pnpm-lock.yaml
@@ -32,10 +32,10 @@ importers:
         version: 7.4.3
       '@types/node':
         specifier: ^22.9.3
-        version: 22.9.3
+        version: 22.9.4
       '@vitejs/plugin-vue':
         specifier: ^5.2.0
-        version: 5.2.0(vite@5.4.11(@types/node@22.9.3))(vue@3.5.13(typescript@5.6.3))
+        version: 5.2.0(vite@5.4.11(@types/node@22.9.4))(vue@3.5.13(typescript@5.6.3))
       '@vue/eslint-config-prettier':
         specifier: ^10.1.0
         version: 10.1.0(eslint@9.15.0)(prettier@3.3.3)
@@ -62,10 +62,10 @@ importers:
         version: 5.6.3
       vite:
         specifier: ^5.4.11
-        version: 5.4.11(@types/node@22.9.3)
+        version: 5.4.11(@types/node@22.9.4)
       vite-plugin-vue-devtools:
         specifier: ^7.6.4
-        version: 7.6.4(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.3))(vue@3.5.13(typescript@5.6.3))
+        version: 7.6.4(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.4))(vue@3.5.13(typescript@5.6.3))
       vue-tsc:
         specifier: ^2.1.10
         version: 2.1.10(typescript@5.6.3)
@@ -651,8 +651,8 @@ packages:
   '@types/json-schema@7.0.15':
     resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
 
-  '@types/node@22.9.3':
-    resolution: {integrity: sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==}
+  '@types/node@22.9.4':
+    resolution: {integrity: sha512-d9RWfoR7JC/87vj7n+PVTzGg9hDyuFjir3RxUHbjFSKNd9mpxbxwMEyaCim/ddCmy4IuW7HjTzF3g9p3EtWEOg==}
 
   '@typescript-eslint/eslint-plugin@8.15.0':
     resolution: {integrity: sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==}
@@ -2426,7 +2426,7 @@ snapshots:
 
   '@types/json-schema@7.0.15': {}
 
-  '@types/node@22.9.3':
+  '@types/node@22.9.4':
     dependencies:
       undici-types: 6.19.8
 
@@ -2512,9 +2512,9 @@ snapshots:
       '@typescript-eslint/types': 8.15.0
       eslint-visitor-keys: 4.2.0
 
-  '@vitejs/plugin-vue@5.2.0(vite@5.4.11(@types/node@22.9.3))(vue@3.5.13(typescript@5.6.3))':
+  '@vitejs/plugin-vue@5.2.0(vite@5.4.11(@types/node@22.9.4))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
-      vite: 5.4.11(@types/node@22.9.3)
+      vite: 5.4.11(@types/node@22.9.4)
       vue: 3.5.13(typescript@5.6.3)
 
   '@volar/language-core@2.4.10':
@@ -2596,14 +2596,14 @@ snapshots:
 
   '@vue/devtools-api@6.6.4': {}
 
-  '@vue/devtools-core@7.6.4(vite@5.4.11(@types/node@22.9.3))(vue@3.5.13(typescript@5.6.3))':
+  '@vue/devtools-core@7.6.4(vite@5.4.11(@types/node@22.9.4))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
       '@vue/devtools-kit': 7.6.4
       '@vue/devtools-shared': 7.6.4
       mitt: 3.0.1
       nanoid: 3.3.7
       pathe: 1.1.2
-      vite-hot-client: 0.2.3(vite@5.4.11(@types/node@22.9.3))
+      vite-hot-client: 0.2.3(vite@5.4.11(@types/node@22.9.4))
       vue: 3.5.13(typescript@5.6.3)
     transitivePeerDependencies:
       - vite
@@ -3585,11 +3585,11 @@ snapshots:
 
   util-deprecate@1.0.2: {}
 
-  vite-hot-client@0.2.3(vite@5.4.11(@types/node@22.9.3)):
+  vite-hot-client@0.2.3(vite@5.4.11(@types/node@22.9.4)):
     dependencies:
-      vite: 5.4.11(@types/node@22.9.3)
+      vite: 5.4.11(@types/node@22.9.4)
 
-  vite-plugin-inspect@0.8.8(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.3)):
+  vite-plugin-inspect@0.8.8(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.4)):
     dependencies:
       '@antfu/utils': 0.7.10
       '@rollup/pluginutils': 5.1.3(rollup@4.27.4)
@@ -3600,28 +3600,28 @@ snapshots:
       perfect-debounce: 1.0.0
       picocolors: 1.1.1
       sirv: 3.0.0
-      vite: 5.4.11(@types/node@22.9.3)
+      vite: 5.4.11(@types/node@22.9.4)
     transitivePeerDependencies:
       - rollup
       - supports-color
 
-  vite-plugin-vue-devtools@7.6.4(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.3))(vue@3.5.13(typescript@5.6.3)):
+  vite-plugin-vue-devtools@7.6.4(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.4))(vue@3.5.13(typescript@5.6.3)):
     dependencies:
-      '@vue/devtools-core': 7.6.4(vite@5.4.11(@types/node@22.9.3))(vue@3.5.13(typescript@5.6.3))
+      '@vue/devtools-core': 7.6.4(vite@5.4.11(@types/node@22.9.4))(vue@3.5.13(typescript@5.6.3))
       '@vue/devtools-kit': 7.6.4
       '@vue/devtools-shared': 7.6.4
       execa: 8.0.1
       sirv: 3.0.0
-      vite: 5.4.11(@types/node@22.9.3)
-      vite-plugin-inspect: 0.8.8(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.3))
-      vite-plugin-vue-inspector: 5.2.0(vite@5.4.11(@types/node@22.9.3))
+      vite: 5.4.11(@types/node@22.9.4)
+      vite-plugin-inspect: 0.8.8(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.4))
+      vite-plugin-vue-inspector: 5.2.0(vite@5.4.11(@types/node@22.9.4))
     transitivePeerDependencies:
       - '@nuxt/kit'
       - rollup
       - supports-color
       - vue
 
-  vite-plugin-vue-inspector@5.2.0(vite@5.4.11(@types/node@22.9.3)):
+  vite-plugin-vue-inspector@5.2.0(vite@5.4.11(@types/node@22.9.4)):
     dependencies:
       '@babel/core': 7.26.0
       '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.0)
@@ -3632,17 +3632,17 @@ snapshots:
       '@vue/compiler-dom': 3.5.13
       kolorist: 1.8.0
       magic-string: 0.30.13
-      vite: 5.4.11(@types/node@22.9.3)
+      vite: 5.4.11(@types/node@22.9.4)
     transitivePeerDependencies:
       - supports-color
 
-  vite@5.4.11(@types/node@22.9.3):
+  vite@5.4.11(@types/node@22.9.4):
     dependencies:
       esbuild: 0.21.5
       postcss: 8.4.49
       rollup: 4.27.4
     optionalDependencies:
-      '@types/node': 22.9.3
+      '@types/node': 22.9.4
       fsevents: 2.3.3
 
   vscode-uri@3.0.8: {}

From c80c6e60161df060e219a089850614e0ab246e3d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 26 Nov 2024 04:36:14 -0500
Subject: [PATCH 23/34] Update dependency prettier to v3.4.0 (#30)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 pi/ui/pnpm-lock.yaml | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/pi/ui/pnpm-lock.yaml b/pi/ui/pnpm-lock.yaml
index 8db4a18..ed26d91 100644
--- a/pi/ui/pnpm-lock.yaml
+++ b/pi/ui/pnpm-lock.yaml
@@ -38,7 +38,7 @@ importers:
         version: 5.2.0(vite@5.4.11(@types/node@22.9.4))(vue@3.5.13(typescript@5.6.3))
       '@vue/eslint-config-prettier':
         specifier: ^10.1.0
-        version: 10.1.0(eslint@9.15.0)(prettier@3.3.3)
+        version: 10.1.0(eslint@9.15.0)(prettier@3.4.0)
       '@vue/eslint-config-typescript':
         specifier: ^14.1.3
         version: 14.1.4(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint-plugin-vue@9.31.0(eslint@9.15.0))(eslint@9.15.0)(typescript@5.6.3)
@@ -56,7 +56,7 @@ importers:
         version: 7.0.1
       prettier:
         specifier: ^3.3.3
-        version: 3.3.3
+        version: 3.4.0
       typescript:
         specifier: ~5.6.3
         version: 5.6.3
@@ -1574,8 +1574,8 @@ packages:
     resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
     engines: {node: '>=6.0.0'}
 
-  prettier@3.3.3:
-    resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==}
+  prettier@3.4.0:
+    resolution: {integrity: sha512-/OXNZcLyWkfo13ofOW5M7SLh+k5pnIs07owXK2teFpnfaOEcycnSy7HQxldaVX1ZP/7Q8oO1eDuQJNwbomQq5Q==}
     engines: {node: '>=14'}
     hasBin: true
 
@@ -2622,12 +2622,12 @@ snapshots:
     dependencies:
       rfdc: 1.4.1
 
-  '@vue/eslint-config-prettier@10.1.0(eslint@9.15.0)(prettier@3.3.3)':
+  '@vue/eslint-config-prettier@10.1.0(eslint@9.15.0)(prettier@3.4.0)':
     dependencies:
       eslint: 9.15.0
       eslint-config-prettier: 9.1.0(eslint@9.15.0)
-      eslint-plugin-prettier: 5.2.1(eslint-config-prettier@9.1.0(eslint@9.15.0))(eslint@9.15.0)(prettier@3.3.3)
-      prettier: 3.3.3
+      eslint-plugin-prettier: 5.2.1(eslint-config-prettier@9.1.0(eslint@9.15.0))(eslint@9.15.0)(prettier@3.4.0)
+      prettier: 3.4.0
     transitivePeerDependencies:
       - '@types/eslint'
 
@@ -3002,10 +3002,10 @@ snapshots:
     dependencies:
       eslint: 9.15.0
 
-  eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.15.0))(eslint@9.15.0)(prettier@3.3.3):
+  eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.15.0))(eslint@9.15.0)(prettier@3.4.0):
     dependencies:
       eslint: 9.15.0
-      prettier: 3.3.3
+      prettier: 3.4.0
       prettier-linter-helpers: 1.0.0
       synckit: 0.9.2
     optionalDependencies:
@@ -3424,7 +3424,7 @@ snapshots:
     dependencies:
       fast-diff: 1.3.0
 
-  prettier@3.3.3: {}
+  prettier@3.4.0: {}
 
   punycode@2.3.1: {}
 

From 25204fdcdb0065c8f04af9eef1ff9ee6bf3e3c25 Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Tue, 26 Nov 2024 04:50:09 -0500
Subject: [PATCH 24/34] Fix prod socket and api routes for UI (#32)

---
 .github/workflows/build-lawndon.yaml      |   2 +
 pi/Dockerfile.ui                          |   3 +-
 pi/docker-compose.yaml                    |   4 +-
 pi/package.sh                             |  40 ----
 pi/server/package.json                    |   2 +-
 pi/server/pnpm-lock.yaml                  |  54 +++---
 pi/ui/.env                                |   1 +
 pi/ui/env.d.ts                            |   3 +-
 pi/ui/nginx.conf                          |  22 ++-
 pi/ui/package.json                        |  10 +-
 pi/ui/pnpm-lock.yaml                      | 223 +++++++++++-----------
 pi/ui/src/components/UWBVisualization.vue |   2 +-
 pi/ui/src/main.ts                         |   2 +-
 pi/ui/vite.config.ts                      |  26 ++-
 14 files changed, 197 insertions(+), 197 deletions(-)
 delete mode 100755 pi/package.sh
 create mode 100644 pi/ui/.env

diff --git a/.github/workflows/build-lawndon.yaml b/.github/workflows/build-lawndon.yaml
index c66e320..5cf77df 100644
--- a/.github/workflows/build-lawndon.yaml
+++ b/.github/workflows/build-lawndon.yaml
@@ -93,6 +93,7 @@ jobs:
           tags: |
             ghcr.io/${{ github.repository_owner }}/lawndon-pi-server:${{ github.ref_name }}
             ghcr.io/${{ github.repository_owner }}/lawndon-pi-server:${{ github.sha }}
+            ghcr.io/${{ github.repository_owner }}/lawndon-pi-server:latest
 
       - name: Build and push UI image
         uses: docker/build-push-action@v6
@@ -104,3 +105,4 @@ jobs:
           tags: |
             ghcr.io/${{ github.repository_owner }}/lawndon-pi-ui:${{ github.ref_name }}
             ghcr.io/${{ github.repository_owner }}/lawndon-pi-ui:${{ github.sha }}
+            ghcr.io/${{ github.repository_owner }}/lawndon-pi-ui:latest
diff --git a/pi/Dockerfile.ui b/pi/Dockerfile.ui
index 5e9ccbd..2a3d982 100644
--- a/pi/Dockerfile.ui
+++ b/pi/Dockerfile.ui
@@ -15,7 +15,8 @@ RUN pnpm build
 
 # Runtime stage
 FROM nginx:alpine
-COPY --from=build /usr/src/app/ui/dist /usr/share/nginx/html
+COPY --from=build /usr/src/app/ui/dist /app
+COPY --from=build /usr/src/app/ui/nginx.conf /etc/nginx/nginx.conf
 
 EXPOSE 80
 CMD ["nginx", "-g", "daemon off;"]
diff --git a/pi/docker-compose.yaml b/pi/docker-compose.yaml
index e92adc4..f9369c5 100644
--- a/pi/docker-compose.yaml
+++ b/pi/docker-compose.yaml
@@ -13,9 +13,7 @@ services:
       - app_network
 
   ui:
-    image: ghcr.io/jordojordo/uwb-ui:latest
-    environment:
-      VITE_NODE_ENV: production
+    image: ghcr.io/jordojordo/lawndon-pi-ui:latest
     ports:
       - "80:80" # Expose UI
     depends_on:
diff --git a/pi/package.sh b/pi/package.sh
deleted file mode 100755
index 0f33b8b..0000000
--- a/pi/package.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/bin/bash
-
-set -e
-
-PROJECT_NAME="lawndon-pi"
-TARGET_ARCHIVE="$PROJECT_NAME.tar.gz"
-PI_USER="pi"
-PI_HOST="192.168.12.1"
-PI_PATH="/home/pi"
-
-echo "Starting the build and packaging process"
-
-echo "Cleaning up old builds"
-rm -rf $PROJECT_NAME $TARGET_ARCHIVE
-
-echo "Building UI"
-pnpm install:ui
-pnpm build:ui
-
-echo "Building server"
-pnpm install:server
-pnpm build:server
-
-echo "Preparing the package directory"
-mkdir $PROJECT_NAME
-cp -r server/dist $PROJECT_NAME/server
-cp -r server/node_modules $PROJECT_NAME/server
-cp -r ui/dist $PROJECT_NAME/ui
-cp -r config $PROJECT_NAME/
-cp package.json $PROJECT_NAME/
-
-echo "Creating the tar.gz archive"
-tar -czf $TARGET_ARCHIVE $PROJECT_NAME
-
-echo "Build and transfer complete"
-echo "To run the app on the Raspberry Pi:"
-echo "1. SSH into your Raspberry Pi: ssh $PI_USER@$PI_HOST"
-echo "2. Unpack the archive: tar -xzf $TARGET_ARCHIVE"
-echo "3. Navigate to the project: cd $PROJECT_NAME"
-echo "4. Start the backend server: node server/server.js"
diff --git a/pi/server/package.json b/pi/server/package.json
index 561ecd2..6844c56 100644
--- a/pi/server/package.json
+++ b/pi/server/package.json
@@ -25,7 +25,7 @@
   "devDependencies": {
     "@types/cors": "^2.8.17",
     "@types/express": "^5.0.0",
-    "@types/node": "^22.9.3",
+    "@types/node": "^22.10.0",
     "eslint": "^9.15.0",
     "globals": "^15.12.0",
     "http-proxy-middleware": "^3.0.3",
diff --git a/pi/server/pnpm-lock.yaml b/pi/server/pnpm-lock.yaml
index 3ccd431..77f056b 100644
--- a/pi/server/pnpm-lock.yaml
+++ b/pi/server/pnpm-lock.yaml
@@ -31,8 +31,8 @@ importers:
         specifier: ^5.0.0
         version: 5.0.0
       '@types/node':
-        specifier: ^22.9.3
-        version: 22.9.4
+        specifier: ^22.10.0
+        version: 22.10.0
       eslint:
         specifier: ^9.15.0
         version: 9.15.0
@@ -47,10 +47,10 @@ importers:
         version: 3.1.7
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.9.4)(typescript@5.7.2)
+        version: 10.9.2(@types/node@22.10.0)(typescript@5.7.2)
       ts-node-dev:
         specifier: ^2.0.0
-        version: 2.0.0(@types/node@22.9.4)(typescript@5.7.2)
+        version: 2.0.0(@types/node@22.10.0)(typescript@5.7.2)
       typescript:
         specifier: ^5.7.2
         version: 5.7.2
@@ -159,8 +159,8 @@ packages:
   '@types/estree@1.0.6':
     resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
 
-  '@types/express-serve-static-core@5.0.1':
-    resolution: {integrity: sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==}
+  '@types/express-serve-static-core@5.0.2':
+    resolution: {integrity: sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==}
 
   '@types/express@5.0.0':
     resolution: {integrity: sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==}
@@ -177,8 +177,8 @@ packages:
   '@types/mime@1.3.5':
     resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
 
-  '@types/node@22.9.4':
-    resolution: {integrity: sha512-d9RWfoR7JC/87vj7n+PVTzGg9hDyuFjir3RxUHbjFSKNd9mpxbxwMEyaCim/ddCmy4IuW7HjTzF3g9p3EtWEOg==}
+  '@types/node@22.10.0':
+    resolution: {integrity: sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==}
 
   '@types/qs@6.9.17':
     resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==}
@@ -1007,8 +1007,8 @@ packages:
   undefsafe@2.0.5:
     resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
 
-  undici-types@6.19.8:
-    resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+  undici-types@6.20.0:
+    resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
 
   unpipe@1.0.0:
     resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
@@ -1148,23 +1148,23 @@ snapshots:
   '@types/body-parser@1.19.5':
     dependencies:
       '@types/connect': 3.4.38
-      '@types/node': 22.9.4
+      '@types/node': 22.10.0
 
   '@types/connect@3.4.38':
     dependencies:
-      '@types/node': 22.9.4
+      '@types/node': 22.10.0
 
   '@types/cookie@0.4.1': {}
 
   '@types/cors@2.8.17':
     dependencies:
-      '@types/node': 22.9.4
+      '@types/node': 22.10.0
 
   '@types/estree@1.0.6': {}
 
-  '@types/express-serve-static-core@5.0.1':
+  '@types/express-serve-static-core@5.0.2':
     dependencies:
-      '@types/node': 22.9.4
+      '@types/node': 22.10.0
       '@types/qs': 6.9.17
       '@types/range-parser': 1.2.7
       '@types/send': 0.17.4
@@ -1172,7 +1172,7 @@ snapshots:
   '@types/express@5.0.0':
     dependencies:
       '@types/body-parser': 1.19.5
-      '@types/express-serve-static-core': 5.0.1
+      '@types/express-serve-static-core': 5.0.2
       '@types/qs': 6.9.17
       '@types/serve-static': 1.15.7
 
@@ -1180,15 +1180,15 @@ snapshots:
 
   '@types/http-proxy@1.17.15':
     dependencies:
-      '@types/node': 22.9.4
+      '@types/node': 22.10.0
 
   '@types/json-schema@7.0.15': {}
 
   '@types/mime@1.3.5': {}
 
-  '@types/node@22.9.4':
+  '@types/node@22.10.0':
     dependencies:
-      undici-types: 6.19.8
+      undici-types: 6.20.0
 
   '@types/qs@6.9.17': {}
 
@@ -1197,12 +1197,12 @@ snapshots:
   '@types/send@0.17.4':
     dependencies:
       '@types/mime': 1.3.5
-      '@types/node': 22.9.4
+      '@types/node': 22.10.0
 
   '@types/serve-static@1.15.7':
     dependencies:
       '@types/http-errors': 2.0.4
-      '@types/node': 22.9.4
+      '@types/node': 22.10.0
       '@types/send': 0.17.4
 
   '@types/strip-bom@3.0.0': {}
@@ -1388,7 +1388,7 @@ snapshots:
     dependencies:
       '@types/cookie': 0.4.1
       '@types/cors': 2.8.17
-      '@types/node': 22.9.4
+      '@types/node': 22.10.0
       accepts: 1.3.8
       base64id: 2.0.0
       cookie: 0.7.2
@@ -2006,7 +2006,7 @@ snapshots:
 
   tree-kill@1.2.2: {}
 
-  ts-node-dev@2.0.0(@types/node@22.9.4)(typescript@5.7.2):
+  ts-node-dev@2.0.0(@types/node@22.10.0)(typescript@5.7.2):
     dependencies:
       chokidar: 3.6.0
       dynamic-dedupe: 0.3.0
@@ -2016,7 +2016,7 @@ snapshots:
       rimraf: 2.7.1
       source-map-support: 0.5.21
       tree-kill: 1.2.2
-      ts-node: 10.9.2(@types/node@22.9.4)(typescript@5.7.2)
+      ts-node: 10.9.2(@types/node@22.10.0)(typescript@5.7.2)
       tsconfig: 7.0.0
       typescript: 5.7.2
     transitivePeerDependencies:
@@ -2024,14 +2024,14 @@ snapshots:
       - '@swc/wasm'
       - '@types/node'
 
-  ts-node@10.9.2(@types/node@22.9.4)(typescript@5.7.2):
+  ts-node@10.9.2(@types/node@22.10.0)(typescript@5.7.2):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
       '@tsconfig/node10': 1.0.11
       '@tsconfig/node12': 1.0.11
       '@tsconfig/node14': 1.0.3
       '@tsconfig/node16': 1.0.4
-      '@types/node': 22.9.4
+      '@types/node': 22.10.0
       acorn: 8.14.0
       acorn-walk: 8.3.4
       arg: 4.1.3
@@ -2064,7 +2064,7 @@ snapshots:
 
   undefsafe@2.0.5: {}
 
-  undici-types@6.19.8: {}
+  undici-types@6.20.0: {}
 
   unpipe@1.0.0: {}
 
diff --git a/pi/ui/.env b/pi/ui/.env
new file mode 100644
index 0000000..859bf67
--- /dev/null
+++ b/pi/ui/.env
@@ -0,0 +1 @@
+VITE_API_URL="http://localhost:5000"
\ No newline at end of file
diff --git a/pi/ui/env.d.ts b/pi/ui/env.d.ts
index 27abeaf..c3540f9 100644
--- a/pi/ui/env.d.ts
+++ b/pi/ui/env.d.ts
@@ -1,8 +1,7 @@
 // / <reference types="vite/client" />
 
 interface ImportMetaEnv {
-  readonly VITE_NODE_ENV: string;
-  readonly VITE_BACKEND_URL: string;
+  readonly VITE_API_URL: string;
   readonly BASE_URL: string;
   readonly PROD: boolean
 }
diff --git a/pi/ui/nginx.conf b/pi/ui/nginx.conf
index dbd9288..e17598d 100644
--- a/pi/ui/nginx.conf
+++ b/pi/ui/nginx.conf
@@ -40,10 +40,20 @@ http {
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_set_header X-Forwarded-Proto $scheme;
+
+      add_header Access-Control-Allow-Origin *;
+      add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
+      add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With";
+
+      # Handle preflight requests
+      if ($request_method = 'OPTIONS') {
+        return 204;
+      }
+      
       access_log /var/log/nginx/api_access.log main;
     }
 
-    location /ws/ {
+    location /socket.io/ {
       proxy_pass http://server:5000;
       proxy_http_version 1.1;
       proxy_set_header Upgrade $http_upgrade;
@@ -52,6 +62,16 @@ http {
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_set_header X-Forwarded-Proto $scheme;
+
+      add_header Access-Control-Allow-Origin *;
+      add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
+      add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With";
+
+      # Handle preflight requests
+      if ($request_method = 'OPTIONS') {
+        return 204;
+      }
+
       access_log /var/log/nginx/websocket_access.log main;
     }
 
diff --git a/pi/ui/package.json b/pi/ui/package.json
index 6b89254..1328e29 100644
--- a/pi/ui/package.json
+++ b/pi/ui/package.json
@@ -7,28 +7,26 @@
     "dev": "NODE_ENV=development vite",
     "build": "vue-tsc --build --force && vite build",
     "preview": "vite preview",
-    "lint": "eslint . --fix",
-    "format": "prettier --write src/"
+    "lint": "eslint . --fix"
   },
   "dependencies": {
     "d3": "^7.9.0",
     "pinia": "^2.2.6",
     "socket.io-client": "^4.8.1",
     "vue": "^3.5.13",
-    "vue-router": "^4.4.5"
+    "vue-router": "^4.5.0"
   },
   "devDependencies": {
     "@tsconfig/node22": "^22.0.0",
     "@types/d3": "^7.4.3",
-    "@types/node": "^22.9.3",
+    "@types/node": "^22.10.0",
     "@vitejs/plugin-vue": "^5.2.0",
     "@vue/eslint-config-prettier": "^10.1.0",
-    "@vue/eslint-config-typescript": "^14.1.3",
+    "@vue/eslint-config-typescript": "^14.1.4",
     "@vue/tsconfig": "^0.6.0",
     "eslint": "^9.15.0",
     "eslint-plugin-vue": "^9.31.0",
     "npm-run-all2": "^7.0.1",
-    "prettier": "^3.3.3",
     "typescript": "~5.6.3",
     "vite": "^5.4.11",
     "vite-plugin-vue-devtools": "^7.6.4",
diff --git a/pi/ui/pnpm-lock.yaml b/pi/ui/pnpm-lock.yaml
index ed26d91..44a422b 100644
--- a/pi/ui/pnpm-lock.yaml
+++ b/pi/ui/pnpm-lock.yaml
@@ -21,7 +21,7 @@ importers:
         specifier: ^3.5.13
         version: 3.5.13(typescript@5.6.3)
       vue-router:
-        specifier: ^4.4.5
+        specifier: ^4.5.0
         version: 4.5.0(vue@3.5.13(typescript@5.6.3))
     devDependencies:
       '@tsconfig/node22':
@@ -31,17 +31,17 @@ importers:
         specifier: ^7.4.3
         version: 7.4.3
       '@types/node':
-        specifier: ^22.9.3
-        version: 22.9.4
+        specifier: ^22.10.0
+        version: 22.10.0
       '@vitejs/plugin-vue':
         specifier: ^5.2.0
-        version: 5.2.0(vite@5.4.11(@types/node@22.9.4))(vue@3.5.13(typescript@5.6.3))
+        version: 5.2.0(vite@5.4.11(@types/node@22.10.0))(vue@3.5.13(typescript@5.6.3))
       '@vue/eslint-config-prettier':
         specifier: ^10.1.0
         version: 10.1.0(eslint@9.15.0)(prettier@3.4.0)
       '@vue/eslint-config-typescript':
-        specifier: ^14.1.3
-        version: 14.1.4(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint-plugin-vue@9.31.0(eslint@9.15.0))(eslint@9.15.0)(typescript@5.6.3)
+        specifier: ^14.1.4
+        version: 14.1.4(@typescript-eslint/parser@8.16.0(eslint@9.15.0)(typescript@5.6.3))(eslint-plugin-vue@9.31.0(eslint@9.15.0))(eslint@9.15.0)(typescript@5.6.3)
       '@vue/tsconfig':
         specifier: ^0.6.0
         version: 0.6.0(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3))
@@ -54,18 +54,15 @@ importers:
       npm-run-all2:
         specifier: ^7.0.1
         version: 7.0.1
-      prettier:
-        specifier: ^3.3.3
-        version: 3.4.0
       typescript:
         specifier: ~5.6.3
         version: 5.6.3
       vite:
         specifier: ^5.4.11
-        version: 5.4.11(@types/node@22.9.4)
+        version: 5.4.11(@types/node@22.10.0)
       vite-plugin-vue-devtools:
         specifier: ^7.6.4
-        version: 7.6.4(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.4))(vue@3.5.13(typescript@5.6.3))
+        version: 7.6.4(rollup@4.27.4)(vite@5.4.11(@types/node@22.10.0))(vue@3.5.13(typescript@5.6.3))
       vue-tsc:
         specifier: ^2.1.10
         version: 2.1.10(typescript@5.6.3)
@@ -627,8 +624,8 @@ packages:
   '@types/d3-time-format@4.0.3':
     resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==}
 
-  '@types/d3-time@3.0.3':
-    resolution: {integrity: sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==}
+  '@types/d3-time@3.0.4':
+    resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
 
   '@types/d3-timer@3.0.2':
     resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
@@ -651,11 +648,11 @@ packages:
   '@types/json-schema@7.0.15':
     resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
 
-  '@types/node@22.9.4':
-    resolution: {integrity: sha512-d9RWfoR7JC/87vj7n+PVTzGg9hDyuFjir3RxUHbjFSKNd9mpxbxwMEyaCim/ddCmy4IuW7HjTzF3g9p3EtWEOg==}
+  '@types/node@22.10.0':
+    resolution: {integrity: sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==}
 
-  '@typescript-eslint/eslint-plugin@8.15.0':
-    resolution: {integrity: sha512-+zkm9AR1Ds9uLWN3fkoeXgFppaQ+uEVtfOV62dDmsy9QCNqlRHWNEck4yarvRNrvRcHQLGfqBNui3cimoz8XAg==}
+  '@typescript-eslint/eslint-plugin@8.16.0':
+    resolution: {integrity: sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
@@ -665,8 +662,8 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/parser@8.15.0':
-    resolution: {integrity: sha512-7n59qFpghG4uazrF9qtGKBZXn7Oz4sOMm8dwNWDQY96Xlm2oX67eipqcblDj+oY1lLCbf1oltMZFpUso66Kl1A==}
+  '@typescript-eslint/parser@8.16.0':
+    resolution: {integrity: sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
@@ -675,12 +672,12 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/scope-manager@8.15.0':
-    resolution: {integrity: sha512-QRGy8ADi4J7ii95xz4UoiymmmMd/zuy9azCaamnZ3FM8T5fZcex8UfJcjkiEZjJSztKfEBe3dZ5T/5RHAmw2mA==}
+  '@typescript-eslint/scope-manager@8.16.0':
+    resolution: {integrity: sha512-mwsZWubQvBki2t5565uxF0EYvG+FwdFb8bMtDuGQLdCCnGPrDEDvm1gtfynuKlnpzeBRqdFCkMf9jg1fnAK8sg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@typescript-eslint/type-utils@8.15.0':
-    resolution: {integrity: sha512-UU6uwXDoI3JGSXmcdnP5d8Fffa2KayOhUUqr/AiBnG1Gl7+7ut/oyagVeSkh7bxQ0zSXV9ptRh/4N15nkCqnpw==}
+  '@typescript-eslint/type-utils@8.16.0':
+    resolution: {integrity: sha512-IqZHGG+g1XCWX9NyqnI/0CX5LL8/18awQqmkZSl2ynn8F76j579dByc0jhfVSnSnhf7zv76mKBQv9HQFKvDCgg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
@@ -689,12 +686,12 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/types@8.15.0':
-    resolution: {integrity: sha512-n3Gt8Y/KyJNe0S3yDCD2RVKrHBC4gTUcLTebVBXacPy091E6tNspFLKRXlk3hwT4G55nfr1n2AdFqi/XMxzmPQ==}
+  '@typescript-eslint/types@8.16.0':
+    resolution: {integrity: sha512-NzrHj6thBAOSE4d9bsuRNMvk+BvaQvmY4dDglgkgGC0EW/tB3Kelnp3tAKH87GEwzoxgeQn9fNGRyFJM/xd+GQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@typescript-eslint/typescript-estree@8.15.0':
-    resolution: {integrity: sha512-1eMp2JgNec/niZsR7ioFBlsh/Fk0oJbhaqO0jRyQBMgkz7RrFfkqF9lYYmBoGBaSiLnu8TAPQTwoTUiSTUW9dg==}
+  '@typescript-eslint/typescript-estree@8.16.0':
+    resolution: {integrity: sha512-E2+9IzzXMc1iaBy9zmo+UYvluE3TW7bCGWSF41hVWUE01o8nzr1rvOQYSxelxr6StUvRcTMe633eY8mXASMaNw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       typescript: '*'
@@ -702,8 +699,8 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/utils@8.15.0':
-    resolution: {integrity: sha512-k82RI9yGhr0QM3Dnq+egEpz9qB6Un+WLYhmoNcvl8ltMEededhh7otBVVIDDsEEttauwdY/hQoSsOv13lxrFzQ==}
+  '@typescript-eslint/utils@8.16.0':
+    resolution: {integrity: sha512-C1zRy/mOL8Pj157GiX4kaw7iyRLKfJXBR3L82hk5kS/GyHcOFmy4YUq/zfZti72I9wnuQtA/+xzft4wCC8PJdA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
@@ -712,8 +709,8 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/visitor-keys@8.15.0':
-    resolution: {integrity: sha512-h8vYOulWec9LhpwfAdZf2bjr8xIp0KNKnpgqSz0qqYYKAW/QZKw3ktRndbiAtUz4acH4QLQavwZBYCc0wulA/Q==}
+  '@typescript-eslint/visitor-keys@8.16.0':
+    resolution: {integrity: sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   '@vitejs/plugin-vue@5.2.0':
@@ -889,8 +886,8 @@ packages:
     resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
     engines: {node: '>=6'}
 
-  caniuse-lite@1.0.30001683:
-    resolution: {integrity: sha512-iqmNnThZ0n70mNwvxpEC2nBJ037ZHZUoBI5Gorh1Mw6IlEAZujEoU1tXA628iZfzm7R9FvFzxbfdgml82a3k8Q==}
+  caniuse-lite@1.0.30001684:
+    resolution: {integrity: sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ==}
 
   chalk@4.1.2:
     resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
@@ -1086,8 +1083,8 @@ packages:
   delaunator@5.0.1:
     resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
 
-  electron-to-chromium@1.5.64:
-    resolution: {integrity: sha512-IXEuxU+5ClW2IGEYFC2T7szbyVgehupCWQe5GNh+H065CD6U6IFN0s4KeAMFGNmQolRU4IV7zGBWSYMmZ8uuqQ==}
+  electron-to-chromium@1.5.65:
+    resolution: {integrity: sha512-PWVzBjghx7/wop6n22vS2MLU8tKGd4Q91aCEGhG/TYmW6PP5OcSXcdnxTe1NNt0T66N8D6jxh4kC8UsdzOGaIw==}
 
   engine.io-client@6.6.2:
     resolution: {integrity: sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==}
@@ -1416,8 +1413,8 @@ packages:
   lru-cache@5.1.1:
     resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
 
-  magic-string@0.30.13:
-    resolution: {integrity: sha512-8rYBO+MsWkgjDSOvLomYnzhdwEG51olQ4zL5KXnNJWV5MNmrb4rTZdrtkhxjnD/QyZUqR/Z/XDsUs/4ej2nx0g==}
+  magic-string@0.30.14:
+    resolution: {integrity: sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==}
 
   memorystream@0.3.1:
     resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
@@ -1697,8 +1694,8 @@ packages:
     resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
     engines: {node: '>=6'}
 
-  ts-api-utils@1.4.1:
-    resolution: {integrity: sha512-5RU2/lxTA3YUZxju61HO2U6EoZLvBLtmV2mbTvqyu4a/7s7RmJPT+1YekhMVsQhznRWk/czIwDUg+V8Q9ZuG4w==}
+  ts-api-utils@1.4.2:
+    resolution: {integrity: sha512-ZF5gQIQa/UmzfvxbHZI3JXN0/Jt+vnAfAviNRAMc491laiK6YCLpCW9ft8oaCRFOTxCZtUTE6XB0ZQAe3olntw==}
     engines: {node: '>=16'}
     peerDependencies:
       typescript: '>=4.2.0'
@@ -1714,8 +1711,8 @@ packages:
     resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
     engines: {node: '>=10'}
 
-  typescript-eslint@8.15.0:
-    resolution: {integrity: sha512-wY4FRGl0ZI+ZU4Jo/yjdBu0lVTSML58pu6PgGtJmCufvzfV565pUF6iACQt092uFOd49iLOTX/sEVmHtbSrS+w==}
+  typescript-eslint@8.16.0:
+    resolution: {integrity: sha512-wDkVmlY6O2do4V+lZd0GtRfbtXbeD0q9WygwXXSJnC1xorE8eqyC2L1tJimqpSeFrOzRlYtWnUp/uzgHQOgfBQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     peerDependencies:
       eslint: ^8.57.0 || ^9.0.0
@@ -1729,8 +1726,8 @@ packages:
     engines: {node: '>=14.17'}
     hasBin: true
 
-  undici-types@6.19.8:
-    resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+  undici-types@6.20.0:
+    resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
 
   universalify@2.0.1:
     resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
@@ -1769,8 +1766,8 @@ packages:
     peerDependencies:
       vite: ^3.1.0 || ^4.0.0-0 || ^5.0.0-0
 
-  vite-plugin-vue-inspector@5.2.0:
-    resolution: {integrity: sha512-wWxyb9XAtaIvV/Lr7cqB1HIzmHZFVUJsTNm3yAxkS87dgh/Ky4qr2wDEWNxF23fdhVa3jQ8MZREpr4XyiuaRqA==}
+  vite-plugin-vue-inspector@5.3.0:
+    resolution: {integrity: sha512-F6JNRUOrZl8FaUCTxPhsOLn2ka7N7Sz9ppxmmEwpybVBDYnhelbNnnlZpeFPc4ULnxbitSi8b0V2C0KT3CjReg==}
     peerDependencies:
       vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0
 
@@ -2364,7 +2361,7 @@ snapshots:
 
   '@types/d3-scale@4.0.8':
     dependencies:
-      '@types/d3-time': 3.0.3
+      '@types/d3-time': 3.0.4
 
   '@types/d3-selection@3.0.11': {}
 
@@ -2374,7 +2371,7 @@ snapshots:
 
   '@types/d3-time-format@4.0.3': {}
 
-  '@types/d3-time@3.0.3': {}
+  '@types/d3-time@3.0.4': {}
 
   '@types/d3-timer@3.0.2': {}
 
@@ -2414,7 +2411,7 @@ snapshots:
       '@types/d3-scale-chromatic': 3.0.3
       '@types/d3-selection': 3.0.11
       '@types/d3-shape': 3.1.6
-      '@types/d3-time': 3.0.3
+      '@types/d3-time': 3.0.4
       '@types/d3-time-format': 4.0.3
       '@types/d3-timer': 3.0.2
       '@types/d3-transition': 3.0.9
@@ -2426,34 +2423,34 @@ snapshots:
 
   '@types/json-schema@7.0.15': {}
 
-  '@types/node@22.9.4':
+  '@types/node@22.10.0':
     dependencies:
-      undici-types: 6.19.8
+      undici-types: 6.20.0
 
-  '@typescript-eslint/eslint-plugin@8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3)':
+  '@typescript-eslint/eslint-plugin@8.16.0(@typescript-eslint/parser@8.16.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3)':
     dependencies:
       '@eslint-community/regexpp': 4.12.1
-      '@typescript-eslint/parser': 8.15.0(eslint@9.15.0)(typescript@5.6.3)
-      '@typescript-eslint/scope-manager': 8.15.0
-      '@typescript-eslint/type-utils': 8.15.0(eslint@9.15.0)(typescript@5.6.3)
-      '@typescript-eslint/utils': 8.15.0(eslint@9.15.0)(typescript@5.6.3)
-      '@typescript-eslint/visitor-keys': 8.15.0
+      '@typescript-eslint/parser': 8.16.0(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/scope-manager': 8.16.0
+      '@typescript-eslint/type-utils': 8.16.0(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/utils': 8.16.0(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/visitor-keys': 8.16.0
       eslint: 9.15.0
       graphemer: 1.4.0
       ignore: 5.3.2
       natural-compare: 1.4.0
-      ts-api-utils: 1.4.1(typescript@5.6.3)
+      ts-api-utils: 1.4.2(typescript@5.6.3)
     optionalDependencies:
       typescript: 5.6.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3)':
+  '@typescript-eslint/parser@8.16.0(eslint@9.15.0)(typescript@5.6.3)':
     dependencies:
-      '@typescript-eslint/scope-manager': 8.15.0
-      '@typescript-eslint/types': 8.15.0
-      '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3)
-      '@typescript-eslint/visitor-keys': 8.15.0
+      '@typescript-eslint/scope-manager': 8.16.0
+      '@typescript-eslint/types': 8.16.0
+      '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.6.3)
+      '@typescript-eslint/visitor-keys': 8.16.0
       debug: 4.3.7
       eslint: 9.15.0
     optionalDependencies:
@@ -2461,60 +2458,60 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/scope-manager@8.15.0':
+  '@typescript-eslint/scope-manager@8.16.0':
     dependencies:
-      '@typescript-eslint/types': 8.15.0
-      '@typescript-eslint/visitor-keys': 8.15.0
+      '@typescript-eslint/types': 8.16.0
+      '@typescript-eslint/visitor-keys': 8.16.0
 
-  '@typescript-eslint/type-utils@8.15.0(eslint@9.15.0)(typescript@5.6.3)':
+  '@typescript-eslint/type-utils@8.16.0(eslint@9.15.0)(typescript@5.6.3)':
     dependencies:
-      '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3)
-      '@typescript-eslint/utils': 8.15.0(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.6.3)
+      '@typescript-eslint/utils': 8.16.0(eslint@9.15.0)(typescript@5.6.3)
       debug: 4.3.7
       eslint: 9.15.0
-      ts-api-utils: 1.4.1(typescript@5.6.3)
+      ts-api-utils: 1.4.2(typescript@5.6.3)
     optionalDependencies:
       typescript: 5.6.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/types@8.15.0': {}
+  '@typescript-eslint/types@8.16.0': {}
 
-  '@typescript-eslint/typescript-estree@8.15.0(typescript@5.6.3)':
+  '@typescript-eslint/typescript-estree@8.16.0(typescript@5.6.3)':
     dependencies:
-      '@typescript-eslint/types': 8.15.0
-      '@typescript-eslint/visitor-keys': 8.15.0
+      '@typescript-eslint/types': 8.16.0
+      '@typescript-eslint/visitor-keys': 8.16.0
       debug: 4.3.7
       fast-glob: 3.3.2
       is-glob: 4.0.3
       minimatch: 9.0.5
       semver: 7.6.3
-      ts-api-utils: 1.4.1(typescript@5.6.3)
+      ts-api-utils: 1.4.2(typescript@5.6.3)
     optionalDependencies:
       typescript: 5.6.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/utils@8.15.0(eslint@9.15.0)(typescript@5.6.3)':
+  '@typescript-eslint/utils@8.16.0(eslint@9.15.0)(typescript@5.6.3)':
     dependencies:
       '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0)
-      '@typescript-eslint/scope-manager': 8.15.0
-      '@typescript-eslint/types': 8.15.0
-      '@typescript-eslint/typescript-estree': 8.15.0(typescript@5.6.3)
+      '@typescript-eslint/scope-manager': 8.16.0
+      '@typescript-eslint/types': 8.16.0
+      '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.6.3)
       eslint: 9.15.0
     optionalDependencies:
       typescript: 5.6.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/visitor-keys@8.15.0':
+  '@typescript-eslint/visitor-keys@8.16.0':
     dependencies:
-      '@typescript-eslint/types': 8.15.0
+      '@typescript-eslint/types': 8.16.0
       eslint-visitor-keys: 4.2.0
 
-  '@vitejs/plugin-vue@5.2.0(vite@5.4.11(@types/node@22.9.4))(vue@3.5.13(typescript@5.6.3))':
+  '@vitejs/plugin-vue@5.2.0(vite@5.4.11(@types/node@22.10.0))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
-      vite: 5.4.11(@types/node@22.9.4)
+      vite: 5.4.11(@types/node@22.10.0)
       vue: 3.5.13(typescript@5.6.3)
 
   '@volar/language-core@2.4.10':
@@ -2580,7 +2577,7 @@ snapshots:
       '@vue/compiler-ssr': 3.5.13
       '@vue/shared': 3.5.13
       estree-walker: 2.0.2
-      magic-string: 0.30.13
+      magic-string: 0.30.14
       postcss: 8.4.49
       source-map-js: 1.2.1
 
@@ -2596,14 +2593,14 @@ snapshots:
 
   '@vue/devtools-api@6.6.4': {}
 
-  '@vue/devtools-core@7.6.4(vite@5.4.11(@types/node@22.9.4))(vue@3.5.13(typescript@5.6.3))':
+  '@vue/devtools-core@7.6.4(vite@5.4.11(@types/node@22.10.0))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
       '@vue/devtools-kit': 7.6.4
       '@vue/devtools-shared': 7.6.4
       mitt: 3.0.1
       nanoid: 3.3.7
       pathe: 1.1.2
-      vite-hot-client: 0.2.3(vite@5.4.11(@types/node@22.9.4))
+      vite-hot-client: 0.2.3(vite@5.4.11(@types/node@22.10.0))
       vue: 3.5.13(typescript@5.6.3)
     transitivePeerDependencies:
       - vite
@@ -2631,13 +2628,13 @@ snapshots:
     transitivePeerDependencies:
       - '@types/eslint'
 
-  '@vue/eslint-config-typescript@14.1.4(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint-plugin-vue@9.31.0(eslint@9.15.0))(eslint@9.15.0)(typescript@5.6.3)':
+  '@vue/eslint-config-typescript@14.1.4(@typescript-eslint/parser@8.16.0(eslint@9.15.0)(typescript@5.6.3))(eslint-plugin-vue@9.31.0(eslint@9.15.0))(eslint@9.15.0)(typescript@5.6.3)':
     dependencies:
-      '@typescript-eslint/eslint-plugin': 8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/eslint-plugin': 8.16.0(@typescript-eslint/parser@8.16.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3)
       eslint: 9.15.0
       eslint-plugin-vue: 9.31.0(eslint@9.15.0)
       fast-glob: 3.3.2
-      typescript-eslint: 8.15.0(eslint@9.15.0)(typescript@5.6.3)
+      typescript-eslint: 8.16.0(eslint@9.15.0)(typescript@5.6.3)
       vue-eslint-parser: 9.4.3(eslint@9.15.0)
     optionalDependencies:
       typescript: 5.6.3
@@ -2731,8 +2728,8 @@ snapshots:
 
   browserslist@4.24.2:
     dependencies:
-      caniuse-lite: 1.0.30001683
-      electron-to-chromium: 1.5.64
+      caniuse-lite: 1.0.30001684
+      electron-to-chromium: 1.5.65
       node-releases: 2.0.18
       update-browserslist-db: 1.1.1(browserslist@4.24.2)
 
@@ -2742,7 +2739,7 @@ snapshots:
 
   callsites@3.1.0: {}
 
-  caniuse-lite@1.0.30001683: {}
+  caniuse-lite@1.0.30001684: {}
 
   chalk@4.1.2:
     dependencies:
@@ -2948,7 +2945,7 @@ snapshots:
     dependencies:
       robust-predicates: 3.0.2
 
-  electron-to-chromium@1.5.64: {}
+  electron-to-chromium@1.5.65: {}
 
   engine.io-client@6.6.2:
     dependencies:
@@ -3287,7 +3284,7 @@ snapshots:
     dependencies:
       yallist: 3.1.1
 
-  magic-string@0.30.13:
+  magic-string@0.30.14:
     dependencies:
       '@jridgewell/sourcemap-codec': 1.5.0
 
@@ -3544,7 +3541,7 @@ snapshots:
 
   totalist@3.0.1: {}
 
-  ts-api-utils@1.4.1(typescript@5.6.3):
+  ts-api-utils@1.4.2(typescript@5.6.3):
     dependencies:
       typescript: 5.6.3
 
@@ -3556,11 +3553,11 @@ snapshots:
 
   type-fest@0.20.2: {}
 
-  typescript-eslint@8.15.0(eslint@9.15.0)(typescript@5.6.3):
+  typescript-eslint@8.16.0(eslint@9.15.0)(typescript@5.6.3):
     dependencies:
-      '@typescript-eslint/eslint-plugin': 8.15.0(@typescript-eslint/parser@8.15.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3)
-      '@typescript-eslint/parser': 8.15.0(eslint@9.15.0)(typescript@5.6.3)
-      '@typescript-eslint/utils': 8.15.0(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/eslint-plugin': 8.16.0(@typescript-eslint/parser@8.16.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/parser': 8.16.0(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/utils': 8.16.0(eslint@9.15.0)(typescript@5.6.3)
       eslint: 9.15.0
     optionalDependencies:
       typescript: 5.6.3
@@ -3569,7 +3566,7 @@ snapshots:
 
   typescript@5.6.3: {}
 
-  undici-types@6.19.8: {}
+  undici-types@6.20.0: {}
 
   universalify@2.0.1: {}
 
@@ -3585,11 +3582,11 @@ snapshots:
 
   util-deprecate@1.0.2: {}
 
-  vite-hot-client@0.2.3(vite@5.4.11(@types/node@22.9.4)):
+  vite-hot-client@0.2.3(vite@5.4.11(@types/node@22.10.0)):
     dependencies:
-      vite: 5.4.11(@types/node@22.9.4)
+      vite: 5.4.11(@types/node@22.10.0)
 
-  vite-plugin-inspect@0.8.8(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.4)):
+  vite-plugin-inspect@0.8.8(rollup@4.27.4)(vite@5.4.11(@types/node@22.10.0)):
     dependencies:
       '@antfu/utils': 0.7.10
       '@rollup/pluginutils': 5.1.3(rollup@4.27.4)
@@ -3600,28 +3597,28 @@ snapshots:
       perfect-debounce: 1.0.0
       picocolors: 1.1.1
       sirv: 3.0.0
-      vite: 5.4.11(@types/node@22.9.4)
+      vite: 5.4.11(@types/node@22.10.0)
     transitivePeerDependencies:
       - rollup
       - supports-color
 
-  vite-plugin-vue-devtools@7.6.4(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.4))(vue@3.5.13(typescript@5.6.3)):
+  vite-plugin-vue-devtools@7.6.4(rollup@4.27.4)(vite@5.4.11(@types/node@22.10.0))(vue@3.5.13(typescript@5.6.3)):
     dependencies:
-      '@vue/devtools-core': 7.6.4(vite@5.4.11(@types/node@22.9.4))(vue@3.5.13(typescript@5.6.3))
+      '@vue/devtools-core': 7.6.4(vite@5.4.11(@types/node@22.10.0))(vue@3.5.13(typescript@5.6.3))
       '@vue/devtools-kit': 7.6.4
       '@vue/devtools-shared': 7.6.4
       execa: 8.0.1
       sirv: 3.0.0
-      vite: 5.4.11(@types/node@22.9.4)
-      vite-plugin-inspect: 0.8.8(rollup@4.27.4)(vite@5.4.11(@types/node@22.9.4))
-      vite-plugin-vue-inspector: 5.2.0(vite@5.4.11(@types/node@22.9.4))
+      vite: 5.4.11(@types/node@22.10.0)
+      vite-plugin-inspect: 0.8.8(rollup@4.27.4)(vite@5.4.11(@types/node@22.10.0))
+      vite-plugin-vue-inspector: 5.3.0(vite@5.4.11(@types/node@22.10.0))
     transitivePeerDependencies:
       - '@nuxt/kit'
       - rollup
       - supports-color
       - vue
 
-  vite-plugin-vue-inspector@5.2.0(vite@5.4.11(@types/node@22.9.4)):
+  vite-plugin-vue-inspector@5.3.0(vite@5.4.11(@types/node@22.10.0)):
     dependencies:
       '@babel/core': 7.26.0
       '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.0)
@@ -3631,18 +3628,18 @@ snapshots:
       '@vue/babel-plugin-jsx': 1.2.5(@babel/core@7.26.0)
       '@vue/compiler-dom': 3.5.13
       kolorist: 1.8.0
-      magic-string: 0.30.13
-      vite: 5.4.11(@types/node@22.9.4)
+      magic-string: 0.30.14
+      vite: 5.4.11(@types/node@22.10.0)
     transitivePeerDependencies:
       - supports-color
 
-  vite@5.4.11(@types/node@22.9.4):
+  vite@5.4.11(@types/node@22.10.0):
     dependencies:
       esbuild: 0.21.5
       postcss: 8.4.49
       rollup: 4.27.4
     optionalDependencies:
-      '@types/node': 22.9.4
+      '@types/node': 22.10.0
       fsevents: 2.3.3
 
   vscode-uri@3.0.8: {}
diff --git a/pi/ui/src/components/UWBVisualization.vue b/pi/ui/src/components/UWBVisualization.vue
index 217ec07..f33493d 100644
--- a/pi/ui/src/components/UWBVisualization.vue
+++ b/pi/ui/src/components/UWBVisualization.vue
@@ -16,7 +16,7 @@ interface AnchorConfig {
 export default defineComponent({
   name: 'UWBVisualization',
   setup() {
-    const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000';
+    const backendUrl = import.meta.env.VITE_API_URL || window.location.origin;
     const socket = inject('socket') as Socket;
 
     const uwbData = ref<AnchorData[]>([]);
diff --git a/pi/ui/src/main.ts b/pi/ui/src/main.ts
index 57cfd44..6c8739c 100644
--- a/pi/ui/src/main.ts
+++ b/pi/ui/src/main.ts
@@ -9,7 +9,7 @@ import router from './router';
 
 const app = createApp(App);
 
-const backendUrl = import.meta.env.VITE_BACKEND_URL || 'http://localhost:5000';
+const backendUrl = import.meta.env.VITE_API_URL || window.location.origin;
 const socket: Socket = io(backendUrl, { transports: ['websocket'] });
 
 app.provide('socket', socket);
diff --git a/pi/ui/vite.config.ts b/pi/ui/vite.config.ts
index 6c1b071..65a696c 100644
--- a/pi/ui/vite.config.ts
+++ b/pi/ui/vite.config.ts
@@ -1,9 +1,30 @@
 import { fileURLToPath, URL } from 'node:url';
+import os from 'os';
 
 import { defineConfig } from 'vite';
 import vue from '@vitejs/plugin-vue';
 import vueDevTools from 'vite-plugin-vue-devtools';
 
+function getLocalIP() {
+  const networkInterfaces = os.networkInterfaces();
+
+  for (const interfaceName in networkInterfaces) {
+    const interfaceInfo = networkInterfaces[interfaceName];
+
+    if (!interfaceInfo) {
+      continue;
+    }
+
+    for (const info of interfaceInfo) {
+      if (info.family === 'IPv4' && !info.internal) {
+        return info.address;
+      }
+    }
+  }
+
+  return 'localhost'; // Fallback to localhost if no external IP is found
+}
+
 // https://vite.dev/config/
 export default defineConfig({
   plugins: [
@@ -11,5 +32,8 @@ export default defineConfig({
     vueDevTools(),
   ],
   resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } },
-  server:  { proxy: { '/api': 'http://localhost:5000' } },
+  server:  {
+    proxy: { '/api': 'http://localhost:5000' },
+    host:  getLocalIP()
+  },
 });

From 9f7e62c388c683eedec5bc14652f3a8131ec8752 Mon Sep 17 00:00:00 2001
From: jordojordo <jordonleach@gmail.com>
Date: Tue, 26 Nov 2024 05:21:20 -0500
Subject: [PATCH 25/34] Add apiConfig.json to ui runtime

---
 pi/docker-compose.yaml                    |  2 +
 pi/ui/public/apiConfig.json               |  3 ++
 pi/ui/src/components/UWBVisualization.vue | 58 +++++++++++++++--------
 pi/ui/src/main.ts                         | 22 ++++++---
 pi/ui/vite.config.ts                      | 26 +---------
 5 files changed, 60 insertions(+), 51 deletions(-)
 create mode 100644 pi/ui/public/apiConfig.json

diff --git a/pi/docker-compose.yaml b/pi/docker-compose.yaml
index f9369c5..87c603c 100644
--- a/pi/docker-compose.yaml
+++ b/pi/docker-compose.yaml
@@ -16,6 +16,8 @@ services:
     image: ghcr.io/jordojordo/lawndon-pi-ui:latest
     ports:
       - "80:80" # Expose UI
+    volumes:
+      - ./apiConfig.json:/usr/share/nginx/html/apiConfig.json:ro # Mount config for API
     depends_on:
       - server
     networks:
diff --git a/pi/ui/public/apiConfig.json b/pi/ui/public/apiConfig.json
new file mode 100644
index 0000000..179b6bd
--- /dev/null
+++ b/pi/ui/public/apiConfig.json
@@ -0,0 +1,3 @@
+{
+  "API_URL": "http://localhost:5000"
+}
diff --git a/pi/ui/src/components/UWBVisualization.vue b/pi/ui/src/components/UWBVisualization.vue
index f33493d..e1a17b5 100644
--- a/pi/ui/src/components/UWBVisualization.vue
+++ b/pi/ui/src/components/UWBVisualization.vue
@@ -16,7 +16,7 @@ interface AnchorConfig {
 export default defineComponent({
   name: 'UWBVisualization',
   setup() {
-    const backendUrl = import.meta.env.VITE_API_URL || window.location.origin;
+    const backendUrl = ref('');
     const socket = inject('socket') as Socket;
 
     const uwbData = ref<AnchorData[]>([]);
@@ -36,25 +36,45 @@ export default defineComponent({
 
     const scalingFactor = 50; // Adjust to control visualization size
 
-    onMounted(() => {
-      fetch(`${ backendUrl }/api/config`)
-        .then((response) => response.json())
-        .then((configData: AnchorConfig) => {
-          console.log('## configData:', configData);
-          parseConfiguration(configData);
-          initVisualization();
-
-          socket.on('uwb_data', (data: AnchorData[]) => {
-            uwbData.value = data;
-            updateVisualization();
-            if (uwbData?.value.length === 3) {
-              console.log('Incoming UWB data:', JSON.parse(JSON.stringify(uwbData.value)));
-            }
-          });
-        })
-        .catch((error) => {
-          console.error('Failed to load configuration:', error);
+    const loadRuntimeConfig = async() => {
+      try {
+        const response = await fetch('/apiConfig.json'); // Fetch runtime configuration
+        const config = await response.json();
+
+        backendUrl.value = config.API_URL || window.location.origin;
+        console.log('Loaded runtime config:', backendUrl.value);
+      } catch (error) {
+        console.error('Failed to load runtime config:', error);
+        backendUrl.value = window.location.origin; // Fallback to window.location.origin
+      }
+    };
+
+
+    const fetchBackendConfig = async() => {
+      try {
+        const response = await fetch(`${ backendUrl.value }/api/config`);
+        const configData: AnchorConfig = await response.json();
+
+        console.log('Backend configuration:', configData);
+        parseConfiguration(configData);
+        initVisualization();
+
+        socket.on('uwb_data', (data: AnchorData[]) => {
+          uwbData.value = data;
+          updateVisualization();
+
+          if (uwbData?.value.length === 3) {
+            console.log('Incoming UWB data:', JSON.parse(JSON.stringify(uwbData.value)));
+          }
         });
+      } catch (error) {
+        console.error('Failed to load backend configuration:', error);
+      }
+    };
+
+    onMounted(async() => {
+      await loadRuntimeConfig(); // Load runtime config first
+      await fetchBackendConfig(); // Fetch backend configuration
     });
 
     function parseConfiguration(config: AnchorConfig) {
diff --git a/pi/ui/src/main.ts b/pi/ui/src/main.ts
index 6c8739c..ed8fe54 100644
--- a/pi/ui/src/main.ts
+++ b/pi/ui/src/main.ts
@@ -7,14 +7,22 @@ import { io, Socket } from 'socket.io-client';
 import App from './App.vue';
 import router from './router';
 
-const app = createApp(App);
+async function loadConfig() {
+  const response = await fetch('/apiConfig.json');
 
-const backendUrl = import.meta.env.VITE_API_URL || window.location.origin;
-const socket: Socket = io(backendUrl, { transports: ['websocket'] });
+  return response.json();
+}
 
-app.provide('socket', socket);
+loadConfig().then((config) => {
+  const backendUrl = config.API_URL || window.location.origin;
+  const socket: Socket = io(backendUrl, { transports: ['websocket'] });
 
-app.use(createPinia());
-app.use(router);
+  const app = createApp(App);
 
-app.mount('#app');
+  app.provide('socket', socket);
+
+  app.use(createPinia());
+  app.use(router);
+
+  app.mount('#app');
+});
diff --git a/pi/ui/vite.config.ts b/pi/ui/vite.config.ts
index 65a696c..6c1b071 100644
--- a/pi/ui/vite.config.ts
+++ b/pi/ui/vite.config.ts
@@ -1,30 +1,9 @@
 import { fileURLToPath, URL } from 'node:url';
-import os from 'os';
 
 import { defineConfig } from 'vite';
 import vue from '@vitejs/plugin-vue';
 import vueDevTools from 'vite-plugin-vue-devtools';
 
-function getLocalIP() {
-  const networkInterfaces = os.networkInterfaces();
-
-  for (const interfaceName in networkInterfaces) {
-    const interfaceInfo = networkInterfaces[interfaceName];
-
-    if (!interfaceInfo) {
-      continue;
-    }
-
-    for (const info of interfaceInfo) {
-      if (info.family === 'IPv4' && !info.internal) {
-        return info.address;
-      }
-    }
-  }
-
-  return 'localhost'; // Fallback to localhost if no external IP is found
-}
-
 // https://vite.dev/config/
 export default defineConfig({
   plugins: [
@@ -32,8 +11,5 @@ export default defineConfig({
     vueDevTools(),
   ],
   resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } },
-  server:  {
-    proxy: { '/api': 'http://localhost:5000' },
-    host:  getLocalIP()
-  },
+  server:  { proxy: { '/api': 'http://localhost:5000' } },
 });

From 1baaee36503c024ee35bc0d061206b1cecec9e21 Mon Sep 17 00:00:00 2001
From: Jordon Leach <jordonleach@gmail.com>
Date: Sun, 1 Dec 2024 06:01:59 -0500
Subject: [PATCH 26/34] Add Lawndon Pi readme - Update dependencies (#39)

* Fix Pi apiConfig.json location serving

* Add pi readme - update dependencies
---
 pi/Dockerfile.ui       |   1 +
 pi/docker-compose.yaml |   2 +-
 pi/ui/nginx.conf       |   6 +
 pi/ui/package.json     |  14 +-
 pi/ui/pnpm-lock.yaml   | 587 ++++++++++++++++++++++-------------------
 5 files changed, 335 insertions(+), 275 deletions(-)

diff --git a/pi/Dockerfile.ui b/pi/Dockerfile.ui
index 2a3d982..219bc3e 100644
--- a/pi/Dockerfile.ui
+++ b/pi/Dockerfile.ui
@@ -15,6 +15,7 @@ RUN pnpm build
 
 # Runtime stage
 FROM nginx:alpine
+
 COPY --from=build /usr/src/app/ui/dist /app
 COPY --from=build /usr/src/app/ui/nginx.conf /etc/nginx/nginx.conf
 
diff --git a/pi/docker-compose.yaml b/pi/docker-compose.yaml
index 87c603c..ba7c933 100644
--- a/pi/docker-compose.yaml
+++ b/pi/docker-compose.yaml
@@ -17,7 +17,7 @@ services:
     ports:
       - "80:80" # Expose UI
     volumes:
-      - ./apiConfig.json:/usr/share/nginx/html/apiConfig.json:ro # Mount config for API
+      - ./apiConfig.json:/app/apiConfig.json:ro # Mount config for API
     depends_on:
       - server
     networks:
diff --git a/pi/ui/nginx.conf b/pi/ui/nginx.conf
index e17598d..888edc4 100644
--- a/pi/ui/nginx.conf
+++ b/pi/ui/nginx.conf
@@ -30,6 +30,12 @@ http {
       try_files $uri $uri/ /index.html;
     }
 
+    location /apiConfig.json {
+      root /app;
+      add_header Access-Control-Allow-Origin *;
+      add_header Cache-Control "no-cache, no-store, must-revalidate";
+    }
+
     location /api {
       proxy_pass http://server:5000;
       proxy_http_version 1.1;
diff --git a/pi/ui/package.json b/pi/ui/package.json
index 1328e29..80adb72 100644
--- a/pi/ui/package.json
+++ b/pi/ui/package.json
@@ -11,7 +11,7 @@
   },
   "dependencies": {
     "d3": "^7.9.0",
-    "pinia": "^2.2.6",
+    "pinia": "^2.2.8",
     "socket.io-client": "^4.8.1",
     "vue": "^3.5.13",
     "vue-router": "^4.5.0"
@@ -19,18 +19,18 @@
   "devDependencies": {
     "@tsconfig/node22": "^22.0.0",
     "@types/d3": "^7.4.3",
-    "@types/node": "^22.10.0",
-    "@vitejs/plugin-vue": "^5.2.0",
+    "@types/node": "^22.10.1",
+    "@vitejs/plugin-vue": "^5.2.1",
     "@vue/eslint-config-prettier": "^10.1.0",
     "@vue/eslint-config-typescript": "^14.1.4",
     "@vue/tsconfig": "^0.6.0",
-    "eslint": "^9.15.0",
-    "eslint-plugin-vue": "^9.31.0",
+    "eslint": "^9.16.0",
+    "eslint-plugin-vue": "^9.32.0",
     "npm-run-all2": "^7.0.1",
     "typescript": "~5.6.3",
     "vite": "^5.4.11",
-    "vite-plugin-vue-devtools": "^7.6.4",
+    "vite-plugin-vue-devtools": "^7.6.7",
     "vue-tsc": "^2.1.10"
   },
-  "packageManager": "pnpm@9.14.2"
+  "packageManager": "pnpm@9.14.4"
 }
diff --git a/pi/ui/pnpm-lock.yaml b/pi/ui/pnpm-lock.yaml
index 44a422b..2cf3913 100644
--- a/pi/ui/pnpm-lock.yaml
+++ b/pi/ui/pnpm-lock.yaml
@@ -12,8 +12,8 @@ importers:
         specifier: ^7.9.0
         version: 7.9.0
       pinia:
-        specifier: ^2.2.6
-        version: 2.2.6(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3))
+        specifier: ^2.2.8
+        version: 2.2.8(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3))
       socket.io-client:
         specifier: ^4.8.1
         version: 4.8.1
@@ -31,26 +31,26 @@ importers:
         specifier: ^7.4.3
         version: 7.4.3
       '@types/node':
-        specifier: ^22.10.0
-        version: 22.10.0
+        specifier: ^22.10.1
+        version: 22.10.1
       '@vitejs/plugin-vue':
-        specifier: ^5.2.0
-        version: 5.2.0(vite@5.4.11(@types/node@22.10.0))(vue@3.5.13(typescript@5.6.3))
+        specifier: ^5.2.1
+        version: 5.2.1(vite@5.4.11(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
       '@vue/eslint-config-prettier':
         specifier: ^10.1.0
-        version: 10.1.0(eslint@9.15.0)(prettier@3.4.0)
+        version: 10.1.0(eslint@9.16.0)(prettier@3.4.0)
       '@vue/eslint-config-typescript':
         specifier: ^14.1.4
-        version: 14.1.4(@typescript-eslint/parser@8.16.0(eslint@9.15.0)(typescript@5.6.3))(eslint-plugin-vue@9.31.0(eslint@9.15.0))(eslint@9.15.0)(typescript@5.6.3)
+        version: 14.1.4(@typescript-eslint/parser@8.16.0(eslint@9.16.0)(typescript@5.6.3))(eslint-plugin-vue@9.32.0(eslint@9.16.0))(eslint@9.16.0)(typescript@5.6.3)
       '@vue/tsconfig':
         specifier: ^0.6.0
         version: 0.6.0(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3))
       eslint:
-        specifier: ^9.15.0
-        version: 9.15.0
+        specifier: ^9.16.0
+        version: 9.16.0
       eslint-plugin-vue:
-        specifier: ^9.31.0
-        version: 9.31.0(eslint@9.15.0)
+        specifier: ^9.32.0
+        version: 9.32.0(eslint@9.16.0)
       npm-run-all2:
         specifier: ^7.0.1
         version: 7.0.1
@@ -59,10 +59,10 @@ importers:
         version: 5.6.3
       vite:
         specifier: ^5.4.11
-        version: 5.4.11(@types/node@22.10.0)
+        version: 5.4.11(@types/node@22.10.1)
       vite-plugin-vue-devtools:
-        specifier: ^7.6.4
-        version: 7.6.4(rollup@4.27.4)(vite@5.4.11(@types/node@22.10.0))(vue@3.5.13(typescript@5.6.3))
+        specifier: ^7.6.7
+        version: 7.6.7(rollup@4.28.0)(vite@5.4.11(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
       vue-tsc:
         specifier: ^2.1.10
         version: 2.1.10(typescript@5.6.3)
@@ -372,8 +372,8 @@ packages:
     resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@eslint/js@9.15.0':
-    resolution: {integrity: sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==}
+  '@eslint/js@9.16.0':
+    resolution: {integrity: sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   '@eslint/object-schema@2.1.4':
@@ -450,96 +450,103 @@ packages:
       rollup:
         optional: true
 
-  '@rollup/rollup-android-arm-eabi@4.27.4':
-    resolution: {integrity: sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==}
+  '@rollup/rollup-android-arm-eabi@4.28.0':
+    resolution: {integrity: sha512-wLJuPLT6grGZsy34g4N1yRfYeouklTgPhH1gWXCYspenKYD0s3cR99ZevOGw5BexMNywkbV3UkjADisozBmpPQ==}
     cpu: [arm]
     os: [android]
 
-  '@rollup/rollup-android-arm64@4.27.4':
-    resolution: {integrity: sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==}
+  '@rollup/rollup-android-arm64@4.28.0':
+    resolution: {integrity: sha512-eiNkznlo0dLmVG/6wf+Ifi/v78G4d4QxRhuUl+s8EWZpDewgk7PX3ZyECUXU0Zq/Ca+8nU8cQpNC4Xgn2gFNDA==}
     cpu: [arm64]
     os: [android]
 
-  '@rollup/rollup-darwin-arm64@4.27.4':
-    resolution: {integrity: sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==}
+  '@rollup/rollup-darwin-arm64@4.28.0':
+    resolution: {integrity: sha512-lmKx9yHsppblnLQZOGxdO66gT77bvdBtr/0P+TPOseowE7D9AJoBw8ZDULRasXRWf1Z86/gcOdpBrV6VDUY36Q==}
     cpu: [arm64]
     os: [darwin]
 
-  '@rollup/rollup-darwin-x64@4.27.4':
-    resolution: {integrity: sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==}
+  '@rollup/rollup-darwin-x64@4.28.0':
+    resolution: {integrity: sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w==}
     cpu: [x64]
     os: [darwin]
 
-  '@rollup/rollup-freebsd-arm64@4.27.4':
-    resolution: {integrity: sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==}
+  '@rollup/rollup-freebsd-arm64@4.28.0':
+    resolution: {integrity: sha512-lA1zZB3bFx5oxu9fYud4+g1mt+lYXCoch0M0V/xhqLoGatbzVse0wlSQ1UYOWKpuSu3gyN4qEc0Dxf/DII1bhQ==}
     cpu: [arm64]
     os: [freebsd]
 
-  '@rollup/rollup-freebsd-x64@4.27.4':
-    resolution: {integrity: sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==}
+  '@rollup/rollup-freebsd-x64@4.28.0':
+    resolution: {integrity: sha512-aI2plavbUDjCQB/sRbeUZWX9qp12GfYkYSJOrdYTL/C5D53bsE2/nBPuoiJKoWp5SN78v2Vr8ZPnB+/VbQ2pFA==}
     cpu: [x64]
     os: [freebsd]
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.27.4':
-    resolution: {integrity: sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==}
+  '@rollup/rollup-linux-arm-gnueabihf@4.28.0':
+    resolution: {integrity: sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm-musleabihf@4.27.4':
-    resolution: {integrity: sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==}
+  '@rollup/rollup-linux-arm-musleabihf@4.28.0':
+    resolution: {integrity: sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-gnu@4.27.4':
-    resolution: {integrity: sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==}
+  '@rollup/rollup-linux-arm64-gnu@4.28.0':
+    resolution: {integrity: sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-musl@4.27.4':
-    resolution: {integrity: sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==}
+  '@rollup/rollup-linux-arm64-musl@4.28.0':
+    resolution: {integrity: sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.27.4':
-    resolution: {integrity: sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==}
+  '@rollup/rollup-linux-powerpc64le-gnu@4.28.0':
+    resolution: {integrity: sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ==}
     cpu: [ppc64]
     os: [linux]
 
-  '@rollup/rollup-linux-riscv64-gnu@4.27.4':
-    resolution: {integrity: sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==}
+  '@rollup/rollup-linux-riscv64-gnu@4.28.0':
+    resolution: {integrity: sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g==}
     cpu: [riscv64]
     os: [linux]
 
-  '@rollup/rollup-linux-s390x-gnu@4.27.4':
-    resolution: {integrity: sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==}
+  '@rollup/rollup-linux-s390x-gnu@4.28.0':
+    resolution: {integrity: sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw==}
     cpu: [s390x]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-gnu@4.27.4':
-    resolution: {integrity: sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==}
+  '@rollup/rollup-linux-x64-gnu@4.28.0':
+    resolution: {integrity: sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-musl@4.27.4':
-    resolution: {integrity: sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==}
+  '@rollup/rollup-linux-x64-musl@4.28.0':
+    resolution: {integrity: sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-win32-arm64-msvc@4.27.4':
-    resolution: {integrity: sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==}
+  '@rollup/rollup-win32-arm64-msvc@4.28.0':
+    resolution: {integrity: sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg==}
     cpu: [arm64]
     os: [win32]
 
-  '@rollup/rollup-win32-ia32-msvc@4.27.4':
-    resolution: {integrity: sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==}
+  '@rollup/rollup-win32-ia32-msvc@4.28.0':
+    resolution: {integrity: sha512-kN/Vpip8emMLn/eOza+4JwqDZBL6MPNpkdaEsgUtW1NYN3DZvZqSQrbKzJcTL6hd8YNmFTn7XGWMwccOcJBL0A==}
     cpu: [ia32]
     os: [win32]
 
-  '@rollup/rollup-win32-x64-msvc@4.27.4':
-    resolution: {integrity: sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==}
+  '@rollup/rollup-win32-x64-msvc@4.28.0':
+    resolution: {integrity: sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ==}
     cpu: [x64]
     os: [win32]
 
+  '@sec-ant/readable-stream@0.4.1':
+    resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
+
+  '@sindresorhus/merge-streams@4.0.0':
+    resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
+    engines: {node: '>=18'}
+
   '@socket.io/component-emitter@3.1.2':
     resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
 
@@ -609,8 +616,8 @@ packages:
   '@types/d3-random@3.0.3':
     resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==}
 
-  '@types/d3-scale-chromatic@3.0.3':
-    resolution: {integrity: sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==}
+  '@types/d3-scale-chromatic@3.1.0':
+    resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==}
 
   '@types/d3-scale@4.0.8':
     resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==}
@@ -648,8 +655,8 @@ packages:
   '@types/json-schema@7.0.15':
     resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
 
-  '@types/node@22.10.0':
-    resolution: {integrity: sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==}
+  '@types/node@22.10.1':
+    resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==}
 
   '@typescript-eslint/eslint-plugin@8.16.0':
     resolution: {integrity: sha512-5YTHKV8MYlyMI6BaEG7crQ9BhSc8RxzshOReKwZwRWN0+XvvTOm+L/UYLCYxFpfwYuAAqhxiq4yae0CMFwbL7Q==}
@@ -713,11 +720,11 @@ packages:
     resolution: {integrity: sha512-pq19gbaMOmFE3CbL0ZB8J8BFCo2ckfHBfaIsaOZgBIF4EoISJIdLX5xRhd0FGB0LlHReNRuzoJoMGpTjq8F2CQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@vitejs/plugin-vue@5.2.0':
-    resolution: {integrity: sha512-7n7KdUEtx/7Yl7I/WVAMZ1bEb0eVvXF3ummWTeLcs/9gvo9pJhuLdouSXGjdZ/MKD1acf1I272+X0RMua4/R3g==}
+  '@vitejs/plugin-vue@5.2.1':
+    resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==}
     engines: {node: ^18.0.0 || >=20.0.0}
     peerDependencies:
-      vite: ^5.0.0
+      vite: ^5.0.0 || ^6.0.0
       vue: ^3.2.25
 
   '@volar/language-core@2.4.10':
@@ -763,16 +770,16 @@ packages:
   '@vue/devtools-api@6.6.4':
     resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
 
-  '@vue/devtools-core@7.6.4':
-    resolution: {integrity: sha512-blSwGVYpb7b5TALMjjoBiAl5imuBF7WEOAtaJaBMNikR8SQkm6mkUt4YlIKh9874/qoimwmpDOm+GHBZ4Y5m+g==}
+  '@vue/devtools-core@7.6.7':
+    resolution: {integrity: sha512-6fW8Q0H1NHDXdEcuV6dylT5U2Yxg3SdMnVCey99Y6S4R2PNgFL2vC+VU9U9rHIiaoEUkeza42S7FfHxV4VI3Jg==}
     peerDependencies:
       vue: ^3.0.0
 
-  '@vue/devtools-kit@7.6.4':
-    resolution: {integrity: sha512-Zs86qIXXM9icU0PiGY09PQCle4TI750IPLmAJzW5Kf9n9t5HzSYf6Rz6fyzSwmfMPiR51SUKJh9sXVZu78h2QA==}
+  '@vue/devtools-kit@7.6.7':
+    resolution: {integrity: sha512-V8/jrXY/swHgnblABG9U4QCbE60c6RuPasmv2d9FvVqc5d94t1vDiESuvRmdNJBdWz4/D3q6ffgyAfRVjwHYEw==}
 
-  '@vue/devtools-shared@7.6.4':
-    resolution: {integrity: sha512-nD6CUvBEel+y7zpyorjiUocy0nh77DThZJ0k1GRnJeOmY3ATq2fWijEp7wk37gb023Cb0R396uYh5qMSBQ5WFg==}
+  '@vue/devtools-shared@7.6.7':
+    resolution: {integrity: sha512-QggO6SviAsolrePAXZ/sA1dSicSPt4TueZibCvydfhNDieL1lAuyMTgQDGst7TEvMGb4vgYv2I+1sDkO4jWNnw==}
 
   '@vue/eslint-config-prettier@10.1.0':
     resolution: {integrity: sha512-J6wV91y2pXc0Phha01k0WOHBTPsoSTf4xlmMjoKaeSxBpAdsgTppGF5RZRdOHM7OA74zAXD+VLANrtYXpiPKkQ==}
@@ -1083,8 +1090,8 @@ packages:
   delaunator@5.0.1:
     resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==}
 
-  electron-to-chromium@1.5.65:
-    resolution: {integrity: sha512-PWVzBjghx7/wop6n22vS2MLU8tKGd4Q91aCEGhG/TYmW6PP5OcSXcdnxTe1NNt0T66N8D6jxh4kC8UsdzOGaIw==}
+  electron-to-chromium@1.5.67:
+    resolution: {integrity: sha512-nz88NNBsD7kQSAGGJyp8hS6xSPtWwqNogA0mjtc2nUYeEf3nURK9qpV18TuBdDmEDgVWotS8Wkzf+V52dSQ/LQ==}
 
   engine.io-client@6.6.2:
     resolution: {integrity: sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==}
@@ -1133,8 +1140,8 @@ packages:
       eslint-config-prettier:
         optional: true
 
-  eslint-plugin-vue@9.31.0:
-    resolution: {integrity: sha512-aYMUCgivhz1o4tLkRHj5oq9YgYPM4/EJc0M7TAKRLCUA5OYxRLAhYEVD2nLtTwLyixEFI+/QXSvKU9ESZFgqjQ==}
+  eslint-plugin-vue@9.32.0:
+    resolution: {integrity: sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug==}
     engines: {node: ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
@@ -1155,8 +1162,8 @@ packages:
     resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  eslint@9.15.0:
-    resolution: {integrity: sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==}
+  eslint@9.16.0:
+    resolution: {integrity: sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     hasBin: true
     peerDependencies:
@@ -1192,9 +1199,9 @@ packages:
     resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
     engines: {node: '>=0.10.0'}
 
-  execa@8.0.1:
-    resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
-    engines: {node: '>=16.17'}
+  execa@9.5.1:
+    resolution: {integrity: sha512-QY5PPtSonnGwhhHDNI7+3RvY285c7iuJFFB+lU+oEzMY/gEGJ808owqJsrr8Otd1E/x07po1LkUBmdAc5duPAg==}
+    engines: {node: ^18.19.0 || >=20.5.0}
 
   fast-deep-equal@3.1.3:
     resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -1215,6 +1222,10 @@ packages:
   fastq@1.17.1:
     resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
 
+  figures@6.1.0:
+    resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
+    engines: {node: '>=18'}
+
   file-entry-cache@8.0.0:
     resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
     engines: {node: '>=16.0.0'}
@@ -1247,9 +1258,9 @@ packages:
     resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
     engines: {node: '>=6.9.0'}
 
-  get-stream@8.0.1:
-    resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
-    engines: {node: '>=16'}
+  get-stream@9.0.1:
+    resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
+    engines: {node: '>=18'}
 
   glob-parent@5.1.2:
     resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
@@ -1292,9 +1303,9 @@ packages:
     resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
     engines: {node: '>=8'}
 
-  human-signals@5.0.0:
-    resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
-    engines: {node: '>=16.17.0'}
+  human-signals@8.0.0:
+    resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==}
+    engines: {node: '>=18.18.0'}
 
   iconv-lite@0.6.3:
     resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
@@ -1338,9 +1349,17 @@ packages:
     resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
     engines: {node: '>=0.12.0'}
 
-  is-stream@3.0.0:
-    resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+  is-plain-obj@4.1.0:
+    resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
+    engines: {node: '>=12'}
+
+  is-stream@4.0.1:
+    resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}
+    engines: {node: '>=18'}
+
+  is-unicode-supported@2.1.0:
+    resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
+    engines: {node: '>=18'}
 
   is-what@4.1.16:
     resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
@@ -1420,9 +1439,6 @@ packages:
     resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
     engines: {node: '>= 0.10.0'}
 
-  merge-stream@2.0.0:
-    resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
-
   merge2@1.4.1:
     resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
     engines: {node: '>= 8'}
@@ -1431,10 +1447,6 @@ packages:
     resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
     engines: {node: '>=8.6'}
 
-  mimic-fn@4.0.0:
-    resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
-    engines: {node: '>=12'}
-
   minimatch@3.1.2:
     resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
 
@@ -1455,11 +1467,16 @@ packages:
   muggle-string@0.4.1:
     resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
 
-  nanoid@3.3.7:
-    resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
+  nanoid@3.3.8:
+    resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
 
+  nanoid@5.0.9:
+    resolution: {integrity: sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==}
+    engines: {node: ^18 || >=20}
+    hasBin: true
+
   natural-compare@1.4.0:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
 
@@ -1475,17 +1492,13 @@ packages:
     engines: {node: ^18.17.0 || >=20.5.0, npm: '>= 9'}
     hasBin: true
 
-  npm-run-path@5.3.0:
-    resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+  npm-run-path@6.0.0:
+    resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
+    engines: {node: '>=18'}
 
   nth-check@2.1.1:
     resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
 
-  onetime@6.0.0:
-    resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
-    engines: {node: '>=12'}
-
   open@10.1.0:
     resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==}
     engines: {node: '>=18'}
@@ -1506,6 +1519,10 @@ packages:
     resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
     engines: {node: '>=6'}
 
+  parse-ms@4.0.0:
+    resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==}
+    engines: {node: '>=18'}
+
   path-browserify@1.0.1:
     resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
 
@@ -1543,8 +1560,8 @@ packages:
     engines: {node: '>=0.10'}
     hasBin: true
 
-  pinia@2.2.6:
-    resolution: {integrity: sha512-vIsR8JkDN5Ga2vAxqOE2cJj4VtsHnzpR1Fz30kClxlh0yCHfec6uoMeM3e/ddqmwFUejK3NlrcQa/shnpyT4hA==}
+  pinia@2.2.8:
+    resolution: {integrity: sha512-NRTYy2g+kju5tBRe0oNlriZIbMNvma8ZJrpHsp3qudyiMEA8jMmPPKQ2QMHg0Oc4BkUyQYWagACabrwriCK9HQ==}
     peerDependencies:
       '@vue/composition-api': ^1.4.0
       typescript: '>=4.4.4'
@@ -1576,6 +1593,10 @@ packages:
     engines: {node: '>=14'}
     hasBin: true
 
+  pretty-ms@9.2.0:
+    resolution: {integrity: sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==}
+    engines: {node: '>=18'}
+
   punycode@2.3.1:
     resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
     engines: {node: '>=6'}
@@ -1601,8 +1622,8 @@ packages:
   robust-predicates@3.0.2:
     resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
 
-  rollup@4.27.4:
-    resolution: {integrity: sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==}
+  rollup@4.28.0:
+    resolution: {integrity: sha512-G9GOrmgWHBma4YfCcX8PjH0qhXSdH8B4HDE2o4/jaxj93S4DPCIDoLcXz99eWMji4hB29UFCEd7B2gwGJDR9cQ==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
     hasBin: true
 
@@ -1636,8 +1657,9 @@ packages:
     resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
     engines: {node: '>=8'}
 
-  shell-quote@1.8.1:
-    resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==}
+  shell-quote@1.8.2:
+    resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==}
+    engines: {node: '>= 0.4'}
 
   signal-exit@4.1.0:
     resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
@@ -1663,9 +1685,9 @@ packages:
     resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
     engines: {node: '>=0.10.0'}
 
-  strip-final-newline@3.0.0:
-    resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
-    engines: {node: '>=12'}
+  strip-final-newline@4.0.0:
+    resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
+    engines: {node: '>=18'}
 
   strip-json-comments@3.1.1:
     resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
@@ -1694,8 +1716,8 @@ packages:
     resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
     engines: {node: '>=6'}
 
-  ts-api-utils@1.4.2:
-    resolution: {integrity: sha512-ZF5gQIQa/UmzfvxbHZI3JXN0/Jt+vnAfAviNRAMc491laiK6YCLpCW9ft8oaCRFOTxCZtUTE6XB0ZQAe3olntw==}
+  ts-api-utils@1.4.3:
+    resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
     engines: {node: '>=16'}
     peerDependencies:
       typescript: '>=4.2.0'
@@ -1729,6 +1751,10 @@ packages:
   undici-types@6.20.0:
     resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
 
+  unicorn-magic@0.3.0:
+    resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
+    engines: {node: '>=18'}
+
   universalify@2.0.1:
     resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
     engines: {node: '>= 10.0.0'}
@@ -1745,10 +1771,10 @@ packages:
   util-deprecate@1.0.2:
     resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
 
-  vite-hot-client@0.2.3:
-    resolution: {integrity: sha512-rOGAV7rUlUHX89fP2p2v0A2WWvV3QMX2UYq0fRqsWSvFvev4atHWqjwGoKaZT1VTKyLGk533ecu3eyd0o59CAg==}
+  vite-hot-client@0.2.4:
+    resolution: {integrity: sha512-a1nzURqO7DDmnXqabFOliz908FRmIppkBKsJthS8rbe8hBEXwEwe4C3Pp33Z1JoFCYfVL4kTOMLKk0ZZxREIeA==}
     peerDependencies:
-      vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0
+      vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0
 
   vite-plugin-inspect@0.8.8:
     resolution: {integrity: sha512-aZlBuXsWUPJFmMK92GIv6lH7LrwG2POu4KJ+aEdcqnu92OAf+rhBnfMDQvxIJPEB7hE2t5EyY/PMgf5aDLT8EA==}
@@ -1760,16 +1786,16 @@ packages:
       '@nuxt/kit':
         optional: true
 
-  vite-plugin-vue-devtools@7.6.4:
-    resolution: {integrity: sha512-jxSsLyuETfmZ1OSrmnDp28BG6rmURrP7lkeyHW2gBFDyo+4dUcqVeQNMhbV7uKZn80mDdv06Mysw/5AdGxDvJQ==}
+  vite-plugin-vue-devtools@7.6.7:
+    resolution: {integrity: sha512-H1ZyjtpWjP5mHA5R15sQeYgAARuh2Myg3TDFXWZK6QOQRy8s3XjTIt319DogVjU/x3rC3L/jJQjIasRU04mWXA==}
     engines: {node: '>=v14.21.3'}
     peerDependencies:
-      vite: ^3.1.0 || ^4.0.0-0 || ^5.0.0-0
+      vite: ^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0
 
-  vite-plugin-vue-inspector@5.3.0:
-    resolution: {integrity: sha512-F6JNRUOrZl8FaUCTxPhsOLn2ka7N7Sz9ppxmmEwpybVBDYnhelbNnnlZpeFPc4ULnxbitSi8b0V2C0KT3CjReg==}
+  vite-plugin-vue-inspector@5.3.1:
+    resolution: {integrity: sha512-cBk172kZKTdvGpJuzCCLg8lJ909wopwsu3Ve9FsL1XsnLBiRT9U3MePcqrgGHgCX2ZgkqZmAGR8taxw+TV6s7A==}
     peerDependencies:
-      vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0
+      vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0
 
   vite@5.4.11:
     resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==}
@@ -1882,6 +1908,10 @@ packages:
     resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
     engines: {node: '>=10'}
 
+  yoctocolors@2.1.1:
+    resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==}
+    engines: {node: '>=18'}
+
 snapshots:
 
   '@ampproject/remapping@2.3.0':
@@ -2149,9 +2179,9 @@ snapshots:
   '@esbuild/win32-x64@0.21.5':
     optional: true
 
-  '@eslint-community/eslint-utils@4.4.1(eslint@9.15.0)':
+  '@eslint-community/eslint-utils@4.4.1(eslint@9.16.0)':
     dependencies:
-      eslint: 9.15.0
+      eslint: 9.16.0
       eslint-visitor-keys: 3.4.3
 
   '@eslint-community/regexpp@4.12.1': {}
@@ -2180,7 +2210,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@eslint/js@9.15.0': {}
+  '@eslint/js@9.16.0': {}
 
   '@eslint/object-schema@2.1.4': {}
 
@@ -2234,68 +2264,72 @@ snapshots:
 
   '@polka/url@1.0.0-next.28': {}
 
-  '@rollup/pluginutils@5.1.3(rollup@4.27.4)':
+  '@rollup/pluginutils@5.1.3(rollup@4.28.0)':
     dependencies:
       '@types/estree': 1.0.6
       estree-walker: 2.0.2
       picomatch: 4.0.2
     optionalDependencies:
-      rollup: 4.27.4
+      rollup: 4.28.0
 
-  '@rollup/rollup-android-arm-eabi@4.27.4':
+  '@rollup/rollup-android-arm-eabi@4.28.0':
     optional: true
 
-  '@rollup/rollup-android-arm64@4.27.4':
+  '@rollup/rollup-android-arm64@4.28.0':
     optional: true
 
-  '@rollup/rollup-darwin-arm64@4.27.4':
+  '@rollup/rollup-darwin-arm64@4.28.0':
     optional: true
 
-  '@rollup/rollup-darwin-x64@4.27.4':
+  '@rollup/rollup-darwin-x64@4.28.0':
     optional: true
 
-  '@rollup/rollup-freebsd-arm64@4.27.4':
+  '@rollup/rollup-freebsd-arm64@4.28.0':
     optional: true
 
-  '@rollup/rollup-freebsd-x64@4.27.4':
+  '@rollup/rollup-freebsd-x64@4.28.0':
     optional: true
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.27.4':
+  '@rollup/rollup-linux-arm-gnueabihf@4.28.0':
     optional: true
 
-  '@rollup/rollup-linux-arm-musleabihf@4.27.4':
+  '@rollup/rollup-linux-arm-musleabihf@4.28.0':
     optional: true
 
-  '@rollup/rollup-linux-arm64-gnu@4.27.4':
+  '@rollup/rollup-linux-arm64-gnu@4.28.0':
     optional: true
 
-  '@rollup/rollup-linux-arm64-musl@4.27.4':
+  '@rollup/rollup-linux-arm64-musl@4.28.0':
     optional: true
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.27.4':
+  '@rollup/rollup-linux-powerpc64le-gnu@4.28.0':
     optional: true
 
-  '@rollup/rollup-linux-riscv64-gnu@4.27.4':
+  '@rollup/rollup-linux-riscv64-gnu@4.28.0':
     optional: true
 
-  '@rollup/rollup-linux-s390x-gnu@4.27.4':
+  '@rollup/rollup-linux-s390x-gnu@4.28.0':
     optional: true
 
-  '@rollup/rollup-linux-x64-gnu@4.27.4':
+  '@rollup/rollup-linux-x64-gnu@4.28.0':
     optional: true
 
-  '@rollup/rollup-linux-x64-musl@4.27.4':
+  '@rollup/rollup-linux-x64-musl@4.28.0':
     optional: true
 
-  '@rollup/rollup-win32-arm64-msvc@4.27.4':
+  '@rollup/rollup-win32-arm64-msvc@4.28.0':
     optional: true
 
-  '@rollup/rollup-win32-ia32-msvc@4.27.4':
+  '@rollup/rollup-win32-ia32-msvc@4.28.0':
     optional: true
 
-  '@rollup/rollup-win32-x64-msvc@4.27.4':
+  '@rollup/rollup-win32-x64-msvc@4.28.0':
     optional: true
 
+  '@sec-ant/readable-stream@0.4.1': {}
+
+  '@sindresorhus/merge-streams@4.0.0': {}
+
   '@socket.io/component-emitter@3.1.2': {}
 
   '@tsconfig/node22@22.0.0': {}
@@ -2357,7 +2391,7 @@ snapshots:
 
   '@types/d3-random@3.0.3': {}
 
-  '@types/d3-scale-chromatic@3.0.3': {}
+  '@types/d3-scale-chromatic@3.1.0': {}
 
   '@types/d3-scale@4.0.8':
     dependencies:
@@ -2408,7 +2442,7 @@ snapshots:
       '@types/d3-quadtree': 3.0.6
       '@types/d3-random': 3.0.3
       '@types/d3-scale': 4.0.8
-      '@types/d3-scale-chromatic': 3.0.3
+      '@types/d3-scale-chromatic': 3.1.0
       '@types/d3-selection': 3.0.11
       '@types/d3-shape': 3.1.6
       '@types/d3-time': 3.0.4
@@ -2423,36 +2457,36 @@ snapshots:
 
   '@types/json-schema@7.0.15': {}
 
-  '@types/node@22.10.0':
+  '@types/node@22.10.1':
     dependencies:
       undici-types: 6.20.0
 
-  '@typescript-eslint/eslint-plugin@8.16.0(@typescript-eslint/parser@8.16.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3)':
+  '@typescript-eslint/eslint-plugin@8.16.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0)(typescript@5.6.3))(eslint@9.16.0)(typescript@5.6.3)':
     dependencies:
       '@eslint-community/regexpp': 4.12.1
-      '@typescript-eslint/parser': 8.16.0(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/parser': 8.16.0(eslint@9.16.0)(typescript@5.6.3)
       '@typescript-eslint/scope-manager': 8.16.0
-      '@typescript-eslint/type-utils': 8.16.0(eslint@9.15.0)(typescript@5.6.3)
-      '@typescript-eslint/utils': 8.16.0(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/type-utils': 8.16.0(eslint@9.16.0)(typescript@5.6.3)
+      '@typescript-eslint/utils': 8.16.0(eslint@9.16.0)(typescript@5.6.3)
       '@typescript-eslint/visitor-keys': 8.16.0
-      eslint: 9.15.0
+      eslint: 9.16.0
       graphemer: 1.4.0
       ignore: 5.3.2
       natural-compare: 1.4.0
-      ts-api-utils: 1.4.2(typescript@5.6.3)
+      ts-api-utils: 1.4.3(typescript@5.6.3)
     optionalDependencies:
       typescript: 5.6.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/parser@8.16.0(eslint@9.15.0)(typescript@5.6.3)':
+  '@typescript-eslint/parser@8.16.0(eslint@9.16.0)(typescript@5.6.3)':
     dependencies:
       '@typescript-eslint/scope-manager': 8.16.0
       '@typescript-eslint/types': 8.16.0
       '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.6.3)
       '@typescript-eslint/visitor-keys': 8.16.0
       debug: 4.3.7
-      eslint: 9.15.0
+      eslint: 9.16.0
     optionalDependencies:
       typescript: 5.6.3
     transitivePeerDependencies:
@@ -2463,13 +2497,13 @@ snapshots:
       '@typescript-eslint/types': 8.16.0
       '@typescript-eslint/visitor-keys': 8.16.0
 
-  '@typescript-eslint/type-utils@8.16.0(eslint@9.15.0)(typescript@5.6.3)':
+  '@typescript-eslint/type-utils@8.16.0(eslint@9.16.0)(typescript@5.6.3)':
     dependencies:
       '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.6.3)
-      '@typescript-eslint/utils': 8.16.0(eslint@9.15.0)(typescript@5.6.3)
+      '@typescript-eslint/utils': 8.16.0(eslint@9.16.0)(typescript@5.6.3)
       debug: 4.3.7
-      eslint: 9.15.0
-      ts-api-utils: 1.4.2(typescript@5.6.3)
+      eslint: 9.16.0
+      ts-api-utils: 1.4.3(typescript@5.6.3)
     optionalDependencies:
       typescript: 5.6.3
     transitivePeerDependencies:
@@ -2486,19 +2520,19 @@ snapshots:
       is-glob: 4.0.3
       minimatch: 9.0.5
       semver: 7.6.3
-      ts-api-utils: 1.4.2(typescript@5.6.3)
+      ts-api-utils: 1.4.3(typescript@5.6.3)
     optionalDependencies:
       typescript: 5.6.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/utils@8.16.0(eslint@9.15.0)(typescript@5.6.3)':
+  '@typescript-eslint/utils@8.16.0(eslint@9.16.0)(typescript@5.6.3)':
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0)
+      '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0)
       '@typescript-eslint/scope-manager': 8.16.0
       '@typescript-eslint/types': 8.16.0
       '@typescript-eslint/typescript-estree': 8.16.0(typescript@5.6.3)
-      eslint: 9.15.0
+      eslint: 9.16.0
     optionalDependencies:
       typescript: 5.6.3
     transitivePeerDependencies:
@@ -2509,9 +2543,9 @@ snapshots:
       '@typescript-eslint/types': 8.16.0
       eslint-visitor-keys: 4.2.0
 
-  '@vitejs/plugin-vue@5.2.0(vite@5.4.11(@types/node@22.10.0))(vue@3.5.13(typescript@5.6.3))':
+  '@vitejs/plugin-vue@5.2.1(vite@5.4.11(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
-      vite: 5.4.11(@types/node@22.10.0)
+      vite: 5.4.11(@types/node@22.10.1)
       vue: 3.5.13(typescript@5.6.3)
 
   '@volar/language-core@2.4.10':
@@ -2593,21 +2627,21 @@ snapshots:
 
   '@vue/devtools-api@6.6.4': {}
 
-  '@vue/devtools-core@7.6.4(vite@5.4.11(@types/node@22.10.0))(vue@3.5.13(typescript@5.6.3))':
+  '@vue/devtools-core@7.6.7(vite@5.4.11(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
-      '@vue/devtools-kit': 7.6.4
-      '@vue/devtools-shared': 7.6.4
+      '@vue/devtools-kit': 7.6.7
+      '@vue/devtools-shared': 7.6.7
       mitt: 3.0.1
-      nanoid: 3.3.7
+      nanoid: 5.0.9
       pathe: 1.1.2
-      vite-hot-client: 0.2.3(vite@5.4.11(@types/node@22.10.0))
+      vite-hot-client: 0.2.4(vite@5.4.11(@types/node@22.10.1))
       vue: 3.5.13(typescript@5.6.3)
     transitivePeerDependencies:
       - vite
 
-  '@vue/devtools-kit@7.6.4':
+  '@vue/devtools-kit@7.6.7':
     dependencies:
-      '@vue/devtools-shared': 7.6.4
+      '@vue/devtools-shared': 7.6.7
       birpc: 0.2.19
       hookable: 5.5.3
       mitt: 3.0.1
@@ -2615,27 +2649,27 @@ snapshots:
       speakingurl: 14.0.1
       superjson: 2.2.1
 
-  '@vue/devtools-shared@7.6.4':
+  '@vue/devtools-shared@7.6.7':
     dependencies:
       rfdc: 1.4.1
 
-  '@vue/eslint-config-prettier@10.1.0(eslint@9.15.0)(prettier@3.4.0)':
+  '@vue/eslint-config-prettier@10.1.0(eslint@9.16.0)(prettier@3.4.0)':
     dependencies:
-      eslint: 9.15.0
-      eslint-config-prettier: 9.1.0(eslint@9.15.0)
-      eslint-plugin-prettier: 5.2.1(eslint-config-prettier@9.1.0(eslint@9.15.0))(eslint@9.15.0)(prettier@3.4.0)
+      eslint: 9.16.0
+      eslint-config-prettier: 9.1.0(eslint@9.16.0)
+      eslint-plugin-prettier: 5.2.1(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint@9.16.0)(prettier@3.4.0)
       prettier: 3.4.0
     transitivePeerDependencies:
       - '@types/eslint'
 
-  '@vue/eslint-config-typescript@14.1.4(@typescript-eslint/parser@8.16.0(eslint@9.15.0)(typescript@5.6.3))(eslint-plugin-vue@9.31.0(eslint@9.15.0))(eslint@9.15.0)(typescript@5.6.3)':
+  '@vue/eslint-config-typescript@14.1.4(@typescript-eslint/parser@8.16.0(eslint@9.16.0)(typescript@5.6.3))(eslint-plugin-vue@9.32.0(eslint@9.16.0))(eslint@9.16.0)(typescript@5.6.3)':
     dependencies:
-      '@typescript-eslint/eslint-plugin': 8.16.0(@typescript-eslint/parser@8.16.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3)
-      eslint: 9.15.0
-      eslint-plugin-vue: 9.31.0(eslint@9.15.0)
+      '@typescript-eslint/eslint-plugin': 8.16.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0)(typescript@5.6.3))(eslint@9.16.0)(typescript@5.6.3)
+      eslint: 9.16.0
+      eslint-plugin-vue: 9.32.0(eslint@9.16.0)
       fast-glob: 3.3.2
-      typescript-eslint: 8.16.0(eslint@9.15.0)(typescript@5.6.3)
-      vue-eslint-parser: 9.4.3(eslint@9.15.0)
+      typescript-eslint: 8.16.0(eslint@9.16.0)(typescript@5.6.3)
+      vue-eslint-parser: 9.4.3(eslint@9.16.0)
     optionalDependencies:
       typescript: 5.6.3
     transitivePeerDependencies:
@@ -2729,7 +2763,7 @@ snapshots:
   browserslist@4.24.2:
     dependencies:
       caniuse-lite: 1.0.30001684
-      electron-to-chromium: 1.5.65
+      electron-to-chromium: 1.5.67
       node-releases: 2.0.18
       update-browserslist-db: 1.1.1(browserslist@4.24.2)
 
@@ -2945,7 +2979,7 @@ snapshots:
     dependencies:
       robust-predicates: 3.0.2
 
-  electron-to-chromium@1.5.65: {}
+  electron-to-chromium@1.5.67: {}
 
   engine.io-client@6.6.2:
     dependencies:
@@ -2995,29 +3029,29 @@ snapshots:
 
   escape-string-regexp@4.0.0: {}
 
-  eslint-config-prettier@9.1.0(eslint@9.15.0):
+  eslint-config-prettier@9.1.0(eslint@9.16.0):
     dependencies:
-      eslint: 9.15.0
+      eslint: 9.16.0
 
-  eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.15.0))(eslint@9.15.0)(prettier@3.4.0):
+  eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@9.16.0))(eslint@9.16.0)(prettier@3.4.0):
     dependencies:
-      eslint: 9.15.0
+      eslint: 9.16.0
       prettier: 3.4.0
       prettier-linter-helpers: 1.0.0
       synckit: 0.9.2
     optionalDependencies:
-      eslint-config-prettier: 9.1.0(eslint@9.15.0)
+      eslint-config-prettier: 9.1.0(eslint@9.16.0)
 
-  eslint-plugin-vue@9.31.0(eslint@9.15.0):
+  eslint-plugin-vue@9.32.0(eslint@9.16.0):
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0)
-      eslint: 9.15.0
+      '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0)
+      eslint: 9.16.0
       globals: 13.24.0
       natural-compare: 1.4.0
       nth-check: 2.1.1
       postcss-selector-parser: 6.1.2
       semver: 7.6.3
-      vue-eslint-parser: 9.4.3(eslint@9.15.0)
+      vue-eslint-parser: 9.4.3(eslint@9.16.0)
       xml-name-validator: 4.0.0
     transitivePeerDependencies:
       - supports-color
@@ -3036,14 +3070,14 @@ snapshots:
 
   eslint-visitor-keys@4.2.0: {}
 
-  eslint@9.15.0:
+  eslint@9.16.0:
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0)
+      '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0)
       '@eslint-community/regexpp': 4.12.1
       '@eslint/config-array': 0.19.0
       '@eslint/core': 0.9.0
       '@eslint/eslintrc': 3.2.0
-      '@eslint/js': 9.15.0
+      '@eslint/js': 9.16.0
       '@eslint/plugin-kit': 0.2.3
       '@humanfs/node': 0.16.6
       '@humanwhocodes/module-importer': 1.0.1
@@ -3101,17 +3135,20 @@ snapshots:
 
   esutils@2.0.3: {}
 
-  execa@8.0.1:
+  execa@9.5.1:
     dependencies:
+      '@sindresorhus/merge-streams': 4.0.0
       cross-spawn: 7.0.6
-      get-stream: 8.0.1
-      human-signals: 5.0.0
-      is-stream: 3.0.0
-      merge-stream: 2.0.0
-      npm-run-path: 5.3.0
-      onetime: 6.0.0
+      figures: 6.1.0
+      get-stream: 9.0.1
+      human-signals: 8.0.0
+      is-plain-obj: 4.1.0
+      is-stream: 4.0.1
+      npm-run-path: 6.0.0
+      pretty-ms: 9.2.0
       signal-exit: 4.1.0
-      strip-final-newline: 3.0.0
+      strip-final-newline: 4.0.0
+      yoctocolors: 2.1.1
 
   fast-deep-equal@3.1.3: {}
 
@@ -3133,6 +3170,10 @@ snapshots:
     dependencies:
       reusify: 1.0.4
 
+  figures@6.1.0:
+    dependencies:
+      is-unicode-supported: 2.1.0
+
   file-entry-cache@8.0.0:
     dependencies:
       flat-cache: 4.0.1
@@ -3164,7 +3205,10 @@ snapshots:
 
   gensync@1.0.0-beta.2: {}
 
-  get-stream@8.0.1: {}
+  get-stream@9.0.1:
+    dependencies:
+      '@sec-ant/readable-stream': 0.4.1
+      is-stream: 4.0.1
 
   glob-parent@5.1.2:
     dependencies:
@@ -3194,7 +3238,7 @@ snapshots:
 
   html-tags@3.3.1: {}
 
-  human-signals@5.0.0: {}
+  human-signals@8.0.0: {}
 
   iconv-lite@0.6.3:
     dependencies:
@@ -3225,7 +3269,11 @@ snapshots:
 
   is-number@7.0.0: {}
 
-  is-stream@3.0.0: {}
+  is-plain-obj@4.1.0: {}
+
+  is-stream@4.0.1: {}
+
+  is-unicode-supported@2.1.0: {}
 
   is-what@4.1.16: {}
 
@@ -3290,8 +3338,6 @@ snapshots:
 
   memorystream@0.3.1: {}
 
-  merge-stream@2.0.0: {}
-
   merge2@1.4.1: {}
 
   micromatch@4.0.8:
@@ -3299,8 +3345,6 @@ snapshots:
       braces: 3.0.3
       picomatch: 2.3.1
 
-  mimic-fn@4.0.0: {}
-
   minimatch@3.1.2:
     dependencies:
       brace-expansion: 1.1.11
@@ -3317,7 +3361,9 @@ snapshots:
 
   muggle-string@0.4.1: {}
 
-  nanoid@3.3.7: {}
+  nanoid@3.3.8: {}
+
+  nanoid@5.0.9: {}
 
   natural-compare@1.4.0: {}
 
@@ -3333,21 +3379,18 @@ snapshots:
       minimatch: 9.0.5
       pidtree: 0.6.0
       read-package-json-fast: 4.0.0
-      shell-quote: 1.8.1
+      shell-quote: 1.8.2
       which: 5.0.0
 
-  npm-run-path@5.3.0:
+  npm-run-path@6.0.0:
     dependencies:
       path-key: 4.0.0
+      unicorn-magic: 0.3.0
 
   nth-check@2.1.1:
     dependencies:
       boolbase: 1.0.0
 
-  onetime@6.0.0:
-    dependencies:
-      mimic-fn: 4.0.0
-
   open@10.1.0:
     dependencies:
       default-browser: 5.2.1
@@ -3376,6 +3419,8 @@ snapshots:
     dependencies:
       callsites: 3.1.0
 
+  parse-ms@4.0.0: {}
+
   path-browserify@1.0.1: {}
 
   path-exists@4.0.0: {}
@@ -3396,7 +3441,7 @@ snapshots:
 
   pidtree@0.6.0: {}
 
-  pinia@2.2.6(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3)):
+  pinia@2.2.8(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3)):
     dependencies:
       '@vue/devtools-api': 6.6.4
       vue: 3.5.13(typescript@5.6.3)
@@ -3411,7 +3456,7 @@ snapshots:
 
   postcss@8.4.49:
     dependencies:
-      nanoid: 3.3.7
+      nanoid: 3.3.8
       picocolors: 1.1.1
       source-map-js: 1.2.1
 
@@ -3423,6 +3468,10 @@ snapshots:
 
   prettier@3.4.0: {}
 
+  pretty-ms@9.2.0:
+    dependencies:
+      parse-ms: 4.0.0
+
   punycode@2.3.1: {}
 
   queue-microtask@1.2.3: {}
@@ -3440,28 +3489,28 @@ snapshots:
 
   robust-predicates@3.0.2: {}
 
-  rollup@4.27.4:
+  rollup@4.28.0:
     dependencies:
       '@types/estree': 1.0.6
     optionalDependencies:
-      '@rollup/rollup-android-arm-eabi': 4.27.4
-      '@rollup/rollup-android-arm64': 4.27.4
-      '@rollup/rollup-darwin-arm64': 4.27.4
-      '@rollup/rollup-darwin-x64': 4.27.4
-      '@rollup/rollup-freebsd-arm64': 4.27.4
-      '@rollup/rollup-freebsd-x64': 4.27.4
-      '@rollup/rollup-linux-arm-gnueabihf': 4.27.4
-      '@rollup/rollup-linux-arm-musleabihf': 4.27.4
-      '@rollup/rollup-linux-arm64-gnu': 4.27.4
-      '@rollup/rollup-linux-arm64-musl': 4.27.4
-      '@rollup/rollup-linux-powerpc64le-gnu': 4.27.4
-      '@rollup/rollup-linux-riscv64-gnu': 4.27.4
-      '@rollup/rollup-linux-s390x-gnu': 4.27.4
-      '@rollup/rollup-linux-x64-gnu': 4.27.4
-      '@rollup/rollup-linux-x64-musl': 4.27.4
-      '@rollup/rollup-win32-arm64-msvc': 4.27.4
-      '@rollup/rollup-win32-ia32-msvc': 4.27.4
-      '@rollup/rollup-win32-x64-msvc': 4.27.4
+      '@rollup/rollup-android-arm-eabi': 4.28.0
+      '@rollup/rollup-android-arm64': 4.28.0
+      '@rollup/rollup-darwin-arm64': 4.28.0
+      '@rollup/rollup-darwin-x64': 4.28.0
+      '@rollup/rollup-freebsd-arm64': 4.28.0
+      '@rollup/rollup-freebsd-x64': 4.28.0
+      '@rollup/rollup-linux-arm-gnueabihf': 4.28.0
+      '@rollup/rollup-linux-arm-musleabihf': 4.28.0
+      '@rollup/rollup-linux-arm64-gnu': 4.28.0
+      '@rollup/rollup-linux-arm64-musl': 4.28.0
+      '@rollup/rollup-linux-powerpc64le-gnu': 4.28.0
+      '@rollup/rollup-linux-riscv64-gnu': 4.28.0
+      '@rollup/rollup-linux-s390x-gnu': 4.28.0
+      '@rollup/rollup-linux-x64-gnu': 4.28.0
+      '@rollup/rollup-linux-x64-musl': 4.28.0
+      '@rollup/rollup-win32-arm64-msvc': 4.28.0
+      '@rollup/rollup-win32-ia32-msvc': 4.28.0
+      '@rollup/rollup-win32-x64-msvc': 4.28.0
       fsevents: 2.3.3
 
   run-applescript@7.0.0: {}
@@ -3484,7 +3533,7 @@ snapshots:
 
   shebang-regex@3.0.0: {}
 
-  shell-quote@1.8.1: {}
+  shell-quote@1.8.2: {}
 
   signal-exit@4.1.0: {}
 
@@ -3516,7 +3565,7 @@ snapshots:
 
   speakingurl@14.0.1: {}
 
-  strip-final-newline@3.0.0: {}
+  strip-final-newline@4.0.0: {}
 
   strip-json-comments@3.1.1: {}
 
@@ -3541,7 +3590,7 @@ snapshots:
 
   totalist@3.0.1: {}
 
-  ts-api-utils@1.4.2(typescript@5.6.3):
+  ts-api-utils@1.4.3(typescript@5.6.3):
     dependencies:
       typescript: 5.6.3
 
@@ -3553,12 +3602,12 @@ snapshots:
 
   type-fest@0.20.2: {}
 
-  typescript-eslint@8.16.0(eslint@9.15.0)(typescript@5.6.3):
+  typescript-eslint@8.16.0(eslint@9.16.0)(typescript@5.6.3):
     dependencies:
-      '@typescript-eslint/eslint-plugin': 8.16.0(@typescript-eslint/parser@8.16.0(eslint@9.15.0)(typescript@5.6.3))(eslint@9.15.0)(typescript@5.6.3)
-      '@typescript-eslint/parser': 8.16.0(eslint@9.15.0)(typescript@5.6.3)
-      '@typescript-eslint/utils': 8.16.0(eslint@9.15.0)(typescript@5.6.3)
-      eslint: 9.15.0
+      '@typescript-eslint/eslint-plugin': 8.16.0(@typescript-eslint/parser@8.16.0(eslint@9.16.0)(typescript@5.6.3))(eslint@9.16.0)(typescript@5.6.3)
+      '@typescript-eslint/parser': 8.16.0(eslint@9.16.0)(typescript@5.6.3)
+      '@typescript-eslint/utils': 8.16.0(eslint@9.16.0)(typescript@5.6.3)
+      eslint: 9.16.0
     optionalDependencies:
       typescript: 5.6.3
     transitivePeerDependencies:
@@ -3568,6 +3617,8 @@ snapshots:
 
   undici-types@6.20.0: {}
 
+  unicorn-magic@0.3.0: {}
+
   universalify@2.0.1: {}
 
   update-browserslist-db@1.1.1(browserslist@4.24.2):
@@ -3582,14 +3633,14 @@ snapshots:
 
   util-deprecate@1.0.2: {}
 
-  vite-hot-client@0.2.3(vite@5.4.11(@types/node@22.10.0)):
+  vite-hot-client@0.2.4(vite@5.4.11(@types/node@22.10.1)):
     dependencies:
-      vite: 5.4.11(@types/node@22.10.0)
+      vite: 5.4.11(@types/node@22.10.1)
 
-  vite-plugin-inspect@0.8.8(rollup@4.27.4)(vite@5.4.11(@types/node@22.10.0)):
+  vite-plugin-inspect@0.8.8(rollup@4.28.0)(vite@5.4.11(@types/node@22.10.1)):
     dependencies:
       '@antfu/utils': 0.7.10
-      '@rollup/pluginutils': 5.1.3(rollup@4.27.4)
+      '@rollup/pluginutils': 5.1.3(rollup@4.28.0)
       debug: 4.3.7
       error-stack-parser-es: 0.1.5
       fs-extra: 11.2.0
@@ -3597,28 +3648,28 @@ snapshots:
       perfect-debounce: 1.0.0
       picocolors: 1.1.1
       sirv: 3.0.0
-      vite: 5.4.11(@types/node@22.10.0)
+      vite: 5.4.11(@types/node@22.10.1)
     transitivePeerDependencies:
       - rollup
       - supports-color
 
-  vite-plugin-vue-devtools@7.6.4(rollup@4.27.4)(vite@5.4.11(@types/node@22.10.0))(vue@3.5.13(typescript@5.6.3)):
+  vite-plugin-vue-devtools@7.6.7(rollup@4.28.0)(vite@5.4.11(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3)):
     dependencies:
-      '@vue/devtools-core': 7.6.4(vite@5.4.11(@types/node@22.10.0))(vue@3.5.13(typescript@5.6.3))
-      '@vue/devtools-kit': 7.6.4
-      '@vue/devtools-shared': 7.6.4
-      execa: 8.0.1
+      '@vue/devtools-core': 7.6.7(vite@5.4.11(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
+      '@vue/devtools-kit': 7.6.7
+      '@vue/devtools-shared': 7.6.7
+      execa: 9.5.1
       sirv: 3.0.0
-      vite: 5.4.11(@types/node@22.10.0)
-      vite-plugin-inspect: 0.8.8(rollup@4.27.4)(vite@5.4.11(@types/node@22.10.0))
-      vite-plugin-vue-inspector: 5.3.0(vite@5.4.11(@types/node@22.10.0))
+      vite: 5.4.11(@types/node@22.10.1)
+      vite-plugin-inspect: 0.8.8(rollup@4.28.0)(vite@5.4.11(@types/node@22.10.1))
+      vite-plugin-vue-inspector: 5.3.1(vite@5.4.11(@types/node@22.10.1))
     transitivePeerDependencies:
       - '@nuxt/kit'
       - rollup
       - supports-color
       - vue
 
-  vite-plugin-vue-inspector@5.3.0(vite@5.4.11(@types/node@22.10.0)):
+  vite-plugin-vue-inspector@5.3.1(vite@5.4.11(@types/node@22.10.1)):
     dependencies:
       '@babel/core': 7.26.0
       '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.0)
@@ -3629,17 +3680,17 @@ snapshots:
       '@vue/compiler-dom': 3.5.13
       kolorist: 1.8.0
       magic-string: 0.30.14
-      vite: 5.4.11(@types/node@22.10.0)
+      vite: 5.4.11(@types/node@22.10.1)
     transitivePeerDependencies:
       - supports-color
 
-  vite@5.4.11(@types/node@22.10.0):
+  vite@5.4.11(@types/node@22.10.1):
     dependencies:
       esbuild: 0.21.5
       postcss: 8.4.49
-      rollup: 4.27.4
+      rollup: 4.28.0
     optionalDependencies:
-      '@types/node': 22.10.0
+      '@types/node': 22.10.1
       fsevents: 2.3.3
 
   vscode-uri@3.0.8: {}
@@ -3648,10 +3699,10 @@ snapshots:
     dependencies:
       vue: 3.5.13(typescript@5.6.3)
 
-  vue-eslint-parser@9.4.3(eslint@9.15.0):
+  vue-eslint-parser@9.4.3(eslint@9.16.0):
     dependencies:
       debug: 4.3.7
-      eslint: 9.15.0
+      eslint: 9.16.0
       eslint-scope: 7.2.2
       eslint-visitor-keys: 3.4.3
       espree: 9.6.1
@@ -3702,3 +3753,5 @@ snapshots:
   yallist@3.1.1: {}
 
   yocto-queue@0.1.0: {}
+
+  yoctocolors@2.1.1: {}

From 6917b1f72bb858de044c840d02cd80098a1e9078 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 1 Dec 2024 06:52:52 -0500
Subject: [PATCH 27/34] Update dependency @vue/tsconfig to ^0.7.0 (#12)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 pi/ui/package.json   |  2 +-
 pi/ui/pnpm-lock.yaml | 12 ++++++------
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/pi/ui/package.json b/pi/ui/package.json
index 80adb72..55473c3 100644
--- a/pi/ui/package.json
+++ b/pi/ui/package.json
@@ -23,7 +23,7 @@
     "@vitejs/plugin-vue": "^5.2.1",
     "@vue/eslint-config-prettier": "^10.1.0",
     "@vue/eslint-config-typescript": "^14.1.4",
-    "@vue/tsconfig": "^0.6.0",
+    "@vue/tsconfig": "^0.7.0",
     "eslint": "^9.16.0",
     "eslint-plugin-vue": "^9.32.0",
     "npm-run-all2": "^7.0.1",
diff --git a/pi/ui/pnpm-lock.yaml b/pi/ui/pnpm-lock.yaml
index 2cf3913..c034a1d 100644
--- a/pi/ui/pnpm-lock.yaml
+++ b/pi/ui/pnpm-lock.yaml
@@ -43,8 +43,8 @@ importers:
         specifier: ^14.1.4
         version: 14.1.4(@typescript-eslint/parser@8.16.0(eslint@9.16.0)(typescript@5.6.3))(eslint-plugin-vue@9.32.0(eslint@9.16.0))(eslint@9.16.0)(typescript@5.6.3)
       '@vue/tsconfig':
-        specifier: ^0.6.0
-        version: 0.6.0(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3))
+        specifier: ^0.7.0
+        version: 0.7.0(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3))
       eslint:
         specifier: ^9.16.0
         version: 9.16.0
@@ -823,11 +823,11 @@ packages:
   '@vue/shared@3.5.13':
     resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==}
 
-  '@vue/tsconfig@0.6.0':
-    resolution: {integrity: sha512-MHXNd6lzugsEHvuA6l1GqrF5jROqUon8sP/HInLPnthJiYvB0VvpHMywg7em1dBZfFZNBSkR68qH37zOdRHmCw==}
+  '@vue/tsconfig@0.7.0':
+    resolution: {integrity: sha512-ku2uNz5MaZ9IerPPUyOHzyjhXoX2kVJaVf7hL315DC17vS6IiZRmmCPfggNbU16QTvM80+uYYy3eYJB59WCtvg==}
     peerDependencies:
       typescript: 5.x
-      vue: ^3.3.0
+      vue: ^3.4.0
     peerDependenciesMeta:
       typescript:
         optional: true
@@ -2713,7 +2713,7 @@ snapshots:
 
   '@vue/shared@3.5.13': {}
 
-  '@vue/tsconfig@0.6.0(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3))':
+  '@vue/tsconfig@0.7.0(typescript@5.6.3)(vue@3.5.13(typescript@5.6.3))':
     optionalDependencies:
       typescript: 5.6.3
       vue: 3.5.13(typescript@5.6.3)

From 9ec61d8d64ff180d75d55b2b8cc801891cb72077 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 1 Dec 2024 06:53:12 -0500
Subject: [PATCH 28/34] Update dependency eslint to v9.16.0 (#37)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 pi/server/pnpm-lock.yaml | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/pi/server/pnpm-lock.yaml b/pi/server/pnpm-lock.yaml
index 77f056b..be24fa3 100644
--- a/pi/server/pnpm-lock.yaml
+++ b/pi/server/pnpm-lock.yaml
@@ -35,7 +35,7 @@ importers:
         version: 22.10.0
       eslint:
         specifier: ^9.15.0
-        version: 9.15.0
+        version: 9.16.0
       globals:
         specifier: ^15.12.0
         version: 15.12.0
@@ -87,8 +87,8 @@ packages:
     resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@eslint/js@9.15.0':
-    resolution: {integrity: sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg==}
+  '@eslint/js@9.16.0':
+    resolution: {integrity: sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
   '@eslint/object-schema@2.1.4':
@@ -419,8 +419,8 @@ packages:
     resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  eslint@9.15.0:
-    resolution: {integrity: sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw==}
+  eslint@9.16.0:
+    resolution: {integrity: sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
     hasBin: true
     peerDependencies:
@@ -1074,9 +1074,9 @@ snapshots:
     dependencies:
       '@jridgewell/trace-mapping': 0.3.9
 
-  '@eslint-community/eslint-utils@4.4.1(eslint@9.15.0)':
+  '@eslint-community/eslint-utils@4.4.1(eslint@9.16.0)':
     dependencies:
-      eslint: 9.15.0
+      eslint: 9.16.0
       eslint-visitor-keys: 3.4.3
 
   '@eslint-community/regexpp@4.12.1': {}
@@ -1105,7 +1105,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@eslint/js@9.15.0': {}
+  '@eslint/js@9.16.0': {}
 
   '@eslint/object-schema@2.1.4': {}
 
@@ -1422,14 +1422,14 @@ snapshots:
 
   eslint-visitor-keys@4.2.0: {}
 
-  eslint@9.15.0:
+  eslint@9.16.0:
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.1(eslint@9.15.0)
+      '@eslint-community/eslint-utils': 4.4.1(eslint@9.16.0)
       '@eslint-community/regexpp': 4.12.1
       '@eslint/config-array': 0.19.0
       '@eslint/core': 0.9.0
       '@eslint/eslintrc': 3.2.0
-      '@eslint/js': 9.15.0
+      '@eslint/js': 9.16.0
       '@eslint/plugin-kit': 0.2.3
       '@humanfs/node': 0.16.6
       '@humanwhocodes/module-importer': 1.0.1

From 8d7ec4ccf5cb94a60c1f32c0f97f08fce47bad67 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 1 Dec 2024 06:53:21 -0500
Subject: [PATCH 29/34] Update dependency vite to v6 (#33)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 pi/ui/package.json   |   2 +-
 pi/ui/pnpm-lock.yaml | 309 +++++++++++++++++++++++--------------------
 2 files changed, 165 insertions(+), 146 deletions(-)

diff --git a/pi/ui/package.json b/pi/ui/package.json
index 55473c3..7422b05 100644
--- a/pi/ui/package.json
+++ b/pi/ui/package.json
@@ -28,7 +28,7 @@
     "eslint-plugin-vue": "^9.32.0",
     "npm-run-all2": "^7.0.1",
     "typescript": "~5.6.3",
-    "vite": "^5.4.11",
+    "vite": "^6.0.0",
     "vite-plugin-vue-devtools": "^7.6.7",
     "vue-tsc": "^2.1.10"
   },
diff --git a/pi/ui/pnpm-lock.yaml b/pi/ui/pnpm-lock.yaml
index c034a1d..ba70dcc 100644
--- a/pi/ui/pnpm-lock.yaml
+++ b/pi/ui/pnpm-lock.yaml
@@ -35,7 +35,7 @@ importers:
         version: 22.10.1
       '@vitejs/plugin-vue':
         specifier: ^5.2.1
-        version: 5.2.1(vite@5.4.11(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
+        version: 5.2.1(vite@6.0.1(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
       '@vue/eslint-config-prettier':
         specifier: ^10.1.0
         version: 10.1.0(eslint@9.16.0)(prettier@3.4.0)
@@ -58,11 +58,11 @@ importers:
         specifier: ~5.6.3
         version: 5.6.3
       vite:
-        specifier: ^5.4.11
-        version: 5.4.11(@types/node@22.10.1)
+        specifier: ^6.0.0
+        version: 6.0.1(@types/node@22.10.1)
       vite-plugin-vue-devtools:
         specifier: ^7.6.7
-        version: 7.6.7(rollup@4.28.0)(vite@5.4.11(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
+        version: 7.6.7(rollup@4.28.0)(vite@6.0.1(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
       vue-tsc:
         specifier: ^2.1.10
         version: 2.1.10(typescript@5.6.3)
@@ -212,141 +212,147 @@ packages:
     resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==}
     engines: {node: '>=6.9.0'}
 
-  '@esbuild/aix-ppc64@0.21.5':
-    resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
-    engines: {node: '>=12'}
+  '@esbuild/aix-ppc64@0.24.0':
+    resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==}
+    engines: {node: '>=18'}
     cpu: [ppc64]
     os: [aix]
 
-  '@esbuild/android-arm64@0.21.5':
-    resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
-    engines: {node: '>=12'}
+  '@esbuild/android-arm64@0.24.0':
+    resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [android]
 
-  '@esbuild/android-arm@0.21.5':
-    resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
-    engines: {node: '>=12'}
+  '@esbuild/android-arm@0.24.0':
+    resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==}
+    engines: {node: '>=18'}
     cpu: [arm]
     os: [android]
 
-  '@esbuild/android-x64@0.21.5':
-    resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
-    engines: {node: '>=12'}
+  '@esbuild/android-x64@0.24.0':
+    resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [android]
 
-  '@esbuild/darwin-arm64@0.21.5':
-    resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
-    engines: {node: '>=12'}
+  '@esbuild/darwin-arm64@0.24.0':
+    resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [darwin]
 
-  '@esbuild/darwin-x64@0.21.5':
-    resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
-    engines: {node: '>=12'}
+  '@esbuild/darwin-x64@0.24.0':
+    resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [darwin]
 
-  '@esbuild/freebsd-arm64@0.21.5':
-    resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
-    engines: {node: '>=12'}
+  '@esbuild/freebsd-arm64@0.24.0':
+    resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [freebsd]
 
-  '@esbuild/freebsd-x64@0.21.5':
-    resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
-    engines: {node: '>=12'}
+  '@esbuild/freebsd-x64@0.24.0':
+    resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [freebsd]
 
-  '@esbuild/linux-arm64@0.21.5':
-    resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-arm64@0.24.0':
+    resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [linux]
 
-  '@esbuild/linux-arm@0.21.5':
-    resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-arm@0.24.0':
+    resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==}
+    engines: {node: '>=18'}
     cpu: [arm]
     os: [linux]
 
-  '@esbuild/linux-ia32@0.21.5':
-    resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-ia32@0.24.0':
+    resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==}
+    engines: {node: '>=18'}
     cpu: [ia32]
     os: [linux]
 
-  '@esbuild/linux-loong64@0.21.5':
-    resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-loong64@0.24.0':
+    resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==}
+    engines: {node: '>=18'}
     cpu: [loong64]
     os: [linux]
 
-  '@esbuild/linux-mips64el@0.21.5':
-    resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-mips64el@0.24.0':
+    resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==}
+    engines: {node: '>=18'}
     cpu: [mips64el]
     os: [linux]
 
-  '@esbuild/linux-ppc64@0.21.5':
-    resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-ppc64@0.24.0':
+    resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==}
+    engines: {node: '>=18'}
     cpu: [ppc64]
     os: [linux]
 
-  '@esbuild/linux-riscv64@0.21.5':
-    resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-riscv64@0.24.0':
+    resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==}
+    engines: {node: '>=18'}
     cpu: [riscv64]
     os: [linux]
 
-  '@esbuild/linux-s390x@0.21.5':
-    resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-s390x@0.24.0':
+    resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==}
+    engines: {node: '>=18'}
     cpu: [s390x]
     os: [linux]
 
-  '@esbuild/linux-x64@0.21.5':
-    resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
-    engines: {node: '>=12'}
+  '@esbuild/linux-x64@0.24.0':
+    resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [linux]
 
-  '@esbuild/netbsd-x64@0.21.5':
-    resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
-    engines: {node: '>=12'}
+  '@esbuild/netbsd-x64@0.24.0':
+    resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [netbsd]
 
-  '@esbuild/openbsd-x64@0.21.5':
-    resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
-    engines: {node: '>=12'}
+  '@esbuild/openbsd-arm64@0.24.0':
+    resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openbsd]
+
+  '@esbuild/openbsd-x64@0.24.0':
+    resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [openbsd]
 
-  '@esbuild/sunos-x64@0.21.5':
-    resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
-    engines: {node: '>=12'}
+  '@esbuild/sunos-x64@0.24.0':
+    resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [sunos]
 
-  '@esbuild/win32-arm64@0.21.5':
-    resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
-    engines: {node: '>=12'}
+  '@esbuild/win32-arm64@0.24.0':
+    resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==}
+    engines: {node: '>=18'}
     cpu: [arm64]
     os: [win32]
 
-  '@esbuild/win32-ia32@0.21.5':
-    resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
-    engines: {node: '>=12'}
+  '@esbuild/win32-ia32@0.24.0':
+    resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==}
+    engines: {node: '>=18'}
     cpu: [ia32]
     os: [win32]
 
-  '@esbuild/win32-x64@0.21.5':
-    resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
-    engines: {node: '>=12'}
+  '@esbuild/win32-x64@0.24.0':
+    resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==}
+    engines: {node: '>=18'}
     cpu: [x64]
     os: [win32]
 
@@ -1107,9 +1113,9 @@ packages:
   error-stack-parser-es@0.1.5:
     resolution: {integrity: sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==}
 
-  esbuild@0.21.5:
-    resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
-    engines: {node: '>=12'}
+  esbuild@0.24.0:
+    resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==}
+    engines: {node: '>=18'}
     hasBin: true
 
   escalade@3.2.0:
@@ -1797,22 +1803,27 @@ packages:
     peerDependencies:
       vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0
 
-  vite@5.4.11:
-    resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==}
-    engines: {node: ^18.0.0 || >=20.0.0}
+  vite@6.0.1:
+    resolution: {integrity: sha512-Ldn6gorLGr4mCdFnmeAOLweJxZ34HjKnDm4HGo6P66IEqTxQb36VEdFJQENKxWjupNfoIjvRUnswjn1hpYEpjQ==}
+    engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
     hasBin: true
     peerDependencies:
-      '@types/node': ^18.0.0 || >=20.0.0
+      '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+      jiti: '>=1.21.0'
       less: '*'
       lightningcss: ^1.21.0
       sass: '*'
       sass-embedded: '*'
       stylus: '*'
       sugarss: '*'
-      terser: ^5.4.0
+      terser: ^5.16.0
+      tsx: ^4.8.1
+      yaml: ^2.4.2
     peerDependenciesMeta:
       '@types/node':
         optional: true
+      jiti:
+        optional: true
       less:
         optional: true
       lightningcss:
@@ -1827,6 +1838,10 @@ packages:
         optional: true
       terser:
         optional: true
+      tsx:
+        optional: true
+      yaml:
+        optional: true
 
   vscode-uri@3.0.8:
     resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
@@ -2110,73 +2125,76 @@ snapshots:
       '@babel/helper-string-parser': 7.25.9
       '@babel/helper-validator-identifier': 7.25.9
 
-  '@esbuild/aix-ppc64@0.21.5':
+  '@esbuild/aix-ppc64@0.24.0':
+    optional: true
+
+  '@esbuild/android-arm64@0.24.0':
     optional: true
 
-  '@esbuild/android-arm64@0.21.5':
+  '@esbuild/android-arm@0.24.0':
     optional: true
 
-  '@esbuild/android-arm@0.21.5':
+  '@esbuild/android-x64@0.24.0':
     optional: true
 
-  '@esbuild/android-x64@0.21.5':
+  '@esbuild/darwin-arm64@0.24.0':
     optional: true
 
-  '@esbuild/darwin-arm64@0.21.5':
+  '@esbuild/darwin-x64@0.24.0':
     optional: true
 
-  '@esbuild/darwin-x64@0.21.5':
+  '@esbuild/freebsd-arm64@0.24.0':
     optional: true
 
-  '@esbuild/freebsd-arm64@0.21.5':
+  '@esbuild/freebsd-x64@0.24.0':
     optional: true
 
-  '@esbuild/freebsd-x64@0.21.5':
+  '@esbuild/linux-arm64@0.24.0':
     optional: true
 
-  '@esbuild/linux-arm64@0.21.5':
+  '@esbuild/linux-arm@0.24.0':
     optional: true
 
-  '@esbuild/linux-arm@0.21.5':
+  '@esbuild/linux-ia32@0.24.0':
     optional: true
 
-  '@esbuild/linux-ia32@0.21.5':
+  '@esbuild/linux-loong64@0.24.0':
     optional: true
 
-  '@esbuild/linux-loong64@0.21.5':
+  '@esbuild/linux-mips64el@0.24.0':
     optional: true
 
-  '@esbuild/linux-mips64el@0.21.5':
+  '@esbuild/linux-ppc64@0.24.0':
     optional: true
 
-  '@esbuild/linux-ppc64@0.21.5':
+  '@esbuild/linux-riscv64@0.24.0':
     optional: true
 
-  '@esbuild/linux-riscv64@0.21.5':
+  '@esbuild/linux-s390x@0.24.0':
     optional: true
 
-  '@esbuild/linux-s390x@0.21.5':
+  '@esbuild/linux-x64@0.24.0':
     optional: true
 
-  '@esbuild/linux-x64@0.21.5':
+  '@esbuild/netbsd-x64@0.24.0':
     optional: true
 
-  '@esbuild/netbsd-x64@0.21.5':
+  '@esbuild/openbsd-arm64@0.24.0':
     optional: true
 
-  '@esbuild/openbsd-x64@0.21.5':
+  '@esbuild/openbsd-x64@0.24.0':
     optional: true
 
-  '@esbuild/sunos-x64@0.21.5':
+  '@esbuild/sunos-x64@0.24.0':
     optional: true
 
-  '@esbuild/win32-arm64@0.21.5':
+  '@esbuild/win32-arm64@0.24.0':
     optional: true
 
-  '@esbuild/win32-ia32@0.21.5':
+  '@esbuild/win32-ia32@0.24.0':
     optional: true
 
-  '@esbuild/win32-x64@0.21.5':
+  '@esbuild/win32-x64@0.24.0':
     optional: true
 
   '@eslint-community/eslint-utils@4.4.1(eslint@9.16.0)':
@@ -2543,9 +2561,9 @@ snapshots:
       '@typescript-eslint/types': 8.16.0
       eslint-visitor-keys: 4.2.0
 
-  '@vitejs/plugin-vue@5.2.1(vite@5.4.11(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))':
+  '@vitejs/plugin-vue@5.2.1(vite@6.0.1(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
-      vite: 5.4.11(@types/node@22.10.1)
+      vite: 6.0.1(@types/node@22.10.1)
       vue: 3.5.13(typescript@5.6.3)
 
   '@volar/language-core@2.4.10':
@@ -2627,14 +2645,14 @@ snapshots:
 
   '@vue/devtools-api@6.6.4': {}
 
-  '@vue/devtools-core@7.6.7(vite@5.4.11(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))':
+  '@vue/devtools-core@7.6.7(vite@6.0.1(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
       '@vue/devtools-kit': 7.6.7
       '@vue/devtools-shared': 7.6.7
       mitt: 3.0.1
       nanoid: 5.0.9
       pathe: 1.1.2
-      vite-hot-client: 0.2.4(vite@5.4.11(@types/node@22.10.1))
+      vite-hot-client: 0.2.4(vite@6.0.1(@types/node@22.10.1))
       vue: 3.5.13(typescript@5.6.3)
     transitivePeerDependencies:
       - vite
@@ -2999,31 +3017,32 @@ snapshots:
 
   error-stack-parser-es@0.1.5: {}
 
-  esbuild@0.21.5:
+  esbuild@0.24.0:
     optionalDependencies:
-      '@esbuild/aix-ppc64': 0.21.5
-      '@esbuild/android-arm': 0.21.5
-      '@esbuild/android-arm64': 0.21.5
-      '@esbuild/android-x64': 0.21.5
-      '@esbuild/darwin-arm64': 0.21.5
-      '@esbuild/darwin-x64': 0.21.5
-      '@esbuild/freebsd-arm64': 0.21.5
-      '@esbuild/freebsd-x64': 0.21.5
-      '@esbuild/linux-arm': 0.21.5
-      '@esbuild/linux-arm64': 0.21.5
-      '@esbuild/linux-ia32': 0.21.5
-      '@esbuild/linux-loong64': 0.21.5
-      '@esbuild/linux-mips64el': 0.21.5
-      '@esbuild/linux-ppc64': 0.21.5
-      '@esbuild/linux-riscv64': 0.21.5
-      '@esbuild/linux-s390x': 0.21.5
-      '@esbuild/linux-x64': 0.21.5
-      '@esbuild/netbsd-x64': 0.21.5
-      '@esbuild/openbsd-x64': 0.21.5
-      '@esbuild/sunos-x64': 0.21.5
-      '@esbuild/win32-arm64': 0.21.5
-      '@esbuild/win32-ia32': 0.21.5
-      '@esbuild/win32-x64': 0.21.5
+      '@esbuild/aix-ppc64': 0.24.0
+      '@esbuild/android-arm': 0.24.0
+      '@esbuild/android-arm64': 0.24.0
+      '@esbuild/android-x64': 0.24.0
+      '@esbuild/darwin-arm64': 0.24.0
+      '@esbuild/darwin-x64': 0.24.0
+      '@esbuild/freebsd-arm64': 0.24.0
+      '@esbuild/freebsd-x64': 0.24.0
+      '@esbuild/linux-arm': 0.24.0
+      '@esbuild/linux-arm64': 0.24.0
+      '@esbuild/linux-ia32': 0.24.0
+      '@esbuild/linux-loong64': 0.24.0
+      '@esbuild/linux-mips64el': 0.24.0
+      '@esbuild/linux-ppc64': 0.24.0
+      '@esbuild/linux-riscv64': 0.24.0
+      '@esbuild/linux-s390x': 0.24.0
+      '@esbuild/linux-x64': 0.24.0
+      '@esbuild/netbsd-x64': 0.24.0
+      '@esbuild/openbsd-arm64': 0.24.0
+      '@esbuild/openbsd-x64': 0.24.0
+      '@esbuild/sunos-x64': 0.24.0
+      '@esbuild/win32-arm64': 0.24.0
+      '@esbuild/win32-ia32': 0.24.0
+      '@esbuild/win32-x64': 0.24.0
 
   escalade@3.2.0: {}
 
@@ -3633,11 +3652,11 @@ snapshots:
 
   util-deprecate@1.0.2: {}
 
-  vite-hot-client@0.2.4(vite@5.4.11(@types/node@22.10.1)):
+  vite-hot-client@0.2.4(vite@6.0.1(@types/node@22.10.1)):
     dependencies:
-      vite: 5.4.11(@types/node@22.10.1)
+      vite: 6.0.1(@types/node@22.10.1)
 
-  vite-plugin-inspect@0.8.8(rollup@4.28.0)(vite@5.4.11(@types/node@22.10.1)):
+  vite-plugin-inspect@0.8.8(rollup@4.28.0)(vite@6.0.1(@types/node@22.10.1)):
     dependencies:
       '@antfu/utils': 0.7.10
       '@rollup/pluginutils': 5.1.3(rollup@4.28.0)
@@ -3648,28 +3667,28 @@ snapshots:
       perfect-debounce: 1.0.0
       picocolors: 1.1.1
       sirv: 3.0.0
-      vite: 5.4.11(@types/node@22.10.1)
+      vite: 6.0.1(@types/node@22.10.1)
     transitivePeerDependencies:
       - rollup
       - supports-color
 
-  vite-plugin-vue-devtools@7.6.7(rollup@4.28.0)(vite@5.4.11(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3)):
+  vite-plugin-vue-devtools@7.6.7(rollup@4.28.0)(vite@6.0.1(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3)):
     dependencies:
-      '@vue/devtools-core': 7.6.7(vite@5.4.11(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
+      '@vue/devtools-core': 7.6.7(vite@6.0.1(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
       '@vue/devtools-kit': 7.6.7
       '@vue/devtools-shared': 7.6.7
       execa: 9.5.1
       sirv: 3.0.0
-      vite: 5.4.11(@types/node@22.10.1)
-      vite-plugin-inspect: 0.8.8(rollup@4.28.0)(vite@5.4.11(@types/node@22.10.1))
-      vite-plugin-vue-inspector: 5.3.1(vite@5.4.11(@types/node@22.10.1))
+      vite: 6.0.1(@types/node@22.10.1)
+      vite-plugin-inspect: 0.8.8(rollup@4.28.0)(vite@6.0.1(@types/node@22.10.1))
+      vite-plugin-vue-inspector: 5.3.1(vite@6.0.1(@types/node@22.10.1))
     transitivePeerDependencies:
       - '@nuxt/kit'
       - rollup
       - supports-color
       - vue
 
-  vite-plugin-vue-inspector@5.3.1(vite@5.4.11(@types/node@22.10.1)):
+  vite-plugin-vue-inspector@5.3.1(vite@6.0.1(@types/node@22.10.1)):
     dependencies:
       '@babel/core': 7.26.0
       '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.0)
@@ -3680,13 +3699,13 @@ snapshots:
       '@vue/compiler-dom': 3.5.13
       kolorist: 1.8.0
       magic-string: 0.30.14
-      vite: 5.4.11(@types/node@22.10.1)
+      vite: 6.0.1(@types/node@22.10.1)
     transitivePeerDependencies:
       - supports-color
 
-  vite@5.4.11(@types/node@22.10.1):
+  vite@6.0.1(@types/node@22.10.1):
     dependencies:
-      esbuild: 0.21.5
+      esbuild: 0.24.0
       postcss: 8.4.49
       rollup: 4.28.0
     optionalDependencies:

From 84671302ea645e0f4144f4e6add297301a52f92d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 1 Dec 2024 06:53:29 -0500
Subject: [PATCH 30/34] Update dependency @types/node to v22.10.1 (#31)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 pi/server/pnpm-lock.yaml | 36 ++++++++++++++++++------------------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/pi/server/pnpm-lock.yaml b/pi/server/pnpm-lock.yaml
index be24fa3..4082236 100644
--- a/pi/server/pnpm-lock.yaml
+++ b/pi/server/pnpm-lock.yaml
@@ -32,7 +32,7 @@ importers:
         version: 5.0.0
       '@types/node':
         specifier: ^22.10.0
-        version: 22.10.0
+        version: 22.10.1
       eslint:
         specifier: ^9.15.0
         version: 9.16.0
@@ -47,10 +47,10 @@ importers:
         version: 3.1.7
       ts-node:
         specifier: ^10.9.2
-        version: 10.9.2(@types/node@22.10.0)(typescript@5.7.2)
+        version: 10.9.2(@types/node@22.10.1)(typescript@5.7.2)
       ts-node-dev:
         specifier: ^2.0.0
-        version: 2.0.0(@types/node@22.10.0)(typescript@5.7.2)
+        version: 2.0.0(@types/node@22.10.1)(typescript@5.7.2)
       typescript:
         specifier: ^5.7.2
         version: 5.7.2
@@ -177,8 +177,8 @@ packages:
   '@types/mime@1.3.5':
     resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
 
-  '@types/node@22.10.0':
-    resolution: {integrity: sha512-XC70cRZVElFHfIUB40FgZOBbgJYFKKMa5nb9lxcwYstFG/Mi+/Y0bGS+rs6Dmhmkpq4pnNiLiuZAbc02YCOnmA==}
+  '@types/node@22.10.1':
+    resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==}
 
   '@types/qs@6.9.17':
     resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==}
@@ -1148,23 +1148,23 @@ snapshots:
   '@types/body-parser@1.19.5':
     dependencies:
       '@types/connect': 3.4.38
-      '@types/node': 22.10.0
+      '@types/node': 22.10.1
 
   '@types/connect@3.4.38':
     dependencies:
-      '@types/node': 22.10.0
+      '@types/node': 22.10.1
 
   '@types/cookie@0.4.1': {}
 
   '@types/cors@2.8.17':
     dependencies:
-      '@types/node': 22.10.0
+      '@types/node': 22.10.1
 
   '@types/estree@1.0.6': {}
 
   '@types/express-serve-static-core@5.0.2':
     dependencies:
-      '@types/node': 22.10.0
+      '@types/node': 22.10.1
       '@types/qs': 6.9.17
       '@types/range-parser': 1.2.7
       '@types/send': 0.17.4
@@ -1180,13 +1180,13 @@ snapshots:
 
   '@types/http-proxy@1.17.15':
     dependencies:
-      '@types/node': 22.10.0
+      '@types/node': 22.10.1
 
   '@types/json-schema@7.0.15': {}
 
   '@types/mime@1.3.5': {}
 
-  '@types/node@22.10.0':
+  '@types/node@22.10.1':
     dependencies:
       undici-types: 6.20.0
 
@@ -1197,12 +1197,12 @@ snapshots:
   '@types/send@0.17.4':
     dependencies:
       '@types/mime': 1.3.5
-      '@types/node': 22.10.0
+      '@types/node': 22.10.1
 
   '@types/serve-static@1.15.7':
     dependencies:
       '@types/http-errors': 2.0.4
-      '@types/node': 22.10.0
+      '@types/node': 22.10.1
       '@types/send': 0.17.4
 
   '@types/strip-bom@3.0.0': {}
@@ -1388,7 +1388,7 @@ snapshots:
     dependencies:
       '@types/cookie': 0.4.1
       '@types/cors': 2.8.17
-      '@types/node': 22.10.0
+      '@types/node': 22.10.1
       accepts: 1.3.8
       base64id: 2.0.0
       cookie: 0.7.2
@@ -2006,7 +2006,7 @@ snapshots:
 
   tree-kill@1.2.2: {}
 
-  ts-node-dev@2.0.0(@types/node@22.10.0)(typescript@5.7.2):
+  ts-node-dev@2.0.0(@types/node@22.10.1)(typescript@5.7.2):
     dependencies:
       chokidar: 3.6.0
       dynamic-dedupe: 0.3.0
@@ -2016,7 +2016,7 @@ snapshots:
       rimraf: 2.7.1
       source-map-support: 0.5.21
       tree-kill: 1.2.2
-      ts-node: 10.9.2(@types/node@22.10.0)(typescript@5.7.2)
+      ts-node: 10.9.2(@types/node@22.10.1)(typescript@5.7.2)
       tsconfig: 7.0.0
       typescript: 5.7.2
     transitivePeerDependencies:
@@ -2024,14 +2024,14 @@ snapshots:
       - '@swc/wasm'
       - '@types/node'
 
-  ts-node@10.9.2(@types/node@22.10.0)(typescript@5.7.2):
+  ts-node@10.9.2(@types/node@22.10.1)(typescript@5.7.2):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
       '@tsconfig/node10': 1.0.11
       '@tsconfig/node12': 1.0.11
       '@tsconfig/node14': 1.0.3
       '@tsconfig/node16': 1.0.4
-      '@types/node': 22.10.0
+      '@types/node': 22.10.1
       acorn: 8.14.0
       acorn-walk: 8.3.4
       arg: 4.1.3

From 584dad386a616341c8bb8397cb5bebb60eace092 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 1 Dec 2024 06:53:37 -0500
Subject: [PATCH 31/34] Update pnpm to v9.14.4 (#13)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 pi/package.json        | 2 +-
 pi/server/package.json | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/pi/package.json b/pi/package.json
index 6fd29c9..74666a6 100644
--- a/pi/package.json
+++ b/pi/package.json
@@ -13,7 +13,7 @@
     "dev:ui": "pnpm --prefix ui dev",
     "dev:server": "pnpm --prefix server dev"
   },
-  "packageManager": "pnpm@9.14.2",
+  "packageManager": "pnpm@9.14.4",
   "devDependencies": {
     "concurrently": "^9.1.0"
   }
diff --git a/pi/server/package.json b/pi/server/package.json
index 6844c56..a6ba5ab 100644
--- a/pi/server/package.json
+++ b/pi/server/package.json
@@ -14,7 +14,7 @@
   "keywords": [],
   "author": "",
   "license": "ISC",
-  "packageManager": "pnpm@9.14.2",
+  "packageManager": "pnpm@9.14.4",
   "dependencies": {
     "cors": "^2.8.5",
     "dotenv": "^16.4.5",

From df114104d26463adc8efd531b690b993b3410128 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Sun, 1 Dec 2024 19:25:19 -0500
Subject: [PATCH 32/34] Update dependency globals to v15.13.0 (#40)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 pi/server/pnpm-lock.yaml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pi/server/pnpm-lock.yaml b/pi/server/pnpm-lock.yaml
index 4082236..5472a21 100644
--- a/pi/server/pnpm-lock.yaml
+++ b/pi/server/pnpm-lock.yaml
@@ -38,7 +38,7 @@ importers:
         version: 9.16.0
       globals:
         specifier: ^15.12.0
-        version: 15.12.0
+        version: 15.13.0
       http-proxy-middleware:
         specifier: ^3.0.3
         version: 3.0.3
@@ -544,8 +544,8 @@ packages:
     resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
     engines: {node: '>=18'}
 
-  globals@15.12.0:
-    resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==}
+  globals@15.13.0:
+    resolution: {integrity: sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==}
     engines: {node: '>=18'}
 
   gopd@1.0.1:
@@ -1601,7 +1601,7 @@ snapshots:
 
   globals@14.0.0: {}
 
-  globals@15.12.0: {}
+  globals@15.13.0: {}
 
   gopd@1.0.1:
     dependencies:

From b062d1604577ad0c70d9e410ed6470a4460d76e0 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 3 Dec 2024 04:37:51 -0500
Subject: [PATCH 33/34] Update dependency dotenv to v16.4.6 (#42)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 pi/server/pnpm-lock.yaml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pi/server/pnpm-lock.yaml b/pi/server/pnpm-lock.yaml
index 5472a21..b0e595b 100644
--- a/pi/server/pnpm-lock.yaml
+++ b/pi/server/pnpm-lock.yaml
@@ -13,7 +13,7 @@ importers:
         version: 2.8.5
       dotenv:
         specifier: ^16.4.5
-        version: 16.4.5
+        version: 16.4.6
       express:
         specifier: ^4.21.1
         version: 4.21.1
@@ -363,8 +363,8 @@ packages:
     resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
     engines: {node: '>=0.3.1'}
 
-  dotenv@16.4.5:
-    resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
+  dotenv@16.4.6:
+    resolution: {integrity: sha512-JhcR/+KIjkkjiU8yEpaB/USlzVi3i5whwOjpIRNGi9svKEXZSe+Qp6IWAjFjv+2GViAoDRCUv/QLNziQxsLqDg==}
     engines: {node: '>=12'}
 
   dynamic-dedupe@0.3.0:
@@ -1370,7 +1370,7 @@ snapshots:
 
   diff@4.0.2: {}
 
-  dotenv@16.4.5: {}
+  dotenv@16.4.6: {}
 
   dynamic-dedupe@0.3.0:
     dependencies:

From a61f7f839953c798f47a88423d1dfb02faa1ecc9 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 3 Dec 2024 09:38:32 +0000
Subject: [PATCH 34/34] Update dependency vite to v6.0.2

---
 pi/ui/pnpm-lock.yaml | 42 +++++++++++++++++++++---------------------
 1 file changed, 21 insertions(+), 21 deletions(-)

diff --git a/pi/ui/pnpm-lock.yaml b/pi/ui/pnpm-lock.yaml
index ba70dcc..b76500a 100644
--- a/pi/ui/pnpm-lock.yaml
+++ b/pi/ui/pnpm-lock.yaml
@@ -35,7 +35,7 @@ importers:
         version: 22.10.1
       '@vitejs/plugin-vue':
         specifier: ^5.2.1
-        version: 5.2.1(vite@6.0.1(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
+        version: 5.2.1(vite@6.0.2(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
       '@vue/eslint-config-prettier':
         specifier: ^10.1.0
         version: 10.1.0(eslint@9.16.0)(prettier@3.4.0)
@@ -59,10 +59,10 @@ importers:
         version: 5.6.3
       vite:
         specifier: ^6.0.0
-        version: 6.0.1(@types/node@22.10.1)
+        version: 6.0.2(@types/node@22.10.1)
       vite-plugin-vue-devtools:
         specifier: ^7.6.7
-        version: 7.6.7(rollup@4.28.0)(vite@6.0.1(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
+        version: 7.6.7(rollup@4.28.0)(vite@6.0.2(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
       vue-tsc:
         specifier: ^2.1.10
         version: 2.1.10(typescript@5.6.3)
@@ -1803,8 +1803,8 @@ packages:
     peerDependencies:
       vite: ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0
 
-  vite@6.0.1:
-    resolution: {integrity: sha512-Ldn6gorLGr4mCdFnmeAOLweJxZ34HjKnDm4HGo6P66IEqTxQb36VEdFJQENKxWjupNfoIjvRUnswjn1hpYEpjQ==}
+  vite@6.0.2:
+    resolution: {integrity: sha512-XdQ+VsY2tJpBsKGs0wf3U/+azx8BBpYRHFAyKm5VeEZNOJZRB63q7Sc8Iup3k0TrN3KO6QgyzFf+opSbfY1y0g==}
     engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
     hasBin: true
     peerDependencies:
@@ -2561,9 +2561,9 @@ snapshots:
       '@typescript-eslint/types': 8.16.0
       eslint-visitor-keys: 4.2.0
 
-  '@vitejs/plugin-vue@5.2.1(vite@6.0.1(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))':
+  '@vitejs/plugin-vue@5.2.1(vite@6.0.2(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
-      vite: 6.0.1(@types/node@22.10.1)
+      vite: 6.0.2(@types/node@22.10.1)
       vue: 3.5.13(typescript@5.6.3)
 
   '@volar/language-core@2.4.10':
@@ -2645,14 +2645,14 @@ snapshots:
 
   '@vue/devtools-api@6.6.4': {}
 
-  '@vue/devtools-core@7.6.7(vite@6.0.1(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))':
+  '@vue/devtools-core@7.6.7(vite@6.0.2(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))':
     dependencies:
       '@vue/devtools-kit': 7.6.7
       '@vue/devtools-shared': 7.6.7
       mitt: 3.0.1
       nanoid: 5.0.9
       pathe: 1.1.2
-      vite-hot-client: 0.2.4(vite@6.0.1(@types/node@22.10.1))
+      vite-hot-client: 0.2.4(vite@6.0.2(@types/node@22.10.1))
       vue: 3.5.13(typescript@5.6.3)
     transitivePeerDependencies:
       - vite
@@ -3652,11 +3652,11 @@ snapshots:
 
   util-deprecate@1.0.2: {}
 
-  vite-hot-client@0.2.4(vite@6.0.1(@types/node@22.10.1)):
+  vite-hot-client@0.2.4(vite@6.0.2(@types/node@22.10.1)):
     dependencies:
-      vite: 6.0.1(@types/node@22.10.1)
+      vite: 6.0.2(@types/node@22.10.1)
 
-  vite-plugin-inspect@0.8.8(rollup@4.28.0)(vite@6.0.1(@types/node@22.10.1)):
+  vite-plugin-inspect@0.8.8(rollup@4.28.0)(vite@6.0.2(@types/node@22.10.1)):
     dependencies:
       '@antfu/utils': 0.7.10
       '@rollup/pluginutils': 5.1.3(rollup@4.28.0)
@@ -3667,28 +3667,28 @@ snapshots:
       perfect-debounce: 1.0.0
       picocolors: 1.1.1
       sirv: 3.0.0
-      vite: 6.0.1(@types/node@22.10.1)
+      vite: 6.0.2(@types/node@22.10.1)
     transitivePeerDependencies:
       - rollup
       - supports-color
 
-  vite-plugin-vue-devtools@7.6.7(rollup@4.28.0)(vite@6.0.1(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3)):
+  vite-plugin-vue-devtools@7.6.7(rollup@4.28.0)(vite@6.0.2(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3)):
     dependencies:
-      '@vue/devtools-core': 7.6.7(vite@6.0.1(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
+      '@vue/devtools-core': 7.6.7(vite@6.0.2(@types/node@22.10.1))(vue@3.5.13(typescript@5.6.3))
       '@vue/devtools-kit': 7.6.7
       '@vue/devtools-shared': 7.6.7
       execa: 9.5.1
       sirv: 3.0.0
-      vite: 6.0.1(@types/node@22.10.1)
-      vite-plugin-inspect: 0.8.8(rollup@4.28.0)(vite@6.0.1(@types/node@22.10.1))
-      vite-plugin-vue-inspector: 5.3.1(vite@6.0.1(@types/node@22.10.1))
+      vite: 6.0.2(@types/node@22.10.1)
+      vite-plugin-inspect: 0.8.8(rollup@4.28.0)(vite@6.0.2(@types/node@22.10.1))
+      vite-plugin-vue-inspector: 5.3.1(vite@6.0.2(@types/node@22.10.1))
     transitivePeerDependencies:
       - '@nuxt/kit'
       - rollup
       - supports-color
       - vue
 
-  vite-plugin-vue-inspector@5.3.1(vite@6.0.1(@types/node@22.10.1)):
+  vite-plugin-vue-inspector@5.3.1(vite@6.0.2(@types/node@22.10.1)):
     dependencies:
       '@babel/core': 7.26.0
       '@babel/plugin-proposal-decorators': 7.25.9(@babel/core@7.26.0)
@@ -3699,11 +3699,11 @@ snapshots:
       '@vue/compiler-dom': 3.5.13
       kolorist: 1.8.0
       magic-string: 0.30.14
-      vite: 6.0.1(@types/node@22.10.1)
+      vite: 6.0.2(@types/node@22.10.1)
     transitivePeerDependencies:
       - supports-color
 
-  vite@6.0.1(@types/node@22.10.1):
+  vite@6.0.2(@types/node@22.10.1):
     dependencies:
       esbuild: 0.24.0
       postcss: 8.4.49