From b526a80e1f7fda5ce39d47e979590b35f6790bc0 Mon Sep 17 00:00:00 2001 From: tactile Date: Wed, 1 Oct 2025 15:04:07 +0500 Subject: [PATCH] init --- .gitignore | 38 + FINAL_STATUS.md | 58 + FINAL_STATUS.pdf | Bin 0 -> 144314 bytes Makefile | 38 + README.md | 249 +++ backend/Dockerfile | 17 + backend/main.py | 226 +++ backend/requirements.txt | 8 + db/init.sql | 120 ++ docker-compose.yml | 48 + frontend/.dockerignore | 8 + frontend/.gitignore | 24 + frontend/Dockerfile | 24 + frontend/README.md | 16 + frontend/eslint.config.js | 29 + frontend/index.html | 13 + frontend/nginx.conf | 18 + frontend/package-lock.json | 2823 +++++++++++++++++++++++++++++++++ frontend/package.json | 27 + frontend/public/vite.svg | 1 + frontend/src/App.css | 213 +++ frontend/src/App.jsx | 113 ++ frontend/src/assets/react.svg | 1 + frontend/src/index.css | 10 + frontend/src/main.jsx | 10 + frontend/vite.config.js | 15 + k8s/backend.yaml | 73 + k8s/dex-authenticator.yaml | 12 + k8s/frontend.yaml | 34 + k8s/ingress.yaml | 41 + k8s/namespace.yaml | 5 + k8s/postgres.yaml | 199 +++ test-deployment.sh | 70 + 33 files changed, 4581 insertions(+) create mode 100644 .gitignore create mode 100644 FINAL_STATUS.md create mode 100644 FINAL_STATUS.pdf create mode 100644 Makefile create mode 100644 README.md create mode 100644 backend/Dockerfile create mode 100644 backend/main.py create mode 100644 backend/requirements.txt create mode 100644 db/init.sql create mode 100644 docker-compose.yml create mode 100644 frontend/.dockerignore create mode 100644 frontend/.gitignore create mode 100644 frontend/Dockerfile create mode 100644 frontend/README.md create mode 100644 frontend/eslint.config.js create mode 100644 frontend/index.html create mode 100644 frontend/nginx.conf create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/public/vite.svg create mode 100644 frontend/src/App.css create mode 100644 frontend/src/App.jsx create mode 100644 frontend/src/assets/react.svg create mode 100644 frontend/src/index.css create mode 100644 frontend/src/main.jsx create mode 100644 frontend/vite.config.js create mode 100644 k8s/backend.yaml create mode 100644 k8s/dex-authenticator.yaml create mode 100644 k8s/frontend.yaml create mode 100644 k8s/ingress.yaml create mode 100644 k8s/namespace.yaml create mode 100644 k8s/postgres.yaml create mode 100755 test-deployment.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e7a097b --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +.venv +pip-log.txt +pip-delete-this-directory.txt +.pytest_cache/ + +# Node +node_modules/ +dist/ +build/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.DS_Store + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# Environment +.env +.env.local +*.secret + +# Kubernetes +*.local.yaml + diff --git a/FINAL_STATUS.md b/FINAL_STATUS.md new file mode 100644 index 0000000..6cb36ce --- /dev/null +++ b/FINAL_STATUS.md @@ -0,0 +1,58 @@ + +### **Финальная архитектура в k8s:** + +```mermaid +flowchart + A["python-navigator-demo.127.0.0.1.sslip.io"] --> B["Единый Ingress + DexAuthenticator
(аутентификация)"] + + B --> C["Frontend Service"] + B --> D["DexAuthenticator Service"] + B --> E["Backend Service"] + + C --> F["Frontend Pods"] + D --> G["DexAuthenticator Pods"] + E --> H["Backend Pods"] + + H --> I["PostgreSQL Service"] + + classDef ingress fill:#e1f5fe + classDef service fill:#f3e5f5 + classDef pod fill:#e8f5e8 + classDef database fill:#fff3e0 + + class A,B ingress + class C,D,E service + class F,G,H pod + class I database +``` + +### **Правильный поток аутентификации:** + +1. Пользователь заходит на `https://python-navigator-demo.127.0.0.1.sslip.io` +2. DexAuthenticator проверяет аутентификацию +3. Если не аутентифицирован → редирект на Dex (HTTP 302) +4. Если не аутентифицирован в Dex → редирект на Blitz IdP (HTTP 302) +4. После аутентификации в Blitz IdP → возврат в Dex +4. После аутентификации в Dex → возврат в приложение +5. Frontend загружается с аутентификацией +6. Frontend делает запрос к `/api/user-info` +7. Backend получает JWT токен и валидирует его +8. Backend возвращает данные пользователя из PostgreSQL +9. Frontend отображает информацию о пользователе и доступные ресурсы + +### **Для тестирования:** + +**Откройте браузер и перейдите на `https://python-navigator-demo.127.0.0.1.sslip.io`** + +Вас должно перенаправить на Dex для аутентификации. После входа вы увидите: +- Информацию о пользователе (email, полное имя, организация) +- Его роли (admin, developer, user, manager) +- Доступные ресурсы на основе ролей + +### **Тестовые пользователи:** + +Убедитесь, что в вашем Dex и внешнем IdP есть пользователи: +- `admin@example.com` - полный доступ ко всем ресурсам +- `developer@example.com` - технические ресурсы (CI/CD, Git, Docs, Wiki) +- `user@example.com` - только база знаний +- `manager@example.com` - управленческие ресурсы (Проекты, Отчеты, Wiki) diff --git a/FINAL_STATUS.pdf b/FINAL_STATUS.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4d768f893c5aed1b617665a05e87f8e0d1266e33 GIT binary patch literal 144314 zcmcG01z1&G6D~-1C|!pZM7lw`ySq!eTLh$4TBREmq&uX$L8Kc&x}+QK;lTHa{$BmO z&vO;su-87b-J#ZRHC*1Arc2V_*)?%}u9d=4fdQxGy3u z!!M<)sKl?NtVnBRLasFxQ33dEZ7q#ejSVEt9O+n?*l3wpffq`ODM?Gw04&Wci~*v?h88x!3&o7}jVz5F z90X16ZLEyx7?@aT>976*6!lH??agjJ47|CVy^WERp)v68iWX-2bU^d~E8x}tz%sM3 z7SeYF!WQCWq-SJdpr>a5{-bAPdq7W53H$~n$bWUw^`%mR0D5>jDGAX3s#oWwr2x$Ebc*}{M|&sZU&~@( z{Vq(z%+e94C!L5TP$3~>LmMODhop?HA3K@?SecnPcz6H~jzCvf!Mi4GXsd^jl}C8y z>qhiJ2Q;yZiGV?X4@7@~!6_K3zwqAftZ6Mlv{*Plf7vc&!cg4K`O)Hi8!5ccYn&cL z=rH!9h+um$&I_ia29zzWZR%hdqocj*(-CAqMUcMtJ?Q4y#K#fOP;2fEv!9*JR9&7l zee2rP<+AtIH)4JCM%UAQnnM~pV5iROeBM^UWdGwY#vA7F4`t| zr;t34z68csO%S+a1pqj?m)gD*KPr4qD=0=Q^5q`PsRbDYJ#HcNtC^}y!VLQ)xHkc^ z2c>*I*a4{8XlbD0KwLA=v+4)$n-of7SP{Amd@$Z%DcYeq z@ES%B-&3qIJ=B4Tx}Z`rUTG8f@Pd$T&;}=7KsuU1Ywvhq)9i4!w}bxNqKud~jN(}j znd8RR_)O^~*U&|>vu7~j#YvmK`)J^uL*D7l>C5Bfy0g8B{L72I3G{pQ`1dBcH>Y|1 zz|)Es-RYX<`?zA}aIrt3_%+S%ZZ`Eap)}3&I`f5sLk_0BX{$pb+B8k`6%;3oH2Nsz zxD`ag)ctiLJ|ZXdHCZs)fEwrGXP%;O0(G(u+hWaG-$)h@i9~l!11rqlb3&qYJ8;N4 zR9d+d2o9SvZM%{(a)^6FRt9dKi(zJ<`Kodc*||8_efEw}pg4_n`$oLlct$(Ob)QMR z-kiyuVy4dsTpW8&#dcxVEW*gzBzI;QiMMWk6BplVcI>5sR`Dd`?CGSIiVQIumOTWw ztIkInsaMn-&+pj9lGDT4T~?fSZPGm!S!trd#;Ty)RMBQov-|lXy1xMk5Z*5o=z~+JV8*k{_7A1t+WA4oklrSSFLs zH(Bde(a;^|io{tKGV6ou@D()HQhxrmA8J+&Ht?$zT%$3m$@u5N?XKrVj%`a*7m|D7 z`2H(hmC$t5#JZj*IF2d%{E>8s$scb*jw^jXcc5bZvRf1+_4_fi{?gtxivpw4HBN;VR*CHb+ zjN#(ZTH=FgD^gIAlU;b7Sk}xBOw6HS`x$~ph=uLIP+Ofu$fV(3WC%Y@)V+l8Yk*m! zgi%qM`(}MPN`oXe=TX3T`XY>a2Rk>G%60k~QBL4FnCDsY`AC%OELij1Cu-Cp7&gS2 z57!c4zQVGzFZ%J>;ZtB4@X0VB&IS<|Vd3&@X-VmPa==Zv&qNVk=vW2)Dpe(LD2L~T zSbKH2)q754+*eKkk_W1bq&tJ}ys~yYrz7s7K=NZ!n74Gu>wSZT;C#EB&b*M>b*8j* zR&9vILe^SgV6gq%k0HiI9u`VL@JAhbqE&!OoTHf z!?Ws_q5i%UT7^NqiX>m2X^By}S-p^ZbMT?_z%3e|e7{}4g@ltF6%2RO&8WqW+ni+3 z|7q6F)9L3;LipV*QZ&sHl$6*J-5npz%28Y{4m1QU(`RKGWVc->DWHD zx(jIT_GRy*?tZ0=0Ot*cML797jdok{O)-uupMn^DkfAhB@aKq^R1~rngb%6A(Rm*x z7&eS?W2Y6l<2FlQJa&{2M^$X@CV49mRDd=YzVWp_xG>YGmD$}iZT6vGQ43dr5dPCb zF?1D;!ghgXWiDz<%0q4jnOM65IY|8CuzPNDlw|TPob4abN#Dz{a!RY@`87ZGeI z_;2s_LWM3vxq@xU`7~lvbLFRXf}0Jk24J&1PhQPQcG=RR_ZbyF&hi7aSz_@c@_xk;+f%#l4H*~Q48pQKY%iVSkU9Cc{;Fo>W04L z68Cszk$??<+uLxvrP;peiy9ORI=2qrB=vvKD1rbSUZ=Ub2>{o8u>PCPA{;)Wbz*4q(0j_PFFCe=|>sF?TH?w?lbUsrB$)&-B;P|5)4LeU{Y z()!t1Z$8*`)o(-K(iz=u48?M;E|~Wxpj-d(6bO!l>z4~LqRsm+MEQLmA4ovltyka0YX2MgGZNA;VK@tj8s zngR$|*9-Imk+>{>It9bX8NR)*FE|YKR?s<0{i_=mSVGD>?Do*6HqVmWadVAISCmjq+;^qT4eH7>__NJB;Na9!e{)ihOO6?696P=f$V z2@kzfPJy96!Gm=*{Wf?mmC?e+Pz>iEPw59E8&-p>RMbI9Sx$fS2t!WGo#%-joLx%y zC70I1m&7c=jwTI&VzE;fQPJ{aJB$>6>l>gwQTyge2YwAgV*rcC)RT_W7hI;J{7pdB zKLK$QFt4ZR!yx^1ie4m;q4uQaD;zV;%wdW?zcyb}WE6jskBav{(oH@UymkCkU@=kd zKzF>B7O+tB^$)fKnlrrrS#Ulw-ex|8Y=Qo9Z6FfD>K(kO5=1=Ak~^$4na{fzi{==c z0?6MrU7?Oe=+wnwnbeir`~|`vPhI)nRq#_dM;@Oba_t>l`&o!j;iQD#0SO*Z%wNfj zaS%SgU>oxe$N?&zXEg(F8iSp1l8=nDXwIcK3f_c_?Oa~~5kNq{?yV1l_|qv=kwAJK zFzh=9_F~TYYz3TK|WEiqp(DFd7Kqb*RzRl#FH{#&*EBDox7jqkWw(t4Dz;e;$#_pBjsRk()cE%zsPB! z{k-edo)H{=`a180D_Wm>yk-bOsYj=$X_sXP z`FfPrz6GqiIjj-rTdi2{>fF7^9osv~U+XJJy@YcU#eUs+Bs=yFz@vkHv4K-_|16#H z?3M4gu*v1P#n~-q`1txNc7NhmHXoB4kyu_U9U^afhQev{KT&HeQSJ}yNWD|BFkcl> z6!uKTl3V#H<@9i(8!2l}8^wNm4u%3j$X#56lha9Ek56w+mPHw5DmlJ2l{k)aLYf7+ z6-Odea)zdEZwohgNcx;$m$9L|KxR+Bua7w2eiZLEXJR}q z_4bs;acmdGY^WgcLt;nS0=)cI>9|jnj4{R9s)YoVo3{HlZ3gyEOXu9@-Z@9o4x_(Fi*sjxZ-F%;@GG|5(VSQLRCCnAKa z29!mx&OM^Q^y!(Uc5EkSN2>k~wH?OlVhj@PdyiSX8%4Y&3UP_bvU1pVxPzST_KP2` zPr=ueVsIiXxYt;~>>M!5!q;uJ?$xW8cm6hw$_=}69o9p(u>8( zSY+u|@9c4(tUa`wFMvwaRtsfzQ)AM~Z$XkNShucP8^W`OC$keLi8%m#e7ic}?YxS! z%{ZCTxgwmT=@~t7N2!fr)t=3@Pt@pR{wC`}{N50GvlrB3Brq9@heT*@WNcq{^TCe8 zir0HTJ{ZQ!E38408Dw0##}gGx<0VpU`bK+UkHjXIVBs=K{(?}&aqaM_T3s>fsRbX{ z?ECcE0sF+sOtyTrOQJ6$^Uw3-ZJv)ksCeDzzMb4G%vXV0ns4ha&klaU)wEFsvAH;R zN{{uJLoan%06zWF-`I;AE*?(pZdtZl7;>aYgxSi*n0;47wF!$?g7IOkcH;EsQw2wz znsI()XD~DvbSH2U9Y?~t)@9>btlr(4=;M+NmfqGoC-7Jzcu&4E`@&Sz^q5bNfR z)deCQ@dn>Di3?(NA5c7A$XH@{7*{^V@F=Ip&dNRK^BC|6E8WO!%!oz$6N<`n>DTdL z2+M-_hcyl^Og2;ZVPvVw_gG(vi~E>U%q%1wT6VY^EDFSON$eC9RBDQJiel;1{0g=LhQIL|IjN#8p0k&C1{!FnrA?>5+9oXWqte(JjANF6$TIO_ zI)Rp^l5EoW5!*SQ6XF?IwF0dPB`4uYF=Xp|Lx)WQNu94RU)DN6(XcRp43mzY2Dflu*%!F?BBmlG38oZL#7%>C&+2t3oa9Tv%k@!&z2is znj8{UK?sA@Hp!A;cnUF>?`6Z8p-c@vF{0L|T@oMK@sU>ci!qn?BRQ#xP}yE`#YFsU zql)ZT+vxV$ofDE@tyya2$p@kKqF?Kz#7djxM(43ThP&H0KZRW)@}P-4_p3;oM50T2 zHZJMR@|OF9RAQH!Y>n!*;??VUoy{?We=$(GG~txzv|!Zd+Bm zsqCu3z&G)0Nfs^m(1EXMU^?CBthHyW(c=BvK1P)~Vho8kT&pW8g9T zP7}RexOz9P)049ioi}$_>A;tyY;?l1-TGX?_b@Hj!lf!_-zT@OI8%ffL41QgtnjF*F|LD*6BFiwbG!xlly%pcALI=Xzy+?k8$ z)w{;wUlN*-i$4=SP-c|~#7}ycnc-X6GM*B%NVN2bl7iDkJ^akCpAT6(Ur%$c!i;61U5gKeBg7hRdO{uSz<;vCz*Cn(#+IkjU}s26D!N0f%WN%exKRo zdZwKP1#ZcM=Vv}2rt*1nIoi)YydTPVELG;7F&KE1)-pyOj3=_JsWxkMTtf|M_ASAo zsj%ryLlZEa#85o6cwoHz>=D`t1Y;}q`Dek`fo*M(;)0P;EAKcf2_ZLk3lbk6$Ws<4 zufC~nUe6dX@yaWWKes-ps~F>DWrbnQ18;u{C+IjyAdPFV)SimEMB~EjC1JL2<{~=c zB8tQJ{Ns3JckD8oWXB17aj8kpnyqBy1obN@3k|0;W1SV3O`@jP6IgF`Iwa516Ar>0 za@$-a%MNx0%Ez8gjA(`Gl1jK$R-F(c4cT3Cn4yLSO4QV~d-WzOXgzg5_=eAC9#YfO zDx%{nKIBh0ljA|~`9N7Py|~9NjxFCIS*HPi&CDU8cdUAIu_Aun#(1AzTJ4kGW8`Qg zpW>XEJ*x58rnv#@b6VaHcKx){hHs7*CV9*QAPg*=IA3aAvrUYJ|21Xj(kkEIMy`$x6Jo-CHVqIXS&_uep;2g2EaS4SguJpq4ANo<~^@fNhDnnE+f&s zK$_1OInW6uxJ5FEyXWd5oGLE;{ViA@PS@%`zLc6`2^G?6GNNgFk3hJ#;@DFBGKjL5 z*1*1hqUN1&;p$SWM%(OaMiOpYyha-`9?S%8TM_O%Ixd3|cCR7!TxDLdqtyWoXEKlV z-HA0ECQhsFMGehx+4EV2wU~jq74zeqTF0CLxs{ipFWA!JPPtWy{Y!YU&i!*H8&0-p zQO>FJQjR%cI3>ux4TJUdEjNe^4$*sYaKqvnelD@{VDWUD%3IsV{IXS|4vaQkhI8sDc-$+Noa_mdEE6n%iKj<_Ilj zTbsI$w<0T8Y((Wkmvlyxb-?Qxt1hiCtm z`{CucJ#rtKZkMW7YyKiEf4y?I4=GW1AA3RUqd)LeuHSS@6%&lbTm1OUn8jzvz2Z$& zSr~V1^Rp9sA74@$7u((>itKQgsiIBe(7@FZ+kp`^7o`#C=ncrF3qU^cF@&pIv59H$ z37!xK7QvE5Nlkw{=UOXkL*!JJe^`sdUD^9G+0Vlyk-@~s(khaI13wVkcRR$8gEv4B zb!WVyBA}Ig68?-f=(V0~?fnh@M;Rid4}~58|6k*OKqn{|?!$wnoWt`#OtlEGZn^AL$v z&(h_J#ekNRQMDGR*bXkz&$4BbTT-QfeD4 zO0i_8=wL_=4K*JdYGg!&&%1t$kUv^P>6%eCSwPr*r(FI)hb?fe%p`zY0@42Ew*-Bj z)zL2F#SN&+Ib3RIqkD&IXH@z7mXz>(tO0mcJk7KSRAGhXeCcwNC2negk|WDtg)N^4A%=GxF@N^#2BT z{CjS-j~fO}dFRjPjucZ$E{#-9He$M8D%vNZGFYehm_V&JybF;^9c;dYNk?*vy4uyb z*)h4<-(g_?Z`&=Hr(mdCurE^g)qG-V+!1Mw`|0<$23D4)GPHLi)uW znF!|dODNHFG?vJNIR~xu%=0A10Y|6VQ=E#T-P-?&gr>cMXd2 z3$+f?LXa>oC{v5d80p|-<#3^my;G5*Oy*cv%9cpre%$o=eqYC(?%cGgP6Q*a4GOyl z6WOs@8)4^ut}Q*WF}1Ua*_HmcFskh*LKBA6$I5k=Q+IYM{#qcUw+7hx$|m*fj-^%oqQtQXai(~9O9$P}q^E;TzM zCXiWO-2RN?O#G#Ha*SA)??$>T<$n9do?xU}PP+DLfw5zrwS0fwqqN{*tN1KgQNqF# zyxe=P9=3|J@)oR2{?G~1oqPX$d0(lCIsGJ=Jh=0 zvMFlG8`|S2Ytu9 z@<7+h%4uNmrx;||5Hs&jG0?z6(5Dy}*bsFRZ4vD!j)qZvw1lX*#oTR${HEOyeyuc< z;k*FmMZ#3LToPtv7(W%SJX^hF5pbBAv^zL&xS&L{@Zmq2#$t4TIFWbFG*{rK%yoh) z&hpP>Ft|`b#hnLBWk3cVU{edX&~Q(|1q^wGOxJ<;d-_aJqu*0hrjw*rkH(6#ZfuYU zZ2ZJ`2c8$q)EsBS(i7|x_13umIckb>;&RBBC*JP|bIS3M!8A&>saX@jm|dXw=3j$r zz*X~5GtVyDjWDkv62^YzJ_z7R}Avzc>iyFYPSykm=Z5+CEXsP2M? z1B?C^As|ltv*<21azA$fKXaHqYL6kd$SyWaKX)teM?35uLwup#7*+2@nov76o9D3( zrj2e=$DhJhP}>&*ap_l!sY{lfl(4h7KX@xwsy0YVX5G;rdhsGg*^9cuTsxajYeOJp zWv3jyi4?`awVScZIhKYQ-^cYWPmm}sWZ*d-#YudhDq^Ja>P)w6Tvk(lS%rA!`M@9rWO~LjhFH zv`+~;E03Cmm8CPuXbZ>a>|S1w(Da0$+XI}U<_-dGD%>`n3JFgd5+_~13YL2C|KPrUp)FmLaCj4`4ETw z=U_IsecHYp3wk-RgpLg*z1VGjAGs&TO%J)V(unEmN{bgzVVx(wvRd|e4f8Ej3PVPb| z>{{?=Z(s1A1fE;BcO!;pE9?RNq^I9={`Ha~TGwlIS7hI^hd$oPoHffe%Po7ZcCjMl zEenx~#)s#{pLL~+@U%z#w8s2#2NrBJX}1(s9o&eW_g#q(_FbdX+Sk*8cf|JT6Fwlx zK!vHS4zJSE3DTyeYo?9HGKlV~dQGQ(b(=ok_1+Z1ywLRM4?OSLv!*j$^OGjp)oZj? z-fqIYtNG|slD5XG$oc_KL7fgGTY}L>Ah-ZK)cYm>3;FSM_p}m;eb{hZNZ9leBAPr{ z+w6xTzQb4gmJ4TOECPl zV*F{3wPANsZQ#I_Fc<)(8^Xzt&}(h_EN3(LxJVjepa{7ZycN%cBDpSYh<<8y9o~3= zm4$pU9QnhkrV&2D9#z_toDuc>)~rj2g)((Ek!9O^_28o5doCmbWCgJm^Y_8vq>xeA z;j4uxmhkYQYPa|?=2Bpk+qF@$P^*w(h>sTc#DORx`9(CuM>W8lECN0|Z}?sDMkCQ1_|S!cfXFVFQi1k4FGtjhwGUOd@o zmQdLER;98xTrjmCq*5|%nHa}&e<+Ar>sh8ZQyQ*d22Qnjd2kX~6wYu3l4-wc z$pN(KLmfT~9csj9S#{MMYE^QU--JF!na7V$ScL=?$E{dBtDa57X8+LG_162$%WcBN z7y*5~H)J-uPb$lgRx=DSM;YErKT_edlSRpNcA%m`aUd1s%YlUPH*8;(IDT*%|8HQ}(nfXR&zy;kMZ-5KaIee7Ecqq?1^jV(gX`|fTHLrPnQM2~yb zF*r|dnPkiR{ML*Wc#jvyz0nlyR*1p1J}rH4n2C?VFtr7I0sO{nGmm{Mpc=r4vQ{$!p%j z>%ZZ}SkEG(rZ@kfjGBw9&|kKy0A-Tvg3+8ElF49rp)yxjT|dy`^}g0iHOTjsw!i$k8d7X?)Si;rU0NX3qEok4IF>SypB8i0YyWYE9uJJ6v;2rnbn_-$$%l zG97-M+La-_>np7>l83r;5HN7sUHEDOJv%Rhl3VNBKv~x5fsaS(&M-;_kxX6Q54hO>$OjXQ>0L@=gYf0Cn{zE!=VCoSYoeg-DfaK!QqRL#`}%v zq0%a3(Iwq6AvC@%MNb;w#k8}YIJ&kl#ML*nCI+7poT!a7q;-T=Q%$@%ybDo)R3WOw zOCc-L#xQ1W4V}5*;yMudN}M8nyZTILx?5A0Y>CDcshdb#?`y%%euR|+p;eLcK~15K z`RSzf!pfcd1Yt}`MwAyYxurd)|NAt>`2Ar@$<5XnKqsgF7}&C=VC-PyWN&Ef001@> z0^9ct6^$JMnxM`-IwfOQM*y9;74SlVo1@^(QCu6IPT18^RMGLOpAYy^Q6|7uKb`_S zohUQls_9Mv*n>wWVq@)iH7ddaV7VR@VFg@u(E+a$VFR$;e8&#Bdepl*#{uBD`Hq1e z0Q|Z>%fJ8te%*Y}2w=F<R$({i9L;liB{C&2!_XAI$@7p8TVEuFUm6=5e^P zyc3WijPP`lW=0MGO^{;&AY&?Mf445|wD-`3{VV#|N9%1z_~mHH_VnEuMTOgGlOtrXLD zrI`N8@4pfTTp0StU!dXNnHyvwrhna~2~tG?00MEFji5_@C+J7N{cEage76Ao&z$j- z%YKjpY|aKvbhk~#a!c5)-sE5U?^?T?Ss3Vl5Ug9DzZ&_SQbK0VAO?L`2L$N51m?e@ zGt)2V%=%pzX!v(mTPtXWj^!IRQXdN|F1eAK;IFt{3)0GpbpCoPj0Kj zKz~cx)%fq&0@8=&rn-^@fbM>aZJ?3geg1#+xkhvg!Ji27qdqr8`9Yss8Jp!#bh*~& zChY_D`H3tne+uRQ)dz&=yFQG6^6(G(FoNg~tZ9G+4am`e>#PD`0s#YnCMN~Jb+`kr z!vw$#q8H%WD*zDW+err`^LIY^5dr8%R|r7JZ~2z-PoDY57Qf(_=Cxt~`dg(L%b#NB z2V5*SG4z9{8Nb^ZH2$BpWck<5AQZQ>1daSo&d;^kHL&kMSpO74Kd8le6G}g*b*qkH z{SyIyl=h2U!f>mQVg0Mg;va0101%2>Mgooe&PYCQ}3146=#>00iJRIa&Wi zsvq_HRk+_OWmx|d?my!K-Tt;>e-*8)H!S+WdACXt(D>h&i4{chTU`39T*V5SF@T1@ z{q%CHlmLzY6`Y^47297?o9$QBzEui<#{UKm==Qgq#r9XR!S<`zxRuI5<9`JQlr_FP zi|wzd%=QZ^Gv3O?pz*(g1Dd#R!TBpHv;Bg~jJFag+g~w}?H5dBWW0s#ucrQgut@?y zC~h<03iaO@Ns|o}VSsCZw~`V2Us0R=7u06FHEV;${{|Q6_O}#c|0`;<|AN|#x2Ap2 z_+P>KiQ4RcMQ!$9QTx^`4I2L|I6qMvlvjQ$0Q9_hQ-@q-ACSL*zg>IvAPl&kwgK1E zCE$9R0$fiIfa|aXTvHBk?PtKXeSwR9S0BGkn!lnkJBWHePS1?DvIS`TcUt|B3fcdy zVAw&y_C2^jBfpdLa|Lq^?7IMtzlsHpU&X?$ti$nFROa{m7nLh8+6~z z#X$cjQvQVIdx7*_yIVPyf&Ndm$WI}64dAv?ppB~kZl8blxPIW&tuzaKLB`(%4Fmm6 z)Bx>$y=8XGSfG8g{|@aRQ9nStmFlkV^*edjbZ4Lkv!*ILr_P=O*lQ*sy2O{4s#xedKdH;#YAE4b{ z*<=7+@jG3AWjT<-z+GcdjdPoFpq;|sN&C@qpdCNp?(mQBZlxrKKkf@(I|GR*yc_aPWHa8opfFCvuZ+6)L*Sk}I>s_6j z-4MX_F2c>?`^}m);Cdkw@XtcqHJa=7Ex`4<5`gtO)nBip06@zp3INcu1@Lff)9WW} z!1ePe0Q4BD002Gy0T0*F@J|JNeFqLu<*5Ddg!k`iv*vXX2Q&mIcz{RHybU~pJ^?)5 zc=0;f{4=iuk2mAj9TmWzZwi!aCjq^FW9S=ZeZTXSydQXV-EpC0Zw%~Kk#VvDb_oDG zLvCyj>7zadaMNWI%( zd1byI@CoWW>RZ}8zWUr_U>nV~qQIsTeR~UWYZIHR_Lu8f^q(`=W5!0HH%$RW-*lV& zgYRD+tC$(P7~9L)8=Dvd`%rFLbgo7H&k#JFkeP$6rM??5Hm+X{cJ&&oE8+i!$ZaLA zoN=SX?_OlZ4xB%}zs5@XMI}-_JtD*tnFgpk%XWktcOhxNo=<>zhAVMLW|;7&1~?4Z zV=Gt7xjv}PP0ddydmlNaR@n>x@>RTqzF-An_w!oaZnLw>2N)3;u44a~eO7D`RVC zqc)bN0s#&sg6g9zXrvLb)c0swY$>7G`- zDl1^tZ94zMSoe9kA_>KDc1mHY87?ByDx{S8Cv3VO( zNo1_*c}+}S0gAn#Sr+(b5DZS9#lo?m@rdcaYZZiDPsD8;(F~A>@CDm{HO7@Xp++UV z@}wtRiv2lNBc`m2cJh{S>jUif&!HL2z99`w-!+eQ>i;m1)j_!S<$Yr{lUQN!X@#6G zeVY5(I;{)!(!1u9HwL)#K}Dgo$Ikw#+hTTtOMIm&$NkFA&jP^!=_UGv9ooe&P*39k zZIK3b7zFBV;#`~7#z}~{kxokCP5~hbdHg*V_9hCJ<-C)*X>fm};Ew9}Fq;DoY)VJ#}kmF?1 zI}sT*KQ~sAu>J`#1)ZRQ#K$K#qnYKz8r8TlHLUu=GhJvH@yENXts2&Ay<9$9A@0<5 zXW!;j-+zGCsdaZcF47f`*yLucsjhO8vf(9}$y&QcBz5FK@W(Fn&dS*r@IeIxUhOa(F_LVf!{Eo;m!z|2<^z!Xr7z>&C z^?kqc2h^Je>2sh8%CQ45aJ0;{z&v=dFn$H;Dg*t8m|z1wRD6e|9b5=kPmc<5nk;~T z{kHG!6M&(@=xOB19p^e9qkdARUDmrX4RVoOKu67rcQNt zXgsuYuI9N^TVRcK&S!v=qxeNslTy;Qccd-ep^!EuX{AI^-UPFpL9mn`PIbdzW0iAs;-M zTRK$K(+YE|#aZ0RrYW%VqJIE`g8me1WJ=~s&5-URIH$NCiNPqJ+V$1!Oq$$sXP;u&o=1{-F2K?ULRLjbbs6^a2arWO^d1j+1*+crQ`V<_5EeL`>q>LeON{n zhinwnguRk40h_L9zlw%iOIN>(1{T&ILR45uJQ5!m4I>YZzzEh;4qrEYX*#-8g%LA9 ze5ZgXNio)q!zTycZiy&G0)VVLqSD~|b|_BC=lm?0GNdjX3-FRJ2U2VMOOV(zF&L>+ z_G;ORJ0CqHl$8NS;8}v z@2L+rjJ-*iw=J=wCeJsNy=F2ZDy7~bs3Cp|p_sixd}pt8UL0BvpA)BK*bF5dvHIQf zA(~+3!nv&1!dOv|WXBx=7?3>_Djmz@ibN2!jIhr2Ey#qG4X3W6W~Nl47+Oktj6y@J zNhQ`JYQibwlns<+N|b}<0;;>h!~4%!qFcQ>Ij7{MsISRv!}@sSE@rt^~Xh<@d@Q zVb1v?6R{%p7@t4NjU4S#OiOMwVBcTJ7)35ad$GkJR=^sPxqjqX6KFpLbAaS606(q7 z>Poa#7c*w@!GEVZD9P)~q-SXvSdUOI^61}t2noRk9U0P7 z>`;#Qh|zVQVOOKX4nO!Zh*DVvX50vXzr-%Mk7!Uj-SwPaEK??^`vnB=bjE8~Xez4e zu=h%q{n&SCRufD%UBYO=A*LxyYH@m=${JXw_)3`?cD9R{NrV`gA4E6mi998F5eJnK zo#?{ZxDsD2(H^yr`!#?r%5|qR*;9Te6yklaCvWCshsMIu zu4T!mn^RP^Pn5C)&zn8VhZ1e8pbEEna7bp9LwdCINC&e%Kdj|yuJ6F||M|?J+S{ zpd7<{5x%KnS(-9OOvfHp7`bQug*?~{GGI3)j>kph)#s;q)-m(OL=nzx50{28A~y(n zUp_Fhv0gYJY+*Z1+GPywMD<8Je15D;Yb;iJX`PaOX7&I<{=p=2q+3{DutR^ztFZ9+ z=jy8>T;3u*-3MS&9E`G|Vc-v=ZFLFxJX&2LHN5hv?E2S8_N@;R0aNe}fV7YHV#^{@ zqlS!-s+VvzE(Llc+7RR(Fm%J!LT^Ugr$}W2KdCv|33$TE`F*vg+~VNyO`hFt4xW9x z?@DwxpRcBq=~Dn}1>^JAIe|E~c~TDtWt!n~vQMLwJAy&4HjbxG9g&pp0enzA5jZ(_N*j z5dykz)#U3wI`NDlW^oT6Ge8qXu;^hvbJ+uH!NU5hymkls#GmyWO4vf{{|!z{|e7nfY3wH&h76f&Po4X>@r&Z?W{ zRIVXfm-I5xSnbZ`=}MnSUqC+q25SDBya&Gu)LUx?zYA1m4&Wl}_dr!vk_;tafSo*~ zs_;%e^6F)hf8Fx+VglCk@O~8|w&J}ZX-ja}udpwJ(1RwyzF<8P@+pW!x*){2<`ddO zLxWS}-w{V61|a$`@Dusw2g;qzZ_*YdtO;)F$cY!=oa=rzT??Kr+LX< z5GfOAoU}wN5E<6MI4~e%rEsU~p4a_tJYzQt$qY;sWs+nc?|1SL7(qfdxf&?B)5@I; zEK%hmS!G6&+;r%PnjstnLEZUAIaASM@R+28$Wp4bMXMgg+!)RA67;AV^;ujjLrNGl zBVU@}FdTh9Mo`{^quESjt|3_h)AteM;P>P6(~-iv^X2rIq4jY74P7W(l4JDH<7dGg zDSPg*ba;*E_xlcM1Nz=>fC;=xR@R}Cv$giqE`>wugYd(siL@WH$f+tY;F1pWO~2p` zL-$Hz_9GAYc37_X5S4}01nUKLV8s;}?jnN(u@#ouFp_TN6EK6vDE|P* zO@9`{>rR6qGhtw0E6-Q!4Gz&Ht<9H3<@n+A%Pr&XWE<=1<$OEY+)t((S|fHpjl6O< z*7R|Qo@o4rDg5P)<=(tAn!*~}Cj=6mb9g9jCtp&bCQ`pX(Jcxr=-~kU^W^)SD8fpY zW*;Aysru}GOUToQVSe>~|MVC}XL&8)SIoJ!n)5s6FfswRz-}=|o<9`0-cYrKaSRsJ zEDqROF=q+vz07}b->Ov07O_s zA(Y3mA9~4iY=#9XzrgaeKU0s<2&o)JUrVxkR)*BGx5KS-`if`o>xlvdCMTA;2l{xG zE@d3us7=t^v!n-t$vTf*2*q<5)oBZGv`TZE87DZ!tOgkr?F$)XOAO(BUUE72#bx*K z&HDn>(C0sE(j*(DShal?wZ!SJ2yUm^@$4hlwwB-1Btsg3ZCI|o+yF$FRI$6*u@TM_ z)l%%H>YhM|g7q z(jWxEgFg$`M-vCvXP|GSb5FolVQ89ZqhVu?M5%nzKz;91P*{Tm+YwLJo;aGkS_gRN zUPKLI*fU58_U(Fms8ga433Ib>ou$W(=)fpOpK#bV7c{TK&)5wo)Rji6O|ny zBeG<#4Ge!xgQ@-G*C4xghRu0MOt&skorfgFVIFthazE=72ds)jK@zS!)-YEsR*#dn zv0B#PE4j31g?i+^@2y!oy~5ZaPoF=i#KEGGMcfcCfnl||;?nFlejYT}0|=f{_nvzz zzkAHHdSZfhI*3tF6Lp7@d->FL+7w#`Q(6TPS*+2a2r1n|S>3O)hf3~@7Yhj`d#4AU zLFB>UNSA2Hu*w;Jk-0gVMra4i0XzM6xOxO9S8HnX7N7e@5#?t!#o49)&E3XB`^>p# z68R@D4rxC~HO*Chq}F{X{MAy`D-e)D1=F*UHd)5x5m@h>VJe_X8TB;#PWQr5;Xr4e zn3R3+nmtl6HbkoscN+9b0P*pnKA~Y?Sfo^SzS)9iV%aKq$8l1R{=B(8AMfkky>Gl0%|)6tVp(lUdkkFedD( zso)%a=X^4!r>K%kkc-%#*qS~o?~BGuTZr)hiKuuCao_o3-6zo^YZnaWP6az8cJ#971Tpe zkWd3tcPcSg_Dg_$`as;{9Ti56S|8H(6PulqFREGCv*4PL6;>CGCp zq-1F-Af~M{Q6HQFnW<3%YQG}35^@mIgv6K3>KkGqJ7cOl2U-{tzf}XgE41jkFMgn< zYkFsC3jf>Jt3|_w255Xqb@QCVZxNXiW^shL(8%d|YzD^TFrMoWyB~)$!uvj##HfWR zEaXTzg6Wacob88!-lJKq;PiWpI!DRspoJ4cAf|&G)RoVCG8yX%J&y-Zvc27ho6q|e zQe9g>X!8udRA-{V*YDE9)~ASQ4^f(@!a|L(7cXjTy3nn{pt!r~^8l{CsGlgVC71PX z8GQMHN>Y?opPPY|=G#}RM(g%ZaNe)7NF69PtdD=q?cs5@!u>cSBK~~!F*hx`Y7aP} z1Pi%OApfV08Z*~NRnSt>YZV9q4KA*=S*xCUa^(eaiRFS^h(oJgtF@f%rQPq?9LfF3 zwoVE2URN_n-hW$DdG`U^$|ICUuJCRr?G9@6QaFnEWSlY4A{D2YdkBoQJ>r)8kW%Y# z68(7i)chp6EvwCQUm(A*Y*p`yZ9#uZl%mK)zc0l(fIXWHliE7=cGDIm*3D=9Ay@93sRqrg!JIPt#5UUfD4hhBMz=*sv9S<;uRsBb0GA*w3&Q=xu)3uGsT;2ifp0~0u&0pFy=6Z?r%$p$BhRz@xtbFJ zyYx(?%`ESg!u{yi;CB8Gdv5_%)zh|(OLv!aNcW+;Q@XpmTUwCr?v_wMy1S7EML-%w z>5?vyZ-cMTqtDCx`@7b+zHhDnTAwWUIeYd#v*+A1XJ+ne=DO~7N)k4M1nh;n?YwN% zFSYJbx?=8`S+_)l?sUYtG?cYm zd}W3eKj(qMi3{}Pisj|;ig&xU3$02@uV)zq=NAxq3J%N|6U*Sk>dC|(bniej{kn3xfsXC`DRyh_20xbGyFEY`xSh+I(` zNURW=mLv1GlwQ&6-`7amB(yp@RVY?o@g_}+kMY&R4{x!gQG%UQr_yEGYd=mRp4}2_ zoz#x6@Z}%>d@|}=-(wg;U_=XV9@N7h)n1Y^yy5*$khb$}r*Kj;{$VY_zUdo9l?4u^ zPh?c(kH`uJKrg&e;RG46Q9r9pE6W9Bu=>9%KL}fe7mk@RYbQ&)PLAM;Kz+SGo4=`e zMj@(AARb=-%(Sph;gi`yOK1U+^mSj;^L~NLX`796b|THOyfMc*)D6nh5uw>shRAIq zT^WtTHbf71RUJf_nOUCle63=G*c7D~>Dnf94$S@bz5I3K_H7|4SuZGEz{^8FLUuC~ zsAw8YN#65f|wZd-3xB}a{--8kz zZf^(gjr9GbAZ+F4Vm`i}#c#lm3TDgtp+@tUcrn)>ODJ;D!pQzYun-5nP{gxA6+K5J zCVZc6E6tOOUpI(~&P|I4S4>KUd;`NrLx{{K@l}zk+{UvtH3i`tf5?AJR?q`PGb&F{C=ktN$iDga49KTsW zO!#h$6R_6V#R7ZkZZ6M^Aq;DO3JtYSd5b5Q&fuAg20G(^W?K|zBWhunxP1k{0~|h zm+!iAnfb}_#AcwQOL5;HhNQQL_Z#n}eTCw~VXhH*OK(=3gAkW?@G9V(mWrIpYm!pR zJZF`ELjX2JId1}&DzYYbr|n$He$A8V#7c0l^#*LDBDDIUTKG%MlIxF3P>be8?jk`H zJj$8ICzkmZ>QaIVZ&B)860UrWC8dh9PGWFc==vIfdzNEqYW~&T~0C zXXMqQ_JUl?a`7j7?aVyN$)QBHZWm8muBiyminb_qJT+L^3HEg^%(-<09 z*tqge-dNZQ9vj$4@=x-8Dp!EPMW%&S0%&{89)qcfOyLyuA4SU6CJDpy^ zo+VsMzyezMQtjaY^6ckX^P$w#X5CjfCl%hG{MB8CxToNFo}x>><$1b2m=1$szJk*t zC*zdQzKOfDFSGd^9jR=tW;^~}R{@SXi-Z7=JpcN$Wdss^4@I8loT4vz8%IOoOKQBButwpvao3uc}f7*5E*_ z;^qOS!t33ZVQAcg>uZi?jzvyjkKXLz1U*!wzjVJj{#@>oQ3NS7u>RY<#QMcE9=aAJ|ZYK<{?Sa zPpnZhDr^}?z-(PsH+obk+c2TaqWJ5}^xf!Lp{;gRFHB6hS6IgugP&Q`jA8>kSY@ab z{nL8HR^gKuzgYmT+wh{4kgElp?0JVPxKe0|{#FL_CBEHGIHBBZ*iOw=W}6PHXH{gL z4+JLlb!e?gUD0>kq5~W`u_$ZTejK_U*@GWDYA`c_tf$4aS_gWYd4s`CULPlsQ+bN_ z&98Trty=`LF?5v1b{k~M(3QIfTf!$l)`6@Bv`@n6h7Yrq7y8^y1z8o5k!#(5Na3@7 z{m}s{@C3;qXKeuzxe5E!@S}8hKY#nv?llB+_*^kvD=G&GW|I6K#J&jeby3hwi)V&D z*twS705M~F{WvK&k3tWbAKgTIh7WxNwT(7YYVlkeYxNi z4nKG&QlZ$gE#;B&&3;^oNI2jirg_T<-97Dj4nv21hImem@tJR;j>Y^;B5Oi@#jK_?`IT?9PKPf6cD^Cdnt zi(mB11V3#zg@Io}DsKui1Y-;cAA~D`uKNUo{G@zyLUBtdE633M1*Fy?wXCTJOeCMB zIdmcx^^y5AGBEUg=PAk0glWrCh!{wm#gFFSzV??6f`MvIr}k{j*L^qiN>XtIr|7ki z)9X@-3Q^W(PU_(G?O1SUb#z($`71_^6*K*Cs~?yq;NfSF-)y{U!=qx1Hg`0vKCr6T zH$~O;25b66xau6FAU6D=#8`6eS^imPfxG5AKlkxV1^G`_(L~>36nVU;R?!EiwMCE> zxOF2CIOz?cxxW($gR=_Y@qNn=Qi@Ib6u{hDbiQVzhlDl*L7L{fkZ_KO)h3GNPQA6H z;Y#^hVffvq#wqgWc22t2jE#9oi60~7)mqy`&_evKW@kvPO!f7H4a}7VifBEtFt{Io zMqW00KAt5giuUo_GG{_L$L1IOQ;brmrK2V`X<}SL+K=PMCIuuxJ7XSiEM6!ENYC;e zp1Kv_^-b31((I*!Cv$FFRD+HCJgp*XIXG|k%ZJM0DVxq*juk-|zx5laJaqXEWywAQ z_CUS=PKEzRMB$I&QZ7mu-XGW`aNsE}Kw@iKeJDsqG;?dvuRWzg2^W#;tbcCh;aA~J*kp#? z@rBnT!G&O%-546r)G$3**}o7iHMAus50j#xrDa6hiu3%c_+4sf(E;=&d}T7IX?6fH zxv}-StN-nSZHLV(_g?XECNx%~_lHSu-E3wwiVCVJLexgkp&5~~p~jwFq&(w-Og%$s zzhuz5J$-j&tdt3>f6y@!l~89-T>g;>w+WEb!(?x^jS51d#4%&3)8x~I&z)?c#K@cg z0x!u1QH;E^`=ruyy|LpgfU*z&a!hK+N>%S29|QBNKmus>0hMB!*c|nYt?+eKwS_AI z@weEPJM?u4*2F+>_iRc=E8a22$`@B}i;^zjJ@E1H?s_=X!#7VIZ&L!K|{4Og(8uL)a z{iV~%!S;s(E(buTi_EY~cgu--qXEa?fx&JZSWZOU9v_~S=)y}Lf}Sq>JSs-pzk?JZEbx7GWK;0%?IA*k@8%LNa8==e;+djn)3}1>jqAjt-tF2ZykbMn0(;67ewa=~u494{8U-v3!vc|> zZ^z6;El}93ZpA{+#lBFp7}LfXR~JP~I+U*-5z$exPh%n8VzrQ?vO{=_Ns%1>s4@}b zS9zn)UyCbiOE7QgLUI%zgNo3!nZvr)NPt^E#HeDP`@V2XcYLfmVDF5353Q9;u;bQK zUN`eF$M-_8BJ2EkL+J?O>>!A|Acf_WUVQSEky0#%>abhsj0+dNG$GzJZi)v2KD^9V ze~z-cSH0Smu`KIrkONPLS;{8Ew#f*UhB9FYYjqvt)dk+bJwd03P`Ax}qV5vBN4r2% z1Cwr4PPH_TM`4O-F)w4~MWYy6pDy&qUovpLCJ2vBFNU~?nSFj4Jr#j?GCeJ>w!%goaa}J1# z9X2y`Qa*Xw%xEj{`s2sqf=9kuoe@a_owwdTL{hPnShTKVOMfZbDw*EaA> zs8z}Jp=BkaGU*LvufA5*;Mo2nu`eW-|k=`XV0DM0)ivQ|rwafTv=72l{>wHHDV z6R6lIQg(RfaQh)?_F2N*8kJ+*7!|B-(C{eOj68{t6Yi^G><|S|K11c|wwzdB;}c!& zFxY~Ow+tI;cGHuV5gJC@^RuInYRSs-%naVM&+Qi*ip|8Ww+$zquH3Mgi}KU8_%hc_ zlI1`a6RHwx`*I*PrPphyi8o&}IK*dFc)60H0zB)x-!ut0h{JCoB6MU!W`k*q&i1$^ z8w7l$JlAk=Jy^xF>5rLUc)h%E9rwY(b>XMUw3n>sVSm&Rc!l-}A`@*opVch|}ULqMI4 z4{Rh|PpaDKyZv)uvAuQC;7FzEc(y8)D~R>`L|)L1-t$K~pCaEqR0DtM60>mp*)J=N zIEj*?wyFfO zdE6;Iy9}$F)|w!^A_K~ofQ&4nLf$am48BEPuub3_tgGC>9JL2GRZD!iD){BB zyaZ=>uaNe;y{eYpR!W!_3Js4w#`4W^=eSlXmUm0*Ef_AbpEq+{`?fKn1k83_oQptS zjwF|RsI|1Ha9&5V5Mgb{MKZy@l}0f+=?GLFm=1v$7AEN^Ol*KaJ*`yWY49H7$UylSc`lj9nXnQdvwzD>F`I9yx#UA|7LeW=a< zrQ^-Y{09K8r!->UOM=>VjOA^MjMiX7Q63a$yelPe^0MXf5zGvuax@wx%&jj}Q^^#B zrTa4r=w~ICJ2RFjCre{Bq9}nJ!=G7Jvc#HJi;;#96t%AyA{(>3WQr|!?DhEM%W=(n z9zVM2Qes*z|K_NCjO8BG!5u80iAKbjzkoKsRCeH0UtC@C;TiqdntsG1N*= z&8jMmaZ(E6`Yhg+sS-6(*a$-D$|E=AY;CMs|R>tML6ly8A5da$pc~ur-Bek_Mq30F9Ij$l2D&!iD5FrtELS*1x|Akb#@A z>%IKxT_T`=c~@*XBUfiDKzx-E2=&g!#l^z)lfA=C!tkFdfE25Qm9vYhsHKtfePni{ z|9a2D3dCl=%S+_HWL((*>OP1a&^%xOv2%0ZNw`XjsX9nXi^&-|0ur^R06Wae)e{gV zbx^anx(m*JcQ>%#Tlr@;|28?0epe|#x`vghD==l$0Z4pzDgW>9|C9wlZ1;?`|I8l5 z4(RZK*zV=f0EX93&LN27p3C;1_uicY1VWBGA{ZcM%W^;BfLH()A3!v_zsL+Q;y?gp z;J*$m0HF_{irrsg0XTm^C36A?Kqv%w4sa(kz!(I$YXNJWCxCSd4a?@?~nJT6T2%NNbEjmvHP6G?s5i+iQOOX^A@|y8zla# z9KXMU#P4ziiE{wQU+Exm?mHeT)7|iOC#8O`sSaEPIwqjM0EQTgI~#(2N-y7?0d@lY z5*)s_7wDIUF>oddT)T6!?>`6F4Csn}odNa${nV1Y%LT9t=$AV1y?sDG#eMIu-F@!A z2EKb+fqn_^0^bzq?S3+o@9qTb2K1M|o*;JDaozz|_fHoCy2PL4<@-DDde(oH1+XdT zms~86%Uu_K*FXOJ95--w&!D~k98l`}-t^}+pwvGp(D&DXZ~IG-^u7*2zeG-fGvGV? zlqkKwQvx{qmtBCm0mhAA&jacN^i%xv?oOa?K))m+fis{izf>N7qr(0!g7{Cq%KvpL z?C%PcvS#)cu9m>O?B5Uo`+ZCKIW94in1UYK5%9ZEV!!l6Km}v9kyNEY!#4~orI=|;Nu~f)D}rpC?vw{8j6*-YNKja zthB3}Z2>{PL$2-9QuUJBkyb8axowMwn)YNeuTdL!W4y4l4NoNnHDH6=I1IN?Rba+9 zEOAD$1ab8d4;yS1tj!WMpKt^}G+S*G1q^)2Y1wjux6_eh$RV3s6T!5T^j!HW%lZwm z^S5PJrc>FKY*M;U9@5c|6?k5Tma^%fqDfXf)sPcTAyZu?$S(zOVmWSj3?OZ<={-?*Npn4bpQBxG<=O%0Yoyu6}ml+~Er)Z~k66F0mI zR4%)9XhL0LKW?9BQ36|QstJ}EJI-_~a}V#w1mR=*I#1ctj0@E-2KAUj$bQ7_#sm!q zI*=vR)(mMO+W5h8Sf{1Z)I1t%dVy?bqUjoywQGfgtUF>spnSY&_7$%j+bvbtxIZ1V=`|QcN6V9LtnbCqRh3`O9`<& zIxL(;3ENX?gwL-Jnb9#`@keIyW?DOUpO`jNKtV0c99a~a;ePX~Ii9{yf#bO6^5y*C zRBf?M$fbR%-bBk|_uRAD#6^Ubiny&aE(#nM3n`b-}54gm?OXL1iAH~M`$F?f9Bw+w60=D9DL*X#5!Q^}7!Owt!L0`DMLp*FTH$op+48}mXtm1mr6m8 zXg-X>yq#gQQ!FlDowSq`=*dx@;C`9P^GM;r%4$)gC@|shHC3pH);Qi9uRC#M(pUTL zH7hTfbK3MV-`VDP*}e=fUO8;%C8q>&pb!Z6)0Or}^?N3d7qz0YECGq_ayC0W4 zuXs8?tQ*&?>d)D8H%4gIB1GP}KV?2cgo{B*SKYhBNjgcmXnr2h|Nd>HS@|?;Rg>7- zy2y2DZ0WOv@cB13$wC;@Eiwt1(GgHo{3TKApAjjVOpgeXpxYQ?DOc=31)J!m) zktG+yiwq$QE9x!(8G$4k2n~BJ_8vR&x)CjX?DbJEiOIOqV?$)>C+LfBvsaDY5d^;$ zgFfMcml=kCT9aOON$6E>HS=R!Q#sxt+PPS#z&*)RZ&qFKc9YR5`^II>_GDqd?)hm- z)=?Y|iY%q~L&g3LCi$n<&B^(neC`@TZrH_-05eIS8D+yWCPu&MpmXn$^y!`&DY;_8 z)8eNPK~FWsloc(zU^_RhY{HqpH_9PjhJ{g^ZfQbBrjv>qDv|r-vRyT`;?Qpg%AOnc zYp$XWRkbOE9`Q49r_g!e(jT=)fu{!afAgh>3X3I{!D~vu70K~mS zXCS$K>_hx=cF6xkbYWFCk8qls;H5$hbJ^amhdl>4b<6NF6^7>GCdck4N@Q5Hl<^l0 zP2kT3``{v5F(W6cfH>X*M$;O~Vmk~Tg;&^+tCDJZ|=xD0qD1ES}+kb9pEqL*olSMBdkIXjDw#E zttL%@&2!W5xXm)W9i|*K3n18%!{ZphBEUDA;V`XoNCf9uHBB}evnCQg`NY_a+iz(& z$ROr(;As*{VZ___wW_`KUgp$=BEwAw-f@d_whWy= zg2G@;fjv0K9{#Rsuf|!`=;fpQEULy6mB*fqz0TIJq|C6DUsLl0poXqFy&>C17P_%@ z8rfUIa;`oR3U9LVN+$T``!)i+CrE9P<`u;@AFY)FSsylBu)!Ec1lR{ndA+O|r4zDI zN-@T4u~~}n811ml6UqLfC@G%K41KhTUDg(rDy)(Na+}zl4HC+V2-fgAz z841DmhKg0bcDx?=%+y1#FA`((e!-uXcm!+xpthn?8|B)M@@gl4Odz^l{Rmy$PRnV( zh3CLaRvAb73_C)LvRd3Y#DJLKt)W9%;>3!NP`IIIy)?;Ml(h z=4Ryj{Bq-x_~|#7LhfUYwkDFEeWJxl4o=WhBX*{Ul=$Nj3hre8H(P zFjK2tSZl4pqn<`4brE?4>~#d@3psA0R2UcCKPpVlqkrZjX31iUoVi_Mgovwgu{doIWp7da^W zlx+%(ZY9Z21Aa{b1(KqP8W{o3{4YM~=5ysz#IfF* zUuWj;ov1;fw#89%O*BKsXQSgV(P?lBJVjNg3`e1YGxn4Eu3NZt&ajir9v9rS zo&65{{VUY=Psiw&gY-|Y0MK{mA+z5r1OL;F|NGYf@bK}JFbL4E5Cy*@4}ZBIm+OxuIJHO_WdA#QOa!w8*a6GKkJ6PQ$gUvja)Jnbdc2Y0xOjz+|_p8n=( zv8g)&i>@R4*luFsXH>!&X_jt+NJ6e4j$CQpFIs-}ELDd~i#}sR#@7W#yoFx#o;~`~x479WA4_gk zrm!_wONOc`*hNz!LoE*Q27HCsl8>+UwqC_NzaUL?QU5kK%wuPR#~1sd-Xs*dslubN(MHeOVM3+Jt=QolcV0X>Sd8NB&JxZUp| zF@8PZw^!~_5OK4_Ww!&0dkIaSN_NR|I(Ra`IE%0mcVQOh!j!m z&Y7w@T3P24Q_5KEj7D4*n=>bdMfm~}t9e9}(HjpxJprgC5hLL_3q z@JK{6Klm%MccBtP66pnOy5!B7VjZ}=C%_VXHL0dz_!XUbHt_Mv(oni@!}pREtZcQ* z`2tI;NAG!v!Vc0OX?s4?#M$k3+D&kU0gqU;ZgfJhntx)-{`!Lsj7&9FfzRqe@-vl8 znbY>~oGttv5+R-3M7kFlvR?{bnA_34uh?!TFUyQ;?5Ra`vBqbd7rm)WpO61a#M`SYhEuT_-z(sS|x(F$80~d zeTno-z0_0iSq;ILDX6IeUIDDO9*H*uf>-6i$`3V}zw`tD6WYsRnPcCn8sk-djS@#K8&2brYG$4p+rVqq^Oz3*EY< zhcu<{mzCb;?ZR=yU1ib>mytE)=U{6n{)ON-y*k z%Wz%ivrD{A>kwx~I+#iq`r!al07XeQy=|4MJPl+xeVN2!a%P?g7W4AA<~rdK0*8H9 z3?duuNg;Z~ls8=)&R*N}3fZcSqVz^kf(Os>Br)j~LnodNzks4Bi{kfx=H#OKd?D<0 zps204CFvE08(nS{YRna?XpKr@chV2>K%wlnSGq?>f@nq=zUol6nKq5=(CFrssp%4h z#;bO`-`4j{#&|-B%}5)kpi$pUaL|92<_Y9o@*K>+Rq)Rxqm}d5VK(<(aG56&-?THZ zaDSU5!8JT9Tqzr^7B;GqIqtYsDJ!KO_{E+p10@@7RB_LgFJg6B6Dkqwh`fDrQ}XV23a}6)X_{ls zt(qIC{kGxhdTgnEKy^OM>?i{K2mjfrfD(*|#`=OjXhK1|Y5z2N$&_dA?tx`f@(IC< zcJiy!X=k9|UI7E{ngzRY?SZVYVItItzBf>{mH515$S=Yi!I1j>a0zrCoiAby#XK6MR)$0Gc}Fh|Z2C z(0nBjJ1UzWPlY1=lzJzBkLu=!SKj-AZ&eXOJ7g(i8Jx)38X}oHHfLy$w+t*Wp}%Y+ z^8F}Z&@nhez|iAle&`$i<$R0vPkxGcf=o;wGpyJhjOr-%;xhZSWPz6JiXZP0Q}#3~ zxUL##Dm(3yG*WgXNlM>IppEhe_$%9#uDvf$V85!XAz6n#?_COf1jg>z8Lf|_Y284_ zzUN+AN{%%`j|~#HSbe|sW%&G5?XZLI(ummu#V#K9H8c(HAd$&?KGp4gXxW&cp2lH$ z20QtxX;8u_hOk;oN$?1bvvOp`2%bvho`W5qPMhglP^rN9FdcF{m-eX3`U@=g9XHFq zo0cCH!vcxBCv%8KrSAjKE?UYw-gcQ#+PQ}3j6OLUk{z{z;@Na7$=>#M3Q$-_cw%Ox zZO_Y4#DTqGH#KRZO~?C8yV@?RbmSWQkezM!#Zs}`podN(Lqh3BVtycagCPP@(`k=u zn5sb95^>`z)rgJ9zJl{l3n&LKAx@j05@7H$P|(U7_=@fwoT3E9Wn?^1C%?xJ`ln8n zh5Zj`TU<#x1`-4dh|BnpP$0hO@-Ai15FNX%EU=yCr0ezoE2jc=2h!qE2}AUq2&Wf6 zBfqh;)njsZrzjo@sb}wuBH2<^yEb#h30p5JoiB;#y9iy2V+PMuik5emhES7+C(Ou^ z;2R|BJ4vc6dEL~2BWW=2Nnd-3C4)GB)N>T}_${rNbGq`yk2jAt*2`>5np2;urr&OMAqb*&`(T*;WRj7tuonoC`~MbSRm#C33t5`Z)&&A%iP_m3yvj zS?D)`k1_A(-mb2altiW z%@hZ!hfMjGbHYDawR+Jq@LhMUJI9z2)iEKh57=%V4D?$;LVk3)qnjoiBHbKun$yA@ zp~eN6F68EPTsY^uu%vH%=XlWG9OaESMSE8q92Z{trN?+&iXhLefuE}q);(0b2WEu7 z)#?Y5fPgA^I4<>9XVS0EO;K_T?`L0`!*lBz$_?5=phA=hhSCMnMI$^q(8hR3 z%=P!sPybXFU@O+|-Klt-Ow7+c>j6lbhXMX&ul4o46x(mDU<9Y1pm3s1U>Qq98wS#P zA%O4XNf0MVvxiI`tiNm;9S!Q*OjSOuV1)ypWd*skAZR;qZ#_;8gQZTR;#S)e-mm$@ zm|y)ZeRh+k#AUqb6qB=!Ago}4XIwwb#p^3!Ogvsma}-_LwY3IVpU&5$wh;wk))DEN zBh#)V5xd4RLw0A}%!ZqpvD8#9sQbB6X~xXdKBtv?TF+IIVxO z0vqQaOA_KLa+82O;KH4zGH#@SOO7j#Ufuc1vo86L=?_mDP(?zD-W4L^DW*fpJ<$^G zbYv$2d)FC=P~lxgL|g@z z0OE{91-Vw1i507h_O-uuB8Mu4`Eu2F1EgS73ac=W706>5Zq-w^Qs0HkawtKKmzaVs zJu|IwV0n){K}bm@;%|=i2K+thYdlyn4Ki}*1b5^+UhOz}3N3Jwm{%7QbsMc<>90}u z?e-Z?O`?Ed?xC5dyjENhfRENa{PKLxQd-dId4(X3={VD3^vA1L7RRY@iFD2#Ef`H7 zI6i)V->V%@jp=31bfU|67Ojm1adxSqmhy`Jkc=O8AigVHISJqPJ(AqAuC!nh=_R9D zGXX(XZ8V#djsEk5&*d6X`BBu0D9)h*?qDv59j3hO>|Pu@HMK;?_N}|74y_j7J93CE zMmAPH`dR5sw>{J+{N_-CcYi=%$tn^#~rHf3z|w`SG2_vt3oaQFC)1-*aKj6Mp5~Q(K==NnP-HJzrODU1HsPAUBXsTS@OXteL zr83vLY@&jpA6OC(%Gt`Y(hVd?i5$b{3|VWaX5kM{?KCX)JTQhqqJ&iTUZT^)H_p;4 zd&fsW%_}6vhYLT8tDb*O%NYP+9)a@mv6-PBF5fgYelpqI^--t~_#4^rVO!BikFKm* zH=za0*vdx**)PJDCO$;G)iOQMsu`Ce$I52!N|Th-=b7;>B5dt3V4>O1f%7Hq%ZU%4 zdbLc}tr!2;UCSKGcuW3$CU(OU5ae^>7(RY_9y0V|>v=M_O0fD=5&D^a7M|*lo7_)3 z$)sSIh;};Yx9I|1-(3xd-HcXFG`~*qD3a14P>{@TrId~eW|WI-FbELb(M)D3mZ>Wq z&Kbwj0ri90l&V$ALP((Spy*V1`Yz(kigN2>JR7o|BSOi`3K`m^yaF8&Br~UiF>;r) z!ul$drbMEwnq;& z=)d$H*jfK*&;Wn&-`do>Bi`0*8KS4Rl~1blwpBtJN&HE|3m=g}V56}pR^om};eR^6 zxq(&(^zEk!TKfC@V017|=y5po?jC|-Jnq~^Xd{M)pDn&#!n3sHN)WzeTJtmv6T)H& zG>iH6oNH5jzjqu~8FnPPTNdB7t9bJrY%WsZc2A#w7~i}GvS{d!G zf>k=u>B0BQFkyJ6IMNu1@x>bf;&lcXu7K|4w(QeWjhff5u%`2U0^=%%g^)ugUbwa( zCd)k@lMI86j0(w&tII5R$>%yR>PJJRw4XY+Mu>KiVB54wI9w(hK2$O}m^U1HxybF| znkFOOw5E7nBqmP-ORDl38}bc9@(g(DQJDP9PFb3YEkn=8w(!ZA=$)#NT&k#NuQpN~ zYFp$;oE#6AK|;Z6mYb&U3f-WiAJ^%J`w(Ab$}R5<#(+I z+wJqgbneS4>WmemJT7UxPc7D*F%~7$5YJ6^QRz~Y9Hv05`)j%RLWy(Es;J{Il~&SJ zH|3_+I2J^@^2J5zom9el$VnBg!-(F)giPFDzPwY{HLd~E7&CbJ(w+lsyve8-F{qRx zZY1if2GMC%iUFM77l)-*I6&|@YUDKhp(gg1ei|$5Z+IKfXDV|$1M|Gy(}){?K7v)R zI|-Yi+Xcj$tfs>NLoH-VEfP~hmc+aOk{}c!^bo6&!{aV-@RFuBL0OtsRW%$}npa_8 zh%!90635(&xo2L)43SkOqcnxq^1WMP>@_XVW(v<+%+YL;eB4Nylu3;mpSBt>*tZ*S zZn>o*{*8ZFo0KZT(?Zn+Um1e64p;9P#eTm$h4)Eic9Q@ za(=77fbmL)Ab4LLf+kjXRbO4bUQJT_WlB5iQ1Qnt1lD{nbEUM;AqQwbIwmkh=?@6F zJM#MIhq0$+N{T{f=RhO4zMA-pCb;eV^`gg>{RuptrFug;4N0AxDJSZSINNI%t@GYK z(G!OVU*AXRJXA4%>t1sHu~?y}EE7Wk2z3B+9g?DNR2wDli*YIsItjU@)f&w=P^-GT z$`YZ~6fNkU5IvEGzWJ=)GsPXe{F%md*EFSM<2CjJ_!i@N>p_W-V;)RV<*ASo1kGe|A zaNmq~=OH|?$+royle~IzqT7))tSP zn4E8qgxod&n%`XJIoXvhc+TiJCQz!Wvs0lU)nzs4`9!3B^S2YiBzapCTKV%JhOBb` zZgfaPoIb~_*ugr+8 zJ6IR~y1IRP{PJ>RFOP3+B5HW7a)o9gg(gR}?|)bRRunbJQ`FRlYd3~Z zw~I72v5ddsqd+b;lnSq*e8~)8&i-+w0m(D9Y^PdA} zlFS|PUwlIw3O`QfyAi*95zFSTUk14J4U3hYtrG%r6B@UX@MBrsbblLeSeoxWFVPup zkv~sI%n4x&bdQ3YLYG}pw?TaH*AM_CM2ca_zdNpoN(do&z&rtC>9$~oGeoVbkbF`6 zoV&WtgBM;!oG~^as2&Gg+@JRR64_#VW7!E64EmgV=rf@XL(l}QwuYDc$c1XT^{1=> zUoQc8Y=l=*8b)5#mq9} za&#U34DZCkm29ucNB<|BHgiX{X8EG;8edK2W>J3#PVU=z&D<^rbF4tOzw)}hP#CIq z#uX+P-Xj&+j`xE@32%&kM>(!LG&r~Drv7g0_Q6GMssz;MgA@t47YMJIxMjTp7mW*Q zjJsUJc>2xotK|cNPriP$ORzuO+E89VyZo_^BPx0~W?Tu_3 z`~*T>uk{uo12-;pV{~_6RppZ&a@xM#9iL7Nw+Vmnpc``|CzIt>LcB{t3Ys+fH0qVu z(fp{k7t`SHB1~4kOYMDkXVZB**3f+1P3^rThSkAZ`r?M@_7Rcs=0nj+eh-=ZPd{Ro zKR2qYDc|$N?*wS_`X!&=OC=2f>JbFK2mR4Yup$s*3I0NfFF!=V1zGxk6PFu&DarGl zLy1rw#w&v{aMoVTmm+Ym=jGk@)Oks$MDUU)dDAD*>DvNP|FOO2JVej5KE5(z`Ya^gS)g0 zwuaZkrxR(r$6itq@-xEo_m998MWpLQTM>*|frote-;O2jH%qI{m{l>omQe@eZ&MyQ z5g&XO#VqQ6s9=r0Hr$pptvYcdd#>+r3o>u3zOqmMHn1zDkT9K)qYr^mQ~PL0vf!Oh zIsEX#McL%(a%t;L-N6}5C4$#O;<3MnIQ}OK|A#&nh+XXtVBydJ0G4WIC1wsbjh_K( zU~%#BXaIzz(4|QTUU3ds36$D)X=3T(gJ_b6KzPz*4Y}~BbmURl698zTzQD77sOR|0 zg*qVY>mNP0Tr*N1GvW?A6b`D;TtfLS1|*H^bz|sPA7NU7PbRj=JzgSXAB=1<`s@=p zgnaq}6;}u-meA)}u_>NbtP}eo(4DQLVNkg2D4I0T$zMG&hIE~6ySeU|@gAZH^#Oal zNNo2|VSiiW`n78LTZhJWPt3R@qW~ZnfFJ=fga9ai2ZH@82?XF`_t*X>5bXEpxc^i| zY=7$eimON`kdp#(-E`lQ;o4Tk1shUPhN(a_VbBZ73O4m5 zraj-7=gaOukT~4i*TEe0GKn8AIBvRppJ`zmt@nWKgLLncago7ef;2<#rb~E*Yf%c9f)q@>#|OzxehDvP-42{OK^vLg7g)lIub?@Wa=YdvDTu!l zUa&#MWNAuq$-%?Ff|I8J)VUGL6qP85U5Pd+q71 zy0aU}`!9#R3{SbTQ@?Gs@hwk@qDOW3m0--;brzpAKl%#)0dBD4dj5EUftWVV*E35+ zEjP*7dhVRG&POl28}5~WG+e!xxerf4(IriWlj~^2Y$7EE(`!s=#-F@m0OcFcoYfn6-t((F`^(1 zE?k@3_}#Src(evHR`pPm`b(FSmH9trF5Gtu)w`a-f{$trZ*rpZF*9!)(V!Dl5x*Gu z7?Q~gk#KB^alJrALKP_PR~5RbsMUGghTn2!XifH#~!P ztX;6GNO~5eevA`6#d*aWN8&1y%*qfoP4lJypeMQHacmD_l(&A3=7<5uu-wR#AIoql z^DjW5jOIs*hCRNbxThhOORRMP$9RQ{mn`K6gVwP=y^Ymx%PF&CRNp}_fv-O$c{W+1 z&O?D)Eh+z)1Cwo^;C!|06^B>GfZMXHrY&c#DCwJ*@>06XTUdI6mEN~r#l&9|KUrDO zt<)wtTwCYv!H_2eeSgU6f9r+)IbRf4Znf)YM%)E@ojB4TL!OwBRyBV3aS6wZ7qW`4 zuOG)_U5Upu&Mq>0mOLG8_I#_-KI`jzHw2hVPOzYX0=aS zLnYW`up)F|;8i>gR8WT*Aj!={VC&s{vE@S^ zr^>C$`^Cr=sV>kUSkUhMF(UbTZRH{gJWbaeC6?`a94+4F;MS>qC* z4)!xE?UV(pZ$4k&43@d5T^NWsT66ynRom3j`roUVMFa^_7y+M^S|jqjX%Ci?1=vcbnVl=_=;OxCzy3wV-_tUTUFqA(z7BQ0}|y+YRjr{=8?`+53Io z_j&m+p>eR`0E3WVDs*}55r7P|FF6*@vo?4uDu~EG7>B5Am{`T62Pb{`yZ%+rcXX&2 zGcKxJ(#bvCACrsCNGfUOF|75TxQQ2DpkYQ?ei47pDS(EP+O?B9MaPx&98I9|Xg&Y+>(2J( z$JyK>7t=W=Sbt+#GXOY5H(lIK4pQfHi+E82H3^k9{keMA~EBB;~XI zuvpiQSy2=E@k2({*~fGp`64YvVmlY>-ED-HNyVO+7b?T_`9y}&H zC5g|Y&N(S(w3Dk5%W96`(Vy)TXk256e3ir|W|U?Q-Nbsj#Von%KWblQI*nk^6!SU# zxpq_Bfb`j|{FL^@ns_n!vo*W%0e{DV;){+|oM)<1iG*$+pgQf43cWLiGQKY8NQ0^Q)t-oVgkILM!-25bnx{i_$1))ox8NtrEQa zbXDkQ`x0{o`66D%jX*j0bZFGr*?ToPCN4bYP+sSZiRgOqW0Tyj#Ch1um9ul0E&H2$ zjNlpXf*3!brvV1Nf6lP#BU12?B#6*o-K7s`GZQseoT>9dx2|i24QD&*4qS00gL?RU z8R1KC=8^E7=xO4*Qp{As?W5=yhG+HFV}xwHd1N~rlQiY@w8qx0y7^0bH0R~5Py>M| zfm0YTMT_o}=L=JjY~rhtrW%#spUuCrH`+%|pI{Jk-7Re9ETw>#2z2IR=xs?BE4mpm~?#U*S{zVEsnOB9; zT07)^iv}0vp+fq@(>GmVG*x{2W+BC+8137d3#zBR{gcL9A1Py37fj2u<;@r@ovl)M-SOf@#v06ulJ`CKp^N~5$iu9S{x+r)j%Y*ztc?U+rP`t0GFwgu zV~9nUe=HG(g`z9TKvBpWgJuJZ=p2ilTRQhb556VSx$m>|#}@9t z+F$+MS9ro1-~pRtNY_>YB3Mxk%7dE?_L}|cR-Ot!#V1f?!Gy#=1<=Py#`xXz#Jtbn zV2_eQ=$FBjH7&wEeY>ZdRinAy-uZzmK$g`o{WAS*nM9K;xZ+yB$SF4;&=%MH5muI*%n(X^(TqA2NJ z+7hCupPx&78)IT3>n9mlIGxMKuv_&CUCG8D`Dish7Lzwle2KB;H{|8_#q(W0W`$ES zd2IEF0e*RK=sZ4+)i|0{8WIrs zNc&78!7%J&CWFT4&le0Id5NkV21p~+A!nC1Z8%8l^@IKR8@gnDeI+<@u7)Ze(T z{vNusV*Sg6LtVb#!j~Sk>59rt5*dvZCt5T<&yV?WAg^LF3URq?u1=IS@=y2bMe;b8 zau$uf-2o?pwNFLm%^RbEc-xP;EnVl>6MIrhyfw47X<`^GFK3rzv_Axz=*VfL_UWI} zW7H5NO{Tue7&)nQM9?HiK7cpHYHoa_X35pku)3Bkyk?&4p#`>GuhqT(=q=XThlyil zq_!N{n&(f-Th98|qZ2%FHT$8skqyO6zJK&~E;g}>+wn3cL7dvC)YeMzbaXAQt2uF8 zOT-FZejB8Mlm7`}IY;{~A%t{Iz?KtUxqQ%>A5#~{GS4@r<$3xvJ@A5H?YBi7AoYE!++|YX3zJE{v3h}H=382B^xk{<~ib%j`2~? zNHS|VR)bh@>N#Mh`Hi(33|^v0ud&gUc=WuA{bpm}dy1`=`#G7mG<()!#}S^?il0)Q z2e|Erl8O7;>yJIzzZ|#zv8BaB>BE@+rVkVNMIZLBazGqn z|22IWJ;!hQFb;sL>)+VJ=-C+o+5SCy*dM*`UvKk&j6IA4B*MRuhcOc~GlFVF#Ox$w`n`X4K-#;4fy4hv zk2jFR+Ha1a-|k)vcOGmYexSRPJ93~qZlK@PKz}-+F@QLM{=D#?odfSgX9)DAU%3I_ z#(z%!=YW?SFbaQN2bu`r9sHdD ztRe5bU_iM*ByIpv+dtWWo(WL9{gVLoK+rI_%LdR3vHkvaPzPrF{qvyVem6`&*+4F+ zclMWe37~(n|CRt~Duz2>n!7sy&Cs3AB`5(j6T_X+<6QzM`p)+6E&+6Z_TLf!%>eSj z`6Uj}6b$UYB>8(6kI3za;>g_m>Ld?ip4f@mm6*xqt1B z?(VPyiQg`bK=c1v+1}j|022Qufgq5$s{>x(AL!!++HWudhOQ4<2LF%yH!=cU?O~Gr zy(Jm|JTK{Cp+*XBRTvHzr-*k<+!nx?$m2r>o+gGyg7L~v0R zJO9Tm#Y7lwea%c3K0(89%{Q4fLFGnX7s*NR#tqt(R7-x6`!)~^pw@W6p6!1vt^U{2 z>c3}c#mMl7#rl7>(*x$FKQ$myQUB2Hq<+)nlQ<7@Ntcv5>1esF=}*jcUCRM;&g6Un zUrKDe^=RVR{@6jm*p_I{i$(eOmZ|axOuccY3^qbg%QR5}ERNAgyWXqs*FprV5AC*hiJC;Sp`Xl-WMMCLTP8TC3dUCUdx4{U07p-&1s1Jx%$l&n7om69yK4T$&r3iA>S7Z3MG6J^>ENZH1PT4N>yOZMim!LDrLYYbB* z?PAOjI`p@%7zRT!V6KYl{AcBg(Q0#RKGo@ezfph0QRHd3<=$yWzEi$<75rEUC7PMs znK^col2)vSY@;ss(78ok_EOh^HgE!v(0cI}DrwPD5@dmXG;K^W5`9yAL4ADFokR_22G zT^9RLN3}}?X9fsrXVIoA?j0O5v8|_biz?y@zV}rOXubA;iedlD<`on%5bzDYix^lK zE8`!1McjqD+nOz(-LYCQ{ZUx5Evn3@qYM^>-hpJCQ$2=lN7N>Qh&MR*;8fq^dTg>Y zs?uoLs>jZlrF17Z2Dt&8`-&*-KIuRU$OlNr_7?(Q$y_4~SjDg^0Y)+9^z!A*^vb`C zVnSDKyt{~h;KR<1eUT*z?EmuRaguRnRHwdKL}r#eu`Mcf;d5LyV>K|VDQtrK#ChPN z@_+Sy_v`%_AGq{oW&OLiDTzgc9KAuf78&4VE>Don3L4u#h|vufdb zrFa~>%me4F>%1$Ja0C$-^DQW)@b$FB+*Qm(Lh{>msCy;qdb_i1tV4b%KwlD}s5=Qn zetbcIEuPBPX~q?%@@^CBNyHlmA#7u^7pLm@kGc*cArp|X-S8?t&txdDSsFhJ^PGjOpRXM=RDd$04I>XjGPttriOl9RsF?Ft z>)?z=-e0~%YB2Em?+Uk*p@*CFQxNI$(tUQG8euecxKig?wmzn{DJft$vt20x3k3_0 z4m2$iEOYRICb#&Rg?p>^)x4Fl9|>Tq?145MRgV$u!>5+^`;_g1uTJCHE zU*iz8q+X-JiUr%3OnR#B6TAqB-I$^1GhBVQ$1tTYYTpmnSV_{w?LO0MR6?t>3U;tS z#2pc9J;dcAk_*Lf^{Gd|QZPEjE{$PUeK-$(RL_2LL(BPV9ihCF#9|3Az8x=lt;3t% zmpepF`}_^}Oc9I^+&%s6oTwi057z=HWSd^h7%RZF5Os6=f=0Qd0I{(jH4_Jc?73xB zF{05rd7NX^mgL;vBjgvmnmu z{7@_m{C2bJlIe(ccNMDm5c|=?1D1_k4fwec=aAHe5uSeFmd2=Iu+{Dt*?5yC zSWZ2X@YU#}JWd|^c`|V#1MDk_62Hpk_+i#HqJsV-DXW?fMBz|n2HF0Gw)vEAESy$@ z`>v~~p$rL_q>{1`1}j2E^Aa%>+d7m82(&Mcl@GLG9qd>d1J~rhan%!$eUE;|?IEF4 zvG=)ryU716IuP$^j|mtNf_Fy5^U`^pE&nT8#iug&D2McPoj_ z6ot(yTrFGl!T6DNiygKIBW^uETiI#!7aDJ`W+PykXC0~qR;o1}*hYUYoU2iH@OC>F zi%7#8&WYQ-$BindUTG^1L1+HHG{eBrpy5#+L4VNYtuO4c>K7;cB`^$Kc}+~yw?#-j z)u$}lBjQJ#&AEQraDpLnspP64+GkSozF2mcf#(=b(xumZ;=8goINQ2dhr9SshzyCY zP|{3&BlkYWAeEJSfPJtCt^JR+{Ktp>e?TV>#BKOfDbMncQXbG(0o-tm^bb}@FU_sRM?05JI=>8qM{;#q) z06XJf00iru`WA>E0_s1%{sTA)!XRLbN%%LvCFltz;opGa@8|EXirk@eA|Q~BN#r-& z$0YI_@B2IN{kYgKGz0{lfNJCy0&$lDBxbzR_y0Pnp2mFy3tmf87JXpS#ub zuM`0501=RYoPWVOcR=m~6axSAunqw8JxEpY@6>MqEeXh|7W$0$QzO{t?sLcVh-~V_ zD@(FXS*@RhRnez_2+_2tU-v(8x17# z)}!qMqe@E9jOg#F%QH+n@av4eOq+qff3zdx=Gmco_Bx?&Cul1ZUyf1iHh=61N{zW$%gj#|Q+pdRXN!{$+8jF53^{ zPj1?#I!(m)iT#>GkKyly>@LefL@Z(YTK0nrV7H$~ zajEGa9pWa`83fXQ#Ry8H`|3-@WwJ2H%20MZVJ6Z3W*jFe7VIZ_L3wOb`|_tA@W`-M z!$^)1sK7?XSKWijV<9d@Ly5w?uIN7^9Klw2ciOc*b?}kLI3|?2@FIy1C{+d zO4)xaZW&-MbT9nRcsDzMrVk(oFn!!jP=8M!57l=5rB9R!sYe3h21B|^w?jj0S`+S| zL(#5Y&w+VFDRM^_81QEW*pA!aC^gACQMJ6!`jA7OAHASbKLj5dlOnDyScf`%>dhf z4bS_P))#E$?D)&%qsm*E+Wk=`+-@(v=t_kg)Z$WFJp(8?7wg_sj`5{1wG#-ieAr_3 zpN!uo3q`gQM%5GjO@#Ca%_qfW2^Q%b&iKtl;!IPwx2olqX zM^oRvaXS$||7l%}sCBk(5m(J3pp{{Zol=O~8p!G3yH^nS((Givlf#MZ{2_}>q|Ae!ANYy-GBTkJ3U-v5&3q$1;>s8 zajra|v&kn|c#HOX3(UcYR717&x27Y5#Ns82qW(?wXld&_lo{>_JqgrD8w-(ov@ll^ z^Y8qN_&3L{`|-?<*uu#+`=!4EtYwkf$6FFR)~U4w&IwD03OC?xPX1{3)c;*ztp|LX z7&-n@ZUS*6qIQ}RCMw}< zG!Amowm};7vJ-B%Ut#0|?TJs~dc!nV*VVfedYPbvwD5xPDkRCzBTnAMQN^!f7ECe{ zQEgt}^h;LSZk@|~x9AU4@T^@6A93wzwGC8GacIIcnGOCnd`qF*$jS}=E)``DE^2{j zQOt3DIyN}XRY~2tCF^p!?V3yw+r?9+p>^r#W&&UqFua16sdB!@KacTlclD48XZ*{G zR9FrGyy#&;fEOR5NJk<7cuAvd-F_hWu|PwK3JZlu#woyF=(i5ld7v-V9)4XTfHK=Q zj&T&mh|fxA)WuT)rsE+NzQ*)#WVn_Ti`aKeez)Ou zA}8G0cb4qJ)<@O%26wLG>1QV~z1r%*1%ZC&Sq#ygNzpQGc$Fu#%1l!8gPgUJJrs!( zvaPJK^zn-y$#Fi+%3g?>w8&WNXaew_EHV-H-Jy{3u{{M8gVL8LEj0AQL@C+Y`hvMguk!f~GslRm)H+#von$l%t|DvmU z6p}^}0X@}&T{t9Z>kQ#jMG!3K$9PQMQkt_kLUXCgv6N}@lq8msPTHhblJoSH$C*{7 zUCm>VySCjdr|>*ph7_?abNW?~InAWABrS8MiW5%}syKbw+BuS2{9@s1%W%E*c({bQ zGiMu~aiYz2kUNmX=exS@ztedAtsHBEEX+w2fo>;Yx3=qOP|`A_e6Nqik`qNs1D4ma zfw)b#)r&UsXRsP&9H$6tZ;h!c?BttD87f5CSFfg@aL9MAAbeec{PL~&<@OW4w7H>| z8M-})cnXR7Uz^SBrspHLx;kSs7;7yf9hP@<_D-);ZVEiNP`;@pTHn`-e*o40wBnxu ziP-MQ-hq`g@Z|;=;o0tjPeqR#N1Dyvlw@CfTO`NE>ml_sgWl`t8*7@&&OoifM)r@>K(@0_o*1 zD(gLyqZv#8H8v(sFDhY;jO?TPV}h_y2FX~=9U zMz(W^;fQ%Q|_rq(5EEOzb_^oO8D#&!^_}c$0)^~PpcfWZX$6644A*nk~Yr>o8H6A?XZFcm*%0yCAw(-DaHX-o zxF>i~PQ>~Rt!6rdG^etg`~I+t)Yn@He98@_Cw$+e%{SQ(`Z%@*=rXxj{B}O@#XhmB z03UQaA49PPaVAPoS4m6ZHRn)^Q`;Tj0bnXI_ZsMHRXgK`;!Ej(1H`LVMg@F0<=W) z2dlzdlKt(Iwxt-=uKDJzOUM=WHR#kXNc~N?F~Zm8%YDWzoL4hbr88rkqt8;spN`@A zYUCVBmn%^eun0LmuETS4w9ly9y$RbpfZ>o+_q?x~9=NpqcZ84tLE4?)E?~^UhkUxm zGr_ZjmT#Etrz*M7^>Pxy-o#hr}7$avMH z^q=Mynzib`(U1yg1($OpX(T9^TR~4r+plo3;Z*GL9I&d0A%CEvw{)4!U!&cA)cBfY z;ZYWRiQ=9?h|FSfSj&lWM79FP26shz;`!OGk*1I`>RSSR`?4-$NlBWVdh7#Km~XZ= zc({ZospFQAY}a4l4-ITB97w#U^f8GHw3E!hZLb6nB&}Y3kXWGXXUjgPhrjqC$wy6u z&$)9s?=F_q4W;oX;aSPKL5h%c?2^C zg!ySUYkDPnI$4}wS1;M3s?D=h z)-AmepH3~rx9kdY&7*aUlOO+pugW1`X$l@-Fh6vDZJa|r#LoN_en=g!7s(03Zy+u5 zs%dtrKRbh=gAv(ENae{9f6*%h|1;#~Tl^!Li-@b9!?R1bx`C<#3b@+P!R_jQG8D z$^$6+llL9x#t#e(97NY@E~z-kVZsmtH^j5&2B@`j*;l*pYD24hJ1&%Q&=7|)vmHy- zvYK=um#F!jsp=5I+MKDl6DrdW)19bP#;)2SC9A^VhN~sf&+F%L0wf4gFXOGckVxdT z5_J;9Jo}B(S(CG}=vvkgo^Vf9yn$mpsgkO%d;EEVRSaB$hXp3Up3%39n#7<{8ntZD zYE+TZJWmJWw*276=qr;JvHhIwit0{<*OT}Poi{IsKN)jDN@*R6ka^iae1^9W^GjL? zZx1@6v%!z`t)LXGw2GI3dx_oW=Uq?c_0%3Nz?+r|>$AkD_Y?o1m8gIUk#Gn-Whkbg zEyi?--o*>uM<+k$xy!?H8K;~p+27{O^!`w2blMEqq^3c|dTAj+!>hux`b;GMEAGil znbNX%$j7sh&j@DR5thni`=|5WZwJr|##wvQ6_et0r5ao9CpK73BOG5PYasX_cUt+Y zd?x9to9xNuVp3A+34BAKXnr$@Nsdye&A@Mx*Y8A|-Y}4%Kl$uN&0Q-+tp7_i_cR;x zdu>=6pSNMb>G%`1ioIuZn(Q_ojP+Hiw%eN46Xo&XpwsPzZ7fo}pGCHal6o;T_Un*m zV+Wg>9yCiosvwj$A#uG<1y74l`-D2Uh#s57r<$Q7Aj-;X(xM;9wBqJke1pXq|AFcG z%hx;y#UulkufD+@mQA|UU>T0>1?QVzO+{tZd98>Po9g^*_A-r!Y-N**Vm%$v3Dz~n zKahAG9L^)h3vIaj*$nG*{8Rgd3{g_UEat z^s;^Z30#H`>~P-lhI$@`bIxwO{!|`!(XCY^^6-=6;DXP4UxqqN1&9o@8(GlMAdoB} zceTtRnX@6PS-);Gb|JzsxFQx66^j^L4y-P2Ha5}?rWCFszB)>i0~e&?fiJl+sybGI z4cp&$7Se{y2}uiWN7#6Bx~oiQ2=PP_-APo6Rd$bwY&>uFxJ{BposT6#qPxYJGw!qd zVE}%uUQ>N@#>=d%{z`F;a88QIot4+!r(y>~k2B16UnMp&j=gGp}2L& zIjP6vv`X5J*NDodPrZy^>?5j$YR$eSqAGr2^FounhKh>8GGO3kQH&V;lOHPdz0p?N z*ZsMlpmA9b7W~9f)4{O0$y6*FpzHRif~%p*y~zs7U3fcJFVWM8Go=H4u|%C((nWobrTQP08*-7;ZxG2?31 zk{KhF45=;EzEVQ$$5*tyu~Rt=s#i97q26?868LmJV^{BygkCUc$1lyaTpvEUOyb^9 zi&q#fS4o&TKu+J~HtZm7M@lDg(9O0-^heH!veT;mR6^|#(b>&UP!~N@4$1LKZ7!cF zoimY>j{~KNGp>sASQc1yHtyD;P(6$Kx-K{iG2^*K@(qM$c8#bx-LZXZt~$8}9r1+C%Me82?^9#*01PW{2fF z;{`$!h!0q$+u5IdyS=h(^Y-pQ*S;0wKs zuRxr5>yW8|PU=R-XhBu#AgOBbRd|?|Y}mZCWU@hX^|(dGgsy(a2K9Y)lm|wvxF-2q z%^VB#o=BgW1B+J!aUz!*mTK!MJ;TY}0x~nE=NcamH(2rMeD!pc8cp3`XROhel>Og& zZp|%phg$udiL1opG&If_bkp2F5+H3pO^##{Gyk9zQaeDkJRE1rGaGhN!QDb1*E}0N zAAtr(@d8UQ_+fc;e3Ifm1XDyRaT%uLE!Vy8Ot5H5* zBSbC&p3Ax!*yX!xRenhz#1OR6 z=|EUM<^B%r%O#c~>OQ^1j!BKtFk~f2$HbUKmis!y%EsVm*WQ~u_!XH(KkM<}TO|x4 z*X-6y^pwxOEe@M|YM;vCGpH#D$&ZC!%C*zi>^Gay_cSm6d{Gu-F|4$=ZJ?A6sm*B} zZWI_Y6ruEqk&3|r7FXt)z3&zHRB^@}C+!jfcCX%EO*KoOASQSC`>Ai!vlO-`t-@}l z`W3-sWuL5K-prudh2@FS2!2<64Os$9&T5JG&5CD(n#}@&U&hRd5W#BCrqsc=z!_0D zUF*q2E6eL^H@dtSrnkg3PZN+U3UMuAg^`!D`7VwprmqK}imfbGi(%d#*sXI{Wm+l) zrW|{PPS_`H!{zR1hWosz4YonT681NocKdKk8@>Scbx)r;JLyOEFq52Fh@DHr-m-5E zo)>zZa#QCOnC~kJxWFQ%0OYy}t45wNNmAcUla{2(s@e6l@TVSGJ1~(WiB*N0E2>5? zDtPreby)Q!)HNR`p>aQXC;3n+oo>^fDgEHAJEj>zZfPiNR!zK+)PY{{noHv|EsvFD zxfqDMsrWXqqpsx|zrYJAt#V4mY)>X!<#?s#SV!aL7~RoUdUnxfhlj9#W2&|A*aQ|u z_B+C}e#ZzjWlZr6AmXY%GU$`jt+E<*7cI(S=&EnGF?Gm#tWu6Owp&m)0-296FCMzY z)j)x0={k&3TJR=b_=N|BG=3d;Z!zY(`(~a8nvF5gGyOI7C@G6o=s$-|TT&_q_Y_6h zcE-hIDQvwphMt~xV^ZWx?vxXPPxKns%JhSfew+#Cxi2@~d~t=d96xwLnOHxy`_jMw zQTpfjZ1=wN`~}P)wdt#Gfk`tYBN`GFl_g{GsmK|W(Pb2K2kAo927Vac-WYi)%KS0( zxm<`{T{*3jY9FbcyMsyHYZT=q;Y4?AbH^|(P~gZGh{YmJ(w?o7;{;>wZ@@+7;Ag#r z&w?dmmgt(*Hx)(+L{mXl+j2V2HQz0ypU+b~c_jNy{-moo>IjbenTxIijspM=~B6&ExKb8Icer!PLS6nr~D>sn&ef8hfc*77J+S@w+U6~ zAdg=-W32$jYVv3h{;{>bfxP21O}>)(f$3I>oHBuSTfiJDLYqI9Ix=35AO%>s3`1Z5 zmi}9)+i_;lUoch`&mN?vvf)@F9*;Ctly;2M-9-Gkwx|ggq?nhEA97nJ18Fb z8R{x*D>Jq9v()MQO8;c|XiqT9pTKX4=Fk{yZHGu^k=A*V{ler^Xq$DkfmXvc3H4X& zkEH7_z%>tXHvMgr?7rN-XeoCLGbi)-z~4fq(8U~u_QeV=qbHx}v=k|(uIu2*+tM=w zSI1?aj`L)x}>AE3&BAIS~gHa1nM3GW#f4_H8sUse%%h zHB30l3k~J!yO_`S&~^$wdC@T;cPa?bMi!SybMJX=SB1|#N;2b}>G#ppmNS=n)VW9I zOw_lCRugI>!gCZ=L21UCGm;jOUF!cc`y1i7Zsp_`{4a-LUyzwRde#ZHk*sUx7$q)D3O~A6U;7JzuV!p`Ccz@yhcE%QWe-Bp+5aXEl2Q_@ zPz5NeS{1Co>$?y)oQo8(UY;%G^ZBm$FGQme8@&y}pa(B#=YxBciou8Ou{>@C1@m)Z znx>DB7rv7bpznoaA9W46Q3t>RA1 z@q_{~{Z#HVF1_f-PCr*y0i47WORIS#v3+X#{ze3%SMgfQ^mX#ACUXu2#j+QnS2cNX z_}53gKWfIy)VUnn)QX;zvul0Q=}zM{+8V8otG%$JJMmMEzSI|j zcI^S=%D2x^^zGQ7QXmV)-JakI(Br!kyxre;ZkT|9nC_g=cnU`d+gT;P$JR~Xhvbf3 z%ZfJq6zigLk7%l>+uCIEd4|w?dHE8YWDQ+g{2~J8;%@P(wyU6SV@-gsY@A$aPDwkB z{)@RBbt}x6gT9!nNhqtm>B^HulX5n;>cJ)xN*0>W>eq=?-T2jH)uKjtu_5GX(WUxl zCR_c?FkRwCUu^<-0wAZrT2V}nljB>11_QjgD;Xe158G5>KIa}8b*w)jh7mEN%3^Ur zHrq}WtxPz*ZRw8VPo6z_AA;8$x$K$!7!fPn&BwNe?YWI!B-kTAH>F@6haaEcm=?Rm zY*|5UiO5vCMi_|6*LaW|piM};P8Cq!ec`_7rP=I}@iOv-<7dCt$Zzf68l8l=SkN@M z-p5wt;yt&g?B-7^gfPW0fw16jgVJCM#;HA)it>Ke$p5Z6-C1=yFFhK8CW87|g{9!A z;%5hu8QP9`t;0UX%XV!WoC&b_Wb>YQQ|ieVRyEj^(964Zy0}3*uLk%ju@fbF;J*-i z1oBYg964^ewhS0L&wXdvvFmw$=EgCV@;t5~MbTor=s?H!EDmDhU45pPilu4Qlh}B3 z?@)5st*vOJs^dJ8A?I9*a}!eOQT5$S7X3=RgPOyg%pkYfb>lPY%Y~B($>ipNdXX~knetdMQ7`aw?bdpJ9+pw_JlL~5rDug}FlB4LhlWYyC}?OA z5Nfr~kY|QwXNcQBt(@slU4(=<+pW`@-qDU(I}%uSYvs_OVa3 zN?s0PYRd8_+k&$^?U%P$eG7EBMFdQ0BaXt$I`a+Gu}--dQFFx^f?#)@8K!r+!9_35)gY`%U~(92g2jaIf(Oxn^5wl!DxLv<4_iFTc7wgG)n#1%7C$Az!^PUCuN{c zWnQ#Cs<#R0N7#b*-tx8UwZr=M<|`=7O^I9a!M7*Ew|(a?Z%sc;hF5@PM$}+}DP3s7 zfhk^?fPww2TJQ%0yPNfz8Wch}ESqE@V=4oDZbjb!*r7UmkX zo7<-Qd22NaDL#^Ox7|LEV)GTAN1Sn;&^&g6r1P}HLYMz$>N9kvCnauyLiN)O<4j-a z;kne3R=o?hm$f`r8LAJrFA?%MnV;O(Z61nq{7pb;F2?}`(L>xmfp;ekKn{&xifPwD z7`~nJPu0tx=_W=B&NZ$kq^5Q=dR3uuoR|C9e=(Nnd~>IgHiuVKqH-=DTqg6HHlQV|t@8(eUw z*ZMSw<1MgYQX^l|fz!;*Y`%vakkBe-DQBxOXE41xL~pimSk6m`GKc>L?{d%YdtPT$ z-SI_k;?d!HV$0FzG*X)%r4MZLrYDD1XImVH7uLZpplxo?)MZWi}39gRlT(x8zIvg zL#iZ{OV&0Yxp5~w4t_WZw5JS6_7yr&q0;)-#T8nysxSL#yqHiGQZ$cVHoxv*)v^hs z74~#4G9_a`HDF7&SP(WcMK5XE344d*>d?_kPg<(_eb* zhcofQ$Drz7)&UeU^YFp=Lp_0>kb4tl_*caTxHcH($rLf==5^bLt0 zX0Jz6N+=-(mbNjeEKg<4Iin0>z9ZnJM8&X@>C&Ed5oedMRUBC*5Ev>`A;DsyyG)mc zI(ZOu*rP$Klsy{M?5XPfm|~j2F+nQD-)c z{E&ougM`6#_g#yX#Ek|{cDm&5M8dqE#X=?1rIB5;Cv8eEYkSsvQ}YJH^zPu5(!g;p zWH@-0!I?3`_AiG={y#nqcYHPMF~aarFBR^wqzS(<7}8>$TS06oi|2-C4)#S^dn@d1 z#VzvG@AHi5)5_sJ_n7U>uA!ezk+qXO+OR7Eudk2EInU$`%qv!Bu9XYEBvcCZugHCv z4cQK*|I9m=HD)Q{$zf6uoQs*hK}wclk|S)dawYZCEUKx*%%$+G^lE2hc3H#(f#AAl_&CdfWk9el0cg(;S`7;e+Xr>R86y^uC8sv4 z=KBqjMKn2cd?lXP3yG`OfBE@2^UG!q^R9R zRLpS+0)ESHGr&r8FDnK`NIW$&O%3Dm*7Gp5j{27$`olBg>3!Zn_==nwxj4j?L$n%7 zO16DGiF=Q?&VHnio;fdwu0mRHu?ivK9Fj22qo*Qrpf#M$zEZzrASz=OQDj5g$Cuzx z#6c~a{7A`c&=#DEQ2a~Z@?kR_gD_yVh9)e#p7Sc26Hj-##^!Wa-(7L_(kyc0&F(8J zj*z1jns<@Y9qEP1@0E$xGPfaXMJFZHL#v$f1~xafP$eLd-cpac{1U+NEG`JQ*H^oT#1LZrQ3 z;Bjr(i68d}yJ%#;0X zq`~!mw4J8(3A)8Xt^371nTZwO#g){x`gr55&tg{dik~D>J6NC5#co*`4%978yt7Eq zE^9Lt>e}5=s)?-YB;%uJll@QzvCPx*ZLzcWCHpfJ>1=o3Mhy>kLzAf=PT@9OW0a<| z+5@@G(kGLXL?+b{g}MP^32G@N*$W)+W1Qa`MvB%jKAqG3X7_0QY(I^^0l#?3O=IO& zE2IPQy|n3yq9TSdN-#*B88m>~K!e zpE$vPGK{G$@a4&OS>q8Bk&UXqHdbU3N!2gg8LT*BN1AL9b|GRW(|~C)dS>JJlna;c zNr3pMuT>YTKyo!>Lbth?VWlj;aV?yXKZ@i;2g8Z4Iu`27)XKrZM@aOg4d8tmmCK@E zvjdG5N?Z)%N?)RHzl_`2E4^CU=AMoWh#%5*&+sGUdVLi@z?}G$AWZoYc64!V92j+T zwwDb<&imvrhsZ~??*)uF4nC-e)Gmd;MKus7)8E_NQZ*#NvZp)1TCt~N*eLczzTv$H z(c@cxAJxDre3bur@{^aBMCi6`5LbZyX$*{5=#I(~rQ(g_h9P;Q!RQYryb}{ihhz&@ zL>dj_^I5@l1%)dMFpWezgPafE;Ouo2+0e0+A|{qhAs;6trFp9TlDc(nuJPn;&Bjmc zG3Eu@y+JVPfif`$nVWW=8@BPr!I`4cx}i8aSgw)!+&D|I zP}RpVwGQ|jTjNA|4GYJHq+x1tt3X`}>1DPNKt45T8$H;~b*Lg{j5 zvOkX@%)$~FeFRm4CM8e9bvo<5K$23KW?G{^>uhm3u6Z6-t9A&{;!bRH-xzwJdiUQE zXACfaf&%+5FDNu2V0=*#+f@*UalNg^5zCCZ-M?_d!BMvks_zhPuk(AM@!i?ysg`h5 zdVI}}86%cai+RK+wF^fyrp1g2+j3aCw8N|v-pxzGoBO1DpsI+G?XTsql03f+7ztn- z3mAh(z-6AN@7TE*-Foh1?m9FiAU%PFT2N0`^b_;94?zytN$ufq>f!2^Llt# zC`U7^I4KYnNrTq0h`w1-2{)UJmR$8k!)=-k|6K8`g6C z0t4@{Xme@fNG1ZKXydLt6B_bjRbU1P#Zu*28T~%U>n>C~n@BCaPtl5CIYOm=0bzr^ zX%R%Sd3ubnfv}SXu>h@Q5MH3%B(lC*voP9{9Y5emvPALv5T%k}RjO(w5wXgVBj86> zL4LZk6fDn>?OW;X$fD!pn2+idN6L{OW)xgAK z;x$bW93=0n+6Qj$S^w&nz~`4<0U#x*s!{~SLS_P3NvZ*o5>O~)xVuouxj-mn8p+XV z15hYrVjfXRNPqmoUHme{GGqj0)vaH?OHQ;nUUL<~G7r_csiRg(|O+ z`iVVuD9oMo7)57(tyG1KDL`9eHZ$dPunk?&ZKxDwtx0Mn!vA^S^2LXP#W!gL9sy&s zhlJk&#Uf$qxW5d!EmdqwZv7$nEaYs~{HCrg_brbrM7k;2SHs6l&xTG_wwE7M4c*fF z;cCyVUq(r3MAGCEVq5Tk#-e^lk>$m8e+K@wqA)K9z!NM5HSNqN# zGkHeT?b_6Hb`G5soN!OnQbvH#@IiAJ>)(qrz|vuCp7WR0m4eBQ-w;#LYs+_lE(k)b zrq#@#+$?j;0%}D%txW{!iAdCQ@+Zd#uRoawjbsos&h{+0KPEp$CuJ;5+I}Sj1$A(a z!xApGgrM`&yAgrKUojuuZzVP4Ev6iV%&;W#mCAa>(n-?$$Ei_Sr3#3=^*H=U_*Cd) zC2_I{>cQfhokd$}#6BZgU#3|Ho?M(kA5UwqvZsTuCpjxx%7qa6vCHUklE4sVRkb=4 zFeKKE@27h%^&_P`-PL1-pCx}vK=ukdZ;!(Ei()1#XRy}rbQ>`t(@P=5ON{~D%CTub zR6GTi(qYbHoFdvr0WRl<)y=m44{XHb1E)whCCXdC^0>T@Lb4p4NWo-G(5E5XA zp9|JL!*A{q7k|7(T0+uXo*Rc72>F?N!Y;Nt)91S}7M_If<#fEw3eV`_k=5ep8T=H{ zUj9t_fjW5eCa*cz{>O0yDVJRiWv*W88B4f0#qO2Y0jYY=-$YY2M}}<|QW1R;|6zPB zt3idsx%p;b@8p(Zz)*t9jwics5vsr-eqwA^s9SBX_jb`%>O475kE**Q03VG;5decnLoA_d${`tM3O$Rka)M19q z+8qP)NWi&l_pq9K?kPcJ>r5_=YQhq!mP0)c4bK`v?^(xmW7O!`;XVP^nJ44Gj?(C5 zlcSxz;uKM4?25nI(_5S$`jyxSYhCqp-g9{<@77jVFB}}-Ubk-3^>T@n-8a!ZaJ$OJ z_}5AnKpq}bvB55!0Sm^4#K0r=xIWVQTUO}kAFYfdLCXhiezabEg}`n#v&jqXCc7Yy zLU?}(ZAKFf&8NZ%3Cp$R5d|lF_0AE7T*>Oq6TQ{#-RoqP^=kH9_EO1nZaEtP+LZ%4 zf7u3+8a7qMeIXSt-*F^yr?HJgL-l13W`jb!YSTf`rl#$Tp`GImN#0NqjvN6PN=9s{vI`-m@F z$3}VH0nW!Z=8HfCsMq*2wkyw7r!cq5>W&AD)DbPe;Nc=u@>;;k}S`WbxN>qG=HMBbRBGFlh7-nqADPQo~+atAjn$G({6b&w6=Z{`&66R1{^1c%6cs7uIuNw{xnlOP|{0`KlIgqSs*iTMBo& zq+|GOm-gdc9_F#i0?3$ghI*{0wAMp+kBtd+%YPi4%8fTojUt3c!(@upWn zJI*1bc!{I$n5fyi>mndaPck)ICu6|1K*!<3jg`q)5t+DnQcK=+OHoY7XYb=Crg~CY zt)n5uT@IdJF08kJntjJMsM+^5E~BX?TRhXD2%9IrA0hwhab?KsoNW>OrXrEp!ne2G z`dpL$-dCO*9b&1qefR|Yktzz&^5W~CI>sYs-~Sq}0YJlqgTDe%pk2Y71z(k{#_m6k zwO`%q?y_XYQjesL2_jG>q4ClM`PHe8)XoR_R(D7x=@T;-+~$1lDz zvraMNum3?So=46*|GRZ5=5Gf(F_ydZ69tC2?zOu)9PQ)eqHree4-MiU1AW^N!X^mm z3Z_~&<%)hC97qMX;4k5q6EHnX*GB#1zPP^++m-7YD$~Z1sV5oeJAKBZt&8*m|IMc2 zK1J=o~lu(;!e)1?wOw*wvk8P+`^z*ReOmJn~eE?dsDPHV>OUDb40v1xTISm9NIn zb5Wn|#6^o1@KKsBYuZ{ZC~VEEVKeZKkqM?XZvPw{X70yUYx=x>qA9RVf9-We^(Nlq zl}4Z`1-99ob4i9-qiMWq@}lo&Oyj2g%;WtCGV%}RRi~C2=E{x@QdvYXEsYi-*F(ip zK~kG0Bgd92MjTz8U3vi!b4ZuN6l?>WG}3muW^=}-CKsGE@NBV7eAyI2jlxmNKZlXa zyKoe+KB>EwZjJiT3nq+~EBA6%dXRlIml^n>ZNAz}Uium@s7vi;VO!}|$aZNeedpo~ z{cQ5=_)jhAv6cd^-y&wE)a6Uo1Q1d^ajku^d>rK+a=nr8aqNkw& z;)0l@NPu{^(&$C}(!Vj_trMBO>P!EFidV~*lA<~uLP-G$A%}Pkn4xMPqU6yR7ris5m7!0ZwF)BLwIe>zbU_4iBYJF+JZ+aq3oN5-Hzi+b zWLnppLBdXh@IHka{PF5o<0xeM2MuBcV%cT~jj)2JvU0;~g|U&~!vG$R88TzoZ}=N+X&8W)f-jqknsR)wB`sB>3{ z>b`IL&CykQv$1B1*iVMc{lF+qLRF$uShM@h?3_@~;MJNa0L6&SodAeQdM z<}6_^oDr~{)9(H$dy#tP@_cYJ9xmpv2B2~*x&MBzenZ>*r1HR=mHXtiU{t@k$;GI6vZ3`8bMxvz9D1IZhTsz#Ukr|n!X5cB#8 z(42|sbf*^JhH18IgM&M;T2SkDs{s!mOr@7U2)S^>-94Bq>b@-m_&x0v$7z?DP#2R@|0RTq}sE`jHQetNMN+=jrM1*dBo|Y zfr4eB@tv5?sz-;J?0Ej-G36jd%SR8IxNhvvCk`i>kFi*q8L@LyN7FO1b zXjH`|tINDZQDBny7v1D?ebwrHFh}zmwMSu% zEA6kbyfkW=GNC2Z50(P5sJLfbI5ih|FqW>?qdMo}4C5PUSwD2i*=3NfiBa8HrshZb zMd#E#(M560lY^|+TvJ=@6hZZkpVK@Q4@%2xmsASk`S?Af_c8cgUt`SrE4==A1-(og z#7LEbey`tJ`eI5{UAXs8sC+z+G}iF^?gb1ix5R)wXLf+-7nZvW@b~L0fZPp zARz71snCxA6C)UgHjy8r0-EYJE4qhG7Wytiec|V@j3F!V87o_4p=Mz%tMT!V^* z9mZGlqnB|`?GIl#nGGx?6`SK`MzCqXa*}ka9>SJ)T`7pRb-vp&D&M=)L}=>Ox(`%1 zd>1j2H5V5^7AtTR6fYY{>?{_|6GE5VYttNb^$gZ|)xyR`42q?^aw6MWgXtg`;>A%D zU2a8C5uuOD`DH2 z^pi!~MRCjOSLOb!=SIX;CeA;K^7|AtKS3+l^i6S6d2LK$KaH{D%*3j7vSBajTl(np ztMq_Lx1wL3@Mq??gg?8pFm6fTN*nD#*IK_fHAaAYk?c60m2$ChQUPUj*EGIL3XSO< zR{s<$eP#Vb21@phq{bvR%%8r&^wV)pT#-q>*PMEsVA8wA5mwBac;!STk928G)ANz% z?65na%c!9V+j-oW%hOL@wW=m|$<`i=RJS_M>up!_^0gIui&HtZv{=34k9R|7>0g%U zigM?fHU;&tH82C#-N}rfIt5aabwp#i?r6u~c3gb5hW;nRmRpvWaZS9+{p3PhYb+?)< za@}bj*2H2F7Od8(oMsK2AX@DuhZ~|y%#cc$JaGa=3u6Th(BEVrTmc6`Am{pQSjIH zD(dB2j!&@r=Dq#RKaAA#oxZliihd(LOYq>XFh0M(##cn~LyN_e$kFZyBL3_yNT@;Ypafnwc-~ zaQmhf?CwZ?dNqg4UmkLH?TKBb-eENTavaV*JlGW^W?OU>;yg|Btu`tcBiw{;MXz$k zE~nGPwyXTPet78_l$hckr_^6{C+D?eiXqg%fWpwAJ!&&?nCyL+x_KgFGSwMATsV`S zMP?x+?$z-v=Puj_vlQ~`PtEO-mQ4Wo?q4tj+)@-ZZO}BuX zbJ0Br8aw5J5g2tocn}v9%o}Y<9I>wmNhtRtCW zl~brI;>|ALR?Us)oN3>pOvo)n2qq?f`~JEvaKkKXR5#Ex%kgy8E0v*Rb(vB7##R+t zQeKMzp@I;PGrdul{xo1)?FSAlwLs*=o2c-5EE>NUr8YVe^_)QLrEkYs7%E?0NZIbz z9(oU~28%`_lZ^&E*;{)8ht}>K=ACIHGLlIQjV80!!jN8>N>NXhW*R$3)IHiF-|S~% z!wg3VS=fwC>A!m=77SNn{9Qh)&_e9(%iXAYR`i78N!T&Q@4I8X8U0Y1O)IjYH&`?7 z#>6Ec*^SRBym);9&o7K(txyt^Qf6m(2t}+=Y|j@jQjl7bPJK+Buryw;tVByMG-yj4 zdS7<8+QJ_bFcVHhBUuE*9Io}?tO?(J!ZD9hlsJ>7K&4QwBb>hQ{=$c&ZicynCx&lKAU#4UzI^~28 zP4KfoOO=dhnii+nH%V%kF8>-@Rtv0=Z1XH8bOZpT2Z^hG-mn(u_Gh4-MKTxBhOsEDg^B?I(tw_Dd}_M zQm|`{iWdm<+zc{jlCdD!w3F;@cwl*T`F$qGlEjU*Fa-DL4^f2aHPg6m#{nF1~NDaI|e4vIgaOWa2gtb0Cj zFhDU0T}W(`aRPO(_`l!dHxiO?J{(1rOvkiXb6&x$i076JA(G%s-LX>vlu5www#2r-J zc19jTLJ*E{9O8VyI7Gwr%|7K8~BN>LSiZmtdX`Z>Y-DNAF`XE+%Gr@*yBFYi@{r|7ZPr09_ITIRQC*p zh`F$gZvvv7N>=xU7Z+t{wUC8AmSlA}Lx}Zp(t8oBif%Fp)On=DCNp~ycvcAd-9%V$ zHy}9`4n%K*#42h`)fwTFhN2`xLxp7oMIob5B7q#C0$-d)NkVlj;l0o9y!iU9G*JgCWH>%?#wf6- z+Mz=x<gD8dt|gnpH8CkR z=O^P|feyvCr5yCEfm7ilZ5?x zo!3V(c+|+F>;2hxvw`(pYAjlxlF^diCa6vQav}VeED<={QEK% z0zwdRG@=)ok|7dR87mXn6IwkMXmfs|Pp_f{DGS@#e&S7hik;2M*4<6XmV;di+PIDW zJYrQ}b{D*!`ZjoV&BGT~MMgf&Kr`P1OYSqZbYYPuo^jNn>(zBugj=n&w_?UqttUAP zKHG)Vk_3nDD3-i(iXppid2$uyha+a@)ijR2`jt~1MUYApE03lQ3TLL&<$Dz-J~=y8 ztJh`j)Vmc4q=T18O5f_pcg0dG(Wvmr;GAjMdMO;ROf;_0bHN^~hH&r@&t z#jA6(o3!q8?>nK^bRXr#)674#&<7&GM>L=R2Hqy`01No+129(0IA8CDV&zQP!3$j3 ztc=Mu;ue|j;1GcMcrX-#DP#x!2R4|%C6O=Los~w%re>coDp=kT5(SB|Qod;yp1ud0sUtT{#s2YCcY|8TD11)N{x9Y zYyp^jhk;~YLr9vn2INSjVVsca=9Uvmu)g!C+HVkbP;MXCO4kdr`6sms z(xmfbUG5D>&#m?%?p!vQR-OG6#6(RC`;nN;^|q~SD3t`>VD!GMxVPhNtxm&OT4~Ol zEcrk(t!3)iv*Yap_8a6=`$aCoH?9Znfki07@FhC0K9r0g($^?Sk|70Zl54BcLWO^R z9^2_#=0flTE%x*Nw(Wk?7j4FX7u^WIoWd)rmBZFz6Q?vnKLz!A*q)z~Mqr#sZrOuy zuKYCkS^RD>QZ4W8MR`|j?|H>NO1hciEGltDs$kNEf`C1Zk z!yo%;&Q)BXol5uwWSIGQ#P$r+vy_)#$r(@rOeo+{?+H*MBHO+5SIk3$J5;Qw(fUrh zX7smkaLl{A18-vZmDPKmX?}#^-ep-03~@dyqML+mWgV@EafMLENM%zKq$B9UkhbWa zj-9w2{D#~SO5#%;iO(4}WNU+l5QE;2!=al9hVRuKf{N19g`O{bSmTp|;cxYMl*w(_KBs+J zuOT{roBQdO`#bY+s;0)!k0-NesZ z=ABmVn|PW_jiM~TVyC~B_+v<@*>F$m$cyW=L`;4iQ;xL#t{+1)~}T4OQPpodw|v9f(du>zqCVUxVcs$PE7;JOq*H+ckQ3N;M#WTV*1^Zh5taaZL z%dG8Uxq49>#w-WFXo1YaQ@!x&RAF#bQ|+JvwmPJZ$o$MH&|;${*swG({0Zc%`UCoX zZtA6cL{ep}kH@Tt1iZqtvYqpdD);H!0&&u(tP+q1PAxrCmn5wkM8RHvD)mQNrg*u3 zH%tM9v2wxflryQ8h z9CK-N7658WbfZtl{FR|mVe=VIcldJY;PUz`$4^xdZx`Dn*lFIf@h*( zP1JZJ_6djjeIAB)^C8dH8R*U~^qEre6+y~kI*`$hZdmFkn+g=2 zvoN)Dm!6+&q0QGCLzBtWxB1__zlxiI*0+1H(&Q&x-8X30f5@iR#}sXto?Sr`_POLj zoaxhU?^JNrXGr#bpt%-!pN90I)qIVsC+AwWS1QZ=9n73!z3(zrV7yU?kmt2R#ji^- z+LvlP9i~Iyqjf4%WfpZD1r^J5P)SofIk>Xq;|bxRxVJ2xdSqF0s|WU3I=E_cDRGqx zcw88cSfyo^Rr+R5$8pL^6sL%lW2rIk%4D^xDTC%=4p)^g@TiOkCNDEov$++_W2MS4 ziGR9#_*RWL%*%lG>x+sEF1dgxWV)v(8wBdU)}!^t87=$nYx)y(xmzKb>5?2x#&xZ+ zg5_JSGwk?D)W=GPRW^fSN1s1@sO?OfO`H_I^B}~GOjmhp2~B+~!+mwRUou886pt8; zV3thFSqNXO_!)hl zd>hPXwQ*=^KspsOF}uivjj}}cnmVC`VWRJ=1CKNVqS9bkrh1NZescY|{gkWaWy9bs z1Qv#NIv!Jpv}qefnP}Bmb@S4~#O7A$aBy<`=CNu37&u@lQ05$w zUihlswYldOG`Kb2*T@l^&X%xU@zL^q^2-Pv+77f2!VszBhZX4WaCJ;0Za7|tEe#H) zun!BR`(aBaSnQBr&=(jYLF;VJOQnfVxv#vhh^h_h? z^v^Yw&o%LInsl{s@UxWzuj>h(!f41>=HaO&80n*tV=Kf@al#Z4R*nxC%4w`{v>Q*c z<0|@U<%jz+d%t>G#(rFvoZp(=AYD#0?cyXR@v<&iPUxMml9Hyii_@F0GSLf;ORs87 zGdHMboS;*6T*Acp@DEf&-S^<>8!$9V{WcVSa@LhFj|k zV>-1GxFpZz-ZAfaam!}*jWwZ@oUSE}d_-q4#(V=>T1|wI6Q8L;@kg)p&)&-3Ue_C| z^gZ|hn}|Po=RDAaKVq)I$@v?f6ku>FnP)=*hL9#-=pEMdC+O$~3J0ysCW7>4y(1qf z)hNj#ARVN>uw!!twnK|faG*l_=;>y~!ig}SiQ+U!N+P7v0r;O-2Jc1IIV8(xiEOp6c+2n#W1JipWXTgNleq`LVm39>!bA5T~-;Qx`C%s}a!;gov zr86^ZkW+1TJDOoA1VH(L2d_jFCtAxDJXS`qed~m%DhG_Ue@cGYiu2#30OBNDR5}_Q;Bx?D6-$1v#Bh$NQRi$`HB_ z|<;Fc<{z4HfaSE3eXZ>*!d6*{K_&P7>^uO2-)pZmV^ebiEQ>p4$TQqFz$WT<}j$IS$~^_uUvf-w#$ z2@K`gg*_W`R&PFOL=F>i@$?zQXy$}oTBY4q>8_JokFymn-jJ7^0~p(^0hFrbe=3oG zvD^Hwq7?amYp9AJ#KAERFb6HKL@`vddv3pc1QW{2s}Xt38sR@P;$|zzM!1k1hU=j) zNueXd<-OuX8ygv93B}?s|Fuo#?HJq~BVOvJB5zn`$7rq?AGIZzb{!L;^*#|X8Ty#*SWXzY$6tI%@2H#L0;Y)cL zoQQS;QccP$aU#)@rxZo^shv89%rg_U8sqW9?IF;-Po0VK|D@a_J*YUjegob#P#cU0 z01-5u;E>EoK(8v#1x8i5B)d(g7tO(!U2Y{6$wZNP-3-Fp@XiF75fGZaSJu@0(ua~( zfzG={B(w)NE|Bmgd0eb?1pPg=e@(#og@)Tsd3j*mWWJ#py0K|Ph>R@#P2k&vX1&er z5hX2c?DKtH`+%*jZME;OJGPUnMcMq1K4O2iKqfU>e{H;!gh_{7p*ZP(6!3y4zT$4~ zs9HONvM4q|TA6vEW^|co96CCMkzv?es;%n21y}XW-l^-4;I;g$7{_+p=v5MXO$N$w ziMA_E%tTX2{18eGQMjfB3K1zU1d$7D>^n9$6U0!C*^KaPMXYM5W|%bAno$zPF0zn1 zDn>TcTm|EBlI&bqEY+AV378?)_yKTG$R?vT2+W2!aqdNWBqgS5A;$b$!PDQ~mslA>zL{HzZ9RjU6oQo$MU`;)#&gdcZY0*{PNp`R0BsmQ607## zIY5FB5Nd5??g#=J4ETY-Lkau8bb%eQE~?VrJ*#0&(#J7Fu3@ zFbFwd!+jKe2p_m>0CXMyP9Fl;dLNE!S(-RmID+)pIXJ*CA~w+PG#KC`JOJ+y`0pVX z;4T6K+H-U9gTRpJ?Cb!m#{aDi_)4&(e}36HxB!MX4qo8Nz!2yFFA7*=b~ZKu6Ca4- z0XCe#?fJPt+}z*^%0tFq*Rrz%FC{RQ;KO0S{fM3I!3POA$oEiJ|J>rA|Lkmh;6)LB zEXLz^%ag0oSmB_y86cet-o9tkXj# z|3eu+5YGX^**O27P_KY_sQ#60>fiUlTvihQa*i~x4?HWd0rO%3l>=Wu}cD*f4|c|Y2Zm&&4I=XY`~H@{^7&|nhTHu z3*rC^;s6Wc_(u>2Sg(I1fd2y&2EYa^>LCS|#QBe;ha+G?|9ZjyY#+d9`s+k4unaD+ z44^vyJ_HuR1=fb^pEBYC(|vJ)wE^OUfepBnfS^ZU^G`_wtqMqig#m#?zy>S~XyAWI zjZTxlGffA>Lf z<^4tS06q(-M1U9eZ?QmS{zVM%kOMG#0iEHm8v>OJEZzN+0C>b6(5D|x14zCe2ooO? zd?1boEZBzxKrqJjKQsXN#U5b&9&!MkA1DnT5`fYVI9Lw}Ngx64dEf-l7&w4}|2qL_ z093%=2|xp&BK}SQ8a#CKhiibFJ_Kn$B!HU&mGJjzz)c?_P#ccMfny%H=NpUWKDdJv<&n8_BsCl0a+6;+C6F| z_CHuN|G}F1-vDdofp7LP_J-eqr8Ht>!S)}?3va*xgttDku|@X~z4z)`RObj{&a_Vq zW&%+Tbz5P1o7(c!ZwG+vg-E?@E!^Bjw8t`gtgr}IwSNSm! z0+Lq2h8D&z-20jv93n;H^(B~&d$5jODTL83+f1`|)Ddx~#eyYa1vJVRGU{Cz5*dz1 zPgQI`HfFU$FHnsos^?<0sp7$+L8hP3?LTo!2hlR&KjAFnt6}CyRQeN?_w8wtwoy61^E&)mxPz1xT7odUz1TXU+Z;5Q#cy-3K5XWshDTQ?F#ec|6oP;S#t z?=KH0k87NJn|I;by;wd|@!qGC6?ulG=F~VZk+eH+-c->#;+^9*;qGSrI@(msj|2_T zA0bMEZi?ozeB9O33v?&!FRTV6`hsE*O49+4+9>EBh98JJWy_eo7x&Q$MN40Nam;-6 zr#T4th53jY`kg~bV-O&X7Xz>an!mW=BTSHt-bhxJ-Pjq2=9*o!->9x&uu)@&B`{XN z5+siV#9nvNUB@)k&LsI|anJ6u9fTZ9qI(9AciW^8@1~>AZb4XTS~`izxBhRxkrV=kpiUjdsG@)Bb9gWFic?By)%V& zYb{QS4?h2pNHW0045*r!)R@eBe+fUp*qlpnoGV0QYYQF;R1s)L|$K4*HAE@gxACxBRzR=&!7hWugODM6f!*ggHnOey& z!p@&H7uFB@Kz{L^rm`Zw{%#=d%Qx9l%fVr*9^`A7$ekCl>FMhGs!tj>yf@|~pjk1p zXN@1f&IWbU?eMMDD|yhe=^5#KcUjMHmReb!bnScwSb zm*<9?0?7_fE2^zHZ&C%gWEDMZzvNI%ADhfp4Vg_Bx2lO~1sRvKIF)S$`h-Dmch{?O z%ojrGA<1nFc*?~n7@tE_7q>=4UrqCU=irr^bSPyqyO3Ma+ue%7tH`?9ToLu`G(Nhg z^qrKOGRXQV_}a()b{e!*vNKNa$Vb2Qni|3Bg<%BrMA~Od*|KkGYN&2}c8UE{3q;Mf zt&#WWxHI2N*{spk;%amDLvp`wRgr0k$AhH5P?&%D@g~8;#g1d}t3E!{>6=8F6vu`Q zoVAu9B?%>BN&cNc^YTs2nD5Ye%OVXuDDXdo`L{2`A))OM%X-tgO(4jQKO@Mz$krBy zu=RwgRLoe>fZVh}cL;?EzbxQ8$v+QoT+6>$T&^LPF^c3kt>u_DO=Su7BrNN7%QjS) zwwj;Y;5?ZQqzLB~{A0t^ZHQ!NU zAq~Pm-QGlQ^}lIN3>#qDV9!l)fG&NG|0hIjz&P+IrMNhL^Z8JGt{AfqdT_FN8M-7r zbfVbo5jOCv_PN!70*`n`X1it%9J{3oeo)LnC5N}H-p}*5wFV`dK{m_ z<BG$8C9=blO*Jmp8MX}?8!GqnK419mUf0zIfc&?rcm*!;;uK?2OQ^Ova2;_ zBlR(Bro^&J4F+X;baT2=`-vu6s$u3oS<35KevF5^cGY&G3|}{N%?+`2MoqTM8NzH^ z{`6OLj&(>TDa31QpU^nYK@)Lverw^5>nt_cET7i=dcBhtWuw5%LvkSb1vL50ykfN> zan=N8%>bYFB|S9Z7vv%YfpNuL^;2Gh4;jY$_zpPmvFpq9kkb>2k$umn9QdV$LjzA- zcK6z+oTX;7dtb;2N5Pa@GEq{@HO-Nj1W`pqQ2nS1o@IVj>zg`-;XDRO0wU1Dh`G?O z7vF-fdLsQ3x$I3rxNu`6D$$Yus*rfNI4PgJB)jw4L=s!Sr?^kr`NCbT&mO!qd6(}W zS|UGqKJ$o{$n&2F(*Hz|{%;*Y3K%#a)sp#mevifioDlwj7!{ZnKcEll3_7|mMH?C0 zm(PZk5s#5tgG7*o3}8Y)VD`m)J35|hi+#t!`ReWb!V`?Spgwhi#5-9`3ee}s_Jk1f zfD4K+k>x;ff>|=CkH^v1?I4alMo0zcu*A8z4}#DARzv1u&5NZ_yYh-q+{mQ@#QU%( z@I^|iP0bP6_D=HZhF;e;IU~WW7!aolv3m|KYI9h$dPLFNH9cnx(>YCqpx7!|zQM4f z(4f3@M*x^wh^pFR^YZyf3CanD zuR_uH4TjQF;j#I5xF4j7XS=fkg1HPQw8^Rr3)p_{B1=A5Svw(XN2Ds2qaWzMb)PL1 zXkaBwGhd^Q0ujU(yyIwPek{u##z2cYF-#LmiQ8Tg-H_#wV5F~`+)jvrW94U=kz4n+q-#omzb+9b$m3Nns^LOLX;b7JJvbFkKx^#rYGwDLM z(THXU!`z!vy~arK-c&$hR_doyhqx1j;qPTy1>Dta|B)VA!42*3p2Oq zv_=7`#EHR=*IqgCUv566_$^!ayOGQPy3gS{VoGh#L$TRI7OaT&|BU@?AjUpGZ5y(v z-h=#`&^(f;ew^kfOb;$8r}`1iwzmKm1(|E$jFH=+Mq7jNo=UFi+v=k>4iWMvTgwv7 z>ydkHF^PIqQb)E{$fXB}hH8}Y^TwzWjH20lh$_YeIC|VSj2SLHz0~(NLv5ek-`riC zpwIVyG5OPQ^GKr+H|KAiR7zbIZH*75aRba2x!3P$#kMJNc3+CxFTX%HH}RC6!T|~f z>7BSXVxX??A-Qm14qFW3oh^xUX``ojToYU;qhSEYe%mqkrI6GF#pLTKvr-_8txPRg zo!}Enx_LX>rN}oYa#-7hpRCU1Mnh5jmFS!)Gz3fYFtT)m#2hx-qXvXy+Xc1)idLI! zy#fl{uk*yd8pzKi$>q(%_)(a~7s5&up=D{?A;ZC6;Bdx|tyH}1$FJ;A5YDUP*ZzXc zVM+T$eReA$4=*;eXp-6L^N&L>T%7kPdp|LAi{JvB3-*N7o@UtkVWw;phz`$-bX;83 zFW>O>qCZLhbt)TdO+MhpO@c9V!QW4_5s5il3Olx=6sVc3HEl@wF@^J6YDm=W<={aY zgX5F@UD^0wy3)d1VP36EENFh|>7v8wPoRy0LAMKK7NtyQ=Ovt7`MIWHZnvUbgt<4v zooe>0up*HOtd|B+)_pst0iJ_ZXhPXEY-Y|km2kgR^}$9PU6cd- za*fz*U;*>J}&;J%er zsj%_Z{*C*_?!>I9fa6dft~dx9AYmI9qXAVlI(Pmna8$tXoLoflPIHG_N9L5j~ zqCanSkSOzLXbELtA-nhRudLH|O1bvjt5?fi6qH#bi9f}A2;fpX;p-cr8H@e-&;qo6 z=Na?eN9pj z^EfX4sZJkhdHGL-^?xF)|6ho(=6|Fy4OspC&6pO^hzfYrNP6bJgp^M~z1zi>qEGoX z_tod~>KhXH5fi4OT@Vc?7b5bD42zzjtRum)NJi6-qP+4nm-a#<%gR(SwXbt{Jtxqp zI>x$at6vumMZ-66xp3v;<{2#=F(h9c#E~^@LB9!LEdNsS^gE5#~;hw)=7bk~)y)zLRH^UN1N)Fw7OHc&biy}_RN6%TUw_wD3 zQpDIE`VliGCtTO5nN}g41cmr2_>-C+&jy6bjf!Y(%@$_0iVeC-A7)D=%%b{^*t9_S z5AptQ-WiBXL8i5oE*T?u+R3W~u}DSpuo-gFc_^ajE9#te5!PPNGH`WbpFH9B`h`W=29JAak!zOdHsa}mx# zzI9`V*n#wJ4cyBz2v6f(U>o6pHemM z(7DrgJ@jGTHEZz=8GhuPRR3sX^4v|GjpxmampbQ{RRnONk;F^*1A9Zdt2=hQ8*PE6 z6^+YwN4ru4-OzGok@0Xd3hif)_pxzn6?y`0SeimlcnZZv^Y;|#a z?G!Zd`n(238(+QPESFvri!-;dv3iQ)qYLcTeQolP91F=uf@r)=p@8 z;=``ow|`Jz&Rq7qn$Q2GR!ukG8T6F5F~dR^spmyu+Kb>;(){Qr?#1s)^~7Jh$X|-+ zm&@N0mfvNFw)#w^Z!B#9j7?8_?LPgf>pap7$N#$-?t?hsnFct}HN6rYfkCp_@(Dju zx%c6oC^)J@Mrn+Zhn7g9Ky(c%V8+edQE z6JBf{+##DeU^Z{85S{d)JR4PbKJYemGW{g;vS8`QFo=2{$_I{Oh_}<1LGZaG0-D;F z=aSSJSxU(Woch`NN)8wP0n}GcuK091{8=YV1Gwzza8qA{BkFg#WOPbNzAHefR$+V@ zLZ8HaU6lqgyeT(H=f}A>$30LNCldV0rNj;ndeYj(+NFD4K`$T$ZuOXgX6_>N9cBr| z<(trJm%)0EQHzQ1eJwXqlV{VKS{q`MR-S2nCfV=L*|sbBr&K)lFH}OcGTHqqiiob% zHCEo$F~>J}2n=heD!xE7eLJ#gdC^sp**LtH=j2HIGbnfOZhBk!)o{ilq0FX>vChE+ zH9?BaQpt)Qs<55xlA(99Ukwlag_(UbF~gPhGwt5EVXeWesv1^~+46&3Ne_*D0qQf8 zSJyIk>X!65_OALh**HEMC&Mqee1OtowGHen{Zs8e(gw%>+cJdWhjuUyJ@kEA2eP_c zhGmjB#;WT1eby(uqvLNCLkwzk>ca{Qs)Q>zWJs)YMYe&H-%vlf(mI-UHrgDHL^qn! za;8rgvKZE*7@wOnerH}X>U@Wdes4_r2OvctT;`DYcWvSRR!B%XKhKoh^MGnKxy;e-ine5HJ7t8|`QV<|~?%I}_6 zz+r7=`kw3+>!$yHPF>R0v$1YvE0X|pI(ek|5&F*2cTif-v&$ox`dM^zRIWo6NzoAH zW>)Hw?7iN-51<+vKmJ<9sc$o0*!`YfLjtq}AJsH@w0U ze2N0rrs|R@Zrr)XwteE-%?xraT=_i0iYKca?Q@0alK7cUbovrH%E}m(FkG8;5s6wi zJv4<7y!0LK)i5cG5IkTuLMhqC-Y#9=efbooA6fW)b?u^(()DKPu38is#t!OFHUG%z zFem@-9~{qROV-66R-k+lLSG3C-&}8Z@7&nwsRT?EVjv0flpH!iL7gLh{Rk;E=DQq} zZt%-HLCSBr5h?ssgY#t|#7SBFpaMka?&S7}UhMdbcf*|nC#aZj+6YFmaY}7aOgk$Z zphu|iL)BjB(A8Q)!R5+`O_jimNO^pJ9g3Tff?kOY8Oxs`)wY2cb8dw9D}{Pz^G3!j z8)amyK_O~F(@e@)Qm$Wue3pSGCr(i;y-6zc#>jDS&P43ORLQ!g)_?u;cLXIOlbpW9Z}yWDrZmsOxPHaT@N`7fhv?aMu1;H~NV!|3;1k@N~h zHN=@7oz$76ZW5+zksdG$UWWg)`&IITj|sK>C6U(szx&k6>vIsOO!@8k z!h4q34;|{>PvG!m^PIo7b3F@Qust1<5dACx(*BcrkDQ%y{U^Bh|LEXePQX3&QDzF> z-@^7Z!s~(Q4hpoVVmmBKRK%)zI>YLty9+dsws19cSVzXqE8uR_B9tvLdK(Er50g9z{5pX7{ht)2n# z?=`Yo6kTd!YEOe<5CZKn&BSQ^Xgl#g)LsbEolK->=Ga?m$1&9g%WyL(<#)KIWnpw@ol_56pS#$ip7Q#e~AbqXz-0=pV+v?WR;m@P;(y zrHnA+popz@ZOly>uSii>reVlG zeAfZ70FN5Zxp{w|NCK>d)PJGmyoC2z6B1@W!V|t&Qu5U;b+!2RBzAyu9Rh~ZPu2{L z+9c!KVNlOge_2VS`yM2pkl<5Pft=a?msllk%$SIvuIf^z7jOsS1XZkoK*%bTq&4e- zy_wt+Ee34+Jnm9Jf=i)qy@S>h5Klv*{0<2uXBH>Sl^M*>9yWthg{EP%2|Gs3NES^u z)gk-mF*iJg9!3cri`a>zQL)Vb7kh6VR@Jk<4@-ARmr{~~z^1!Hx=Xq{rMr<(x zq`OnPLpnqWX%Pj4cd_~SIeHG~T>P%z`=0mv$M@QoY-g>tXU&>t*33LJ&wbyvDxXZr z#J1ry)w2rXB7;6AM~pT^)1mh?%*rbK7_U@a?%b>$wF$3?q}4y@TMc1z^__NsPvG`9SDp*0qjUUXKoD>XFv9xJxn@On1vb9JKB-gGJg z73ZZZhx^M+!7gw7`Eqdts!e_uN!Q^~P}t^9LoWQp;R1Y!M}Armg*Y}Vw=Xrq~& zN1`7fhZcO=<9o3J=L)`G;#j6#*76ALDfrZ$BV`NxB))>E_r|(KCKm)iQ0~+Q|Nq6L zf&g&Lor?LVho(Iw1rER)^^%Q7f(MzO9rBSeZ2zn^fD&!&M;c_Tio@#PZou$RB9KdQMwkCSwmc8(3qk^N+*&N14%->Ec6N6wtxA*I#e4d5?(y80t!4m4h&|kW3qwH+E~iXNJJV}2p;%wBHuE4|kS-!X z40vQ}s^cdV*3TT3B0eNID?}2TTHP0R>9CLx-5jp;Y|lM6ABg0K=%}O}{=nKB7#PxU z<}2YXoa#i>(2qsT69tk+Rw zk4;bIHAOM~oq8)rs%67~H(Dn6RhI<=hi)OOm6t!8N8ah4S6%JuoHffkB1Z9+v{_!_UC8#gBoy_hm(8jx)9b@%lZQc_3yq7dz_I6 zY}i;)br=wSs^Cr(GRlw!qH~<_daJQ=&Jz6!P4#jf79GjgKR^ol{_1`F$V3NHcOXp- zG42PxQwCv$af2}o+6pPnv&zeu%BcHTQc~nQ%xlCFBs;}&pyv9Bq2yDvm>#*r1g9&Z zJ}>W6+fYSmx@E&4mn|+R82=OVM*7o_1RO^EK1}BUONd( z+`6HYZc_J7!8rO!l$XX2G$@`vHu>JcaaNQ&u#?hBJr_Ek)+xP6cMcRr;eX}y&z(BFD;K4D1y;0cjV zOLWkEv_IVDHI}Rt-?Zi&PX9xw#Hhd6sJH8Y4p;H41Wo0fpEKi;C%5O9&y;B2*0diM z9ZzL_B2bXD%$KuHey&Ml{nPj3%=%d5e867uw$-*n-21-%laR9TyifXC2Hx{-#x_G& zI#&?ddD{cGG#fCs{SI4(AeKK~Xx2@fk^hjU)3MKVuq)Xa3li(*L1h(Mn0saWi zg+-)~?s$0`t3)}TIiYq2n%ksgLL@aMT}x45KVnwmbf4sp4$ly$JkhS;TKhR@0kv7~XC9zhPqnbD#SZA$ETXT!`{)UYz3n-$Z>G3pHonZmo?_&t(KI@I$bA`k zl~x*ycbkpw=&&*Wx$~zTJz&#Gf-ZXM+5I?HG*H(|628xws4tvPnd;$6+pK62Q-{%N z>$`jG@eTKeo^gP(xvOkmmZ+j`hNO#zpu*{NqSW3pj*TA_~;N>37X; zJ5eSYPJmhg;Cult0RI8gnE)&z*C&C?z{hSlMF46JFtNxrD+xdz0%QRY zpfIrjJR4v}5r7m0IL36HE&Day3HuFY2`j)50lqbGi0%3=0JX{g4e!Xmquco3<2U>t z|633LGwOj!@E5ZSli)SA3zOjQOfyVE*VHbz*@{UBOnxKui`eDYHB3UkC~JNlxyv_` z@HNv6lkg3L4wynm_&OgZVQ`s*!K^$Y*EunXfO&aDz{ETvU}hc>Fg1?|n43rBnsm}iMF|-4b&~?_4(^k0QrEw`UP8M`0c8OP zaXz`sDD-<^^1vx z39t+Z=#BrnJ76IYOfPSTbX8=a6Un$@jz`lm!@9Z!j<~yzfbNsP}Ci9{k0SGOCKF?gkWt4!F`o>3E z_v+L(00Uah=OR5t264)tzQJzr^?^c~c->)YmpyJq32iw#-|Rvh;dC=2(iCN-KN6j~ z>mh{S5&mtVFU-HK`~D?$!TMVX{-QDi0YfA`8^Bn^0kk4|7J$qLP`mj1AS*x!#032N zH(D1U;CFwoE&r0!_$`3quY0fnjgbDjeX)ayoq!wsedL<9>H5sSy}@tmlYiZST622x9qD(gWbF1*og4E|%zm&HZlGv~H+Wyfkv$g(j=dt zp$&qVt8|QUEHzGm(w0kK9uD#?B(4J2 zThhUou=nR?1*cwOi=k-c4tC#Vg_q&-Yf+2c-By{*-m~5%&?U!z zm`NDije%+2h$-gs6n-dOa#4qTf7MSs%CURU9f!KAM}#|LH==@ADqPAmBkf_Bx$&!& zOV)T&o;etlQaev->^&I*3u$TtV&6Fh`lX^DM4?9y*0n0z_@ASSRkRbx*1yRohQQ?% zo-8RlG-6CaMH%nlRmPziVF%e&$I} zN$DQ=Sqsp4yw#j#AtA+dgoG^YpsEKKscNH5KCywA!QwTQ_M2qOJT1{=hE)NNAPj}&zD8(`w!F<8D6WI6L`Or7iT3}JRI zn8SpZ!0Mh{H!IJ|pGNMF2;yJoi5zjm&phiIw4(e8NFEcT@pDV6| z2U5U$OR<%&Oti-JTKzNq^m@gjI57oE&=xEDBms4FGc2m4LL%T(fAlxkCYO>5B4T(&bRU2s!WGem&_QJlFLdDL!B`O}qcIcaP{6$* z$YG5{x;Oci0P41KUC$BjQm%iZ3c3c!lOi|G(0zsA8f9qCCqjMLbJfa4k$|7c^+u4Q zkOC>}K4oZ|00Kmp#B06pL2~P>MF%d1D(8$KauNb>ELc;|(ZT!Z`BTW<7t~E?vb)%3 zUlC05e4Uo(;bi^bHCDWM)(o}tsGeE$@kJO!_LZqJBpsa8F(UT|f5FX46P7R$hDEYq z=o^}DH;PuWtza`nLqJoTl98n%PU!d^%is8%HNfvn2;Fha8y9&^Tr9kgj=Iqc`eo*5 z)+moE5^yJZ$R_eE86SJ?n~WFj_Z{%P_1+fmTB5`pF(UB6%CY6NGx%za?Z$4s@>TOq z>8S|^##Zj@?HU{uwTlh3bJ z(|$HFomC;%X$~RW$?mYBqsF1(GOr13S@@J~$v-C{B55EP!#UTai`;scK+E@s;C}Yy zVnRx^x6ELbOJ4+YRmsfzXAC`|UJRVJXWf*(+gFpc5uB$(;wNxB(&6>K28DC4Oz#bG zSSmP+LLk!7@2IaAj|*9mBv6S;x95G`KTrCAF@TjuB>zK9fSP2)h9+@*=b2xc_}b5` zG?Mi8Mg+1~ihRtirm~?Qvrq-$yT;061WFE=%z|ED|2VE8$d!=K&#CWYTV)2i*oRO? zE&bsBm?7d5=4|XlSzq;+zG@c>IfQ`?r*vY-(r%2!Ud>)+k521f=)Z4@u+vQI9 z^y78CG#*XG8uETRNVY;_0jpn{u^eXGgAA$2>xupRd0a8`#5l(WUoD4e_@bPY(~{IN zUdtBzSeUC8#>@1`dS4?Lmrmx-v`@GObTF$#9?O$DY)u8OD%iYQSu({|;F-^NTaTr$ zVNLUdA=|z3e6CiV@mj055dT3*Ba)|;L#iV2+0P58=gC>OXu5*HJf3%WCt3d_o0n0# zk=Fr)t@L1kkR4VgrtrOUTPgvsMLJB8KnhY|4z&go3y&X4bPe7k5u$z4eoB&c5*pg| z4PCPJmGg^zmsqoQZZ_xdMW$zUjYwZ6px}SzkwFXPaVMVU>O` zx%Mi0ykEpL`D(59WMRau-5{tB5BsTQ=R1sYDT$;mZ^M9G=FFI`cI^RD$bGxFwk|s5 z(3pHQc!EmJuSVC(j2IH_Dz?DE+h(3RTbjAxKL9rHAO*_)^kv99`*U_|w^}45P_DgxpJm zUBdiIbkmo6QO)biqaT?eTSBUZsN2H$D}F}Td5F3zV2ijw$2k-r{)~uz8AwD1N%>9{ zX~{U1%${<>Ai8TguSnZInc5R&x#DeKk~f8U)38s&5$c?~6fF_<(i_uPs7f=CVLB`) zngZsfd5CPr9mR=^QgK2MO47sYY@C%H1lu;E0)g%=%|C0A4u_>5hzB_-lRL00@FTOX zXVOecTt;0PoSZ4#*607`@BGI}I@_PKK&5)?drZhXyfC;2f-g^o90gyIeP~6wSRmap z3rnExH;IPyiyuvvY>R^?^|aFxB=pZj5r!suks+H{L@WGubO6$2LVr^_FR`)Fz?FEM z;R`L?BE9t0#?eT!P^t2iKnY>jI`cwgn0dSo@y|7wWUE?)uMr&3c@kekI~JLVc^3{A znZKCF8ib^hXI9|5)D!sA)tdqr|8Mfu8lix`2jTlbOCVe~saRsWO5ce_qcT zeo`P_;qb+{_Uxdq3d%ho!g)%?X=cTDbFGtdR*N9U0H}AU7(LD)5!t7S$H$YK( zKnJ3)6+QJ^b9v6@%or`US&=FJ5j0ef8KlR@MEc#clx65>w|->Z$r)|>5tFEM0;~9Y z{pZogf|Cz~m)_aezWesU8^TyMbLKXC{pLIVM|*Mp;Zy-)AP#hZ+UYfzR|x>~CQlYW zPCK{N^<#0SrDJEb(Og77hJ=m@luSVkZ15#+G0Oo-BL-zgH(f{lNEV=PG0HGFxX8il zCrKx^^YYw>Kc&FoSXC+GlqvJxBOO2`<~}%Lz@juHm({~a5`jD=(Un#}m)A|YicsAR7rQS?=P$r>kIl`R zc4$Jj(Q60zfd<9d#z{Q1bh<}E1u{iT5f{wNfjlwziP$}Cd(IO0g37u~Fkfb!vs41< z+05f>)^V3v=fo$5?c4R>5UugIJgdq@ar+&DNMFzqvS{CjnLKQ&rP{%QTO=3qUK0y` z?Co(L)B}M%4qa^ka?5(^!|={HS@2}}Lrr(2!pd=_Wac4j8ls6?`|0;;G;MiHlIKJg za`dq^{FbMWseD$|$x?M-0UC+Pr?bTpo0U$)8Xv<-Mji>5IhNeGA$b-V`<2Q&!LZ3 zEw7@3(`!~#6lK!ua3F}8MM!*mvOb@+bPaEr3;96GEJVKq6 zZ>z*RE;F$H$zBTtoL4LWt3cNs7Q`Y#!`^dZGsi>7wW^5vdFG!tjK$|Pj_EiPGyS$*s`eQpY}Ze^ z{8J@*6^PO+5uTI}f2sE{H!o{}kD`qTQCDw^uqr2JzpzT$M3=2K*yif)z&0^)3Obly zFV&pFBb$Drgd4iakTq!&zPF;(KF1D`h{f9L=-B&a%mwkdg%l#R9vibQ$Z4qC(<)KO zc&GEgbJE?9pH!lTA9EAD&^bxAB&9m%igd)xh^VUM zuE|!3vP*-AMIRXvn2Q}7Y=+IXKb6^X=~Cp~hTUgufA>hFy~6UfHEMN{X^BIu-D$IG zYM}5+1HyZ?R8>9ZeR|{*tt4*Y5y>w$vo8Lu!>#N5uS(95Zfj|GT;2gTJb!;(WnM`| zbzKLvr1>MwGW&4IX_jah?X?mIaZEI?DRjs)7%)7OFz1j^icZk!2w%zm>0f8d$Nuij zU}9^JftrDX7O5LjTy+IvZKnrQd771>qE`k6Zp`3okqlhi0TxDBNmZ_beyTqL+N_wI z#K;v_)0cLM9Z->{Aeb}7`4>tTu-8vyYP1|hZRAnv0k65Xwco-g3 z?bM1c-k00vO+I_UhwcM^w*y``Sjd_IDw2H=#Bzs$2C77fC4sHVz;~#4bDp3G) z0SQ1L>LtprCuA9Z@X(NF}J**ZjIPu?N4rcV(I~ z2!N4IVJt`F(nd)aB9Ns_ z9rn2O0Wy74q0}nv#=HPA!1Ntd{7irb$2B+lzZbe&M4|G`@X{1@(ufWvp5${puX)yAS3R>5nW68mwej3wtkMLHqrW5X02x;YA5_;jy;`)wt7k8~ zuQ>w4-zhcXKbMB+?=^x5&#$F9D6()4rMPaDynuMJq21T&irJ6FhEuM}llO7bGqj9e zuUBEhYeKrw^;0{GrCYOUa;rU|V~4F_LJ#n7RL&6RLzO(Sp~+lvDv}ZZ2WAsxhFOQS z2Tbr`=6wYWWbUWszO{)$77(AO-;wU=7Ob}(x_Clh-YYD-&2)F%x&kyBe(z`i0k~gc z)30HU_(yUSHKc*q`>^4r{{Fo9U@@~{Xy~Ir*+5KqV+`HN4oGzGoi7Q9r@;@n1_$p8 z*;9X&43o+Ff6eBlr8lKnl6H|{~wohNk$4ccl8 z<*it%^J=J_iZKK#G#J$-jDEKK4OZbD#fN@8ycA_;AZ@mV1DlRIwENOKAmZKE z#oakRE@78Fo9ap~5_G0`Q@E>Lr{`V6A1y`QH`lFu`3jjWy!I-2UCzyychN{`owjeQ z)Vn&Of9eR!#7JHv8i3wg2=}!RRLZbYRMgV=M?gOtXJjgfO!X}iA$%hCS8n0wuc2MZN?BFJA!iA?4->-qONw-QmaqAsFk+3y@ z#^6=hDBIPGPq#Qgb(oWUURve2yJpZN`rJ!g>hnE^mZ&nV&?#C_WJBRwkDwo)O$H8> z=anPA7P!=ToDP66@iXlosZ-^M6O&EBO>qzD2QRP4Ov0=t9&=y5?5Z2p(%fVc|Kg4? z5i2JxG5tJC?kFytS5%hH^erWVSa~;h-1P1QAT)9ia}51}E| z46_mTdnmcFpT}@R%+xzJ8yAD7gy|ezI!DpCgbX{s(uDc82Z<&17>Z#5U5`4+%*JHskhE zIY^-g19FP9f(~YAx~gQfI%Jj8)Ps5)mJ659RXU;Wl1vLxXH`3t+*!0NKShb6G3NM9VJNz8hY{3Qp3{za87JD@+;;% zXFn=LF|HA@PLNZtbe$J2&)22@NZ~ri<$m$*C&WrcbsOJ3MHOWLvIskJZ z(d#+T`)-oF<2t(dAjd0fM2)?@_y-4q$d45zsFmy(V@W1hC_I@S6Lg39$P0!P(XLbH zb}lwE7PCc;w<$NT2BM!w)!{@vUgksUj{f56udo7Nt?k*aB(P1oBr8?HKpylWX;!LCB9!?__PK7$RaR>FSNlfV&8qMoV;a3pQ^Uf|}lP_`W zhDdvD24;B<*YaSOJ9xRR^SR5-1@XJIuHbsFJVekltGcZQ@96ZgaQwDq@0tb>kp7Y? zz6W=H^gvE}Q2*>|2Gyvrh#{Utq#|dK3*wz&v#Aqcmi)WoEKW3;iNm* z#YLbhzH6_em`%(>klTiQE^6Lv2l;(ScX<1=EMdkWo^l=8FBiQK?TvL$5fuE-#cJxtF-z&oX8sZZcL-UQ9HorMyvXEV6KL?pT3(D;&0 zXiR^A?8%=!;*y&Ny`~B7_u8wOcS~S^Z7y{0V%v`kgFEMTq6}{qW|@B8(n96&8e(~W z!0~6}rB16x`BM6A&E<~nEi>aE%|$y}=6Ws=c$$&*5(lp23irT zHj>Hs>>_6Tginm%kh~kCiTw+lqW*$~lKMt8zxl$liXuEAI1_~a)7Tu>jwSY|M3}Va zZI45qpCnJ(9(wnRJ4eJ3e`FpKo@NV=YIz#UmeHnn>mE{Ty>8c5dF~(R^3p$+Ds*Nq zxOix5@z_`0#ngp8B=X}1O88IT;vZZ!tk_+k7aEY3bP_!sQ)Jp}&eWYyIiIQcN6eFJ z^blGJU)Epoiu`!lSz!b#$y5E3BD85AhY5|_9707nFu^|}!BB==;;Hd_I+G)ER`m8o z0UX|S@wMtAQH7GM-7SrG2B9iC#G%y!oUbo<9=g`byA`h-?x_x@Km|Phk*LaW*uODg z2}@W>=-H+J3L^IXj)miwY(ZG%oUZ2qu1C@0ObCeqk0Psi$lwY@Pp@?;N5jc+)e4(CS&NV7yw#$IhxAkm z4u9iezfqf=R8nWz28EXAkzEyxH3-R2AXU6OLEahX8|+DeUXB`fnXza_*y-_;)_M9w z&kYFbMB>X^N(xqKyF-Hj0+WAl5WJC0s6a5#4dqdXz_?vz(RN$w)mc3CwvpALRmvkQ ze6vY&lEpW5G+VIUaalb$63eRA5xRb>@nBr za+KMYVF1eeKHLZ*d1ZZ&b;n!e_O9N*fZ7X8*wd7qZ_4wFi%{)q8mt|V`O@8nEB6V# zm$_z9OKT?{B{iKYJu@_x+sAL@Ej=p9YM^@GL4#3!s#LzSTZq&npxH^7pCd=q`AD}^ zD(UBxq(S&7&AA}qEQiyh1m0>C!fJVCtLaMbDIUTgsUH31HV>*`ljK>J-BHG#YhZFxGOK1y242)Gnd(9Z4>jU$;hz>n~kYP z;Sb!c<2v0MxWnu^1qnMGbje_SZ#4d%ZD5Osy8jy z{9}$?O}fZTG6cd1GsJ}h26W`vsVYTJNuq8#d0INm!Q=UdFspB64>iy;_6?sI`5$D3 z9{Pzk$7@_}zopkO>+4rfp7DE*-WE=)a0*cezhXSjtL%Tktelt+Pjq~b2aZG`O3t!2 z=ON$yL2nL<{oyx<_T*#nDy`H?f+Ax_PR{}mm0OK!ncO@-$ZbO zgTa9qa&X)r(Hpn?-XF!|pKw}3G~e@ol~ z#sK}TaSPnz8fA48R}NIfO>oS027uNo2*5J^bp{Bi|6Al1n23UaNanxJ0HNrBlTVRp^#N_?;8=!9h;+TIWfPw<$ zySWb_FAMrxD74{p*j?Glca zylrs471<4x+OP_^^nQ6+e4lk>hQOf#}Agh@3BYyajyTw zsVo3m&K<3pYN?JY|#F-s($Cy`7aFn>>R%j`^;cu4HFyC zqX1|IM$Ws9`hRs8|1|0||H9te};z3^a~=kksynV{Y*1pAfcckXqKa4So>Kks`#R@cTlZT z@J&jkE9eoS50AqZu{r>MzOJGg)KkvcvTX*@_eDxaR zOn(iLV+UV<4U779jy%OwE317JpQgNg!<-GHKidF3x0 z6!`S7#5EQl42rtB^50^j!0PaK7!5(3zu~o9)2x-GN&yy~8%&fg)aSm5O?r1AM!_%L zsg|V?QtibW=Jht01BXNtItF70T4ZpqQ$QNmGxPE(=aHbES_NQvb> z;fiFU6$b7;c*pp<;O(qo#GsR^%V65vwx{tV#}g|AMhc{j>7-;0(!~tyRUHlj8)Yz>YCxy+0{W6hNL$3a8vcqXP!}Rh|L_7HFgdw=Y~ijC(q7RzI|;Nn~U-V z;$fI0+9B_Tjl2m%(5N6%6A$+>C*UXUs^$ASh z1Z0@a6GumG%c&|`CzQ$ycTmL@wb`tau(k2YKoH_P7#~j6?Vtq|nr)pU@qw5_&R3{2 z3j`^TsORWQk6UE42k6IHl|QQDw9MqO?UXspPZh|A+bF+Ehzt1OWerE(PNA_v06IMG zK1oImKMnWw_=%}Jf$Yvk(VjX-$UbSh{(}#>Q-gk5p9NXj6~*+-U(~g^{vANk5#dvt z-y-m@;~9wUj%y)If9~VUyy8ZM0`!$VWZPk^n0)!uOtY!GKV4cIeUZ@m!4xrgPdOk+ zKNe{)074DN+i#`#c&N4jdU-No*U$I80X1%Dt%V8OY@_nmr2r|B9Qc~TYWuKK&*-q3 zWCw(kxrw(HThAFty-YRj#-iY~J0~fAu2aJfG`ySJ5GhOkiO^!!&3Pq+y~So@W@_UX zb*Pq_F8quIBV>QblyZA%c0tt zP$@ihNx8@VW;6S~y!n0(M~ij!>)8#qhLH0BVplFGiRuH20s80ubXpY}-@>i6T*axA zKPIEU`D&W)k8qG%IVrGR5ovu*XoZ26c`+B^4G12iz2b)DJ(10}$Vjn?KD^BApK> zYrsFRBk<|rtQx7W9Qn?E8TGJktmL*>M*zZlC+rW?pCUmja)MDnGj2Ra^dV6og1f&2 zDpo<8xY8}|D=2aAfHh?FkYkY-c_Zde&rNV?Mu=Sjmyyt%{gz0*L`^c0FDB{!3&*uG z_^HrPkumfvDn!%y=6dr^eZ|+S3)OZOsVHXd+O;KV@iX^>J)UDF7OgWT?h9r~FCB%2 ze!HY{dx$w5CCot7gT}W@ce1 zd|`pWfi#F^{Mo@cB;*rP02790Z)qdb{{QK&FfHNevu4&45_E;E=IXD zmIv#X%Vz1RAmFYQq*BMhk`SNBqF#MGLz7i&vq&}`#V{*}k{c!+swv*F3>%xH1($Z|f)!0(8@gb8vL&q2Gk2$&!gpEf>54K}+&W50a#h=D zvg?QN#xT^$BM5eKKdDKf2*6Vz|mZv$PX>6$TZ?%To3 z4CG{%t9EL|;OyH_6$kH5%zq-kG`4n^9DRCJGrAEeos=h;*5>`NW#7+ad~yZJXaYp= z;yb+-3sr~ts=w*mlI3>M>YwS^c85m<`psr4%pcE5sApQ|7K`?w1~0rcEg<>VN51o3 zLUet!JevG^-2my#*1w{TLoX~8E|=>16H+XWJ&O&gIx1o$mR zNnt<6`*^FkktA?|{Nxf(u2BT=f=qBLBymblFAkcuCwF2_$AIX_&FsAPNb?I+-s8x@ z3F?U?QJml+@K(wsOp6T4Hor)qSjc`g!6mvq?T>%O()0AAK^>fysz9CH)e{m{ZVWQB ze!)}qZx+w+4Lw+2CQQ7+Q>C99)ZUDLaa6Z9%s7#mP3;|4Z@n6vSzZ~qs{3ro2UdU7 z2O_)~Zh8;l%bS$Z!Mslk14>Tr&jG4sXbVo$YH^sMP4r`RG*SL-iaOXq5_y>NIHYPApUqPINeTJ(@#{cJH4A)4IQtK1s+mK{S&Yh#GVez#8yK;~0VfK+Q{UoHTaMhG5!p}AHP&w8 zFwC3=nVc5j7BNnJdG>wI;brEb#{~>*WLMN}R=;D2Ea*=Jw2C5UDKW4Z)Uqdk8?pRfi8^Sk~BIjMIbyf^eg*^ zamNBBsS)<&qXu3v+Os0zO>8aR*DjrP5&6_E>N+hgRB}T8npQty+aTS!2=qwq_{h>- zC#QO0mII1Uxc@RB2IlGIM@m|ekV>u^oth<+0yOIqW|v29*7j73Ut5-tfkopeXFZzz^BmbV#)Tj%ndaKb(^ z`N70cWf4dNh!H&|^s2-eG-@dIuM1?l0<;Intk7hEr}E+?v0lQ0n5~78kP&!Ev_nq1 z6nY=zkY@YT+r(Dh{`3ld(%=$F5@*|W*`J4rArSo}z?5RFP{{v@gNvhtj^5{VVwu7R zg!Cb&5~NAe%4MkR$Y@FBn4InOvb&PV!IR6T660Id2E#BDFHkDmD6%QVa2PdCj6GQH z+Y@Y!BJU`+>owEQe%?*4X;`%2(POA&7#QG78!h0*dG$QRb`XxYbGq9`?MgCfAqWbx zxmZsPb->oRzqE;Dx*?x#udOC3=~3Zk@IKUr?2}x)kqtq$ak9FF@t{dxB;?< zXekF#Qh1TirT!>pW1+q~O!56ICRY=2tuA!rsG-(K=v!UTC2?=wK$qR0z>PsvLIs`4 z%=tUaP@9;f6V!@|;SXjO$)KoaM@!dDYSNiJ+BTpHpHGFraR?VC7d=?D;j-00ch$TH znvl?A?{Ds}NfN$zzqeq(=lO-7=40dsXk2YO9_Tuj{v`zijgx{(bnM(Qi6yJtDF5f} zq^C+_g43uGlUX)a_wzYJw6!DYyPj_qZF41=RHZv>v;CB{&K&|`Cmtr#HQ2w~)-b(F zZf+g?7Trmf4rNsf@Hr6g7iE5pZJBszvTZDXTczDGMElR^3Lu6a%mKdd21HkE_DD=z zg}Rzu`NU7s?$QV|HwG5LAmkWk*ZWmP1jfdM>l;d%jVGoTBa^mWaoWJ0!ZcGC6_mdJ z*lv3@$m{wNX9(Zgj^~>sC`5^he>{<=K92gf^=I}wJE4Dc5h|}_N})(^;uT(D{0R8S zbNqF(`|IWWi#MXyst%V+_SFNT}eFQdw!Eq5l~Nzs=D6IqIf`sip#1>(*2 zSZ7jeMun>u+h(AcI$Rka2XVhJ(d~;K`m`1t2chbi7KfvUtrZzD+a$gMo7jFZYEV5lTDf89E{2jbnT2V&^AY zVCd#M)%AdoKhkhp$&1uokXxSd>8ht4OtcHH(1S#O4%{G}4TL!wD&$72;5BF6X!eFw zg1zpU@YOUGUcG@AF<$H-9d?tlSJq;3pLyFVy5`;vsUMgRucPP82bb2Ea=Jdi326*k z`Q!r6kFP#gQKQ{}5O#$?^=$Y|@smK{2B(am$lAp6EH8H3gJ9dkCT^vv?X#r5ZyDUF zFg}&Zaq(zpsY4=HLnb+@Jx8`|oj)E8|AZ(&eck#Pkp{u^~g6U477J1w^{FwUMKUP95gbDoDR@T@Pj9ae6MM2+C6$U z92hTswjs@3hzmIz1^*-Gv5R@{MAodiiJgTrZHy}y2tHY{j% z^})aqA_i$vIXcdV4Q|$$b{V$1kf4i~c)i7@9J;UsOWFzy0UiYwr}PL#*tYse>}73F zEyhMj;!nAS`5B=k>cen4rRg_v)TQwkq$$Rvi5L-R=!8}E*Hs^z>f^D7WD#8;cYpKH zAtOV=Qd4tv%dLwmNU|FdnF~V%xrq~-ba91$$a|N8%368adi&d!qyUP@yLI+| z(!a~d|Hc`>FF0)2+huhIXuQblHqRx=f9j?9bT3eE69}ZI00QZSOY|Q0A9$4E@h3b) zhsDO)96WoK%!y7WNL6FnW+C_;Q2lkv7{S8GSiw|UDu|j=ruQ3tT>rggfJc}0{*&s3 z=BVUznRum7+Vs(&2SbSbz14con@q^6m=2qP!cXS~r=q4yJ2$hYI`&H56}gXc%-6n~ zx~wg97@#wYV*K#BQS$$3?n}UG?1IOovb3jEq@+c*a+mjRvPAnL(W0z{Hc@F&X;HKh zp`<-3zNBo0B3V)-Qiv~Fv`Cv4q$0ll^Il5#J$=8Y|8IMKJP-HIJ?FgV%$YMYXJ+1+ zVBsD^R$RN^^0~cjYmX_r+-~r(3LraOdCSzQ*mB-yt$Pkbho)tsw(#NFkmj<`g)dVQ ziu?1@Buyi~D8J#pWxSNzlr@>2^|XDh!e7-Q1vg7(2>2a*wX1H^b(fz<9@jiyy4hQ^ z&FCN9skJR;;rarnZF4^C?-UmI4H@etN5(jB@fhbzj!=FzQt9_Cp8p$POU9u?TW;SO zy1`wbsL0*RvtL?Wa^#JGfMQy_q`mpS;}c1Qka!I z=j`EC^35b2x^1NB z==1!q0K1~Nz|TO(1slIB zrJPT?zcN;Ob%dFkUi*}&H_u%5DkZ+JG&-JVA6Y+R#>umqT9-mE^u4+2cwca4%`Srp z+w)UDetlQ=I;*x&dY5PHjyBWuO*E|>SE1NdRfnd_nuufh{_cfmb3OX+_a(Y4`Agx( z>x+XsC4AQEC(rsBU}s-N^OSvmx+3L~jJexQCzUNF)63uJEAw2nPig7$F)Rz0(fN7A zx0%+`wKjo@4ea_dV{23Q09U8llk;P7vvJ<3A@}cSTh3~T1?HJEC7AsJV)0#XXePNgU*G)O*3J#lJ)-w>=}I~T58uc>LB;XHGO*q3{P z)E~hoTxq2>&qSQ8H#7*!R<1OBTfBK{sLrl0e5-RV>gx^!3=Fa6ev(e|jEudk9Ot?A zxy7n!b#fH7c~7?6ZN&DF_IzAx%vWdiGp$(IVt&Me6-nzRO3-SF>qtfcSCefk$cHMfTckUYNhi-4**7^Q$(vJ=EO_tQn z9`s|s94s(#d-+%x8|QtLM&Zy~R-3XSsz?GKfAaaOPwH-3CUihNVJMet{+lrAW0x9^ zYaEK@7YUmxA<8F`GEw12s$=~$*8v}rSnZ41B1>wa>`-{#q)8gVwUeH7(7i4_y#-~7 zUr#FJ5lv87a+uoJeY5xJI^p6}%lGv9l8_J$-B(4K`{FRGwe_7pI|Z2W21~7{PWrJa zQXstS8C)LE)n!!bXe7`11DZ7tVs{HtKj&EN zRlB#~$xHdfsOb$U5;1yr4Z>5>-(Wwm3)I=Yo2&=kR6tFdcW;8G)z#Fr#|>Q!_36#XoTgU(0Si~-dm)m>23|k$*}H!9C7)xhezv1m#=Mu#~&oTtF;%- z-O#iz+MCxRy#HgT?7JkFW84~d`MYNuzI8rO;Vs_!^#5JCoYB)!J=gubJGlI4p{2k}N$G*ZUP5+5k2dC<`q3=&CUWbP{R&My6BHy_$};Ut zH->4xHB)(62>0d%Mtl0@PQNfu(O`AiR$`7flw#L-_G^W{kKpC~uscfk@O!~yCoV1a zR#XYkRqJaIYUZn3l=&LF(^TQ=S$gkPCTp7Z%@)hON`05edt%OrWR!2u`)(sqkr-{f zZR48`1Le4{SD!x9t6KcH_|9Ri^-rv7Y)(#W_b6+1|2VBDvUWwG8#^_itFEKr80Z5> z?YI;ONNGKqku8IqYv2sv@!4{9Hff^7_Q7S6TJu$hoE5f4rp0dyvR$_AzPgTE0$0D5 zl&tD@MFD3$BPpYs(I!$~K0FINb^U0vblSDP6ybdP6mo`Q+`I()h7gTXZk1Hy6YX2c zJ9p~X?9@>R?Nc#tSM*fh;P9x#!*A|}uRj_#bx3-YTyNYuAmdtUko=j8tfX>ctVNCU zHbot)@rvZ}UTKQTK@e^ctql`?rjvT}o|w;o>;s!gg~UsiK$LVlyT^v`)z zH~UvjyWI*LBR1#M&+I7VOV4X=+|sWuR2~uAqmR`_cF$f+4qrT(TSZbE787W7QFNgZ_v!bvfHmd_iky|?KsA7r&Hrlf3met zaaT_LZAOpNhm%{so!CEPiMiHTh#Kc*67t)Q-u=yMSBF246TdyUu0Mv)Xn zan~wgdiCt%?FQymQslnl`|qAl;rB8Auqi@51d&$^sKZfPxy1bXHZV~gk+mX`XbNbgBIgq-2+8D4b7~^c5#=a9@b9h z1#8O6l6ig1A0~K)Db~0|TiREs;u-x);dkC-*%LWqA$`<6_D@{Ye_wbvgGBlj31wux+eU7& z)>z@)Dyy7#?HG5AH;vA>*h1IF!Oq?tPZ3WUE5>`&_3qE2{+D9BAyO1$XR?R1&+IUb zaYcJEe+P6DqP?iYd8|jGy*MMX$BO#;*O2MofhNEDjC>jSFxFWA8Q92x><_=c{rNn` z&&ZEs1Mtb?N5arB{XJUVWaBOxc4Eqa&Jky7Rq49Wb zc60&z7l`ME8YjOau<<^>qbuzo5Sj`#FMj_5!O-;KU}(Srm5wWP_yBmIHrtrZ{sO_! zjFEtF2-IfVLGfSkKyAkG4Ddj0#z;6eEP-txVK;*%u%ucSz;wItybpj0{!ViZ>u71e=G=H{Yg6HkWQ$F7^9^Bn_k(G=tEk9UZL>EM21LCsO9+85RVGrNt9#s}N>A1G{#_Rm!NBFfg z&&c06IyFrE!k+w+a9_31yIYSxy0cQZ`RufPDbGFfH0i0!l0(-7k&d~TZFbA3Y7FPkRC3m@{-eDi3=`xr|HmsNRBwrXa(eizpgEb!Z= zvE|Nvao;NYyhR@FmPK_dC27)^onf7AGRm0D)q7W~ z%ktZAtf~yXB`xwLsx_knoKi+U`yZ08+;Mi*bEXItWSYd>@-I2vpg7qfL!Db3#oEw>*|H^$+1Df z=Q1^{&99K-5AIOkW1Pi9H!oJPS<`pcQ6r|m^!7hOO;0WSJJ#9xfAyt0nhPI!==`B8 z(nPjbYxdl+COFQU39!V`3O5)x@tWKpJ@`}uLN5n=VMghFavh>8%wR=JTBvNziJZu# z#+A*_WuheF`>A4`y3m1lSCgdoYl|)4Uch=6apSFDgJ=Jiaw-{r*1 z2YnXh4^Lg>jlUBe&E1vq(JpLJv)qjk)u`N%iRHVUyAH1Q+Fg|QcfhVoW#$pNs^K5& zu)iZOnk#?!X5QkPo!2_(Bom&F-O_3Ov40NFH`5a}?akqiJ2F>g*V>*Azm!@1HP?UE zB$~);H!LpPT9{`vrFs!V-nY^lr$c0`KiYDCe!7YhQAY3E}goCVO6UB z;d^H05v=8ut?^40^&gBA7VS0o7;I?Io5^)=!wx$ah8N}V!n8TNDn!hDbhQ0{_vwNZZUN2f<(M=lp_?p(%DT}=K z+T3`$%s}L22aE5(;oV+uH@N?N#%pqCZfe8)PL|&`{Q`fLHgWfzlSJL=wO3>onc8`& zT|Fh6by$00`haKJwK@{twfbRve@)U!MFCYNP|Z|(AS za>B`Pce}s8y7SgbePXr#XXvJpH<1U=48M#2NQmX} z`_T`1CtNX;kr``VsEUA%lJ9hOxf%+Tw}E>V+^#ToF&kX^j({jo?S}o^zsX-HA~G%w ziO!L$50*}bm5yFIMh9F#5`28=aZ*wmZ&=1RZU(LrlC&UGq>CoB9ehq&#umgXf#PGl zk3?k`_Zi0_W^zo#tPI|`z@RIw3&PE=R__sz~n0H7LX^eS>9?X zA?f%q^p^Fb)bw0e-*^Sd2RTJeQSzx8ZBLZc6|Kc=lH2@?Ooop>JmzSb1&n zqht4TOt%OXFK^?~_HDC!^>m(#M3rByUGU70Sn%m+o0dVNJ27*Ms(0$N1_qs2w&b`S zcB0{_M3t)7z)_WIO{dF;qP-T(%y~4p`pyPRfkReum3OL)0_`9kMXzOt?%6asN#^_` z&VPAINuF=9F64uXrss zp~?^SeLR@j-E{HxWhnzSJ5mm}QW-r=&ZX5#YsF>z)EyN$SN;FtcJipu^>LB^=YoEJ zt)1>8U4_h_Gf#Q<9X`HiR#t!g&)lUQcfWRj=T;D@9aDuEr#i{t$gTmg%``Yp6*Rn} z1e=jiXzSUh=>R`465bI;!dPV^J@36s+{`#Asy(ZUq&TF;M-- zb-IB~jAp`SCssP~&iPfeV?qPZD%WJlp5)(s_vf~H&(>!v5C1f*k~hBJP_irK4Bwo< z#ecPYVq~~={QaV~Qz1cW=ECf3iS@5SY$Fz3JQcCfgS;bRgT3vfsKbVxQhwnJzlEh< zSn^s!U>Ul11yHKI$7dH|AFkrAQB5i?40tu_@WDc04%TVXnYJnYeZ9 z|CwmABg1Ce295LO&Iw71whm2wC)LxOtF~)s$(o2(+mzRM2XH$@L=7%6`l7$0O|5_{ zCf{6VER2lO@y56^vFkCyeF@$n!rwL~lfWi`fz1h+>K_@_d@u8MPnAd$iB1PB|U4 zXa6a$PiAt7pBk4ysH)}D3^G;Bv@odlp0@Kuv-5A-=NUG=l5?J@6X%CU7o-a_lXady{+Rd!x| zW~pQi-PE+Dd^u4P3q-f)#pY_c^GB(1uX?3@p|mF{+Ud^V*%;7yNg*7hK&}b^b4nt~i?yS_|BRk5tFaF!-lg<59qxYSi;i7aX0?!88nh|l8b!#*?$|b>wdr$K3Qko8EQ%G~XsA(#_uc{HBAuWXqK-%Z??qW!Hb~ z__^bzg-2wf>^!3grOhvk+e%OE+)`X$Hzxbs}l;0=TiSkx|yCYo|dqp$}!#NRz$3$Qj6g8H+9_i zF1IWldXe_?T6f>%mv1vN$J(%QYF|*RZ}f&iapj#wa^fNV%Y&c96`%P)=?s{n(kjVP<>al)%jcMF%+>ZWtgDq*>+!18W?94}Lg3m5_sG2{`{DHw{>E9`KXXNGDX8vg&{N@_CFAUzyeI3Kw zP3Xumx5EW|n_o=oO1l;=m~q8_LZU(xHWu1PDVKko0{`ae(6rfUZSEmwV(sMi|DVK0 z`_%Px!fo7etX#Q&IQgZbNe z^DBD|E)j6y07ZUr6`V$+kH@uv4HuatxHhoia+8Q_gVPi^Y!dwSKW^j0f2V_Wg^X9T z{PPdqfc=fL$M83J@gA3jzy6Y;@-X}3uR8^F1fMeApT;hH^Scq`kphPUJPocCxS~V+ z`&)XBYK^pDv1tDlcg24u^~?C&6;L+wk7<59-UxV|8tniHQ~m%c!*y)&;V8pJY{4J) zr^)bxAZ5W~JII0p|BQDYkn#i_2dwufaQGp^|I|Nr3kx6vi|HJ_!0TUyx7pkq}>ApDH0SZtU zlaBk0INL$>bRu5B7K76tp&bd`OUOKg^o2x6NMES1i@?Zi*BId$>;l3B7~6SGfYFgX zCzEM7{S(?@c+MjN45$NvF>zNqp&b)15=?+GhrOuE#Sb5!$hf zHxpq*8iHql+(i1)5E>#=Fx;C(7?17hB)}NRIU-Z&c&;ZxI~s~-s9KG#g9+X%VqXUC ztmGUIYB{4Y67GuSYzK9#k?~*|D2#^EIAp0p+R^ZI9)xEAzeUahI4uzTQJHu`EYA6X z9~S8goDl8H!kIc}UyOoh6C}XMcn&ZEjGc0Y0HdJi7sGJwPH2aL)ZAb6GTW z&p}-y_NCy;4`F^35*^{$AZii5K_Rj52pNv&QpgNEK8O=WVe7}7FmQGf_YImMa+Lz? zlt>2@$f=LOXmmvOP-rX!2NXKGZxlKmktwii=sFk_gb#sKKxmS}U?O`CS~emtDJ&8~ zYZMml5+mRb)Mp~D;3|#BRB_-Ga#|sD1zH`#OQ>WLvTszlPb1GDGZEei8YUu-AX6oR zE67oaoPB^Hatp%55WWGyUI?Bc<_o_(ajutv%2l}Wp=)E2@I;}UeLGY2a3f9)7$E0(W1MM>iKnR}JeNsFXai%R@&o+gxNU;t z8KABq`#~eY#T#iyNA?`J8Y1&(K;pQ1HmZ`veXjs2lfSom(T!J#Ir@i*ukHi zYs2UWpQOQpi8KUU65&HM8med0Xbe2klJgm$>mlRONr)_?(b={a&b|yXA_r*<+@MH! z1_Kxt0Y(Qko7fH~QzC7E?GnKs9nu35=M1}xTcL^^;|iSRWD-b8o~sJnC z9w)BgDum3L#g6^tY{z2ZraBH7V+}G<=4H*yOk&yAASTE8J zvUd`3%7mbIBAqh9^o8^VVS&JC6g(c8b8WyBi8zH*iO5j6f+2DSL=Pg5;AV>K8-CS5 z<_xMgG8Yzx*v*(Mwg-lD&Y=4c`+{nV&=qJ5hnC`dyu}c zA1I84;GP9ti9CV@q((e%phe(i8#va>qA}3%=y3Rn?SN4ted(w@hs7WxdNB*`$;ez7 zOhg8-n5d11#iS!PUwn&*G|mD968Rqs*gfI7!2Li_bFK|wh+YgZCJ}#dppml=F!p;I zqj3r_8j-gH4DlO)0*B~k0E0?3#Q6dG5EvbNio|xn*U)x|jt-mzBl2h5ML?tjxLF~3 zJHRMJT?Jr>O$%U%Z4O?T11NFMOz=_?VQ?-9v<8X*iKxQ?4Bbm`IS~0DD6AAB{Q(TI zHA6Fa!;3H%{GBi2{J<@X;2zW*{B=1FIv|4&gK&Ov4GEneMkd;?z$J_kZB^i!AQ5>T zzz{zPz`(#poC}z_5Ev+S$lB;&TqpJgvmUYzCS0zF?U-zTJ0}j9bfRqpTo|B?68nP6 zfIJrtFcAiv385jdG7$AdIH`1^-vD61jS0^M4V`E|1u}rcg|uT3`5%x5+i}XVUJ4}S zCh#GkK6Y&m&USDtk!OIYCDIV?ia}wB4FyON@gL!2O4LgL2AT=6}$QV24hOhxr)fFb%b!0;FkoHRy!(Evm4(*Q$cJg^s-Ah9p* z+(uxC4+Hw5G!AZ9gmxkN9676C`xfye?B*NYgc!+-ygORm@c1^lGYk+zW)d1z<5Of literal 0 HcmV?d00001 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..be0e188 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +.PHONY: help build-backend build-frontend build-all deploy undeploy clean + +help: + @echo "Доступные команды:" + @echo " make build-backend - Собрать Docker образ бэкенда" + @echo " make build-frontend - Собрать Docker образ фронтенда" + @echo " make build-all - Собрать все Docker образы" + @echo " make deploy - Развернуть приложение в k8s" + @echo " make undeploy - Удалить приложение из k8s" + @echo " make clean - Очистить все ресурсы" + +build-backend: + @echo "Сборка backend образа..." + docker build -t python-navigator-demo-backend:latest ./backend + +build-frontend: + @echo "Сборка frontend образа..." + docker build -t python-navigator-demo-frontend:latest ./frontend + +build-all: build-backend build-frontend + @echo "Все образы собраны успешно!" + +deploy: + @echo "Развертывание приложения в Kubernetes..." + ~/.kind-d8/kubectl apply -k k8s/ + @echo "Приложение развернуто!" + @echo "Доступно по адресу: https://python-navigator-demo.127.0.0.1.sslip.io" + +undeploy: + @echo "Удаление приложения из Kubernetes..." + ~/.kind-d8/kubectl delete -k k8s/ + @echo "Приложение удалено!" + +clean: undeploy + @echo "Очистка Docker образов..." + docker rmi navigator-demo-backend:latest navigator-demo-frontend:latest || true + @echo "Очистка завершена!" + diff --git a/README.md b/README.md new file mode 100644 index 0000000..4f9f92f --- /dev/null +++ b/README.md @@ -0,0 +1,249 @@ +# Dex Demo Application + +Демонстрационное приложение для аутентификации через DexAuthenticator в Kubernetes кластере с Deckhouse. + +## Описание + +Простое приложение, демонстрирующее интеграцию с DexAuthenticator: +- **Backend (Python/FastAPI)**: Валидирует JWT токены, получает данные пользователя из PostgreSQL +- **Frontend (React/Vite)**: Отображает информацию о пользователе и доступные ресурсы на основе ролей +- **PostgreSQL**: Хранит пользователей, роли и доступные ссылки +- **DexAuthenticator**: Обеспечивает аутентификацию через Dex + +## Архитектура + +``` +┌─────────────┐ +│ Browser │ +└─────┬───────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ Ingress + DexAuthenticator │ +│ (аутентификация через Dex) │ +└─────┬───────────────────────────────┘ + │ + ├──────────► Frontend (React) + │ │ + │ ▼ + └──────────► Backend (FastAPI) + │ + ▼ + PostgreSQL +``` + +## Предварительные требования + +- Kubernetes кластер с Deckhouse +- Настроенный Dex по адресу `https://dex.127.0.0.1.sslip.io` +- Docker +- kubectl +- make + +## Быстрый старт + +### 1. Сборка Docker образов + +```bash +# Собрать все образы +make build-all + +# Или по отдельности: +make build-backend +make build-frontend +``` + +### 2. Развертывание в Kubernetes + +```bash +make deploy +``` + +Приложение будет доступно по адресу: `https://python-navigator-demo.127.0.0.1.sslip.io` + +### 3. Удаление приложения + +```bash +make undeploy + +# Или полная очистка (включая Docker образы): +make clean +``` + +## Структура проекта + +``` +. +├── backend/ # Python бэкенд +│ ├── main.py # FastAPI приложение +│ ├── requirements.txt # Python зависимости +│ └── Dockerfile # Docker образ +├── frontend/ # React фронтенд +│ ├── src/ +│ │ ├── App.jsx # Главный компонент +│ │ └── App.css # Стили +│ ├── nginx.conf # Nginx конфигурация +│ └── Dockerfile # Docker образ +├── db/ # База данных +│ └── init.sql # SQL скрипт инициализации +├── k8s/ # Kubernetes манифесты +│ ├── namespace.yaml +│ ├── postgres.yaml +│ ├── backend.yaml +│ ├── frontend.yaml +│ ├── dex-authenticator.yaml +│ └── ingress.yaml +├── Makefile # Команды для сборки и развертывания +└── README.md # Документация +``` + +## Компоненты + +### Backend (FastAPI) + +**Эндпоинты:** +- `GET /api/health` - Проверка здоровья сервиса +- `GET /api/user-info` - Получение информации о пользователе + +**Функциональность:** +- Валидация JWT токенов от Dex (проверка подписи, issuer, exp) +- Извлечение email пользователя из токена или заголовков +- Получение данных пользователя из PostgreSQL (организация, полное имя) +- Получение ролей пользователя +- Получение доступных ссылок на основе ролей + +### Frontend (React + Vite) + +**Функциональность:** +- Отображение информации о пользователе +- Список ролей +- Доступные ресурсы на основе ролей пользователя +- Никакой логики аутентификации (вся аутентификация на стороне DexAuthenticator) + +### База данных (PostgreSQL) + +**Схема:** +- `organizations` - Организации +- `users` - Пользователи +- `roles` - Роли +- `user_roles` - Связь пользователей и ролей +- `links` - Доступные ссылки +- `role_links` - Связь ролей и ссылок + +### Тестовые данные + +По умолчанию в БД загружаются следующие тестовые пользователи: + +1. **admin@example.com** (Иван Администраторов) + - Роли: admin, developer + - Организация: Acme Corporation + - Доступ: ко всем ресурсам + +2. **developer@example.com** (Мария Разработчикова) + - Роли: developer, user + - Организация: Tech Innovators Inc + - Доступ: технические ресурсы (CI/CD, Git, Docs, Wiki) + +3. **user@example.com** (Петр Пользователев) + - Роли: user + - Организация: Global Solutions Ltd + - Доступ: только база знаний + +4. **manager@example.com** (Анна Менеджерова) + - Роли: manager, user + - Организация: Acme Corporation + - Доступ: управленческие ресурсы (Проекты, Отчеты, Wiki) + +**Важно:** Убедитесь, что эти email совпадают с пользователями в вашем Dex, или измените данные в `db/init.sql` + +## Конфигурация + +### Backend переменные окружения + +- `DB_HOST` - Хост PostgreSQL (по умолчанию: `postgres`) +- `DB_PORT` - Порт PostgreSQL (по умолчанию: `5432`) +- `DB_NAME` - Имя БД (по умолчанию: `dexdemo`) +- `DB_USER` - Пользователь БД (по умолчанию: `dexdemo`) +- `DB_PASSWORD` - Пароль БД +- `DEX_ISSUER` - URL Dex issuer (по умолчанию: `https://dex.127.0.0.1.sslip.io/`) + +### DexAuthenticator + +Настройки в `k8s/dex-authenticator.yaml`: +- `applicationDomain` - Домен приложения +- `sendAuthorizationHeader` - Отправка заголовка Authorization с JWT +- `keepUsersLoggedInFor` - Время сессии (24h) + +## Работа приложения + +1. Пользователь открывает `https://python-navigator-demo.127.0.0.1.sslip.io` +2. Ingress перенаправляет на DexAuthenticator для аутентификации +3. DexAuthenticator редиректит на Dex для входа +4. После успешной аутентификации Dex возвращает токен +5. DexAuthenticator устанавливает заголовки (`X-Auth-Request-Email`,`X-Auth-Request-User`, `Authorization`) +6. Frontend загружается и делает запрос к `/api/user-info` +7. Backend: + - Валидирует JWT токен + - Извлекает email пользователя + - Получает данные из PostgreSQL + - Возвращает информацию о пользователе и доступных ресурсах +8. Frontend отображает полученные данные + +## Разработка + +### Локальная разработка backend + +```bash +cd backend +python -m venv venv +source venv/bin/activate +pip install -r requirements.txt +export DB_HOST=localhost +export DEX_ISSUER=https://dex.127.0.0.1.sslip.io/ +python main.py +``` + +### Локальная разработка frontend + +```bash +cd frontend +npm install +npm run dev +``` + +Frontend будет доступен на `http://localhost:5173` с проксированием API на `http://localhost:8000` + + +### Ошибка "User not found in database" + +Убедитесь, что email пользователя из Dex совпадает с email в таблице `users` в PostgreSQL. + +### JWT валидация не работает + +Проверьте: +1. Доступность Dex JWKS endpoint: `https://dex.127.0.0.1.sslip.io/keys` +2. Переменную окружения `DEX_ISSUER` в backend +3. Логи backend для деталей ошибки + +## Дополнительная настройка + +### Изменение тестовых пользователей + +Отредактируйте `db/init.sql` или `k8s/postgres.yaml` (ConfigMap `postgres-init`), затем: + +```bash +kubectl delete pod -n navigator-demo -l app=postgres +``` + +### Использование собственного домена + +Измените `applicationDomain` в `k8s/dex-authenticator.yaml` и `host` в `k8s/ingress.yaml` + +### Production deployment + +Для production окружения: +1. Используйте secrets для паролей БД +3. Включите TLS сертификаты +4. Настройте resource limits +5. Добавьте HorizontalPodAutoscaler +6. Используйте внешний PostgreSQL diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..ecb1f57 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,17 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Установка зависимостей +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Копирование кода приложения +COPY main.py . + +# Открытие порта +EXPOSE 8000 + +# Запуск приложения +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] + diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..b7dd363 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,226 @@ +import os +import psycopg2 +from fastapi import FastAPI, HTTPException, Request, Depends +from fastapi.middleware.cors import CORSMiddleware +from pydantic import BaseModel +from typing import List, Optional +from jose import jwt, JWTError +import requests +from functools import lru_cache + +app = FastAPI(title="Dex Demo Backend") + +# CORS настройки +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Конфигурация из переменных окружения +DB_HOST = os.getenv("DB_HOST", "postgres") +DB_PORT = os.getenv("DB_PORT", "5432") +DB_NAME = os.getenv("DB_NAME", "dexdemo") +DB_USER = os.getenv("DB_USER", "dexdemo") +DB_PASSWORD = os.getenv("DB_PASSWORD", "dexdemo") +DEX_ISSUER = os.getenv("DEX_ISSUER", "https://dex.127.0.0.1.sslip.io/") +DEX_JWKS_URL = f"{DEX_ISSUER}keys" + + +class Organization(BaseModel): + id: int + name: str + +class Role(BaseModel): + id: int + name: str + description: Optional[str] + + +class Link(BaseModel): + id: int + title: str + url: str + description: Optional[str] + + +class UserInfo(BaseModel): + email: str + full_name: str + organization: Optional[Organization] + roles: List[Role] + available_links: List[Link] + + +def get_db_connection(): + """Создание подключения к БД""" + try: + conn = psycopg2.connect( + host=DB_HOST, + port=DB_PORT, + database=DB_NAME, + user=DB_USER, + password=DB_PASSWORD + ) + return conn + except Exception as e: + raise HTTPException(status_code=500, detail=f"Database connection error: {str(e)}") + + +@lru_cache() +def get_jwks(): + """Получение JWKS от Dex для валидации JWT""" + try: + response = requests.get(DEX_JWKS_URL, verify=False, timeout=5) + response.raise_for_status() + return response.json() + except Exception as e: + print(f"Error fetching JWKS: {e}") + return None + + +def validate_jwt_token(token: str) -> dict: + """Валидация JWT токена""" + try: + # Получаем JWKS + jwks = get_jwks() + if not jwks: + raise HTTPException(status_code=500, detail="Cannot fetch JWKS from Dex") + + # Декодируем заголовок токена, чтобы получить kid + unverified_header = jwt.get_unverified_header(token) + kid = unverified_header.get("kid") + + # Находим нужный ключ + key = None + for jwk in jwks.get("keys", []): + if jwk.get("kid") == kid: + key = jwk + break + + if not key: + raise HTTPException(status_code=401, detail="Unable to find appropriate key") + + # Валидируем токен + payload = jwt.decode( + token, + key, + algorithms=["RS256"], + audience=None, + issuer=DEX_ISSUER, + options={ + "verify_aud": False, # Отключаем проверку audience для демо + "verify_at_hash": False + } + ) + + return payload + except JWTError as e: + raise HTTPException(status_code=401, detail=f"Invalid token: {str(e)}") + except Exception as e: + raise HTTPException(status_code=401, detail=f"Token validation error: {str(e)}") + + +def get_user_email(request: Request) -> str: + """Извлечение email пользователя из JWT или заголовков""" + # Попытка получить токен из заголовка Authorization + auth_header = request.headers.get("Authorization") + + if auth_header and auth_header.startswith("Bearer "): + token = auth_header.split(" ")[1] + payload = validate_jwt_token(token) + + # Email может быть в разных полях + email = payload.get("email") # payload.get("name") + if email: + return email + + # Или берем из заголока от oauth2-proxy/DexAuthenticator + email = request.headers.get("X-Auth-Request-Email") # request.headers.get("X-Auth-Request-User") + + if not email: + raise HTTPException(status_code=401, detail="No authentication information found") + + return email + + +@app.get("/api/health") +def health_check(): + """Проверка здоровья сервиса""" + return {"status": "ok"} + + +@app.get("/api/user-info", response_model=UserInfo) +def get_user_info(email: str = Depends(get_user_email)): + """Получение информации о пользователе""" + conn = get_db_connection() + cursor = conn.cursor() + + try: + # Получаем информацию о пользователе + cursor.execute(""" + SELECT u.email, u.full_name, u.organization_id, o.name as org_name + FROM users u + LEFT JOIN organizations o ON u.organization_id = o.id + WHERE u.email = %s + """, (email,)) + + user_row = cursor.fetchone() + if not user_row: + raise HTTPException(status_code=404, detail="User not found in database") + + user_email, full_name, org_id, org_name = user_row + + organization = None + if org_id and org_name: + organization = Organization(id=org_id, name=org_name) + + # Получаем роли пользователя + cursor.execute(""" + SELECT r.id, r.name, r.description + FROM roles r + JOIN user_roles ur ON r.id = ur.role_id + JOIN users u ON ur.user_id = u.id + WHERE u.email = %s + """, (email,)) + + roles = [ + Role(id=row[0], name=row[1], description=row[2]) + for row in cursor.fetchall() + ] + + # Получаем доступные ссылки на основе ролей + cursor.execute(""" + SELECT DISTINCT l.id, l.title, l.url, l.description + FROM links l + JOIN role_links rl ON l.id = rl.link_id + JOIN user_roles ur ON rl.role_id = ur.role_id + JOIN users u ON ur.user_id = u.id + WHERE u.email = %s + ORDER BY l.id + """, (email,)) + + available_links = [ + Link(id=row[0], title=row[1], url=row[2], description=row[3]) + for row in cursor.fetchall() + ] + + return UserInfo( + email=user_email, + full_name=full_name, + organization=organization, + roles=roles, + available_links=available_links + ) + + finally: + cursor.close() + conn.close() + + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) + diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..750c7eb --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,8 @@ +fastapi==0.104.1 +uvicorn[standard]==0.24.0 +psycopg2-binary==2.9.9 +pydantic==2.5.0 +pydantic-settings==2.1.0 +python-jose[cryptography]==3.3.0 +requests==2.31.0 + diff --git a/db/init.sql b/db/init.sql new file mode 100644 index 0000000..73f4f24 --- /dev/null +++ b/db/init.sql @@ -0,0 +1,120 @@ +-- Создание таблиц для демо-приложения + +-- Организации +CREATE TABLE IF NOT EXISTS organizations ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Пользователи +CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + email VARCHAR(255) UNIQUE NOT NULL, + full_name VARCHAR(255) NOT NULL, + organization_id INTEGER REFERENCES organizations(id), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Роли +CREATE TABLE IF NOT EXISTS roles ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) UNIQUE NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Связь пользователей и ролей +CREATE TABLE IF NOT EXISTS user_roles ( + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE, + assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (user_id, role_id) +); + +-- Доступные ссылки +CREATE TABLE IF NOT EXISTS links ( + id SERIAL PRIMARY KEY, + title VARCHAR(255) NOT NULL, + url VARCHAR(512) NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Связь ролей и доступных ссылок +CREATE TABLE IF NOT EXISTS role_links ( + role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE, + link_id INTEGER REFERENCES links(id) ON DELETE CASCADE, + PRIMARY KEY (role_id, link_id) +); + +-- Заполнение тестовыми данными + +-- Организации +INSERT INTO organizations (name) VALUES + ('Acme Corporation'), + ('Tech Innovators Inc'), + ('Global Solutions Ltd'); + +-- Роли +INSERT INTO roles (name, description) VALUES + ('admin', 'Администратор с полным доступом'), + ('developer', 'Разработчик с доступом к техническим ресурсам'), + ('user', 'Обычный пользователь с базовым доступом'), + ('manager', 'Менеджер с доступом к управленческим ресурсам'); + +-- Пользователи (используйте реальные email из Dex) +INSERT INTO users (email, full_name, organization_id) VALUES + ('egor.muratov@gmail.com', 'Иван Администраторов', 1), + ('developer@example.com', 'Мария Разработчикова', 2), + ('user@example.com', 'Петр Пользователев', 3), + ('manager@example.com', 'Анна Менеджерова', 1); + +-- Назначение ролей пользователям +-- admin@example.com - admin + developer +INSERT INTO user_roles (user_id, role_id) VALUES + (1, 1), -- admin role + (1, 2); -- developer role + +-- developer@example.com - developer + user +INSERT INTO user_roles (user_id, role_id) VALUES + (2, 2), -- developer role + (2, 3); -- user role + +-- user@example.com - user +INSERT INTO user_roles (user_id, role_id) VALUES + (3, 3); -- user role + +-- manager@example.com - manager + user +INSERT INTO user_roles (user_id, role_id) VALUES + (4, 4), -- manager role + (4, 3); -- user role + +-- Ссылки +INSERT INTO links (title, url, description) VALUES + ('Панель администрирования', 'https://admin.example.com', 'Управление системой'), + ('Мониторинг', 'https://monitoring.example.com', 'Grafana и Prometheus'), + ('CI/CD', 'https://ci.example.com', 'Jenkins/GitLab CI'), + ('Документация API', 'https://docs.example.com', 'Swagger документация'), + ('Git Repository', 'https://git.example.com', 'Репозиторий проекта'), + ('Дашборд проектов', 'https://projects.example.com', 'Управление проектами'), + ('Отчеты', 'https://reports.example.com', 'Аналитика и отчеты'), + ('База знаний', 'https://wiki.example.com', 'Корпоративная wiki'); + +-- Связь ролей и ссылок +-- admin - доступ ко всему +INSERT INTO role_links (role_id, link_id) VALUES + (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8); + +-- developer - технические ресурсы +INSERT INTO role_links (role_id, link_id) VALUES + (2, 2), (2, 3), (2, 4), (2, 5), (2, 8); + +-- user - базовые ресурсы +INSERT INTO role_links (role_id, link_id) VALUES + (3, 8); + +-- manager - управленческие ресурсы +INSERT INTO role_links (role_id, link_id) VALUES + (4, 6), (4, 7), (4, 8); + diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d79337b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,48 @@ +# Docker Compose для локальной разработки и тестирования +version: '3.8' + +services: + postgres: + image: postgres:15-alpine + environment: + POSTGRES_DB: dexdemo + POSTGRES_USER: dexdemo + POSTGRES_PASSWORD: dexdemo + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U dexdemo"] + interval: 10s + timeout: 5s + retries: 5 + + backend: + build: ./backend + ports: + - "8000:8000" + environment: + DB_HOST: postgres + DB_PORT: 5432 + DB_NAME: dexdemo + DB_USER: dexdemo + DB_PASSWORD: dexdemo + DEX_ISSUER: https://dex.127.0.0.1.sslip.io + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + + frontend: + build: ./frontend + ports: + - "8080:80" + depends_on: + - backend + restart: unless-stopped + +volumes: + postgres_data: + diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..9ec6ddc --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,8 @@ +node_modules +dist +.git +.gitignore +*.md +.vscode +.idea + diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..2faaf92 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,24 @@ +# Сборка приложения +FROM node:20-alpine as build + +WORKDIR /app + +COPY package*.json ./ +RUN npm ci + +COPY . . +RUN npm run build + +# Production образ с nginx +FROM nginx:alpine + +# Копирование собранного приложения +COPY --from=build /app/dist /usr/share/nginx/html + +# Конфигурация nginx для проксирования API +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] + diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..e1bb550 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,16 @@ +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## React Compiler + +The React Compiler is not enabled on this template. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..cee1e2c --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,29 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{js,jsx}'], + extends: [ + js.configs.recommended, + reactHooks.configs['recommended-latest'], + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaVersion: 'latest', + ecmaFeatures: { jsx: true }, + sourceType: 'module', + }, + }, + rules: { + 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], + }, + }, +]) diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..c20fbd3 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + frontend + + +
+ + + diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..7f7cd71 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,18 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # SPA fallback + location / { + try_files $uri $uri/ /index.html; + } + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml+rss; +} + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..96261e6 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,2823 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "react": "^19.1.1", + "react-dom": "^19.1.1" + }, + "devDependencies": { + "@eslint/js": "^9.36.0", + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.3", + "eslint": "^9.36.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.4.0", + "vite": "^7.1.7" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", + "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", + "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", + "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", + "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", + "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", + "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", + "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", + "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", + "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", + "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", + "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", + "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", + "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", + "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", + "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", + "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", + "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", + "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", + "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", + "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", + "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", + "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", + "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", + "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", + "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", + "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", + "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.38", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.38.tgz", + "integrity": "sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.3.tgz", + "integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.3.tgz", + "integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.3.tgz", + "integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.3.tgz", + "integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.3.tgz", + "integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.3.tgz", + "integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.3.tgz", + "integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.3.tgz", + "integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.3.tgz", + "integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.3.tgz", + "integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.3.tgz", + "integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.3.tgz", + "integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.3.tgz", + "integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.3.tgz", + "integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.3.tgz", + "integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.3.tgz", + "integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.3.tgz", + "integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.3.tgz", + "integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.3.tgz", + "integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.3.tgz", + "integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.3.tgz", + "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.3.tgz", + "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.16", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.16.tgz", + "integrity": "sha512-WBM/nDbEZmDUORKnh5i1bTnAz6vTohUf9b8esSMu+b24+srbaxa04UbJgWx78CVfNXA20sNu0odEIluZDFdCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", + "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.4.tgz", + "integrity": "sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.4", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.38", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.9.tgz", + "integrity": "sha512-hY/u2lxLrbecMEWSB0IpGzGyDyeoMFQhCvZd2jGFSE5I17Fh01sYUBPCJtkWERw7zrac9+cIghxm/ytJa2X8iA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.26.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", + "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001746", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001746.tgz", + "integrity": "sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.228", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.228.tgz", + "integrity": "sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.10", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", + "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.10", + "@esbuild/android-arm": "0.25.10", + "@esbuild/android-arm64": "0.25.10", + "@esbuild/android-x64": "0.25.10", + "@esbuild/darwin-arm64": "0.25.10", + "@esbuild/darwin-x64": "0.25.10", + "@esbuild/freebsd-arm64": "0.25.10", + "@esbuild/freebsd-x64": "0.25.10", + "@esbuild/linux-arm": "0.25.10", + "@esbuild/linux-arm64": "0.25.10", + "@esbuild/linux-ia32": "0.25.10", + "@esbuild/linux-loong64": "0.25.10", + "@esbuild/linux-mips64el": "0.25.10", + "@esbuild/linux-ppc64": "0.25.10", + "@esbuild/linux-riscv64": "0.25.10", + "@esbuild/linux-s390x": "0.25.10", + "@esbuild/linux-x64": "0.25.10", + "@esbuild/netbsd-arm64": "0.25.10", + "@esbuild/netbsd-x64": "0.25.10", + "@esbuild/openbsd-arm64": "0.25.10", + "@esbuild/openbsd-x64": "0.25.10", + "@esbuild/openharmony-arm64": "0.25.10", + "@esbuild/sunos-x64": "0.25.10", + "@esbuild/win32-arm64": "0.25.10", + "@esbuild/win32-ia32": "0.25.10", + "@esbuild/win32-x64": "0.25.10" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.36.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "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.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.22", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.22.tgz", + "integrity": "sha512-atkAG6QaJMGoTLc4MDAP+rqZcfwQuTIh2IqHWFLy2TEjxr0MOK+5BSG4RzL2564AAPpZkDRsZXAUz68kjnU6Ug==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.52.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.3.tgz", + "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.3", + "@rollup/rollup-android-arm64": "4.52.3", + "@rollup/rollup-darwin-arm64": "4.52.3", + "@rollup/rollup-darwin-x64": "4.52.3", + "@rollup/rollup-freebsd-arm64": "4.52.3", + "@rollup/rollup-freebsd-x64": "4.52.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.3", + "@rollup/rollup-linux-arm-musleabihf": "4.52.3", + "@rollup/rollup-linux-arm64-gnu": "4.52.3", + "@rollup/rollup-linux-arm64-musl": "4.52.3", + "@rollup/rollup-linux-loong64-gnu": "4.52.3", + "@rollup/rollup-linux-ppc64-gnu": "4.52.3", + "@rollup/rollup-linux-riscv64-gnu": "4.52.3", + "@rollup/rollup-linux-riscv64-musl": "4.52.3", + "@rollup/rollup-linux-s390x-gnu": "4.52.3", + "@rollup/rollup-linux-x64-gnu": "4.52.3", + "@rollup/rollup-linux-x64-musl": "4.52.3", + "@rollup/rollup-openharmony-arm64": "4.52.3", + "@rollup/rollup-win32-arm64-msvc": "4.52.3", + "@rollup/rollup-win32-ia32-msvc": "4.52.3", + "@rollup/rollup-win32-x64-gnu": "4.52.3", + "@rollup/rollup-win32-x64-msvc": "4.52.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz", + "integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.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": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..82deee1 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,27 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.1.1", + "react-dom": "^19.1.1" + }, + "devDependencies": { + "@eslint/js": "^9.36.0", + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "@vitejs/plugin-react": "^5.0.3", + "eslint": "^9.36.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.4.0", + "vite": "^7.1.7" + } +} diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000..01997df --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,213 @@ +:root { + --primary-color: #4f46e5; + --secondary-color: #06b6d4; + --background: #f8fafc; + --card-background: #ffffff; + --text-primary: #1e293b; + --text-secondary: #64748b; + --border-color: #e2e8f0; + --success-color: #10b981; + --error-color: #ef4444; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background: var(--background); + color: var(--text-primary); + line-height: 1.6; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; +} + +.header { + text-align: center; + margin-bottom: 3rem; +} + +.header h1 { + font-size: 2.5rem; + color: var(--primary-color); + margin-bottom: 0.5rem; +} + +.subtitle { + color: var(--text-secondary); + font-size: 1.1rem; +} + +.user-card { + background: var(--card-background); + border-radius: 12px; + padding: 2rem; + box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); +} + +.user-card h2 { + font-size: 1.75rem; + margin-bottom: 1.5rem; + color: var(--text-primary); +} + +.info-grid { + display: grid; + gap: 1rem; + margin-bottom: 2rem; +} + +.info-item { + display: flex; + padding: 0.75rem; + background: var(--background); + border-radius: 8px; +} + +.info-item .label { + font-weight: 600; + color: var(--text-secondary); + min-width: 150px; +} + +.info-item .value { + color: var(--text-primary); + font-weight: 500; +} + +.roles-section { + margin: 2rem 0; + padding-top: 2rem; + border-top: 1px solid var(--border-color); +} + +.roles-section h3 { + font-size: 1.25rem; + margin-bottom: 1rem; + color: var(--text-primary); +} + +.roles-list { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; +} + +.role-badge { + display: inline-flex; + flex-direction: column; + padding: 0.5rem 1rem; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + color: white; + border-radius: 8px; + font-weight: 500; +} + +.role-name { + font-size: 0.95rem; + font-weight: 600; +} + +.role-description { + font-size: 0.75rem; + opacity: 0.9; + margin-top: 0.25rem; +} + +.links-section { + margin-top: 2rem; + padding-top: 2rem; + border-top: 1px solid var(--border-color); +} + +.links-section h3 { + font-size: 1.25rem; + margin-bottom: 1rem; + color: var(--text-primary); +} + +.links-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1rem; +} + +.link-card { + display: block; + padding: 1.25rem; + background: var(--background); + border: 1px solid var(--border-color); + border-radius: 8px; + text-decoration: none; + color: inherit; + transition: all 0.2s ease; +} + +.link-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + border-color: var(--primary-color); +} + +.link-card h4 { + color: var(--primary-color); + margin-bottom: 0.5rem; + font-size: 1.1rem; +} + +.link-card p { + color: var(--text-secondary); + font-size: 0.9rem; +} + +.no-links { + color: var(--text-secondary); + font-style: italic; +} + +.loading, .error { + text-align: center; + padding: 3rem; + font-size: 1.25rem; +} + +.loading { + color: var(--text-secondary); +} + +.error { + color: var(--error-color); +} + +.error h2 { + margin-bottom: 1rem; +} + +@media (max-width: 768px) { + .container { + padding: 1rem; + } + + .header h1 { + font-size: 1.75rem; + } + + .user-card { + padding: 1.5rem; + } + + .links-grid { + grid-template-columns: 1fr; + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..3b39090 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,113 @@ +import { useState, useEffect } from 'react' +import './App.css' + +function App() { + const [userInfo, setUserInfo] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + // Получаем информацию о пользователе от бэкенда + fetch('/api/user-info') + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + return response.json() + }) + .then(data => { + setUserInfo(data) + setLoading(false) + }) + .catch(err => { + setError(err.message) + setLoading(false) + }) + }, []) + + if (loading) { + return ( +
+
Загрузка...
+
+ ) + } + + if (error) { + return ( +
+
+

Ошибка

+

{error}

+
+
+ ) + } + + return ( +
+
+

Dex Authentication Demo

+

Демонстрация аутентификации через DexAuthenticator

+
+ +
+

Информация о пользователе

+
+
+ Email: + {userInfo.email} +
+
+ Полное имя: + {userInfo.full_name} +
+ {userInfo.organization && ( +
+ Организация: + {userInfo.organization.name} +
+ )} +
+ +
+

Роли

+
+ {userInfo.roles.map(role => ( +
+ {role.name} + {role.description && ( + {role.description} + )} +
+ ))} +
+
+ +
+

Доступные ресурсы

+ {userInfo.available_links.length > 0 ? ( +
+ {userInfo.available_links.map(link => ( + +

{link.title}

+ {link.description &&

{link.description}

} +
+ ))} +
+ ) : ( +

У вас нет доступных ресурсов

+ )} +
+
+
+ ) +} + +export default App diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..3739253 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,10 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; +} + +body { + margin: 0; + min-height: 100vh; +} diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx new file mode 100644 index 0000000..b9a1a6d --- /dev/null +++ b/frontend/src/main.jsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.jsx' + +createRoot(document.getElementById('root')).render( + + + , +) diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..83ed612 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + server: { + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + } + } + } +}) diff --git a/k8s/backend.yaml b/k8s/backend.yaml new file mode 100644 index 0000000..1627f17 --- /dev/null +++ b/k8s/backend.yaml @@ -0,0 +1,73 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: backend-config + namespace: python-navigator-demo +data: + DB_HOST: postgres + DB_PORT: "5432" + DB_NAME: python-navigator-demo + DB_USER: python-navigator-demo + DEX_ISSUER: https://dex.127.0.0.1.sslip.io/ +--- +apiVersion: v1 +kind: Secret +metadata: + name: backend-secret + namespace: python-navigator-demo +type: Opaque +stringData: + DB_PASSWORD: python-navigator-demo +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend + namespace: python-navigator-demo +spec: + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + spec: + containers: + - name: backend + image: navigator-demo-backend:latest + imagePullPolicy: Never # Для локальной разработки + ports: + - containerPort: 8000 + envFrom: + - configMapRef: + name: backend-config + - secretRef: + name: backend-secret + livenessProbe: + httpGet: + path: /api/health + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /api/health + port: 8000 + initialDelaySeconds: 5 + periodSeconds: 5 + +--- +apiVersion: v1 +kind: Service +metadata: + name: backend + namespace: python-navigator-demo +spec: + selector: + app: backend + ports: + - port: 8000 + targetPort: 8000 + type: ClusterIP + diff --git a/k8s/dex-authenticator.yaml b/k8s/dex-authenticator.yaml new file mode 100644 index 0000000..b430859 --- /dev/null +++ b/k8s/dex-authenticator.yaml @@ -0,0 +1,12 @@ +apiVersion: deckhouse.io/v1 +kind: DexAuthenticator +metadata: + name: python-navigator-demo-auth + namespace: python-navigator-demo +spec: + applicationDomain: python-navigator-demo.127.0.0.1.sslip.io + sendAuthorizationHeader: true + applicationIngressCertificateSecretName: python-navigator-demo-tls + applicationIngressClassName: nginx + keepUsersLoggedInFor: 24h + diff --git a/k8s/frontend.yaml b/k8s/frontend.yaml new file mode 100644 index 0000000..94d43dd --- /dev/null +++ b/k8s/frontend.yaml @@ -0,0 +1,34 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend + namespace: python-navigator-demo +spec: + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + spec: + containers: + - name: frontend + image: python-navigator-demo-frontend:latest + imagePullPolicy: Never + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend + namespace: python-navigator-demo +spec: + selector: + app: frontend + ports: + - port: 80 + targetPort: 80 + type: ClusterIP + diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml new file mode 100644 index 0000000..eb9615b --- /dev/null +++ b/k8s/ingress.yaml @@ -0,0 +1,41 @@ +# Единый Ingress с аутентификацией для всего приложения +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: python-navigator-demo + namespace: python-navigator-demo + annotations: + nginx.ingress.kubernetes.io/auth-signin: https://$host/dex-authenticator/sign_in + nginx.ingress.kubernetes.io/auth-url: https://python-navigator-demo-auth-dex-authenticator.python-navigator-demo.svc.cluster.local/dex-authenticator/auth + nginx.ingress.kubernetes.io/auth-response-headers: X-Auth-Request-User,X-Auth-Request-Email,Authorization + nginx.ingress.kubernetes.io/configuration-snippet: | + proxy_set_header X-Auth-Request-User $http_x_auth_request_user; + proxy_set_header X-Auth-Request-Email $http_x_auth_request_email; + proxy_set_header Authorization $http_authorization; +spec: + ingressClassName: nginx + tls: + - hosts: + - python-navigator-demo.127.0.0.1.sslip.io + secretName: python-navigator-demo-tls + rules: + - host: python-navigator-demo.127.0.0.1.sslip.io + http: + paths: + # API запросы идут напрямую в backend + - path: /api + pathType: Prefix + backend: + service: + name: backend + port: + number: 8000 + # Все остальное идет в frontend + - path: / + pathType: Prefix + backend: + service: + name: frontend + port: + number: 80 + diff --git a/k8s/namespace.yaml b/k8s/namespace.yaml new file mode 100644 index 0000000..2b28611 --- /dev/null +++ b/k8s/namespace.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: python-navigator-demo + diff --git a/k8s/postgres.yaml b/k8s/postgres.yaml new file mode 100644 index 0000000..57c829b --- /dev/null +++ b/k8s/postgres.yaml @@ -0,0 +1,199 @@ +apiVersion: v1 +kind: Secret +metadata: + name: postgres-secret + namespace: python-navigator-demo +type: Opaque +stringData: + POSTGRES_DB: python-navigator-demo + POSTGRES_USER: python-navigator-demo + POSTGRES_PASSWORD: python-navigator-demo +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-pvc + namespace: python-navigator-demo +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: postgres-init + namespace: python-navigator-demo +data: + init.sql: | + -- Создание таблиц для демо-приложения + + -- Организации + CREATE TABLE IF NOT EXISTS organizations ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + -- Пользователи + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + email VARCHAR(255) UNIQUE NOT NULL, + full_name VARCHAR(255) NOT NULL, + organization_id INTEGER REFERENCES organizations(id), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + -- Роли + CREATE TABLE IF NOT EXISTS roles ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) UNIQUE NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + -- Связь пользователей и ролей + CREATE TABLE IF NOT EXISTS user_roles ( + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE, + assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (user_id, role_id) + ); + + -- Доступные ссылки + CREATE TABLE IF NOT EXISTS links ( + id SERIAL PRIMARY KEY, + title VARCHAR(255) NOT NULL, + url VARCHAR(512) NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + + -- Связь ролей и доступных ссылок + CREATE TABLE IF NOT EXISTS role_links ( + role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE, + link_id INTEGER REFERENCES links(id) ON DELETE CASCADE, + PRIMARY KEY (role_id, link_id) + ); + + -- Заполнение тестовыми данными + + -- Организации + INSERT INTO organizations (name) VALUES + ('Acme Corporation'), + ('Tech Innovators Inc'), + ('Global Solutions Ltd'); + + -- Роли + INSERT INTO roles (name, description) VALUES + ('admin', 'Администратор с полным доступом'), + ('developer', 'Разработчик с доступом к техническим ресурсам'), + ('user', 'Обычный пользователь с базовым доступом'), + ('manager', 'Менеджер с доступом к управленческим ресурсам'); + + -- Пользователи (используйте реальные email из Dex) + INSERT INTO users (email, full_name, organization_id) VALUES + ('egor.muratov@gmail.com', 'Иван Администраторов', 1), + ('developer@example.com', 'Мария Разработчикова', 2), + ('user@example.com', 'Петр Пользователев', 3), + ('manager@example.com', 'Анна Менеджерова', 1); + + -- Назначение ролей пользователям + -- admin@example.com - admin + developer + INSERT INTO user_roles (user_id, role_id) VALUES + (1, 1), -- admin role + (1, 2); -- developer role + + -- developer@example.com - developer + user + INSERT INTO user_roles (user_id, role_id) VALUES + (2, 2), -- developer role + (2, 3); -- user role + + -- user@example.com - user + INSERT INTO user_roles (user_id, role_id) VALUES + (3, 3); -- user role + + -- manager@example.com - manager + user + INSERT INTO user_roles (user_id, role_id) VALUES + (4, 4), -- manager role + (4, 3); -- user role + + -- Ссылки + INSERT INTO links (title, url, description) VALUES + ('Панель администрирования', 'https://admin.example.com', 'Управление системой'), + ('Мониторинг', 'https://monitoring.example.com', 'Grafana и Prometheus'), + ('CI/CD', 'https://ci.example.com', 'Jenkins/GitLab CI'), + ('Документация API', 'https://docs.example.com', 'Swagger документация'), + ('Git Repository', 'https://git.example.com', 'Репозиторий проекта'), + ('Дашборд проектов', 'https://projects.example.com', 'Управление проектами'), + ('Отчеты', 'https://reports.example.com', 'Аналитика и отчеты'), + ('База знаний', 'https://wiki.example.com', 'Корпоративная wiki'); + + -- Связь ролей и ссылок + -- admin - доступ ко всему + INSERT INTO role_links (role_id, link_id) VALUES + (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8); + + -- developer - технические ресурсы + INSERT INTO role_links (role_id, link_id) VALUES + (2, 2), (2, 3), (2, 4), (2, 5), (2, 8); + + -- user - базовые ресурсы + INSERT INTO role_links (role_id, link_id) VALUES + (3, 8); + + -- manager - управленческие ресурсы + INSERT INTO role_links (role_id, link_id) VALUES + (4, 6), (4, 7), (4, 8); +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + namespace: python-navigator-demo +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: postgres:15-alpine + ports: + - containerPort: 5432 + envFrom: + - secretRef: + name: postgres-secret + volumeMounts: + - name: postgres-storage + mountPath: /var/lib/postgresql/data + - name: init-script + mountPath: /docker-entrypoint-initdb.d + volumes: + - name: postgres-storage + persistentVolumeClaim: + claimName: postgres-pvc + - name: init-script + configMap: + name: postgres-init +--- +apiVersion: v1 +kind: Service +metadata: + name: postgres + namespace: python-navigator-demo +spec: + selector: + app: postgres + ports: + - port: 5432 + targetPort: 5432 + type: ClusterIP + diff --git a/test-deployment.sh b/test-deployment.sh new file mode 100755 index 0000000..7d144cf --- /dev/null +++ b/test-deployment.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +echo "=== Тест развертывания Dex Demo Application ===" +echo + +# Проверка статуса подов +echo "1. Статус подов:" +~/.kind-d8/kubectl get pods -n navigator-demo +echo + +# Проверка DexAuthenticator +echo "2. Статус DexAuthenticator:" +~/.kind-d8/kubectl get dexauthenticator -n navigator-demo +echo + +# Проверка Ingress +echo "3. Статус Ingress:" +~/.kind-d8/kubectl get ingress -n navigator-demo +echo + +# Проверка логов backend +echo "4. Логи Backend (последние 5 строк):" +~/.kind-d8/kubectl logs -n navigator-demo -l app=backend --tail=5 +echo + +# Проверка доступности приложения +echo "5. Тест доступности приложения:" +echo "URL: https://navigator-demo.127.0.0.1.sslip.io" +HTTP_STATUS=$(curl -k -s -o /dev/null -w "%{http_code}" https://navigator-demo.127.0.0.1.sslip.io) +echo "HTTP Status: $HTTP_STATUS" + +if [ "$HTTP_STATUS" = "200" ]; then + echo "✅ Frontend доступен!" +elif [ "$HTTP_STATUS" = "302" ] || [ "$HTTP_STATUS" = "307" ]; then + echo "✅ Frontend правильно требует аутентификации!" +else + echo "❌ Ошибка доступа к frontend" +fi + +# Проверка API (должен требовать аутентификации) +echo "6. Тест API endpoint:" +echo "URL: https://navigator-demo.127.0.0.1.sslip.io/api/user-info" +API_STATUS=$(curl -k -s -o /dev/null -w "%{http_code}" https://navigator-demo.127.0.0.1.sslip.io/api/user-info) +echo "API Status: $API_STATUS" + +if [ "$API_STATUS" = "401" ] || [ "$API_STATUS" = "302" ] || [ "$API_STATUS" = "307" ]; then + echo "✅ API правильно требует аутентификации!" +elif [ "$API_STATUS" = "500" ]; then + echo "⚠️ API возвращает 500 (возможно, проблема с DexAuthenticator)" +else + echo "❓ Неожиданный статус API: $API_STATUS" +fi +echo + +# Проверка health endpoint +echo "6. Тест health endpoint:" +~/.kind-d8/kubectl exec -n navigator-demo deployment/backend -- curl -s http://localhost:8000/api/health +echo +echo + +echo "=== Инструкции по тестированию ===" +echo "1. Откройте браузер и перейдите на https://navigator-demo.127.0.0.1.sslip.io" +echo "2. Вас должно перенаправить на страницу аутентификации Dex" +echo "3. После входа вы увидите информацию о пользователе и доступные ресурсы" +echo +echo "Тестовые пользователи (убедитесь, что они есть в вашем Dex):" +echo "- admin@example.com (Иван Администраторов)" +echo "- developer@example.com (Мария Разработчикова)" +echo "- user@example.com (Петр Пользователев)" +echo "- manager@example.com (Анна Менеджерова)"