From f0a97f8e136dc4ad8cb8ac16d0aa32a0c42fc39c Mon Sep 17 00:00:00 2001 From: Chris Boustead Date: Fri, 10 Nov 2017 12:15:08 -0500 Subject: [PATCH] Initial commit --- .editorconfig | 13 ++ .gitignore | 34 +++ .npmignore | 3 + .travis.yml | 32 +++ CHANGELOG.md | 0 CONTRIBUTING.md | 30 +++ LICENSE | 19 ++ README.md | 80 +++++++ example/thumbs.jpg | Bin 0 -> 64319 bytes example/thumbs.vtt | 13 ++ index.html | 30 +++ jsdoc.json | 3 + package.json | 107 ++++++++++ scripts/banner.ejs | 6 + scripts/modules.rollup.config.js | 47 +++++ scripts/test.rollup.config.js | 59 ++++++ scripts/umd.rollup.config.js | 50 +++++ scripts/version.js | 10 + src/plugin.js | 346 +++++++++++++++++++++++++++++++ src/plugin.scss | 18 ++ test/index.html | 19 ++ test/karma.conf.js | 43 ++++ test/plugin.test.js | 58 ++++++ 23 files changed, 1020 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 example/thumbs.jpg create mode 100644 example/thumbs.vtt create mode 100644 index.html create mode 100644 jsdoc.json create mode 100644 package.json create mode 100644 scripts/banner.ejs create mode 100644 scripts/modules.rollup.config.js create mode 100644 scripts/test.rollup.config.js create mode 100644 scripts/umd.rollup.config.js create mode 100644 scripts/version.js create mode 100644 src/plugin.js create mode 100644 src/plugin.scss create mode 100644 test/index.html create mode 100644 test/karma.conf.js create mode 100644 test/plugin.test.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fc6f5de --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..efc5fcf --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# OS +Thumbs.db +ehthumbs.db +Desktop.ini +.DS_Store +._* + +# Editors +*~ +*.swp +*.tmproj +*.tmproject +*.sublime-* +.idea/ +.project/ +.settings/ +.vscode/ + +# Logs +logs +*.log +npm-debug.log* + +# Dependency directories +bower_components/ +node_modules/ + +# Yeoman meta-data +.yo-rc.json + +# Build-related directories +dist/ +docs/api/ +test/dist/ diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..ea8c75d --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +# Intentionally left blank, so that npm does not ignore anything by default, +# but relies on the package.json "files" array to explicitly define what ends +# up in the package. diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4deb53e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,32 @@ +sudo: false +dist: trusty +language: node_js +node_js: + - 'node' + - 'lts/argon' +before_script: + + # Check if the current version is equal to the major version for the env. + - 'export IS_INSTALLED="$(npm list video.js | grep "video.js@$VJS")"' + + # We have to add semicolons to the end of each line in the if as Travis runs + # this all on one line. + - 'if [ -z "$IS_INSTALLED" ]; then + echo "INSTALLING video.js@>=$VJS.0.0-RC.0 <$(($VJS+1)).0.0"; + npm i "video.js@>=$VJS.0.0-RC.0 <\$(($VJS+1)).0.0"; + else + echo "video.js@$VJS ALREADY INSTALLED"; + fi' + - export CHROME_BIN=/usr/bin/google-chrome + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start +env: + - VJS=5 + - VJS=6 +addons: + firefox: latest + apt: + sources: + - google-chrome + packages: + - google-chrome-stable diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a184176 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,30 @@ +# CONTRIBUTING + +We welcome contributions from everyone! + +## Getting Started + +Make sure you have NodeJS 0.10 or higher and npm installed. + +1. Fork this repository and clone your fork +1. Install dependencies: `npm install` +1. Run a development server: `npm start` + +### Making Changes + +Refer to the [video.js plugin conventions][conventions] for more detail on best practices and tooling for video.js plugin authorship. + +When you've made your changes, push your commit(s) to your fork and issue a pull request against the original repository. + +### Running Tests + +Testing is a crucial part of any software project. For all but the most trivial changes (typos, etc) test cases are expected. Tests are run in actual browsers using [Karma][karma]. + +- In all available and supported browsers: `npm test` +- In a specific browser: `npm run test:chrome`, `npm run test:firefox`, etc. +- While development server is running (`npm start`), navigate to [`http://localhost:9999/test/`][local] + + +[karma]: http://karma-runner.github.io/ +[local]: http://localhost:9999/test/ +[conventions]: https://github.com/videojs/generator-videojs-plugin/blob/master/docs/conventions.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bb44497 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) Chris Boustead + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c737c2d --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# videojs-vtt-thumbnails + +Video.js plugin that displays thumbnails on progress bar hover, driven by external VTT files. Based on the spec at: https://support.jwplayer.com/customer/portal/articles/1407439-adding-preview-thumbnails + +Note: Plugin hides the default skin's mouse display timestamp on hover. + +## Table of Contents + + + +## Installation + +- [Installation](#installation) +- [Usage](#usage) + - [` + + +``` + +### Browserify/CommonJS + +When using with Browserify, install videojs-vtt-thumbnails via npm and `require` the plugin as you would any other module. + +```js +var videojs = require('video.js'); + +// The actual plugin function is exported by this module, but it is also +// attached to the `Player.prototype`; so, there is no need to assign it +// to a variable. +require('videojs-vtt-thumbnails'); + +var player = videojs('my-video'); + +player.vttThumbnails(); +``` + +### RequireJS/AMD + +When using with RequireJS (or another AMD library), get the script in whatever way you prefer and `require` the plugin as you normally would: + +```js +require(['video.js', 'videojs-vtt-thumbnails'], function(videojs) { + var player = videojs('my-video'); + + player.vttThumbnails(); +}); +``` + +## License + +MIT. Copyright (c) Chris Boustead <chris@forgemotion.com> + + +[videojs]: http://videojs.com/ diff --git a/example/thumbs.jpg b/example/thumbs.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a346e5541fdb0f341991d55091ee0b3c99b99b45 GIT binary patch literal 64319 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

