From 746bb7438e86d33118a820a573e7b40964499f1a Mon Sep 17 00:00:00 2001 From: Sergey <93754860+slaid098@users.noreply.github.com> Date: Thu, 30 Jul 2026 23:31:02 +0300 Subject: [PATCH] fix(draw-image): use density 72 for 1024x1024 PNG output (#161) * fix(draw-image): use density 72 for 1024x1024 PNG output * test(draw-image): assert real PNG dimensions via IHDR decode * chore(assets): regenerate cover.png at 1024x1024 * docs(handoff): add handoff and ADR for density fix * docs(handoff): set PR number --------- Co-authored-by: opencode-agent --- .opencode/draw-image/render.mjs | 2 +- .opencode/draw-image/tests/e2e.test.ts | 4 +++ .../tests/render.integration.test.ts | 4 +++ .../tests/render.optional.integration.test.ts | 4 +++ assets/cover.meta.json | 2 +- assets/cover.png | Bin 36703 -> 25458 bytes docs/decisions/068-pr-161-density-1024.md | 20 +++++++++++++ docs/handoff/pr-161-density-1024.md | 27 ++++++++++++++++++ 8 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 docs/decisions/068-pr-161-density-1024.md create mode 100644 docs/handoff/pr-161-density-1024.md diff --git a/.opencode/draw-image/render.mjs b/.opencode/draw-image/render.mjs index 1053d1c..cd2552b 100644 --- a/.opencode/draw-image/render.mjs +++ b/.opencode/draw-image/render.mjs @@ -33,7 +33,7 @@ async function main() { pngOpts.fontFiles = fontFiles } - await sharp(Buffer.from(svg), { density: 96 }) + await sharp(Buffer.from(svg), { density: 72 }) .png(pngOpts) .toFile(outPath) } diff --git a/.opencode/draw-image/tests/e2e.test.ts b/.opencode/draw-image/tests/e2e.test.ts index af0143f..65af9cd 100644 --- a/.opencode/draw-image/tests/e2e.test.ts +++ b/.opencode/draw-image/tests/e2e.test.ts @@ -28,6 +28,10 @@ function assertPng1024(filePath: string) { expect(buf[1]).toBe(0x50) expect(buf[2]).toBe(0x4e) expect(buf[3]).toBe(0x47) + const width = buf.readUInt32BE(16) + const height = buf.readUInt32BE(20) + expect(width, `PNG width should be 1024, got ${width}`).toBe(1024) + expect(height, `PNG height should be 1024, got ${height}`).toBe(1024) } describe("e2e — full CLI render", () => { diff --git a/.opencode/draw-image/tests/render.integration.test.ts b/.opencode/draw-image/tests/render.integration.test.ts index 8b4a9a7..d513049 100644 --- a/.opencode/draw-image/tests/render.integration.test.ts +++ b/.opencode/draw-image/tests/render.integration.test.ts @@ -41,6 +41,10 @@ describe("integration — render pipeline", () => { expect(buf[1]).toBe(0x50) expect(buf[2]).toBe(0x4e) expect(buf[3]).toBe(0x47) + const width = buf.readUInt32BE(16) + const height = buf.readUInt32BE(20) + expect(width, `PNG width should be 1024, got ${width}`).toBe(1024) + expect(height, `PNG height should be 1024, got ${height}`).toBe(1024) expect(hash).toHaveLength(64) }) diff --git a/.opencode/draw-image/tests/render.optional.integration.test.ts b/.opencode/draw-image/tests/render.optional.integration.test.ts index 111f1ab..57a3d41 100644 --- a/.opencode/draw-image/tests/render.optional.integration.test.ts +++ b/.opencode/draw-image/tests/render.optional.integration.test.ts @@ -31,6 +31,10 @@ function assertPng(filePath: string) { expect(buf[1]).toBe(0x50) expect(buf[2]).toBe(0x4e) expect(buf[3]).toBe(0x47) + const width = buf.readUInt32BE(16) + const height = buf.readUInt32BE(20) + expect(width, `PNG width should be 1024, got ${width}`).toBe(1024) + expect(height, `PNG height should be 1024, got ${height}`).toBe(1024) } describe("integration — optional subtitle and badge", () => { diff --git a/assets/cover.meta.json b/assets/cover.meta.json index 07981b4..2c3121b 100644 --- a/assets/cover.meta.json +++ b/assets/cover.meta.json @@ -1,5 +1,5 @@ { "hash": "de8036758f935399699ef01446e3b9c8d23d211863dae052932d0ffc1a298120", - "generatedAt": "2026-07-30T20:02:11.450Z", + "generatedAt": "2026-07-30T20:22:54.851Z", "size": 1024 } diff --git a/assets/cover.png b/assets/cover.png index 6b3380d1c55934cb923da3370b4aa6abb9a8e367..acd763f64d479ad959434ce5830f6c87af9765de 100644 GIT binary patch literal 25458 zcmeFZ2UL{HvMBtKa}tmoge?jvh)B*Lq97n3L2?jLG9o!D2ndn{C4+*JbIu@X$Vkpv z81j&Z%-@6ioU_lp=ia;bd+)6O{cHWK0p=^JtE;QKsw)le9x2HY;nU(n5JdFg{#{iF z!US(IAzWf(Xb_-)K;5JT(L{Ll5psJ$4>jn>>zFH=R7#@=R4t zxg--o%F!ufOfS7H`3N81Tsv9=KSRs=Q;g%Si0p^55u$7yFXSWV&<4IX^>wJKaz;EN zXD2A~IVDJr| z-$ne-or^!r{#Q8vE5!eTp#QDs?+;7;lW+e5=YNvoh1~v2K~t|Kw(H~UHn?Fz=n#0} zLJ%|R+kU)A7%M3mCxog#@1d006LG08A92Ur1=b!~O`k zfL%<5LIO{J008hWz`vgUV$?6n0Pmlt|4#r(=vKOso(mTm7bqM+HFtN18Ngyz_MtL>HO00r`imFlW2%tfKpfA(`Q)k9;3Aa5z6-~Wyx(fIq{ z|Cw-OB|3`#zl{g9{yrU;;_uS|6hd@TqU`|2*H zGtzsai8^0~f1TYwO6P*zKS+mm=%WLcHv~c0eE-DmFX{b~&fnzlhee>I_ebemNcYdM z`$y^gZQK5v&HRt-{+Z4E8)N?syZ>)&=D+Lg{fW)|yHfnuXYc=w&HQ(rz26PN#o76r zW@7XC{B1gKsV-m$uu%GX0i{H#74-n2IZ73NhsL4|1yBnV?z6vZ9&h@S|D@dea68Qe>dkB25YgR_;+*tU+p4wWwjh?@0cNPFc_Sef9)!O z9lgN#g#@1d006LGfQy?NJb@P=bOB?&c=`pnfC0cS*u_);=@$U}`~~>e(_bKYqiD@g zp-$~T5B^U86st307iRK*<7&o)B%$A5X#c`9z(m1-l)($&0gMZz{{TSy9pj%JlM5s; z=+~$VLICgAYykFO050YMgTSBv=`ElJzQF8%B6|9dVZpME4viHb3| zuZpY}_ZJ3nRbJOAwS3~(uP9)erIFLVC(dKk;CsHqCvotdAS|QJDCCf#uQe^5~QsB4NF-gUiz_tTBL$ zwQy@5W>#d@)4kZ8FzzCDQ!D>7tG?ORv~nE(&2gt`kD>&}u{K0DR-)(8@|wi?p|W+| zK{NNs6n210$~_0aa+6T8eXsNG1iKcR1h1Cq@)%yTXE;pmw)~E3T6Nro0yWEuqj=Zi z!*cX%gpc78=TDYAjNEwEY|i&gx+qVDnT~$=iwZD9l6E}LJEM76bqb$8a70cznQ_JU<$)D}*t5iB_E*trqnwONi0&K6^jSDSqP&*=S5ix>Z@Znp;&!W#C@$V|4}L zb?&y`${PAgUz31}YpsdDP7}@h4y#Zol4V=Pitq#2t@p> zUEhOi4z-EXt3T`pROC$Q1gvlH?GiwMr(uRyKco_7lb7nJlZN%(a(1pb_a@7Z=2mU2 zICX_>`3Y2i*^$~B(y{3}18iy>RpQqj4dxXRa97*-A_ zqi(0#{)(^lh{W&i#pl^Y-R+*|$I}AamH2jdZgcg%TbdCfg$}&-C+vIHz`l0qdmb*_ z(+|q4Ju)wKKiuv%wf8*tQHbACfyI&mzsdXFT2YU9f?9YS?W0l&kJ}-U)Ed;5bH#IJ zi7u&}d0Cr3Bb1-bw*>F#;zH6BUNRGh@FatdQ303cX|0~-nOkPv@nb{pP;TXb_1Vdx z-sz(OR;8E;i@X}qYlML*?+fPb%M8?J!(@oJ;&CCFvE%9UGs{LlB0aC0EmI7Mp|25p zd0rkls}E3an0bxo`bt)wb#qwXyt3Dkn%Oy84+)0)8X@Cq`OQ3bG)aiMeU6w<-C&*@ zBDC^naMa3Ky?ZfGgyH2v`^OyGv$9FLnE=+1O;MhiCja7tu<6v{2(WpCbjfkn`tDoJ zIj%SHAs-oC`42~tvoO$DIKWLa%?c1GYHjW1cLUp{?^q1sq=tu~j#18l@-i(St!E znm^2o?K;0hjtl+R3J-PvT1ax+jzfpu^l(M9E*7Pa_E|+XUdQ93iUO05M{oAbVeE8^ zAl52UO6FvS49>Qr+WmY15A4Tn%AS1{vd^khm4NLCDgSiHX>cfSHXR~}{?Tb(2_;K; zpyWEsKN!6_G_tkAZ$2sfO{d&;_Pmyn>ZW$2p7ZS61lFoh@uTOt&~=EA*vdA@%fiRexapI{hH0m<;uoXfB*&e2q?C$$ z4CWU`PS`7u(2=#>3uV+hGJ98L)-*ik3A0BnoC9TI`>t@k8QYv~R5+yv0#bupRynK= z;Xyu~jBaZx&UzlFs|Ls_3{V9`)*@@TU3>}!-_$v8&8beGGa#Jh_7CPmuX)|7TKJ?> z$UiC0^QP%N4R2vesCXsonV927a7v+-+Xm~{TG_N`f)0VlK_kg2Zs`@kZRF z9gHCu+qn;YhJ{{d?@=#0v|unz;3TZQJ0tSwG{r`I9%@jH07RWJ&6enZfgYz zNl0%D;#QAQl{k!govH0lmglEPglKJfR(omQF}NQb6N|p@3UKKoP5sVMD!=2Ywbw&g zcMOCo5RBz5=yUySs)fdROBc3!EAd!RbPpIofmpnDAYt{~|tFl|vu66{5NI9vD4En4%*J@a%{5@2nGmhU%K7^E~iI~$l zZz^$>Z)auQV!d?SI$>1;Q2|fvo*5dp^3=%D9;8{DBY7&2xH@uUDc-(&`Gnr3r+TE? z#op}s`*Q=rFozRelqL{JFnOHb;IVBU!GsM^B7+s#i z`E_d&QW@r4Hw0bj%$%EU4empvG6wF@d0$lRz^DWZg3Xk>dQ%$iF=wB2tjljbQEtgR zJ-XO-E$N}BSj!HzSJKMSA5;pFCx?L;QBva&qN0^y+*a8C@w9S-bh?w^Pq52T5IJaH1#ClNV1oI9q{ zGuSHn>2Xq%(M#DAE|+(0Wq{WlAion8;p|Ez2U7{4&Y}9_0ciy zcMHzH@2-E0w5Ml!KZo%whUEKrAjt=yzQ*4q+}vAl9NEXaZ?}oipJ?XkClrUw*)==t zsMBMuaANLJ6@^Hg9khh>J$5MO?sk1Q(>FY$d*_X=)1*TJ>qo^CE|!2@`gb>7@%q#? z^#<{@tO%g3DbFbvxO(Zbxg{5kMNzQX?LFkgr_opTa!9>AnU8Y!SsR-Ds$6i+L`4SW z)|f;bEx(0UEFCvnl}(m5LQHYVEpGREVS6V#J!U(LT^%)L@}Z%gyD54VYIoi5(GC@k zL-(>n>Jnt@&Zp1z%er?i_e}2_>|~@Ra_p8E=P@L~!D030_S_13;1w|U;k96JRd50sqUNts0o#<)UeUOXc7?RQf>$1t#dJIwH~&d4uOJ5ut(0jdu5k49V4+7qHLz zupaU>hqOd2(qSqAFv({2cJ0O%T52F`&$#fxj1Q;C-1IcrY6o{+_3l+7Q*=o}J9V$6 zVAiyZ(1ijRqe5-ay>OB*lb6k2H<9(FZiF*NeSYk+E2Q-L6x`@~%NAFZiV)AB0Fphs z8rR7lx%fG{@Gt9SM!_j78OP%jp2zDLizU>^@K8AGUO{R+nJw_P9QdH7n|>Wrvd?ww zmL;c%82w$LfK!#Z{ZHy*xmsm*Iyx6i5Oy6NFVQ}Ir|7eCRKe)J`Tc7Wd#-)92ow)o ztG2qL+9s8w5%;XX>FJZh9Zn(&mnM6S#q>w!P=Hz{Lt>M4zwhZIk-19T9k+hVM6J>Fp0CRuL8^E z*16pYuja=NB9l1z-LrV1CK_x9Mx%Er9Hush8jHI-g>$V=D;u-yXw2A)krpID@i8j9 zw1=`mm#^Y{wzhPSx_!cJP+M(bk!^+uzg?FlsPnDcf!x?6lMO?1LZMoxM^3ke!in59rbj}Xz)7#zm)NwUHC}nqoiY1WzCcAQK4k1J)LPZ( zPGdKXVB60xpY@}|ZgRV)yV1MOciH&b@&GOko!`*uPBV?5Y~FWeBFIkQjkN@mozrXK z6x+;zLH5vVjsZ5u7CbCk*)japjPu*^Z0M5Yh(i~@wlB1haa|M}7m}m0pb!GF4A$2~ ze(JR7;nSjT`Y>UuvEngwNj2jzWtz`%;_G$otkUdgpYiPC>REggC$za2j+>MQFlJ-V z`Nah6h-`adq;bRQE|az&Rna9a69;!+oY#yxp8N1S4_8fnJkeSii!PBL*0oo(7_yG1 zp>kqO7lH1WZvSjUuXmig{NC!c^EZ_5kLH5FsH@2o$Q}tcUG5%gPh)@XG~Bv2UOyY&#p*x6>JqF+$Q) zUAc3+tpi4ub*K9lr=C|#5o}}@>`GLj!;bOns*XjK)WUOT`z}l}x+1#Go@86G?{u(R zdJG1whQKOx6F<26x&hOc@QBJnL?5~3$@{D`8WqhY=)|SczX%qQdKi7{3!eqQ(t^_A zq~%y~#-3Ldyfa44wWfLcy&x`W9?V(q)8r3cab=2{J83y+hNFe1==J<_pX$^tIq1D) zR-6$Oz%lz=Pv_dcx3+a9Z!HAj(zN*m&OWY==`E-y!5=t&h9rx+bp!aL1E*#4`yMV{ znp@FowQ}*@RAq##2n~s0122z0rk^ZPw;xkNb23(z4VR_nKl>1`(0@EyGn3F+scf!} zszwDLZH-#J$BzVVniB0EY=wGF;7N;Uli)Bq8}8R2Jh%_j-F+;!=oj}OzTC|y*vZ=8nhI2ImqoBI! ztrOU@Z{uE*g8l4%4F^@9D*bvS&QDq}Brz+Ty@Y3d$qO5aMY8R2;t!C{jB3cWcb}(+ zdl+VUrpijGqFz#->XTh{+AVQ>igKUip@7wGwWOqSiWLik#an@kg+m8^Lk#%Q`&uY4kCtCvXXarNjG& z9+VaSj-wE%9C^3+@tVH!a44L^@qsc;AEW2N>_nNN!r|zS=czS~fVFNP&a!jD&zj;Z zo=zFaoF2E8tyn&b54E1|l3s9;>6q03`(6u8HH`Xnl@xp?si2bcWo56*AAX_i2m{%k zvy6@pR~0@a`U_$tcjduGl9(6lYJl-OO&pur(T}N&u_Tk-}QY%oBAK+v;KB`vv15mrBV7Yazxb}^B0 zFIRp?;KI)@dUh}$Ve^fjT7cl@w(#;HY4l4x*|puN>EV$6akW~c>d>KfBks;R@dvJ(yqund*6402HLLXLm z3NN(II1K9zBZj#u30|Gh%VNhGUA3m8VXE9_lw~J}0^JQmS5wtO?(90Qm{O)a$ki%f zvDmRXpL;bDsk8S@cPQ*-#`mne+A+jB?fmVqNH|;aT{R!GaOlx4#U^!eAbreDd#7;R zXBpF3L{T|SH|fWZT;JD7t1_x#rlfN5X_Z;)t)nq>owC1t_hjN*@;lNy)EM^yuR__R2aOdez22gf{jQ z{#yukOTIeHe|Z2RrNgJJSvRD&<392r7Wx_HS!XRe6bZ8hA#mxwhcWOhAB3_i)hHf% z+I6YB||DHnUzZgQR6bmj6f8-<6cUAuFPB9WT7ZHo#r6DN4Y|7%-S^iLuv z=<-dDlM)k?;@hjAmrNm4+<7#nIO%nDN)zlr^?ZnkxllxQ-csDlmH`ljY3e}bAkBm? z5i)Y^YpwzzwmlS3bVIdU|Hcljyvrm$D9webwX1w39eypawbQ(D#4cfwxcV9>Wm)ZB zj%q0Tf`*43`&}CY#C8WacQu}tyCnM#wML%4p2D36c#z$3b0myB4(sx%zTKgW-mwPC zS<3N93J~K96i+PAc^ZSL0D)-3wEgMPo`-ex@Bo{km~l}J=xLd-`@D4Zk||!Zs|Ju{k^$6Rs2&j zF2_Vi#;i@k==C539viWB+-|#uJa(A(ag5{6dSTvJpr$1BZ20O6rAFGxO&zpAxZ(3ImIG({wdN)Vx|BC2AYe3Fdx%HOK#7U^n2*X`wP zS2DU#PDq{scZe&ouasfm*9RqpJsEN$%|DjMchu4MLs!@@_2 z@>kgGA~_c@`lxC*-;RZfrV>Ifh~Y$9Xj`{Ke~&DqHso$9{nd3pQKmbR&US|oJU(J~b_obSdk-!zG)Kxpdsj4_6KaxtylD4r;#zIq_|mrtZl_yV z-qkXP`_H;!)yI*QKR+mZ@RYM_Q5c_MGKL>m>#;<>WRtbo;x?5CuP)02F5uz-{ONUU zZ&JB>HP3wluJX?gd9d=g7QL{oCUWJ-vs!jUm)*VsLtc2~mufj(^Q#cWah2#JuB1yS z@hwi4x;Z+Af_V3+Le)Q=h0f_4rgwn}?3v=UT1_5nIEE>2{uc^Jb)Raf!a;S-b0QV# zFKETTfNMS>oY{#C=CxAw^siYbPJB7+j}qDLrG)BXfi%WJ#y~V?2ksb&lUb5)JqPBE z)2n7l9;LfTvAFQvERs@>`vtSuK7UE9>JQjGPlVzU6F*!QLPvenxDXw4Nx9d7K~a5=p(Igwf&hq zonO}9_wb?_O4dQP{Lpcf@HyOpQ=Qe4h;D-XFD|;I`y%_1g8|5qs)m|^yU`jW0D>HJ z3XEm@{8L6GZ~5(IdL>5AO{BEqbVow@hTq?)hFW#S^1ZEtgG9sF>ab3Ux%zyWZ)S%f z9%KYk*AG?&Le@Ujyw#`UpMN+RU(i`!`=UQpzi#ShTLghuO?fpNV&Sg(dVHb))MPuj zt1|PNBV^8Is#<7ykGigmW4|bU`$K%Bh-74qhgCa`i;GDr^kV}Gb5$7XO_94M@ky{8 z701+{7dxf)ynO7ypTKl{&VSdzHIqt~A={v? zwnXCvKD2(S!GOcRuwFK;l!+ke@EEOhu*gc_G)DP73)3ecHqagE`mFT5b3Rm}cAxLj zK!E|eWI~BWlE;2bDWbfR-PF`d>za=+0+xzc7MOBQ*|t$NxQ}MIi{)KFJNeLi|FsWA z0wr`8P4er#RM}h!e`UKLPqWkk~4k&ws_Y!2kHg1qWYHs(!{9vdK|V69Hi=~F4-kW8Ge`5C&! zhv^N@c&!HVop7(;-K>Ea8+<4kV!mB(4J~)Og-6g5V4EkrWxDPsHyiCq04WkKQ|UW@ z*^8pp!^@=Q6J+y|$4I{oz4szg?b2)j0MM1|WkU?D%|T&3u>=Y9+|w;3sV{fW50cKRZ?kJp~T?V{tB# zfYW$o5a%j57NG3>3$O(~24+-(q~wP8_*Gd?7aG!3Tc(5AblnA1>1=3zgz z_ZmGoWF0JQTp+Qdjb!GJ6sq>i6gqOiCmF))U^7miRuLVzdZqB`$dp|Go8owCU--AK zUHZ!3d>wR+F}*>~Rlsfur+4tpUb49ezvLR@s#?}VEbs3GkgnQb0fs6g@M!5gh(Kx3 z@~Mw`M-aOAeInbTSZcZI)=`GM!YYuF!f{G;+o-r#*U|kTA)ycS!x|Phmj*O!W^0#J z$rHb1fca6`N1QC`qw=N+p!VUSyJkq`UWpHR+v{CJit4-%Q6hD)FLz-aW$*&7l>K;L zGcM#SIV|KvxWth`=s;Aj7{@RBE}r6hwyR}Jw!Eef5gq?P?XWr~q?)+0`^;u6nTKRi zQ)}iG2<>GKL#jZLh^~zPHx5j+kFg*wI_0ifE1YDdW^l! z4&m3--(>B#b!WwTu4JSQRZg*?L$@?=sZFkW!~xZqtQE$1zm;@;0@vnV~343Q?q4LeFSNbZB9jsr_H=}N3P(l>G% zglq|*?1%(&X<?GL1n%&;82US+1969u+-JsDx5NE5*+g)M!9? zMU6xM(Qw*^;MQ5*2lQm<7A<=H8!Q4^G48sP1#WVY*VCf=BPJ1d*zq5k_>+Af+;w>j zkzzsN*rYAfEyncw%q?ha6tpr0D;>3jnQw<{hEmZbSE%!g+POrz@8f40t>tK!aI_WV z>hwcgHM@OF7PCbTMA9$2>}s>t2#vf|^YK{=p8M;d8T$8c^(UPb54t3Y>#W@aS?fB* z_em0`%gQB*dDLi-b?xEe7+ju?-*Guc0EgaIa*mm_S2DA8WiZPk=&3h3zs8qP*N6bZ zA)Y2h$7+xXiikf)g^dAJg1(A~7nEqsiV2xJBJaPKT8)1o-5r^ho|&n9@Q(TP^K&aY z4K!vVi#*o&rfLFG;~3U~=GpTszaAJqWWuLizkg{Nc%X=%E-WYI_nD~ymP!fG0 zj7>~)WtvkX`#wEk!!x_7=dW8`)Y~M~p^*xLXsRnbgiZL+8Z8SyVSmMZtGZ&aGiYq^ z+poyufM&{1=cwwQ$4kq;7IoZVe+9GmnsgYx>a|!QH}kVrGo$x*C%U9|iZ3yLqvLrN zF0e`}mNwDdbxLJuSl+#4&~;o9OM6fi1@=lC6C+pY%NMEFy*eeN)9JX-TK6Dc?^VK* z8xw9nu0w0Sw+^apmsQ9hZ#{$&59FfM4Kf<-$k({OMz79Ra3RSfnaSPvo;+*lk{quD zMwb&U(LP(td3y%hiR$1(YMRjV6phD(UZB3n*+YN@sX(MJ@LtlzX((>sNF25)b7)f8 zC_wd+2;^Q4xEc5Px{@U7c0#>QWY7$8u^{G&Ql@3d=Mv<7#L&o0JoU2UW(h)OGBSpv z8tlZ!;1OZ4PK&3}Ln|tzkhgTY13@t{_-y0d7Z3Mcc#)a8tYB~0p?XjfI~I<*ugG%y z!NsE4@Enulig7II0-Z|+>1skC$zBPL7f7{AC+%-VUogI_sxOLLV^g^8lmKUPf@Pa@_hgjOyD+q3aJ1mr_EN z#)No>dxTfB$L8~(TdwHP@;k*UfxVY!{UMycF1ofKPSI4ePigN77E_E_>{J4IQtP04 zSU9ojwlja0l8uHf`k!q$)u}<$(rC=4TXW5ATwB+H;rzn?{0kmVM$WP1nJCt+;}=cD zst}ZEp?TW1%msb5f6Z|k!62(52i>pO8`9w%eDNHEA4f@atLdJ}1uK#q=*8@!;nLFD zO0FlF;X+^En&+V`JJaW}J*Klw|JStCzO*|Z*ezK3?}K%*DP3JC&5ii<#!BeiC@voq z1HAegN;2dgee*hcMGp1tJ2_3*!M^_ds`f3ucIK7Cb5)g7RPRGQzRBE7>G}a#aPN@N zT;~nla@&&wpUY66`Khj2gf))hXI?A@m3vzZa=uijtvKuV8$m0@r(#))muJ~u5TKAJ z*&Wpou1;VJ3^fB-|Kaw+gQ%#~W>ym6rO&zZ8aaG2<_V6KK!d_am7l|;ORQ^N#*=!D-=+qKh`h4*7103(hz}q3_3R)#D!6bB9Kk|{JQ)yEv{GXyYxz1>OZde#I_@i^5D>amgnk} zMw(vnxoBW1AZ8xl<#r8 z-+s?SUw^$*f63(W3qZ?4Vm@@hLtuo6CRml=S&nrpW6bFydPwY$`Nr z-fj|ap%svO&{xDs*s^PREbm89S2<-any6VOx>c$E0j35My9>aPk&aJb+L#ogCz9w zg!6pxJ6rzyy)Q=$yI6m^fCNaJ@cGedUTCPnQDS4;)9%f9)#x>F&CAn^(if+M2%8(e z(a>o9yx0n2j>Nn4wo}F01g!J{r1L#rNlBGXL63)1E=ZJiSG(+_X4kVe;-O2akFNoW zPHW%{-o@sI0>IaO57O?;v1hpIAqhb5z-ZJCZV;hYg$ zzcK=}k)>+1df_wJJ{MaRBg2Boz|BKOMn>})3WI;{zeH~2{VBOPlSn6oA9zMLr$|tL z23TZd;cb_pqqfz&x~Ui*6F;QP-{*Roi4WZ+NA8!}0xx5)YYS{R zGGaQK*DS<@>mQUy>4C9L01Va^0afkRv8fq61olOzWA+@BFGpQKXNvIZa311@esy^f z$lMCo{$dI2NA?=3@q{%*Fxh_WMVdoy^>%wV=&Ty3INcv7hk1fdHN2CUIs*{cn}KL4 zVYJ9hMDH+1@^8HWj(;P>!UEX&_4U6D`roJU_o-2DZd(G;Wz2WRmY7++vF z@Jg&WE0`(?FGp?(6@%>Cg6FegJ*V|p3!uDJCIX5hUgsDsh~tThYr<@|uO0w@e1YuN&3PxN{!5%8na&TSLgK*jZCyfT<-6@*T{nM%*1L!Lub2*7<>-q( z9e`_EqPtg)X?V=euD!v;YmPjLew4?!`|VDCZoylrC`Oa+c$0W9GIzMhxrn%bh?t}G zhMD+L?-M=ybxi?`c9@bEgA%?Ww;zIxv4J66=IQY*Jg3q^&BwZf0{Ni~St zyx{XwGYPlw`D zZD}abRu#0T2R#P#6fgxDX^C3!$w;@ypE69EG>Ni@mSBxrMi zds^r$PE)VMTs{<$Q2i zen(&<#Y=bs@)c-A8pkwS%Sby*vj;9VJ7~u%CdLMN)<-aE4QMG8lI%W@JR&o@99DLy2_3m!b`Cutxz9nQ z9Q-zfF`mxlq>&lwj4qg`9XHo$v(vJO4e{9787a^RW9%I&YT_$sS*Om635F%OK(Bmh znxb#P1(Be2OW@sp3?Z8LVN%EyE3K@1l)9D)H5!>A2CKYOHUjvVJc-AS9{QB zYemzm6K;r>uPKt*;b-5tJxr^)&?vw3Q=Q9F-9(7){us)gH?V6SY*XS%syW>+(L49^ ziOI2tK;}R_cdLQ#hJmLnP-gyDr8Zt%Wz2KI0#jp$Kd-^>T!L;90-Lsz0Gg%?SEon6 z%plCVVh07#ARk|giF_k_cm-TPar1ascc;1gHO{`o%QrlR)=yIu7JDQzvgokjA`j6a z;-+L-Gq#=va%ItJKF{~?PdCTKTls2)mbn>B9;z8e9M*aq7m*9wO?V(r6Np-9S>(E>DFUvp^BJL2=h*^5x$Y|yhlH%wj2^o>?}|Kzo+!BD-`t#0sc_Ni+o3pa}QGe3%%a$0hx&!;noBn{Whk4{3vga z?uSnM#O5_xn1g+fv$*lJTs3F^G@%SEBecH66E4E-Sc4eK86S93zWF{egbDrOpoNva z;^JZXZXYMOdL#smLJAne0!xgW;o|u;#P3{5KuciX(;Q<@KNw3*&@rjbUBP$@O#=@r zO?j3b!Kx`^0$tnh&0+}Fd8QDf@*%s>LeQym$ukN0^vI?<%6RB4XbZZwht=Q$JNaDO z_XaD8@%#qjaCgKWp7=SK17!zA(IH=M2YpKyc>!#_p&#uSGIvrX+@3#T@H)YJcO}lO z37xwSq%gm#nqknVtl1P|=q*rF|F*`ZP6_fHMcw+B??&EE$q0eGw*twj`>{Xpti#=P zOV|3grW-RJB%qzq2|Fg8t`&YEgUYYzl)Ce}hJYSTm$y}?vr1-Ug4nMV0{AwH_CyLd zv-MxL1rAj~hUxxE^RyCs8Om^BpDVZ+89y|!zf_sp3wn4;^Sm?5t;t`n;>e){*OA%) zv0!(jnsN=Phi-h9epRL5BOZ&F_)0rkK3otkuHcY*%hhr+SXNZo~LT zcddJ8M782NSd>AJWA;{${lspYJ-qW&LKSm3XjZ*pr>-kzG?E1J-jGIVWTo?MI9W zo0skAph;mw^JW+&qGQ|;-K_tF_m<%B_{J_e7rq=w^1&n0vAUULNM8Fe_+(#sgP|!4 zX%+q~ns1>Bp_+dXz$wxq=rTGv#*&hk3E6h>4J!w98saSZ(bb-Ouc_SfadVu;Yz!+{ zhv_!m8m~U26Z5*ZMp)0)a|;(Dph^^^qrv`x?Z0N2kok%y*tAwG*Dd#DXp>fd$cTzUp*Nb$RCUOpB?o%4O|d z)LWvYSAFc{^rD0>T9Q`hX_WH(&8XpDn9%FTN>-~j2v*4~@GY>g#Vus_Mf)kD^g9#W z<}m#|VA^{he~tw>8+uBdY|ei}hr;nDt17wKR`0-DxWwUYx&&}7ax`za(CP`Plx!p+ z(r@~wN4;6YF=IFD6yNd@eJ(OU$i+hNscZ_lvhI(oFL^WsXGPU?d3((HrGt0a6(~FK zL7LXtQxw^0(ar8gvXk5uke=b1<1m)|EagqZ9?z@dL$?293JJ0%(fKL4SIWFcb)xn}XV$amKNh+{kZ2}0V@hRtw(%2-#_dzNV{ z2eMx#zd-dtk>{R!StJi_U-Fpa##p6bLoFQK<_)B7Z>{a!=@&Nz+5_=S6RH_n+C9iy z@RY}4Sp^?I-VE=k|dVHhfj$Pvsr24UiO0j zk^~cpj}R0HqDo4LQ|@%X){f;L_G6@aev97JDY5VcQ>WC1&hz38+YiX4rs4{D`@kX< zjy3Tsq)n`X;Udkliog2kpt|HHG&3=S zSvWsok;AOW;{FLjOBv>|la!Xrc)rf!fo*5f9Qd(9WNTKpnPye}9F16Pck`+Et~SgV z6Y6tn;KP9BXuhK8A>7Hno?T;21iBFK&NK)0EXQaxAXpdQs%W`{Th(BCzf$5oFHd@s z79qa<<$mK%T-gvwB{n2|x0J}1TZ-(;25-`n$h++eHEHh*ao>6@(kWT)KQUD7xF@vL z3B!e|acD$_(m_v&m-KH~Cb5(5%-Z=~v`_&$#4a)#kW@{JHG3?k8wZpbrO)@E`uw(9 zLmAd^_i5XxAiaWQbOma&FGVU(aec9Gq0st{l13t#jP>u?+2ZIYp`jX~0QmPKvexsT>;0o%e$I3JlTueM{2 zl;CWk28|UYoGgfv)q$S6DmlH3TH+Cz%f4=u}&D4N5i{ zx!QS8x>&Q-_TKI(309+i2wRBik|g|{eZfqmUsb$fP<{(8lyp2n^@bGA?ZlQv02#kn z)0XuJc%;jD>5&W?G*ZId-UhEBuWby}?k19kT62l2hfM-0Z+~)`Z=_!a-(@BV%{0rGKj+WmC+sD1 zqPXRlenO`h`V!&zr=wM${nfrW| zl;hAPDFZO?_xPMIs_&Q$3qA}tXDOe3xOU@>tHJzEC>hjn#BMb}O!Jtoe$(Ss+tnyJ z0*FHKM(A~&{*U~(V$da>6L6!|@v{~OPw@KVjfG1#^@)d1^s$7B&?S4>r0`1~7CK;H zvqQS>TY^qpPx@fFCoh4;#VqSIr$?#9ivksphj`f$nsLZ=2}3xTKh&EuCLTSgem znCshVBeT*Ql!bRK;^p24Y>2y7Ux%8$QH?MeC9% zE%NO;egPA@IzbR$$azJm~e?3!3*3g}z2_U3zJG&>^daOxnjHPW)V1H#VC{ zX&)9_-RLM$l2AxL0Ex#hCX)j+SFG6XL3ACb0=3NWV$TGJqNq>H|R#>`nM5WHxd!b~9( zNWY;!kLJQoG06v=U6Z#oy@?;HQ;Y!uoB9%e3o3h#XEy@6@sm`3MSZ}RaxMs0iyBT0 zs^c_=J-!Qg>nI?^@{Xo*&_Rbnh%<<4w2AkVufvMuzt@M|LWn9029QC8hkifdmo#W( zo}WDO`4TMDdWzeVwl?K=j}@Doc$XOq@{QhfXMZL%lpkz}JP{Qp`vfW3q1_1Yzp^ej3a%sK*(@9_^DLYo1f=vr%!_<8;Uua!DHG}KC?Q+)ey{TtO{M_GqDPPgD9ND_CPapMSk}p7$#h_PZ}1(5H3`)(C-3R*KVhzu zV8e;)>5Q>|s49YP#M2{$Sa_}pRMS4lEU_f049y%~H<0tg!Io5v?zps77aXC8V%14-v{kty4JKO2K?< zFuiLmPhl?ZWogWjH+?B}WX0XY8#$mK9JklLGsY1x;p2d$3hr~GuRk7KOSt5KkcQU; zLSc%-%zY;Psd>Y>oP|Y#Q}3Ikh=*^nNNvZbfp*iWv4`zaTAd#@a6yH^sH$H-+*J1F zL~14!MyX$b5le3a{J)Y!_P*)%N{{!kP?(2AQ-G37iRLfI;hr=^-BHXN#Mjr5I3hcV zv4RhciZ({GSj4LhqMMIS)PaS?Rp}M)Xlm$Q2aeef5?EpGPVc?%#haodvJHp9S8s^Q z+O_L{**RT8C%u<_=cZr42!$|+fhh3^xZ9JOg$QM{Er&yBvdulQ;y!D_ej#p_oNdwCT!%)Z>!5Vi@kwuNFV2OG;tLd}dH%ICJay>) zM3KidDK6x+SF$kM8N*~%GUr|z^1$+nnl_=og|q&hv2;9$T&-wmXeHqDuMpridOgS_ z4}I)mh;Vv{Yu-uh0scEvsj_+%dDu9Ur+-2wNAp@HF7%Ta_S3`vbhM49J8OcT6@px1 zzo<}-mu4t7FiZ$nJ@O6pyLTgG^)n_!UwX^8^aX{ivqURKFkTyxlfbBy>{7d@U-jxP z??76*_Vbs~SkR*7bwO3~&(%t8TYB(5Kd={zf!ZP8^0UTMp)dzpJNGzxPI|Y*e$YPj zMC!LZz853Bwb$p;qIi3_9Ny1U57nV=up72C<8rke1Jd`sd~iRoy?_Q1a8m@0TVmk? z*)LxA>kgHeJjV3SZ)KTpsLH&-a=mJU$7c1uyKpRey*V@pnan8qKL(JQ$z-`;BijDI_G~#(Kw?^whrgfreENl(7tn~ny(!E3NDo4E@TyGATG1cYQ1urlqKTdbvv;Qdz`J!7` zqN!@fx7=H?t$h_i?Z*XuDIeCT8Z2Zm6Xq#a$MHE-)9Cn(LM3P@RfPi5vS={Ftx8QF z>IO}m0!EYS7hVkw4I0GCVX5e+GL==x+wG-wC$vYkJlhJ*@J2sZ@Ec7YrTfm-)Qm17 z*%?wv!^o;Y-~fQWwZ24l!r}=6nX7KH4?u5&1dv{Z zPz6IUfQAJj1QJ-J34*W`X#z@E!ba)30fYnyZNUYkZ7{2V5Kx*lW$8`2R0Szg1OrGD zm(_2?J?A@nj=N_+kKdg6XJ+2J@80vy%-s9?{odQ{Qc#)?-&|;~3(SD%tdeuf2duvE{PyO7Z5-8ElKp%<>6>_3ILXU0d`@AQ7 z;15(DKEGh5p+|MiEMX#U_H3Ga+Kj(5hsENZiyT>qO9J%x5No?I8SYGmEsM-?m#%Cj zTdL`O9s3HO0LEeloUGV9$fka!w`L@xpz@n-IW*JFFmn~`SxwPCH4Fc#i9UifmMQu2 z&>7ao0f>J0?%6YnUD;P)_AtqF-WG@+RU^L8ybyc#3*LFO>i!64Gc|HWmr15Y z;1e#TIl@oqX7!R@blhqcZx(NRIP{=g=MW>>X(n(OltKSo#)f%Y@5;-sB}^|bW}@~_ ztNvWSvpsa(4V7UbV9bw;CxkMgNPy}7NU2%1uzn0e38d5hi)G~eqJCt& zgVJ5{SY!lJnrb!3mG8nAg9Z1X$gb%h=wl&eb!4i;BG)Pu*rGR!IV81N;YLL8ZzLSp zw>fb$h8cC7Y}o*f1V7%k$}0sCa$tn=bloaNkBsLG7X!)HN$CXmU5bYj;fYA9m8af?^MJ{1 z!8c7&HXzPhdES;urU9Fe;9!R#&RMfZh20vZ7kcZ{TQ1hHg#BO9I?)il?lub0>1k(#vN)!-5A8E{s@#tY@2vu~tq zNB(Xui$p&j^5wc6!KXXHUFgNG2~U7N=V|4)EPZSSpyXxZi2E}LnAqiX*YO?52pC<=Nlx_2ujS z%eyfcfLt>I%Ec-eEPWO(DvoybQ1|@eDb8gv%!;hQd_bu7;Qs@O}EJL1_jk|fJA6?|xAg4hj zlZ$MtG94*8bwBlJFG%;9n;FNs-<^FSFPOg98Zw(kV;;F8bLANyX+lrW0y(p%Ga9GR znPQspE9hC~+gyk6dPjs}k zfh$)KEdVPe!k>w2>X6STvnjlb2Qx}gppX{bk)ytq9T=AwPc~CD;_(}Q^JJqk7Fq&c zA4nJ|FM}*)jB0T06s!3%q9lePsn5T>NWbpTC7gpR*yKN)YJPe3lXjsaEoMKId$GhZ z&t&4vPu>=ZIyD>f5~8sZYY7CfW za3^N#?W+D%35bDjSy7=JA2JX+=+E>^*M!k-`U42pr>BS#*`&P7?<;Io5%lp(9zlM| zwu>0gcjzsCDC+Er`TGP8g~|7^2jQ6v$?Twyu-t*ma-v8dZRe$}rN~#1mv-j@R-K$qW>se3pLS0p!>?GaE z0|yR}DcrfOdEmfNIP{O?82Al+40kp7apJ)py+;QQoUVob9Yk`Uray4t!U2Wbw_vUV zbA7od4&9=X?bOqFP<-IlVKz3h;>!kC3QibYDLkBe;M1YZTOXeDAHMWl{B9=4b+Q|i zY7ew}oeqMx9{A`IP67Qqa2vD;zBH(z?>Avbp&w`9`}h3u5tan{<$)wF==&2iWbT0r zm_yKyKn(Q#&yNsC_Ame2%YL;ZwEy^jec7*e==N>-XUqO>bjW_=|7_Xcjh5TL^M7>n z?=Sl`0&M$t{*P|{{bl=iBw>CJ=ij~N_i+Aw1b%zxe~TQy7yIwn`Nw|q_jU7oEBd_^ z{ht*(H;wY$=z}lbG$L~BMD7EkCFhEeF}f4)xc1HxmG)5-z!m@nK?vhE9KtS;Z);Hh zyoPT0c@5nFZu@z|P1w50AqYwVR0IGAgpB}}0oM?s3B>%m1~>e?{&gGl0CXE>kMbXz z`}z8RZ0_j(1CUj_KfCcin}cA_zcjaR<-S!j)kprLcYihq_37`u`>Q!L~lm_z^jmodlsoY=>%KYRCQbAOFF^uK=_ z^Z)-%@jA0%g38=!v&-LIMkRlaJ(!&1aSI_E9|Lyy1_Snc3nm+b4nlJQciSHkE(|Ls z=nqE>!USS|juRM{Brb60ubKGck$sni9w{RSv+{EmG^jP;e>guJCJUq9pAj%+)Nl&8 z?EaDfcgn(V!+%aB@QYuM?9Uzah!Znt^{2=GYIWaD{%G|-4gKjX|JBf$zk2_x)qgj1 zf1LiSp;)i|PD2CntJQxu^v~e|Bl<6f5`H@DU&Hg$ssG8)KZgez(SJ5{gXO=?QYM=D*G3-wcH={_penH$#VhJKR64jDMcT|6Ccr9q!k9{Oxdm?1=v}6#tHJ z{}hUUN4Q_;>-Uxl4aDy)_5U%3!ycTHKa1Nva&pV4w%v){JQ=Zoc}KmfM5U11AiLwt0w@+KrQ_B zz|R|gUjOduzl_aall1e3U)O&d^Z)O&dO1_rE|b7{6aHr!4w!f-(1R~1NBHXpND{yo zl+uB(zu)omBM6ZH_d9+){9oSj_mBRmmA~Kd+r{DlFk|2{-Du))3lb4jx@>ibAo(s?J#$}{TyR1d;7>0 zhirwS#bZw^H)1?`VhZtFqc_G-PlW_+CXEHFOBZILD&d)*{(b=jslTpBKvD?4e19$i zX%Owe7i!{Qc^;6aCy*{&%mQt-&+g ze`o=IulL_R`a8n^=SKHCD*bPdN*%uL8w>Gv`K`i!nfYyEp%-r+98A2X6i}Ux01k1e*pFN!~iF-Si4V3TBYMXei+kXG@ zx?h&o=xuAfxQNHz?nb^3aW&DeVKmpQfp1yRhkzTQArgEx%15?{!1>j)6C^w==zZ*Q$>cq8n|SQ;BancIw=#>Uig! zC;GS7zw{V5#LpLxc=zWvFvT*u3_5N_J(`TRw3v!_F($UN)W=h>$=w&9FL=`=?Tdkp zC0IWv6aL~nXQ-fQ&lDkmZg_;B|E81v4mUS}zvjIqh1HSrWM_;B}Dt6AI5 z)@uG#qL1WW`Pe<%jI5%j2tAAQ4f<#L7G?L?7;;fMq3MSK%gL%N;8|y~yrs*qnQkA+g(neO!dZ=aqs`q0@U;o6NB?FmNPi$&_x0OBHPvG+s< zAIRW$H%5BrC#^aI{Lo(l14hgvje1-T@!Eq4YO`pI(?e4hqS5y&f278)1nrpY&gfLp zqA=>n({;^JQT5CREtBiBmo}>RBw}y4%7FF(={HT^Tw#6~&$s18=C-!IW6W+7(4FhG?q9UC(8iD*kC9nkrcKZqq_! zOdjvYlt|+ksqRhUJ9#a`(jS7zFyEJl>abq2?`7A`k$&*qGL zD3aK>=UVqnMot3%O{_Bc@p+lhR*{jx4HA|6V6xkBZr8?L#|dYFJ>@yn+PW;U!gl(S zg;o9a{3tH-vZWeQdS`o5_(2p`#}#O;b-@jIW?~(x8vV)WCkein9?YbtMO4v&c>}js zJn^2(ZHU1eE1(tiB22uu=7RaFzjI6vc$!&D{VXW|o&z_g_G%8@*7?l)Z z`G zR8`bx8l0+Yh~iG#p8xRq^y;p3*i06QC(f=|X!jf&m@&|)#9PmgnJ0ST^}51Lq~T3h z&70n`PfgF}R3|WcZ;t8#mk76u-5=}5tm0w!ey}b&G@l{}LD=cuX%Bydu0^zYUbLWP zQSBz{&o`R*%``Ck3{s9q0t*MdHirD#+ouvd_zWEDsNXpJe3wF2*$k#XBd2O0{3#+f zr)s-t46aVmDmh^S)+9B?S#37E+#bJBzF5?M6>&v(i*x7wHeNcC)aaZ`l2Q8t_1+>m_ECkPgQ0`7;A#x9^N z+Y|cVs7dM-=W;K^n$E0BYz)3J^4VP{4KWcJb~VHIRuhFh<_mlC7OM!VXvK-!0y3{x zeTX|-Zm|~{D1pBtX-Gwp>o?Ebvi41r2{=7}rRC*On`7imaViNnM4?Eogi9smujtq~ zO-6}jkv;UJZF6U}JwGG6jElUXN7pPw)xluRCKwYX*nUIMV{NRu>B>>Kn{<~*rZ z9d6=LyM%}V7^!@Hog-n0BJ>rdN;@dV<_$UHS~tj@X!y00Fqq6Mtf_Qzi3M%PDB`Vy zA9icVo0ChdsaJiASBC>L=Bx1imWew=)XQA%nl!KsSa1)UdEZoh+y()(V^j%&;*wC* z56qi(TCP2-c0B?lOz0xC)t%i3VEyoa55E4suL`Seko6Y(33y`voKE#N^3j2rX|Qc_ zWwA=qybMHS)yZJdA@(i&m%3{o}#o-wz_y zk?wk+>!c?1(5}d!1hzw1?~{XvPpY-*!PYhuD-|t$6T4DXl!zD3c74}RXMHCxGHWwM zXP>>wO5?JH$2llPbCnafXM63Ww%2DPjUIQ(_HT^%j8LHvu-)SzJYuiwm*=-jmcP{# z)Jj$6TsQAXGL2AOdu1P?jujoK*mw$v1r%37MK|?30okG!*>}XXG^vY2!)P(<)DJ97 z6-6W7gq=}7nvI|fQy>U@@!tNCmbk%5b2LzvcdR^Q&tzvWmRJ47l3i^eMgMz+H!DA0 z<$hH6)XB{WR{a$EY9TpHvMIrRCVeOBcJm9c#ze@g%DM_G>Szq^L%R<6yip~X}W8d z#kZ%ESB*O#O|~>9xJ^dy#OMyz_$)7uRoCrt5UVM67~Q_Vc0(EpRz|(ZeNaAM)Xbe| zmKiLNv-|9_<3fYb(pM#ZbCD6xN5gC`AlS^W&G~ARz{iWPhjuPEFemEhHWX5tedYjL zOK>E;U4zYVh1*ter|uC>;-I#Ho%!o19|N$SRwLv%8?u?bH(3a+4&Gg_c3;e1aR1@6 z^yxSm89X^){lmS_Dq*Lh^}?H+YM60*T=(k3>{^MDbpw#?x6KmA5?+3Gdblws8vTA z4mu!6SnE<-UA;gQB^vQGi8_we&Vj}5^m*a+{^YI$goNt2D0{QN*kR-=^Lw!V7v7T# zQw&r0r$#+w$T|`fSXUZ>KfkvswfDy8!Kb5JDq6gRb1aI!A+wqJs^$)q+e{`?+saj&OP6CY(3+Gt;gD;SAh8`Zie=c{x4C zI02djAL7>5NLLUFv7;RacK5BV606Jb43doT(cO2?Ph)D=dq|d9xhDFk%|^exW}~k% z$;ix8BpOg3bgz^YTyPolX7^6i!l#AM9&${iLYAy&?vTkdINlx|A?RMnO@4CKLPs`G zzu7VPk^RD8>6U~8rbowdq^Q6vXt88^gRa`Kkf{r+W8#&LV}9z{xtL|lFIP0|KD%Sy zJ7yf=Y!MP%Vlv&LPq}UHGcFlEHqNW*$>?lOdGl$leJi0jX8SbHRVklbCUmt_g)P~7%KbM0si!_}UpL_NF zR+=_Nk1SF-8820j*%-dFzK+!2J@KrgguIg9!j-IR(r?~2@ErO$dSun@0cPFwR4+j=smHGvEl z=Hl^CaX#yh+BW7+Jl9~8u%2!dLfP`hRNSL*HB^p#Vl@>xA3S@7D_=~I$rH2mfR6m0 zfJ#Yn1eLijwRz&I08?!tR?-mdj;15eBA48}`?)L#nzHSc=)D6IwdA+p+r3;V6VCa0*S2@2}+*r6l>QcUJg=6n!y( zKNhpzUi;DL{npIgoej?tvzC@=oGFc_qpmF;e5hpPAjbAFOCGDNOshh1EtM*`=q-FX z>MAT`HR+{nLIh3bLSe6QW2kIXx(&rxw;9GO8L1bqciWd8XPd8LWc;yveBx`8Am4zy zfVL2x48AzORD@cDGL1|SXQ>#UREO*fUmN*k?H9&SOK&8 zx5TUIl~TmzFllU%F9x7L@2^R(+gj9vj-K#?OfS1kgH&99K1ggRNl4214scX-CL+IQ z*dSdRi&hVjTL~#ye(802wS2%fzcrliCUV>Vs`i&`1Q^RF8-tJ3hQgHActg_zINg7w z%pIf)zFfGy$w2NNVinS{1Kj zSP8|3haIo?&#}MvnM_0(FS9dXlOy!lWbEK|Y*lv|r2|HNJzR;;db)M0FV{?IE7W<= zvB7hFnrp;W{?6O7NUI?ALYKwie!JqKo)6=c_v|~zU$Mp^m=)ImvP@~1>QWWYS|IN( zRE?GbTc(oeF^v@L7q4JsV+IXIZhZt~N4i_9tj4E`G(W^Vbe8Ss=cniO&e6=o8_)R8`zhcr!dv$lrNO|Ge?3rnbzSLD8Py> zy3IVO%@jNSwCwyLES{Qpobj>xSF7)p;li3*&~ z9hmFAUy!8$g7={;ctbWxhZepyJ8jb|h--<<7Xv@vUjC4r9j0;?lWNG8bP^g%zlaTh zh1fGR5!GUD$+rt{1?|2dW){MF3pCL&h#=D&@86&5n)v|&j;f|PmJ5YI3tbCdy?}it zrXkRS*rmhOzAbmpTrr$Hxdh-jqWBbf^J%!*rxJ@(IJ0F8qjMiBJ*RnkWN>9_v4P={@-X4?YO6?ln&v`5muf#MfGK8og5Rf663`&`6KT6X_+j=& z{Ai7Jy95I1XJNPpy+$sDb5Vx)Qc9Fz|0hFbHU5XEOk1qi3idF57%u`|MJ!v^;5qt6 zSVAsgq1>VBrU}D_l?P?MYEs#ngAc*r>xW7J5>-5mQVpqWL3p!o%;m_HqUJU9T;+C3nPJ_(YsYNVOe&XPag+Ri@j-!}Ata^RaS5+dap?j3% zk(Q@(rf=B*A_Fc73*qX}J;#2iVY*@c3tJdF*}Z`ZV7n0G^XqC_b}}%5qOmAXfd1L{ zs*C~jw;ii};ii7Z?z-t<7}v<=6q+gSs=1=D!*Ip-Pj;_`4=kIquR8|XjIK2Ex1R<^ z+kSb)vekFq%<5%|Kx(~oF#I%}zkDw5K9tB5-+z9LY-VljS^Bma1cy~T?Aj6}%I!y0 z^W6a&eN){XFKDUiTuUK;|M0O>o27>&y+=S|IPU4Uy_uyXbU3N?tlA9?D}O}^X)KA4 z3COl~hFL>D0rJK~zx*xO)K9`^&tU3B()eMd&ZU~W3uZB$1f}akITqSl^s+?=a)e8ob}^+CI~hP z8AC}(C0LcE{2kX~2&$Z3D@T^gj z#_=f}ap&Bo;GY+axKy9Z6@bzB_-e%?IQkgHJLk1hh+=gnZcTA;=TTsusZ*CB(VB|u z$>-ru;eBam1MeQO5WV`$mev)^87@c@tTFw`c~_3qNC#&izIg04K%s>W2|uP zQAhwDhNFOo8V&iQ=~ULY_^UQejbG!C?P0mX>!0m9)rAJ)-=nwb#U5Ls9$l+*e9<&* zQ6Z^S5ocqdQ4R)V{Gwtqt#EK8hemXyDZOcrJXmQZkwF6Nhd#8~%D|RW5hzOJhje3x zR1$zq1L)6V>TS_)c^xjP#Jr`|du&c`>TG;IKlW#^qv?{;+_g%0{8a(jmo}Z%|An5= zjhX>Oj{Yj5fC5vy_5r3OwRW++`mcQe*97Y!%fk2gnO@@Il{lrY%p?3-dI(QQT8ersp1bhB}Et zWWG0={cH@A@dQjCncihCbG0uP#!D8CNhWtOh}5Lu%=bHXfw^hi)WaJ6F_V;$)#4;BVf*N_)2-nxOTo7t^!mRXc4i>(`R(LtV`^oD%R>a(&>fu20ko z)qJ+Q66xqLp$wu|8wE-?$AV|8uB&HWGFn?T?F@c9RsUL^NzXMliL$H{H3NzRu<7gX zqZAnqI_XG>m?suzYpduO@qE)N{CEi~ibFCbfZRlNCE77G?fNlJW`Iq5MsWdFb3)e18pGUbbx|Gc1}BSP1S%|T z$Vs@(iL8ho6QR1pa>{B88V%U;@p~60N-4;X%4US9O6{)sAA<=v72R>@y#k6BH8_gh zqaN3+I`dpNtqOBDqb(B@G~=UJMlU`qfpNFG;02ih5@c^+(LeY8v71quw2|&yw7OM>@0(x&@IW(iwd;EYAp3I3bWs`uk(b7ppu_<7a|jruK#E z9#8@_06+p3cM4n-op?-z->OslVs6G~Jb);!h>XkWDXdrEXff}d&G!uxIX=5K;l6x#f(KC>sl$K)*lqDJjz-J(skzvuh;l z#*{-0udv9X>5$s=wjXYN9lr|LPxglQg5)(I3veF|>gfJ-C+s#im3)d7n+!IpCo0Lb zm_lm_@Uk=ado2XvrB3WzC{2bp`W~gcDg`$ca_uyDv7MH7xpg!OlCuDlD7l{+i?M;B zW0G_xhCLSJcPavndu5Alz>$2FMMs^rxer12{a?13Bx!!f@)*NyQGjAXPT7V33@6tHmWK@koPB?&Y04J1CnUa^JH9Y)WI8%$m0CZ8ROP zPLQ|#nyQ*aXu^Y<6Mn_Bn2c-$X7(D_cdfWYs2rm-Bww{$Pmd&P(roE?;hhUSecW5P z&e0Vx`6nHkpB`rdX)Y&B#gVK|!eVBeVx)B6w-`Mp-)Eo3z>kooh#`TR_${y8hLds;MMljiMN@J(BVrM)9@h4@A(=w2C!LsLCW^h0qb~eUE0^2Fd6F* zKDRMg`pCdyE~x(`r@r&sLf77-w1%(lEB~13i(!TBLPXizb&wb_`V~*@R~I6vFsUUX zVVJKNB9Rn|TfDs3$IVwj?Lcu(izf<+NEFjT>d66jaIgpq*M6NKU?ykuIiG8$2{Fkj zgM|u%{iUy1Gk+jloj`%)65+J}X~u!8;}3$459_j&$t!Ssq>;Sg18Gp1vXc)W`xQk` za~5S~0Gi<$tzELFdFM!3baq$LZb`G@^-K+j-C}QG=SuW|xeD}z2oInaz?Rda;KYz! zz95D|$jV?Py|&p!<;`%-o4b2}1l><+BQB+Oq`Ke4G{LH1l`=MD_L{yJ^YxhE;k)b8 z{4`zk2BNSmbBbKR`3Cv14%EJ_cMrfEXLxWkZ}DC9wtzGH_HkMXCcp~&SmQ(hu&lwk z0FdsoDGJIYHlyu;B9&JZJ;sT#DyPHP0}_T;B27)o3DrQ0~ z1kat0m#{do{^5%;Dncwi7nBJu_E%-|eSOOc%bJ*&53gNJQ7?=^Au#8kn;Xdpkjg=s z!Mnq`n<_!fYoNSaKuLb5Gwd4nunEs}SD<>KU~pMC>vjb7P?7F*x;!II06I^XqLKoI zSS!YSh4p0nW71Pg{jAJ8xy+V8T}^M()evyQDRw3mchVb_iZ}PDcH?wkRJ=4h!(TST zh=CF$Fwp);ZT)oX>@HAu=kE6oqZ7hhtk1687#43_?sLZ3DfL$!Jba8D7=A1Wtf#m6 z0-6Q<6qpE*C}QxTkq^un-ZU_IDDRujmT%FDurjxtAxfpw;L(L620`jh?GhU;m@+FF z%bHB8;4)bM#Yn@iuhgB^u{t^3Zx{4wj<^H#{>Jn8-p-U%zW45SVeL5v(r_vXrsWR9 z30k7EP7O{%eQLZbE9Q#f^Ri+`heKGu%Ynn3B zSS?l2i~SpbL?th%Jr|c`sTVB)PXq;#vNbWRmeV~0yF#RTp{Ztp3TJt1SOO-iQ%lGk zgk#Wu>?ycN-`(fzQ&xI)=Qu6C?qeuk{%5bDszGCzJC8er@~_#NFnO(Y-Y35yzF6_- zvotnOTR0KgW9)*{KT6DexDcUUC?tc0X$=kNA9z0}h`X#Nu?`zGa|D&S$RaOJec8v8 zegL5kY4;yJYt|VB7AU8sF$vrQW}k~`;~Ca=O5eV<0MZ;VPaP;3dd;J{h3Z0ehq9%y z4?(czjvr%?!1tNGVGaX|j7x5kRQq{}A61t)u2w|>P=Pe;cSMC8N+%+0bkXjB217o4 znE;rZ8l13M{mZh!kECz|L0Cvt!(zp%YTn`)wC`*$i^!V0@Lbo`PMmS5+Dcu*8@Z19 z=}FLNr5DmV%a6t6faTsBsa~iLX%p@fm^Qf0dHJ;XLAk^^+)Se<_`8(?(v%X%Sa z`K`K;=W-pbpOnbZ@a+h})2U#ytc)K_qivUH)0c!q=CFzTn){sbHpI=$`e!%C!r4yEt zxl{%U#P@L~-z|gz1|sc!F9|r70*!F9M{Aw(E}MFtKAF=~wMqI}k3&wt_o|^H>JQhm zUf#(`d3x<3Bn(;%0ldY7SGS7lbQ4T*r>}o|a$1U8yIgfyPvD~Hy5nU!`yM(>>a-;; z$3$vFlzLXhSlhKH)5{{+Nefi0?KFby4c6Y;-uCf9AvR9HwO(=xB(pma3Sn6l`$?Mp zIKc1<_*YASn#2|s&t&T5huaYVvP)Q>!DgVqo+o!(J#OWk?75h+&D&}%p^`SOLcN)5 zA^GZsxhRAy$UNU1J6p)d)0^x^L;CXMayL%cZlIt{0w#OeFbC7eM5cSIB0_yVh&%i8 zPU&ZVvPWTW)YlBuM4i1r(C7yRKwLos+xA6TiDRHBX;(BB#C>V;c(kd$6K^HhD5bG- zJbAHW;)oINOW8^gwtUKW9aJmAB`?Q;N|H+NI8SZ&cUIgRRv1}YytDq-@wcYt^MKsT z?Yg%G%8f>@Vvo4tx+mbY5JSRVX+J&)i2}HnjpVVmI@-aZ)4C;<2o>QTDRy}%{}6-`1;BMV_=A%nBsBmn3We(bEkE-&T_ z-Ijzr+zhr`z0$<3S4sOic-bc2sYjuRA9UC&d~u=(XP;rCON}H#RC?ZBQ43?JymItm zkoN*$dkt4>f_a8PZS4*J0ChD*$SLh{!!YVvP%3EVgI6vBsRokNSF^|(Jz)+N*axUP z4a8*OPCNLz^{6C8p#czk0OsnxfMJWh_&pGAIn^2)`Pjn&Aix?Nk_$!#q1SLTYVuXi zA`X03e&TtNZ=3^nYE@j}+ox%*tyy5ZDIm9GRIZKET1;qT(G~zIkL~v0qQ^q85Cocv z-~)tNmvCuO_!!gJv0H`tiK37yB+(7H9nAiiJTD3n_$YnaQoeeC>t!+p(=|fPLpuTm zzob@}Nhfnzgx{H9x~uo^;JdZdu-%I6G#QG^(vrrt4wdUY3V04qz_bD| zxd(1{4j1IAaWe5q-*9!pKQcSIu(dixOKdrD_R_Vrm_TEf&fY6rUcoo!PQq#Tt|TN$ zKXRmw1w}k#qir$3v~!T(PA<#X6$V`%f=U*xXZnBu=j38jltHs|?Wx>u zvlu8N=$L14?z`9MqdxwL4O0^2e?^0lQiQ-+Qzl!7Pr2?50Q8yQ2Us9CBkzQQUiiE# z*r>`Y7y;{R1oEauu~e2`f>_|IS$>}_B}g2TSS*W<5r)~#UeuPmLXi|Mu{~2)(9=C7 z&x4^F?FgrNI1LIlNj2;Mm0HSA*uJJc0V>({LutffKL-k~l+8K7W+qQowd69Ua|HzX zW0p;Mq{osHeE{5@sz$AQYk0H>H1Kpwqu>BbX|>S0v{O|}ne*JG9#J^rtYi=eP~;3i zU6HmU?P9Z;(ga#u0>S_>-^B|_ec-Uj`sSRs?Wch*Pd~CMcAD9^emCmpINUz43>IC) z%DXd_Lt_^C+(;!m@`DukRFY>AROg&0CX*9@D=Ic-tKJIU)fH0Vv3S11|< z%A%aDi$*%Ql-t|^o2@2~>Gc8KJ>py9bJ=DtNbl0}!-vH~&fHXi4Z0ElgY8r-C@Nh@GzF^tire+#+S)lzqme*!$k8YaQDOZhj^p9{C6o=^sW;_5 z6qkSlG5X%1lBb`p(`Eb>w&r?aUj#=j%Jr@nWL}94308U(dkn~@0=xJ?w$Lv)5{S>; z`mCUnA!`E?x3pM!!mI3;b^^OsCdTeTiMYy?%#9~0y1F^7K%5m5z#D(!?D&KVW`+o8 zlb0IlT!CYB=dRqn$?4@M4-^d5Z;kXnU=>jagXW;k5FU@6Ac1K}-JmM5-fas6Lyfo# zbpm0I*S#A|=YnWSK$hTUjFNJm>$`C~gqGB(G68V{KJItFX*Z&sLE7=SWf4TNyxSveW=XAOF1q3iU$7F zQL7($MgiiM258IGiG>~2a7{<1ZEnFL-R#+*n-cg+fg7}}hgu6741+e_q>!P(Lb z)JXG|JAvOWVjemVSf_c;y_^{*c_VJ{bP%mBv<_Q!%{}jRC3r19kBw#jv<1r7kow^Y@HuusU`WG_US$P{NsqN3hh(y&rseqFN57|W@#`mQp1Ktcw4 zUCvy!jurykRyTdH0R0wSJd8yvDl(odG)2>ijC0b|-zg+fV>`m0=-v79h64eF5=0Ina%wjYIX z^fc)trUItC9DWv^wRi=rPozN0IE-Q`Q_Ql)- z!fV8K5SA6hGCWi3xdhq8de$x?t&t%|{RPxaO_JYJkWnX1xvcTdwV@Ec&7A>d=QjwT z^rG^$*a*n)cxT>fsakKYKLGKVBU+RVqQ;jTa%bj1gwr4UB&>~|?@Cj%Zr}pwH&_G4 z!tlb+Q93xPl>oQnT?pY}_N`+eVP!b4`I} zfAl%DUm6Zzs^GNb^_A}HT7UEkH~t1dwb*vKLFGa}5_6_xU6PC2d?!Irj(0;tVL8UWGYJOf%C85bo>f|~>;!cA zDGA2`XJga1)_^{hrH>xa&ZYB7Q!gwNhO$%CZ9#tw3X%~VCcmNn!^;a$Ug5>hA&s-v z3!xK`__Qorq?2g80TKbu9Tz}al_ewsgvZ0>{UFwS`=0W>et=PCoNa;feA#7vu3xZx zwM~4M_|YJ2Np$$x-g=G?q~#T7tZn%0kRQk0F16?Rb$0}y?0g^%{_v0hDH=H(&gQ*O ze?@EbSi@+8m+3xkn_F7|X2*QwZ6#}&dSTxoZ73(h17(&_tIK1ct+EDNe_PeM>{2hI22UmI3!9{lUs8@zkEFSldk#i8YJ*|G_(zouqNB-- zmO%8B&Ku*S(C_|{0%4c07SI# zWYKYQeM`!cVPh5^=k-Kxx68hA2V-xZEFY%25@7_g;EOkp2*PKI=_I~{N)b297~Z&k z2s*k!SD2Gaw*-V$9UqoiYohb?J8lczTdOf#6U<=36!in%n%>=>NA-v`#ie>ttuVba za6l~1PR#SL_GQ#;oMmyS$b^K>iX$87lM>J|j!3nTzTW96rVXpkh<7c!R8Y?-=MU6} z&_g;9ONH!R?wpFN24*2b5u0wSQ|$78$h+7=`o zSwN<*pM=YRG;1{=5!N+5X2HRCfG_G*pRlkDneYp1&ORcI%sE*O>^X2qryb= z(tOKn=Ck?Iy$RS+scLUiziqiW0t8U_t2RJzkEqHK(7xRqN%A=3igs-j^(`ZeaXBcv z_m#aL4r-;u6p<&G`Os-lp9lD{eA(b!7R!-zrPK0IoqnenH7t>wJRt5~8OkV*pE_y{ zII&g|rdgrc3l@A(Nubdq4O*=0r{TCT=H-W?16EGvaeH8sTcQ2B{4hB=LR;W4t1K#b znF-W&LP)J@KfBzxT?GU;DL2*{$k&0&DwYu-Ex%NA5iRt91tg?4IMh9m%*%B@RW&{) ztD3ltln7u&#{fM69vnlN6nScX16F_7sNzT<*EgsX%I$%Q*Zq(j__lHc)UPyLJuuf& zxSz#>C@P%jzBip57NNP$;A081uBTeFE9y@j<$o|xZTrH{e4%tIVdtn8W;f&{(p8U| zx%b;Q}u>DU3VAzSJ*{gCjR_k9~lwv~@?) z8anv1s4!bSE4aFB7pGVbon<0!6I)5m>4?$xGx zW4U(6|FZY=zRVkVg7Mf}yz9HLpx%;Bpq(vWJw5`I4^tvX;pv&SF!TWNn2>!%J7Dc> z+u}ssIvBceH1We{$!Q%=vAutJJPKcVDE1c2s-DaXcHs88bE6tCeYrayNt*=iAYLA#g1_pblQrP#wJ2;~f_w zMUQgidE2Wz;<8g;dyckJ){Mt+O+3_*h3Iv|w*ES!nI%kheOiw6k{zIA-O3ZJiYcdD zU|w}wRV9&UDCX)4DdFBZ)kOCy^+CWig)^6HB+E^hJ*?!~D~sK14PF)0KVK)I1$*DH z92T^*sT$$T3lR@n4#qp$^$x>;G3Gv-)+C4<{wC|d$vjvq>!5+v%?sA5v+VM|AQ0}5xJB$8U7fh3}T>JzcT7hP(VkY zrIyMMgr9Dl$WTgIww>)XU$5fJr@1-sbT0dd@sxoAQDgQ3qvoNB9s6>%%&&9oR@mi> zbu|w)&M&$GwDs->bh7?QwqmFJ4OnUGIdE=#|NOneZG$)~HqAA?O9S=S6M(W1D4`A< za5`I=QJ(h!7UoNuk&4y~sU!6Y`}4C^z{*SnlhoHd^V(iZZ?=T0ZwiO7y3-H9fq)eW z&=@$;aNt0<2+YodZxf_rt5d;1cvteaJ*e(Pnp$E4yE++ASOzbi0Tw`3y>mn%JfJj0 z7~}}-*bE5|JRsG!r{^H)0u7otYqHk_&TVmzu>7+6rjYc{GdXzZ7}8k|)fOPX$$^CB zjKp}L4>A-6MHbt&>CS$zd&YYKLawo|Le15Y0Fp6vc zEaw@Fb3G8#FM9mPFD&k1fFQBe<=C%BFTm!%fPO}WoENO$IQk#I1nF7iJP6TM-211G zz;S8R%-5aO#J!emQ1ZLiq@f6T_UfG7(eQ1`SDv}|huly-dCk#tDK?hHr@^XlDshkUq>5q1O2$CGW* zxeuT7`tvPm^R^~q#UsF9V{eBJU63(L-iWfkhfTn=4C#Y2fGvzS0yUui z^t6N+km6qllE(=Kn|((*P*5~n>0t?pAcbxFKG*_cq+KqQA-ebsP?ep1xZJMcMz`k; zfM);4r-$>aya-~>z|X*8>3{yC5C8br4*u!yNbC>F|9r{5TY@t5-%i5+zj@`8NykA) z4s%f8-RrL+FsVnj&*NOy{Uk-!7ONu`eeKQX`m|`uj(P9SdWr7{5*B@yqCK@l(@8LfB)&ca3Ya#d1X;+*tzd@uBd&np)eh@6$<*dd(UVDmwP|0L5Z3UA>1d zPG*z`Hb0gE2km5w>S&&enScApo44LOa%HwFEyt|){Ux=8h}HzJjrLGUcV#0y}LW=9{Hbc*YvSnThAOj7p{~&)9nR*1H6g-6dYHM zABbJ$l;J#t!j$i<*w7AaxT_@6Vv0gvvyq#O6;e@W?jOpdMj;%R34`9{V;i^)tY}xN zQ9(zp_*w@WBS5M4gL=W0w56l|xcnboIfgT9J}55W6L+XE#65b9U_QWb+WTj@_{+xRV7YYQttt=lqwmVys<6!>8^3EZ-!goR#k(k0mG ztHSt-y8UDePLa$HVpg@OcU262jod6Ihl6sb8Y3|pl&>bccE^v|I)3VS zcgp}Gs(@T0koN3+*sZmz29F&B0UaU(`T`!4hR--lUA}nGrJd3Djr3HqogHzp8Pwu)>cFV7$;E8x$V# zY_8MJ$gXmi0;wjL=r%yt=64WBT*u=wT^6x<@h(hx9o1n>mb-+U{quYKuR2d$3LxEO zItaEqxd2RXxj=@~$yFfij9mZGwK4MatA4*CcNT2JjKYqrM>pAr$qF!$iT-|=+^9~j zjLenQqRcYH;(gL5P8VSzfL~2=7{QYIct@jVfO<61!OYes$Cz8V^>B;ti8tPoi_pnM zR&o*fqWfsNDUBnIj>hsS9-YKoCCYL6IC6o6+7ivMT(-955{t+pH6U~x7k{ysWS9A3 zH7&DUAB3`1TKnap%)BOy$};`=298iAoGXLbqnDlL-jgS}*P0p%bgx{JEPlCK!jz~n zE4q>>V47uE?QPS2L$#Y5W^~Vwnn<88a3)rphuAR2r#WVt(vNLY@3z{fstaW|><#8* z?v4n!)_u)xF7ik!SwwGm6Q_u-^Y;^_oMvPj7sS63o%g0ZGHYCqfKW9>^=kQPs$n{;6L6!k1L#Ei>nDPpcap?%VWNlemC%b2<^6HEko^-2h0M zgPbN!t$Kwv<6S7Dha&MwFR|UkXSb$dHttcI$#gY;8=!-WyX{`Sx>r2pZ_W|-0D*6i zGOpW?5#cc2|9Wc0&mxG{JYfobpIm)vXjsoEJKrC^AVDiH!oG<8U3-lh{{ zWN0!1jvh*g&t^W#AN{TVMse?XK8M^$aOXc__k_nwnm%45Ht*D~u;Bvg#gO{Xs!AQH(i5{bE@1~uX z*Ew)LCIG~nMIOpMcv!eORCLmJ?infHZrxU&H${B9R@zkcQyKqLt=h(J$U34IC|Py2 z<0z`z=l4Q4cNeBN#Ht9P(jx$fa#c6HsP<79A1Le{jtgP-QDvIz*$#IFl?_52zL0#) z%)p~&|HBnus7P3oo*ZU+I3>&?h3A9e!y!H zGXO6P7#{NSDv3~1-N+si>djf0^1foZ(A>K}WFzo*`ic~&g0vbw3LYNg3$Sk%Jj z`o@?L5W@H=^u^ySZC?RLBNsP#GVI3<$p?z;7O=BZ6YgRJK|OM_1)Zz<98)Q8I7Sk1 z0jnYw^a+mj=RLgBysP7iCAt7-@a>~N=0wR%>u}PrvW}5ctN5)+rFcP9Z*0bmkEh8R z5!Fq{nOxWSk>+lJ8MkIK<(amI**&KTjJN`cyOVNPXUk{B3lyjuqXM>y)P>+?ZU^S_ z=LR+?ZQMo0>8^3hsZwH#f9Z*P0afqs+%%N3ap!FoO8y}$`v&w4I|SGq ztI-w26>-?1VJDsOOKbKXoE;O9)t-);uSYCJOBa4hf#Sx%LYkD<_&Y8?-UoAO+QL3 zpWr9l+TAeE{sGYG-4P%RdEP@GH)va^BQq$zUC&2Oah|3?8687Exf358fmBR!^2e-~ zlOa!HPuh=K zY_dJV>aQOZrM_}p0H!kGgi(s<1eE*F#6URF2^N8rCJ@Xv?VB>RHrsro# z;94s2kxI>;IlgtVfO9M?pL1`iy(t>NLywtENJ@X^uCY`!EjuS1fN?`f3O`AlIm?sA znpY%wjjkAO6K z9n%-f_|Sc)?KENPUv)zR!{5Lke;Dm6hx{+8~07^>fi! z1AV@#fhse%b`yvEmr)f1NszGP8^}ZCqQ6aVrQQB_&?A(mbIUf?2B5yZLU{`~822(+ z#ty5M7+|U)I6ALwQ7Nc}43|jxOSgH`+9YSH;A=xwK`W^ii}NOn5fN{+LM9_pSdRU+ zh8tBAN(R*GujgBCI#9yNqx=nv^(;mTDixKsfH<2R0sHGnDQ1~t}fZf|+4eY;{|TIG^BQziRzompGx<={;A>t$Q6J?!OV;Lzgi?ok7&< zlL#zwPOn zNOL{ivPC5YGZ!b4w%bS6Os~uSXT~A$S&ARR9;Za2Sw>siRX^nwg-;@z(Y*$$vM!qq z5ncMi>uNRGf)N34(lAvc1L}G!Y6w(Gz-uU?I$Do^xApkT?gGXP?5L8k8QVi!xD{P| z0i5kSoOv1hK{robPr%J-M!ve}#i{0})I*TRzY*3~vmR6wTi8RddRT9_#(*nM!%x?e zJ*GupeC%67Z&+3%IpflRM1>etJC0ez7Tu!L8R?rEdds5OO8XH+<$nJFNnhvS!6|~Q zxpiIA#6-gV1l7N!a?@9v4j5>gg9Fp zTOV_4se0T-%OT;BmyF0!#F>8krWznjp*EK=`MTqwPAjvty;}wPv1s1Zuo5@!i+DU^>P=TM3dx{)s3Y4zX7owQ|ID}~9K$CS=Y6ja!#irMBJr1Ctf*T8Kf+UC*PQk3RK++B&vVqf z(|yuXP=8#dmk33n;;ONE4*n!fk2_zGSjK0ILg!y2F3hk*5aBb&!1(PlwSMACGf^!N zO$+JF&EY3xai-XF)!E7Kx9*XzI|)zs@(}K|2*mq^?Z#1RVSR2|ZT337p29Y&o%AHTzHv|sPsmEW2!%a zh%8a1u<{g!8zHM}t@UCZ0q5=PtFgNY9d~kB#z=T`@l4u&M6AHD5B*y; zJ`M?ncJB+xc~v&se&sKR*f*ZDV+B>#f;>bf$}H{q-l_MwWWBWwf-pi%v+w(bL({H? zkTK$V<#KUXvk&$0Eo|KP0{^Ji%Xf-C4#!xkZ8tDO%)1e5qs9xTvv`>#MCfy* z#5ilA58n%dWUWz2xde^HW;0uCkgG+$PC+g&Sz8&FYBP1Db0-p|>q>Cd3+Egppy`1! zO0SlDo%3s(BJJPiI^Qj$p`v83{qW(KbjsJ4@HinsN6{E|CjQP1e3o;7E0tOWzR-gg z=0V>x#TZh6f;=o_wqsFkz`lY8rl`;MsKB~puWN<=O@$R2+>i2}R&Dl3#UbTMwe zX6VC;mY*6Bg*wYq!z(546e@C$M?(^n@{a3Sq@MHm^9t08+iS+m%;Ji!eXmMy=$hfY zs}Rmst4{aGFx1B{q=rQeX-K`2hcRs9vFaX9*#A2`w~7rZaNUqGe6e1+&!#* z-S-&8xV+Eo;ZVkS;kNbObD5!>kNnvV{Dbh&XdjcZ>(WRDQ!w}h!EFf`-m3Q2Oc*XM>ho!L;^j}RARL;(cS$;;t=_^ z>7UOEL^LT&W(JtJJ`NFyhQ~&~!3ZG$#rFE+Y9E3q1DZASYte706mRQZe+<&xBs7yz z+Ao57omxLTN7vH*BA<2b37`#Crc}XLVV0&?LSLiaW=ORfTyG|uwMV#6rkEV3Tg55$ zl4o{BOc$i>oeH1kyh!9Pe7C*mi62rs8-y7uQ)b+`YVon^RoFL?Z=9Zc!$y>=cLz00 zO}d8Omo9(2)p}td=NS4vwIQKg55bsB36;?CaM8Mo^KKBP_a@@bz@-DFv(0%l^IqI< zG@=xw|@csB3N>X`te>tE(@<(VRIK!N~IN z$#|EIxZiMFtTMc_BL~@VV*95|qn<{+mua$ikoscKb0OI=6eo; zas`cj4h0y-gM5kMtrw7XVF&b_&(n7+yc5>p>26iLPG8>}CO9k&3%xn4q5<~(hI3gf zZiOwMyqs`@!R^ChrQRo-W9nzuXAZ2o-i{JravEc;05x= z>_0yWQ>3;_V~N_?wM;>mz=2@K zD-&^V{7#Wxb$(}k@%B|oeB{z$N+8r|4%{l;cCvHb4jUxrj;IfHj)`E)Ko zholcZP-h=^W~+dnlt0D0^=5GsOh8+iZuL_~-OUNKJDC(>8CAdCD8KRxNZ1WJ=}1gZ zVc7IFwI+G#3o2}z{ zcm^qeU{qZjc$+vCv792V4nOat!(oYX78cys{2J_qR&w}uW}G|N|HW<*K)+`R;}ZGB zzmygN#2{z_=dH-&bNpQbn0-G!x{n19=QOw!ObuoGD-q7dL}M{Gzkq}qrb|s1Sde94 zSXK^BiDF5>7JajeHjAdio|oND2mkqYL0m-LkJDOZECvEKa3 z&&igRjL6mFpcgz-Z|d+y+Z$;60dPtXM2L6((EkGb9f+P1>)Wmd#a^N*0!<9iL*7P9 zfpm|l(ECuRc(HlQBKz0R#h(*NrZE)tqSRoJH~G|Esm9WhBZo$Sb(0T@1)Z}-)?D4{ zm%O9PnSI*!N!+OK+V|*i^rJ+xSP11nDE7N8f5%}eFA#k;4ygGjzj8Pfh}wJ*nvOs? zd`u9g%M9JcXPK;mlMkAD?pYNHduUYbNSVUHUwJl5-(%8Qq~` zMZox@oN#niI8bhm*WPY&QlTGL&fvhC%T(ZIy8|6GbNn?oa{x8GE@f5EjN1Zi5u+_p z53;3fe^w_+?7CR>=v@)T5Ndh?=`cI0{inY=$4zh9{f>;>A*jIk!&qh$R%+AEOjrU; zk>KxOuUPR7vn0DIqq&`l{C7c)!u&w&zAW*s4sfInRiJ(a93$DfXT}zMZXRbkIUE_8 zdFa!N^X{J+>z*J~D$Tr(Pwd=unyb@TWzj{LRVHQhj=}c&fAN&^*fVef%u+cpw+vgk zXjz%-SD?JDvhGY9@2{l4^TTVipIT(=X@$7e@&|A$XtgYIncrs>V2WbR45x7w7-ySY zk(3_!sOHx`Qvkk)54zVx3L1;0t>)Y+HlLJ-F9f$pyI{D(Evr9VDv*qTdkyUNHwI-S zM|#!@{qz%P?y1Xmztp{7^LSJQkZ%Bx7*vfFfXOyZ^3m)&p)vMry%tln@<<2l5*{x4 zq03RzhL3R#J@mJeAhBU%i%IPk*jmu$p-!Zf>&a@I6RtSIsdxbpUhxqO<;kfj|TP*k7}yI31YhnKAS%(sjr7pZV~S6TTn~~FwA>v&d~HwvVK|SWT8*Z=4%EO{C@3A z9*RD!7oM%2Zc!poqKnJ7>at(mCZ`wyqO+ymd=}L9{5J=oTtId?>qGSKx5I+d3;^)m z1svU(JIAh?>nx0(l@d*RDko%xx0iXj{MYvLq==wTO2qBTAiQ?4JWHUVMAneH92*0f zZT4ApRxGqw$4r(Ik(Uyw(aXaJq5jO+H-7B?ZrLG}R%WZm{No_O=RMwUEb=!8R!`Y^ z2r`90Z$6g8uCKc5vLK~j27?p0Oh(!E+V{Acf@4wbKm`THl@y}S{4?;}r4z)?vLC0u zsI=d7#}5S&+CnSOyDAc_`auoLELFZmTJPlUjUt1+$q4^`5}!v}I2{u<@of8?va-yu z6U8kWDAQhAR_3RgQ{#W-JPbVhpW(%Wz1_|c5HU8M6Zmo^4<7b)#oK#cw`e4dqd$=C z+b$zQ-(KI{)I9}w9kq|4OrWYZ+k^pL35Q^i+oMyt=Q@A%m#;r>QjNm6NWiL5RoaGO zYrW33L$@7b>`Uo7Gffq9p0H2Vdxl*NOMn&PKYbU&Ee$Ex&)1g7@~!t>inXXg!7i!t?QZf%Ayd8#VRv1c1L0d*WV2mQ}!Rdg3`-YmHH-nrs`y4?sc&!UD*U za@f1ZRajU3+jq=tMyw_K<&f>K9pJDpjC@`2H=d9PxF!cbJS(~o4%Uc{t#l@)!VCxx z2ke3~ZYutW*$^~S@%280qYq%d0kk}PG4MTv1Ow{z#IpEGzgBG@1hvt(2G!qeBr6ht zVWvQiuZ1neEFqzM!j*?B*P_2eb-_^bs> zZcw5Yt^c_1D9#(H*3H%s3T<_{f|ulXqQC1Y3p7YCFS4vGYPe>r%v=Gc3SQlqvl8AL z-;%aEzo!`#JWnZRB>fn>HF5$kA}jrMFd>uLYS< zOAIs@suQuUys(}Sfi0y;NJ3wPBbO#eltyH@11;67HzDCAG<1!N$JrnI^*)B`2Q0dD zO)1QbV|X2@S)xWzPkBfqcQkP3jliIJ;W$$fktW+`V0XW>ZtuFP-8xwQ#qPoTomKMm#@{6_qn%>rlgaX7b9 zYn0Q;yH4u9!>g8+$HX&r@AXpqhT4OD+1wMrLV#tNpDsATTmtAk94nangA(#86Y&XJ-a5hC_ z@9qw>R7bG(YTGs^6_n>2HllB`VSu2Km>LU#eAPC>+?Z6b5Eh!y$k%_B*R#6PyB&6} znNfEva#aqF9I`12k`&_!iq&HsatVEKwC4|2q7G@yQfvF!sBFqYz6>K-mTHFf6m|t` zML<7AkUc6)=R;xkAp(VHIw#p0*Y_HLMSzJp5`t((-jTovAbhUBEcIC`)JIW9Xg`~T zBkzU`m`=zz(%tPm6wB7m@0EoUT0UzhHI<5#u970hpU|EyQbtgnC5Uo~-51^NlvLCg z4SRoUuYPkFQqMUXusx`8x{CuTU;gcNtf>MjNjJLxVv53cAyv>VMqI

YJzLPFOhI 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 YQalW{%enN&&VZU2n_n(9y!Gh60Jl^RSO5S3 diff --git a/docs/decisions/068-pr-161-density-1024.md b/docs/decisions/068-pr-161-density-1024.md new file mode 100644 index 0000000..ddca66d --- /dev/null +++ b/docs/decisions/068-pr-161-density-1024.md @@ -0,0 +1,20 @@ +# ADR-068: Use sharp density 72 for pixel-perfect 1024×1024 PNG output + +## Статус +Accepted (2026-07-30) + +## Контекст +`render.mjs` рендерил SVG-шаблон cover (1024×1024 по `viewBox`) в PNG 1365×1365 вместо ожидаемых 1024×1024. Root cause: `sharp(Buffer.from(svg), { density: 96 })` — density в DPI, sharp интерпретирует `viewBox` 1024 (в user units, по умолчанию 1 unit = 1pt = 1/72in) и рендерит 1024pt × 96 DPI / 72 = 1365px. Тесты `assertPng1024` проверяли только PNG magic bytes (`0x89 0x50 0x4e 0x47`), не размеры — баг маскировался. `meta.size: 1024` в `cli.ts:78` был контрактом, но фактически нарушался. + +Рассматривались варианты: +- density: 72 → 1024 × 72/72 = 1024px (точное совпадение). +- density: 96 + resize до 1024 → лишний resample, потеря качества. +- Явный `.resize(1024, 1024)` после density → double-pass, медленнее. + +## Решение +Установить `density: 72` в `sharp()` вызове (`render.mjs:36`). 72 DPI = canonical SVG default (1pt = 1/72in), даёт pixel-perfect 1:1 маппинг `viewBox` → px. Дополнительно: assertions в тестах декодируют PNG IHDR chunk (width bytes 16-19, height bytes 20-23, big-endian uint32) и проверяют `=== 1024` — регрессия на размер теперь детектируется. + +## Альтернативы +- **density 96 + explicit resize(1024)**: отвергнут — лишний resample снижает качество текста (subpixel), double-pass рендер. +- **density 96 + viewBox в px вместо pt**: потребовало бы правки всех SVG-шаблонов (`cover.svg` etc.), больший scope, риск regressions в layout. +- **assertions через `sharp().metadata()` вместо IHDR decode**: добавил бы зависимость от sharp в test-слой (сейчас тесты читают bytes напрямую, без dep). IHDR decode — zero-dependency, быстрее. \ No newline at end of file diff --git a/docs/handoff/pr-161-density-1024.md b/docs/handoff/pr-161-density-1024.md new file mode 100644 index 0000000..f8cbff4 --- /dev/null +++ b/docs/handoff/pr-161-density-1024.md @@ -0,0 +1,27 @@ +--- +pr: 161 +title: "fix(draw-image): use density 72 for 1024x1024 PNG output" +--- + +## Что сделано +Фикс размера PNG draw-image (issue #159) — три изменения: + +1. **density 96 → 72** — `.opencode/draw-image/render.mjs:36`: `sharp(Buffer.from(svg), { density: 72 })`. При density:96 SVG 1024×1024 (по `viewBox`, 1in=72pt) рендерился как 1024 × 96/72 = 1365px. При density:72 → 1024 × 72/72 = 1024px ровно. +2. **Реальные assertions размеров PNG** — пустые проверки заменены на decode PNG IHDR chunk (width bytes 16-19, height bytes 20-23, big-endian uint32): + - `tests/e2e.test.ts` `assertPng1024` — добавлены `expect(width).toBe(1024)` + `expect(height).toBe(1024)`. + - `tests/render.integration.test.ts` тест `"renders valid PNG 1024x1024"` — добавлены width/height assertions (были только magic bytes). + - `tests/render.optional.integration.test.ts` `assertPng` — добавлены width/height assertions. +3. **Регенерация assets/cover.png** — удалены старые `assets/cover.png` + `cover.meta.json`, перерендерены через `draw-image` tool (template=cover, icon=opencode, subtitle). Проверено через `sharp().metadata()`: 1024×1024 (было 1365×1365). + +Тесты: `npx vitest run` в `.opencode/draw-image/` → 55/55 passed (5 прогонов подряд стабильно). Все обновлённые assertions реально проверяют 1024×1024 через IHDR decode. + +## Почему +`density: 96` — историческое значение, дававшее 33% оверсайз (1365 вместо 1024). PNG cover используется в README и slaid098.dev showcase — 1365px ломал layout и не соответствовал контракту `meta.size: 1024` в `cli.ts:78`. Пустые assertions (только magic bytes, без width/height) маскировали баг — тесты «проходили» на 1365×1365, хотя в имени функции буквально `assertPng1024`. Density 72 — canonical значение для SVG-to-PNG при `viewBox` в pt (1pt = 1/72in), дающее pixel-perfect 1024×1024. + +## Pending +— + +## Watch out +- **Существующий race condition на `.tmp-render.svg`** — `cli.ts:64` пишет shared temp SVG в `__dirname`. При параллельных CLI-вызовах (vitest concurrent) или leftover от ручных прогонов → `render.mjs` падает с `Input Buffer is empty`. Это **отдельный баг**, заведён issue #160 (вне scope #159). На стабильных прогонах (clean tmp) не проявляется. Не чинить в этом PR. +- Density-фикс **меняет хэш** всех PNG (`computeHash` не зависит от density, но `generatedAt` и пиксели differ) → все cached PNG будут перерендерены при следующем вызове CLI (meta.json hash не меняется, но если файл удалён — re-render). `cover.meta.json` перегенерирован. +- `buf.readUInt32BE(16)` / `buf.readUInt32BE(20)` — PNG IHDR fixed offset (8 signature + 4 length + 4 "IHDR" = 16 для width, 20 для height). Работает только для стандартных PNG (sharp всегда пишет IHDR первым chunk'ом). \ No newline at end of file