From a3870224fa6cb10daae326e6b24d96458d8ba4ae Mon Sep 17 00:00:00 2001 From: Ategon <benjamin@barbeau.net> Date: Fri, 17 Jan 2025 05:19:57 -0500 Subject: [PATCH] Create new site design --- public/images/d2jam.png | Bin 0 -> 26024 bytes src/app/about/page.tsx | 3 + src/app/games/page.tsx | 3 + src/app/layout.tsx | 10 +- src/app/login/page.tsx | 11 +- src/app/page.tsx | 44 +- src/app/settings/page.tsx | 1 + src/app/signup/page.tsx | 12 +- src/app/u/[slug]/page.tsx | 29 +- src/components/announcements/index.tsx | 8 - src/components/footer/index.tsx | 20 + src/components/jam-header/index.tsx | 15 + .../link-components/ButtonAction.tsx | 25 + src/components/link-components/ButtonLink.tsx | 25 + src/components/link-components/IconLink.tsx | 18 + src/components/link-components/Link.tsx | 17 + src/components/navbar/MobileNavbar.tsx | 111 ++++ src/components/navbar/MobileNavbarUser.tsx | 102 ++++ src/components/navbar/NavbarButtonAction.tsx | 21 + src/components/navbar/NavbarButtonLink.tsx | 21 + src/components/navbar/NavbarIconLink.tsx | 16 + src/components/navbar/NavbarLink.tsx | 15 + src/components/navbar/NavbarSearchbar.tsx | 9 + src/components/navbar/PCNavbar.tsx | 152 +++++ src/components/navbar/PCNavbarUser.tsx | 50 ++ src/components/navbar/index.tsx | 521 +----------------- src/components/posts/PostCard.tsx | 13 +- src/components/posts/index.tsx | 32 +- src/components/user/index.tsx | 30 - 29 files changed, 718 insertions(+), 616 deletions(-) create mode 100644 public/images/d2jam.png create mode 100644 src/app/about/page.tsx create mode 100644 src/app/games/page.tsx delete mode 100644 src/components/announcements/index.tsx create mode 100644 src/components/footer/index.tsx create mode 100644 src/components/jam-header/index.tsx create mode 100644 src/components/link-components/ButtonAction.tsx create mode 100644 src/components/link-components/ButtonLink.tsx create mode 100644 src/components/link-components/IconLink.tsx create mode 100644 src/components/link-components/Link.tsx create mode 100644 src/components/navbar/MobileNavbar.tsx create mode 100644 src/components/navbar/MobileNavbarUser.tsx create mode 100644 src/components/navbar/NavbarButtonAction.tsx create mode 100644 src/components/navbar/NavbarButtonLink.tsx create mode 100644 src/components/navbar/NavbarIconLink.tsx create mode 100644 src/components/navbar/NavbarLink.tsx create mode 100644 src/components/navbar/NavbarSearchbar.tsx create mode 100644 src/components/navbar/PCNavbar.tsx create mode 100644 src/components/navbar/PCNavbarUser.tsx delete mode 100644 src/components/user/index.tsx diff --git a/public/images/d2jam.png b/public/images/d2jam.png new file mode 100644 index 0000000000000000000000000000000000000000..1a7516224afb8535a13a78c496392689cfea1333 GIT binary patch literal 26024 zcmbrlWmr^w+daGi2}u!<l9rGbM5Ga<8-`Bl?jBM~0YN|-2I=l{=<bki5Q!m&X2{{? ze(vjfp8xmv!^E+VW9C=qKF@Wowf0w4Wf?qd3TyxX@Z@AassjKD;_+3Eh4FX=$3&#z z@dL$8T}A?^7<;)30JMPI#}6RyjDr=gj8_(!S0^f({kwxcmR4>i3mvF92Q@*m-_75v zj5|j@JsHz@`uwAs_^YS#&(Oz0$)4fvoScmXMMmbZ&<fO6`FOfQATbwrNbjZy#skH1 zcTH`NQN$sRm8Vz1-fl@Dq!4n2P8a%20_*?hTdmcvOn@$wSC8|yM83BKs4`8|nZUj$ zumo^7@*~z#?!<r}i8Fx?7^i`}*HTKz@1N~o@CsxTkkc@~6~z}n3M*8A(uWRj(h=w2 zP8;~wqN-NQN1Ff)-%GtmMcN3SFt=+516s5}JfnkRW;;i3?0pXsQ7v{QEo;e=kgFq> zZ)$GPKUma$AKj@077jKMtm&O!`pE?lpw|(hRjIq4sQ+2xCoUhe7hO_`$q$XF+^8rc zvSa|m%RVd_?i@9sCXQu6wn9N<O&$?tA1+RhB2Jqso_~&DP5sEI0q7JHv!+|zxzQw? z%6~x}?56+tbjI-7gXD`Q>URfwdP9vP*T^9mYwu@(+fM>E{x&gpWS|^5Q;*$uN1w-? z?Xub|*T%wp8=&UZn0|rxiXsiADrX{@ss1Tg=Y=KDhq`v{2N&xg<XQf$5ta{DD2w_q z;~d+}B!0(7H#XLnE&@`hL7;dYr(|Rsn7=jaA@7Hm>Dc!X&K}%4gWTjiMi>XJq2`9C zYY%RiOvJ_97}a^kE#d2jP$xN>?8kMl^PX<A_7JBzU1g(wY2@m_4qH)<aTce|%rQma z`>F`(4oR+4#;1nOOD^Gwff#U-n=*0NYtjz6P=7O$w6SPRa&SFv-&J%=Vx&O%7TcP( znS$1@Ok6dL5+lDPNUi8Aivkv$c(vXPwR=sG00^WJU?3HiN-gjY28Fi24*R6bl5e&V z=4EfOSw<kcO$d3la{8Zu8z0awJZ>$iyc5^O`&Kk$OG}CbeW52`z?C*Qav9&jI*D!9 zAJSMSk0%e~(p<e_u-HVa1I^`pMtRkY)35WQGdrN{<UMdQwZ@RD^h=)S=kd2RyY|B9 z8w^0*;~QsTfjhA;?)*sJ*G(^zsQx$1f4^q{CJ6LGft#{MW6uzJ(xi9#gbbPBYI)Gy z&(Czx0$S6dn7-BF?!?U#ZsyM+EF$D?q>uCTlU`+!(k+GV={GGbPAZqoW>rtHVfsGB zB>;X(%j~pX<pV4pg^==a$F|wCahH{1E4Cn!sl~gn{X{iQ$pS4zLl0$|fqZ6dmn0fl zt08XSjhJv?R%oECC6PrTvL>Ba+!9HzKI7rYT*#6gN}c=d+$&c)93UI;$nIAbk#X_| zpYgf<75J9v8?$&;`_8~K7nR%AR_<Lxd`#c6LIz$J^p<k59?xzV2f!VT1GCAiG;M`F zo_bbXqqN#P3a%z)#-vL6bp1&!$D9y<GD)<eP{`&x_s;&U>-4auVMldokjkot2i~i_ z;YuN~_wP`}H^Z!U+Bew;F}pnDi$h^jPPeV7s&Onv<29D=6uj-p_)2h`NNAXavf0i` zsPuQBTWqUHz@p&#NA2|0XT~S2IRII;{Ky^!5I<9|Q!s~ktC}+S1Ry%{581@~L^-I~ zm9W?DUx-SEa4u#4F27f~VWvamd$9bH>eE3jO(JXvt)%Tr$wG72h`N?ux}hf0{{3Iq zl7gSaiIV=f0h<o-fgdN#M+_e$=$PC`k{B}KI?CY-ou+q^w!z7Z|87@a3X0U3QwA_N zT|Gm{KE>WBIDv@DsQ|Kd?oGWDBZtvBColx=rX1J8Xeq@L<7{}J&tG!Yw!f_0S4x(I zz7UO+oXtUo6)yOnke`lReY|Bf)&N2jF2jPJ2Gf3zBHnqEs3OwU0aHnCVaDEWHDG_d zIlkl!r2lkQTH!~^I8Car?G&f%Syt4q{&Hh$9!qqbfaYx2im*?&n|wuBF0e^$nb3sL z_&0CLs688G$;q>AJEubb?r0o@9foq`9uh8tL4H5bKEO-Iq`7j}&UzSU_zZ8jOLkv? zCVPx3+V2K5SZZ3?qE>?apVad1bembeVB1bitGLe+0C8`++ScvFaNuU0q4?nU)Y(?t z2g{EcA9(Tr+><t4Xd*y#2J$FAu(wpU;8{#b%jpt!hrJC0Rq5$RQor7*Se{qBvAygw zhB;`9iU-YErMukhI6<m@xHBtHJLaz+USI10>QEfa8H0O~M1!iHLv!G}A0jS(F3gNx z3|Z^SkA8C=N2O0DfRKE=77fLDoMHe&Vsn+@y^@&b1^+#W5ymxvhfRv)owSKR5}lN7 zyOm`)P8U)*h4wC!T#Y_3T*Ddg7!o=M%nb(4Fjzu1@ABU+Uuh(R2}4O`5#Oq$R$%QV z0MGCxBT-CKJcgU!kKYZW0IsoX!KTA{+G12&$z;^UyCJQznoSr%o_ikj_jVz9ywxY= z_@w6l=0a)aNUhtgqo#K*oZJN)1?eJ&(E*oYuf%Z1i;;gDf6Oc^zatH3n5JJceqwSZ zyx`!%8JErxsqWRSqM1{jp9jNLj7Bqw#^t>nM95OVu*lwHYh(j7H53|h&J8a}?pJUj zmz+LE+gQ*EQ!QIZWIn2FZ;8C;m^c$2kxSo$M#C49t@6|K_TLGstfuA)zp^!6i4GkX zJ0-!a@V~$D`fs_wQzkCET%_6g$;ELdPr)}VTZmG$RG+xQQ^Dz%cOU6goE_5(6sg+i z9fv2P(=N9vG#FjJfmY|xCa&EWTWmG|QFsZdN5UU4#bKj!asec5QvrJm*d{ykfv*q0 zCU%V|$x=P+=x;5YlJ6${ljLT5>R8*a1BW6tlgfIj6&Z`bC***q!HwKtuIZSL^Mv;F zpLk8cyqgfFQsjP2?&cSL5<B!!Q!Fn1wUNef#2AQ(G8UCqLqj{>4FmHQAQL&vjOXgj z(DvUL2NhIgo5OHoa8bE$svC#TU}&yHy>=ySC-{lQ4(x+adLpyXMu!4eP3<c&?COXl zoVFYwJ}N1Jbz<@AkE$36!9yk_y)p?_9X*)x&mmee85LZH3!QJj^%!eh>~3vFArswi zjVB08kv`OR)GjV)h|gdXo*qB8Ir<K(i5?s!O4xJ!CCO-~z}rW<dP3iwb^m2kjL!1s zWfcE@^3nBJzv~x#0)U$((N8!2-=h6ZQPpc>+{5nYliI%_Tsw@845Wbur;um0p!=`~ zt%ZCx1=os!W7?3y`*h(-3u(vsm)c%a4@Dr&@(tKif{VD_#nE{ZD|rY}7lvk5vk}S@ z{Ux~>J^|l~ft_bSNY-}?*DZ<vP1aQcW$*$a&61M7>J7>yDHB;IMc&iFz~mODP_#u+ z?}A7gZ!#eDMw&aru2!lqoc!);X;YUI%{5{BXVXKQhKyH~rjXlbgX!W9aT*5L01rQ% zeExRRl4e82Q$-l-)$c1TN%c-JWcjB+!S;T;sxGrvQw=++zabFqw|5`k9?DXkX6LA} z5N>&o8NqsO;)ffw9xL}&pfdj7m@K!8DZ1X~A_*e-D(DO8sBpjyyzGvz40TpJ)Lhd9 z78=lFApMFx<kvg;n?49y=xaCeL;ir`GQTTX%&vxTqK)&1<j^SMt|*FjBu;Cr)O7Ir z`+V*MA^`Ux8FXqy|1T;?Pa2<DIp$6Da85|u*?HDlIJ`O~;cYlI2Ui`mg?VP<M&t3m z5?b(){(bla%WB%#EuGq}w5(V4l17ZDY_2jrcG>BnGwfLdj_^5u&i=da?$-Q5r8P-6 zb^%2I#<+kkt55PE-f?y~o1@x-CyZ(GvFh(6UhGT!$V@_H6;Gp3qR8^qz4wHO!#puW znThOHUYWuFi&xSUW8cD1<z6LMx@=y>bs7P_;{maolypM|XZ4N6=B;k!h=DVe-{4;6 z{4(dS(a>92&vn=S7%FObvb-S{&92C8tMrtI*zEA;9)*KgTfKGIVLx$L!aw1bw`H`Q zrXnCS9wzojRXv*<2(Pj7^+UezJR$r{@UW6(hE3PPaQr2pq8pP+xj<d!3+SDLd*ahD zn>J)&(=(qPQQvm2^7FgI?*<1vbLaUqEJ2DI>E-==>`~&J??0}Q*LZ4Twc9nV!+fvp zr4k)T{EVu+`2DkxK|dH6{}+;_gUIcg?u3ReJDy!vT=l$fEtDIvR11K=hr6TyeqQ!- zaQY2NNi=PzcAYebyOGN4s84L&Kjl$>XUUz8t@%5QZQpNG!a=FlBbV=qgU1|zbHl4R z0kDID9H(olZOf+qP+H@_<pzfOQ{YC_=ti6y0XerpuX?p((sKR0N)=bv1_&=FMq3~T z9!{4k2hJ=Lj9&&8pz07+FxWnk_gt+wc!GJq7uD7ttHggVVbMMI@%vk^Hra>Xhpk(? zop-6K+65y8S}*4{U(7Tw1?RRkEX7{`LVeFZ8~!OM4m{`n*DWzV_=A5%ua1?p&JXZV zL;Kcqbg-W&x^1k=Qq~Mde@K9T7_ndFaX-<Rrh=qz*Br#B_h;R8KHszSmRR5^8&1de z2#Zt8uozJvD3Cz<RzfDm;a#JUtHFsY&o<}(z+|V@x3I{9{eT>xtVD0Azh{IhefPGj zUm>0u37emqX|hHG;+}h!`sxPYzIU`3V2P@;Kym(Kh&;jw;0P2!YGD7QBg^l?eSU7@ zlIYZB^+QU+LT*d%^!B0P4s4vpiEQGu+vciAbjTBuQRN^Gypr$vPI;ieEb^{P^b-@; z<c8`hC2$Y?P=X*_ej_J$$GN#4+IJtSx8q|HlWO0vb*JR_ANKRSjzP8tHU%2*LN>-U zfs1S`zJk?8I@R#r=`V?v?{gz`@ox{4hmOz=vakO-v6Py9ZN;pwNkM}imW?Mfx+?!~ zWAm*T_pR}b!-~l4BuYW+ky&sB)%g!+o1rPE7FiTfTDE%Tq}St-itN4z`jPOon!xa* z_cw3RtGM+f^?!C9Zq7Rn$WJCly7`eKe$^TH)~^03aA@<#9NnYCtp#H~Hape}MwFHD zID0OKG{Oq`1Y#3$X6cfx=NX8$_^n_u8pP{*7Ilr1snMq{iP^j&_#M^ltxX3I*cwe> zjNXe1^p0~Mm+8z2E_2ENjW#zHy*BsKn!U6`ks=Gin<Ycw0NKV)S|4Q~8*6tzS<Y&Q z=>9Q<$YTC+j$8};Yo)31JCK97Qnr{~e=--yIMzRE+&L0D@tdKJ7Vz|Ac+nNTOHI%4 zvIi^8WScVzHF0KY9mIrFj5`~;nl8@B6H`%1S*<P%*}?p?DYm?MeS0piwAM1bTAEAT zxh@Jc$<NOp3;l1G5ieYJI5k%?Qp49kGdGVE23nIdwI**Lj^#*;Zp6(Ovv@x<S-M>x zs2BdV0aeUc&GvWHsFJrvE9LVRg;MLkLU=LCC8#fF?X0hOt^I*o-AyELf`Iy9=ZStE z36zmOvd(Z@0M$b?g-`G(#e*azQLvpnPP=h=a2u}HC&4&X^s6mmA@0fS{1af-I^*CA z_2mG5jM+CP2WYHZ{#j4bG!`?mqNVR|I@d3?;;>;V0ng(w=G~^fgPJC4BSzAR5WTg+ z#@RX}ulU3McVMC2wm;n!%ZPsECe;~Y?l{ihI#L%)_?#ai4?cZ+RV961Q~0Na0>l(0 zzu%>=lvmw%&hw5$#Z)P;is^h@@8pTCFyh`@-3xQ6jJdRtX!Xx&)4nm)tsHulU}Ixv zS*W{=+0TN{CbiUs1PyaU28$nm<;p$C>N5l<2?8Us-df-fI)lUu!Gu7~eYd%9I3VkV zAGiCGc1k4Uwo$D{()Xo*14+OODHZ8<S^K8_p_<pR`kc*DM-4k2j&7|qBGvX4A<1%$ zE&oM~;hP^Ep!rUzFK&}E$u^o8C&8D^2F<Y8O!w>(&YxS<MvHwdl}MSV>L9(@t0xPc zYCQf;Tgkgr^izCTJ6fL_*yi+Jrgu=Jr`Vss7%oWmkBC-)78n74(p!SNyA<B(y}=bM zs|g5J+7nx|&}~>uwUf|9ebfbFSctn+fj=~Xih}XNlo|MH<@w#W04K;D2Bdbrk=+V6 zv2-3jbwCobjRv>3)s+oxZrcUsfbv_}Og@V=13OK}(=|;mj2ME=ga>TlKu2A%Du0b` zG&4=zmQJrKSrO_g^RS5X$ZAUBV=$0}Za22<t$|pT3cx^6kDHLJaC5>YQPbZlY38cG zgUjO<R)z~sW!7ka=K8a8m02h3Z?dS}!Wa1$#osqlt}CiTXt)T*Dw#b8)LCP7aKx|p zDvXPt?3EPjv_B7uykKMQ-{^n1e6oQMk-3o)*%uOeiCJ2ulHN|pXPE+45jLh$lUCRo zVWtVas6-P-KPXl%+xBUAX;txWbsXBpbE&X?*vlNkRuRi>F-em8)gDDfz4O<1GcO8M z|3<3Ok27pNzkCGosqs$H0@EFFe<hY>ad0$_@;#u9)Xo|;k;Ad?5^kTtAph+9_SXbX zEfU_Zbl=lQ9i=5e)Q#9dO@Rv^i^?Z1#pN1m*~0^TXaEK;(T@#*xC7<6Ss#*nC2d+U zkeTglv3Q2tf$Qnmv@7;U_-U3*z3{13y%%#GQJY;pAD67!a;yfp^lXlTiHOcKE<9D1 zVG&lUP_FMo_l)w{u7)i{vF#ndsSs~YjxTcv(qknM9Xh1U4-2cKAjtb&Q}hw`_o^MV z+$vl)cJHq*Kva2I=)5oLd2PmqRtkHzGjW$)Dv`)Is-hE6XT(4|X4%zD7{uEVXU6qD z6I+OqDF}K<-c%u6T2>LFJ&@8YQ)Hkc)m=dddn)q6TG+2rq`x*;9b0n%cDDcX)}9<g zT0Gf_#4hQX?&OER0*27oJNT!+1diw_C+bapU!gfUs}(;`kLSOV7CSrksE}}rbrk*X zRds*3u7e=o0jP<D(W>MxRG^TEfk~goOIiTN1I$SJDB!dG(N#45#eaV2(GJbCXKnZs z68I{eGLYGJd@RHk$)g{`OZqRCw>Z<<u{t!qN~+Nlv=@TJDNlIxwfn+XMcw~~p?eI= zBsEn3v76)&BVo&K_Tg>qRuqPk1jRMe&|RC)MaL#eO{C9I7Pr6~Yu9AARyRa*0X?fK z;}%_#Y}1e!lGXDzv#0eW)q~$;EHGrqk)t8ecbAWJ-%2j}13Xtw1c{6hGdP06uIuZv zyvkRXEs^S=qAMyal#%7hUn^M;u{FOijMT#KjgYx}>4%4r)YNq+CYNf&mv8d51AHw( zq#5fm(x~;mnO;TZkho?KbRJ)XMi>oFNP$7oj2Jb-#^d~`B2ANSF}`#fr!~-0GbK_o z>GxH|9NJZlDJIsewuajXGNJiJ%;T(T&`v%Fc=>Re8bRmp8CyN<_qWzGrk9A0<pqe} z4<F^}8~qRGVFPV0x{o~CCqE6Y5*eL&)9OC7co&L?=@wqM>|ptrL)@dFrWd9otShJl zv)a6_<3tG@J$~L(t<+5XOp_GRE^{tf>HzZ{d>Fm)&x2=|70#+&|Cb%|^Egea>6h)0 zV+q8&{MU)93L=^jyZQdRZEAhUBCuA0{-n;Q9r~Mx7!jG)YsaIAlgzs7r_q}GYKgrF z;i8}LeB0(baLK+K4SSZ|AD-f6!@QDH^7gB}kpauyn{%*m=1IQ(s;=~VS@&Z6O~RtD zpQg3AT@hwLo)=#B<W%K1{x>?r)58;vLO1Rgt6~8TL&6tlX71uC7`%GN{y1EdD`hh> z71^d+pHTx#N60bOuW#J69Tt<_3#kRuy=}EWw71cMwsa;le)~rSys^LC<2onvGr>@x zM-((wT$zo~sY9Fd?uTef?mWMFP7iE5x8upUB&jRjf6CY5GIAt}dt7jRe|bOFo`lC0 zA~tTZqOp;Y|57OPVq&*nP+P|Goieq3aklLSHeia$nVuoUO}Z&&*IROJ9;1gRUIPAZ z|8Tx>M99!V)t^y3WMOJ()MijaVZW34^odRTX#}<j!b+L5bpH>=zXhMwi?q)83QwvG zLsIQ7aPWyV&j(mop*K=VtJns~fXJ%*<3LzeoOd~oRxENUZX08yU~NQ|?c>2Rm4P(c zMBYxFz>Y2)mwgTvN`7xV!;xfHwD0CA!Cy))>Aqx~HgS4e?Mhfy%GBWay4pjbcs@|A z6JZ+Q_$x2N-N-v~!e{+;c%0(A`l^~PS|VSa;qjWfyrN{&mV))_pw-?<A%$VbVou`} zWaGyd;kbN~0i~)^k!_%6L$GG~cqKOBwR5>gly2v!jK2X&#!X?elUlP%QN^EIa>FD_ zD=~M*2`LA3WKv*Q+PPo)o+QHJAeaNtVfgwwu&|GPjShFFk#d{kHBub<y;E9A<nTyH z<FKv1CH%F#oZf$XE!pAL(CNL9^EvB$5lls9wI%)~x3cFfb&1U7;9K<lk|F_1Dj+r3 zMf%8x!Y-%Egeg6z($VJOFCL88FMUKOWe-mfOVPc!ZV_c91;6UK;lzjDVBg-3E0J-P zTX4Ie7MF(K#!48M#yM*JJ$kfX#^rasP7Br&leLgYv6xu7EhBlT{0g9tk>mw_wWyzs zZHUdBL3sGvaKod&oI&V?)p;A~xfn5-=q1eQJ=pd3k5BMW7d#)O`kO+LK_wak`u>p9 z^1NJdN#r1DR&~2QB$~C6KT2ZD=f26Qb?ndVK4AT@_^_aX`;pCFAlmqGJo-cJd)bBh zIah%M4>&xkTu;&0I>Q|C3>bugac){kI+JI~Nn3jD*%iCxD8F*rWZCe*Ld7k9l-zr4 z)`}}Irb13^*t~;|+Ta^|l7ZFyicZDzmwpksb;cgeMij6U5z%GZfBIP2$I)0RB)tYG z!DZKJ_ko6m?a+f&(An@Zww@qkhMD+QiPW^cp44c8<}((1n`Ov#<?mY^{~poMv>`jM zdUf9J4<pXsBl)OQ|M2wcm_+VGQqOtZq~i$<iF2#2V}o>>V8&7I!$S6}R!+Z%*Kk9i ztLtUNMW{w{+n1WG-^3lFhdiPHvOr5_vj^{q*^TrWU#t@3uZz$$^QGU#n_h%v^MxT3 z?JZwSZ>XN+T3&0xfp9|=A4fTtewZVune*Y}0-txrD~|tiWqDihI%$!(fvR1p`m>S7 zfu-W^vZt*J3gc9PoCl<eXbffg>YaPo5)K#BFRQwWHy66NM+a$nJ!Hx){_0Y7yb}6R zxBdqsai0|JnPX``*21&5=1G%i41l8js+6fVjg~>6D8ozoWi0}6`)gtPL#2xRi7z`X z8qF?zt!uYz#LhT5mOi8_&EwutZ;oyr>a#)9z1g-uo~BoVRt55KH@iu&RmoWNyEfks z%Uc3uE&C^30iE2mMM9k;vMJ)4Ki0*B(Zq{d)8?@ak3sNeXV?DsTv(u;i#`&Cr7z_( zcI7f~|LmO&itrB`lmrv7q8JkYmIVs`bM)|svLngAy_`JVE+D-%{m&?$HPrz6D7_*f zOFq7RgbQP)wCKEw8D3~ek+<VR@avMcFKS4s#`10_3t`O8*Z+t*ir4K(ADtzV#>E@g zrsk36O`MgUzQd;;tc_#uXZZbD##+e(CA{1)DcQ)<E1UgFFV~dr)ouw>=XCt&9f)m9 z@-LmKKD%#rmKS~eMEA*fklW)Dw@!<2*yB2V{W<9y`8TIUaQ~f$-jHaqCNvXQ<9m_( z--ef8GegY#{)SRq1RPDEk0zAws`J|AeqS;(QmTfk&O195b#q>ymaQbgeXN@$B0>&t z%0ffXi<haUkjPq+#Ck{OZnNoWl1ASJ(z%7W825)Ky8(usuNn|>aLU}9KU+YLRvn?E zXCRZ$Pab)DDNG=aQAtsFMWl3NvwzmiPKYVuwLGISA(Q9jBp7xNM2offyMD1?UHJNT zBlghzn9G-};4rhY|I05`C?!Vl4ff7U3~{BC7?Q3{AsIgj6u+xg%6uNQXn87&s(XK{ z;*~}tepuyK5L{WeY8isQloa-cySZ!D?mVsIb;Z}}{&An+Q5-lgn#)b#HTTB&j+XyD z<z+4sYsD15@t6J}X32#?(4J({n-kDnS<I(UcNcZ99*PlFo2VCT+5N?j4Iq{ofdiU2 ziE1^>g2*NudBYBrI|-rE!=u;J64#THqLkwvP{}}7Cd*VEJBJ_n8n%%p-3l`jPAZlh zB>z;KKAOq^qiLQs$cFi(yV0eZvHP1u{Xb-dlY%D_1c25a9Y_n&`mSwVT;c+LxM{g< zIi`Ko{$mw;rQCsWl6U^&Dy?dzRo|S0!Pdv;2zd(*b^j(d@G+#YYO*$k_j|pp!yNdK zw5GAyOai6#=JTnq)?<ZPkok658dxA!LDenbnb>Nf%<2^}KbdQ%%Pnf#9o&*_z%;!j zkQvhx^Wo;m7xy>Oc$)HWI`&cP-DH(*<~B`sYq}Z(m8*2`600FOL$2HV$Y&&M=~N19 zpqdRLT(RSq!0w7!t=M;kgdHPze(O@faG;To^1;u)&|57{JC(VXe>e)Kye-Gt2Cn5C z<Dx_BCgu%y|Ml*+op`P<S!wj>@^CWX=L3H%q3%!pk$O&t0#x6uvy$cTcVA&Q*p5(4 zdOFq@`*Lcj)lZ{?9QnymG*l{hGlu(8tDkfu#PpXxD29laQ>QueluuPprhXod>4Gcz z>@@9!V2Ce~I%$85T)Qf|h(BTtf=7wI^%@%2{ywxKU4A4cqqbtQhLZ5~Hf9S&UP&X6 zlIuQ$^al^sS2*1beAe*1LJK}xH^}g>7a$v`))f4SyKTmzIUjundU&6?c8vqHw)_8> zA9@Fv-n#$xzS8T$<BQ27?VwP3bGhSqAECR-%V6@{5JIVi^-G(^T;{pX!tN3#Ir}Oi zdH-J9B|m;%4N9r$Yqvg|Fs{BLmXSXEH_x?E4M9-xMvp+a=8BarpY6gitA5c!VrA8$ zU!%uO;Wq{x92u@us^EQ@oYD#KmAWN@e`ieasgkcGDIStRUfxney>LVtk(nk6jKGA{ zx1OYZJV)DE_5z;9wt$BCm5}q5GwTU1Q_J_VqaTI=Zd6W(XSu|?@an<+_cJo`tYY;` z#*-J<1mxw~NS(lQ=enJZ(V0l9f7CuI8|YHe#UzblExP_ET$<7Efdbt#CnMx+E_%ES zk2LbiLxvrP7jB5SVq;1d&fb7%mPEeQtUTyN5y(+Mk5%Cvgr2tmoU7g(P7anmnx8X1 zVd=b)?>f6sjv>KGFVZP%KC_$SNR4(AO+3)~{FOsQYK4u#{JGJz_|!fEv%~#Oq~SY= zPcf-!<oJ&a+uJ4{t4+(XQq>a<#O$!6<_c*`Wi&R`t?ZU#NRr0bT&!8$WKysmo2b$G zo1vu)zZca^=bUyY%wElD_zc>Qy6Q~){G!;#(0%=8b6RaG@q){}z*Mx^0kx@VsS!d< z3z{jO&-oj?SBllR?{Xt6BQk-m1mesroSIP;@maPPmNUH-SF^#H?Xed2#p_HP%8B9C z*J8!%_=7gS_tXPDr3!n^zj9dzI<!($O@|@*N_rSmEZ4WqYb%2d^nb0y(oa|t7X`qf zy{7A4yYeNr3trw!-B(+1Fn>J1dYTWZujIK2-9@^DQ~N>|Jrq!|_aF_LaM4hG+c&5v zKjeixSre<Pq}_WOMr5D6#x4Gg5U)2%dr?+Qt3#P?Umc-m_c;5CyOECfyTMmwaLp>^ zqe5f19iBb@L{IHj5#84-*fdC(C1{a#r?!U!2LHP6=`>?(N1<B1R~+E#8$M`YUmpU3 zH(0moR2J9ebCOZz!QKUq9cOqp_=&P+$jo*@6S3JcEq1a;K`_g5OnE~nER@oY_L=jc zpt*Dcm;Vai+iQd+`^Ta;YhjKiqo(t;I{F_*=@bBKQ5z9L(%iEx7<05}4H}yriF<25 z?`tcftsKZQE@Jkqt$?M$RVI0Sg>=020{*WD;<3g0$$ILPELG>?it@3k-7nl}jk|S% z8X?MN_oliW6MLCz>~&`oE0*`s{aR$9Twg@NL+2d#0!iTU^E4Gb0&&eayThnYj8oC= zMa-%No_H!tjAC(1pdALp>YpW7`pR2{LP`$pm24K*H58K4g6nQlmY~%mgYDb>B<93s zs#6r^$fDNhU&hJpUsKhSq-xyAw)EV243S~BrU^>}Hy^4*-z|`=xptvUml+;7ehxa9 z%|L>)o?xeMOk`y`zn5Ay?U7N_l)_IX+s-JNKFf3EQs0NQSqx*jAV`%@{I?T|oY8BD zcacvK60zH^rnE0mrjAGMS)_}y;}?~NZ3IpLphTO~nNQ@J(dp|7TC{Kt9t(T>g~ea| zf+28IyL05k>DI$*L7avFc)D-p_3*<KV#^*9Xf*sEPXdbUu>UNDo7sD<vZgC0NR2h} zx79_;Z?DSZ!*mO1-7?O%!b5Y^8Y1$!$`JCW+0i_Y85mchUJy&J+r`dC0124lEPoYv zkRKFn-K>159RGg6ZZFGO&MtaG`Vd=xRQ#Xave?>&5EH@-d(XpVzZLo^U#z)aXZ#9R zhmObVrbi)PU~9PZeJ4YB<hK>N7A>E(#N#)TK~>AQJgH94o^+krS_dv+02BnqO268M zM$MJfb)$O5M1h-!8~4sRFBwnx=<x3Sn1%FN@7h#yQHq7jqfq0PZ;JVkjw+)EuQlK9 zB3yg}D@L5rq)<8~gc-3_z9ngm$4>^;@tI(tZh27QeyR8kjz0n;o4+({S?YmVOeS{R zgck0jFo6MgQqd3aO);axOae8DR?@)VfOXp*tXHK>=tz3zK3`?z>pn~3Eqk%`NP}5@ zzz%Q8Z8LI>UhOZGbPOl;lc~1YLiDZKLl`$h6HBRp`x}LajgJ8RC?BL`y1#K35NP!h z8yk>Z3hp}7(e{L6B-n~bjmy-S&0z`+J5(kN`6rdhI^HT!%C|vPt0k`$i-z6!s6HTM zMtF$iepo0Fo5`Ttu~`c$M+_W#FEP<8`L6kNpU;}{ofa9L&La8?2Ma7;)Lg6~-}4WB znVa;Fg2Ya{TbyuskzDk(M5#NsKOy{&lvbX%C5od2TghYc%4f?lG<&3q5`OZ~aUi<L z3O@YI?rP=STE-mjA=06Cg32t~+W2oE-_o?w0240|s2z&=FhuOcMQH?zZ0sv+WNUfq zr^0xUCu+!w`M!Rn&=;G@^I}S3@wH@?GY_XPAb73GlkSF4v_t5rS=*RF<*P(+Fe=7+ zgDq)u;svwHx1Q><uACo<@#J{w;Gme4Ib>f*k-@5b9dvdJbZ9u9mG$#X`@!5V-3a+E zVgDK_HaaTjUYUPe5=*XRe5X6HFo7F>C>sO&mn@!i%|a)^^{L>~crpFf!HZzj9s@gS za~<e@`<27C3+oqHWKwdYNO87)|5*O|i=#&bd;a}5%-th|{-$Mo!F1q*8{mF_O4paQ z|5&`<eTB!FHxdo9+Ien%WP8i62Y(FX&)DJlu3i#t&r>%@=de;hN4zw-1FFfr@|WLC zEG)I?1Q?gqrS7wbQLKfK;<lHWkKVL`*~kAqXgUi3q_bAPB@PhZHS4=>99S)bbNg34 zSF98!xMiBQ@?EpC`^Nnw39(Fx7t-ErFucnz@*7CGed}s=(Ak7-JICJRY$ITRbYu1n zujYZrLS?pRCeEAutwoe&+)MSA_go_{C0^e=K*@s~8rO!e+?uw^<HIx$uy<<z`W-{; z^3>O&I(`M5RJ($E$KkYe0LA!i)84~m;Ps{6@IS)-<MtIhPbXEcuMUeIEb;fX#!>AR zy!f*BLdxEPE%D4S+9>Jv&(|@!GWXd`uq6d(q!jb#*44eWh^H8<-u{a0;17`2QF;;u zHFH~e>?r>Vy147y@n$>8ki<!hK#Q&t-RYL)28q{*sQ&hl6T{1DW;JTY#9KuYLQhpP z;+pD`)1K{8)k!52yb#sQ)r+c=Dt`s9Rc-Z1dwO*795>>`YL*2L?hf32`qvFbUoRVX z<=jRowV#)qtTtq8F)8HOJqv<}iS@?NJW#P!RAvS+q%C;V3iG(QjB(!^Cf(jnJ$>|d zxP7l%wvr_;(AL68%<O93YIO-#NX6!*c-0_r)HV|^-SixCy;Y`|l+AoWy`x31k=6pS z`;#{DEV1<59O^;Z9$_+)zEZ&2_}RoypFrp#3qrVdU)mof>e#`3y-}U4Y;;ndN~jEl z4IAzDoe~S>xKM;Qm#SqjJ+3pA$*9<G=v$4rk|t;iIeJl=ZX9PNLso(+rDtm+YN@oV z{EJjeaQF|=5_3COFCP|YH(w5~QonD}ZLQ*A(hf|0@M&k43b4QK6v2Ja2t4{&U}!$; zjF?DPt{@P(j3xC!<ow9MBNhI06k`_{DEPQoQ~dZOa4?1FtpobcXz>ZIZ=Fa(&sTC+ z;^q*e%B9nA&AOL0lUvg}oUnCfx9UEt_e<b+T^yQqki3vK_JIC%h+{RH`~9v<!wOOx zU~0e@Tpcw(I9V~2F)V|~A<&$+joY^maki>dkCZ2sdL7L3MxI@e-izZIody^iDHJc` zBu1gx&#PXAM)*dFN(-cUj)<(G?E1F1Y`gQ&AI{WKSF$z4B;Z3bUc)ii8u@Jf5VU40 zyVhYz-HBj(Iif=+f$x{Ov#8I?q6K~17AU;!aR5Uvk-k5YOR{*;C_g6|UpAtAmJTAx z?u&;^m45E_H;*h^M!~vj#6CY_u1y`%+#3o{vzp^bzCBf{enwr~SeR#YGUJ#yoCa76 z1&A#gRrW7*LoJ*g2YWooCXo@wv_c`}>Q+P1Pl{JmNQVxY%W~I$=2rw>7P}*ngiQg6 zCqVAFdmK53ut6zN7gWyV0X!|W#}U|MK9<Cg*=XQ6ZFrNAL!%~UEdDRis0T++v#+|n z)*S!j=I?Z^>W^M;2b=QyMXm27?EKa0?{}-d1mJw-;WNXW*z`|^rh4l7@P=G1G*xI( zs;WmAnpJxKvZUzwGUp8(OdZq~Xm4qnP7U*{kufXCk^Z8OFQ$!>ajO0GT2$ZYwOt17 zV}6}3Z6oB(SJfb}+l&yE{{h)0=6jNG%f6l@U-DxAVjpjxRHM-nMKkJrwkDg^31pvo z@}zrwPYz<14z?%vUDT+{O=?)?X}{zz%eBpp(%*+my-O@W&_JJ+w+F^&GoL?i9C6Nh zMTBF;u(xsYS7JUv>HgN}Uk}5`p>{j!=<Oe?=@h*FH<NOdG3tH97AHCOPh894vwK|u zt4|n;m<7>uKGD}Fg1Bac%ER<aiu7{y;q`1nSU%3u-Z_CkAh!^7TA{R>i}~t&r8=tS z@JqAvk|3RQ#OHSNzj8O!0l_PXO{THd-(iH42;OPX&0xSIN)B3XLxM?9ukr1s<QY}h z|7I2X&<Go&xCb0XS*Qw<D6=9^AHU1zZsUl}W{US6+<yMN5#q^M%wCL&*8hv+*~G-c zcDslFr*;#Hlyw6ORm~4l3LM{Vxja~X8U}kVhPMZ{Im4+(59=(a?!R)(Dl`bLy3!t2 z8Y|?r4JB;|cFJ4H;eFGnEu%6yE!FqDJO2PohMc?(<mB!bG72$6%cG2J*A6KCAJiv) z@3W@5a7PnK2HS1F#5K00Z-$C&Qy2YS%H{-*$8i?+e8BGWx<vWyf5e;T+jl6`@JThW zC;)0BW-CTdZM28+tm0esu~NjZ%xJllb}&WIxvF1U#3x5m=JkmWlNY_(@QW;D5-M5C zo|vD<2iRq4)_=5A$w^l(@rInfqz(5L7(05^{^h>kMfiIn$Ht+sq_50m0R==s!4VCD z&c>U{q;^eg@8>F=P5gSobIHujE`J;uF(iUf*t_et<kE%{1%IpwdbK~|vo@K=s(nYQ zg15Z(4<A)3?dc3+PN#PAT(cI(Xbi1DhIbp#0P;)uf1OrIV3cTqhZuW;d~AE@@RsNF zag^af+wJRcnnQP?*QVIEX%(D4%6ayXNDw^79Q!bdbW9d^Z-v5h?f%I<fuqZT5!rx$ zLx_Q-8t%X5xyL(<#dM8=PWXfFi_Ac8+@QzsVHVk2cKmw!i@5OjKJ%RJ(01`7?+P%+ z#&MW8w16AdF1Rz>1D^+d9V7g)wX6x5QjoC!X>_7_kojX2%>1~_g8|X-Q%mz=_4qf4 z$tC1c1j_u$unZaa4QbQc%*;J)dpZQvyN0*|NE#a+%GkI)X#ZbjK@YC6^ZwcZom+jH ztp>UEs2#|J4yW>DfKkL43X)=bA=C<0x2uH<mr)UqG;DRO{bP;!Najiqm3_IBoV6!7 z#29YEgCfpEkvor)9g{Gw*&N_q?<Af0xTTNaBT7ux(VjOYL$GVg;Ag?pYgV&wz15St zeV)4&2za|lf6SU_U(XmF0BUmYmQb%=GFu#GnM)O~Fc|%4+N*MjwV=8R{^CCTA8V*6 z0|(-DQBXhvTLGi#W&rf4Y07VEWkVPW;*A^YwV?5{ByB#LSeNj6*tKc3YEc-qwgE+| z?=%}lOjNY}iI6AJb<{x#tY3aaE?w{=1A72u=&es^kx1;;6_EOoB51RNC1iZh!MyQ) z?-Rz++O}m)D4u!dEjx?mc@AQQ-}s<ZD-85_c1+`&fnKYxNsGtyIZrgVQ(x<1Yq!|5 zO1he4tdl+}>B#=G^E-cDV6fs1f5IgXPhigD&O7>2(KM-|M8BZ;Wr`p7k4x^+#1&+k zwsidlA^y%m#dK)Cj>GGshKs&m-fE!W+>%O?%k!+u0Wopb2s2bpbdEgtB_10|EcJB% z+7R$pZ^nOPp>5YNamXKgw9v2+V2XU-XRbV$Wc{r=SxH-Oi!+Hm-xs>_u&fBnI-jk+ zM>sLeXk!esZc&ddLiU%p1eF;|jeR*H+d&IK?Viwowev@`O`^fi_>Dd-{(6m%eL>%d z{02|IrQtl(Xma*!{@o4Dg2Y3v5Rfv@C&2_k70<7QIE}~&dw~@ZWdnScu1-FZO0}8B zD7aZ1o&T9z@sRH={<aA*XR|^3iM<8InECOINi<fc01XKzUy@0g!Vv|3LupoZrvJ_D z;eAgd9_n|pFA1r#@!$4OKC&fntVO9;?0TwYo59x&PW<-?NlQAT?<M)PDr$t!Xy$wy zXKUZkYS+|^HJJGX|387kB56!5Gz$gu!H)xJJRv1`AmEvYkkBTlnyGaL?C36{*(SBv zfK5ghn=OGceUgZ!mdJ)@qp4X$B>t6125si8n<c%#!U`l+Hg43b)v7gB8$16;&aKKg z%Cm~-=N0A70#aLy0vUE;DhYTa2K=rgSVV5l#^*~$2p_(TnW;#z%SJArQj)A@>2Rt9 z0=z(_qQ`y-yP?OWah=Sw*?ohVxMNb4fkb-XaI;0;_Wf8=mdK7LkELY|m*yv3?8KRs zFfFF2MEyQXM&6>jFxC*dG*cz_Z$Q<CcI>C{drfOW4v~0gp{HC#jIwW4@jA+74DwdJ zDiQBC;=|A@AJH4J#M9OdZ6<Zy9<|fTq*ZU~wwB!l+W_%cj<BcRt_!;FQZ4NFe=ZF^ zd-ugF=2zyEPr29<%K<&TwlZ4dlmbU>mA~|>$ia%foHg7yO#O$laaNBGf&X#1Jfdz1 zmh837NL#4NyXBR`w3f>%H{uTA+Hezi=2*x#PrA_lFsv%>xm&N%g4!7Y1oVCSYF(uP z-gVg)=i|hhoF?@NXEOr#%l1154V=uFGK%f+#q>SM_<$#QwbEE>d9+Q!`&iSbTis(? zK-P`^E}KtxpX-c@{`lwr9&p=ZCO{=7XCebgoUOEOdecR-UyxlSh+s{UaD`SW0106Q z@SKOc0+k}JWiK;1{q}3unWTJYQHICS1x5XeV@T{JE7U%Cu_9)I-}wh<k8y?`H{Ugs z+qqgOz1BrB4BKsL{iW`>TAW-mmd<sqG}i)xYYavZv0D<Q8}zMRXGY1V*#x*=<wFfM z<$bj1dJK}dyjCyfCPDGdj?j6aNazSGbwp%oen$e;=v)5p0~SfQARg$UxWjwUK66`6 zlhd8K+^S`z(4B_Sa=J9u8yN$ytHNpB!26nwEyJb$N)3RBtoKMXU2gF^{n7U!f7V^l zM1*QM{H5GH`6ow;a*%>k%d;-Na!~T7Fp(x6z#`-c)MvLKp8r#3`uKU#GC|-~go6zh zLEtW0jM0%kezPnJ1Q&xLG)12Yl%|2)w*OAvJY?zI=Eupmn}ql}j4z@g<(AmwK8QoN z{DM!yz;}ln`%hPU;EXnjmB9C*R>0HVbfeD4vYeTr<tg2EMa(h=zw+ydngx!3{fv*a z%-@x{?tT3GAE;mGHx4AQoCcE>j783;_M+R3x5VdKyoN0sA0^CfiV@bv1lrJN8n*GS zC7b_8GLm+bCcJBF*!4?{9e0Fgyr2tB8PewWmqy8OSxap27zfweU!oQ-XC|e0QD=}< zUsNU2xF(hGnzoU<U~5VhTfbJCTlqLnwp;yCZp<QC2h0I<Mz)TZ4>0jYA5pMs#=q3& zGd|k04o3surz#3dJv!{HhMiE=3`b=4OrMyo+XM#K_tR<#+JOc4zR3feBAbdH>u7UJ z>YbPy-GDi(ZAXmZp5Mp+#RtvZ(Pj7y^WZ7+hBmEPbheXWjF3*9l%x>S>Z?#bMuD}c z2Lzc2fg_r`-Q2#!3}I611pwH-;DcYl`kJM^|LO-~&=RR6zj9E1(~Y9lvTp*Ij)K<; z{p?}1`hnL^h(b1iyl`gu_%_#1Pn7m9XIy4sR1MK-=-_cNpV+{Cu4qRZaf=M{gHnRU zImu)$ZOXAG-QpmS6*#_B4d6|%)FPL89sQ#<Y?P03Oaqwts4BWpU*KYhd5gToSVZW9 z;?FFYCVRjh+VJ1CB8&pCvdz_%ZJJsFg_sF0Q9dd>@cl`31)VST92ZA3JYe-?Q-B_k z%HWX5{@xv(`@#xC$Fob2`e9-X@LrFJ7%Xt*vPXIgTQbi{8P-=NX&x;C9e?L4Id`-% z8_$os#%bs#gtlcTZNHpuNseh|`TnuJ?OUCC%54f28#$=D#z=Xz<roF&z@~ag5BN9M zczNY~odj2Aw>>OqO=0Vy5$LAAe*UA)_~{Lw(TZ$K&8AYFp8SbvuJn5AVD#LsY&!@+ z^R%O<gY0pt_yC*5!!H*o*33@zMTn>`J5G$wuB)w4Ijhd6{gWG~tI!F*K7QgLlC%%x z#q6e;D*I;hY4nJHjYf~yvm++U!0au=U!fwQ@;LmL{Gt%U?aen>>0OM98?=7{e~-ji zq(2C~f8TN1*<+o9khhFxcXo~|ALg|5GWFs>YJHCdNEf}9h7Xp9?)aNvE`*9T8x+?5 zJ@jppxQ}emrRg5D%KiLo&S6?%W9%kEL}3FYSls?}EN`n~*h^^ah)*3#x|82*tdqn) z_4ISAzJrov7$HRQs})<mF3W|Lns>7+zha29|Bw4s4;EmeY3Oqxzd<ohgOXKSoUy1M z@1VPnGI#o}{~OnhvbYj#e4$$<U>Ur{wF9mrGEvq%9bT)3<oB1O)6Ui*2ES-5oRPbP z$FYqc5WyF)#VuE{%T(*CjMb4VbpIFa;OtZ0DV&V23upDQ3$rtBE7@6^zz2per-P@B zBCl~t^^W?6+kE9{Uz#J;*oy6EDd$pxyVg3tEstx}Pfymk5M~DY6SB1DyVQ;mV1VlP zxN{@?{(2)_R-Awxa1IgMVQ&Bhn5<x|A7`~HKG8{6OaR4=$%k2`*l&1c!HU6thpx{K z|2|p}cGT-N>V-U595j2Ygr7TR_(*kuf^`c{?vsm<GzR9wQ))83)G)}IPlLC7>v_#~ z_ExC0DrpbNS&8obCEgSI+CX8vDU)^tbzjHS3J~3kIs9sWp8sI_SIf72<_a|0Hz_9O z3R!qE7ZCARvS%M~0G(U-w`nd%`e%!`6#?jK?~(SKIQ@F24b+W?nJ`v1ex#!kKN6k| zTXTFoHJ0~pu*I45L@f4KUd}BAUe1H6jceA~X{_3XH76rw4!6LKVQd#<=C|RDZ`o~L z-*}I*!6sTN(1JEqGp4O;cFY7FY6$uZC~Hj;jM!+7sJ42I#X5J!K7jELP&$_zYVhn9 z7moZRP=42$Qd?GCO=!XTmyJz-4hsZD!P_f#Tx<6WxyrMg1nrJEyd`y?JRd9DLZJRX z9X|HAoy?J%b+V!5TZ>xhgTtG2C*^h@IjBa-`Npb|Z8cg-HlT^)@$<)4e|hcUui>>i zF+fuPhRjdeYl3aqE(^&xv^_(c2wnKAuvQiBPCq)kCW<cJ(%cd<`h@<&-sMEAQ0ZEI za12`VuZ)}pUT%9XSlX&v_Lzi*`K#_4tZ(OwzD8VeL#^UMj?K$}wZI{zt<yb~rE3P9 z#?aACX{+_AG18>vZ)tN^JxLvPUgMgB?#!npm$NYBY&})9#~zkmarSBC7psV-FeP@O z0FKm9h|aG=xRoq^kD>C+2syO+Jk3Js;H+{VMaOlMRe$_s(p=C<n6h@l>oy-le81mO zRm+@9L9U!#$gw@gPi*t;|J9%I@7Xm6<(2(piw^rxN73^d0q)c>Dx-y-zLCbB7ko9p zJ4HHRG!37FVduZq*EX6}bsNR5r!!;?r%vCMCAdd_aTgxE6x;nHn0oR9&8jwwkh{pJ zDEYeFdkR@5ybH*T!=&+{*T09Siv!y`+Jbnx4aqnZjBnTcD7cwaSP|svy8D{iqXT;t zZ52z0hw%u{6FV9&QCOX%D<h%J^l*?KfsyQO8hT8o)kNQxyI2lGAD+`bv%t<o=Kjkz zI*!uq{i(ZB`qZU@hO@SE3qtdPn));nr^&N7vn}_}WXAJVE5s;0cbX1)axdzfSV*v1 z_tt)Ft*z{e|F@*A#U<V1UijU=&D|PFpMfMOp)y?dJ$bn7Q*axV7i$qB&8aI7O+G~5 z{LA+a6~iMc9nh(Afo{O;L{wGg_x*Dc;DL^a?Xv4-v&1#V(bwg6q|SdZV*vM*c_edF zXVe@6^RuVb<LLzED`0%LI|J7Cft7qK24kfECJpK0BoPJKE}cd@?3Ly{)VIwg+=+QA z7zF2?JvoAvD!%K6tO#eWH8K-z7o#@*y1?sB!M#&^ZX3&}&1tl+1I9a~Bt`peyMcS% zs$2J=T^v_<uD2sf<P~O%kX;*bDTBV&KIhGx{|(38UfGiDrLWiJ-CZu?)9R;65l?&m zumH|Gb$pM22Z_g(A1SsKZ$_vIt=^N@@AqGR%sZpsP0GqP6uZg!WUii|pK@vN@JXMy zADvQccV#=IYUXdZcFm<jMAfCkSWH%%`q>_Sn*1@38YjgI{<DI8WltgUN*kowaBASR ztJeK~@LR4d|EpP7b1U6>xBG~1kbN=xpTyFFVR6dqut=&r*Z+CU?vZ7;%HH2Eq%1F7 zD+2^qT`Cqm`ezziQ5q;O+EVfbiSnse-Gm_E3;NcKVox8U&Vf>^6T$Le%EI6OdI3Bx z{4fX(^B=A`0Vdx*1qcS$8&oDuhx#%R3nD;e<q}xPyB*G3e8+D7Xt&2WuNt+dZyD8k zjYwKc_sm^wF!N~H(7>k64N~-xlFi^KUbf1<kkl^r3&Ig%^9-UznWHIDcQad!Nl(1} zyX<=@6;bZIe()?Z+<LJwbDOWpE<k_Wts>8iHNTB3JCfUO)%O`dB<a#{4<`rI&GHEv zrhYY$1l_OJ2RGFF8U;~u1v@57eu#{=Z;UYX@>_W1Jnxs}Er!gDLrni)#e`Q>k=Eg8 zWYLa)PaM<D@?wd4`D(ziJhW8TKxL}L6JVt*RREugyk&Q^L}m{IJ3b`|-pTwD#uQ-o zFt%MErI5WO3ShR?4XxN^#q_Af38y%2vThX*t%P*Wtz*y^y3}${N*JR=6uXRa{fUR` zht!;Xi)!Xstb)Tn6&|iv+~sk+jF8-;DAgQj8dw#KeJ&}?U$esY!#ikv)EgcZYalml zi?Aw@DaoljC^hEMBhcxHYWG>(BHn7LGbuiYjkW5&<+S<K?qv_qrVsqS7(WE^`Z9LM z_!RwDtrZwC1CFs?*#G7-bOarUI~Eu559N?u8ig3HH9FbpKdNpkXq;l3Q%2q~+r-*T zlN?26tHDu4HkEGc2bEg9Dk?g9&O5H2Vl8MvUmd;lkBt`20#^fk+5Vg_5zwbkChRv+ zFUWazt$y_kw|VC9|J8Qg?{Gz3_bO2{m}t=%LG&KIw?r4wdnao2P7(wWU38-a5uyf} z=tS>CA4KniQAYR8+y8=Zew*jH^PDsLoW0Lld#`=&gwEi3*fOg=ZC#e^?4tD?Cwb74 z9SnK=4!1H(ki70cVhSoJSdi@dou%1wk38@nP&`ZTzS?4;a=PwG@^d}1@c3KG+I>Sh z(wmqCdJn-DDa{j}ra#_@?fgW<k3JDRm0pTTYvBF+{D*YUx5nLVx9hDa%-@5;s+Xdi zXqoMH^Lx#(xo2BmX`GAUyLJ||dUKU*uen1ORbOw-HTur*2p{-V1g2D4=G~vAJi%F9 zi1Mjjo>Cz0Miv=hC2H6OaOIWU&$;FQ#CKR3-~&#GjDJVzT3C2fyKWsV-E{{1uNZIk ziu5aHbItm14YwcjYV~9zcv)Ox%6<U`&w?6*_+Oe}w6EG2AWyXze7Wgfj~+^%n^s6# z$-J~-r{BI3Y_A9seK$^`;d~Ywljbfn)^Ix4y^-$`<TDpF`jrrYLWLgrCJ!tsoxS>% z8zEMf+6B}!s7`PGYA!c%R`Z_DEPp-0OHU)?{bcn1a4nqP#<%?4nPH1S*=>F#*J6pq zbY9EJIF+leGmejn(`44O=~mstsK%r4?uODD$EBbu%=|nwUDDS7-#OK^-uZs?%(yDu z8E$H@HyhwZQ^+Od23pzus_Rf=#z<{Djvsh<&D|Cn<-)P2D&E{MEnemHMzDOjFv$8* z5x)7wS;`dy!E=|}0SZ9B?N;{l#wwwICS}$A7Ll|HhUwj3;Rz`j5L%i2(n(qB@@VLR zrb?tBCQjOkj|l4Ry)PblJwNWqYMh5%MA3*SINU63PBqvAo?YDhG3)cLYT&&ey}YM4 zW;k6~z?*^PvgOj4`umcmRRdbsz#KNki<QOna*9bB8rd`~^bj6*S9Px<hZi_L3-VGx zy783dxnx4Qfu!Bo{sX1Kw^ol+7V>x`1I2ik&f++e#+!54kOc<W>5ZO!xJ0;&Vp;iH zNh32im%B<n`>6Q$Mt4^s7^cf_D|0eo-N{gsu=rm$OinFYO8=qa2G1#9WsyHl0k1^T zAW7->^DZo-u>Np4TQ0V3^UPXOHB2C-K)BH0$5IJC0!Q);0Efvt+HCiaX&qhQhqz8g zW$zDNuC~W-j(08iYwhsHX1*2bRYi&YU8FHY56$L>&4p_oL(nJ!7TouJBw2N%O3(0> z53-@{n--TH8NFCaWT0et^DE?Qe<U%tgAW~OXrVgexb+Rdij%Vp8+p1i^41q^0$|8Q z0y>i$N*ng;ibvLOvMTJb0N@I{ZT-DF;4oL7-U~nB_=F75@kGmK|C1km^lSAmA(`wL z-p^bz_cvC?af?=6Sm4{IQ@rlhP`LTJCe;BQ6ZGiy2fr2*pViP)>BR@z(_hnScpV3v zk3F5=aJ=<M?50Wg+74p{`kqX1Nd-Ru(lnM9Gd)!4^C~HxwA<l^=$jpla!Tf?`k`68 z{9Dsv^U4Pqx~hv0zMdmP_zOYk_}Qs3=xDm_vfj8fycp-50Puw^zUwbG(sW!b%{|BR zb~#zeeIJKPWnF6G=z=Wpf%J2$p$q-hkpj&Ynh!L}T1y}XrHUhB>+0xgtFzZy;4ExJ z>M>}dx`Ha4<-_;tm1td>*@@m0lqA+RMPW*sM0>I+;g79Mo>hfFv&N01&1cBCpQW@t zaCEILv5rVg0mBs4ZZLS{GiLO^G=<uiF2~LzB;)67Rk)%yLFy;A@3|JEu{LKoEW>J9 zt`Z1mw5f{2ym(u*b~0Acixfw^JGY;9HZs(QFqB{j57?vRt5)LuNV6cd+6Q9|Xqnr# z`9_;{APb}rWx+M09MIO`*bt50HW~OFdMRs2TsLIW7+&u#nSWtRZh6u&T6>LdTl#{a zI1Fn2z)jo?w9l{2hy@}Z>`_kMUbq3PEjETu4s0{{>1lEgctIsC|I5!Js-1~pF38)) zhvpGmIo605!-Y6NmVV=0N(Q^_OK~^<<+{bVefm6glp_kyOlu!A#~tQZk*S61Q36g~ z5Y{fGU1lep!gdpaGIO47=ZS-V=7>!hhcBd_Zy>5Y{bk|$SI)Nio{^muqps}}8iN?K z_uRPiDHZH4FbfsBy?}=7Aw_%SA*lksa>HhWq#i++M?<i)Nl|)tedT=r*Y{~n$q4<K z)Ipa3eqpo@mz6jgH48@8zk%BC8l<`jjE<-@V)4PHkl%z*Zc{06#C3;-*glquXjXg! z`FDUz+J)V0)K}nF$ZXPyVPD-EYw`M3`hPngjS)n@*8hG?_2$*o(z(J5;H`%{_=V{= zyRmS_8#-CME8(w(F@*pA`@4LCmSIe7#aFT#MC%6IvdL_$bSYg6SCVYGeWNnU2G@17 zn_<DV_yX_*9wj4He=2<O^wWM|<=(4tN+5C#IQWNU>F>DhY=$gEzf#V5K3*<)g|4W~ zmT;nG<`<;07r5R0dr7p=wFW?+u6|%#NO;KI@;HjcTe?938Z?+Mg*!q%sG3J(!2YzU zwj2{5tBLJ@1)xsm=4DmBF4k+>?|y~ZjSWS%TZ=baTU3Woh@~20f+cEA@;==GW6K0l z;25oh%Wcm^*}lnZaa$yXdmj3Xq>u}}Qi)#EP+j|6<0{MT@TWjM;hvN6yZ~(Ib?>bS z)nOg4xFHNX-pO>lv-#G$*nCRcE-1^qjCY2dICfs?>`KD<kp0<v)}0t3fLV(9m582A z#t2A8Us!*<Mw9nbx!J4Hv1i%8Hj~7q-GiSJ$?}msd1;j8w&iI-;5TyR^+%Z4OmM4r zvG~7DMiO7TD|nkkmbJMQ^@2bEh>*s~q|DE}+)P_#ZI!jJ9<K5$ifVLtEbkkO5#MAx zUMfP$YqVcJvP7B$R6ANz?r8d|Q<fa2R=fG-w(p6c?!Aj9;}=v@@5-<X^0mY+V0g0W z0}on5C#XtE1<D?pFu*a}CqQca?=jMKv0F?6srt_ng-5D*tp=s;+mL^(r<-!Iwa{5p zB0{~WXkmSw<pPGN9u_o#cf!v1f%=N;j|L4dI~z_sKis4TUNZNuPJ;2lF=_UjV%wC- znXM^8T<+HWOln`<Dt%dXstTOs*3fAU9iNm|`fJ+D`-P^HzoOqHi2p9<DNH<XAR7E^ zyG9!4PAARymaGZRgl+TO(9qK@zti5is|Wu@dpHIb{yHPmmzP!rqO~s=Wn*V$hpce3 zf0Og2^VrZ>^nGyaPgd@Aj7KzvHPahC?nlOz93GYqkZ1Qzj!G@wUZH{xZ<m7qR90aC zr_ZQ$XM3L4+&x{oX$!*y<Rf8Eb2$x^uxl%V6cO`3NblCi3}XUj=wMh9e(z@wzKdKY z_Oas?IA+B)5ZF@`Ui@IsXbC0)EO(nz%9BD>rydGTiC_TYovu!eFYx=ugkGh7#7~Kv ztMq>I+BC9cfW^)xM63Vg+5Nl73`^@?git}DDc9A_BcR<R;m!*__;o0+y0Ax2$Y7BA z_D~ANGjf0a09bFvtP?&+MNQv{oLj$6gS*ZAnPke5+SGj13y+D8HjPROin{ga9`<}b zGobvmx5vB=S({{$mC*c>iYlEaCw4%M69b6UC7isvm>YCh`)dLM!Vz6&3W7g$$vFrx zfLMDnrmDW?J}LT7%mtL8RC_@j;u<BtDf1e~lAn&SBV&=(H-wx3P(M1u==o{{sp+Q~ zMUF}+Y4n-tA+Fx;R06_{1|66)NQFu<Ft=atc9vK)69C(e+p?MTu<RGI7!j9x%z`_u z<enlU!iRx=rJ}wFDt&B1-Nq}soV4u7RhZ}MOq`&@+6~Rf;I<XAblqm@foF@~PpP<` zUFNaX*Q(GJ!VB#N4-O#T*4#;XLFBVx*u$9ZS>7kD`NV`WoVvqeq+W4upVwF<I+V&e zw6Fg(0g2}yp=lAUN4r>60KjABMP;u)jLqj#Yh991njojx32we=A9TFeCh}q7BsT8o zr;~W0K1d1%!yYoD9@Zg0%|Kb0KN>j<cTk1}r`;sruoQ1X1<|)oedAai$2LC2_qC1_ zKkk<oRr30%(^`uO@HuqT+E9zdcI3(Z#<2#J-M(o-w+)1<9;?ale&Cb8WU9;BGVC|L z-U>T0-Ct~`0mi(((8&8J(~35yt#k!$w6NX1^}0VX1={0cGovc_X!q%`ft!OBZqeG? zv0lh|K};lTo;)GZ3mnU>Jh^4F;}Es-36?@+!C1%_T+me%eI2L6<G`UyXy%7^$(X@M zSDHZkoomo|{rz^dOty$CraezK2rvxouGLAOr{boLhdkp8{3V)Sn$D@uqr{lq`1~80 zQpe3l+slRdt#9C+m@hX}`a{$7JN_C|pZqN>Rw?`Nt><sbsH8S!M}cqm9DdUamz_jv zy2oe%O~cJD`7$MO>$R6weA7vPnw(PK2de^40-RsC9$z;Zfi(%gu(5=RNi`kOdI0d~ zAs<YN{$%RW&2Yq=!6`AK9okpTKQvBTG2_SQ48pQrKV(lNZBOm?B$Dm!-uPVU<;&4s z`d4=*2OkhUMaU`2IKHL0Uw89mvkvVowEPlepoX`<@QV`QEBCbTkwCScOE;tbEC9(i z`_nQq@in?86N31c`XU_yv|elwh$mGP3)MPlsL7XFoL|h26=IP4BcHVdO1vxZSbA+H z><hOZcaSjvw9thVQM~BS%Xt5Qk_QKf5LJ+N4TwaFXLy}EZ9DBMSvVi|-*y+=!iN*6 zRhn1}TWSf`uJ@Kzd_$j6<jUO9p^Q=Wm_UOx66b3EN?EE^Z<G|H*L6!8!cf7>>_U$A z0gtuX><WtoX?1IDC5QL4sHKxl=^+W{%5Lu359d;zF*^6-ci^`23OJ)GWx7>|gQiHM z6_0-C_#>fdVWLk>Y&cq`%oa7<tm2vcn+;M2UqcIUI!uk$ZcnwKpQme#iGQr*ZY^U0 zLM}3*KUza8&NI7rnmXwYd+e4;+d}UtM;L6LgC~)Sii$j4eNtEiWI=j7r1F;eYWjE! z$MZx(*Qcf;L!YS9ONdRzYTB^ExvT|~3#n2(HiaD+p~CBe_7kU&A#s~&ZPv0a4-|gb z(#UurweI+<g@~&dPv4-gj1fD8TA|%4Ti^g9>DeEoL5+Pc0*^zt$S>byVRmPu1SL=p z`=Z25_)8#0R24NPU{L)BKAV1aU_H)<_sU~|-dm)9aUkBpFIQMDG_06V_06|_XGF{2 z)4EL}SD`=4;~x~)*Bh$piyYm_aF>%fbvy~=6};4A2M9DTP=ONH)mw+P3d=1{B3BF7 zpIo1UYSvEX9{$(K-&|D{m%ECU2~p*q*PmNm3%VEW{}=>-yO^bq@C9n|u_IqBoYNO( zy<ryJbsL*}CwFP2J8{|q4g~g<84m!*h2{7nTxA|lp&W9Nseby}Dh1`m>&63>@u3$$ zcwMFRT142E$1QmOD!XDnowl&gHd|mI(xf%idB+bMkTkvJ9DS%=>%~wiX;Jyz{hhyQ z_nj-QhvG7Gp#H|g6JJ<uB8hCUHjzeiV=MK+wfF~)3lQZHXf@SS%(mvQ#b)+=&m96I zVqxPEzfWqXj56LPHh+OXCX&EQLt?X{xYo<P!dYG`h3g=&F!Mwou3JW5S1KHrTNMfb z87<y8C3)u0R!4nkcYo)JoaYDlsSWhj>`KPzD9yh?25*|}`F4zi<gj+O?)}05FrO<v z^1lsh!~}SZLck^5iFyri(+~}{ucBWJzAV%`Q5FW?BGxoAB_2$sQ<-!h`t#ow+bl0I z6xiF)0gR96`3^2Fb&)z}Fi$RVzqd}kToOJ6z~56ZO@&@Lv6>n?mBFtvhY|5gADtZ+ z7Knh^n+5KlH=jqD!)kMyi!aDdeFx6=+@(&Qox3a$`$VLx38+Z#3-8Q5{I@fsqTFzr zT5pt1$SbMTymI4`mx9mfa!#h|FaSF*wZA7#d$*dpcA%5I20rt!QxG5<w$;u?5cy<p z9vax=!X@T@L*Dyx{D+D@+X5_rlDZ+pcDDv2pZt4<P%+>o<HG?ka@sDrtRe|1iyDoH zlzt%49VXAeZ#E}Qz|vD_Na#Yh@ERm*K$JTYeGh6eXYU+frMG$CuSs@PLr!wM7gF)v zXuMDmUxd@Zxr7%y5XIR99!Ou)g0==eP_@_aoua<T(-+ITMk-{Fn=VT3j;8T^UMnj8 zs7%i$97`fQ$s=}O5P;e~5S(8+z||H~(Zsp_Lurr^xPPO&ZcpFs^>MtlY4MK@4GVot z(cPtZ>UJ0LaR;`qOR5CRd7jITnE<@;TPr7h<z?~5JpUpugIQ&?zsJER(>7Ad)Y(cZ zcc1mWFpVwzI}~OTKQWiW?WSY@L)hDL&*mNgXfLkc>a@{>H86Y}NfTV7%yC5^8=0H3 z$Bz(9fULV3)NW>U{Joju;*@=V4P_qlXvq3D<6YoAC-YsL*}0ci<hR$e$g$}HVl}sr z{T~hy3$F)>(}UfDFeFey!9_R9{Blqh_dz9uP4u2xqOZ#haNHp}^7AznV5v+{EOa10 z)$2k$#n2-IosW+?s9jV4tqjeSbnO>P9ZK8LokXQpiA7I`)ap*X+T)=wAfR0u#{>Ki zyQ#S>jO^xXQns2slrdB(KIi*vRj0?TS9+AxZ)T{&2mARi)L%68|M_I|yJo|~O>kVH zZgUP+%0y#y1hZ7M;ggJ(GC^5aYq>eWT=ZBdat?Cn*5`?RtWh$yFGh)%XWvf~FDQSf zAqG0|J@bCA5>i-Rt?H^ft$7UN&c8Wo>DqR!>3zIEu3!d)W4Q}w%){6;EW8QyD~w~? z5W#1&Vcu3B1f(h4QnutnFOG(hQ<wn#_pq@ko5PXAg6QbzZJWoiQiV689}i^xuo+z4 z0YEVjXRKC>XHxuNNS7b2VX|S%)mRiY<kUU9Ub?~i<Ref|>vj+s`0kY?pl$~FKv%-^ zRKd|Yz6M0sapxy3e|;BEN<@{xPM_8#$Q`txeSgIR$X;G<auYt(80z@*!0xHpD~^Sl zIv>ZCC{m6;nO!%3U(%x5+=_lW1&rZWduhGbI)?3bGVG^0U3S%LY)|-UI$z~K@X{Z` z22zK4yM~k|hZc1-WjbPh#xh?e*G%8KioRTq*d*Q{e2z-uQM^{x>LO0hg??h8?2C0t zaiDN$ZDA`YYsFvj-D4g9RMI3CM^}}(48!tNo3$(FzyN6c+~1AufvdgJe~w6<1WUU= zsc1Q_dIEtUNt6*zN9>4TwwE>>fz)>3jHL!?p~ED-1J%vX*1p}|Wa=qF`jn{~MPFat zNmnrnA*i;pzA;TmL|2=CY-%iW|H|X1W%F1gP-31&oWzX)>d}=nj>i<GkN-qwl<VNy zj{B^KZa7*+Kq6b5)H>;L{B{@3!d*38@kvR>0-&Uo!Vx|eMgvkA*JeiHh|d3U^(Vb* znk;8@_(2g;m)YV@FZ+|*Wq?21ub$DqYfnh`CRW6^$95|Zr)||wPp>h_5g)Yp!7jG- z+Jl(L<sMIIk`M#vu8Y+XP%CL593&v&=#%49F;~5-0B$fYk<1@>Pld`8bANtLTpNzb zh>eimq&fIlfc_iXOiNSnPYC<V>%1<jV2E5s4=mxVTf6QJF2MN865$_YlS``~tzzBK z;@0WviF3cX?9v@)H>n}z`^sX4F)9r&{#^0+{A3CUuSh(eVkG6C`n;@R(^6so7@ru@ z=29ruTj824!7+anZ2QjSecf8Xn#Bu6#dRj05D~%Y-QvcNk}S49f-X!X1dOG8xCOyf zAcqY*<_vCT?yz>;0)x&mAq?TqvXUH=AvJFw7q$pke~c<}DxQytemTXqem>Cl?JlYO zTBc-U@^8Z&at)mDge2j#pb<8@TS5Z<#Gdb8v|wW_4GPVB27#yyC{xrXMY<bQzVV6c zF^`Ro?z3x7g7)Rpl)I6JViy=Kl0YD;*$dE>xPNh>TnR8ZX^@W_3Sol7M)|=>r9W@P zM*4F9@k6v@^$j<%fWZL|*#3ag&lT}`YGE<^K_In)z6f^RSr{o;ET{12uOE{Cghg&> zSfnJ6ftp~3c#5aX^rEB0bG!~Y^z)^p2*e`#Yn%;2QBjl4^zO-4#=i#k)JpQWy`~2z zUBg<UK@uo3{HG*t&=@%?o73|A)oSOYt@rz#2cvIFHvUcBVhv65UXDjSlhnH{kNbc( z!*k1k0Wh&d1nVU3B#i!~FUCb*j0o}nbyW}tP2lA=;|B7vSu*b&J=s@~2U6%c#0Iqn z>Ko#(9q&SCA?Xf^xI4C5dNLWv%b%e;+?Z$?V@#$y$sBYLy7ph}L&nd27@8%W&|tFD zb6Cz`8L|H|L2voK=J4EE&HWLe{WMmTe-sOTN4rIy6Lnu{%ytvZ>zOOMx=ohr-*7(D z%qxk=`bP5rAS-%|f&JZ$h$>&}plj_61v@u)AWp*}r@qk_c_&a}aEb}!eQjB;7ZESs z%*?Xk^`FtJ1Ot3>nU-enpV~Zn4uqFW_=f+(hKiMFTZ5>}|A?8B=>YA_!hTIx2f00m z$MZJeC6Jz9w2Q6D94&Pz_N(%2O9J$1YPp4Q0YCg(lf_V@Tbe5M(yP%kWXiR+2k=DC zr&!J>WW^}PDF6b2q+6}$;b`E=TK(M;0xJUHIuf7MkXNwQ*WdA&Y<;|*D!Emur~e+2 z0%fU8h`T*fAWN8ncWLGdY}RB2rP6#ukO7I^Z~S|YRE_g;gQ#B>$X923JS69!FEi?O zj}zbtFbgT)rC-^?G*Wqv=dS*B?O*!<9X1u@zbe={UJl;1(N`J`e$9P&<}qyCy}Osw zrin0+*G(FOGqebDL!(Of!Ybj~J_!I&vKoz-NQXO@O+Y@nf7BBS{`a0v3d(vEjb*v+ zX21{bvZ?nASOw+ouQUobax7PvvH^_w9(7a_s?v=u;kiLstMcYgj8x;hxeHbbtx13* zMef0Y<V*VRExH)tdmM*kMg-}-a~kTuE8N~WWqkf8A3{gf*qx*xo;8JqXloKJ0c$oI z|7PD@B#7c?4IQr%Vjn(9Hnpw<$+)hL#J2WNmS|4;Bw0ZD6IS^;7cboA$oI%)kyr90 z&T=NT<V=G8<@BNd^WzC@kOX15Re83oP@%bb_GdLlL|MN#Ac6KC$c&Y7Xbw&{BP-vA zeP-x5A#mZ<!oSndnT8rH)~KQPZVrSxybLrKxU6$lgm5w}@>cS%Ux!rQVQdVNH^xfZ zy#q67u`$7zzoZ{o32PdBuA)c@VbK@qO2Gkl96Enm*!g~hgvJ|tbCK-H^>V_+r=3_W zJhOptMg0!E`>LB?O%^}r5)aSH)BNy&K8*CytnX&d7(hi;Sf3z@>tBp$Ewk}4vzb8v zI(xvdoTd(*Ve7fk<b9m%q{J3ItMUZ<CI$nL?Hz<0Piw~VrVWk@h2d#y-rem7u6&3J zNwBCgWqU_EAovLflrXb|fL%{`-CHsT!;rpc-(ELfB%2YuQqKJ$&>bK0u)~qf*y0-h zQ1E@4Y4At&MhC5d?JfF{@eIv8jJE-h$L$Qxhi7o9K71dlu~Mzwf#Vu45n~Z?(Dgn$ zy4S`wutr^|>2YLl3m^6>GH}Pg`{yO5+q27L2}Q`oaleyt8Y??&`$M7Yq?X{Fln*;n zJTHPB9X`Z0p_{|S(ZR0hbJdednTX2UY$E|yT-QNg)iPD>+^}5>#d~TGe{^LB`CYy2 zd<Dh8BRAuv5rnq0$4dJ(KBi_iAPZ?W6+3sRqc@Y!-(#_{AH@o`&*<oLh{_E>GPtIe zbIwFds{i;!Re2U6C*pKm?0KVmu`k&FV*F9K_69=)YZwNJ{Cg7WV0@Tcqt_rWoewc3 z6eU&gVErYZ(y^RQa4+jGcIkGuMs14N5aoyD8bs6HZbJ5zy&|(6G$jlz{teUFXYBvp z!dk84k9TA)DDvKDQD^992ltCJVG?n16sjO&pUaNND_yK!I9h0(5k^~$w*e8d;*`5% zlm$<<qb4@OF&{wHi?Cu>af7At><kt2I+KMYbRL1dn?2L5W*|_yOo#QlF2sJywP(yz zi!Nv}06dWX$2$TFfp9MU0%}SSKG1&XJVTul+vfkh^-c`e>CE$U3f&j&S|BVC%C|(y zseBot8a6H$ArrCs(Va54imI++w}0Lwtx9i+Gu69IoBC02{**>_j>Gcnxbw4tYuYQk zy1ZEIJ4@|SBA|O~@k!!faz`q?T|PN5R@0qv`>^$806JXA*i`+vt(?vX!OZ!cdC8CV zD{68J5U8tNN)KO({JwWy;$g@h0*(w9xJ>r(jB18<v3VoOe8#GyrOHh90D$a@wQH12 zs<{88jIgMJqwNLRMeKLCD4Va(A1{?9*bhY3dY9pC%3ah}IAYGF7Ix?VzQzC!vJXYh zu9_=uBoszTO18Dk6Q*N8XT<)k?l#7G|2SkN?|p5)IR7Ki^fmmViYJ-=Rb6oB@Yy0E zW%N2vmJwTaB%VTAS>3$A(bMp`4zsK&1^({rYZro0uJp7j3Lx>2(>^xzA%95mgkU{9 z6KuKV_Or_4U^k=C7<+VT!HYDle`}emtxfjXUqbrw9DCJFV}Upl?TG!Mg6?p|hjdj7 zeyCQx4H**B5G8a(n7;eFB7DGX^{=^QcnKM*FUJYsY5oy6H|_JXnfXfB&Q=Ixr-XkS ztsPCCngNVLh70s&Bo!RBBXF3K%TSN!@6T-(%>8=Nz*%r3E4E2dcE!u~$(yccw>~_* zrf^L^*Nql=t3uo8@TgMfPjk^-F2$5^t{;}3U5wpuvq48}DBNe1a!;xJ(<FjzZ(bGF z4~DWN;~2SJYT;0{cyi>CklqGGR!DBF{eCQ|w}m*anILpe*>J@Q`0Hwq(?Y6sTHsUX z1wnd;l)du`c=hB+Jy&?pAve4#?4OC7MtVY1-8WT7uTSs1a~qwd({r1Qqb)H2{a&^7 zLL&JXv^v&!$$zYFX0o1qX|YXS0do|6i)<JqN89189QV6K_jgmne2Y-Yr5Y^R!RZ=_ zqS_O8FeDN8=~9za3h7dCjP#G6mT~as{gfM=obHr!kkQ@E430vO;UYE~rTufoFYH~C z(l(qGD|wf9?H{YkGGb;*htz!5F=W+{LS0`aH@nDIeW+rWQ(F+ChDHy{^8;kUv76sz zw(1kDI63WVicQJdhZD|1HKeu7zlk)Cw2>ZT&$UnSWM5{P<{=&8W5Ah+Z?Qq9Vx?O~ z=~dPJLU~--KL=O_*B?c%T>fhEHv=1O6O#Mk19+m%{-1Hu<m9tNH#O0KRUnwTHk0>N z1u4d?*Z!$uq5O#XqIMW>6+0x`P_I?rmXoPGJBh*`qxS>bNR+-FoC#ngJh#Bjm8A_U zYf6~`Ck>nJB(^g}$pP(AznU&hsc`>|+T4|&HPqhtCVfYT@;EJ$F%n8NW+8mGH2x`C z8i7RCL%!N#Rc~zy>hOnCiv36jOU69&n@g^T{7oqnYU^efCVvC82f9tmuKOFjf^$~{ zm*9oeF@W%&2wl1aIN`pJRIgbsr$GbMPlU_KwCl)N9|iaM*cgl4a2ycFGd^oJ&on*q zz0)gd4Bg37OiT{jsZL5!<|PuzG9n+N?p%oDkxL-RlQaRu1(911EayN77boh|f}4M5 z9!0I;`^Iqg*4!3_;{b*hel%6MkgE(%j?gDPG*kDH67pH`|Ab@cz4ia}Swr2wQpHe5 UlZ3zMSFZpslr$A9<t^X+9|O*hJ^%m! literal 0 HcmV?d00001 diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx new file mode 100644 index 0000000..23c68bc --- /dev/null +++ b/src/app/about/page.tsx @@ -0,0 +1,3 @@ +export default function AboutPage() { + return <p>About page coming soon</p>; +} diff --git a/src/app/games/page.tsx b/src/app/games/page.tsx new file mode 100644 index 0000000..3e3a60e --- /dev/null +++ b/src/app/games/page.tsx @@ -0,0 +1,3 @@ +export default function GamesPage() { + return <p>Games page coming soon</p>; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 274dd35..dedb14f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,6 +4,8 @@ import "./globals.css"; import Navbar from "../components/navbar"; import Providers from "./providers"; import { ToastContainer } from "react-toastify"; +import { Spacer } from "@nextui-org/react"; +import Footer from "@/components/footer"; const inter = Inter({ subsets: ["latin"] }); @@ -22,9 +24,13 @@ export default function RootLayout({ <body className={inter.className}> <Providers> <div className="dark"> - <div className="bg-zinc-100 dark:bg-zinc-950 min-h-screen"> + <div className="bg-gradient-to-br from-[#181818] to-[#222] min-h-screen flex flex-col"> <Navbar /> - <div className="max-w-8xl mx-auto">{children}</div> + <Spacer y={5} /> + <div className="max-w-8xl mx-auto flex-grow w-full"> + {children} + </div> + <Footer /> <ToastContainer /> </div> </div> diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 15a9106..4ab9cfe 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -48,6 +48,7 @@ export default function UserPage() { body: JSON.stringify({ username: username, password: password }), method: "POST", headers: { "Content-Type": "application/json" }, + credentials: "include", } ); @@ -57,7 +58,15 @@ export default function UserPage() { return; } - const { token, user } = await response.json(); + const { user } = await response.json(); + const token = response.headers.get("Authorization"); + console.log(response.headers); + + if (!token) { + toast.error("Failed to retreive access token"); + setPassword(""); + return; + } document.cookie = `token=${token}`; document.cookie = `user=${user.slug}`; diff --git a/src/app/page.tsx b/src/app/page.tsx index fc90ece..6873197 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,46 +1,16 @@ -import { Image } from "@nextui-org/image"; -import { Button } from "@nextui-org/button"; -import { SiDiscord } from "@icons-pack/react-simple-icons"; -import { Link } from "@nextui-org/react"; import Posts from "@/components/posts"; import Timers from "@/components/timers"; +import JamHeader from "@/components/jam-header"; export default async function Home() { return ( - <div className="w-full"> - <div className="fixed left-0 top-0 w-full h-full z-0"> - <Image - src="/images/bg.jpg" - alt="Home background" - className="object-cover w-full h-full" - radius="none" - loading="eager" - removeWrapper - /> - <div className="absolute left-0 top-0 w-full h-full bg-gradient-to-r from-black/50 to-transparent z-10" /> + <div className="flex justify-between flex-wrap"> + <div className="md:w-2/3"> + <JamHeader /> + <Posts /> </div> - <div className="z-10 relative flex w-full flex-wrap"> - <div> - <div className="flex flex-col gap-4 py-4 sm:py-8 md:py-12 pl-16"> - <h1 className="text-3xl sm:text-4xl md:text-5xl">Dare2Jam</h1> - <p className="text-lg sm:text-xl">April 4th - 7th</p> - <div className="flex gap-2"> - <Link href="https://discord.gg/rfmKzM6ASw" target="_blank"> - <Button - variant="bordered" - className="border-white/50 text-white" - startContent={<SiDiscord />} - > - Join the Discord - </Button> - </Link> - </div> - </div> - <Posts /> - </div> - <div className="w-1/3 flex justify-end py-4 sm:py-8 md:py-12 flex-grow"> - <Timers /> - </div> + <div> + <Timers /> </div> </div> ); diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 58f4a08..72a84c0 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -28,6 +28,7 @@ export default function UserPage() { : `http://localhost:3005/api/v1/self?username=${getCookie("user")}`, { headers: { authorization: `Bearer ${getCookie("token")}` }, + credentials: "include", } ); diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx index d108b89..3ad456f 100644 --- a/src/app/signup/page.tsx +++ b/src/app/signup/page.tsx @@ -1,9 +1,7 @@ "use client"; import { Button, Form, Input } from "@nextui-org/react"; -import { redirect } from "next/navigation"; import { useState } from "react"; -import { toast } from "react-toastify"; export default function UserPage() { const [username, setUsername] = useState(""); @@ -83,13 +81,13 @@ export default function UserPage() { return; } - const { token, user } = await response.json(); - document.cookie = `token=${token}`; - document.cookie = `user=${user.slug}`; + // const { token, user } = await response.json(); + // document.cookie = `token=${token}`; + // document.cookie = `user=${user.slug}`; - toast.success("Successfully signed up"); + // toast.success("Successfully signed up"); - redirect("/"); + // redirect("/"); }} > <Input diff --git a/src/app/u/[slug]/page.tsx b/src/app/u/[slug]/page.tsx index 9a86a8b..2c91b06 100644 --- a/src/app/u/[slug]/page.tsx +++ b/src/app/u/[slug]/page.tsx @@ -1,9 +1,34 @@ -import User from "../../../components/user"; +"use client"; + +import { UserType } from "@/types/UserType"; +import { useParams } from "next/navigation"; +import { useEffect, useState } from "react"; export default function UserPage() { + const [user, setUser] = useState<UserType>(); + const { slug } = useParams(); + + useEffect(() => { + const fetchUser = async () => { + const response = await fetch( + process.env.NEXT_PUBLIC_MODE === "PROD" + ? `https://d2jam.com/api/v1/user?slug=${slug}` + : `http://localhost:3005/api/v1/user?slug=${slug}` + ); + setUser(await response.json()); + }; + + fetchUser(); + }, [slug]); + return ( <div> - <User /> + {user && ( + <div> + <p>{user.name}</p> + </div> + )} </div> ); + return <div></div>; } diff --git a/src/components/announcements/index.tsx b/src/components/announcements/index.tsx deleted file mode 100644 index f48dd64..0000000 --- a/src/components/announcements/index.tsx +++ /dev/null @@ -1,8 +0,0 @@ -"use client"; - -export default function Announcements() { - return ( - <div></div> - ); - } - \ No newline at end of file diff --git a/src/components/footer/index.tsx b/src/components/footer/index.tsx new file mode 100644 index 0000000..b433acc --- /dev/null +++ b/src/components/footer/index.tsx @@ -0,0 +1,20 @@ +import IconLink from "../link-components/IconLink"; +import { SiDiscord, SiForgejo, SiGithub } from "@icons-pack/react-simple-icons"; + +export default function Footer() { + return ( + <div className="p-8 bg-[#222] mt-8 border-t-2 border-white/15"> + <div className="flex justify-between"> + <div></div> + <div className="flex gap-3"> + <IconLink icon={<SiGithub />} href="https://github.com/Dare2Jam" /> + <IconLink + icon={<SiForgejo />} + href="https://git.edikoyo.com/Ategon/Jamjar" + /> + <IconLink icon={<SiDiscord />} href="https://discord.d2jam.com" /> + </div> + </div> + </div> + ); +} diff --git a/src/components/jam-header/index.tsx b/src/components/jam-header/index.tsx new file mode 100644 index 0000000..9bd6942 --- /dev/null +++ b/src/components/jam-header/index.tsx @@ -0,0 +1,15 @@ +import { Calendar } from "lucide-react"; + +export default function JamHeader() { + return ( + <div className="bg-[#124a88] flex rounded-2xl overflow-hidden"> + <div className="bg-[#1892b3] p-4 px-6 flex items-center gap-2 font-bold"> + <Calendar /> + <p>Dare2Jam 1</p> + </div> + <div className="p-4 px-6 font-bold"> + <p>April 4th - 7th</p> + </div> + </div> + ); +} diff --git a/src/components/link-components/ButtonAction.tsx b/src/components/link-components/ButtonAction.tsx new file mode 100644 index 0000000..34581cf --- /dev/null +++ b/src/components/link-components/ButtonAction.tsx @@ -0,0 +1,25 @@ +import { Button } from "@nextui-org/react"; +import { ReactNode } from "react"; + +interface ButtonActionProps { + icon?: ReactNode; + onPress: () => void; + name: string; +} + +export default function ButtonAction({ + icon, + onPress, + name, +}: ButtonActionProps) { + return ( + <Button + endContent={icon} + className="text-white border-white/50 hover:scale-110 transition-all transform duration-500 ease-in-out" + variant="bordered" + onPress={onPress} + > + {name} + </Button> + ); +} diff --git a/src/components/link-components/ButtonLink.tsx b/src/components/link-components/ButtonLink.tsx new file mode 100644 index 0000000..fdfc3f3 --- /dev/null +++ b/src/components/link-components/ButtonLink.tsx @@ -0,0 +1,25 @@ +import { Button, Link } from "@nextui-org/react"; +import { ReactNode } from "react"; + +interface ButtonLinkProps { + icon?: ReactNode; + href: string; + name: string; +} + +export default function ButtonLink({ icon, href, name }: ButtonLinkProps) { + return ( + <Link + href={href} + className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-110 ease-in-out" + > + <Button + endContent={icon} + className="text-white border-white/50 transition-all transform duration-500 ease-in-out" + variant="bordered" + > + {name} + </Button> + </Link> + ); +} diff --git a/src/components/link-components/IconLink.tsx b/src/components/link-components/IconLink.tsx new file mode 100644 index 0000000..c09e3f2 --- /dev/null +++ b/src/components/link-components/IconLink.tsx @@ -0,0 +1,18 @@ +import { Link } from "@nextui-org/react"; +import { ReactNode } from "react"; + +interface IconLinkProps { + icon: ReactNode; + href: string; +} + +export default function IconLink({ icon, href }: IconLinkProps) { + return ( + <Link + href={href} + className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125" + > + {icon} + </Link> + ); +} diff --git a/src/components/link-components/Link.tsx b/src/components/link-components/Link.tsx new file mode 100644 index 0000000..c93bc58 --- /dev/null +++ b/src/components/link-components/Link.tsx @@ -0,0 +1,17 @@ +import { Link as BaseLink } from "@nextui-org/react"; + +interface LinkProps { + name: string; + href: string; +} + +export default function Link({ name, href }: LinkProps) { + return ( + <BaseLink + href={href} + className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-110" + > + {name} + </BaseLink> + ); +} diff --git a/src/components/navbar/MobileNavbar.tsx b/src/components/navbar/MobileNavbar.tsx new file mode 100644 index 0000000..b500fe8 --- /dev/null +++ b/src/components/navbar/MobileNavbar.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { + Image, + Link, + NavbarBrand, + Navbar as NavbarBase, + NavbarContent, +} from "@nextui-org/react"; +import NavbarButtonLink from "./NavbarButtonLink"; +import { LogInIcon, NotebookPen } from "lucide-react"; +import NextImage from "next/image"; +import { useEffect, useState } from "react"; +import { usePathname } from "next/navigation"; +import { getCookie, hasCookie } from "@/helpers/cookie"; +import { getCurrentJam } from "@/helpers/jam"; +import { JamType } from "@/types/JamType"; +import { UserType } from "@/types/UserType"; +import MobileNavbarUser from "./MobileNavbarUser"; + +export default function MobileNavbar() { + const pathname = usePathname(); + const [jam, setJam] = useState<JamType | null>(); + const [isInJam, setIsInJam] = useState<boolean>(); + const [user, setUser] = useState<UserType>(); + + useEffect(() => { + loadUser(); + async function loadUser() { + const currentJam = await getCurrentJam(); + setJam(currentJam); + + if (!hasCookie("token")) { + setUser(undefined); + return; + } + + const response = await fetch( + process.env.NEXT_PUBLIC_MODE === "PROD" + ? `https://d2jam.com/api/v1/self?username=${getCookie("user")}` + : `http://localhost:3005/api/v1/self?username=${getCookie("user")}`, + { + headers: { authorization: `Bearer ${getCookie("token")}` }, + credentials: "include", + } + ); + + const user = await response.json(); + + if ( + currentJam && + user.jams.filter((jam: JamType) => jam.id == currentJam.id).length > 0 + ) { + setIsInJam(true); + } else { + setIsInJam(false); + } + + if (response.status == 200) { + setUser(user); + } else { + setUser(undefined); + } + } + }, [pathname]); + + return ( + <NavbarBase maxWidth="2xl" className="bg-[#222] p-1" isBordered height={80}> + {/* Left side navbar items */} + <NavbarContent justify="start" className="gap-10"> + <NavbarBrand className="flex-grow-0"> + <Link + href="/" + className="duration-500 ease-in-out transition-all transform hover:scale-110" + > + <Image + as={NextImage} + src="/images/d2jam.png" + className="min-w-[70px]" + alt="Dare2Jam logo" + width={70} + height={59.7} + /> + </Link> + </NavbarBrand> + </NavbarContent> + + {/* Right side navbar items */} + <NavbarContent justify="end" className="gap-4"> + {!user && ( + <NavbarButtonLink icon={<LogInIcon />} name="Log In" href="/login" /> + )} + {!user && ( + <NavbarButtonLink + icon={<NotebookPen />} + name="Sign Up" + href="/signup" + /> + )} + {user && ( + <MobileNavbarUser + user={user} + isInJam={isInJam} + setIsInJam={setIsInJam} + jam={jam} + /> + )} + </NavbarContent> + </NavbarBase> + ); +} diff --git a/src/components/navbar/MobileNavbarUser.tsx b/src/components/navbar/MobileNavbarUser.tsx new file mode 100644 index 0000000..1ab542a --- /dev/null +++ b/src/components/navbar/MobileNavbarUser.tsx @@ -0,0 +1,102 @@ +import { + Avatar, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownTrigger, + NavbarItem, +} from "@nextui-org/react"; +import { UserType } from "@/types/UserType"; +import { getCurrentJam, joinJam } from "@/helpers/jam"; +import { JamType } from "@/types/JamType"; +import { Dispatch, SetStateAction } from "react"; +import { toast } from "react-toastify"; + +interface NavbarUserProps { + user: UserType; + jam?: JamType | null; + setIsInJam: Dispatch<SetStateAction<boolean | undefined>>; + isInJam?: boolean; +} + +export default function MobileNavbarUser({ + user, + jam, + setIsInJam, + isInJam, +}: NavbarUserProps) { + return ( + <NavbarItem> + <Dropdown> + <DropdownTrigger> + <Avatar src={user.profilePicture} className="cursor-pointer" /> + </DropdownTrigger> + <DropdownMenu> + {jam && isInJam ? ( + <DropdownItem + key="create-game" + href="/create-game" + className="text-black" + > + Create Game + </DropdownItem> + ) : null} + {jam && !isInJam ? ( + <DropdownItem + key="join-event" + className="text-black" + onPress={async () => { + try { + const currentJam = await getCurrentJam(); + + if (!currentJam) { + toast.error("There is no jam to join"); + return; + } + + if (await joinJam(currentJam.id)) { + setIsInJam(true); + } + } catch (error) { + console.error("Error during join process:", error); + } + }} + > + Join Event + </DropdownItem> + ) : null} + <DropdownItem + key="create-post" + href="/create-post" + className="text-black" + > + Create Post + </DropdownItem> + <DropdownItem + key="profile" + className="text-black" + href={`/u/${user.slug}`} + > + Profile + </DropdownItem> + <DropdownItem + showDivider + key="settings" + className="text-black" + href="/settings" + > + Settings + </DropdownItem> + <DropdownItem + key="logout" + color="danger" + className="text-danger" + href="/logout" + > + Logout + </DropdownItem> + </DropdownMenu> + </Dropdown> + </NavbarItem> + ); +} diff --git a/src/components/navbar/NavbarButtonAction.tsx b/src/components/navbar/NavbarButtonAction.tsx new file mode 100644 index 0000000..82d171c --- /dev/null +++ b/src/components/navbar/NavbarButtonAction.tsx @@ -0,0 +1,21 @@ +import { NavbarItem } from "@nextui-org/react"; +import { ReactNode } from "react"; +import ButtonAction from "../link-components/ButtonAction"; + +interface NavbarButtonActionProps { + icon?: ReactNode; + onPress: () => void; + name: string; +} + +export default function NavbarButtonAction({ + icon, + onPress, + name, +}: NavbarButtonActionProps) { + return ( + <NavbarItem> + <ButtonAction icon={icon} onPress={onPress} name={name} /> + </NavbarItem> + ); +} diff --git a/src/components/navbar/NavbarButtonLink.tsx b/src/components/navbar/NavbarButtonLink.tsx new file mode 100644 index 0000000..8bfe20a --- /dev/null +++ b/src/components/navbar/NavbarButtonLink.tsx @@ -0,0 +1,21 @@ +import { NavbarItem } from "@nextui-org/react"; +import { ReactNode } from "react"; +import ButtonLink from "../link-components/ButtonLink"; + +interface NavbarButtonLinkProps { + icon?: ReactNode; + href: string; + name: string; +} + +export default function NavbarButtonLink({ + icon, + href, + name, +}: NavbarButtonLinkProps) { + return ( + <NavbarItem> + <ButtonLink icon={icon} href={href} name={name} /> + </NavbarItem> + ); +} diff --git a/src/components/navbar/NavbarIconLink.tsx b/src/components/navbar/NavbarIconLink.tsx new file mode 100644 index 0000000..ec4fdca --- /dev/null +++ b/src/components/navbar/NavbarIconLink.tsx @@ -0,0 +1,16 @@ +import { NavbarItem } from "@nextui-org/react"; +import { ReactNode } from "react"; +import IconLink from "../link-components/IconLink"; + +interface NavbarIconLinkProps { + icon: ReactNode; + href: string; +} + +export default function NavbarIconLink({ icon, href }: NavbarIconLinkProps) { + return ( + <NavbarItem> + <IconLink icon={icon} href={href} /> + </NavbarItem> + ); +} diff --git a/src/components/navbar/NavbarLink.tsx b/src/components/navbar/NavbarLink.tsx new file mode 100644 index 0000000..c64e1c2 --- /dev/null +++ b/src/components/navbar/NavbarLink.tsx @@ -0,0 +1,15 @@ +import { NavbarItem } from "@nextui-org/react"; +import Link from "../link-components/Link"; + +interface NavbarLinkProps { + name: string; + href: string; +} + +export default function NavbarLink({ name, href }: NavbarLinkProps) { + return ( + <NavbarItem> + <Link name={name} href={href} /> + </NavbarItem> + ); +} diff --git a/src/components/navbar/NavbarSearchbar.tsx b/src/components/navbar/NavbarSearchbar.tsx new file mode 100644 index 0000000..593d97d --- /dev/null +++ b/src/components/navbar/NavbarSearchbar.tsx @@ -0,0 +1,9 @@ +import { Input, NavbarItem } from "@nextui-org/react"; + +export default function NavbarSearchbar() { + return ( + <NavbarItem> + <Input placeholder="Search" /> + </NavbarItem> + ); +} diff --git a/src/components/navbar/PCNavbar.tsx b/src/components/navbar/PCNavbar.tsx new file mode 100644 index 0000000..6cc5965 --- /dev/null +++ b/src/components/navbar/PCNavbar.tsx @@ -0,0 +1,152 @@ +"use client"; + +import { + Image, + Link, + NavbarBrand, + Navbar as NavbarBase, + NavbarContent, + Divider, +} from "@nextui-org/react"; +import NavbarLink from "./NavbarLink"; +import NavbarSearchbar from "./NavbarSearchbar"; +import NavbarButtonLink from "./NavbarButtonLink"; +import { + CalendarPlus, + Gamepad2, + LogInIcon, + NotebookPen, + SquarePen, +} from "lucide-react"; +import NextImage from "next/image"; +import { useEffect, useState } from "react"; +import { usePathname } from "next/navigation"; +import { getCookie, hasCookie } from "@/helpers/cookie"; +import { getCurrentJam, joinJam } from "@/helpers/jam"; +import { JamType } from "@/types/JamType"; +import { UserType } from "@/types/UserType"; +import NavbarUser from "./PCNavbarUser"; +import NavbarButtonAction from "./NavbarButtonAction"; +import { toast } from "react-toastify"; + +export default function PCNavbar() { + const pathname = usePathname(); + const [jam, setJam] = useState<JamType | null>(); + const [isInJam, setIsInJam] = useState<boolean>(); + const [user, setUser] = useState<UserType>(); + + useEffect(() => { + loadUser(); + async function loadUser() { + const currentJam = await getCurrentJam(); + setJam(currentJam); + + if (!hasCookie("token")) { + setUser(undefined); + return; + } + + const response = await fetch( + process.env.NEXT_PUBLIC_MODE === "PROD" + ? `https://d2jam.com/api/v1/self?username=${getCookie("user")}` + : `http://localhost:3005/api/v1/self?username=${getCookie("user")}`, + { + headers: { authorization: `Bearer ${getCookie("token")}` }, + credentials: "include", + } + ); + + const user = await response.json(); + + if ( + currentJam && + user.jams.filter((jam: JamType) => jam.id == currentJam.id).length > 0 + ) { + setIsInJam(true); + } else { + setIsInJam(false); + } + + if (response.status == 200) { + setUser(user); + } else { + setUser(undefined); + } + } + }, [pathname]); + + return ( + <NavbarBase maxWidth="2xl" className="bg-[#222] p-1" isBordered height={80}> + {/* Left side navbar items */} + <NavbarContent justify="start" className="gap-10"> + <NavbarBrand className="flex-grow-0"> + <Link + href="/" + className="duration-500 ease-in-out transition-all transform hover:scale-110" + > + <Image + as={NextImage} + src="/images/d2jam.png" + className="min-w-[70px]" + alt="Dare2Jam logo" + width={70} + height={59.7} + /> + </Link> + </NavbarBrand> + + <NavbarLink href="/about" name="About" /> + <NavbarLink href="/games" name="Games" /> + </NavbarContent> + + {/* Right side navbar items */} + <NavbarContent justify="end" className="gap-4"> + <NavbarSearchbar /> + {user && <Divider orientation="vertical" className="h-1/2" />} + {user && jam && isInJam && ( + <NavbarButtonLink + icon={<Gamepad2 />} + name="Create Game" + href="/create-game" + /> + )} + {user && jam && !isInJam && ( + <NavbarButtonAction + icon={<CalendarPlus />} + name="Join jam" + onPress={async () => { + const currentJam = await getCurrentJam(); + + if (!currentJam) { + toast.error("There is no jam to join"); + return; + } + if (await joinJam(currentJam.id)) { + setIsInJam(true); + } + }} + /> + )} + {user && ( + <NavbarButtonLink + icon={<SquarePen />} + name="Create Post" + href="/create-post" + /> + )} + <Divider orientation="vertical" className="h-1/2" /> + {!user && ( + <NavbarButtonLink icon={<LogInIcon />} name="Log In" href="/login" /> + )} + {!user && ( + <NavbarButtonLink + icon={<NotebookPen />} + name="Sign Up" + href="/signup" + /> + )} + {user && <NavbarUser user={user} />} + </NavbarContent> + </NavbarBase> + ); +} diff --git a/src/components/navbar/PCNavbarUser.tsx b/src/components/navbar/PCNavbarUser.tsx new file mode 100644 index 0000000..c6a60b7 --- /dev/null +++ b/src/components/navbar/PCNavbarUser.tsx @@ -0,0 +1,50 @@ +import { + Avatar, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownTrigger, + NavbarItem, +} from "@nextui-org/react"; +import { UserType } from "@/types/UserType"; + +interface NavbarUserProps { + user: UserType; +} + +export default function PCNavbarUser({ user }: NavbarUserProps) { + return ( + <NavbarItem> + <Dropdown> + <DropdownTrigger> + <Avatar src={user.profilePicture} className="cursor-pointer" /> + </DropdownTrigger> + <DropdownMenu> + <DropdownItem + key="profile" + className="text-black" + href={`/u/${user.slug}`} + > + Profile + </DropdownItem> + <DropdownItem + showDivider + key="settings" + className="text-black" + href="/settings" + > + Settings + </DropdownItem> + <DropdownItem + key="logout" + color="danger" + className="text-danger" + href="/logout" + > + Logout + </DropdownItem> + </DropdownMenu> + </Dropdown> + </NavbarItem> + ); +} diff --git a/src/components/navbar/index.tsx b/src/components/navbar/index.tsx index 333d8fb..4408691 100644 --- a/src/components/navbar/index.tsx +++ b/src/components/navbar/index.tsx @@ -1,531 +1,20 @@ "use client"; -import { - Navbar as NavbarBase, - NavbarBrand, - NavbarContent, - NavbarItem, -} from "@nextui-org/navbar"; -import { Link } from "@nextui-org/link"; -import { Divider } from "@nextui-org/divider"; -import { - Avatar, - Button, - Dropdown, - DropdownItem, - DropdownMenu, - DropdownTrigger, - Image, - Tooltip, -} from "@nextui-org/react"; -import { SiDiscord, SiForgejo, SiGithub } from "@icons-pack/react-simple-icons"; -import { - CalendarPlus, - Gamepad2, - LogInIcon, - Menu, - NotebookPen, - SquarePen, -} from "lucide-react"; import { useEffect, useState } from "react"; -import { hasCookie, getCookie } from "@/helpers/cookie"; -import { usePathname } from "next/navigation"; -import { UserType } from "@/types/UserType"; -import { getCurrentJam, joinJam } from "@/helpers/jam"; -import { toast } from "react-toastify"; -import { JamType } from "@/types/JamType"; +import PCNavbar from "./PCNavbar"; +import MobileNavbar from "./MobileNavbar"; export default function Navbar() { - const [user, setUser] = useState<UserType>(); - const pathname = usePathname(); - const [isMobile, setIsMobile] = useState(false); - const [jam, setJam] = useState<JamType | null>(); - const [isInJam, setIsInJam] = useState<boolean>(); - - useEffect(() => { - loadUser(); - async function loadUser() { - const currentJam = await getCurrentJam(); - setJam(currentJam); - - if (!hasCookie("token")) { - setUser(undefined); - return; - } - - const response = await fetch( - process.env.NEXT_PUBLIC_MODE === "PROD" - ? `https://d2jam.com/api/v1/self?username=${getCookie("user")}` - : `http://localhost:3005/api/v1/self?username=${getCookie("user")}`, - { - headers: { authorization: `Bearer ${getCookie("token")}` }, - } - ); - - const user = await response.json(); - - if ( - currentJam && - user.jams.filter((jam: JamType) => jam.id == currentJam.id).length > 0 - ) { - setIsInJam(true); - } else { - setIsInJam(false); - } - - if (response.status == 200) { - setUser(user); - } else { - setUser(undefined); - } - } - }, [pathname]); + const [isMobile, setIsMobile] = useState<boolean>(false); useEffect(() => { const handleResize = () => { - setIsMobile(window.innerWidth <= 768); // Adjust breakpoint as needed + setIsMobile(window.innerWidth <= 768); }; handleResize(); window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); - return ( - <NavbarBase - shouldHideOnScroll - maxWidth="2xl" - className="bg-transparent p-1" - > - <NavbarBrand> - <Link - href="/" - className="duration-500 ease-in-out transition-all transform hover:scale-110" - > - <Image src="/images/dare2jam.png" alt="Dare2Jam logo" width={80} /> - </Link> - </NavbarBrand> - <NavbarContent justify="end"> - {isMobile ? ( - user ? ( - <Dropdown> - <DropdownTrigger> - <Avatar src={user.profilePicture} /> - </DropdownTrigger> - <DropdownMenu className="text-black"> - {jam && isInJam ? ( - <DropdownItem key="create-game" href="/create-game"> - Create Game - </DropdownItem> - ) : null} - {jam && !isInJam ? ( - <DropdownItem - key="join-event" - onPress={async () => { - try { - const currentJam = await getCurrentJam(); - - if (!currentJam) { - toast.error("There is no jam to join"); - return; - } - - if (await joinJam(currentJam.id)) { - setIsInJam(true); - } - } catch (error) { - console.error("Error during join process:", error); - } - }} - > - Join Event - </DropdownItem> - ) : null} - <DropdownItem key="create-post" href="/create-post"> - Create Post - </DropdownItem> - <DropdownItem - key="profile" - className="text-black" - href={`/u/${user.slug}`} - > - Profile - </DropdownItem> - <DropdownItem - showDivider - key="settings" - className="text-black" - href="/settings" - > - Settings - </DropdownItem> - <DropdownItem - key="github" - href="https://github.com/Ategon/Jamjar" - > - GitHub - </DropdownItem> - <DropdownItem - key="forgejo" - href="https://git.edikoyo.com/Ategon/Jamjar" - > - Forgejo - </DropdownItem> - <DropdownItem - key="discord" - href="https://discord.gg/rfmKzM6ASw" - > - Discord - </DropdownItem> - <DropdownItem key="logout" color="danger" href="/logout"> - Logout - </DropdownItem> - </DropdownMenu> - </Dropdown> - ) : ( - <Dropdown> - <DropdownTrigger> - <Menu /> - </DropdownTrigger> - <DropdownMenu className="text-black"> - <DropdownItem - key="github" - href="https://github.com/Ategon/Jamjar" - > - GitHub - </DropdownItem> - <DropdownItem - key="forgejo" - href="https://git.edikoyo.com/Ategon/Jamjar" - > - Forgejo - </DropdownItem> - <DropdownItem - key="discord" - href="https://discord.gg/rfmKzM6ASw" - > - Discord - </DropdownItem> - <DropdownItem key="login" href="/login"> - Log In - </DropdownItem> - <DropdownItem key="signup" href="/signup"> - Sign Up - </DropdownItem> - </DropdownMenu> - </Dropdown> - ) - ) : ( - <div className="flex gap-3 items-center"> - {user && jam && isInJam && ( - <NavbarItem> - <Link href="/create-game"> - <Button - endContent={<Gamepad2 />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - > - Create Game - </Button> - </Link> - </NavbarItem> - )} - {user && jam && !isInJam && ( - <NavbarItem> - <Button - endContent={<CalendarPlus />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - onPress={async () => { - const currentJam = await getCurrentJam(); - - if (!currentJam) { - toast.error("There is no jam to join"); - return; - } - if (await joinJam(currentJam.id)) { - setIsInJam(true); - } - }} - > - Join Jam - </Button> - </NavbarItem> - )} - {user && ( - <NavbarItem className="flex items-center"> - <Link href="/create-post"> - <Button - endContent={<SquarePen />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - > - Create Post - </Button> - </Link> - </NavbarItem> - )} - <NavbarItem> - <Tooltip - delay={1000} - content={ - <div className="px-1 py-2 text-black text-center"> - <div className="text-small font-bold">GitHub</div> - <div className="text-tiny">Source Code</div> - </div> - } - > - <Link - href="https://github.com/Dare2Jam/" - className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125 hover:text-red-100" - isExternal - > - <SiGithub title="" /> - </Link> - </Tooltip> - </NavbarItem> - <NavbarItem> - <Tooltip - delay={1000} - content={ - <div className="px-1 py-2 text-black text-center"> - <div className="text-small font-bold">Forgejo</div> - <div className="text-tiny">Source Code</div> - </div> - } - > - <Link - href="https://git.edikoyo.com/Ategon/Jamjar" - className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125 hover:text-red-100" - isExternal - > - <SiForgejo title="" /> - </Link> - </Tooltip> - </NavbarItem> - <NavbarItem> - <Link - href="https://discord.gg/rfmKzM6ASw" - className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125 hover:text-indigo-100" - isExternal - > - <SiDiscord /> - </Link> - </NavbarItem> - <Divider orientation="vertical" className="h-1/2" /> - {!user ? ( - <div className="flex gap-3 items-center"> - <NavbarItem> - <Link href="/login"> - <Button - endContent={<LogInIcon />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - > - Log In - </Button> - </Link> - </NavbarItem> - <NavbarItem> - <Link href="/signup"> - <Button - endContent={<NotebookPen />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - > - Sign up - </Button> - </Link> - </NavbarItem> - </div> - ) : ( - <Dropdown> - <DropdownTrigger> - <Avatar src={user.profilePicture} /> - </DropdownTrigger> - <DropdownMenu> - <DropdownItem - key="profile" - className="text-black" - href={`/u/${user.slug}`} - > - Profile - </DropdownItem> - <DropdownItem - showDivider - key="settings" - className="text-black" - href="/settings" - > - Settings - </DropdownItem> - <DropdownItem - key="logout" - color="danger" - className="text-danger" - href="/logout" - > - Logout - </DropdownItem> - </DropdownMenu> - </Dropdown> - )} - </div> - )} - </NavbarContent> - </NavbarBase> - ); + return isMobile ? <MobileNavbar /> : <PCNavbar />; } -/* - -{isMobile ? ( - // Mobile view - user ? ( - <Dropdown> - <DropdownTrigger> - <Avatar src={user.profilePicture} /> - </DropdownTrigger> - <DropdownMenu> - <DropdownItem key="create-post" href="/create-post"> - Create Post - </DropdownItem> - <DropdownItem key="logout" color="danger" href="/logout"> - Logout - </DropdownItem> - </DropdownMenu> - </Dropdown> - ) : ( - <Dropdown> - <DropdownTrigger> - <Button auto flat className="text-white"> - ☰ - </Button> - </DropdownTrigger> - <DropdownMenu> - <DropdownItem key="github" href="https://github.com/Ategon/Jamjar" isExternal> - GitHub - </DropdownItem> - <DropdownItem key="forgejo" href="https://git.edikoyo.com/Ategon/Jamjar" isExternal> - Forgejo - </DropdownItem> - <DropdownItem key="discord" href="https://discord.gg/rfmKzM6ASw" isExternal> - Discord - </DropdownItem> - <DropdownItem key="login" href="/login"> - Log In - </DropdownItem> - <DropdownItem key="signup" href="/signup"> - Sign Up - </DropdownItem> - </DropdownMenu> - </Dropdown> - ) - ) : ( - - - user && ( - <NavbarItem> - <Link href="/create-post"> - <Button - endContent={<SquarePen />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - > - Create Post - </Button> - </Link> - <Spacer x={32} /> - </NavbarItem> - ) - <NavbarItem> - <Tooltip - delay={1000} - content={ - <div className="px-1 py-2 text-black text-center"> - <div className="text-small font-bold">GitHub</div> - <div className="text-tiny">Source Code</div> - </div> - } - > - <Link - href="https://github.com/Dare2Jam/" - className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125 hover:text-red-100" - isExternal - > - <SiGithub title="" /> - </Link> - </Tooltip> - </NavbarItem> - <NavbarItem> - <Tooltip - delay={1000} - content={ - <div className="px-1 py-2 text-black text-center"> - <div className="text-small font-bold">Forgejo</div> - <div className="text-tiny">Source Code</div> - </div> - } - > - <Link - href="https://git.edikoyo.com/Ategon/Jamjar" - className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125 hover:text-red-100" - isExternal - > - <SiForgejo title="" /> - </Link> - </Tooltip> - </NavbarItem> - <NavbarItem> - <Link - href="https://discord.gg/rfmKzM6ASw" - className="text-white flex justify-center duration-500 ease-in-out transition-all transform hover:scale-125 hover:text-indigo-100" - isExternal - > - <SiDiscord /> - </Link> - </NavbarItem> - <Divider orientation="vertical" className="h-1/2" /> - {!user ? ( - <div className="flex gap-3 items-center"> - <NavbarItem> - <Link href="/login"> - <Button - endContent={<LogInIcon />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - > - Log In - </Button> - </Link> - </NavbarItem> - <NavbarItem> - <Link href="/signup"> - <Button - endContent={<NotebookPen />} - className="text-white border-white/50 hover:border-green-100/50 hover:text-green-100 hover:scale-110 transition-all transform duration-500 ease-in-out" - variant="bordered" - > - Sign up - </Button> - </Link> - </NavbarItem> - </div> - ) : ( - <Dropdown> - <DropdownTrigger> - <Avatar src={user.profilePicture} /> - </DropdownTrigger> - <DropdownMenu> - - <DropdownItem - key="logout" - color="danger" - className="text-danger" - href="/logout" - > - Logout - </DropdownItem> - </DropdownMenu> - </Dropdown> - )} - ) - </NavbarContent> */ diff --git a/src/components/posts/PostCard.tsx b/src/components/posts/PostCard.tsx index 2e1b8c3..0624f36 100644 --- a/src/components/posts/PostCard.tsx +++ b/src/components/posts/PostCard.tsx @@ -1,12 +1,13 @@ -import { Avatar, Card, CardBody, Spacer } from "@nextui-org/react"; +import { Avatar, Button, Card, CardBody, Spacer } from "@nextui-org/react"; import { formatDistance } from "date-fns"; import Link from "next/link"; import { PostType } from "@/types/PostType"; +import { Heart, MessageCircle } from "lucide-react"; export default function PostCard({ post }: { post: PostType }) { return ( <Card className="bg-opacity-60"> - <CardBody> + <CardBody className="p-5"> <p className="text-2xl">{post.title}</p> <div className="flex items-center gap-3 text-xs text-default-500 pt-1"> @@ -35,14 +36,14 @@ export default function PostCard({ post }: { post: PostType }) { <Spacer y={4} /> - {/* <div className="flex gap-3"> - <Button size="sm"> + <div className="flex gap-3"> + <Button size="sm" variant="bordered"> <Heart size={16} /> {post.likers.length} </Button> - <Button size="sm"> + <Button size="sm" variant="bordered"> <MessageCircle size={16} /> {0} </Button> - </div> */} + </div> </CardBody> </Card> ); diff --git a/src/components/posts/index.tsx b/src/components/posts/index.tsx index 64f398e..b0ed317 100644 --- a/src/components/posts/index.tsx +++ b/src/components/posts/index.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from "react"; import PostCard from "./PostCard"; import { PostType } from "@/types/PostType"; +import { Button } from "@nextui-org/react"; export default function Posts() { const [posts, setPosts] = useState<PostType[]>(); @@ -21,13 +22,30 @@ export default function Posts() { }, []); return ( - <div className="flex flex-col gap-3 p-4"> - {posts && - posts.map((post) => ( - <div key={post.id}> - <PostCard post={post} /> - </div> - ))} + <div> + <div className="flex justify-between p-4 pb-0"> + <div className="flex gap-2"> + <Button size="sm" className="text-xs" variant="faded"> + Newest + </Button> + <Button size="sm" className="text-xs" variant="faded"> + All Tags + </Button> + </div> + <div> + <Button size="sm" className="text-xs" variant="faded"> + Cozy + </Button> + </div> + </div> + <div className="flex flex-col gap-3 p-4"> + {posts && + posts.map((post) => ( + <div key={post.id}> + <PostCard post={post} /> + </div> + ))} + </div> </div> ); return <div></div>; diff --git a/src/components/user/index.tsx b/src/components/user/index.tsx deleted file mode 100644 index 790ef8c..0000000 --- a/src/components/user/index.tsx +++ /dev/null @@ -1,30 +0,0 @@ -"use client"; - -// import { useEffect, useState } from "react"; -// import { useParams } from "next/navigation"; - -export default function User() { - // const [user, setUser] = useState(); - // const { slug } = useParams(); - - // useEffect(() => { - // const fetchUser = async () => { - // const response = await fetch(`http://localhost:3005/api/v1/user?slug=${slug}`) - // setUser(await response.json()); - // } - - // fetchUser(); - // }, []) - - // return ( - // <div> - // {user && ( - // <div> - // <p>{user.name}</p> - // <p>{user.bio}</p> - // </div> - // )} - // </div> - // ); - return <div></div>; -}