tPx<L|_ z=~6&>c}@Rdj)~AYCQaou!Co2qeHDHa$X57%Z%9a{u5g;EJE1&3nSU^+j7P`wFr$x6 zWE^S6HiisKz4Ef;-MxGNLq3cy)i6FyL`!M9-8O%bOp+Ci4BCB`y(gOX5H{w&$8Ar{ z2aJ}_%n8x=i8)`PJEZU=m&DTF6da>5vCeuXHB4hn?{dg{x_O>GOPtH8LLTxd%B)Hu z*hd|rRZ(egoh!OHIUcUm5DVWvbfG*|S>jQ3YuS#uweE%ciV9*4UniQI#wY5VtH+1% zYZo=k7yP~!4*_^3>Xe!2wnq0c@Gg{6E5QNuG5ExVdnyb- zQcbe+*TzH_E!U&O9wnf4D68x`ko{)S9|jY@=3|w}Lkr1NF4#dE+}Z^ c2$q`fT UB9gF2qeieqI#`;{%n )#XG| z92qWeWCj7h*_7HV7W k+|(%E!7x2C+c>n|U2C#sqKnr)4Xp5_ z%VL{U=CvwSYtr8KGAY`}ddQ|HMk>w-Ky=eJ|A`r*Pc^PWdl$SC=V`6{=hemR;9#dh zzw5EOE Q-X3`z>!lbS>qLO71BuL!$0GZ zJ?dM+(FfxayR8p? JbExTHTk&X;@a2ojhEfxrkYT>R3nKNClhR3rGKF E^Xwr&wUD~T?@4G?C$`g!{AJS)sCvbcpbn&0DNWP6OQf(I2X zO@YHB%|e#i*Em-2=mMcfW&9c5NAGs6!0}k-ddI(YK4~Jm2D5{)6#5x8TH)m{Ggp%& z(pk-=pR)MohMs{bE-m~`GD3t_7EO^yA}WN~6W&&IPoUid64&>XReK9;x6V&NOqL^M z$um3&9ku#%4F+f(z4UQI4OoDog1iCm`%EOU=|s_?-e&?!Z=NeiWKL25LwQAnr0hN2 zTXVd!`u&T~vxj!SByI#B|GM*;kWg_`5zGA@SiHxJ&ml>ybF}O)|I+1XRXfUm?76zz z^>xJW|MAqK`4^x(9mIcOC3U^;fPd-XM1zDd^au~ }L zi9Vg0{W%s>I7Q)i#Ah_Z*Wp~dv2)`)_r4Fsrm3payv^o> zYJrXUu3bNx?gUkBywc)m%syQ%&NDhD6M^s(6A0da+(Aoc&a*YRt{|2iFF&iu20cvs zlDM_C0MyzXx}eWZ&uH&k=ql=M$n?*c+u@}a)y|G3#eLQ 8tWIsC*#*P#i4`F88SKZq83#{{AO`4-xxSBD;&I?_DL;<(y1Aa57) z*y^8@Hp00ned+FTQN8E*D~zH}SgeklOOlX9L#Pb4n!{Cv??Q8Ov_^_h@|CaPR<9 RzR#R5o!G9cMA4QRgb%g-3H~CT7EnZn`~#sC1%E zUBe;$+`j&d8*z|(2R}9gGci|^$Xvi|()A?Y^?PQk#;T|#LL^z6`f~hbWC=z58HTI> zEOea jeK?ckH%G@; z$@)w69&Pi8)shiJT)2nYp;C5Yy_YpO>CGQs&HpN6sVFMs4tJ9^T%=WKrZU`Zjtd#K zBL#PqFWk{2{eIum5zngkn1C0ePIf98N;v|j3i{^EZnmK=VCoDQ>J@M+jvoXCl>;OI z00000CH?ps0m8%|l{t>WoT~i;4F@mziwk&S+EBOV&sg4FVv*M`$*bGsvtp^MHBpuV z52dG?sB)0fE-O&5E>~A>Z?6-)df$KMLw9WRac$BZPMO?8f6B>=)#_j3qF 4w}Pq3Fs}jZLwk2KOC{clNsKb}&+7ZR>^L z10ng?sq2AKsNqAXuyW=imj4hvl2e{{7a;Ys#@^bOkiGU*Mc$6EWn32+K?(% _d+!F-$_^Jk37r{A1=f0X-mMfD`) zE}Z1#2gqY%6P4dSh|SM<0`c}g>L}goy9+P@RunhwL;p{71w53fPNtqo>VUU#u&>)( zmj+We4u873BY-q$utI#`%3fxggD2ZvQuw;lW*`(t{EI1|+uq3{l=|u}S7_!e3obAj zvFT(or_kaI2z9^0QqJ`5+T%<9{52jY3-OHMgL0EhICtA7>~@TLjKxcBsQ#1Q_OQmZ z|6R-SUbun{vDxjEjGj+F#4^m&if3gD|2i)8`Y$x<6n<{<(lRU&j^+w|Rx^t #Kj*5pUy*U9f)(+Ky?)1oKvDlA~=? zoZe6{8pECWUSiWZAd$lOuJk&u^g)XKMN92V!CtOwQ%1uC`Qy+Jp|27-c(#ft3_#p; z0Z-aCfr+94JlOV%zFw-+gDiDREL%G0Poew*>QOY%F%BpfGUxeRb>LYfha?Ow6D7u> z(A5AHRhJ>K`Jmuog`k4+?I2W-mvlTn>sV@ALO(xMhj5T6grJ&x_yeSEbd;xEOJX&x zUQoA_%L#`lAOVav*bwTV6Bsah*v(J6qOu36$R%li>b&}MT2!!T2n50 `oZc`koPHLwyhaUAp|(jGOreK>^0{I<{-~cJ9OI&B zXKR>!SY;SZ&;ZO#nqQU1H`=!+vYv!V|M-DqBzSQ{wk60o1NC TJ6+Y>te q{V4@Pb&xg*aI0HO`(E zX)h4721TwBsxq8WiUshCJ>cw^z&AAs@q8~9`9eLrhZd?9&6KAIdm
*2%>hBhds$%OPe&62VAqmW@Gyev5JmiV`0Rjtp+- zBM^aIWsq)3<2wAyuL$=Y^G|1s!*`agrVL>C6S_;T7# q#Yf`FHbAU?_1Bkd5us(Zli>n9&;^i^g9M#5V9xx|~>ZSbUV$mp_ zc2)wyiql(XIfx#nuRB+!8^Z2nZG*R7G|L{XnhggMto-i!%VU1%um4S^l2I0D@3%K^ z4aNaywvvsS){g9gYQg!DV>Xc(t_)s@4L4ZX)7SA+Zszh`;|01K9g)C}h8M@}Y!RS| zx 4dDE1?Y4PxRoD3i)Hi5v+z_6pfUUH zX^pX)r|5s> 3orP#Oqyy;p z(`;So;)K3*QT$=xqL?-FEk_U3=>oU19*^f7=MAtt(n(qCW!Hiz ZLitRSsIhNLCk@xia+BwJe)DQnyF7L9*b;S1|#CUAI9~8Yjo-* zl#m}VHBsFct}|<$(tjk&WRq11fX|G&K^v~3CX484#!^bjqnTp9M+zbQ?^I)@({BTR z1~cm`B<8CTylJC2`S>!))$!p5&^VsF!^>RZuSed{tp?ZZvV~brN&)kL>~l@xL%E^N z;EN|*0*;oGx_J|;Os|NM3tVVy3`vD>{nnB}mPCd3?ha2_ZgAq$yR6HiS-_xt_f)i@ zVY4ux_9kv0Obp1vFgxEUj7?c91=+&Zq=^yV_(g3_Z7!|=6%Huq>b6Zz^N_NUF^6Qx zr~}=PZeua6!u%&o=$HLyVbT29RzaBD!Q8wr0Cml>tZSeB^RlPRn1r-Ib*qS)S+Ave zB=O8})wGXp!zosXcV4!!cXc6Z>{P^d%qa+rwyI#(3*DIX;*t`qBch6}R0VR4a;iZ6 zX*vMQx=#PjN%fW(jw|N2T!DHgVDYbbaqhZsZK`BQNy03$zE*JBDgfg^r$G2jw>I9$ zb7p|+j54YA7DyPJ7f{Uv9Fe@X zQ~1>CJpR?kBXtBJ qW6j!e3~xjwtBc@W7i ze_Jo tfarOIQ45#06rKh#Wnv1bW6sz&6#C_#z}~UX56Uev zP=z;IQq%u=P(7)B+6Eqn?Ly=I(h&<0B*kMv4FsEE|O?B>ChRINS@T!mtp9>VJ84 zz7cey(VZ7wM+H4-JMAeXV{mv@lwhQP+G^rEQL~V#GigVRvkKy1Y9^`n0t5?iAEN@l z9uo7P2T2eg+iA`g;?5lL{;`TrCR?ysul#^CPtVEA2UY?lI@eGS0{CBx7y(omo+Uh= zGC|tsCxWMWK!M&UHn*|{kSP_X=KsXFytnU>S_F2&_l6Aw&&Z et&>f$luS|%G*W(jyLY{J=mzlpRlZPM*-1`G1n%;v6bcSgB$BX`eOK# zNoHhqC1NDB#l@q$!C&i~#1!3f6yx(W!D5IieELH0kJ0`}pbm0_A>{(yma34LS9x`g z^|Z}T5gbAN%PC0lGeh#BEMO45>Kl-JZya*i)QiVAkvBK(ixp(MRx-UB02i@GaAY{I z+dQM^r~?HDkrtj}80|=uxu41J0KRRvXN +P007YNi8+`7>0zaxG<2vFz4XTdOqpAtan!ukJ-T?~{kpNg znD@(J{Ex!EkeYBb&|};B`QirOEwcyHN7qK{yTcIFPwpk%`S$_VkPD;b=0;r+#J+K$ zkbFTWqI3!t+VN<;ZH ` zlpcbzA+7n5M(e7Meos3lGXDQ~Ef2AEhD%*_ZPZgVh8HQ6=>&rq4IcZETl7`66;}lr z+cN~I?Iir{X+?pLlqI@d=C74M6<$9)&edSJd*=*l8n=U9fV1m?j4aVJ?vKFIay6@O z=&oBxCb8e3X4BhusoRC7HKt07tQLGD4=Saok6JDFSTc#`rL!WI{P8rUpx)mOT7&MS zZ{|!#w3(AWYJ%wm1W7l>GN8ao8984rJ6pOX87Y}j+2d<0V=605@S6p~jF=do5`j8l z!nYhp8_56uSK54Ia9=V1oo1&)2Y!_?rY(+^F0FvFJg*T(9!Sp|^V(My9&N 63> zDAQ@!kE0&1_@f@QuDaOK&>vLRHEW#>5DZjY9$~8ROccMnrcU6qhEWsB_Crq_{Q07o zhsi5{dZ%;;XPjpv;Mt@S@^tQGj)4UIs3nyA_y~Wb1t9?e!8fgAS{q?U*NTBOzQCB* zC7u>4v^RB_p3d)a;v%CyErFwDkW2WL=wFr(lym2-7L9qA)T=_{cI@=!^(qt|U zwxzE_@{rp}Jm^@@#=j9+MlHF-T-X{yCPzz8lQsXQ9G-{~mxyK>;NY+7+UqEb{dQ{0 zo9nwD2Yy!Rd>XS6sb*5MDPbi>HIk{7O4blj!=xoRj^20>Gb^C&NEUFlkD|*kfTc(x zy;hD9QQFwq@;V*s*4WUkv+$ObK@Ob--c(P?S7y|ENSjB0>YydRuX=JIw6|k&{7FS2 zq~{Iq|5q-8_0!}nT#p=++;lnVig(L=!ek}JxqRUZ56|#27378%l%v`QE~Q^Z 7{)ntG-!=eJWh%`V@M$ zJT0xy*dAXW;O|RF!JN* ^TNDB0glQO@EHP=(dpjUz7!2%2INrB+_F< zAr*a$gfgxuQWE`x3U~_CY%>c^zBrtnqO<|uGE;~n+};%oYhftxPBX!$g9k;_sU<)y zT$wNVgH=P&kJ j-SqLd(r==SPbfTGVyj>l>Lt>GDfhC(Qnkt0 zI+Ny7d_5QyJdNC_Op}m_F_g3sWgL!#v^oJqqSs=YLY0c#lmTDWx63X7r$|Ou7GHeA zI_lV9EK=#LD?u1``#75;W(OXBa=FZ$-|S-{=|-l9L4ix(J&2pl4`%oDR0*fGAKGUc zafqK}2pQJxty^a^*5;Y366cZ?B9m@|eQv-NU@6os>U$YyTa9AuBUqcf+Bcfw8dHqd zsp!x!Phj~mJIpF7HI6mhWn(6xm8Ell9Ur?t9NUBM#|!Zbx;ykvp?3I(ivwo9We0kp zFF90{6fMn5f)S?PAqmR0dr$AIa)#u&2#x+bI#JdQpm{A1hC6UP$Cz2^s@ksq{@afM zZ{0mE7iPPfWcxKJT>r9v8sP1>Dv9ddQbM!1C7pC?(eJ=~bqBkZanL;`c7?lFD7x#1 z-w$@h&r^ob^!CYD`cYw-LhmeraxzAVZ&xufYXYj`O7~YfxE1kfobdOP|A$!#_6*0t zYje9PBTj`1C|Y+D4cfnHv64s}9HrFA0B{nw?{!VQJ@-Zy-P*ei{`=OB3?X${w6(~& zHavTu_H(m1jGTkw9N10_cmj(UH#&hP=)}*%^9qx2mAw_n&Jhc4HM$G&C6o{R&H>GB zL4nwd7xrt)zjJq*d&==ctnz(ve0#yYaU-CIbk+^-5El~&+Tooe(9WJRd!nWW4057( zD5rvVt7| L%iNNhE;1q~D?`>b z`!+!VaQpp5#Q%Zy)w~QgHUW9Fi~@q1jizntluOH%9WPrcL4@EyU+GwCvGsVG9vWZ+ zDFVc#+P>|W1x?SC&4V`3X7t=)IYnAjCG>EC`izI``Z7+J52pvi86;OL;0HU)x@~Y7 zVD$fOXlN@e6{26VI=d|Vf&V`TanMXj#1aslZ1cn6*dMAAE-LX6|DQ#(Sqnetmq8j= z2I3I5ZV^t_s}9Fnv{!gdR~F_!)XB-Bc_hCG7UaD;pAVUf@`tpg&K$VnJWn2`l_Fv3&G{ z!yj8^w1dO~QCrcb!TXfYZA9u+21qsdx*&zU{$pn%Irnv5r|S^Vohj*U2p|w1_Xm8_ zV?u!W> jtezo#WN!DaVVjg2@U4vrnv_j3NlCh zY2(~-*nb|de!rM`uQjD=iexdx8ZA4;5Q}r_+@>t{d&>W8Lq0{{ug}$Yv^(6!>koxc zLQLZbITxTZuJecIGI!W{ 9ed;yO@T6_d|x?2Io`=Jv3L zk8=1NnU=rt#_{5YJ&b 8qfFo!?h1Tj|5Mr7b2p~g82PMi!zynt z%nth0VU L2bfcZ8a99E7xCnSElb!`ss~x^WTVUO(}jCb{6E@yQ|}SzMnLI zlPuJ7D6LsLI$Q9k)YJa6G7@ugU!AXzXd4o-n)N%}Ddt94e2^u~N}Nkpt6O7nSe6;J z=Sull*kt59@(jVP)O0B$jM-wF{8pFUwZK{Xz*q(cfc>X6-alcNWPTqt&WS;F&pp^N zaTNDM!aW3W&N&4q-eMw^bUGsd%vu7v?-)Au9L Y0C>F-qHkj&Z1i4f 0GlIam^cRlf-k}%jL^H7MxGiju8MZwvj|+s6gMs2X zjdJwjBdG{|ODQpE(yRnIY}Jz^4T{18lUJyYRoNRsyEW_MIe5*&3<&e46mm2haw~o< zpf*ge@I{BkFQUHwyiLMdLdYI<8!z(Ta8SgtA&YOl@|gY=E@p #Oo0Nv~t=BcHllu0p=#yxVds3?}6d5i|RZQEN+}u zl&{W%0|7ygZ$DBm+$O>QZR j$yf%mJB3FBv-7U5#<_*fdaqk!`A{d)2MGZ+?lVG3>lj52okR HVSlp*xsO1f2J;$fjBV4k?QE0Rh z;(jsCIC}de_-5$G4{sI9$ChtHS}tV=*sdMh3JpV{ZH&S867XRO9%7cLfGRg`60qqW zTgAOJ=sLS?kIuM2n~>_z+*;}QW|HjeinSIZZLR}r% -90>zM5O1$yP?!%4$dAw($pV$iPbF)IvC|3jnr_Js~+6 zHHDlW#(n!0gTs()oTT{ynqdI|y#XSmq6n!FK$mT5ohh4;_2f_+=E;+3v(uo{QLw0I zmIoX-!e8Dl_)L%MksuQhjEkdHeRnw8hw;StIrHA4Tbik?IAyJFAAMeO>kl`dRsP-C z%~0}mAR{Ts4M5J?f5$Fof+L5I1!$0NW#S06S*?; gK|eldvQH=s(OiS!dVXq zVK)+}3n47>+)+RO47oB$(n~g}`D#oUlt&^Eh3o>}_&d6G=Mw|tA`;LNxaPIVOqt8V zGWM>gxQ1CVGDfIijAgMMW=iQlj|n((aaamY%G{z{r*X`VCWJn9NEoD>JC|kU{452F z2~0h^`Uw|YC|GA{M?SRD{@U*smXq$;`FY7dnpQ7V`9HiZGHj<3%4dk BkX>2 zkzZFcWBwi_(K%PWE~)F(=JCpB4gi`D@H+klDTL5U_IHm@S{ySty#!l%OqikLuGnjc zB3399mohEOsc8mWDR3d7O}T!`ksTod$*7O9ha78ypc_@4jTPUc{3_K$2Te@QqIX zQ_v?pErI6$u~x{s+-)P!O+i zE>p$a!7WK`5b6{l-`}Iq#G0|FCw8y7eI)o!1_Aq&eienIv3(npH}vu2i@Yld3vBQ8 zA?}Q&M-)j@)Uk<6+`ARc70Y02@l5ynxsK0ApJrc_8_ju~LJA@*g qJU~q}{MJKEObOm; zC{2l2{S>Nbwz`fBJp<;X X5SlkT3=zax^)f0gk!2==#i9vP5Og?cWp3Qo6si}AtW8?Edp z?rX|@YqJvDJ@pg9fMjU?ja|{gmH3oD@h|$74kb%EpgYx%4b9S$K=E-x9=guf$2@4E zeX1jUG7B0D^FIXGzWvuBu kY1UXc1(Ahl3J$>oxj~iR{=9iV&R((#J(W_>D}R9$QzB zB~rZ)oTYheDV_cjEJO|tx|msCk1S N&M>^w0}scybo$K% 8A#Zv25lr4C*FDr2nf zbzVHMowl%G*gNSD4}WPk+vK!VRV=Tj`ITi1rBPi_P >< T9t2cn@$2YcWjzniSm zCtrpc=8N3CB6Z(q-&gAMtfckCF+TL*S`w8&xN8A7A`0N}+~TMS3Goa``4hu$ko~@H z0*{k#V3E`^WH~1Wt}3!$#!{FuzQpzs0+Q)0oUdHtL`Zs)I~bG7M*1^>_>GkXZeL`O zu7oC6@HL-q@SpRS$odRQ)uiXyiqf8RCrO_DR30T3(OY-AYbOx(D`NJ7JGx-i>*eww zE-t)OKycOGC=<&GBVXGos10Bc{n~gOe(zH-q0O$Nx@lXZ-<;Md{upNudd>gSp2<*| z_TjU9I$g(bo2~v0m%-79-_j_AZ=B69vXDRKA#Pwj?7@HAzBL(4x9uKL!yA WDl!jo@|zV8?1M%aG7J4Uf-566X?`Y^;?(s>(2Y$~2E z#Vc>Qy($7^*`nR&?QHkICjas@WkgC5$9GP|ow`__@D5iFu^IFfLN77_xYU=+O<00n zj|;u>Wg3Np%i}wg+78n*`pP=9elj(XH&f|m;N`tPBr1D1pfg;=fVlk6IhLD!s&ZPH z{i|+v0TcJ 43hU5dF z9nZk9{2uF;&b!7?xlRe*6@^#*-2Ma#G=}Us(tAHK_N`GL@|XO```J@W;~roR&5Jyq z#zmOtF!%IN71oWadVfIF XFC!9%8wvSo*>mg5iw&pqx-P$hv|YH7=VTH4EjS8YYo#S-cK z-f37iv$$WNxCM_?OBO%#ZyIYcFFZ=UJkA3qGZPdwE;%3U?&=TwZoOKl2(jW2VFsyX za4HdpGO?R>98pxuT=BTB@#IocGRZ|!8y?=wiEW9Q{{(q%=3G3I(KjD0)pS(cF@yzF zE<}T9Qu{46KonZRoU3ac4H=P{fMx{lul=vjGm3OxmW-q_p|7(G#z7`W^eIRGFqJr= zQ+8TnO9-^zy~c3f-t?%~>}a7iAZ7F(O`CnqBQ;h euWvp^>^r%)kWFYDRO!~5YM z=>`J1j9*CFN@&fQjrMyMZEqX30fzy7=AD1EM8qog3ko|W-US0*0Vih|3yrVyk_U+Z z( U zN^9jb5zNZ$ZyI3`^T!V$Yn$f%(7-#)1*mqO!9$t-)CH2%{ IrLWF_8u}0h*W;PzFB_JIX020GzUMB` lhEGU?jAE8!TPE}&yot0`SHd|?j$gy|9+gLdvJa8r)@T)K zgWvgh*XbK$(A+4y%`ugQ$h%i?VBoWiy7xKrD$BR}19Zxo0=hC0N<>DS&r~3__1bPG zZqX~0#UTh06p |OR*v| zFbv$E((L! rSQT=sdV|c@YI9;-ev?UHvvo#H%k_;=QHC?m>XpMqt0sdD+ z0+mNU<*B~QM~_Msu^qRKBjnz3UP!*lgFe^_w-;ekZdu*VBVT(JwTAc)AQ6pVIf&hU zaZU{g^EAT|UB%GIAfu~fUt*c`co|jQ@s&*uL WtG|^Ei{w4gbG!5e0&E(XK?@Nq56QaK>7^H}V{{d>(2QpE44;jSG8`D+ za{sUUQG+Jw;Y9Y2O_V n;e^bzrm2BX`_{qqo(su-C7WKP0FlGX9XI>t6dhj=2ont? z60LGtbjdK*sF^4@5kPQ8!e;qBX4o=m%{2Pg?FkmfHJICaXaY_mWnmz+^E>-Z1Tv4Q zt7R^Q#TzdfJXM;&bR$|2BB5+Rs`^$_;U9Tzw@h0C4kUz#X@UDMb>ra;pnmi|C;n+X zBRb3RQVTdl+M@yOAU)hre_gj$v}v{8V2B=ZK3UJt0S6%#sTyoiAO? A7&ks9afw@jC!!OSFBOZ+m~GDjSV+V}w|y0&@@bXm;B2k}>!E%E*qkS^e9jfN@IH z#+(xhxe1dzKfVz}AT{HyqjmLz?I+adOq+vqNCYnbVq}Owcc`U 0yu)iRu#(vJNjKApXTC1{P}c_2k$+)IY`uv;{V&f?IOHmGXFh2TN#{RZ|tY!OMX| zS{5)y4eoaNs9l;%Q0TNeTzAb>aBeuVgGLMeWpjnB)F*8Ai@ARJ=e|-j=TGa)Qi;oT zuKA8uTaV+UMZ7iXjueS2r`@eC|3*P0PURZtYFt60g*6$0IV>2KDA5Pb&}udRRc9HS zDS{c43gI^n^IoX23?$qD9c>e2R7a9Ds`Z1xC|$*Jo0;LvfzZT-n`R$&p8=}E64bJF zvW-0Z`sJG1FY3%x0o2j)4+)}K&+hS2*NvXP>*x$I1%B(sR$8s|54n?K;jFMs@t mL(Y3|K*EzYyxm4NBprvk z$MbepIwG-!E?E(UY7dl0r)r4j+;bIJM)Z#WjGNWC7?xgR(aTZC88QsA-9fWLWR8XZ zh;C-;&t{0nNi6qY-g9%H {k*zSY1&3d9uEr5~Gj?0woxUP{n%@4-@ )^g#Bww=*TW9ZJ2e?a4faervau 4vPOOxR4H8Kt<9!2M@3qjf=r<=irz;Y_FdH+K)7sGSqpv3^D57u z7R=8;hZO0m)J|+7R+ 2w|-cb)N8Xj~sKjIBA6X zE$D`y0?5cQ#VR$1Tw!)+2|xn3)2=tDkK#Dq*_Oq6_+YGNNf7VDo4az} zdG#vuHA@_$dGncZe_;NkwfSC^Bqcy+?k`Ryp?Hf|IfHIaBe?WmF4u;usGZqtQaidV zeaZk}QPQosft5Spp;FMS7CCI&CQ95%Xj-T~9(xKW+$Mz%TM7Z`< 2VB5q*L4~k#W-46f KeE-ZL+BbV6njnHM zVmtr3W8hJEZ;rzj^4!dhg5#H}%n|t_Yr~JU@ti#qnMI28^T7o*p_9s)m{lcCa#Q8^ zx0F7w#Qnx9cXX^kw5xlf-uQUJN?UWBsEd=*t!D YBzQssvtr(Ta{>Ya*+qJW_@hX+87Ot&XTl z xwYFmYad?|csij|D)JrDIF(4Ja(eJ4_+x1oe3T6TnK z4yNn;-?+MA=pnB@6QWriI$wg|4>S#Ybt;6;O;E_z@R~8d1+up~a^s+!RDr#QlOHbf zZ9~DF5Vg5^=Uy#Ty(5o!{^TGk%PA`<$yj}m%Kux%VmG@Be3t#=?_>}EB84Qt=3|?0 zfgncaSEsg_r!GNi%X?Q!mcA;{S594R5kkxD_yY+*+Nz35`3QT7)0+Fi^P{NxKFH(< zxpGJ@7q1n2^ZE(c`;{)Iy67NXpScm$)_#*n(Y#}bYQw2qw9DfQPf^CiNnTbjN+x?H z0;3nhMqi5%#fk8y%T)rkwce*Cg=E2k?5S>I2ERJNYi`jyv}ffrR)WIM(YVgNKIR?f zEf?>h+65oYk|Cu$#&|`B&B_P;5;SUAnHZZKA$2=}U;VA^Hsc?V0f`I^sNXq2%-Ags zHo?yeQjr#dL~dC`Ehq*6ub34%)`};{q~$G @q{SMsKrT#TscN4y$sx1n2R*4pj4P{tWAS{PHj%+mc^ zkfC+XOX$#?3sr{`CQM B=`0#6>tV)DnqkxLYDm^KT)Kbz&eqMavz+_E`6P4y56^97>lDl@?Cbpu`{ z1>nj~V^E7z%G4z;%@Bme)XX$tU$B$CVEEY)>CP}82?RLKG#GZ;@-! !VCFApw7c&0QvJW88l0D;H^r>CqxMjCLycMocB8ls@gX2; z!`vPus*~fW717aJSE*p{ O!KZei^G23D;lf?~ZeTuf=L_vH*=x z_(5RC2xrC} ?GQ+n<{))=&3;iPdrjfBAYpTz%xxEA8b(Y?b0AIIXi1CI$^Q2UYo#HgTI z7HQDHa^xrsP>q!p-c2o~VK-;S5$V)P1VD6mWza9#0&-J{A~Gt4=Cg^>aigYfdU^x) zN;!!9@Y#b4l?a}H#0kf+8 c4Vu%E=!5BKWaX_Ql$p+= &CEd3LkaKe}&a#Z(a zc;pwgt+IUhve mi$Y!(fz42LOOKg7^-~!vUbOYg3EvwR;2el$F|6)%*(X{i+toY@)c$V2C2xcO0G^ zJ*!QeGN%e0XL%>Ecr2Gsquo;u>^93Ne8+1V)eL;_rFqT{k03_8bxNCfsMNRqc0kn^ zCxRN>JUS4OgE0e8mxopHy7mQjj~@P{UQ34xsyx^es~`%-<&ju2k;XBAT|epJ`qcIJ z@lF_BKv}SA58_ZC7cs=7wR# Okn`L%| Y+0vcgxA0+n ;Rr!j zWQG#FOYg7qoiZNq3d>6g lDsPjaWnxTu)ZRgH`i~bpRA~gKrF)PFVG@Ctdxllu`$H4K4qaLBcGMvDt(f0xe z0k_zDt7?_83UD<0gyZ*ORB`jD>mdx$0P0>!DAqMiSCAd0{i+u}OAm7=3O&>$EP7f< z@%pYc-M}9|7Ib phEx49%7%*w@ty zRt6U}1e&)SAzL&$tlzWcw4CHM#OGVA&)3_084vG`kLf3|mFDTuyn;!?$x4`Ov{j2& z;O|U4r=GL{gt(+oJ8 HbfGPm(tI zS4@vY2**lRAT*8;GjvB4gc{E?L@U0dJ6Z8m_C}6|=!V^jd4CXCKiO2T-28GpKcLrB zW5mnl&$Zs1!YKhVsXSLV4MZ{|me5Qy^nZpJ%WyvRnKFbdZt$ *DX{vNY2kh$pSe+5*}O+|9|KV;IaW( zdnB9I^8HbJOL0n!p?muG!p2Gdd?~R!R~Alb5cE>j+*h@!78b09hFmqV8s=)4#U;Nt zSe|vnuJx`HCoUfSAZjpQSBL)b8gU}<4VKDevv1~*O$XGgv_c2x?aQW&@6b@}A#So` z+Ha2obSR3E9OmNX6-#suUx2TNBf=SuxqhR+yUj9HwP!WYH}Kt^+!5!8M8grwAqw&Y z^D7^^6R3i>QV?k+whHs4oEa;>_*Qn&qH{{80W=ua&@idvIS={f+OMYT$5_O17Q(rm zg3bV`pd%nS&|Nj^oW^WPN7*ewdCNuGfaTDHaj)3U54g9woTlm5;huEBSK(spZVKnJ z7JGU{W8tt-*=n xm znFUR1R4&`-Qeb1YDVxmH{8W`w4dR!vH7^w-`!>UEj_5|jbU_E9c86Dav6kt0-)eLA z$oBb QJHcCxn!-<3s(vn6f6DRx zkA6W6obG4hk?4*iWCQ`)CzYS?FRbkKgPY)pvoVT^H!om^BM4*32Mg*|!-3Nn-DU k4Yiu4x%cu5PiMCw4n5uy{@0tE@urpM2Qpgv@l(v4o)yQfEh; z-d)H`Fbu1+#O;Rn_bXrC`e%N7CRk!5+4Ethk{rkPj0wv#Gj9^XByLJ`z@>Y*-JmmQ zAm}O8ly(2AQBzu zY?rU1ZckLh(ex%uSzDG{JE8dwtG9v>5Otbv)5==i6$5=mM|4f|cTx?!+4+cj>D@ U1WGY2@#2;%N<-2yEDwFn}_FvB+_q ?b~tmV214o&gxsc@Z;!0kDQKDb~-3L>dack+Qht!rRzm*W4Jg8tsXr5nKQ^ zU@7Uu#LUL=R*(0ir}!%w+)%0v6M#+qiI)vyJdxE$b2CmKq)YOtzf*mfBe$%PK*s`y z1h;P@E&H$ezb_4sE~c A?Xa%PPE=Y1-|OdSFDG>vzyE zj7AY(e0qxO)BhWOe!b{)|AZvB3V(^3`&mi`nNiu-00C6M0ZhKBgG}-yhNm2I=k@Wy z3g9Oj%(RBJ{AY>q`kPR?a?VZ#q_?XNxAw}@B$}<4C1^MR3!H6e7C z{LMge3fyn#f&eQY0;-$(b`K;o6n!)G-a-|$ofE>38%|C-mrk{Bvuc0_n>_|zn0G+L za+pKvZ7<08P{OSbfU>NN=R%Vt34+#JYm1a&yCxZ)d#$AR-Ia)zW78XRGB59{5+q1Y zr?Fr*aAk7C$rW^V!Di)>+lJGnRlM`}DGS(9q}oIMm(G`r9gqwIXGS}DGwHIzUJ)@r z`~N%E#>~a46Lz0Jv7y|qrrWSeT>t ^fge5v)}9@vGPa{1|*`QEZ$Ex|1l!B_qbR04XGW z)zh2Yl$yyrQ>t_CS;*i1C-Pv+AUYydCGefbCpBznByy0ezj# iFt;cc) zcBZiEiCMf%qm6^}T<-s;7=_9z5w#~@_)y(G+o-tWV-e<*2Ze$laHuCQt1WUEeTF$) zT)Zz&y=7K~BE*P{AA|O3q|{8US-WNyE(gMP+oCqig<&8MoCUK|_C4P?u0Xg&NW9Ww z5SHzr*mvk%@`Aik^eKXy^hAtP`argv0yVe?Jt;NbxfyK3X2IpNacL;~bwRKu!j~=? zF8;C9CAeOMQ`sdh0b-+1h(~iGk+SDfV4UyD!`^OZ!`4ERJOP4x3t?NAvegEL@o)SU zvmu{W;>Lh4@#p0Q++P^sIFFAhIj(^IJ2Z?3V=yji`h6h*y2W1km#tH8ML6^XCw$3~ zXFLR(<3AoRPXgKEJHVzBhQ{zS@s2#XWM&;;od{SLb-R8$V zZtItx;*0UIwNwiNR7%Jh_opEL$x`4mB;Y`pePWe~N_J0(-s(W&8e;}$`keJc41hqA z%)H<-gRe1c5>vsR!o+p+k^0u3jQn`IxvW>qf8i-#-1fmyTmvQwJWyjDS;?g)2pu(s z@H41N{C@UHk)Bb)?@#yGt8`KjHTUr&%L}vN%yD9*q>U+#siwdhtMnhf4m+XOACwgh zAQDOh@!BoAxyN+*G vKBmwnkh+b%Whn zoUP7MLN?@oy_>_982oVK&zWyGDN6_HE)y-2 B~z}>PzimO;j!%haUq$Kh j83=_&o^`~mnE_%L2UT6jP zvTcb;6E~)WI=drakZ|Bc%+34oMi1LV;0k9zXO5r19p# uwd#I!_THVgcI|$=bH}iGY>k&J^D`f-PNuVw%YxXZ3iKyPlgRXwAGa zyL-y_uIIZf#UV#Vq1~N<($j w?oJykrq1*-g9+ns%XpIeqLf7O{JGbB zXWo91owo !}$CT0)8&w9sB*m58!A*&kul ie?!Q>H!SFYE@CcMx%O3XC` zWcXZl2s(xpUGf)C!=U(j`aZ6_m )Y5b)qW#Q=Zc0X$*`2d1;t2;}$x z+>CH(qP@O9@fBPHEN9;W!xdKa;sHrVGxaCK6a-3BvV2Gh?RmX~s{emjfXT13$!hL% zL=n^ S6r%(tveb)uIhMHVxN>Q zF!oPG2CSL&gfrB%_eEs>p+&jmXFU0X*=guO7t*x7hXld6I&!hXr1zE MA+zk znB~j~)j}>shA6p>l?3d#fB=ukv54L$NnAbGcymvZb(cF5jYh8-=#Ul~*BE1k@8AS^ zm#VV9n%Dt9Y9k+?Fa=EtYI!PX;7Psqot|dExVuBfFQQ{vAL}Qa7!lj^g;%QJ0f8-2 z0Rc(npA!wzK2S46DXKeL!zV5*<`7lup^?A?zyJ&&Di>N0kN^MyWTE$gXad~;$_6n` zgxl|xQVF^&R8}c40bTbw#74(gkC{dSg%_G^@V`#-CBNBr;W!O&F(;qf1YWxGqcjt= zvC4(e;0p-=HF8~OSedORX@BGBB)|e1lgM>hfd|Ns&ueaXF_q&uW|Z$ee_%oQgTIzp z3$JPAIJH7ZZhsiamVg7tS#*qInBxO2sw7F9#P4Mlh*~Xv?;0AWoZMglD2aiy`eIvM zPKnTWn@UQZQgd&IHjjFo2w2~p{ro=;P1}F~000000%(CM(BmGB+lFph_v=m4@+KeJ z)kK0SK6a%>4_VXufBosdd4-oQZ&FlPV(@xd+brAj{V_Ngk~UjAL=Qqcwm)*+C>m}^ z?h(k9JCu)mv_hn>#LOu(eSt8Zx;a{6M>JhnzgQvngiqs!$=Iz_QwUMY^L&0uQuhv_ z*uUcitQVjd?k<&23!7tPT&a+=n4Fa%8Fia!2sU_eXL L+&%-svsz9&Fu)IPw3bF^mJQ&hYU zdGZM>>`+Qs7=u0)IC1}aB^=VVlKN~n*!2t&bWhU@wQ$mD&p)Sqhq4O~Yid{D{Qm71 z=k(+N@h*Rp=!C;84{n)^{Hs0UX3GhSk6g{3 5%FA-lL<_$PTtEay$c%J)v79Gh73cw-i?Qj(gOh)c_o zrbA*kHb;s8?%3NPx`i>i00000003R!pzC1OH{a+74|c*tyDn8-SqToKj+Ejr+5=}w z64l73ZTHjsij9%!^O2_QUJG7xmC_OM4NvihKlp%^?215$0aD~a*pg>7x_e56;e(tq zma{p+S>NJ(J~Mw=*Xhj~wKw kX zGj|&lKkxORD}i!s5?{I3OEX{)8Tco3>KaN}XqnZ8YSK}>%V{coo|EBo2IMI;O>`l> zEyw1EMdk%Kud`gmrf$nIW<;z5Q_Ou$JTWm-f+G(>8F83fOt^!6j5RAL8$%0NuLnGZ z@|hZVxvlTF<@x T|0v&Z})Xy2!DPzS$)&!v|Z$)$rM?tPHObnrEqoGbxtzEfA8 zo(@pwyt~d23khgS&Y(RLO#er8CRK|j?*GMeYT1pgpeUZ)l1!65uL^$b6;ZS9l}Fj| z&-r;+fz0ToGk7fQM?O z2sl(gQdZuGJ*nSSQvT2^1rla2f+P-93!1jx!~jfes;l{JzbMeUq&H@kT>`p@jTiGn zze?(_LJzG{zRx%Nb_5mN000000004SY*@PNXSCyiF~Z(xK=GIl+@)BJ#P+eqQ~&am ztuK-}x4tI|Fj?hy0i}3Z%f|_;BAQ64H06PeL(uBi!v}P3(YYe8o0bo-DE%cah^Bg) z&6x(Q`VYo6)>nFknD?V_oVEWGV^BWU4w+A<#K{FDAttHAYcU7Hr Y$9Vo<+p)GtHTY;a&@Q1;ID4qVZRIP!Lvb!u<0n_Ef5jFVsQ;z)^T*_QDs zsQyw=IfiB~E0L (W~Kyp59Ii$@hEI95PWo%0y>v*_;dz}?|HBZBNThg%r% zh$0bQF&O5FB1aZe>s=m~2bJG*0{@XZ;2WjAUS+`0d)_*oO*TUVvN0O}1DiMZq+OZF zBq9rsrzj5AV$DNj35$!(XN$N^UJ=k+{(J+3mS8C(%G*AJzipx~AnAzBu-d1{UV(Or zRB!ZT5r${Cvo`Mm-dHe;%lvW?t+U&qwpPei@ndC^ExOFT;q;AB3HPVb!8sjY!Rms^ zZ7YXq4EJ}n=VgJD-KD2)^ko#OSA#X5;b$$nz)eE`$eSOjV3~d`r1ifmejqd!D08$f VSz505g1(JdH%N9J-4n^X002KLdoTb1 literal 0 HcmV?d00001 diff --git a/github-activity/translations/en.json b/github-activity/translations/en.json new file mode 100644 index 00000000..18ec9da8 --- /dev/null +++ b/github-activity/translations/en.json @@ -0,0 +1,42 @@ +{ + "plugin": { + "name": "GitHub Activity", + "description": "A native GitHub contribution calendar for your Noctalia bar." + }, + "widget": { + "tooltip": "{login}\\nToday: {today} contributions\\nCurrent streak: {streak} days\\n{freshness}", + "cached": "Cached data", + "up_to_date": "Up to date" + }, + "panel": { + "title": "GitHub Activity", + "year_total": "{login} · {count} contributions in the last year", + "day_total": "{date} · {count} contributions", + "hover_day": "Hover a day to inspect its activity.", + "no_today": "Today's contribution is not in the returned calendar." + }, + "metric": { + "today": "Today", + "current_streak": "Current streak", + "best_streak": "Best streak", + "days": "{count} days" + }, + "action": { + "refresh": "Refresh", + "close": "Close", + "try_again": "Try again", + "open_profile": "Open profile" + }, + "status": { + "loading": "Loading contribution data…", + "refreshing": "Refreshing contribution data…", + "stale": "Showing cached contribution data.", + "missing_gh": "GitHub CLI (gh) is required to load activity.", + "auth_error": "GitHub CLI needs authentication: gh auth login -h github.com", + "request_error": "GitHub Activity could not reach GitHub.", + "invalid_response": "GitHub returned an unexpected contribution calendar.", + "unavailable": "GitHub activity is unavailable.", + "no_data": "No contribution data is available yet.", + "missing_xdg_open": "xdg-open is required to open your profile." + } +} diff --git a/github-activity/widget.luau b/github-activity/widget.luau new file mode 100644 index 00000000..debd9b11 --- /dev/null +++ b/github-activity/widget.luau @@ -0,0 +1,60 @@ +--!nonstrict + +local data = noctalia.state.get("data") +local status = noctalia.state.get("status") or "loading" +local stale = noctalia.state.get("stale") == true +local tr = noctalia.tr + +local function tooltip() + if data ~= nil then + local freshness = stale and tr("widget.cached") or tr("widget.up_to_date") + return tr("widget.tooltip", { + login = data.login, + today = data.today, + streak = data.currentStreak, + freshness = freshness, + }) + end + + if status == "missing_gh" then + return tr("status.missing_gh") + elseif status == "auth_error" then + return tr("status.auth_error") + elseif status == "request_error" or status == "invalid_response" then + return tr("status.unavailable") + end + + return tr("status.loading") +end + +local function render() + barWidget.setGlyph("brand-github") + barWidget.setText(data ~= nil and tostring(data.today) or "—") + barWidget.setTooltip(tooltip()) + barWidget.setVisible(true) +end + +noctalia.state.watch("data", function(value) + data = value + render() +end) + +noctalia.state.watch("status", function(value) + status = value or "loading" + render() +end) + +noctalia.state.watch("stale", function(value) + stale = value == true + render() +end) + +function onClick() + noctalia.togglePanel("alexmnrs/github-activity:calendar") +end + +function onRightClick() + noctalia.state.set("refresh_request", noctalia.nowMs()) +end + +render() From 57c43707e94f1c2f9f06a6783c1a404e173b70b2 Mon Sep 17 00:00:00 2001 From: AlexMnrs <10886760+AlexMnrs@users.noreply.github.com> Date: Mon, 24 Aug 2026 18:16:56 +0200 Subject: [PATCH 2/8] refine GitHub Activity panel layout --- github-activity/panel.luau | 151 ++++++++++++++++++++++++++++++------ github-activity/plugin.toml | 2 +- 2 files changed, 127 insertions(+), 26 deletions(-) diff --git a/github-activity/panel.luau b/github-activity/panel.luau index f894f109..46ecc1ca 100644 --- a/github-activity/panel.luau +++ b/github-activity/panel.luau @@ -9,6 +9,7 @@ local detail = noctalia.state.get("error") or "" local selectedDate = nil local selectedByKey = {} local cachedHeatmap = nil +local cachedCalendar = nil local gridColumns = {} local gridLevels = {} local gridDetails = {} @@ -19,6 +20,7 @@ local gridReady = false local open = false local tr = noctalia.tr local cellColors = { "surface_variant", "primary/0.25", "primary/0.45", "primary/0.70", "primary" } +local monthNames = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" } local function requestRefresh() noctalia.state.set("refresh_request", noctalia.nowMs()) @@ -34,6 +36,7 @@ end local function resetGrid(source) cachedHeatmap = nil + cachedCalendar = nil gridColumns = {} gridLevels = {} gridDetails = {} @@ -45,6 +48,72 @@ local function resetGrid(source) gridWeekCount = 0 end +local function monthForWeek(week) + local preferredDays = { 4, 1, 2, 3, 5, 6, 7 } + for _, day in ipairs(preferredDays) do + local rawDetail = (gridDetails[day] or {})[week] or "" + local date = string.match(rawDetail, "^([^|]+)|") + if date ~= nil then + return tonumber(string.sub(date, 6, 7)) + end + end + return nil +end + +local function buildMonthHeader() + local segments = {} + local segmentMonth = nil + local segmentWeeks = 0 + + local function appendSegment() + if segmentMonth == nil or segmentWeeks == 0 then + return + end + table.insert(segments, ui.column({ + width = segmentWeeks * 11 - 1, + height = 12, + align = "center", + justify = "center", + }, { + ui.label({ + text = segmentWeeks >= 2 and (monthNames[segmentMonth] or "") or "", + fontSize = 9, + color = "on_surface_variant", + textAlign = "center", + }), + })) + end + + for week = 1, gridWeekCount do + local month = monthForWeek(week) + if segmentMonth ~= nil and month ~= segmentMonth then + appendSegment() + segmentWeeks = 0 + end + segmentMonth = month or segmentMonth + segmentWeeks = segmentWeeks + 1 + end + appendSegment() + + return ui.row({ gap = 1, align = "center" }, segments) +end + +local function buildWeekdayLabels() + local names = { "", "Mon", "", "Wed", "", "Fri", "" } + local labels = {} + for day = 1, 7 do + table.insert(labels, ui.label({ + text = names[day], + width = 28, + height = 12, + fontSize = 9, + color = "on_surface_variant", + textAlign = "end", + })) + end + return ui.column({ gap = 3, align = "end" }, labels) +end + local function prepareGridDay() if not gridReady or data == nil or gridPreparingDay > 7 then return @@ -63,7 +132,7 @@ local function prepareGridColumns() end -- One native week per tick keeps every callback well below Noctalia's Luau - -- budget. The completed tree is otherwise identical to the original UI. + -- budget while preserving individual hover targets for every day. local lastWeek = math.min(gridWeek, gridWeekCount) while gridWeek <= lastWeek do local cells = {} @@ -77,18 +146,25 @@ local function prepareGridColumns() key = key, fill = cellColors[(gridLevels[day][gridWeek] or 0) + 1], radius = 2, - width = 11, - height = 11, + width = 10, + height = 12, onHover = "onDayHover", })) end end - table.insert(gridColumns, ui.column({ key = "week-" .. tostring(gridWeek), gap = 2, align = "center" }, cells)) + table.insert(gridColumns, ui.column({ key = "week-" .. tostring(gridWeek), gap = 3, align = "center" }, cells)) gridWeek = gridWeek + 1 end if gridWeek > gridWeekCount then cachedHeatmap = ui.row({ gap = 1, align = "start" }, gridColumns) + cachedCalendar = ui.row({ gap = 8, align = "end", justify = "center" }, { + buildWeekdayLabels(), + ui.column({ gap = 6, align = "start" }, { + buildMonthHeader(), + cachedHeatmap, + }), + }) end end @@ -101,10 +177,24 @@ local function selectedDay() return selectedByKey["day-" .. selectedDate] end -local function metric(label, value) - return ui.column({ gap = 2, align = "center", flexGrow = 1 }, { - ui.label({ text = label, fontSize = 10, color = "on_surface_variant" }), - ui.label({ text = tostring(value), fontSize = 20, fontWeight = "bold" }), +local function activityBlocks(filled, total) + local blocks = {} + for index = 1, total do + table.insert(blocks, ui.box({ + width = 9, + height = 9, + radius = 2, + fill = index <= filled and "primary" or "surface_variant", + })) + end + return ui.row({ gap = 3, align = "center", justify = "center" }, blocks) +end + +local function metric(label, value, visual) + return ui.column({ gap = 6, align = "center", flexGrow = 1 }, { + ui.label({ text = label, fontSize = 11, color = "on_surface_variant" }), + ui.label({ text = tostring(value), fontSize = 22, fontWeight = "bold" }), + visual, }) end @@ -127,21 +217,22 @@ local function statusText() end function render() - local body = { - ui.row({ align = "center", gap = 10 }, { - ui.glyph({ name = "brand-github", size = 19, color = "primary" }), - ui.label({ text = tr("panel.title"), fontSize = 17, fontWeight = "bold", flexGrow = 1 }), + local root = { + ui.row({ align = "center", gap = 12, paddingH = 18, paddingV = 10 }, { + ui.glyph({ name = "brand-github", size = 25, color = "on_surface" }), + ui.label({ text = tr("panel.title"), fontSize = 18, fontWeight = "bold", flexGrow = 1 }), ui.button({ glyph = "refresh", variant = "ghost", tooltip = tr("action.refresh"), onClick = "onRefreshClicked" }), - ui.button({ glyph = "x", variant = "ghost", tooltip = tr("action.close"), onClick = "onCloseClicked" }), }), + ui.separator({ thickness = 1, color = "outline/0.35" }), } + local body = {} local message = statusText() if message ~= "" then table.insert(body, ui.label({ text = message, fontSize = 11, color = status == "ready" and "on_surface_variant" or "primary", maxLines = 2 })) end - if data == nil or cachedHeatmap == nil then + if data == nil or cachedCalendar == nil then local emptyText = data ~= nil and "Preparing interactive calendar…" or (detail ~= "" and statusText() or tr("status.no_data")) table.insert(body, ui.spacer({ flexGrow = 1 })) @@ -151,26 +242,36 @@ function render() ui.button({ text = tr("action.try_again"), glyph = "refresh", variant = "primary", onClick = "onRefreshClicked" }), })) else - table.insert(body, ui.label({ text = tr("panel.year_total", { login = data.login, count = data.total }), fontSize = 12, color = "on_surface_variant" })) - table.insert(body, ui.row({ justify = "center", align = "start" }, { cachedHeatmap })) + table.insert(body, cachedCalendar) local day = selectedDay() local selectedText = day ~= nil and tr("panel.day_total", { date = day.date, count = day.count }) - or "Hover a day to inspect its activity." - table.insert(body, ui.label({ text = selectedText, fontSize = 11, color = "on_surface_variant", textAlign = "center" })) - table.insert(body, ui.separator({ spacing = 2 })) - table.insert(body, ui.row({ gap = 8, align = "center" }, { - metric(tr("metric.today"), data.today), - metric(tr("metric.current_streak"), tr("metric.days", { count = data.currentStreak })), - metric(tr("metric.best_streak"), tr("metric.days", { count = data.bestStreak })), + or tr("panel.year_total", { login = data.login, count = data.total }) + table.insert(body, ui.label({ text = selectedText, height = 14, fontSize = 10, color = "on_surface_variant", textAlign = "center" })) + table.insert(body, ui.separator({ thickness = 1, color = "outline/0.35" })) + table.insert(body, ui.row({ gap = 14, align = "stretch" }, { + metric(tr("metric.today"), data.today, activityBlocks(data.today > 0 and 1 or 0, 1)), + ui.separator({ orientation = "vertical", thickness = 1, color = "outline/0.35" }), + metric( + tr("metric.current_streak"), + data.currentStreak, + activityBlocks(math.min(data.currentStreak, 12), math.max(1, math.min(data.currentStreak, 12))) + ), + ui.separator({ orientation = "vertical", thickness = 1, color = "outline/0.35" }), + metric(tr("metric.best_streak"), data.bestStreak, activityBlocks(math.min(data.bestStreak, 12), 12)), })) - table.insert(body, ui.row({ justify = "end" }, { + end + + table.insert(root, ui.column({ gap = 12, paddingH = 18, paddingV = 14, fill = "surface", flexGrow = 1 }, body)) + if data ~= nil and cachedCalendar ~= nil then + table.insert(root, ui.separator({ thickness = 1, color = "outline/0.35" })) + table.insert(root, ui.row({ justify = "end", paddingH = 14, paddingV = 10 }, { ui.button({ text = tr("action.open_profile"), glyph = "external-link", variant = "outline", onClick = "onOpenProfile" }), })) end - panel.render(ui.column({ gap = 12, padding = 20, fill = "surface" }, body)) + panel.render(ui.column({ gap = 0, fill = "surface", flexGrow = 1, align = "stretch" }, root)) end function onDayHover(hovered, key) diff --git a/github-activity/plugin.toml b/github-activity/plugin.toml index ad9751c0..4bf7388e 100644 --- a/github-activity/plugin.toml +++ b/github-activity/plugin.toml @@ -17,7 +17,7 @@ entry = "widget.luau" id = "calendar" entry = "panel.luau" width = 680 -height = 360 +height = 430 placement = "attached" position = "auto" open_near_click = true From 67798afcf4019d31fc2cd614098e45e9ce82b840 Mon Sep 17 00:00:00 2001 From: AlexMnrs <10886760+AlexMnrs@users.noreply.github.com> Date: Mon, 24 Aug 2026 18:21:39 +0200 Subject: [PATCH 3/8] align calendar labels and typography --- github-activity/panel.luau | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/github-activity/panel.luau b/github-activity/panel.luau index 46ecc1ca..1117f6b4 100644 --- a/github-activity/panel.luau +++ b/github-activity/panel.luau @@ -77,7 +77,7 @@ local function buildMonthHeader() }, { ui.label({ text = segmentWeeks >= 2 and (monthNames[segmentMonth] or "") or "", - fontSize = 9, + fontSize = 10, color = "on_surface_variant", textAlign = "center", }), @@ -102,13 +102,18 @@ local function buildWeekdayLabels() local names = { "", "Mon", "", "Wed", "", "Fri", "" } local labels = {} for day = 1, 7 do - table.insert(labels, ui.label({ - text = names[day], - width = 28, + table.insert(labels, ui.column({ + width = 30, height = 12, - fontSize = 9, - color = "on_surface_variant", - textAlign = "end", + align = "end", + justify = "center", + }, { + ui.label({ + text = names[day], + fontSize = 11, + color = "on_surface_variant", + textAlign = "end", + }), })) end return ui.column({ gap = 3, align = "end" }, labels) @@ -158,7 +163,7 @@ local function prepareGridColumns() if gridWeek > gridWeekCount then cachedHeatmap = ui.row({ gap = 1, align = "start" }, gridColumns) - cachedCalendar = ui.row({ gap = 8, align = "end", justify = "center" }, { + cachedCalendar = ui.row({ gap = 10, align = "end", justify = "center" }, { buildWeekdayLabels(), ui.column({ gap = 6, align = "start" }, { buildMonthHeader(), @@ -192,8 +197,8 @@ end local function metric(label, value, visual) return ui.column({ gap = 6, align = "center", flexGrow = 1 }, { - ui.label({ text = label, fontSize = 11, color = "on_surface_variant" }), - ui.label({ text = tostring(value), fontSize = 22, fontWeight = "bold" }), + ui.label({ text = label, fontSize = 12, color = "on_surface_variant" }), + ui.label({ text = tostring(value), fontSize = 25, fontWeight = "bold" }), visual, }) end @@ -220,7 +225,7 @@ function render() local root = { ui.row({ align = "center", gap = 12, paddingH = 18, paddingV = 10 }, { ui.glyph({ name = "brand-github", size = 25, color = "on_surface" }), - ui.label({ text = tr("panel.title"), fontSize = 18, fontWeight = "bold", flexGrow = 1 }), + ui.label({ text = tr("panel.title"), fontSize = 19, fontWeight = "bold", flexGrow = 1 }), ui.button({ glyph = "refresh", variant = "ghost", tooltip = tr("action.refresh"), onClick = "onRefreshClicked" }), }), ui.separator({ thickness = 1, color = "outline/0.35" }), @@ -248,7 +253,7 @@ function render() local selectedText = day ~= nil and tr("panel.day_total", { date = day.date, count = day.count }) or tr("panel.year_total", { login = data.login, count = data.total }) - table.insert(body, ui.label({ text = selectedText, height = 14, fontSize = 10, color = "on_surface_variant", textAlign = "center" })) + table.insert(body, ui.label({ text = selectedText, height = 14, fontSize = 11, color = "on_surface_variant", textAlign = "center" })) table.insert(body, ui.separator({ thickness = 1, color = "outline/0.35" })) table.insert(body, ui.row({ gap = 14, align = "stretch" }, { metric(tr("metric.today"), data.today, activityBlocks(data.today > 0 and 1 or 0, 1)), From 7510952d24bb3b8bfe10718782b7c9c634f367e2 Mon Sep 17 00:00:00 2001 From: AlexMnrs <10886760+AlexMnrs@users.noreply.github.com> Date: Mon, 24 Aug 2026 19:03:31 +0200 Subject: [PATCH 4/8] show GitHub Activity preview in README --- github-activity/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/github-activity/README.md b/github-activity/README.md index d6ec29d1..f6db5685 100644 --- a/github-activity/README.md +++ b/github-activity/README.md @@ -4,6 +4,8 @@ A native GitHub contribution calendar for the Noctalia v5 bar. It displays today's contributions in a compact widget and opens an adaptive, theme-aware annual heatmap on click. + + ## Plugin | Field | Value | From 8a8ddbf508284b658dc5a37ac4040affc7d3ec59 Mon Sep 17 00:00:00 2001 From: AlexMnrs <10886760+AlexMnrs@users.noreply.github.com> Date: Tue, 25 Aug 2026 09:20:41 +0200 Subject: [PATCH 5/8] Use generator-exported plugin thumbnail --- github-activity/thumbnail.webp | Bin 28918 -> 59466 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/github-activity/thumbnail.webp b/github-activity/thumbnail.webp index 73837a0bad54e3a4cee1267aa3389c6c9299d888..6970b141c850cf042ef03cf783219f6c864b2151 100644 GIT binary patch literal 59466 zcmaHSW0WO9v+ZfywlUq)_Oxx=wmr?$wrx(^wx_LWP20AO^X9wv$Gtz^dzmYuc1G@1 z8Bw`vt<0<_6=?|xkrMzwQ(QzzLy21p9smH4{*yik00B5aT2xeN4D4SM05 G|FGFVOl$eSu*v_zrcN&Z z;m7>r@Y&nB|7WcKl>bDBXzrk{`tJ(+PY40702P2FK;%F1|7-s%jzs_f_aguRk?=n@ zlS}}hEffI2UHcy!WdQ(y5&{6U&i;?>f1Zhxv5WD4-461f2Dh*P0Pe~F03>Yy0OJn; z0I&OBegD+|!#C1@U4;Mqa{N~<0d@d$04YEk-~ccMF#bcV0A>ISfb-uBld2s70I2=o zxnOkt5QN|YaNvPLfj|D4($Yq3tNJXqUwf9dfTL$NEp^(XseM9f+|G;CLnFnUg!(*^ zFO2V?tQ)zzx@(=!2Z($3skqoLtC-E*W>=8Um*s(A2x0W~_L09XDEj&OK;ZH7ao_L; z)VA#-xO!oD3_J(zJhUAtK7n?Cu6+|A&|3?L_lxV5>7L}}%NwW-Vgj*!v|TIy5j MSlIJ*Vv z|LOq+fK0!*uIr}bX||QW(z6UF1y( Zvtn5USIbhpHGrC z!6ne-hwpRM^Y)$LT^~uGPagmn1tR@Q{hWMUcsKmqeh`cRZh?9Z5}&T0x7&a<+d_uJ zz)Yaq*Y=m|`}1?(r=UyU5vU#{`sMS<^}_Vkchj5b-}B}A$@B_($Uo^D1$}~mZx|o0 z|2%QL%s=Rx0@{Oazs|4DSNblF-$5>lODOl-S^0gSkKZ?>n3=^0?0tTz4)5&w+~bx% zBsf6KZUoa>)tNj@UFgiYmm(Jte_76!ig){+B68b-Q{OZo#i+9V>7U<2_DJRA4G4#a zifEd>t(!joSmphB{Z%Ruf-9@6yY|IYJOQ)Igv+d7!H5X08hpjzSK5SE5|urVd9i!z zx?9{ms*EniXX0pvvFGU`TklLfz+^K(4>R3|9hLpJ8hbt&c0KQ63V)fdDyOE!tuwJ> z&jkS8V1+c&5tXjTZpy$hM*oUoIP~3u%J-B8`Gu4@k;RH(2TP*;gNZ2om9fj8 zF1ij(NQOwo##JrJoMRU31(P?tVy}h!#_7bKCF;plPts^%r621zsHG^9<`m&cA^Sr6 zkkB$i-_3f?Ls*7+8{vl6T5r-`Uls8-NV+dhD3MBFIxVA6CtMCC46nt*a3>STLNHAF znK^yfOE7it){TPFR1mq03jgVqZ=S1U#24O&(f}#YM5AqKGL7?a4rB2FCt|!sK(I3m z^z<{~8FPgTYx9^Lc#W_Zm8kY{tRNB9d&Zu)Xic=(^|c@}P$&wH?vX cA!tB1SVy z;B+2XyU9P#h5o__Z3ABi$QKRiH9|PiQz0&xRmPmgC}?|vZBDgipGF)wovM=7{>5TG zIdLK>Qkgm{vXaK4oAq)zsd#h!Qm9^)M9Isg@K>@)Fdv3%=~RYR6hAG3uD%DLIQME? ztts_MWI-25ZM!Ynt#xKuiJXmmAe@Q7pV*%2>Rq+-#$q<9Z#Kiho^4llq$5sqHZ@w- zh^&?@QxeH~N0FC}43EL-pUZ2HN3^GbX!o%9rZ^fdJAYU8*9_uLH}qaKcZUp9Kz_>4 zNMU|#EV7y}^nARI#)A!*)N-4QQI$D8EO$_KHI4I8moC6I*Id_Xh&vQ_@X7GXyV<0& zV ~GK=i)VDaT}X-oo9(i&FjstXMu1*mWxAOQQ0 zD1UBlz#SB1d+UB3$4e6=G@5cR&isC+F5INqD`&I`1-)V XVqh8n(HGU!Y%#|FZjJX>@u)lWTuHGK#)6y$OU|Q=@w(aKQ_l8bR7< zIFL1r;ma5Ue|1eD*wEo-7<_PI@;h`bYE2z;0)Xchf}?yAhl5G`g?A8l^87GtWNOq^ zZg#tguGuui7n)8hOL;w9?d*QX*N22i@v@dyMV-3tPP>mT`5Zp5?~$vhO`xBjAcD9m z8WUTZe2Rgqe@Uq`9||U-!%>D#R3eQ13;roeDi!u;y(IpyGpn1Yn{wQpG%>({PNc@Z z5)8$unyq5};sg_$tMBd4#c zI6F-7+ |YF#ao%;A@0iBO-vHe+xGS^N=anb$Wvcok-Qv(*^)tE@s9uh3%4 zn%6(H+T+Rz!`O{~XvFj%azdUxvE5 +^o-?bA`oC@}!9Ol>ZoD|Mge_byTM!yXOVYQ+@bZA)2PGrf?lNdvbtEEa{rR7tE ze)xy0bmMH#$2a+M7`l-Mb9{7=I>WU(%>H2f4oTUpW$r>11M8RmH=hy95z!%mP{@}N zGq)cJ{~me`9ibFUj?Y3#vW_-*5L?tb+8A=7mE6Q95LVdz+}NARoYHr8rN(*>LAy{P zCBTBS%Y$Dl5Gvv-82{({p!JBta_Hbf9#1u KwNFXWfp4MC{)HU8D9rdbo1@TZ){rXKzP0cg+z#h&n~ =+OLI>la&$0fgZnQ}AMQ6ir %WXN zA& b0P7>D-=gc&@w4)H4cdW;!J>-^~c{ zHGS6o88QNfSJ+vKkL$vgdlr9)@*N;4$ECC^@J$}4TxE|^V+Z fgkRL974!G 4aM(A90i0%?2nW((>6VhRdMQ%BZUio zr%@|t6s0n1b-0Q&f~HI~nnnG613@D$J~+a5o?7vov27Kt@7)$+l^F5$;{aA1O+7e2 zdzpPV9g+@XyY1wNm7L-y;jdr)2oX`|F^V2%KN?U!jKItP;08<3(eBXtOxpE$=N`Sx zPUQ}oPA?IpJxp>a9*9n1F}pdj?VI^*JPo?|lFtx)Bvl>c^gU#0!*Cp^hxgtFt;}if z&veEfJZef&C9EmxM5(jsU>W+@OQozz^Trkw*wxKU*Di1zs=Q=5z6ax<*cDwu<8>7k zLR=m;AaYjzCE5nlDz_^%ZKK)5qiPk62>q2M^leLzCx{CRL$j$JZ`*C;$UAazdnxQ3 z4D*OM$V|I6N5!`gdeKTvzkFBk_}af@7{0D0>#o?VisgcAl^@U7=bFr%`Hc-Pk=f z8rP<={Ght3)rf!|{LwMjp& `xr3@$JL(qLw zB9(n+2$obDQ?CHud!5IQY8W>}(*!!a#ndT3t63M5^ZoEqzceDb3E|ffM}T|sRa=Bl zf5zAWO3gLW-z+hv3SK`=2na?NYkSL0NCma2hY_SHF9j^vlTyUuDp_^?hXl-=aZPRK zcFaZQXX*ofZOpe?zJ2J}pkIkr@68K^iVL1W!Ap$hOmFZjyV>)D(+VDrAvjY^`Vi)p z&-hls&6<;e6EU+%(MLUXZs_(wO<(ohF?!n99M|UXPcyoj7By=fn^?i7+sIUhTlf-D z9XUlIX5V)u?{U-=>@-B1`gkuw?&trwN~C?e#3e=$UlBIuIj5kU$y?j_GZ!Sdwcj_y zhpM&a!isXHDS;%gjV<2a7twq_3xB!8S!%1Zz)aq}akNT6g0d;C=tajvfLF}aV}>mK zb)C8#Vr_kI*zT!HKabSy#7>+#l0)!77QxEAWOfs-$-67Y$PZ1s^jo-Mn#=in4}SKJ zD}15(dDC-WHPffET;1%S4JUseZQ8D@s8@FQO8qE!P9Ke-2!!?#C_tTFE!=5GhliSt zRs>ODN~A!bRA YtxE^8q_?@1S86)~|HQN&A`$42x&zFea}?OvGRv$kQCeObwTYR9p9pCUJO75^m;1 z#ot>p5}5h9Tvha)qUk}xEUDUoEFTlJd(`0%Ke%VKnKUVVYvNv5>LM0;?6K?;-3y+E z%24g-KB)be78GO)1j;JC5?*J&eWljPCD`ux%4(t?3QzYojAW$ce#;i>ei^E+rtb$Q z{6%;=BxV4kAv`mphd`k%Do`Cfc sO_f{*d$FhR;sokeEA&xk4s2MPOWD-|b+qkH%PN}682fCPXgtqmAdmQQO zF*v#jp=^)Fk{+u*dqQ|M&!zJVFVT^K+b<7CMl;B8?DnQ}_uwyYF!k}%ye3<|_gSpV zMrCCN!kwaG#jScF_(4S2pX8`RrMKh(eBozz3SOW%T-S$+3{07fNC{P0%Gz!&1Jek( z3JbTNW0_@t;m3L@(+$em`5jG`-gOYg7aB}XDxUPJ+myoGL$7#F02*OZ#dp&-e@I68 zUezd?Iou3F-pIT@sb{=wxr3 mtG|@+>Wpj6vDVunO&dt%f3+wr|7bP@V!3+c5>G^JKZoseUd&lUgabcs! zelM6&=D%fu=LtJifPDl0n|(o3ua$-)PI?(AaY(c?W)pC4uOCtm)4W=tw=`1T;P>&T zO9~)*vClHMdZl>dHK_3oNGL_LmQ)0&wN#o*6|X=2vcrv!72F$@)YPa0s7{Io(CLD= zi00>IDlX6dBAZQ7u|SC6R=M}Fg4+Qj&PBjxy1}B8H_&rRQ!O4S#Z{ABO;8E{BgIZb zM_Xc@)0^!<5vlm~Jf)rzeq@dd_C8Hm5Id(pJ))OFNkRNs+*xV}ho#;{4t)G};fa<6 zmAT&1z)518MM0hziI^boh7aSy*7Wi#+}FV~+<4 m8lExBz%(w+!= zpNvL<89OPxH(y#M(m)RUgK^*3c7%pTI{f1tI)iOcJAcoO5=NX-!#NC{H@}_(eOL$O z-Sf5EYo*$jtrBWn((RXKDCUuS;TL?O&h pwIsel^yfzilCeV6$euxKUW^-EKlo7vv!%>fqpvylJ^keiN0o>-0m0 zPM-x&3LTNXC$JcUoqK!`No5>|p|4V>Sj-rh^V0ex|Cxw2H}kOx{@iP{UwHG84nAP@ z eTiPK@2;qSv1;sHiUP{m0iyUX+GFr#n>1b6SOGjN+bbOshT3b*%EwSGV= zPGshJ6$z&^R=;^tYtJ-=G`8lYTm$v0khQ*TDsImw>Wj)c2UM(=jGBbJ87kNMlIRMz zuq<`)uv^lNb~T0XJ9Q>=Q!F$@)I(JzRt_YoAN;;*K-0TXb%!@MSc3NsHn=*OM) DU2k7QepB`!m-ER+)wH%RmH3GD=mA!&Yljt54>AhuOcEt$B|4P1Qire$j zL*dkjToYsCi+6utlCed75i+7ls_*1sY*sphU5j7<=VeeqN0=C|1a@#w(Svy|sf}gs z`tXS(ka$)F4T$k~_zRpvC9`6$Ddh@nyw*AuxTdBfwIRfp5ieoB#CAIAcMG}4(FluP z@%-L=SR6G}K^qJZ{<>XHStE^8K~{xMG_Y?)UA9s8naIMickKOuFGu%_d!OJOjixU{ z?$;i)SmCk%T>T*y3r+72pU=pjF-11`jINMyNdnGrS{*1Um8+hh6J$FlwJ5QN!q5rO zPw4n{tR3@ 0hF5>bdc)&-#c_ejJ?9yK z=u~qE?ybn4EqXm8sY!(_C{bXQSLfL1MY$q*;uhf(wvvF9ELc=1XC%24?B_vm>Cp6# zAT92EqRhX@KK|>PxB@E?dteZKaHPo~VKJ8Z>)6r5evVggdDaN~6z3_OGOW+m5Cg-} zE*kl*VS&41UzADi$!r2~+l8&HDDKWF4+cYpwfucUkQ~rs;wSID`kv0ONIb`FiMPgE zz3cfF5Ic+U7w~P$Budt-jrx^PLdLT9&wCk|Q^;P8E+gHSm I>iX4M+k%hn3&0i2&{ojmE(C5ZP>|G?dvtcOcv>& zu!HekDF$>1FTSAzYsOju0PyvB;>NY~%OeCBDEopL^S~lgnF?<3=}NjsmV62uTy0YN zZ^zRvMlwmw3QXR`^)VKu7rT;QVh3G1=iyy{1;&BTDd&Y{o<})+>$;tka=H^&?QV-9 zT!mkboDsTxut-FE+J7WL*qJ&uPGH&V16u>WWKZV$K-fn}FP0xhxleQcJ}8f@Qj??z zkG^oSsZc Jg_Hr$mzD&Kzk}s40q!IRpI3VOXf5=b%u~ZCwEy~|BIrw?iA7>WG8@fF z^r70o4xPVd5NL||bMBjUc!8-jhGhp6muO?-sshv$Z{~s(QzYBkXp`RcTsrSU{w4NI zRF~7DRTu48W>K{KCd>sgF75f^4#vnmlROy-UH$|2Y=bvN^2sCwjToi^r~gm5I}sr6 z7;bguEl p)?$Df3~{vgK3twatvEA(1p>QNi?f;t5ZVL3H&k>t7>zyohmhJKFy=~m1u=J(iA zIr iFf7|^R(2JZyXmX zc&`->9mbbqZTFIeK2wu0^VoSRS{A-%kfPD)Ta4G_L~zqY7BiAwKP6)D_|xD_0ojmw zyxw9SFf*f5+aGV5W4dV!&u1*)L#rl|-H(SA2U+MIQ&go5#z+}86~zeFuE0UICUV81 zAax`I`T0+a;ql|-qq3T2|IuWNd^&@D0jXTTIeDD=Zj!NGxRV== e~F^n;!bqKhk`E)PzadV`h`8|MZ z(WFefzsUi={|tZA?Q=gOw-(c|%CaA-0HQOa|MG_;LM52Zt|FqH(Q|)y!m)=VR5$P_ z>!PA96tEpBJVxg4=OG$r+3*`B75D+wNaMncC0&?MUZK)DZMj{1_d_{=`xWhj2nk=v zFg3d}b95nZ?*3ga2eLyDRS40O;tqRg*z%MyJle?H0WIIbVS}>O7BhvoOTrGW@R93G zr%E#`D;0LBj8xnX$~kj6$k4-4lie+g3z7epmq9t7WGYo!P5Kw|hWM+0Q2n=zWCPZ} zpK)!~2v4oqtQAqC5|Ra$D++2z$4Ux|6Rpm{)^%0X@|i6bOwJ!pwBKVfKXLNnQN@Ak zz5)mL#j3~6jXxq 4b>P?fFQtA9e5iR4 z=sa_?JXGd)H%y6%PyD D}rk)>Rg^tESF=KVm|~*nV6g zI`0#ST_n7e-syU&$yb4_<4ubqfp`^A*)!VHPS1+>&swH@=i0DMfVk-jObP;kGCUYZ zk9fn&U3y9nk-#&%3i GMX1{O1pNz^~nNUEo1cF_Kzu7CGh z&Y|1?_7c=YMb9qs{S0BNTgLd>ReP7PIL1Hxgl$k7Kp+PLL5geId-C1~n!MrhS#Os| z0gQX6ddU&zvnte~2EZ*b)(E(^03Fe-3JfUC>)tIo35xs;Q_ai+IzMDtpH8ykpMG=B zuUiPkK7RS6w$>mc*h5iMfh!TOn*@(6p{#1k`5lLMeM_w;+Is}+f%;QJ`V7NFkn_4V zxIeJl_Yq@^mi?zEqSSjf{#R|pe496TKxg}DwE3^Qhf!8$3SH}ux=LUPB34-27)oLY z`WEPeI@4iCz-l>0Rq0cyQU_RNn3aiGQ-zo~{+ysWA@OEakQgT#=X%zLdJ 4=a=W)hUvrJ{&)YYK&&>MfD2$&bf$5nOc7QmMOB-wCBLiCuU3{ zS>f0e_JVB;^KN^m<>CSvJpbDaKjva@nqpyiGMav_0K>b8MAS{jW`6EZuff7Id$!?R zk~mp5 =`5aycri#ma;9@Em+!G3XhW2p|1jLuYPK+f-K1w|)rP~xCw zNWjXu8pd{<8U94&tnlPE(dSpTWC19@V5eK}6?ZhLX-PizH1A$@ISkWxRyD=sN?xik z(}L!uV`Q%%;q<7b1waS=4oOHZ?7^_gU1oLUCOrgjQNjspOdUuGvx$tU`GWR4p83Ch zC6_da0#>T#cA&}SfkSc?TSV>eD>C2xv!PoB*}j8ee>}wxwd@2M-x*l_Ei%mTZisb` z?Uqva)CWQHXcVa`XvD?vut&mb59-XE`?2Pdfh7wrL%wrI8uG)mK^cVjUgy>xSwEMb zby&2BPUifz!?Mg|CKyux3#E<_uX%wCxp_-@oNx8ik{8b5TX5s|J`Vv?8u5ev{rAS~ zwB}ap55{_U9}LdvBeC7)%t}Sz{Uf>kZt~H^>#W)Q@T0|$>(qh15qgZJZBnfGxrGQb z@+F-Wx>T!z`ZV*sMiH1zMBN+41?1ehdfp^WGnduQ%}r)BQQz8&4lU!>Tf3UkFf99r z?Wk$H2F+q!_*>3oa2i*}vu{*v)=?c{7Wcr`Jq2kk5rWR&xt5i)af%aDhp4ueTxrkz z54hahV l-eJ`GT~QOQ?^(Glw5}#&ELn+Bzy0@c0aNE~T>oSy zT&tZ610BR79M*++YO~!^>ZZ! N zPEb6;TGF@MHv8zhO9~A!nX;A9RLMxNxI`tJuar#F>@f$SuIS_){rzO;eSL4O6^Vq6 zumwlt>BrsSV;aGH&}s7@RPO@dqg(q1>+n*O}75jXKFR_t&E--rH!=`K(sTqycg zrBk=yzW9qWNdS)jfJ7Fa=h;0n%l1;I>yzBmmRYUnT=R}){IVd`TL&J2&v}->+HUVN zAB10Xva|=y+$UkFCu;mn3R~QtY1rk$LAZ+M*TP+d>_wC7;KK)x-8Hquih8#f@>B#= zAs^U16R5F(fMYjL#G82?#U}Pa;ZIRF|AM@~?T;pOfrQ?tac(6y3`>#xqa^Y-Puc{H zlJg}!J O7CdXZ&{AZ%R1;awyMqsvZ9zQ}22HNw{bW?^jxmvq}}o!ZmB(YrS@xueU!rM_|# z(E~D7=M}6=+aKAQXAWMRTVPXWYy%?~*!5cl4%(i>y(pB2$b1Uu)hvNOwibERli9XA zc;bIc;Jf+%ar5eosK)#}EdGm)klq?vd3Sg1gBV_- TILB~qH2XzHLr8KVC}Cl??v^++Z#5lz*G6yTILcP_qK!_ zs~U_Pt=wXrT8NbF6LQ|))Zcoa;AMvJ7~Ml$1tQ-VtKa?V*73VT!+q?a*GoaIT00Di zxjg5zZ9uYhJR)hZ2qJ;9Oe+VyyVjTepk0nB8O&mT9hkl$pJ6i+o$g5jv?I@5yD_g< z+}hwZkdPiGq9rO6qS~SlJLMB|4S=V3-Dzzk;$7vweJ=3}C|`|xfA4CO-jkS&aZ7Xj zC^^4mo8}$qJMIPd_8OYWowT<6+D_fjtHm&T9{ojiE@h!XC!CEt`q#q-Vk+N*H;bkK zPC$fXgc2?PHW9}|Nuj2D*F9dY?e+rcRX1itKBCH2Tl{X>;yQ8Zn(4t8L^(W!$Rh?D zc+NWhOFwo4%1R|0CrwJqC$vLYF_Si)RZ>0n0)3w}?7m=>{0V(JdMg!D{di3rQDL0@ zllS_ibQTVZZ=yRZ!%_WP&*CV?#vUE{^J{J|af-9;a6&!nEhaMDE$MY^=1L5X%G2@c z0sa&CMhhdo9%kr3$cVZ?!9?xSc7|g{x$+w{2z8h01m 5l9f{4x6UlcKeFU094yMlN_uqf zc+*3Iu+DT2f704fI27lQ1c>~o5EvGXe7L%-JpP1kf=!LBClb^M2J!vm)NkOrIcEaC zBlW$mN>5swvWf?0`Snvhc*4xe84@$vLR1b-*K9p4(2fMOxufq?-6;XU?y5RzYc@ z@Y`PG!a{|U5dQqB&WE}$#miy>$>%+P8WOdq<_D9Ur|w~hg<*w|uW|8eTMFwn`@H&C zrjSl0n8~vkb$~l*;RtmeA>Z4FFv9DPKkqpDI^Ki1SkRK-KqQPr;vJFJu@*M_lun}^ z5@j}JuIKR6*(yNMs@yTiCj}nSdqE!<<#3wJseJN+(nS|fbw(le2)w M! z-#VqwOIrGiR}>YVGq2iM0^z(_E ;oir=@UAiY*)p`H!fwcC(YatI4eL8&IPmOEoG4xkh3 z#zzBi%2~H~snCp?s>E|e3bXAu67 y6lf<2ioHz5Wp9RyV6(CZNF!LO46=$K$##YKkT54;=dfX5|9Mi1sbxAiFMt0|hTZ=5JR{7w&{ zI;2kPG4d4KmKXVpbUU)7vC=-1fd{9QUXMDdYx~0n?;ApPho_RtEw (~kNX=C8a` oOtrH)Fd+A zz*ppHk^z8-7k3^xny=%$j#|oSvRv8vVbYQMA5^lT6;l +S+ zaks+8Hf&jl_z3`7l#ZkI@<8hGA|L7 rNIjVJ8YFij+gyKYv2i8?q zN$^jBngtG`!E&+A+`qf Ds9&`&XPXvxFEqQVDR885sT^5d) zH{81BY=NU6nx-z_n?pXUlE$qy1XE<_Qu)`Uitbn*OyS)BWP9X~QDpcJog6}Qikk%Y z*+{=J-w4=L3&hdDd0(#{&zApKt2iJQ_RsT&@8a6`dNgR!3>$`;x6e#1mLr>%F<@ J!Lp*T(&JPO$A~8t5`ov(wAjW|S@XmICd!r~$UL($q-|G$Q=KXf`7kWDl}Il1 zY+t<4-%%V1-L5-yS`VB3ICBb{REQppxS6!1vlFp}IH}g8v71W=AIm7r*=VM);~?}s zIYFLw6EtGUc5)^*S_5M6=lEqLjD7P#Pumr0s_MXzHknGyK}uv_2J3kWH-z0Io-P=1 zUw+sd086yjtGgfbBXIcVS;-4HX?Iqbyp~uqJ2-~y=I?9%$PKM?BmfJw$(xc<(H~kv zrA=^z;F5Ea$YR76-o 8%kscTr9rF_5IY_DU<*&|`QtYR!{T@Ng@6CyI43aB1cW z%bZ-C&qCbnQbz4u8Ewg-=nI2-Ikbq tI+u1^b&`eU3AtW6@M{ctj9{DfVrDL5?v<`NwD(=97a zt-dPM*kt6F{>5Cs4*IIUCPfokN=AU?zHRe?VpciYv`w==bHst;cIskGDZyOa9L@vq z{O2*kfn(O<;Eh(qLxZrRfqJcE*Y5?HP`c=fZQH6e@~T9Jm)%&v)3XllsYLgnq=lCG zMNqFCB922|EHq22Qzhs3Y6iLQ9qd~e28!*xSSHkyfZ`x#x00cLnS5RZbj5^&Mvl4j zC~w`^JX9LKW2tyDIEitbINI>1VW8%;$iL$YzvzQq*h%LVRLNNq3?8AjS6fL(rLBUa zZ+vK!j|L^$Pu8-38tTigHw9bR7ZN%GE<=8T=Q?9TI?rw_*M&wCTKGg9+qrf0_-T@6 z)wbD^xk7B9MRFooIuNhI;j$(&28z0mddEEy-N)Pzo^^qlv#9_3tOFUzU9mhFwV0#d zl`e;$zcg^#V>wU2_!w44Qd$|pybaBvR<1;o6=x`hr}ZMR0LK1_t#UJ0>1nSBuq)j< z-8PW~?j1-END04;O){mklW$> xrU( zU>e`8FV6waJr|N5yWKsniSsCHZ92-jzckVWnuBhl3&h>0wKZMsj2P)rUNwjilNTH6 z%B^n|438IXNqAsx4bZ&LxR3;h8M+6 iD^+6ahB%n{JPX zw?Sz|Jj9s0aqfqh@LA3ZRjE9_{s%Q4+IvhKjOC #_e+wuC_0>6nan3u1iWbKZ+3(6|E0%L_m%0%&eR(V}O9v9AJ79pFV*BUnMDwcW}2DEi*13a_xR?#ecYx={FZokWM!$ z^?xWoy$R>J&fTmSV~4cOo-UgAZ7_l?G2|bgI8KbgP`)uuDJ gn%x$A2ta>}D zP3`jdKw(0W9qDO5DPR_;zrA6y8DtZ%h5VtTjxg0wJ+UsGwpOBD!y)CPSIm7mJ=l(F zlCl87drK?+qMUT#OHp^iT%GY!|IL)xXOX*t$~>hBON;o?H&IN7MU(4+Uf&=_^hOhM zjKO3qsx5mWDJGl?6YV pu `{Lx{}Qq8E$ z9=cw4W}h_52dRNOx!7DM!Y!7Y8A9TsSHJfQIAw|oaT^R#27Bn !qnEwGy%!h~?$yFS5P1<<_ zjrG!ja Pw=$5&C(aPg5RCaCtia}&h{;VnR#$YXM=Gkd&D0`pehNnyEHz>a8y4ys( z+bj8jN5lRt#GK~jibB*|xdPR|Lk%>3cv&UXVc^O{ekQP<#GxZ&X~x3pCKrSlNGCwz z;DsBz8RX-2GVv-AmN%K~g99VC-jZ2GlMLSLd?SAb>zS5ywh7I>3pRbL11n0u9~&r- zqU| IBgtI@RaO0rfx)c6r zt|KyeIf$Bfkcvx7HzV8iWurTK$2l+Gl^Iq#pb0^udQw84Nc`3tr+}{-ds#bTmoek9 z*kT^OHcjvA;hdbuQgi|32s4mG<^EDC`))sAPOFJ*-`_~m71!)LzteT@v8ML7YF$au z^j-Z Y(0J=QzOOv!Dvdve{T$iu9ycJe)HW`5a@#(-y+l;Cjqe4e zWE+wm8TM|1qH{IwKK}z^f`MjeYxo6GADp{JO%CxG!c1$@vW+m>y&O%3tlpb&HuFLz z(cW?NTXu%=u+~*ylpBAa$Xv052g^9Ts)aZq+c)!su@)8?I5IgnEGYKz@PrW^i0k|} zNnY0Y(Y^D@?7!; LZtcdk6{(@Cw4buCI_Jf`U~y$elMf;K3h6GfyDK9)U9yGld8iLQghZF0=~ zv=`3gWSElQ*5Oazz2RCj^C~MB3HlVR;J+Urw%^-dfz~zTAvRzJaWmX*TMte2ZZNjK zcTDtKB!Ss~IO s_8#HH1R!@Nqj4a?Hn5wo$Vfx`!&weW SlM0ke-kM`iun!wuyH``AXF0HJyDVt HF;0#7lVk0nz9yU?vIEBI~V5&=w~(9`q`>#N#wNHT=*jzGx3Vm2@J_ zZ$#^PJ;!~+1LAQ4I(eJ+xnhNo@jIthutNgpKMIyVBOxtz-z@ta-;+Ou1?VX{f~?fT znDg5>-1~Tx2uvh??}S6BauKZ*h&-cnzOk=!WTaVuF9Al>&}gwo&}hDb6&!|C4=JVT zj>PnlxSqZU5+&o)2r>s~WkzI~Cnv2P2=Dr Lof*4n0IvfEf zZx3Xzk?M$CB`0lc$7Uo!r6J T1D<8NrFRLj1PKrZv!~B4vU5wrTltmj3O!eHs7G3@Yz%C-U zzq!A{h1H(NM9C|M$j%gMRDd7{i@N=5iaZLy2?VP*z=lGXz{fI7oL|3*40dID_y27o z{yv}O<~itflK(MB^+;$KgbjaEQP9+TFrLqE{Xsz#V_`V%u@Aqs=--S<^L8P<^ta^# zPXMv2_lv}LOp%bRM;0T8k^>gL@`f)w*(0|3$8Gd!mi1#sv{B;|%etxHGYY-WRNW1# z;Ca!<89sfGhKhO0J^o=U?BE+hv7Z?Xv9^*!+qiS`JWG*EEzAaHEuqAfXU$e8doU-m z!J`{V+K8BU@f`H9T=^J5QoyT0X6x>z)8tEdA9C&A?FUlqcvQ!QnrV8%%V|6@q(3h| zXFb M ztNE}&HCgyR)*m0Fato%5HFY-^*^_uiqvR#lB6+$=a6*51x&VJQwn}cCX&t$pGu|m) z;}RLMwO$OqRXFSst;RWK4(^}7B^!QN8i{98P;^H zz&eV>vRJH_cQTUg(DN^0u`R)P$zg3LdQnHSKv|&xt4+rulbq;fQ8EL6ZiIJ%vRA@x zg&gzo2Kr84Z8*Y<@r!8fMDt%5L7vU_!^;F8#nEIGvc@?sfhP?-ZzWVEt-lLLku4a| zrk;t|MI@l_`M=AIz3#d1iWOl#X3`+Nfvq;IWq(2GbBOJz18Jv^0*n=H+%K`bEiR0L zVdO9K>ZNqb0^$ySV8UD|^iHC!?O3t2_Y5 pn~9l z Upu0m}soTy5a ze%gxWKnelsuf7wb@(CB>u
pL3xjAbY!N#Qvf|+jP*= zQgEiY;SEHQI$vL{q#I5LB7?_o>u2V)z=eihnJ#Bk_N4s4H{ z;t{+QhRY7rgr)@nJn9SO#p4w&eW~A=Bos}?B5K*3dt9b=y@iul^`XSG UtrdtHL}$rQJ?eX$lbK%{f!fZlmu9m_J|o z@$KG^ub2iqNFJ29ZsAoo%DQ >BbZZtgAS!7zd-x1Mk5kRI)=8iCm;GTFm=V zTw w*k)?=?q z=1pS2kVJD&ZCz{fezQCA0t=Gq!%yF@pPh42c*yOXIY`UPYPb3yP*6R >has!;#6aIQJ)`E*#BgKO!VA6M`A~ z5w8!=kl^+tU%}}{oA7yIfF8+EQC$;V Q{|56)UlI^8;^`8*yPlBtcX;gr?js q4<<~AI>h+xAW5sF zR?BE>_~dtF52|s`&fjbjzYC- z6u_vk>c3`)cz2jBw!4tPC1K1h91OY{F^~r(WA~uNu%#O>zsRq86%KWi_TFQg7HIs@ z?XMZ4rMu?Z89@0rI0pL)oEd<9S#_TfD)?#b}*K`E$i_4OC)XzYQjlsdEY`1Ee|v zU+ 4 zqi@EcnC*p})YK|!L^IL?B<;5NC@0N&+Q&w)_uPilYJ~ZtHl3M5fg!AX=wZHI&Ap Tw7+c>sXmQe+@ zpEh#V^Q9R-wW`|s|DSB-&VRMyfS$M9h!m3WizH7uE-Eh #y2U7+Id$zBWf0p{s?v-!C-TO0I*~5hFk^nB{MbTqu zQwA9Uc7XlO<_0sveKF F~_Yj3e+y%x@Z$&=RX*(T9@SwP& zr;aO1c710D5LhpA=`pTC@44YuJ(*PMXKs~GLvT &Z!)V$W3-oNRrTxHYJFnJxm?X#{&1DiOD@Gg7Iqms-ystAXK! A&Cs+TJe5nlWrC)Ni`Gg#5wgfeOD%F}#{>v-&{gIhi5Ky}ex^$t0!O+78BAPSfw$ zMwuQ%RHUm%|KCMG;G{^@D7aEQqmtNMK+p+aeCBCj&`^jW`CG#Ehfb!|(Pg}6l;B@D zDVWOx>=P!4yCR6|Q}R)dCfv4GB #&4)AKppPPI?9sANSQ8;&fNbUfbUu0;e6JXaCIAW%)<%L44xfZDP9;->Yu z6TECo{}AR9qC5gVqpRXF!~!JQd%$;pQQbt4$@lh@Nz964G$s;k*Um?_91=r0VmqRL z{EOFKS^t#eKz?`uj`Wo>n$H64mQz%>-d0FzT8~Nso`EucJ`iZ_#@6c7o7Y}nD}sSB z&?gb1 %kVqKlZ|H!$I`F^+#%p|vF)MR&|i&K)tb)(i#PS((^3|B z^Z|WD*{DT_nn9kFu6->JPH89l(({b@i555^ye>DL zmhxu(t@F7{AF*PFv!dm9t=s2GpWn$eH zp`AlArgV)G?K{E;gbIR{MqMp!W-EG})0RKN0W9-UlASy_jqF`n=E$p <+B-OSojw>**1s_e>U~!`zW~Lbu zC}!>$iPt!=cQaM7<8-}5F=-)bpRb) H>I2?`DogS}qI^9*sqsfm~3(Otug2dLdX{j`A9nA>PI{%15Q z#>)DMsEI`L>dnO8=;VD(qH}Df+8Wn1>h!dVRa#3d8>xCkC7uBBZ&`Xgfn)g_(64mD zZ1Zzei2hg2`!^Ezl!1r=Lb7}L$m~g;bdmBmPsrsmyr&% MndR|AK-958u zQ)sc8vIr1$oDe#4rM)S2`AG{1*>jJFgM`?pLQU3Wjg$;)vJ46k?c}azqpqF5`w;QV z_(dyJTi4vQ 8z~66}dVnIFgGcTJ1WhGo@{0l*C~wj$PZA z0sQKk_!!`d_7{kr023FIOhS4%_UR17j6;NAWr?!`p*Rn{#| W@5pI#spo BB*9|M z`Y@Se@^l3kXw)+4m-&(eGjFWh(GuchhR{ a(n#@4YvFN}*w>2!|b3C}+v$Feu`2 z)TtNb
e0>E>91a6{oOT6o@zN+u(QRB6xlSpX3B+)r?MqwBx;? zS7{d9U;RI${rB|BK4^~hMa}e#dNzZF;}a!O$EwKiOH}`M^$Z%9ziOliu*5O|00000 z00001O$b0Hdea8I*H^8B=nZmEmDJDDD^VcB)i&P<&x9&qAR-|&5fm42;i3OdSGm4{ z>Ar&i%8m`u8Qvut1<|ao;h5-T0}8=T*r69Iq1n#C7J&ALq1yoR^@;bl-+zQaCXMBI z-GOZ+H0-{pH|YA@IQ eQb z`=933CP2Jx3=i8 &SPeOTo zTgAkd^zFx2QNZz#ftAA}Eo3wbZCMB=q_)5D^%49@j4~#BklK|L=zrAIz($aki%?Te z6eJ-!5KCx+I!xe}2mGrG>4`19@#TOnfJgFQyOIOBB5?Vead9(GJe`0u9T{4q)slUW9LH+=+KQvLj*X? z`~Dkq25VP4dSMAPO(R=62bz;snU!%*>ncF&C&NkovR^77 s3WTGCI7pMc2=7(Y2fj2d~H^T^rzKjFp{$D!*o zfOxlXMR2@3KW%iwvswvAD*{HbSivmTYc{ OL>%c%2y{)$}_CQ-DoFt3Ha&oWhiZ3t3^cKDgr zdtf02O7PDsR|U=9?#~+j_xOp7AZnZroN1v3nN3PVNW?W9>N*)CSuX#inw3V*bl`IV zz6!q=4yop|a0_$bgcpQgZSP}uoDcwEN2;Js<65cwN(%sm1DIfqgPTI24{OPR1P6}B z7PIIagmK5xL8J9P0YUsoh7Pb1lTdWnC&tKJa0YW*lR1edRIIoFS3QqSkYBh-K;N%{ z1#e6zh@>2LtKo@291ma!8xiCZRK;i2st>0((S6q9{l2}EFDP~jU6cCD){L=4sRj68 z4S&lk_;E_-?FqtB#pEU%94waNK!@_26;UX0eVV)oOH&i(x)gWe=tfxNJ*IRgjRN05 zwE_}rbeP#PU;WPN-HLa}bboPUG;Rt}wc&DF+rqbjy?7We$&qbjlM9csY(VL({6voF z`{a3M{4;cl>cv7$Bv41O8S@!R&`Ow=kSql8Eh=$iJyq+AafvYN23y(9qe;ikWDiR4 zBs8c=Q|YP7vvaO)Z9b;_V1w?l{;nba?4K*kWNA?M!1d`*oN>C`bj)EJ!ctBcd%$6; z^rv981Al4O!I12dkrl^I{7ZWPOcCa5D;DyXHHuN;X0(aA-77&Oe4Pw3{IcY4TEq@6T0XOx16W=if`NU z{lCm0mC0XolqW`xTi5g8_Oyf6*cS!Dq)$3lnLM@|uO7iegu^HkC1=zO*jMMTgA}Yo zV=iae1ov>ox+gK%7 tsftKb|YAmOW47Z&JLu7dGEPi>H>VG!bP7QBTV5tLQ$}p*D@r$!FUO|5U|sg z{P=i?v@~y|VI2>~DqtABSR!Ml>+qO0!mU;MJ9hxeY=yv5#H+7mkwn00zGCW}0CYGW zVjpF+$ a+3b@5Jb?jj#SI2jbH7TBi9!qG4U~