From 3813dffdddbb9bc9ab13e7ac6d8448dc6fbf8056 Mon Sep 17 00:00:00 2001 From: Mathieu Lecarme Date: Fri, 12 Sep 2025 11:40:33 +0200 Subject: [PATCH] The web page is more cute Using 'embed' and files. --- .../server/assets/HelveticaNeue-Light.woff2 | Bin 0 -> 15256 bytes internal/server/assets/logo.png | Bin 0 -> 13012 bytes internal/server/assets/logo.svg | 1 + internal/server/assets/style.css | 174 +++++++++++++++ internal/server/internal.go | 43 +++- internal/server/internal_test.go | 4 +- internal/server/page.go | 208 +----------------- internal/server/proxy.go | 19 +- internal/server/templates/default.html | 73 ++++++ 9 files changed, 303 insertions(+), 219 deletions(-) create mode 100644 internal/server/assets/HelveticaNeue-Light.woff2 create mode 100644 internal/server/assets/logo.png create mode 100644 internal/server/assets/logo.svg create mode 100644 internal/server/assets/style.css create mode 100644 internal/server/templates/default.html diff --git a/internal/server/assets/HelveticaNeue-Light.woff2 b/internal/server/assets/HelveticaNeue-Light.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..91b9d69384244543e10283f65c8699170b7a6807 GIT binary patch literal 15256 zcmV;JJ7>gqPew8T0RR9106Ulf4*&oF0G(t206RVa0RR9100000000000000000000 z00006Ruwh?ihKwd2^0!}@&JL?KnsT?01}-@0X7081BW04AO(p42ZM49F&lV`2V`DL z+`)MOfz<8wpN)uM;{b_)j(B#UhK&Qn#x589|9?)PGKTAfbO?z4LI5Twh{8(LxQC9( zDw!J@6^Jq6AdIzW^7OO&KkvOuOG#Xy6oAmdS+bR8(ENN+r~?wRFJ8~w*X#Psi*%BD zb@JQk-+G$5-Ko^M>249&xQvmkoV@-M-shh?GeM;Vr|=qi%`1!)%I+)WPo^?6z;CmM zr>@)0#sE>_fjr{D$cqSSfes_xoa+}-E-k9*p=_kD@- zD8Rghf}PnQkU;uC%ba2o1rJtu511vk14&4yvQpDl3>(2YWAER;gL-y2=dhB#X^1mQ z@_zQ0DS1BE!F-9S9S+XPhfAZFv8Hxx;%PAs|4_PM8*4Ywd)c)jP|dc@Jc|c``0_E>~uQR8_#^meW!z zbYkz2eCR%;OZts9sdJgf1j)bD)qmZVtTsxrWsnI5cwrv?sapR748rL0;B2+ZE9eiH zjZ!KqLqzunFt#KcNYeKEm6~M=aIoa^E%}l0nKMCjIJ|RsnIKkZ955Jg05q_M=Cn&E zYL9=0JWHM-?^b#YfYrmCAt~o9m;5t)Ia0=&&^S-={(bhPRH2VfX=Y8NAWReq`j;EG z+ncvgI=)Y%OuHeqF0#YmWK^^%a}21wvPJycl;2P9k8lE%XUhn ztpr=gE@OwOb{XLR-91-8Y22k$5^X!yqJV;^-aPmq_#xK*(=38$PKlaVW6$|7L@m?Y zL#_j5LfHgeXpD%Al;75Ww=wPP+;+ka7&J)me;FGk7Zq0E&=qx`V1S(usp2wSTE841 z+L755F!oEQ0%qye>0fc-X!aF2BgYQwzMGA}7VyjX{~HN&O?z}O2(U)qH~3Xdrvuojca+nk$8OjbwjPzTM6`jbdUZYGZ#4XNDXAJv2^WYa2mzSWhLz5eoqkkYFJ~g~16IAyO1P zf@nlBV#OgL15r@Lqk+&dFtNbcI1qhMJbVH|B4QF!GI9z^Dry>9I(i8Vj1nbDmLgS} zbQ$=WS0|awp0m2a&SDDAb9w5S*WSS3lpp=%6%2CxY`_l&l^F5_2E$=s%6HQ}y>7Y9 z0fVCoVQ>PcyFHIi>AI|wBVVQ##gx5vz$N~kyV6~D5Lxc*98YduQE6FurMIeDtm`m)qkdef~f&6pqH?iDW91&E*TlQn^yC)f>%LyVL6rhNJOh)zy9N`hyoQ z-zGK;1p5V$B!Dmcc>SMZQzeblF@rNQbNhb}_Uiodj4UpLWSA@^%gGp-CcPy~l`2{) z@C9>SfYh1Tc+w~x_W2R~f9S$7i^%{PB4u4FqC2syPDt{1cG?lUvp4(s@Ax0kU&s6> zPLAY6?WwWWZKU6>yr#t@AdDd5URCa%ve(ibdjyp3(ZL((-qZ)M z0G)uL!Z3vfz!d)ghU);`fH9tqWMG{lhvM->mT4lulbV8v3EZ!fVyBcy96L0nl0p13 zB7o4I0-`-eGS|b=aV}nw_hG=(?mEmc{ID@JGv>TU&Q$pqALD#(f%K#aP&B}}TJ+PEr1AR_=_i1LYl!DJ>v-4G4>ie^ch&7mA(BN{~`VgPJs zor$$O(z9tB$RMU|v(mA4KK5GC*wO_SO%EA;N9+T91LUa8LhvMoB&H{nt`-XcngE@i zkT8bfn=?f^S86nVzyFFM z{7fmCfHGGK{TLO)7=BOXo{ZIFBh}ZImFa?aV9%O#u6z>)caiuY>=?KK2k!@J==yK3 zr$!6=&^<7aAq91`v{hM!p}fjnSqE0sMqY7Qb>Urx&<-lH2aN8E*tpraRi@IO!4yOE z3L9Rf2kur?RzeMEt7(3`@SDW;k*guGwYupOHt^25VuIrvC*i#V!W`qrJmKV|w6yfF z0E$m1aYoWP9ViqC6zVBWS)~s9AT^u~4t>?P4aC7(^!`8_x6k+ZpiVs47zbfb}L#q3i+=KhT$ezO6?>~US>YGh7(6Y%i zaPt2UrvIvpY3>S@yA--F_2q1v8HTjdWVh6j$~9r|5B2%zJ}|IC{kJ!X zZzV%nYksLvRbAfFVN{yPg3IUQ-jzuxW&E|o=z`qksWG?c9>x?rC9fGE94qU}_hw@h zr`WeVc7GCgb3tXV4#}cLS{C6z-BXmkG1#XN!tmV^3N<&8Msbn3+^_@U9lA~^#2oTE zADp}OMYN{c5`MPaD-{_jvdh$hj%nLNokmHgMCRJn_Q($nQb>3pfpI{^PHhQgNaHL6 z*vLqIP*!O|Hz>u2^zY!nAD*n@jSbw4#d3Ge*NQ}34eOXA7#BAg+C^Ika+S1O@s5I~ z9Ch-^*yCw_eF-)WYSxa&bYrK;>E-IPUh9@GVG8F~lc};mgvh_~9&asf7#s zFTrPgLeX}@h&$e834st&Fy)x;76b@$OVe<}Eit`H?1$G_aLM_-c8%Sgay_4MEoZiY zSq5isKAi3iQ|NA4#>y_O6Ktno8yDEx9I_3{?eI`zO<#^hq^?Mrl!|BTHch`44bZb> z5>xMAy~PZ9fr%i4N(n*!=o(ybhO4gD)pBAUh|)htK;m}z@Moa*$3q%^)42}#6SQie zRQUF2J2!c{x-W-iI3Tk_572|?yMxVYWXovSMtK+Xjk_`OGtCTrC1_xwTu4{eC&&jm zR8oM&H9V=UU)`>58dQ%ZIdDV6r`lS|5%%)@VmL6o$vSM?o3s>MI#aZhLtmif>zZ+OvF2vq1X^B}iE4}!; zXpnJOtUw@?I{EN3LCsSNO5202+!l_~{DDl1#%FTgP$^|Fhn217{a3UT+GNAUVKuWH z2|g<=@#n&4GFWb)I?hmeq)TZ7EAYXe+XpW9N2`7CV&Ar>|H8mc?T~%GkC80S4U^ux zn1Tai?l9^h;iQ?x{<4*pB%f+xQjOudGIU)8sZej4H>)&Wn%%vXb?>dqHa1zlW1)%$ zU#~W3P{wh@qh=f!L2Y?xmw5dl9vnFSk<9U8qG83ed7ror!{!g_Z2=2PNGJGk_xG{$ zn>r?gvMH%vNW`~KsMJWEsf%rkEmRW-rXCWs>=;NOP1zOpJ1ta{sL#%dHn-gOz&q4U zHL`Z5P6`|g>JC<1lQbmJJHc|VT{_?5uEs4Z3_b9fS%Tnj^l6~LbJh9wP3@bjARVXe4 zqx)lm`mit$(kMo~yGZxl;ua56;?Cv;SVrU4=*{v1OVi#|_S#k(TTIU{qD(Ifb&6}ud5tlA1B!ziUh3()qA9owdDowaHF7LaccMGuh_`fE{ zSp*|~85=9tmWenjbL~@6kAI!XJV<<#E()AYg)k$Enl(ihl|Ym3zGMoXssI0zVFpn? zdU5Ih^vI5W-0io%`40QhDC4`4y6f#W_a?&zYRl2qsq~-acTP?JMETvSxKCK94?NGZ ze5UE`v~ZM&kUXR0g%}RnVr{MBfQ|L8ZBt_8Mq}LdYuHi4J)nNucCuLvPAav^5zK`3 zo_nrxsywZ5wK!%#GLs$ib`;0I6#i9?rC|K0Kl1U=)Aq>zGb-a=DT3l>e&WvQQAGpS_kdQQysI5u%p?eqI&tL&}Qm_?HCz#ZTO8?c26 z*H@(sGyFlxl(1`+qd)>?(tK|aA1(`#yWh5d{D<5{1#eNMI!w_LvaGlv!M<`&iwYJ?alM~q-w|U z0{9OoBG^pHH!kF;PfJ*LzyIz$jM=|#PPlc!u|Lub13QdzoIJ7jH*6Y=5vhzwK-PjF zVO(m8fXXJNV01Wr{*2fsTLtYhz2{OinnPuHE{p|VA!RO1jcxJjo>m+hZ4n*1Ro?jX% z07pMVpIN80Sz5S-LV8PL%x^kHBA+@%CY>aY*c~JG-wRp#L_($y#+{Zxx(H8_|4D=H zV3QeqR~l?ZN#BZ*wHH+^ke64*zyGg`MK8YIItg8m*bt z1(9(Fhg1k~POJH?2N(Zk4!+e~5%v?M+MD_VDr6j{d%H*Izxf zy0GnF0@Duxm>dp6pkZdSb-ZM&WVvP$d*#FxJ<^3eBK;6gH}t8Y#l6_DR`37e;)XRB z3Hsh>%Pur3g@z_N0d$fom46HKu=W2R!pc+6A>9wYPS*} zn~|{1;8t$CT@%&|O)Ona4NEV~sNW5h3456tdCv6o_$p?6ifaq`Akv8ig3}vBX&0{8 zJ3=gTGmvt&x!3!Jx{2mz;3gQiQ#Nw2nWM`9uVn2IS9}w9Z#IxhhQ3 z?`!Zghpph~0cP9c|2PH-%$18J*})%BpDVwi862D-Wqt6Lux()t!ZEg}!6T$97#KSW z$T|#4`4h8P?Obvw@+_f+x1Oun(I}>?*lE745C!_whPLV1m>%@}>jw{CpGSL2V7^e< z+!Q}TVSxsi)FXQ91zvlFlX}G0ul0yJJ-{K4?yv{g?m@eIgeSeg32*91&R<4lPvreb z>2MO(tsaDaf@I2>VT*RCJ~-YxREu|ni9=c;fYGmc2I2jcGW~kF^P5i46m3d2&Y;A+ zB{6&kpR8i}4SL;8zW*;yyU`#Rt{90MVVNa$O>%n^z2Sx$@ZPZ5e|%_+CZO@r61!p6 z8`wvGP?&Og_nffMDI37x1%!`X1c1Wt!Cg)JJ&{*_!xu{M0WJBUl60S7 zE;?{36;7%vYdRkSr&hbAo+D2Eym{u3;}!L7=09Zq}YII1m6$5aV|h%N17X@IAk26a>H{pm2URVjy zVu9jlIBTJ1&ia6C84|HUeSP{Vn5jlMVBzOjo@bGAHgD#X8m8zCTdoREO53`n-2pn1 zgDSTF6%b6B%$WjXk&Hwzu|03d#}`Tg^3vj=Q{4(!*|J8uRj8kt-gDiD-`Ndp?dDwd z5g*>=!>v09RGt&G1~4$V;|8Ah*w2}drDAVPJv**g-__csI5&24nzD0n%b@Ze_70Z2 z@647n@Kg07?#4ZBAt%B5Ibpm%J}Zw5Xgf2(BP3-z1g^1%T#kz*AkfBbLQuGE6GT8F z>O8M@#yjvG8#nmN8~r*6JjsYlP+*-&`29eCqwQ>~b&G4QtxE)Vynv(ny>vWLrQ>AwAWdFncpD zq9A-Z8~1;dAtRYaLj&8@Jz!3P3A(&y*~(QEPJ$lf7VC<+R6_!XV%6!Wh|w68K5ZR) z0SL8t$+*(#mdS9GjgsFoL_*dBm+-plri=H`|?g&Dt4r!MWr< z{i$AgRiGG7V<@5(1Wgc2L}ZGeFXeEAzFb{qvKF4p5s-;}I6TV$s-xA> z29}06_UO%Fo`Gc4f*v*V43#k)%jSWB&5N5z>u5Z2w(-?0ene{K(?Pl3s#2}%mV>OQ z7SbfXRUtALR9P&ioLMw`w`RW_B?9q%=`(Zhgf2d|I0hvhT21tGHCCGsE?hiO9fVBq zKV0q)gtOH}>CDFSsgrq&xdvTjMp1nQ>s;Wb7<9xoWcIl-{+&JWJvcs9+B#_ES#^+S z|FQlGcz9P+vBf#3>N2b3vH{PBzpaWDTQlmYu?&rdalMK&YA6bSN5{X7kNR0U8#xJ{ zd=5UZf6kwQ7z7*#EBN6=EMKCMA_|zP$m3aU&vt*rMNcm4=tm;VnfTC)QE}_NDUCz8 z;NZ?z!J*xqkujoa#F;V~$}GJ^l}cv;rxrxAl6X8xg3K2suDC#sMq?tve<4;s9;|C3 z*_-IIj($og&XK1UShC)f!~7DyAu0IWi&;8OB=u!f+{vf@1TZQrDtP=qLy!RX+%cmi zd=HT#@63^C(nKOnnnZPfvsk?Oz9fwLv5!pd`$%1P$letJxE{B@f)!eE?l9H|PK|C` z?-2TZF(@!U#Pu!3FGF#tImXoac<11EAK$gvF#$QPSG55DS2_01Kx>}kw|6dsG0rXb z>fQr)ROeG)AB<*6K6K`VPyTaRVGkC{0Sr(z^>smbQ_@U+d1tXxg8Pi)IBio^Zn1eo z-FX*G%VMg}plxmI#9s-&2GykdPT-6;o-8C(`hC7|5xv;xH^zlhj`F$Wrt^GxVQtsr zeIjk1w#o{0lvmJRewD406c!o<$ukSON z3)4-kWNmVrI{wB7F~IX{)Cip)c4q?HOjOcRF6LCADr4iXcJNRwMarkHUO%V2JCr9r z^;Rgov4gRX?abltbL;)|U*AfoSXK?s)#s-gTcT@#18B%q$)Xh~u8V#^ZJv<@kffg& zMezorXyt*k@U*0ChZgen%~NPHipW*fj6GS$e?D@m=5{Cj+K_1c<`0AZ%Z+qx;~%eE zGO2+ZubtdcGYp!# zI3HNBa8^az;I#U(Sz%w4iVHISLsB2s3_qhu1jSARFU7&)9?>ums?gnjAF#PO-0m!` zt0^yd_3ag~FU%IieuFyx-V^`#?mye0zAviycpGC43{OQU)V8<^*7iF##2=MLb3gxN zpm{|pJbgn$RCsl~xh(kt?tg#>@+|ol8saaqgi^js%{JIOI#Bps@m&pDME?=mG-=2f zDPEePH$<^D-#i=iQsH^JqErJi8~F_gsoj`ISCNH9`BX7e?Pa_e&$$iga8 zxX6rMVDk>gx>%h}@ z;(y0q9?7|^c$vTLy=C=ZRwVff8)o3g6Y&A|+c<=pu7>#!yD;VSqRNovNrdSj3CDjI zy4(;oD^xjKLNYf={hPKSL6x(T|Kbt5YdvG<@aPCM!eUjAy@TGdjt^*^L`}(osN%oe z_d;pvc`S0s#Dsz9w{P9huA!b&=-9b+^vree`Vc&H1H7StGe@J(rZX0=!`BZ?C(2~Q zMuAyuvx&v_hO^(X%}#ls&C*I8%ht5&+`L+@dDFgqo8%}P@#z&6>0@H?FYaZ`Uk8^X zSD!1eBe=p*qLsT#{;NZuMgVgf2^>PBPd3=0tCv;FbRD1zSm#{c?i8rQ%?HOp)|#SL zTU0v0KhD2CTYm9LhR8ljP8#~UC3ZWW;`evS;&$<=GAiAC&oJc77s#?Q7LACE=+G<~*hCsr8=^n>1}J%DXHK5i>V z5!$qEC)RT5tHONP+j|GAs?0cA_2kd<&bA#NkA-2Hj@q^pi{xor8w*MgZo%qVHv)`W zRVg#(=#r$ouH{|Y{r}N+=)$F{kdAl&2h zUalv>z0Kj#TAE-SHe`kg+Q0gD@)s(NLvZ-s{wN$}$AI0lSW@i*ZNPsMqYwA9h|11pyIDpjsRRwjuMK03E4lQ z(aaXk{iWM24&f(e?D64+0Q&8%{5B$8Hy)TKG=i#VoGH+V5eE^f-J_EO0AHdr3jauT z-LKS$LZUC`7QFZ%ihZG)=Z^sNnrFw|hPp*ck>nrg?jI>FjZnIYCg|5NTdK-Ug;826 zX(cHoHD4)D8f1h31t2-lm{wjQrNZTA5K)Y|HcHzPrP1%@^YT7!s_omREKI88)@nFW zSql3@nHlYNZCy!sdy7KrEu#cq8>h%qmbjh~wMciVaIr>7D!cX+`U1H$h{hM>4os1WxmCw{aL9Sc@kZoS{x2utd@9$f-zb^CW;}^RjfivZd_6yy;g=QOC z5%LQUxm%#8tg7}wqtg;!c=#(*ct`Jp0dl{(1!0fG;4phc45pLF5&w=rjOI0lfG5jH z0%$28LDyvrQ8<#rSp!q$pw=LbB+NA#)qd4pq_nbngC}OlZ5!c9xttLY_5{gm-0hcZkA?LnnBChLZf=L$#(NJ(AQ!ZG#4#^_ADUYkY zBaJ0VMmI7CD_XC$9DIA*(o<6->~}JLqGl#@B9Fpe_9~v=->`QPkYGO?sn#wb%I*lx zZ311(06(EO2nNkYuG~fjLFZFzZL^nXbA)xJ0rQa(LXzZS?%{AE45e9CvF|t8nm8g- zt>OnF3W*FZ4XUMxx~eGT9`3FE`mGG`iuyNJ#`Agedt2&K(V?f%^w|-nqdTE;eg-LC z+N4luLWZQZgWkwiIHNOgsWx6|RtQ(wOz)Y6+oseY1hzT%bepb;uh%w~%bb~?wA1un z5}$e-f2Qnq@|u&|AikvofDe1!?B{`B;#}${*#Ix}nq>)3r_Ec+LdiQtYv^Sm{$3vr zIJ)?vRY(Ph@d8GdvJOt@1R`qza223so@MxCmvEAN3xS3*dXU7qtYVUEKMa+J*fDBf z(;#<_gCbJ*Kn=%wSZWm|DEZ0}lCB;^I1OaQ`@iwHU{Q1>b3lj)+qALM%EAo@$wH#l}`sODRI zpRq|mMpNc2LkwsDk40TA)4y)@_uVRYGJK`WJC&UNlhBXvzwYqsdAy4w;?Wjg()}I@04SN;~#m?i+zX&3c+I0b1X}A zOfR-X2CBLt{^nHz9JQKvgxsI<(y%3St^bnR)U`{b+89{Pihgwn33z&FvsPr7IQWp` z_y{K#^~D8Kf)$RN@1Hc?fJ9JlOHq4iqvz2_HXmqjC1_}BWIj@qg2AJ>uI9edYS^^v zv*FCD4u?4wHD4J&NGVrfwB%I425p6+y|t=Y+yd(gPT7~G;eNl_U{cOLUze00m}}A} zWG44@9WR{$(;>BEu<~q-PwcNi590n;~JL^x%&jq#MeaaQ`uaX5nt{^`&VXgP%60DDp}k zzP9*U94sy(ZA6KLYaFz93XXrpF{+s9@EUZKlMUy}6Cn z*D8TFtOA0#py2_OUD`cOt0&5%Nlyl9XQ$Y<7EgrJt{`KcZ`h4SwOB}o{h-rJ^T z6(M5XEZ9}n!F)=S1~2G18QbJ^x=e(r0O76QBXgc1WRxSJqv#W&!+wuC5&{z{a>aJ8 zFJmQD7?fSk;E;IyoU=l_DScAa8&T(OiS1d{wbReLTxT5oh$d;I~-; z)8lHeZ?i>U{UY#JPMIna(GVTs5P*twk#tD&YFKorYGF^~pyJ>DWO1{pK(>$SVhQKS z68t4o9<%dDL|dCQn(WudLcDV#R&W8flKPWt*QHIggDPLQV43Z>qWJX}0TRmU|8(CS$XXq-@k=cw6@!f%AyisdZE7- zxsp-iYzpP(j~2%)#!CXb2tH>{cPD82E9DtQu4nKn!8r@T&R0uDU?9>tR?SnIBzls?>fx6R{T>5xBzLh0%w{asVM9C>euB=CgouF;02C%sDklhehw#N5T0lHQLX3c zWfpBH9w_7GvIUY#PnA;!#HDASK0Ty|O<~>}!ETakWEou01$;0qbHdv!3atP^-+f_N zx|qHK?)RX0mphEknyeO~Tm4#I#xz|Ul7903L8noe^%#CfEaK+yFdc^Cii%0y$%uyl^ zrz3|0`m2)IBtE}YwOFg}M!JH=db|A)xFmmDxH!+74pU{d@`S$2n4z$Tc|FR?PM{VU z7b`O=Y9Y`0;Me15V`uTdXx{oW;FC_&lGc1%UFn&?R#{1Yls%)3K&kN*dZz$##yq_U zClLrovk@UD1y_ygQ_PghA{bk2-fTM5TQWsf!`0h(`g_uC8e)5zS<`J9`*JmlXgd^6pkwkLX`=e2}` z_xl#xaj;4jK3YlpjiEDwr_Y*%&ogUS;s%a|P&nGxhnrC_W!VaMbe}>;xvmQF=qA;f z_O$_Vs?|_oHvy}kK&=)l&dn}l9wd@B_HtvqqVgm;<`xJzCgGK&bFCWS?Y2d1p6Y5E zuWQ9DHMOE;K_IH8aid0j&j+kzK5pm+5QBl3$2axF{ANK2 zDk1kx`=KjHFiSW0^xvHOWcA!ZtXa5EX7@zqx~IfN?UTUj*-b>G!rrU%P*#uW~&3-jJtGF@O#i@IMv>s+sy;Y6AO%ai{-VDSl#`U&nxUIU7ldnC@ zK@Q7uqau&_UF0xzGyw!aaz+WbA;pBS#0Y*C$WNb0d z6PbQRpyhxVy&f>#n2*i)%nwH-&5Pt|Bl->tW}F(1Wy>K z={QZBhFT$Xa>#dK918L&44pf6+y%D6uIhmL07m9xLK9?ytmJ4VXKs7h=E)SN#ASX? zBi7cAgrRozmU+f?l})9fe1SU9Yk@+b+XoHubK#|2 zgoNv4$;5)!xnKlO_>5k1R*(*qjhi^o;%5|LU2l9^S=C_*$+i>ub3Mk6J(RSxbX(*o z{-O_O$Ylw76c_^2D>4f;ssVHFuW>X>P-xIguR#kBhAfK05w?F*ibLze&}SyVJE8+k zyLu-&tT7b}J@OUftc`pGMB;?&L4$jX_Vw=viz0$_;zg`RFU%Qn=B4&TGQ0G3{9wD9 z*-fi;sjQ5fzPMfpkV z^RS=BF!=a`UWOhu(4T?-Nci`5d8+#LpUeH*74gRffeLr}N!D$KcC32MvGY>)fd8WZ zf{D)!lwHDV7qPD^<+ADKMtwZjMLhEu_?j~qCW=mbBSDR{`#iCy>quF(xn0kv;;dCn zq8o}_pF-eY$*E5@P`ACR6AqQplf(s=&mxCyyNSRGQ3Q106+@675s5mTc&Bijp5={S zj8oL27K~MqGUEg&eH4atYKY4@z6ue~Xo+<3FsnnduV&)musQ8+&CIV)#PTrQjkwn^ zhMkBG=n#dm%^1)9uJqcrQD9*XD+^ks<@KZYd#yJHEXLrBhD0d};!WK|GAx53;Mp@C zO-p(sBG=`9q+t7!q8~qp$MJC0yMdP)&vlnk>m}Sh zSlF<8*V#6p`1%SuL2Dp!Fl7JP`s`8@%lA zE$YKyx7v2vK5SsypP?#EvlD^OmD6=@@Re(`DDlOn0vyeuxfeuZkMHEZv{S(cb~Jh| z%2F9tte$|vB>JjkNs{ITj(H6BuqZYHfy?n$Z?U%l5EW>(UH6U0gpiGoRc(wR}G6jf?ASC*A0h~`m zwZ=iwn@07OYx(e292+iU0bLQB*ATpqAR*^(lfr>Jo%i%1si#go)dkJ#F>9mIV>q(a zwI0=pd_(&pk2o7mj^p1{lCCiU-6WoAI^aFkfiK$%=y9F2Jy5|8zMqn>a>93I=4G^_ z$0Hp|0`WyEB~ib=N)IFfS~fWoS>$OFiEz8iJ?d>=N&;&I3etRaeq*%qD+RfY9^ru@ z;v7UyQ~I^H3`t;YlL9w~w-hS|hb)o~S*Ev;nHmI}qz_BV${n9@bY+vo{I~w(EiWxH zGox>_HfHCN05D{t6VE0UGo+eGzA1tdvn*!}cYL*Dl2hB`PN@}K+$RZ$vQr%Qi@3f4z1mN)cz3y6pk3#LQ zwJ>`Crsfk{6HAs+G6LH;Uh-aO)f3G7lxk#|)&BwtZYtCCRQ>XD0h}f+S^zabOxmt0>UA@jFjm-s!p!HhIftAI{;rH%amn zilX2xIAB=>tqU8q)JL~UAQ0c2XwlX} z!r`N>!LsUxN@#^y6E`%znsjh>6G#b0s*wz92WpK70M#7cqCK0=I@2VKxTOz>`%T2(^ z5(qL2%*vNjfMF59glO+ktRtQQ!+bujhr@k1d5Qi>-m*33EV0K>8CzNOuIfm4Ev*#W zZi*G5fy);Jp^c<1$BL9?-PBp7vu$(jDRl8e4(l$_ak7@Lg(k+*!B%HSSX}drWPI;2 zV$j43f8XoG`=znHsw=Gi>~s1&MK6dLJds?LEIlhZVBZP`i9E;5y1~d^fX)%e3rr6` zB_LU%2$f{FJ3(MB#8KIPPeN)TKCaJijP@}ja9Q)J;nI_l-jn9Da`qSr?swNp?$uem z*Unw3m9O#|m6MGM^FuPoc2g3YdSmvP!;p{|k*~=)TedmlZgQx1JpnJv?cv<3F_YFq z)ErSNKT8l2`fp4MD)PGu5$oGeU8Y9=5Y`vKHoMT zd?8q~gX*IiEt9?+esaHb2TvRBre6^Z*H^;Z0*^sUoH;eaa1`zONWFjg~*wFi)!|`^G>v_wuE;-2tV)CqWBh4duUE0OhNl zVwU=T&X{iC?g`X~2r4O^l1$bF^gX*!7APZ)%R5#Js1sgLWBE+ascyzxhjW+k{&nKA@`ZJe5UfUO-0W@FC`nBk3rd56plDLMsQO>5$%joEVgOLLeU9q>(f^*+ zBgn+t@`&#KG{-r8`q};BH>>sJ;N<3#ZJT~_dF0(MP(Ze5{Gh}vb+ghN&sqasfAAB( zJMTPy0z)e>fx^7t>%XKPw;K=&10|Ds28k06?y{ z)zWj*Q&tkTfH<<7SwhUg>@Y_toDKk@;xMS0g+17f+8k_c>m){d)Ye5yZEGn;tIMa% zsSK3@+t|u^yMQ&lRkSR;?Ja~XX~kb+io%590FGccGisQlgOjT;OpNv~Tw(a{kGDB! zssCbevlpY)Q&yvvg1CUG`Pli`IcZ;FQj5A+S_x}N%l>0A{7Q`0#?1{X%)#O5>B;WN z!wzw==HL<%65`xXc z|03d_P@0gX9|KIG6j{jvFu5L0Oa6|mVsQ*Q${}AA+c2cKY-aIjBr#g33&hgh0&Mx0 za5&uGR9A?Vo2QuzSi&0aWHDL^D=S;LIlZXW9;26~bA_i37>@Qo?~UUh znsGd4-G8Vi%JFZ?i~hy%pVAb*?(ci>^oQ3Gj(-*s_{Bd<5!eY{e_Y^&CfW7(EC3(` z*}~oOZ|(p9P@;R{1dFsf!JxR+?Z}b2nS?T#4i8!WXIWAV24#z6lMI20TeZ{mv!!>I zWe&xlniKmTlWrY-7MTJ|CVTU~Kbh7=R!Krlt`l4SM`Fc36DY3*ONwF@o)=6NQIS1u z{`HI4kdH%a8rf~C_)|DPKDLVb?8~E)>h+4bO6~iFE}bq4KCMrm2&r`V774O(v)}OW zWq5|pe+#W;^?kAbbu+lvvyp1}v~27|G+@3wLMng72G65kQBb4TPEr+Pbkw;xsmyNm z<+&nR-p&uwD5Et>l28)Hz!>=f5^&(la7(K0n2*N#>MxcrGQ^c-U9{8qX20sws31N| z_*AAL#?1A?TT3>{z3n03#_sd=-PP_Bgiqd$lEujU=Jjn8aQOtNf zbXi-SS1!&N%__`})K>RcJ!wpQ(%8dq8XAPmgdl~nlaNwG!-UZ?X@bztHn2{%vmf%S1kj~#Nl)`JDu;NgEJ1vKe;*Rtkwbd z4y{M|Q`FObNbi+Ab6hbB|7@zU_9N*16cF@cvUPt~Js@`ncwYG(P9HSG!CsHcfc%v-LCj<1z-`}-aox(#0+GopATR)FMGfSL z0=-Gw4S2yl7wW?Z=pm159vce;I>PliwLQO61{AfHbnBXd-4V+WuN0J`Wh|IV=sP~~>zx20Nh)BwCJcGgpb z!{oDfRar65eKDDPy27w=YXt;?Mv<-_ya8X+QojkeU$IbxTc+Qu;_-A1K)kB(A=T*+Mrb-)9mJ0CH!tNuOC-6vhRTQqr91XFHfzD+oX!s;Jm5^K@``M!cF2EtkHvu<~ z|LzUaN*a6DQ6Jdt!4b%cUAKGbMB@q9XfDNB48Ht&6eD4EGZ-?t|a>R+cUQ^`mr>(c0UDMGK9%#agfIsTpkJog) zSx;9)%25}CR5${ckuGC($YpZZ0pE8^m2#fBYhm-0+YX^4)cv&3I8>Df(LfWp8vO4q z0?=}ds*voIngOUT+$`z`uRL>3MS&_>VuY{K$s1D4kgDC^H$BR5e zpiej-0JW&&T`1kNyT+AtF0Np7%7MR1eZa7^sc1& z!=WLrQ&a{3gyJG}2ma(DoJE4wj2_vKiq}48UccT%Z$j#BQfc4Dar4LQem3-k%stVv zzr=a_TtTsCs#(hPIkLXI$}Vq-gcG5x z45x)**r@Ou9|nEWZE}hw_wd$lM*5@91ytQ6T2?vVa*~5J{LCs$);?2`m#KrdfE+5D zSG+W9J;m|s!$uB%&Y^)y5-myPoB;*!(O^Y<^w2Pbg+p)UzVANBR^LTt8JM){&Q?-nF8Fl#*x8ag<{-ve2H5!5v0DCBMOFB>wMhmws!gy zq78DgsQazw+DV?7)Ke*;e(D>)W!jViQPm**iusDA6dD}HHggz27XNt)Y(lLH_j*TN z$+;#Lxjj{EA$Mc`>h~DUgFUwoc$Jc#Iq&q4{po1FW8`3UcL5|jzi+7af8Xe+L zW5eQEM$JLd9=iw#&@JtmpMw|@N8qzpD-rpjJlKpQ86;hoaQdRgtEu#o%q(IcN{LR=4}5UAkD zEzQDbK27LtmOm#d4FXkQZs4`MkYA|!RGl39({!F`mv!gf>m||AVa2^K8@%# zf>L(85yHxApmPph#K~P~{W~B~y#xDQ6gBIOJhlczA^{O1{JfP(8=^L}cSDi;kZitJ z_LW?}yc)h?PNvt>Q5ZCb0w4D)d%_E4*d6-@0@0A@Fnm{2=|~c|0wVX|>5k$T(pUP9 zV=-tP4BS5roCm&W+S3h>NGQ}$ylNdiTE9k(hRUvT=t@0n;OL=9>jDxw-vvh$}zxep~o6Su1t1w zk4`tOKCx!p)hni7&!|7S-@ClD6xbHPA1xhzg>;J^i1t^qj*oU-41P#nE|aCrqC~C6 z4Ww)-Z=>mO9p~kdX(rCROqh1kCx{xw{Bw5%R~xT;+2hJD0r65kZk`K!#jYb}(Kiik z&e)KQxdENx-cpmN)B)4~w*0zYd~^*bf_?}SWuiL0q&~8TOaD}mCdbq=)2J($axH6l zqvvc?5>}reRN3J-Jr%aRCFnxuV!ntRg5(}H70JqyO3>bbK25zq$|We(^M);ZJzYrh zdPHz8Xd(ZgP>=hR)olu!QYUz6X8+sV7|R|nni_oDsw}YUisoO=07+J)`7-+SnpJ)L zOpDhypYPYSp{8DKcBO~%hB$lXlAM?;x*!UQR`0PFdWoORjWo$6fspm3=B3!3|fbI>_Xslnd*I$ry^6G7Ee8w105y7W?y zAF+N$eA?~5mO;l94nzo{xOC3Tw4g!-*<{A5bHS}n9MpmYxdSy!`<%A!sk;kR@_C2WcSei9g%yx5cVnIhhwcXVGw7GM!QL~!IY+SQ%3u&ePKpYdFD z$w;f063iJf{`CTOZHQ^Hk)E$>(?dq5@tm07X}29`x)ZXi`AnPDu_NW|9NE&0aDxid zOGS8JNX~jK3_c^pMroJ_Ve;L!fWaVHnbDmqPT+nsjdOZ}iVS0ICS+G!MG_s^-_E3k zeiRMV-_IOD1aHF*#n-Hy&qB*|q>(6I+ri@ZK}|dg2B>5-aDaWu-Pts}A<8D`FahB0xTs;-=5F=5AY8HKqx z4ML$yHZEi1Qu@BPjDs~+G?Wey+}m2#)#K1eBPq;l(hEnzBCBG8uyAl82$?@k23X3{ z*wVCofv)=~NoFOL_9BSHs1N{#6u5cjrF8<2#Hf0bvbY>tC&RsrgOzlWabgR?#P^@o zT!0MhHwy$(kX}b3hb+tMp!zrQxXLAI`sd$aY1TKFD4nxa$82Z0ihI6!oCV5JMC_k? zlyRnyD}XU{)j^gr@~0Hm)(!KXT0NclOzP&!lZIKM($4Aw$|HU{JL^gX-+5dJpIjFzO5E8Wq?fO=`LW#l)v3b z&ytgz!dJU4$lZdQ&8*;rFL|2kytQ?>=y8SyF?_ngN(UT^ia%6l!uT!%s58$u`Yfg8 zU)t5P(S)XnuR24UE%F=ZR$cT?!pQB-ru|>uz*|D?+;A4oG@GGvOq&OnNZPVl)7rWF z{)9sEMy-yT!}oTt^2(B7wO(`-t<&I#{iTOC$MrLTOtK+@&*LzO4*_$;N5o=$e1qS{ zH(hj=aYKo*tIVs>DR|6l+&Z5SWgk57&kEO+hFrA?7i)I$Xuum(*6AOMpQm=L)jYLi zwMh;z!lT>x`1|>z*0PiJi{-7h_0x|&5{ib5@U50-US66kUfhO{%@M|R?i4^tIM5wG zUQQA7WbfMX?TvrAr=ehkbng60eeQi5b@%+H*Drh#6KjHh!|};=35`xnpchq9^F&D- zr(^G!?o@V_M=Tv@eIg+ZvT=lml)A=;I-4-x`=GA0WABa%0NKP~e$zAHpp38SuAi|< zq^va9?w~|cGptJ7d71cQ*KPXY6NUpVD;AiuuV0=J+dFvN(5BASdCY`AsUo?WM-H;^M=dGuoTya8ES@1??>>frQP2ne zJW=3&k6{1fc_D#IWiZV-gX72&HV>auyOP~}I`S!K7>(Y|qF|wRo=k}QuRec%6X&z})14fhM&^fW zuG%&UJaU(7uTt2mYVQSOf_~IyA=$>td|%5#A3V8tE?Ujz>7AkYfY1#wNm$jB^K+14 z;)GdJ7+mZcV8Z?Ae|Xlka2>t4DtJZbB1mD@;1T+E7hI-W(RIq&E37VM7caX3YG(ZLt>!IH*;7{{oJG82%P|%b=a1bP5#ijvE?js z0i(b?Aoz}auZ#%MBvxpKG1rE9ao{J7uX9N-zgNqCg~`2Egaa5f3}%=yGwGd2kS^=Z zX3e%^`gg-UdZLv=aSG_p4fRboRqdrtPfoFw1UU@LasPB-~MJF(o4_gD@Eh9OhF)C)f(JbkOtY z=~Cp@hvaY{eh~K`dB{)dV>_Z9tM(;+FqX;U>2({=+)=5rl8HXL_axA61PPOY7Wk<% z3;rC??O61Lp3qX|(Z6;i$}KqTro8Yy=QGBQ&I4wQA!o;t_d0E%JX$0_q0DJ>>wJHV zK+#F>2@7oozRb-wHnvn8+$HAb=Dkg(KBkWS{AU<>9rMt~(j1LuMA(E6O(vHlM%^-Y z4#hCk5S&vt?$(NV6UQHG9k}=!-B?)e_#FN9n*M!<3wttQA%0>@$4n1PYn9PU_!u~7 z0kUOai5@7jdNMmw_roC^bE@CGCIqzj5H$aL>p9EtOg(z0POh~!K^Q7nrbRj_C>I0e zwIn!-NHAh2ZklOf!W=Vd4OpqyN8;2`rU0lF6bK9JxA8nCbh|oJaW(J-2$6fRUd?SN zByAw)h*XsmYJZ6Bx8vhICBP2TVoM)AFs9OLD78r}(Z(G~50tm6^~qV)C5Ioc15{5I zYSZ{vofchJr~{9bH{ml{gJGKduUbh}qHdd$o2OX;Uu9Bl8yenJgcoTt72+W|Vq@s8 zcu8pw_9aIC@@#}>!&&IMC3RDJ@OtFx*aN80MMoSfw%-~)rn`$L@btS)WZv-w(rF5P zwQWk#p66##U_=xB_%Pd)AO$vweAb+)B9)*gqyAwt2OwEs!(0&@mpkAn&|n(lRg=Ia zGIU>$<3NB92-=J)^+S2+K5%!3hO--f&Ur9|;)VVgzE=%(H4%1?-={?hW|J9CjS_D%EofSWE4 za!AixCu<UDT=;awq)ZeRz25Zpyw48rlv}_Dj zRr!gpW6N)Bw9DJdzR@ijDr z+s#?k{fM$;Zt(uGI|Dg)oG%hGomr`bf3wI$xct(n$Oo!3q^mA_?7i+jcZ6!Xr&rzm zT^b0Kqp$rgM-Kq|9ygziGaO#z-}DLCI6q;VlPjKPll`f!t4qE#ZnrX-D+V}>&Kc@d zCS2FSF^NY+w_>?0{@N2~;7x2}*KUY_3$WtcMtmyTHL+q#|6YNTcdELyRADa3vR2=y z>zkb57pJ7-}5yhuvGB}<6o1SXGc zvzI^Wt0s;789T)>d)p_Ou*J1g(%*N0se|vrWMKWzy%{#l)M2YQf_NT$?aAjK!urt6 zs1;khmPGqj`&SbaUvB8?Ka(t-Flcp4=+QhnAg7pBCKk*10(#8mK*VNpVUzP#`^q12 z!3g>>6HSPLb~7ivbLQ2|qv!O}(Ak($Dd}d*ecch?P^68lErVhFC#cX>EK8Nd-6C zv!~9EIxO`HDMU%bmTq_RUdqbhi=iQRcMReR#<;i2Y;1S2)1A`-f({^V*T%HO-o@6f zZjg(5I6nJ4Y}3GQn2SBNObR!&DfW-JuyAQB)1V~<4w~kQMkTkU*OlhGYBD*@QL8!& z&T3)YNmJ)e=CcEmi|q0u#$dwTHheCv>$lxn6J4BGOta_>?Def|?vp~FIf6LGkl zs8on!U8}DnbW6*>0KMaZd#o+CQGD(T<|^VV_?-P9dHA&uizc)RzN^J|)C4$tsOlZY z5TrfWiwrPk0vMhdcfI~KuuRe)@Qpt+qmEcz=?S^xy!TUW?pU%NuHzdi$8)0U`(bf# za1fJQhbfRTM*D7Xg|8}ORCRf6l4a4F9d8V=Gf^ zkEz-PR>Kd6bd19_TWP{ShGC|l&#^Y&W@j}zek&wv2)pcmSk3YRZo!a=lzTQl<(Gpnh9+P8EbqgIO%lzTM|7$oG`oYUYb|Lg@O$}wT zMt-kfQIqxPI_kU%OPxxyS0{P{WgephI?a=_vvLPUP;{`K=y;~anRiDzk8W*VphhW^ zLrR%x(|lU5GCh7eOB{+s;NZcmvZXk$pp6?t6XMD#97?24qxnf(#SsGRyWG4-R{)dS z2%oZ&5+|CTaYBSXNJ@ubY$Lm*6*E~wJ_S4$r0=L4=Oobv@070yWb#95KVF|D>F(|t%;@?V!FSknp0uZ&&`HW-PklqA`LIp^O+5%~m zYA#pYxXZ%eaYGi`qriNcp8nx$n3~`hf=aj%36egH*?lZyFw9cZ<4*gQSyNV3W`U!` zR5-Q0{pssS%*CTw+O2;ondv;{EYjp6-qiA}2N$5+#Yus~bNAJ*O1I9^nm=PkD_19h zHKq)t@P;KOW?k^xY;{>gJcYIdaX8c91N1Zkf+>-WaV=A-4*)E5=Cl2HtDeZ-NHl9Fv=yfcIw#MY!ngwU!3 zdMb);`l{+{FbdX(B4z?i+czo_xT2xBGij{qNh6KTI^kuw*kQbTq|xu zx`pro9h8<5(-)@(2D;S5P>vvtXefQ25NYmvt#PILujkI%%c=BCDol>LB09Q*m3)iK z%Uw?kMSd$;VWSQp^JGQiu>-ukjrTa+qviaf6KxCH+9D0{byi>7jjQau=w@$2()$UK z5i$qvTN4+$%Qr}9klh;^(w&0yKxt8?$C6{@Lj7~zK8=}m%Ch4`gbWL!t;4@4v50Ic zjV!KOv|AK584}TC)N?@5v-cGY;>bGEG$h!i(Ie|8@V08BB1|FVUH>$22|wHKLvGCT z(ZaRIQq@Y?Vr0te+_o44%Z4Aj-zpWp9k$dm(v*zMd9PKeZ6Z-YmjZ>)ox45WS+udQ zfbayD1N8ua&Gz`W0MjGCT^o`{_)ZDGv9i+< zq`(pP#JU~c*ECKc7N~Rk#5Uf)XX-gR7e|^u{CN+ zp8=noFT6WSuZqieOez#M_0_d5#I>)#);6y~_4x2UW#cK!6;8jNPKz~0-!Fy-nwwNu zT~zd6g#ZB%2RH24Ky()al#=M_@pofrMV*><^F=epO4HT(V#*rixJfWe!guuQz-Tu& zPXjwlj0jquJIkoozzdQ)C%(NQ`y8SKu2zh(O$(MsTL&p~Npc>jw5$gExd2gIiry)6 zD*U$5%vK6m2oEQML+4Rnw~4r_a$MlsJMEyD=mKgM-Tj z$+aU1oj>R6Ht_~d^p*SJHAozoG!*>)b!jPR;rBjm{Q^qWpr(dcI35m*Bntr%c9?=8 zQA!3Ur{GYurskJ!^Dov(LNt%k=>s8ll%9eVrxiqs^7p|h&_yRtkr%d8pUQHCIAaDC zQ-jycewBaU=-hQE!zfqYV4jb1F+F<^4OF04h|fPvFG zG-GQS_;eVSQdM$uzj4v95XPKZ_L*^JNO!WKITt>^T674<6{JgrgovnDQ|{Bta)q4J zie!9YdK9byT-Z~ZOfLDqe4+g8H7(xEj!8o>MIQZo{(|~#9-SCwWT{+QMaPCW>e||^ zONkoH@B=k?X7tVlGC{N`347n2iT`(Lc^XLkE&hi?W6?05JA=L~tFiF2y)&3X(umA^ z->_Z`bV>B`l&LkoDCs;KJZnEA%5NU>Vjj1bI0_{?`yNl-ikexXgcuh@(}#Z@_yR&=&_z>)5{Z=Eb;_0 z!mAod={IAhZ6|MaCJICMP2m`2K~imMmDhwqRqGm((c&RWpzzC83;C$6Xv|F^%J_F5 ziL4vCXQl!#zf0@khras?v@N2x<$nitK2wZSTpSvp11S$(QpKffEGnOISH;VIqu1DDQqGFd4qnkNV#~hBMQIG=FZX zevT^VcGkwONHlaB$ui6(`NMD8B@QSc;9#N`R_nnE%7Vt_*yD6YEa^8cz$>u(4Q6Ae z_Xr(U6+;37ZyfjyzSjPM(c|Y+Y>k7EA)_%KCGP;rW0C}pO1TD>YmS~}OWyj-7M!E>F0h=2sCYDB2F&N#iP>r|Xd8-L?S0jQ{ zSW^1)?D``>qiZSp3zi5V69ny%6!fkNI*4X}1!5ECAtO8)O?`bbPq{7!R;~Eq@cWJP zUg48%efv2EBV|BN6z$gL@ zwO^uYB#0r{c3n^LS9YStXDe4PSK}3w@+Q7WNE0B~zq6YqL(gAJbH$MPacQ#A$SAS#9>>3B^*- z-LPbcX7ofmi6M@+u*tiOs9P`zODK2zycyH665S=E+5BEQ7aGAsBueSF{yq{*qi5F{ zD_2i9>y@Vw!5Pun<}Zy0vjQp~#I!AAlmg8JSM$2wPA@SE1O5>EZ=j6iVcNUT`~_5g zh$A-6hk4)~FX%!$>1+c_pnMrULD*W$9iC|Wo!}BF6ML9Q*Le6myZz@UqxNP^&_Jez zk+rOjw#uI$tHn>XCgVDsU#s&yMJqP7P^QSRvw&jlmYLN0`MmYs$B2Xw>Dtq3s|Y%^ zlbt^`LK-({b&(*Hm;Lp$iu!8kh~_C_RpgdI`pV@bU$v1BN@T_NkA(!V4)O4arssX} zL2K=XRhr$(MrW65x{D&Z<@)YHHRNd>U(y7f6khnMt>NK^CDqEQ7)w;h1ZT~C(&3O- z8wgf=p^$nEH5O?0I$JT6x?+&-LQ`kYp)2;#eL~=W((5eRz^Fx# z?fO&jPi6TaTQMz8QJI>2`~C0p32)KtBjQfa-Z?+MwP3xKJu+v?f*?A%hrxGp;y0LT zYG|R&M$kV_!TA?txSsw`3Rvk*7wm=RR@9sio+_GhQ3$yAwHAGALJ4dN$A`ggxY{p` zPuiti-Ps3x)<=Y!9g5A{^FXplqiuS8t}MYqbPIFFzW4qR_lDj0Xs)zAR~mtAENz+> zS@=Qk)yii`%b9x6rykC*rxL_<3Am^|$oJ$%3Yh*~ERhcvYoK^!)w8{7mu6=fN~ex; z1U9~(8P|l|x}hT0Ne&LGFHIn-rA~JFBMeso5m|AtvR0&IT-@HEbwTV7MRh_E>;N0| zP`YaLmIU=A`k`9HRO-O4971!&Cj;D=hPvgbFsii0qgToFTAQO0%$WT!-f(e>b&v8Q1^fd!%hto3}wD4VJn^Qb-cxRPYMJoCL_5pj%PNzSM zcuKeaK3{#p8q?9#4EJ+M&YlD}sFT^VEd4}u1;yKI=zr+tOMCyXk1uIGKN6ps+-&1z zKMbYqCg8f%&K^WXZ-(NkDM&D(3_9oJ~%#Yaky4MBf|DKN4^^-fnY21|L+&~;H63RJJ1`E&!dTt|BNCp Mqas}?X&U^00E*iacmMzZ literal 0 HcmV?d00001 diff --git a/internal/server/assets/logo.svg b/internal/server/assets/logo.svg new file mode 100644 index 0000000..e96f9c0 --- /dev/null +++ b/internal/server/assets/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/internal/server/assets/style.css b/internal/server/assets/style.css new file mode 100644 index 0000000..53f96b5 --- /dev/null +++ b/internal/server/assets/style.css @@ -0,0 +1,174 @@ +.clearfix:before, +.clearfix:after { + content: ""; + display: table; +} + +.clearfix:after { + clear: both; +} + +@font-face { + font-family: "HelveticaNeue-Light"; + src: url("/assets/HelveticaNeue-Light.woff2") format("woff2"); +} + +body { + background: #343434; + background-size: cover; + color: #4c4c4c; + font: 300 1.1em/1.7em "HelveticaNeue-Light", Helvetica, Arial, sans-serif; +} + +body > p { + bottom: 10px; + color: #fff; + font-size: 12px; + left: 10px; + position: absolute; +} + +body > p a { + color: #fff; +} + +.container { + color: #4c4c4c; + height: 249px; + left: 50%; + margin: -125px 0 0 -223px; + position: absolute; + top: 50%; + width: 446px; + box-shadow: 1px 1px 16px rgba(0, 0, 0, 0.58); + border-radius: 12px; +} + +.header { + color: #fdfdfd; + font: 12px/17px "HelveticaNeue-UltraLight", Helvetica, Arial, sans-serif; + padding: 18px 20px 20px; + border-radius: 2px 2px 0 0; +} + +.logo { + border: 4px solid #c9c9c9; + border-top-width: 3px; + float: left; + margin-right: 16px; + position: relative; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.6), 0 -1px 0 #9ce5fa; + border-radius: 2px; +} + +.logo::before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4) inset; + -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4) inset; + -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4) inset; +} + +.logo img { + display: block; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.5) inset; +} + +.header h2, +.header p { + float: left; + margin-top: 30px; +} + +.header h2 { + font-size: 26px; + line-height: 32px; + margin: 10px 0 8px; +} + +.header h2 a { + color: #fdfdfd; + text-decoration: none; +} + +.header h2 a:hover, +.logo:hover { + opacity: 0.6; +} + +.stats { + background: rgb(243, 243, 243); + background: -moz-linear-gradient( + top, + rgba(243, 243, 243, 1) 0%, + rgba(236, 236, 237, 1) 100% + ); + background: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(0%, rgba(243, 243, 243, 1)), + color-stop(100%, rgba(236, 236, 237, 1)) + ); + background: -webkit-linear-gradient( + top, + rgba(243, 243, 243, 1) 0%, + rgba(236, 236, 237, 1) 100% + ); + background: -o-linear-gradient( + top, + rgba(243, 243, 243, 1) 0%, + rgba(236, 236, 237, 1) 100% + ); + background: -ms-linear-gradient( + top, + rgba(243, 243, 243, 1) 0%, + rgba(236, 236, 237, 1) 100% + ); + background: linear-gradient( + to bottom, + rgba(243, 243, 243, 1) 0%, + rgba(236, 236, 237, 1) 100% + ); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3f3f3', endColorstr='#ececed', GradientType=0); + border-top: 1px solid #fff; + border-bottom: 1px solid #d4d4d4; +} + +.stat { + color: #4c4c4c; + float: left; + font-size: 14px; + line-height: 17px; + padding: 15px 0 14px; + text-align: center; + text-decoration: none; + text-shadow: 0 1px 0 #fff; + width: 148px; + cursor: default; +} + +.stat:first-child { + margin-left: 1px; +} + +.stat.enable:hover { + color: #747474; + cursor: pointer; +} + +.stat strong { + display: block; + font-size: 25px; + line-height: 25px; +} + +.stats-bottom { + border-radius: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} diff --git a/internal/server/internal.go b/internal/server/internal.go index bcc641c..647db50 100644 --- a/internal/server/internal.go +++ b/internal/server/internal.go @@ -1,7 +1,10 @@ package server import ( + "embed" + "fmt" "net/http" + _url "net/url" "os" "strconv" "strings" @@ -10,16 +13,21 @@ import ( ) const ( - INTERNAL_PAGE_HOME string = "/" - INTERNAL_PAGE_PING string = "/_/ping/" + INTERNAL_PAGE_HOME string = "/" + INTERNAL_PAGE_PING string = "/_/ping/" + INTERNAL_PAGE_ASSETS string = "/assets" ) const ( - TYPE_NOT_FOUND int = 0 - TYPE_HOME int = 1 - TYPE_PING int = 2 + TYPE_NOT_FOUND int = iota + TYPE_HOME + TYPE_PING + TYPE_ASSETS ) +//go:embed assets/* +var f embed.FS + func IsInternalUrls(url string) bool { u := strings.ToLower(url) return !(strings.Contains(u, "/ubuntu") || strings.Contains(u, "/debian") || strings.Contains(u, "/centos") || strings.Contains(u, "/alpine")) @@ -34,6 +42,10 @@ func GetInternalResType(url string) int { return TYPE_PING } + if strings.HasPrefix(url, INTERNAL_PAGE_ASSETS) { + return TYPE_ASSETS + } + return TYPE_NOT_FOUND } @@ -41,7 +53,7 @@ func GetInternalResType(url string) int { const CACHE_META_DIR = "./.aptcache/header/v1" const LABEL_NO_VALID_VALUE = "N/A" -func RenderInternalUrls(url string) (string, int) { +func RenderInternalUrls(url string) ([]byte, int) { switch GetInternalResType(url) { case TYPE_HOME: cacheSizeLabel := LABEL_NO_VALID_VALUE @@ -77,9 +89,22 @@ func RenderInternalUrls(url string) (string, int) { memoryUsage, goroutine := system.GetMemoryUsageAndGoroutine() memoryUsageLabel = system.ByteCountDecimal(memoryUsage) - return GetBaseTemplate(cacheSizeLabel, filesNumberLabel, diskAvailableLabel, memoryUsageLabel, goroutine), 200 + return []byte(GetBaseTemplate(cacheSizeLabel, filesNumberLabel, diskAvailableLabel, memoryUsageLabel, goroutine)), http.StatusOK case TYPE_PING: - return "pong", http.StatusOK + return []byte("pong"), http.StatusOK + case TYPE_ASSETS: + // [FIXME] logging with fmt.Println is ugly + u, err := _url.Parse(url) + if err != nil { + fmt.Printf("error parsing url %s : %s", url, err.Error()) + return nil, http.StatusInternalServerError + } + f, err := f.ReadFile(u.Path[1:]) + if err != nil { + fmt.Printf("error reading url %s : %s", url, err.Error()) + return nil, http.StatusNotFound + } + return f, http.StatusOK } - return "Not Found", http.StatusNotFound + return []byte("Not Found"), http.StatusNotFound } diff --git a/internal/server/internal_test.go b/internal/server/internal_test.go index ec943e4..54e9512 100644 --- a/internal/server/internal_test.go +++ b/internal/server/internal_test.go @@ -39,7 +39,7 @@ func TestRenderInternalUrls(t *testing.T) { if code != http.StatusOK { t.Fatal("test render internal urls failed") } - if res != "pong" { + if string(res) != "pong" { t.Fatal("test render internal urls failed") } @@ -53,7 +53,7 @@ func TestRenderInternalUrls(t *testing.T) { if !(code == http.StatusOK || code == http.StatusBadGateway) { t.Fatal("test render internal urls failed") } - if res == "" { + if len(res) == 0 { t.Fatal("test render internal urls failed") } } diff --git a/internal/server/page.go b/internal/server/page.go index 2ac8545..b1aaf7a 100644 --- a/internal/server/page.go +++ b/internal/server/page.go @@ -1,217 +1,17 @@ package server import ( + _ "embed" "strings" ) -const SERVER_DEFAULT_TEMPLATE = ` - - - - - - - - soulteary/apt-proxy - - - - - -
-
- -

