From 013db5964b64332b5bce099b18939b40b847b8ff Mon Sep 17 00:00:00 2001 From: Nazar Kornienko Date: Wed, 27 Nov 2024 21:18:42 +0100 Subject: [PATCH] make options optional & handle unknowns --- README.md | 80 ++++--- build.publish.ts | 4 +- bun.lockb | Bin 446578 -> 447585 bytes cspell.json | 4 + examples/{main.ts => 1-main.ts} | 1 + .../2-mono-example.ts => 2-mono.ts} | 18 +- examples/{relinka.ts => 3-relinka.ts} | 14 +- examples/{simple.ts => 4-simple.ts} | 0 examples/deprecated/3-basic-example.ts | 6 +- .../experiments/state/ui/number-with-hooks.ts | 2 +- .../experiments/state/ui/number-with-state.ts | 4 +- .../experiments/state/ui/start-with-erase.ts | 4 +- .../experiments/state/ui/start-with-hooks.ts | 4 +- .../experiments/state/ui/text-with-erase.ts | 4 +- .../experiments/state/ui/text-with-hooks.ts | 4 +- .../experiments/state/ui/text-with-state.ts | 4 +- examples/deprecated/run-example.ts | 47 ---- examples/launcher.ts | 53 +++++ examples/src/configs.ts | 8 +- examples/src/prompts.ts | 104 ++++++++- examples/src/utils.ts | 5 + jsr.jsonc | 2 +- package.json | 7 +- src/components/anykey/index.ts | 1 + src/components/confirm/confirm-main.ts | 12 +- src/components/date/date.ts | 10 +- src/components/input/input-main.ts | 137 +++++++++++- .../multiselect/multi-select-two.ts | 10 +- .../multiselect/multiselect-main.ts | 205 +++++++++++++----- .../multiselect/num-multi-select.ts | 9 +- src/components/next-steps/next-steps.ts | 9 +- src/components/number/number-main.ts | 11 +- src/components/password/password-main.ts | 11 +- src/components/prompts/index.ts | 9 +- src/components/select/num-select.ts | 11 +- src/components/select/select-main.ts | 175 ++++++++++----- src/components/select/select-two.ts | 4 +- src/components/st-end/end.ts | 4 +- src/components/st-end/start.ts | 18 +- src/components/toggle/index.ts | 30 +-- src/components/visual/animate/animate.ts | 4 +- src/types/general.ts | 14 +- src/types/internal.ts | 4 +- src/utils/messages.ts | 25 ++- src/utils/platforms.ts | 125 ++++++++--- 45 files changed, 861 insertions(+), 356 deletions(-) rename examples/{main.ts => 1-main.ts} (96%) rename examples/{deprecated/2-mono-example.ts => 2-mono.ts} (90%) rename examples/{relinka.ts => 3-relinka.ts} (97%) rename examples/{simple.ts => 4-simple.ts} (100%) delete mode 100644 examples/deprecated/run-example.ts create mode 100644 examples/launcher.ts diff --git a/README.md b/README.md index 7207530..260b423 100644 --- a/README.md +++ b/README.md @@ -74,11 +74,59 @@ bun i ## Playground -Run `bun dev` to launch the [examples/run-example.ts](./examples/run-example.ts) CLI, where you can dive into and explore any of the examples listed below. Experiment with @reliverse/prompts by running examples locally or reviewing the linked code: +Run `bun dev` to launch the [examples/launcher.ts](./examples/launcher.ts) CLI, which helps you to dive into and explore any of the examples listed below. Experiment with @reliverse/prompts by running examples locally or reviewing the linked code: -1. **[1-main-example.ts](./examples/1-main-example.ts)**: A comprehensive example of a CLI application featuring a well styled UI config. This example showcases all available prompt components, with code organized into separate functions and files for better readability and clarity. -2. **[2-mono-example.ts](./examples/2-mono-example.ts)**: A quiz game example inspired by Fireship's [video](https://youtube.com/watch?v=_oHByo8tiEY). It demonstrates the dynamic capabilities of @reliverse/prompts by using a prompt() that includes all prompt components, so you don't need to import each component separately. -3. **[3-basic-example.ts](./examples/3-basic-example.ts)**: A simple example highlighting the core functionalities of @reliverse/prompts. The entire implementation is contained within a single file for easy understanding. +1. **[1-main.ts](./examples/1-main.ts)**: A comprehensive example of a CLI application featuring a well styled UI config. This example showcases all available prompt components, with code organized into separate functions and files for better readability and clarity. +2. **[2-mono.ts](./examples/2-mono.ts)**: A quiz game example inspired by Fireship's video about CLIs. It demonstrates the dynamic capabilities of @reliverse/prompts by using a prompt() that includes all prompt components, so you don't need to import each component separately. +3. **[3-relinka.ts](./examples/3-relinka.ts)**: The example which demonstrates how [@reliverse/relinka](https://github.com/reliverse/relinka#readme) extends the possibilities of @reliverse/prompts. +4. **[4-simple.ts](./examples/4-simple.ts)**: A simple example highlighting the core functionalities of @reliverse/prompts. The entire implementation is contained within a single file for easy understanding. + +## Extendable Configuration + +**Example Configuration:** + +```typescript +const basicConfig = { + titleColor: "cyanBright", + titleTypography: "bold", + borderColor: "viceGradient", +} satisfies PromptOptions; + +const extendedConfig = { + ...basicConfig, + contentTypography: "italic", + contentColor: "dim", +} satisfies PromptOptions; + +const username = await inputPrompt({ + id: "username", + title: "We're glad you're testing our library!", + content: "Let's get to know each other!\nWhat's your username?", + schema: schema.properties.username, + ...extendedConfig, +}); +``` + +## Mono Component + +The Mono Component is a special component that includes all other components. It's a great way to get started quickly or to see how all the components work together. + +This component requires providing prompt id. To have typesafety use something like the following: + +```ts +export const IDs = { + start: "start", + username: "username", + dir: "dir", + spinner: "spinner", + password: "password", + age: "age", + lang: "lang", + color: "color", + birthday: "birthday", + features: "features", +}; +``` ## Prompts Library Comparison @@ -133,30 +181,6 @@ Run `bun dev` to launch the [examples/run-example.ts](./examples/run-example.ts) @reliverse/prompts is a versatile library designed to accelerate CLI development by providing customizable prompt components. Integrated into the [Reliverse CLI](https://github.com/blefnk/reliverse#readme), @reliverse/prompts enables you to create a unique design aligned with your CLI app’s aesthetics, similar to how @shadcn/ui supports customizable web UI components. Quickly get started by copying configurations from the [Reliverse Docs](https://docs.reliverse.org/relinka) and using components that fit your project, making it faster to bring your CLI app to life. You’re free to customize each component as desired, with default designs provided to ensure an attractive interface from the start. -**Example Configuration:** - -```typescript -const basicConfig = { - titleColor: "cyanBright", - titleTypography: "bold", - borderColor: "viceGradient", -} satisfies OptionalPromptOptions; - -const extendedConfig = { - ...basicConfig, - contentTypography: "italic", - contentColor: "dim", -} satisfies OptionalPromptOptions; - -const username = await inputPrompt({ - id: "username", - title: "We're glad you're testing our library!", - content: "Let's get to know each other!\nWhat's your username?", - schema: schema.properties.username, - ...extendedConfig, -}); -``` - ## Learn More - [Temporary Relinka Docs](.github/DOCS.md) diff --git a/build.publish.ts b/build.publish.ts index bf52519..bde459b 100644 --- a/build.publish.ts +++ b/build.publish.ts @@ -96,10 +96,10 @@ async function bumpNpmVersion() { async function main() { const { jsr, "dry-run": dryRun } = argv; if (jsr) { - await bumpJsrVersion(); + // await bumpJsrVersion(); await publishJsr(dryRun); } else { - await bumpNpmVersion(); + // await bumpNpmVersion(); await publishNpm(dryRun); } } diff --git a/bun.lockb b/bun.lockb index 9d21e019faad1877067532cede42640878c379c6..bf29834dcab7b6f80dde46284724a1e6d75d3f03 100644 GIT binary patch delta 82993 zcmeFadstP~qQ<@E!li3^$3ihRMa96ElV;IGgB%OD>E_@Q!6S8Eh{VxGwXZbImRN+{@R_r&v%~l$M?9N4exk=<9?lU z%}MUFPxYxU?eoxZ|Itr8zkPSa2iN6Ah5kBo?$P1jCY6rAzi&wU@1NQ={>UeXcGete zZ{g>Lf;s)Ozq+cOY2>dS7E8`YR*OZ4J`L>%UF0Ek1AJ%rAK)*Ceg*9W?WOb};_3U@ zWd7yx$+5#ymRdF_-xb;o@%>SI7wFy49)1YKInz^CC0i^#JIIJ+MDzsTjRLzvpNDoa zytF|B{n0KqAq#{}tiUf%=!;PLsU3}Z+LiizG=nwGFmkp2VdLPs!X4CP=3sdCD+i4r zPf2!V#3#8db{P8P)hm)X8ktK|%t6^;)NB3i8S%-hU0JReBgg7Ja1)xv`o4hnfx41X z65Dsk9Dwxf?=48ga$kfZUv|!u2(aRws=$o6w3N(cah5b! zYNjha!}4-(iv?}WDS@(p4N8lzkVBCV&ml=qk6W=E6@84>vVucU_TLo~Gb|x}Nphv) z40FtuxiVHb)7zz6g8CR=cnrzzd8O>gVkq+ufU*N?Q9e7KtaMv{na-K8lEqlY4X{|y zl$_yEF1h|twlgy=9`#x*D>9Q;$E3u%oGO15l;u01EPs;p*XOKAPegH8@&Fvsb>32w zF!94;msqX_XNR2WtCM4v$7c-eZM@pfvE*4KWB)Utn4_GOq-D$Ems%`Oz_a{^psX)B z)|r;(Tx|)tN)~%=h|HIn9G}W+6IQsEvB68zhqX6rXp zwqi+|GscDPJp#|xegb7P+&e4DmA1@voyGDV^0685>BE*KrYx~^y+)>g4V)WisWUx; z^{2Tq)8m(}wit-#bk7PQ5#|&GV9P3(|iEKu8d(Ux|H^{V0ENQ*u!1$672{k~u5mGq}Ai zAKxe!!oyIUAvqCHw*FnEJk-)xthT&i9PH3PTZ~t`(V3hQA8WC=(wAh$C&pU(O_aw^ z2Pn_wrf{jpl~yU;0c9;uKsk2#N;8x$Rysv#u+qU&vvayDaB+fMXQ!1ORr;3Fm!KT% zr1|3Al%}O5CT6a%SiYTRvGhaw!_dCaaww0LBE_?zy)D_;{s`b`${7nC4DAOU z0R3aCw8XkxD~7pJ6W^L6 zC%PQUofd=i-1*(+${m&-zbrl(*EcSJ6)7o+p@`4ck>CadxI!|M<=mg0CtHlImXf&A zWoe!-d;BewTlOTB4O`a3YJEM)zdl<9UGxt)4tdm$HB%gf8<>NvbqcIi`SckqOCl!LCnnaJgc<2y-?(ev;e zE6-%PzeHUHWIc&jt7S;Xa7m=Y;z}`eC)4Huw4BK0n+CmHVJ` zA6V`|b02)}KDZAub?#*6xKA|q$>=_?+$W2lmBru)EiA6PNz>B%^JE$wn;1An?p zp6A~{xsK9TE=$1zv9!yRE8y3=3VpEbbGvX7|^AN#(Fk_t@ zr*TSM$rBCmVIWwA!_^#JxKZD}9Y~M$9T!0l&?y?j2$n7xVURjDSl-uKJ z75@;L%LTa(p2uD)lnXH$%5p|Rx$CZk^73SXayvELDYsn}bO7&HpGSZh9)U7pHk1V| zQ$8joF(nOqb1M85;G>~DT?Rwh&`%$d-R-F2o6#`lKMrO3H=!(dJCx}+Lb>zqhIY1$ z!MUI!F67Ab;uWkf4*dK_LK4k8zayAhnt-wV%X9eGN6_r2J2;5>i?leo;foLeFr;yw`_ zD2VBXLfMepw#fD13D0%*(+1fvS9)T6a)!n7_Or49vO%~Z_@@@-{Bwcs5y?S815n^H zC<{KaP2NECh4RSX1NCNwTV;dX6CDE1e1CxR4rMy>Gu>%;-H7TjXyDoxW@%HpD=2A^w-`ontEKD?Y;>9QYGV_e_L+9 zjZm(>PA|(*d;p${;||5sT*z1+A@;Wpe%Jh}0j=WdosBSqZr~NW`#&1D+)MQtB zI`SKvF4ME$0ppHL!|gGT@eGXo5|q8>UD?eQ(2j`bl8aArT9(1H9|>qPXL8b8a)9Q& zCXbXaUX?q!0r6aNw?erycD*mva#)tDc1X4*AHY1T{>_~d6FW; z_aG~m#wWT^z{j;R-uGj;f**UyY&sq@qs@AWjDt_*}DBu9sw^wd2g{F^> z=lt}U+)8(RE)!%zIdPwW_lCX&<+yBu`a;(zoedofKN{K>>H}r^+An2;Jm7hDG=3@< z>w8e9D}^%OI_N;&yRSunGk@pTvLeeja<$$CWlNKwY`}MjXOC7l%8Kl#WJS5qLEu?X zHeCN!UNiDf%Y5HMd5^dc%8~mBdIj`Js82QmsR;CfMnlxe#8)5Hns^E9(rl07sx!ZH3*?Y=W7u$QD+gl`Y|Z_Tb3b-+Kj?Emj=Hf$HfSr#;r_;5N%pV|=dyGifQ!R@ zET3zZ{wTCR_(5n_Xp$>s6;`%oH$3-D36xW^0m=g7mnEm9VeLNv&#B;Xo{$os?7r%* z{Y{QgdZIHOYuu9EY?lR(zGz(GeP#APU)>T>4%@}MoeW&bl46sG#X2*b^T4?oKE}Xs z#5Id%9zL;9j{Hqfp31JYw3IYFxtXhJX1bO?WV;?&tg1yh1CXvS^h$1q3m9?ko+>Ci z{uPu{kIQHVZoAT)%CBvwnH@Zh25`yrwrS?nz5~xG{|9EUCo~hvcqjC7=tw9#)EPPy z`sJGzi$AmidNnu0g9@yGvH>?khd}+H1EDtPAn2!WSS(jTpNC!t&4&(##wrbka<}$_ z4urN;N`Dl}4(@<5|NT(ri=$>EFb)A$*c-~0HSd=R-ho~X|2XtoXcm+WiG;GCtD($i zfim66eKLLrbQpX-)DOA!|4Ub!1jL%rbNhjLTxR=&KqX0FYr;W?G-p)5BA>J6Qz(najW_;XQRuRs?lTiEcL zYltZ}&$`P0WD%Bi_iaTl~7`~;{EbdXXF z%0+ql71^OfP^Qa%MS;hleF2h{cOQ@;@ZR79p=`*-a=FbWj*$C5DJ2=l8E$ysb;QSH zrls-uX#hO8<>N=>aBF&Xw7W!isj3O;k%DT-BwvVLu7pw7@2PE zSb-p?p3}5IsreOQSW z9-1w6W+rB&$0XvxGs?dj@tCFToE5{8oGSntC&;}Y5w4lHYGa|DP(ah&@;cyt*t=k& z1NsP~oN4)!e>FR*sWLS$6>G~s`xeNpOY!=^Gp-F2Pixcz~ zryT2yI2Z2aS6fCa9}Z>3Q7C}NOp~Dx++=@sd*Lk0E9v)0r{X7^e`k!SKH+RQtk+ z$L~SL)|*0Yt_~K|8F$Aq$%nvr@_kZm5^I3}_^fzTLZdw7Vg z4x#Ie?a0^>YjZf#+KsaCAUy$&&9@i<6NB^}aICw%*{tt7aa42%CI|a4$7VE52+~); z&49BS0TY6>{l?ZQp?W(^T_ECeG5H^Iz}^!kTzBjGx*0=*|L1}g5R zAbly^2*jZ&;XxeTX`%WdFx9c}AZv$i#_4Ge>y6!vq0=2&x)DD;RNsm<8f@k&TM}fg z?`CY9>Ck&&2e8Q~>b795yD@Z@!&VA5+SooNM8Am8BE*_grq9RTW&!9B#>0SP*&jjZIFj9GfqQJUuF!Q z6P8&^zP!;8dDxR9^(g4Cv(CAbmHS>Q+>cwYitEZJxs# z>}{NelzSUP=R2(5dK(Gz9r^(rPScRjW&~hPysnUCa<+rDD~zEF9Quo3oHaRoKmF;@ zlz;RwP9w<|IO$~NSdZ3$eT{^L4t-(Y)|o=dg>Xz_HOk;>;pE(7QCe;NjD$rFTL|uM z9OBFXZH(5>IK9YWeW{-@^j3%U3q(V%zS7ustHYLfCAwyunGj+vz0w%EFw_puu`!pC zJ_JY8xYowTDTgtg8)SPK4!dJYi1o+*MuO9!FBo94EHK-|VXYWooCdVx9!w5vz^ovB z8r%&?(#~jd23ha)F}5vnSYP!qPA_rjzafrY!kUN*(#H&x?XVfCSXXP{RCnhD*O06tfGingY>Rd1kK%rltKMof%?r|xz{wSk1#0ay*hp|W^zgxM z$l<#k4wp#wc~h_*hE4R~VW9sE$C35m^0p54HMT8vSfhN6(@PzCuCLrCW}LngZh~1z z8FuO~a8pdjg*twSaeA3U&l=($+p@L6b{KXETLmSzyUN%Whj!sx%@`AMgXhArNUYOY zLAENm5j>Zy%}5;Y(67dAGMnAWV2x|x- zrTH5}mpk-&Kvvn#2v`tg8#2_~=1W5K#R$o9Ko9gM;h3s}5g>x?Fl?7x_C5!=>b&YX4$zk1cjWIOYVLf(@k&x`r zyA88g!jS<5u5NN_~Dq4-ug(nxZJ=$Q!3LbROrgK#_n(eCgdTO?M%H1q7+gwQy%9xQRa9*zyd z(4lue0dlNyfFRFYI9znFJ01-%hNe06qW~NV3{ymq&4JRdGm>zTSz(5FIzEliM6;S^ zgjkI{i-N9~1Jz!n_Q52=$<@tDx5IHr$(b*k6Quj1W>zPgxe$&W#0X+O zOGg@~GadTRfU-Jn{}DlQz>on=SpgS;4BP?1b{N@o43FLsLDt&NydsZ*n`jK1AELj6 zklYcexKf{^`!u#Z6pQ;mnK}-GZ11! zQ``MmW|dOF4!h=SY@v*;v_P^FBhK1lO9+_7Gg8k(3-_JCD#rE+IV>U6-R( zhEYj{!>CLM(aRBP&8eS(!~al!69)fkY!|jvj!_aETZ5@c53+5An`$IY4AI$qED??t zcI-%u1^Xl2O1PU_-F`Thj(xu-*bc)XmkDn`J#=zwt%74$%{c8{!~c#@-EX{mQgC8h z7mqi#-QmzT10t=tJc6{J4gc&=>-7_iglvcH2JHN4MpAZ&?M{TIn4$MuLs#77W=mT` zFClcZneI1)BFxYPEIt{!zcutxYpCxecWi8H==s*r&#j>`7z0cL+tM1UZVh#v?2es< z&;&DGQETW#YiIy27BV)rHM9$%Kx6y5G3};XEc1<|ybxOqLUYZ~YY0V|Ax|uI8JmdE zOq1R3?;XQ4k09S44vg} z!CeT+bYHZxkSL2~j+ysigfJ}ALi9R>c;a-2x}i#(ac=%`XxARSN8(*dYB^IJpCO zmIlvL>Gdx2F(xRrn+Wj4H#MpaF?F4&`2n7=-UC=c3g#VwDwr!PQqSJf#cpmFINTIVX#_qdh-ge>$l2H%u_eW zHVICCBf1MA4j~RCH2(mcLnO7=#@H9jf-!s;=xK1~fSHHi({O(t$6v!?V4_3xE1m9} z05;|pIF^jt7~IO;35SKvW~T+|@5AvLU|wkTo=aru?afQo0yrKpI0o^J=7izDA=K&} zVF=<8$Lz2*1PO5@aqQ2nY{*)cP@z6_3MAI{#T{5rH%gxW5GT_?7)Q!d#g%p9&& zDJ~nhSRW z1{OEc8-nx);JAS>&p2@!;bxnzX=AW`wM>Y4!xCR_Y%LAdDvVR5p*GtZUMtR&hS(+{ zG|>z_h){$XI@%iBXRVvXAT-5{eGws2eX0=?=9ruiJU8WoQ}t zaCm-#@7eFIH^23^%Qq5sI`pyma&_PU#C>lPT(DVeDvrDo!~f+_{Zp_RU?>&M7=8~< z066(=D<5u_>3Gn71LuG6FoEjv!MmWx+j2m3g zHeU|hpS}ICH7)?P4}aKQJLi8L+@EEB0v936M9P7N{MOLXym5$un`PdP??Q-6%{+;% z+9Ss4*BpAnBiI;#SenSX15Qp8?v?cK;Q|rY#eBLrsIYZ8B4sQb3&VtB*q(&D0S;?= zIUWaKEpTQ&UV`;}xDiN+FQn6g^f%!gEYs|bwn?VM zO2o(IQgxu zXL0LUfCt}8i;bbx4%_p9VdlJ@K`07Y@g?lVVBD6Ru6Ahm82$%C^*5iEYZA*0y=nK1 zJR0C|nT~kI7>dPkJ0R=D@?^bmY#|)(M}jxYE~5LH^zoouis*9d(WX4Y>~Gtxb065(vBPc zheGwi&&sW7J~`A2;5eGP5imVS{{U_b9J+(TJD0jU7l7%Q0rzLhJUH1{G{aT}hx@^Wl$96f!Td#zv_>A%1&LIxQZ^_)Bnq}vL|EiK&- zaI8}9jccEm>=LY!{Nn+2(jIR&;-tfcF5RzB-HBvqH+3&!@BfEW9UZ?z4%27{vwz|*tCrI z+FL&g)qCua^Nc-sF{NdvT!8Y*oCL?V;Dp6xver0tG*n+%CCic*zZc-R z!f+#s$5xiNTSpybjfZ1jaX!2kq#uCeOnS&iHV1H9XqL>ioe0OCm|yU;Eyk&1q52PC zp=JuM_gU5Mqn3|o*TKy;&fvkx352)?qO49gZ1-yV)$2%e4ZI{WKq#THC#2 zoIdW*C%hvo>1G~%cfnnUB-~WNc4g$o{05GPdnfKi>viuMLr*%id?Ws3sQ&7^a#^6@ zc|o?{;qaUv4|bQm=ibZ6qHQ$dKMmFDjIE!B>b(!OIbHO4xL6d8ds7VBAviWu9=L(; zyUPw(7Ho&%X(;FBjsN1h9+nB^;{`r@p~<7)GJ%U$q+Za%FoHQ6tRp`3a#(vNV*vs7@YfC=IP2 ze9T=opPEL)EjAxikNia5%Hd*%XCyn|@GuS!wSPnC7Bj`P;}*-kW~(>~b#NRLY$%++ z6HZ`8%rBCUAe3OH|D6me;M(*Yk6^ zbmc@8z%iS2pTTiY<8=Np*j_JhK4op`aI?(vIl2en(puf9FWS22;8wK8_4!iXGRsod z!p&=qI|Iia%ef8zN;XWU+zQ8|N4gFTZA)1O#~Ng#kHRIkrX2sZ`v_t?i{RL5c@xv? z8+l1lU5Dcs;X*Mn$W{wC%baWfMmfr|RS(0l#pVUwHsBOmY|i3lgp$o!>2X>Pnr!Ky zZ(CpVVb-(ZaD$7t21gKDWQGF1Q}b`T3!w!j`wSs-j%}09@R&pneG@{e=D6T?->YS1 zJJA{%^Mguid$={!h|m&dx8D4tG4z~6d%%c47phnOD0c%cG+3i8aO2H47$Hq+Z=2`X zZE#c2SMx6#^lCWHjvTE4XXU807Xd?YOv5ow_W4t|*{0*#yWuBUio6BNf#d#_WqSQA zjuO!d%KJ>TXIDF3aYKnQNP`=LIN8`2;HJxb zp9bj{;W&2k-aqgUc|f5>hk|U$aCkB{GemzCAqP^)CDF;Esq&hVgRK@#EcejH+V+8B z9pjShqiJeB`1LIoj(N>HIQ?ljjt(wA;XxiJG|hN!%2<7XRkO?ol`r+y!p$=s4}@B{ z#c-;H9-8^hSZ<2~I6P^YGo~E^!N!?+A-XUAteC6GTnx6m;YRcC0rb}qVikkUsSm^- z8$02;(%H7c-N?64`YD9CpX5mr*j_WgK-3DRix6ktLfFRXnkCUZNnSuG)+~XkdUVvxg(@!xS#T+c z>ufaPY3es{cbj>ZchW5J;JA50o{!+-T1y_&S;J|?_iS4b!n0r8)OG9vh7@x7Wx(-T zEZg-lTr!*-hY4L}S!VsV=jqI{F7Kw=Ys*0>&1}V4gjlaEXI}T#zT>OW2XJ?osit|# z9?4WM!?8BGyax8rEJ<)3&C5Fs2TwNAcA3g1%Esb}7~C4;%-%6}1bB6k`J*n^ELm{! zq&ozc2#0I>@nE}`W?5$%uB?4<-2d257%<m6%9U*Wp;pWoAnKO4%#9;by|gCA<$IwoLZD zTYs4c=P62`2X_nN%sl$@a2yw;#Fz7PDh?NU^gUvLoPUfp9zwnj_vb_p_i>LWXDkbj zb0pjNIUJXiTm;hwYUVwYtnUdpmLjhzod>y>?i~pIxrxszCrem5ShL)ZJUDj}@kce9 zB_Bq1ZH;g162(yc2**Vw7su=&a*xTu*$;lgbFw| z6h|N8{QTugmJNCYjzefpyzOf^^R7i7JyfQW^>2aW;VJi7`>W+rl|!@yjyuEbh3$Q~ zdFJ}?yGEV_W(l@?>BO0ic*}$k&!i3_z>du~3`1(Be-a@c*K%-M$XngS;c}77Ha`ic z;?SS(;JA?G(j9lLW{HIBXf8Mdj=hsRx&cm}%3O>yMz}9OeC2jO9Iw6R<2>7bIQe&d zzalgeT-H1yK(mCw;T{7ofKuUDo*cWqbY@TV>)LLnxp3Ur@&Q^o+$?t>`XY$N@sK1ZiZunam~Yt@&lawRunc$o-}fm6~pmh zlf!WyZk*}3hC@b6C%@I*uAE%ohu|h4rCbk#Lgf6(ft#zGJgOgrlk0(p$#FP&IAI6b ze8%uv&bx6ZLf4@@^9a)nv2~ahsviVo<76d1W96BFxx(!g4A-2TxZ`kKKZChqY@QDH z?pTh{p9Q=L$H|s`^$eAxB@ek3aB^kx25mDOZ^F!7qWvo3N1!!#-l&;>^VXBM)7k-X ziqIY=*A_+vZ*!x=L_&aucf_D;+7V_J$3k9j%w zuaLWI(|>{E%y@`^PFPvta_;4h6mVQ;awXKl@wO0gsK47pSqhf52-26qsg#H-fn(2Q zDL=vS_Fm4}^*6~H@YoM)EfY?b8L%hVuA=1kiY7RA5EY~aw~N5#)qLRm2tpiV+{YvT zJ8S3dDxWrgfR3Hn7>WaOLFTwjS;VbISIZ zA#4oo2XShQ7HS%YR8ZVum{(51}|X<}>g4 z^}}%LfI_}5k<#I*8@_8yhT~ylKETl*fD4xn!}}o|H$qSTd5g8)_sllivjC`pj+X z65wPT8Mh0L2fxf`o!7=$=gkw_A~c8X9nkS&dxRFEPnj?82+Y$-+als8p$iRwY$dK^ zhw&#N3*_O5dBat07M$D`_%mDE6L5GRkFO|SFoZmKyNRtoZlTPB#fZOOT)9vTos4?- zgW?aj=Y-&ohR_0}#*tEqC;V{N!{Jc{*2xh#x!QQZ^t)A-i;JQG+{NaWv7Ka``Bk;g68AybR5(g(n~p{Cq&PKQ3(3Y9ahxn3_`!Tw zH2mN~Xnv^Vm{IfdS87EJ=HJYBi_)29Y774Je93W>V_~}4%Hvy*nI0EC@$L+*cQ!m1 znEClDWutIEXnv?H4EIsyhsyYc(*C9Dh{NETA1d?5NlP{BZwt(*HVMoM@I~0nxE#s~ z6U|go+asK&Je8xCiJx}(S&bil{y8L$%@OLHsaz0e%?&uu=ocPg}K$Oca;h3HWIP5dzaE&T99Wi#HU!KbY<-2s!Dy2Q*_4Zw(l_~Eb}!VfF>06+ZvowA}2 z%~VpE|6}D%6}QjSdWDf3RhY`^>XfIlm{0ISejGpiQ0Y(5h>bI~YsG6bweGFI?6`Zi zSUzVgKA)Sv|EsdpFYrVCO2tz(aT1N1#6C7E+*XX(q0k{SCMHZeTxZIl?1Q#mv(^iRaaWX%DrHsO5 z$^1~+0bB&k4#X&RLGeG!QvNa(2Mxo^R79MLXscHs{!VZfn5WXMQ|YJ-u2-JQ#dkln zGqgx?D)T?Ce5-Q&dm-W_74d(q+5a0A@(g@ib>KhL?Eh)T|0o~Zc0e_-t$Of6^Nxz3 za<#t)!dnd~YcJXX%4qY)F3^rA#+a z`M=O{WeRr0@6_Twg%uC211$>5Bd zieGHjEGP$TE}>Ee=PM7*HY;AF0F@Qo3T1&YjuR^7qWc`4BUZ0CmF0Y;c!S9?{|qpq(FA|3j5@92 zsVw+gDA5_k+bYoy_{DP1Lb*H6sdR1C!;Xmah|t6Xi?AD+Uc)aY^-yY4+CizVv=fx= z=nTdGEZy*n^<1Wu|L!tBRF>Bp%7M8O$|xVDgP|t%|2I{x&EZc!%Ota&unYDmE@wGkupzMrE?Qq0GKc z`SmLP@0989QR&($%ez-`D!)G&%0uN$KcWDY1#M8C$^wM)f2E9iOvSfVmh%MoVCa4o zPi1+P%2#?|rrDx*6reJANO>y1G1fu30zQRut9%V*1&vUCs4V!D($gybJ1Cc2lhSig zep=N%BLrM>zd~8i1(lG>;P1+}Ri^tx@wUn_O$%2PYSzYk@@4lCt97Qzpeyavh?~4DGa@^Tg zCTYhu|C4f@I)ihLyFr=W)28-J50!z+0(zX_H zZjEbIJeBoaZ@Wwue1nQGRd~7ZaE19s1GC%^rDLFMxI-nUa)nNXvi#Xlrk|tYsSGZ_ zFV6E4CSDl?BgL{(r8rezPE!b*n0{ zt+JxU;A~)wil>stDo>?%LD|q{ic{(1m2ayYq2z2taQ4zvg0{*6(-r?aWx5QcV?`^W z^s7|9wo0C*IF;%9tx*})st76*-bNKCscF%D=7jfYNFe|BmwSLh(H_K|5?d!91<-w@SZLdPeE@N`FxLBh)HR zF2~W&)?a{SuXr3$ZBWj*4&^5DAUn$2j9Pyml$xs$NMd?(PkIHhUEC25( z%b77oWtahFa4vqap!tfoRkk<|oDEt5#an?CykubV3|MwBOJ(~jWqqre4Z239`!6a2 zSK$0ZtmO`sjQ__dEIwByr!xDUir=k#p3-$teyB{hUU@28{h;!HqjEv~O#*YFZ$Lp@ z@EcWu|B138B`P149eh^#XLa1{aiX^au!5HvAzZ2IX75#%j>-=1QNFEm#lNoN_bJ`4 z()}w%7W36^1eE#z;`Q!4Gh#YZ;`3L^jzr>z4ZB6@OejB8^1pn)E2?Ioe@r%)zwr4h zW%hYytW;LVH@f^#8Gq@`E{Bl|XB~bxLizZ)^k(-j-|%vHF1^{k^k(bZ+8EkSIGR5ap}!2MuD4JS9Z%V+iYTza#6>CG-4rSfqyKL5F@ zJ85QQ8ExO_vOs!Xix)$AJ9p{LF2+M#db7(lap}#jd1HU+&8~Xy)bJmBF}*5$ zLW)!Ldrb2dCm+=&YhJ?tF>Q{hdra#h9(W9(mpDd{|2V+d#{qhadmaY}egfbOK_4;d z34lg|jZXmd6Q>A@HUdoD2+&_Vx)C7aNq~z4K4Q|704)UDp9B~r&J&bw0$8{Sz*lVD z1Q7icK$oWgt`c*f0?QCQdNDu+K~k|cdFs^|VfUxquI>pl-hB3+Vf#yt4*t~M zDe36Jktxn%`;w%Klb>#BiM(-8(b0e__Kgep`Sw?quUzlg+1ID(mmlw_%D#P?{p_%P zGhAZf(-@L|Pott?BK~PqnhH@q}{Ad7hyXAs$W2glATBq zDVhkfUId7G8DOR;ei^`T2Y{^{AWB4*1Jn_e6U-6XEBKXP2H<)HV6G@52!08`YZt(L z;oJq#NKi$vP*eT>~(-@ zf|Az(QbiL%)~f(f`vB5K@jd{*Jpi`-0GT3kKR_KpIl(HSRRZL{2H>g$SS`v3g7*S= zy#cURINtzhB&Z^|U3k6;Q1m)L=9>W7qLLtDAAs*$0J$RdEr1q+T7tWTPZdDvet^6x zfILw{5M2on_%^_Lk^44){SANyf_p^30e}jE!UF*JiF$&#Hvz({0Ui(q)d1da0W=dl zD8dc`R1=gO1Sk+q1X)!8QSSg4qWB#EzqbKw?*bHx$aewi2+9eB(B1>cKLFr*58zQz zMi5*L;B^S#ap61!&`3~4uu*uv4^VUvAoG2IO`?(@;vE3r!vMu1^)NsSK`p^E!si2k z(su#!J^&~YH3ZS`0R+|nJS%c*0PKeV8VI(EfDZvG2ns(0cuv$4#Jvx&@mYZFBDVy< z`!GQG5r7xPF@kD>v0DJj#64R8vOWMfL$FhfdKSR1hGj5Ixj1zMppIbbM*zFTqaOj} ze+Y1qV7Hi53lMw+V0$gV9&w(akznD+0DHyOj{%B40_buSV4s+K6dM%;DG4;34py0;Py`d4vK>W6$HbM1H3C%9|wp# z25^$#knleN;Qa}}11A6ui(>@U1Y=JE)QEdd0%RQrI74tm?B-$bcLJamN3K>Yd^Q8r z5#((KI4Wug@=pQ;mH-?Rr#OVcp8`xR1voAqEd^*KxJYnPjQSLy=re$gp8|X)P7y?W z&Mcn+)Qd+y185<*NbseY^f^FjJ;3(Q0UE@4g6J;*7S;oNBevE9*uMnm@&&*tG4~6A z3WB`^-wORpfVi&!lD-5uBX$F5-wV&LAU}wB%8#Ow(jF@@9Yq#(oc-~8Dm`3jG8z8j4|#nP7y?$1DM(j;3FPw252F;NH9oDItNht3&8es z0KVcpL39hi!e0Qc5?g-(u%8F$(gNTw=C%M-5bPzmTIlBi;(i54Iu9^R>?ZKO0MPGO zfNMqNuK?8qz83%jMCt{Atlt1?39c7DzXAAN1jzdhAW+m0)DZ+;1PBtj7Xk8r2WTJ| zB?5j22>t`0@OOX^QBTlF5dH_iSW)nYHm9)2iW8YWv@pzy(a@}23QMfGZzybnZGjb0 z8vJ-stO1l-0c=))a1m(*i1q*|C%8#y9su@s04@)JNurFPg21aCz+~ZU2M}iis3Mpu zJZ%8p?Ex}v0MkV!K{bJIdw@v%|BnG=bpWU(m??ZZ0QlJf@;U%Si5h}Bfxxd09+!n3qW*NfO3LmLhA}(?*`!N3J@>K2r3A?x&bT~&Tas4-2tiy zl7we>0B=u#%+QZL9|qL^~G zI8VtFk=~GXVk>36(0W7i#azlgqKtB{(64~pC!Cb~#cs+2!m|(i+LwLpgN8pSDha9y zeER|vh}6F9Yd`jtzz{zD*w-uB*M0znq6R?QApEa{2$4%E62~Zyih%x*$HYC9$3;Em z2{CE_WTPmc;2$`mY!YEUkf+3>lw#3Dd0I>w2zf>nQ#On9loAm+2(m?Nr93ON!H`lh zm$FrqQML))7xJ8NQl1yPDcgnT5XcK6p7Nrogox^^*wL%djWUsX6+7z3juPw?K7Q<| zKRfCNP%df+>Iee;0d|R8e}Md<01X7Y@eghT1YZqMI22%ys3&M72)`O&uPC@0py(Qa zW`cbp>>7ZGVE`r9091-5f);|PVE}K6;$Z-#!vSo=0jfmgaDeD*0m=yu2<=(``v?Hn zwEzc289@bs*9d@jg>wWzTmV27!6D%p0N{NcKxP2IVNpp?P2hVSK#fSf4j}7#fLekh z!smJbzZ(GZt_P?UH3W48fj0mg6}dM604GJk zNPwasfM$ZvL|70&L@+=}5J0_XB4{Cq3I_O66bA#8jsmca0%#DCqX42u1C$edBec-~ z_7DKqXn<3qjG%(RD+J(M;S2$Y8v{^9a7K8J0q`CRkU0k62T@5-P2f8gph={T1;}y$ z)Drw8d>jCNp#XUffM!ucP)86L3h;}_4F$-*5ukzKya>1vAUF)5@J4_OqMo3UAUq7< zq9_OhC>jUQOz?*Y8wU_E9-w5Lb+Q%zLDSiB*4d(kBx*d8c!=WhNK!fhz%~KECL$*Q zM27>E6Lb(-IDma3fGZq87i9z$1YQ#XItk}QfVi6gstCFW&zk_eBLFgQ0_Y|x391Qv zBLF-_Y6L*mB!F6i%Y@G)0Kc07@+JXzi5h}Bg20;rdWqbd0rDpUG!XO_0h0lOrvMaA z2IwQ|2^tB)rvUU51ycZurUEn*^cP`M0V1XWluQNi5lsXw1X0rf28rTn0HxCbY|{aJ zMdWmV=otXz1Xl@d27o;hz%>KFUz8D45O_rbTrHfD0CBefR1pjlp0@ya&jiT41>jmy zNl;DTI};#4q|OA$ngvixaJ}%E1>hG2kT(k;P}C6A5d=m71c}@zfc)724Fsb^z-)lv zIRJ&T0YXGQK_fx<9DuQ+U=Bc0G(a;!s0fP&h?omd5)BY0nh075qUHjO7sYb{O6LLC z<^hC@$aw(K^8v~UZW7vj0Q&*}*L;9UqKu$|z-s}(WZ_%@5VsJZieRenTnOO32q1GI zz;sbbP)*>w2q03VE&|B96`+=2rtrBHz;7`?-mL&pqK2T3AaF6j9Fe;iAm0hlKrmMX zI01r}02Dd_=8JlQMuPAq01HLI5`dx@fM$YQMOX|#L@Yo_41iNK5ws9Q#R9~L;#h!E z7l6$L;1ZE8fas+F`VO>B1)gz;8J~UIIX-s3E8$2wV=ZO5`pF$WH`lAXqH| z5&?pf016WU){1(9MuPApfZIhu5ez;nWRJ3!nW096Frh36dr z-q`?|cL2O7Dha9ye6s<{L~1rbRt`Wd!A{|m1K^hnke35cE@}ws2m*5fc8T0vfc!fF z8VGiafI9(#?*b^i6JU?1Cuk%HzYAcmD7Xut=x%^!f_)m55vi5WOCtoZx`a)&tn{0bJ_=4vI2@3IeZufOmy6A0X}?fGUDR z!t)*g?|T6h^CF-KAox)LeCsrCKnNNM!XE{=C<-10D0&Q_ncxo*_836K;{YX( z@dm_0oPCTpAS6+bBZ-G7ejG_kp8&8u0bmo6PXI)31SluyAheAD_9p>c8v%4tMo>ZE z^&~(i;d~MxZWBNiK^Nh<3Bda)fXqz*-9#lpHG%I_0G=ZCDS)hEfLemfgikSm-_rnj z#Q)E0n2qIe5H>9YW~X90XgdEu32c;iB=4Fsb^zzYDuF9H<401zVT2^tB)Uj!H{3SI;# z+5yl^5GulU07R4lloT7=Kg&^uRfEZEy8bIk@0NY*wmx$a85dAtpIl(fa zy$)dC2jF@gAYPOaR1kRW16VGc`vBth15^IoVN z!m9vo7X?)SMQ;N%6J(39w*ev!0F=B9kSm%9S_q;J0Nf>t4*--_1K6qo@$b1*zK~YIiP2l?; zK!Heo4A`i zpqjwm0nkjaPlTNSh&TyQasr@IG!e8AM4be9Qxu;BDE$<` z_9;M>i2M{F`ZIuXf&)VP48Z<5fa^1WgQAR}g23x@fOm!SbAY&dfGUDR!m}R0`wM`~ zdVs^ClAxNv_X~g;k@^Kd)|UXa1V@C=mjHfW0pxuNP%CN(>IeeA0yrvizXHf_0B9gM zCIT7&g1-hRYydbe>IoVN!oLPMDGI&@DEbDVncy=K_6pg}~Q0*F2hP)_iT&`tx`zXfoe1~?_k2r3A?z6JPJIKKsm`wpOr;EeG6 z4#4{iK<0Mb<;75SqCV;{p0WOGof<}VyCV-2gpb4PpEI>2CA0q55K*Ub~C1-g9(oUQ` z%Nr1qsGpF;LlpmnB&9zC*nS4EiO8P;qMNN<#VbEsZ{`h1Gl2aZQn;FtLKkHO6$D=A z06GchIXoBaEOt}62+v<2T}3>lo2aC87rk2`o+6dfLmZ@BCVb9AE*GmQUZMsf>V8GB zfxn{IULyBbfcy&p4FtVKzy*Nd-vA0P0Q3>{1dRmYzX9|U1-}6lT?A+*=r6)90z~`{ zP;wE#M>G+%5JdeBFh~^t4p90Bfb9YPiPt)K*lqK=;@=t(#Zx`Ti_UiVApDne`mO>WkMSB4 zb$K8EMhX7)xt!zpM;N-1@6p7L>pa>$8{OH%!*+Tw{sEI+VSyg=wJuBJmnFI~@PC2l zj}mt|hj`p&R?}thb>_dymNOjMoh2A%$-81N_wd%*HUd$*Sf@32thmF=<5kf7F@AVLy8JV}e)0H6R#@m=W}$rtU~n&6j+W!G z7$z)-{zM{r#A0TO&j)z4vn3|uAI7DZdH$dF-U7U;V|~<5cGkuT4hh-8p=gNX?ht}| z3BetL7Ef?@8H!tRw*WUiRHxG%!$rs_pa?a_o4jzew+HJoLlCQ8POZ- zAvsLZrB9K8t-E(~E4V^!^z(S+z9n{HQI8-G?g8fmJ?c119@7nLrqzJd-e8XbDZI~E ziOPK@wrq%pBU$R3&#g)x@PaoTCG=`$D@y)Jr_OCVk>UffOUrpIcXwX(aHj^OC{v%n zi==Cg)7>qDczNz2t^A53{{P}uAW{AL_UuOynMtaa{4bZ4W^S&OQ8JP`s(0reeYt#)(e+1O*NM@UO|bs)D#K#D&&mz)

