From cb66224c96a50c177b53d204f5103609f8e24e8d Mon Sep 17 00:00:00 2001 From: wouterlucas Date: Mon, 30 Sep 2024 19:22:27 +0200 Subject: [PATCH 1/3] feat: Add containBounds property to CoreNode This commit adds the containBounds property to the CoreNode class in order to control whether child nodes are processed and rendered when a node is out of the visible area. When containBounds is enabled, out of bound nodes will not have their children processed or drawn on screen, resulting in improved performance. The default value for containBounds is true. --- examples/tests/viewport-bounds.ts | 55 +++++++++++++++++++++++++++++++ src/core/CoreNode.test.ts | 1 + src/core/CoreNode.ts | 34 ++++++++++++++++++- src/core/Stage.ts | 14 ++++++-- 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 examples/tests/viewport-bounds.ts diff --git a/examples/tests/viewport-bounds.ts b/examples/tests/viewport-bounds.ts new file mode 100644 index 00000000..acf3ddcc --- /dev/null +++ b/examples/tests/viewport-bounds.ts @@ -0,0 +1,55 @@ +import type { ExampleSettings } from '../common/ExampleSettings.js'; + +export async function automation(settings: ExampleSettings) { + // Your automation logic here +} + +export default async function test({ renderer, testRoot }: ExampleSettings) { + // Create a container node + const containerNode = renderer.createNode({ + x: 10, + y: 100, + width: 1000, + height: 600, + color: 0xff0000ff, // Red + parent: testRoot, + }); + + const amountOfNodes = 11; + const childNodeWidth = 1700 / amountOfNodes; + + // Create 11 child nodes + for (let i = 0; i < amountOfNodes; i++) { + const childNode = renderer.createNode({ + x: i * childNodeWidth + i * 100, + y: 100, + width: childNodeWidth, + height: 300, + color: 0x00ff00ff, // Green + parent: containerNode, + }); + + const nodeTest = renderer.createTextNode({ + x: 10, + y: 130, + text: `Node ${i}`, + color: 0x000000ff, + parent: childNode, + }); + } + + window.onkeydown = (e) => { + if (e.key === 'ArrowRight') { + containerNode.x -= 100; + } + + if (e.key === 'ArrowLeft') { + containerNode.x += 100; + } + + if (e.key === ' ') { + containerNode.containBounds = !containerNode.containBounds; + console.log('Contain bounds is: ', containerNode.containBounds); + } + }; +} diff --git a/src/core/CoreNode.test.ts b/src/core/CoreNode.test.ts index 1f0b286b..f49db7ea 100644 --- a/src/core/CoreNode.test.ts +++ b/src/core/CoreNode.test.ts @@ -61,6 +61,7 @@ describe('set color()', () => { zIndex: 0, zIndexLocked: 0, preventCleanup: false, + containBounds: true, }; it('should set all color subcomponents.', () => { diff --git a/src/core/CoreNode.ts b/src/core/CoreNode.ts index f95ff7a9..9aaeef98 100644 --- a/src/core/CoreNode.ts +++ b/src/core/CoreNode.ts @@ -665,6 +665,21 @@ export interface CoreNodeProps { * are provided. Only works when createImageBitmap is supported on the browser. */ srcY?: number; + /** + * Contain bounds will not process/render child nodes of a node that is out of the visible area + * + * @remarks + * When enabled out of bound nodes, i.e. nodes that are out of the visible area, will + * **NOT** have their children processed anymore. This means the children of a out of bound + * node will not receive update processing and will not be drawn on screen. As such the rest of the + * branch of the update tree that sits below this node will not be processed anymore + * + * This saves a lot in performance but may be disabled in cases where the width of the node is + * unknown and the render must process the child nodes regardless of the status of the parent node + * + * @default true + */ + containBounds: boolean; } /** @@ -1101,7 +1116,10 @@ export class CoreNode extends EventEmitter { parent.setUpdateType(UpdateType.ZIndexSortedChildren); } - if (this.renderState === CoreNodeRenderState.OutOfBounds) { + if ( + this.props.containBounds === true && + this.renderState === CoreNodeRenderState.OutOfBounds + ) { return; } @@ -2134,6 +2152,20 @@ export class CoreNode extends EventEmitter { return this.props.textureOptions; } + get containBounds(): boolean { + return this.props.containBounds; + } + + set containBounds(v) { + if (v === this.props.containBounds) { + return; + } + + this.props.containBounds = v; + this.setUpdateType(UpdateType.RenderBounds | UpdateType.Children); + this.childUpdateType |= UpdateType.RenderBounds | UpdateType.Children; + } + setRTTUpdates(type: number) { this.hasRTTupdates = true; this.parent?.setRTTUpdates(type); diff --git a/src/core/Stage.ts b/src/core/Stage.ts index 504406d0..3e490041 100644 --- a/src/core/Stage.ts +++ b/src/core/Stage.ts @@ -19,7 +19,11 @@ import { startLoop, getTimeStamp } from './platform.js'; import { assertTruthy, setPremultiplyMode } from '../utils.js'; import { AnimationManager } from './animations/AnimationManager.js'; -import { CoreNode, type CoreNodeProps } from './CoreNode.js'; +import { + CoreNode, + CoreNodeRenderState, + type CoreNodeProps, +} from './CoreNode.js'; import { CoreTextureManager } from './CoreTextureManager.js'; import { TrFontManager } from './text-rendering/TrFontManager.js'; import { CoreShaderManager, type ShaderMap } from './CoreShaderManager.js'; @@ -237,6 +241,7 @@ export class Stage { src: null, scale: 1, preventCleanup: false, + containBounds: true, }); this.root = rootNode; @@ -394,7 +399,11 @@ export class Stage { continue; } - if (child.worldAlpha === 0) { + if ( + child.worldAlpha === 0 || + (child.containBounds === true && + child.renderState === CoreNodeRenderState.OutOfBounds) + ) { continue; } @@ -614,6 +623,7 @@ export class Stage { data: data, preventCleanup: props.preventCleanup ?? false, imageType: props.imageType, + containBounds: props.containBounds ?? true, }; } } From 11cfc4c7aca64b9e38656f6f4f100c29c7bfbf40 Mon Sep 17 00:00:00 2001 From: wouterlucas Date: Tue, 1 Oct 2024 10:05:24 +0200 Subject: [PATCH 2/3] refactor: Rename containBounds -> strictBounds Provide a more descriptive name for the property, indicating that it enforces strict bounds for the viewport. --- ...ort-bounds.ts => viewport-strictbounds.ts} | 43 +++++++++++++++++-- src/core/CoreNode.test.ts | 2 +- src/core/CoreNode.ts | 28 ++++++------ src/core/Stage.ts | 6 +-- 4 files changed, 58 insertions(+), 21 deletions(-) rename examples/tests/{viewport-bounds.ts => viewport-strictbounds.ts} (60%) diff --git a/examples/tests/viewport-bounds.ts b/examples/tests/viewport-strictbounds.ts similarity index 60% rename from examples/tests/viewport-bounds.ts rename to examples/tests/viewport-strictbounds.ts index acf3ddcc..b845138d 100644 --- a/examples/tests/viewport-bounds.ts +++ b/examples/tests/viewport-strictbounds.ts @@ -1,7 +1,15 @@ import type { ExampleSettings } from '../common/ExampleSettings.js'; export async function automation(settings: ExampleSettings) { - // Your automation logic here + const page = await test(settings); + page(1); + await settings.snapshot(); + + page(2); + await settings.snapshot(); + + page(3); + await settings.snapshot(); } export default async function test({ renderer, testRoot }: ExampleSettings) { @@ -15,6 +23,14 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { parent: testRoot, }); + const status = renderer.createTextNode({ + text: 'Strict Bound: ', + fontSize: 30, + x: 10, + y: 50, + parent: testRoot, + }); + const amountOfNodes = 11; const childNodeWidth = 1700 / amountOfNodes; @@ -38,6 +54,10 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { }); } + renderer.on('idle', () => { + status.text = 'Strict Bound: ' + String(containerNode.strictBounds); + }); + window.onkeydown = (e) => { if (e.key === 'ArrowRight') { containerNode.x -= 100; @@ -48,8 +68,25 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { } if (e.key === ' ') { - containerNode.containBounds = !containerNode.containBounds; - console.log('Contain bounds is: ', containerNode.containBounds); + containerNode.strictBounds = !containerNode.strictBounds; } }; + + const page = (i = 0) => { + switch (i) { + case 1: + containerNode.x = -590; + break; + + case 2: + containerNode.x = -1390; + break; + + case 3: + containerNode.strictBounds = false; + break; + } + }; + + return page; } diff --git a/src/core/CoreNode.test.ts b/src/core/CoreNode.test.ts index f49db7ea..1d53881a 100644 --- a/src/core/CoreNode.test.ts +++ b/src/core/CoreNode.test.ts @@ -61,7 +61,7 @@ describe('set color()', () => { zIndex: 0, zIndexLocked: 0, preventCleanup: false, - containBounds: true, + strictBounds: false, }; it('should set all color subcomponents.', () => { diff --git a/src/core/CoreNode.ts b/src/core/CoreNode.ts index 9aaeef98..c9d127dd 100644 --- a/src/core/CoreNode.ts +++ b/src/core/CoreNode.ts @@ -666,20 +666,20 @@ export interface CoreNodeProps { */ srcY?: number; /** - * Contain bounds will not process/render child nodes of a node that is out of the visible area + * By enabling Strict bounds the renderer will not process & render child nodes of a node that is out of the visible area * * @remarks * When enabled out of bound nodes, i.e. nodes that are out of the visible area, will - * **NOT** have their children processed anymore. This means the children of a out of bound - * node will not receive update processing and will not be drawn on screen. As such the rest of the - * branch of the update tree that sits below this node will not be processed anymore + * **NOT** have their children processed and renderer anymore. This means the children of a out of bound + * node will not receive update processing such as positioning updates and will not be drawn on screen. + * As such the rest of the branch of the update tree that sits below this node will not be processed anymore * - * This saves a lot in performance but may be disabled in cases where the width of the node is - * unknown and the render must process the child nodes regardless of the status of the parent node + * This is a big performance gain but may be disabled in cases where the width of the parent node is + * unknown and the render must process the child nodes regardless of the viewport status of the parent node * - * @default true + * @default false */ - containBounds: boolean; + strictBounds: boolean; } /** @@ -1117,7 +1117,7 @@ export class CoreNode extends EventEmitter { } if ( - this.props.containBounds === true && + this.props.strictBounds === true && this.renderState === CoreNodeRenderState.OutOfBounds ) { return; @@ -2152,16 +2152,16 @@ export class CoreNode extends EventEmitter { return this.props.textureOptions; } - get containBounds(): boolean { - return this.props.containBounds; + get strictBounds(): boolean { + return this.props.strictBounds; } - set containBounds(v) { - if (v === this.props.containBounds) { + set strictBounds(v) { + if (v === this.props.strictBounds) { return; } - this.props.containBounds = v; + this.props.strictBounds = v; this.setUpdateType(UpdateType.RenderBounds | UpdateType.Children); this.childUpdateType |= UpdateType.RenderBounds | UpdateType.Children; } diff --git a/src/core/Stage.ts b/src/core/Stage.ts index 3e490041..3a575071 100644 --- a/src/core/Stage.ts +++ b/src/core/Stage.ts @@ -241,7 +241,7 @@ export class Stage { src: null, scale: 1, preventCleanup: false, - containBounds: true, + strictBounds: false, }); this.root = rootNode; @@ -401,7 +401,7 @@ export class Stage { if ( child.worldAlpha === 0 || - (child.containBounds === true && + (child.strictBounds === true && child.renderState === CoreNodeRenderState.OutOfBounds) ) { continue; @@ -623,7 +623,7 @@ export class Stage { data: data, preventCleanup: props.preventCleanup ?? false, imageType: props.imageType, - containBounds: props.containBounds ?? true, + strictBounds: props.strictBounds ?? false, }; } } From 3f04e9925fbf954dbbde12b382a752f2d755cfee Mon Sep 17 00:00:00 2001 From: wouterlucas Date: Tue, 1 Oct 2024 10:36:48 +0200 Subject: [PATCH 3/3] tests: add viewport strictbounds screenshots --- examples/tests/viewport-strictbounds.ts | 2 +- .../chromium-ci/viewport-strictbounds-1.png | Bin 0 -> 13449 bytes .../chromium-ci/viewport-strictbounds-2.png | Bin 0 -> 12502 bytes .../chromium-ci/viewport-strictbounds-3.png | Bin 0 -> 9169 bytes 4 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 visual-regression/certified-snapshots/chromium-ci/viewport-strictbounds-1.png create mode 100644 visual-regression/certified-snapshots/chromium-ci/viewport-strictbounds-2.png create mode 100644 visual-regression/certified-snapshots/chromium-ci/viewport-strictbounds-3.png diff --git a/examples/tests/viewport-strictbounds.ts b/examples/tests/viewport-strictbounds.ts index b845138d..eeb0cd66 100644 --- a/examples/tests/viewport-strictbounds.ts +++ b/examples/tests/viewport-strictbounds.ts @@ -83,7 +83,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { break; case 3: - containerNode.strictBounds = false; + containerNode.strictBounds = true; break; } }; diff --git a/visual-regression/certified-snapshots/chromium-ci/viewport-strictbounds-1.png b/visual-regression/certified-snapshots/chromium-ci/viewport-strictbounds-1.png new file mode 100644 index 0000000000000000000000000000000000000000..6cb74d62b8008424dfff3ac9d816e30c01cf93c5 GIT binary patch literal 13449 zcmeHN_gmBF`bMdsRkW?G6cNyZ(i9LS$R75fVwegQj4TxrKt|XQAc;zqS{V|;2rDWh zT9$wiW&&y%VHJ=W2rEM*0ZAYsWPKCAf5G|XT-Q16PtWIbk%add_kBO_`#z7ZxY(@#}z;qe*Pt)h&2b# z4}jcS-|)K@@5T&aHn`l|Lzn*7n|&+bd^xeI>U)ol@8>Gw1*O;=KIzMZUsbx&7|2 z!Y-M4dVBYA`RgxE3SY>~-QV}!mB0SCe>sk@_in9O^eyCO#ar&v(!{loc~P0s6E6{h zKCg^k?KMkboHf!H21Ie~A9KYhGksOj@_uCR0NNThsN8FN3Ki_@BeO=nDJk$r#Q;MSGjv#FK9Cc;x zmSX|fG+nqBx>%G^lw4bqEL5fN;PXtY+rBQAHSy%IY8{GFcYV}g1Tjg_r0;jmczH6y zHI7qf4dfy#Y>Fd(iTCBd zzBtype&Vr3wCL+O>^#NE9=H;8%10wzzj!hW=vq*i*&%w`zLfR=J3c+d&%U7-Av zNp>{q6~NY;ifa2oUtTHke!`+%2|Fna+Qy-x>{?NBhz*ij5jta1Fj806P?~iyy3m}t z`2arChiHji4w7uOJA=@NGgvOA8}o-pTr3h9sVXFXp*oj4e~`7E>=76>wN%r-!8!@& z*fAJhq^aDHaH=hIcSg@pwW5FghMv4?7vgW>c738H4Z1X7lb70^TVmh#W>8Wu-I%s9 z%jF}Z?V2_-A#=GS#_E0QLpRX6MQ3yep&Y3sCRoY-hM+RDx{o2L6ZQ8W9DsX<4%79N zIio#>y{!hNf-(MFfoD<6nPIC&L)1prxfWLIH;h(`qwr5>t<6{86OW-T#J5_gx7-=O zX~QiwX!-O}qQADwDwmydxGO|>vp$k? zvxW%&F|&1^HBfTyoUi8VS3p-}@2u6@wz>IDr2Rwt8(`v{GdNO0$Z1h5>Y;uA#_;C? z{Z)Ce4~Ghht!a;pAV-UODi03LmOzI@_-nFIX0CnC`ua9!XSOv`XE^D7yNG&XPE9S0 z{-#pMeYPGx@JH-n(z+rembM2!I9#grS7Axbr5Z0_k*m)u^TD~8iAgKS)sjdDKi}xJ z8Shug!f=#ws^JPb$+y|qk(qbm)1s~AnQEQdop%MI&6#`0+Ft;N&B_fkTm4|U1|NER zgQ5lp?F6g`UQrSbTkU8tn+MM}8sn(pH|baT>w}{LdYzufA-bdOT*108(Lii>II}}~ zYtDQXLzKf}bMSQPJ?W=EB*|;f4O$kP7GLv3Ve-<-e~3`(bf%C`JmXu(c!XoWRp_a{ zVTv|IAm=iMZFl_CffIM=Y086xmZa7bp4CSO$c@@yi&|~}Y7aS14`+NF8b$%1s86zY zl9x46Z;IL(fXAHGLM85mVYO?ZeH8B{mqWre>cGUKJIQ=bn4r+LcF?w{Lr2+wHPCO{ zZlPUDARQSj8SkwZXiUFG=odD*zUz_)(s>w+*3OHgXER|#m{i5W83JT&L104ANECS zANoLUVZz8crU%RMm03WSqU=eV*sBMi18{R zqZ}0!n)j*k&fMj2idBIJIwvGG-usu*oQ$%ZW5hC`bbC5huu&`IZ(B2COu1yBDubb- z4gb@Al9eA8H!?0Z=YOY6uX5kvq&pHA1%;`jw#K=29wB*Z@k!CLRk0K1(l6Bqh1<)S z@UhT>gVhQfQVG!=x|lE%+0h~^Gr>Z<3F9!+`B!2{xEfj6w_e}XDma{oTxkL{XyZ%A z9d1;6tVQ=$i3F)0>-p?>dU}ev5;EwO zN?aHU{^|6vc-Rk&LrFgFbX@9tmgOM_c!+6UXWQ+qzcNsj%Lmto7B1^)iEGmZA5dX4 z8~Ob;>b-e>sLLh1zlhdKoe-{N^!w2o@8mF)4P3&|AovlZ&ZMwWJ>cT>7!!2?S69Ec z>7IuinN?Kd`emdHNla@)?_4}?G*8OkQfU+0$g$!yY@@hvj&OiJE^Re2Gz;@B;eH}o zt=>XRH;3t~H)SwpydHAq{KAKakz=@#;DHMvC#PSB>3hz;;$M&FbR-&)2w2w=QZV^h zg*qXfUUaAy)MI1lQy2~mejSjF$t$+8fh}j&|19S$cVYcaD8f3YtPT%V<>>5uXaOrS$?Y_}xYDlr5*M*p* zGemR>_gyx_r3F!y!|5j$=gynu=b;j|PD?8Yjc;Yz#Ip*O{cAawy-;k8TDU(n+gc|I zr6~1qDu$~m8*SE03pK}HKLy%K+2<$1OLp4Y0{%Q-<7HXYqdBklx$l|%S$N>Czdyud zCS=H`P*F98(bcm_``JF0`2ykt|IiW0Ov`34bSHVtFuxnNCj;1hyskQgZ^TkBZ#>15 z&G>4O5?#Jf+_7N`>!<5E{u){)^Pj3bOddm#45ge=Meais$w1Y|9r~kY<7+*cR{@UF z_mi`CawbQ|M|zjw8U$gZjF187}UGpa%w;7quk{Rqa3Du*fWto&& zoZg6jn;S#lr#sLWnHnD$>1W?MqYVN1*-wO0xg>5nA)RKK2OCz?9qJ48Lqwb=R@kcg z0iVcfF1Z3!cyxvt+EtVB4H4<7diOd365~+S z9*n3@9>0PVtxA#^*OZyHC9lt@k{WEQJ@#EuC$J^(ho>|~6O)_+83FO0+SNRB#ds5= z#5U5FW^qD{D|NHz3N3^|EdcVfOhG(3@B!#2;CKLzQ@REd18@*!t&J#3(B zza{r|DvvJL#VuDajAwF;vrO{Uj)p-(^;=^8yjv-jH|nAcR-7*7eoNVtF@ktfNYAa@GoSuFzqx`mxi@S6(tTZ?I4Y5`%g;*HUE8cUej9 zMRWd28XS_~pjlAOd$VLN2Hg^a?5A>21I{C3y+wuKO%=Wkpu_>7=eI17iazbF(=HGgL zAGeb=#hI-r7$e4XY{V&dV6_)#jr>{ZAwIHdT~o9`W~*t2(Whlgka0_etTN2!2CX*A zTFjyNGguvY8zo!fY>gV`ZE4?xq)yiCkg_jn%YKt@5#-@3ni8~x^#?I{#$#iRLcdM9 zw&M~6Kf`wbh?T-Qrv~uz4?pj>td9t{Z&HzI>5CjMlQhQf>Pm9auv+{%+K7N3s4p6u z8(5LCg0gS&%@ScR9EcRl!|v2L85}IOs}`~GlMEGEdtvT+J-ALw7J|cQKL+Befcitv`B-d(nksI}HmybLct!+omZ-YEd!nFn| z#Mj84i_S!TKO4@|W_<1g7W@;9&dieArw2s~zq4hH_?3(q=c>Yt5SSVne|oA&Rn#SF zm!9mGZ^SlZOpGckv*dlQA$<0}@u{5Q1}%?+1jJxVNOTv64czh7F0D@fcz+Py!_8x? z58ptqP9DTJXoJo0+FIMZ?jjj&4I!`lQH4to!C&zvYn*K@?-pxUvs*5bCE2cqY{!5K zZ|Vi>C~w9F^tETS3-+(p-&zflsT_sWZi8sZqZN&*G6u34P)g@@#-?#JZI_SWPx8Iw z6}(qG_e=R^CjU`}ou(uYHcQbk303d+L=n$<&KqMEv&u@Rdp}%ndD1I-!pB3!*z#h-7 zK=Fjp;|LkGB{Ml;YDWQ{X&T3Z=^{O$oK|sBu?zDxnlARG?_L#7q|;JmpAR@BUY8+q zW$jJ?W#PI^f9d{ZIkL@{EY=b7&0X?>mwE$a^xE(CJ!qV_f7Bkj#N)A~k~zTbA=MDpTNwRytTz)L;ob#LFAA7v$s)y^9t z(+5qHx* zz*=;-CMMoQ)|u=yB#u#Cr{X_79~xi>Gn73y1O0{G4^3;-`%da9)juU@H?$sYL9#E~ zY|o|3`1ZWVtVK~vLt$`P7ES^^uK2e4Smb%n(>`aP%*+08@0kZt{5TBU5o|0csgI!n zH=A=DJq|Do{Je2EY25K;Z8;p*rFW9Tr>_h(m{nF{&&jkEs|D*zBDO^0R59}8-kd+G zOA0UVH2D_$Axm43UEA~>s_)N#Yb@V90iLL(R5 z@A2_|-78-#Z?5&58KjN08T!ZEk4)spx*o+t|Ar@u|6Z*BH%?Li>wJztab|R+Rx8{( z9hgHD-B`;um!_313Mk-ffjBTw37TdOI*ny4PoY$F;NfU3=hmodkNiIzfm~c#3tEwZQmJRpJzlD`BmX+NnzD8OO3Z;mO(~h{J z`jTBqoPG>{fu0>s!O+G5%P@;39YG~#!&+k%-TqS_7DUYn2r9_*mu?YtP`LrCWfWo- ztq_=yQF~G#I`R~p|1hAeOahW?%gZuA_X!DEcdz4(*nkxKKA#be)rY%njEqb{P;W$XD4G%>IsK^4-n zKtrH?PsY-XSfm=KM|TO0wpyrRT=9feX+E|z=RGK4w+4J1oXd+BUe@f0jku(aNHg zf?S-ziyw#HeW}UnL!hlrL3kgAy4|2`X8AMGohf%pcRaoiJ-Md-Mgo=T0MU%yv{Ck*4X&$z9$U-12 zenPtku#6mX7>!Ov?&c}uD?%-O>I!SaoH!3Ld{MO94-Re_7X)LNv%G!~pL7xuH-Fgu z6f}*snOvA%ViY4z{%lPNuE$T2r`}E!_uD}_FvEWW)BWmuf6k_E%eK-_t~t%#7+<( zA2U|V^y4kvB(RvWDt^HhD)YF)8ms^S`%!E7eej2Q7mg6na3jNYItdky=DHs_fvVvSb*bxuLX4V+|1|ga@0}*{jHtfYmwFf1oxuQ$Q*`IxtK?=& zQ^@k>CrtXpYc2i+CNePyorf<93}SM>L>SRkxLKqWTuQ68-pK?f8xr|C*`sYv4f82}9dkA@!68U;fvi8S?Y^5@#aosDAmGFc@f{BfiV zI0qYx)`BCZNax({b01(6XR1Tc`Y!trJ!QwRd>o)_?Kcd46$I#~lsRp~W5}T&v)!PC zH7l-k17P1IvY>zBJ@&`XTVG)Mh<7*dKQ<>xNIFIBG|@G`If~eEqsB0xOII2)FyLRR zF{Y)NklWfZ#%)6rGiZ(ykIZAbI)KfrBrZGVFDAE3CbEHBDuH1RdqL!f>^B(ex>cjd zy~DFSoHl@2De4CiiZL(suG#?_l;@uq32*w47d7zCi3eC97&)}VGw?R^vK~Z~F?@>M zdm$m%xfKyC9KMIa{&)zt*^XyzA`ZHOOESE2ql`9y>r?%KwsmF@U8=g#*&~FCw9<_Y zGhGO8ya}VBx6E*9l`?^mU%wh*wT=aV(EjewCSx~~?*}{}ttHvt(1^v4V#m$_R!c!@ z?vLHOm%;%3^RK0$WodosfQCxyjATY!&*bhhgw3l~R9ZrG=c3V8aieTI?9}tMASuEa zf@mm32$y&`MS%bMCdjYNuVU?Kiv@(S8VgvKPGMe|%{@L-kMep_uSg36X}%|h9q$u4 zwKIoZro*)c-m{D00E#Qc?euXYVz@=FPvEoDF6K-0azN@Iw)*Z~yW_ofFGyZ!)1S=R zLziqnE&|TN^dXB8cI2uTR7|CFliLwZLK6vjolKS#&iGO64p->N4!lg|vj9(`de;NA z9I#(XSv+Rxv{>MCGyX`$z%K}X8AbESBfDf}cb>O_@xW7*{TgyY=A&`R=tIx1gb_JM@ zEoK?3dIv0A)h+jqSVWblk@|Flkb&e|VH7u$JE&q>H1_(-kC)BM5@z$<4hG|_6qR@t zSyA7;c6YyRZ%-}}Gb{8Jr3MiH*Vy_fVwd4$LSxp9mtQmaJDN+1CHQs zYHs~)UJfSOuPa6)pQC9@v|GE%rbs-=`K`zMNchmIxUzQ)MWeGR>-zvqLLy8UiE}ka z{A;poz3xXy%F;D}dP9ht6$TCK)CY!Z~*y0pk1#uY~CbdI@qQ?P)B>Y>YEP020t;)mrd zdK%{KxT|x^lQ$cH)UPs!hg#!>XHmsQRMIyM*Ra?<0LrGT!@j%xFq9~k?9zd9G`FXB z0s6;CF6Mr82w;8r5UAz+prw1aK$U{ivX(jR5WFuFG<|~mik0}q{&>gq*K304`Wr5@ zBs+hYw7|2h=v(+L*+n(xH<`kz48#KTg#I#Z>JHqjcA8eSw29q*iUIF7#sB&SDwHgz zzD^G*AMkED3(;Pja&o#ie`wsoMQf)u2iPigov&}3+XyQKByF#ot~9B;{lMu1z)U8~ z(|V{paiV+oOb!5eH8XUc*#%I4Hd*$?2Xg=n*)iRqXLOdxx6>sdq0-!QlvOjIVxtKp zY&6W)5u8EQJ^9=_r@klVXCcxD&`^V%RqS>@{I4O7Fg&O_xpizebb)WskWc!2$CrzQ!2vn`R4$@ zsvfr?wU`g#L}o7LtWAN@8h^PM(7p0&N5+_*dpB*{V$=Z8-%C+K<~HG)|rjYh9)=xzTTqGyho)&J6 z0QhB*ca#<+wB}+PT#nmnkHOGdzc_1JeBxVWf-SEstNxYQZKc{Qv6p>CH>r#Tw@4mk zE;C;hRb{`1OV^y)h6FP6=k!C;N$PTT|`mHDf6i_`@ti_-T5{8sOVngIygVzf5J z>!CUY>c4FkurvEK&#bHuvXJ6BQ}=m7kM7Nw0RHOU?5(na_xD$3;4PDvTRNG|E!fr? zi?vQx;?df&n0pB4-=^U3LF@aK3i2_bzt?!xf8D{r-**!3KbWd>H$ba3$gexCC+++Q zoEqdl87?|~^LPCX<_-UYQU2#>@ozcN`d?OIb=`fnUem3We)78}*%0k5rQ`C4|FJRS ze>*8sl7{6I`Mu!xG#DbY_Ka-cgha}OaVu#;Kg#Y_33e(gR0fOFSWCuyHX?{X~wpJ_+mz54t+-4_oF0AJ|ruAP7l zTJ!sr&-Upa&w2qq*P66nx2EYH&wRV#zdHNPfxpG%(enL(PBl1AF^)6K&&&RxJV_&K zuqk9a*-}{sIF`L?Z)IipyD!2hv2WE#js=zCT1)o3EO|tQ~s-6m~2R~QR|TT z-kE~ApWrSvkT5eR3y;HdeVKL5k!F?WeBU8}6wuJP!)Ku0n=g?M}}*md;|0j3L%OXkwCjZbJTM`X`0eed?MZHur^ zfDS#6p)zNUqQH`s&XjjAU*Q!Bab02;1QN z?e5*zt7?x>KXN6ClFFN^uP*E0l=yK>u>D1x&kVEC=L3_bjzPE)Uai)c?_sZ;7#!gD zwo{_;pyM6&*h<~9z*x`N|&YPb{nii#8vliBj*q47JZldqz8~VHqa3s;b}v?l4_QCHIgZ{ z2riDP@|To&@TM)<5}V4xgF>p0jQ(!Vy@a~|FO?u2?0XVAOL zDTZqHPiHXu>+O=5y(ve(u3*{WdfH|Wlz^jwz#svnh_RhKe-GMhprY1wj zU&KVWVJzX$W~Yve+}F&F;shZTUPx)omN8#)q(-I@=;>c4*O(4P^7X(icFw`sM76+> z#pYNqiX6<7jyg@)wfBdlOl56Bq2Yn0in?7eQeMt1eTz*{@iHMc%Itzu{zT$xjmzA- zxy&pl#X5Jh;(mQS^H+yJ;>5PFCh@K?7R#>mn1yRe7LSUxFKfScd@Nzme`2j250-sY zw<`zFQLjx;m8kXg1OK8dcVK_q-EVXJT{jK)T_KF0NM{5k_ngr$9LZJq$k;uCb z`(Trn|;=mTlBUhfbcotN=!3)mMszQxs>dB=^wb$R!~e)d~Ho%z9xTW;##>UdBq zfKR|v^kLcBJ%_+f^#`XB)B{dmqt<=2)xqPsc;wgbWj^qiQm#72{rR3%oOl99SQ)y{ zYee_-6n(7Osgh5L`&3$Hp3MINAi94+lD;*_#Amk?U09xq*5;k#&_Q7Di`SK{?)#`bLc zp{te})!HWfU`Ueuku0+JoTpP*G%}f>@I#{_&8_ELpF|%fe!P7Szo5n6@*9daiJPc# zqN{TO4uy5u?&|kWyQhA6by2sh$OEUZn;WcVHp-h7;;{E+Man67g4u5I$0m`~@NnQNhht zIRjaeXKg;72VGlX@Y)haLO#_FS8fPkZT78k+-pIS!dl$V78p#ka2QUpHkBF^hE8nu zIFRrK^3ZT5YB7DmKJPHnp;h!?Z~rsve&Lf%rpiWk9ep|4lqZT?if<9=u_;9=v%O3S zv=z&YYd=Tmq#Xq7T-aa1iJU69 z*(NVEaD9$yp6FlRE?%)32d7*Pn_6xOdsXV?y=U8dw za;DHPr!4nXEif*Xj|Ca#V1Cl7<6Pi#bS5DR2tY6f9ax< zvpL;b&SKYcgf*cCN%%I0VX`2RA}6q zod&%0FaAZ*Rw`y}Mtm57nCu*Af%=}9d_H<~k*08c+#_!?b`tB-wGrcdaD?Yp5D{(8 z7~IuOjd(kn9|vO1hI?Xg7VO0LnL()oq>FCBxG;cn{)DTosr)=kL^|R9E`Qh(`~fp8@QTv@5rb$)uX2fKS6UPGclv&YFC0gLG;`R zKcqm1#)R%?`ejc&U=B}Zt2_pWPmLL8rD&SFQsjT=wWYL|iQBVv;Gg{((Ck1`^P4lX z!!n*Y9UJWX)geU5({j?3w)oH=unrf#EfqaZKt8M5xj2lmITztG!N%9ff&-c^yD-g6 z`WHM~?ON(xBb>6t_irqGTyCx$7+q*$mkyMo?E=wL4n_U5-)vgCE={&H*;9DRBiX`K z3UPbQ%TVIp4ClAM@AKEXl4%U`r%3MUMXd*yr$Cc`$u1Z4^BS&Lw$$vQXV9M7Z0VQw zZY$-L>FQ=6@+hcY^MR3>?OLV_H4zi@MY^QVCB1??+lHs&-ZjSyJP?2WqWx=>XkZ;yOKC| zV_4yPR#d+9Dn_U!w}$Ym`yV^y?{~D#KmU-s6l+kx%{|$;bge<;nD2T)-@z?Zot+aa z|It;o_8c-aKG0j-xm07kXJYaXwzb(n9@s8{_&hF5vOMUV1nx&;Wn)j*)<=d#`MbLP z?b|H08WSabk?5%J6S`2m)HgG}Fd_fbqLo-jxvLA$S8cUdwVNt#ZWg|RQ|{i=$ZJe& zX%_1pQIvu4eBD+*{FJSe0@*=43Rh-KXDnLX(#y$@8$F_pTV3|+VC1t9tt#SOUydn_ z{{dN=avh^B$y%Qag%-(jQZ?&z`Hwc=M3|jOwac`gq-Ou)y7JMtaQ?xjdYs zTcdX^yM3;hs;d#0wa|UXYZa_@2{DqF&x~ZNfhCSQI|pk|4@fwFLhe z!ntXo)x)LgTSn88XwT%phD+y@2{QjUTYKlMkW}0x*9pV=kZ1==ovAT7TCIge)< z)76)U(wCtmljh&;w9s#a+a2`i$@9Lsb6R$UmglZ}XSUmuL)vu{v_}PYUupBl>vL;{ zsdJC);^+|ueRq6ha3UsfZ0z(`t})TcQ>;k#i+OD&Eu?DhFX-Hmt)-eq_zbhJ)Rx&$ z-*!Y?%)5675&0*tuj=&i=x}s(rCzqZ0t#*V1_L2}AB4M6?9eOr}FJv_hmld5KqyOCq|-vlgisP#uXIdB8B05XDzWwkhiF zfqGpZGgA#H6S?t4uN6*6eiCX_lCLKsmnX@)wi$*E{dIyr|MV^-=t)R&J#mS*-7EN) zirnkZ9Bdm~a(`YoRSwASr@WKL2j+K7vhJtM&1B)}h%glWC98&itaOg$o`3j5Uu(ZW zsQ}fCV}H!UtR=<;uRik!Yd)%rlg`4^-HRx1x%rrhXJB1!eF?c8KNPDgZ^CoN@?VjZ z5`)g9Sgl*(a1V=%C0RPIz%Tm84Ho#iff7%Hc}xBG>KIymC3_o}rB%rE$Bx1dTGK(D z{L=F1Rp+rhYO0jFGBG03q6^!FSXHRE8H$f116#V+X{m~-;?yfhf~J^?=jWl(iWs|a z%WW!dqZSx8t&!elWA>%D(Q-p%P?4Iy>C>sF7=cD#y1GBrHA<%`2+vXuBA+J=`2>Hh z%Z=pn^>@|kE-tR+#qo_hm4gOOqUK}|Tj%^|Ee^JBKiX6*m_v;juaFn-5m))?+qYv} z*OqU@hv;z7HxtEt-E})CPIbvq&oNH8p@f>eIGTTG*2g)N5SG+BhJAbFrhnU`zWqUM z;V#Iw*nJiuqF!^I0MptaTl&BLo1eNDhPw^Iv15Vvpo8!Q56P}Nr9Gt;l)paQme&rX_ma78dBQ(B5;NIW%E92nnsG~zr~JJo zl6yz>RFf1>0%=z~M&AURE1g>RK)mMF4h0bo(>;8LAJNJ}`|V{tY6;7d?I|w@wnUvW zYTG!JlIaZ#e%m^6ef&2e&vF?~jXFmfsmh#yhpuu>055ib_O6`X5BxGFULskroPce; zG4$;~0HM_wncFzgKHKQ4WhfJy<*yl4+%kWh9R0ZOjWI z3+n^T!w!Hifr3G*YAz||8+tq-r7Qym66@K`xhBm{zsa36aMJeXXk$aqmEuxCh`yTY<7rrQ9j?*J+G;QjP%q@d!G9x}YLZ(g-XerG%F3bfm# ziEUF|EN-a&XyCi_B3gC`Xoq55bDb))3fE8xpDk^nk%V=_Jxjxu%5w7WYw4B-&CxVF zI;N^G+TR(a+*K78P>KZHU*?;V`oz+w+%k`lpQ$GCC{FF4YY+zCwyL zhNvA;v(dDnIYF%mnHMVZF_RSmA!kcTLXhugE&ge<^hdC%iaso#Uu7vftt`^C!RErp z-N1peO~9`)gN0;3YP5s^f$X^CLn5APNMuAoy>Gw8z_9KTs#_x*st1@I`TT`P5fskp z4d@t``dKNjGSW47NG?aLN>kkEyMV@?<(yV0jVv$4L8SMx3l>~pDqxrbZ%mE&IzH@F z%&QNc{Udh=Jh;R(th)1%)qdhWI?od0V}vub!e1fbDl+LXj5jusv|Kg2NF*k>=FOY? z?pMCd5M=zG6lMvwfy|wug6NID35D%Y`aQ&JerN^ZoJ7E^72T^cK`Z5CinXB?71t(q z{u+iM5q&5W+tDQb(L5;Z_A6#{+j=3{^t#50PK0j-;to(D$>ut}oNp{VeF3QyjN(^%r?L6|(^4ccdOGWfuA1irUi7_XmYv z)nQFGIqDY8+i00sXsVei(|)k(y&EiH9umn(SoFk*`4@mCq&7pmJs9cgh(@XVz(mC@ zT2EEq8Sr6L{fre*90qnlC7kn<5M-zEJKHSj7;CCCgf+VN6vZkAM)E{Bj&9!AHsVD| z9rThus-^Ug5vKxB!0^JGr73#+7-n*)>k!Rq>020H`tVK#cn( z_+FsoS$$=3HFhuuF?-N_gS5yah5u?sPyneqG_^mP)cz@LnXs617adsDs1gJ3x^%T?* z6*-X`#eQ383HSETsq>N3$sdl5Bb=USu2s!?`IB}ORA*C|h-SZ>)8LAfIL6n~2{`A+ zl>)NA8s-ltK3gM@7A8p_ zW>~J*YN9PEWeo$*oKc{NfHGsC@o9;#eq$nBlH7ytc|LLvJNkNUxF_@2mVHA*c{{R^ zcZK(=d2Xe(8M!UhvsbQGWj>~l>un1t%RuO(pb?Da(t?sQ<9QDtCNd`}G1yIYn+k-S z;!|of@}hihAISTf>SXm_eJhHCs54K(y+JlQ43E;1kK8@+52DXtNdqei4qlUn!1(H; z>n3lcJThY68k5XDU;QVoBJ(XCC|(ULlD9yBlF23V;@m1Z)(zafnl_tg(`9%-dHFO? z{g&+CP+}lpf8XJZcs&w_NchETZ}0HYk$l?&9)T_J&h7&4stc*XZoVD347MHO&dt)B z#jPfV^5=jT&$g!JxHpng98vBsg!mgiUSOdU(D=s|E3hR>HY2<}BMU#G_3Z*M8O8R* zcemQ2^5329sWdr|5+IS>DA>vPXj&+w1HX>(#WB*AedGmuaChaAz_1gk^@~(%Ivv>f zg$lv{e0#H5NU~E!CTcVgco&q=EFEdu-Y|ldr8aj(hY$N`l>c2?KId5GN*$(f#y@mv4AWTSD zJ~;&8NZ4Ir68!X_D5i`q(m10qQRVTfCYaiRySYfQFO<;vM>F{^lB~HD2?`J zb@e2SU@bbj#IEey{lQz_XGyzfD+!&M%wwOTuJnN_|NLNGY2yBD{cp3Z}LZ*>BHdd4iWG zRK8dGaK1i|0Yi(o$R)9Ph5?Dk83O=w7>C|+&}!ba1p9^P6>N?@>`%bJDL2OM?bu8! z!e+mFWVjSiiq&U|EBmgkFGfNnyOxAJa-0`D=^O(XW^)RBO+*12wt%5@4Gi4#(V%hh zYW{>5_@$_i92W@k;%qKhE1K}~zsJ98Yo`(bC1ZY-eJ(L4<{<#Ue{bn}0a8$;JYRh) z^#_Z2#fI-hd&Ax2VQFA#Ez^9udIdB&hQmNNVIWS`w>s#0_f-fVY)ZiC6VyW}84(vj zrPPX|VPN6xUu6HEteZNQhsaL)cD^igd+OTR<;p(W4_=fnkA+w>XRR}buCxfQdovD` zdf-%PI=LI4?M)HkJQ??$8v~f}l_qtBI7Xi~hOgzZz>EzOBwNHK9|pd*s#F{{SPK-i zVXP;(IeW?jr3uCSLfcaPtEuB&)Q2xaeEBiaUPicXpx_#ek+iHPD;_LX6z{%&NYp<& zLtdOtJpH-LVTk+8-C9&(ZI8=g)e*t<$K%;UMF{kqw^@3dGYUQQ2uQpm>mR1EdqO2% zkF;FOy-}+&DQf8KutORDUcLC7%bqj#9u+}%h4(w*pU|f9zjIvp8~3&Rqa3!k zx&wt*7_RTvT}z~Sr2l(A@i*zt`bVMoAKZoaZ=C-b-Io6!aaI!{3Hx;YT}OK-R?U!? zKL;E=@To8LlGmrk(-#GwS~t?Z-SDYtHRQ8TU8%1>->0hqnOncpnLRtxbmrfQ|2jt} pmH$P~zuBStoAqDn_y1G#Y3B$iYQF&t0E$nAejmZ0wJg8rhnl1@p(P3`2(*H_lukR`druNefV70 z>%M(9GfEsv?3n0*L(8*(; ze{;QZNkqI?NywC~A_)BY@k9j_o&v$X|b*5vLwnQ%1_(0#@c}&rz5)Y4(%; zHCbJ8PKu#o#zyVy?K{CiFo$G|{vooCZQcv1WzH1dT&Z+Qh;B|J=(A-}6j`Eap#sHJ z$Q_l|7n%)*~lR4X{*CtlS7d_7p`>{4#Nv1e*EzJTwFxZv!Q^3%cZA1FBQXyBZD?; zewuFd-%0y%Sqr~AvOBatdAvw~KNu9kTMiNutKbsbmDh86Ri7OL`fIdHcQd#E#)NZo zD&l;L!@L(TzRek}C3u7bFE~ouX!zs3ut$uVeN2Z6m-G0>V3hCSHo1k3zq?pJ8V`OInyaxvkSxuktXy7hC32xZIfLC z|Ms(ClyZdIhgam^>|*c_rh5AdIi&7RS9T3Vz+_jUTy-i{pK`k2^K?tOK^tDrTl&JE zvQ{`O@D*|N6dcBbo%QJi<^d;xhCjw!#>&o@%}{gsmkY~ihl+B8T0aq+SHFF*i1Aha zwZl4P#@>~<)MQ*(j+6A|wdkIeuvNp(zO@Wge-c2pxSu9G}@hi2#pZ$vjC}W?%(8~fVhpE-x z;;2ru?o3b9r+i3SM3nx!GwsiL0yo)fB!&FVE$fN^t*4*+Nm|mMM?AWc&X|;VVM`o& zPml5Lh+(i--|~-q{&#A%>JP^9P#;Fi&=x+d?^M=F1N8TQ2GF3K8&>K5njk#ZfvVKo zJTI1)L5cgy$YgxO1-LOgp%Hr?F{5H9G;4m;PvE%MA)Z;X%K_Tf=LQ|IR;hQHynil# zna|}pa@2$&X;A@&CqyR4@PcAL=4WxM1h7o=E@#+yv9>D%BcBeW=fXRQiV8N{CS_!}@9h?dhF8rl1cBRJx1R~b-bRyH@0*P2(! z5@i1v>@TEfA$@Gw9YK6{By2lNDX0Yl08N+16*v#&hPvw*>@ zeMeT_xvcA3|E(af5`|Flb@BQ;we1B3w?$WEVu zveqNn_&$csJc5Eh@7&b@51%<)f?*2pJS&5$7h7 zJZPdBWC$GhygzJ(*SaraTs-u*tYJU|`6y*I#~+Qox>`oo&zF;zUbF=$K5^BJYTZ)+ ztOIj=G`8&g^@)sUUuBXQ?2!wOKO_U{77;K#Sko}wCW-g~T?M`0?Fh6sFPqF4+^{CIcT1ji=*#7F9x3gJ+@`cfT_7<@`Kb<^`KL!){46 zPLXC_*Y$6gzOcm%VyN3;CUxsBqOpCy*E8{u3;FukOyutjJtwiD9pg^XujfeH_>jF8 zlpGCugH~%rw$(~0X+DM1&y|nKcj+H)vH~ro+r~onD`NE|C+R?BW`-ah$?(ZHAQAq6^3i> z;UZ{P%rC0}1(Btp{yIHwIqJWjG38iUUmo5<=sIVNQwpe)zYdY5Xf=8<b8o#>=tCS{yOFIdw>kuRoUSdb{>y)TN{d03OAISBU;`X%p#uvz&J*P8PjuXge@wbS$wM30Qyi=`P1Ft3jiPb#V(H&bclsIf;rZ2@ZhZFXk6Z=5~5|Xu6lzWXQuNMO(r4w$Wu2 zhu3L+`nye;IWAkl=)i?I-_fxtt>b>@KYd@xW!!kf(BsyMJU14K?~w`^k^ygY!du() zTJnPWXd(+jA<4THB=uD1e5KSjBjjOWS8|;ZKh;byI_0uxw3I}1kXfVW!}clwk1AE0 z4ZYzlP1jGy*Wf8DhUZQ#mPTc3LPnXRa4cT(m92vNR|q(g;JKMA(8+0MS!?OJbRJ8hoI2WE&Mc==6bJiVuX znU;Av-x|nt_U9(?TR`S5ael>RL1{jW>+9FX>tAsOXlV99m-zXYO|(kw{ldz|YJb)= ztZaBro70GLjet#^LL%nw<)j167j;qbU>NVrI?$u}@edn~uh-d(J1Mi^ot4B__ zYv1k==&+kfRC@`$yFKCji<>mT7zvq^70;1Z7#{Z6~IX816|cXV(f>u`Cw?(cup zHx(f<-J;=}{Gh%2OAnTI&4jrk0+V+akS`#yU1$%&GBND=GIf45-kpDa=xnLle=OcT z8ZdE9dvFZOv_g2AX}tB@5Q|L|ls}9h9YbVX%sa4%o4;AJO|iqh7*OmP=<^@G`+n;4 z0Bp7lhACdxyQ=IMJGoVn zc49Wrb)<*3swR-rP1;Gtj9)Rh^0Rx|s3-0_ysJ7*u2il4 z()w~~eibU3R}q+Y_+GgVd)%7ZzO??^^pj+)BB}Uj8W)Dw32{q<`|0gaMW(s}cn9k|$80)N=(c1CJR|#T?io3-DK; zDiuAjNE|>S(um5lA5DoIfmR~CfjX2NtdWa8sMzPyl?VkOTxotb0@W3MF9;Cujv&4J z@2X<}wN{u1c}CM4DKITxw1ul|#hpEy(nHdf1>&lKVZv~1*45PrKn-5(&QnbL6>E(u zoWFZXEWmj+;vDSM|xlr|MC?v4%IF(#^PWcm{lFHb_QYJ(QT)To~r<@>Rf zJPmBeRTiRERkG;r&AMk}>*LM-XIMIckS)_i-7$eH80!33{DHnkHuIq5jzdk$%5I|q z;Le)X&nrME(33lKH?i)h!1m-hvT43~wNpVSptOiHpI`|^wYVyd7E2#~dDgpkQ{Ga= zD+zZZRC1XvZX!%XHy@srC>^Rt<9z_n9r!C1OmENFOV??RS?~$?-C$bojxQ}8FE$O? z09Y}$BVnOcR6MRhVfVCuYkW5IfGD~pFvM`lfJiQ+7rV?HD(e9%9Ws=zTq%LIuaB2c zw}6|0j+APm9dGDJ4y5*J;cgNpNe1jofW1s%p75g-Y2KK{zmIq9*IW`<9#H;I;%>uY?f!hU{vOKmGlDp|tH&GRWZAW_?HRjo9{9B@bse03a ztQ$0mg4lIWfHzN%)+et!_(PAiq+2#h*+`#rJ1uYI-+GL+QT>hT zZ-D>bd4#;d{SEGKaDRjQ8%_R(5^gBrh7$g7N?7^r=kp-YCW_@<001u=Cjn; z3-g@@EDX0%vby`jcUplybAMCpD