From 0745dd903d1912f0a33b529aff98f282cb64a837 Mon Sep 17 00:00:00 2001 From: seriouselly Date: Tue, 2 Jun 2026 02:42:07 +0700 Subject: [PATCH] feat: update landing page and add new library and scan pages - Refactor landing page content and links for better user experience. - Introduce a new library page to display disease information with filtering and modal details. - Add a scan page for users to upload images for diagnosis with a preview modal. - Enhance login and registration pages with automatic navigation to the dashboard. - Integrate vite-tsconfig-paths for improved path resolution in Vite configuration. --- apps/web/package.json | 3 +- apps/web/src/app.tsx | 87 ++-- apps/web/src/assets/images/dashboard-bg.png | Bin 0 -> 58808 bytes apps/web/src/components/layout/footer.tsx | 9 + .../web/src/components/layout/main-layout.tsx | 17 + apps/web/src/components/layout/navbar.tsx | 84 ++++ apps/web/src/components/ui/button.tsx | 41 +- apps/web/src/components/ui/modal-footer.tsx | 12 + apps/web/src/components/ui/modal-header.tsx | 18 + apps/web/src/components/ui/modal.tsx | 82 ++++ apps/web/src/data/mock-diseases.ts | 61 +++ apps/web/src/env.d.ts | 4 + apps/web/src/pages/dashboard-page.tsx | 437 ++++++++++-------- apps/web/src/pages/landing-page.tsx | 51 +- apps/web/src/pages/library-page.tsx | 182 ++++++++ apps/web/src/pages/login-page.tsx | 36 +- apps/web/src/pages/register-page.tsx | 38 +- apps/web/src/pages/scan-page.tsx | 143 ++++++ apps/web/vite.config.ts | 3 +- bun.lock | 8 +- 20 files changed, 1029 insertions(+), 287 deletions(-) create mode 100644 apps/web/src/assets/images/dashboard-bg.png create mode 100644 apps/web/src/components/layout/footer.tsx create mode 100644 apps/web/src/components/layout/main-layout.tsx create mode 100644 apps/web/src/components/layout/navbar.tsx create mode 100644 apps/web/src/components/ui/modal-footer.tsx create mode 100644 apps/web/src/components/ui/modal-header.tsx create mode 100644 apps/web/src/components/ui/modal.tsx create mode 100644 apps/web/src/data/mock-diseases.ts create mode 100644 apps/web/src/env.d.ts create mode 100644 apps/web/src/pages/library-page.tsx create mode 100644 apps/web/src/pages/scan-page.tsx diff --git a/apps/web/package.json b/apps/web/package.json index 03f9120..51a996d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -29,6 +29,7 @@ "@types/react-dom": "^19.2.3", "postcss": "^8.5.15", "typescript": "^6.0.3", - "vite": "^8.0.14" + "vite": "^8.0.14", + "vite-tsconfig-paths": "6.1.1" } } diff --git a/apps/web/src/app.tsx b/apps/web/src/app.tsx index 29a38cc..cd597a9 100644 --- a/apps/web/src/app.tsx +++ b/apps/web/src/app.tsx @@ -1,47 +1,72 @@ -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { createBrowserRouter, RouterProvider } from 'react-router-dom'; -import { AuthGuard } from '@/components/auth-guard'; -import { DashboardPage } from '@/pages/dashboard-page'; -import { LandingPage } from '@/pages/landing-page'; -import { CatalogPage } from '@/pages/catalog-page'; -import { DiseaseDetailPage } from '@/pages/disease-detail-page'; -import { DiagnosisDetailPage } from '@/pages/diagnosis-detail-page'; -import { ExpertReviewsPage } from '@/pages/expert-reviews-page'; -import { LoginPage } from '@/pages/login-page'; -import { RegisterPage } from '@/pages/register-page'; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { + createBrowserRouter, + RouterProvider, + Navigate, +} from "react-router-dom"; +import { DashboardPage } from "@/pages/dashboard-page"; +// import { LandingPage } from "@/pages/landing-page"; +import { ScanPage } from "@/pages/scan-page"; +import { LibraryPage } from "@/pages/library-page"; +import { CatalogPage } from "@/pages/catalog-page"; +import { DiseaseDetailPage } from "@/pages/disease-detail-page"; +import { DiagnosisDetailPage } from "@/pages/diagnosis-detail-page"; +import { ExpertReviewsPage } from "@/pages/expert-reviews-page"; +// import { LoginPage } from "@/pages/login-page"; +// import { RegisterPage } from "@/pages/register-page"; +import { MainLayout } from "@/components/layout/main-layout"; const queryClient = new QueryClient(); const router = createBrowserRouter([ - { path: '/', element: }, - { path: '/login', element: }, - { path: '/register', element: }, + // { + // path: "/", + // element: ( + // + // + // + // ), + // }, { - path: '/dashboard', + path: "/", + element: , + }, + { + path: "/scan", element: ( - + + + + ), + }, + { + path: "/library", + element: ( + + + + ), + }, + // { path: "/login", element: }, + // { path: "/register", element: }, + { + path: "/dashboard", + element: ( + - + ), }, { - path: '/diagnoses/:id', - element: ( - - - - ), + path: "/diagnoses/:id", + element: , }, { - path: '/expert/reviews', - element: ( - - - - ), + path: "/expert/reviews", + element: , }, - { path: '/catalog', element: }, - { path: '/catalog/:slug', element: }, + { path: "/catalog", element: }, + { path: "/catalog/:slug", element: }, ]); export function App() { diff --git a/apps/web/src/assets/images/dashboard-bg.png b/apps/web/src/assets/images/dashboard-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..613383d2ba2b17e2c402d1fbbdedfdee75d84f1e GIT binary patch literal 58808 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�oUSH==^IEvKS*%# zB`-9*yuA1Vd}n4!zr91q(AozW1_8V2iu#_Y45?DxUO&`FP7)$^`ccptqkxpUN^U)tMcIy_v`G$Vl$V6hT}E!t(%;+D(ZH2}ajgw={BxoTX$w1GI-nV6Vx zj2}!*EiKX4fkO_!!a{^VXm-2YDC8oeu(YJ48-U9R@YU`*GXvZ>XFer-T5GP zp^YN5`h6pn9t!YffD_3*04ByOFIR~0*x2Au91=X`)fSKMOh_Gn?UBox9ggww1JB-`-XT-T{~hFK_X6=n z85J58hR#m;=wrD{J~}@?KRQW(CsoY|nXDZt*$6Ks6dLDTYip3Ou}oM}aP{id67cOV zsVP2FaI@vsCzs1TXBL$TPM`hyY)x%#9&pP5-P+vku6DJ#||S8Dul)glcA-TpKk>y^r;J9USZ3;d;Joj2+AF1ccvw|`zp7bd zT|RI)mgj#~LcuC9wk}&$qw-FrQYi-D;U=|OJ@@F@Umnf1wGGsDbd;Bu-w-v4#hsm< z3&ZrbRM+EqsCaTzj-%h-4`bFuRhQ1h83x}q*BpZ_fP}v(t`G=>r3D4W=WYxPv?1Sj zl-CrWg@S8p%aNix>RJXUMF>ibO^xW$?v?~XYMifS= zelJapKsucn@p!-}G#zM_Lck^yE5NYPHYwvf6(%Ef6uvMy>Og?;4KTufnaX=A#5BVTz7*S$9A)9vSdJxFj*S8?5eQtk4;N_n;rWprX!PW_ilFU| zz!8820Eb({2-+JY7<(8QdWi^EQi~+Xa&f*1_O7gWXTeyhiaYyy`z-YuE&JGD0KR_x z(uprbivLn|z9DJvdmMK@b=LNg@?15Jkeu|>^rru;YOq1pEth5oF z2VTq}Ls~|$W83g(f@x+1Ym2aTIUPf5Xm*mrfx#dylfZ1N?xQVkuy)^{@6TH2ORKH? zx9{uwet+Jd@9)=7jmGGMN7E6}_O2e*qm27}5raXsFdHj>Ec#WDpw&uOIpocGY-~&> zGfuhPw0re3pLc`rB>1*!Q%0HI?;q=QwYl@jxkM}mn^t@1lsk_{FEgHw&u>>&-X`o~ z@f9AhZs_S1q-t%`P2mtM**w{CTL-*1z8UJ^0fTUmsPw$hjmD^ zl-YDH>izm;GC4Olmn6nM6=Akmda9pJPB4jSXp|(goMR5n1nhKv6>u8UNczDvAC+iA zGwBio%=i+70^mWc)D<=C_|0nqR3MLD=Nn(9U7jvJe?U@NV;SiKY!~CW4!sr%Q6k(v z63*cX@aRiGxKyIgC3z27{F=@8TNz+8uwWHZBEX?n&Tn8OVKE*b0Mqrq4BSqMb}&$632-f}bdrJx&H=_pmWV>8P!XHt z3~mm4EEH-5Qx1fuL7yCzQu7*rnbG6pH*2^70N0iq&b;=O@A7(W`3)dlPNsY-isnjd>LRG;5&l9=jbLUc-~K8a%Dvq)m{2NmI7YpW+nr^OQkRM?kcZf z0!&FOJUtDEp6D}J<6#0kVD%ATnGuro>s2srgilmppGRi2-}3mL7*s0%yLbM@t&kyP zfvW+r0RkR;*4e3tTJlpVml@UxzFG!2(At{n&nNU_@1EUzB#{U2Oc7v}9)L0Vd85(p zw!_xP$A9@{YwOpQ;)Y9bjhZ7SVX>z8;ziLYmb85P?T}6=lu8LO1bm#b12X604Sl5svmZXa^dp8+|jE z&2q!T3erbol6k&7;HU``ZDOb6(cM|s%NnAC_3eW%(BBbwY%iY@=G}R{0hq$?G5LCJ znG#feo#ab{-q|`#N!+3@GR`yVW_xARxJ9NeZ1RS?GEE5wm6FPIMUbWz<zrrh%U-uzy*I@23QX`h(swps`HjdqK}=K&56(#gWYtRd7xr;zsPO7+@&4I7_Fzo&uH;U>O69q^qUs$nbV_kel8~+3!*fe%JXgxjdN4Cw5g-dN0#1 z7cGXA#%<&Qe`J80dB7r_NSxRL;N*1y%vmA<-y7exNKXM1Umoy3{{ELXZ9pzS#xm|Y z90EmqChH9?&Z@AK<~ANbKBr)Ga1m5~pQBsBXI6-*pyYVIM3{?naRLkhkDLN#4xPzD z*lCo(C@Gi-pYdf5O&*QvT4w!5Zj;ZwF4@ap%6|2wSLm+{jY#cB+u;2PEq|JHNi zX{?-5J3?`qUsfEaXaHU(z%XR=tix1Fumnl$nf=gnC#Pu7z5m41t?pH}NWhyDbk5Pt z%ufL9Ko8N4P9I@P58Sm--#ML zMSwr&Az{itz)O6g;GkBcvHSh{1wG27JmBijCzGhWm#=CufcBs;yTyGyB9g3Q*|3rmbI3Amf%70eOf3?Eh-3eGU*4Zw}T{GGaDLX4I2;lrEv zCr5O1s-&k?7!(=aXf3)7TRuEItTV8M+&aS=r2jxxgZ-c@YdF+*h`1FbsK=M+re z+XP<<{Lh_ujp?lO#bU_Y!zt@01JwUg1upMV!lGj=;occiFf!IaA6-7!>WER-XgCGz z?Xz0xsDb7Tu#Z|I|7b*ZX8Ia3)z^$V$jUAIctI})4I!L%b zkJ9VDdNIfpcw2|;rFG}Wcemz$UwykVcm`P1d_5m4#}fTbQmIfPxYY6|z+V;o6ojvc zh9pv{M4a4>SOr}Qr84^hD-nd@DGV+_P9%0C5g$c&kGf~4q%5(PC)dTHFXFnn?l^@j zNCy7wi#2i9H)7O(Z*{SdyQnmam^!uOxHV&ep_zpPORoyL?GRjt&AyzNUE-vDZ>aNVDCvj5)9IC|Bm5Rebh{?cR<~wdSlX-Uv-O=Rz$=i<}-IIfJARq=|nR+o~ zRT*7!d7xPwbhVK^lLkAe8`2T>2d`Fs`)%db)Zn1_S~Eo~^uX8K9=w|Sxk)4uUb=Ki zNC`_Q(Xm15#bEcxT4)vt@Ndvq<&B{QOjd8)l9w6Yn0(<#y}oDO^+X@<5(jE*y~Kw3qV@f#@7)AISKTOh+*&< zECb#6hsE$DV)5>L+N@L>n;gFr;2jHPaj&(hVKl)n8o@e|8qdz zBx(u-|IgO>g|wBXVLW*2;)T74cvUFO&_H2?xo|J!B!MJY3^e0L*c4L}G>NSq3XI$MUM!dRs`Lrk5DmqHmv!kS9U2tw*kFYG?gdrn3#_Va%{ zistt{@B6&(_nmVvZjS1JDabaZsbl8R^XJ)OY;Z6dZDS+LP}@*&7>$wtpSsu_C_l;DhghXk2#(_c<{_oF85#9=yF-TbEn^__B%YwG{G2f zSXyJ!>rGt({64oRIgo&nR(HsEIS*=81tV2f0c$NMnCJYbk6J}V_cN;#23dL00H2?~ z{rQ(|`eE+VZV$l)wd0o_>44#~{wBnD;<+qS%^DN9JLKbsQi_h%ZJchn{ z0z)evzkSN@DV5_Hq~WGLMUw`w9NH}+AE_S z9i#YK0}R55&pKeyFV!!Aqb7r~(a>=7oATqdO9Px6disnKcDa~_=V7)882`{6glLDu zW0sb}#)c&xxWMa`7)`*7XK&s}0nCzzO%X_V=WJ{3Lct(>88D{7&f=%H&~F0eRN}7~ zO?0H&t^wWxV6AsG0d>(-g(mo&S-Cy;gj`Z&p?yIIylA#D@54KX7G#5og&W4$l!37P zoha&uJYzq==T5miC#9-@UmTyFo|gWY&;h3a*moa*?P@lwuD{sd-+!^Gszs#L;LDd? zU;Zr_0bo?c4rfb{3%=5$L}BG@N$>8thKHTO)+7PX@FFhV;bb`0or#5;?*T9ggRjXH z>uSMs+x0IK07I@iV7hDpY;04WzKFiU1L+bl-l~8pVO{+?;F{W| zUI2Ev3|6nrY&P3getd>GYEP%tmLPi_MX~v8c=Qi<25gBfAkNd0nlA=dkK?)bKkQUR z3BYG({D^2Rru#@P&MO{EB)lz453Fq<-FF{%*gJ$l%D3wZMg%5d%!(scM(0OAUjXK2 zPsUfkRxF%kO^` z9N*l1ZFf2|v5tWb;$`=0U^JF_n90N_;pTe)98n@BgDKM6_42FMQAGhAC8{S-Nue60 zTTQI3H0UEE0vLo9IB`XRKW@|z*BD2tczGm@RgIxmX=sSn)iu<@uYdF1W7m|y3fE+Y z;^HemK1H!tR_9xQ_fZk^R|D*Dd3-ht&}|^}dcBwUn!z`ad!KvrQFMvm$X1pH7%BT_ z0zT3K&+0Lnhe@M4xBTg?*3w)~5O6j3GQkQ&c=q<$HQ9al_IyGv@uW*u2(u|+^>Tv| zKjd=eoHwx{{(4IjI5R4@oe15YxfC~60I=PK#N>y9d0U*-eF*q)Z_oB&f`A#ISpZ{J zbfyah^*{iF@Ikidc29>Rk%oo_MQIMBl68)|rw2jWF2m(|Rd7324_`anu`u~QgnS1^ zA;Vav$RnGZr!m3=tQd@lpS=hLMx()ic#NU(y`=+Iq7*Qrt`OD)+i2uhh}bncV55;! zfe#Y#h_SxWV7SsyS63IUi#8}tqscC{48Z2N$77@Kg%qDYbE<0pJOMw-f-qkD?Zd7< zugz=Ok<^Nc6Qhcl209nt;k{KfTcZPpkIqxTx`G85CHzMCN}Y_c{RW{`T3-1L4J?n= z0YkyRtkF|#fBg9IXoD5%l2*qMF%DnABVJ$6+#FB3L7kg3CpNTcg7;#vxt$-d2)Jji z^r2Kr`G)*<$1u#J>*PcO{A%^|@cxizXz~K^9B-i02Mb*;6b`sdyf2Us4vLwvP|VbL!H9F2~4bZ4>$2L&}-e15Qgkj+FC#A%bE z{_nM>Z@-1&>PMt@x};$CEuu{w>|Fz;%%lccPzDtJ}{EJv{o5|77i+go(Lxn=9(#Dvi|*}Oj3b}YwNFGy><;YxUqgjb4a7Q zO%eq^p~dQGind7z2H?8_xY78309O?($r>eCZ|}{UH{rNPqX0ZSH8o}M_Kexb;${I1 zq7ZZqGU~_;0KpNHnXPTwY7H35Vt?} zc>F74Lza-9$7dy#&uS)YQri#Z@7pbLtKr_zn9J!@(`CouZ+owihAYP>o*piI50$)g z$iI^*pRccz=LpI#1OZNj$ zl$vT8WE8+6V6Gz7T)oN(@_EhOyCb*xv(hY6Owr!f)>cUPGX!a*;Tv5)O?~%ogy^xS z_OTxG;*Pnq{|=nLI4u4HS+!6|r}57M@SN>3UcS&{vB(Sryk@+|_Y-nrvDT~1pK<9J zfVZ>^41sdhaEp07{vL#v7k55wSlK&960QQKb_FnhV0(LEVPUJChAb2+L0VoLPhd8~ z1^_XRI66=DKy5?3)@DK;s20;2UwUHtrN?$x=icrqilN=zz?j`R->*6k|Jd8x-TU*u ze~x(qJZ0Kbngn1tEZVdq?&P3iGG4n5z+bQrbzAFE z5b$kDwglV(!UIo6#6=0zOMq#!HNKydFa1>$SCe4=qODzBT{lMSu5gpUXAL*L8vg0K ze+ppd)3NySvdsm)+(?vF<*&{H?<p> zlaUu@*V-qwy`AyIM*9U|IC1c zm|T*O&>t8jau+cs!5HndNhtW@Cz>EutdWcw%a|qsQ#A>4(@a8a;a*y_g=n+p-Jl?s zhJd$h(oF75$6#gWd!9t?Wk0csF)8u$JLi1QpXYfTqv*gBVM=$2Z>dmpQX=DV7e3PW zrGNhWP0Bh%fo3RJs!}j#z(I3xNT;Rfaq=>g&u^hVkdM4A65rKOJWfmrEd%DS&p=r0 z4e;n${JvBymELRy;xprv5shFq(!gZoC65FB@ZERM9x-5b9WVjxIuDrDa^oFx(G-Ha z?lNFJCNia|({vXWO!hlPe7oxLyP-v~ux7U#+fCOmGT_VY-Jb9N@&JJKD#>is>8vnd zi`x>~geri4CcdLquOmhm15Rqs8LmJc@1H%Y0XqZ!@$bQ{Y!FN5qtbB0Adl1z?KTHNdrm7kuw@P1k>F?cOKx;KnpiKF@q*?5`BFp&X*oXn8zV1HM{Y4wjn^ z29pN|+Z&zyxLCe|-tqP}8Ej9D6%*dArN6nOVDJbY`qO`enogFvCvN#yUJqaBpaModF+mrL&eKX-HsUc1ka)Km;FJFCCXlmE+>pRyGrq5rgGwAx3~>E?qi(lAFuHfb*&Ro0pM@-r#4?7TUMO z<2+v;HyYy%n+ITsFid!CEdFSJTs0c2mNqRG;|Tnv9TPh$Rp|YnqMQtodZHNour=PvV>1P?it{88h|$lFj;URmpcO-J_q>qYrb93#Q(b`#?MVyVov46j}_peh|#s~^iLe=3p<$`pdF zL97x6V0u$WCnrbgOeRnF#ZM7yf4>tiF+=id=H6H!9*B<_M@H7xj6l3YS;w9Hb>n966MUkX|xQ9ux~=~@Y=PT9XC6EdlfGB{{dzfO^o{l zU+xXIHQwnGoyQtr5?@NuhJ^7`My;4d3fFqMl>sXWu*cJVtAQMC|D+15moTQ_-jqV_ zkhGd#jgY<~zLaUACV7sZAk1anbTE_20`S3}jepMd51USl?vkdHcd#86EuWQ|hu%oe zrH_tI$Z$U$B$K4E3Q2ab0x&6~I^chN&?GdPaxzJ=eO{|`kp(-7NQxX7B*D6<*kWQn zy!&~3m?E-Btc3ldix^IH+FUaFxePeum-NM@GFM9$ovIyj(bXT1vs;F(l-@~Ro_?`d zX29OLG|wyhw^reWe|p?cQHoy15VG8#Ss1xTb~+YV1K@kum39Z`Icx$i+Vz8!1~roj zS5hGr&sSkWW5=UUN$wdDDx<#fiHA3?eRJgsiRi|f0e2~*%IJB($dl;R27Q7rdixxL zF&XRQt^xq7r%ki7r+`~p6v~N-uN7p#b-?le4%ja>kixJ|4m=o8fv+(X^5d_O&l@5U zCj_*^!5&)s3&5RPN_J%M%_dQv+V0d+5{X*@*2NPR8k=pA*Euoa9_EWq6KSlY6H1gm zea;Dy1;8xEFruM31^iiSBPEwZftwg632-r1jKzutolJm{-@0^~c}lTf3_#uIT+D9%cX*YEO_WBli#8+ z1s%2XWc4vO4uk1no*J3!Zl#CP^I_i}RudpD(a!Z2F^Psao)>Jg0ys z&6)guT=_c&jLy=Hn*wl4&49anU0(tw6Q(?Rc^TD8=G%tQ+~r%Q>kRPp?3xKRD^8uh zYHUKHvehp)nAn?uK4N4oaP1;UH|0D00002szP2 zWCy*-=Bw972%=x&j1Mz)fFGMw*2lA72=;6X7Rh z!mDWV@;HuJvM)@S1_#N6#gQFjWB7x^sx0)`JFA@TT@J(-+8;ais%8}&VsrDYOBe2W zg2BjUpeA;^)QTHz#P=IA;6B(M4A=+0eRuDYmLl~4Jvx}Nf!I-Y^fgV*WAa^`u8#I@Fck2srseRwJ6 zqU?y@1{tw|@V?9E<#G?}xWp(~@X7Jdk@xP9is+}Z=4{F!k*qFI+HBr(k1JZ+*WA*k zB$4Tp3&&~?hl9+(xUkTB>t6jd2Qfo+pFR*UDH^JKdr5yOj8^UEGIrT%ZocsAOTW6H z%4ZEb%hPqjQ&-zMZr|<@9-0A1eMpk}AdR{H(byODF<@WxP75~+A(-h$xSf=uIQ>=2 z#p@@-M{lr=|!&WM@t zzI&H9IG4n^&+Chl`)%pxYAE!c-YzK)o~iRA2$SE}hsOW#^#m3WiEEMwV<}6_+pmWS zn`(pwUdWy(TKZ8mdS>sF&Khs^ct3tjfiDBTWG9g|v!FZ&_{#0uS8iUtTeD!)DewRx z2H;Z@ULI7cryB3nO3un&tW5DK2`|#%-%RmprR(aJzNs5GpuxU60}L0Y@l92V81z;-yc42s&V~H%0#27R&ZZwfV9wM3&+#p^FRNaa;7PPS{r5Q&=TiEW1oi3WB z3B$m^yy*sOcaesjMZ3FDXTRtCZfs|NvC@`$t@+$@&iDMzIlue-yDY8MMHQ|Hs#f=(^$tgJnx%d-alBttg z#Gt~K98&5(|AyK)FuPply=VxDYBZX_w}70MT!B9X#{CU?yba(Rl8CU6+bJDn!a8?V ze^sFck`T{@DFq_FHG_Wt!jpv(`%Lb=48Z5kox6PbcVAZN_2xi%0dPA2-~9aZn;!uS z3#Lg0VZj9xp6v=F0P}`37JaYTw#t@a&D9D4__K@DY^fRU)oK~=g--#yBLvue5rBii zgaEA9hrC8<2uoBr3xqohVVEIL6_O;%B;+FiPC!yUHh3Uu9Lhp^MVF=Fr3_6#P}+Nv zTSj_`q$m%-?>8TB65uq4U^czUJ4<{<7kTN$2hU#_3>n${F(zj+nXMQM_@~sNDLDD&IqimQI@i-b zr$T!N2hp`qbb5Mk&uorL@<~3Mv099-K=oi?V1xkABYuZM(ivc{9e}Z@1F}u&vS|_9 z{Y_0ZF%Zph{cTGN)K@%W&_FOE)i-!L0%|Z8lQU z3;Bg>2p3I%Nh+&tF3bgELAYh0l1s$2Gp*ttkNfxU7Yz^hs>%dl4FeW@BkqVUzFp-W z4-;V7B$3Dp>6?*M6yQ1(!mOkTG4JHn)318khdw%jhfW-=N|FpsHDVmJIPv1F|W&CJvis5ntn4(IO=kROjZxx10RC0-F*fa ze8cTZiI0R5tavcs_m3o#4JEpv{O(Zec2(8+^VdPRvApz}w!HutY6@x6Z2~;p%YZA% z!4pj6yPpE?8c>$FhbkGc5`$G&FWf@ZrFJb%!9`QUostDd+&Xu>)vYe6DT1NNF-gQ5 zqnJ5!P=m{-gbDEk;xKIl6)YGAye=P&%0ph0b8HfTt)jPq?h%K>Lyu#UEn9e{ED0xa zA?hg@HEh)8;G%b7!Mkr+L+|I#PMNpWlL^<=!5rVbn(THuGkg)5X1c5nUnfnV72lw@!-^m5MytJUhMGF;ElsUeEHu(XnCP4>R-O0cHEcCiT$1F z>4WOMo!I)?9suu!Rub8F`AlrctJjYZV6%%x1gR)!jv5`fiF_7^QQttvCiGK?Y2w0C(K&3o>NcssOfnNK4b)zX-%M&2K{lmdS$C38_Pp zmp31ea@Hx<4y?y2_*yI}pmBvAG{yp>8JsR<>YJmJp9SjIbJWx_p+?<@^2y}hILn?KKZy17B$ zV51GqrMg-~nRM+~&r1fpn}=h@3L(U-9l(6o2gXMl zyoWQXnM3b(-)x*lxWh;?)g`>=Pjv8`%GtZ(F&?}%ogAz-|2;EaMdo{@_}rD>+^_g; zg+>5wSM_qxEW=BCt*n<!e1*coJ=o}`rPjIK0xm9X3HyT$8Hw|^gP;rDzfCb+GH9|JDJ~})+ z{IK2H*A*Ykvg30Ng)2>u!V%fB2y>O#P1G$N)uK0l1>9LaVJHz-3r-1Mxms@R^I& zYBl8pv(H|C&s`wT?zM^WP|ATzwF==27lZ{fT>`AwuPzdy2tp?Vz<%Rj0B|<|o8V9j zl_MS;grDZv$2M$L2zN4JYPLIKzM<#}9SR^hpv2DrBd_57zaTD#y9LSY22ZxU6^GsM zF*yICa|M=1ZMG>ARZHxnZ4V717X#o#bY)HY_2M+HgPP4Lff`Hz;gz#cz zz)K(;TD47{?RhABmsB0Lb<>GJl+<@i`sSS^(x}Ap27Zp`rx5oKYjY)ZkOO2uB=p*(l}D7 zssQ+waM8R-L9>4bT(#9t1tWduDatdI~_63pC2tqE4k#pT+FQ9E#3 z(l}7*j6FOsqL7<8qOrhYJFIg*@6Ye|v$lKSeq|Cd)O`Iu|Ns1CI}E}s;OQu`&`5Fs z@Y2EpMIdCojfNtLp`meNZ0r|xqiEZ5qPV~E{>vYKd^ts@$EQzNlyY>mbF>_6(`Vz- zJrj9Vxd^^wOzThR6_%FpQhb0`;8`Ge`dB?c>SlUFAU9}cX56)HG*IYn7NvmrpuS%> z@oY^Vn2{QQI62)0+e5y-$1G@KnSB0TvQQw`zTZinfj%rw=2qY4SGTq_Gqwga)?uco zqNHljA2?OJX#fBq07*naRG^TzR!Tm%PazFGS4JKYw=`E&+>>?eU6c`OnzerVc5B`1 z>vNUWouPpcuR)s4O%0%-Mg}ZN?F~roO<6^oFa^=xQ}eWmM~-fi4_48R&9|>QO^xJz z!8wy>9ApJuf$~wE0$521jmMSXC2^X%Z@>|`02#LF=f=e2`_PoF*<1((~};u7Ha z!~_Cc^i)tJc2?LOM|x?9S6$$6`qa)#fVhj@>!9#9hk_pI!<5-by1Ynuf4^R?QJx=? z;5N~bp}5Ozw-_}p7Xi-aDXM->7a%DPF)ZeCzpdtzcs&9f>g#*je3!mYvYd@30z5ZI zB27PV%6KP1*{&GzJyGs*3Upfa}fyD+%a71I(LLunkFUBfN-# z!bQR!Nb#6L82!cKwiuKJSUP9~LG}xiSb`$KeNChFJdk~Gy19(X6EY(Ol!x{Y zzI-6S&)cH11k=B$C|Q7bxc+Qtmku{ku(=gbEJ$Y3=``zbPIE-X45mxAwuWb7+ZG=M zzC;*^;~l^Lf_3$BD+wM??mUZ!F^D`n42L^9BKuD!I-bd`U{WJJ@x>^2x4}4R z>9)CC-KorP6oC}>(pcSiOQa~@m+RJKK8`csP$)D&(Fc8xS`+c@s=fTz>w!R^hWwXW z1}wEj5#YfhV2HGzRCL#+OP7B9`R|vit8>5qzGt+1qdsHW5CULRPjl0~YbBE04&G(H zu-m4mQ}5Q1*nkvInVL(qJyXN4NIwV2I|D%N{{bxXB@e5*2snfuX?ItHN?Hi4$4rXu zJk`vrg)z8gRx%z+&Vw!R7%6BH#sUL|Pjy=ur;vr6-Jx-yF?gf8(FrWfSkooAuJ~ds zkw_dqU&DOK+KT>T_Asq)%PJE=5ifRdi{VH#oTboo_dkzYU9Q-4vXD%sc#4yp@pL*W zJMzTWOrHpAjAC1YC++cd`+NqwhebFZf3n}f32tRnVj^rdU{jG*W6fBxSzpMc(E1wN zaB@Gc!xMHGV%CPGW5GpA8Z$^&)v$Mi$oY46HCA888Ztg63WcvwWFi4BDS<7$$5q`L zt(GDxYs(MrGI#ga>dKbaXsml&-Gbv>^U4f)T=G-!3l_N+G@C9C- z96p{C?jQReFnTFrP7{T~H-O1fZV6kG00XcX9@TB0zS-3G_d}t-z;4zcW8WF{j6PMLWTpA@o@v&0iYcJl6o+m<~qvd=z) z1%#uKc>Vsbk_@woeij$ydRc=l7t{RQV$=AxlRTdj%W!eR^^J zhVXpg#7|Zkjh5}EzT~22Mrt7-C2b=4eemi*rzxNSW_2-0c+1aEg&h4pDwUh;02rmr z;Roxp>n&a6DN2C#(k7K#Iu!FG5nYk6ULrgui*U-GV!+OA`W?2j9Uc}6!EYs)2*U!q zb_uZAi@gu0D|9)qu_nI#!R7fCjO(w=I}dlV!JtEopl^$wIv6k(8as4}1)gO^tTk;e z0-m0h<1my$jPUAEwI&g!r|h`^jU(^2 zCBUG~u@-sk0sj>RF3W3$)K*&rtWwe&mDd%(?ymK(d|Q#Pvz_e(w-y^z_(u>77o9aU^%EEz9fDIOTYfs=;>w*?g9C+JzR_b)$%FSawg?ZZK#@`tteKP+y2h zRQmx~-D861)1xh|E1o7|8EE3^T*NF47`8OvZ-14{kgEvgxn$Ij!q$Ld8Pvu<$S~9eiWtFO`n>W=`n~cfdR99DC zUQ)u2m&zXqpz?F=#*K1$M%Ry3pzgQ%<6{C$gm;ef`PIIG`vbEU>|Ks_oMm0)D)|E@ z()SeaqN^Dfh+6BfX$;g{&F$@Jx~4kUr#QVLk0O;wAm!9E11`U)qGbVA;9_ueu4VS? z>f5)k-w|M56w_eSsCX4{ef^&SW>?zbQ2_HGOMgZNyz!ELLF|zDA0isOppi5y06%Ws zb;VvL=Fgrdj1()Qx!mbQ`xI~f*9GYX3h6|`>>!^C6MZ5t#=uw~3Gnj!gZJ~x3GS$3 z!k&zTcW7v9X=!VV0soh-a|>xRP2;$FCrU0RV1f>FBV*Y>pn?~cB{(xn6C(XelTO4< zNprv$r5YoG7YQ1XG>j%>@ECMIg&ca2pk{(}x`VM&SV&Ebc z-!HMY>%J{|(dtEh-~Tzk&qs+^g5!30pFZoe@Rm~EYFHt{OI5;T`5*n`?!dwm(yvA^ z1&*Vm357PIZ9=VjK95~E;qb)Jx|JRc{gHAa%RiWj0C1O7DsBzszLba@HMc`OpK~({)vI&G0Hk%F|*1*QI-=Cgpl!?TSZ>t}hTX1`c za-yG{@twrR*_p2jc;0MYTsP<`D>FNs3%U5Kb10ar5+<|3v-$~Ew8mnI7#BnB1Ay@# zum@0)Dd7?!NqBb&PYO9=G7JlUr}T)TZH+;XYl!RXL*#~yrY7xg0L+c4)qSQBy?Kbl zYspSbV5s{$rf;fl4Ke6+X$`!(pu8yn@(!7)?l z{l||WOy?LM^5I;cU*->`S5}q`k4wcst7O_cJXJ>Ubf=Q*dU== zuLCefbFKs|gwT}^L*)?QMhdd#=E~)AM60&B`Te78T2u4m73T}UR(4mGf*foW%4VJW zJ|${c007&30hpAF$`b&Oqf>KQd>Y?TDk)!vdMkdO9KBUTKaTyKKBoY9-D=L8EqXI0 z*+b6KRyY8SRhoSI!Hg$nli{OV{-Z`B z@Et{Y3Ad}6iEfaJC}c&urcJ2*wzYlxZ6jI2Q1<Rp;_&gJAm9+Ov%@N?$c6Lz#fZ0+k@3eG9pRxHV2t$X11l)QFaPw7wC6@ptb=^WPd+NmN;+_ zQ^9poyeyO}>B->|sS{R%hM#{E46+ONbWGZ9%A<#tmL>?Wfe|JNFRm~A(`s>Qoc*i; znpqjaPRPa%V`0UjKT?c)!lvO!2+Q|X{Jz^+;z_{@8=_m50?kxg2&1D|fAQz*&EVTe zhm`>Vx1~k&-R;|QzaoiEF7FN_<>zD{H=obN@P>~mmsB*U?d?@)^5zyKlW{K z;7jtw6NF!Bw(1SM3XO#LKKf18MF2Ki*XIou@>NdjP$`o-NgnQ<5MKaRvG|;RCrgVt zK1okm3c%lBWyaX}xQih+saJ|-hah3{!h(h|LN@R(tHnZa32;VlH2y|_J36$X5pIN! z>0U-6Sl?2@Yq4vGdkTRo1qkl5vFr*Z4GXb_=-s(O?Ea4BWh{+4A`@e zml_o!;i(uJ1m!sZCNHd(O6Ad7tx;1tKYdU)=>_1azBP7}jKxjZY$)#*+gJ2Cg$zj( z+3U$_$M-A1lFx_1m6Bqorzu?c40u}Pmx|l$BineD-qZfA38V4h!>bfrvCslwbHxjj zsUs&`1*}S3(l0qgvZerx6ZDLYzjfzIaW_kkPJj7fc3}b6$NHU^4Wb`~5O-7>pWZA0 zrbu(e;=m#tYcYG0OeXh|&jDEI0|#`$U=SlxDSEzmo<&;-?dflUjY+l_;^?RWcp=w+l(j>XF5*c`b*0=#`Z zEE2ahMzu!6d17RWnq+Z z8Q*V^Igv^+q9d-D=xJAHr>&4qr;EjB&x#q1l5+mu-YXO=6gsL3zK}5S6(sxxVB|#s zb{B#GdvFDhO|zZlP8048CFMu`&)C|k*?yKilPBFs5%*L=WzjRh>rk+Di2$4RhWUIt z3Bp)wbC8I!=f@TtdrM(Cx51;@kxFn?z{Iz+Q-;MTU4#D6`=cQuyemi;=`e+EPv-AG z`sCDGZ_ek7Lkc7Dt*uA1$D|%8Fd5wzz>sUJKlC*x`dTpj|Qm8ef zO*h%)C42kV-|QW7DQ2IfQi1Syjf0`RRH_gfp$DeAM2-OO9q+A1#p0F@yHWr9tF53! z;>B{b;n90lz!K6dW!&P1azXQfC%~f^0SO!qGtX_^(bd&xw43cZ zWIot$<1@v5id3AL{3&PSj9RU>w`TUz`3(5N2UjKR^Tp%5j|I~wJE>5D6_Rjs7L_Xo zSOuwhbYtWk-DkP21k0r2zF&S?cyaf|!2KPRr|40@*2j%hikkpnjN_uqU{JCFTSM^- zi|kKM4)>C5k3=ZM9fV21;XooqeA7FSFGXK&iqzkNudrLp@A9j&+YSc!VS%$;K6o$cWWsZCsrXI)n7dJhec9a{|H#2Y~!C97C?MS@x4x6h}vfUcK7V&D40Y z8N6?8QkY|ALp&&{h{c5wWiBK|)x%r4AUfGB1Xq;p-ng;6%;uf(M2Rr0K~TG+_9=RZ z4mZnl!%-2NwOf{JBiZ-G?1)K`-cEBmvQosiwzdZ99he>X8gSJIvk@Nlk`YUs;blX{ z3670N0dRo)E*8HjDas;Kstdk(lQ=D&Ayel0_}1wU1CJiP7`W+UiB2GpAW{3+;Fbl; zAxOXu$I<$VMi0P_e`g6V{w5ER5aCZCtkdDT)DG@q(^DcmSb;`Iyl^-WD`lXirs_(MT}JplZ3XDXY?9jRpuuzCuuI~Q5$ zzkHAGdu!{Tnkl0Zp_62{H%f0b*9nI62JEF&7CtUq&d3PClQT>gaBfw+$MH zUOnEjb&0W5sfPqC0B)Atf`GkVaa&TsNj=W|~AL;DaGmLX^fEB3g??ZVQQ7MiEmD zQo>-Ikf+pWLLc)|B2wHfVV5nd+oj{O=bUS_w%wimY*RtSCcpg8cg}atcaBVob(_z( z0_oq@MvVpnX4nI;z0=PAdV11LB5u|jW6|PafiYTVqwvI7nhn4MVB~sP!j~0Xi-oa5 zxghZcGl>QNRDgw1kqPs*;E_8Juqm=yJ+7`!&l5~hh1)TwJ109SyoCMbLIrdzx znuKW+eoEy73k(0Y*$pCRWPtCN@-G#zct%&mX@Yp6k`#PDo?0!I7`2l#vEi^A_U2Rf z6B))UZIXhO-vuyP1FNA94JTa>`VD658tRt!^RZb3JZuHF@R$P=jn%GhcLLnnCf@R5 zMVJUTK}<9{S`)BL`A0CxSl`tgE^xZHoDTr&NBgAlIHNZy%5FY`TU+hdT3cHiRSAs; zf5ZAI2zbuzN$4B!h@;=5*gW#1Cuw9o#+;%a7tdc(@TY*K<4Te+Y{J4B;5Dj!i(z>q zURevGgwbAO7AR5%`@99da3-7GNIqY|>bJW>Dniv6mDEA`4w!OM{UvQ!5w*4B_gykc9Xfc6E%dTz1ZZwzZ~UpYMrpY>YdoN!ahQ z)d0^g*be^+28Mxn#RvwvUIJ_=V!M9E=t+c8!M%&SixXlF0P|tB)|rTdJJk2&Nk9#9 z>jlEHIyE?~bUm}~iM6?#}_ zK&uV>p8Pe^Z(y_!`34%oI}G>M(KL;X;rmA2n7$h5yIJ$pk?`+u*etAOknoSq*P0rg zc>SEp#K7f)s9fW5dlcYa^xx@MXyl5V(W9Z~si@~a*@G`SVM%}~P#TgXj4gKaF|eCh z5srt#3~Jn0AH)YZn4de z*h*%DdC!jhS^@B@>RLLpUQUN43T{2 ztF!ZUXHYDmn>8y*_^dz#bH|4zF5&=#rI4#sa)8Y8OmW^8o!x{NA~EgL-oM`&G-3U7 z9e>1!m2E8;3uCqxcX!|YP;aMN-GE(Qsd-t)4O$)7TL`eWa}oe=ANgZA6^G%_^t-9P zGG{#H!{IAUYTd$J%kXksA5d$l3le6Aw4(#=NgR}Qr8)ePoqfh4V7eRuKRkyG1N4nl zxPI-EC1rjaRS>ahoXFY(pTLsS{`2ATcz6l$nSwvk?*|Gdz;;0ZzCf3Xa!8RF8DS7( z!s3fOMz{d52#z-HquJuY%B`6nZgrcvO2q`8O(n9r0bfsU0pLx8#bSHv8Yx-~mJ$Gt znk|;YR~zNs_4Tr(VIYj^9T@X+gA4cW-R@2rq_otaZBXcO0ANsUZF#+w$eMUiIA=*s9?J=_~(OiCY_cx;QU%Fv%d@S4URdE<{$mx z9?k0|z(WK$Ojy06@PQqBcVqeeo#H`pb9Q!fb9VSKU1)Mi578+2mLOn>F9P;shf9m! zXiOxKZ^(x;WPMs?Q!askyI^%dAgt7SgYo!ed`uk*egD<eW6{`eTH2Dtkn0#0H_9dRp9HFa^S+AvXDzaUC@5j!Uv z#Nx)5Sik~u@W!&m(%xMNRm>ts-vOa)C52TT$t|wOIz@nO;8@}KEd*?^&%DWkVF$qQ z<7L=@(;2YP!5}|ec>3s1w{PElID~~H0Jt2G`a4VW zYm6~2xa2`aP$gMsGo{$CWG zXz+V=W|UaeU`W3C=R{@)02i{)irGRw7F$oJcjLe+I{(9?88imt@cS|OSjbO!$Hu~E zct^bh1M$gaZg*F7S%&ZY3QQADD#O$fBh3@*w{AZM?+Ql{I`vKsYxR#jy>;)lnS(IN z7Yco)?sGc9%dJytoz78Z*VQig)6wz(UT$?r8)kFyU}rCQzFn8`0$>PKwaTD)4%m+! z(>MpqFUQ`#!sOD+LM8@&glV^sWtLBQk3t2$R$$sax1PboEsh*tN~hZs9{qG6KnKlV zG&E7^EkgSka4Yy&AYGL-U`au5SCy)Um_Ad<Hv&9jHXC&C7x>!khi|GA zYY;yc@J=sQJ;%~(>2z$FvFdE|l2bpvL4ddNsR$(+yk68Xd0F5i1%vj(d4GR>Jpsnq zuKn~uI^N!v*J}_kz9@_}I(X~FOt;Pv40=05z1luj6Aow}$h3O>w6+Ze+$DQ}ovj)g zl+u2;__RQkK$l?VhF4>6-+&0cSHfof5N;0k^ld{@8xabBX}Lf4*S*0btsT zS;b|*|69S`=L)XzMbqq{qvu6T?pLzK<740}h}arApWhP3;Z2cTjhw@M4UZM#E1<=+ zZf@dGCfkU`qSKizx~L&)5dQvVHMM-Svr{C%#X=c7X5t)GhO>+ej^lr42mniFBm!?Q zHt419%{~wzctKafLCeXBUjfk&&zg|2k?smlS*NC^tapy4>Q!faHH>lCeCwy5UL+?L zy}>8oV-0|l^&i&GKctN`jpI@F{*a{MIIlX}R=J+k-3BZTNQf+RbGN0D3C*@MZ9?ke zWj2UIT9fDwcw5RpTJk{0sgx=PrXgyP8(dy>Cg$42hY`)5g6BnSm*!R_rG z-re)OlW2Okch`e^#THDdn$LWn_j#Z9`JrVxTN#@<6zU{#2)xO|Tfxpw6(dR!A*7;gvV*PTVYOP|-3Nt53GaInaczc7D)kdEbmn4FB4>w@!3IKp{SPy62 z7eD|2AOJ~3K~&C7{%p+Y449lw6Ds(-zaIpPTXF@Rw2A^aI|qa}(ll*y&NwmPIO#O4 zD12igoWKP%&}c_>I`~7&3S&2a%k|sK0syvRCqV$r173%tY`0I#O$z2|(=Y$Qhh z_?|Rj1i+b012BVai8@<5^%xfH)AquV=Alj!Q&S^R9cY`FW(0g176LYhMA-zt6y^gu zD+k~I`L7_|WI6?&-;&Ly$BGtxB*U`vV>0b6+_t+t02rfCu)f3|c_UN)KKIl}L{CD> zYO@Bpb$6ctKHC7?+e@kYYey~J3d-YWpt~8dqTq&!t^;mX@Cn{yz+`k-?ijE&jODY6 zm5x_Y!PPh^l1IRpM6a5H=N^$roeQYH*Ol{8Tnd>5PodsPj{1mxmv} zd2?Lky#kKlRa${!xEH(%%DMgV+f#N)v|xU)y64XHk5 zZrr=~DFcp7L>p>X3i?{rV5YZI5`&)B`O}Jm4TcM?*YsvBomeRjuH<$OzIpXMz}?6a z>0;V588`Y!KM{$TU4ijcFGfpwx4V2qQa+wPCw}x)n2ExvIih9Ml-a|3`Zayc>g_&z zePLmt{rdImU$v{k8~e4Et`-F8>FVxPT&@$q;As)ac%z~K_IUnBz`+y1${C3)V7Uhj z!H-VJAl^!%hgdFp^$8H!xfb+O>@aok!hnvN0iX^^1BFa_P1Xe_7yyH8=@UyhQ_i}?v2<`>C(CbHY zQ!Ep8Pc6aIL}^;4r#qSecN5vRx2qQh2EH2T_0;zFlb*J&HbuZ^dyjqU24L{b8jcnM z#$e?;0UkX8jKPh#L=H$+Axs=-BkgUOkv z0JwAv7$=Dh*0wC(z@Q~}n1AqMXP2C#aUUjt=dxr5o0k@7!2z7XHJilwF!034iJBKB zOaLQO~q z<)aOp4pXU3w{^E^x(WTpEz`RJ{uT25xH$~n0DR(Qy@Yob-B*k*klwo1aNKD0?U61- zabw&{d!5+*(-IRE`GS6pKI2`m-YYM@#!le58II!?=dqIw0LCo7n%9xeZi}&C5^tjh zEzVzmyNhg}!9jR)H1DRFvhxTzf%8 z{%Ea6pKQW{qF{R&jWr#OvVnu0pP)Hu9e3QMSL;A=$w@#8_3PKG6%+4bT?n|I@b{kp+iw?c--g4X-Tvbi)k((A4+6e= zb&UWX%_%kCAYniUfHBo2jIhy0r>c@K3>FketJdR765i&4x?`|m9%3`g2a}{-4gf9? zz&OzajzHP8C|VMU0KDQHo17Wi1;9vH#wbq~E5xR@fiTa)cZKt)#l)fitc1d&elX7z zxIZ;nPJNlohzFYSnhTj4VD?$qWY&WnL z8&AHV;2u(R4TNP@E9{2= zPZwqjfEXCte_GGU`pth+@MvAYv7o2`hIa)+Sp;w@mI4X0V!3RM`s%ZVNX8T%kYd&R zGBzt69pX6W;^JaS0i48PH0fh>Fi+uP;+=pd6Ma+@zQ`=MNblEET z;lH+GejnO+vT&DY{Hb-cVg#@Qq|ORWfY}bAF5i}aPox7qHr#<7g=WqYDuU|JtPv%W}^~4#AtL326>Zxx^ut&tg z(R80JX!!zFmlK`yKbw4q5w~~UJnsk$s%V_rc1h-eTy6zaBV2X`_2>)&|;yPI0 zS@w_r_BXUe&(AMk%EWhIV3VHefLoJCYGnPwtSU?~8k|UTTyt~t7XZxb+F4%>MwjEr zxL@(E3|w7L>Gb6dl)#*wot^K^9c2CSyRF#1m1M9wz^2bB7#ePFOz#}7RFES|v-z8Hp*~PX4(G@*9 zeEb;cS>H~3AGpK}`ALQM>>3Y%(RVwxydJ|ysR-g$z13wC?P13E{IoEHLL6DP$x;Mn zvo1qotb&Tk@OUiHV`4w%ORJ zCBk{G1DW&fXu9>B)0BO^Y44jvi0|y|G3eTSK8XzS{mCDZoy6;L4>D!BF>p$#Oa(3 z2Y3bW?S~Hy4DjmYD%x>-kX+f-#i|EAtRbvt+%w*Ft4Wt9$L3zVp*tQAg@nsZNCm5{ zQLoKAu1lEkn&wSL_ewf#>&7-~)apYaB5ZDLXMj5_!r@_hWo1Ca2eiNMG+N~M8|Q4A z*6I1#$;G}sK;E#~9PbY}stEuUz$OK-r55nOfFj_58x{Pr0{H%4YXpppsI6Yy0lOq6 zOjxs0v{eT|SmhgJ!&I?KC2{WKPZ+%OnAoBcbtQ_&KZL0zm|pJ_#pvMZ=>0GMfi?@h zECiX#5Mdm_YUax%%W}wu2{64xqHjux+~8K*CLX*ez8gQVG&7rQvt=b~wgQ+}m!jde zwl5kx6GA>;6%V5S1`q2BUFpK%LDMU z@9BiRu&LOrE%c?;@^rY)U*DgBmu|NOT{%(<4dNS0RNhTueRtKzG~Yf@`8(sYF$|j z)K&h>080@JKxR3pm8I%Q}upnUc?16ys^uzwiSWZTx`JTaH%t3lK(L={^I8_u= zu7lmT3g8%1Fzn4(%#U`xVX~yyy>HJ-PY=wkeGKs4wZUE>|LSI>5lIX=yPw z8nptav%AFJk?G;-K9Owb8LJcykTT5XhqnRPK!95v{Vx=x7z{birk$XF%D5t90~zjy z?(TVif4?IT(h9OHXp|5v9e@n*{B^)ka6Yl(EhLqT3UXDEaYKa77Kg)R)NUS)Y$mi8 z@_dt*8*dA{<=wrti+3kKpC8f_j_b@FO&xrQ{~rOf+u8pDe6w-I@K!CXA__*}21~B& zk}JaQJmB-D2(Z&>Rq>K4z_)~a)YPkwXcs;P0RwP4s@^lLuA*VlP_A4aIr{e5pMLyy zB5G4hBf?f|Y8@$|$V^lKvsoAt9l~L8sxM70RhbO(75L738>gqo>-GSwWSKsi_L5nj z&t3;u*`r%p+L~L8now&%a@l&wrXB1PgWou5Q(%DO7=`c$gJF>X&--W9&afV$>VcNo zPz4XFl^&tTJ{yYBpU>wxy&&s(UWvo9%YMR8qv48x9gdLvCS9;uOq?1l&=N6Ahr_~n zyEhI6eTO9wNS2Rx$vTmD_qV@0`}uq-&_0XWh3Bmno>^ajsQ|tTNYnu~Do2uArTnXa z7e>`kp~{z@eP#ZpBrUD4rz8*#r|77SSXIDI;!A`HueB5mmX^MM{r8H18DI!lzy>`z znO(JKbGsYwKD>K&e{5t*w6m*EGSlcoB0F#2qRkrv48mC=Oop^%i{d(k1WY=f@;z9? z2E8u?ctH>ayfOf6^HpsuwzR5|)z;W(Gzx)a#DxyB(}UhjivHITL>=Spv_rF!g2jd= zGXeH^l-5Y-iGzO50DNa0r|}C9ic1U)cjIP0$7|`$LfBmmy{mw)5G#EDI8XkNKznqQ z>$nQd(=M%Z;BTBZlr9W$yd@dV6ms&WEaxsWuP#2FOfjPw#x_T<5667u{{yi5Mg?D4 zR}1$?mHBmlW0zRPQYlhWbKJsYK4o^7SirX)FyqSvTq3^v3on^~N0^C4HZ%l`c8;+u z`h-Zaony?L8|x$e;>|%p3O*w!?XWt%7z3;l&W6MGsq_TqU9qU6D3&+QPtFTsk_+Kx zDhmrR5{%e$^?+3glZczOTt1W}cgu##ijlrvvK_7ftIbUmz>feH!x&g~k_y=K0zDQm z0tesMo)>tyUwptAs09qfU)L+x@n@2-?@cr*co6wKI#%A6s8L`tajX-vb-! zipjrHHUavC>_64?aiu$ne|2)1O!p|-UU*s8t0hq^{PgMMv^UuvV1O}o0)VBQJX8l* zm2jimsMmJn?a8Dh_O5xoee_zvFi$2C7&O!X?vH!G7tQuzYK<#^yYA3-je%7-i0K=3 zp>~c=UyZSLsf7E#p05LpD-HMNn{>j&sk=GLpyDb&B^+MzoY_vEh2VA%47fMTd%v7T zX6f`banE(arABY80ybS+Ty5CD0$5!QwdU1VwF+KXs3v1E1FKjpRWi*L#IEAp+Qr2y z!cLE+M&H^G-4tjNwCwicll9dN_^qj+A@2m=g0f#!W${Q zpdX)^VW;?Pc!vRId$fPm?njFwdyjW`c-R}o6d+e|`|RKU`gBMxS~86IG!z^r?>m{x zk&8s$EqOm2M>aCAZ3YN-0k6$Z+Pi17L$+u-8qH+n@N58p(NLe2qp-TJ{(Byes$hb7 zXB?=(TUixfFU z%tkF&*wq_#azvjKMf)O%>;swm!bQ*q)M_H$!Ud%3hhtbuSt4HL3;5X`)ZVZiniMRt9#;RM?EFI7%Ck6b+{zG4GSC)v z>mQcL=t%BEo_J(yp}4WTyLksGvxq1_5P&^XtLS&;3j`f}#3E-*xRxZOt{lt^Cf!Lj5^#mX zllfdwMQ2v``SX0^=**cj_W*c|T{Mp>L&tpvj4p%&Ja+DHU%2T(@UlRH4K{t=WTnM` zk*c!W%^5-1YXGLdEffP>xNr!!GT;gpe3-#H^v|>bll^X$M?Wfb)Scmlg~!DgJGWmeTL>w;b-+VI zU(Ic1cXqPRpJE{NwHw@v>GEI5fCC;q1I7`Rlbu0CjreEg3fpT@Wk}~s$T;=AQJ1jOiH3NMyCJu~)ovp8@+Z88=OU6(xGTZM_b zeuQQ=TP%Z_nAEMP(d(|MsdB)-D+7GKS6>(5sWA=|GP=E;gfzuaW3gDhEn~9;gX{N10tGSv}msf`g zZ-fEsMJ528bA?~ty?HaA_XX<`sm1x159WQSgvhW@B)^&o#_$xWNl%&qbFbd&Q>|>m z0PG!e8-U4C_lxe)6WmIa2w$8S`@$U?gtBS=ISUSjIB8h|Op7*JWUO;C0XAp!%O=KZ zYBq=Do|Ml3ygZqVgD?DWO_SYv6tM2An|=}S*|U{%&=6R+zA5qD!W}$LKT49K5oY31 zEWRjac^YB%$xd!ggctK3GMf3jeXDtoMb`BX$&{ciG#-Q?tDsXy6ZqYt-Kh2|&6>oJX}_8uCC zGEULtOkWf3fmYI47kmHij@fL3gRouMfXY@V;MxV?P{`}$7PW_fO^Nx`V#308?agwg zr7qNL?BPuq@8HXelaZY3`va1K%(wANJ6^Y`!s2%;`!D<~rX#Bo;Bp16Z@~Arly2QS zt4=ovlR_rIKklF}|MZh-GS`j0cYE)4clVYT7M9Umb_Zn~xBh(N2IB?cn~=~wH8!uo8QlcDLAnaRmlOQ=6wdiCrXLaTAoyKW*}+c1XTkt5&>1<_Fh z>Bcw#hLt-N)jxIJUdQZ z0d6249g|?M?V{x`Sur8ksy}1Ek`%Lf2SXMk`u#b;ayejpwV6yfl@JB%q=rL2LmLag zG6BZ5?SLjy(bA7-%b!#~4*-A+lN z4^3mSd}sBKeoSBH4o9>hFDyJ9e!ca2X?=P55l?~6Vxkl`@y0!&R9`^IE9}3-cVNIt zcu>>~f2}xa*L}6NU6|u;Q)paMKzEb1HNK5Db8vvE2woywMPX40IeSy4rSU}72Y{o_ zXqqh8gO*9bWYC;I-xb->C@NYQNYG*nj;wDNcK0@4PC{uL7F+^cJC+fEY0q_E6lGRq z((SG1S}9^e`*vGqFw<<@ET{ySR;IZOu!SFoP~uYRQl0odIdjm{+#+O}4mcNfu?0_& z_H&5+_rm9&e2Vq4^;)eRSS7&N(@mzzfIC09F>0JJ+(K8%#|ydv4;lJOA~>4nBtYFx zzhB%UKou5W$ySuD*XzaQEFmAw z7Muye&59_eB4E7NQ45yMJ&DvM6MhayCU;;WVRZ<&safuu;?knrYM6d12bWh~_~bVg z0pm@|0dsg(bF!y0;B2Sf`i%MNd+?!yX27&JPNjM3P{g_zBRx9>n zJV-SiHMFey!uy^)={xxm;LdDkwk!p$XR&T|>3yN~6c%R>6$qLE!FRNqD>i7eL|n^3 zXauKk_cu2-wh8dgW;Xl$+s-bG7}NnjC;`^gkr779P65ukTjM_>jUDyFj7d}%_V)|x zD~hH%nJ^DyrbRJItf7so!jnl{&<0?bFadU|N<7Wyz(HJ~ZfkScaFpsWmPqD09JYu* zs%fV*wV@$a0$lC(hU8F)o`B}TXDe~1hF;Jbj$36y}Bb7CnJ2;RX9|48|2Yk87zB<_fy?5{b@#Iz)_%h)ST=eW(zhS{}ze?G{c8q65B{N={AuG~U6)hFq zlKQo7!30>6aG9ee7`PrGz;Nu?g}D$+?f1u%K??xOjuTw>R9j}k#&KKBHm*vlTdTs@ zdm;?LzpuB+p@VsI!emNJ&PzV+}Au!Sp z6w-)1mEb$1IRzePl!cNrIy1MwySq_*NqCFf8}Ig}vq+cd7R)X>>>}HWB>zX+`GvHV zXK~!f_#oOKdv{Fg>Pw5cG?0fW7s))7`%qd?YG({lQp0YtgJVf_1{`hC6yYsUZZLL~A_j`Wl zoZtDKlU&|g=7%C`)C4RdFwTP-Ug4zsu{Q&zTNhW%Qb}Cb8(Uv+qp;?iZ(NZ(o6lAa zVF{AMe0t&c=I0<_t~T`8xk8O>G?%#41I7qe+_`R2=nphk7XWv;T~2LdL$ldBiBDFjSgfsvEAOJ~3K~zDa zwEenKDv4NF6v+w)_yz;M<6EnQ=|8~aTLj-5=MW41wp=@}V5|#?j=xpcy#~G%B%L+@ z7KvsCjH-SHtP2xslDM|^Kc1bDb`SUVkI^>yKWG0!!7ZC;K^WzBoP+A$*~;re!A2ck z-qeww93B>#YT;eGtwnF9Q%Rn5&YPy9cw&Qx|5!DIDX;*i$L27szyiz)7L8$D4SezU zcBiw=sRb%NXHyf;Xi%D5Xjl{IoUhzjLXFD$@-nKkaymvhkMJnx{||6sK-P!hu9Pnp zyBx637AUS|$2r3s{x}+i3NC(PZ+*m6ojjQ4c=ZY0h5LPvd>bm*Uhho zcW_(%#fy`}6T$c2Bw!v#`D49;k?4plrDwLb#@wbPT#wiE&gC~NXr#2WxtyO%F<{wz zv&qf0(`k%O{>p*kIK|UHc=&o%JbvU3AiwQ3lE`N=Mh1*J=jXCdi=}69;HS(pjTl8P``$Z#shdYDb_{%dhI9$l*=hy}GqIMUXc+wKEQ?YAy z7wPwF?$D~Zls3Cv&Ny)KBy>-~zQIjLbmwtmiqLc2>oW&^}>pxQs9Gw^7>Rf~0s5-ub=+l7;K zt+l;$hhpGRzu1DiyKjgMSjNE(KGsFQi$2DJ+yEGJvc+&Lbki9hUlfCs0GK;!yk;BC zKf@~Ey9xGx_+$yLwBU>Gc0#}(-@pIoz3(TVV0aqzi*Cd#WT1<7dbcA5GJPWP*T(_mZ&*kXqEoPWdiSqk{o@vC2c85ff+#abJ>e;b-wPJTG;R@KC6`Din3l6`gnu&QzsRvt-3do#JxZaqzi z4FXJiFXAn4GNtQqtlE@I0izKx0~Ux0Fm3M-9BPz4wLVa(?8d_$`j=X; zFqo^ZiCl(Ew6g%fD2@r(TI-K>tAJx<|Jyxs+#y-yI>5G~?+U=-_+TaO1K*zZ9>Mny z10MPglfQrXLGm46tnS0(0`P0pVtEZIU<o;{BnN5`h?BEfr z%}vc;H*qIyX9f{)I^j)m-&j8ZMhu0Pzs}15^L-!zm^ALer{F9Wo}CAJ%vY)W>T@cY zh_75u`ptzA(f(D!6{m)oI>6`-c@<#VNXa_mA|Vm32Rt9R0&r)1YAWQ@I5L)e|1e-N z+2qNS@w&Ar*QvM@Y!T*i3t^!Bvb2U)I={Jtn}UZ@!N&*X@{9Wz^*bo&YeBS9y#1-*v zlr(JLm7}m~A}SMj-KfFC3jphiaIyl~mE9~iDZT(0*N-d1PLX7* zaIk(4@VN}gm4FAyDxjj3hX~iNQ(`<`EqPChWHW`q2~}EaRRGSP$ORW`pTc2`8H|Ur z+3fCaJTyNa@P*MG6L>L30)U6BfTJo2_{&)UPLr2K#^1y>@j%2?K_GCx5Gree`KKSXVbKoYiHP;e}EIpB6JK2@~!JQRRs zxTXjJtY6L>PlTEw+<#)h?|(UQNo1xJ1>T7NotKdxw~_fG+LC z_W*-YhF0Q5Zrq#=Q6zyNLde(2Q{y7Bbv_ezW7v7`bpqU{62#$WtH@7AgMK3z6%SKi z;>G)eRsRQ4FrPZ19vSyJ+1IK9cDWQHJeHpm74sxG^1m(sZvInk4owv>?ZzHO1YwS8 z`dzAIiwNdQKF#h4N4gwlvuQS~mV&?4y-0Ytu?H6r2G8%VTn*S4uoPSnX{!tELK^|M z5a42VzO(xwpZ8j^^*O+Ni7|ky-5s|-4=@N%i3wvE1TF|~)0&*z9v!V!Rjx`2TlQJL zxq5PNvR9^;z4HF?+r5K>?d|V|Rg^*n$i^WOQ45v`0k-{II)PLvMW?z;#dv zjUz;I>}(xaw{N%9FC0Kz02~g3@O|7X83kZLnDA~-lBR)}@2$M!vdTujDy;Ooy<@bO z<=&e6^w#O8@lhxkm)--c-=3a`$PM!t7_y3JR>gqLs0939+Ri4V&2)?7=A}|XtVnB3 zl(7^;3bG#C$E(7F+^L~+qY zQp8nJoS{p1&O*usw_Oh0^FPn?#w4|V^v*n;=?qSBX8Ozjob#OLd?dZcN839`rq(S=%I3!z(LP)kuWtJ375ir`O1flu593&t<^I zuK`BjWowbE1dYa#LV*AmvW20XCpfZN(_&@7r`hmSD(;=`O+5H%M96g@_jSOSfg+7D zx(=%V>q}+46@UkP`tRy))aqnqh6m4!b37e_&j2?flg-dcn%6D_jEaoFDDRLvjkH+7 zZ?R;1XkHtTMT9lL@v^g0{CKdxUp#<;-YFjDPd>jK?4Q~U2)xXefFGX%PKtp< z-i57`69ViK9~^HuvV_Oyu{{d6$g_qc;S`#$c~O<$Z?U*K%yL7Zt!Q4dY$ zDfVHJm4<{-`X2FC19rua4tI+C2m37Go#Kbr`IDdLDaM*(y!h;5-h@1yT|kas0``XE z0`R!^DF6900J|)ZFcDtjUh*-%e}_45cG^sW@)?L&TLq>V`S+jOcFEWy<3)h!ixBWd zfD@;HTT~C3=o~A!klpp9ZHiGCc=2yhQa1I`?1KmOoqzJg*SZ^Xg~H}sgYIm=I_>V; zdIn(pGYxQC?J3|U0^C~b(?)FY>;R9P$Ov}w)1i~D`4WKBvpvtcD3*3VotFwmJHvdw zUmUtZX;Ugi>5f&qE^*2uzHs=yxW5m)WT8JSujRM%^B6Mdq0d=sooIMux(RWR=|I56 zsVu)YiMfEur1!(;mnX+iFk9((IE-gzjM)+)9S5@9$jLps7K^!qM2w;A%#P$LH*%^e zuiwmo>Cc)(f&o{pWHnR*R<&)fict*(Jz30&S&ZfubHN}1wy||E8nN=-ri4~_B9CEt z*~4TSNmdY*1l&~l*sgEYnzFfUw^G4njSaQ;uV15(N#EHtM?o}-VJp86`(Jjsr%w0M zfD`F4o@~xXm-T6PG0T+u#50opkOByJSq5w%K>wJBi)rKnkLQ&wiHEf8MWwXNYOJFxm ziCY%|rU=}o0any>XK+*iri(?ulNQg>>;+}L}gLWj~>T_wp>Ld}y=$fhiegOXYg!yiZ7Tl>R5FVVzN?1;Ka5ln{ ztOP9ddvJKTcYJ(2v-RN_<`?05MB!AaM5YWdUj0$99$5;DAPay`^2c!((RFEnF)7b` zmVyyySqVSgeW3giUfXsoas62X;l z^QzkOW;Y1w*@M4*Ld{(pB)o~?NxE|Z->Ez8L~1INa5>;MT&+CxtpwnPhU<-u*Xgj! zr6BsgJXZ&-PE2SLX296akcWVQx4?<6!9kYrmgrEj#$B!oz~KAw{Xy~F-XmvZAv2Si z4TVa8v5r-`V8$x}*C$jG@KQL=F4z&DA;8W!JTO5MJ{ScWsWQH&3SMF(%|om#a?zsz z{4YZyob;X#xSBCtfl7dnPZmdeCRGh^%9cp)?)LC*9(&;TDJZy!B)pjw zR{C7Pcj{Wd1YCNgK!HZ>jkel`Cf`7#2Kah4;Omv=3V`MKsc!dc1XhIiEem**uE$w1 z&W0F2U0VZSepy0Mha>4qI$%+~dk37Md{6o%w;6f#3`2B0LHvyTRi_?nFvNkL>mP(x zMMEwV3p3#H`0?BPOoW0q`i}Iuu>kOdDE0zlR+m4lOh4p~PV@~CSqp}gS;$nkwD5Ke zg-|Mfughw6Srx0brN!L)i>d`l@~W%WR2_o>yKayhvs#S=douK9{}Wc2Mq|-jfI`7( z8K+PFw2Pb^QWN&k=+vf8ze*CW#hRA^pEiZ1^08ef%W>}5MW1)+KC7!mGMNhmG&nbO zjkO4*ID64ns{^bu6J1hnIb7}FYvrITeK4AR#~@*MKLF?RFwom>y6K2?vPA6WdH!@A zCco$sv2UCollC}6`LTGoK8D)69#5ACj|-fM#y$)ju_=*|iKU51%4kgCF<40{{PGkgz=ZhmbHCMW5r9eb7!`V#b*!|Q@$1YkYnTBW z|9MrwHZ{HaCpo5}=uj*+R0yfkKvVejM(_pTsc0_iYg#1o(k}efq;bUE&(RT=t^RqZrq8bMUU1+3i|Nfu(`vWn&=v( z$V|*@Kotl5J()aQYD|%td%51uNVL=8}NPMn60STt?V@uY(YW{boEX zVn<-_Hvmp|dIAM()QUZM5}?@1h7r>Bp`J3p`$LN?VRDVWuLJh!PM6&@T?knJHvvun zFy>EX`ntEbxBH>qweIddUOS{Q4v><&-5+=$I6`S6>>Pfz@@jZ?Z2j=Pm^=8~VQo%E zGW}>{kXz+YuERAui$jYTyU^5WM==yO&71HJYmH2 zmypGB+rowCV)MFH07g&-!uROlrRm@B$^L0(ZtkcNKub4Wx(U@7s~OiAd#8~WYcck! zYNFj9>I}{m=rb9O{r-t9w3;BFlJ4Ou9f}=8K?1y3m}{cDU_&EjkPe8OQgzWe1|R9G zj`sUs60U@-YhxGeYv8XoG&Yo-a?oe+{Q__~WKu8?^BmVcGQ(npYj+>P^H=RwdppV+ zO(sJPdDh8^^=0pECY&VCYsCf3m1M}?F9B0j7v`B64@Hww!3c9&F%-;eS z7N(8}k7f%)iv$?*#WOYun_utHU);41la%%82(a%j0t~2^1AOgjfK3|UXES|R?+N*0 zbrpi50B;>4x^TvQ-QCDoig8NA3>TArA>iW1#&D|Hnt*kHvEQ1LRry!Q3p zJ%!iwUJ>wgFWKUDE|d#;JibCU`iX8)iTbpt!L4iMDK~ce=zflUjz>p;vxV$m0k}=q zcsan=z6mhWS(zC^xaOW>zFp}1#mb?847T8l-C~XR(ZbQ*-pbL+%8OsuA>j8L8^2n| zNqi*Wx8MKxo6W%jo}G7}R?Aa+n}1;CnuQ`LZ* zZ#O$E0x(~Q!)B>qm}#@7UhHnZclR!KOOlB;UJ7tcNx|em+L(L)Rdvb04+SVDF;G(kyVa)^s{aFdm;5*=gL zN-r9s#gC<;MCL-o#a-~@q7o_^qQ&OG=6M5 z!*pI+Xt62D`Q`t-&-*@K^8)ZLZN0{+9|-UbVtN(8*FF|-Tb~yt`axyTu{g^rx7@h* z2Iz1w9mFntlF2TYO4)B;?(87d{msk2Mg4XX(?D+H-F?z8mSUmdigqFGF1F^eDe*aE zyVYRE>R;NthyTg-?L2HH>uaCzv(W|JmI>Fu^fXU-#bqR#argUgjo3-TYU??)yiovy zuPR~TYw=B{xWW7AsXCxAGaOz3NxJhh%$ORd=h3ynoa z+K5=*1BCHFhE%9tFya8Kn-{6A$~$Hi92(Q9Fe{-dd+#r*IavBt9k6YD9M_d?ac?-B z3#x!|Pa*o@(?(2qr?%LkP6yDZA-A`*bbP$C^KxhT7$>BKi4i|S2CeJgzxsy;SgY(` zMBm?JyIIMmPyste+gH4W|8Aa^vZc~P7g>00rYv9uFcK+M!1VK5WTTy^us;L%jER;^ zDqu!z>i^1DPb$^{2JQ<0>pJ8o676ei9#y?6;xB7!WdKIkuU%5^Ie_avJHT<=RtEKe zWmUjMjQ^vJm<~?qz&80|n=||S1$0Kq9_PP%eO!u?k96x+%f#vS^D(h&8}aFsoQZjxi7tY%3H@Hd+D9Y4HwC#*!lx z8V?~1p%Jc1R}Wa%J>A^iZuV5J+8eRMLo|khgs0@64d6Zo9M&rsV=Us_SdZ40kZ^$L zZgg!F3WYbvL}l;I>wNYJVnQPV@C(&M|5?#5?Nw=S+ic2X*2Qmp15h6Dlr_xB5# zNkY85oLxbfYFGytgjK*!aeHQOL8@MVrGhm9n?%kf=f|^%>#d=?K3~XSDid~J3Rq!j z^LQF7`8Kbu72m#{Pb1$O68<>=rad^JOhUk=6(^~CQQ&NeBWna(zqeCyxWa+$9$Z)# z_DR9n((&s;zSIick%Kgx*?0xOkA#ASdgte2NO<=(p1Iz48(c2ac(Ul-JJ=+*Z3S)2 z!rAP~03UiLCS1tlUn0Zm5G7M+R28eHWrrc;3pqqw z@m#r()$i<~T<3OFb(o|$m5lL#A)ahVf?*wN~w&*O~+1Ca>stFZ}}vIO`z!&C_HKJS;}`FY&1!;yv( zWJSQAA7IqV5#ZJJ_4S~L03(Hu6V@SKZ6=Lq5f4Y20MjDAr3C<9?uasA*L3Od;Kh9? z7|qWh-x%r{=utH^>Hv=_fE^~=>S|y3=wG;4344j}7)BXB`$GzK4AU+$`zN@z~tFvgRb1@8$)ROJAC&_@3gluXk7UQv_=Mn4*Vh&O`=3cmeFFVRB&CL>g=e6+58~uK3vrRSAx9? zU=^}HK!LmH@!8aD9{_JNV3x1|EDOMK-4p;pczZopDm}{_6W@-3XcT~l0r=H(;XQZH zZ%xVZ#O%1utjzc+pFfE&O1d4U#$quUJL-)@BI&del^pVRv<^IZ{C89hi_@%7Fz&I+ zfDumYM;AKMuN~oI2Fzx}O0Tev-@WTIC=nz*V4p!b30GOj+z<*@(tA;;*3ei6oS>() zT9licEd=;x=K~_Vi(j$MDG~3l+=HtDHdXFUBgZKtIfi8j7K!SA-^0lVoTI;HCAkK+od5$nZ-K z^*u%f+Sgx4O~OK}fc*rxMFR}NS4vjxnvLncVhvqzBiEX*G+U85?yD zf?}khwcv6vS14ppq5?1-L0j{O&wIPN=hGO%X+cB*J>;;Nq1a1xWJa;Lh`T0Hl#}uO z^}exSWN}d>`Gw<;kuXU}oyrn{2Fck11=F$%z)q+dfC($%Z;}|W6IWhNms<6p8f5F4 zFV}}o7R)o*6*oEqsBlI5r83}}hE)F0o1lZzRuM37h)#oI`#lFR9`faYudd*0S6F%n z3;z!RMw>w2z>^aKyxG$eNNc0PZ8jBFqjMO6$+(=DC&iw;NjM#!``Re zC|i*&U(`0*aAT#FTGxo`0)ZG+`jW%92Ea?}nLclNv4>Pl)+09Q7FhLw`Mi4;u%G0M zFM+>_utLi^z!Db`>+YV<0Cu0V|6T^zY+K1MwgiTECzm< zbb$Tvr5SJ&{*2fHm$SI=jeu!C7qI)I0Gn-zoz=EPg5I;71k8KauifLbP`UW;UbSP+ zRTX^R5$RI^;i5>}c3lgAs(GGHR+w^su8!`A}f zJAQfx(w(LzI|KH!fYm+#)d0Jz0)`XaZ~@@i!S`q|&h?;=7j-aw@Ln@G6LEy3zSme^Kl z^|K&hp@kdG)us~@%$UCl7{kslfP$TV_NVz~4W&y}qDKVST|rr2`OmF8lsDgCQ#z^< z=fU@b&51;_Ob6j+0kpg>0DN6vf&#ub&smkPz0A4#WyPvU0Jx^sh6}(bsUKKiz}@h6 zIL~!U5dI%?_Y>0AmIeSk-cES<5=E|m;#DK5Ax$GB=nz7KNEYF>C~3!$DyBL!>BBZ8 zeYL2iiwTWG{6nSEtW0TX7H!>>2c3zOVW=)1uB08>_3XNNbkprDh55ekoOACtVonvWElf!U#(-tx;@a zZ|&28y{5puhXrm-`DgZah5&eDX0(l7%C^eL2SxCH3Dvg;Zm0mh%79@KyS}|G39|^+ zOYY$f)1yfiz$7!!Oy5+Kb z=DXh%a6<)f6tb*W|9DNnYz=N(ENHc1F#)5pv9rE@Bav8mzPr1Se0;&JwT1}TJNEA9 z&T-%$&48VF&jDU~V5j@G zQYm~P@zUVfy;~k!et?+KR_9;5Sjset5=Dk>IBY8v!ni-nmmKgk+(3sVxC1F@aPa-# ze_2Q-lb0@CP9##jnlHe{Q%!?JOY`7t7ym*-up}Jy_;}2g1Jk=oIxq^GMR0|%88E@Z z@QcP9FZXt^K&&|U&N31iw;ECa3%r)EwBrDlzOF&swZwoGuLpo1l>x8Iz;-{oH=f-) z-PpLc27T&aVkxug3DJQ_w>w*$9$RZ~6cudrN;dlf56>WQ;`xV(udKGt62iAP}9+`*X@g$m$!BOhEC1Jz20@$tW z7s9nhaO(EnOUSJ3&W5tnH|4-hrVSTDrg%G1;D+-OF!;jMkxs1xFkaPdgs=d%+q+RK z9A?Fr>G12?&`>55SykXW0oap`6=yTO&HjCWRfXR1WWbQ<20GMsf89)5FqOFY;p3(G z%!~hgPGl@@p9CC8kEA_O8CVZsOs;*ps_a`0yiW+%1nl>xc0SYnDEz)(y)<%;i@-H7RU%1-)w}0J+0QQNX z1m8eBM@kG0m;xi4@4jStH$vsfm_DG#R*7*z;18FVM0;03$HolLKS$7XoI!3>a&vpl`vV zsxIDGGMtU&Cb4>CG+GO=S^?Y<8_BH%YYTtLFGa5Ngn~&m5s&D`TZ~*$qe0pHoxfZaE$bN2{wPR z96eUx;ONfIhI50h02~Y~cg=ATy+?@PV+U4nr5BAPVB!@K3?nf37Qz+4E*Dk~%aZUB zfy2$`I^lu$W(MEShoV0BX|0+(CzTCWi{fV^H5udQa2+XRrO2UZca&_Rw!D^Z85P+R<;?JTD`Z%$cxH!HZD6ZhzCfU|gZ797 zE1Q)M&Q_1XdJ-t%iYAh*VVLlX8%8l}7I$u730Qg0)hv3^7B^Q#1lN*i9R;`&R{)D! z!Yy8$Z{S5Lw;nGL@C^evUs`Tz=;}Ua2298p!gTg6?HwuRdM(wyUjtUD1X6J9Dn%gX z#y)vo8xp`F1a4(BB##-ede# z9@PfSiiXDXTP4#j`m`e0!}`IyqKkfT!dAamnjjjCV)fb!1M)PHY8L4M``>ueBD{c{|k%@vni(mj3v5BxU95>4m?4< zbAHQw44$Bm(}-a5#r|*c!Y2TREbH1IaSlxJ+f)x7^xFb1R$zD9CpFBJTx!@7i{w`D zPB6Un6%WQ%0`qw@1BO5Q`UVC7SXI(44B;5xfQZD{x&_-k(jz#8i&s&P=FxD1q)~Y! zHglp^k)qW-G|Q_jR2|1*G}8qQ`R;~Jep#0Eo@lTQT%BcCKX#> z{JvS%zEONeMiq15rtc1h#dhALk!rWmEZi3>8U4D6zz=9GixRB5dg!2WXZH_p3zfh~ z48UPqd;dyqWp@1O^!N%MMz)v(yCmQM0lU&l7CHdr?NHR%h!P+t0_4CL!U&9sv=3qz ztD$-E4PqTw4`IEB7I||SFuxzK7GQiR9Jqe8&DbQZ1m;H|dc*)c5zm*Xs&j?!)>nbg z94l~Fxo@|H`e#<>=EB08*Iyp|@K*xH1iA+gS9N#W?N_=xv5$83RS<(P0TsQ_L@ z-Xj8oaQOwU2C%$xg#$1iqQ!Y-iA{f@v!)Laj!$h(ZJikKl}g|ct3*a2{OHx|FaP}W zjXxnUPLS#e*zW9U=(@syUFDs$%f*1lr>Ae?#s${mne6hT4u=c(pG-!lq1;i4b44< zKKkYl*YVASZMqEzZ>jF8w_;xnA%O3%*DK7IOh8keTUaUIW`yiph}1IE{oa*^uO zb!MCT>}Z01o26d|rp*Ps#=XAk66T(sCVjrkwiM!}?d|n-@Lkp$kL0QNR#^m3ec$rk zLBO>pS7yK>xx$~JlQ#qS?%hQggkKXd%z*1CdF-+`^uX_Di?x9Pix6g77D9w9YnTDg z_T#lG&9*9FkweSCuC(effYtVx(m}OzU}A3U6ft1$2qtm5?;R~4}CdpC6J(HjB0jMcoIm?gI0 zjJ?GwBAB8W7ahX|8$V7L=?7uDGYWkBao@NCSTzGqgRlUG%*bZRI%e_qI4W95!e^QB zS?Hr@@dCj#_Z7hGQ*jdh8vvK{8o<^TwqgakCAuvmj|mp#rT97sTi@7%xaAE4IDT3Y z+$sUjeR>7JH?FPc<1JY0*x6HuKMKCb(geWrVsyArU=JyWcOt}cm}#v*0QdJ}p;4Ow zTrdM3aM22u1D8q|aEHZ`&CZTMs1_kp_Ill#)Jtn#ynK5^FH=oDv;dACD{walwhLgh z2!=T?2hOt>l=38;$J*~S08dQ`+&q26PqvG`QU-k2Xp1lizkYD@+Wpdm75B*K@mZmRLTf3*j8qg_Ct@WD81%{GN#I*lh?N+y zrETCzRODN$0prn_3pgGC81_h{;F6U0M$<|pi|uLf#!WkAPr6Ae172>o^Tbk2Dt%g& z{j!Fr9?63c#CIy@01oQbkeVgHO2nxGz7h`jlZ6G&ISaUCxc&mKXb@jD$cp;xA^>O9 z)D^-7O9ug-`O9yw{{5eSem(ne1k)R^4)K9Ph_OcG8jNdiY*gyEFuEV-VcZaTY1ji$ zgXQ>Oxm5!Z$NB<}>v7u$crB4L?B;5#)wJ8FVTmv~36U2yO$r4+tVk^W_Ow z!3?(-aIvWCQ}wd|%NZQ!1U!tfm1}%$6-XLAZnV=0;Csmm4gj3)1x)7!zWnz7{rgM1 zh5b^!4(qIvn~jM~WwykZ$%&?CHT23F`IJ`QX|?%)s+A&XH+*oZwO`zz z;c?Ai++KHc!4H>P$U(q>Z!8)BxEl^P4DcXuRnIIx?*UAi=o$E~CJpM_dAz&3DtmE5=oGhR&oiqR(J4bDgtcv0*-IEfZxxlz^)| z1udz#XrbV|pRjb%mj-wkCq%#D>*n4(=Z`mZV7@~NMhyq?jRx2S0KPu&S$%zg8;z5Q zOd3(BVoXv9iVjzf=am90gKN#1m}SgTjuO}1lCOYB1tuK{AFN_PiJ8BJDD_mfN3QBBZY7;;K*Qr)u>sqFu-4Yw)kA$ zpg&7zTtEo$VqV8-nCd0m&jEF zVqA`=NyY$J%Nq{E@bLEHHg!q{0`Bw$e31%Pw?t>6%j~Vh3#wjF%akWf!rtjlwdZzBMf>O^59bzGWF=^gI?D2(Tu*HMP8EI|&zI zRr1o+V$sgK6<@l7g9oJHgciPD#T|>sfX92x$vX!nHW=}3zk=1{Wtccdj%x4oF_r`I zql2wi5?$8=INGJPxf2HWJPHoVT=hUcVT*kA=cNY%uq6dNDBv{@Vz~}`07Fb$*_sAx zwrw50|JYHJx2UfMcUlf$?CK~=zzInh4}fEvmR%HptJFG~|40CfitnP=2m2Ka#Jzmq z7qDmV+s{Rlf`5E6d&f~Qb!(u9HU|>0bg1m8%VpUJz!$m7&Sag&M6@?-ydDRc0!#Q> zv4~#TQLt{?hF`(3HNq@(k*D{2y>bxXq69qtApm#z(X+;O{0i=(fIAnd;LLoFi*EQ0 z0b|F_T>xCsHQy3!p3QenRFajDnpMpDrSVjvBN+kX4ch3UVWVnm_D0>N>yT13xdXL~ z6N44NVl=cEJn8_hD#IB90c-Iy08g9&_)0&(;+%r)tCO7j0cOG%s^Clj-$1~0&iBQW z*`@V}W=R2~^$W0+;>4Oi*h0XfA|a1j*CEa#!yB~Q?MkI7tc^1(+ifxbT}Oz?87IaP z9-4}Uywj$n2LiT~F~db3lO_l1U{5r1$Zs77M%PjdkGU< zu^C>KCb!1!Gw~@N29_t1DD{lx4>~O<02`Br*fu#+Xl)(~j?$GA-r?^ZS0={33t*|< zUcfwS!#PYwGVUlfz`#TmvL*)|9USnj^8lVp&jkUd(E<_g>9!RDs(Y!e@7*fiN}Gp;J%k;J_@hS&Un zH`FsKqCNH!08{(LLUbXj09N^Syb^XEUin@U;1C4|0=8t*IV@t%<-D6@{9*bC%as@b zx0VkG=pNW>c+jt4^vO^70=|mRHxEF?hc;iP@)YMnRL=lRUY^%>UGTjez+wuLs^G=W z8489MhsZ|{b=6(c~24t#Wl>IMi`0QR%x#gSIoa#(@uFy@tpC#OJ@j179M<%d5Cymb0KG;JnLK7|nym`_C%%x~}&$xYPZYw7PS1X(N4&s#z+Itk12rp&$xg^wFwWlfRA22KnD$?1(K&B_6z&~$a|YB!1%B! zF1?Zh?g5O~tLRf0oCDxU0N~odfHUs{IE=6RYd1O+?4-{HIFkunf9D!=X8|6KTUm>X zf@WBFl{2kusf2Tp0I?~4v*~f!3SeH107d2;o;cnTZVi@gkdpm z5#k~j0)<~Te0(*X$C*{?eDu)+czmZ;`w)PoT7%p(E4VW>M9c66DOl`}-d({rMumd8 zjuccHME9I6?VsQ{G%Wc^ihTf=QSd3PzJ-C-9ua21zY3F>CEp`-(|~vx5Zg@H2m}o4 zt>9aUc>&YrtWXOCJkbYmCJgW&LjXG(?IU4eEEIfhz}~*t>tXA!qF~X1;{bl~j#P?UWglQaS&ygAHX+o4rFajP;ZxSHYl!!)(C-lk zU*M-(!c9-&>}bod@+s9IwyIa^QRGTfDZ!UqiUA2P;$~`3#L9a_)-GF7$1xvIU9rj4Bve1z4N`9*_|ED zO0tqBbvnjz5IF?@_-5X`_q{hiEp0v-AjrnW2Qjo%`cM=tKGS`A?89O%x19g>AU=uZ zIh-h1>X!rd{SMP`Yoo{hYiLKLS5#1i@;V;qZm?502m*#K;C20!!QHm!hl>kIT=k8z z^x}RhdKQ2w6rK=ptXL6SO$vA%+)>7jIB;fPH8~W;MJGzFW`#imzr<<+ivzwq|Lf@G z@$+Y2vj{QF<_gvUGs2K65iXY!z+M2ny^jt! z)iX94uyQZ@@&f>-`>zVuc<)UErtO&kZ{r!k5{qOKR0R0<FE zQiL@r;Pk(!SMyHbw|Dw|oWvLJ;!BcA!6-t6UeMZb^3{@fwtdFwwFs}{#DML89k9yx ztH~97y)AmAg4x$33Leyuo{S`7@U2-7$~;|!PHQE_T)I3yxw?9D^7GSsU)*7UBbR1t z)R7M<0+;TW)b21`?Q$TaN4e9_+4 zS9)Wa_MnBf(u$lGs068l&4er6lTfej+)(ytC2qadfQiGG>AMEZBN|`u43zQeMa)S^<2>CG2^vR&4%ZfJ1ig z1>nH*!eEg)SyO;z)##=J&a%foFq5oWFXmcldSZdhMAUuoC#fbo?yjTBqz{BaujFuF`{;aCEnhX@BDv#A_ZIhQ#n%dGTrlI-^ z+^Sq$podS4hz*8317y95xPdC7-`+>|@|2tJ2r&Tft9;qA?GW$?~{*7wYA`?1yxh<=J5p}76M|$3; z1dO+rJdE_9jk?djX_MX;5>8fV(__SZLOW6!jPU0KSikoUk3>?+gGyi}FGsGt{36#V z{O$NHTONuw!jDWn)a7NNRu{Q&7W@=I_mlRx68bqnZj05g;kSp4LflQG^On{qG#FB8DuSv#J0M6&< zoCea<2#oC9LDs_ zoji>RF@Mr#2yi$4WP$$PH36=VH1fz#Mdp)JzAwWLToGf6JiY+n)mP1m|xdE3pwFz>3rZ*mdhw{=1c?j07+v{2~Snv@zi| z1K$L2ccCkrm8JnalP{InKHX`z`K>qseCU5%z_&j=U~}KE8Sp?DIBJ8KFd}Fj@IU|A VBS$MnIQIYm002ovPDHLkV1gJk1jqmY literal 0 HcmV?d00001 diff --git a/apps/web/src/components/layout/footer.tsx b/apps/web/src/components/layout/footer.tsx new file mode 100644 index 0000000..6995856 --- /dev/null +++ b/apps/web/src/components/layout/footer.tsx @@ -0,0 +1,9 @@ +export function Footer() { + return ( +