`E=mjNLwtXO))8(aVG7C?l@jR%@Z0LOetVxJJvQ5aq}psgt!@CWi{haY0fi z>$J=Vdp%_GvxD`>BmpjHti#)=^?Z>9YS|_^4N-EQ4df(Q`P-~zQd2dxY>SrVKvrAJ zwrW{UWDT%OE7+!GQk6M$B-^ztH?rTPS@5?*%c8kIxc#n$JGC$`vRM)uf3e6UC;4Eu zmhIMhA0b63t#MD~P>NGLEtm;XZ`?GaHt>)sf8OSfykN zgHy}oNLERVG`2h9$lqZt^TYmF%Z?%wYf*TnWyiG4A6aiL`$CR(l>h^v6+-EA$F(AN zLpKH(vuDVWui_^N(qoaHaS|E-LZ6XvNf&5+6dc}xe zz9DM{>Nn~=Ul(g>ewX78Oky`JHmX$^}6qz*c zt6EkDdpl&&(rV?vS}6s-v*T6)p|sE&TCp7V7ueJGrH06|j%hvfIeS zOGW64OxlhdhAXm4Pzu>cm>h&_9qn70ivWbuc;r-EQLF+*k;&iB2&4&Ag*;j&r{zkO zR0AI=U;dsU6Ki#lqgbVpz0!I$upiXz=r=8^DMzVA5ul7{_NltHuxCI~8lHW!ZWwki zEtAuArP0=g5fqy=#w57OfD#V5q$v6GMJ5TX11>Ggre$@JnJ4*6{b$!gIfiO7KBR@_ z&;i!R&Ue1#BTH_@kV)zH+Grdh^&{E^}$|B%X(`W z6F0L#ie4Wr>xW%3AhNz%)*t&yOBU_cPYY$XwLvTP*Rlb~f}||@`$WqIVlSa(1CWVz z5O`vdZZ$~j4aP2?nvotfSj&cBmk$DoY=|kN{D*S!k(4igL$%^C>^{h(TMg4Psj7{- z!9^pJYKno)$fOsH(6Zs!C1PpFqqW}=*d=0-jnT4^*eA;woNcg-MJPEL1yXm?LdR>x z(OT9HbApzQ(K2bE6SZtCcIn2_o+fG8IPCUsASWY}IL2!|X*ZS~dl{3{E1OrDapGOIr}xY)cmHHjN8u3?iJP z6{lmDwjeU4WizmE*Rr{oQv5SPN=sVue62SNdoM~=8u9`yn~l9HGHJ*QwQLUdQgZN$ zG^j;dsIWf+`CF`IbFs^bhEo4awCq#tJG5-6md!)9Q_GeilcJsvQYA5%E41DM>~grN z)c{2C?%WasF zk<}pKEy0v36fbKmS+tv^bf*@s#V(#DrMtA}b=aHWc?ITfEnBZ;lBhjewgJ0*UPJyq z*RqY+FCvqK?$xqQ*d@b~(0xXvmBP(jNF;K#tXw)rTxL zUc)jbjWW18`NyZ}?e`LIoo+rbJ3?DLFERmsWDHiF@GDgUlAcNTm7zv|bG{{|4 z?wUhDZi<8HLGAQd@8H#1z2QZ(ii%} zCm`Dw^39++*vms@koiJsOoiKIkX@4TFb=-L?Q6IQsfaK&c!GS6Y!vRJVK@wi{_qL> zf^KhFWp{=y&=tBt4|oo;xcP_(^MU;0_az>Fg-qDff+vh&XpjT2=x=56>9WX&RLl)R7+(m=EvcOvJPZv{C<-wU#WEL>dR3vz&U5+_X(l0kAv0V%-) zUK7R}cn0#3E*XDhyg3B&o9*c!AL@`b%OCItWKF|zF`t_(Ol2W@0ZKzTkk2;7z*rau zLsg^FMy2TfSU-UQFc1d8U>E|jw#f~7U>t=c>l#_d7~q7L)Wj?J4djz*m*5-t7QO>H zWbb3vR;=SO&{s1$+i8VHK=~HN3gX$d$2-kuh3s zQF3?h1TwoA3&Y@JXaMz~1Ch0d)zt19SPQbWk)=!*=ng%g3RHz^P#t6yQWIpCO?K5T z5ms8lNdrT;9x6*(`2tTL=nKE2@Cq{HNq!hE$DGS@i(ILO#e31)&i5Lm&h}C=^Q)%ovZQ1eAqxP#!8k6{rRSAUh<3B_ zc~BU;ANWIngKq~1Vvz}dFoZ%eC=N2=m(v6bLMliNX+XZF_XoUDqstjZqYq$>gM)Ag z4#N>R3b`N;K^IcHQ(t=u``+pv$qJOFWU5Dvj%I0DCD zIV^-lumtAAFzDv=Ay-%mfgj|8@&sA|Dyq}vjTX^gVEq#0anlJn38&yRGzNY)<<=Cc zLm4Ow@`b`@(h4G>9LTPq z>;k&OLlW8wYJq%}R5tS#!F)LyUQUyjT{|&lS58c!CA5QPAe(UWh;Rms1qmcCvJ8+0 zWb;in+hlW1Hq&J9><{{#%P#;!Ff?yO#Mr1>$8%Vga86zJ_(Fuz-Q zH`!Q`jTG59k&P1B7x@TN_CjRuBN!@z?0LvaLH0E2U{4sPELmlVDoavXg0=$L6MwVPFDU8*y2G+tlSPw-Y2Y5Qvyvjz!=yhafJ#2uDun9K97Ldh0 z-=}wL1kIp1w19lz16iRng(m5g0+&-0Hp3Q>0_zRE;6veXnntww5e4>3&Cg><#%-k9 z`Jj$lBd}{kB1%|8QUh`zQuFuZvXc35VM$q8>$P7EI*zO`WkvatK%RiqTw0I>gi}&- zm}@CW3-amSkD&m#U^}_n0XrdK!fZWR4o?M1kd0)bHMD^`(fs!tj(?YkgiSyKkrk&< zl>|Mf_+yD&0!>&-Qi>9;jqS&l%bKt$G`20Wn*0VN0$XOAB5R4VAPd5KAOYJlu|Ghz zqy`rLmPDD0!|2RhriN7DgL_I$S)0fjMb;{^W^n^q!|VjvfR@RfO!8zW`vH6hvi|r6 z^At#XlxIBKVH>Ok8Pv;x4C*qtKSn3OowoHO;a>%rU(4<9C{%~sa0Fzums_FS5?8@8 zkcrh)kOhS-1Z1qs12v%vl!j7J5}1*gJM7spvw<&oLl(&FmVvo8mW&`7C1E`4PhH(; z5bcGPdzxETaDk-fBghANLE_5^Il%TRI=LV}6oAZ75P~2OB>n&>0)@dJ{6OSIwW-~r zO%Ejy7KdUG0-;a|Du8$_59L4tD+3ZpS*Qq=K?+h5T@@s66uKJ?#N65CUS#gbN}|Ic z9AsrR0wz!~=4xmR7o%YmjD+D31NERTpk_{Qhhh$a!7vC0!Y9xV`a&=04N=e&Izk6% z3^Kuysd0UfiB1HxmVo7+8wnz41udZkd<@N?IW*OFk%^n=3bws1_V&<5%iCd!tTS|i z?$8Zn;?ME=e;5Fg8%eDc(KwLONeN?NJWPSfFi9i!r7$1nf`aKV6K23H zm<@B_QC}>4zx|Lh41rmU+m1 zmKwa#$peB@r8c-d|Kp;q>L|y`lLoo;fk%wZ2Kro~sEXjOn z?4>~5%VWyCx~x1RtDqevFmV*+YS^oQH2aE}6`&GS263yZserrfPyE_;k=I5?Dk%)= zKsZS4G{tNJGNaoi-9T=qr7)#v8iOcHZTEwA&=*9x2lRoS&=ER;MA932L3@xXqWEb8 zGD(uDVsmH>kszxInHWWYBqmyvWZozNb^wW7QYaCMOcD~MuO$HzxgD`wi(WVA0-d2N zR3zZ;S|)bUu`?hEw)K&vFaw;7aFQBW*Ju(gZS*de(q1-WN;^`Rc3YA*Buzw`{Y>OD zU>Zp2+kex!wySy;cF~=UDFfCf%#E-C)`QHw*TNcD?e3=P)H5n@UWhgM7LQUuKfoP0 z1-Idr+EUNRknRfB@8L3h3*V@V^^8K%C$WA3$6y<51qsPc%2BSjYuO>pIM@#dU?)hu z?7-XwBHshMAr?fo7d{7(i)q{UVLvEcO8Wm{E{=fMB|3Wl0DHA zY(KUsy0-oAO`5c=AOT6i+c~!bu_O9U2Wns2{zOj_D*h6>UFF)&h#jV#U@=XZbSXO` zTUi3LBeNCl$ZQ=ciJ#yeNEO)bkGTFB+exym^n`2KLdp-(`S>p{ z$o{2FxwAqR$PAf41{m36?TmRHHi2}r49L=f>^*sc>_w%7Zn!7GlzZJf^dvEFL1q+h zF#mwx;1xWT`j>w23m3n_OLzg#;TecruEqQvD#L4#z#K&24hc71WkH`Dl0gcP&9S2B z%1TTUD)#hRE^glP)YA)#L?}6woJnr-fTTPJW;T!%`(kE?T#yrT!$%-~#GP$Bb63tT zTG`4g3bL&yi|E4e4<##~oA^IewC2Y}q;&r-bI}QNXXiw6CAlw+Sq!AEqyb5Tk;QEY z$UdYrBxzLQPxdHnx$Iero5*Fzlj@R&DEpTZo~=ujM4K03D9Ihmb`X!ED3RKMiJKIy zL~P5dVV6a1MJNvypbAumO4=OZVI|~K%rzh{I7Z@DAL==nFxSN*%l6vJy$P>@*1=i} z*fZ#Jlm0-#n#9MVh-?7XPOwjQ}p7x z1=r0z)U{4VW)<1g$e1DuVI73w9%@)qqoA`Q_6n*(XJcD*Pn>M|@ziHp0KQo2$&QW%neRuBO#p$&+RAf+#Ql5nZf_A&y9 zgG45g*eULWT~gZ}x?dSwl1PukmtaROo!|?wBTg8pok%-^ z_pc>U5{~Vj(0&@Zl$4BZQfhl;Y?4$-{j>%dZ^XO=7qvYSvlhsBA_JS~q+=9+hbbd- zD(v53PlNfbc2AAnmZ!j;5|TqQND4_nCZO-4@$eS@0I3fd;$LI`4PL-gcmj{%5xhm` zXUvE20Pe$d{QQI|v((3M5Bpt^>95RMf5d(betH*838Hufu4?lJ z=5@G5z_+yhCiWX3kv&Hy_o9T;;b+KxfmiS=yo4?2{*IX(lEE8zLi}z{iXsOW*}x6E z%%^G-pa*sbxPt+b8!1M+I6bka1u1$#s^vpOe9(>h|BxqJo`iJS_WzVAZORNw>OWzi z<%vuhn=KPXc`ZnCV9WpXto6j**8e;6eSe}Golt1EaJ%LIU10wqx9ESGa!HItCM6@L zB(ftUOr)J~+muY%r6sa<+K=6R<7K-3Wonq42u5NKhZvA4g1pQlk;)WTdWOs#bE79+ z$S#3|*9pTB|Dq!@+Z5fu#1XBPL_y}LVoK`XkElPgnov-?C(Mz&3nEih8Mqo_$~Yn= zV9ma)j7VuT1PR}sKg$om%=#Bc30xc{)pFfK%jCrq`Bj+h)(N@oE(x89n>;!edrOdr z#V#+cNO89UNtiTXNtm4|(ZN01416dT;&CpXB&QOYtO_Ji@@!fLBuT9#PA0l`VkL2s zP&?7KTt?8*uo!m~i@{jzlO!p{$)nVI`hcz-KU!>j5C1WT_&lmz4jj z%aE=3k)Kl~vJA0SA@a7$zq1UH-)qS$KbzF~CPq(hnao=&5$4v2n?e)Vpqe!`d_KG; z$-;Sy%{m+N6J46DBY+D8{1u#sF)$KFz%a;7BbFMI>P&2DBKN62VAsrtmL`{^`t61* z)g%uNb-VaezER8mV_@daM!pD+CWLnWU0D)4`*s43-WH@hfh^t6jzWcGL^kY&tZ@BwB1;Cfjn3)gK{jU z$o9izJRQWmiu^F<5jYAbLFN}%tH>G(a5DK8&N=!5~;lY3PO)93})&tWe1rrW@L@Nj&csJ zgCIL(1Ib|)OkZ$;4`cQTEdVAg)+&NaIRtI#FX#@F^fQ9C(+QFkL>bb9Pm zO-w}YQ=@Y=_UFl4guh=vF+YFv>AWW{%NBH7-Rp3^jmg{t{7U#q!b5;Eipf3O{mRjw zD)z)rh+lwTK(P6WLke6nJDd52`;I)0O9{V#5JIbiAD)87)G9fp-(cU!KX3`~3-pVo zEq1_3Je^2VU|%i&;V(=lGq@;R=ovB7y}GrID{!g1=@ROPpE0Oky9n43=>zTFc%yW zTGOr_vi0h*BMUA;R%p-gBcB(o|p)M19hvSJdkn*;F zl7GJCsZZ~n`tB24O86D`3t(0f5J-TGEN|vtJ@U~g@gt$((!NhO4g+A!Y8W$kQ`*(9 zOh0BtcoUBgNl5#u>(Ho4(M^-!6ih^f)P6wwwygs z=oFV5A9Fs)_Y@a@8WRbX8-O(0h|Kk7R2>q%2p3%lWfbA z#dP>N)Mv@AZ_1SmyMPlJ2qmfgdRRwr#Li!I^xWjT^K@|JK@RYcQ!C1x4;~v(C4Cpu zCBQFKMNBqaUK0pXD)Uml)alN?Yw(3b@*e0Hs0K_nZYQOC%Aaa8#dzpno2yi)dafGj zd%EJx5vcl8yF}5ww>%7B3oIbC#J-$YVy!?_&8bFhL&`pO{!}BmdlqTzdp682ynIpl z2B_d7o_BMopyAEAMdc{4YuF}xkMQ>dn&7=e!BDb@a=hL>9LiILS&F{O$_ z=u?;cNfsT0QtzCQD{Vi*rDmmwTBy_mjNu%IK6QXm+mXyuT^nG8DgOwkPu9*o21;j$ znv=)-m(#EA;zvq_80V-~1C7iaR#SM8;ZidO8Y4KU98svsFwn^28lBb}6FQbG8~$~l zKdR#~*iRag+w8O|+YlqrwHOV_Ov@c!k^7<=c%mT<6pdSHRktCAmt#kISO0*kqQmtH*@Vsvoj&R~tL&E2=}t#NopDN`doa&u z^%Tpxc5IRLFRd1B*7G7<(&6$`bitVoz306qc7`p<(WWfw3PCygda0L$;TYqk3Pxj2 z^HLGf$QO920dl>{D|UUf!AaD)yw&Cy!z;9qw^c|}Kdsj3na|py)HnmHWN3_+HK-Kj zaJ7h&tzVcfjFTnYg1yyS@mm(ZQr|hhFB6?A+;a&n3MFkE2dw$ zH@P+PR$;>pFIQ_cq&uZOvf=FEH|h-<{!}0J-osnPpb0Bo z>6V!(ri|~wxB9Gg_J7r0Tx8^6f|E^^9YwnAFe9_6Nu$U_=4@)E$k%39m&MGULwSy- z*!^-a$s(lh#^rlkwgumKkQUB8iD!8^RK?L~mi1QsM;r58|5*KI5po-(kPD9?tiCi5 z8F`BxoVasNCC<)sC$(}@c6*smMUJ5i5*@L)eK5cNNNpTrggM&fSAP(eqi25QKQ`VB zZI|CFi{dp;JPMqjb%0^rIYW6Lqkz@#C$^ilW$)$V*UfS@!`xdy4U;fV6;N}>l9O)= zs8eG47MwHA@Cun#&?>|-&*Qe7NE^}#kHu&cWc@NO(w>Z_felY5S$h;0-6nih?Quqs z%d@cMHO0c4mq#VN9c0F7dYxHVEf{Bnh5lozWT=tZTSF$;(o)7IEwHHe@~x}zE0c{9 z?8p~U*~XK;L4K+}rr$O{t1Euj)^q*HGi|o(22W}IV_OO;s$Pp^dBu3C(#ZeIH$PP1U~=j$2P0`Y#(cuz3H-|xbmA!V#<2QR-bG%_28Vu zL~r-%vd79J}-q=UG-q<&0sTwW`{QX)i9|Bt8N1AYH{VI+@!7a zZxZM`c}VyPd`S9`2KdhzEQ#<}SqD=qmnIk)lIiKkP4#%1(b4C_ca1En*K`VpQk^v2 zD$Tg*gmphuU7l`)xq{1EUF}A-@q>MD6kluBkQu3UTgdKdO)6lLV)?jCjQ2ooR!ZT@UqtK8(_NM5FTG1o()pNMZ zfJ}wD%_y%TMPrrL*t!1O;-iM-I1;b1qr7^^ZQA{i+Ax!O)k$@J0mdbDQ?7nguV)&C zQoO`Bqhw6R3d(;LLyvC->kg1EPxcHA7yNNAu(iz(a#cLc<71kRWl3D?NqxLB#f%nmtsu*Xi@6;+?Nk&dP#y9FI3XZf@RI0f| zoTrM_GrfNQJ)r)H(Dw0)#jB|DXoQAojqgWxs2VWm^1*nGNbOQEef}W#iMN-=yY#K1 zrikBhT4QFZhq=2-jy)f*u}8I@M-#rH24lG1>5zV` zkT=;cl`0>J*Ygcmn+YkjOt`s26*FVFtJ$#rzvPeCXpM`cqT#gzOUJ$#o;BVjI$XWQ zZ^&FU*n^Al9aP8thaneZ;x%^Qk`a@TpOy^N^78p5f-@5nOo_%&SvHGb;EAQuDr&(WyuA^6xM^yHO z#&AcPX0gi_8l&7@b3Co#v|YyaDR+idwpTX{|A*4gSw^MyXs$k9OnDD#Zq1S#x9`xS zdBJ3<2~yt+C#tE7$w zPcEgQ{1{=~CXU~KdHva>rC-Z^&>U@aN%(v?Kr^|8QamFB$rk;f(=ww2#|58VM!i!+QjfSE0sbphsTD?{Jh5%8jK9BOkk_&%9B51z|&!KE^Y-XYssyuY7?7#!7X5 zjgiSIYLC=)B%$(ymP{ntt;7B!KTqq$s)x30u1hlEB11s4K`ZwClIhyU_!xpaD!)~X zM7ve%RRnrSrQ2ge#|$!gk~m08#JuGY{P@!b=@@FiKt$0tG;>pxUUTB#DV51D0b7n=%RV+32( ztWT|&9|pF<_}Ii*K{#ElNpY; z&xgGFyCsa<%Ie?bOD8*F8vmMlE8~tXl~vq2hWa7;rr&vY>D9qWy8kHmumCoA1Kkpv zFjy{37(Ca1V8UQ&m=gwX&bn@~{sU75OV6AtWGHQ2q_uC@N$aYr_qrd7PfB7_2+I#s zh-)Mz(J6$bk=PW%a$yQlcmorZ#HJ9I#)2v;5{;0}q)a;8A|L-f7k+BnJU(TKO(875 z<`g1>irZi$2~BJYVQD2cg|J+hLj19T4!cto-DsR;!vAg~w}C?{XcNXc)p3*2%KftX zVv~`Rg{O?(UOv98FJ&XDQRKsSzfWuZG9xRU97BfWhzLHfa_;9<(V)#nF$W8cZkx$= zqEkk5lZTnx5;Q^+%N)}c?pldeh~>iE>Jfh3?J1+FZkC0&gUTwsg_sj-bC&P0Dk=(% z&~NnQa@m?;T^=RN`;|GfGJ{I2C@sGf*naVwScO{}i50fxLQ_b!l@d&>&08AxRk^JU zg^veXQ@+4-LmS`RQ@>4o!V+smmRGvV6bU@h38|T>H|ijPJGNFEQIL7s+x7#C6&$`+1~E}! z!Tbv@(hu%TOw!=zS-aTOvWMUlBb48E3O~;XEAZOQ!?y37e5gQt;3aX9^+LUDAS-O8@o`D+IU&I=R zvhodxEuS+-+PTvh;q!Eq-UIL~(Ib7PS~bjI%qhX|szEGsKx4ET7i)y^-Hcwl7zrN4 z8oqq)BKajZHIW)}Deaaw%R;k}uwZ5gj3K77H(yIGw1;x=M~{hj%+YBnrp{h-96Hhui*@;6r!Q1D;MG+_=BiMTGY>vB(+phObkb7j1h z@y~uRhMZfSbPySmCx&F)s>6TuFEhUJ`^`Zxywl$&^!?KRsL<3UCuGfY3#@1QZ((PFlG zdys}yXO5c2B;Kdv9IID$_^#a6Mn#IVkEWl&xy7hjhm1nWm!Oc2;Ge7LLo5r@DNFz6 zxbORJ2|tm{Y6CJ!DyUS{Nh4#D5|m$ArOq58mYPc4JH$RybERB|>89QAEpyW5V{;BF zvE!xet}z<&;{iwR0V2pUa!m5Ucui9K~#l9qIBl}|% zz7kJ=hLiLpO|Abb>jv;IHZ?C-SzYJeNt)#G$=`pLSs;x2yr!qrijzD)YmSDjoBD)x zZ4_B)#As8)9CQD-nVK#y`V`ZxD@XsDn|o)h8E59GgzS7DQ=85(3r(bo z>7(V&5zi@H(KnNgj%WMpGbV7e|- zIrBNa8F+lZq3wOZioTT-=>&sY=h4wbI4GmD46+X=pn?DP)3PB=0@ z9eh4nw_I@p-3TYZ3g}l{vf#2I+3wwOwfmQkkHK??s#}E^a_><4gPdMI0XwW==;9E+ zL%Wxbmma3)l2&FNVLMc+U_7+lq0(L^69ads@?87O#-l_&zL@|1AAanZ6OVfA+n};n zCAD9u$-x*usOgu<0xJrx92s`1CxrTC); z&r_|x$Ma4#J;dqlb9$$>liTWxW^0@e_Oy}wTIF_M^{PhbeB0{=p}BI$^0pgk%UOT& z*Ui#7dFEmlaOGGPSB-eu$11;2;u)a=t`Na$)jE{uj;X<7{HQ8aC$v<%)Mj*C`F2^e zfWpt3o-Vy0|7}9kd!^-escF?Ev|Z}0gf>~ZUnR6y6;O;|?yFiD4)5Kn?p3l{W4DSa zM#1&iZN8=#ld9j|`md(XlG(eShfPFOyrUXib87r52*+%Ko{vgMPO3#U{I6M9aRC zZgZ7BSHo%&aMRD#Ln7c^l`Z0LooZcx@Q>h6RuYf0-Uu8t`KYv2y{G)0dMA3%RQD2u zpLDO)96DBBa=B{z%@^YJvhP*lwFtk;UbVLbX7jx&?mFc$WUqR{wa;{X$~vqTF8#pB~eoz%|P#&rGscc~c6SPlFDv4QrpPF`q;F|AKaa{ZK+-D`E zu;XIH&+DdqM{v5f#;V0(gtJVgE`_mMrMpR8olzC!>b`0r#ygG*p~_tQ)wEL7e3kvy z(3d~MroL0Ae7b?KbmwThUq#g>wy|jBA@P~l<@|VGs@)0p(buV)68^oGKTNzs+icc)zN)+#>I65>5#x z&xPXDA9#1vi<@(s&<4aQ|1yL&KhFGpYD~IWE0*?cKWA`!NuE$?>JZE?YG@hi%KIQM ze3Ix1o9}*FIy}5pyk5nFDxwYncRHv}5u*DfHC_B|R`2lV^9}x_TUEa_Fz4F44-&Ms zSL!-?4&Os6ye#Eb?vQy(R7}?Mleaqxb*@AN(otEkw>zZ#>JsocHNPwYuUD<>Vq8;a zxpFxVTRmgY_<>b#`Slu!H{I6qA6A?3=BRmCWh+NcqtKLX<<=DzhgA4uvArX}2s%~O zD@TaCR97)>tAKi>E7cLT1aGd;BUV#OpDVJ_t7#|YF|)j{$jDpch>H1%qHKLcJ(39e zp(*S4{u$aH3qCQYFq*tCCqwiUm9sn{uTZ7L*saR_NG)GfJ-KrIj4v0yetj}8GP@yXQi(*fOA zeKobFycZ;oPRP%%>dif-vcuY1cj9}iC#1P_DTzJjK;elB_=zl9!^xk$*DkPO$JD%^ zn0-%Hr$uX_>h%y~J>T+U=4^SjANSbzMK$6+Pt^5I(z>^0F#_H6t-&7pt-H6w?@N{A z0Tol`OI5ar(@WKVz`SLp>i&T4VBcr$psd@XeUDL(9`Gm|w;bGXe1;vfRv|ls4-K!E zy_$4Xb2G}{%}TN^%rISuj*chQ3(D3Nb<&z`+u=gswv`MTDild9s+#`e(I zGuA?-@UpuzpS&LGV-~I1ROGBE*+O`JBTM1X2d&HDLMM>f25X(dk66czS)iA*LozIF z6LC}CkP=6qFwL5P$=N3L+>>LpC_CCI4OecK1 zMD)Z#-%T&8p^vE?Yu=(ZKc*xt7Ztjk$6DnYJDu*^)jM>2;x1UX+8kqgzMa4ANrFd- zS5?pxs`h8~_!VKjRl}c96OF%COP-R;H`$G z1ZZD6@?KIg9t8EipOVjva6U*m{uzth(%-7`&xxk`w<^_h`hk6|{rGJC)++y11$EG!j^Z$3(}DJvWiQ>z_v(De}P9^Y@7a9)D6_M=RcjbI6=ziq#)CFMs#rw zzNR++!mi$!Yu3JB_7oX>?(Xc|EIzrbagp0lcty{-_a8OyiHmMWr_`HY*lxUsf^0X& z^!{|`{!k+_-mAxT>mJv;^V`8G+j!By?J~%DU4^}*nUzFCMzu^;l5}~QcJt+UjmFni z3>qPW(8!C%$`lbpCzj0GI9_A1b~$uD-P`p~>Ya*rIigO#qzW#hAOmeRHD}C)ch_>o zD?C^3zp^c0mj#tt_g4lQtLhy6Zm6ht6sXk>9m8&@X}?nqy5+s!nsgxlMBCP~qWIU8 zRn%{^3GG8SjlWORd$Ig)`1uzzZ%6d6CFi0l`Uf5Q-^sgOH~*HWf2~;my&(UcMC*|M zNg4i`wGry+yZBb<{%|(a3WwX>+0C7SGB!z*balUL-OO7y{4sRJ7box0hV|Uq>cD>v zAMVbqJ|j_a;*vch$d+fT};*b}}3T@2U0f+{PZ>vzCq-7DQKW7wd1m zRB7I9-l*&DBu!Vk*(@E&eo}r8#uICRj2~C*LDd?x0z*DL4i5Oqnif=DH=^6v0mozy zQ;(2({If^M;VQr*!Ek5~Z1ym2kAq?N)piexPRHzMdSAH=QrPRhicH4vse8jXH8m*{ z&PDgF3FpgWJL`1K`Ia})?1Hso_c?%qtWH9!{kr^{-?A@=SFqB z*FjJFZ);&qBE5GEk#1Yt{4nZXdWkx<);* zT49ym4YRLoT55#Dy`Y=**Z6C>VY;%?2xP0hY=mR)*N>B3k5|zB;m@vM*KbS;>UXrN zlg(K~MP_j((K8{}m(Q%eJfq-mtB?7m?4!$ur3+gWk+~>RJ(;rRr}h-ro{H*8tXhlbPQ zCa(Js`y72?t$8N@Sh4I!^J>U8gv@40*&X#%yuVf{3Do8K#k!$;y?A$eM!F}m_|jdr zk>bzZ!cf2AI#qx;>-q>`kkf_SmG zWP0az$KXHIlk~_-zELSMuyo@A#iR_BsQobVl}eqF`q`&G%IM^yzi#fTWkzR!^&VA5 zwLGIUyKgl&ck?C1QWKV43C#K1LE@HQqfqGWP`{qhnZ>7>ySw@5>t&BQBQmB~nHv@6 z`c}cD%S8BhRI5zR%-P8D`%Q&6lm2Wf=y-<=Hmon{4%%E{RCwv7*30SU%Ea60ZjKH9 zrQdegaq`kDVhNCX3UIrrHb^X%p>AYy?qpFGJ2$g)l)L+#SYIzETYzS#@Kf)yIkT#< zS)C0WXVR-HS!r@l)N~)Gc1V+%b%yRfJ5);_ioVR3R?%0DEt&g55+%>PrKhlxF+B$T zC1Ct#l_;F+l3h+5cP{>JBLzj8^ZBrOWs6u&>pXoZMwe<*S@jsJ(15 z7m``{McbdgWT5a@A_Jr%0G|O#oiSg&_Rv>f0gPZl2FE9Ey&{xb5!+L_w&%;^d7D9%#6wl%aI-?G<4wK;cBs`K#TQb-$U0g3=H6;r{tx6d ze&^TP@s*zoQn<3`YY(XAhNG@&Z?&<2Gu&ROW-jOqet#3)+6>R2p7=3#|79#DEv*_m zgXR<>XVy|zukF2bDjoeoRAf;iHcK{xLbKs}no?V`Z4jEswt+l~p=7R#WY;xG|V(C(`^R9~|!Jd(nnZ5^P_6>Bq{FSv|^6R=*{i_dq znvCpO&y(CntD^pl16IZo>>OBWu#%ElewOo>=Oft=Bzk8t3-f?_=O2Gdu^vzBkmOlR zx>9OH0PUP@lXC&wqCUKBqT}QVO(5CPHSvG?L5%x#)syElu0)=?=o`rYRZm@f=JA|G z=7)j1JyzALw|hTzua_v+gBbVIY8}sGn6Lczsmp)-sY`aAru_G*OE#L{2cNp|M#@!r zTJzsWEc}q+|A9v=?)9nc|B**5SsnlWvz5PJGA5kin77RMyOMqTkDmwqWs>{Jy|{?_B?VU*Ny*3;eV91^)kiGqYdBQu`YwSjqpnS&`VS>bA$t|~__tvDYy=)DpWx{u$@chfA8hO>Ue%8s(pj)BxYGY&ecPo~c zkD<85tUliT`&=I_VQWsmz2FvBUfpW!oM#5vge{Se%d7p(St7Q_M{;}wy_~wE!Iv}o znmbD7@jA!V$tKR)jvnPz)~4(Mey%z;<&yI_b~a_{9HD+{>g>qR>*_UQPx78x+>96P z%2cto-ja>4l=S-YW%h4%88oZoA{)=KMLa%BbM~AK$B<&510L(-W)>IJZPjNzm`dSh)^#l_g2Cc^cK|Y}30 zRSWXRdqpi+surp4Zoa+ezP9z@&Qn9Sp(w9yvvvsA9a!fEuw^TCh#UR`h|^%-PIii#Rl;C3}TgYQ^4Z>Er;O z#w@M$B6Y+ze(qL+G&^b zq1!4RIP)wb-epJ=wE(|9v(b>JWqane_o_Cl%i(y9&FXA>_VVX9x4PricGI`D8DaXs1r7lDN7#Kv`|Yr&^vOsP&Q$ByYF%gQ z)7tD;p?TRp{iutxgsUXqzLECu*~c({eL&f7UyUYia237M$^WJk9%QbM1bh z{pOrWPTqOaPP^Z}(R#}6vK_Yc=33&s{Y}HYbvsPzHq1wj=;|!X`zZ&zI?G|WcXMWs zuDRKIK-PEGR~LT$xpIvrTuX1+)pq$nx8{S_b>do@ORKZ(tG(V+=6Ltk*132GKAp3* z&acHoLbq9|`lR#hZ(Hr!QMxkM;{V*?d0$`o`l?qWuEqcCM_<&>zH3%Y`#w>vd-rMY zcB*38J+G^^s2ErBlr_eGIbrJ`hwIi`S(9tY_U*nSI;U+{uyE* zbRN_`ws4fQt<%*vp{!_;q6x3n(mF}<#ZK+#3`mkf2HUY>j0YRppSN)_Ws;#7rVoIz^LFem4|EOVBPJvPi4>8>JToL;db zmN~yns_ve4`lzjgotf3H+t>4YtMg&K-mmBT{`NS|&*lD z^r#uxsp`wbW}dPCv<(ERUk(NWVf1b^2F;DKdNckI{Kxpi(Rb07XpFDV5HH^*Wb;og znm#dU#-zXk-#ch);@eVtD|8mxwljgk{IVIdrjx0KjmRhB2>3%3*v9SXbjf}%+E!f& zQPYa}Cl(!t%KynfDsnpUYVyqd^1=doG@~@%>(?Q)OI17W0;f~Pk0d!1x5=@ket}o6 zWQq`-e$i9 zuSR@?m!9np=*0H6xCc;`v(_KFkw@BcrcNuUD5W!WKtY@ezK3 z<%Oj)Dkc{ON()LV3d+g@7k9LyI2Ba^V|<-_w4IZ|c+E{&S>ep7RCIeMTY-zJ{{<5! zBuyzBKmBT-D@?M${^?riw$Z^Nm{GZ`95E+!O4xi@h%q})v z{*;SUOd$H$K!Cnh{{+|K-;JuB6{SVg8wkv-m_B>LjEMzZ{rnwJ4L~zg<+rx})asdK z)4T$IJ<3*aj;~B>LDIzWfd;%flwUS``h=-P<$I2DbDE`%F9xgsXQ0}oGm0lqE}9ew zjK{0|5vb~$J~6+vG=Fx$Il&hH%<(o~#q^>Q)jMTo!DKafQdv?#WqE0S&l7FF<4<(I zY<6_j(j=Sh6I87jUz$ImfbN}tlI3@yYDQ#_7Z;RHF6a>m+(>#gqo^!t^0XP_18<*f z(_ap61D}*%R<8O>3o6QrCe03vI>k=+k;%6DNhrhFrMlG5@FxpQ<6B-l(;KeYRH6p; z?cqKUI{NtkYSYbSd8r)-HjjIJ^OMdRb-HcNq@u|crSxYjUb8r%Fn`)q%AcBIyTwVA zG-+D?w`j`?}WG6}~R^b*`_)R;#K<`Jg|l)ALkcJNw$o*Wb>z>-`H~-}QBauaEm$ zI%C?j3YN-gg98D!T6H|imZ<)7kR9w@sQ5;-eV{5({Qv>Zmg=j}W6?=yXEe*_iD(ym z6v|mq{pmT3G`hjp`%&e;231AVd_KhYJy9LVzh>AqGqGTN#pFOBP|R)$xO2iSkNJ~% zYNebrDrOb{zsGBDZSnQFboYVq(N(QR+G+fAge~YHqBUZ9d3HyX6-_Rh&Xq~iJafj3 zX;NLA=95l4W)7-lP%+(3aoPE{VeEq$(=I9qTr}EtY#OR7#s#RF@jL0rKfd}$8>rrm zDxwpr21L$?$TedOT-S!l7uY?00jdUg2Lk;&^FrH@(@^o}QEl-!w>GBJS^dea71smR ziaNxv<+oAx!ZDm(qvmxTZ~b4WO4*5Oe!d&!23sCiR65Z=&lph+e?C4I9f4{TR&r)0 zh%*CSL#TDGgYnbJDpdQP>A zUHen=z70tT6KZy&ruCglCL)w+ii3l=~YXHpI<|kKdme<7q2bNU6j|# zQ&CO9#-b3|i!Ti}WFKUirsMRdwXxiX4EEb18BF z`UhHdo@}g;F&52NgdQ?kcy{`q+%Ztj3SZ-xyNwW&`%S+wahn`$@EX8Tr zo=bIFfW1&H@39N)cBn&D%6-1?;>X9%wasaP*Xj1d<#r)%M^(-$RJ-m@RM(=psJ7E2 zRNJmU+FAE~Nd%N34pqVcsujJ53gjoum^MRu_BH&G_@_}FF3V6g^uil#cdzl|FG7`n zjPLuQD(56r>5oRW^Frt$fj*oI{=i>~>byu{eQDs|ckNcKM|Ci0%Z+ChO)M()ZeiZP z$^E$XaaF60t-r$(Tkf=ii>5POfmpnz;ZHw48n5Cf78J}(Dk=-Cfa@gq*Te3-TW!9j z#H$&VXd+sSsu__RsY&|sR53Fr(BgI*@%wF7XC_UXF(JR4TBh+upgi!qA3v4?)bT}2 zZ3W!Ml$RGw4Db!)A~m>|Ti>Q#)wg%p*n+Z|r3D3nKn}Sy+(mcV=BMG+tYLRqAGuLF z5w2r09@TpK^=3QY-=I3U{;0JYiQfTN`b=upFuj7Obyd|l_t*-jlqJp1FP;|2FP%Iy zzqG6%4X%pI@3lG^Rl&h!wjq%d@i_`ox(87;q(iQf|?}Lw5g?Ke6GSScAm9HAr4aXbg zSGuYAuv^f!%Q3${Z1>#s{PK#@{Aq!^9}v_C=&|mu zww;`Tu5;MQRgsHej%7mxvPM$px%0=W24`+gK!u8$iUD* zb=6xo;rFPf`V&-Z_gz#=V>+sPkc%c2O)H>)ky~y2eW;cQ-_0f!u~p0MSK`1JHyqcg z>H>19`c3cJ>Mx#ERIUdH3%1!K*=)B9tO;z_o)tb5;cBd4QF9AOc zJse$2xoTYo{xI}ZRO6aKy|k*Ty8J`CmHtAN;Ad15HyUk^W}+IG&S)a~+Xq(PMUTZl zjq2KQH>&g_KCunD5`Q$l2-US>5UOkNV73G#710f@YuFmVWE= zVpKi)6|RaF?6DOEzp%IP`|)b{!Y}PLCXA}#Gf*|4FRGCn?#FjR6RH3|P=G4jj;i2Y z-`JLJ@bwW?@y6GSQBCL;(rai#-`k2WqXAmN#i$lSPlj0W3;mTf9n}boK;zLf&?}Em9_y_8#Wv{ppY77- zuB0ldJb!Z8LJHR0M~>xLzRyED!E;b8n&N^Pvsl@IGw@op$Dx{%cBl@PqRG=|l(O~@ z#jC+O&Zo>MnjX38{{EXCp|WZDWrZqeZ?rA=>EGSs4We=a-s78Jxsu?OMEuB%y6Oi`;j+bsWTC7|8YAMK<9P)$9T(Q>@>=1JM)EqtKJl{mcM73?BQ8nPx zwN_t3kH#-W)xc`BBRUn0L(fIEsm}2I>FphFZFa$H>Y`DV`~5R^7w$lnZ__i3zZTUK zKDZ853n!s!c`sBwNklc5ZBfnTA5Yr?Uh?%WRQcwk+U8|GKOfav$n^b*zIRZK*!HKY zY(+0}X;h2uLe=xxsD}6)R6RHrRfB$8V@IGK)ndBe*Anz}{F&%!Xj@bje7D+8*=AI0 z<5u((bSkPTO|2rJxi}P6fuF6i6+MrtNB5)c(M9Ny=wzP{Ks)2Rqnes1pMUnGoth1( z7TvwRUXE%}PDR_HgVBy?Rf-Qfphp3|TWJ$S4#;Ql@$h?4HRLk%2=wKX9q;C&c*b;& zGj4eB+UFB0N=x+|@*j9@%Px=G^q)Rqb&_9R7D~gbs!t-Io*m&w{7OPKWDBa6{sZlR z-izu$ydI51EB*M>eIL17irk?^?#Lo{RzIKScn9c9H0W^hKY$*k{r|`5j<*O4rk7L{ zl@^psUV~>&ot(sXpMZZ` z6}SFhw{+rekVYnpcY>U~8>DlRK7n$DM=vhpo(U1rMjXL?t> z_4pY4tO>JX}K!`7;4U{p{Y~gtj8%vuI1S z^h$djh`ij{nQ3|Cd2{6DPUN05@^U8f@+b1#`H=yRH!G0`&M#0-2liSuXO6c{_Gj4! zOi1RC=2JSMm!}hXq+3epQa8U-r{KFe?&Y0M4=%}ZpXzjaLfs%cdXYR63TI3$;(7n8 zgY6U^?Z$R)*M2Ffv~n_s*ruQ1Ym%GMxt-I;9o6~d>|=)7+6^_S`&Xe_9N)p^HALDz zlTeN8Q^V|h{ORikxQ45bTi>};Rq{y3+oeaK+5>-#aJ(hB8`aX>;_H*B_R3wTN?+(J z%goy=Jo4I`1W3vwxjUUsZh^0o`S=hNf~K9XJZFW}iCN z4$O)%wp+9Lwpm7HXJ6oW-8~IezQ|Xt3Db%yiinTA0iB(kIvpGMm~uLi&Rd2(dD)|H ztZ4DYisb}-Yn&Zx-a2wGKRfWW?_WYSp6^hAj+s)o{V{jC&mXhL9oO{~cS+Z7?nm9a zJ^omiH=?4SJ+Eke@XxnyaG&qG@A2sFHw2?+-h6EPy<7Hu{`is;X2yr#2y%Bt5n(qs zHzgbw72x3p_sVWfUTU;&n!CCEQXJ!M>X#n;Im(U6Omq6X*_r9#dCdZWG*UNrV=GeK zO_25>TUwM#bB4Ov{nMQr-39&AL$4Ck-yM|O+v((n2c$a-+-&~c>Mr2lBiu~`(nEPI z0)b)fpaH#|hurL}^x#h|+|5~O&hc(IJ3Ty!`!-e8%xlZN?t<*}&}Xo|ZmaCxq2rjw zGn6%SF`*oV!Y_)E!*Oc{q=Z^BZ)bTa1`+DxZprK&T0p3WyM?q*6FP~s(QbWaO1J|{ zus<%~=Jrns=i_WA=~D2{mg;}_J0KgZI^@QZA%Qslo3MxW1mN$xaFH z#c6y)-e{*Yhf0Yf-xaviaV@;KmmA}1UPyHgbr)8qIl1np!Rg^Dt^EY2VKD92|%^i>u{scD|=gmntnH7GPtur?zREA>+U+xo4AQjB` z(&(>*adZfG0$W%k(87()P6^$J>)|fa2rJZ>>Svrr&F0Bw^QjtJ=`}c&9_5YidYlHq zHt#Q-x?@xJI?N6NYmS;{;2K*My#FvaXGmIjC$I+@0&Y$Jlwj+_-Oa%198D_4t39}$ zx6`?4;RLRf$_`|fM&VSgw;+OxMfRvaH%2aa3Mg!IN~6asF2R!Z=( zBixvwX~94{HwPKf&RvK+*3R9GeAmv68I~42C*I8&mKM4rp1zw!&7D3@yt{c=TDTjh zj%ub;ucSKd-JE`D;dQVPFy_7?HTpqvIyRFq?!)u4>Sk6N= zjU*#d!c{oiv)J=ef=_pFb4I2G|Lou{L@qhX-Hg;7<;LWtg%@$xqqRV-tc)Evf6AyR z;b?brURtQ)Xcnb5YIu!8UL}l1a9AgI;i$CW@=osNG3jjLnDf)ZT{_zVW`rniG)|2Q zdhRydDLCR-rmu8%V@9Wi9PY}}RCq8w!Cg2yEm)c0Zbp_TxG`hWf(;37&X}}Nk7MYM z+bXkncsd~!>CIXA5u65wjhUGe`WnZUrlBWs8-Es@UNIUM<1`v}DLmQ5jkzE#e9WdNNeLgGXzyN|xiuH0gtKsV?ZggA305b%3olFyzW`Ko0xkm` z4jyNl6LM=$|!!y{XX{f7mH_ z^YLy@L0YIK7o>W6rz|CBh`2o(ZBQ4+~PjMGc zNefOp#odhDd5Rk|H7&fAerJ+_r8XiZbX+psbQhi1Cz^oP6}yv`K8;KF5^;QnfA!;- z7Zzep4|n0TwD9GeXKJE7nx5(5#uTT8LZ{Lix7GOG;ogLX5p8GvdYsNb+RU0*L!zBPgP7=CoTiaOopG-@&CQv?i3e1-nwy#)3=X#&-k_SARO>VVb~r8}&TauU z_3@tW=90AVCw>O+LLBNyYP%*TC`8tPsqaleI2GjW!O#~3hv-5b?v)a`WWpP9o)*5AXw_lY-A0^x$3=y{Mzh?qyf`w1N8@bM zHRgBYG-9;*uheFk{_d8My~8VsZ0wmfMj^>Vrn!-RWm{*Dd8V*gFII=BsH2f$kfv+ zE}P*hMoxog-E#zU8eN!ns(cDzURcYaGfNCTQJTt{P(+yXr2QNL_&8bcc&0zx#b_Z4W4!uI?952+4-D5)&8bgmZ zhJGW&JsvqS2QcYgXi;NmYh$P*>%qnrG=?5-4DD?UotYg;H;)iEscK7OD3*b-Y-D3- zDIu7#hh@M#whc zRYEr13Bw}nN3$Fjh2 z)P-a6B8Q@ONIp(28Iaj}8PTd56PqgzQetosr_CxY;%7p-ab5wio-PLgZx& zb9eFm7&qp|wD3t|Z67#&xoo&Nn}k+}w&2e4j)~S61OhCN>h|76aUo9o*xOj4%{cFS zP`KHJb{x6K?w1nE!g;q{;j0L#RK}2*cpZ0O)Vhp|oQJA$GA`BKGOTy_PC`04>`mBP zIL#0jH^w$9-`-1b>op@Kd@hb{rP(9S;8eQ3!tcbfS-9@R4sm+9n{Q4F-ZI{eF=^rG z36Y*M%g$Nu0+SwI4(sdXtuIVff>f{Anh7aRXE%FEdg!u=tVZu1@p(dO5L3@_`~5^W z=9aWjW-~unSlTd%RmFewtaW~zX z9(tls-$nV77>+Hnbw|0eb*b)x+tP#A7P*^mOAGESa${~!3m-crGN9`61vsq``$eY~ zr-9@9@sO0zR@^wR1-VoC%}1}^2MKY@KwG{dbe38Wo;1zwGw+BEE}7=$+>sXk6PRNQ zUd~tTJJQ4V7u(UbGasDJa#QwD8X>R9@MVNFY%SfI@u|@mJJqqY^LN}iMA_?e)(kBV z#wK{x40rQgY2hsZU5bcidHyxS&8bZbrOagExr1tZhpr+t$P2yI80uUSVa1K1rw9#n z7uEJ@RvHLodl?2dhD>AVqsCD8GB4eIefY&%Kf}|Fp=RX~Hi*ywueh5ULpvHn$5cdO ziwI?U=^kke{n!{vy(khpClU(2aFLsHe_A+dR%FF7F8F^9C=Cq#I1gvYQDF_VRw(7<XlA2+cvuH_<5PS4eRm>P}Yy!5lx;|}!a zgzF-O#7^ZA7tU6$>b5qBAt$iYrd{bnOxMd2@!1W}qm7Xhx?!~2=MQxlu(S*)`+q?Tti|sgY z4V_GzaXlL2=HUk7qTSe`DNee(>ACda3&xFkJ}rF065C`p0FNKaaHqJ7F6bS;hmeNH zd$th%(0BGeyn~y)ES>?p-)eNrZEc^;=hvom=3U3P$YwoKeuoZ{?vH-j7E zIa(e3{4RI%#uL%hvyN}iNNG? z{%pG2-TWN43P8<>{a!rw9@{i~N3sT|4l*z_<~y85mn}zkyWShgr#pmd-`Pd^Jnk$~ zI`*N#vdF6Nh8~wf+@Tu!(4{zT8^`qyKC#Tr*^(A+d!M~?VPTe}IAh#RThhbJVCQ+w z&_kHE_uH`yyEW(X&;qA%wuk&8TtCmTXTtUWl(PBqNP}rcs6UPyJRX-^Mkw3MQ!^?h z`0{c$=8d#)+zNY9_O?#=C!Ad_Y@~4I1NJntaqr-Uk=?rEAGG_yx|ukQt=%1u;WU|H z@1*|{H=;3L--qnf+I%7C* z=^cuBgj8A?&R{qD?eyRSkGKopP78kih`af19(Fz&;8%RTToY5Av2OO(^so!lT(eXd zNary-y9p^75DroVK z$dmM37E@2$Igym1xj6d-R zzlc*eIYzed!1g6Oi8kMTxB*_tTH4>>l#ewy&iktNUV3=u%l5IH-N9RNJxIY0=2pGM zMyAqj#Y35~gfww>Gu?wzIkt|SxEz&ga$~ukY_fgiXuW`Yflcnh_tU}~faxS@?VWqg zHb*K^^H2Ak-I;~BfuwBdJ*#_sv%7g`niF=zAEbwSzhajJC67!ARpI)$i!SUP-b2XW z%mY%Kquua_>CPxO`@{6`t*YeJLo8`RAuYWzgvQOR!sYtH8%`bjq8Q8 zmG8i55wIj?r$%qJ6U=u6vQ5M(8@GA5^|# z+QpqoqkHO~+=(Ca?%j*4AD$YG8P}Nbcie#@r+;F*ZukF7xQUG^kN?!3KeoF0xC5o^ z!Knh<=)B$bHru9r5vL;}##_DZKZ{&^)P*Iu5smrUer^YeI}j#o1a6o&Sq~7>Fxyrg zw#T;EyMTmhaXbfMi6njz2uvpxoRVFf2F|wO-Y*;9jS;_$gTE8jy@h= zgofh$FUW)j+w$nzcAWCqA!)zg#&Ouw;>oy4#Ch-C!&`9uY(Azw_E-DM;jMInh0eld zdC!Iy{_1Z2GcCLWpmWz2oA8@GknER%;W({CW{&P%k4yC`(U*~zaXoQ7!QzJhciUI` zLrXJp8b3P@vv6vS?dwL|S&eZqf7mOIJ(LIHl#iQws+)s5$#eC*hkX#2g<~$WQ=>6` zFt*Jn|7oAPatwc%621(lR&wd)oeM_i8mIS`l+brLUR`i=-uo}xUb`}`#o6K*p{Sq# z3YeD7oHN6H1CGC>)ttL=Dwj7elT*UGacMa3<}cjEalCQjjSY8HV{r#MT#Fk`O1nOP z$DMB-Q!^syczeyZ6*Gn?Z<~bM@=L{9t=^R^G#!`fE*jZ8bRVG+y3mF~{F?C@?xH1q zq6w&mW4#_@@-f`;a}XULa(Lj#yR7gOLbf~|98coTBNg|&G&s7soz&LN!Cge0eHrzmA7`(I1<{WG6hif_$4w)pT>~eF9q(|a6jqCi z)7rIHl>N9FxI^5U3wg*A;{>j3%=a~}2-nio_oSUIodb_HLThnTytj5;521AL2Jsp~ z{-H-bdvW8*ZQD1pmE#?`wkI2Kg~WMn2qhhg^G0nkp#$x9THD^+UXk z1C-mw30wxZsb0saced7^v9?yb?(V@A6UXI+_O)qiN0YVsVrn$TW~*U)e~r7Cgfw<= zYV=`_cfqn{yo0MG%APBO54Y|0W+e0!?kcZMed8L(_=ePIjDPW^+Fx-aDS$gIT<#IJ zQ|v6<8sFJLN@!;nsJ*|OgDdqaRtvwwXhtqLt7wIWSI^H$F?)hq*N}<)%{t8Yzj&{D48r{KGZpVKV zPGfBEzFxx3#M!vhk8%Q+;OynS4yO}_IEvhlQ^Rl!M0!VC-C^GM=!H05a`FiCZ9)gu zN8h7u6YU_}fKwBBq{Mgc9k}znJV~8w`{*)zZx&8F(LTD^gwu|qBRtwn@7#2zAI0Sm z$Dq=p{Wz^Y+rIn+yRN)d5PBWQYg^ja@fcfzT^);Xnljt_ziH*+P}^-2ew6eSC*z1RZnL& z;0`pUS2x>eiX?6YPD5-P7S}y8Tsl@JjSV$Q- zU8`*`F2S9Sv$faZbf?9$f${w60ZwJw+S8J)v-uX{bf;zG-ot6sD23Wj?_o#V#x2At zWiwMVn5o98w|3NeoN9Y($F~}%-r95H9h~~Wg^h20ZBMhCZ(Q$lg4v#q`R&9MrR%Jy^_?!f%CPHB40 z<^isEt>=2qGq9U+y4HKoAHr|rG+UfJd{<1hjpAC#0L{c{FWUU*j~W zwv>Kpb~^2%xDls;^UlL?1MW1Ocdr*pOlMJO138zNa5Bw_>g#wnZb#_$+{rQv1Q!6c zv$Vs55MPXu;%E8Lc21z=T|j)1I(5lrQpA%AJ?ewSg zvtw@eVZM^ZK=^1O&lFqk`mr?AYo=|bbs4# zTShrf^KB>W37pF3Nf7JgXPjT+Z>iA(Y?S?uQi@aeD4)T538#y3z{DO;BeU#4P$Ua# zAkN$T-WS%}a2Jr04MM)?Y}+}z1_tAF7ticyLK>Y?K83RjF_wGPuW|fJ5Ld0!ax7<< zOH#s<8Xe!ULMw15xr=!F|1lwLOG@Tf;5(aeAC^eoK-)|^4%gvQy(FscCEs}s3q_y9 z0`z98k3zN}-U81w3(j=XgHN4fHlNAr`i@8yL4g#}b&&52cX_GNI6EJ)AMiVJIJ;Ez z68c9!&bxmPCl0m~f*X?J6qp6+wDl&Sinb@=dpO<0c-;w~d~W0>MsqeBcOYe5qpRn8 z$rm`~qZA(93>gwRt29m5`Hts_;uX`;>PK-14UkbNpY7+CHiNRA-r+3-bz*u)l+)FObLhgP z(YBR5@qQyET#M6+WNf+CZN=%j#k_Gf>Nv)3FFN~JYP4^7!CK)N;Zd5c8CH zp>^URCL^PKk} z%L{qloSDmC5>$S^vwI(@+R2@k_xU>w6I0;FOO=1J@Bcfh^0}aU`M5!^@+zF_7a(oU zPl@lP8mMx9n( zGq0ZKL}#c`i}+FA>-f>F(u6dLKXfG}V^)I`4U`4d{Qn7udj$K222>z2s4=8pKWfXz<_UNAYj*qmNV- zZk6HFR2|cCnA0w-8Ee1LLGeWsaZRMWK@;kz4{dhv;gpB(ECUgxhfs$swQ1vOQneLnv?RfB)`Srn~PR5Midc>(n_ ztY-Vl1@?TWU6DvDA4`?*P*m-T^?h5E{{n~ekLrz=@m1+M`2OE%3zIb3X_ujt$NI^e zYHRqZaJ4ekk8i4~whuhYETR~5`)DWH`^Vc}ef3gZhBhTlfeJ)jSY|ua62zcN^KS5K~roYJN|9SO~xBq;>wAydDMqm~lXoFtu zcXR9!n3aH>uQL)dwh<{e0J^Z7Bze2S; zzVp*HRmJ}RS9w38O0w72Ur@#W>igfNT7Q4~K(Pb^L0_ApDky~VUqFAvKouY2Ya2m6 zQsrxlYNXnsiaOHQj;P8x+Sg9Lc1FGRcMJjk3ncQ73OL^PNvJZOq)1;iH9dXbRF&@x zKYedM{_j*BNGHAvIzEc|*Fj$BXOOBVQ&3er)#v|Cm2R4!Z@QoU->K5i@Y9z>`TM^F zpomgm%X}?I^^q#R0#yTN`CKYq>HDTCKHKL~1uylzRPl2($3fFE-@nnU_T!tX>bur3${}d#S$H)uUPg?`kHZ3491p1-tx&QWgBMub=qwyHTyVFMa(E)#vY2Ywkzl zRnAX-I;n!c`2LrwNB~vBeZFt1y2`dtY5z@CN5X#kzf;}$#u0xnTD8VcC{=K+@1-&L zmr*rrqpzF%c&YeiR3rGN&;Ok&{ab!IDZVQ3HUZ7WdwxVy)wbUS*N}ha$NxK31)uxr zr1E=w{leETef`SQu7SYUKKKUJN2*!)-uIrG%!&T9h#!3{Rqz+z|D9?k_WSAn@Y6L_ z4P1!Msk&%X>0?l}yQMnqtFpEZF<43v>nA+SPk1=0f$HFAXsRmcD4#b~RoDrx&C<<} zm#U%@QRPeW`2p=>BTgbh4LQY6AXR~nY75*6P{ALfx)1x#PbgJE zKl=KMufL%x@K00){e|iyl~?Pe4yybid6P7mV_exHlXrmFI$`5d)_&;$@|6DVOZ302VyRDPzP zv8jre_*|;=Wq$f{pG%dl!q4rLRx=y2{tpzOM20 zDPN!V^_k`rA2e}Oole=RXT8r~MAeQ>zTfQYE55$!>lR;M^YwM9shH}#t}n@fudvc@ z(2$ukjeVq2|HNv_gPca%KQTI${zWx{EqyMPKLl02HolKVwOYdSoC9DD}`~c?M(D*ctdzsirl+V=}keWc2_(Dza`>{|3* z^b}gJ2A*0)KoLDrwdf2~2|n=oM?T+;s;sYl-+=1VRNLaCxB#i*X1WmhDj)Lwzv`Jy zff6?N6E;;fFxux*`54qObEf-8acfy{8$Y@&s?Xo4E;UE{@l935cJ}$-sp60E1%&KpH$^!`Tl^~Q}AyhY(d$6 z2C0JQ@sA1`=JTei2Is@opdwTanc}CD%1=d=zZlh0Ejh$bP$D&hW;*S7Kh81gfc?ymujN#Lsh}UKL0z_7JZC(>EmI2k@5oc-0Upj`QGm(PP8{Ft4XBM zSmPJ=U#apB@PAZj4nN8_kRN@d;)7-Q{GF;ZOs!Uam%#^L1|NJGeDGy3XMvu89DEsk@MZA9m%)1K&gbCEU7>l5qdVOm%#c+bszelcnPfXRE~;1@Df;5{@}~tz`>Wn zfxy9+!QTEj_%c{8g%7?A=4d?lGWg)jVDGK;|JBQ2?|*^z|NAm{)qdBxx4GGSt25B- zxz%Z92HoZibmGh{w>cxtlG^}(2(&ZVw*#_n2RwW`puO2Iuuov@Qa}f@VkuzxQb4Oa z03FS!I{*8<{e0dr~r zUCkzejRHw`1G<~pcLOT#1}uFaaDwT651`vUfJOHJlFW92Z31WB3pm*u zNj4cf0jbLX%XR`zH4OrL1crYA=xOG@4p?%Z%6J2iVivps$hsf!jX*Ecs~)gVU}-&| zx9P4d%a;QlRvBlS{T~4GRshC+2uL?8J_JNR0LWYh$S^g_0ILP|3Y={+?gJD)2v~L> zAk#Dm#6JWWem`Jj{pi*00x;lfo+dChngcE zaB`e;P5uLb`Hzxf!vmzqHL(u@QXd0UJO~(S)(PwpNPGw|+>|^7SW*YrDlpO{Jj_2? z{{YN;7%+nCY#!S05%GQ9tRYe+{Xcxs{tzorW)r7K({r3 zf+qmQrcPj+K-@~e43obSF#jpQ27wY2`y?RsX+Xu3fHJdAV2?oJDnNxPSp`_~3}CCk zER(PrkhK;tZ#7`HsTbHM&~pvo5;J!VVEMCv-2!t=@>77k=KzbJ0#un@0@2R{GM@&_ zH8oEIRtxMExWZ&S11MYvSoREHo@o$>e*rLjE#PWXyB4rfAoMI?fysRqP`MtkQs5fn zJO}9ZBB0y8uuUNDdBF81|9Qat4S)>-H70f)AoV3c#X5j%)(PwpNPGdX*p$2g zSn@JptAH^H>j7CC0rS=aZZY)&`viKv2)ND6eG#yH6JWQ%Qj@#^khdAIcmv>0vr8cQ z6+q@ofLc@Y5@5B!UV(c|#>;@hR{_gj1}rlT0`Xe_!#4u%H?;#;-1Msd{umh0w0pJ^f?WWiJfPDh3J_778qdo#G{}8ZN zV5bT10_1%JDBcD5(5w-N-UaCRF<_S|`WUcUV2i*fru`>?!jA!SJ^}1Dn*`!N0VI72 z_}t9?6tGcXr@$Ac`))wxr+`Jf0biNz0^N24&ioAUjal%SGt&9i?2>$EdVP+3Z)zkB z<_pOWCSwosqq#-$lWCCbHQ8SvKbu;~FJ`}FpUM3a*>6@zel^Zl$ZuwpG&3cVkl7?@ZW6vjTA10AXj3l< zo9^EuF=no$rP(ey#3VN$t;_<+p=Ot)wdwT((#F(CV$BzlwkG38jt>OJIHrKxQ-`*VIG^NkWsg+ck{gT-x_Xy--vqEx-aoQo5no*KDrcQF13CAN*!IZfrbu#yStq&Dv_BG=XG$bjnN5Z^cjGCHZR0?Rr97MliubF<0rj2KfZ zSz`7}ZZWwD$gO6DddUN(dspN^GgtDE*)Dn5BzHp|F$*M*nq7#Q-%TCu zPB-dIO?P#)yE-cHxXCzP9Tix1JYc115Lj|NVE74uRi^d?K-LL>(20OGCig_ZK7o}2 zPa7u*u>3?oK@woCsT0Uc0>qsJc+TXX1c*Kfut8v*i9H#xTA<=&zR#$UF^DZ)#2hq@D)YEAW=d=n2>(u&gIwt7#Be(i1TJbili&_H;nj>3~oQV7uY} zOo8zbSShf>IH`c;DS(1hz)n*qke3RG>jn7GA%MhOK%yzpl{h{ZuvMU|NjML%QDEMAfbOPVpz=IG&!K=5%-o@X zZbJdP1(HniFu*o}#lrw6n_U9)hXFE&1CmY6a6syCz+Qne06k5Ez>*Pw z;UfVlrgkJCYa}3)2k2#T^8ot#iEFvuid2-qgD_(H(BW|zSH3jvwq0J)}S93XWZV6VVXlaUYDBd{zVFx)f< zEXfB99}gI5YR3bz#sfkV0HaLq1i(Ill>(!UGZCnZMJBlduuWib1>kzKOJIHlAoC(Xjj6c^ka`hduYhYZW&!pHESm*b zY#IcX%mNIr1Q=6W3COAhgk}S7F}bq=`vg`B+-97M0n29t3N8jLHFW}c7X#uh0o-Zw zF9Aef0@xr>Yho`2tQM%a6mXAOCs24PAaM?0nJJkAh@S)4DsaC^xD2pSVBTeb6{cRG z@-jfrD!_wgZWW+g6=1i(!zQ^JuuWibHQ-UROJIIAAagFD&eY5Wq|OEG6?oiaTn^YH zu@CJx-A6k7T98vuK{cmSbPoOb+b!g{xyKiYXSA9=2}4NwSc_> zZ<&lmfIR}s76G=J27x7u0K=~XylZN&17uwX2we}@ZgQ^&>=Rfiu){bv0G3}5D7XQz z)6@y%-2jNI0eookYXH$TfDHn>Oze$-)dCea0zNV81PX5iB)Wjzro;uryMV0%pPPi6 z02>A7-30i;)C*MJ1n9XK@Rgan7|?AoV7I_GCi!N-Hi5-A1HLo61m@oi$TWZkQ)2+B z2C!G)N0YGxut#9o62M;5Ah2W!VE8S7Urg;SfUH{pp<4m_P42CLeF7^5elyN(faSLW z3T^}ZVd@0(ZUe;Kt{ae`8GpNOKyHU@fanHfDM?lfR4fHVnRNn%O96>@079nZ4nX`J zfUN>8Ov0UjjRN!T1cXh!K;@l)o_7ISnz?rYy4?lXEzrs&*8;W)EUpE#HoFAo*8(!{ z2E>}0y8)?p1NI6WW-{&p>=9UY42Z*~L(8=WA4~V`Wut6Zf#4ZP{7N}Sb=wj9h6fOrOt^g#O zk`;jX6@aY*T}{FRfQ0eQXdBF6*$#oJObDwu)tuQwLc77@(jIaHgpf$g2ay{R5D0^8W#d{s&-#K!%BZ9I#rT;&H&)W}QIc zs z7Ow)FYjz3DUj@iq4ahY$s{yI20ec08nv6AoJp#+t0EU|efhB7I!=D0-G__9wvYrBj zo(7CExlaT339J+tZJcKS%bx}mJOdbO>ICwh0mQ8ZTxjyw0;1OfHVEXK*k=K&1uC8e zOfc&N3ZDfeJ_jf;CC>rkp95?am~0ZB2W%9W_dKA;)C*KT59qlLFxAXm2k5pAuv?(m zB)_tF@X%JZQB4GFiz${a{ z0g$x;5PAtP+vL6k*e9@3;1c7!3|RgWpx|Y|98)Kd_c9=EBcRIUZv;ed1Z)tPYhpJ6 zRtr>Y0$gF%2^4MuByI-GGbNh=@tXl#1+F#;uK+d*%zFi}z|;#=z5?j^D&QJ3_f=kfL#_NDR0?S?pEH(`SOI`;I ze*<7l?HhosHvpk}z%3@X9w*iS;0n1FuRzUn#z*d3#O~N~XjRN!D0jx0f0+sIodcF&I z(9C@o(CuBoZh?nQ@;1OWfyLVZkD6Tq^S1#qw*%@-&2~WQcEDbN$4$n2fIR}s-UF;O z4FXHv0}S5*SY>K=0J3%fLhl3CnB4aP`vg`BJZ+qvfaUK43U&h4nmU2Joq)Ix0MD8H z4*=0005%A$GqE26Rtr>o2v~2{2^4+^Nc;$}!IXRii2n$%Rp4clunVwJVBRjkCQ~m^ zxeL(qW56qB?#F;`9|Lv^Y%$570JaG%{si#4*(EUl6F}ytfO=E&DIoPzz+Qp3OvY}& z9)V@M0b5Ojz>?j7;hzECHMO4svOWWZJ_l?!xt{~}39J;@VVpgH<(~rz_5gO8I)S`B zfVeLJADa9x0MTCnHVEu8v0nmK3sigw_{6LeDEtzT_!VHcDftQz{}o`Xz~?66YrsZ< zd0zv*F!chJUjur61Nh3!{RYtO8^CUXZ%p#HfNcVczXg0}b_vY?7LfTJpuyC92T1)6 zuvg$mlkq)ZkHE6;0eek@z>@C)!y5p$P59JO-(53$IvtUHfl?g)Gq45Rc|Cl{p8%(ubJr^j*4bH*L95Q;J$HoH=!Fq zp)U^QuTz+Yn5cU|tt$EJPy8*k>dVnKtDsLSbIeUgP$0Or-KuY6qF!hgdaN7i6hG1{ zyiF`6?K_b_46~~4#Hg#IsNrz(t;&jvig$vW`Fuu#d_)UJYG_bPknNGfaK4<@u0yCO(op@%5? zFq4!Jb#=I6`uK{XX%iU$`r0*g6o1a%%lwIB-slu{r5%G#F;j}li&WKP6U}9vqi#Op z?8)Ar!m6Gyt>~mFW%d_(?u1=( zQp*dv2D2x>7!N8eMpL19PX8 zu49Ir9(8G}TesNO-;1_YgtuN+ot7GPZ}V2;f3z!LKN_o+4$xehQ!*&2FaMW{Oxs~m z#%VRPw4l7asG#%${;GK!vo0ZOWOyK1_-iuN?N()+9ra_&e`(7&wZ%41sm({{;y+1q zY{%MZ9W+}NJtS(F)9RJ89sR|#>i5wmYpl=E#@(X9n@GD_Y3fR6@<)>fI)TPzZ2AtP z>Y|7-6pz^gdg#_yBEIk*H12{{3R7YQUJ z!3nM*NP(h3gFBSsC%C&zaVhQ|+@&~_7Fyh;xRe$O$~0f=)3C1B(nu8-Xqgm$ls3acElZ1Rtd@PJW%4TV@mjV>%hDs0*WXAz7Hb(J zqouFbTVlwZt{17x>vcG`EY*tgcHytJ;xa9hmtV_kdZekAYni;|UEb&;ZL&hkeDPOB zCT+43nH0bQHFSEbwVofcumsuopWhlS%!I!je)+A{vdn5w9t@QQQ8*%L*Y!F`8q7zh zyFtscB6}h4*5S8N%d+8rsb!nAEIYDsk{G|u$fTS(V7!*aXuX`sX2?6hA}46Uj$jc`7aW1!UK<9mx1+$pdf2!*93NlOFX!%l6<(VYozDu1F&M_Q@+z zMJV0uz81=RRK*(r54G%|mK8wOLCX&51PdbL>pR8^M73TZG6yp0>fa;dpM00e;-_WD zwO%2qzb8WJ|0fVi^9BQBpX>L7Rt({&6G;C*r}aYdHwXFsqV)>nmj_;@k6%Q_KlzH1 zWrl9fD_T#^p?=7m(s{3Hp`1hgwc<4`D~?QF4<&u~x|Z=pE=v<+(kJERv0}lJ@EE`R zy=6^B~KG z`$+4R!=D?O{N&BFaw3%nKP`Ky<5obHPRz?s-b^driqik&6{ufuAe~1_T?w{JD*XP` zvdZ||k)U+4cUo2je{%Gslf6gAKi4~*Qfrx%L^^FX=!c1<<0VEWZ(9!s*Q@BI{WBw! zjH^R>u_eDOS{8xde0RRTmeoKuoCwkxv+4wE;+Ica$gil@tA$@alOdh4n3mPXpH<6> zBXhmM>Pv)eKz=2(VjcXn7+T38mLRK6h$Kqj5ByjElaXKAVRDrlK}lc*Ik>5LV%tO@?ITCb9p zMItMwWtFw8DY8qnS4nCADq7eK;V|84s%lwtWYRyvaI0xq3oR>+8?I$7wX7^|buDX! zU*0-e4mU!}qVQMHvKq)-FDGw}u%Z^$5+TRZ9F?@}OC7l_GI{-CdE7e4q{Fp?+~`T~ z{z~gHiL~U=viis*y$(=9%Npvq9g#WZg})W?G|`b|kU50E5^f}}SgtdO6{_I2(Ry9* zODC<0+g8h%8Cj&0imaWMb;BR5W$m@BJF-wi=CpLs!X5~vv=X_amN84V%+s5%X%TpC)MQFMa#ayAEaenk%?D2bRs-*R&{sj(f)ln2-K%e4;{HLGI^d~PNAM! z)(`(u-I2aVCg$%C%aPHOExomD0DgHlWHa1;$RuMqYsAvhp9bJcc4TdN=H#FSo`G5z zEkgV)aV1O?2Z5Yo(*Ds}HW~w^}CcKg(?68Cn>PJ6kKx#4k0L_L`$*v+zrerMc#6*=+oBdWt2*77o!c2nNFt7z)E+IE(;UG{~zqWlfL(sz5ae zhw2akGQY0{^5<;1ArIt*{15;Ilkkm?Ks-SZ#>rC}%0O9=Ie&!&DrFIymtQsf)gc0E zKuxFxwc$%uvWTs)GaE@{ha4a;Z%qg3!3#2gH^{mmBRIehOI;q46)lWspU|RW=AL*YI428*o!CFK+YA z|0`Zu3p|46&=T4}TWAOELDmHwp_6)C+?LrX-~E!0j^&0gU;(AdgqsYKLkf6<>>a#^ zSMVFW200Iw!ZKJ6(vPLzN;j2mxdzt4I`|Ic1e6m_&TJX$7~7l{8Ovns8U->M%jhdZ zL{n%EEkLeY4MApS)gT-qpaxU|_986vXgOIv%m#Tg>qM9YlVKXjilGy9fvzAQ#uyZiWIllKfzTIvLKo-?@)f%dxGkV1 zw1OyT1F|xB?Bu^JWSAN30ud5IO8iM65jg30^15hw^Rv7Kx)=ORZ}|nf;J<`>8LojW zF=R=R2{J=skk3EJ+rL+WynjA5qy<@KIMef=7s%_LV^NgVya!kz0odRj@>lR0WO*qo#}Ft2^6INTR+dfCcm{#ILrcE) z)C`(K3up{DrHBLkKrUIb3XxUEd-woP;W@kj`2^W%I0I+l2awN@{Ro3W zRur-l$qT8$3-~sm>pOuTNc&fK4lm#-tS8F@VYbwMTR4b;t*{+-z)si&yVcopY*rk= zdk_}EVrT<$t&oe7TLSwEBm*+-Lo zvpb|U9EQm`^)-TC&<#35Cy*D+CxTQ&N(%CRa9Ky3rqRyA4Y&!n;5OWW@^lP&`F<40 zmkJ!<2bmx<_(L|x4qrfC$d{0_v;dxh5C}m~7>YnqC`3&;a`L1yatp#T(wKwzex$&wXMHn2lNNDT4;y5Hcn%3sNr!M*|SMs=l(t$AP|DU^-)ROmo(TzxChtaFdTvJ;ShWYb)Xr#n`pDmum$AB&`V(@tb%qhfvU+z zh`K{k^gWTKmN!x-!&4o*wuN@k9-4v7%fABo(3pJiU<& zZ~=Y>`Ci#YxdL6sBQGPD9b4Iqm5tVJARDRS6h`(HWgjsooW~-vjmJ*CYwJ$7>SSl` zPYT%yBB3fYMkYIK3Z_DLkexJfWur`7p`K(ayJWRt3Ym?8XpjUlAxjR4L3YSwM@%-r zWW!4~xPIdllPUcZcnY%jlpHrT$X-%^ctbiC++nz~9VF@cgKY0)17{ZgO9N>kJ!AlH z@PSz3_J%&t7X|_bvqVE{ki|_^s0Q<;kRZDwW@&?Z2@XsMO(U@IT84PYzD|@Pp83jQ1C6xfSDfZZbe&JXF>cq zK)y8F7SoJ?K_Dd)n{UJv%V7nGc{)IQs0iiZCT5cT0I89Qw6DBeC<06~OL}q5E~e$Z zKTgY+p9IK4B@6tM8CL7eWc?`X#iyk40HjHhfE1t-m6s1%%4#`@_^FJ1Hg`3JUIS~P zN6m#|c5)zQlY~vo zNTyuYc(uTEWo>u{Bm+}sx+1HGt{}@h`FNqEYs$ng-zl_I!o$ycyOV3J=S;_OGDr&2 zcaq@RK-L$s*06xAKV&ah?uN*EMAjv;K9NtLJ%9@!pH}?|_ZZa1zY%u>tcPV#k+e%f z2`CPa(cv2Bbj^`vyq0nMXOOX4hS01aWA{GT3wvNQ$m&2=*RmHd1DZes$OcR$To*wZ z(W*dYC<}QY6ZnAxyul07TQFBTJZaUg2wMiVx2mnYGXuvN!3Ve;xeAgKHwR<~$tVkC z1~Cwoaq0Nu=7QXi9=?DwP#Ow?WS$T5LIKDRA`8&24s*J0FqA}C0zyF&3xPrq1c49? z#i1w^h9Xc5!k{!%f{IW9%0YRk1+oN^jAc1g9i%YTKnf8KHD$b%$)+@c)LP~fQbP~G zSeClL#mVv&WQB&%7#hJu_!fr1RFJjWWS9gKU_3Mhx#*08u`mWk!zdUDBgkK7rLIH< zq@=40x@zUt|&{aRsM&(2av0 z&{YR?#}(N(&*>tbmmuaaY3{*aRD4J!}A};1<{gJ7EWe!gdIeDs02E z6=I+`?1n>d0QN&-I0)avVeLPHdlZhr1vn2U;WV5AvEEPcBbBim*ZbS_Dp0NBh#5X z9B09my^{y{@4;=*k3gvVHEgMimD2k0d;Nm$ltPjMf?Lr^zr*fJ-J zeS%kN{n(=Q}r$&@5NuC#0!khE*bCEz{*<6tb5M%WX#Ce(m@ z981O#_^Uu=C<2wBA`}Oi%SfR_UKm0l1Z4dr@?cz<2FO|}6=_N3i{UQ{v9ZqB*iv{( zLJ5$}Bv2BQ$f8^Ue>sp2TpBkF%0O9=u<{y-YsQnfreEaM(2<6!3e_PTq;={!@zjMc zLChpq&nmc>#!?4FSz5dYw1Dm)x*ee#bb=^o4U$P$=mIU_E08$NpdmDX`p^_2p$Rw} z@t;ijq%@+`93;V3Aeq+#$xvickk0y83Lu%A8OyQgwTHIQ2HHVQ((RyS;ujsW0u(sq z#|R?qq)L8i`>J&>ykCQK=R3G%aM$39fn-{1_9f{<(nX}}PeMKs#)DYj{7vB4XzJM5 z$@oQg3ho(;di$lVVFj5U%QX2mtOW@dJKTbsa13t1b+`ss;R;-ai|`BltY*})<#nFG zdmN6!Avg%@VI4@aX7&d--k@cBad*Q`*ae$FnqnjF77+PXh=I)@vhA=9L@uuOJ1sjn z*aMGAU?1*&?UxMAdL7pO?{TG`(ix6`=tx~fPcY+{uIQTn_+97U6(k`swpnvC5i_F; zI#Ki3Ojz`!pb{@`*d>n5ikNAd1%5>Sf6K^Jmc-1=L?)5V%uF4z#67qR(gYvFIerL> zkv+hb>(8&avTq^#JhI>Ogkz^{3dt%gJIJ0-Mvz&xH^}y5I!FsLsHDM_xx{6V%debT zsgb1u*)mNINkJAdeF?YWen96vNMYW=QsjT)zJ=dqD1D9RDM;UciTfM8f*0@{o`J~a zSlmCLEW80pOfH47_&so~kN{-kN|uxfK{l_FK>(^LaiyT*_jJjzzXWCgFOUqShEg-B zjqHR;$ur@~#w;avJue~~v{@iC_=9Y(NgN3;fIl~wmJPt4AM!z7knOjRmX(i^|6elg zN6X5dpIA4($~oh!omms9m4wSiR|rT`Ne7btQUqktDjQkSk)%^eJXyq=ayAoPVIr4d zPMS+PqHKUkdgwY`%F-qhu_{V(l`<2Nh@vQ&nu$r6m{u}2Wfkz3htg0AWD#2q%0e0K z%9f9$r*M~oJRuk(g6a;s!9NFqIvwE~GNw?-R2AuVw_ueVbu06#7GR z;`YOpdoz8YH#9}|jq29c)+K2p4jV!jt4i6(mM3Wq{BrZLvsGnkXUk)ko0DZ#w??*& z&UARwLMLLU!A%XG@Ds;Bz*&&>{Am~pgCH7Y8!RggD_de><4|Y}(%53ANRW=y4YwnR zsfxmv_)Rm{md+ry?h19Gjt=aC+Zm)#4M9%c2Do2AJuq!9r;*rO!o_U?Vj3}p6rc%+ zxf?+<5FJ5GFM7TnbOveAmLLI=nPg&?xHWz$Z3k!%?Vv5R(MUl|q=wx&?g8IGFZddI zLm%iXP*6Gl2f_fTF9%X;De(}H%m(XYN$?c@g)jxi!6+C8BVhy#kK-4aFdjsAv_2lA z-Lcvgoyjl}oTDkx1UwQ+B20pLFcl;b@k@jSFdyc^9GDJLkZCXrX2J|m@U8aC@obQA z({H9HE){TEHsaX;>tP+Ng*C7WR>BHc4$ELEEP?MJZsDXRQnXqO8K2oC(@kKScq{(R zV0I|6P7Fu~ls+KxEnu2kjwOw4a1u`FV~MvNq;E+${mSX;FcKggYbQuYlFqdQ%uXXc zSNzAoHK5~4oP%%xq$zg8evtTkLE`U&J+KSRv?QMB8ppK1cq9RdWGab**>lB}UM&%i z!uKGV9)ZJPW-e#NAuuzJo2gkyGyRXpP9jJKlDH&fD#Z04M=qzJjCNu+F`>kf=4lEt z0Ezn}oYVeD+$tc0nv8U!lZs*eJ+2JZN%3F6?@WQ`yjDnt-wa5MKM5p)gkT37$m~=S zc?WMnnnT9?H~4>t7w{CGz+-p>G6{T$`zt(v`!J6<_oV%03M*6Fy9n=qIpw{L|1#Wy zoA3+#43d~^jr7NV1FnJSU4%>8y^4DUuAz5b`>*1^0+QErWO8v@O#UC|%FhtKgxByJ zyn-Sq|ACtb62hM#tCj>`d2mOWQo})~aL;Y00BIt5skJ zTboZ-CtmUz;&!&QpI@Y8=Iw=(8ri1&<631o>7S(zXW%ppg+UMv{UIwSfV7yjW_)WE zxlVNhvt>TJR{0SnS#nADkXDlWhGw@g<*T$TK8yeN4PNNqUkCmlTKu1vh5uuw7IT`W zlz}j=dClQK`m8jAd4h<($l{)LB3n-TAH>}YyFunGwdo5E;_l*DrXkYiJMeFpzfIeQ zXDh_O7LfbO#Zlg@Wjk@@ZrC2&OUU=(?uP?#6l8vJ2=_324@clfkX^KMxM$%E$l~iX z?n#i$Wo~mqhV$bP%)u$#A3&6{aU4v9qHvyL*@n4?TLAeUxQqWXNX_If#wGl+`+N~M zAMVe%GFK7ZU+|Z~Ee&#fhhw=HC2^b*=?b3Pa0{-(O}GJ9;Tnj{bnoMT36H^#%w;P; zO#6uAU%}k+eu!UgYDqyPQ*(=43T+maN;q9LdV)|&Ev1)IFTgJwN-sEm4omPq#bqAp z+Jk?Fe?BrnWUo0EbN`0>3jTmM+7*AS_RFKn=8l{lej z2`NCH50V|yOt^vI2M&;0r^oe$jNk*_Ao2{jUQmyCWaAp3vT!VElqG-H#8(o!OieP2 zi>x4SUdRKvL7qd&1vx-WF1p$AXM?Pe6XZH1@@vEwSB}3x9smU(ALQ4^-ktIAv)*j4 zR&=%HlybOkI?^knBeR_tmbG3%wpTkg?-?b3N1uz(l!Ppt-EmpB13A~YLqZ7gBqZB3 zkIRQ1lwbRC-Vx%|8MW4tjwSqJSG9tJ zUb!Q6CB%zJle{}N-}}Y+PVSK5grp`UdAZ?@ZA;fAa)&5FQV^1TYb(dY3^}askVS-~ zBBWC4ZCU27P3h}&2W=!M4M7o=hQG>?DP?VU$RR?K5i+Sj!*Mw`mn`ZIIYWq8p>fCe zo^O6QIL#e$U8h#RL+ec4I&aS44tYU{Si9+$Px4%MUOm3o9VB<=qzLtW=j?Akrh9}t zq{Y{rx{4*vHaRil{)u0{afh@F3_{L+-uqcs4(1vfCr(t4j_o?rh}-H!_gm|^><@Pw zAM)`fAJ0FW9R_qATR)*YD5`f<%jP6Epg{w_X0N6=+#%-c5k}2E|COy?@;Jrr(7BzI z&9bj&px(~lKd&Z8r1G$oS?6JcKMoe z_i-l7GdEiy=_b0DbdPG){p-%$>vNnd>i3iHZFfj1GD<>5NiWtrY=4;ZxjUp1A<~5- zy=qOa*xxzV9a5JNr<879j_7=Ohefq;2gxlM>00}H%-?z;Ec~21#C-on%&fVG&WyV^ z(;Xsj*N}6j2!CKPVQd(_lL*aA#&p`IU&Vbujnx)S7dv4NXxd(J9eOn zPTv|-Fx;#0&t?eczuYC1NQ>$&S~25z!n++UXyd1BS=eOHbdxX=~ibeT+UD9KrwDsAcR z$@ip6Oc; ztvjSc$EdHnQ`fIb4j$WNTeUb1QBG$xA+r12-zzmQ>W)*L5V3a3r9=EqmYw1p><;SG zy`5OjvL-OJ=q~@uG47BCM3Mn0?}S;F)mf(6-dqAK$#Wg|b~c%}Ad5Rv*XBLjMiCS+WJmS}S+8eshji8%^%zz8 z!=}aA`@2JW6CzD?X3@#eYzJ`(X&o#}DDndMY#38zTDW862Z{^P64%$hOly3a=Nz0FA%=N+@a)c16 zYu$5?viI%c*}xr=RQdO|Rp$kKU3%Mmd@pdpmlPhgYFPJ5iM{2m9+80s0}2HcBx8$~_(*b4E^r=>X7i;30cJ~nUPW~3$weqX85 z7xlctzO{M`lpN7-r&9H``B*f~*gcs%Haj-l|t$TSn{4q-rukUk|DzwX*h2 zv1N2t&#%cX)F^Lqb*-;$5HCn--p^Lux-ErT*UuKN&QG=ldsJ3~n%RA(53u>EHvMf* z_i;v5YKkpm=s3-}sFM>U238FbqeD;4o$k9UT-y?KDyEH-r zmp9d!k+*QD6>{9k5py-#X7zB~ zXXPe#{W7*@f-)&Gm8Q=Y3@9u~J$I;ggRpBj8q!e;@H)fDjWTZBFcw~olHG7ZIiQ!=aCxQ^MGjrt|GZuq%-qb2KH4dFVy(3me8Becew6j?W? zFFpU6y9hc1GoyGkU3R9hXqFg8eJ}ePEfIXP#|nF|KccK2m_uq~x$CdG4x?OVnmMzo z-Ndk7_E$fPylXaLa|as+7=qkqUGtMI-tTc(%0aoXyE0i>BMJM*Z?Bd|fd18qU+A0Bzt z^$`pSdGe`%ku+FHKEs~xE>yKvsB>zh%UUjbem;b_{fX=7p5GWl#x+mns^?YCF}6gZ|1`84#q9M}kl(oe5yvQ|jMEhUKq|G9 zEUXOe=f^O#j~;7_@NE!k)W4$FktmO0j#a43XiA^lsk%(IdD-#(6sl5=qjBZlm<$sU z1@qjK}2+sW{#i?#Nio zI1`Q+uUK65TU-NU>lmB?YsQmX02(sN?D*ksi)B4Fjc_%GtBFhKkcEZLN6cINrm$`( zDrEDEc{koxKY_0?4~jDll2$JwUEgINAM@IfM>UyhYvcR*AwQ|QHkBDfhr;SD*Htk^ zz%&|Ta1m8`8r2vOX4v+6rBVI-t`}KB#l;}Rw!!>|MknNlrzz8RaWJmi5agWm#b0UpT6sUw!?{Dx_k6J+?DYuL| zhj_nz8(8~TXp}o-P7-Pz2GLc)z%*p75wX_`!!G1%r2 z91s*75X^&Vgm5*A-W>brMbeDNa=1e}hO3A<)P9WCI3KhlcH9d-clR;wQbMH5CG7b_ ztB$8$THPUs!qsfz`rbw(Ga5yn^{bK6bHrJ1-^!Ig=niRJU1gd}!G@!e9gWF@91RBc`YpFxV}*`e=jxsXBVG;4=nnb5 zy6Q+=#|<=?&qVw6t>JN}|4-3wjStn;d^CK$BMif&x_LG*-@D`Q+#12^+*}qF#;D+} zPoE&GK5zf!q}p4b((3#?TfW5Rn4+3ZW4P97Y7H|<-w*YSb2&j%pO-(TT2)ey(TtjW zTyMT@khOmO7|#W^p&l$|0{h$2CHgprUug3VdCK2`Bo|9AT^}>Rf0GZfnJvmi^tQ}u!D5@2jhRW13j7X1MO6*Qw{b&bLY|=5#`PuIProV! zlB;~MrH68U$1=|q(Z_nHp*r;){qSW&)i{;i+xo7dvM#cfPhxLm#E(=p7Li;>4&@p& zWL?zhJVk44BRK}*04{O8)R09i7xh{pKHJKrhik*F{3<%g?yYQ#ZD~Austk*5{-KeP zda^RSdCK-%HV)CrQt>u~h)ItA`uf_7u?v2b%b{z`a*h2M%{QKiOs^k)zkE3K$LIjB}=YmyR93?#db3@lxefi_1((=V6ApN{~)U(5Qti%~jK7G<|o~Wfgx6IB}V+y+oSG_@Bj2W|%f<9z`+mcd6bogoBKb+ITqc{*wSZ~ z6<>`u#`Wm1BkhMd4^nxXH7!a_{Z>-6Zfy+{6`I_#@4h+n2UtB?A(6TbP_u;v^l&ZU?>@M4x zCQ=Jmv0b4>In{HL&^G*x@j{pyyNu`K;j(;&1VNTKt&v}i6Z@A}*VfREyI0#B)_Wb* z<<$&_@vhQY+yq-DcT%2f=vS^-KE9t*EEqSVzZO}SC~oe$GZ}4cbPFr9g@Co6nJoks z4Pf;Y-)zAMVYbkpxbe;w3=P+8A#HKvAPZMqS;;P3KCu0h#5sPD>!uMB-!#Gqsi@ws z{meANP>63DVT3S^2w#VhB!6%+OOwiDXiwKx1H8oVruvXTSiWt z1sfR9YpH!L?Oy8P23r-rzY((0=5N(QuJ5O7!gMTBvh?qtB{I0Ss+E>V3pUychyH!$ z?;7J7x;N@M-qovn=By0bRn#?&ac#{sD6dj(qVpLOp{&eUIOXFrv=Q>AylO1!uEtK6 z(50PSP1qzOh_>v0%ackuA6QGf^N4qL>WVt8yt+zqzVUSzBSV)heZ+eCHq*fI&IVmc zVdrLOgv8gtMpE&0ZX=|STD_SXbnRnI_JUFmsCQ#~?H2AD#Md{BsGJw?$kz9cuKo9~ zqcitkRCkIyZbemK3yH@&&32W%f3$H8?d@oH=Elg#7YvrNH%YS%i&g`-&}UpDtB>_* zv^uecyzGOFv2gA3s43UezmmN=cADiHom$0`S4bulWZ*yb^w!Tm9ro_;8h%~Z@Bl*C zNQ`cjW=SpI(Ee$42(#4ks(Oq}J5b1o!iT6nh4TzrA)ARZaB@NKr3Rs39WzMHj-e$M z>y)ZD4Bxb6-2N}zDeV}f&Y%%;N+(s~?%UDfjoYz_XO6-531QPRI?Kkb>2rTOP3AUw zbhaqJt&}4DU_;^ls;8r>|B-69JMsLg779#~I^Z~3qsOjev}66KoA=WE;czE3Xt0{U zmGga`PAT8)DpePk9wA!+I;$8$vJ&@N$NG^+Z|;wDhx{^FJt3~|3pC`K7%*U&{rCFz z1a1w!v#^a)WK+d)e2We-jQYAnfw#v7CVZ;(Xuuk(izu{3K`vhT#x@@ty6wsjS^=Z> zRNrmm)OVf5KI+*vMuY6C+g>Iw*|*yU`*t5{bdB>Vi*`h*S8IG@D6wu8Oo20Ltie81tF9RwX1rdI8s7H5YUOXw<*ZSD{G&L(>&LB=+QE0Ryh z=M_F5qVp@a(^lAey{ziLlQq2IdYctEv)eVaF(>fhHUELTxVN!nlraJ7f8KNb`-P#I zC`z!gQ|;>E>593o(0`vBxP~#trS-&@-GDVi=8YR!IM!zMG4> zHlwQ)as9@*^x5FJs{e*xJ=sHEahvZi>AMS*QSILQv2o*w|56Yg*JUjqo$B9p`SjB7 z`{*=UJ5J_OnvCxMK3^{Nx5n{N{SSOBpAl>%9H)TZ%IhHSXEgE+{d+SVoMKGj&f?0J zTBW^_s$crNvT+tnk7KQId5Ri*kO9qY+4SnxLE8xH<*BOUA+C~Vrl}9i;H~$ksrel9 z4U}z%YN-H6* ziAO=pp6RN@VNyOmUDZ3x=FKHE)1cWSyhGh)ww4cX_&Mb{fsM)75TbIJ}jS z=Bta@(l1zkE6k5!%v`KGu?Ry51IXbn@ zZ;Jj=y?t$8Qj@z;bpBxFe~yuPxl#c~Dfd>TDj%g7AC&5k%S3tYQO?2C>gZ8=jjwvo zkq-1m8pu+~JoMem+Df*x8ti#upMCXlZZeS_JDSc|Q_-bn9HX4?W~o!hsCgoF@0hK+ zGPP3=IrMPZ!AE5}VM~+! z?L6bkQ)j+0<~85%f2h*8KEJu@>AImnhTZM*)R~f z6~|6$_eopEkT#2qzPi%C{=&DzVzav{>KcA}5+bek=#Lgxnx;)0?8@I2VpS_pQ4NYzQni`8C}%zPhC`9Jw>!p=_<|X z&v#KY{>?6m=HI)h*O~wIF6z6LzAS6zEWN6(>z=OaURT|V%E?zr`TfB3IG%_lAqiB>UtzaBw+<8J_I$7mJyBN65-SLztXi=ViA?24~;{m3?bub;RR5zP4{ zcTF-|Z${R7@E;(acH`Oh~?&Ii-f}AYL zUUOH`m#=Ia3KVSkIyLc4H&XtKWYuT05usR^Mtp~~67o*_X&OEuNJzCD9qK})ku#*DY$*~A^Q?(JhCr9dO8 z-s;9>y6c2ZYIkK4T(n8$D~!8glPY+H5!k4!<0SDjV4&rz`sJ$sL%PYt*SFYiZ&G`6 z6XES9wT6fuUVNDj;{>Q1g(*p8^`|(k)xIh?166?{b|351&8qYhPL1z2E6=J#-MLu} zEQ0&vW|c`Q^>nj}M9!@9>DQ)U~Rl zK6s0YD2nkGq3BPM{cDf=u|aA(cW_MO9onJ-s*%s#EowHJeE6(+HA?q|I$e|uD#h?e z`^5V_;%KE_2^T+dXV6BiMbA1eMrA5S21`+tX+pji^-q?Vo%LX5G3~XKq!|;S}uHR`rBSFma{@aI z+C26+smT~aAv{#(2;${ar}N=dP?K(wSVOgsRQM|9B}r`L4s~4;o4-SOmZWHhcNlA) z6_zAN>%8hxki>NBUD~0ZMNs$mJ5(f^d>F3zEt1QjCY2<)qH3i$tyQ-gq&j}5x-O}0 z+G(Wb?_K-Q#bk422U1(|=uWk_2B}@zsRBw-iDziak}#k3r^v@E$Nz$+ZmYzqc_~uP zruvCfOqHpLQ$wxg$hX5TBmJ-IEy^2yZ~b9+)uYt}^sIAssShOYIES|32ka)LotHW!hf8$-rx5?T<7a*TXU5hL$DQ6g+qd9ug~B()^_jns-&^S?3g_UmSnn$bV-~xs6G{M26U7A0@_N;_p3~GNcs4FHMT77^Zjb#ea10QwTFnlMGv@M4Hx~UUckER z`$KN);$pN`YF-^mHtv8*Sq?MBpqQ2%(qwH`_m7FkWmy|&G}y5N>d7O@eDi>6jHdPV z0oCjQIiyk(%8^5kgGQf8u>IO&F(FfJ;x}-Ay zpvqSscl$wA@K;hlb5J!aZ;zJyF5~OjTboz4lwaA?s-_QZiLBQTs(}@V@bI8|`;;k{ zdHZDISN4qNg{rUesc0``HLhXaoISN3QGnj61CDQO3*%CIf7$3%=g%iJ=MI>noAM8! zgvPL7HBU^l-m!;N;>XO7QmW#QNiCDQ_7o?->hhSWuo2a~+hbnx)U(ITH1ze)xJYHN z1zGfUz#Qj|3%7Oc5j9?}4!e)2RRy^pwD$>%lw9i46Rf3^FkSklT;FU3&7@T0r`-8Q zYkUEEQ|uvQwXr35|Dal#E6F*hyShL%V#?Ox#Q;pk?wrAX%<^5Tv zDFs;y(Wn1Brq(^PZE_SjWh_YYExb47*`EVsv80EwL8nyob9SfR%`!CV)jA&0+;fvW z$HvVOIXe^S#Z0CCX&1DJydlrT=#e0aI{BP+-n-e#@j^}t*Ki;_yEUIcG=k%$i))U7%_!s4wgc7P>R6tTj)yyh3uGkD- ze@R_OO-KG1ON-+c{LCLzNLS<^?EUVF%Jha^f<#x1eaOs7)B4`q(yoEKQeUWAZ>W5A z)d9!R4n5iHjP5>T%kEH{486MYL$9j&q!hXk4VmvGt&;8fOP^ln-5NUx5xbhhMIflpdcoqvnPjrOy?zNVr-FcKSG(;9nCmHrc3>7Ms#*U>5c3vE-&$l~8qR?&9G zU>!sEhJP;8C%ODL;{2PHH#7Q|#W`bV4K*kB)-1>AFGx?HBG=p)wEZM=xe)Y ztd6}OZb(_9YmG+qeSKm0>!7D@qiyCN@|IX~R?Z6le&x8;&3h9|&w)GfTFumiq@C?7 z-zuh{@$jWb{z7XX118f^oN@c%z^GT)n#*D{wPCU1PI~dPEBf z`TVf?h7!p3M8%bZJC5jmSe_)+V~R02{=6GI`vUZ9ScQ7eZwC?9`#}^IjXjl2wup8U3q@CgV@5qGl&& zkT8nzX_$GeuLh3Km&>$+dHK-knTN(eVl}TK+DaaMRdXtDRgYDFE=g8x8taYo}YezC*;%;>2Vy4T6Pq<-tnb3nx9tQeC=|A_C?nu6IfP^)#QRn3$;qjol_Nxt06Si( zHJ;Qny*lD)FT};!Rn$wblviqdYwP1zYG7)*ViRTDY7XkS@#V48ZQBr^CrM-)ZBgq} z+pDt*iA_y(8UDjg z2WQp4S4+-TDOM3)>8DPoA=&!MFD(guLqf6&T+};nr2=)Xq(V>D_0;??cdytj9hR1B z!sy>rvkoj-en@K%_MJmq8Gcjb*i)kX*#c3-mEoMYuEJL55v(rUgpog(8J*4^&V2Dg zI%;x3B}-4v@%+Jz-ug+Z!ccJR``y^GycJYBS+Vn#rjWcIa8~%Jp6O{t9-Cg4-oDBD z?oSoz#c96do$Bqyl9GESZ@sXwc|*02>X(5ru#}pX!ColA;8=?~p1~f-lIuOke&5F$ z3xVRJ7hVqXf3we(4@KdR|Iw`OZO`C)(ZZ*=sp;#^(+8(Zx+EJatUnAZIlb*ZAyM># z3@CJ~er$2r-rqfWTuOJKPrH^oBubr>sP7UO-`VcFuFTMg5)0~*w%jpdbu-iE;hJ<7 zEb*cB=Huu8kPvCaKuZhd>qCVMM@9J9x3HLuNtn^jCML;3t`P`P!kXAQWw;%UO|2iZvmZVpyGTDo&Gd_$e>wN6K zF<)l2^Vh0cPgTvq{9vk=YMp~#U@ih(OJyHZ$2~5&*5!_z^bZCVwq?^Lq?ejYN3#fK z<&EXN+LDtD%!KuxvrgX_ncUg_B~;DN#gJ%hDXYjlB(kA^dX|g4cNb7ebKAqMM+&I= zx#^q6uJWn)bk*fAXpL+tbso-^e@IJR&tnfZ_pwz#UU!jo>aEo=gj@`FvD=|O#8EK! z(Ldc@&^y&$wm=!CWv|*Crd@lWg|wNJBfmYuTwo8)Pbogx*ryXc&QA~h%ivByPBx$? zbPk|U#_HVnA)85ZUn29mhn05st+|k1<~nf#Ek>!WuY=Vo9-3t%NmnxImSAbDTx2}3sfYRul87*_<+GOmjgh&3EefrY2ay@XJc^|i7%sm`!9le#) z+D!CguX2?-y=ok2&t&HDK2%K)WWP(-Z%kq39mJK$Fqmo;L@Vl@E^RN<1m;GVwt*RH zTu8k9f-H@GFiQRV%BH<459H7JWz1*D^8D(P9G$w?Lk=a`RR*8UGu%mHv4x8+VfAnsd7*y>|2JWKIQS zUK&T0Iqt^^=KuWj692h?{pWcJ7LrU;{x3Z*@t+IWf1a24&+`)h`gw`}wQHJrIa2xR zyDvn>(*~|Blus{Je_!A80ABp-dzP|<75Zcq>lns@c#!Qzx0;&KBd9FqJQoeU-amDoZ2w z2db)~jqKH}Ps7y6M(j0~P(R5bZxPAfnALSH^;Khb54x*8joJHbr&2UwQ+IcHV^=Tn zsB(#}eYep3lVvhIN{DQQ$K+42IMwL`FI+2W*LboGBc&qVE>={Gic|C{T zYl!DnO7c1;vS_TXNEzaJlasN4x?L)2jEVUBCD%=tx3Ox@@+$p$g0}4q02t~Qq65cHlM#0l24s&LwUB> zQIFcN_xPcX%F-6omr!4}wHFE8vtx4)R_saxYtJ@pKYkoRcFjnG}IDu+3~Dl*Tl;q{e= z>)zbeh}*cUNQND5u(k;|SqPU0$R&{LhW)8>Fo9L|*X| z=z4&Dd_$G8J>6hYLzTNd)hnhNwdZWw)KIOE!&>S}d+sqT=jEz$mK-`-Io&E%R z-gThGm7SL=)4}eiZyAL17uKsgVB+&l)XffT_S|fu3U;Ii@#xlxj+i_+(!=$K6KmV8 z{OLs6-SPypv}_0;FjBD{Df{I*i9*B9YfPS#r&>g=r9dTht?nbnKV z_EK0Xu!}tmr&||$X6Ny>#vQhvQ-3)3`{VLee&$$iplogVT^~!sek&hvEM1`K>8MJ7 zZZCPXQ*-0hoqJ|X-%#WCA|Wvwj8c8mZraaHw{9+Rh+~O==D^G!FaLPO`#i@If7+8H zwKH#>8XeW6YxC|sqAVxMmfHSjrAB4|oRkQ+dJb;q*{aH= zpE#DgL5n*y8q>N`8ONtDDQ&K5b!Csgqw3bx9+t5d&(}$%hZYGr;n6$g2inAC)QhKM zj&-%4v&O9GVQ*oNiRfbwOqh)4*nsaO-B-8L@{{Y( titleVariant, content, contentColor, - contentTypography, + contentTypography = "italic", contentVariant, } = options; diff --git a/examples/deprecated/reliverse/experiments/state/ui/number-with-state.ts b/examples/deprecated/reliverse/experiments/state/ui/number-with-state.ts index f59c46d..5a9adb4 100644 --- a/examples/deprecated/reliverse/experiments/state/ui/number-with-state.ts +++ b/examples/deprecated/reliverse/experiments/state/ui/number-with-state.ts @@ -27,8 +27,8 @@ export async function numberPrompt( titleTypography, titleVariant, content, - contentColor, - contentTypography, + contentColor = "dim", + contentTypography = "italic", contentVariant, state: initialState = "initial", } = options; diff --git a/examples/deprecated/reliverse/experiments/state/ui/start-with-erase.ts b/examples/deprecated/reliverse/experiments/state/ui/start-with-erase.ts index 20f55f0..289aa88 100644 --- a/examples/deprecated/reliverse/experiments/state/ui/start-with-erase.ts +++ b/examples/deprecated/reliverse/experiments/state/ui/start-with-erase.ts @@ -24,8 +24,8 @@ export async function startPrompt( titleTypography, titleVariant, content, - contentColor, - contentTypography, + contentColor = "dim", + contentTypography = "italic", contentVariant, variantOptions, } = options; diff --git a/examples/deprecated/reliverse/experiments/state/ui/start-with-hooks.ts b/examples/deprecated/reliverse/experiments/state/ui/start-with-hooks.ts index 522fd26..7cd8a56 100644 --- a/examples/deprecated/reliverse/experiments/state/ui/start-with-hooks.ts +++ b/examples/deprecated/reliverse/experiments/state/ui/start-with-hooks.ts @@ -17,8 +17,8 @@ export async function startPrompt( titleTypography, titleVariant, content, - contentColor, - contentTypography, + contentColor = "dim", + contentTypography = "italic", contentVariant, variantOptions, } = options; diff --git a/examples/deprecated/reliverse/experiments/state/ui/text-with-erase.ts b/examples/deprecated/reliverse/experiments/state/ui/text-with-erase.ts index 7c57dd9..74fac84 100644 --- a/examples/deprecated/reliverse/experiments/state/ui/text-with-erase.ts +++ b/examples/deprecated/reliverse/experiments/state/ui/text-with-erase.ts @@ -33,8 +33,8 @@ export async function inputPrompt( titleColor, titleTypography, content, - contentColor, - contentTypography, + contentColor = "dim", + contentTypography = "italic", titleVariant, contentVariant, action, diff --git a/examples/deprecated/reliverse/experiments/state/ui/text-with-hooks.ts b/examples/deprecated/reliverse/experiments/state/ui/text-with-hooks.ts index d3c95ab..dd3b385 100644 --- a/examples/deprecated/reliverse/experiments/state/ui/text-with-hooks.ts +++ b/examples/deprecated/reliverse/experiments/state/ui/text-with-hooks.ts @@ -32,8 +32,8 @@ export async function inputPrompt( titleColor, titleTypography, content, - contentColor, - contentTypography, + contentColor = "dim", + contentTypography = "italic", titleVariant, contentVariant, defaultColor, diff --git a/examples/deprecated/reliverse/experiments/state/ui/text-with-state.ts b/examples/deprecated/reliverse/experiments/state/ui/text-with-state.ts index d34ba6c..da4d32a 100644 --- a/examples/deprecated/reliverse/experiments/state/ui/text-with-state.ts +++ b/examples/deprecated/reliverse/experiments/state/ui/text-with-state.ts @@ -32,8 +32,8 @@ export async function inputPrompt( titleColor, titleTypography, content, - contentColor, - contentTypography, + contentColor = "dim", + contentTypography = "italic", titleVariant, contentVariant, action, diff --git a/examples/deprecated/run-example.ts b/examples/deprecated/run-example.ts deleted file mode 100644 index cca616b..0000000 --- a/examples/deprecated/run-example.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { selectPrompt } from "~/components/select/select-main.js"; -import { errorHandler } from "~/utils/errors.js"; - -async function examplesRunner() { - const exampleToRun = await selectPrompt({ - title: "Choose an example to run", - options: [ - { label: "1-main-example", value: "1-main-example", hint: "recommended" }, - { - label: "2-mono-example", - value: "2-mono-example", - hint: "not finished", - }, - { - label: "3-basic-example", - value: "3-basic-example", - hint: "not finished", - }, - { - label: "4-experimental", - value: "4-experimental", - hint: "experimental", - }, - { label: "exit", value: "exit" }, - ] as const, - defaultValue: "1-main-example", - }); - - switch (exampleToRun) { - case "1-main-example": - await import("./1-main-example.js"); - break; - case "2-mono-example": - await import("./2-mono-example.js"); - break; - case "3-basic-example": - await import("./3-basic-example.js"); - break; - case "4-experimental": - await import("../relinka.js"); - break; - default: - break; - } -} - -await examplesRunner().catch((error) => errorHandler(error)); diff --git a/examples/launcher.ts b/examples/launcher.ts new file mode 100644 index 0000000..a02d6cd --- /dev/null +++ b/examples/launcher.ts @@ -0,0 +1,53 @@ +import { dim } from "picocolors"; + +import { errorHandler, selectPrompt } from "~/main.js"; + +async function examplesRunner() { + console.clear(); + const exampleToRun = await selectPrompt({ + title: "Choose an example to run", + options: [ + { + label: "✨ The Most Full-Featured Example", + value: "1-main", + hint: "recommended", + }, + { + label: dim("Mono Component Example"), + value: "2-mono", + hint: dim("not finished"), + }, + { + label: dim("@reliverse/relinka Example"), + value: "3-relinka", + hint: dim("not finished"), + }, + { + label: dim("Simple Example"), + value: "4-simple", + hint: dim("not finished"), + }, + { label: "πŸ—οΈ Exit", value: "exit" }, + ] as const, + defaultValue: "1-main", + }); + + switch (exampleToRun) { + case "1-main": + await import("./1-main.js"); + break; + case "2-mono": + await import("./2-mono.js"); + break; + case "3-relinka": + await import("./3-relinka.js"); + break; + case "4-simple": + await import("./4-simple.js"); + break; + default: + break; + } +} + +await examplesRunner().catch((error) => errorHandler(error)); diff --git a/examples/src/configs.ts b/examples/src/configs.ts index ad36ce4..16e2020 100644 --- a/examples/src/configs.ts +++ b/examples/src/configs.ts @@ -1,18 +1,18 @@ import { emojify } from "node-emoji"; -import type { OptionalPromptOptions } from "~/main.js"; +import type { PromptOptions } from "~/main.js"; export const basicConfig = { titleColor: "cyanBright", titleTypography: "bold", borderColor: "viceGradient", -} satisfies OptionalPromptOptions; +} satisfies PromptOptions; export const extendedConfig = { ...basicConfig, contentTypography: "italic", contentColor: "dim", -} satisfies OptionalPromptOptions; +} satisfies PromptOptions; export const experimentalConfig = { titleColor: "cyanBright", @@ -21,4 +21,4 @@ export const experimentalConfig = { ":books: Learn the docs here: https://docs.reliverse.org/relinka", ), endTitleColor: "retroGradient", -} satisfies OptionalPromptOptions; +} satisfies PromptOptions; diff --git a/examples/src/prompts.ts b/examples/src/prompts.ts index d7789c6..58d440c 100644 --- a/examples/src/prompts.ts +++ b/examples/src/prompts.ts @@ -2,7 +2,6 @@ import { detect } from "detect-package-manager"; import { emojify } from "node-emoji"; import { bold } from "picocolors"; -import pkg from "~/../package.json" with { type: "json" }; import { anykeyPrompt } from "~/main.js"; import { multiselectPrompt } from "~/main.js"; import { progressbar } from "~/main.js"; @@ -24,7 +23,6 @@ import { promptsDisplayResults } from "~/main.js"; import { numSelectPrompt } from "~/main.js"; import { selectPrompt } from "~/main.js"; import { spinner } from "~/main.js"; -import { deleteLastLine } from "~/utils/terminal.js"; import { basicConfig, experimentalConfig, extendedConfig } from "./configs.js"; import { schema, type UserInput } from "./schema.js"; @@ -51,8 +49,9 @@ const IDs = { export async function showStartPrompt() { await startPrompt({ - title: `@reliverse/prompts v${pkg.version}`, ...basicConfig, + // startPrompt is a special component: if you don't provide + // a title, it will display the useful technical information titleColor: "inverse", clearConsole: true, }); @@ -82,6 +81,12 @@ export async function showInputPrompt(): Promise { defaultValue: "johnny911", schema: schema.properties.username, ...extendedConfig, + // hardcoded: { // For testing purposes only + // userInput: "JohnDoe", // Predefined user input + // errorMessage: "", // No error message + // linesRendered: 3, // Number of lines rendered + // showPlaceholder: false, // Do not show placeholder since input is provided + // }, }); return username ?? "johnny911"; } @@ -221,6 +226,48 @@ export async function showSelectPrompt(): Promise { ], defaultValue: "en", ...experimentalConfig, + // @reliverse/prompts is a very young library, so something might break. + // If you encounter any issues, please report them to the GitHub repository. + // By using the debug+hardcoded, you can try to manually fix some of your issues. + debug: false, // selectPrompt + /* + // [debug: true] + // without terminal-size library + { + terminalHeight: 16, + availableHeight: 12, + computedMaxItems: 9, + displayItems: 9, + startIdx: 0, + endIdx: 8, + shouldRenderTopEllipsis: false, + shouldRenderBottomEllipsis: false, + linesRendered: 11, + } + // with https://github.com/sindresorhus/terminal-size + { + terminalHeight: 19, + availableHeight: 15, + computedMaxItems: 9, + displayItems: 9, + startIdx: 0, + endIdx: 8, + shouldRenderTopEllipsis: false, + shouldRenderBottomEllipsis: false, + linesRendered: 11, + } + */ + // hardcoded: { + // terminalHeight: 16, + // availableHeight: 12, + // computedMaxItems: 9, + // displayItems: 9, + // startIdx: 0, + // endIdx: 8, + // shouldRenderTopEllipsis: false, + // shouldRenderBottomEllipsis: false, + // linesRendered: 11, + // }, }); switch (lang) { @@ -281,7 +328,7 @@ export async function showMultiselectPrompt(): Promise { "- Why did the Swift developer quit his job? Because he didn't like being optional!", }; - const selectedOptions = await multiselectPrompt({ + const multiselectOptions = await multiselectPrompt({ title: "Select your favorite programming languages", options: [ { @@ -334,15 +381,54 @@ export async function showMultiselectPrompt(): Promise { }, ], required: true, - initial: ["TypeScript", "JavaScript"], + defaultValue: ["TypeScript", "JavaScript"], ...experimentalConfig, + debug: false, // multiselectPrompt + /* + // [debug: true] + // without terminal-size library + { + terminalHeight: 16, + availableHeight: 12, + computedMaxItems: 11, + displayItems: 11, + startIdx: 0, + endIdx: 10, + shouldRenderTopEllipsis: false, + shouldRenderBottomEllipsis: false, + linesRendered: 13, + } + // with https://github.com/sindresorhus/terminal-size + { + terminalHeight: 19, + availableHeight: 15, + computedMaxItems: 11, + displayItems: 11, + startIdx: 0, + endIdx: 10, + shouldRenderTopEllipsis: false, + shouldRenderBottomEllipsis: false, + linesRendered: 13, + } + */ + // hardcoded: { + // terminalHeight: 16, + // availableHeight: 12, + // computedMaxItems: 11, + // displayItems: 11, + // startIdx: 0, + // endIdx: 10, + // shouldRenderTopEllipsis: false, + // shouldRenderBottomEllipsis: false, + // linesRendered: 13, + // }, }); - if (!Array.isArray(selectedOptions)) { + if (!Array.isArray(multiselectOptions)) { process.exit(0); } - selectedOptions.forEach((option) => { + multiselectOptions.forEach((option) => { // By using forEach, @reliverse/prompts // has Intellisense to each selected option switch (option) { @@ -367,7 +453,7 @@ export async function showMultiselectPrompt(): Promise { addNewLineAfter: false, }); - selectedOptions.forEach((option) => { + multiselectOptions.forEach((option) => { const joke = jokes[option]; msg({ type: "M_INFO_NULL", @@ -381,7 +467,7 @@ export async function showMultiselectPrompt(): Promise { type: "M_NEWLINE", }); - return selectedOptions; + return multiselectOptions; } export async function showNumSelectPrompt(): Promise { diff --git a/examples/src/utils.ts b/examples/src/utils.ts index 68cc1c1..54a3c11 100644 --- a/examples/src/utils.ts +++ b/examples/src/utils.ts @@ -1,10 +1,15 @@ +import { detect, getNpmVersion } from "detect-package-manager"; + import type { ChoiceOptions, ColorName } from "~/main.js"; +import packageJson from "~/../package.json" with { type: "json" }; import { msg } from "~/main.js"; import { colorMap } from "~/main.js"; import type { UserInput } from "./schema.js"; + + export function createColorChoices(): ChoiceOptions[] { return Object.keys(colorMap).map((key) => ({ title: colorMap[key](key.charAt(0).toUpperCase() + key.slice(1)), diff --git a/jsr.jsonc b/jsr.jsonc index d3e599f..43cf536 100644 --- a/jsr.jsonc +++ b/jsr.jsonc @@ -1,6 +1,6 @@ { "name": "@reliverse/prompts", - "version": "1.2.4", + "version": "1.3.2", "author": "blefnk", "license": "MIT", "exports": "./dist-jsr/main.ts", diff --git a/package.json b/package.json index 61c22c3..6397c45 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "@reliverse/prompts", - "version": "1.2.4", + "version": "1.3.2", "author": "blefnk", "type": "module", "description": "@reliverse/prompts is a powerful library that enables seamless, typesafe, and resilient prompts for command-line applications. Crafted with simplicity and elegance, it provides developers with an intuitive and robust way to build interactive CLIs.", "scripts": { - "dev": "bun examples/main.ts", + "dev": "bun examples/launcher.ts", "check": "redrun typecheck lint format attw", "release": "bumpp && bun check && bun pub", "build:jsr": "bun build.optim.ts --jsr", @@ -72,7 +72,7 @@ "mri": "^1.2.0", "mute-stream": "^2.0.0", "node-emoji": "^2.1.3", - "nypm": "^0.3.12", + "nypm": "^0.4.0", "ora": "^8.1.1", "pathe": "^1.1.2", "picocolors": "^1.1.1", @@ -83,6 +83,7 @@ "std-env": "^3.8.0", "string-width": "^7.2.0", "strip-ansi": "^7.1.0", + "terminal-size": "^4.0.0", "ts-regex-builder": "^1.8.2", "window-size": "^1.1.1", "wrap-ansi": "^9.0.0" diff --git a/src/components/anykey/index.ts b/src/components/anykey/index.ts index 73bd496..0011bf7 100644 --- a/src/components/anykey/index.ts +++ b/src/components/anykey/index.ts @@ -22,6 +22,7 @@ export async function anykeyPrompt( if (message) { message = fmt({ + hintColor: "gray", type: "M_GENERAL", title: message, titleColor: "dim", diff --git a/src/components/confirm/confirm-main.ts b/src/components/confirm/confirm-main.ts index de4bf0b..82df298 100644 --- a/src/components/confirm/confirm-main.ts +++ b/src/components/confirm/confirm-main.ts @@ -37,17 +37,17 @@ export async function confirmPrompt( options: ConfirmPromptOptions, ): Promise { const { - title, + title = "", defaultValue, - titleColor = "cyanBright", + titleColor = "blueBright", titleTypography = "bold", titleVariant, content, - contentColor, - contentTypography, + contentColor = "dim", + contentTypography = "italic", contentVariant, borderColor = "viceGradient", - hintColor = "dim", + hintColor = "gray", variantOptions, action, } = options; @@ -64,6 +64,7 @@ export async function confirmPrompt( } const question = fmt({ + hintColor, type: errorMessage !== "" ? "M_ERROR" : "M_GENERAL", title, titleColor, @@ -90,6 +91,7 @@ export async function confirmPrompt( const fullPrompt = `${question}${colorize(defaultHint, hintColor)}: `; const formattedPrompt = fmt({ + hintColor, type: "M_NULL", title: fullPrompt, }); diff --git a/src/components/date/date.ts b/src/components/date/date.ts index 6f36064..2db1f66 100644 --- a/src/components/date/date.ts +++ b/src/components/date/date.ts @@ -76,19 +76,20 @@ export async function datePrompt( }, ): Promise { const { - title, + title = "", dateFormat, dateKind, hint, + hintColor = "gray", validate, defaultValue, schema, - titleColor = "cyanBright", + titleColor = "blueBright", titleTypography = "bold", titleVariant, content, - contentColor, - contentTypography, + contentColor = "dim", + contentTypography = "italic", contentVariant, borderColor = "viceGradient", variantOptions, @@ -110,6 +111,7 @@ export async function datePrompt( // Format the question prompt const questionText = fmt({ + hintColor, type: errorMessage !== "" ? "M_ERROR_NULL" : "M_GENERAL_NULL", title: `${title} [Format: ${dateFormat}]`, titleColor, diff --git a/src/components/input/input-main.ts b/src/components/input/input-main.ts index ba2bfc5..937cecc 100644 --- a/src/components/input/input-main.ts +++ b/src/components/input/input-main.ts @@ -1,4 +1,4 @@ -import type { TSchema, Static } from "@sinclair/typebox"; +import type { TSchema } from "@sinclair/typebox"; import { Value } from "@sinclair/typebox/value"; import { stdin as input, stdout as output } from "node:process"; @@ -23,6 +23,7 @@ import { type InputPromptOptions = { title: string; hint?: string; + hintColor?: ColorName; validate?: (value: string) => string | void | Promise; defaultValue?: string; schema?: TSchema; @@ -36,10 +37,29 @@ type InputPromptOptions = { borderColor?: ColorName; variantOptions?: any; placeholder?: string; + hardcoded?: { + /** + * Predefined user input. If provided, the prompt will use this input instead of waiting for user interaction. + */ + userInput?: string; + /** + * Predefined error message. If provided, the prompt will display this error message. + */ + errorMessage?: string; + /** + * Predefined number of lines rendered. Useful for testing rendering logic. + */ + linesRendered?: number; + /** + * Flag to indicate whether to show the placeholder. + */ + showPlaceholder?: boolean; + }; }; /** * Prompts the user to enter input, with optional validation and default value. + * Supports hardcoded values for testing purposes. * @param options Configuration options for the prompt. * @returns The input entered by the user or the default value. */ @@ -47,29 +67,118 @@ export async function inputPrompt( options: InputPromptOptions, ): Promise { const { - title, + title = "", hint, + hintColor = "gray", validate, defaultValue = "", schema, - titleColor = "cyanBright", + titleColor = "blueBright", titleTypography = "bold", titleVariant, content, - contentColor, - contentTypography, + contentColor = "dim", + contentTypography = "italic", contentVariant, borderColor = "viceGradient", variantOptions, placeholder, + hardcoded, } = options; const rl = readline.createInterface({ input, output }); let linesToDelete = 0; - let errorMessage = ""; - let currentInput = ""; - let showPlaceholder = true; + let errorMessage = hardcoded?.errorMessage || ""; + let currentInput = hardcoded?.userInput || ""; + let showPlaceholder = hardcoded?.showPlaceholder ?? true; + + // If hardcoded user input is provided, skip the interactive prompt + if (hardcoded?.userInput !== undefined) { + // Simulate the rendering of the prompt with hardcoded input + const question = fmt({ + hintColor, + type: errorMessage !== "" ? "M_ERROR" : "M_GENERAL", + title, + titleColor, + titleTypography, + titleVariant, + content, + contentColor, + contentTypography, + contentVariant, + borderColor, + hint, + placeholder: showPlaceholder ? placeholder : undefined, + variantOptions, + errorMessage, + }); + + const questionLines = countLines(question); + process.stdout.write(question + "\n"); + linesToDelete += questionLines + 1; + + // Simulate user input + currentInput = hardcoded.userInput.trim(); + + if (showPlaceholder && currentInput !== "") { + showPlaceholder = false; + deleteLastLine(); + deleteLastLine(); + msg({ type: "M_MIDDLE", title: ` ${currentInput}` }); + } + + linesToDelete += countLines(currentInput) + 1; + + const answer = currentInput || defaultValue; + + if (currentInput === "" && defaultValue !== "") { + deleteLastLine(); + deleteLastLine(); + const defaultMsg = fmt({ + hintColor, + type: "M_MIDDLE", + title: ` ${defaultValue}`, + borderColor, + }); + console.log(defaultMsg); + linesToDelete += countLines(defaultMsg); + } + + let isValid = true; + errorMessage = ""; + if (schema) { + isValid = Value.Check(schema, answer); + if (!isValid) { + const errors = [...Value.Errors(schema, answer)]; + if (errors.length > 0) { + errorMessage = errors[0]?.message ?? "Invalid input."; + } else { + errorMessage = "Invalid input."; + } + } + } + + if (validate && isValid) { + const validationResult = await validate(answer); + if (typeof validationResult === "string") { + isValid = false; + errorMessage = validationResult; + } + } + + if (isValid) { + deleteLastLine(); + msg({ type: "M_MIDDLE", title: ` ${answer}` }); + msg({ type: "M_NEWLINE" }); + rl.close(); + return answer; + } else { + // If hardcoded input is invalid, and an error message is provided, return the error + rl.close(); + throw new Error(errorMessage || "Invalid input."); + } + } while (true) { if (linesToDelete > 0) { @@ -78,13 +187,14 @@ export async function inputPrompt( } const question = fmt({ + hintColor, type: errorMessage !== "" ? "M_ERROR" : "M_GENERAL", title, titleColor, titleTypography, titleVariant, content, - contentColor, + contentColor: "dim", contentTypography, contentVariant, borderColor, @@ -101,7 +211,9 @@ export async function inputPrompt( if (showPlaceholder && currentInput !== "") { showPlaceholder = false; deleteLastLine(); - deleteLastLine(); + if (placeholder !== undefined) { + deleteLastLine(); + } msg({ type: "M_MIDDLE", title: ` ${currentInput}` }); } @@ -111,8 +223,11 @@ export async function inputPrompt( if (currentInput === "" && defaultValue !== "") { deleteLastLine(); - deleteLastLine(); + if (placeholder !== undefined) { + deleteLastLine(); + } const defaultMsg = fmt({ + hintColor, type: "M_MIDDLE", title: ` ${defaultValue}`, borderColor, diff --git a/src/components/multiselect/multi-select-two.ts b/src/components/multiselect/multi-select-two.ts index 11be976..51760c1 100644 --- a/src/components/multiselect/multi-select-two.ts +++ b/src/components/multiselect/multi-select-two.ts @@ -18,18 +18,18 @@ export async function multiselectPrompt( options: PromptOptions, ): Promise> { const { - title, + title = "", choices, schema, defaultValue, - titleColor = "cyanBright", - + titleColor = "blueBright", titleTypography = "bold", titleVariant, hint, + hintColor = "gray", content, contentColor = "dim", - contentTypography, + contentTypography = "italic", contentVariant, borderColor = "viceGradient", variantOptions, @@ -53,6 +53,7 @@ export async function multiselectPrompt( } const question = fmt({ + hintColor, type: errorMessage !== "" ? "M_ERROR" : "M_GENERAL", title: `${title}${defaultValue ? ` [Default: ${Array.isArray(defaultValue) ? defaultValue.join(", ") : defaultValue}]` : ""}`, titleColor, @@ -83,6 +84,7 @@ export async function multiselectPrompt( const fullPrompt = `${question}\n${choicesText}\n${formattedBar} ${colorize(`Enter your choices (comma-separated numbers between 1-${choices.length})`, contentColor)}:\n${formattedBar} `; const formattedPrompt = fmt({ + hintColor, type: "M_NULL", title: fullPrompt, }); diff --git a/src/components/multiselect/multiselect-main.ts b/src/components/multiselect/multiselect-main.ts index 85e78ed..6915a9d 100644 --- a/src/components/multiselect/multiselect-main.ts +++ b/src/components/multiselect/multiselect-main.ts @@ -1,6 +1,7 @@ import { stdin as input, stdout as output } from "node:process"; import readline from "node:readline"; import { cyanBright, dim, greenBright, redBright } from "picocolors"; +import terminalSize from "terminal-size"; import type { ColorName, @@ -16,7 +17,7 @@ export async function multiselectPrompt(params: { title: string; options: { label: string; value: T; hint?: string; disabled?: boolean }[]; required?: boolean; - initial?: T[]; + defaultValue?: T[]; borderColor?: ColorName; titleColor?: ColorName; titleTypography?: TypographyName; @@ -25,28 +26,51 @@ export async function multiselectPrompt(params: { endTitle?: string; endTitleColor?: ColorName; maxItems?: number; + debug?: boolean; + terminalHeight?: number; + availableHeight?: number; + computedMaxItems?: number; + displayItems?: number; + startIdx?: number; + endIdx?: number; + shouldRenderTopEllipsis?: boolean; + shouldRenderBottomEllipsis?: boolean; + linesRendered?: number; }): Promise { const { - title, + title = "", options, required = false, - initial = [], + defaultValue = [], borderColor = "viceGradient", - titleColor = "cyanBright", + titleColor = "blueBright", titleTypography = "bold", titleVariant, border = true, - endTitle = "πŸ‘‹", - endTitleColor = "passionGradient", + endTitle = "", + endTitleColor = "dim", maxItems, + debug = false, + terminalHeight, + availableHeight, + computedMaxItems, + displayItems, + startIdx, + endIdx, + shouldRenderTopEllipsis, + shouldRenderBottomEllipsis, + linesRendered, } = params; + // Initialize pointer to the first selected option or the first non-disabled option let pointer = - initial.length > 0 - ? options.findIndex((opt) => initial.includes(opt.value) && !opt.disabled) + defaultValue.length > 0 + ? options.findIndex( + (opt) => defaultValue.includes(opt.value) && !opt.disabled, + ) : 0; - // If no valid initial pointer, default to the first non-disabled option + // If no valid defaultValue pointer, default to the first non-disabled option if (pointer === -1) { pointer = options.findIndex((opt) => !opt.disabled); if (pointer === -1) { @@ -54,10 +78,11 @@ export async function multiselectPrompt(params: { } // Fallback if all options are disabled } + // Initialize selected options based on defaultValue const selectedOptions = new Set( - initial + defaultValue .map((opt) => options.findIndex((o) => o.value === opt)) - .filter((i) => i >= 0 && !options[i].disabled), // Ensure initial selections are not disabled + .filter((i) => i >= 0 && !options[i].disabled), // Ensure defaultValue selections are not disabled ); const rl = readline.createInterface({ input, output }); @@ -68,14 +93,17 @@ export async function multiselectPrompt(params: { const formattedBar = bar({ borderColor }); - let linesRendered = 0; + let currentLinesRendered = 0; const instructions = `Use <↑/↓> or to navigate, to select/deselect, to confirm, to exit`; let errorMessage = ""; // Initialize error message + // Check if all options are disabled + const allDisabled = options.every((option) => option.disabled); + function renderOptions() { // Move cursor up to the start of the options if not the first render - if (linesRendered > 0) { - process.stdout.write(`\x1B[${linesRendered}A`); + if (currentLinesRendered > 0) { + process.stdout.write(`\x1B[${currentLinesRendered}A`); } let outputStr = `${greenBright(symbols.step_active)} ${colorize( @@ -84,52 +112,71 @@ export async function multiselectPrompt(params: { titleTypography, )}\n`; - // Display error message if present; otherwise, show instructions + // Display error message if present; otherwise, show instructions or all disabled message if (errorMessage) { outputStr += redBright(`${symbols.step_error} ${errorMessage}\n`); + } else if (allDisabled) { + outputStr += `${formattedBar} ${dim("All options are disabled.")}\n`; } else { outputStr += `${formattedBar} ${dim(instructions)}\n`; } - // Determine max items based on terminal size and provided maxItems - const terminalHeight = process.stdout.rows || 24; // Default to 24 if undefined - const availableHeight = terminalHeight - 4; // Header and footer adjustment - const computedMaxItems = Math.min( - maxItems ?? Infinity, - availableHeight > 0 ? availableHeight : Infinity, - options.length, - ); + // Determine effective properties based on provided params or defaults + const size = terminalSize(); // Get terminal size + const effectiveTerminalHeight = terminalHeight ?? size.rows ?? 24; // Fallback to 24 if rows is undefined + const effectiveAvailableHeight = + availableHeight ?? effectiveTerminalHeight - 4; // Header and footer adjustment + const effectiveComputedMaxItems = + computedMaxItems ?? + Math.min( + maxItems ?? Infinity, + effectiveAvailableHeight > 0 ? effectiveAvailableHeight : Infinity, + options.length, + ); const minItems = 3; // Minimum number of items to display for better UX - const displayItems = Math.max(computedMaxItems, minItems); + const effectiveDisplayItems = + displayItems ?? Math.max(effectiveComputedMaxItems, minItems); + + let effectiveStartIdx: number; + let effectiveEndIdx: number; - let startIdx = 0; - let endIdx = options.length - 1; + if (startIdx !== undefined && endIdx !== undefined) { + // Use provided indices if available + effectiveStartIdx = startIdx; + effectiveEndIdx = endIdx; + } else { + // Compute indices dynamically based on pointer and displayItems + effectiveStartIdx = 0; + effectiveEndIdx = options.length - 1; - if (options.length > displayItems) { - const half = Math.floor(displayItems / 2); + if (options.length > effectiveDisplayItems) { + const half = Math.floor(effectiveDisplayItems / 2); - // Adjust startIdx and endIdx to center the pointer - startIdx = pointer - half; - endIdx = pointer + (displayItems - half - 1); + // Adjust startIdx and endIdx to center the pointer + effectiveStartIdx = pointer - half; + effectiveEndIdx = pointer + (effectiveDisplayItems - half - 1); - if (startIdx < 0) { - startIdx = 0; - endIdx = displayItems - 1; - } else if (endIdx >= options.length) { - endIdx = options.length - 1; - startIdx = options.length - displayItems; + if (effectiveStartIdx < 0) { + effectiveStartIdx = 0; + effectiveEndIdx = effectiveDisplayItems - 1; + } else if (effectiveEndIdx >= options.length) { + effectiveEndIdx = options.length - 1; + effectiveStartIdx = options.length - effectiveDisplayItems; + } } } // Determine if ellipses should be displayed - const shouldRenderTopEllipsis = startIdx > 0; - const shouldRenderBottomEllipsis = endIdx < options.length - 1; + const effectiveShouldRenderTopEllipsis = + shouldRenderTopEllipsis ?? effectiveStartIdx > 0; + const effectiveShouldRenderBottomEllipsis = + shouldRenderBottomEllipsis ?? effectiveEndIdx < options.length - 1; - if (shouldRenderTopEllipsis) { + if (effectiveShouldRenderTopEllipsis) { outputStr += `${formattedBar} ${dim("...")}\n`; } - for (let index = startIdx; index <= endIdx; index++) { + for (let index = effectiveStartIdx; index <= effectiveEndIdx; index++) { const option = options[index]; const isSelected = selectedOptions.has(index); const isHighlighted = index === pointer; @@ -151,25 +198,63 @@ export async function multiselectPrompt(params: { outputStr += `${formattedBar} ${prefix}${checkbox} ${optionLabel}${dim(hint)}\n`; } - if (shouldRenderBottomEllipsis) { + if (effectiveShouldRenderBottomEllipsis) { outputStr += `${formattedBar} ${dim("...")}\n`; } - process.stdout.write(outputStr); - // Calculate lines rendered: - linesRendered = + currentLinesRendered = + linesRendered ?? 1 + // Symbol + Title - 1 + // Instructions or error message - (shouldRenderTopEllipsis ? 1 : 0) + // Top ellipsis - (endIdx - startIdx + 1) + // Displayed options - (shouldRenderBottomEllipsis ? 1 : 0); // Bottom ellipsis + 1 + // Instructions or error message + (effectiveShouldRenderTopEllipsis ? 1 : 0) + // Top ellipsis + (effectiveEndIdx - effectiveStartIdx + 1) + // Displayed options + (effectiveShouldRenderBottomEllipsis ? 1 : 0); // Bottom ellipsis + + if (debug) { + console.log({ + terminalHeight: effectiveTerminalHeight, + availableHeight: effectiveAvailableHeight, + computedMaxItems: effectiveComputedMaxItems, + displayItems: effectiveDisplayItems, + startIdx: effectiveStartIdx, + endIdx: effectiveEndIdx, + shouldRenderTopEllipsis: effectiveShouldRenderTopEllipsis, + shouldRenderBottomEllipsis: effectiveShouldRenderBottomEllipsis, + linesRendered: currentLinesRendered, + }); + } + + process.stdout.write(outputStr); } renderOptions(); return new Promise((resolve) => { function onKeyPress(str: string, key: readline.Key) { + if (allDisabled) { + // If all options are disabled, ignore any key presses except Ctrl+C + if (key.name === "c" && key.ctrl) { + // Show endTitle message and exit gracefully + msg({ + type: "M_NEWLINE", + }); + if (endTitle !== "") { + msg({ + type: "M_END", + title: endTitle, + titleColor: endTitleColor, + titleTypography, + titleVariant, + border, + borderColor, + }); + } + cleanup(true); + } + return; + } + if (key.name === "up" || key.name === "k") { // Move up and skip disabled options const originalPointer = pointer; @@ -228,15 +313,17 @@ export async function multiselectPrompt(params: { msg({ type: "M_NEWLINE", }); - msg({ - type: "M_END", - title: endTitle, - titleColor: endTitleColor, - titleTypography, - titleVariant, - border, - borderColor, - }); + if (endTitle !== "") { + msg({ + type: "M_END", + title: endTitle, + titleColor: endTitleColor, + titleTypography, + titleVariant, + border, + borderColor, + }); + } cleanup(true); } } @@ -248,7 +335,7 @@ export async function multiselectPrompt(params: { rl.close(); input.removeListener("keypress", onKeyPress); // Move cursor down to the end of options - process.stdout.write(`\x1B[${linesRendered}B`); + process.stdout.write(`\x1B[${currentLinesRendered}B`); if (isCtrlC) { process.exit(); // Exit the process without throwing an error } else { diff --git a/src/components/multiselect/num-multi-select.ts b/src/components/multiselect/num-multi-select.ts index 2039ea2..191e3b8 100644 --- a/src/components/multiselect/num-multi-select.ts +++ b/src/components/multiselect/num-multi-select.ts @@ -18,17 +18,18 @@ export async function numMultiSelectPrompt( options: PromptOptions, ): Promise> { const { - title, + title = "", choices, schema, defaultValue, - titleColor = "cyanBright", + titleColor = "blueBright", titleTypography = "bold", titleVariant, hint, + hintColor = "gray", content, contentColor = "dim", - contentTypography, + contentTypography = "italic", contentVariant, borderColor = "viceGradient", variantOptions, @@ -52,6 +53,7 @@ export async function numMultiSelectPrompt( } const question = fmt({ + hintColor, type: errorMessage !== "" ? "M_ERROR" : "M_GENERAL", title: `${title}${defaultValue ? ` [Default: ${Array.isArray(defaultValue) ? defaultValue.join(", ") : defaultValue}]` : ""}`, titleColor, @@ -82,6 +84,7 @@ export async function numMultiSelectPrompt( const fullPrompt = `${question}\n${choicesText}\n${formattedBar} ${colorize(`Enter your choices (comma-separated numbers between 1-${choices.length})`, contentColor)}:\n${formattedBar} `; const formattedPrompt = fmt({ + hintColor, type: "M_NULL", title: fullPrompt, }); diff --git a/src/components/next-steps/next-steps.ts b/src/components/next-steps/next-steps.ts index a230577..47c6c7a 100644 --- a/src/components/next-steps/next-steps.ts +++ b/src/components/next-steps/next-steps.ts @@ -4,15 +4,14 @@ import { msg } from "~/utils/messages.js"; export async function nextStepsPrompt(options: PromptOptions): Promise { const { - title, - titleColor = "cyanBright", - + title = "", + titleColor = "blueBright", titleVariant, titleTypography = "bold", content, - contentColor, + contentColor = "dim", contentVariant, - contentTypography, + contentTypography = "italic", } = options; msg({ diff --git a/src/components/number/number-main.ts b/src/components/number/number-main.ts index ee1e23d..2b2678c 100644 --- a/src/components/number/number-main.ts +++ b/src/components/number/number-main.ts @@ -17,17 +17,18 @@ export async function numberPrompt( options: PromptOptions, ): Promise> { const { - title, + title = "", hint, + hintColor = "gray", validate, defaultValue, schema, - titleColor = "cyanBright", + titleColor = "blueBright", titleTypography = "bold", titleVariant, content, - contentColor, - contentTypography, + contentColor = "dim", + contentTypography = "italic", contentVariant, borderColor = "viceGradient", variantOptions, @@ -44,6 +45,7 @@ export async function numberPrompt( } const question = fmt({ + hintColor, type: errorMessage !== "" ? "M_ERROR" : "M_GENERAL", title, titleColor, @@ -69,6 +71,7 @@ export async function numberPrompt( if (prompt.trim() === "" && defaultValue !== undefined) { deleteLastLine(); const defaultMsg = fmt({ + hintColor, type: "M_MIDDLE", title: ` ${defaultValue}`, borderColor, diff --git a/src/components/password/password-main.ts b/src/components/password/password-main.ts index d9f7bc9..438025b 100644 --- a/src/components/password/password-main.ts +++ b/src/components/password/password-main.ts @@ -15,18 +15,18 @@ export async function passwordPrompt( options: PromptOptions, ): Promise> { const { - title, + title = "", hint, + hintColor = "gray", validate, schema, defaultValue, - titleColor = "cyanBright", - + titleColor = "blueBright", titleTypography = "bold", titleVariant, content, - contentColor, - contentTypography, + contentColor = "dim", + contentTypography = "italic", contentVariant, borderColor = "viceGradient", variantOptions, @@ -37,6 +37,7 @@ export async function passwordPrompt( while (true) { const question = fmt({ + hintColor, type: errorMessage !== "" ? "M_ERROR" : "M_GENERAL", title: `${title}${defaultValue ? ` [Default: ****]` : ""}`, titleColor, diff --git a/src/components/prompts/index.ts b/src/components/prompts/index.ts index f5ad090..c69f36e 100644 --- a/src/components/prompts/index.ts +++ b/src/components/prompts/index.ts @@ -1,6 +1,13 @@ export type { ColorName } from "~/types/general.js"; export type { ChoiceOptions } from "~/types/general.js"; -export type { OptionalPromptOptions } from "~/types/general.js"; +export type { PromptOptions } from "~/types/general.js"; +export { + isUnicodeSupported, + getCurrentTerminalName, + pm, + pmv, + pkg, +} from "~/utils/platforms.js"; export { deleteLastLine, deleteLastLines, diff --git a/src/components/select/num-select.ts b/src/components/select/num-select.ts index c686e50..c990395 100644 --- a/src/components/select/num-select.ts +++ b/src/components/select/num-select.ts @@ -20,17 +20,18 @@ export async function numSelectPrompt( }, ): Promise> { const { - title, + title = "", hint, + hintColor = "gray", validate, defaultValue, schema, - titleColor = "cyanBright", + titleColor = "blueBright", titleTypography = "bold", titleVariant, content, - contentColor, - contentTypography, + contentColor = "dim", + contentTypography = "italic", contentVariant, borderColor = "viceGradient", variantOptions, @@ -55,6 +56,7 @@ export async function numSelectPrompt( } const question = fmt({ + hintColor, type: errorMessage !== "" ? "M_ERROR" : "M_GENERAL", title, titleColor, @@ -94,6 +96,7 @@ export async function numSelectPrompt( // Combine question and choices const formattedPrompt = fmt({ + hintColor, type: "M_NULL", title: `${question}${choicesText}\n${formattedBar} ${colorize( `Enter your choice:`, diff --git a/src/components/select/select-main.ts b/src/components/select/select-main.ts index bc148c3..8b6a6e0 100644 --- a/src/components/select/select-main.ts +++ b/src/components/select/select-main.ts @@ -1,6 +1,7 @@ import { stdin as input, stdout as output } from "node:process"; import readline from "node:readline"; import { cyanBright, dim, greenBright, redBright } from "picocolors"; +import terminalSize from "terminal-size"; import type { ColorName, @@ -24,20 +25,40 @@ export async function selectPrompt(params: { endTitle?: string; endTitleColor?: ColorName; maxItems?: number; + debug?: boolean; + terminalHeight?: number; + availableHeight?: number; + computedMaxItems?: number; + displayItems?: number; + startIdx?: number; + endIdx?: number; + shouldRenderTopEllipsis?: boolean; + shouldRenderBottomEllipsis?: boolean; + linesRendered?: number; }): Promise { const { - title, + title = "", options, defaultValue, required = false, borderColor = "viceGradient", - titleColor = "cyanBright", + titleColor = "blueBright", titleTypography = "bold", titleVariant, border = true, - endTitle = "πŸ‘‹", - endTitleColor = "passionGradient", + endTitle = "", + endTitleColor = "dim", maxItems, + debug = false, + terminalHeight, + availableHeight, + computedMaxItems, + displayItems, + startIdx, + endIdx, + shouldRenderTopEllipsis, + shouldRenderBottomEllipsis, + linesRendered, } = params; // Initialize selectedIndex to the defaultValue if it's not disabled @@ -65,7 +86,7 @@ export async function selectPrompt(params: { const formattedBar = bar({ borderColor }); - let linesRendered = 0; + let currentLinesRendered = 0; const instructions = `Use <↑/↓> or to navigate, to select, to exit`; let errorMessage = ""; // Initialize error message @@ -74,11 +95,12 @@ export async function selectPrompt(params: { function renderOptions() { // Move cursor up to the start of the options if not the first render - if (linesRendered > 0) { - process.stdout.write(`\x1B[${linesRendered}A`); + if (currentLinesRendered > 0) { + process.stdout.write(`\x1B[${currentLinesRendered}A`); } let outputStr = `${greenBright(symbols.step_active)} ${fmt({ + hintColor: "gray", type: "M_NULL", title, titleColor, @@ -93,45 +115,61 @@ export async function selectPrompt(params: { outputStr += `${formattedBar} ${dim(instructions)}\n`; } - // Determine max items based on terminal size and provided maxItems - const terminalHeight = process.stdout.rows || 24; // Default to 24 if undefined - const availableHeight = terminalHeight - 4; // Header and footer adjustment - const computedMaxItems = Math.min( - maxItems ?? Infinity, - availableHeight > 0 ? availableHeight : Infinity, - options.length, - ); + // Determine effective properties based on provided params or defaults + const size = terminalSize(); // Get terminal size + const effectiveTerminalHeight = terminalHeight ?? size.rows ?? 24; // Fallback to 24 if rows is undefined + const effectiveAvailableHeight = + availableHeight ?? effectiveTerminalHeight - 4; // Header and footer adjustment + const effectiveComputedMaxItems = + computedMaxItems ?? + Math.min( + maxItems ?? Infinity, + effectiveAvailableHeight > 0 ? effectiveAvailableHeight : Infinity, + options.length, + ); const minItems = 3; // Minimum number of items to display for better UX - const displayItems = Math.max(computedMaxItems, minItems); + const effectiveDisplayItems = + displayItems ?? Math.max(effectiveComputedMaxItems, minItems); - let startIdx = 0; - let endIdx = options.length - 1; + let effectiveStartIdx: number; + let effectiveEndIdx: number; - if (options.length > displayItems) { - const half = Math.floor(displayItems / 2); + if (startIdx !== undefined && endIdx !== undefined) { + // Use provided indices if available + effectiveStartIdx = startIdx; + effectiveEndIdx = endIdx; + } else { + // Compute indices dynamically based on selectedIndex and displayItems + effectiveStartIdx = 0; + effectiveEndIdx = options.length - 1; + + if (options.length > effectiveDisplayItems) { + const half = Math.floor(effectiveDisplayItems / 2); - // Adjust startIdx and endIdx to center the selectedIndex - startIdx = selectedIndex - half; - endIdx = selectedIndex + (displayItems - half - 1); + // Adjust startIdx and endIdx to center the selectedIndex + effectiveStartIdx = selectedIndex - half; + effectiveEndIdx = selectedIndex + (effectiveDisplayItems - half - 1); - if (startIdx < 0) { - startIdx = 0; - endIdx = displayItems - 1; - } else if (endIdx >= options.length) { - endIdx = options.length - 1; - startIdx = options.length - displayItems; + if (effectiveStartIdx < 0) { + effectiveStartIdx = 0; + effectiveEndIdx = effectiveDisplayItems - 1; + } else if (effectiveEndIdx >= options.length) { + effectiveEndIdx = options.length - 1; + effectiveStartIdx = options.length - effectiveDisplayItems; + } } } - // Determine if ellipses should be displayed - const shouldRenderTopEllipsis = startIdx > 0; - const shouldRenderBottomEllipsis = endIdx < options.length - 1; + const effectiveShouldRenderTopEllipsis = + shouldRenderTopEllipsis ?? effectiveStartIdx > 0; + const effectiveShouldRenderBottomEllipsis = + shouldRenderBottomEllipsis ?? effectiveEndIdx < options.length - 1; - if (shouldRenderTopEllipsis) { + if (effectiveShouldRenderTopEllipsis) { outputStr += `${formattedBar} ${dim("...")}\n`; } - for (let index = startIdx; index <= endIdx; index++) { + for (let index = effectiveStartIdx; index <= effectiveEndIdx; index++) { const option = options[index]; const isSelected = index === selectedIndex; const isDisabled = option.disabled; @@ -147,17 +185,32 @@ export async function selectPrompt(params: { outputStr += `${formattedBar} ${prefix}${optionLabel}${hint}\n`; } - if (shouldRenderBottomEllipsis) { + if (effectiveShouldRenderBottomEllipsis) { outputStr += `${formattedBar} ${dim("...")}\n`; } // Calculate lines rendered - linesRendered = + currentLinesRendered = + linesRendered ?? 1 + // Title - 1 + // Instructions or error message - (shouldRenderTopEllipsis ? 1 : 0) + // Top ellipsis - (endIdx - startIdx + 1) + // Displayed options - (shouldRenderBottomEllipsis ? 1 : 0); // Bottom ellipsis + 1 + // Instructions or error message + (effectiveShouldRenderTopEllipsis ? 1 : 0) + // Top ellipsis + (effectiveEndIdx - effectiveStartIdx + 1) + // Displayed options + (effectiveShouldRenderBottomEllipsis ? 1 : 0); // Bottom ellipsis + + if (debug) { + console.log({ + terminalHeight: effectiveTerminalHeight, + availableHeight: effectiveAvailableHeight, + computedMaxItems: effectiveComputedMaxItems, + displayItems: effectiveDisplayItems, + startIdx: effectiveStartIdx, + endIdx: effectiveEndIdx, + shouldRenderTopEllipsis: effectiveShouldRenderTopEllipsis, + shouldRenderBottomEllipsis: effectiveShouldRenderBottomEllipsis, + linesRendered: currentLinesRendered, + }); + } process.stdout.write(outputStr); } @@ -171,15 +224,17 @@ export async function selectPrompt(params: { if (key.name === "c" && key.ctrl) { // Show endTitle message and exit gracefully msg({ type: "M_NEWLINE" }); - msg({ - type: "M_END", - title: endTitle, - titleColor: endTitleColor, - titleTypography, - titleVariant, - border, - borderColor, - }); + if (endTitle !== "") { + msg({ + type: "M_END", + title: endTitle, + titleColor: endTitleColor, + titleTypography, + titleVariant, + border, + borderColor, + }); + } cleanup(true); } return; @@ -228,15 +283,17 @@ export async function selectPrompt(params: { } else if (key.name === "c" && key.ctrl) { // Show endTitle message and exit gracefully msg({ type: "M_NEWLINE" }); - msg({ - type: "M_END", - title: endTitle, - titleColor: endTitleColor, - titleTypography, - titleVariant, - border, - borderColor, - }); + if (endTitle !== "") { + msg({ + type: "M_END", + title: endTitle, + titleColor: endTitleColor, + titleTypography, + titleVariant, + border, + borderColor, + }); + } cleanup(true); } } @@ -248,7 +305,7 @@ export async function selectPrompt(params: { rl.close(); input.removeListener("keypress", onKeyPress); // Move cursor down to the end of options - process.stdout.write(`\x1B[${linesRendered}B`); + process.stdout.write(`\x1B[${currentLinesRendered}B`); if (isCtrlC) { process.exit(); // Exit the process without throwing an error } else { diff --git a/src/components/select/select-two.ts b/src/components/select/select-two.ts index c61b10e..5c9f930 100644 --- a/src/components/select/select-two.ts +++ b/src/components/select/select-two.ts @@ -12,11 +12,11 @@ import { resetCursorAndClear, moveCursorAndClear } from "~/utils/readline.js"; export async function selectPrompt(options: PromptOptions): Promise { const { - title, + title = "", choices, defaultValue, schema, - titleColor = "cyanBright", + titleColor = "blueBright", titleTypography = "bold", } = options; diff --git a/src/components/st-end/end.ts b/src/components/st-end/end.ts index 07fcca9..5f5bad1 100644 --- a/src/components/st-end/end.ts +++ b/src/components/st-end/end.ts @@ -4,8 +4,8 @@ import { animateText } from "~/components/visual/animate/animate.js"; import { msg } from "~/utils/messages.js"; export async function endPrompt({ - title, - titleColor = "cyanBright", + title = "", + titleColor = "blueBright", titleTypography = "bold", titleVariant, titleAnimation, diff --git a/src/components/st-end/start.ts b/src/components/st-end/start.ts index fc110bb..7877d53 100644 --- a/src/components/st-end/start.ts +++ b/src/components/st-end/start.ts @@ -1,15 +1,23 @@ import type { PromptOptions } from "~/types/general.js"; +import { getCurrentTerminalName } from "~/main.js"; import { msg } from "~/utils/messages.js"; +import { pkg, pm, pmv } from "~/utils/platforms.js"; export async function startPrompt({ - title, - titleColor = "cyanBright", + title = `@reliverse/prompts v${pkg.version} | ${pm} v${pmv} | ${getCurrentTerminalName()}`, + titleColor = "blueBright", titleTypography = "bold", titleVariant, borderColor = "viceGradient", - clearConsole = true, -}: PromptOptions): Promise { + clearConsole = false, + horizontalLine = true, + horizontalLineLength = 30, +}: PromptOptions & { + clearConsole?: boolean; + horizontalLine?: boolean; + horizontalLineLength?: number; +}): Promise { if (clearConsole) { console.clear(); console.log(); @@ -24,6 +32,8 @@ export async function startPrompt({ titleTypography, titleVariant, borderColor, + horizontalLine, + horizontalLineLength, }); if (!process.stdout.isTTY) { diff --git a/src/components/toggle/index.ts b/src/components/toggle/index.ts index f7a49f8..0a5356a 100644 --- a/src/components/toggle/index.ts +++ b/src/components/toggle/index.ts @@ -24,16 +24,16 @@ export async function togglePrompt(params: { endTitleColor?: ColorName; }): Promise { const { - title, + title = "", options, initial, borderColor = "viceGradient", - titleColor = "cyanBright", + titleColor = "blueBright", titleTypography = "bold", titleVariant, border = true, - endTitle = "πŸ‘‹", - endTitleColor = "passionGradient", + endTitle = "", + endTitleColor = "dim", } = params; let selectedIndex = initial @@ -62,6 +62,7 @@ export async function togglePrompt(params: { } let outputStr = `${greenBright(symbols.step_active)} ${fmt({ + hintColor: "gray", type: "M_NULL", title: title, titleColor, @@ -128,16 +129,17 @@ export async function togglePrompt(params: { msg({ type: "M_NEWLINE", }); - msg({ - type: "M_END", - title: endTitle, - titleColor: endTitleColor, - titleTypography, - titleVariant, - border, - borderColor, - addNewLineBefore: true, - }); + if (endTitle !== "") { + msg({ + type: "M_END", + title: endTitle, + titleColor: endTitleColor, + titleTypography, + titleVariant, + border, + borderColor, + }); + } cleanup(true); // Passing a flag to indicate a graceful exit } } diff --git a/src/components/visual/animate/animate.ts b/src/components/visual/animate/animate.ts index e2055d7..751f24d 100644 --- a/src/components/visual/animate/animate.ts +++ b/src/components/visual/animate/animate.ts @@ -26,11 +26,11 @@ function calculateDelay(text: string): number { } export async function animateText({ - title, + title = "", anim, delay, type = "M_INFO", - titleColor = "cyanBright", + titleColor = "blueBright", titleTypography = "bold", border = true, borderColor = "viceGradient", diff --git a/src/types/general.ts b/src/types/general.ts index c8fcc93..11b55a9 100644 --- a/src/types/general.ts +++ b/src/types/general.ts @@ -81,6 +81,7 @@ export type FmtMsgOptions = { contentTypography?: TypographyName; contentVariant?: VariantName; hint?: string; + hintColor?: ColorName; border?: boolean; borderColor?: ColorName; dontRemoveBar?: boolean; @@ -93,14 +94,13 @@ export type FmtMsgOptions = { addNewLineBefore?: boolean; addNewLineAfter?: boolean; placeholder?: string; + horizontalLine?: boolean; + horizontalLineLength?: number; }; -export type RequiredPromptOptions = { - title: string; -}; - -export type OptionalPromptOptions = { +export type PromptOptions = { schema?: T; + title?: string; titleColor?: ColorName; titleTypography?: TypographyName; titleVariant?: VariantName; @@ -125,7 +125,6 @@ export type OptionalPromptOptions = { action?: () => Promise; border?: boolean; borderColor?: ColorName; - clearConsole?: boolean; additionalLinesToDelete?: number; hintColor?: ColorName; hints?: string[]; @@ -135,9 +134,6 @@ export type OptionalPromptOptions = { endTitleColor?: ColorName; }; -export type PromptOptions = RequiredPromptOptions & - OptionalPromptOptions; - export type ChoiceRequiredOptions = { id: string; title: string; diff --git a/src/types/internal.ts b/src/types/internal.ts index 20c198b..7253eda 100644 --- a/src/types/internal.ts +++ b/src/types/internal.ts @@ -1,6 +1,6 @@ import type { TSchema } from "@sinclair/typebox"; -import type { OptionalPromptOptions, PromptType } from "./general.js"; +import type { PromptOptions, PromptType } from "./general.js"; export type StateDeprecated = | "initial" @@ -25,7 +25,7 @@ export type RequiredPromptOptionsDeprecated = { export type PromptOptionsDeprecated = RequiredPromptOptionsDeprecated & - OptionalPromptOptions & { + PromptOptions & { state?: StateDeprecated; stateCompletedTitle?: string; }; diff --git a/src/utils/messages.ts b/src/utils/messages.ts index ea17a58..3c262ce 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -1,4 +1,4 @@ -import { greenBright, redBright ,dim} from "picocolors"; +import { greenBright, redBright, dim } from "picocolors"; import type { ColorName, @@ -88,8 +88,10 @@ export function fmt(opts: FmtMsgOptions): string { : symbols.end + symbols.line; const suffixStartLine = opts.borderColor - ? colorMap[opts.borderColor](`${symbols.line.repeat(28)}⊱`) - : `${symbols.line.repeat(28)}⊱`; + ? colorMap[opts.borderColor]( + `${symbols.line.repeat(opts.horizontalLineLength ?? 30)}⊱`, + ) + : `${symbols.line.repeat(opts.horizontalLineLength ?? 30)}⊱`; const suffixEndLine = opts.borderColor ? colorMap[opts.borderColor](`${symbols.line.repeat(58)}⊱`) @@ -207,8 +209,19 @@ export function fmt(opts: FmtMsgOptions): string { opts.titleVariant, opts.borderColor, ); + function validateColorName( + colorName: string, + ): asserts colorName is ColorName { + if (!colorMap[colorName as ColorName]) { + throw new Error(`Invalid color name: ${colorName}`); + } + } if (opts.hint) { - formattedTitle += `\n${borderWithSpace}${colorMap.blueBright(opts.hint)}`; + validateColorName(opts.hintColor); + const hintColor = + opts.hintColor && colorMap[opts.hintColor] ? opts.hintColor : "dim"; + const colorFunc = colorMap[hintColor] || ((text: string) => text); + formattedTitle += `\n${borderWithSpace}${colorFunc(opts.hint)}`; } if (opts.errorMessage) { const formattedError = applyStyles( @@ -258,5 +271,7 @@ export function fmt(opts: FmtMsgOptions): string { } export function msg(opts: FmtMsgOptions): void { - console[opts.type === "M_ERROR" || opts.type === "M_ERROR_NULL" ? "error" : "log"](fmt(opts)); + console[ + opts.type === "M_ERROR" || opts.type === "M_ERROR_NULL" ? "error" : "log" + ](fmt(opts)); } diff --git a/src/utils/platforms.ts b/src/utils/platforms.ts index a86d444..8b23508 100644 --- a/src/utils/platforms.ts +++ b/src/utils/platforms.ts @@ -1,34 +1,99 @@ -import process from "node:process"; - -const platforms = { - linux: process.platform !== "win32", - win32: process.platform === "win32", -}; - -const terminals = { - linuxConsoleKernel: process.env.TERM !== "linux", - windowsTerminal: Boolean(process.env.WT_SESSION), - terminusSublimeOld: Boolean(process.env.TERMINUS_SUBLIME), - cmderTerminal: process.env.ConEmuTask === "{cmd::Cmder}", - terminusSublimeNew: process.env.TERM_PROGRAM === "Terminus-Sublime", - visualStudioCode: process.env.TERM_PROGRAM === "vscode", - xterm256color: process.env.TERM === "xterm-256color", - alacrittyTerminal: process.env.TERM === "alacritty", - jetbrainsJediTerm: process.env.TERMINAL_EMULATOR === "JetBrains-JediTerm", -}; - -export function isUnicodeSupported() { - if (platforms.linux) { - return terminals.linuxConsoleKernel; +import { detect, getNpmVersion } from "detect-package-manager"; +import { env, isWindows, isLinux, isMacOS } from "std-env"; + +import packageJson from "../../package.json" with { type: "json" }; + +/** + * Determines if Unicode is supported in the current terminal. + * @returns {boolean} True if Unicode is supported, false otherwise. + */ +export function isUnicodeSupported(): boolean { + if (isLinux) { + return env.TERM !== "linux"; } return ( - terminals.windowsTerminal || - terminals.terminusSublimeOld || - terminals.cmderTerminal || - terminals.terminusSublimeNew || - terminals.visualStudioCode || - terminals.xterm256color || - terminals.alacrittyTerminal || - terminals.jetbrainsJediTerm + env.WT_SESSION !== undefined || + env.TERMINUS_SUBLIME !== undefined || + env.ConEmuTask === "{cmd::Cmder}" || + env.TERM_PROGRAM === "Terminus-Sublime" || + env.TERM_PROGRAM === "vscode" || + env.TERM === "xterm-256color" || + env.TERM === "alacritty" || + env.TERMINAL_EMULATOR === "JetBrains-JediTerm" ); } + +/** + * Determines the current terminal name based on environment variables. + * @returns {string} The name of the current terminal or "Unknown" if it cannot be determined. + */ +export function getCurrentTerminalName(): string { + const termProgram = env.TERM_PROGRAM; + const term = env.TERM; + const terminalEmulator = env.TERMINAL_EMULATOR; + + if (termProgram) { + switch (termProgram.toLowerCase()) { + case "vscode": + return "VSCode Terminal"; + case "terminus-sublime": + return "Terminus Sublime"; + case "hyper": + return "Hyper"; + case "iterm.app": + case "iterm": + return "iTerm2"; + case "alacritty": + return "Alacritty"; + case "wezterm": + return "WezTerm"; + case "terminus": + return "Terminus"; + default: + return `TERM_PROGRAM: ${termProgram}`; + } + } + + if (terminalEmulator) { + switch (terminalEmulator.toLowerCase()) { + case "jetbrains-jediterm": + return "JetBrains JediTerm"; + case "cmder": + return "Cmder"; + case "conemu": + return "ConEmu"; + default: + return `TERMINAL_EMULATOR: ${terminalEmulator}`; + } + } + + if (term) { + switch (term.toLowerCase()) { + case "xterm-256color": + return "Xterm 256 Color"; + case "alacritty": + return "Alacritty"; + case "xterm": + return "Xterm"; + case "linux": + return "Linux Console Kernel"; + default: + return `TERM: ${term}`; + } + } + + // Fallback based on platform if terminal cannot be determined + if (isWindows) { + return "Windows Terminal"; + } else if (isMacOS) { + return "macOS Terminal"; + } else if (isLinux) { + return "Linux Terminal"; + } + + return "Unknown Terminal"; +} + +export const pm = await detect(); +export const pmv = await getNpmVersion(pm); +export const pkg = packageJson;