hE;zoYlslW|Q{) zh0|Hu6X;ij1I>Pi9!q6LB*`r-4RwE4-*V&MEpR270INybH->Xk-f2=sj&iz5#$Pl< zmkLIy_=XZl*gjq169@o{4D)dpREH-5Sl0^k3a6%{3$YW)bXLL4s{t}y`RjXQsVU60 z1mP!c3fF1QXB|^Lo}?&jyR+$h=MFXJZtK$uWbo?%o{1eHFNWb%!{20wnCZ2gUsCGX zDnA+61lwE56ZC2X<(K~!RwoiXT1uhiaTj3!81X9Qq*FHGiW38b(g)O?8@9RcvJdx{~x=PinnR2%ex+P$<*{A zuG*znW1sE1g+xM?k0{+jCDx)9=p;IU%nLeZa}*fv*FCk+h|pJb@6vIQUN zCSaBRrfT4PALd6z+_wt(h1|Not*glvVK9BV?6+b9Ze#6>-Ab}4^+VO_5?MgIyIX^E zN@%F1#SbpJ_Q?+|%2+G1u8RR>nPBdT0MZY1>e$$LR7Z`G!ft^Z@oxI7-g?nAI(X+*z8u|G= z0-R*PzIHm!d5?hVcU8xDA!=v!Y5Wp}=z!wnHFulE3QWee_f|QZrgCbMOzXmctAVBS zVo6N(D4qEc4Ul`&O9;ah$lzfmERjjuMEw+FkfRB+q-pWe=_TpLw1ByjrJ>TgU9wQD zjvoup9~ml@UDY{d>iII_-G0lJTlWu|Z)f?kN8p2Q#=zo0R#la=HUGlylw7n;eH%S( z%JeFQjIXOWD(&vumbIaLdbufz75-NAEc#o>ND;H zB;pFF38h7@saM8Qq?A^D+6uJb=e64Xm0Pk%;CT^&s(rulo<%gB0kl6sMYnn9_0JSg z^8p#B@S4lSN3Q4nsjV!;jH452-cd%0S3gd=c~0i8n9J4eg@R!@nHlikBye%1m3ppE zF}UmfO(JAKV!mK1A51>Mm)$~mT^Z=@9JSxPCXt9;X-S!vL4*_i`}j`sYH#@z?jaGFT4Rt(2B_BAf+l3zip5-H!6gBCIb+YtD>IH6k z!a^q!t2b`tX?bAUx7G|-pNfGKen3m$4rsVU8tqF9fB zQJ{DWQNEa}iGiPT)>F^}5pMferedLzjM$Oax@L6zy@3HI@rsEM=6T&(*<8oQvcI$> z|5rg3_U_&cWD*&ppP6Cqta+UJ21O{z@|&1s?x$?vl04xLcoV8k?8Q|q=cFZE4ZWPW zl+zvQO6*R%;+2U&;N9&%lTaH-lva2C3d+-25nKH!@JACa z9!&^g%%96u-qo!?;wpgXqE8h0cTn6C7RM#LcdIrXP?R0RypBHrZND7$i$e2ycmzNK zrexTYNONx}4q8vKbkBwDQ)oN(wYK}lHf#f&PSbYh;F9{3rnIHbEN1$P4JVugL3)aN zH(_RRKYJQX4WE*Ro;xvl`1UOJF`cH2X-ZK zS?_SZoE@k$89!k-WjQLQb%A{HC4Z2&eH~fG@qWbdz>$W$E_^ zB5g6>hI^g5zM^FI*_%&#;7S9!jMQ%bfY~*vBz%c4^aau4qSdo3%zy4r0l}VIM1C8n znRPG;9Yo@UHjfPla7kVaG}RR813N^l;?WvoU!xz1`;P8Gqf+j<%INWzhfV+c7r5I8BiYI44TJu zT<}b|8Mt3ysv}!p`xm!umJXy+v<>b;mK<6yFY1m`!<1nvS|_T(0_6&D$6$hFtjN>_ znN(Of7$Z;Sl#g|VnTjItY0K5@Y_>M=L(|@RdF07CYbZjRr=)%+$g*W@oX3JdwNkHf zI_fQu7x>o24_vb!@J=2FdmsKyTvEW$Dx&Uij0=;Ynd7=apxqs=P_~WPtc31JE=sz- z^!E5r^2vlhyzSwVxGr-$USt3DhW4gQBa87TO=*qTVGg!e?hGbxix;@Ru|5y#X&XK1 z=|GZm7gxy~;MC|?krXf|{R#;#JK+u`C^M{v&t|7(M^i|amezEB8!oRV>k%yVpVAbc z9|#GjaRIL&Ur(i|K1Dl?h^X zF2a1J+Sya#mHE~~oEP#}o<2DDN`aZ)h7gqCD;p+qWC7Dj)$A_tKtyhwNF8|lc@&X= zTuc&0Y1;<|mWjsX*87RWImd=9ipMU3^tS`-HX61`l&Lot7hLM`IX^*@;f6GSVlr@1 zTyGaPny7oc26J`P6OC(mM(0-M%j{38_PB@%$X}rD$Y&a=!hGPJk{lhm-)!@kMcY&4 zYcp8v(%?fO2sO$DOP>DKs*Xw&HvLmYUawAZ!(ld}g0$p_g<8E%cOgfPm*^>$1kZ7m zbeTUa)F`_>g^Hei*#H#eN8mXvlX$z-Wuoa7q;OK&b6|t?x}G~l(+1xsKLVl&N9r{g z1r;z6yaAZh%fl3j#{Bu-$+eON%T3oTW*$d32AbqiAFmBHB(jHwHK;oiWtCVE z-$hkh6y<-Sf&a=Va;9>f)VQ zt{B8v`A1~Der;e89_4xCLDs-ChV)8td*`o!+C_F)akn^03CHqAM2&~LeYDK3`63w~ zIyCIiC&Ca-r`!!^UN(<%U#zt^Z95fBf?pskfkQ871&4)2F!j%8<}Vp5nQPZ2Gp+p5 zltWS<0pCHo%bzh3Q@<$lZYg;uezs0M9X7~U2>X5i2%B->%9rF%IOsw0d;;yy{98?Ax z+0u@mwFIW6=ncbG^=+2-O4CJ{K0E_X5`hSIB{ax=>a7inQu z?%{4j$=5PzIn?9=c;H-^6}dFaZ{cU`Y%fl=A<~NDUls40n#}xlF!u_sjkcQ~0Tm_9 z^U9^oubgizIqb&(_7^Djg&}$?@za~3VZy?UosKeFYc0VCEOhm^8T?Q2d$qqwAoATb zyBb9U4>J?FUh}G}>#N>wdYQ3H3=P@~{$DKF6m<%xdc5ehl-fRXsx4o4#dCl4m82pw zud#1ksH#-ODJtNT=`i$*r3F#>w(9yhg3N00(Ai0fLn_lxSHXgeaEYtD&n0mV$BarvKIvy8MUSeg7~D|5?0o^i1)&)TYIbfHXQnOYF zGrpYnYznFzJl)UdAZ*H-d^Y--OLN~$8i%Y;=jTxJ-!kTA*iuHdJ=E%J#)yL z3`g<5CtLS#)R+;yHqd?;&k+MHxXr@cc_> zkq?9{iXTzCoI(1N z#0Uj}i3Tgw$4^-?m`}QKHd)2hmUo3wt1=&3IveeGM{=(zkgL&8z^S-4)s^ zB1WldD}l=|v^7B==X=R_{%WZWtU9Q#tl;|swRmWw(>8dd-MY|FGk)<8??Af#&Gqrb zBf$U9*G)9Vj%My|RUpvSX1iX6+%svoQ?20oMdY-~$P43*ksLmtSC>)>&KlycJ!cxR z%kQsFP`41v?p#E2s`jh69A;+1eE~?FH$Q@W^s3RDhCBC^Hz{RFYsyJY_|ZolpI<8>RAF{(Q#MKe=^&-vowGZ|3^2xIZ%Vsjg?&8c!v z?ApWpg7Z4GTj|d9y0*p{y*AzF?H2zYwB@1_l1n^Als|Y=R%g*tu?dN_6hg{s$Sd66YcZqJjxrgsYX%e(v;0;?Hx74fAGo5 zgfT=rnzO^;6Sf``OiNaGL+90Id24qj6U&wjkR~C{pzduoHZ)Td(|u0+=&$EhHuxEm zhaZj4PCTh(rBX8I$|IJh5->)}u7d9rO+uMtFkT@Ia_^znUEG9T9!gTql z9_pML7>5C#O%PJ|ZPDGV7NR*bR9>YH^8B`9E7dk3`PQ`Ki-WI)#EKDUVDiEvx< z1a|o^s|feuqZwC_n`ZilLOs0`;xTR6r4^aWKOp&L48^PtX=v!adA7}Fd%#btiJA0h z1Cd|VjPH*P4`n3ME&Xjrd&;`*6!AS}at)V7fm4@4=aisjep^W`Ux{}QNtA4Kqc3dQ z2mLCEMlZOlDTrvwpc~Hp!7*>AiTX|8&jQV!eAO&BYe%Pih507G5koxf3oMDBvDgeH z)POo|R+YPfN%cmJ8Sln>^?tU#{${22*@c3>cVQQiNyiO^XzCZ$8v+H#h~sOEIq$Rj z%YBtgDsPK85z)NWwbp`@vW|Vm7$Q>dCfW8Qn{X> zMOZ990*c#f(}N!Y@i#ITe|id6Jx}l4Z%os%C~RLSb!U2%*PJ=B#id1)LjCmTu3L3J zHLnVbP2UBqG9Gj6=M^}>F5hHRd}ObZ@A?A7Z=DNNa4gXv+pnM zCE1Vmi5EAhm<_gN@~}H~c}3&%abet!W5|mh8276!{U%mX`Rx^SQLrmjDJxgZoo*mrFmx&HuLG?4dNN@T@R$FD+P~4XVgUaPWA0Ljm5kj8Z zW8=qmor&HH00n@l;#Zs*4(RXnOkB)fHV81V4;wo)aku|-WM>K9@ zmt_I7@>qEpq(9PYl+Rte(xYy)8ZgOTG^6ZgBkLpW20N#)o&GwzvH@q!TZ>A*IQTk8W3tX<)2J^cp0Hm)w;PA<3aqxs#(Q zxGO7y2s#mC)}kPZeMaAOjD6x{xg_p4B$IMa;%ZV8@fwo~CLbdHfkGvariNI7_Fbd@ z07Z!Yijgd;&3xl<*%F@*oxg1e)XitNBA19|PN@F?D=e-x`0G&DUc+@0shP>n^cvAU-48iB43>5 z-F81Cl*#>;o{g^&LLWS_Etc5WAj z+NE%DiSfKqpX#ZBENwV$5S@7JU;7yU0K-rPrwPJ?amV8eY6ylqgXeoxfysD&K2@{g z#NdDkWLCYu5l9ls+^!3a7u$ybKM0V2RZI|5GdNXfHDfgVbzysvYi^&i_>Nt71&(8}6w zBzaugq>|?s1hs=)j{tg5L17mk9+yCW5qbzE{SG)I_(*jA$o-UJ1)N-PK=vclYId*e zs3r!}6Bsx7DgMZyfpcPFGT26dkM4>HA+{uX`Um6%0S0_Mw;^mig-HcYULj;7P<#l` z(5pscl7lA91G>u|wgipBp`vDD-lZ?E#_Q+Z9=W<2O;Mt##~-M7alPZv?6Jn#hr-@ zbL0T3S(n9GQb2z{HwmieX^hDt3|h8m);(Jo}By#+~z*9xw4517&?04f_LRHvGC*y=0=j+V7%-iEcQUsxLj0HLi&7Nb#8h%K<8yDEO_ z>i+-}TU5oGJR~B*-W8!j>lQoPN|X!iShl6e=|D?a5OvUangk{p*HzvNZXf_^2_WWj zv&E4Viofn42FPYc57$d+(yKa_e0QfUV1kO~BUF1s^4hj}svp*xT``L^agLa?sofe|Q~|Tg-Gn zoo4?4V%1ud&(^SJ=wA>dgG7t0=Gs?(c&%0s}(P>AnB%+AiT#_P&A+Y7bie zvEnLL&{W8BFo@?RMWb=KKLc3XRVK7?_!aCr)+VTE)G|*kL`poVBK817N7agVsNk7z0tHy{FZVbxE+ zz)z@`F=TXDpXru13O(Q5-bg-y;s;s+w*htbjc?7IvKLQ1Sw?RN{@wr4@p>!Z# z^)Mv4#^#C@kQ){C6Zlj>lbiETfywjSzcHD}jA)KgG2=v@QTUr_9FGA?iiU z??;vx;Dq~Y5Chys{wdq0tUW9FQb^)JJ=K#Mu!2(t{{Wez)z9MRT5=^&rJIp+BS_BD zNdEvc$V;1kbw_kvP-Ui-#mTVn2dzfrjqGA~NNfjBI*24mXtuwnb6DA%TYN=Bh$TrH zur&+N9@T*MFa|(RMHe+>RhL|7vRw6TO>bR{p5})Opfz%g(O^R%P)axCDo`xJbh0+& zbNj3Q~at2rE|Ml_EDfrzXuo57GX8{XeP zT`$t3kubMrhYuf03J-&|i0#6T^0AE04u-*=I8fWcS&|#%P?4ZSC#_Td3BQ zyL*#rnf_ayn;Kk2#VH_`&e9!yH|g-JwpA@n7FIqsIUeRpjl$z_+7IcZhKN})C_8~3 zVAPdmCUF-vBpD7`G4D$s?#!f*^p{$O;h_?Y)17OXsL<(|)M3uGRs<7JN#*DQ^-==$ zB%alQD-DV6h%k~ARx6b3(+gxYxgh?PH~s;_wNC$D2X$Huvik(TRFz>Cxa!f44ny+Q$cka{Qy zINvKCg@{r4)zL<1uz(1lbQQUgd4gEQR69VeWQKWXh}#i1KT$i`m>)G?lzw-rJdD<~Mc)UPCSF4#U8FFe zb~aT~hgLm2>s4toh&KFw1`Lp+Pn70W@kO%V^Q#a}&p{Rpt%onYAVX+d(p-U{29hA+ z(`3smj?xksu`a(G>MNes)ZJG>268+qB$ymS4$x(k>;C{p7}xVBN=Xe1Nl+i)#0U3} zslQ3n^;Dv)_jjvwmvTCQZTjlO4bJeiPFCTT#=_?M{_*gryG<6VX^P`=_{diwb8wp} zq=}au)>9v~DFiE(8aY{R0qTXDkgc)XVsr{xppToq|$te`v zuXCAK*^eDn7x$R&-ff|<(@Lb8Y)|a2Tw&!I&mSgAs|S_gATo;)rT+ltimm0SlRTB= zpw@DKbs(3I5%HX0ifvTMBMrMj1rF7)CjC_DT%JFS=-Y2{^~rdL*yjqfG1&_`%t6%; zRsHQetzEhyEQ^rsuEKVW2IeOA9vA6C)yUMxQI(bVqxEIm*I$>VSj@N2&CJY$5X{Kr zt;~B^0(W_j>Zt~c9M0I0`};(pKyaq&J_4kxD>)SLpfh>OttH60UzGV8hDtIy04%J& zMAjGF)|(NJTB9r^)G|boRUq_G1*%Css{(ckD;h|QO{cCZzxnQaFn7B@T8?b)=GA;_@Rd2N+V1r~=3y>#v+3*3Cu#Vbb*$TFiW#Zw zY&<-ii57gwQWcD`-71X%7j36a2bE!;EvmFL$!;+IBfoWIiwh?LE;}+gpqe&!Fgo~< z0Ajxi&%$=$>fGNpb&}GyDf@H2Ij#?p#mvP-pCTh@+Ea8ZVvMi&gVws*lQhKRcZ%fq zhx5*AGd?~CtmJ!%=kjOBjv3uHkzmX$#ry!bTDd~}x$z)=RUl@5hsWbUQs$B`KxZ@KM=}yU zdq@rNG-N%DgXl=|u4`^|S7DRZs>u^jN#PX?9sy86?W$2Hqt2`gEGW>#r1ibUXpEVA zh^kF$P`t!MTQTScwImksayT*B8elXUjX?ZqA_?m(XK6iJnvEmvD6&1me7Ze zAOwYmgc3FIs@l*>teXDzgR$^2aDVXrQs>P)O!+L#Sff}KI&Is1eKnmd=Ti1ZIK_O^ zI1tGqg%UF~VXn3$A5iGNk*~(A+Ui6V51lxYP13sV^s<6=2SPRYRH9{(#`g56Bom-L zDoU2H;ANyyO+GPbK-(0qEG_+z#9E5Tm02`V`XWFOMz$73Hv`};Rw)q1@?&kL#(<#D z1QT<512f7*jTG%~Q5v5xHK=46mN&g~9VlS<)Bz@-khKBY@pX+^Th*x`Xid6ax=i4A zce=txbM7gywuBGHq=Gj&i;c-~>kfLf<~x`t8czxHM)4orG(>ed=XCbO{kn1S^Z5*H zj#gp6D~-pyzBFv1qG{wFk8#1(7pIM8%Y01pTx(T=$)1S~8xR-0Rgzh88S`Rm(vOOguU!k4;kXRm-^%18d9eAM2Z`svBFfOncKfmUt^7gKuC>(8 zTF{G=mo)Qu8mE>?$H#QH7SQm|+x+M{XPW!L8PH>$#`#goYx`67}} z-0FJV3b{xh!|nN|$$?8E#VacB-UwTpYgqf>_B?IZyUwk9fK|1cSH|tu-kzL7h=^GRmasVJHxHs0? z>v}ds6|-bB*tA7S`1l_hgvn^kWuNz%Khiao_cW%&MyfK$l?0w1ln0lf6+i-jpN4H1 z^W9pbHd!-E?Y7H;Iva|)K`_H3u-r&uMep&dvdGk6V@C@iksSv{u~VSy@x4kVEkrm{ zrFI>^x&r_Q;ymg|W2uqGuM;jA!eg9RGUZ)B0{15W05$EUhLsG6k$is_23bD)w0inT zupg$Yv3?2*ervY4u0Myv$;8U~jGUo)qGnC#eP1Y3=?k{ayRGU|7OF_6TxKG~eww*L zR}tIXuOpHpz`=aE&~GtByPCtUf}yag_ABPX&Bo0kSKE{^)1gv4ysc84hgj%oarrps zfH+bJux&-j8XXUfNdtm$B9H-64Tx(4R+6PpCnmsbyNNw04DM{CmaN*(- z52?QsT=v}EY%a3E2RkTJn z04Vcbj7ZocsTBBap5_2%>=tHs=nzKqBW}l_-YX82;hx9L)UgLzz67dGcB3l_ZMV z+WUPc$Xlgy08XcGPA>c$}l?!m_f51=E!$o2mhQ zz-9v^7}CbC5)wkN3<|gTfr086R@HqB*{1JoJ`Lc<{y>R`fsYrrkY<-+2@89SiM*CJ z(?!0uYbYseYX&com_SY-b(NOhrC10yb2A;DL5hjoNX$73@e zn5fdJEW9J!v61yZO!hKFL`W3fZO~ezbUL?X$1V(5{AM$l;(N)G>0s|58kRrNTdJrq zZ&O(`5-0^&-05?z=mQX+a?A+6fSQ8I6}{cr+%G)4vZE=;DE<~?T1lsapWh#iLrIe@ zY;j2VjEQEIVY0&;l`_ae02JEfx8YgwC0|WRaB=t^;6Wsr(|AXSyM_FWjbZu)SyZdI zsqi^GY;4K0aK4igGO7G^JIgV;tW0foBdH!05YSFY97Gry9F{tAE@8ou!c8KZ6jsx9 z&r4Nmr*m64S{sRe-sa1X4;;Tr*f^52ry#7$Jn+7@D`r4PonMJbH=WF*7qqf)k=jUO z4GQDfG^)euUuTAn^hVr^hP01MQR+@3y5J%#;4N)RJ# zjnvxyH6#((=3|z1Ybt6Rr9ouig+C*eIlQu#E7RaAsX$^ME@mJzk}cEWQbFcU{Fs(Y zg<*BIt#CRLeCY(DKe%HGq~6_WuzHkPG6tF09;{7gcd4Zog9M79VK)^3@R|V9fb<#z z(NYQgeTp0P2Cj_RaRRHYm|Rwc3p{K{*j$slsA1PivO-HGiP8`OET`o^5!Up8n>|T` z!pSMUwTytb`0LiH(I`MzY;-!(2~Z_g+8sxg>PpD+JZNkM(0r@-(g89Y&uzmCD=$j{ zQEH~3wQTC;7yYyM50mYlLQL*BMkkRBO}Rl*tZ&m#5mi`{Eo`=KS(oMcX3Nc+GbKw& zJF~kDE?AO(S*s-l8*HzOH9^SQ{@9&1THA&1ex3yR)rc3yWXRcZ#96g(OA<(1YR2D% zLE^5V0?p(GkN$5J%PNc?$W<~^?Ruq3dteQdDsfdRn3ZAxespAXtgdhKHauBue2%@_!ME zFOB?v3s4V8(c{DI#NLaO*80?tQS$j8!Xz7$rj-E8&o+drq~c4#3-#wp*_blKW}ntskSWI{5Be`wk)?eLXSxNE1cN& zfb<#x>~|r~wz#!fG8!%Lr#n=6bv3GHOuEFL*7K{Ptg=kYMn{SLH8K$BmfG6)9u)+V z@$xo}g>JUgkPnBQNEmg7XwXRzvh>DCo*fwez^}sDM5(B90Y~ zTS!K?@;)~BQUMNjRnc?s-0MuxD3VR%8j2A={rX9ZsBru%KJNIaHrZG*0a9|{O7b93Z(BpVL_ zQ6dT)%&8gNme31)E^9kIOC(kny=5kX38*CUfG7gg0YG{v2{19+*X1DoRdmX*;Hi-S@?`Qz_%zr+Dy;qjyiJ^ohTy)D1hzxC9C zqmLAF5xB4h-7WoeB=AQcxISxG-hiI4;DruEmOXt#tq44J$5#dP+vB z=KfK}O*CX2oTV^hFNiEG&!_fB>#DNbvkUg?wePT-AhO_re7T^RbsBBxbv8idX&T# zSlNt6T9OIz3Io~!QMH^@q8VY0lwQBe5T{5G~ny$o?q4K1JJhK9uL9src{XZ&@ z$IOJT&Du~2X^}_;t!*j*mdqBio8PVINZ`!4*Y;2iHdqYSJ#?TV&67Y^WaW5Xl1W*U zk^;81R>CSLaz@`1sjTeFq(?PIT=JkEPz3-`1prVELe^vP7O6xuSmMq_*7}ONv71Eb zvKr(AYmT*fA`2{DR%mvpH&NkMOo%Tr`FK68gcEUNW9L&OK`FUhLm07Ta4lE%!cpD9$^si_h+`F$;=2@?6>3lptCDl+*Cu@|I~ z1wLL!q}b|ejh97K&2zWQ`s+5!q%cmkLdTQ=Kokd5pCch9P#VKa{&nc_Jo?<1iIKS* zb6j{|s5Kl3_XS6lAYufIsnfvJaAd5cn6q8?M=gr@gV6poRSNyc&nFKgtdl_#75?I% z>Z~{SXh7v1RucEI+Cutk&k8el!(Xa zy(9ZB!j7R?cNjg3+H-nTOBe%15*uElfjs^rvi3ItBYy;(NWz8o;5WUxvlh7|)bSx% zuB2%`+VL%Nx$`ac)G4aGW&PGCkL*k=E=CNso1h=e$}#Z>M9$tf3S__H-5LODsz`Q9Uy%I>N1;Kq3&l4zM_ z(`}S3?f6tWw&b;0(Vk?`$m*edZ>Q?3!w8AbXYpUiIYey5-f^V}ilpvxgaLmFt&@gI z+~N!JkHWcR-1eje1fJfaNgR#zw}|OhOo$~#Kc1C&D$y<(l#nlKvSk@MJe{r<;ExL$ zx*(jB$rZP$P}-?I58+Y=zOZ@F0m}!*fb?X8{{Y)S4JiY0P)FlHGxdq&2j@{Fz{I(r zU;3#iK?ZEKYxo+6VqmVGa>m{QsM#u{IUjQqS+-pWH3Xh@p$C-`Bq~5qke~{QB2Pi7 zLPwFb9%HBBR!o9iGH4HtSrAK(YO-ZuosEz|8r72_qIza27wc9kNL;IpDIoFY1(cK2 z^rVCy;J%cSL*4?C2=h)a)_{#BPMR759J2xm=~a?S%!#gbItrs@k|D7ql6sn>E`%Da zlOk#)fTVz=i3$=GASfjY6hulNj#KjvqpevG(K0!2V^=E#l$TL@k_nLJMl5gAte%BQ z(d7mM`fAb%F}nHN@u2{68sPW=Z_0rR`kgO&0S}O!N!F4eVobI3p(IJN{Eb6o!CySX zQ_`x~NUxS_oy7hEs@Y{yDK(s$BqY=c5^5xb!laO>B2L+p+t~Rq#WWMXEb_CF64~Qa zKspB3ug0Y|`j&Cnq|D>_OgYmPCtRGFY*G|cy2r)50I7Djay0Bnm$x}2j!EUpiBciD zLIF0?=B2wXq)O~VoMvMfO!GPSVlGi^tfZSLABT-m-J;No{{Xifeh(p)%1OvVxlrR` zw3}qTx8FtyU5p_e9nbdqUXcIIXmx%GJ((z|L>Mf3a* z3ysCkJ}(~vmE+g+X!f#M+F;C+AMt>DO)$M&CRO2jt{i~^U)m!tl4Z(MCLu zzqY4>>_;|p(-%1X%gT;9Bg>19V8@ABmtemQW{4l#`@i5=ZWwiW}e{A&*<($ZQQ zV%2I^a8Jm$8na{*Gh{X4X*E!zOASX;=T)L{sAYrjc_i+6wzG6t)Gph2+|~G%=8A6N zmp_iij75^_<%jVB*4OE+R{A2k3kx!8$WJmHTSE7h>MyEFtYq*$pb z))y^aU!UdKSt__XDT;SQ-dpYOWngkr;;$J5@gWTihkwZ6GK+4|fNQq~can*iWTLCs zN_H=0FbDZ2G5-K>{8gtr6@o;4f!R7=g~j|mf8wZjw{lV?Ct!9E`M4}bK3>1^)j8X_ zXhQ!0@E>82RB$+eb^idQ{{S6FD|ZlxbA7z*ZQh;_7ykg#e~PsCE!?akGyT5oQP+dT zKlY#FspiOpBz?Nv zM4#iSRv@A8e!}eR4%W@g!{PDq@?!S=xllgk-6Df)vG6snCpJi}+eqch#^89szpwV! zUj^9b_buF$7Zt^wPAVVM`)R>;9_71|;^Fv1^I*h(PwlAo*J3%_xbkpZ6My*(U-G|g zM+Mk>mhKrmKLG|Lvi%Gpl@8PD`Hq9et7@o|N?pkhlH+*Hd<=hP2-a74)XZ*41Zs3O zo<@~ZG^=vP@^P>x#YD%2@$-?g7_4!zjz9|gX?FDuyH8Q2a$hBt^!ju+tyF8dR)-~E znPeMfHW9fbGLmm`;nPiOc{i$JB28WsaI=SW#j-2nL+eIeNdi{upgw2Du*rL^B-)%_ zts3LQcKYS4Kg;3^A(v|UwArZf@~-}f;g_i{KHcr(*N%(+SNN%^UPP4bUfpYd-LU@v z%KrcrLu97OiP+t`$-Hrpk^Z;%s+`Rsfb2flZCiREHazEGq02N2L$>J@3)pk3dR|AYk;V1a2J;Hd8$M&DF2CduJmiUQ3 z$5nY5ZX~7qQ})cQ&u-!0{{V^pI;N?thM>or#1$7ur9zBwY61 z5%_->{B<7d98rk-#rE&BxPH!T?jwfA!O0VMcaM+H&1 zfTHB+0M@o{4s@j<*)bk5c{bn0FLEfXi*R5PJ8Gh=Yg(v0+aG`wA`t#I9Jvw*@u6v4 z_#nUZRb7(YwQ%y;Nh!XQ7TE0;?2y1n#9qh66ko5z)m5z2$?4+pF{Kt$Vt+NnaWGwh?W*-A-7892l=3jvwM{L? za$@E1N_+9m0?GbgxGj5EO{;3|W9;yo)?dK zZk2)ACX`4v6YZ1gQS)v5YdV%+Zt>onDuu%&+icN;u7`2I<5PVIUv0v-$6wU;QMGjI z<9g=3na7{O!&Ks^seH*IP{eO#IxFf9wN6%jPcGEPm(q!mwS}h8J;K2C&D%J8jGPyhoy;g-jPA*0{ z3y}-PdX4HuZmyok1@OA=GqrrFCU8l{f(BE%Fi)LQ&r@nqb|fw?AiyZw^B4r$FYU%({*7(@kb2cxE}Wvzwq}EegOf!JKBo?7pK%7n zMoL(Zjb4rps3mQ|syJYrZ0I~eNL(aN9OpDv9?q!;eTO=-T_DHVoCp3#0 zgpp>b;yDi__aKvMjwJFV`^u}Mb*k|#$cp(U_Z5C6WJ!`o)O4g3k!bETJw-$bBs8qa zw4ct9OrFB+)2$gYOpYSU()Cu!LvpRI2U;M|z{h4iD#c*U{n5uU_9rCPwY{JJ02ymf z9s1Esi_&nUHRznAnAnb;DoBCkkc&`2`bfjaN`VAUV=_4Ly@?!#a;$7FLIdisJ~f7G zE+*LP>eQ@%V`9w|LN9LRT1IX14x5x8Bd(RnD@4whR<%5nD=QvC7!P?pPLc*k8}+*1 z<_FHB(%eBVcaWDS+(c<&4p^H7W4K#Rq;xb@s@Vx|b9eUt0FE8kFDHyL+mGeb*q0HV z7EE#y>#@q+(4PaegV5Ipn{|tei8jEdONnr~e17D^$K+Tk22}t?V=^&_+^zorchi3_ zBV9Q2yGF88COD~}gBC>~k@;6!vB;5W4n6EZ{A!kEk}a`-*{}}2b(1wj8WhV%sQ>TGIA59HdPKA%VW1wK&!T*h#MWk|FUBpf9hR zTC~UF^}CbU$AYw8l9PnV`+LLkX_)Vs%1ID!o+zS)nDrrDov-Cx87P{Kj%>D~aip1U zcQY3^n#jw-jo`$23C4ljag96{{Xa!vS}LeVjpW6zHQ1exyf!XWN*-)xS!0dEBe-4~+q)d#(wU`zsZMS#}n#!A+DC9Vj%knj^;KH=F=FhVg zKcy|!xUwE!nBbwwVI%olO)7}+GDVon1_`$Qo8N8pt3g%iXw!D4Cp(tXWbw(5CePuv z8&4EP?mCo_m7nnx{yMSj;-ze*Xp3G#eshU|;^la^PCifsc(MZ`YuDSDfcfO`12+)cII- z2hOVRXJZxPLfEU8MO-I|0y-Q_kv_1NE^3$bPnl$#= z*f{mS85~;g$W6Lf4Iw;h;Ds!CA$V%=107lk7>AtP{I#Sz~uEVdju&47Y*ZnIm_0%oN zXLUICjyTj?WN5#kK(3T?rcif`Ww;s9qcUHlSK1hXeXM*J#l97mZH+2ozRb;g{H`oA%P(#;ty;c z2ZfKOyz4tVv$ZPlF?i334A}F8FDw!ytS_`PBL*6+jkK%RLpy4Y>B9FvAI@R@RwRSm zusJxy-J)i`htH;ly&7FvXmVLWo6VGA{l&zX`d2bD6S2@Sbg=zvSgkWhb4RT{Mxsek zRhi9|M&>8L4NZ0#9FK{C`;Wean@hr8T&YF0*%iv)@jFF%e7QM064DJy9LL!n!mO%U z*}8yDfF5T`w0QJ;u3IA2T}uE>!@%s~njXYVFv)Q?p3D@7_Cz#>K|R z#l)T&@uiXsY`mCS8M7e)Sma8VD88>q16v;pS09U#_ietvq0yF#sKzL8Nx#jZy>_z$ zefIA^JBo*z<~Ya<*w|3QxtR7-B&jUHRIY@EM)h18;qy|mRefIO{yTy<%;p7jvZ_q#iUx&tsS18K72Sgs zA|UL=#??@h6~Rl+VP}laB&y@)?Ee6AjDpQ0`bb#*YcqpuR(WwNz9uxl>voovBy;`B zI2@^09!1LTM$<1nPWbUy^@kLEyVs*j=c4)7RViF`gpSvBRsR6ZOcq8)UR2LC@-&jk ztGg1!ljcsO)VrQ_{Fc~|q*Zr3+{rsh=R;LyR#02f4<9H%)W zFB&71z*llSsTnNy3ahy4O{;ph8Xmq(yeac0a~+G`Tn+)`d0rnU4+nTdm9=DuF&6=v zGQod3*A_=aSH<$u^roBadBb*JcXK#RnZC~lol8A$Sx!pq>A;1W`ddTz*AsHj z7tPC~E2D-b;^xbckVJ_T7CLURc?t6Y0Q}8aYwBkFno#I%GEXh(c^Fw{nAM~SZq5T!7KIe5ji$AMU z_5;B8GZBoD@ZH4!03s^|!pd{|tGoP(8^c|M{{UvD6^_a^b@2UBm&+Wc4qVu|Vn{Mj znB+&gup_8odRBJElSehHYS}NylfrTwYCHX0W8GCBPEM)__;{08Wv62Jv0}XYo0ojX zUuxuFVrAeW;^Zh;@*s?s5B;Zm{H=chT!~p|aQQwxX{~lV`pyg5zq&7LZrXNU-gU$6 zZJOK3gG(YwdDBws~=OFP=m%*=_V({DX1sO2*q@x8by;&d`^^sGv3WIaOP!GE-Ts|?22%#Yca zeaN}NqPj~J(NClgS5J-VzBf|Yjr+pau-3YaMxGY&(t<=ojV#$Q!ipW;f})dPN$3d| zu&QOt!i}=n>CVu6^WGA2T#rAl{{Z0yLR2h!we=s7de@(h+|->}YsJBv zO&8Q`zDG*c8JmB#d2>O{<>Inm9(;YqOA=7Vz^K21o!_Rqym#t@9H`Ov-W1IqHzkj8 zE-M093+(~7DL)I;`AMj{=uRkU)UWP-_{5Wh5#nxK?6m>ou^TU=FUN7|QJ2W8tS#8b ziX5`X?I#TcJP#$y5Z_;skoc0Lix`q18)m*N&vRVMbE2!MA(k3C5*%btY z4R;Nss9<^>dR1(_616VAqd|+#Nr&5z1$9ZACS#F=P1S<{5y-v$7dHHDtzqo*$*$44 zuRb;WN3i?HmdB4BvmE7Mhggyz!ZJ$w+T>~%)unYO!_!?lm?(PL8;;xN;mr}^`{GHx zuX;CsNpe`;B)`teqp0g|I<+#sr7~k%qW(7|{3}xuRB$`3#p)!O6u8ZQ8c2dtLO|BD zlF&La$>~WXc04yEXlU#poengD+GskOqp@^}%a0qTqyj3kN<)!AiKXx5P}wOGp@K3l z4_^waWWeG=Rf%f_wv>`lhx@xIlkN8~n}yfdvW~X*(z<+atr+I|S@XY1g+0k0JuVTla?l#Ebxb8A2jBsz0Db`169yelBVl!$ZvA@U0 ztB(|9clbxZz3(+ePHZC^8y4y}fExB-I3l-U{JgP_% z9v2(8U*Y&v!4f+dLM|*n8iGeG5ly~h(twERF&%aCtEei-b1aB`Lv6a&h|Qiwyq#rG zTV3?-X-lCL3dM^%6ff@X?(XjH-o7{iLZG-8cXunr-92c67J@sZH~%~L)BS$$OeQnQ zOeS+?&e?14wVvnqjI*GWMI^e*OhBQjV`Rc7w}nc^7WAHhOPh+GbO>21^H$}YwN>cF z5NU2fC5#%;1SrF#v@vm=>kCh%(o%1@_#6Bf3A z)puRZ@N3jcTtiTD#GK6k2#`}mKnCR~)<%`TTuzJ;>qp$9$*c>@C1~Dz6yo`1GzHy$ z^{=KsGsXbJ)zVbaTZ}>_eW4TNY74OnoJA_W?@fTE-^yIh^y}0r4Ijt|5R_ZPt48>K z;W^b4b?@+^q3~MIL&LYi^3gwc|FGO#YPccOX9c8ZhBz45 zch+bL5CqyujoL_z4qkY2DF4kyE~t*>h4h%H7DUxx`tH9BOEkT8RMI#Jl;k-tIdU`V z015flbF{=9KQq~6FHArjc(wRpd0PQCD|x$x7+1Ff{0cT*0b}ondl}&gZnrc`Wu&PK zcF!4`k@-tYfesLe@tyELBpMps#Y>hik;o0R$IG`s@-lkmR!1BV0zv|Km>I}V4cB$cO2O;s6K)22T)cV%Md&=;mNwDlHUmS2MDRdv33TWyh* zT))~QC1^9Y*CyIegn!BI46^T#nIdU!S?UhuK8Qxq^(5N{#NzVd<|9p|3D5wj&91C$ zNyK8XDz|=lA3GjgnF(GRafOjdrFoKj8t0LOkL<312KFzMYf>qrm>X?`9H(LUV3*?% zX6CO7G>R>(8Le58=k(R2+mmM36`b7puGgG2u6Qcm7{9)XsRM%n^k?%mpX;O~t zk>pwuf4Hq9Tb?W2T2Jvq%s=Q2xDFvmG85_Bld)ps#x;f39DL`r*-~)&nz+E^yKgE^ z^PH{qri`LODuKyT3g3x53Faf z*m2#>e7+`gNM?1ysY)ONHno7Djsz70(kv#g!AGb=ivsEeK2`-sgAxNxDlPj*eswK< z9p%Icr(E%yl}|5PgU1qLqbJu}@eJ**)^DL;3$80g}eK4{rb3$2+3Uco^4i|u!x@bN^xY;vGoi zTFB3Snm88uq|s_XwT2(kRPV4Wt$@3p)S4Rdk-U~=@(yLW;wRcMwoD^#nbERK+UGT) z;?*LFWjK$ICQ-_BcLT6ZxGO?^WR>upQP8o`!})T2W1W(yZ{ncSat}>Xppcp3TWd-Z zM&81=nl{G9@ulr5Gj1eC^I|Gh-1j!?%O5dv(#A%)8lRaYD*@a4BoePali;R8s+aEM zF$_*hv_xL1#P1R0$rw@4&Ag{&{J|6#*D0MFr83wzD*!#wY6B7L*)tc{>ZdZNs_@qh zql@yG#7#+7p#~g&A@7R!6w%>k=PZp?p`E1+Y};Z-O6s82XMz6xgFYG*23iktv$A)@ z;-Ps~9XT||i-vz8#uQ#pnu5Z5pR&nq>>!?(D=9xN4OvdL=%rBZd}+rg`>T=a_(rPs zAILIUk8M`G{V;_|56Lh;6B)R1LajJSXaU^4qp@a1XxOXS-Yr3-g)~uo>`gW@zVUz` z9$~-NTTAS;ggAMHTdms4Vjgsp)QvmFxe3XAOp!-HV>g7G`&z#ftswj$_s+OP8j;Q{tExj=-L(2 z^o`zHIYFLDVfTV@W8{LZ^+VqE1zjRM3|xY4YyXg1U(DF%?&Z88Q)12o$sU*PzuKO+ zq@{wMb3gnin&6h%eEmzNDq&N`wEQf0AwL5|n#T?tf|28gC7fomX1>EN|9I#eS5?q3 zYtNj*vpsnm?Cb#BWPkbsJA00dMYS0lxuCSNw$8UAOc>IvrSp6LUBb8LuaJSq>2%9p z;AY(ka8b|a9vK9^%V!=RG?drx)guwvy8bXAYs&MON$PM}?YTK*CZ)`esK@gv+N6E% zUL53Z`A|CuB_%Hwb|QQXZI@o^Wg{c>Ag#v9&c$eU?~XB%QD*GcdK&c|QNUUUIg>3E zm8uofi6kkv@yj1js_ZbR)1@bljCH5V$Dfu_j_g=tN4#FM1t`KlmfX>it?Q8WNaRSO1}>90QfROn|@ZDHgGhxG08v-w3r>b)90srU1( zXM{CSV}gF5=}6f=cB>htCzO$mh>)CmnFf;D2gHwZm=ynqRvgDj`rb}|wcHl{7r`i*k2fdU+R}X2+InC@ zjc;La{-?8#%GT{L}El?2?^#`$EeYZb&n!0^Om z$%^b|@KsaJ=*{fQAB5*m!GNVbpuyw1!ShNylO`o#khU_3dwobw$^fdRtK!G=GQvK5 zFpWwCNGCXYZBZh2O1JQ~!fW+(L1>OYnZM6!r?4+D~ zqP#S==x(u&}OjPo%9~_CB)7JjgYS zw;bRy4;F>?nzWKHcb3ICyi06^PC@CNYf-!NaB^~dnF?DIPtuB+@OTBwI6O#$J+W%f^s7*E=R*j8_A_dkg&H5diiF~ld>CGP@H51eC6A3-xp9azmdSX{ zDoLbT;&r#MqF#YS*i{aTnVd8uVRux5R#m1JG4?Gv$T77m>PDCW+#sCCwNSQxWlY{MF2`ugxsLa-8F}on2rthoN3nQ4^V@(=a~P z8B$|}g&^k89)1ki1X%BX@X=*r`~}f|Ml!I{v0;;-QL4u`I(7OnAi%(|@p4bXBr|5N z^8LzegNSCWrEIz`Q@E_G|P>9?z*=nT|j567xHGh^7BO{79W_wJ^(*oC*ru)5G% zDA+i$NiY3x)=da>W?t6?*oLROeeYi+X8D203nfj>CT?aXTl zisble=@pg^4^QnqurWs)&_b;CC}kpPX{;MN=xv1*w)k-Mg^gWrg)Zk)t6BBq*(jJ3 zp&S9^IE-b3w?kt)RYKC1E+N0`DjR!?Z(36iyV-Nd*v86Tg|MIN!n0#w!KODOpC=OV zFdjYJGh#NeRL8=tls-{+zR$g> zvdBIVkoo8C7xxj)ov3>w~`NwpYjmX_NVZFEryC z8xVoNJ3RFxY+@VlzZEjsg}^G~`nBPhx7jI1y2gGzzc@jsksbZJU z)kHsKJwI9)Kp$&4Is-@?_+gVc5gQL;k}i1uHIL-F(ld5ZYh-AekjXBa6u-T^7q+Le zt%4A4AgGZwO|EzBAWyBSE9X_lv=yJi3Y;fUoL*hO*U{QKi~f1ZIUW)IFRDC zde0KGp3B94fr=iHZw1>NCnzY^^oejmrw~ih*e~{P19P@I>yNdqQgp=!29rhqI5243 zc9cA#g={DDWn_x1vG3wwyCv9xBxNec)fx*FvZs>vA=EVIe3K&q#~KjVu}Q}+Ui#>s zu2wb0$gxOgER%`uu)hyW$G7@s$8ruS9qo~)2#c|><3s{2E6L|JyM$K6;?<+wMIC=Y zmayq>U{W~ZeyJQyLqx1#WnzGw%=KxDPn5^mSK7LYBj3L%{ev$8Qik_7;G%Kg8hK4& ztwVd&7?7}?$|cImXTBuqp2G%;V?~()oxii z=2fs7Q9_dci- zjUH2M@MS%PrLzfXI9@*wp7eqW+0c-}8fr9`9mjAgvuvZ!%m^dL{bq<_)5%{Pu5`$S zKu2Gp0Ju(cx}UMH(~E(#&*D)|o%xQZ<|ulvFo@+_e-gX5jwQaz)0%j@2=jh-Si32x z_$pJbDp(;aSSO1dHxiJBeI*>~%n@rw<;TsHZVv&qnHUiLWF3)o4P0s4W!)ZxOdg1$ z)R8*J$h2t7zWp)#m017dUt8+7=$q(z{LfgQnQ{1xA7^p!Swalo%+6vT1Z1>TsiEs6 zOMkXA_(5AiGuGbeOoNmDUjW2g96ZKbV`wqaG8Qv(%$6&aO@rYxYEGf9X zt7G4soUA}FjIdqX+QH)UwN`^mg+nW{*Ve-;n#UamekqbzF^jDZ8O&xMurLT2N=c6*}h%FXLkv$pwD-_`0> zC$~`{aQ@?345kwLrgy_^u$uw#2RR>`yBmvH1gp!$JukjFn@4POEyUr4XDh{ojqiwc zktYEdX_aQl+~L?`oeQR6r@B0JW(f|k7#bN$3w~=z6wi@NXH8!Ga|rI5#$dwCg2osv zn6*_OS1ZT|vL20-LZ{NcS5Mstjo%1nV^+F!(K*qLz2Pws1`6ed)8m0776!Pnr&2Hm z#j32cbS=ThoF_> z^&aa3%}gOgl-(C$Lm4V%QsIcrM0lp^$bHC+*NCPhJZ^%FxGBHN^WKpgk)rGIutq4X zhO8w;WnaM8)%_{RjezANfMU4wmu^>vM8z4@zdb&bhBYKlDPi-2xF79Uqxl4cGVWB{}!KThTd;OQ&E z=%t>Rx_#J3>(We4_2E9h)HND%;uj^Fi8ClGN2gJqmUakCK&cGZZT5{e!!{AQv-O%= zg~v|eJ`cJAi;KsXn!YcXv7@g0^(z!S7UKy#vSY*1)FadLD1^0ag1I@^v5?+%-&zK` z{OCywcB&1c_>=)a^B_(3QukTHe*l7;vleel&0Fx?QYSyRv6^#8*%_ZRd>dk!Er9py zKy`{nwoI}YIx^nFXG{9z!LmjsA{PedGTxbVqJVz1zho}z1tLpr?7Uhy)!=)3OfZMfEr61hN4E;~@Q@9_6b==@bR z1({_=lGi}cgUdtXvEJ4TtB#RO{l4PDMV~qGmjpR(xwh6w2ESo2@NFoN_tV2kCa$nD zRu7&-$FCqVsgEgX2|tS=2{A7EBze!Wdro&X24 zNsOMYTFyZB$)O)$$n{Z+g&ntF85AehqUqC`d=hrN;HQIH+2G4P>jPzylW)r(hHG|6 z4j3R>#=4mV3(|7WUYLr$=c3)tIL%ke)<8^N(c_5EA@y!SXO-3^g)g8uZwKErH-X+O zSaqUS^qwcbeTOM}vQ#kXPeu^I5FrbN{$05HxSNsg)K@zmVaL)#wzUr`MAsirI~N@gK*S53H1oy ze>r7lo4AlL_u2mtp&v_K|Hn7s#d&} z$9l!C&<0~W4M%4n?DVoybrTac3j#zbNMi8I-zam$Fl;*6pK@PRY5fT{R0DeTQ2<0E zj}s6K?aV}}KKDk>mCWfsp;~MT8%ORXnR_EYEGW6;8=SZ!pIqnb>tfbJDA@^%|H$P4 zeW0EJbIwDFp=!Jn6vPrKzF9wrAFz&UDeSb;mb7rB{~`55w)$rGPq&*MWk;UEf)3gc zd5C(io6A*RL7`=QjU68bAq~nUP%HaiI6?s}PoX}4|A;zpYV&`YzC;EFiL5Oi5suU- zQ`^MP!~!veti$Gp2c3}-xcuW{dx8Ttj-Pw*Ye2HM7%@~3y_c*6065G&;AHH_Ht6P? z{8eT>rljr6AJF)i2;SqT`_G;O!{Z+k_XNnda3r>tItKoj_1j@qPgTSbl#o`vKR!J2H_Z#42{lZHJ_mk$DrsitMVD_seY&m-Jw)O`nCrV_>BL4@qz@*y;^U7lSg%)N`((1zU>aUw zz}oH4iDi#`r-+l#{vmC`Wr=m+v@gLDbxuHc>9itmWdZZAvDU zP7Ow`Joh*TxXlkNQUas?o}R{I6$Cm+fluESZ@9;7cdTj=+|aDu%CY>oh_`jZiW0|` zd$-(%sUfO08{O$0Te%zENApx>aFQUaTRmHbzYU2p$1?EOHN}zrw>$aY8J?<)&*$ON zmZrtF&uW5Sf^0ZF&78kA#gY$hJVZQ%^5hZpG#9sOZ;tr~NAVhuv2ZdE&dTr9N!BFw z2R;Dw*nw;+o*UAV)6gR2SdOg`C;R-K^3Ae8owyt=*5NF75iaHmO#viX^76M7;E|14 z{R(na!&o}f>#Uf8F(?(mt=W}e++piMOhPO|w=@9HLUd|W_@s?&9cPoAy7%a0>LTAj z9WkBC2^onIYs+A^j-BS_$1OvDn_&L}3)-$tsLbMjva@0&SmPa@>#FgNSXO6nLw$@t zSeEa-6H1f5ziVd+UwSRk6u+z%U^@VtS#9k>8?TCoM#ht-UV5<{@K>AwM)SXQI7-V7 z*!JmGvl9~4HiA!E-?<-nSYn7ZdX(^1u5X`JK5#lU(TCKT8j9ZUHu5*eDYuNmaiC#o zMo#GD(K^+^Bk-zVz~1s$uqyas0zfO399duHA~`nK31-gz_^`q(T|z!cuV0F_a;HZ2 zu7ci~fs94s-g+_E>9|_}LIxBPTND|%a?1S!vFQ-~OFx;91(EetD0@;0Je41cz1Q1% zukiB8zEY!AK|mhRezastJGyc$gr)T#R;1cn0f4Fuso~WzS6uSu4Rhz$~&6W|Dij`QfAOc;cY%KB`8=+j~xKmX1ys=4b*uxu99=W z4wSa_L4kEpH$y!+S{^!jGsm5M=`XFvxF&yE3>1ZJ@W8z6gcNJqp!c-~rbk)gl?Tt8MKQvZ3nQj65ve*;TC3Ok$wZAP#a+Pp@M_vGolukh& z7->#>3ihpMq4DiPcfrW6vsjUIM5Bg|eNB_WYM@T~8Lw{uoN8uOK+Xv()0c?{GBc{{ zh!eN&Rs}oO;26tW#(az)CsKK( zrkGbz<#9xk`bj4id4`|sU@NQ*P~WAa^uJMHx3juY3s(*>}daLCNYIRL{J41=OU&;tyuzbfZcXoQ8&WciM%R;+x;hbg(6DimcG)% zv)w)}t+|O28$M;Wz#&nUqGeA++dU#c(0XoTcPj-=CU*dlW6sZXFpaF19W5+mQg(ju zRuuml?yGRCzu)Vdf&UbjLk+k5$Kl6h48;3?=ek&VLi5-667OeWL4Ehg6TMM4SSy93V-WX1BCtT+1p_>-2a1HO6;C z-ZxxrKi2{5qd(R~>h5WxL09hn#z!SdpLJSkzLAq})H}iTN~!{qIRgCWm$<@KH&gGO zxKhUs9$6Ij^0epRz-wNSy%S4@`?+xIRA9Js1U?A=1UfN+1i95V^C{1s~}5YTwU z%%e0@d?TFokd}_td$7TGI>VR#(gobx9g2<`e!M1Ov`(tx0ki(F420J)-PDKGCh!&? z_U~H_sdAcFzox**?f7lIgcrYns6Ti#)bBaOs~0p<+TJa$Ik99O8wxI+cceV02`*J4 zR7aA&3~PCVdzY4~a?A`O9BWVw;a|v`bZs~ds|d;2eqZ|WUaP>_JvtAX1T~x7S-z|C zwa4m%e`b8|#>~g_oHCVCsg9b*l{|0`$Vq?%QG!%A?_14i?9>9cvP1FTg$qP9eE1bd zU{05Vl7xk<8h%aR2cYJ>X2?7Nkoc|h!sPeS8wMQiAmFBhxYfhl=^w+Q5|4$>%XWKt%X-;QHLbfy z^Ghpo$FjVJaZ_Wmd)KeW7#`2Qei8u&3d{V6CJ4iXfo))xhNM8Wtr%_sIDqqNhLAkt zWGXSa1D#}R!y93!;>C}&RLaVm1+-i}Cb}_*dPgS#s1+W(NA8hpHQ0Me3}Urv}- zG^cpw4uqnM{E-kz?@Ay> z$DMJ+UyWwR{27O&2`XEtzNQN=?J?k&=3%LLu8GcCeF(OjttLtU@k)Ol_zKb$P#+)r zf?O`kM;ZL}e5#!F5B3-(2*=;XXA3~hMRg=@F3;oGZmVV>SoU!R27|iUSYXU%13&+P zyw+u`xZ?9Qq{4sN?=n4p9$qi1muu53Q^Fc4amfrc_|jYKl>>8e7jhy*;qPhU$jl;x z@($22J*WL`2QHa5y=#m=JxNW5u;?d~eke~L5;vtif_ z*VM)au?UsylhZ4A=}}FqZ6P|rvB`WQwIabiL|t5y#4mTKMuSb- zULd}A(O_Z99is&O>n+Z&-&nxPCRUtrW#t%)tZ##m-^<1oNhnEFZU%&x=z#}gzkZhd zeY}xV@e>rTR|e#d`JRLjQUED>5IK4J6D>3w=e^H`KL=NIt&T;6tA3w0_T7nYu>Uxr zozFT=bR(p|lqH%FsF$+cVCX*F?5Rbg=*G&Zv)E)6%u%d)N>IZ}?T3c+PVA6SrL#WT zb2jYTZ)~Jm#ex>G%PIR}YK=Q+=*%1&AC4Kbw%f$quC?p<@EVH03FQ!Y-;1nJ4xbLV zQ{fH_W0`KmDmuoKyh1VqYND{gJh0Bodw8yv&*DVYF!7@BQ&x=5|$a%c;~6wd#&H zUh}^}e(&iQ?M3XxRQz;K&7aa7SlsM3Pss7Bxft=*)cHy>Omn0Gaijr0?JY_+<|%mk zm-Wa19TJZJJ=K@m!`di32+&VGkn7DwLy6wjZah1o)H7)yGDtc$~#yWswmX}!*}@l zw0RE?9mxHd&K*SWbPV2LkIVh%^vd&DL7hgW|IdGZpFiKsJ^;sy|1);!I5$ssI+d_l zJbEwFto+l=i-7`|ieJdtyU}eFw`Sn{X}GPoWel>pp;g7IqnMFwoO5J8ti`Nw6?vi{ zhQ+kCtf2I*(&Y%z)@0E=jh&`#4_L6o;j)gYP7$A338@bAJ2+xKbl5j|)O^&fbD>sK zTupz+wSI;{>PMA7TD;(aSw^4}+tc`nMU?h+;FAo3y3D+KW#qy%!g~8U&0-Qnk{A@V zL%mNa&#?ytT11m_T8$1J=nlUxO@l`IX8+dBBV?ZI61e5I$-JDysrQFd;7C3^%&|mE zp1$LFneu&>KL{Z5!D1FP@WUyRfTF4}i4;qXuC`4Cuqhxv&VNW5Ok*qltNA|ma>mGL zIN~VKx?hZSfa1-3fh19Cks8TXb;RfW(S$Iu1o>pr`N2gxZo@l0Z<&ppQqut7W}33@ ze;*Lt@*qJ_*UsA&zEojvnD;|f>Q3trOt=~t#^#)wCr7tV`F1t`eXXN%USEm**Zux% zik`Sf8QG;xNg^1Aq1|L2tyV4RZdU)Cm#v>NVzHX*{uOVKyGI zZgvC?`ubzE$@0r&@5cmnZ%1XC4qY{Yzw;^H-NmAop<+kpw;n!~pXzO2iHarl?T0!1 zA~Y3y@56xbCyS^7C)BUR4TRId(UVXQ{`R=ds#F3LeZe4ok8f>*o#;BITF#Dl9w7e5 z3|F&`-s4^AYq=@NC=a#Vz<06Zw>3u7Nj@tBgLi7z0zmhWq34ECG>$8Q$4QjkWOT_d z!6Egfoy!cJJarKjHI98`${wbWWJx2cHu72pmM|PwN7gZ1yIlKZ`B3t8Sf<32`c>1* z2D@)<5ESOMwbqp#)@tUA%azS}pQD!W<85>z5!T~OXnZi9W=mC3H~hU0>qyGdooLKI zq}MRqL@^~=_dQ5mv7C8jf&Uxh`$ed4Q)@YdZ0_DDDTP|Tz2dc7UEUyHGL%WU>Z>!X zEIv8StJkQLe$;cVV_sEFqkZG%qaf#U4-?10SoXxhD+5uywL9M!rDngjLr2UJ6FJ)f z@5~x;{!1IIr}rPy5`yz}x9-@W*?Trg7~u7iPX@+px6u?SG6%f_xy9CYnl#N^UFae_ zR5IVr^Pm_tFmc73Ze~5`v??{{b6?(c{a7+R!8$8W&3C?{CYxmHI`*76Y{mCvDE&I@ zh*^2wRha6-?xGB%t~=gaMKjl4!+Ba(bbC?Ay{j;GKiTYdC@w42D}yFT}0mfh$0{p-xfmk8nb^WYqpUnm+adTwg+^9^@ZYGsm{q8pID)7O7U zHvhJ3D6UfjRdaBUf5#$S1Vp>+}wI@*fal{iLeuWz9pc z0R+~emSCX%br{wi9GOSt@j5!j(PUMmfAh(zWJKe#s=L*ZM8Lmz5-w{htDSuq0t>aJ zsV)Bd>-__x(mIg{cIy&`pqx!Gh^Q#GfRhaTKo#8U|S{K&i<(9VWdlg6y7()aPaS>JSfna7QhJDmVd z9c-cin7GRl=GJ!v7`{Tn(wpoSeBlvbY;9ok?OGNbbN8RYrynXK>ZO~^=>CW^M%EH> z^|B&^WVL62`1XRw!aTD-=YO^gK3-oO35n*vp*ODKt&T<#ufYH6>h&uL*f(|=5eS=s z^Un7?Mc{njEL%XnvVgOsID(;1i(BhpM# zo|$HKQA(a?wlx954|fk%gJPA%9@2WxHCtHMN~o_9VU6K&h)bQ^LX0kIqTxegHnbj+ z(VOJ3w}0$i{vme#8yt*o8i+Y9LEnmnzGwH06VoQUUy?hu;X2yjbBMbCl znDR0#5=pc}%B>QqlUq!JDz!^_=X8FnNjAZBU=qaJ8 z-y}{$`p0Ncbd~=5(D|;MuiQuZ=a?|i-zzu*4p$nsp2caXym|PW)J&Ph(UGzOIwS`HRJL_0$dW{eban}pe|g-4m^VXb3;~ z_t~K0`G;QkHSpax{LklqNQf^`VgHbV$eVtLy>5xENeq1w+YG&MZS4PtWI=ISuoscv zX#x&AHvflI54=tYY{#d=2UH*BOd9TH)QjM?|l-`IRB2M3*batzb2!ig3!yaOvlSaHm zQO?DFMTF_-Jv=-Uz*SZEP+z1%Mp57}><3bQmsflrgeDfQs|p^GB>eyI(GOrizr}((XRVdlDgJJKB3qrS-Hl zgEPY~Bz!7 zXV!!jSJIDR@`hREt1ZFRscV@PqI-TUi3Y_{hg$aPVLDFdMjd^xEdP-9pV27XgOW&| zL1AkQr)6EP(6G4~xLJc$^nF;5$A@n-NHiro@dzZUKZ9e%fiNyIbZ1;K0bhN_}{~dnckFRWJ2G%y@E3 zj$!@Ppa0Qg-zx3@))5`yw)iRWF2`6P@ zDt4Q?@9CdT-n8ZyJ#p_0x4DJ$6TKwdS!89I%mHGelg=+Jh^4-&a zGE@`yh5MG`_pzxfDZE_7O|-j;xp3+<`2E-ss^MA6D4DpN$%m6q=sF*%i|3JiikL)z z09u$2_ApO?*}ldr!v#JLM5=1!l0Eg0dRH>-03;QiYt-1Dh7!qTX#xwRaP&#zO5NA& z>}I%hUUAsxj>Nn%X2G6C<$L>y6aR{4uTVAr+$RR~lvHHgkuw>3)$2&~4xFj_wKxaY zPV`)-&Wu+sp`3>oC&TV$tp-9&g>)(%{gV*$BT+tl{`>IdXImo!rsKnP!9RVw-v|f$iEo}sj;c`mt6UOu#Y zvXWFWl?|t%846&}{Y(lO<~VS~MkQINvG%n7o_uaKEa%b|xHlSP6EWA}IpGx}A?kCQTv3ExOVh zpVDe1OfxcZoYzP=K&`)icq&{VIq9kC;}1Bs%U%OdF;Qdw08-9P)sA{L5$Nad!HDHr z**l&hvE^IfYA|iftGvMLvPplWK3!p3M-csX-(%` zU(ZQa*lZKU7|R*2x4rP*X$A|fcg$S}@o>mfm2{1qVBK{W^Uc|d+(7DK>&>_-Ipu%XICc5!#H#Pb9$LN=RPem}Pt=g@; z{me;?NcrkKoT+n(MriW2W1s)J$1YAk8g=C8RvknV1=ns@L3Vqh7b zD8Pzxh`cu*h~-lE#1!~z6XVP~Bl3R}h~NLedw;VnB$YjWI(?I#FfeBS5M$>o{7|aH z%wm0l=%%Nm8s()Pu6I|k`-WgOw~t8lb$DGR`Hj#FUI|R41CnUQAZyn2T*!0b#NW&8 z+fu*PG9uaqmJ^s&u2CG8NsyTskee*|P88JDKJi?olfx*1xsOguGco6(1HxzUvh8}8 z?q*?UamlfFPi`I5paD=gEu=W){fD$Ail4?3?BiU*G2aL8>k=?ynDi7bzM4vBOx<_$ z=sz-=uHBgsbyYKoi7T5>OTZEub67?N(WXf!NcdUeqXK_xON<$wsQxyfoJ=9N;h20M z@659CFV73MINh7ST%W|+a8anGoMD=K+`kPZhzyXwEBDg#X@SrXsaA6 znE!a=WSpZ3BI{7hCXaSTA#LrSEMK+3a-#H{TX(E;n-Tzga2dYKK`5BHXxFOc8pw?u zDStj7IA5Yt58+y7RQFZ^)1IA{P`N3cMBqdSf5mo<)Nw_Y{n&@^J;pAuFqK9c2SYtmEsOAikGy4w`0 zJS%8*2)f&fx)Ci?-4%1Zo{2Z%tIpSWf<5#Oned-d*ZQkEDT05K$0WJIsKJNkl)tCr zZ7=GM^WWc+1kdJcwhh--`&vxa6tM&WiItV@J4ETtDtEl(+Wbgz^|7wX;8*bDN;AA{ zo8t0FvDq09&5`u9_G>7Ho9FJJP zf?v-6De97_+A=ruIASqGlGzU=+lt>6zFEW&g&_2-3xD9;?`&B#;-h_!@7I2d zb$N34{(Q>`iKVw7Yqb|gxkEgqqTa|Emlq$cxavtZrK5dIRPMo{uuWER$&6tet1r!d zHwl5EKD24>5RbF;5yp67uQfYBz^13O0&|er!0qCjKD+nrnTI;>Skzi@L#yzA%qbAA zD6P(79hhJCnX&K>NmGBCt3rE)E~Z`MCB-`f)8QL{DH*GT@e%H|0s=L{AuyLkk0EZ@Cu;HC{7fiG7 z<7dr3^{v`Ylx{XTs2{~$BK}&gBRC(l>0UeTm;aDN)+o9;G~D1)wi+gRu2_nfaa8)$uHD;_R_>fs{kkr=_gZ?AB(9z zUd=A#1t0adB9aii>Hm94d&2;XB5F(%koYQvgSYym-3GcUr%b`5AivycPC>@ydevYN z`B;2IoSjPSAdVG1%!086e~j3FOGP~3&WFD0^e~Iv@xeTrSAN%gHtOb^Q1e4R2SiiB zfsb%tE45oIDe^*G}{gDW%pM_}0c|3e!~`ByIWiPQQTpxEEf$_-3SMR;svCz*X*RK-9#R$$e0(AdBtosTt zaJ7JN=N0y(O*R|1@-1`gHFf0u2lxf?3ajIiT5+Jjk)9_9bmHq+2RlwV(2IaqqF|)} zznZ}WJ%C%cxaOWaH$NJcZbE{k?K5PX$roi=KMH~%dGKZp2)Xa?t7WFcdX2a;4jP*p zSDU&czKY$#{2jg4+Z&X!@9&!X!XjM=W%G|Lj&MSAzCB`D~pq6R)o-5q8* z@P}T(^ilZcN6liMT0SmN8Zl=3MYT!(DST16WYcVG*!l%q+aLY8FGcDwT`?7YHB^p-hZru;s zThkZ~7LHSErCNTnd?89(`fI^qU%?DG4gagPw}6TBebYvvSh3>n?oiwYcXxNExI=*g z1&YJK;8vixTX8S$#oY(j!TopUeBb`}+nhbw?4F!t@@Dep&15op=6UY>lKYA}9F3N) z6sqr$I{wrJTVd@g8((Kc(SokhA$BB|`Nt>y^FJ`70nxjc$Zo@3xPs?rw&elYEG2!^ zPbe#%mZdBAUOEfaUW6FkO`V%M9u7^>BJ}wrMk)Il}N&i+Zlxp34*A^1F({%{I zNl#$S6i}7kirPnN{Or1(2t~UoAIq!DrsWWAoN;~*G6)Y$4qa!G239uM(=xTcn{_?K zI|8Dn#{w;W@jXd><~mvxa^bUpRFQcnN4zWTE*{5Eg2p40NQN3%3K4AKQ|sowl_|E~qcCn-Q;G#{qOJZIP7 z8~xII3@kxDpa51=o+ZM|Ask3Y(glU_LnZV+Q+}~1OhlAX&2lQ9eQ+wu_8AaIY z`=O0BEXhzv{g~sGAKaKzvg?41I?Bs@*w}ygFn$$)Dtbs`uv)4|xgybdFts*$U!rXq z<9Cu`?`S0w7=zFaV5$&aO;GOSA$d-)tL06n*H^54YYF1roNx~_*~7XY*ycL=Hhq5dqQ}N*ZL&)r0T6U)h?Z}t znXA4uWQ{>%+V1|+{gR?@@OBuvcBQnDgBuuo2Con$1+EA>#q6$pTAug1aK!a-dwd^U zB7PL<8VufL-yWOrWQ}>8nHI(_R}V#lX6_BQH?8^=ZP%)a0cxK5De@ z`ixjPl`3S!*+u-gV|uE?eO3FXZ)yauQElZjX)mn945>q(g{DdUaGM5 zy%yanhoGJOELDc|50iP(Bq|V)m=wi=p$S>H@62PyD1BVlIHRa|%%L4n>62PgE+6hl zgn}hj^9vI*F6!NfKLyfpnU^*swMkjkQe*X2s_;u5>Q`>G~wChRgV^5h|ej)3+Z%e#2)!NnHPT|MJ{jKrR zlq7lJIfn&~>vD~iig+MF_%M;+M>clH@AT5~yN0sIkLWDz-6C7PEsbfbVv1D098TZ5 z!TWhXW1~Q(F0RiX(@trd8`891IU!c1xnGpo1UlXM`i)Zqot8cwaL_bb?fy!-1~Gn( zOI6@b_;?R_o2tkrm~j(IT5P?s)LA<|^4V@!&^sm?z&iWtVSM6~lCUNZ>9TNOA!#AT zHcw9&>>nElBjsa(vo8@2L8>k{_GYoO-OmsuNd#|kFdTj5?fvsbu?7&Pjasnge{-=V zpo+C&R{DhwnrgJuIod#zN$~`w>mHL~9wvC*EKfC{ z_u@dv5UkhKWDRBvqzpgpi<&V!phf`G|AeT-lu|3~%qv-x>>Mx~E$v#Dwf^DHs6)OM z%TrjNHCJmZ92iXGsBeB|(K$Bo{Oqx@;_o+h!aez5e7Yf^zcB}-n6Vs~>!V)T9!py^ zX}NwMt=t#{1>nS@+;5>%oN4*;JQ+_rdHT6m5sLR+lGDA>Vx6r2L{W@69=Ido9OPNr zb;Ceds}AVa4SpzdmkINE&N3|yQ^5aX)IhR za#-#nz`c+ncP!;gkxNwN@vCFrx{q5zEeTrd*_WrFiL=KJJ2?KNUVWPgm`8o%Fzz$# z+%b?`HznI%TYM#JX3ZOS{esWgs(+E@bZjm#JgAT&u0LcbDAnx>8|Q`z8?9?nC)p5g(Ax4tu!s|R-Bj}!(CTXG`O|#l8+0h=Wn^CclJ`tDMrct`Qdw24a z@;{Y_|9>~Y6anV@pgyzwze{=FrcHhgbD@2K#Hd!%vMm^CsHZq-$Vb0)WbM{rvl0$j ze;7m3mAG2cFv;N1BaPN)vg0G#?TAgzX3gobq0Ifx#`HiY|9&*PaBP;4qjJ4dOQ8zB zkZOFaQ9ALsYMk+iYJjUXsGX@f6^ePd+exFtb2Ns?HZ~ zU#cE!S)f@6(j7hyn?L!I>NF<=0)hn?{v0 z3OW(BSDb)hhNRr-iPn=*AQK86!qAH!)}B7NMNHel0^m9*rci!PYHAyZmcWq`>m|Pl z4D?1CYc651X37RGR7$O{3%(T&@jMEUuWL5XC5kB2Dn04?{5b;8J!w1c+6a4g2nuvG zkV^^1wcN@Frab8wRuNi`2drGL6?7hct(lt`BBh7w^*p#Den*_*O|B$9>NuNFk@32w z71g#r8DQRTkx)=1F!b&oMp&gxp@v5GlBt-^3=t{1YmC_mtRrn-qg3HNc#2QL>40o@V7-a87lugOBc7)iJ z_a3H2M&8tDm;W9@$I-20y2ftB(5HJ6ff&|Hq?}tQSIasT11r=Z$85o0xKBDR)8$J> zhSQ9P_!N{KiEC3I47By~K&kt9%e$ivrN_%*8(s0BP9&BQ+xpqCHyfD-6LJb2^EvZ5Kj&Bzi^-O*cPU;>V-3;L30()7* z^)&{^R?tza-JToK@TIAFu$FFW2h`8O&*5Pm3_;pRhuOA(-K%;NTdS-Bw#glUWlE4X zow}t`hX=p`jtLX8-^yK$CCT8^Cv?|mg144U zsyv*8ca1Q7?p6YA=f0>`bA_8~k(fe;2f)UpMeg+zlC1_oT^Syx{n~tT`@3%0HuMmx z%xeRD%p=Pmjpi~Bsv8jzi~k|l9Qmfa^&uIfK7gC}YbmLDmO{cVUD7gB*IC$ujNaod z)wgL5k2T z{eY?pIDG@1kT+h@LGu0d4bSc(7mmvZk>Oq=r0^Zc^ytSJ0?jIf%MR~J1Ir3SBD2YH zZ$1xhewV%L?j?p);haqc91eQtGW}73&DA;~<70IQLtDqoR6c>(8>*h1k(SKtnpm1%}5y zwp~tQuopM%bG+8$Jk8US;`+tT=veRAk6k4toI;TeV430WLE+=AA@Bg~&tDL&%0(^C zx>W%ZFW#f%u%baAQ%wOi1W@25STj#8lF7FyD$vJ9zI-DOrcMMTvopqx@|+MTy~gmI zH~^`t-AAD;`c!zf2McwzXF2czwt19_sY*quaNj{bm|>grnJ zWtF7Db(>SmZlLp9D)y|cBHk^=tmO4a`N^sA#hrQ%Fq9rsUFQrs$7>>D;xiNM&P@@K z_}QtaQ`{ErWdIdX5BTlvt7Cs!DpebFE&?_w93RO^xQSUwpROr?(gnLuC`%=sQ1rL{ zef^@Mxbj>3Q~w{zq+}X>!5v_gF_k#{M!UEJ5Q{|C8MAZ zZ8rup^rrFpxzK;3Wq?&^|K5J!>q9z|To(~$Xo8lzk#lR)dG%+Ljh>K^*3|QRyB6A= zC4PeBQKw)0&D`NLzNzy+PA%G-;?2YNiK>2%pNB(%22*S7fdg`>b#0p;%g``zaTr}r z0mw~ZC2a|~LwVc!1)IB$nOsnJkW5WhYL~Gf)yYM4^Lu1DnB0s_8EYgJ$-xr9Ej)E{ z^!=AnX?ZNHvs@}MGO)+~>i7@6#aQ~u7S*r&ZzPh8Z6X~0QS|c0!?1&v?*G7O#ir6W zz!Via*U^Ufqwpwau7>`H!acYDsuRcodR)eerc2<&!$C$k|zqP-VSn5}!(ACb`p@;@w| zxI*e4|Dv^6KWI*6oDYrBCy{Fd=RA@U$=Yv8e!%RUiC&V_4k=kTAG9yK%sU1+itAhl z>QN1X-}t=fkz*oJaWo{Kt(8M9(rfiQxCK*^UhG^5zY;SyxBUyV^f60*h6BZ0LMiwR5UM z!6<4^B?XBIf>Q^4Y8)3N-Evj^;83i7ShgEU&|? z2|q06t{sX}=pwrm=0D@CKp0l*`O zT*|{BdSz8)lx5_596l+WzmmD4yxFz<2vxHSXEaTI-5jagH#ctdSuT#~334=~X=}8! z&;d0RQ7pR{hDW={WC=lvs|^G$^hlnNz(agWy?u8pqE}fb`)}JcjsPT$<6xAyH1CGs zRctj$SIpe$4~HIrB{G2icVaR*}FHXNQ)LO}4z;uf9yv*5GkK8yyTAYd)xQ z_8x!nzWTAqvT7=J(1!qP4AL4H_u82>I`Tn_taKICuj0a@3~zX9wTw4~S@iFj;1g5b z!G7=5aPmqTvRd=Efu;rD#Ol(dO8z9a-*l!W92YIf!&jZ@M8KGr9X~?}LKL>s=4^$o zQ#10(Uzy530rf=mTW)0HNj^@-*CPxRm{d%I*3YCa9p8EitfgdLZk6^GYE8{0d& zY4+ETf(@&T*V2u9{l`lCWMlc{*(oOqi;X3!!eoK~Jm;uDAQ^Db{qeenyRn=ll`12J zHcxf1j<*DmQ{`a`G36~!KC~K$5qQ$Tvezg4DuCB=G3;qo_^I-S&B+s2uoIW?4=IzK zp!!gDU5TsWizeBT-UJpjY*QJX0XamrtmUe{Oj`W<>ebKHnAwG=73ulJTD%tg>gQ}} z5>_1Y_B#nd&j-?4=O*Y%%$#(d%15x!J3Ei*nU(nO;2Z7-kOC$sPCLm`qN(p{Hl$_u zD>8M2?7MbOOn=*9>f4@;TY+!JG7OIM5`-6?yhY?kGjD9vVDwCXy4*+yY`NB}YD74& zf7+7AhGx5d!%Ej@^~p5R36(_Quljd}j7U6XPq(B(CE~*04K^(8iA5k^VAfJTwFhaA z0pU!P-%%Zu8xY0MUihsa%iZu8yR42^OA8_y8625CQ{a0&QNZ~hNF+>j6hZE)@sCEg z3#au;`R@+0+W>ZEE#?{5wQK2|O_q^_qlU7N?TsX{u!cG*UD_n%R>{#F zb73hg6+=}f`UXu_vrSHeT5|;f>Fa*T@I~tz27F1uId}wQ&ebH$WmN4K+SYLNF)8*SwX6xb{Z^UBiFkG7kmqR)d|f;mxFk&3l6|P170dZSpaRPR>(n{IBEX-s30^Yv`6QdV(hJfF%k@C#RepVYp zNG=Y?w~VLZTqp>Bp-m#W(CP2!sQVS)R+%J$4BUu4GudGQ22ti^f-ANQ9%R?jpeXL- z<6jEYc?E~R2Nw?vW>aM=4N^AKxjGG_*aF8{tYt+?_;P6M?Ccn+y7w_HR~#|r8ywMr zPx7-okr*TaB=z@tTBfeoP60iCh9R#*M)W)c&rV)#^Dhc7jEyUiJG&+rvPR{t5m><# zU&L%gUT|KOzx^m>7-3pV_`?qx`NgZpB?(tw{TR|HK|*c36Ni5+j>L(a@ux8L9{ z2=x0ShiPxZ4;KLIAw;q{)IkJh8!=9R<5-&_q4FhaJ*$dSSccX%h*~`qTKg4?`0n;R zHA8$ZzBB|>g^V>_R(CKySq(Bqq`)>RA!tE90OMdDO+orV?%cGZ_7H-LEqRgi>XfiD zrju%LNtW)`)dEC87M%Q72|)gXHUwexFEsUeMzLmOWb8!VhBEZy$;W&Q7qzpWA-5tJ zQsPEFTVf+uM3>p|DC5V-0L@CmF?=+B7`e3cGK7I(wq@EtrN{OEzb1V@YtwxVE<{f`2PV+BOt zM#B^bSMZ2sIou|E7-@m&bCM`O_b*uAU6g%Bhm@_OxFtv#b)4yH%>zY<)p5tsCJNH{ zm=$0`QvTzFtc##<- zQM?@PI`tER8c+#m$RbQqS(TkuF&nBVcd|9uU9w=~g8BQ?VrLAGmnyxt*G2e3gN>Aj zXGhPq788wyJby<(U(ybwE*C+t@yH26V$0@}9z_Mm`9)mv>RwfrSfCw#8=_K`cx!aK zq@kFStvZ{EVU)zzpC-&Nt-4rJ@i*ElhMIW*DD-=ePD-J)8;Q@TtDl`c-;;Hck`1#2 z3uDYmBr6JN&E)ZsoX_9zpsMyfw-Hh=sO|AK|ee*(G!Ahbdq3o_H13qO-li z&p8RKt;s1VvB?FzTT1jm0#OFN@(;*+VM0VX(!LSrvEv|#mW5_NaOQu~)mU{c-qiJlU3_sDbv-^0eaD zIO1W+7nq<$!9wIw62L_|i?6^cYSrTQdOVQ1D1E)r+r|lrMVNPAK82x0fUl~h>m^1f zuho{avrgynrC6H+kL0>@f3Aak2JhU^MB^}!t?>U=UPObcay7dGz3^)s${Y_|tOM*C z1vmBBc6SS-8!V!Dl1A=PuI&q!KUB!2Uh0i{{kEw$nYor8sT0m_-W~_Gx$Xa`|2ax? zQpHvlKU_SVYUQnGGjJOicz=I(UWMEC1o2S7C$%UTNfYn0ArUtdI;l3l5o98r1cYZK zpT*)(rvAKFcSXr&18N`T zb+tMb={Ksu6!Zk|0RtxYdj>V{?Y! z(&1{e{#&(#kdF4s%-uu##MzGdI30tPuv1$m2v-daluz!2MEQwjU8|ujAJDtHk~*`3 z8&X@U6?xmkf`5F(;_!A?zCr+hl}yZ!A)B$>7Z+_h z1C~wv9Z%T3arB_{tod$=3!mB10ZelDwewK6aMh%?8juWkt7$+5y<{83%N!wxb+sO( zRNMfnBvvn(h6nz_2CT?j=%XVY=d*Bi)R1dq$wLm|n) zjaL_H9<0M_u03`EWgD-sKo6wWdh>qg8oAVSaT&4|Ot2@d;L~|LPb{-SGj2v#Xd`Be z4&N2AVMt2uY1|aHsa%q>k3{F-Cq{4YfZCAtW_ADgAY4S(EEjw^g~wwL>om*hG_EvW zs!3q!8XqOdUco@~-zlX3`VDGKfyNO=ab&JxRi!3Wf6j|6qE!r2`Q#`~%qmcoK?Q~s zrN#`BRDtxdWw(AfDiN8s6Js3ecSZ(4o;XF9F{z12oe5xTpBxPVG1ik-w-S{TW7=Q3 z`s)BBn7QmKw*;q)wTx5cIn!LOo*7Nm4Jvv0i|8lbI`#^?hdTRi;46dF9+QgWR7a_R zys^OnE6<|#%+w7H?blhn)sNk!)h9Y}=W@RU)l>3~DAYw9KgmhTl)89e#7ToMeU8Z*YjdS9Pq4+p|{WofU)-5oPhVj&c9Bh^ zt~DauY)>~%=f&0kd@ZkQtVP9KvFK?oQ70B}1Y)djq?&pV-#6C}r>7z@-EX1j7iVDs zCp-BSj7qiF^VuVJeUNIM9VRIPQTZ)|3Bg%vlZlJ^l>3(<_b2;MH0ji2tl-G@a$%|) zW4W7}R7>ytJjd75o?E|W{khtejp)+~u|K}BVmsge2Zol_)(2|$*aI^>d{daa)!H0h z2HJ5vkzh2f1D%K0*S2D~xOS)18%x4Do_UQ9i<}huQhUly3Ca4_R3hRTuuw+c8@rMp zdKRk0l2r$KNcf-Xp|x;V15}CDS_f{1uF{67>A;x4HRyH6;Kk&T?! zfugu6;JxpksewqzKOTB#p{l-5iYX-+NDxz-idhJ0HidzN{h^S_YCJ3Gk(`kjuj z&$A@zMzqtlC@z==pb!t|aQALJVlFSF*G2@Qt)G14LC5ST(baBBGKga}8G)kZev;TA-|Gol`Ja(uU^OJ}UE4gWi zX5gQYJJwmeu9h#Jb6Vm`dzZj5EAABUjgIR1ip_`PC-n#u%aH~TH9#Y?l&HGj#Kc&E z{&UJY$rnA{`2@yDF*1_tb9#17n@pRpSh!p+XbKJZGu4M$@V%StILa_--zRDS@=7 zLydHmg*T#`gMI6AEc>-%@l|SYDb>zfZ5q~6+3`5mi6<}0TKJ1F64^?Gi+P7AH=O8ar0S{S`MZOt1 z%ZFxK^hpCYi5OUR&bW>Z% zbiu3TWdWq)9F2lZZ4)zmg1`jw!zNk}r?|xKh*h=fAJ0jqcYt;`Rc`=^AAhqR>Z~4Q zP885Rgw|kX2cP_jaaJ?m#VEJ$+oxpNvm&!<*QC~;QQ*FK!?9ZTxKDOz+sT~Uzp`T02l%g)OfV^|h%xf1G1y(T)^a}O_+9|`W z=*Z3Ik=Jj$OkQZKH)bARUq$Ifxyi5M;L?HEcl;EX7`K11ZZ=d&(`4Y+)Y*2KC7ntO%>PZrf~x*$7d#yg_=+iS zl|_$El8_H14l%6t^A%V|Vi3C;T{%7OIB40EL?$)!`u&C3!$9yFtF5khaO6HtQ*kMC zMPB$Jp3PlczcxUUTz)E;?RHjt1XTf5sC!$ci?9B}rWaE3LjV3Bp?&!Tm&xMhGUD$Zj3~^3ctV%3brVX0?x(^qpgqUS6#3sJhLh^<1{Ww zQ-=)Y48w}B_~l3`wEss+^Iz6Mtrd7=k>%ymaCtiNfDa+wtc2UcCXN+i;}^Zu#9`nZY0k)wy(QzBw>twrpkL0kOiT241+p=)#!eH zj9NbuwC4~X9q@yUL$-H?SKS}@duw=awitFmfmWE6_^e|tftbxIl6pNfL)4#o7}YwrA=e(__M}u#JKahJ3SiP zI)ilKi$J;4Uk#Xu68Ud%e~Qpl0Pa+_A_$H#3Fow0bPLR9&v<}EdZ7|VgNvI@rw0bG zrTp4X%S=J7C%Mp)3})cF@={~2b#E};&ectLEU1-;km&O?z@z@YWYEoMqXR=au79?h z`mXwVWuYun#?3Ojw7Eh1Ay0VV`fpf>{%=i=4i@@BW(7Fc_*#R<)=M1>tI~@gRn|P! z10SY66Wp`S7nwHLV@n z0zg4u1Z=XX-~-jj2q@qkT#7AiZIdBB0eSH<&>W4;L2+6}a}kDt+e+%f7#C5zFOE0G1$e-+v_FRw_nh3UhxP^}MVr~NvV*JiZ}XYIc5e>seNK~2X7k7lE&57W0`H37 znC2`_qlm4p9rEAO_^~tpkSl<^=~%+IOsdPniL(jCK4xP~6@|`W{C-j{8<%Se)FI3b z7RsPjJX=SbAYe)&G$|PU+hZy(glV4CZ#jP z8X8c6JBgB55Di13o{$TEI(9^_uYjOFTH(-W9c!shOeLE5b|z@Y0yIn!Po|yOdjo{d zOh|#`De?H%*`AsAr4N+a&7I9<{@m8Ro*1ZOg93$!76E9~Dpnbt@I)llV z(Y~hm0&Qqmgw%oqzBV|?#YplNkxeR3OQw-}N&7cG(8x9ZML*qC(f)&8*J zm&4#Cdtq!I@7Wrc&4RGb&gYlQ4zNeUiQao?=<%|usEHYCV@3Vq=#NyoCraNJrnz2$WETI{TKde&m85_N4vt^IwOKkuzL=I| zZN9Y3*&CM@M_7=fky@vFZ3Q~X`gCvGUps7Yd|XBM3xUx(C-LTckpJJ0psz*$z;MY1 z-$skgiBtbe4EtZ6;Eew_lHs5dxhKvp&R0&no+M6boCpJ@I097^Dc2x@`0#{Z0JPTD zNvD~SW5azCM@YFQ)P&=92egNzsMf0QivqeG&NOi9~ALlD8OX+d71?hoBwn z6)&9u1)C8AyrtrchOyA4(ahZMAg>C{9zkyT`n?o&*#K0b|x2E%3k`|1+yE)uE zDFrnG>~T8&XPMf^*!e`->xy*DvbH0|Sp~}dYa+Vlmr~OLuMg!TXc7P={*EMq`5)v_ z8yW1Eh>i}6?~`{G9hh|cy0q~eHCeF1RQu|^K=%-4;*+hlf+zo^6(|Gqy$HZFaWU5hW>22UpG0})wTZ!+i|a#_Zo07Vz$2v z728PqDLjq7VokqK8kqN3_KXj%|GjUa=`xF^4)!sCf3>0q)abFvgu=Qca}>Qgq(j*6 zIMjrRPbOkcN9W3w>|sSyX0G7Y?Jgw=F-Bd@sFnt9AT1|hg_ODi6nYsu-t zenT0WOk&?X5L5zLV=JU<+pLZ}DOxi_gTSd+HHp7uizujqPiy_NblA(Ds%lfSFDAwn z&tO>P$zT_2N2bNuC-Ny${R264kiN$x%0;RXe{hj?LF-YZ8X&yH{lHq-+CIr>`E89N zZ95hyly$^bzS}!iF~fD&)?9e26O%yCr>oY(B|Xu4WIJLGi!}uBrjgK;Afq+P2C8N7 zy~&1XWN}`Df+;X&6fn7ygz8a)#TO>4pR|Rsldv@>2Dcq>kN3U@s2x?4Bi)YFQ#<=8r^B7Hxm)`aLTJ%Ru+v zz|0RJU4Kj$BlwB1zZr2wN{;f#5U&<2FN(#BQ*5lFBrGZrX~Qd7hfX;8x^(?$H=KuW zZLQfFjV~RDQo{0%_j&36^5OhOWb#P->inJ21!T-g>T3O6N$JG-n|P#1An-UJ5ZTgN z$`~IdT?C`e4VpkaAXYdQ4pxIsu6=78<_cGq!X0=F2w(BGXaZlK*6xV2tHQ;t%I~2# z)0ny6nXJ}9$1vE2T#Ih*67v9$V^#a2>e9}HXb`Z~v91NC;P8bHByNYb+qeZEU=~z| zEg<>Sm*?oEf79E`pzx8uk?!epnF0kf`C1Z@`*j*VLMF*tW)0i1RBPUSwYVnnf8f9W zOLE5OSeuzc31g@TW7O5c^~ROICH02{?FG%T$g~X9ZL_hl^k$eM#h2sSW_7W)+ERPf zWIw{yDSP=U#bD$dZ1+ZT?0Q;ez9w<;2KagTqn+>^I4Y>=x?SiQW*u!U=hTIzMX(Bw-m8WTHfg&&>vpX z=D=OGFU=MHD!kai5V6ZQv;Da{ElFMy*TZa;_saf~x25iE=juzH-vmY3nJ8Cb#^n7by^xlK;bq?P{e+7}t!^|Fy z_u%%;!@X}}I{sCV_J-fycreM4uRUfJ57o|mhHE{(*FAYEhDqxes6I?v(5Vl!*_~`qA-*DL#Rq?z|ZHtXCm15#Aj{w z-e_av!r%FPtD3Ez#gnNdJ4oeQk4U;O$CC!4pYQVdskj_y4Sxx;rH)~B-}U(Z>wRkf zfo;io5NhQ?77aV6aDAfpo%;tSwfKI`W!j=T;4ui#h@gD{48xZ{X zEtt8HaiX`3G7n=WcxTgY0#{d@IRF3v literal 0 HcmV?d00001 diff --git a/example/thumbs.vtt b/example/thumbs.vtt new file mode 100644 index 0000000..5e13a7d --- /dev/null +++ b/example/thumbs.vtt @@ -0,0 +1,13 @@ +WEBVTT + +00:00.000 --> 00:05.000 +thumbs.jpg#xywh=0,0,160,70 + +00:05.000 --> 00:10.000 +thumbs.jpg#xywh=160,0,160,70 + +00:10.000 --> 00:15.000 +thumbs.jpg#xywh=0,70,160,70 + +00:15.000 --> 00:20.000 +thumbs.jpg#xywh=160,70,160,70 \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..bd1421c --- /dev/null +++ b/index.html @@ -0,0 +1,30 @@ + + + + + videojs-vtt-thumbnails Demo + + + + + +

+ + + + + + diff --git a/jsdoc.json b/jsdoc.json new file mode 100644 index 0000000..df0756c --- /dev/null +++ b/jsdoc.json @@ -0,0 +1,3 @@ +{ + "plugins": ["plugins/markdown"] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..7854dac --- /dev/null +++ b/package.json @@ -0,0 +1,107 @@ +{ + "name": "videojs-vtt-thumbnails", + "version": "0.0.0", + "description": "Display thumnails on progress bar hover, driven by external VTT files.", + "main": "dist/videojs-vtt-thumbnails.cjs.js", + "module": "dist/videojs-vtt-thumbnails.es.js", + "generator-videojs-plugin": { + "version": "5.0.3" + }, + "scripts": { + "prebuild": "npm run clean", + "build": "npm-run-all -p build:*", + "build:css": "npm-run-all build:css:sass build:css:bannerize", + "build:css:bannerize": "bannerize dist/videojs-vtt-thumbnails.css --banner=scripts/banner.ejs", + "build:css:sass": "node-sass src/plugin.scss dist/videojs-vtt-thumbnails.css --output-style=compressed --linefeed=lf", + "build:js": "npm-run-all build:js:rollup-modules build:js:rollup-umd build:js:bannerize build:js:uglify", + "build:js:bannerize": "bannerize dist/videojs-vtt-thumbnails.js --banner=scripts/banner.ejs", + "build:js:rollup-modules": "rollup -c scripts/modules.rollup.config.js", + "build:js:rollup-umd": "rollup -c scripts/umd.rollup.config.js", + "build:js:uglify": "uglifyjs dist/videojs-vtt-thumbnails.js --comments --mangle --compress -o dist/videojs-vtt-thumbnails.min.js", + "build:test": "rollup -c scripts/test.rollup.config.js", + "clean": "rimraf dist test/dist", + "postclean": "mkdirp dist test/dist", + "docs": "npm-run-all docs:*", + "docs:api": "jsdoc src -r -c jsdoc.json -d docs/api", + "docs:toc": "doctoc README.md", + "lint": "vjsstandard", + "start": "npm-run-all -p start:server watch", + "start:server": "static -a 0.0.0.0 -p 9999 -H '{\"Cache-Control\": \"no-cache, must-revalidate\"}' .", + "pretest": "npm-run-all lint build", + "test": "karma start test/karma.conf.js", + "preversion": "npm test", + "version": "node scripts/version.js", + "watch": "npm-run-all -p watch:*", + "watch:css": "npm-run-all build:css:sass watch:css:sass", + "watch:css:sass": "node-sass src/plugin.scss dist/videojs-vtt-thumbnails.css --output-style=compressed --linefeed=lf --watch src/**/*.scss", + "watch:js-modules": "rollup -c scripts/modules.rollup.config.js -w", + "watch:js-umd": "rollup -c scripts/umd.rollup.config.js -w", + "watch:test": "rollup -c scripts/test.rollup.config.js -w", + "prepublish": "npm run build", + "prepush": "npm run lint", + "precommit": "npm run docs:toc && git add README.md" + }, + "keywords": [ + "videojs", + "videojs-plugin" + ], + "author": "Chris Boustead ", + "license": "MIT", + "vjsstandard": { + "ignore": [ + "dist", + "docs", + "test/dist", + "test/karma.conf.js" + ] + }, + "files": [ + "CONTRIBUTING.md", + "dist/", + "docs/", + "index.html", + "scripts/", + "src/", + "test/" + ], + "dependencies": { + "global": "^4.3.2", + "request": "^2.83.0", + "video.js": "^5.19.2" + }, + "devDependencies": { + "babel-plugin-external-helpers": "^6.22.0", + "babel-plugin-transform-object-assign": "^6.8.0", + "babel-preset-es2015": "^6.14.0", + "bannerize": "^1.0.2", + "conventional-changelog-cli": "^1.3.1", + "conventional-changelog-videojs": "^3.0.0", + "doctoc": "^1.3.0", + "husky": "^0.13.3", + "jsdoc": "^3.4.3", + "karma": "^1.7.0", + "karma-chrome-launcher": "^2.1.1", + "karma-detect-browsers": "^2.2.5", + "karma-firefox-launcher": "^1.0.1", + "karma-ie-launcher": "^1.0.0", + "karma-qunit": "^1.2.1", + "karma-safari-launcher": "^1.0.0", + "mkdirp": "^0.5.1", + "node-sass": "4.5.3", + "node-static": "^0.7.9", + "npm-run-all": "^4.0.2", + "qunitjs": "^2.3.2", + "rimraf": "^2.6.1", + "rollup": "^0.50.0", + "rollup-plugin-babel": "^2.7.1", + "rollup-plugin-commonjs": "^8.0.2", + "rollup-plugin-json": "^2.1.1", + "rollup-plugin-multi-entry": "^2.0.1", + "rollup-plugin-node-resolve": "^3.0.0", + "rollup-watch": "^3.2.2", + "semver": "^5.3.0", + "sinon": "^2.2.0", + "uglify-js": "^3.0.7", + "videojs-standard": "^6.0.0" + } +} diff --git a/scripts/banner.ejs b/scripts/banner.ejs new file mode 100644 index 0000000..4966491 --- /dev/null +++ b/scripts/banner.ejs @@ -0,0 +1,6 @@ +/** + * <%- pkg.name %> + * @version <%- pkg.version %> + * @copyright <%- date.getFullYear() %> <%- pkg.author %> + * @license <%- pkg.license %> + */ diff --git a/scripts/modules.rollup.config.js b/scripts/modules.rollup.config.js new file mode 100644 index 0000000..15895c4 --- /dev/null +++ b/scripts/modules.rollup.config.js @@ -0,0 +1,47 @@ +/** + * Rollup configuration for packaging the plugin in a module that is consumable + * by either CommonJS (e.g. Node or Browserify) or ECMAScript (e.g. Rollup). + * + * These modules DO NOT include their dependencies as we expect those to be + * handled by the module system. + */ +import babel from 'rollup-plugin-babel'; +import json from 'rollup-plugin-json'; + +export default { + name: 'videojsVttThumbnails', + input: 'src/plugin.js', + output: [{ + file: 'dist/videojs-vtt-thumbnails.cjs.js', + format: 'cjs' + }, { + file: 'dist/videojs-vtt-thumbnails.es.js', + format: 'es' + }], + external: [ + 'global', + 'global/document', + 'global/window', + 'video.js' + ], + globals: { + 'video.js': 'videojs' + }, + plugins: [ + json(), + babel({ + babelrc: false, + exclude: 'node_modules/**', + presets: [ + ['es2015', { + loose: true, + modules: false + }] + ], + plugins: [ + 'external-helpers', + 'transform-object-assign' + ] + }) + ] +}; diff --git a/scripts/test.rollup.config.js b/scripts/test.rollup.config.js new file mode 100644 index 0000000..ed63b27 --- /dev/null +++ b/scripts/test.rollup.config.js @@ -0,0 +1,59 @@ +/** + * Rollup configuration for packaging the plugin in a test bundle. + * + * This includes all dependencies for both the plugin and its tests. + */ +import babel from 'rollup-plugin-babel'; +import commonjs from 'rollup-plugin-commonjs'; +import json from 'rollup-plugin-json'; +import multiEntry from 'rollup-plugin-multi-entry'; +import resolve from 'rollup-plugin-node-resolve'; + +export default { + name: 'videojsVttThumbnailsTests', + input: 'test/**/*.test.js', + output: { + file: 'test/dist/bundle.js', + format: 'iife' + }, + external: [ + 'qunit', + 'qunitjs', + 'sinon', + 'video.js' + ], + globals: { + 'qunit': 'QUnit', + 'qunitjs': 'QUnit', + 'sinon': 'sinon', + 'video.js': 'videojs' + }, + plugins: [ + multiEntry({ + exports: false + }), + resolve({ + browser: true, + main: true, + jsnext: true + }), + json(), + commonjs({ + sourceMap: false + }), + babel({ + babelrc: false, + exclude: 'node_modules/**', + presets: [ + ['es2015', { + loose: true, + modules: false + }] + ], + plugins: [ + 'external-helpers', + 'transform-object-assign' + ] + }) + ] +}; diff --git a/scripts/umd.rollup.config.js b/scripts/umd.rollup.config.js new file mode 100644 index 0000000..58f9648 --- /dev/null +++ b/scripts/umd.rollup.config.js @@ -0,0 +1,50 @@ +/** + * Rollup configuration for packaging the plugin in a module that is consumable + * as the `src` of a `script` tag or via AMD or similar client-side loading. + * + * This module DOES include its dependencies. + */ +import babel from 'rollup-plugin-babel'; +import commonjs from 'rollup-plugin-commonjs'; +import json from 'rollup-plugin-json'; +import resolve from 'rollup-plugin-node-resolve'; + +export default { + name: 'videojsVttThumbnails', + input: 'src/plugin.js', + output: { + file: 'dist/videojs-vtt-thumbnails.js', + format: 'umd' + }, + external: [ + 'video.js' + ], + globals: { + 'video.js': 'videojs' + }, + plugins: [ + resolve({ + browser: true, + main: true, + jsnext: true + }), + json(), + commonjs({ + sourceMap: false + }), + babel({ + babelrc: false, + exclude: 'node_modules/**', + presets: [ + ['es2015', { + loose: true, + modules: false + }] + ], + plugins: [ + 'external-helpers', + 'transform-object-assign' + ] + }) + ] +}; diff --git a/scripts/version.js b/scripts/version.js new file mode 100644 index 0000000..d2e93ec --- /dev/null +++ b/scripts/version.js @@ -0,0 +1,10 @@ +const execSync = require('child_process').execSync; +const path = require('path'); +const semver = require('semver'); +const pkg = require('../package.json'); + +if (!semver.prerelease(pkg.version)) { + process.chdir(path.resolve(__dirname, '..')); + execSync('conventional-changelog -p videojs -i CHANGELOG.md -s'); + execSync('git add CHANGELOG.md'); +} diff --git a/src/plugin.js b/src/plugin.js new file mode 100644 index 0000000..39085f6 --- /dev/null +++ b/src/plugin.js @@ -0,0 +1,346 @@ +import videojs from 'video.js' +import { version as VERSION } from '../package.json' +// import request from 'request'; + +// Default options for the plugin. +const defaults = {} + +// Cross-compatibility for Video.js 5 and 6. +const registerPlugin = videojs.registerPlugin || videojs.plugin +// const dom = videojs.dom || videojs; + +/** + * Function to invoke when the player is ready. + * + * This is a great place for your plugin to initialize itself. When this + * function is called, the player will have its DOM and child components + * in place. + * + * @function onPlayerReady + * @param {Player} player + * A Video.js player object. + * + * @param {Object} [options={}] + * A plain object containing options for the plugin. + */ +const onPlayerReady = (player, options) => { + player.addClass('vjs-vtt-thumbnails') + new vttThumbnailsPlugin(player, options) +} + +/** + * A video.js plugin. + * + * In the plugin function, the value of `this` is a video.js `Player` + * instance. You cannot rely on the player being in a "ready" state here, + * depending on how the plugin is invoked. This may or may not be important + * to you; if not, remove the wait for "ready"! + * + * @function vttThumbnails + * @param {Object} [options={}] + * An object of options left to the plugin author to define. + */ +const vttThumbnails = function (options) { + this.ready(() => { + onPlayerReady(this, videojs.mergeOptions(defaults, options)) + }) +} + +/** + * VTT Thumbnails class. + * + * This class performs all functions related to displaying the vtt + * thumbnails. + */ +class vttThumbnailsPlugin { + + /** + * Plugin class constructor, called by videojs on + * ready event. + * + * @function constructor + * @param {Player} player + * A Video.js player object. + * + * @param {Object} [options={}] + * A plain object containing options for the plugin. + */ + constructor (player, options) { + this.player = player + this.options = options + this.initializeThumbnails() + } + + /** + * Bootstrap the plugin. + */ + initializeThumbnails () { + if (!this.options.src) { + return + } + const baseUrl = this.getBaseUrl() + const url = this.getFullyQualifiedUrl(this.options.src, baseUrl) + this.getVttFile(url) + .then((data) => { + this.vttData = this.processVtt(data) + this.setupThumbnailElement() + }) + } + + /** + * Builds a base URL should we require one. + * + * @returns {string} + */ + getBaseUrl () { + return [ + window.location.protocol, + '//', + window.location.hostname, + (window.location.port ? ':' + window.location.port : ''), + window.location.pathname + ].join('').split(/([^\/]*)$/gi).shift() + } + + /** + * Grabs the contents of the VTT file. + * + * @param url + * @returns {Promise} + */ + getVttFile (url) { + return new Promise((resolve, reject) => { + const req = new XMLHttpRequest() + req.data = { + resolve: resolve + } + req.addEventListener('load', this.vttFileLoaded) + req.open('GET', url) + req.send() + }) + } + + /** + * Callback for loaded VTT file. + */ + vttFileLoaded () { + this.data.resolve(this.responseText) + } + + setupThumbnailElement (data) { + const mouseDisplay = this.player.$('.vjs-mouse-display') + this.progressBar = this.player.$('.vjs-progress-control') + const thumbHolder = document.createElement('div') + thumbHolder.setAttribute('class', 'vjs-vtt-thumbnail-display') + this.progressBar.appendChild(thumbHolder) + this.thumbnailHolder = thumbHolder + mouseDisplay.classList.add('vjs-hidden') + this.progressBar.addEventListener('mouseenter', () => { return this.onBarMouseenter() }) + this.progressBar.addEventListener('mouseleave', () => { return this.onBarMouseleave() }) + } + + onBarMouseenter () { + this.mouseMoveCallback = (e) => { this.onBarMousemove(e) } + this.progressBar.addEventListener('mousemove', this.mouseMoveCallback) + this.showThumbnailHolder() + } + + onBarMouseleave () { + this.progressBar.removeEventListener('mousemove', this.mouseMoveCallback) + this.hideThumbnailHolder() + } + + onBarMousemove (event) { + this.updateThumbnailStyle( + event.clientX - this.progressBar.offsetLeft, + this.progressBar.offsetWidth + ) + } + + getStyleForTime (time) { + for (let i = 0; i < this.vttData.length; ++i) { + let item = this.vttData[i] + if (time >= item.start && time < item.end) { + return item.css + } + } + } + + showThumbnailHolder () { + this.thumbnailHolder.style.opacity = '1' + } + + hideThumbnailHolder () { + this.thumbnailHolder.style.opacity = '0' + } + + updateThumbnailStyle (x, width) { + const duration = this.player.duration() + const time = ((1 - ((width - x) / width))) * duration + const currentStyle = this.getStyleForTime(time) + if (!currentStyle) { + return this.hideThumbnailHolder() + } + const xPos = ((1 - ((width - x) / width))) * width + this.thumbnailHolder.style.transform = 'translateX(' + xPos + 'px)' + this.thumbnailHolder.style.marginLeft = '-' + (parseInt(currentStyle.width) / 2) + 'px' + + if (this.lastStyle && this.lastStyle === currentStyle) { + return + } + this.lastStyle = currentStyle + + for (let style in currentStyle) { + if (currentStyle.hasOwnProperty(style)) { + this.thumbnailHolder.style[style] = currentStyle[style] + } + } + } + + processVtt (data) { + const processedVtts = [] + const vttDefinitions = data.split(/[\r\n][\r\n]/i) + vttDefinitions.forEach((vttDef) => { + if (vttDef.match(/([0-9]{2}:)?([0-9]{2}:)?[0-9]{2}(.[0-9]{3})?( ?--> ?)([0-9]{2}:)?([0-9]{2}:)?[0-9]{2}(.[0-9]{3})?[\r\n]{1}.*/gi)) { + let vttDefSplit = vttDef.split(/[\r\n]/i) + let vttTiming = vttDefSplit[0] + let vttTimingSplit = vttTiming.split(/ ?--> ?/i) + let vttTimeStart = vttTimingSplit[0] + let vttTimeEnd = vttTimingSplit[1] + let vttImageDef = vttDefSplit[1] + let vttCssDef = this.getVttCss(vttImageDef) + + processedVtts.push({ + start: this.getSecondsFromTimestamp(vttTimeStart), + end: this.getSecondsFromTimestamp(vttTimeEnd), + css: vttCssDef + }) + + } + }) + return processedVtts + } + + getFullyQualifiedUrl (path, base) { + if (!path.match(/\/\//i)) { + return [ + this.trim(base, '/'), + this.trim(path, '/') + ].join('/') + } + return path + } + + getPropsFromDef (def) { + const imageDefSplit = def.split(/#xywh=/i) + const imageUrl = imageDefSplit[0] + const imageCoords = imageDefSplit[1] + const splitCoords = imageCoords.match(/[0-9]+/gi) + return { + x: splitCoords[0], + y: splitCoords[1], + w: splitCoords[2], + h: splitCoords[3], + image: imageUrl + } + } + + getVttCss (vttImageDef) { + + const cssObj = {} + + // If there isn't a protocol, use the VTT source URL. + const baseSplit = this.options.src.split(/([^\/]*)$/gi) + vttImageDef = this.getFullyQualifiedUrl(vttImageDef, baseSplit[0]) + + if (!vttImageDef.match(/#xywh=/i)) { + cssObj.background = 'url("' + vttImageDef + '")' + return cssObj + } + + const imageProps = this.getPropsFromDef(vttImageDef) + cssObj.background = 'url("' + imageProps.image + '") no-repeat -' + imageProps.x + 'px -' + imageProps.y + 'px' + cssObj.width = imageProps.w + 'px' + cssObj.height = imageProps.h + 'px' + + return cssObj + } + + doconstructTimestamp (timestamp) { + const splitStampMilliseconds = timestamp.split('.') + const timeParts = splitStampMilliseconds[0] + const timePartsSplit = timeParts.split(':') + return { + milliseconds: parseInt(splitStampMilliseconds[1]) || 0, + seconds: parseInt(timePartsSplit.pop()) || 0, + minutes: parseInt(timePartsSplit.pop()) || 0, + hours: parseInt(timePartsSplit.pop()) || 0, + } + + } + + getSecondsFromTimestamp (timestamp) { + const timestampParts = this.doconstructTimestamp(timestamp) + return parseInt((timestampParts.hours * (60 * 60)) + + (timestampParts.minutes * 60) + + timestampParts.seconds + + (timestampParts.milliseconds * 1000)) + } + + trim (str, charlist) { + let whitespace = [ + ' ', + '\n', + '\r', + '\t', + '\f', + '\x0b', + '\xa0', + '\u2000', + '\u2001', + '\u2002', + '\u2003', + '\u2004', + '\u2005', + '\u2006', + '\u2007', + '\u2008', + '\u2009', + '\u200a', + '\u200b', + '\u2028', + '\u2029', + '\u3000' + ].join('') + let l = 0 + let i = 0 + str += '' + if (charlist) { + whitespace = (charlist + '').replace(/([[\]().?/*{}+$^:])/g, '$1') + } + l = str.length + for (i = 0; i < l; i++) { + if (whitespace.indexOf(str.charAt(i)) === -1) { + str = str.substring(i) + break + } + } + l = str.length + for (i = l - 1; i >= 0; i--) { + if (whitespace.indexOf(str.charAt(i)) === -1) { + str = str.substring(0, i + 1) + break + } + } + return whitespace.indexOf(str.charAt(0)) === -1 ? str : '' + } + +} + +// Register the plugin with video.js. +registerPlugin('vttThumbnails', vttThumbnails) + +// Include the version number. +vttThumbnails.VERSION = VERSION + +export default vttThumbnails diff --git a/src/plugin.scss b/src/plugin.scss new file mode 100644 index 0000000..2e49816 --- /dev/null +++ b/src/plugin.scss @@ -0,0 +1,18 @@ +// Sass for videojs-vtt-thumbnails + +.video-js { + + // This class is added to the video.js element by the plugin by default. + &.vjs-vtt-thumbnails { + display: block; + } + + .vjs-vtt-thumbnail-display { + position: absolute; + transition: transform .1s, opacity .2s; + bottom: 85%; + pointer-events: none; + box-shadow: 0 0 7px rgba(0,0,0,.6); + } + +} diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..86b9a01 --- /dev/null +++ b/test/index.html @@ -0,0 +1,19 @@ + + + + + videojs-vtt-thumbnails Unit Tests + + + + + +
+
+ + + + + + + diff --git a/test/karma.conf.js b/test/karma.conf.js new file mode 100644 index 0000000..3e3c0d2 --- /dev/null +++ b/test/karma.conf.js @@ -0,0 +1,43 @@ +module.exports = function(config) { + var detectBrowsers = { + enabled: false, + usePhantomJS: false + }; + + // On Travis CI, we can only run in Firefox and Chrome; so, enforce that. + if (process.env.TRAVIS) { + config.browsers = ['Firefox', 'travisChrome']; + } + + // If no browsers are specified, we enable `karma-detect-browsers` + // this will detect all browsers that are available for testing + if (!config.browsers.length) { + detectBrowsers.enabled = true; + } + + config.set({ + basePath: '..', + frameworks: ['qunit', 'detectBrowsers'], + files: [ + 'node_modules/video.js/dist/video-js.css', + 'dist/videojs-vtt-thumbnails.css', + + 'node_modules/sinon/pkg/sinon.js', + 'node_modules/video.js/dist/video.js', + 'test/dist/bundle.js' + ], + customLaunchers: { + travisChrome: { + base: 'Chrome', + flags: ['--no-sandbox'] + } + }, + detectBrowsers: detectBrowsers, + reporters: ['dots'], + port: 9876, + colors: true, + autoWatch: false, + singleRun: true, + concurrency: Infinity + }); +}; diff --git a/test/plugin.test.js b/test/plugin.test.js new file mode 100644 index 0000000..32a4790 --- /dev/null +++ b/test/plugin.test.js @@ -0,0 +1,58 @@ +import document from 'global/document'; + +import QUnit from 'qunit'; +import sinon from 'sinon'; +import videojs from 'video.js'; + +import plugin from '../src/plugin'; + +const Player = videojs.getComponent('Player'); + +QUnit.test('the environment is sane', function(assert) { + assert.strictEqual(typeof Array.isArray, 'function', 'es5 exists'); + assert.strictEqual(typeof sinon, 'object', 'sinon exists'); + assert.strictEqual(typeof videojs, 'function', 'videojs exists'); + assert.strictEqual(typeof plugin, 'function', 'plugin is a function'); +}); + +QUnit.module('videojs-vtt-thumbnails', { + + beforeEach() { + + // Mock the environment's timers because certain things - particularly + // player readiness - are asynchronous in video.js 5. This MUST come + // before any player is created; otherwise, timers could get created + // with the actual timer methods! + this.clock = sinon.useFakeTimers(); + + this.fixture = document.getElementById('qunit-fixture'); + this.video = document.createElement('video'); + this.fixture.appendChild(this.video); + this.player = videojs(this.video); + }, + + afterEach() { + this.player.dispose(); + this.clock.restore(); + } +}); + +QUnit.test('registers itself with video.js', function(assert) { + assert.expect(2); + + assert.strictEqual( + typeof Player.prototype.vttThumbnails, + 'function', + 'videojs-vtt-thumbnails plugin was registered' + ); + + this.player.vttThumbnails(); + + // Tick the clock forward enough to trigger the player to be "ready". + this.clock.tick(2); + + assert.ok( + this.player.hasClass('vjs-vtt-thumbnails'), + 'the plugin adds a class to the player' + ); +});