soulteary/apt-proxy

-

APT Proxy is a Lightweight and Reliable packages (Ubuntu / Debian / CentOS / Alpine) cache tool, supports a large - number of common system and Docker usage.

-
- - - - -
- - - -` +//go:embed templates/default.html +var server_default_template string func GetBaseTemplate(cacheSize string, filesNumber string, availableSize string, memoryUsage string, goroutines string) string { - tpl := strings.Replace(SERVER_DEFAULT_TEMPLATE, "$APT_PROXY_CACHE_SIZE", cacheSize, 1) + tpl := strings.Replace(server_default_template, "$APT_PROXY_CACHE_SIZE", cacheSize, 1) tpl = strings.Replace(tpl, "$APT_PROXY_FILE_NUMBER", filesNumber, 1) tpl = strings.Replace(tpl, "$APT_PROXY_AVAILABLE_SIZE", availableSize, 1) tpl = strings.Replace(tpl, "$APT_PROXY_MEMORY_USAGE", memoryUsage, 1) diff --git a/internal/server/proxy.go b/internal/server/proxy.go index ad0c0e3..738c150 100644 --- a/internal/server/proxy.go +++ b/internal/server/proxy.go @@ -1,11 +1,11 @@ package server import ( - "io" "log" "net/http" "net/http/httputil" "regexp" + "strings" "time" Define "github.com/soulteary/apt-proxy/define" @@ -70,10 +70,21 @@ func (ap *AptProxy) handleRequest(rw http.ResponseWriter, r *http.Request) *Defi } func (ap *AptProxy) handleInternalURLs(rw http.ResponseWriter, r *http.Request) *Define.Rule { - tpl, status := RenderInternalUrls(r.URL.Path) + content, status := RenderInternalUrls(r.URL.Path) + switch { // [FIXME] It's ugly to guess the mime type from the file extension + case strings.HasSuffix(r.URL.Path, ".css"): + rw.Header().Set("Content-Type", "text/css") + case strings.HasSuffix(r.URL.Path, ".woff2"): + rw.Header().Set("Content-Type", "font/woff2") + case strings.HasSuffix(r.URL.Path, ".svg"): + rw.Header().Set("Content-Type", "image/svg+xml") + case strings.HasSuffix(r.URL.Path, ".png"): + rw.Header().Set("Content-Type", "image/png") + default: + rw.Header().Set("Content-Type", "text/html; charset=utf-8") + } rw.WriteHeader(status) - rw.Header().Set("Content-Type", "text/html; charset=utf-8") - if _, err := io.WriteString(rw, tpl); err != nil { + if _, err := rw.Write(content); err != nil { log.Printf("Error rendering internal URLs: %v", err) } return nil diff --git a/internal/server/templates/default.html b/internal/server/templates/default.html new file mode 100644 index 0000000..b4c01a4 --- /dev/null +++ b/internal/server/templates/default.html @@ -0,0 +1,73 @@ + + + + + + + + + + soulteary/apt-proxy + + + +
+
+ +

+ soulteary/apt-proxy +

+

+ APT Proxy is a Lightweight and Reliable packages (Ubuntu / Debian / + CentOS / Alpine) cache tool, supports a large number of common system + and Docker usage. +

+
+ + + + +
+ +