From aa6b3e710e07c028f5833644b707e79313f28b84 Mon Sep 17 00:00:00 2001 From: Yaowei Zheng Date: Thu, 23 Jul 2026 22:23:06 +0800 Subject: [PATCH] =?UTF-8?q?fix(landing):=20make=20the=20site=20indexable?= =?UTF-8?q?=20=E2=80=94=20blog=20shells,=20sitemap,=20robots,=20share=20ca?= =?UTF-8?q?rd=20(#51)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Alice Co-authored-by: Claude Opus 4.8 (1M context) --- packages/landing/index.html | 19 ++- packages/landing/public/og-cover.png | Bin 0 -> 106527 bytes packages/landing/public/robots.txt | 8 ++ packages/landing/scripts/capture-og-cover.mjs | 32 +++++ packages/landing/scripts/og-cover.html | 125 ++++++++++++++++++ packages/landing/scripts/postbuild.mjs | 56 +++++++- packages/landing/scripts/site-routes.mjs | 75 +++++++++++ scripts/build-site.mjs | 26 +++- 8 files changed, 332 insertions(+), 9 deletions(-) create mode 100644 packages/landing/public/og-cover.png create mode 100644 packages/landing/public/robots.txt create mode 100644 packages/landing/scripts/capture-og-cover.mjs create mode 100644 packages/landing/scripts/og-cover.html create mode 100644 packages/landing/scripts/site-routes.mjs diff --git a/packages/landing/index.html b/packages/landing/index.html index 5da396c..98114e3 100644 --- a/packages/landing/index.html +++ b/packages/landing/index.html @@ -7,15 +7,30 @@ + + + + - + + + + + + PenguinHarness — Efficient Self-Improving Harness for Everyone &umYM9LMZ;%_s&XOYS71N zD7(Y!gL8@VBkQ(bpZ#BNyBCb!p%-Ed5^1opU%}jzjan~Q=%W}C;Wl=iO0`I)vW$^@ zq*2kUxj(3%@*NX>s)N#uWb_{7GxLxa%1!Kj7u59FP)#UJy(4vy3IcC8?sSUit=IaC z@T3p}w~oR+HAc#u=>CWjc}M;31~LqUm1Tm$vZ0`@#_rpK9nW^RYk>~jJ~}m4*p$ad zE&VC5FFP#n^Y@}M##IoYTWx$9y0q=0AW)DNcQ`_NgfImU4fF>Nsv>-1mSx5^sGSYC z=Nmi@TJEiX%ilmx6qluF?K-O16M!isN65}|6=_KHm0uxlK#R?#xD2+3$JN)PXymKK6SDBI)dXYsi|Y=hHwM^0 z6^BH+rX{q1G1S+s8O5?UAl#6ek}#Dl8um_jc_T)~eg$~Zh#+~AYLx1&hFullcCck%A z6SjOvf*f1~Y~`pi1jVnsHW{2Dl@j^@BSkx6DxXj})V(FchSJ=s$Wn$IcJO?ylhOo4 zgX{_E!kUq)(Y8{s`tC^zby-AajNT}*2V8-cyS5bV_|?wH4MHlb6`VrFUlZ2E*B%ei zK+?kFxj0=b;A?5vK6#W5o9A#Pa%4yWHS4!$)3E9+D6^2{R)jIw7(aMTrdjMIFz^Y1 z2;Rj{{S*>OyrnnN6G8GS{qsGNm;!ZB=y3qDPDagDm}C8gw~-`Q;x>uww&93@Mi=B7 ztShRw;5ur!jFB70k0`uTQKVr~-$PJbo|fMBdy(rc`_7y89f{S}s69a_e0szHGx;|! znOmXHKBA;)GG*dNuHnlz?^|#6_g36f)vg3wwA5S{P3V}j`>8@#3{gl4LIMlOo^`c+ zDp6#|#|NrlswUKyyP2Xg9PJwuTS(MPrDX=Ic_hw0go(?#U@p}#LZ@Vfqdg1KgtKoRF8EuECCRwR@{{WFXx@V9q42vPJi7_2lVpTg+PJ`4&YipZKW z*vwT6(33WGF?wNfnJgkis8{MwCV51jm~#m`jk_v;B>wj!nH30k7M&qlozb)c83Db2 zlBy7j>6%BzhYV1R;ivxHAf7w-d3|b9Qt9E`W*+EX!6e6|R&Si>kv&sFjZpMvL5B;* zho?^V32}))qCf=vS20JAM3QuN9|6%r*B3Bjs%I6+v_vmH%O}5p+dfudnpwrgGZdS&8$VQ^a8V znQX8X=0acj3?RWxi79!|Csq&_Db)g1T1;1p^AA;0Esj@{r`}^nIb15_H_I!m^Wr?< zg#-=e{v@7A)p^_z3YRU1fZx9U)>^tSuyux-8^iutB;l;10h70fd-A!}-WWfumH zrf_J4_S0GWh_e+byj*^uH_nM~lgJs82t?hyK351kHA9ssr#gzL5^3q`g;fb}cL?&Eh|RQba!THGl14V)%6qn7@b+0jg@Ez=d=OshHF;sCb|}zV^z8 zeK>$WPlcD@E!?icM=W}14XDHK_5PHL%@wn^&E5ts&Go zsI0wV#av1|cgZ*?=pGpX5)>9iV@0!-+Q;B&QO-rds5@kYaHtova>Pm>IYq1YnG%l* zD()63ZLgjfPWqM zxYT2Gsj^E45PWqFryDXMmbOzceqTZ5sTWzzy#hgcG(uZOwgg1IlGR*H*@t%?lR^GN zLxy(#I$NnyibO{5)QH<1$ij3SiJ%M%UmrhF1sD->V8f9zu7%SPu4l~E&}3mZ(=Kab zsxxP@DLkQrPd`>mp5wb`@xde4&$6IZ6+uB@`A5hc3Us zhRe8Qy5KW>u)#B_!)vVQl0w6g*h+!PSa-bHyWG8pR)$jde@om5*N~dOh5(yBzNJwy zCh3v*=e<#+&u2k`RK>5+s{JUtXUi^w2v!w}z{v?bUpeo8)kr%X!Xs{*9=H7={b!26 zU|5DrX{6I+y2g`{vp@qtRukMnEYC+$mvNE`PSdMQ-6>-m3?phlP)J}N&n8(z#(rH( zTe`B6v}?VzM9Q8!r3(eYXig357I84}O*u+2gK2SuX);1rvQE!Dl5kSz=TJW;FL*wu z7{gcT#1tyMSx%oQZtTw4Rkpa~@r@xyYe{kEUhz!3 zkY*%`0>%IwMrjj+tC7Ig6c*`KaC2C68|gvA9f1^m5?Fbx!r*7CLQZcgDT~lRBe?6S z2fp~;!*4Y+3cY3E+zd=L<;3_Zs&I3!)Az-F@fY#JiAv%pD7e(g6>2_BiypZ#2+$~# zHlde^@$GLOSsO(0z2F&OYq1x?V7YI!DNnKT$rwQTi{Oz^%-~1tOQ{Fja0}9M3OX*D zdtcmkbycZm7PQGjYv~6lxcVswId}@k9j>KtpHxbHz8DJdwy}wxlwBB&&RiJq27V8B ze~Qzta4!0JWzr%4%p-?8q!_C>6HSzLC#n<*vDOAIVs*yXOKs-gLo=~wTvB>$`6sA zBVDd#Mfr01(Uj5dxv3{bHBWoQz?Ox1JrrXuh};7zjsiVeeY*&7A@-?4VG*;{&s>Kg z6ebZ)Mc=gbJC|OhM30)Z`P^Pw8YW6v*NjN-M%}L=0Qor&3bNsirLnRHamCVzki6T2 zeZOVKI^Gi8TH3YFtCqajr((kXtV8JJr~pMO!**}JU?&u#`BOz*C{a~96BWrQvhJ}# z6k7CklmUOyCStI(jx!Yq)b!(x!AStd$u>kC0ZO5gp{6J?`Ea)A$%-%x*jce=^M?wy zw0+hxx>SZ?v7rQ^v*Qx+s?t?sTgFem(JLGkb>{F%$-C+YkB^_iLqq9u<@KhqX~5Im zlQx_NPYrk`VJq%0tAHZ^R|ZfG_? zs_3st^u}#Ye;WMnvukSi)2~mK4#j#yRxq?itndAB>)qASS-{6H3>){lgdpkQ#wFpT zRUXSkH9J185^W6jVNNZwaGwhsoojd}N4*e>V;2LP9;WHly9#U*0;7%PQz=1v6-ZCL z|IRx>JX*p95^qO8U`iE%f9< zK?4+1^on2^+=@{E{s=qvB6J=W#9vrEl&fUF`kBC4WXA`Gi`AG@=8aIOrb2aWq5nOo zh03SfJX?g(pTp=^GfCB2L}bNcg3FsVj@_lki5$;)$w; zjV{W9uhU#e;)}u{&_GewV2moxBeDD1bdbMv1XcWQMda(g_QC|7AAAyguH!f=x@3|m z3Ns^7dxEV7c_})arATe1Zl0qE9gT3_20er_xIK?d6c>r?c|1F8s=7|?pWUZlUBxN$@g!RyS^XE&VI6 z&cIQxjau^N7xwskEAeF&P1*;I!;e#yBgn;Y1h-$GJrI&kcPA#aRl_y*v%9H&d@maG z1e?3^X+_ca6X7p@WcGFcazO#hAF^K*XIxPb9w&s0*`Gq3GAAd;{ZnEyI+F5?VYEg0 zE3WlBAQhYnIQ&ppL5674ixt6ZZQ~YBgiESSrRtFdc5by6&R_jSv6^!^f1TpV zL-Zyg<%Q!^^!)nw<#pt=qDY34jlR|zq9#99-d@cF0^tcBRTsxL9IJb$(yS6k{M7Q2 zi7DDHPz#PJr!j@=3)&OraUPSB1+%0aCQSPt!(`Ip1nI;oRFt{tOPQ4?QrQh z-wfc{D~ru{`a-#MR3K0%o)!aQ+L2boGrcz~JdDz!2Hcehn}t!? ztbD$zf?83bkax1rcEd%8RNy>C9;a03pGOr=U2sFR-W^8uBZ%v4s@xyt8QfNAP&R=G z1;t`5EQb&r4(MyYX$8`)-VP)Dlp+wUNo)pB^3BsWn52aEWT<|NviPxcl-#;j2#Tfe z0y}A;Ob7q|E!Bt~n6i;~gS+vsT=d8e8@rvVVHO)5OvllI9Dt1KOO{*Y>`w| z*^F>(0Kn7BK4jYnQh!QsXeA1rLq#Xr$dkaah0uv2w#L>}e&qyj## zP93U@gN!;w;FDAZLqpaymqU@(mB?(Xo-QQ%Z|&;5oyVkJ;se3RfN|6L=`9~O=T|kd zKsS-Ca`(4GdRtYm)WorU}f?u%Es9X@68iUFz|~Jwa{kA`p+VS z@H#I%cCX z|KY0G_|}E>zN!o5-?g{}-G?$4RW@g_fR;*2x#gMM6Z!g}sP1Mga@&N*8Kz>OIA`|fylH_an8b6upH zKmlhp^xC%-1gGN$N!O7mBwEXqFaF|**P`Mywk+g!DnO$|QZw^tXK4(B^yj-^j=N(r z4B6

sv-CTwP?OLBjjR|G9t>a5AZ(uua&n-^p^cYobXamonuf{R`5kijzHrK958- z4gQ!w541(FoR}2^mc*%&#>g<@8AYwBz!+)DwA4PO$VmF@7nx-Re2Dw8flZk@b<$ln z5QQ{YfQ7$~K{8sXi|6sYwlaJyddw7VDV>lLYWf9^;P6Fyo^>sMm(X7~FEkBIe9U-q zu8q>-U*}zu-VI^jI!&;Wa;p*szBQey#INY=mybA^QT^{G6Ij@zo*AtWHO|FER@4X> ztbzNCP7z?#ghQ4rvXD$gH!n=7T*I4RbNL>rrwo(7(sIo(;I-yE<~1uk-U~+ znF9|{Ze5(}cm0C#6Zt zx-i*(pkPbvcD;})iDs#5%0}?=>-rkFWY6iWkhlgIvbL^Zm@cCn(J5Ied$dL(Lg6A} z)2lIOiU4{P@ub-9>}3JkB4i9%$g=TKjy<&hJH8PcJ~%Atf3<-tpS~_^3|Ik;7bJA9PA^ZH8uOTME%2-h^+?aw^ zcQJULi(96TB5spfLRKg&AWa}bb~HC4dYy&bR)ng=xS|Yc zIjUH@*}8k{Na!CoR5zPgb3sQtqtQU!dA=LI^d^sp+m?vSnCh$cHC2aBO(j76jKa0| z)bkj*WqQ$q{}Rv=J?|!o-W7&_MU!Q@GkYt4`DXX6)rAqQDmBdGG=#KsxJ1-$V89NS z@%J>V>{vtpDkudYQjt8Nh`%lbqU96=KlTqfMd-8UUcmm8aItFXEE3u8^O-i7gwwT% zYR+!b;vO?d9F#=$zq1 zZzVF{WZCVHSiIjuQ$`E9btkhG8~vT) z`zM{qbR`mVa#P8H}it#jy zAJ1H*RkL#w8|~|SJQCrQ{}c^x>0Og5#Nt1EJ8H@I$t=XHY+3nl!pwO^;BgjH1%a`R zv?_+jDtfA-Xf3u_nnl`=_4s-*I#KHDg@g!ogDp9lpM5(nF;!wd;-5<7XiYcU|356C~pCh#}SRig#Q4KdJ(+elCP8qtI)waay%5{`Zj4mGhZ^G~M5yiL;EY9U z!KIYppUe#Z2fo9Aqf9q4ATpD`IV>yatXuhVQZyS$$<;$aC>3KX3!j#^MN?3X6*S6M zYw-W30zes{H1A<9ng2L|cfI~AtB^hOlPf0+xHpUd1?e7;(;S(qje^}X&5&BHkH6Si z1zYB>+QJD(A~ox{0qZBy%acb)(w=mh=;c^5G$A+fmJ2!6*gINPw=|z*daMzzM%s-! zpyXUc9Jz8RNQ2F6mN?%C6*YB9lwSalym~B;u9cL5e&{^&;0!y8hzC{sh>P(_ zX)=KLhI-+qb+IRC5f=so2aLvI;e)Z|mQxDFT?@-G4^B8&Lyt)Z!Wup8P#oC7E=oi9 z(L$8H%)=0@nZRgO=gFj9?9|#(t>MaGNgx{HPnsr&WmVPYVUnu2@8nXMq(uVkoXT*t zNYr&ZDWGJnBgxIXF;8T{KuG}{$8Ozs+=8}xrh*uO{ilhZu(aq6i(LfR9C3wo%&(=bF( zk0Ob9+EQv-pd2C>aZOhB7K7~;Ovxc;Ry^EFgt}O5!%#v`XxDde!$kzeq`x{di7XHA5|S<{M5@pilu9tSb7l`a?Lb z6oWl3GzsQ4*Np79Qh|`N-J}k&f(WTyZc|=Ni|D&Bs((_XSB&QR_#s@78Qk4V!Nv?@ zNqkc7@qV0)AlI{azWuAbY+ZEG+DY>jYd;O0B5;SeM9UTSEe5IT3B=9U?9CaIA{?hm z?ldSS6)oy_T_yo;72%&_Lp3C~k5zBXIAuvPrZ>rCYlNjxhM|kYx=?h{K+`MO6~BUS z(2o@D+n7`z3;WQ$D#Sz#N)n5Av7!trZQ2YH{)82W3`i;dkgXn8*}H*trz#}T364*+ z0aY);C$C44*oPOEQ!T`bZ>RUNacM%5RY+F=O2rZ2g=tS1$JFU1OlhE7yjDmIa=KpF zkx&_ggiacg=5lhEknVl|n831{nJ%;|`hQpeZ8oy768(-Omjv7{cWt7mHls{1EyfDE0RMO4Vo&PFGxa^C#7W=*5ZHtE4*vvN zG1%GyKjQ=5>FFxyZJ#IfSQWj|ijUak%<&;&e?jy~w1T>E=4WArx)ELxV}vmWuMFAeD^*{qI2oL;DZ>Sepx@Q`kjYaImfcy$Mw!C+q zU*RaibJiQX)jKc;5zcynyw{=4(FDL1zww0-MxMx4jZrxl=>E<<&acHrPx~cAE092$ z8XAgHR`JqgtH$h+V?RaX^s*m4VzlKiZ_MRdDPN@2IUk15V+B32DhdOv#-ytB=|`$G zghh0*e7yU!?$xqSR8(Px1z~t{3br2dMez{NE|tG|emm?xG^DBBd}8O8sfn0|`_odB zlGTO%^PcumJZwXpP1PDS9U3m;yv!M~ijfcnz?R4P3!KJcoUWNZJH*4&hPgQr3ArW$ z;XFxX<8Z*C>40gq)!8UCWutz-oJpuViA07S|8rOiv)_Ls6r3+?k zHXdQb{Yr80E6P`^0xnNr30t;}0D-Ps8Zv!0f*AYfO8HY$i9o`{{u`V*Bz84?%^CG? zg3inK`b?X&xiyIQ51l67U*P2C=4)~-sleX9Q0!fhda^=6_3DNj+2HWz2t#2q3jlx- z$)uUOg^Faumad-A(t*1*{}&)&uvYZ*NtroN`Hz^^AP$xG`_oXLl8F6WO8UDiFzt1< zp$253W3|Rk_b-s=B5>3`*zhi-7+8iZ0_l=f2x%T>UR0CpvwnEiCq}G5LV$)G0Cy~L z$4;<968o5M2<5tq`||uEA!-9uo>?3#W2 z1U%t-Gq!sUqQ7JwQKu(ldu!4dy*?>S6h{TyDGFUux#zF1_Q@z&| zYp*L7XbmnNB=)Y(#pX#`B~9_)5syUQ;q6ENV1VD&n^3AbF|_zKWXp#WrYM~HG)dy~ zB(6(zn8o{lA-k&!hw!C*v`LXkx_2m2uWr&ypslqrqojJZ6<`qKY^(~FqV~3`fEyNu z<3qX<s zeqL&}-;svy?ey5`q~3bK(3lwwHqAtN&g@SIW%BzJxt~=Mp#d{Ue_YeWJJqU z$xniCc|v4k<5K&;zbh0&LGg@ed_)wN?*&=dm8rhsRIV!E#npMrPl&qAn_G0R-z^S! zP0#MoL@Uq-RX@I<4TwZXsfL5}p3)~`yU>QLxSIy@p>CA6Japjk?;~OI|GTs}Tw<>^ zYnNmrh>3+s+IdK!?dSZ>^aX3M{;`l_V^FaX-#wk2;pK2>DDS<>ED6v@hlpP@lJa!1 zl*SlClmABeWQ2Fi_^)VoGeQmN%VY)K(5@Cifmf`4(1Z$^Jp3abadAagnN*fXzkNN0X?Z=04#A^ z)F%Em!3{a+wC7^LgQ*|6hDW<9eZFWMry?ph!3rVrd20 zmQuyhDZrX+TkDr2!w=eK_~2k>1**^+MgPIX6ckTYY!g2iVLp}P{!yt$h2H06xWo9) zCKxM3lEXrw?p{6wOPaRC&@#taI?veF^;&LKMrGn=Fu)(M+Z65p31ZQ22Cgn}|Q8N_XkvTUZ%HmUjcK|HyVwfdJA(+lC%t=QUj`JvYs%IY+iiFs_owVQ` zURFz-V~t0WYl4NZWpUf*$!casT(FTD{D5!gsfUHI$uD#cSB6Bkn(HSHR9+c>W9IHp zw`NW-g6(McMdI(<8*#@KX2+P=fdpaA9+`9SSZ)@!x^ufGm~x0Xx8=tN`)WYJ?gvPB zGm{L2n!h5{(`Onxg@p`NqT5otRmeSfLy__a37my$L~b1WX&0k@2#7kmsVNE9Q@2)v|b9YhZO2PMXOP3V*+nf z>Q$zHU7k0V5#_RLP!QeIU6x0S40d(FdpvvO$65t|gHmg0rPd)%mZO23ihjWcQuBt) zrx*S1#)t|8Xr37Y-WKnyVK5jAo(vef<#cn>>k(&Y*5+MaP;)$DyNeYQk;OHR?q1scBxwtpPmn?q?rJm28)D#=(3>JohGLZp znk%zg2+#jV)LVwd)dfqVSaA2CA&@|DcPBUjg1ZEFcMlqz;10pvT?Th|cXzj8;M?yx z_dIug0>hrYXRTh{Rn^tl757YHz3Kh!$p5=xnUN^?*4Qrg;DYu_r!aL-XhHN7L2ih6 z0pmAgXw8}*PAhw+WP^!R1=zz{GI@Uw(wlJeZdgJA=_xZa92TK+E%uqMGMv|OEaHG| z`ZJJRNnbH7!G^%lBNLaVQZ+@Xmi>M%!*PA&&7C`C`JczQhv+nzbxi~xQXQlkq$vC} zBMQwKD<-~rhk`|gUS|~k(G!kRrR0ATk^taEQE$jVfs1)jujrq-r&xm-j+l#)w-T+m zj|T*3=Kp=cv7CJhf+1wM>uw>B{`3$Qr6X3{yiK7%Eeec%`>!JUpH~mu1CZ120q6B+ zHr%bStz40yGA>f|#UIX5|Np<@#&*RKg$}}HQh^Oo;^uN6+LF+S3!OqoLjEsBqG<2t zv(5j)fb~=GP}eusf1Nwr0JbLaYR$$BQxuEdpcqzcGsS>8^6D&KRFI0sKS~%_2e}ck zU+5`33`N|2Qtx^>3fS@b0)N`#a6B zKLG*VoZNpgk%5INLg-c=`T1{vN6cp_J8{Jg(T6qQrZS>(io)fap$3{he&;q^bIZ8g zHV~!p=RYIJf)*1mR&Bd#=ZS!7+5{T{4J3BeqT51=8-D zYH}1pOX0Sj50|7yJ{XKW;RrBlV!$I4CAFSr2SV6qoptfp|G7VN(&_>1Iy-veB{|XN zpCVl+*#AYil;58i&9rHl!hVHuMwP;uEpc!AEO1O=A&jx_yn4yAb_Qk^NoIRj`>UBOm|{zSPH_i(p}?mt_0) zZoFAZ!i#VWAtv>#kMl=|oXE68P1%5~Gd-&Z#&3^y_PsL}(;>_oX0_qKPM_+9@h$_B za#9{LTr#X^+4y$M>-?jpd#JWY zss)Jm-k*-ZsCfk4z`HQJ?a2rKd)HFH&&n+*FZne4iE~4Ykxvb^P)@nK=- zibCVU_|B~KeGMQygNf2(nDdlam}LXDz^AU=cFexo$RLm$VrKA9_KARGB2J^q|L@$* zi1v34Qlupkoy?M#3JOO1NDm)HOYnO>i9-xn7P-V*E?;`!2a=+ghx{EBuI$K_&?N=% zvceHPvT~~aebwT*BBZpB4YCw*F3Fb~iWWy(TcoL0{=ExqIk}Ig`AJt#0v7D>T zY%)7uY`h0FYPe@HWiZxRKk0;th|;Vet;-M5n-a=#o##you2#F8^5t#7qDmO{%swtY z_U20yH`;hTH!KPt`Ty*;7g4(20e%qbSL5K|k9(U;A527cKJ$S&C9vUi(PpW>#vy*- zQ@(Ihyn|~pUO8#${eB~>`GtQ1!#eYS0gjP)h#Hrtj~*2}23 zYSr|{V~bv0C%Z8KwaOy8#@nz-YB8UEd z$g*Dj_uP*{043-taQ%H;%OyOSCXU+fVs|i_RM6dg=ZTzcBD+a8OYpJu-~V9&6jams z7PB#fkpTz<7XGOgy~#-Rr^8WF~TA0_{(?iiDv_wX?E9Fn^ z1c>7yalcaBk#K=HD>*A_iJ6VKkAkM`ONbR`vP8!u_Ch6~*(`5*-#Ly@mX!e zknC`Kx%*}yXgOETb1=%}v^VzcH8MInzz;7z9tS((_(p+?AH!iaY5XcYCyQ3K>|&`? z_p3^YlF4?CWrHPhP_^lgr*{MlvU2Svd1*Ip|5v4K-;3uxnM8j>W5CL+S3%HXrM$(x zP_*9|xUNWG?xc|4>k~`F{>Sn_ikLVmr?zO);6Rw^>8Nz}pIPv=Xgzq#YhdHaEXbpW zQ?1D;qrk_@0k=(GHm`>arp{MnU2SLQfGW^S>Gx|YU;;=wN2z+1PRq?`Rj2Fe0>hS+ zVjk;W_UAcxJL|lkWLFvDKVr^it{t(KyEvi8?(kT&J3efS(7jN=a=PbaXL2P(W)EkE zC`uONi2m`QzD|AI^g(zlDG;5s?wJ%)FV=`VeBL}9hdpBcVqIKJ%6;Oi^nIh<1C-62 z`+fNDmHTB@$7m8gkK++AhXA~@^UH84U#`V5#`OYKQ@Md?p5ar_a;@3>)ou($MBsFV zMLf0LdMk(3LV15UULYz1EiFjq>wZYH#jM0O)IlPasC&(G-ap&5<~Ta_O4|8S_=5_a zd2s@aGa>uOiA?*?0WgtzSDC9#7-UP?jyrqTyMsnXMtm;k$YED2r<5NW8?CQ*Je&?Q zWPaGJwD&s&A`A}n`v_Sm9lD;|uD4ngcz-f*u92=gh#+80;j_XwK-B8;_s(o}**>cC zKK_ee0oagjb{hswVGWUBK`(FJTP>Uz)^EGm#q6)e*o18GoCNqr2QF@iBRd_mIsx`>$D+oOw_2)7LDEhO2l6=HR zVMi}NG>XK_9F++b$)9PrIH%daUTwn)IE-StUc~WStx3cq=FUlAI@4CgkRUC8;!ojy zKHQg0f?`dPMGsJ##|TFES{khq{_PBYIDhwn+>`v#_v(rAdw)JW{1YDn5`@wC&1y38 z{lEObb{n(No@q?#Y&C9SSuf|I=H8<^UJ0#q<7-T{}$I~r70Kzr~%YL?H7@TS1GZ{KKXPnK$S5gyJVHUnb`@znXO4JUJCkL!9zN*x4A`=3AB1GPLv3}dp}<2`u`!} zaeO%61bO#||M}B@BPpJxAe8<0Z>`gy>rlxjyf~Rg>)BR^_`+D{{{Df&!a_0u@4cAK z!*fN4&*_ck)0IALj{75KBMHY#HAbtAwh)V*?@_<@Ml!)A(6FNkblUCi4%~rWPgA)9 zZLUAEInP%(sO$pgT-lp@}v;6M0kpcL{icXVn$Ay5W3{v{?&DZTvX(81TEtiF{kM_K#T{$AErD{x? zt$UfPZm#DmJSH;=;K%b|R?Rw-)8#gWc++ug9GuAvo;ru#mp?+$Adg+o98UH31@Z^G zeF4a79A%r^dD_dZLO4V_i{B&5%TOv;l+)&~ zOahHg^VKq25e7Mbz4P@yjxu#vxGwa3?Z=BBXTp2Q4 zU>g7VAS`-L7eo*gW$?GyU3T8!jksOBEG1<7A(L?VrEu5CB+}NIzCH9lZlV#gW6C2W z(EPl4xjzN&B>i^PPP@@MZn&3tzDBty9FJ~bkboXj7fOaqz!6t)?IY;T^ESl24B|Zr zES|gb#VYS-cscfqPwNH58zH&DDz5DKu^ipRzpB_}qDHnCV z?nFSq7&LmOHNXA{cstTnJeylf=+q^Ag;EKw<=8qJxl?6)7=| zt$Z>`$n(`X;eK>vKi7*UN(SxbAvDPofkhpo@KvoE!s%4_%__Z%lrhMbK^FUfntv0n zM6JlC#IYgI2nV?jzr*ix;@OFMB1^Ebwz|DPt?t`nz^I2=YQrQ=H(PjgC;6;=YC2-GA3=)9;dKP;KWmi! zJM@t;K5==i`+O3!)>#WB_fsm7F}aot4#(Tl2V(IdV1UzamJM%evD`X8)U>(HJ(=jZ z(YD?w7MiT*@kL>y{hzV3oZLCD%Syc!0@5UUhT9HB9^w5P!u1D~aI+w;i<5=2CX?Ah zfi?jgo}WKINJNq8mZ>rPEXU1|WYBF@UFGz9djqE^2>M)Yb*h}RO$sdfZ3$nSHJr?E z>-X>NrE?`FSI!q}-mX6ih5d<&%L?#Y_q=;@96LmLdqX+Ob!xJ`dXF7{d!1ES`P({r zh}wG}+i156L%Lk9S?6+gcht^P7r<8PZRH*e_nxCGh3?Z0oT9(aiB+3{Rc8bDA=y>y}cVn1cI4$#|#vS_q<2tMY9d z2u&?JJG(^e)=$9Wfe4zR+hdZfbf{JRgnc9DEEr8@9RB;J{mevV{Q(80o||at@OI^4cYQZ%fOFYT1%3V%dB#H2GacIv2rMHijRg zW-TIi4>~DdD^e~itI5uQLdMZz>+V{G*H{XWza`2cD^)(2!rEBathvYM) z!P;(QYsKPr*Jdhm6i)=|G{>Qn*+2h{j})TdG0f(I>Hd8ATR@FMofPbsXw_&vS3CK8 z*$CtOe$|jdJ?r(n4NbOk15p3<#XEe6-_m~`i8|rBtg#%fSKUyp`b*Cw5>gdgFhY5s z{;1R7UP>Q<9&4LVDu|&~R~?BNZm-yd`9hcNbNwYltsOYiN5w+Aq!Q#lef~V^CKS!| zR}mL}{H3D)Oiu0ZmCr0LFTusy00I4V+1d5%q165j1c|f{yA*=qDYUX zvK4`zPs#w(SyN+UDk>`BM?_vr401u-FZyJHItGDLO&aa?6d56*jMF%f7cQHHa^+;k zBKd+hR+Ca1RLy@~@B~a6+>m5_C1s^>e1??(aBq# zPB}AbF6QoQLZXN|UskSuq;qB{$S!s!&6vpazE^2>AYZRo2$yk~W(Wy6y;L!eT=z^7 z7{=N|S{u5c7Amw6JaiAnQYoY#xcm71ZFSy?X^y({v-ne&1zN=x7{*mw!bb^~o7}40 z6@a4Ot-u%b?X161}2;^OaGz zo|%E6i-bNu$61t-C;onPDUKsG>_Qt#F@$iS#kog=k0 zmc)fOzTRLm^K>nX&(P}5lBWlOuZr?!bGumU)%)msJrgV%jMDLb(Pm!>7vitgXnVUO z%Op~};@8zRDfkeq_zG^C)PZQryS+cGAP%{1WoRWwljhsJ-qz@ga3b<0O-jUUv^XJH zf302tJq!#ThPD@L^u%4ZueRE(Dt{|+GMij!bQuLIA;TDiWz#A_g)S|~Yb-^$lhfxH z3?jrfK|=C$L6?(<^Hq$qRVGX+Da2DQb0}-^!RVNt_bT1GgNgH1dxKZ-q!0q=N;K}D zc$rE?VPkZjc$r$|f`9`yYOJT$<5stQxM3f$9SM)OJCMHaAbMSlE&m|qJl|-z9b2wD zoX84x3QH_gx!CBCESV|Tk~4!<{kAWz4h(h(B6*$1fl3YrG9=TdwH3>Xr#Pz z^MT`8ZKmfdz*-fTI0(G`)!}(huUa~HY8QC~HWvEB-Ke0yz8>78Rd2OOCZGveTr5%d z=0RjCFa{VXD1uo1VW$X0tom>Jn8#75$(DI0y>94Q zyNAnYnh-R+yU}rb+!(x}00pjr5JQk0y!=l|JR1A=Hwac$`@bjgfHznKVa{g5^%p}X zRD6Ytl@^@0?wo}!f>3@gV=2J=`@GsOo7z_AQH}s9@MTI}FL*;qV!(st_@&`g7N4Hq z@oK6*m5qXfl-Ydh@f}Pq+TDF>*MT;8)*FhBcD6aWFV+kVfqH7P9{%+cRiYrR$r=fx z6TJCe=t-Uac)7}T)38nM!&_)^YpMX$s)y|!JuC9uA~$zzlRbf7BJiU<4;e(BuC@cS zBV8F7j`HustJL-V02HfS$oGAH636HATC55F&F6lci&^b?tZvfxX#*@wYQNHHJ`Lw? z|8S-$jMhFPogb3G;Pp_uKML!HcyU`}B-sIJyi3}F@$8CiPpnex?!Rt-2qr*uJ9i$> z$>w(0ndy&+CI3o=kE8afH}`W(t(Am+WbxiaJh7)&byDZU2;FLvEuQ>FyTk5S$t+L{8q~E4-E}ffnG&*a$B9Rch)>^ggxQ& z%$N-}%Cwu5OG-p%%0GfceV6jo?Fb*^u|=id7X}wW3_9+6m}i^5PWN^SLJ$boCSngM zpPM{=5?t9`SEo-umy7xv_%Z+jHiNu*8C)BnqSbcS*B!#&dEJcckM>CC4z9Ksfn(x* zFJ>xMnUssnz3~dYdDQdXTvphZVcgB*o_1+~atbG-F@14Z{~?f6ez)+)R`+j^z&Qcu zxr@~zhwV)MZeC0&mZlPw!#d4 zq<6Jf@a3JpS=XPT=or@wu0?{M-jMOQ%DDZj?EqMKDTyS?E4$^Hf&d1B5F4-iBaiDH z@$JKz0$FnYJ;|82YVT)9`<1(#WZM?cYO`#Ydjm=d>&-@8!be?!rDL`-wW_S^(>vCJ z(Rzzj)9D^@e?*VlgY%hryn4-A;}R?GStmMV&)sZwM!JShI`x1u@(B`BU8oyF%$})r z^Ms~FNx&3L!V(IYO5H_xvw~Nr6n!27-6g^BvNx0{DfOGBIA~}1s{N{NHQp}5`bzF( zJ5jcfgs&cL;9}$pm-&WaY_&J4W6%2Pn}DZ5-QV2u*wAuKK}t#iJkt`b)muQBS~PEc zb@ZDjs|odHKc);EW;e=yKA@)ldC8fWZj<3G}+tZYaFtrNZ zOvK3VB3x2QcjwJjhI!-ZOm@AJ{1s)t99c7lLfOhUho!#>y@g(0uH)!K-y~qbV;Ih% zWMy9{O^m*+ZNSo~cfw44Po&K<;W6W9I(uH8f1Smo48_E(RBe0c0Uf^Etu0PbyZhZ8 zMgLW`gjsPp;@&U+0cpWZ1tw13XEv$IuNY zYrEHPOzbwY%^Wm%dl8Yi3yt3f2AGVE5fL1&9e_dsjc_4;KB-^t@-@3)^gHJfPuH+B!U{+c=R7^}{bcfeR<=hsiJ9hE_F)n=y#yW85vIo+E!tIKQhq)dFNhyGHaPL0a* z%FeTe5Gr7gmYu%Hl*#8~+`lsncCEyYH<*%}MxMOI0eKqk?|k!Wgz$NdfDgYoRKoy6 zoOeg&(`8}*)D`(w-&K)6oIhP=KjRcBxZ|XtC7QuKpS&*XcWc+bUS6+qJs-^GA>)b; zXLD#zjBsd&3n>(lki7TTr7yhJSdG>m3w3sSmDzdtZ3nT6M|fn6X`%UuM1UVJe8CRy z^P_&tO-B1YE&A^qw@xhIyrOkft(vZlE#@?{OqQ9zja3@lW@q|rgHI;cf|{goR&-aX zHFTmh!D!Pxf<9p6>AIFWQwWP&Ir!XlO98CJoMQcO4z zc*?RcmCx(C_HV=!i!WA>1UDO5H@240sVNtHq1PXD$1l4AAdj#c`vN{MbHR})EHy0R z;Z%rb096G0o*m~aXrDyNOJ1a;@CA>FQ9x60nZsAe{pxC!+!vKSa~6UnG{oFs<@_CA z;eIsXk5dog)kzo@9<5r?+$rQ|B~yaEKQVv?QErA@FVWAS*_Eo5rRkI?wLZZlnsx)s z(oCHK`H_Hng`Tc~a}NO`m(5i>d9%@~8ev6+QpL+zDhT>fHl+*V{Xq!npY_MZyl((b z7Bn0Wdp|hP4D&-A2gmpjgPfP7IexH_VKqnwz_gapP8VTMx8}BWKkP6PWXurrO+Dk= z<}J~WCxsqOmCUAJwxc9Kd9giF^YJ+LS-mrA!%x{iO;IeTrgK&5cY1%6Kv&34vh$5i z+u05k562D2gtfo7lkWMbkqVoL@L<>fSLep-f#yuJ$!c+8kjziVwbs~zz|JGz0>_Z+ z>&F|()+YrerRy%L8ts4@XTnsw)lMI0xifO7!`G%85cr9YBn4W7YD+75y9sq0lt{Oo zFC1`m)OfxEsbuHx{*)zpx^ZH&ZZcxKR4u}v9h<^!K9w_b0g3N_guvm_?m01zwtY*r zG7fOkFweU#QGRpjB~6j$OHNYZFHxk(zEy9mKka~0$KZ4j&B0c{ND>~J-}JnVBI3}Q zebtX9E#vOM4h!9hkdvvHgvcan{rl{Wa0&YSdvMcyuKjdo{%lG$ugO+twe|w2P*+lo z1~F`KHnAfaj*j6Cixm?BUe#a+i}V|qaZ#=d-{lQ{Iyu7+2!_SV7^njV-Cm5a^dn5Nw2vj z;AnWU)|s2Q2|>=jI}CY0Xim#lCxO2<$WWAv`lBExaG~{ujP%qg;FHUpO?)rx(~y>n zub`y70zKD?7c~IoL27j#IyUP~*7YlRG0?|;Lv2(;vABCq+%a1~t&o8_XMYhH5!fw#@$~z{ zWD9|Ue3oE`b&I3Y$DIC z0=RA~3Byz02>FeN$|&_hmG5VZJU5zfA2u}FEb(Mu*Xk|uo`@)@f;tHhJ8I&_(>b9Z z+dHr7{(AlO1*owdU!j80)}5s(z92tKwVT7_s>Vs~s;jkJ`6F|Q!9H9j%^7S_LSuf# zz6PsB+s)PwVqDA#1Sv!~1k{*34oY3t9(4{sEi5vwMm7s%2}}iXRgLYsp65Q^c4CV*mtA*B$EZ4^$Oa(}4i33KGwsNtT>{-{n!D&n&F^pvXiIyS z8&0a}B+E`=MKWzg^~lY+g)#>BnDNvKjHir$5QluX1C___H;Ub~8Z2FaTE$_vQ?s4$ z-s|TMI{VLOJS||yXZ_8%^@85;UXbjDQEli2HuZkG%7)KK?UBWW?i> zlz6=Nwk=_I{CVVOaTRBZ{@WS#U4N^zfyW1z1#6x@VnX%mAS&5XIq8>WBlDnGpvdQ@ z>=3Vg6a;wHuwl2qOfQ zG>JoYA)8u0C|6}D%J9mVN9*Z>=RCkDl%-_QTjNps1Soc08x006B1YiR!FSZ`$y9cW zy_oQ9PrtIbZBXY4!6jf~#ffWgjqKA^TL{m_#l=~&dfc@BLsPFT=0NMIJdZiX34o1t z5X_Sjo%Z&?V+8pxMhRwTnWnyO`n^KG8~^Rq_sg?-ya36i-Uz+hsc-$};zT#Rx6`cM zLgP)PWJUS0l^JJ;Sf+p*&G!6%SitaAZ`0;PrU1C+B1Rp+UE#+K%F6-pW!d;^@{J9R|endsx|sct$9$!H zF-^g6O~W6yGNlm3r}SmgIJ6s?27NNA?KaZ6?Aq^kB33p>_`H{JY8B+=C0^$eiIfyP zP3e&dzIk$Np+J8?X*2zEvED|{vMO3XftsWJu5P==Vd zYRd2L14!BT1?d5P65V$@^)P!fjl=4r+up;X1t`ZZqSa`XQwlLy(f{m&XiQf$DFBOC zs20h~t+4=o6BO1oP=XHJ{dyxEq;$N$c5wBCMl*Os5!te2cuF!!C5R1df_^GD&_cJb z{(EBjhPn`I@QG2U845|egtY($@L}Fu;&*$1}QW!DpC%Lp1xz1_jp=TQ}i80kum{zmO^5M)F#tTb;~er2KlyxZ`)yhXKux zqE=5mn`D5OP>MEJJZ4POY_ve`at9H1dsk}Fuxi-9rX_Sng1=FCkT-OGKrSHnH^Y5<{5`y=s?m!^opvF>oqv*LuP2IxeQXwKyv}irfzz1#fcp3ARy8E?wx0s|MK z*eTjyO!Lg!BuT^-@UFn;CBG7y!fv; zwOSwI7QmqCwA*`fTH>K=sp+9XflzSq(Pw7m!>HrSID&KKS@<>n=`eiL#y|EHP8!=$@JN~+G*^)_ zaFX4-pXAL39MGb?GGMbk3a2Tfw!33CeVSayi4y2h-W<;#;PbeEwWf@V=zM_d0DfTT z{&iTX>vS;ky&H)Q zzt~f+K>I9ThDh($%hMN^QzpG;D99gQ1%WSZ%U5zal7RRkI?q;Uwy2jV6wRZ#0;jj);(8~Gk`iBU>xf5=9^=PDkelu1wQw!6U-v7> zZnG0@H>oO$1St?I;Kk75b}x?U*8jC`5yvOrY)kA6(uY$?C*ZDFEV`mmyVNjgtkeB8 z5%Y~d*(}S84FC8I0tWOrbbUapnkJi>uo-D%7)5W0^dxYuDnHro*|GUGC~XX1U2eX* z>Z+Y>CNa5G)R%AO-#`zIfTC#Ukkq;O zz0Ir_p6#5k`9W)`(Fc5QHDag9q}7b+y2YHo*({Bt`M1pF@%nULx1uek-DT6BDd3x? zR%dh0rZS`;JBt8NY&KF}h_e7xL_e>Qj=Ta&`&vRT*^@vt&xxo(I+ zv9*LOq47t?&gX2kF3AKV>K=P>Y6^l}-v65AaDT171ow1b_pVOLGzJ~d-8LMMW-(?c za>lpa9FCH!=UT?=!AgQyE!E=sZn~gct+nV~@Lu>K(siz|hRbOU|fLu5;B%*x!M1~i?IHpX0z(c<@=hv87Zc6f4)>R4g;R#4L+Q+pUUXJQm zcJR5u4;Q+fef#bLk3Cyv2pA#P8Rg5h-Ln3yFf4B#J$>n1L8RGTfJ)Er>4MLFs$4wc zC%HG&ZcuDUBCY1jeVkq6>6FMCff6EM1%T&`JR^>F} zgQ?L+)pCFJ8&VAvUXAPlIrb z`mI#nM3q0l59Ho=ERh$YqBrhU8`X4SfEc$Bx!Y!Rj}-m=ts*Pm&&g)mSx~?yjjH(V zKb5bHbnp+Wp1tAmw5G7=KLLp#huK1*ADM;CKlcFogRAvX*z=!02)HzI<9EYaBsD2+ z)yM6@>@odXD%pi{qW*MNpO@s_0S=o5RD3yZSh2Ni-?x@(#b1EJv`ee&28H}Y~S>S?iH)k{@#B)Co+3cH5_G&KRxHe_2SrSUWzkELADF>5Eqo9YFL@ z2M9t#6`QRbk&hp5AiX-2qX^{g5=p6SY3vrC>B(K9=oz$q@2;u$M{#(o%;d9E`Jc_) z6&oxQEoO^2%%_B>&Q7{%RR}qqk6xesR3#B-fJ`ez;dH(NU?~ILHb@%`cO};8E3}(l zz%iTK`k5;Y1&8WyO(*$$XRiyycBn*GDm8t5jwYk1#4kH+w$z5Zh2x=Eg=19qw5PY- zk;!7?AWL37Mn}$$c6%ZiHPIpI7yeu6GWp3rDCiWx%m^QY80!BfxI>oXH*xg&hm!=% zW;r1Uptsx5&GNUSwtGs6_i3*wNxU+SxWCe@`I$Ve(l_IG^qRFCR$!HE5ppi8TU`Fm zD%Q#Stv3M;t!uN3_Mf`o_T}4>M~eI3Z0i_g2B3RM(pxh>m-FzdK+C1}#j?vu538|% z59=X+xX#}?+UknhsYk3gtGBKMHzPQ|jLv&+kAKVSe0v=3*58Y|8%JPB5qKDAko`+f z45^bNy}y_7NYd0_Z+68p?+r)yp9@hF^k~}Jb3^3#r1$wGV_&walU9y`%VxtmaDxuP zrCnQaquJMIFAVE4=8cAv@nQH4vYjo5&HB+lWOp#ekH>Tj5FVwaXjq>$0h2OvFzz<^ z?D9bIjeeV*hD_~O6JD1Dv^r0_ll!;AAo+A!GQazNg5Yr63dI^x5H##owu<%hOhKJ* z`Zs~w#Y+$Yqf)cody!U?6~cSpEt?+%@5MEMPQ~qf?Rj~S9P~6J8;jGq{r+qOO&rWb zcci*IO2Bk~KHv5juh5ffF=S6m;}dJSx=^tS+lXd9Rh_$%m%(SXK)4AHU!1~!T%^LIYdiozy-WM>x7!j&j+d!mh@iuwqJK0(2b_Qa`81_M3b?i5%uY|EdegV?T)CwCN zP6XBXy5y(ugOpO%NsJ3GL|{KUH7aWLQr#kSs@R^ne1E^B_g%|%SOoqrsi}?)7n4Gv zx&>~NEVdJ8%lAa=#p+cXTNGkj*)%OcfCDHWROfhdCY>Ab<0bjSPQ+y-k!rExb_}4f zQcKY>s&qXiE`gmbBcGr z*)RkK#Sgj`WDDpu)p!q&O$Tb5DO`ga`&wotP31gKr^%||J6EV-l&aDf;{+4_1q3?{ ziu7n!G`@25cksQn`Mulj6J@!mHreSXH;z9$4GiGZGp+{WCYfC@k&DbEvYF`_&;d{= z%Xui)Iv7;cD0^~WMsWD!{b|PoG`TUB_R5F~FGFxG1O5Cw(k~>2eU!X!ma@M-gEwDC zk6o+LsfX*-QHMW8Y&V51id8(O==G)n%hzagy3ul5t*%$8(qKuIFBlhR&;FZn;=kEi z7wdUXA5A#M(gnbuNF4NiI5@v@veMpc2ZjF}pI?(AX+@n?rLfnJk-qsza z>H9wZj!wqC)M*rYvrqT^U=oAL>7smj7xaFS$6_>Bi8!%_;j^EZzatIMs)0_!B!ig( zP6tDAlzdHg??xLscXVljF0^-11_?RN$ACB~QIMn~)OCBj20$L+eGRdBr?Ml-AeTa;2!glP&d$yNUzNe*Bw~jxc%2uD zVYgImIPZt7eF$QCZBWsBHWUt~G;F7PUtn42rVQBS37re78w!vkvqM_{F7b~65} zbo>S(%g+3E?`plxspYzbg?*C!M7d^wlQa_sDX+m)PEv(de}BKpWEQFG@$LKDX?oQl z+LckDp!ITlfqD!XMrr$d$w|Pz( zLPQXt{8Ptp-RYaAl;pBrskh{qktM;6i*qpigjy%aRxEcsQy`Pn?DcqYjYI8vxxwZ; z0}K;Eh8UOzS8wVd23$>C)2v3lK~@h-1|QjNXN5QguP-vSiv$owHtZw8j3*@DoJKL>_vck(C;a=UozKhs7b zQzwkZv2o3T9hL||iwynTJY zEZ8GrIN>^t<8xD)&;j)EK~4&b>ab2(5J#536FYs%5ukg!a1yR)gd#n1PfKVD1@Tue~BV_gaKpsKlyjJ z&yY96ojzLC(y_GX)#U7$=~;hmD8T#q5w|MoARCL4^@B?P&k->lc=?Aa$5I{tS*7Cp z0;YNWv=SnMAq#!)tz2lQjDwE87G96fS_0d5aLyCCTyTtv?T>8PYQ;BU_NbHr@dM~; zL;pNrdf5f~@aP4N_i!EZ5`@frJ00fv4Pe7w5y*}c^j!bZ5cf>b$^1cVbk7iobstwz zIpq)Oi4DUX@$X1T0NRM5aus02ySlK91DW}U{_{O>3Lt%`ac0R4_^Sag-(XavAO z!P7nxo&I=1joS?hB6m&ysJC{*2B#U*jS5 zp{~4p3YaCD3rp!1C}DHpq8>Ga?m&bpsXc}???AD8R|^`*=B|a+Kn5@6#9ieSN{l=2 z9@wkJ?-cA%>>=eM4xoM{G)B}X8|uQOvX?)S4iXDD_F*yCs6RirMn3q4`&BqtK$fuq z&q$MEHwc**)fhhPd3J(KpV>?&_g?`DJ_9MSe&lX*nK~<@X+~#)!@*ED10Sc-i$57{ zqz5iM1Phz9_&SMHdCtQ#}8U7WNkLsi=|lWlKLd(-VGYqy?YMfOoZOsMOm02 z(IP@Uqswbu4bN&S1?5I$n{QDl@|CrTBkQsJ_R{|tP1(#VA5ek_azE!QtX`GzM{dCJ zOcW8CE3sMEk8CES!cTOx$YL&?n$DSkXF$Zb2|@NZk6UEG{>kE8e%sktDU8S?o+t8X z=-Il};Rm#Dqp8gU&DLX*Q}--txJ#Yszs*y7{}yV)?84AIUwObZYU(}0b@t?#%ZH2} zf9DLV>Z6B-iES4~?8(9mH@ILGllrY{4Set3gePnyGuw)ukMOFoGpdxO+el;AVTa7Y zkysRSrv;w^(7-$t_78~idDairA}R%a$L_yMdqVhF$ad)|gIJjn(kS4%N?Bk75hOPE zMFdFEtM6yVb3PXC7r~wMZw-|-WH$8`e#$kB52u!z=PljMRUzB8o@zG4=?yFlXm7Y7 z68lUUWC>^o$1zn5IFbTH2-F(UY4xnRdjm-NQK?hDQx`l1!$s?Hgwqrd!}K2psgR*J zq@M&`XX|r4)3!fVPh0nFWjz!qf1yfjn$=(KpCWA@WvH*Zjkt&@lgHN z)kLJ0aF-ItNvTJ$Q`ek($e+^t`&a~|C}Y+{oN%J?2e>g7k$5zsAqjjkD(3Gr1{yUt zzu@LyBQdc9pZ8JKeyeiAhg=1jRBZebGzvfoL^yzdh$-$P#Mb7vG)-O=}t9Rivc?D5#7ZC z;~Z`KeE&50k+5sQ{d)~d-TP&_#`s5G_g;;kakMn(FGbc9-@Tqk&(0}+2rsTSO(?y^ zR~m^uM1lw{d@^=oc0vo`y$o^0asSj?I@DZLH77#Cqd)*|M^)<%!0>ODGVRkwbhcz9 z?sfWH0&}e8F4L49CPk>9SL`t%cO~POL;(FvpGJFPjrkJ+`6F8eMI7BhLp(HP&_Q6! z6DrY`CYqr^H9oM%|7YJj&ta!vo^ztr5a@C{&eQO^BVz}t0am6lj*RTNt#WfRoME|X z+?iRLVr0LI8Sk|b77o<0;dyV)QD+ob z1{>wOY{5a(1M=oBm%)JCG!Pj+5X_fkFua7c(6sMhyqi0R`Cw(=F{l4#r$+yoJaXQQ zJq6Y?NST%4bln|L)}J|oy`(vUaS36TyX6!+Ooi5?O#L3r-pe*f-# ztlWE~w{|{Ejq#%_D>?VtA1W_5)qaX1Q8XJv@OmSIMQX*qV|SAX9N8Z*36i!f$2-qoM9lsn1l0xwEVOx_k~Mg=8rP zE#HEE8MQY}12HaU;T(3tux~R?uD`$f#8T6ccUQXk5wF{l zP<33GhMx8ROx=@K`t?eT7c>u*ISU4rbxu|HOYW;bU-+#52@-Zg1P4^=Uu)8Khn7Wg z7#U0RR}Doh+0uFZA_rd(sxzE9cbNcNr;+*iS)VKpav1<+rnNW;-^PbnxLK|8?#+f(o)?cIkKB_o3L4ThAw zj0?dj%`z41LfF)2ONuDJJd~ppEacP2%H%yLQmfGmyo-_%g<}Lc5&of1;6%QNDjusl z>6_dp)8XWS8D9B($Rp|~te}|7F>7cMB02l)`WsqY>e5E`725PFS^WS81V=Pt1unry zfc-(oyf86)OJTgaitB}k__0ignvAIS@yKo_{z3osGys@{_Sg(}hmsPcU^qTP3cj zop8_HTW%8~n>=_ZM&qG{$40;*};fDPPLRW0)jP(t(npr;*0&{eOw>i7r+CT0{KkfU*I$hnaG zI8X8i1?CX|V1E_f4C`1LWOXbv+_(c=t?+d`IuJjVhGQ{n5J;{De|T2IZh-O;-CFR? zh=q1x$2U1OkBOogH(9)+#zgBSX0uVr5c%RL~N~{jboPVR}J~S zSg-HXe$7N(_Z&_kW*e;BZP~pFR?yj7n=I-lA`Du=$7+Rsmd;m2Py}3Y|4pmmiFahImNt^(F^CG=(z_`}Nv)t)60s!d17^*kqjYrn6a>Cc755vQ)Y&Y-b<7kGcFEobs8Y$+)oSo7XY8J9Ifem?9_2^cWL#jLv8NO_tB(`RPFrCljiQm6&Cl{@u+o^Fx#w>@eGa@^id1{|;=d><<%9Ps=7zC+@S!|1?o)m)gSqy#U8rAa9y-(*J^hvTBHObC^7<2R3S4 z&Ljv4+M}w|AM|6wL7$P4%yos*X zqZpktuswy^kDclNcUfrwkbfEyhJ!0IjVSvZbEN9G&dgBugE8MV?-S6z*ykzowvE7v zAl0b_?1yTh*T)k9|8=mSApiV_^pZulT zPvGr1SJH5AWk}SgD?@G_$#^C5MGGtR|1GFW=HeS#b=U>fi=0^X2;`|nMZ@8?#R6)R zMpaK>+_J)&Qc%Ub&Rb9C^X-*>Bv>=VxU~uXmn)V8{-!hiM<^ z?~Bef>L8>N-|C(ag^h1zb7q)mvXfzJ6va{s;5+9Ec8L~>CH=&jtzFP%f;4`NzNQDOH{IQ>j8`SBi;HSd^diy zFf{|0Q-4si=v%9hND*`j<;95YDH1(78S)M5Dr(O6 zO%c6B$t^0L&)DoYUk$@yND;VuKT!jmd2XBy(m>qoGgZO-nu1PznE-?m;Ckk zZ%(9BhnRH@g+gfwd<^-j1is0Lzs=eSaR%`}PQQin)@Y7OO>F`IJBX&h0V=KIUR;b< zUSWLdTUwm5+ESu`msqr6H3Gze;?Gz(20aQfbUL=AjoOpUkk%VSvoKQhehJDi35%mr zX#nc&D&O%_y?xbw=_e18CjO>56X3s|P~L=iyaEwxcmTW?CV zOkcIbX=$XJ8M-r9LEprJULxX?<5fe04@(t{Uf=T~e`h%0Y=YQevB0zm49WTTK;Mfu zFAzaF5+=?wgn(eOem?rhUUMNo}UabLvBU=!xEmJdIAoo z{cMz~MeLOG-Z-^!=?Y6rd9{-V4}Uz9c7=WJv=7dQdNpu&Y_4@hueRst)bh_m)0U5? zoyZCsJEGjaCQj2K>es=nHi)fkx!`F_>hsj3nzJlf~FIGu!q)|9XIS@lk?)E|7kUo3)*AYktHmzIRDM>W`opovgxA8|-nL2Gq zq}@mTe+wAT1v1M{TIa=`^TL|z^C#RZ?RZUW^mMorMu~SFn$OtN&pCDsnv4p}xM6v>ZnRq5 zQsAKLZO(vOu&<(kN^5~;{(P7{zcs13uo`&T{ZSI|1vM>@ttb7bBy&=);4L(*oq62 zu?nE2uZGW_PKeffwPv)~$F%sA6okW<^ZQn+&P@G^ z89lSR!fQ{&H7DL7QXiyOTk`Q7v-oeF8}g9LIX9bmwFLqi{0@6QNkmOZN7os8=L#Qs zdbfI8V?p!yN+uoO)ySYn2wQxJxfA?SnmCbOWGgu678v%fjM}MP*tcqfwE`h&9q6RX zVa8kSaTii3v~{#&Q$4N1?xFIKV^7FUfCsN@J@`pISpKGR#*GF%fBw#R&e-bc$H(T! zt6z7@YNIQqGx~WEd}ky6C|-Chu!Kpk+PKmWQbHQl6<+6uwjSJ=)4RlKd!7d_F}JKZ zDkiJtoi|onM!(1Y47KjEI!$!0>5A?0WbQDr&!om8gLCcwNM)-aLpZOcbu5+l-4Ub8D#xYRlp&MwY#Ybod-M?WOKTD8R15KVG)Q(D+TF$yLA29!js`Jm4E zEaP&w_7}R1hh)}uiV-M|Axc{)_UBCa#mSjDc8VAFwckYNV<)e;e&pS3bQQDKaB@Wm zyv^2#VbxQgSMKxlBUlMq7fAQk7pIKXqvUkaoUaXOM!D5NmrtVNBluokWr+92fIOLF zZvW`CoJu2(P#Er^sk3^Zu@^K04M5fjs{d^GwxsXWIN3ZxGGj$u8x&pJ{*Rxbly+!= z-dr8)!bkC-7}ftmgA=};_>LYFxiFk@)-S5yTfCNs>@IApad z@mkg;x19$nuLF9d7Bj-O!gAYlTP>T)fR^rnTH?^-pZ<@1WSdiM>(oX1zutiV8 zSCa}qSNA7=vybqJ(lHDVtG>*!PaaYwf9eb$O@laen`CdKKz&96-}&TQL_}`8euR4E zB-pNk1%`(wXlu9e>+6a4*jjzN&(hAEi0r+tdQ(^@OisVHpe$r7BK(mwtA_3q|5GE> re3vux?$@s<*`8zo008{`%*q3}bMV2<2{; screenshotting before it decodes yields a card with a hole in it. +await page.waitForFunction(() => { + const img = document.querySelector("img.logo"); + return img !== null && img.complete && img.naturalWidth > 0; +}); +writeFileSync(OUT, await page.screenshot()); +await browser.close(); +console.log(`[og-cover] wrote ${OUT}`); diff --git a/packages/landing/scripts/og-cover.html b/packages/landing/scripts/og-cover.html new file mode 100644 index 0000000..6552d1e --- /dev/null +++ b/packages/landing/scripts/og-cover.html @@ -0,0 +1,125 @@ + + + + + + + + +

+
+
+
+ +
+

PenguinHarness

+

Efficient Self-Improving Harness for Everyone

+
+
+
+ Zero-code CLI & Web UI1,000+ modelsApache-2.0 +
+
penguin.ooo
+
+
+
+ + diff --git a/packages/landing/scripts/postbuild.mjs b/packages/landing/scripts/postbuild.mjs index 2ce864a..1863ac1 100644 --- a/packages/landing/scripts/postbuild.mjs +++ b/packages/landing/scripts/postbuild.mjs @@ -1,13 +1,57 @@ /** - * Post-build step for GitHub Pages: copy index.html to 404.html so deep links - * (/blog/xxx) served by Pages' 404 fallback still boot the SPA router, and add - * .nojekyll so Pages serves the dist verbatim without Jekyll processing. + * Post-build step for GitHub Pages, which serves static files only: + * + * - A shell per blog route (dist/blog/index.html, dist/blog//index.html), the + * same trick the docs package uses. Without them Pages answers every blog URL from + * 404.html: the SPA still boots and the page looks right, but the response carries + * HTTP 404, so crawlers drop it and the posts never get indexed. Each shell also + * carries its own canonical/og:url, which is what a crawler that does not run JS + * sees — the SPA cannot rewrite them in time to matter. + * - dist/404.html for paths that really do not exist (and for /docs/* misses, which the + * landing router hands to the docs index). Its canonical is stripped: claiming the + * home page as the canonical of an unknown URL would ask search engines to fold + * every typo into it. + * - .nojekyll so Pages serves the dist verbatim without Jekyll processing. */ -import { copyFileSync, writeFileSync } from "node:fs"; +import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; +import { absoluteUrl, blogRoutes } from "./site-routes.mjs"; const dist = join(dirname(fileURLToPath(import.meta.url)), "..", "dist"); -copyFileSync(join(dist, "index.html"), join(dist, "404.html")); +const shell = readFileSync(join(dist, "index.html"), "utf8"); + +// Loose enough to survive Vite's HTML rewriting, strict enough to hit only these two +// tags. Both must exist in index.html — if a future edit drops them, fail the build +// rather than silently shipping every route with the home page's canonical. +const CANONICAL = /]*>/; +const OG_URL = /]*>/; +for (const [name, pattern] of [ + ["canonical", CANONICAL], + ["og:url", OG_URL], +]) { + if (!pattern.test(shell)) { + throw new Error(`[postbuild] no ${name} tag in dist/index.html — index.html must declare one`); + } +} + +/** The shell with its self-referencing URLs pointed at `route`. */ +function shellFor(route) { + const url = absoluteUrl(route); + return shell + .replace(CANONICAL, ``) + .replace(OG_URL, ``); +} + +const routes = blogRoutes(); +for (const { route } of routes) { + const dir = join(dist, route); + mkdirSync(dir, { recursive: true }); + writeFileSync(join(dir, "index.html"), shellFor(route)); +} + +writeFileSync(join(dist, "404.html"), shell.replace(CANONICAL, "")); writeFileSync(join(dist, ".nojekyll"), ""); -console.log("[postbuild] wrote dist/404.html and dist/.nojekyll"); +console.log( + `[postbuild] wrote ${routes.length} blog route shells, dist/404.html and dist/.nojekyll`, +); diff --git a/packages/landing/scripts/site-routes.mjs b/packages/landing/scripts/site-routes.mjs new file mode 100644 index 0000000..05f790b --- /dev/null +++ b/packages/landing/scripts/site-routes.mjs @@ -0,0 +1,75 @@ +/** + * The deployed site's public routes, derived from the same Markdown filenames the two + * routers read (landing: content/blog/..md, docs: content/..md). + * + * Two consumers share this list, and they have to agree: the landing postbuild writes + * one static shell per route (so GitHub Pages answers 200 instead of falling back to + * 404.html), and build-site.mjs writes sitemap.xml for the assembled tree. A route that + * gets a shell belongs in the sitemap, and a sitemap URL that has no shell is a 404. + */ +import { readdirSync, readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +/** Production origin, no trailing slash. Override for a staging deploy. */ +export const SITE_ORIGIN = (process.env.SITE_ORIGIN ?? "https://penguin.ooo").replace(/\/+$/, ""); + +/** Deploy subpath, no trailing slash ("" at the domain root, "/repo" under a Pages subpath). */ +export const BASE_PATH = (process.env.BASE_PATH ?? "/").replace(/\/+$/, ""); + +/** Absolute URL for a site-root-relative route path. */ +export function absoluteUrl(route) { + return `${SITE_ORIGIN}${BASE_PATH}${route}`; +} + +const HERE = dirname(fileURLToPath(import.meta.url)); +const BLOG_DIR = join(HERE, "..", "content", "blog"); +const DOCS_DIR = join(HERE, "..", "..", "docs", "content"); + +/** `..md` -> slug, deduplicated across the two languages, sorted. */ +function slugsIn(dir) { + const slugs = new Set(); + for (const file of readdirSync(dir)) { + const slug = /^(.+)\.(zh|en)\.md$/.exec(file)?.[1]; + if (slug !== undefined) slugs.add(slug); + } + return [...slugs].sort(); +} + +/** Newest `date:` in a post's frontmatter across its language variants, if any. */ +function newestDate(dir, slug) { + let newest; + for (const lang of ["en", "zh"]) { + let raw; + try { + raw = readFileSync(join(dir, `${slug}.${lang}.md`), "utf8"); + } catch { + continue; // A post exists in one language only; the other is a legitimate miss. + } + const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw)?.[1] ?? ""; + const date = /^date:\s*"?(\d{4}-\d{2}-\d{2})"?\s*$/m.exec(frontmatter)?.[1]; + if (date !== undefined && (newest === undefined || date > newest)) newest = date; + } + return newest; +} + +/** + * Routes served by the landing SPA. Trailing slashes are deliberate: Pages serves + * `/index.html` and 301s the slashless form to it, so the slash form is the URL + * that actually answers 200 — the one canonical tags and the sitemap should name. + * The home page is excluded; Vite's own index.html already sits at the dist root. + */ +export function blogRoutes() { + return [ + { route: "/blog/" }, + ...slugsIn(BLOG_DIR).map((slug) => ({ + route: `/blog/${slug}/`, + lastmod: newestDate(BLOG_DIR, slug), + })), + ]; +} + +/** Routes served by the docs SPA, whose own postbuild already writes their shells. */ +export function docsRoutes() { + return [{ route: "/docs/" }, ...slugsIn(DOCS_DIR).map((slug) => ({ route: `/docs/${slug}/` }))]; +} diff --git a/scripts/build-site.mjs b/scripts/build-site.mjs index 64c5dd4..4165731 100644 --- a/scripts/build-site.mjs +++ b/scripts/build-site.mjs @@ -11,9 +11,10 @@ * single artifact — one GitHub Pages deployment hosts both sites. */ import { execSync } from "node:child_process"; -import { cpSync, rmSync } from "node:fs"; +import { cpSync, rmSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; +import { absoluteUrl, blogRoutes, docsRoutes } from "../packages/landing/scripts/site-routes.mjs"; const root = join(dirname(fileURLToPath(import.meta.url)), ".."); const base = process.env.BASE_PATH ?? "/"; @@ -34,4 +35,27 @@ const landingDist = join(root, "packages", "landing", "dist"); const docsTarget = join(landingDist, "docs"); rmSync(docsTarget, { recursive: true, force: true }); cpSync(join(root, "packages", "docs", "dist"), docsTarget, { recursive: true }); + +// sitemap.xml goes in last, because only here do both dists exist: it is the one file +// that has to name routes from both sites. It is also the only way a crawler learns +// those routes — both pages ship an empty
and build their navigation +// in JS, so following links is not an option. +const routes = [{ route: "/" }, ...blogRoutes(), ...docsRoutes()]; +const sitemap = [ + '', + '', + ...routes.map(({ route, lastmod }) => + [ + " ", + ` ${absoluteUrl(route)}`, + ...(lastmod === undefined ? [] : [` ${lastmod}`]), + " ", + ].join("\n"), + ), + "", + "", +].join("\n"); +writeFileSync(join(landingDist, "sitemap.xml"), sitemap); + console.log(`\n[build-site] assembled site at ${landingDist} (docs under /docs/)`); +console.log(`[build-site] wrote sitemap.xml (${routes.length} URLs)`);