+
+ © 2026 ZeaVis Edu - AI for Smart Education +
+
+ ); +} diff --git a/apps/web/src/components/layout/main-layout.tsx b/apps/web/src/components/layout/main-layout.tsx new file mode 100644 index 0000000..193181c --- /dev/null +++ b/apps/web/src/components/layout/main-layout.tsx @@ -0,0 +1,17 @@ +import { ReactNode } from "react"; +import { Navbar } from "./navbar"; +import { Footer } from "./footer"; + +type Props = { children: ReactNode }; + +export function MainLayout({ children }: Props) { + return ( +
+ +
+ {children} +
+
+
+ ); +} diff --git a/apps/web/src/components/layout/navbar.tsx b/apps/web/src/components/layout/navbar.tsx new file mode 100644 index 0000000..352d43a --- /dev/null +++ b/apps/web/src/components/layout/navbar.tsx @@ -0,0 +1,84 @@ +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { Leaf } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useAuthStore } from "@/store/auth-store"; +import { apiClient } from "@/lib/api-client"; + +export function Navbar() { + const location = useLocation(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const setUser = useAuthStore((state) => state.setUser); + const isDashboard = location.pathname === "/dashboard"; + const isScan = location.pathname === "/scan"; + const isLibrary = location.pathname === "/library"; + + const navLinkClassName = (isActive: boolean) => + [ + "inline-flex items-center rounded-full px-4 py-2 text-[18px] font-medium transition-colors", + isActive + ? "bg-[#48A111] text-white shadow-sm" + : "text-white/85 hover:bg-white/10 hover:text-white", + ].join(" "); + + const logoutMutation = useMutation({ + mutationFn: async () => { + await apiClient.logout(); + }, + onSuccess: () => { + setUser(null); + queryClient.clear(); + navigate("/"); + }, + }); + + const user = useAuthStore((state) => state.user); + + return ( +
+
+
+
+ + + +
+
+
ZeaVis Edu
+
+ Smart AI for Corn Disease Detection +
+
+
+ + +
+
+ ); +} diff --git a/apps/web/src/components/ui/button.tsx b/apps/web/src/components/ui/button.tsx index b0ac053..4b88389 100644 --- a/apps/web/src/components/ui/button.tsx +++ b/apps/web/src/components/ui/button.tsx @@ -1,25 +1,25 @@ -import { Slot } from '@radix-ui/react-slot'; -import { cva, type VariantProps } from 'class-variance-authority'; -import type { ButtonHTMLAttributes } from 'react'; -import { cn } from '@/lib/utils'; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import type { ButtonHTMLAttributes } from "react"; +import { cn } from "@/lib/utils"; const buttonVariants = cva( - 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50', + "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { - default: 'bg-primary text-primary-foreground hover:bg-primary/90', - outline: 'border border-border bg-transparent hover:bg-muted', - ghost: 'hover:bg-muted', + default: "bg-primary text-primary-foreground hover:bg-primary/90", + outline: "border border-border bg-transparent hover:bg-muted", + ghost: "hover:bg-[#48A111] hover:text-primary-foreground hover:rounded-[50px]", }, size: { - default: 'h-10 px-4 py-2', - lg: 'h-12 rounded-lg px-6', + default: "h-10 px-4 py-2", + lg: "h-12 rounded-lg px-6", }, }, defaultVariants: { - variant: 'default', - size: 'default', + variant: "default", + size: "default", }, }, ); @@ -29,8 +29,19 @@ type ButtonProps = ButtonHTMLAttributes & asChild?: boolean; }; -export function Button({ className, variant, size, asChild = false, ...props }: ButtonProps) { - const Comp = asChild ? Slot : 'button'; +export function Button({ + className, + variant, + size, + asChild = false, + ...props +}: ButtonProps) { + const Comp = asChild ? Slot : "button"; - return ; + return ( + + ); } diff --git a/apps/web/src/components/ui/modal-footer.tsx b/apps/web/src/components/ui/modal-footer.tsx new file mode 100644 index 0000000..dec1aea --- /dev/null +++ b/apps/web/src/components/ui/modal-footer.tsx @@ -0,0 +1,12 @@ +import React, { ReactNode } from 'react'; + +type Props = { + children?: ReactNode; + className?: string; +}; + +export function ModalFooter({ children, className }: Props) { + return
{children}
; +} + +export default ModalFooter; diff --git a/apps/web/src/components/ui/modal-header.tsx b/apps/web/src/components/ui/modal-header.tsx new file mode 100644 index 0000000..fe8b551 --- /dev/null +++ b/apps/web/src/components/ui/modal-header.tsx @@ -0,0 +1,18 @@ +import React, { ReactNode } from 'react'; + +type Props = { + children?: ReactNode; + right?: ReactNode; + className?: string; +}; + +export function ModalHeader({ children, right, className }: Props) { + return ( +
+
{children}
+ {right &&
{right}
} +
+ ); +} + +export default ModalHeader; diff --git a/apps/web/src/components/ui/modal.tsx b/apps/web/src/components/ui/modal.tsx new file mode 100644 index 0000000..e536f04 --- /dev/null +++ b/apps/web/src/components/ui/modal.tsx @@ -0,0 +1,82 @@ +import React, { ReactNode, useEffect, useRef } from "react"; +import { ModalHeader } from "./modal-header"; +import { ModalFooter } from "./modal-footer"; + +type Size = "sm" | "md" | "lg" | "full"; + +const sizeClass: Record = { + sm: "max-w-xl", + md: "max-w-3xl", + lg: "max-w-5xl", + full: "max-w-full h-full", +}; + +type Props = { + open: boolean; + onClose: () => void; + children: ReactNode; + title?: string; + footer?: ReactNode; + headerRight?: ReactNode; + size?: Size; + closeOnBackdrop?: boolean; + className?: string; +}; + +export function Modal({ + open, + onClose, + children, + title, + footer, + headerRight, + size = "md", + closeOnBackdrop = true, + className, +}: Props) { + const containerRef = useRef(null); + + useEffect(() => { + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + if (open) window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [open, onClose]); + + useEffect(() => { + if (open) { + containerRef.current?.focus(); + } + }, [open]); + + if (!open) return null; + + return ( +
+
closeOnBackdrop && onClose()} + /> +
+ + {title ?

{title}

: null} +
+
{children}
+ + {footer ?? ( + + )} + +
+
+ ); +} diff --git a/apps/web/src/data/mock-diseases.ts b/apps/web/src/data/mock-diseases.ts new file mode 100644 index 0000000..06d0ea4 --- /dev/null +++ b/apps/web/src/data/mock-diseases.ts @@ -0,0 +1,61 @@ +export const mockDiseases = [ + { + id: "nlb", + name: "Hawar Daun", + slug: "hawar-daun", + severity: "Tinggi", + imageUrl: "https://via.placeholder.com/320x180?text=Hawar+Daun", + pathogen: "Exserohilum turcicum", + description: + "Penyakit jamur yang menyebabkan lesi pada daun dan dapat menurunkan hasil panen.", + symptoms: [ + "Lesi lonjong berbentuk cerutu, 2.5 - 15 cm", + "Warna abu-kehijauan berkembang menjadi coklat -abu", + "Nekrosis parah pada seluruh permukaan daun", + ], + prevention: + "Gunakan varietas tahan, rotasi tanaman, dan hapus sisa tanaman terinfeksi.", + }, + { + id: "rust", + name: "Karat Daun", + slug: "karat-daun", + imageUrl: "https://via.placeholder.com/320x180?text=Karat+Daun", + pathogen: "Puccinia spp.", + severity: "Sedang", + description: + "Pustulan oranye pada permukaan daun yang dapat mengurangi fotosintesis.", + symptoms: [ + "Pustula oranye pada permukaan daun", + "Daun menguning dan rontok pada serangan berat", + ], + prevention: + "Hindari kelembapan tinggi, gunakan fungisida bila perlu, dan perbaiki sirkulasi udara.", + }, + { + id: "spot", + name: "Bercak Abu-abu", + slug: "bercak-abu-abu", + imageUrl: "https://via.placeholder.com/320x180?text=Bercak+Abu-abu", + pathogen: "Cercospora spp.", + severity: "Rendah", + description: + "Bercak kecil berwarna abu-abu yang umumnya tidak menyebabkan kematian tanaman.", + symptoms: [ + "Bercak kecil bundar hingga tidak beraturan", + "Daun kering pada area bercak", + ], + prevention: + "Praktek sanitasi, buang daun yang berat terinfeksi, dan pemantauan rutin.", + }, + { + id: "healthy", + name: "Daun Sehat", + slug: "daun-sehat", + imageUrl: "https://via.placeholder.com/320x180?text=Daun+Sehat", + severity: "Sehat", + description: "Daun tanpa tanda penyakit.", + symptoms: [], + prevention: "-", + }, +]; diff --git a/apps/web/src/env.d.ts b/apps/web/src/env.d.ts new file mode 100644 index 0000000..21056aa --- /dev/null +++ b/apps/web/src/env.d.ts @@ -0,0 +1,4 @@ +declare module "*.jpg"; +declare module "*.jpeg"; +declare module "*.png"; +declare module "*.svg"; diff --git a/apps/web/src/pages/dashboard-page.tsx b/apps/web/src/pages/dashboard-page.tsx index 68748ab..aadf457 100644 --- a/apps/web/src/pages/dashboard-page.tsx +++ b/apps/web/src/pages/dashboard-page.tsx @@ -1,118 +1,116 @@ -import { useQueries, useMutation, useQueryClient } from '@tanstack/react-query'; -import { Link, useNavigate } from 'react-router-dom'; -import { BookOpen, History, LayoutDashboard, TrendingUp, LogOut, CheckCircle2 } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { ManualClassificationForm } from '@/components/manual-classification-form'; -import { ImageClassificationForm } from '@/components/image-classification-form'; -import { DiagnosisCard } from '@/components/diagnosis-card'; -import { useAuthStore } from '@/store/auth-store'; -import { useUiStore } from '@/store/ui-store'; -import { apiClient } from '@/lib/api-client'; +import { useQuery } from "@tanstack/react-query"; +import { Link } from "react-router-dom"; +import { BookOpen, ChevronRight, Pill, Shield, Scan } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useUiStore } from "@/store/ui-store"; +import { apiClient } from "@/lib/api-client"; +import bg from "@/assets/images/dashboard-bg.png"; export function DashboardPage() { - const { user } = useAuthStore(); - const setUser = useAuthStore((state) => state.setUser); - const { dashboardCompact, toggleDashboardCompact } = useUiStore(); - const queryClient = useQueryClient(); - const navigate = useNavigate(); - - const [diseasesQuery, summaryQuery, diagnosesQuery, classificationsQuery] = useQueries({ - queries: [ - { - queryKey: ['diseases'], - queryFn: () => apiClient.getDiseases(), - }, - { - queryKey: ['dashboard-summary'], - queryFn: () => apiClient.getDashboardSummary(), - }, - { - queryKey: ['diagnoses'], - queryFn: () => apiClient.getDiagnoses(), - }, - { - queryKey: ['manual-classifications'], - queryFn: () => apiClient.getManualClassifications(), - }, - ], + const { dashboardCompact } = useUiStore(); + const summaryQuery = useQuery({ + queryKey: ["dashboard-summary"], + queryFn: () => apiClient.getDashboardSummary(), }); - const createDiagnosisMutation = useMutation({ - mutationFn: async (file: File) => { - return await apiClient.createDiagnosis(file); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['diagnoses'] }); - queryClient.invalidateQueries({ queryKey: ['dashboard-summary'] }); - }, - }); - - const createClassificationMutation = useMutation({ - mutationFn: async (payload: Parameters[0]) => { - await apiClient.createManualClassification(payload); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['dashboard-summary'] }); - queryClient.invalidateQueries({ queryKey: ['manual-classifications'] }); - }, - }); - - const logoutMutation = useMutation({ - mutationFn: async () => { - await apiClient.logout(); - }, - onSuccess: () => { - setUser(null); - queryClient.clear(); - navigate('/login'); - }, - }); - - const diseases = diseasesQuery.data || []; const summary = summaryQuery.data; - const diagnoses = diagnosesQuery.data || []; - const classifications = classificationsQuery.data || []; - const isLoadingData = diseasesQuery.isLoading || summaryQuery.isLoading; - const hasError = diseasesQuery.error || summaryQuery.error; + const missionCards = [ + { + icon: Scan, + title: "Deteksi Otomatis", + description: + "Upload foto daun jagung dan AI kami akan mengidentifikasi penyakit secara instan.", + accent: "text-lime-600", + }, + { + icon: BookOpen, + title: "Modul Edukasi", + description: + "Informasi detail tentang gejala, penyebab, dan dampak setiap penyakit daun jagung.", + accent: "text-amber-600", + }, + { + icon: Shield, + title: "Panduan Pencegahan", + description: + "Strategi pencegahan berbasis sains untuk melindungi tanaman Anda dari infeksi.", + accent: "text-blue-600", + }, + { + icon: Pill, + title: "Rekomendasi Obat", + description: + "Saran fungisida dan perawatan mandiri yang tepat sesuai jenis penyakit.", + accent: "text-violet-600", + }, + ]; + + const diseasesQuick = [ + { name: "Hawar Daun", sci: "Northern Leaf Blight", color: "#b91c1c" }, + { name: "Karat Daun", sci: "Common Rust", color: "#d97706" }, + { name: "Bercak Abu-abu", sci: "Gray Leaf Spot", color: "#6b7280" }, + { name: "Daun Sehat", sci: "Healthy", color: "#16a34a" }, + ]; + + const isLoadingData = summaryQuery.isLoading; + const hasError = Boolean(summaryQuery.error); return ( -
+
-
-
-
- Dashboard + {/* Hero header */} +
+
+
+
+ + AI FOR SMART EDUCATION + +

Selamat Datang di

+

+ ZeaVis Edu +

+

+ Platform edukasi berbasis AI untuk membantu petani jagung + Indonesia mendeteksi penyakit daun secara mandiri, cepat, dan + akurat. +

+
+ + +
-

ZeaVis Edu Workspace

-

- {user?.name ? `Selamat datang, ${user.name}` : 'Pantau penyakit daun jagung dan laporkan pengamatan Anda'} -

-
-
- {user?.role === 'expert' && ( - - )} - - +
+ {/* Loading / Error states */} {isLoadingData && ( Memuat data dashboard... @@ -121,43 +119,66 @@ export function DashboardPage() { {hasError && ( - Gagal memuat data dashboard +
Gagal memuat data dashboard
+
+ {String(summaryQuery.error?.message)} +
)} + {/* Main dashboard content */} {!isLoadingData && !hasError && ( <> {summary && ( -
+
+
+

+ Proyek Urgensi +

+

+ Data ringkasan terbaru dari proyek Anda untuk memantau + perkembangan dan hasil deteksi penyakit daun jagung +

+
- + Total Penyakit - -
{summary.diseaseCount}
+ +
+ {summary.diseaseCount} +
- + Total Diagnosis - -
{summary.imageClassificationCount}
+ +
+ {summary.imageClassificationCount} +
- + Menunggu Review - +
{summary.needsReviewCount}
@@ -165,12 +186,12 @@ export function DashboardPage() {
- + Risiko Tinggi - +
{summary.riskDistribution.high}
@@ -179,91 +200,119 @@ export function DashboardPage() {
)} -
- - - - - Katalog Penyakit - - - Pelajari tentang {diseases.length} penyakit daun jagung - - - - - - + {/* Mission section */} +
+
+

+ Misi Platform +

+

+ Fitur inti yang kami sediakan untuk mendukung petani jagung + Indonesia +

+
- - - - - Distribusi Risiko - - - Penyakit berdasarkan tingkat risiko - - - - {summary && ( -
-
- Risiko Tinggi - {summary.riskDistribution.high} -
-
- Risiko Sedang - {summary.riskDistribution.medium} -
-
- Risiko Rendah - {summary.riskDistribution.low} -
-
- )} -
-
+
+ {missionCards.map((card) => { + const Icon = card.icon; + + return ( + + +
+ +
+
+

+ {card.title} +

+

+ {card.description} +

+
+
+
+ ); + })} +
- { - await createDiagnosisMutation.mutateAsync(file); - }} - isSubmitting={createDiagnosisMutation.isPending} - latestResult={diagnoses[0] ?? null} - /> + {/* Diseases quick access */} +
+
+
+

+ Penyakit yang Dapat Dideteksi +

+

+ 4 kelas penyakit dan kondisi daun jagung dalam sistem kami +

+
+ + Lihat Pustaka + +
- { - await createClassificationMutation.mutateAsync(payload); - }} - isSubmitting={createClassificationMutation.isPending} - /> +
+ {diseasesQuick.map((d) => ( + + +
+
+
+
+ {d.name} +
+
+ {d.sci} +
+
+
+ + + ))} +
+
- {diagnoses.length > 0 && ( - - - - - Riwayat Diagnosis - - - {diagnoses.length} diagnosis yang telah dibuat - - - -
- {diagnoses.slice(0, 6).map((diagnosis) => ( - - ))} -
-
-
- )} + {/* Scan quick access */} +
+
+

Siap Mendeteksi Penyakit Daun?

+
+

+ Unggah foto daun jagung Anda dan dapatkan hasil analisis AI + dalam hitungan detik. +

+
+
+ + +
)}
diff --git a/apps/web/src/pages/landing-page.tsx b/apps/web/src/pages/landing-page.tsx index 58a0913..d6eea0a 100644 --- a/apps/web/src/pages/landing-page.tsx +++ b/apps/web/src/pages/landing-page.tsx @@ -1,23 +1,31 @@ -import { ArrowRight, Leaf, ShieldCheck, Sprout } from 'lucide-react'; -import { Link } from 'react-router-dom'; -import { Button } from '@/components/ui/button'; -import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { ArrowRight, Leaf, ShieldCheck, Sprout } from "lucide-react"; +import { Link } from "react-router-dom"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; const features = [ { icon: Leaf, - title: 'Katalog Penyakit Lengkap', - description: 'Pelajari tentang empat penyakit daun jagung utama dengan gejala dan rekomendasi penanganan.', + title: "Katalog Penyakit Lengkap", + description: + "Pelajari tentang empat penyakit daun jagung utama dengan gejala dan rekomendasi penanganan.", }, { icon: ShieldCheck, - title: 'Pantau Risiko Penyakit', - description: 'Lacak distribusi risiko penyakit dan lihat laporan pengamatan terbaru di dashboard.', + title: "Pantau Risiko Penyakit", + description: + "Lacak distribusi risiko penyakit dan lihat laporan pengamatan terbaru di dashboard.", }, { icon: Sprout, - title: 'Laporkan Pengamatan', - description: 'Kirimkan laporan penyakit yang Anda temukan untuk membantu penelitian dan edukasi.', + title: "Laporkan Pengamatan", + description: + "Kirimkan laporan penyakit yang Anda temukan untuk membantu penelitian dan edukasi.", }, ]; @@ -44,23 +52,26 @@ export function LandingPage() {

- Belajar mengenali penyakit daun jagung dan pantau pengamatan Anda. + Belajar mengenali penyakit daun jagung dan pantau pengamatan + Anda.

- Jelajahi katalog penyakit daun jagung, pelajari gejala dan cara penanganannya, - serta laporkan pengamatan Anda untuk membantu penelitian dan edukasi. + Jelajahi katalog penyakit daun jagung, pelajari gejala dan cara + penanganannya, serta laporkan pengamatan Anda untuk membantu + penelitian dan edukasi.

+
@@ -74,7 +85,9 @@ export function LandingPage() {
{feature.title} - {feature.description} + + {feature.description} +
diff --git a/apps/web/src/pages/library-page.tsx b/apps/web/src/pages/library-page.tsx new file mode 100644 index 0000000..7f282cd --- /dev/null +++ b/apps/web/src/pages/library-page.tsx @@ -0,0 +1,182 @@ +import React, { useMemo, useState } from "react"; +import { Link } from "react-router-dom"; +import { BookOpen } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Modal } from "@/components/ui/modal"; +import { mockDiseases } from "@/data/mock-diseases"; + +export function LibraryPage() { + type Disease = (typeof mockDiseases)[number]; + + const [filter, setFilter] = useState(null); + const [expandedId, setExpandedId] = useState(null); + const [selected, setSelected] = useState(null); + const [modalOpen, setModalOpen] = useState(false); + + const items = useMemo(() => { + if (!filter) return mockDiseases; + return mockDiseases.filter((d) => + d.name.toLowerCase().includes(filter.toLowerCase()), + ); + }, [filter]); + + return ( +
+

Pustaka Penyakit

+
+ setFilter(e.target.value || null)} + className="border px-3 py-2 rounded-md w-full max-w-sm" + /> +
+ +
+ {items.map((d) => ( +
+
+ {d.name} +
+
+
+

+ {d.name}{" "} + + {d.severity} + +

+

+ {d.description} +

+ {d.pathogen && ( +
+ Patogen: {d.pathogen} +
+ )} +
+ + + Lihat halaman katalog + +
+
+
+ +
+
+ + {expandedId === d.id && ( +
+
+

Gejala

+
    + {(d.symptoms || []).length > 0 ? ( + d.symptoms.map((s: string, i: number) => ( +
  • {s}
  • + )) + ) : ( +
  • Tidak ada gejala khusus
  • + )} +
+
+
+

Pencegahan

+

{d.prevention}

+
+
+ )} +
+
+
+ ))} +
+ + { + setModalOpen(false); + setSelected(null); + }} + title={selected?.name} + footer={ + selected && ( +
+ + Buka halaman katalog + + +
+ ) + } + > + {selected ? ( +
+ {selected.name} +
+

+ {selected.description} +

+ {selected.pathogen && ( +

+ Patogen: {selected.pathogen} +

+ )} +

Gejala

+
    + {(selected.symptoms || []).length > 0 ? ( + selected.symptoms.map((s: string, i: number) => ( +
  • {s}
  • + )) + ) : ( +
  • Tidak ada gejala khusus
  • + )} +
+

Pencegahan

+

{selected.prevention}

+
+
+ ) : null} +
+
+ ); +} diff --git a/apps/web/src/pages/login-page.tsx b/apps/web/src/pages/login-page.tsx index 94aef67..ae5aa18 100644 --- a/apps/web/src/pages/login-page.tsx +++ b/apps/web/src/pages/login-page.tsx @@ -1,25 +1,32 @@ -import { useState } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { AuthForm } from '@/components/auth-form'; -import { apiClient } from '@/lib/api-client'; -import { useAuthStore } from '@/store/auth-store'; +import { useEffect, useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { AuthForm } from "@/components/auth-form"; +import { apiClient } from "@/lib/api-client"; +import { useAuthStore } from "@/store/auth-store"; export function LoginPage() { const navigate = useNavigate(); + useEffect(() => { + navigate("/dashboard"); + }, [navigate]); const queryClient = useQueryClient(); const setUser = useAuthStore((state) => state.setUser); const [error, setError] = useState(null); - const meQuery = useQuery({ queryKey: ['auth', 'me'], queryFn: () => apiClient.getMe() }); + const meQuery = useQuery({ + queryKey: ["auth", "me"], + queryFn: () => apiClient.getMe(), + }); const mutation = useMutation({ mutationFn: apiClient.login, onSuccess: (response) => { setUser(response.user); - queryClient.setQueryData(['auth', 'me'], response); - navigate('/dashboard'); + queryClient.setQueryData(["auth", "me"], response); + navigate("/dashboard"); }, - onError: (err) => setError(err instanceof Error ? err.message : 'Login gagal'), + onError: (err) => + setError(err instanceof Error ? err.message : "Login gagal"), }); return ( @@ -29,7 +36,9 @@ export function LoginPage() { mode="login" isSubmitting={mutation.isPending} error={error} - googleOAuthEnabled={Boolean(meQuery.data?.features.googleOAuthEnabled)} + googleOAuthEnabled={Boolean( + meQuery.data?.features.googleOAuthEnabled, + )} onSubmit={async ({ email, password }) => { setError(null); return mutation.mutateAsync({ email, password }); @@ -37,7 +46,10 @@ export function LoginPage() { onFieldChange={() => setError(null)} />

- Belum punya akun? Daftar + Belum punya akun?{" "} + + Daftar +

diff --git a/apps/web/src/pages/register-page.tsx b/apps/web/src/pages/register-page.tsx index ddf2670..e793b70 100644 --- a/apps/web/src/pages/register-page.tsx +++ b/apps/web/src/pages/register-page.tsx @@ -1,25 +1,32 @@ -import { useState } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { AuthForm } from '@/components/auth-form'; -import { apiClient } from '@/lib/api-client'; -import { useAuthStore } from '@/store/auth-store'; +import { useEffect, useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { AuthForm } from "@/components/auth-form"; +import { apiClient } from "@/lib/api-client"; +import { useAuthStore } from "@/store/auth-store"; export function RegisterPage() { const navigate = useNavigate(); + useEffect(() => { + navigate("/dashboard"); + }, [navigate]); const queryClient = useQueryClient(); const setUser = useAuthStore((state) => state.setUser); const [error, setError] = useState(null); - const meQuery = useQuery({ queryKey: ['auth', 'me'], queryFn: () => apiClient.getMe() }); + const meQuery = useQuery({ + queryKey: ["auth", "me"], + queryFn: () => apiClient.getMe(), + }); const mutation = useMutation({ mutationFn: apiClient.register, onSuccess: (response) => { setUser(response.user); - queryClient.setQueryData(['auth', 'me'], response); - navigate('/dashboard'); + queryClient.setQueryData(["auth", "me"], response); + navigate("/dashboard"); }, - onError: (err) => setError(err instanceof Error ? err.message : 'Registrasi gagal'), + onError: (err) => + setError(err instanceof Error ? err.message : "Registrasi gagal"), }); return ( @@ -29,15 +36,20 @@ export function RegisterPage() { mode="register" isSubmitting={mutation.isPending} error={error} - googleOAuthEnabled={Boolean(meQuery.data?.features.googleOAuthEnabled)} + googleOAuthEnabled={Boolean( + meQuery.data?.features.googleOAuthEnabled, + )} onSubmit={async ({ name, email, password }) => { setError(null); - return mutation.mutateAsync({ name: name ?? '', email, password }); + return mutation.mutateAsync({ name: name ?? "", email, password }); }} onFieldChange={() => setError(null)} />

- Sudah punya akun? Masuk + Sudah punya akun?{" "} + + Masuk +

diff --git a/apps/web/src/pages/scan-page.tsx b/apps/web/src/pages/scan-page.tsx new file mode 100644 index 0000000..28fd0aa --- /dev/null +++ b/apps/web/src/pages/scan-page.tsx @@ -0,0 +1,143 @@ +import React, { useRef, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { apiClient } from "@/lib/api-client"; +import { Modal } from "@/components/ui/modal"; +import type { DiagnosisRecord } from "@zeavis/shared"; + +export function ScanPage() { + const [fileName, setFileName] = useState(null); + const inputRef = useRef(null); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: (file: File) => apiClient.createDiagnosis(file), + onSuccess: (diagnosis) => { + queryClient.invalidateQueries({ queryKey: ["diagnoses"] }); + // show preview modal instead of immediate navigation + setDiagnosisPreview(diagnosis as DiagnosisRecord); + setPreviewOpen(true); + }, + }); + + const handleFile = (f?: File) => { + if (!f) return; + setFileName(f.name); + mutation.mutate(f); + }; + + const [previewOpen, setPreviewOpen] = useState(false); + const [diagnosisPreview, setDiagnosisPreview] = + useState(null); + + return ( +
+

Scan Tanaman

+
+
+ + handleFile(e.target.files?.[0])} + /> +
+ + {mutation.isPending && ( +
Mengunggah...
+ )} + {mutation.isError && ( +
+ {mutation.error instanceof Error + ? mutation.error.message + : String(mutation.error) || "Upload gagal"} +
+ )} +
+
+ +
+ { + setPreviewOpen(false); + setDiagnosisPreview(null); + }} + title={diagnosisPreview?.predictedDiseaseSlug ?? "Hasil Diagnosis"} + size="sm" + footer={ + diagnosisPreview && ( +
+ + +
+ ) + } + > + {diagnosisPreview ? ( +
+ {diagnosisPreview.imageUrl && ( + hasil + )} +
+

+ Prediksi:{" "} + {diagnosisPreview.predictedDiseaseSlug} +

+

+ Confidence:{" "} + + {Math.round((diagnosisPreview.confidence ?? 0) * 100)}% + +

+
+
+ ) : null} +
+
+ ); +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index eeb89ec..08b0a15 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,4 +1,5 @@ import react from '@vitejs/plugin-react'; +import tsconfigPaths from 'vite-tsconfig-paths'; import path from 'node:path'; import { defineConfig, loadEnv } from 'vite'; @@ -7,7 +8,7 @@ export default defineConfig(({ mode }) => { const apiProxyTarget = env.VITE_API_PROXY_TARGET || 'http://localhost:3000'; return { - plugins: [react()], + plugins: [react(), tsconfigPaths()], server: { proxy: { '/api': apiProxyTarget, diff --git a/bun.lock b/bun.lock index 00ebf72..62b5cc2 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "devDependencies": { @@ -50,6 +49,7 @@ "postcss": "^8.5.15", "typescript": "^6.0.3", "vite": "^8.0.14", + "vite-tsconfig-paths": "6.1.1", }, }, "packages/shared": { @@ -297,6 +297,8 @@ "get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], + "globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], @@ -381,6 +383,8 @@ "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="], + "tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tsx": ["tsx@4.22.3", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg=="], @@ -393,6 +397,8 @@ "vite": ["vite@8.0.14", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", "rolldown": "1.0.2", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.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" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw=="], + "vite-tsconfig-paths": ["vite-tsconfig-paths@6.1.1", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" } }, "sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg=="], + "zustand": ["zustand@5.0.13", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ=="], "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],