From 0ef017490e32cb70d62561792285c231692cc710 Mon Sep 17 00:00:00 2001 From: Tayyeba Shoaib Date: Wed, 19 Aug 2026 14:05:09 +0100 Subject: [PATCH] Feat(add puffinfolio) Adds a plugin for Puffin Folio (https://www.puffinfolio.com), an English fan-translation site for Chinese and Japanese web novels. Submitted by the site owner. Co-Authored-By: Claude Opus 5 (1M context) --- plugins/english/puffinFolio.ts | 143 ++++++++++++++++++++++ public/static/src/en/puffinfolio/icon.png | Bin 0 -> 15311 bytes 2 files changed, 143 insertions(+) create mode 100644 plugins/english/puffinFolio.ts create mode 100644 public/static/src/en/puffinfolio/icon.png diff --git a/plugins/english/puffinFolio.ts b/plugins/english/puffinFolio.ts new file mode 100644 index 000000000..21b0c0eda --- /dev/null +++ b/plugins/english/puffinFolio.ts @@ -0,0 +1,143 @@ +import { fetchApi } from '@libs/fetch'; +import { Plugin } from '@/types/plugin'; +import { Filters } from '@libs/filterInputs'; +import { load as loadCheerio } from 'cheerio'; +import { defaultCover } from '@libs/defaultCover'; +import { NovelStatus } from '@libs/novelStatus'; + +class PuffinFolioPlugin implements Plugin.PluginBase { + id = 'puffinfolio'; + name = 'Puffin Folio'; + icon = 'src/en/puffinfolio/icon.png'; + site = 'https://www.puffinfolio.com/'; + version = '1.0.0'; + filters: Filters | undefined = undefined; + imageRequestInit?: Plugin.ImageRequestInit | undefined = undefined; + + private async loadPage(path: string) { + const response = await fetchApi(this.site + path.replace(/^\//, '')); + return loadCheerio(await response.text()); + } + + // Covers are served through next/image, so the real file sits in a url= param. + private resolveCover(raw?: string): string { + if (!raw) return defaultCover; + const candidate = raw.trim().split(/\s+/)[0]; + const embedded = candidate.match(/url=([^&]+)/); + const file = embedded ? decodeURIComponent(embedded[1]) : candidate; + if (/^https?:\/\//.test(file)) return file; + return ( + this.site.replace(/\/$/, '') + (file.startsWith('/') ? file : '/' + file) + ); + } + + private async novelList(): Promise { + const $ = await this.loadPage('browse'); + const novels: Plugin.NovelItem[] = []; + + $('a[href^="/novel/"]').each((_, element) => { + const link = $(element); + const href = link.attr('href'); + if (!href || href.includes('/chapter/')) return; + + const image = link.find('img').first(); + const name = + link.find('h3').first().text().trim() || image.attr('alt')?.trim(); + if (!name) return; + + const path = href.replace(/^\//, ''); + if (novels.some(novel => novel.path === path)) return; + + novels.push({ + name, + path, + cover: this.resolveCover(image.attr('src') || image.attr('srcset')), + }); + }); + + return novels; + } + + async popularNovels(pageNo: number): Promise { + // The catalogue is small enough to sit on a single browse page. + if (pageNo > 1) return []; + return this.novelList(); + } + + async parseNovel(novelPath: string): Promise { + const $ = await this.loadPage(novelPath); + + const novel: Plugin.SourceNovel = { + path: novelPath, + name: $('h1').first().text().trim() || 'Untitled', + }; + + const cover = $('img[src*="covers"], img[srcset*="covers"]').first(); + novel.cover = this.resolveCover(cover.attr('src') || cover.attr('srcset')); + + novel.summary = $('meta[name="description"]').attr('content')?.trim(); + + novel.genres = $('a[href^="/browse?tag="]') + .map((_, element) => $(element).text().trim()) + .get() + .filter(Boolean) + .join(','); + + const statusText = $('span') + .map((_, element) => $(element).text().trim()) + .get() + .find(text => text === 'Ongoing' || text === 'Completed'); + novel.status = + statusText === 'Completed' + ? NovelStatus.Completed + : statusText === 'Ongoing' + ? NovelStatus.Ongoing + : NovelStatus.Unknown; + + const chapters: Plugin.ChapterItem[] = []; + $('li a[href*="/chapter/"]').each((_, element) => { + const link = $(element); + const href = link.attr('href'); + if (!href) return; + + const path = href.replace(/^\//, ''); + if (chapters.some(chapter => chapter.path === path)) return; + + const time = link.find('time'); + // Strip the leading chapter-number badge so the name reads "Chapter 7". + const label = link.find('span').first().clone(); + label.find('span').remove(); + + chapters.push({ + name: label.text().trim() || href.split('/').pop() || '', + path, + releaseTime: time.attr('datetime') || time.attr('title') || null, + chapterNumber: Number(href.match(/\/chapter\/(\d+)/)?.[1]) || undefined, + }); + }); + + novel.chapters = chapters.sort( + (a, b) => (a.chapterNumber ?? 0) - (b.chapterNumber ?? 0), + ); + return novel; + } + + async parseChapter(chapterPath: string): Promise { + const $ = await this.loadPage(chapterPath); + return $('.reader-prose').html() || ''; + } + + async searchNovels( + searchTerm: string, + pageNo: number, + ): Promise { + if (pageNo > 1) return []; + const term = searchTerm.toLowerCase(); + const novels = await this.novelList(); + return novels.filter(novel => novel.name.toLowerCase().includes(term)); + } + + resolveUrl = (path: string) => this.site + path.replace(/^\//, ''); +} + +export default new PuffinFolioPlugin(); diff --git a/public/static/src/en/puffinfolio/icon.png b/public/static/src/en/puffinfolio/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a71199a3176cef789922a56f206c6db1f8bbcfa5 GIT binary patch literal 15311 zcmZ|0b967=vM&6KZQEM0?PSHajTPIrZEMB0Z6_i;t!f&ZSZ-*#;O3Q#ja89@M`E*9p)0POEI zp^>Ml5ul(l_|2zKS{9A(L0sY7OFO~=V zU*7t8ApfoZL(Ay2bpI=$?8G%30RR}3e+CdBBMTD%0M7WOtnQ>PBh6)GYfWchY-?yj z=Vop94-3HK#`RaVHgPf_bhEayapZF2CH|KO*I)e~n4XyMUoK9Tyu|7<@`S>+4kmgr18%1meLU`Ef#$;nC2z(mi) zMElo+*3sR@$-s@)#*yUTLH;FOf$MSF5zvlJtbUgnA<5E{NakRB^{--QH zb|#*GW%&Pr|6Sj|2^#+!;eUevH{rkiRcwAa@%^71{}=o}SpQVZCI8FK#7aZtm$iwF z<3F{rF){Ga|F4$+m8t&!Wd0A!|IHM(wX$_kvNJF;;bZ(yvi|}7$D4=#pS$-z?&iOj z%D>RRYlRP*hyK6T9UpY8k~%m5z=0znBB<;JG~@-9rX%Kjw0Z6QzVu#f(^Rx%su5{o zHNm!muG@=g2cA$17J?wX7X%~(37RklI~)QV1PV-mNQjaWhlxYaI1Fwa7;S7NRo}d{ zmf)ec-M#7h{3^G(T!SIeY{ks1R`qgJKU;HK>mrwE&Z@*>rC!(D^IXA?a5zInyXgJ^sLdI3n&!8vH|$X#L&EMA~aT51_?fB|ph$XzgjRA!My2F=9dY^`~pW7h9Y958xzyT){L zMgxMXNBsSUCZz^ZDtL~D0!%<(Q3&dvin>~Vp;RIUkB^PZ6S8jh}~wWnC5kp?XJ4K%aYX<w?a>XNwo2*D(gP|+4@S&DFzu3(*qz3Vle_gDMXPxawePwXWrbI5yN~f z+pX77TVG2{%S01(d?1$V^6A~ee5U9`$B53s=x+*a7KAutdAS_yuMT=|XS*9?DJ_U7 z$UODpDJzSdx%SY4eEgPK6%KT+nSGjd8uW;&sxofAn;b3M)zh|^^OxZxdknS~_cQu; zPfh_AzPy&zWkbW7R){djM2)$|-0wRzz3-!*?(@y-;Bl{Oz3;_(yo4O10raNPsG(SI zab>*=va^f$>No;_X8Keq+s@Z^{_oM;puh}k&LDWCvgHC7UyKnUT+sd#s@jG$D-aCi z0~K6JhMQSo{`VzUpPSVVz82?mHhC@&C%fk>-UQNt;xG3yB~gL&!j$rZMxr$m3%)xUw_VT ze}l~4Lg_j$CfCwnkng);cF)E6#sxsO7I9OC6U(z>f5^aoV;-IIbtcRRC@J0Vl2Cl_ zC4Mt!Q->vdaH|nC)|;o9d50z|nP)m7G#hL?ndbbrKdzg-+(>2|nO#lnw+ z1{$nCW(>G!38hW6Ig+8GmXZ}qBckQ){D>ECY;GRjXS7qGNij7ut6!z*2XJ^6TYC}b zaJhU;Zhyqi_@2V}R)`yJ4$&L!sc6%sVIrlE^keL+2nip!O*d|McHboPyNz&rdcIpU zVM`AjH>ahqV^jM)^i-Z*sn)Z7BRreraN1Bam50S#3W-*??y<6OI{hI_wUQ=J>>d{g zWQc%+hv$1*naTCM;w&Wy9Frm4^YrPs&fNam665m<)ndl-HU;m2an!P#hvUG`o1dTR2ZmU7ypARK7^>!7)^@x^@OcTGu10h9zN!YbmFBtKIbe=0xp4B;5)Y$1 zI~uf${%)v|RiRyMp6cG(LhwEj2AqgudwjiiZNDwGgoK2kih_kUTc=J0*Z3a7tT)>< zTA)`zas#AaJXO#MeU6j}ypi|-QxhT(8vyJ*Ce_2qm|FNqEN zDAzvM1lAk123@IFTyF=(cE1ZbF;O}34f1!wwfePHk4kn*ZWc>v<^K1s~|Ft)WtfJMnO@ObVGjMYC0+`0)XryMOd ztT|F=nhcz!4%o*9cY_C-zBw^=@iNqM{J8MmmJiLrUYU*TU7f<RzgMO(g-$_-}Di(?Fh%0nUV4JAoq=`3Z7e4?(a>Ho1T`2N~-=^v542RJA4pF z_o<3wO>cHdTvPVWBd7|GfPZ~?F^a&8v1DPLP3yunu;@fjDJf$xgR(&$&Mw1eeNX&u zcGGFSw%EA6AF0sm#?Q?S!|p{i+-$quZ8QR3Xxfj0CY9W0L?Ma}s!iHMuWKb3NVo72 zxvckq%6sIBZA`jP!0qTFe5xsCt5Vsdd6!bJXMS_~jbN!OXp<4cs9%yJtI;h*1?9+1=nU(jxoB2ROTS z@I0ojH5#MtnE9{IcmDhInVk!r^U)iD*a~ zeUnqCVHVf|!C9KL(bpfkH~1?za~vf;fgLbQ-gOWx9`RfY!vr4&mfE}bWLveI5{RT* zRh*raZq0`V5lF~9f%gn?NElWQASMC9t%vS*rD&53TTjg`L;Fx!&kqlShILL{G6>VI zR;%?*zkh>0j2|);+rTj7*4#*KS=K7#TD+iz=>^2_24B(hs*49{MYl^4ysOO;YK-xG zJK331&Tk$aM5bv9$;hX+@WQvQ(1n5)jN2CnqL;>&NlePw|FApd6y~IKu0~1*oOpli z`}&tWr0vHAd5v^zcbz6g@5IWWr#1kws8yX2;1Iatj*B91EC4KW3qd2Xo`bD7%A{oK z8>Vw|E2l948Z1#_%7&|g#GNOCu($XdGEb{I*<-C*3Lvsy5`k1j0&zTo zK|sP4Fak1cIl0dVWI>t*g67yzXm?O?`kX)cTqyV6qwZOT6;TWLxDTVV;lbT!{9%4x zzrrTeitEN@?4Zx%COJN>c!a1`r=5;D%#0_QOBRsYkx*2oGd5YWN@A0gY;<`RNrD~y znn)n2xQgI&-Z$x#PTU&8G{EyFUg3*GI&80i_<4D3tyLB}UZvct&WQ zdR6lBjG;K#{Sx$FxCfps7;p1kU=poPb4e8o4e(H?rFCkcob`)jgT$}A1~4vtV%C5n z_xU5Yb*i&Ir228O^7hr~E zBYww$mz!~w^rOuXNy$y+3eOy=oyJn9p`=SRg6u$dqU+O$+;f~6!Pko*i|Q&yQ1O8JNL8ePI<2KSaP9~@FgNv6TF(GR&tQ|3WWW$ z``k|}G`yt>(W1aG9(aF;I^ks1Zs2S+_8H3(N%PMC<-^kb$! zDOxW*{HMDmSX1s%^D9c&Jh-%{L4^gqn=PveDz#%>2^)!x|m@B*3H2AbD?k1I?zh`nWQT($67O zh1rSR_fLTZV+iQen@3#15zYKGPN2Uc8(5!LqZZM^wYQMR(w7?cIXS|l_KX8(35767 zclgxA4#O_Wfjm&>9G3dm(+ z$G*;c)UGJb2I&L3muP$37Jx}^G!8JOE5GShzs+c4!UYP!ZU z(w!PKL~?Z*N*kZU6y|SU6*tBbDXzPoN5o-QQ}E!ymZ~pZ4R#)7D^i6VZ+xi>3l22^ zK7`P_jIK!j@_l=`Q-qv_utmfn`=-tlXhm3mbmjI5fLOMXP18CzL5`u#`}mDe#(8y6 zp3Hukfc4gd_ufyZ>LCs;8w^9Av(m0g2PT^|+4dY!R8-@6_$*IlJ7_7XFQOtPQ8q-Z zk`}M${S?l?*%?eTgHSD)g)tkU8JgSzqw;iV}YeKX|g1S%CRVR8R zL=h`I4w~~t0Klkz1gIS<`)tF%i>wBsN}Cn1gTWjeZt9yw)@q-kOvq+%!W==6L|Sf6 z(ze_vr24a9qfiOd@Hc5N$S$&N<9?f+LnbMH9=GzfsLhHXx zL)~>;bMxYu_uNnOckiB2Z;t(Oe1Bdz`l&Q3)>82?4qs;!in}h-ztFLiy4RW8ZN8Di z>)CymTq7$hYv2OS(rZ$dA_1>O+mvlmWDxu9coTWqDLFe%SyfAMwJiQeu)OV1CDy}#o+rZD?GMVb|>brE9k6= zh^xXCHbZ1PtdnMKHGRW>^U%8oDKb$(B`Sm!DiXyH!60~_m)nTPrTt-{vC3S5bv8$ z{&D|Yp_uEXhr{#LrX8={STqyjFvDeQ_Isc8CX$RuweU=;L%IWN613I!YNJuf(#Gx@ zxd=r^IRQR+ZI{Ni)!gP9O*O2bbOKx-;oH(r{yBmnpiT|!z(0IiNaRl4M*y2@aJ^tN z9s;CIORXWyC77eKx9v{S*5HkY8lMbqPE4&1vei|x)Z*5L_um^Vite2S3mm81??*ly zX;s8bs6*zY5!E)cd327o-b4LU&CN@T6>87V&wpE-?=V6B0)D27IiWiAixy;wV=*x? z`20S&69)VHD5a3*h&2uPHgSV@l~c(L-wu|~5ex_u0mvMEY5B$TAyI=G}Jf9MrIs2l`<-CP1XfhwtnAI4Uzc)sF|oRed0@jtc^{}*&-tL zRXW%yt;FLJm~{1LFo}ycVC15|FTV7b&#u_J)GW^jNO)W%9fx7~Rp5CvXD>Z-(zlog zh#1OF7v)KRG>9669niC}$O1smgiM3hI7TvvXZzd^=u!5_6RO&Eh3^PDL1cP5W1%IK z*g?s$M$jUHpR4}xwK6Kg6}3v)#4TwP4XuN5%4gyb_#Sxvo-^WQ|JF5}Sgbbc6OvD* zLeT)GLDUQ!y))L}&_Z(((?*I{8x9C$PLF0Bfq(>uQ2Xg9g}pi&U!2*Vd{*{4mWOEzM+hKl0V5Bt8TdDd0`wU`ydOI=6m2gsFd6gaH2z7=i zT6+u}&@$Y44?9P51Rj4e>x_=M?NZl@rt<>?Voi}c|F&=tw!NQu!`-xNh$zbhsdlut zZev`vy&vIMrg>z%5<4sLRC9ULGNt&xUiSaK;6j=n)G`sc_CQbTffDk7QEdYrE#TH4 zue#5xsa>@fO9R#hNp7XBWj{W)O>=#mrr!B*Pc$zx)Qdu_1?@n)yK8&V%6L&lq(OD+ zhL*Q)kLypIz^RIhi_!5|ltGLoSWGEGzdpGy*}o0e7j2!&-IpnEa)yi;O1k7uh~~LT zg}>iwzC$ikP%0LgaL27%HlNDmyrHGaebPG87%a^twDRTc{uIkB|Lx}eR^+wc1BLG0 zOSx?b>rYVXjqdV%i4-yD-WBtCqikl@r+`8u4!&hiVpsxoI5!ka!lLr=nUrLMY(QTzW%o)QR?^AafXK)X@{Dd(ujN4 zq(%sJg?4AMR-Nd@HaUe_-bYK%=VguWCk|$K?JbP8*M$**_qo>gc!uXHK`ign%5wLU zvlUdxB@61VVQC(N3(t5x6DzCz46o}zbw03*$4WZS&{VWt6L4seVx`K7b|NQV8t21$ zv8#_XSV1Q>6E`T=M8*J_ZR*Ge<9KMO7b}InZc0mATSnXf^pvpPIscR0c{y5jf#8$~ zNFtSh%p9`e59*{lx-QX`kC1PVkz506AeVCDhUaiOuf0oK-}j*~r+g;Q=c}&s>Y3z; zX(aGWXk;zBb*?AveihI`fuV91cJ|BB$j+A4yO!TZn(*$m4~B{mk{u;yg%^QoJh9r zrp>NJ|5n)Qe{P>^y-X8NI&hJ$-I`0G$c7uU zshJlQh^R2Pv)`H5oJm`yk&yr87PZ=wo(Gi-w=czrbxRkN4Gsu#sh~Kh($}jLvKWsF z6}oVE$~Asg)rtUw)xhNzrVO^~=!Jv%!ptuzqdvrXYrxUkD^}oBE#3r}c2x z65%i&vUQrGwfnZOLfda$m&}jje1f|m1aADzF!3BTh|lbF-5jiXRZatgTc1emU$%jw zviV-437TKYq^!3dPERc8*@QKJHx5NVm7BBJvzaImRDOeiY0UEWJ-pN@URue9ViUH_ zRKokM&y5uVWe5o>UknPUixPni1vdYJPT1ngvu&H|+zouH^4)S-r2T|m@|?O{r`PHq zd8M1S#E66`@blgI+s{4L+H%N@g$clWAvu}b>&M^$28lqF$PLj(r6S?>7ll~oPOON=uwVlx1?6=AlTCMKvZmELM8=m_#*Btift25t z7#<#|h%ALj(_c^uN+mgR0Oq+PX@8EB^wbm}vP+<88PUA)@v^TpVB=|qv5>iOHVB4B61zwGD>o@Gi$PiX|<_kz%0{~ zw5uuP#JPIOgK=<+MV7`Q3GJ2)!9ul93+_4M$~gLvJ|-%0nTY&Fx$KjkV}t*@_>@E% z?k?7Sj1-wV&Br{2R5kVx0K!3FrREf23sB0KucNr@g`w^(^dt-)1P>>-xq<;j(rhvf z`BU(suVyuFd^{-Ouoy^WI?4Yi4oWa@6sJ0_DkC6z!T-EU(p-x>GiLH2#KA-0s9M^g zDgn#N;z?S{@63e!Rk9oXJkI9bP5<(dCQ=4(B@W+W&gGuK+GpTWiT_P+cy-{n=5Kkr z-On0F&o-i`*TJ%F;dGOsT0N8}e{E!jepH0D2jWExe&EowN|I=KyJSYID1F@|=ogoe z(cyX{Ydl&Z35IdZOH_Y3$`c25Tq|V5a9~0kxN@zv#L$xaLQ$&-L``6eP-BuSC$cFf z`w_I-wV=U=X@pU{^L%=iHuXU;C}Eb@7@X}@9)`Hjqmc^!&*tPzs^uu5-^z;>=*TQA z&R)R~S@1karj86)aR7mxd_}s%xQlP8KhAixxKdq@J&T6c_)KEanrY@MHlm886Y{ut zc(F;lhuLK+_B4~kT_FxRc~H|wF@tt>UR*Wr2A1lAF-uFRWQ_$qlP;w@*|cT@=SkfY zUdE;5a9J=AM8+!AGhY0RuOGb;>Nle^(AytwU%R%LW)RNCygxkZlxfwRKy6c|wk|bb zZ-CQ-nFahHArrT}>il{HC|8!YVlppw76f|jvbFQY!<}2BLV969&&-MbxriF~DE*#TS3=ToGljKB zfYalU9!Vx4;;?9I;I0^6)0u&0r&_l$kz-sQ8Z>z|IRCXj=c^q0WY`w>r!gf)zg}|! z&#U!jV?frir`ql2>To$9hm=cZ=PDYXCC6MCvrvHzWL4|#B1Z$NP-}OQqXN0+fmM%&fWNZJ}x-8NK8#UUp8kRH|oYb8U3L-j#jho?Ukb@e0Cx0m*I8TB>c|U;F== zS--64%8Bkz2XU~^K*tgfjt|=p3|D7Jwi?bFpN-zj{Hdzu zRzwt&TlHKQr}fj8!@x%)Jc6Q`=a6 zcX21_NB!OAZoMU$KY%(|e0fxBU;Rbpy+m6QEH5ultXnj-{)NuIYMu2}Rh0UwsgcGL zqoa~wtuII6%%>$-0u*eRI1J>z)0!T@K=zx56FQMu$R??!u0vsoXXGv^?$2u9Z&i=Y z^SECvks3q08^)oD*{Hk&To0|xc1?Mr=PA<+z+6oiP9_I04=Yp#_m6XVotEDUwgWQZ zIjqkJG3x;Vj5&Ldu~Maq;;5D>G2}!1uMfUzqCTX0CZ8fnkdEkz+0$yr0oF?Cif6&O zw&n4iMjS`s_oh5R-6JjpqlUQ4L`WZt+DMXll)kLAupmxtox~rFe~FGQAETR zOV4Q=mfDJ!oWt!F2{LZsZ8qT>l;c`9tx_CG5n(SM_^Nyx7@7N1nURN6EVgV;6Ei)i zDCFk#^<Ie?5J_}y@XM*EVyrkZ39j5bBEH93>HVc2z90bQfn(=~>FR_Pau$nOF z{J@TB-S_sbXTq;uQ7yJ;@J8w;&L;`R))jy16lzqeR9&Np6*bHk;31uYjg6HFTCz@6 zne%w^`JGXi3lNW$a%;kiqaynC=TVz8@DQ`CZA1#eaZHwI?W*&pcHXfpc&q1wZL|5e z%6jWs12MiQc42bEr;PTDspVTy z=b1xA8j6~&YeIG{Mcr?VL`phD*H~U!XIsQ2?Mw9#9CRTXHOnq#Q&5_mJVU!Uax#w?~K zxCtvra0W!b7>na;PNjp*@tGJe%9Ii>RRq{LDjsBv`sJ*MQQOP$3Zu1QCH)NF>c>fXu>kO#` zjSeqd+fk}Ue@G&%v3NQGDg(=itqN(!l7K3+k(L}ZhlwUg9C?lLv3+A{amdudM4LAn zKbxphu27rC9@aNsRP{Wl&BQ~|gvbi)iL-q})W3u0?<)xvPPe}rv=M>o1(OP!>~L4C zGCZOvE-7wEq$n6l7ogOnN->MP>N_BNZ|>ra&5OGH`$ZH8*A9Ms%p~VJ_nx(Z^|zBv zy;W~W9*qd9#v2IpZ&DYf1SBS5W4(+?iU9wD7`)Wc{u`1@915+ah@3?}_B8LSwyh=g zi6(X#e10Ad4<^l=UuZfR)F|=?hw&ArVYhc*x&F@U3YTWriHMI$Oxgy}5*$&@y5BBa z3ns8)`J_)yT<5yfDh?lI$Bk1pEn*0cKZn?UtiVTLwHQujc=|?2v~hP5pf!C2eV(N1 zNGv=chw2Flh8kKTKa5wgb`*W zM?;{h2yB?L{~AAPR%R-_${f!gRq=->8NJn8VL!{AO9Rg|0!DViJhB^u3D&GqXSVxU z2-Tj0VeF5PO5P!sqf4E#?$M5-(-kbu#qU9`o)fL{yS2jd1UG172j`=IGXTDnXsr)Q z2p=+VA=Ps6t=XZC6KSl}9A$ZlLSOJ`madM&$htKqm#Z(+#C8)-+h7=nJH#nfadJ-u zzaMY?JTG4#Tp=PBW}1<;kE!v{l(!7mGP!-pZMU}AJi;0tpNj+I>b&Pom2vfTXO<=l zK8~o#?`l-@2PK7Z4L#CTw3t+gyB!z5if91H%=rkgz`ZLZ5qglmK>jLn+}Qa~e}f=L zHqU-tX~E{l=DJVs!Va~;#e?3TRlj-jf`?-Xc3>RmFU$v*3JWj_IFU)BYJLbAq78=l7?h99el#h#unof>%? z^}t!Gf9oEXYtCfAwWUko%o*`rqcW8%z-5Romg^@%?LoyC7@eRUn)|`x1msVyG#LJo z+34%WYevbRCY%+NKoD2J4Tvsdqc{4N7;f%Zes5L^~vrD6Dy(OUv+xLEsx+#Y0Kz>*`)=jGD# zCMM^=qb>6f_Dx^WxHKz zWDjQWRcs@S{-anM)o|CGBu%ZSV=@XpCQl;fXGQ8ceO?<-LBM#MF~!4c?ZDwj8NW(0 zv`WN6iGiv#$BmxlX;u}zuGsM!%mM|+6$Jg`ATlIV%e7oVJKiM^Eeg7DTp6t+hxT6E z#_Wh=x~WVY(&~;zW>m>XHiW>_&1h4kA8jSAJBep|%JPO0iljV88c4`mWu<>n!UkLD z`^qRQ1+x^Q#=?bB|j*K{_XYeZYDx<%OwnBM`5GKfltP@(_ z?t(J1w0PWn$rSbwPv*y40J?=sRpw(dVx8e9rYQO=_BN?K4fD-^{lwxVha*@p4K)_S zFp-E7(Gf)oG{C?q?`NdV5Exw9Sk>a`QnGLqKh zKc3|RX!#j zOpqN2{jvDnaN%=RwvTG>L%<$n<1sSILxqv<6k0LL=ldcW%B@Oxaf%yF2%%6FdegXe z9s{0u><{Jv>|(-Zy8USP&MHB^J|U|elSo4N<3PUp*~7WKieGG17|?EAViCiB!H5wR z`Eiv$bJREXosH2$EuVU>fXsf#WQK@|!`-@GnJ z8d=cq7|0kE<&C9~Bf>{~aD_k*Kx3V8066t@rHdaY}2le1lNU z5ENIMW4C3M_jg;WHJ0$eN$%hmAxOWh`X(^CCx{r)31GnkMjlbaEujc$3appqag#r;fB)HCIAjb|O#mn13kV7=Oe0WBA1NPpGH^7h(e36+C}zaL&JT ziK=GaEP)iGz8Da)oBu3)L8|s@OG}re-moGq9qK2tl7q5$Ddo_KE-F9?W#{|;G+m{E z;|EL3#&*-e`ek7CZitTvDF zdJg#JRwd3O6%Q1%*~<;L-b!PWCbMxQK+<3;=RxBF*VlC5HgwUIy+UqB_?hj zT~+Mv&#48>GGJCfsJG++UlJ{HhL}QhW5T|>;ZBm zWPdzozYiFBFqheu@_|DO)h&iPo9K)kc*l9=FX{CczQxj{J>EY$>k^z9vvYxvfQb|3kpGDypOm8E{lLLUdpkX)WOSg>H=)h=+T_U$B;5|1me zE9M&Qkc+6}NE<*>fLdmdvVbK`yXiNo^Cniszl#wG#i^J$&*}^88I>n27(Y31X`mEB z_s=q6h!8f!NDwv!RDEQLc4r>mo=g{jq!j=yz)0ZYiff6Nt-16$NwRiWc%H6|ds&g? z7{2&V3_H2tioRs$0c#D=nZa88p38#Il~LkXqaBGP%TG%7hYMJ!gv~8ucRq zMKO+|zh*Uw24|v}10ss0zksHV6n6pCdl{j?A4G3!Y?%QVIZ44htV?+ z&@38J6Gq^d4>BUZDS#R7tJCdiE~2qKN0i*kIZCQk=PQZxxT=)0bmo<@3?&h$mfe^S z-cPfyKtytw{eS|jd^Dcrp7hT2l zj+BB#bP@S9HYka9ypCQE7vtG(-fALy>68o9Z+9;l@H$f`HkzTrqLo4L6!)Uv2ClFH zf*%&pyTpP@A|m~6!okr}BH=W90T3wg!8{nYdQPC5rdZtmI#6Bo4lW%(NLAR)+L=St zYr&$BtfUEt)TQIb1liLz=b`2q>@rY$ES*rG#3xA^dG;aHZpra=`x+Q_xKR;$i_+QR z)G~i*__kl(MgQ@vO~qzJ%D*!r5#LN7I64~@-g@c87 z{dGkF^$QT7&`8uA#QNizz@3zjL@{KWtwB5;>X?Fkd@$K|IP7z5^SG!BLRD6kqInWU zBia~R#3UXrk2$u@(|CQB8I)#Nrn;~>-4E7A6|Q^iTfKOvU`8;cNDc#*`mTSw6Kvz# zW)fvU8zMxMNP*F1#H?#4;x~dt0^xam1Tn$>keI*%umKr6WN0m-eeXf=hEnQ0Ve;;X zY!QXhUaG6t5?qJQ7cq4&ja2&X1l-s}48g)D-dX;|+vrW94Z$$&b#}FEXR)VQFO?X@ z&xS=7Y#-~s2r+no%td8A=kv^{nCL+)R$xz(Kz$vt$I+5m!F~`(E+gzyS0bJ1MAJe( z8yeFDy4!7j?Be1Z7iEWANdyN|?4blqV(Aa3kj3)LoG}vjD4cF_kqiJaxx86EXMf@(oD!`XQdZdDkbmR> z=#2-hrz-omDDTXQNJ=P+dCqL7#gc)c;w!FET3MUd7&>%{pDXVPoPN2euk%$neyfP^ z3+Ra3LEzrD*%Tw=C$rZZnSfuT9}P%UoXWeH@*&p%wyLTVjm$o|mW9YT+?LrLS?T$}0Ah^4e9 zyOnrQ87qu1s4vgI|Hd%vhh#`LBaIbR9(oy>MIa{O`@IULERZ7?W()>WSJ*)aDF-$O zlZSoG@aw(3%CYI`vM4|V9#TxFCmhSvj1P3w)PMr1*vjo!`yR6T=tO|yxPyT@peQPt ztgF*+5?R+P?F40U1to3#qgI7JoUdeA)E|^pk0J>Drk>Ra)X`4G4rDYqlFlbwen1-T z5ByQ`D&YFwVQ!_CO%Go{d1xduOHhZn_Ot?B3Ev|XvKrbK{L}p*yfod1FD!&vQYLPI z%+;@_y*%EXmfH_7te!;3a6Ffcd^uboHZ2V#V175WyOk!Qmj zdRHH!yEm^V>oF(9cQt{1SA_w~aHXNAShELCj0!s-&M+q|8Pp3haJF-ON|B-uIFWwh z-V5_Iw~#_L7a*=Jh2IAQ1{O_2>*sev#-7G95eRfd9J+!jUhz=|DhDOBoF`cCg^edO z><%^QWL%@)2N9tI&Lf-6!_y0bVH1v((C@+YPNeIFo;?Ny4vZg-b0rfcHdn`cdlGyi zD?GkHQXwgrR8QM_)t_}Ve#8P>qM`O?B;dnv2Gwiq9|AO{0gP;KqeYK>Jf%_fYTp{( zhCY@9DDPd-lPk6jU9pcjZfBr`Tv%i&X-p)efTJ^&jRjxY!CuhLb7ACCH*|gc4Zk7I YqK^dmAzw=W`DIl?R92)~NI&5J1KGs9(f|Me literal 0 HcmV?d00001