From 0dbcdcfcf4e55d8baf14fb8ba99d1bd957480aa4 Mon Sep 17 00:00:00 2001 From: Hugo Pierret Date: Sun, 26 Apr 2026 17:24:38 +0200 Subject: [PATCH] Refactor: responsive upload page, fix warnings, CI/CD setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix MUI v4/React 18 StrictMode warnings (justify→justifyContent, Button migration) - Responsive UploadDiary: footer visible without scroll, mobile layout centered, overview text hidden on mobile - Fix Redux selector memoization with createSelector - RSS sync: auto-refresh PosterSelector after sync, Snackbar feedback with film count - Add .gitea/workflows/deploy.yml for tag-based CI/CD - Fix nginx config (default.conf), increase Node heap for Docker build - Update README Co-Authored-By: Claude Sonnet 4.6 --- .gitea/workflows/deploy.yml | 34 ++ .gitignore | 3 + Dockerfile | 4 +- README.md | 39 +- assets/project.jpg | Bin 82535 -> 0 bytes assets/project.png | Bin 0 -> 66246 bytes nginx-spa.conf | 2 +- package-lock.json | 867 ++++++++++++++++----------- package.json | 1 + src/App.js | 45 +- src/components/Cart.js | 204 ------- src/components/DownloadButton.js | 45 -- src/components/Header.js | 252 ++++++++ src/components/NavBar.js | 74 --- src/components/PosterGallery.js | 167 ------ src/components/PosterSelector.js | 256 -------- src/components/UploadDiary.js | 192 ------ src/index.js | 6 +- src/pages/PosterGallery.js | 150 +++++ src/pages/PosterSelector.js | 388 ++++++++++++ src/pages/SelectionRecap.js | 173 ++++++ src/pages/UploadDiary.js | 236 ++++++++ src/services/action.js | 7 + src/services/api.js | 23 +- src/services/store.js | 19 +- src/styles/App.scss | 1 + src/styles/scss/_navbar.scss | 208 ++++++- src/styles/scss/_posterGallery.scss | 104 +++- src/styles/scss/_posterSelector.scss | 522 +++++++++++++--- src/styles/scss/_selectionRecap.scss | 187 ++++++ src/styles/scss/_uploadDiary.scss | 195 +++++- 31 files changed, 2896 insertions(+), 1508 deletions(-) create mode 100644 .gitea/workflows/deploy.yml delete mode 100644 assets/project.jpg create mode 100644 assets/project.png delete mode 100644 src/components/Cart.js delete mode 100644 src/components/DownloadButton.js create mode 100644 src/components/Header.js delete mode 100644 src/components/NavBar.js delete mode 100644 src/components/PosterGallery.js delete mode 100644 src/components/PosterSelector.js delete mode 100644 src/components/UploadDiary.js create mode 100644 src/pages/PosterGallery.js create mode 100644 src/pages/PosterSelector.js create mode 100644 src/pages/SelectionRecap.js create mode 100644 src/pages/UploadDiary.js create mode 100644 src/styles/scss/_selectionRecap.scss diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..a09493b --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,34 @@ +name: Deploy Frontend + +on: + push: + tags: + - 'v*.*.*' + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Extract version tag + run: echo "VERSION_TAG=${GITHUB_REF_NAME}" >> $GITHUB_ENV + + - name: Build Docker image + run: | + docker build \ + --build-arg REACT_APP_API_URL="" \ + -t poster-picker-frontend:latest \ + -t poster-picker-frontend:${{ env.VERSION_TAG }} . + + - name: Stop old container + run: | + cd ~/dev-server/poster-picker + docker compose stop frontend || true + + - name: Start new container + run: | + cd ~/dev-server/poster-picker + docker compose up -d --no-build frontend diff --git a/.gitignore b/.gitignore index 8070e75..c56f793 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +*storybook.log +storybook-static diff --git a/Dockerfile b/Dockerfile index 5bcac21..25c61e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /app COPY package*.json ./ -ENV NODE_OPTIONS="--max-old-space-size=512" +ENV NODE_OPTIONS="--max-old-space-size=2048" RUN npm ci --silent --legacy-peer-deps COPY . . @@ -22,7 +22,7 @@ FROM nginx:stable-alpine AS runner # Copy build output COPY --from=builder /app/build /usr/share/nginx/html -COPY nginx-spa.conf /etc/nginx/conf.d/nginx-spa.conf +COPY nginx-spa.conf /etc/nginx/conf.d/default.conf # Expose port 80 (Caddy fera reverse proxy) EXPOSE 80 diff --git a/README.md b/README.md index 4de716c..281acd7 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,18 @@ -# Movie Poster Gallery +# Letterboxd Diary Poster Picker -![Project Logo](/assets/project.jpg) +![Project Screenshot](/assets/project.png) -A web application that allows users to browse and select their favorite movie posters based on their Letterboxd diary. +A web application to browse and select your favorite movie posters from your Letterboxd diary. ## Features -- **Letterboxd Integration**: Import your movie diary directly from uploading a CSV file. -- **Poster Gallery**: Browse through posters of movies you've watched. -- **User-friendly Interface**: Intuitive design for easy navigation and selection. -- **Responsive Design**: Works seamlessly on desktop and mobile devices. +- **CSV Import**: Upload your Letterboxd diary export to load your full watch history. +- **RSS Sync**: Sync your ~50 most recent diary entries directly via your Letterboxd username — no export needed. +- **Poster Gallery**: Browse all available posters for each movie via TMDB. +- **Poster Selection**: Pick your favorite poster for each film. +- **Selection Recap**: Review all your selections in one place. +- **ZIP Download**: Download all selected posters as a ZIP file. +- **Responsive Design**: Works on desktop and mobile. ## Getting Started @@ -17,6 +20,7 @@ A web application that allows users to browse and select their favorite movie po - Node.js (v14.0.0 or later) - npm (v6.0.0 or later) +- A running instance of the [backend](https://github.com/Hugyouu/Letterboxd-Diary-Posters-Picker-backend) ### Installation @@ -48,18 +52,19 @@ A web application that allows users to browse and select their favorite movie po ## Usage -1. On the homepage, upload your diary CSV file. -2. Once processed, you'll be redirected to the Poster Selector page. -3. Browse through your watched movies and select your favorite posters. -4. (Add any additional steps or features here) +1. **Import your diary** — either upload a `diary.csv` exported from [letterboxd.com/data/export](https://letterboxd.com/data/export/), or enter your Letterboxd username to sync via RSS. +2. **Browse posters** — for each film in your diary, scroll through the available posters fetched from TMDB. +3. **Select a poster** — click to pick your favorite for each movie. +4. **Download** — use the download button in the header to export all selected posters as a ZIP. -## Technologies Used +## Technologies -- React.js -- Material-UI -- Backend private for now +- React / Redux +- Material UI +- Flask (backend) +- TMDB API ## Acknowledgments -- [Letterboxd](https://letterboxd.com/) for the inspiration and data (diary.csv) -- [The Movie Database (TMDb)](https://www.themoviedb.org/) for the movie poster images +- [Letterboxd](https://letterboxd.com/) for the diary data +- [The Movie Database (TMDb)](https://www.themoviedb.org/) for the poster images diff --git a/assets/project.jpg b/assets/project.jpg deleted file mode 100644 index a522d8235e393b449bfe22d9ad03b936ef1d8392..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82535 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

CBk!Jo!c(6cCsXF^zV%JG5v3S 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 diff --git a/assets/project.png b/assets/project.png new file mode 100644 index 0000000000000000000000000000000000000000..0fc443cdb51243f7f39a7691f063eae2c64f46c4 GIT binary patch literal 66246 zcmeFZcT`i+wl^9p2nseprP+{<0){HR2}ltL5UPmOP^5(3L;(c>=~ATE&dkn|kVXwW`TyxFxn`_PKCv{bYD-?GqKp@Z+ z#pkjbAkc*mAkeu&@{7PhmD>lZkKfm(0Z_Ap;%-T#lHh!)sE4}94owX^@&nFEMl5f!b zQ+|H@$=L19DJd_sabUa?RnCkj+AEA(q_Ri>poMt(A&tI5|2~31N#Dpnoca6X`~ClU zBy!?^`0&4%JlOP>!o|$50h3}=5$?U#mPf;`b^#@zFNk7I>-4^zI38 zYm;rZt%z}#yJ99-{4gBJ;QvbECpL$}@!*=Acez644ujJzpj8ROSrgzE&EfiMSh1@W z#X?JY4iL9={|zNZpm7=-(o`i&+}G}H;E%|g=20+7Lq^Y3^k3rSzZew)jqgTR(<`vA z#y6V_vt^x%8MTb+2(|;?`mtOk2VPH=XI+FPew5C^1XXVr*bHf8(kiii$k?cNOIu0JWQhDwD5m9BbBXP%DvW|H z>&<26xO)?+xEf3{EvbPJB;2ZS#u~C*J<@E3kaLa|mXlx=Eq~>C!Fyjpj{P5AkdZb>CM!y^YQ|BY|;ED~nd=tLB#3LlzFxBl#?Q!;Br<H}3~>H2an1dZ1{v1F z)`rW~dUF@PcM=LtxIa}KJs7anl(9{@mGJKEsS-BV$6Azoc;puY2QR) zdsKvRv`lX;gRH{}=@_>cv|d9#!+?8P;JMns!xoLKJJ= z8rwV{%@9z$#1FQF=UF%)2;~1Psmu& zKD{^{{zzxBZentIjWDy*Tgu$=Cw0eX=RHnylS7dsj^^jP->q+#7TfHd_fb%#(UJ}Z zqW4s~LR2|P^6FZ`Ev>xPE)S}4^4BQHx^IrhKg?su2a`OsZPDPcMJZJ z;v}&h9#+dHaXEH&ck;*2_#TbWK<~^pIz!vPpNC2UdVTjfB($Sfbx$kRSVLA;*7>x8X5OqbBk~EF0!6lwMOPxn>hr*e8 z^|$OL-}f99`u!hBE%7PbC@y~Q5b#!Nm+4kUuKdJ30pPQ@A|$2o@A=&xmJ2Bf`Vkm% zpPob71-xs-8S6M#z-NJoAN1s8PP*P4F^_B8{E*4YLAjSYmz4oCyNl0M!LS0 z`Z1JWrSfJ&j1{0{>cw56y(GWy*|%cOK6~DD^y4cp)utG@WeUkC{d0haS;H~Ow*2k) zSlD#P$^JNTgsqgzY*iyW`G8K;ZAjuTWv-%>38i3PWl-tpyG`>*|2Nss+oOYtEI^Y~ z*}t6cu=fpcxoglguNAc+g!F1I|7?>=9*{WqWg0Llu|Pw=>m&nC&c`Y9?5g&c3QHmS zLo!rl5m*^`Dfx+HbI-@e97#)DrOLiq3C!bJT%@C|k=1|t7}dWb@`Y(6&dS!G4QTha z_F>riXkOCJQzW2Ipy=n8r^-az%~kO_rI9NjI^+RK3JliLZ6a##rWZk}*o$OG)FNKpS(pqN7{@u^^VetC;CCN;i`Kq5Dd>CXIeHlt&Q03ch(myNN3(+S!hc4B()~KN@N$ zU$A|^Q`|Ga6_faC(Zs8^ugXzIm^gnPl3BeA7dZdZ_I|eku;#?q24m_}z}u|PfAz9( zoH>uHsuSp!|8`q)>yz52)rM9gXjc?T^8TTLU$)#bG3?jOg)_?~WCY=qQLCQ=d$aC| zIaH9uN0t7sp6bV6AF+La7)r9fY1GaYS3`Lp$I(VRGTJ3~`ie-jiP&~!0^Bk z`J_)N(2+%EV72_pBuUcyt1FDYJ;qmPwE5Zy;XO-h#gZWMJn(Ij0PwBQ%(EJ1Ch`5L z_tcZ&j0!7CVqKwg!OJg>JNlfDiw)ZxvJM%jy*7T3F>lQpinf_1ivTkJ5rF7Cr*voh z`0sX@M~yS_puXYfK{rBnT2TS z5vXZszmEhhxEA$S>EDT;5fhqhAHMJlaC{;aQg8@+1XbZ}qtz)Peiz(;3Cw||D8ylTO6|Yj91Dz#u zY;N(F@pdWSElG#tJsnFx_=G2C3spXn{bX{jv7o!48thjCtFs{006?zVrt z%cO5fZaRcs0+HVYBr7`TnA4dDb0)gu@O?yETWS2<7Y!y8TlWuLX6&qDD$t|KL^BY% z`b5Q}IDqc^Bvt<4x5AJp!0`DgFvV{fc~B1c+%=K7tj~qZ9oGSIQb&-KS^5_)y2pVYv6RmVzW< zC_8FSRn#UHa29?VGzO>X0A-R9+9xyy`5$0RLsqfD10sAR0dcYZQX*Inj<> zZg$dfrSNYgl~rOSsVq6PLZVumT)D57pxmhu(Uy+2AH6=NZfCJ4mBsK0a4|2MQ%Hu3 z&GRqzc?0=Ucr`Y2qvF&8psqRMRjjJMB9l|s$wQ=bv)kBk+n8!Q5!yzJk#zm4j-MKTlG*SFiEl_9G zIn$kQM?pIhI_O}wys*RQ;j}I?Ub*uTO;ILrjrEO9mLbtE9j|;yF zW1nrgJ+#o;82e2wY3WgF7<`!Iii;`z@gv*2_lk3>_5;RZb!h(H8(iMws7}S4xkw}G zd^===Vq}@4jmU~4$F44T?(2>BJ!$UiOW#*q0nc_bgj38=ATjfXFImJ9uqh)B=vC@7 zfGKQdVmCCvd%+DIj?ed7sw#Gy_WDqHNe>~G1Jdx6gC4G+53_>IeWewXfbz1@0@5Ox zYIz&bgscAAtJD***W{qS-(TWPN*8+Qh1Z!jmCFZX{KLxL+MO%y(Z};LTW=TzgZ%qS zG0K0p2_z{8zbC&%{Cj$k?+JKBmWnf|} zFvV1_;cqdB&rd=Cw?}jBnn^4ubtsIfYWZhEON=_Wok3*AB`W|n9cb+_g&NWOrtyP; z$7$`sQOw~CL(Pe9+RFP@fR4C&NBHpl`<;0f9Y1PLUp8kO11K%}xwcECjeueqJ3gv2 z=`1kkR$4}VtSRjoP*%sx%eB_Q2jL2i@^bO~09NbDcQ7!9kDA4{j~5M)7f!7ABoL^$ zC+V{=W<#9Szib5LekEs6gzD!?XV3;)k7-=n41Gh%c`A|6K31WKGG62k9|bVJzgbr7a8sS zHiggCez2E?-`S?qbsq42cSxG?A5>d(!psrS&KH>Cq{53a$eTCjw6jOEG3+&~%-Y#P z&TNf#KJ-ez=RZbf7-%G~T(kREACCh>nB|NrHKNk~}XGB2R`8uOcM_&3WDEf0h<(mQVZ1$HVAq|RU9Tn49^ zxQ24*Zoxjz+cV!Swl@@1+^wxMDAADbn(Y>MQbHFW-xASBt-#pS7nLWb?a?o5v?|F~ql93ptQ7-<># zDms0`(y-6<^L!UmdgVNEi|{N7nfcw{>Cx=o>1_h5#++YR!dEBb2GmuNRM8YD4&wne zd$nrAO+f2`<|I%6n!2pm>)&!+t2k}b!SL~}w_ABO*H~(_&9>)C@aoRvP#lc~)$>Ss zI4^Vf$q(SO3nWhfBInWf#Rxh*TCR~AAHa3#>=2Bqc<;$$_vkZYvWIwviBDs_;^JIk^6X28C~O14TJ^rN*>^l_cNzwVhipUY{+T1|1DVy68ioIKMj zZ4L4ulbvky-L?f(=j4~)7yI-C?$VdnPr+UogU@Ybi-<5RXLkY&qwTY9$I#kH|as%jy^2H1e#Iq{P;LJP*)Nc?ORue=;CeGeW4J_ zUGi;#Iwr8Q{pb*F-ux!-!27~pYw#w{O7+VzbI_z5#*nr7!b#sK)2A`X%t4o?bS@_tLMY zy!wqDiU-pJbqi4mLa&60!YD z?E4B8Bn+kg?UYwL#3v!sx11MU{0mszYy9Pof6j}!wm?Lxrn;kV{wW|zdH%LUGj4g8 za(B>BF5pSz$gKEjYoi?&ywIGd#1d2|G9D-j$x=${sy}oQlh{@EKJw`GR+#Y&?X7ZZ zUM{tx%a>BgxxaXz1KF}9m;%M?l@vvTXqXtK=`@oanxF5&E)#pcw{cu z$T$vPZ;Ypn{i0Mhx#LgkvuiBo+hS&|PMCEGc{?v2mvdGq_Mu`Z3U>gNfc0jk;oxxp z>HMT;*_m&J^9O+L8F#w{=V3E~rtqqU@r`z*huI#FdvQ2?@u!i@PBgIW;)1${IC_N~ zSiLv{Az{!>hegqr`QUkq?_SxV;kVTNm{c&foGbtF4gh&8UMdXu>RQ2& zc8TQGFl+0cqHrFHQAA_&&LMZv7j{Cu~oVbEizf9cS5GK5BRCp`M$Feg|WAHffH&25PJ5;-8V z9wxfG-EC2v`9VKuNkZ+=7(zD*tDCQL3%Slga}7Vjxioz;1J7xg;>j%^%t#5tegVu{ z&BwTmyo?-hS+w!q_-!V4$`ja< zzx_MM*^I_%>Var=mx59i5KqD!w^I|Ljk&72a>cC+DNLkz@?# zx|T%)nQ?k-_@S&sV*9sl+d`uNmY)t{7<3Ij8P0l=emj7v5wMJM#5nGI8H1w;;Q}ps zrIZmo-ASf&t8Nx|&_#b|EZf8Hk1~@di=o##y-#CX<>QGQ`s0f{at^p@DltOAujMog z=9(^Fx5Y;Goc@9fag{PDy{XeSFi%^!&zfxtO^zAQnoFoxW_}lY^PaQ7I^8R_g)fbw zGVN7Pt5_eMj(G~_)4=>L73!r)A3fSxG<4H%WRTpA`g!C^jGndMq@XdnuR*>uM(vT; zOJ=bn^`uOpD?ZubJOatC^UdMl;{%!rxw(KASFon!1a(3kGg&`VGGo33>+I@s5#P$cRdJ~|BlWL01lKvwTYp0zY--bPM)(nE{H(cSXN4Nj1)QICWu zcJC6Kp)XG_O2%_3A@C9cTZFn8dZ?98btf;bzEXtRT?+3;<7p|`a(+fFm)Z(5aId!1 z&>shWp+3q(joU4DVMk6eC?ZnsZijcwFxSX1diA}kqAEI(RKu@CqYOR7^V##MZ~N9V zf0$y1z-P`q+h&CNQlr`-`SV&u0)Bhx^^;w+3;Vhi{g@#Yvm_`J{Xo_ z@x3M2gRrQb_PX_s^zZ^g{?oZ%rAv7C=Ve&UxTA|lbuXt+J1OE$j`!-1XYy?0$aare z(cxC%tGQ_z>svfZed(+_b!yAxIU^HCktYsV@?Bfg4~c8CowqjAV?6;&^X<@e^HMlV z+=;<{_C*@_=On97WL|pQY>$joI!D->@=m!|wL0@-j}DKF<6M)Bu*zy`hU-V$9D+Ro zI*Co+R}D66&@ts3A(Q2!w_wgz)DrgEitmdp|I}}ZI#r;PBMnM`jhDahMK?j;lTJK` z$rX)I!_Hm2{K>c>>m7D1KfLnmk`AMxhbCDD?N`qe_xD}kEnaM`BF?E36ne6+!=j|I z<t=aDcBrys*W8o?RQAq#de9^9 zD9;x*1PF6zH3VOOigGPk7c$nF)cb9%6a_hNT$LhmMad>rVl`)0R|7|S}c=bI?_qP#qT($U%T1fRN%Ano2g5{Kv8xtT|JQQusjlzEa&Y$z1l&GqF;j687WJPli{t{ma|D2$&f3j|5D7?ts`Pl9A zz8H0q6fBIru^{GonY`GRtJqVVL_+SvC?C|jx56d3?Lk?H`?CQ2J&X+#a9&!PK zuo|S8>C0AAMJqyzP5e*xbSDz8g=AZ|(ykBheGtGcAt#x8Vz@))?=JfcIB&fIj*#vU z1N)M)4orf;wB{=BvR+z_#PJUcxWPDO(GJSL(2_8)sj*(+=4e0o+2rw&VSVnb8u+;0 zthu|AGp!Jhs}Wm{#br3ifJ<~NDr{ZS^r#acuXaADRdnK;Zrkbfd&ujpP80|it8F3; zBf(3%kIiOUOq=TZ}5(H>z3Co9X89qLaPP>2^6b z;HS%&k!NFqoqhcogmSwt z;gN^W;9Dvj_As#bu=zU84*|Ssxsy zzaItj`|?&68q$@cTJkTn`sM3;FssGGb_x0yC;_M1q(BTe0vN}DBO9781u_bu8l5B7 zHqokiQsoHxtZ2gqy4%tFlpsQd$8U@-d3p5ZYF8mkko%X5HdO+?R)d#e zk1Si|*9n&Pj-{#-GTS_cy+4nHo*SZ{&(v`cxu$c7)dKDpAYofe-JaI1jz{gNx?fB4 z3+9Vhm2I%QE;Ag>CAA4j%AxX8x#p7ciqH7~y2_AwqTTX2z1?rrJ6MoONkQvfb{Ql+ z*teW>xR3#(6>Dvg zP|(n9EGjq2FfzKJb+SO<&{R!2t>lWvCfmh zLq2`~iwJ2g2vX~CkBLW$x=-k~?aM0;II%yz9;nkMgzZBiXwkWmQj;3m7pLLLzSVKr z1>1M_;8S1wI`w{!R0h zbRBuvS{jEwS+Bd58!a~SX7G4uyER;I`5SqD(_Bsdj_1j$wEM!3{EGee{=n|AUnF~1 z1#lqECX=}L^~ZZn$T>xi2PDJH!ueqbwYgzx95h^Ezhg|IN!aBQf^JHIvq5ly(;F5RucoDkPhQ_=vrI`IH4S2j*zXod{?S{rN&LDgO{4?utm1~y6vdXKe-TJW(&DSh^M?JsH-;K|Dwwu zj5SyBO-J$Adqj-5RI7M1`AFIO)M*u!oh)c<32{Sb7<)zaDzUG2)myYDTJ+ks4g&BV zMC2IAhyDrd*1g*@cwOCJIUDY+@wnt z@Lo7$$LnO;+5$z}j7nog-=t&fE@n}0?WtsZm6Ti^pv-4<1o`b`7Y6qvXx$*Hiw9k% zwP`(1h2jWfqq}_}g`JK?@uTIVLb5BNiB^8D?8e8bv`TD;EivJ-G6OYP!(k5;CAzqN z(BE=3&eCp{vHj{(ocy(PBB$y3jN{@DzO`+$Eo;!UkeY1@tk~RWq3AkV8&G-)qAMrA z4y#<-wJN;oL@-To+oF;|k6i9mu6lT@Ny~Qf>MI+VeR^_6;67AkNUw3dzHOl!>3V`5 zV0|K7;kr{_dbVgIg)?t+5{yH;O!#sXz9>ANCmg4EC##(fD5mYvmd`xi@z8l`8<>ow(PdEJGkGzvLR) zliF}wFumQNusCPmN^;cUGt<&aJ)hB<;UO+x)-+`lzGmA^Q^rka_1^t)YuoBmOaBzp zhd{)%f*mB!@Aj+SyCaTM1q(_W;Ir+)dAan9H0k>9byLLKATR-bj>wU^R9I)??brb7 zSj#Nzy0lpZ!IO1eus+hKe}ToV5a88#iMNo ztC-K(~^_0yU;FsZ*vw$ZqIEezQ%~9f1v~%IIsZBY09VO5xeef=Xyb0@?8 z_vVUld#1Eg&;8mlrMgX8#{bniWr-L9fm-}cXf zM`;zew_o5q5Pav^x_Cr?6)(>1HZO1`$%pS=2(yU3#5lHKKb%ET3D~q9v6|}eub}m? z-c#_{NRPBT*iBIHORki$fMLCTv{`9`N;^H#+ZoMcTK=}*YK(5!trxvCV%JX!>^_w` z7p%)(W3okqJNBA5SprMt8=<>$LKV^HCKp?kFIpu$tne~$$|1O-3 zXR+#Dq~4jE_|DW?VTE*YhahrmyZMGHJj`}l^kQhIOl!{$TU&H}CIdc&H+hHg6ndV@ zJ-8g3`NsPcg{OUrLG-W9()DAl^M_@tQWGj-8me78VTUc-AvdRrIu<5S&rRiT((EK( zjN?q5ywsWnXRcqNzo@LSm2*ePP3e0}|E0C}oKNja>~9qabX(5a%HQIOi#^3JjXlwg zq!mjOq+Q|nX-Km6!Cn=c>;LW+Yvg7c5n0w*D`8h-Ah+7H73-PoS@PwH&|Uu13^i@C z%D8b)L0Cxd%WUh;{5r}klis*Pa4Xlmqx~VYpTKAVh&YQZfUG6e6$i`7*3>6meYBM% zO;nOs2#2<~8@LgI8Mk0d@R!ngl`Bbg@_BNz+C>rzxZ#GQOL@Vz0rKz$$Z0|?M=Cb9 z!Z#-S*`RV&{h!BE$yvV1d~e&e+sa(QCF@#*Kk#s|AfDRkcm4qe4Rr-YgvnmDqYxj* zA_@*FTiumvaOuCXF;E zHV0NOsX%Z47XPi?)<(TkoLmpyqjM^#2n?^*q`Sjtz$fUF&TH>(|KT=u-P)1-CuG)P zxYyW1wu@)BL?%+;m&^~FgE1~6II=j+J}UWFLWQ--W={LNj|nl^5*#$#_C zj}x6Cd^s$%9|TWiX>7!p6IRwk>8h^Bb#uIg5l`fQl01iVyZ`wjs+reH zUp^%fj?0EIVS@BK*E#bF4{c=CDsWGqcdHp@PP#A%9q?}(^(S*rTI~%fU^e@bJ?vbr zN$khPVJaHyJvBn(1y;F|!wCR<#8RNz>Uz$rlQf&C# z&loq6`9srp&%J{nsy$~L&ib@4mw{dP;bTmG{bS9RGZ|oS#q_;fE$VBVNF~Y>epy2i z=f3Zkx#T~=1 z>bk}VBHq91Yya#yYZ>Q52vn{!Ih0m((cDLL2P328mDgT&x zI+{fg^%>l`hQ}Q)6W57Q@TJ%{^l)#JCnnHdLKnep-b+Bm)}aOq0PB@R>p?t5PYrtF z|1Jd`pR-m<0VG~?D=aQUUGuEoP263BM>)I4#VJoPor{&GCZ>((-UEaR1hUXdy7c|w!SFtputeO=q>jLd{JEi{tdFM{(I_aNXa*tRQaOBG ze{(IXSKVTS88&g{&$oEeRBV2QYPWqCX|qxP!Ns}P`He!Ym9Kq-zdX$`$pu+Z+gO>v zGAFc3`~y_mO)2*EGM2_TUk>7hs7ASS-8%PQ!?!HI0QFpkxq-7%5}{ zFe>Nj7Q!3~Ha})rNYFwS0F<%M800{;S2s}r$^BfM2cIrF>Rb_QNANo)+CV4bZYGl_ zMg|IQArvDC&nu^!SDv)D1-n{K65yR>95xQ@hlDpsE}ix^ks)L(R68prEO+FXU=CFf z8BoVTFr8(HZpD{PGzy(%RAaaEcPKd$%$i=jh%{9Qgbq#wEpv)K`pL!Vv*Qv_GiDA& zDS!o(DV%C(Bs@@1ZI>4x_QVY)x!Ow~$nf5X0vPu=H}{-tbAlPfz(EVMkonat%Lrl! zjP#@(A>iAX>Y!%f|R5s0M4s|ef`R(&?l#A2K?ScFK zE!7w9LK1V+$G;8~lTtEY2}`(VA}FbIxB43A%lUu)69L7!)EEN^-|uODf?k=>=wvG~ zP0#^(aBKqMtav>Nkl)otKx5dbr7ER`D!joEgLuS~IDICRih$rLC!joRVK=bq!m6mS z3WQg)4h2%0m@fn5I`~~+T+-6?419X`GBE_dmpJ+$d^}rB=FV*8)LGD7S-_q7(F{Kf zxONdx(O2M@yY( -R~`bpCDZyx+f$`)j)`F-2AVg@Mi)kaQ@JQdrc&brz%)-;yyF zRR|<3&wdW(BSI(;%e<0WT<0T2fjOTtVr|Qpf0$d^u0_C#k;-mX^wfAGT(XsR=j%RjSuH|D&MuAI2PL z^>1n#zx$V2D{$nyWD+y!C3zhTFM!CwfRA`3b;N|o@utS`|9@H-K6cFr#duQkXwxq^ z4e0K`P`c@|N5_3oYP9>aHnCxk+0Ex&xol3r=pgmiMLW*JHj!^M*H{yU0dMsQP!S;S zL4M8pkBAXAZ3fQR78a%+7S&GNF-d(|0*HRwIbg!6>cillbJkF3%d${3&GHwNUbQfb zaR4_DWXSo&OZxCup8;E{bAD!}BwhJuZrRiW!p~qOQ~`pon|)ZA7l$`R)XiS47d_(# z2%Y+6fC=hMz_Q*oHgBX5@zN|Wn`W3-|D*+U387=u`h}hWGIw+@0xtQkI;pKc*VQf? zb*aeNp$HQo<1l_xcF1`)FhL2XiD3}&whQMtb<>5=GDmVJw(EoDSapl?iF-cprq>t% zZnK|TW;1)m2#AzfvtetIHy+EK2t*Mp0$D~-!y~2wPS?4go>$LFTFsEeZKX>IB2N^F z;oy%SpD54a7AwFH6@lLQf&e@*r&Z2r_NuPT^nT~P$1fY0HM5Mv5YvwpwB~{u5tSQ$ z2ru8~fZ^f5g`1Y;)Wm2u6(zBu4hjzdXJGae-d1z+)dpH{^P>8m|6DBxr)VOu41Rt@ zZUd>@k6Nx}M|;l1FdDkuGT6js1k)pBz^C@b9NePmxe^A!&&7H4vlS{|g#V;|(^?8P zgwQdmuPlo~V6P+71v2BQqaKSs(qJb{N6$=Y&;cDjaR4e3fbue@5*=GJ%og)Sg|#;I zRack1!gQ#{tk}QVb90@z6zF??F2l92n z6U-DfFacy*FzLwqk>QClXF`Tt4Jsy-HDc_k5@aGXcPKeq;SOe`(i>gk`L^yCQ&=s7 zem^lSHDP6uoUy*P78?5yv;QWA*1xgJJ zFDq(}fWea>fFhGj>(zhCXV>dt@PlX%IWQw?K=3m+o7bsB6S@>AS-GS3LFI@kcNu@t z#i7_Pj*tr!<$Q;}CnZkm$rDvWb}VK^#Lbg?m7qh=1nNn(R=3LxvtaF8xrfhAB_6EW zV@R37uM|mWkgc-35>k$jICQKaU&*!&ljw)sD|HhS2shF+R4Cq@461$-R=5;A7H<+e zS6dQEO3ml`M6%3Hj@7VoD%VdDInq;ckr29@#r7M$`G}=OIM`95h+_gb(I#;)QPO3| zZ2;Xw7d`}t1La>j=3oN79sF&H(~{TBfnM~}6mf%bXI11`$9l^i!wrZM+hiP$7#X`M z_sA?klOo2$49>W!&VRa`!D!8~+xA_DLT6~2PdPHPTsfbOHMrdvl=TmvlI;j+t^Z24 zBjEUfWn`?xFr4+1W~@gf=%~XSixeRP%q_D+v1uiyEU0(-xEKsP)AS0GOA%4K;m2_b zoLgg42(B$08CG(@AIq4#B4@fU%h|rr99TbTGzF1sl^EzaXbKbY{C&XN_-VG;=t?OR zZ!-^!P+R2fiK4Vjn~j6@Tnm1gywyV?0s!7SXKJcsRfz85-Y7b*Y^*-;@<-`?Frd>J zeAm#z?*9B-4NrCh5!M$F7Z7lc&Ks3_8ei9~k-x`pBgA~FNOIg1d02%Y8X@SO!+y49 zmZ1+$UFfyiD{hXln4j>C!4ah;de-np{4UYITF{fg^-cEGG7d+bkSFcILs5BOTU#ld z=Dy1O?a8q*uW{-I)#NSjwwRwyOHeMw_)Dr;JE1yZ@7qm*E|SKprvU>j84+f*>M2tN zRBX!m*6CEvsxy9ZGrOE@g@#~cL7OraJ1)E* zbx&Up^?gM4<|AMY00uDE018A)KeQ&IM zb$%cxO8!H-fTPd(t6yK$6t%T=%>%OF=STE6^Ar0_K-BVIVio_sUReI}V#S1@2a>V- zW&R4-Waj7ea-24r1PxEqyTW{4EjFo2UuNoCIFku0WN^JO}yOK~s&%IC^pm9nmBIzK_vd$cK37zQ~zq@qYn*0b8z`6&)Xt zB{9-r_Vkyj1>XSC_IKz$*%grF($;^*apcxy zCrtS}+k|5Bq7!H^#|dqM$?pg%aI^;mjx6|{`2yqNS_Rn2)Hx+JowfQWwrzx8p}c26 zCqzI4m<+Amb^?X6YoeKAUz+~w@&>h}ve#br9kd0{A_N%MuFnWw)}B}f)EU$z4~T=` z)^{Mluy{9fg=4Y!9lWEAu0=bJqeuxNyX6qT+!nd&wqamxjwQ!#iqJ$))TIM4(i^IU z5WTqUgp?Hk4qhD~0_ML(Z)AX6SbPG!Do%1;7wI+l5wHT*Dns1*kK%rVq=}W$ch$>3 z10zd*PZwa&?D&-_J*x1bY(oZ$IC#ltLbQ8b)+D%T6$gQgm_Yznc>Wg zjB`adOqc@1Ld%7P{0)`<>CXYfoCR1WV$t3C;8^1SNI|?_T_a6DCXNZPYC*vCL7;!Y z{xmaSgCOWGj972vhot0w034{k4j}CS0gZIelcbl}#lLyf?di=2Y#avjuN@Psnkhv* zyst0#g!JRstD8#!6ouUT6$4}>eT+DpY6831x69nQDHA^+_v zA7Fmhow9On0D7 z0UlpweL>3^`;gsMPH$(kqyz7s!kG$3&vb3oMU$}eMNgR?y!n;q-o3E%Lr21LCR@e5 zWte%S+~%G5tzO8b`JEegTru1Iv@lGw#1`5j>N@r@w$G}2DYp3nYh_PvKw>%U=wheG ztQOhIxx>k9+0_vd{YF(@@JVpXPHS1kNGu-d%Y>C{oF}H;nL=z`s{^tqbYTPbFPS7!%+*^SK*tf`K7g=y1=eP->w5Fre;i4ebXitD^qJ7zh3P27@Z&8B z2#j8hb=00YL}T2=`>EZ8S19i7>n*)bT{Of_cp%+dt;tg&GHzd?Xh4pN-3jwrMO8q7 zvQ{Zv+&6_MJj%uQDDRQl!SkqvPJY%iXBN{UOqq2SJ|O1(=5`r)<<;QUT$L||{|0*` zr<(-Rm@|m7j(z#a1@?!#Do_7MIF(NB5)JiIh6<0|ugTPF>*7VR^HpbZZswfPoamhr zz{J$p1s@uC*A%TDO@7P~a=BLnuDrSw?mw9<+@kAV%Tp0h~UzP2*tTEh1qM%a)4-hr~% zIDYc*q-3@(H;!r5R28K9XV3zE3I@MXJCJ{{=J|3mk<&3M&U&^ayn?5{XlVHecXI5? zXs4B*?ZR%Bm;Kr34?#?6aH0EHKP2*@CtXLXoCA4@AnUzmfDV5fI^d)k!ZK{$D7O}JEO0?x5loZeY=>kMrx{t1r_e~W>vPw zQb+}7%=nGi3uEj2C&9jph26eAfyYM3Pa~BMKtYVBydYi2#qGo3F6I&4s?T53=2+)h zi!fVIbK?;MTe48h@eS`cH+=gAZ(KaA6ch@OWHueD8$*9Pb(?)asI`i6u|1VpUXUoJ z-)^3<_t08@te4ziSY;2S=t+#%2&()!T5)_5r=lq_>47%?&>%MCSMC{{lGavm63Wj)HfI1yA?YIl1RD7$GR z5Pq-5KU1*3Vr{F+rRU};CiJKMcrtu7(FY@Vlw{}Q6^TCRbi175)0^YL6Tn+(>(^3w>TQ9Zki=^y6?xfJ z3fQU*?4__S+DR->E&UQ+fWX7p-*R=*YrK+%jT71~a~s<;u;Qzz-+Cr?H+*qQnXG9V zuWAy`?o2W3>KwV~qc_5%A5UE;)uI7x~Udz`1G$Q#NdFe0zj8$&%= zJa5&$vlLyUw|QiLxTc)fpU-TKi!y}2Gi`|V-9$f9b2moD*EO3H+2-O7+zD0<{VqBX zuq9TlxZ%K0ZRpzQ?m5Cyk%!3tMc#WrHT7-%qF7N85IZ2C0wN$FU20Sm1f-Wx1XQF; z?SFFcV*bXi+gj?R8WlV_8iAD6Z?+l~hkUv^67*zdX&j9lnYBh+!lx#F4I<(Z$RwfPJC$~;UQ@#56wS+Xs zU|L=1lsA@blff}5mL4VCzjPdyoEC1>8t!{2$+^Ct;+QZmiEMEmYpE$p{C&SRT0}RE zTy1{cfu$%esIA534n?LYEI_m}s3kI~=a^7t zKWzJkkR;1|Ki_ObO2)Y4{@sP`A<7EPk*W(llmd+mj6@rtjnM1-lG6Uf>&}l$a&M$_ zqaT#b$!j-b_W69FM#@QnX<9VZ)XB|;CK_kT94d-eXHG#X+uWN!coUHiY1V@q_oCZk z6{Di>;4?Nr&6H`Be0!yuCX=!%Mm%MId z!Tg|7eBrUEyn5s!a}DrJ8fsa=tYB*eYZ^O(`tKSE7E(nQ=le(dKpBe)R9H{X-3M%1;EXl;Er5n7mL`L{?~kRiQGDQaXV|gQ^u8YYm)j z99zvTyuE{=V}ahziRfx6BM(3*)@2X^H&ls^BO(p%zjc{;_6yeSX>S+#)pv=BMcXI= z+sQmN+)F{bMO?}dL1S`rgNZ}=MM&w&MzOJ>rfwBME zch%}i(U0A+h8YAKZ26=`6J;6m&hd<}x}ZjiQ&f7V8FQddm~meH1vW@U1Bm$A7P1}s z7v?Go^~2ZFu~mZlh``O z428TOPCCcLM*E%9K+4wWoVxR{S*&N0N?(5yef^BB^6sF>Q!yMyJ1oDJhGewrw!cnT zd;g*|RCM{3{EABx8DFhA_gP-Kh*6j&NalHRM6$MnnuIPZIh5Qn?g33BN(&D&1@nWHB>Y;$| zr_CrziT8hkw(5v7zCx8PH4qQoOOqS3`dloo1?vQ6<%i{_ciRP|>I|GmnGjQulb(W{qcJS_}%87`8##@=Z+Il&@c^gbocM17>tvz>MLu9S#)e}LSOVy z$f9=ru$CTjS4oFkVA67M;D>hlASE*(`&Wp9_GFohAS@V70tCB$sq$4y4Zr!Qy9lb< zVrpn`qPA5&|HxRW6TV|r%wB!{eg8VYnEl=_8umOg1@Oo2A6P>yJEbK@nw?T@(oTZ- z3(HBdILuxpfC5wQ2aVP0=H&a@7ssspNiNqL=O100@sEcO5wJJD3In7C>d@+Ot1dNU z&O`NMrylO@QQ;`5nfrLZAA7Gl&Q)I{FbaJSd@xpH;;JKd2UK?Kl~93xPm?}CN@z%l zYY0kEy?8n(_JH)jbUsWsyZufUCK0k=5wLuNQj!u!oa1BO37G02QVVr#;*P~s#HVTW*U}(*T}&+L zBe>lg4dmU6$J9>66IZW9Zpr(!IV{+!M`6Rd0$(Y)I+qcBYKI{*-|c%U?(@=W?-v&3 z52Fdfcdk054hPLPdus09nL3V{&_-KNAb&5il3PE{L7w4^*ng4d+cix|Z!dC34LCaq;g#u3m%tm8Ai;rY;LRyUv?Y zE3q%ZaZ3MTGv2yM`L3aX1h$~tlHL5dP!dl5EEIgGX(t^+=!I&5WuSfmTnjm?Tf;?> z5YND`i%unN-h6p42Q@PD60ICG_47?{=?87^VwI=eBd;hM^90F&>0D*%6zI0q;+JM+ zxKgF32cMB1zR7SQg^9D^Yi{~SrikQ$l=$(u=NBnAN3E|6ATZ6slf7^;wQJG5TKzac z!sgOdzF@GWujP*wtw!$Mx0uQ+ z%ityzUM!v{R7rNBvp1InFdNR$3j3(wDZ$ZmV+D4>*C*~bkMTWPFCqHd_OA(RTPa9? zsMhQ>Y_;ukoOi=Tb5}f*7Zt3T;$M^{df&83HSIJo>i6%;O>Jv`0?aC$VCHScF{PH+(y zq9-zn_rG6oAqe@YD|-#y+P~!c=6xU0RC7rSW&VTug-U6_b~0gwXQxk5D#p{&R+q0y z7To!E&2C|W7FKF_FE;M8#mIz#@Tm2R~s8@Hy1SnB0bjY>X1tHTVhoQB155nDu4eRLl`fGQUc6)9{seRB?D_ zrZ1_rzC^t{L5y9LOK{Iyou^&%!4$5_eEs>AB19j*iQK}F5C6J%7XW%{-gxm8-bCqA zpe@wJJF{H)kpgx#%Cu8P=_s%WD>S8WRACFIq7lXWn&8!Oaw<4u9U(Gl%>g7iJoA@@ zt28cSWTP(F7))?nod!=F;gHSG6#b}Ns^zJ=`lYcE?))QP!`|mzDTUB5Vfk}E%<`U@ zw2aPX~eU$bU>dG46wSk3Zu@k01^d>xfh zB%r>QZ>4t=|OvI=xb}M#hN_&i4agLe4vvePa%8woHCO`b>(s*`kS65R#-Nhk? z*g1s7wWSj;D?5)@5khjiFK`UrzK1_WTHgA)AIV?!xY2;mA(4tk_w~tb_S3PgNR+07 zqP8*6!4|tf2Y<1yT~uP^rLCZLUp2{rj+X{Oq=f#xqO;K^(}yNl$pzl6;O3vR-#xRX;X3O|I(--rGFT zVm_{F^Bmsa@2f4Xoce;nS6!(!K&#jM9>?ILk|3&f{TnlxUh^XJT<(Uic?JVGVjV6g z<&It9@Iu++rU&?D=+laz#%H1W{QOd@&sYQz4o!|p4?*+}b^r(r@ut7#9#Oo4Rn*s& zGcU`(frppjzC$WH@*yOYZs2$o6Cy z#@ANEF?Vs_w@EmQ=I?m3BfYBR)AXicfwKvvq2hsN@^e=RrI3+I_pjfHx^PNuWcz-`=>}x;mi4hb!OHT3|P!o2UFu@ zWTRvE&^^c1xF^V zPICxAgf08H>pLjE=WE8l3s(HI?NFfvDdi_%#n)TG>f|+2c>P z*Dn!K{BojPS*(^TMNP)_#}Fm(HbtLitdPNKLhp7ZR@QVdu@s!&Tw#W4dzg+m~3ZuA|lOk!ZNq zNs{VZ_5FIn68=gjjfP!mYmYz=_+`DHh`TDN4(dw=yZtOTN&U=a+hRxZmT^ zN|zNN)CFzxQlyp+Rr@ql`_3r5YW{Y%AX&`5`#Z#={YlBC#asOXu8UOe?o2{M1G=`W zdR?VC_EOEILMiU0H+IqiGs=QBIhJX)cX{SPZG3onu!aR!i}m?ueVk{~greRtP%IK*sYIO%hHf8i~T9QXac>Xu-l7XMS$O6H8}D;!*NR?U?ykc)6SUM zdt#NyW7R3l(Yf6k%5!q zShrcS$Ly7Wak*Hhcgp@EWsG=-_3`IigRLW_sRW)FLj&k4p+=Pj3Fcy=>c$EEH?y_i z`;A1QmmbMBaUUHW{Wc#u8cpYzrr)%I@nOdcql@MiSRKq-Xz$bVUa>Z1A4@ebs!&(M zulq*3G+s9jMef%Uz5H~oaPfvYU(6Q8MAGx1Q(GTyh-m8jJa+QN0mU30(2J~@zZp6C zS3c|t1W*`wB#$!>hcneK+qd4pPONg{ktNNuEMmh>5ZB*XuDj|*Up&n&PX4I5O$X+? zyn!vla!;hASA6g|?-CBXlEGZx#N{lx`ZIZ(m$w40q++XZMO2Cl+B@>)SmmE16!>@v zkfvjw3+dE3%~~}&bzc_Ws5WVi*2Urg#f@1HUQ7vVi;>|j{zRC- z>EX0{E_`lQ13|W$h!aER){#p9zgwlDcrYixcjow7*J*?9dVy#y@PJTHFy3;~jdVwZ zK&?5PZ{J@Qy5M#vHyvqj9P`c0!TgJ!g-ic*A;>9O=^j_WWDa%G3~p>38=OM^(7;TT zDwj1moB(h}TnsY7OODo#%+>2Y(L@5aU^`9FOm^u4er3ypZM-mRz%Om0-Op{SZ_GLz zOmZR2b8puu5gq7c3Z+l&H#DRj)Iei;>;gmbEu-9~=vO6eDKY`a(sq!hvbTIK4VIXsLlp#|sns9i_x zybm0rO}SuiEHozs;%C!9ab|0;C+6lC!;M7ntUbZrApweR*tr*dJ0ujTf_2ASZ|5)d+VL8m;_Wqxx)pFYfcuqVT zPiT3Tk~HnQxygpFG4t9_V8`0d(D&7+|IqVIwU|17)8U*i^(uAYYmNp<}8GBxG!LW9S9&XeFfNEjf^z?T0i1@9WmF{s8oSCD#fW15YvgPks z1o|_8z}^2Gu=-CiBSXo}n~!YYoQmaZk2(!RfC0kx86d{{kB~(GsnDtZ=@=cjc&CvL zjXnJEzZ}s-Qcb~7N@eLk#Act~1MrPrO;JDSIheD6ge8X=-qJJg{dN5t;nIHrJHH7w zt>)!dXCp-&ghg(?2cpy-d9W_q&wQyKpiz1p{1F{S;uJRGOOBxB07!!WH@$igl%+#i z=zsqyl=;<3^=6~(+ka^CUrqZT#76&K;P2-Z0J)8$J#=cg{S{*+g*wypN+*oI1DlOG zqX1YFh^$`&P?!G%G24;?K$rUVKxROh-s1-d;(srWdhq&#K`j4#a9cJ?a&sbv7F z62R8~LwNeHdjFfU_)qY%s`XnB77;x53K`b)4E9-;GH-vd7<%J{+@bu_&p}50G zap`|`%z_aWJ|N7?H<0*_Hrmq>f>X-|!M z0M(W77jkM$O7sxko)R<0*Upwd4ig^s*5D7!$SH= zhmO==Xt=RC04-yawmL)ys(l4wt42Vu*w)k`5saGQ%mF9{!QQ&@NQD;(-Ua3nQPri- z6O6X!ngJq2_bmWf%Vi@|0PrzLi`wsCF913K`xqUNZ)!aG%)Cc`UFOQz?_0u({F)W6 z>r!0+9Ec9?O|p6DteBs|gOl!(A8rU^@eUX7Dk4u_qZ2kvhk$Pfb;WSSHC{Xnuoc<`jj$l4>fQgV~ zW7Dcog`+munqH`>I}eZ*OcUTXHUR#r>lS=KN6pZg8eCb-%jI|zUxc+a1=tP+vtc>5 zri|+mM#N(mdcHotaigQ+2LT8K@syVRxr~twoKKR!;Qg&E+w`Nar#;SG zyqKTjef9t`b4F?pAW|@d9J>P_meRb*`_c(0cvSLWxnX+h?Y95S9GIn7liaK6XJgT? ze~;ks|8v=YmAaHcH$SCn?L~q{u3Bi<&`z4XWwjRchQlxzv;?K>Kx#E&qv|E3)^o(d ziV4jNX05G;eVGQj-`bt0a$mV1hy&z``8bsn;^^~^KInq=k?K;vKUH@O(B{G~yCs_{J8Sh7<~WHtxg_wb z8wq6uS(i*hfqwm4@->FT`n5hEY{yPNxWsHB z%+gc4V7WMJQH!I%|MT&OrF+tmq~&n(lI8D@W)8(H^g({jegGH?;}^|u$UxwXrCkTE zf}V=Nuj1XX;+1hc_uGdGvXeNBnjGmv6!usC(vV`Ei4{D)2W(XY!!=4HfgB$4n|=ag z`Y(x+>fumJJMF=K>lOVe?94gg4?08iU!FfW2Lw1+fV?f=yfgKfA9h6|R72#vXhAvm zcZJUexlJ+`1k8-m3IOT)do#mquy)~)fD@Yf+c%h-7B8Sba(~Yh+m}WT-}-_;Us2bP z^E2#RZckX99Xe&kb#~7R^_rCMV_wCuDl<7=gK7mi%upRwDZ(8_U89GJI7qcv(x83tN;!j`^?#&FT?s$fh!eBr8qfEu8w4&aApqz-!S*46D1 zy|uBrCk~%Me#$C8DScN^W~u5`1z(4+GK6y&Ca+Trt9+$8=43 z=EMQJ`@~o7@2H}7Am{!akx1_(Vb4Qv@>CEDLoxltn=?26YTiV>&zfO=?kS^)JS57o;B{_y)%{`W`riF< zYV3T|-KC7|z5_V$!M-)aO=xJM(27_`e?eo4%pJX^yIN%pcUkxa9n$;F>q00^uP+@pjXkjg8IrB*U%tYLYBh*vlf~QmsMywy0kEHWvQcDQCEk zrmA+T+jF?_0ZKR@`#WExUgWxjkC61&LPw)$V?4)-#u&tweZct zEc*cQ9bl_p1A6nn?gjrf@%{~`_P^o)|DRYk3(unfVMzJ z-%Do6(6BrpYr0AP$J&e{K!DJFOi!zxwK!G1NVsqj5ufuVv9LZj?wwB2LDtlA=_OoGNl-#1B zboj@Wf{N|;e({GDL<51lhqf|?AHO;A4p_u*9T1nYOacn5;WSqqEobfyj@(N)62s7c zx02I7yPqL-@18CL-{muc9rF>l`nlIf5<=xI1OLQ8q9uK2Q-AJuy!2RKzw;T-=V{km zS@}`tYepcU_vPYPZ~C24Ik}FTyCbsV*3t2(5s4Dhj*EhC@*n8FzN&lmkkpAYpvFV5 zzEg67Y0K-_8w{~n!s_Mr=ic7lsBJ~OA*wcmXl$mhjiHd6vDjuX65pK0=P(~5mUO5V z>}~U1DF6lPVV5IGd&Vute*}BCUYhnqfCCH*iu?03xf1yDcxZOJ1v*YYmy>CGB`x;4 zID37J{W$bRFO7%x&WWoVXO^keRCEamwv$oY8>XAdPcn;^DJyIe^ zd~S@@PO-|0j{|X!gm)Wj?uHiN7Wo`ZaP^b2Qo!6O7d`>ZDh9*()BtNU)!SQ4sv*0D zeM--dVwK7K>X;!bMAGND;U7FzhvSXUtfx`8IM;2MS5=}L-`7WxxzP5#pY+OCSh1{{ zN8f3(vx~oO<)AoGUa_Cvjk+QI8vaVJmzT$@m?=V4wol`pzGu$xjWx%a!dI`;moxK`UbMpW}kb zvOA5yZ;9#cgR!E^!aOy>b4>DcmdG7OyqxbO=Bvlrz)qw(dnH5{xg8(Pz8XAn;wn;7 zH4bQF)|JC_i{b+D&64P4S`0Oddi%wL6$7Y$?+F&}E1&q3_P;U4C&UGAIw;xn*<7X+ zu|<$N3pR04+@5l@W!VUZTqvI%@gC$h9|LJ12^eR{f`_D)&m zI`315NO?+8g%&Ro%X%kuJ80^JpHTYn4tSDRbFfpD8_V$`KGKL~co}T1gedI0QXAZU zd7t=uiR%8V$~D0>Dp5LGmci`#3+F7+V*df+Z&;5%Vl$Np!*sE~dM>%4Ks17}WRN@6 zBBYq9#E9>x-Utxyg35Xzt<9i)VJvS(lUOl291$~Lc3dqKX!JB!WC)<9@8(fE)Y2IW z1u1(V&w(RBa}Gc>MR5S+cUwddmPuWZwZMGJWg=Fv$5p-Y&ld=kh!yzoGIBoXLFQ(J zL{ys+$1sa-4hJSOST(wiy60724s@9{qj3bv@{;+9VW_km8-^rf1G?9 z80VCj585j~wUF9@ zfga2UT@!xLH4oDgKbzi@>HDSYlp@#ZHD9?nN4d6gU8CZ4%}P6zcjIiGId_NuO3`QB zaEdMLVO^2tRK0K9g_qv?N+!_w$hU}vg*ttA6W#)8r9#*f?-^~-Gg!#&r5NS>GRU{| zAATl0o_S8?!+oZ0mv*3Vsn8ACU<20VLZ#RBsX5h}OE#~JHwdZ3ZYw`E7`;~Me+}5f z;w!~Jh-qRlc)I1i(keFn7%w&(6TvG_klXca)>X^MQ=0y|A6?1$h!xE7?T7B`RVT*V zEsTdV+3LIwFCqFM4lKo0$EQ|$tCqV2+nx;ngcDUHb)8_N<%14)>ir}AJ&5b*9)Csu z_mSk*^{f)EwCcoXHOU&3&(SIS`RR%6?AL3$pj+s+fCw@dETXu<7x{v|ONTQ#O6SK3 z?J)BASRS_Hq$IX6jlV3tQel)A?uOLu@wjIthfq2+J}q_(};-XjHu(CTG6Vkp!cP|+f-qJ;-#fPMZ8HoP~&1cQa11j!cm6$^R zx<&LVV6FT)uCEGwc*+WB? z3)-(S=~`ODi&JsECSnXN>7i_Ng~_lHRh?HN^7@;Cjg#is*z=>}4Abl1PdsB7VUD0T ztg%Ot=C?YnEJE|pc*m{A%%q#0RO;y{BYgDEKdxUx_p@UW1A%YUUW!rjrn zeTOir(uqMozU`Olj6w@Sttve-m2zuY)+=3sO1r^$fp89R;cgp;eOR#4W;-$U%pC7+ zMqP;8#Qc17h{5*|(BCaCbw>Cp-JEBT52ja`muKJ zG~7z@{$0OIL(SEBZ>)lP_Hg4n*>d#Z%DxV~rE!LZ=>!IN#@Ua?A|fL8&d!nToQD|d zoQ4Vu3pkcY3{ z6!0s$NEDbpsRm+HgxNcQ4lGjnPx5m21!;GPBmiIz8c+NsL4NHcqlRY#S9h0FaTF!m zi=%&DDgjUUYh?%6DGjhvZJlOU|C>O@8N%c#T~=ffNY$!RY%XnMiWZ94 zDAmC#tz4rO1GR@`HBMi zJ%lU9ABRB)ViaCdolJgYUL7$t3r!)2Ijy~i2KZ-^2QS43t~e2MX~49s=@=SIo7!%s zX`{EcV{;ooflrX+9Vl>Y(0&EAZHA`Nr$j{x>0#z+X2+CsGRZ_jl?{zjL9M6H>~azM zP`L@%H|sd3{QmQr{y>+^nFLD`vOQsHr~^qBvFNc%>yr{sX3dE8+%55(v0C$cDwQ&w=g~R8<>iYIYtQRHj==3*3bCaNWg*_6rY zE#-Zf*cb-iM{31c=&c7LS0C@absA+;W@MO9=BhBJ%!8>T+h^<131ut&IErt^KD9lQ zl8o@h3lLxQk-Vy^`?gK_AkoV)Nx5WG2(gbFJef%mBZfEe;a7)LJR-S$7(YM;iNQMP zU`pT^aSykW3QLb^Lr)P7E%_>9fQT=&jR52e!v)dRpAl9Ua}GA-wJ%87=%YOXk*NhK zQJjbR<2|j=c@FA>eaoRBazz_*J7ST)2f3lvu+hM_11X@r7p3g;tb{_TY7JG2*`X8n zh8IOwW)JRoN2c=Ha*_h8cPXF&RvKBbSrARmu&D>74)6I!Kv|VWu2oZG-xKp>ku;H> zM}Rb6;~FTiByS&-7SLDQ{NP6ZrsL#>l*SnZc+vzVlN5?)SR?G$j#(>MHr?VPhFow< z$fd|v(Ts9}EbFq{r~=ZtxRh;Ccm?g!ntv(*haQV`Af;Y#;)y}@=Xu#B4nQS}cCGf) zf89fg1>;!pZxL`LdNj{#$#tbC6qM_;`U69&upIcn?RhL3x-e5^No=#*!Sy`d;GuO^ zQERp5YH3r@_21ArCg4!ib#3G@L?2KG!pQpdWaSEpx&2x`ikKLUQGFnk1u;(3^PfXY z@vB#;;qwTPy8{G#g{xvIecIrH^#3QTZt~PI_TVMOIpJv_H$l zabiAK4mBcpYZVdmtI&$~=4plkIr=!E9DoVb1Kw$qll)M>0S>SZK#m1;0kdZimG^xC zncyuaCn;ZazrDHiF80ELWi9Pl67?hdm5kxNR^eDVXg*vsB^dQ2fgtH%HV5L4CuR-> zf^J{8pqAE0uvGI*l)YBEPI!GoRID+$y9b1;0tQ?yfI3m-wP%_4WVOL#T_6RVIN(z{ zZ__Ln@NKV;Q`YeJ*TowVTW0k$+7}gzz0`imfS>O+631|r;>)yO80+-lxbqlIHnW+Q}3W=3i z2xyDgl*0_V26Rh175UgCMVisJ-kF>?^6(nHDvge?91X`ah^Ic7sIVWB64 zZuQC0>fBY1byy}WNh04N2RH{~jn%ySPg%mXw0gE8e&LVXPrlwZn-vimz%rK*K?R0R zsVOzqa;Uur(elzIA3?rafi@bIrO@EbKLtbm*0~?*#NfM`l#vq*&}_Bz&J``4v}wU) zaaV>Gi&APvb8N1X_ktKFW;lV*!KrJHra{?FZpW_(uRkD`5K)969Z-epQRH51uux&n z8XH9ma#!tnte0tD(=suPZ$S(@J{P+KfduxZCP7CO``upuvKnlp{c%T8Mlst_5&+qV ztCSqW#Sn5^)} zC1g^_%ct&XW*(nn_Czkoyn9CF&^Lb(VOf_)JtB^~P~9))eLPJ+f@$pmg_LKMPJvu8 zW%=yHUg|3-3qy;-@2>FAvF8QFHa;GC6Pw%BWk8L-PC|qBkV(A0CH>6j}F~1ynYR^9twKD3#XW`=|^+DkhZ+N6s%_^E(tF6+gb{++P&DDj8uWB zXs3Kg7G6KkHr1E=jF|^FKxVQ+Z_|Yz^a8QtY*q=LlnaWiIy2Ml&pKs?OcUL<+-~(K zLt@q0*q|Ge8RyUTdlgW!{urKZT<^pDmi8_{r@QVUia)EENDQjxdXrVG2CZ35R<0N0 zhz16Vg(pl^fYtRCvd+Uec8B$Sa1SjeU~VeMXD;LT!hhd>9Zd=J3tnQ6>ixQ)6>kms5FI^8qBlbb1tE24`sua?pAu z%_8BRNZpGO6umUg78A?XGb%{EO}%mI%U<_M2_G+ z*XnER7(p3!ee(MqF1l#NZ)fdVHTtFSab1&l{#@gGx)LEnoi_X+&S8w zld8rErNT03&`;{nR%>OBBQ2hFr66APe(Vk-aSYnKvW)SzyiVueP88n(g6KiX8#;JG zl$qFjl7$jv&2u2rYlX6CKLso#VboN{2wDcE&#Iq^6{;PIcm|>7BB%pA*prfHNdr)7 zsXUY)4wFw$Y<(v(xb@f5gdMi6a)=m3%(e(g2XUP;8NJd&BdHK~GS&9402%Pq+OJKT z{MygwSmKDS!tahZ5Oo-XGQl@czQh19+*J;Naqoe`S*X29*md!?Cn%JHtW%W%i<<6G z`5Za)Y(h(UQ<_4CGBce(s)4p1A7Pl#Ze#;aZ+-!(q#~oq3CdHHBpPqeMVI9fUutRT z!V-mN?S$~vD;IAeCh6*S{bDYF2FS{%lx84&9_r9CBp1S=Bo#+O?)y(Wfw;}Qa0Z5< zu+~~*yc39=>4haBOQ1nitI_&*{3{^DKDzPZ=>+8*13{yB`dQU?=#Ea~E4J@-pD9!9 z3)E6!*Nr|z_RbviDfNfO%Cycgz`vOEaeC{P1c*cimBI=74GZ4U!Pns5DE!i(WnC}i zTR%$ouO{erYm3WOx;Ioax_0^mbZLl|OCy$18G*?Rtnoc`7pBhq6X10Mmu^fnWrJ`M zNY3b%NdE%33VsoLYRR7gXV2m_-Hl~nXtMrvW4iY);3cd-0oL>^TlX)A8CoX8=YR}2 z1uS$YpkSHdy&Mg|6wyg;Yb!Rd~Y z>Bk4~Q>*^8P=en+X~aCk)##QJO(7iO@J40 z)#yF&rfx1|6IKRa7inWy%WbHwIS(Z^Y`e~uGlVEy`I}#*=6*dEZ*Z)d+QI1<Fy!JpPCYpX6IvXF;fG> z`Vb1sly}zbq%JsQ6zyMGy#P{Ga^qUO2y!3Y;Tz4Z6B=q2a>A#5?@L}l0!5$v{ zwl43kRO>mb9Y84l6cdR7G(N+k1#vD2Sp{2 zFA|iBYu*c|BwOGiG6hPKIcB%^kj=G=TWU*C$xmv9 z)LcN)F>Gr%9d%y#&8*E3z0wm4BChdC!3yVHaiwd!)yd1*B0o$+ZDGwoO{OR~(&6586mdA#V@A z^^tDPdkFS#l9CrrewB{cduXw@3&xZ8A9^?8PFC+Dqh?*mkHjD(g_La_|D)Y&L}|Dw?~XMQa(wWYBh_t463v#{(mD#(}P^#6U>E>liPg{QgufE~#*k ze_1q+o?oXU~qYGf!{UJ)&%j$wq!8;ye{_||o zJ(;M~O8CI@=2-$WyhBpIe{7d+&FN0*H#Kf?wX+z*_PNDnMPIphE}vZ+ z9A9&87Blftx;Ae1aum^@u=_}}?~xX6`5U#A#al6`(7Z zq>Ip(X5q4s_Mb`EPQlDXPkOjL{QALmB5v z+1?=f)32>ncYtYK%ml%T%^EZAecf`43x%4joj#CJQ!DACRzQWsusfD4adQe$m zd>jkDt!kqZS%ahv|LOSz89bdLBYksfb?8f=)y9z3M~TzUchy#SCjx@i-&k~UK#A?zN@AA70ob^&CBRH|FYsb-W*bPvr~PKbxPQA? zqm5uI_fddW_S)<8h;s$}ys4uj7b?=l;qSbAsN=|+&ZOc|T zWD~$1>{ROvr$%fexAF723q9SP4!De%mc`%=<6kumT>{T(++#B4z|?BzF;FA$V%b-8 zRj_dZL6k0C+x!=S54`A;??EfDtJyNS6p5}bC?+az33}Hi4gNKyFpgWDNf(E|%0G=7 zGU$88Z)a7jM&q9gU$h?;ETkmuQ?4;1%FLM-KOi^bui>Ms;12VYQnr^M{V%vW*9T+8 zi}~O<#XB#kfsYu+U|tkv$vd96oTA1k3Hj2wGit^A8uqaj-0>0Jj_QSa?!a`el;4c$ z!@gD0AG<8~#N?}LWUnCdhV+dwB`et%s3KV8T4p4C4$>Y+dnNwWeNqUPJua2|&00P1 z=lr|P+^@WAW}K8-j?DPscH{+R4^(rBNc2iU^J!OqX+R($600)@Q~hMM!=pE)db4wt zrucf*4>%wI)4h*QeLsYfgm>U*kHXFRcUY+bJZ3U@rs={U9h_C4uh zq<^tT1Ez2gBD{nr*CLBF#So>f`7YJT&Xt95^Av8TA-i$jQnuJ7Se)XbW1p6(L-(z4 z={eu~U4usNIoT2{5L%o$Vg&EgBhCdSyxf<)S4^q3(A?JiYu=`$G{Kh;0-hf@)caE4 z1Lu{79eWP_)i7~KuOJyX;t|P^A9E7N)G4V7;~)V9i9f5P0`0nBr*%0`sijD9kz#fd zTsqsNp+FNuEfQ^h)+g#<{24<_$VW%?(-NmPBKK(D9v0$qY`SjikCz(u0W;}0X25G= zQtd6S-Nl))G8S%CGm!==fThZ(Dgm0fAtmhp;_Hok04;hRG_cg)1ohq9v6-1I{A9+ z+cVzF*2k2_S!|WQ6?+TcCbX@7Wz@i5G4^oQ_q6?Vo7_(dZ#chYvOgBDUea#R)>@*l zm-g5ZGxpHJUq$t)-K{@tOSi+#wvP!juVJYn^(Dp(*$d*#QB|Bs{_Xn{=W^{0Zh36j zwIXg>nVZJg_Ho`D9%P%aNJ_#D`_VMvIq-G z?uk0AFNB-9YRKPv0OxjzaQMdak-7hTw8FPsPSAki2o|}?rG#@l;v_7b5sd~H>xg2B zFP~K8Y8+JzimpX&zRpJ%;~ssj?XmA`eOI06(N*)d) zUeH^Kgs>sa`i7mPfw<9{MN32h{?qOy!*9OV@yB8(WS=T;nWo$tUBCNx-6|axtt=DiXH1yNKO-V2O&o`<+Qu3$ z9+8dpxxegQ@!>fo&_u!W@UC#DIaBbQw}=_Zw=2YK;g2g29U{>8-Y2Rfvckn3JJNZ4 z*E(~D#ItS?K3IH~G#l=ZJMo~C&*^PWT8OrN%VJy7#TQRn8sGH2vJnOPLVeV zYwr;`wVS9V&cDcH?lUjreEd^tLqoOL3qBZNrRc%`^em|9HCcwC^VB}Bi4?uY9Ub%2 zE4}i438r|@F4TRlb2$f*x3Xs~kL7kIxA~?~R{^!N%ze%BI|30shDW1C*TT(bew9l^ zjMWIgh$^k#1^4;9TW`MyV7GLOBBtC_XJsR=Y#-e^5>`4`e53thwT|&8G~0^F%qsc^ z!a!f55V8(!bZv>Bu&M#C;G+hRjN{3K_sOI^4?VP9B+M^NNeS4c@!~;0GC%K48ex{YBmmWjL z{hItr4Rr&1@B^i;P@_JmLei=0964#74jleS`FZlnJBSVc#9T!A39!g z|Ehg6SLZJ7o6qohv0#DkbBzt+tr=+Mx2|7gImv~*p1)1_< zdsxpCBWtzjIU94WT4|PZ-}uG(k_u&&sp;+6?BqscRR-slRLQ}BxSB#ZY54Fd+cT{v zGcF@-lnyu5e4U+l+EFsLtav^?1XxPM!%yR*UYa*btjr75V5g5n7j~SM8j0qvRm{)f z*^g_uWJQ=Sapu^M(?0sztsju%0cGJ#moDqh1T9X5Tv5!4XJXdppQu??goMTzfA78b zGOCW5tHQiuwyQK(tE?83uZ%fC(p75bjXuMISLOF!nP(&{eGx(ET%c*+jX`9k)h|u> z$3->}7KLQSx`>@={*^7AnJ=+&rBOG~*7z^ocPSwX8!w0LK1vifIg<$9D&rItAM7#L zOkM^4yPK4erN~QC&5Oc083Ix-*TiBve|4hBN%HpxQcN^T41hxx)R&Ms>XP1XNlDKM zsUk-loh1hK&aWOxFEd(fYdX~_#;ti9dCXryaXY3uGa z?>N5zUHu2Ft_KVjQk|t|g5HOrX8OiRI~1!OSfbKS2)uGpt*u)16dt}fK8Z63no$ebgq`cPOJ;T0m1$sbH%uDx_~FhB7}$E3NR}w!zUG%N zRaUcDc9o`^+@}U+$A@2+v82GHf)|nYdIMvr;Ho7mtE1ceGn>FcGbNgLWZ%3O5ktSV{A|N2W6MBb82}q5Af>NdTCY{iGr~*=? zmxLAqNEMQR5C|neX7l_1pEJ+An9n(LW}a8)+yT zqHB|+w&}!XIN7FRY9O_FCMT1q&f&Yz)jy9HjI8kF)>(&HdSjsQ-))cgHCyLZIkzYYO13s0SVE)G(L&3p#1nHZ1I7M-Ct`*U8x_m3Z^2 zdMQ+HN3`F|p)>zix-1x;5k3lh@uvnG+jIhC!M~*4O~En_#&%#?p~D;1JZqPI^Ls+f z*hpcW*CbNyG^}*O2q>b=y70#{pVOSORx->5AcSYze69$e8>8S*1EA3ewh11yNR>rXbjNI_V-S7 zWQyiHdxO7od?2bwnney9pQdadTJ#n3?N_F*jcg9h=-}5BV(Pzywba3jo5Yn$;&z~r zGXtPI^^^aZKy|I&V4|;3=z+|(&kpjIgZEp-g(QS_U+!3uiF34{?hFn0^)E(~_)ls# zd1hZJnc1Gp4lFuOSQtU&Vio#ajj-$woxj{s2A~u-0CD8;BS02fWGV!zRfk5f#&YwW zn_J$p?DL{|o!&tmm2~`Bm~arnI;ewe(GGYrncZZD-mIr|K$6w@=YMA`e%!M7bguXO zL#LdJDgRI8mj5Mf=KsTgJa<(TJin)YE}xck!r92O?*f{rnJB`Wv|Ki%dQf;i+{koy z6@1==^uc9dx50BQty)|O-0rXSdH-VWKU-earHMwm_ctuGDcuB+2?G=#qx(om@`R5v< zi8a7so#7as08d0Fw`BF@{k7m$60k?&O<)l;4LQrMWb=3o{A!W<=JDN|K+P);XpSzl zUTWtsQa1TnJiyPypHn9;5^m?(UiL*`K?eY$=cUNMZip|wC)EGGPqzK`+!Oxi_J0yx z0uK0J1V#P-M?ODU|Ml?M|JFET?ZepH{$yQWKFa?-@7=$txv=w^%Xr9sf;7gvp}Fv@7ts`8Kmj`&`L4|6dy<4izJSej9*gke znVw4L9h3gZpaMt4I~h0WfrSP^3ZlIK=D6IXjPI4i+F z0=u!Hr&utLwFR?)p%qM$3)8$_3u1kq`*j+5AplJ>3RS~MH0(n;<15m=>{$4FL7p81 zKd+g({e`;YCKQnx@L*U!1vVr5p2% z`zPq*t-DIr+uJ(9&|M}TnX`!D_V=ztzPt72EYq$j)SE8*>ZLS~oQ0(Gx@MAnm7SXz zXWy5>RSsqlf4|)Qe8a?tFi0TO=qYS zSkGiAv;~DeJ-tDu9r~YUf~NYY*G6Yw$+l!usKrG_OXuLWLUnfuDMDgTAwWluxTGHA zT&+@XYz(cEb+k;0N3K+}R{#~M#e2%fqj)=yI802(i1Mti2}tXWErQ)Kx@V@3 zJ!RCI6cNKqpzPmJX6bqjnlQVt)k^>;{hCScb17@XbSP6-eY1!E=ac#hlb^?G_J4mw z95HQshJq`qG9oi~-FC2-bcu8cSBJ2bL-cq?PzQj}9%;G`-m>yER zz0zu!WvC;LddgYx*`T9a$6)A;>&(ZlnXJ#BV72 zou+7r{w98DaEjcY@$#3AG|2Vmjm#LUv#}%%s0wqNRBy>nb_+*WQO;LaPWBp5ffg#C z6N>9a2zaS<^-QSp!2J3Rx{7|60sCY`U?h43kl$c>C$iiIMR$iCXZLT4wag=UgU2lKce8d#VU>HW?Z zEp}yD5rIU0M{>&`J4>oE6lX;fBo6#d0`{v5)-egpA6{;>>TVg6JQ*x-r^=HHqx+*L zb}EL+>B!8t4XH_rkTZ6E#??%*9Luh25ckbe1yED=#xDchFX`+prwzZ@p6*yl%(R%_ zTwTf$Y4@XPZh0;Fh3*}(IODoWEPisMCR5q)JZOYhSBYX`@XlH6ViUf;t?lfwP7P-LPq_sp}^*{L9$7%;U$-jmXwW}R}k zx->-QI#gq%izhQnHNOhGXWrm7%^KbFfr;NjaC&VK8M1B+B(&r;$+c^KZIFV*i~DK+ zYT|4ai(jKv-&lh^F-kC%p` zg~JY5)!gl0%usE6F&s4awDHzfk~W8i!kY9WUnoxzz4E=mR%3+ud~@0wTHkQkAOzgP*o!eTY@LON~AVg>#Tj25ol>TB!Wk$otx32 z&x0GeI26nzAyoRd>+NUM9b?79cBmHKe4SBaQ&g_B74&N|!UAu@G&02ve0Yj81Ga0s zg3G1z<~&OsYj(cP&K`I}B?9D3Oo+9Iv)qRBf2XF(%AnO|{`@O1ObGg>HY9`T`8JWT z@pYa=w|%q0WvMBO4OW$SRY2Cy7dL`TY$?X}&!Gy}S|PKQz?Mw@PjZJgC^~T5Zt2w{ zG%jsR2%`e^jat}zKS9QNvuyVCu@#8l|Ew-}UFGbU%joH&i@mREqO`84jtqx@+T}PK zLTbk|?AdO;z)6grDWBDk-QTTA3THXEU=|8s@kR9BtEQe?z<$MV?Or=O{5k|$!?4v> zXw2%nXJijbR^UgD74Ie_ZsK%ao69B{d$q3fM>r@sZ~jqH$SiE(tE*L3iQBqErRQP! zO4F(RyOj%fJF|)*|C_HKNk{fchKBfAjyI7*Yd$?S$|7rZFPimD=*!>QkKq|LOQmwJ z4sC>pU4-T87&5r?TCRRK&FY6ek|>HlEyI1&u5VT~q=Q6$FhC#%sAui4I<|JrF?oCt z8_U^bpQ@kY3PlxbjSTTqO_(KzGubO}9@}zzB>^^6hDf|}gVJaEAzE(IGm45?R7_UJ zSjftZh^66MVk#Q6i*r09q|<9R2~^C9O?^p9Sr>*sWNQkQ#6c`KHO;JC#qe;kVu4)k zxm1hzgC5B8;{rdUinqcTI)39ig9vAxl^zhMKdY(-;mhec)J=HVX31&h1T4#%Rmpm#w)mQX} z7`m;mJ>;pd0k-r%;K~tGO)QnDSZumXe3=`eD8<^zwF!HStx4FU*U+vRNvv3q9|Dc2 zlx38k@)RQi^>t7h8{)cJ?YH!ZqS0M$Q_W&!Ilmro)0s;|RPSV3(p%HDQK2`FB^K}v zV;sG^yF~TTGD02dp{!K={TwIVM4k5spVz$S6*`a=*5V13!xLOfum&Zt%)2%cCkU)l z2Xh!{_gwP(Rp^&;xjQwDt&9uY&>^qd`o3W0%barmPz}}LEJ(PWlC1gb$7k;Z=h&aD z(sjT?u({%%lKo1M+^mIlPQ~+^@JyWcXR>&EJ!?J}T#Z*Li#?({_S2EH_-jc|qg@FJ$uJY11i25S3L~-V=(+d#mpIG8;45sG$?^hP z7%!Z}RLrbglI>I~ZWjeJ|E;6Cp3x-iLVkqd3wqg(xt~ws1Uksp!?^x+9=VlME*qR` z9{ifv4EM-1R)SwSxNzXDvU+|7f%w(OcX-<2KjYf-zOvHBPp<3BF3L&B0jv6|U?%nq z(Oh-D!*#B*IiD3<+gfu|tDm!iV}5?R z?~_-5mu%S+q*AD?jo*CWTk;DG-ra}DFOOvEl$)7ilLv}dKKTS#be30G&{j04{-WqG zg2+!5q%+c(vq}uq?W4LH?RIO^Di^H{R#6LOUkp8a`lc5DH0o>ptw!IHrLn%ucX2R$ z>-%$d?DJ>43|V7G)w#};G}|&ek0?{xtyfi~p~F1EeI;|lMW@b~FAXM#V7brl4`h0> zL_vimTkPom?HUP`t=#SW0RLB`CJRwqn+*=u4P9QrqW#jf!5C0^q{1xpqGx?Yg15{2 zdHgzKopC*LKev$xT&$T{YeR&0wVZCkf`|*GoX6MVw8}|ewZSwEFe4e#tkP=#6SLLt zeA14RpX2B%+m`3_IZBq?C^T?jKsNwDpX{QOLqUSlVSaH7LR@pq?!qr9JGxu=_-Vf+ z+e~y?8tu(BjsoQb6Ds^zhyRemyTZyQJ2giwl^#0yVtZv#%gx9;*S~t`#c0HxDYo)< z#B`x@roV)bzY-AqtBsZAbMsG$o|N)cSRV?H`}y(;`#86jArD>dmh2_!=AH)zo4Otd z4B_%gMjGvJR)_xjeUyb-T%d@%!ulnC2zTLIBJ54&*7t;r6fW5w`Fx_Rc>8_b&W?p0 zuIE87+CbeE!5O_du-P;60~U#l5QBLQ(+4!bL?+%(Xz(;RJX4lzYZicrydc%sO7cf@ zO{(jDpObE$@K+EU+ni#$+)9bkkU!Ps?euI^e5*JUS`R)A7S#=_(>SPV!Jbi9X81cN zJVv}0vmUU4bU)Zn*XFUwbSx{UBRclHc~nKc4howl#g{dd%p!R+p*m%ARnH7lF4LoBh8XZ5sjt}x@w`?c*F)rp*}%x;#;|!O-bVuYhd$wcT^V;{)X)x&@n}`6^es?Mgh`Sc$;A`& zlpi)O2IRf)ndv@ee(r9>z`8(vX&Ah&rx#uyvlOv#D{qg(fPLQ6Rw+L>s;S(HbVGgL zY)>_z$VQ^en~1W2Bxw^qRP}xj-wU)!Wn}FGIc0L)L)(ULvzYsbh&Jk!o5<6&2H+n4 zdH-I{i4CQZ;;`A^6VuSeEa3lG@`X!t_x{0(bG=KpzBMQ>GEposNeTo~HKIc=j{ zN?|FI#`(~6&y0ta=La?4rVm}mtOy%6oKnzk{Ey6qme0@A;RS4UoWHZChE+Bh^6#*W z|8^SOzsQW2n}h_~T`t7t%UQK8{uCVl>HR8^*e%PvrefW~s?>xK9bGH6j0fza>yo;* ztt+|Oqv#Pj$W*~6?&#Iwa8Cbr>Yr~Z8SMqI&(q*}#vP)vPuykEfwVX@d5uYZ{S`i4 z);b8@VgWu1^bzd^mQ25{fKrm3o4-H*I)0|?pw+-lhT~6qW4-P>6TAO7)deG=drCxo zsr&J`bV1r_TXc2vr{A3d;;OQZgw{_A;vZr(eIKO#ZGWlEm7w^1owkfH#KJI$4jEgq z7135TUDe|)mhB6lKb_fNM|WIj?RCk2RkcKPs+I1#nb;V#p)so?M1!cHx$I0ye}~at zqD0I4bUvsLWgeOALUFnG)W!MKPWokoaz zXd^w-W8s|x)`>qW^lQ~Yme+5baHoE=G#_&x|a^ECeEC{Ao0@F*$3=l2@O?Jwv!G8s% zURA`dKKZ9e#2ePP)!s2@W<$=##gsz!QGJ{Z^Cj5d#0Us(L$CT-i3L>}0O3N_soe({ z!?u}{sMn#$G50sVYwWcx&+<2peDbd`zdP5=q|gqg%*Z2r1&u+7xx0C1bCeW4QZ)m@ zydJIzqR>h;1y9BN&Lv1Xe^p%_&OnyVv;jp=bNw$+dqE_nuvHVXMJ!*GJ% z&T_lj*ZUD}w1EZLz^rRzp#e|Pg-|h~vP#oN^J(X33xOBoW5D<|cavEN)e|VbI_$rY zpeVM|V0GEy!0I@DYg140d-lf6Qtlto1qbq49Zm})Dujlt%DU;;klKMbjX^J2u1o5H zEw;&nBwWV0Ffwx7|I-OIoV)SJ*@LYx?Q1|C3vi4A({Mfa{v=1J{H(G+#=K7Wt^KZ# zg5n%xoj)no8km6&n-gXw3X79^a4*rFO;z*7tu_6;JPt>jr35RMcMb69rxeuv$hsnL z_xcYRE-UePY&3nDx@r-hMkxzuY8M%l8g(-je*jzlk@|FGh--4@d+TR|)%%nF#2Auj z?zYTXKw++X*4mKBgopdsbU8=wcMcT&?fiD(HPK+)Vk!SW;@69w=klvYKE5qnTsud_pIs602mS^v21uz4 zgXM{B)3dz)NQPMWiwj&e%|!Jf%PjfN=MoKZ2Iay@Bb-p#OBFo?lX0ju>Fj2Gm*6{Y^>haoM5Wqj(R&a zAd*@Yx=0dR)=pW_$R>oBo7@d06-!$o>E)8b&QE-;?=^sJ!dze%teKBnW(h}y61t|R z>GOR@h4ak32T&#A)AsBX9TGdRMUNj|ePf+p21lxVKGdFbwgqd#8md{2B*hyLJ9($n zfqNb+W@=g^{sy8aEL)9S14T!o2-1(-*`i8+7rhi*9^$V(q}Ma2{R@?Sk(U82wC`FiLVsF3*mlqB(mGvH=clLTwg_Ld9;bnSwBMEenx+?Re@GVc zbL%pbRRquQ{S3|ZO+iA_xfDA(PjJ2mX#K&h4u91fhU~{B8-Qc#HW;rbO^VqjOYABz&e&+&*lR?O6S3M>7!LR@-RbMP++?R#N)m5uV@)dM6W04 z9^kO)i+&)dZqi16Apv!+WKLvHgr+OWmh7XULO<{+QP@&s5+(N*7V2@~wU(RdJ&EWQ znuv5ejl%1%_r&iU7WjI{O$lz+D>lD~W(nb&Sb;hT3j58{;p(0h=1+>S9=0|;7ABU9 zbk?H-o{VP^I*&|7wvSkX_mY%iR>d%OrNLTP-3>L%}Mp$ zcsTd|&(8F_Wj*y}d@IN~2IIsUW?hpjT6&+tj~ma3NPJ&hSVb1nk^hX5=80a@R{>S2 z18W+ewmjBrd=8t(->L~5ZRxlh9p9VnO-FEoO-un=4;miZ1Pi|HFRfoG(>HTU`!7BC3wGOK5UE{-bH^; ztZ08bH$Y*qpqGzry+pW&exC@=gTC+YH>&SB7BZEAg#`C}Sx(TCDNK3FYW;H#Sv#vd zY0}r!xF%5FVRV1;_yy*-ik91Q>6%4!B0p=*tx|vI)sia8%=kNP-YfHGd+YnYm}-Tuhvbcc zSBPr%mG%m7{fg}m8ZJhZXF>`+Rq}1y$3&0UhhEe%1EZwV@l(?~=MzBow`Hb&iu#!R zD#4~ntXkUb%;%lHJU78C^`n-j@tVCX$TkeM!yrSiW#O4W@$0rpUAgvz?W&q%5&uR8 zhm!6y{qdfjg6*!yx_k~kuVO8f>zfnlN4RDqN6s-hVn zcJg=>dWW6!_btQt+t;rS7_a~4TacUSFypMK`>I#>M5#?fvWgH<#ca(&Q#Lu_vutZy zSGBESJ>C=Fli8kM?@y6ROyRa`1b#$ijBvDR`dZ9*sb6~6TdZo~e7*~-=Cw?%9@lIU zFF0%kX+*<~U&Cf^^{1~>r26br93R0fZL+uAA9a{1`51-+-qNLZ4zCF(d&X~*{0^?j zn{?8#gZs%bUEjZ=F68*=D8|x8}xYGSUax z&b=CY2 zx|%dI2~6&kHhMVR3lg@7d`3|i!47C{3UL&ZuKZ7Kv*DG(s>B4%JbS30@#ux9JsUJ= z@3H7*#eS0qNhwlr9ivD`K>rsONn9}~I%OS)xUOP3&knvj6LEh?WO7Rn3T$^&MZigx{TKtu{=>?GIA zrG)+_meb+%mQjm8WXmWmk_J{a2YqJ$8{MEq^Vio)2x) z(%%rw5=`=b=m?@S2powlR#719N@xIg`M=WncBhZe^8#(4$yNEukz1Wj{lCYx_3e)# z#rZnF7s7E=rk7_qUuBLV`f1SDFZahOGku|7l{7EI&_t#UM7e2{C$jICE4JO;T_+DK zJ7Y9~H)S8TX6c19{UK_B9(ufTPb$6hb38IBYdq{~eEr4P4XSIweQLYU%P;(~yYu zT%iW$fIssKPs-lfS3MLK@&@1V4iXYfisydwwRVQTI}|a-#TzMhVdms8+=irMT)J(_ zCRC`Tw;%*H*H&@R3Z=Ph0xy~>+*Q7r{;nU(o_i%?O$gVfL}zalDLvH6q|4#~uQ5_$E=$we zMpCdyaA4ZAs}<$E5^r3;_nR;H-DFc^&_wL+^4j(J*ithbK2?;z5*;m07#rujHNbzH#ddlKgmWF$+Eh8Lp z9(#~oCs^0@FrVNwM#s1DwpHPRgMt!+GSGEhv$`-TBJC zv!fWkCpPCV(BjHnFhdDgOhzy=wC&w7cWJ>!+;@R5wA@)zV!2}hh?9n_Cb%Dle~WJ@ zag`=s9~bIHF8a)NR2KRDA}ZS~P=8?faorGpXAo`l|3QIrn_pCyjdHiQ=EP zqIh@=Le{`Wy_Z!2JQWxnba~zfk6T1kyVQc8h>MCa^%PBwixOk|nYR&?j`Sm$V=J=2 zR1>UT^yI2>uG)~sq{93CuyWPaOT93C-={vUGZ7E#Z4u6kJvK?lg9tLhdS+T^{#4;! zm4Nv2Ar4Gh6DSujb^Qym9P-~&<+`ucM=iY;u+%ihQ^vI|?zuL#+i{w0$d+W8uI_A{ zwTpA_p-k#o+ZDYS!MHb{_Ij26(j`gJo24-bl$ibnppVjdzH`fpa9wi&w0N6XJa#~L9sc8 zNWw?3*qxy78uGl&jByGMS4n-So`Oo_>z0Q6Gw+*`bcvc4`z2m1;4{koG?ddSPrasG zeEeC9iLSz#C2ORTeVhLWbHu)ttVh+_-$#AF{ma!cU-+}BV?%n#v7hJk!}vDqeS#gD zxu5*dDF8?AqhWI09h1+_rO7n+=_Y&H#bHVe#j!z$FGlGB_g|1}tMyfr;#bP<3I^9` zjIwYt!?nezsigZ#_>fbb#3jo7Hh^~zK>Fog96JrHhhXVn^jE(q470hrqAi#W5TZdpq|y+k{u>@ zY980T$Krp#hlzvbxuR3j^}k?tL7h)l5oElXmPm^z9tgMirtk~5MuyMu@Y!k6JgXW} zlTY0YWe;xCoi?Qz9|fyKjPi}#w7Z(9r5d$tc5cZ5{~w|KWNU8pvoJ9#UgfV>OIXB1 z7wJDUy}0=MYa$Zx7!~?=1&%+2Wu+~|W?nTIQRJ=YbwPPrM)qA-<#`zpuK5V1o0ks0LuCST&Uc4lM7AvK#EZn zzIXxvI}#;q2dY<~KG*RtmO(7);xW#@8GkvEbQo4{uB-pHKovAC+)``e-Uj`;cl+F< zD1Q7n|MNwli^KUymM3Y~qiz*b`J(-N*kB(49?L{5hD00ZTl1jJ?M0V|Mi1!jQG00 zqNTo$V1(GltL#;~n~EHYu~JoIdS03H>eWWxvpw;Am>*iG);Xa}xA8)Gr(RtpJi#sR zi@v-2U!^O=&q*};CSxJC_X-}*84H3Hj=(`Ftlk-ZH2_j2>Ru%6xT0G2w9qHd*~DZ_ zVe#p2K28D(ShTM}y^~b}cv&<zX)@?0K(?Gl~a%}Q{tarDxt;oe(3^Tv|SOG^c>%xF5?C-o_xZ87b9w@ zyUS^hwfxK(?DbPIJrs+URC#p4R4>YMy-Ee+s-kBqh2Js$>}-^f?$@Cv7Tz6 z?Ed|oop+)Vwly8sC!59i#kc}A3?7Y8bPOi>94UNME^}b?2ukKXkST5?%7fsfBKW>; zozx=vy8@XCrvcu)?CW$BOZ!)ZYi|~HB=IfvtQa_BeYF*h1F?z3ENx@UeuJZLLqAF~ zLiM-x)O9u(ANTtkXo>n+TSWaVFM5kjI`dbepHkWP*<$(vY?5}t+T?;A6fVal0j#J> zlZIzRZT~mJz3Da*Fb_T^MYh z(N_1Pg87A_yMt!~{_aB?u<*3@XC?VPrj$)lR6PgAqJyO|t^6QuQ#~PR5%w7|$K3GR zvPUTM!qhKrH=Ah)1Nb?9H$l^HI$YTfPDyw-ervUROfyFG_*G-1KD=n-bMp8>mZqZP z&R8Si=+$(U`&#Oq6DndUhnF}ft-F5&yM`bC{kdC$BiHb}X}Po9Y6*8buiN<^P;;-l zR8^x*)+kmC6(#v^y`_xi(0Hs@Q&m9=mbTA2n*PY9uv8QR&sp&I?d3D%x zU1+iC?{puhsF-8G!ciGeTQc}gc-Koz&R1By?Qov%v$x)UWQ!IB|2amci)4sNiYz#2 zmeh3XlK>_uXqyvrg0%)RF(u0%xqY;fNAuT*9cMBt%U5An0LsaVO2L>8Y4vE?K(YDl zKxv%05=Gp+HD6?;!|BJJ?R`vAUFBv8yJHGW%g_T1J=ap2zlUT-5OCK3h-^CTO9L8%);FZ`Gj(+P!f)xBMJKU^ZUPlI|l@lplbMXw;9xKKt6*h0R+1D1Z`WGPv*f=7hQSVj!ToI^_Q@^Rz}c`$$=NEGpvd!-1nM?)pSaQ_sp-9 zwEyJW&~C)(>eaGO6nf~ji{AyfBrZ*Q7rwW4uG{7iD*g5h4K!kAJbJD|&AWX<5~h+X z8vn;>Gz_j9MrHqpHi{cH0p6?gH9|~IfRmchzQ-%Sqe@>s<4v zPYl+jM4$EV^776?RzbGG(B_LGZcQnF^oThD5G&(#?Xy$Pe;Z$EI9t}jIltENOH|YQ zF{Stnckd?&@P?5WUKkH##rlYS*16ZPDt--53P=XVJ-}R_QkU@>8KU+-INpz5iTVqi zmY-lUWPZ&vtPN}FAFstTh=3li255{qBc}0eOk&9s+TGq@ub0PopQ%>+p;I0UHGQ&1 z>Yt%Pdu9sZ>tX9MKvU3Z-nM$eois9Rrn&#Gt_qhS2W7p5qu~>15sqzSoN;Vc!8U0$g+glaOh{x`Z*rV9_{XV_; z$P7Sm^ab6!O6HY!Uf*R-R>iS8@FZ;l0S+_T^X9Anx;b3_uWP{yab^mwKLIj!jjaE? z?+OO&4fj+(0MGWd;o=1#@!*+cZD8W0<>O-6sTuztgnN>oEdV;hnsvrIi-tjsOQdPQ zJsv$B$4rXMtS0|k`-hr0zgV^4)~NS9GLl5C@;F*WW(Z0ujI zPiw~UM@A}NVvR*c39@@Qy`(vS&-hu1igR`qjUdA?Ji>m; zRbMl(ak(W&0t z)#y<-)Rpr!5EY>DSyo(C&(svz%6M=cc!c>9Md_77YW&Vf(E}BJ(AO_o7{4I|byO4O zqI|#u*c!Pg^nw5o#9Ud@b)h(z2NGe_fZ!Tq^h=w`^+F$Hx=09L#lM6Sb56=skamHg5Yq z*V#l;$=L*0QCEOoh9!qNS$u>Asolf0sx}8=eP{i z?o^(|=`P#LEbidP{#Qy1V6WTX8yh%go_o*4#9TnufEp-9CF7rv8)_^3O3v#nEwGLJ z+h_zZUm;X!9lNc(4wwx+hwV}~*o}M%qgur@!!*-(OGPzvhirI7hOY}}r zTl+Wbsk~Z0Cq(A_Yf|s(2b&Ct8X;YaUjO4Xn@_I3)AZHsg;l6mzQWG9z;L zaCG(fy=R#N*v7q;)gK;>R%(By|GvV-)NX%77BC=jvBF(% z_RG^LL$q?!7fs;$my=8oZY~feaPhjwH^G^Te@`$BD#P7}-`H8RQ06JWN%l5Z8Cn*} zN~4Gh8TR1HX}NDpi}RpFm$|3bDwszPaSFVjE$$+JQccgBimJ?w?&%W1w@A4K*bmxk zv8DKjV8;$@6T%~tiK+rNEU{ksiejQUk&`hm89f?;D+$`=t)cZsK(m#BToVNw?Yo4!jCPq&g>pB5=@9G$xzV02Hh3P*&7njDh-DB=4*aYB#)ho>}&U6-N zk000LiaEHTXVjjXHPch!2XlOIBE*qdU96nz)U4Q9w4o!xl>M77uyvmC*kO%r0=K^U z(Wj)~Q0M|jsu$2m%jp^rs{7b1>o85O?c#pBl4DsGO)mT9cQJ)i`CO>l>}7nAz8Y7M zzOMphrgy0}UHMBZNmw3zZQX8!_y%p5d(h1_mGMl-MM@Fk#RWrA&KQwNUZmsY60|+b zQ(En(s*1A2PSbJS@$Utup3dt3%!QLx-;BFFXTMCUN!-4Z$Y*gDWT4h?S5Ds`(clT6 z_v%yQfe5PNe+c9&nYw(N`^7B`)xqaRo0VoFj1gl$dTJx?vqh{Ay zEpT1L9;K7T@0^-3DpC5bhY#WoI_Q&2XW|>|MQzKPp{O42gn;bbc~zIIwmn z*#p#bOtr}IZ4k;NW!$2}%UyoF2NA9&@wcIn11FV-W)OW|+BW;<&hilx>3OI2=)7nF zU&5^yhO0Z(++^);8nmqQHv!bx4`N1W2z+U)IkRl@?1ggW0Z-c` zD=J#%{b$z5MOfS%d$I8yR z3nvD@X&Ag|NO!?jh3AEX^IB?#L=zk9h)+o5oB04(e{*obkgG691!N(2>u84Fc^YH) zw<1eGy~d*3g)5o$dq&b9dQ_7u`U66Ia_)g~)^}E+0hB&B=|#OjwL6mD;7?PGS(^8} zmpiN6+NWC>d7{GWs<9FWrm?DP%c=Fy0eA|)01DH6s8#r`JIkNU5AskzXWoe5i`>uH zjcW0ml=?n3*KoS(a#gnQRMetm&H0juU?Xb!!8{8+F$pdOv}<~j433ueqQh~z)ONhV zp%+}@`aK)OM4cpa=S~MW+GMoH=Le*OE%u5#tmWghH(&Ccy!bs<8nqnFg?>@^OF6hB z%y2RwJheSjl#upVR6fF)YW~eWq9zpH8hC=WkrHL&;y54q!hc3S7arWfsAbCHa)lxi&h7UyEOPJ2z%t zFE&1^Cb(zbwAb=7djepFDf{c{;FT%yB}exTOWmXAz!l%?yq(@h30>e4>uB}<&2jMc zU0C;m;XOxkDp#&&k0*b{870cRpMBtUMQZS6e0uyX^IWd*GWX(rw3=sX*l0{lfLi68 zvefSwu{#goHrKjMCiM6=zb9;U_GXBD=YL}Iv)(=`G%AmROQb~LMbk*v#QiY+l=1KM zR(pBjm7k*iXz2vzRWA{d`YvgY1vNS2DeaDfJkK5=j&}6wtO}K%5ek3&nfcOxrDk_| zLO+x+D^ zoUZBqP&!4DC@1^dDLmA?i{n#GeZ}Yd6Bh26fqRfsz8{&lv=|DQ7gDlQKyu(i?Sgec z=L0lG{lah#v5VJ6DSQX2~EdyNQ(n{6Z zn%PIuA2**f(`l;=rg8mf)VO_{HX;%nD zM#SzyBqjte8*rKq%lP)(yyY9*slY(7>>9vY=m0-1ix;>kL-#@OBDOmyD#v*!A#LtX z>4Lu2puKtwsyRW#;$6m>$0hm1J3cji`LP63wJK|Q%lWZTLEnz8L)yi6jlf@qv7j2xy1PYqn2d@a)`cc&=geqonIZ<#lqDGW8X%?YQV=I z={D|GV=3rCvvg2H*L1&2;P35FCP3ioK*`XI@eVltlk!!dc`CNoxu&)2kAO*^Cenn$^Qsz);ehI77s$x~&|a#Th#rm7t^ z&!bt%JB?VYN0uiIgT1ymGxPJXATF*p;I#VwprSXvCP}z;z9j0B>g1ANd(}VUS;|5} zN~ZaDvr<%>FFC<;#g^ZZ)x13152NMat5?{vz&+@2+#~%T%lXXFYIxwXzu1?;V;rv@le(3*ct?O*X<+(62%jdmy{3n8}JB!G7}G15QqW1dcY z7pZL3jBJs~jmiud0af!&-@i8ksCu<8HdK|&N{N6Za+7{x+i7fbnm)4CzASD`db8kZ zt@mN0k1<}3VWX`oCj<}~MGN=VZIzQaf;s^lE403yMMr}NvNDr@C5`d%lhC`ppZ(}Ym45IbcnSW=)Tx7ai+!Y*4 zKm(j#A7hc%@+?Xo?7fM#`3@>3)E26GmzLY91K~P8ThLeI$$AJcC4oXLW7|yN zjd0i-FO^Dim2^$8%$C_rS_Z&nG%=uk_HOvR<*StEJ6i|y{sVV-*8c}6b~fT%#tK2+ zKK2J1u9p3WtV4$2BC>gAl0AF(72tXvXjsmC)DvhpKJ{ulhx)?(?*Zj*(N#{sUL)Ij zlA^}epTALL-ZSu&HSgeDOS%0|^M@Z94}mOoTp`3M+W_vqI^ls};8x!=^Ay%(_@^Dj z?>F@S?gRlqlmFT|2Ke9q#TK#uw^-$uf44UZM8^P}FF&R)CIE7da<6|Q4656CcgcV_ z&ug?~AD>d|4y@; zJBeu*n`UJLV&Yhc;gE=~Cvo{T!|~_kSx~L&Jm(3@{-hb^YD+X!aY!R{ontnH_^l)$ zaDhNSbH(hROFu7j-E_0kfIKgi^?8GX7U;ah%bEJ7jOXkg*qp$UC|i`H7fL``;COhL zNgDd!+B@@bDA$LNw@D>QWeYha*+LSI{X__5XJYIOS!RUBWEm&C0rV>Ha3a!L4{5YF4^0Gq=nb#kDO`)*e?HojpbR|477e6_uL*COHE(_d z3Y7!i-oW;QN7^sWs$Jap98jB{P*UG((lp}i2{6U{ zTBfIX=JeFx%Q5XNCm3O)vpE1TUv41);=POlW?eRHKqz>kZ&Q75dGl2*t0}W<9Euz6 zBFJxXS9t@P*MLr=$)Z}~L;X2K8|xsOCS0fW6{vbiNVB(wz;D@+Pb8nSs%UCBfQ3JW zFz?C>4hx_1(2I&dJikY3^1Sv4$mNCNWu}Sb9A-SQ8t*Jh(2zQvv0pLOCj%v2l z1r2#Y-OQ;zXz;#|zP`b7L)w?U)qPpdZBU4+0YT-<^?4gXjc*vs-1%mi^rl#;#rJ|Q zahD{!Ni22^vJS#6CHsRNmp5)q;ExHfK(D$tYk5rbRb)R=e?Xwxl_46_oc*Se!|g(N z=xcS%^SR@MNHBsKMXYE@1O;J&OkCTQTO3ud_zT}it6tlQK3~~cb9A!Eqx?mu0s3H} zV|}FIn7?wrlmJzEY_Pg%W`}DpD{wOfdYDWp?E}uGu{y7L&AsMuo?kM-u=R*VCj{Rd$#yS5 z+R{-pV`zR0)v!X2-co{%1%&E8^Yc$~cPpOr{d=Pu$mc$JM_b5BJFi^^lX%-1lBfI0D4i&dpSf|^uR76zFx+B*3r_AO@(8yhxT3GX3rZh0Y zO=IT_*O417AwTCQ?hm_hy+ngNsG&<_(y;2hNqEa{$WzeI2r4Hy17F%y(juJeQu_pdr`#6RycDc26GA(nAqra zi0Y6F)1}>v3UgM%4wi0u>A{g#A8&MDL=hF(!eWD2e^^{pFTNa<%qasj^HDS-``mF;_))al9|3KvF@2h`vNzV65B=Yq7e5-Ob^o zH?~-pFPQqnDgk4GTZ|Sr6%Xli_(%oTC2>2IP#ku#XF0Fii)rMgE1bt0 z^Jvc6r%eD|qLLgn%uu@gPl`YA-e9FV<-9Ll+Opj`DFPqOQ8R18Y5AL~Q+>!7Wz)K8 z{Cz@_E8)6BYg9UbH2yIUTvL1rvA5NJHOQBlY1<#{l5+P_>QH}xSEiL?lMftPiM;56 zr5jM{PSx}hvwJ7T&Gibv#YYh{%lX&owBnF$VB=c(_#NRO*I3oIr7$V~39G_Aj~ zGa6AenmRZ5{-@nF{E*D^n{>^K#wlOE0`~ICY`UG(i}dOw!OyMj1A$Cvb-0irF{kYL zq(ozmuO;Eb%@xH20le_)ti#JKF7H9uJ}G0OBopgyyfCA!yjC{+r&gKclDH7m)cI4J zPZdUiqU$hjUMix2~8lqta8yG6>;6`)ea%0GkFEsr8N1*oso9KRMaT7KufOJ z+N_q6?qmadJ9OMB1Fy6LK}5{RP*moBs%Y7n4=3G(tROUR`MyY^VIBuAatuWf?~7+< zGg~89ErYX#VDA|p<8oFbdlAZOpSVa1p#oNw4p@Gk%cO7hmg zx_EdFeO(OH894cnv#XS*>|8$bCLx0esTy+YA{>LFyF+5TfUN^iVhg=qlBo_n*qtkI znf;f`C9H3BM9p%5&Sy14^M7F`7IymojhVOs3X^j#r%w85=GV}WGo*Mi3lKqDu9+5# z%z|d=fQVg(V*X6wty^*H?;p^y*GLVFy!Yhe2X9f0rCmFOeEO9#jTi%Ysh;_>PR(d_m+j`PMT0}C268e#{du{12IDu?ebJ{Z zPtgoFbeNa=LC&0K=EiTE0+&+WN~gkJ07*?%0#sWS)DRwfz1PE?qVJ^vs5Acvuqy8X zLABz$ogjQQS^%5|F|xN_>QCJBJH)w@#z6u^ZJ{QuvBVPzO8_1Z&fkeM%a;{<|f2(QC`q>M2(-j7y&GjPUHlJQ!ab&)-pg}28NdhenLg#bB82P_o z5W2&Qx1`)nikTBO5v+&-xt*Qu$x2dh#fjB()}A_b7BSvA@ZO{)Yh579f8nA{psbDwPwk(U|F4Kgl& z>uJhu@S>Tck9WUs-fzk(VZ`3A%enc{G-YF9U#ye=Ldc++DID&bR*sC2G5QDNQLCUZ zaV2jBbdWs2brl^!Tm?HWhOt6pubV}0a$=mE%k$R&IXbrxi)JhJW7=YnX&K7`vlH&4 zQH)34ScKjSHU7YbS82OTKcS-IK2+&+!GX&Mu!N(?|EQ5aiF$5y?>S zdQRajb0a&$`#ct7%Emh2)jEGE`^e>(?QiPx|7j*mwE^S@7P7KJ<&1ToLzs5 z%Mnp#LTku)NYZqx_oCu8hFiO3#;58tlFG160f(FSOaRAJ6v4?v^&~|QQ;aiP?R>#z z*})e~d{CMA83lL6)fb26@=G7h*2WaUXTLgJdnwR0T46drCEx<&t2Z65-AV>-*%(J& z`|myLPA9|UFPprR|1Rf>w{-0|E2so3D3w{m92WlsO+g17JomcmYyBUAifOJj2RENy zX3j2sPrs4Icpqa4Iomw5z!CIpS2AY?da8aF^s{4rfU8Ctp>?YSQ~jGOkjE( zkS~rcKGaGF65p9?Nw3K>y^OPcP&nrj0Pe!JaZDQDRtuxb8i!;o^4fk{pgdy1#i#(n! z!PBZ|J}rkm&mtZ5FZxN5>20vdeHGjcSQ%xT-+(24_~Po(J(l0}k)m)7<1w!osm0^^ z90Q_Lj9VNGAK>m~Y4R%m;cNt!Cb167NKrRdE>1S2t$E?a=-f7b<-0`JYz+=d{~5GX z`#@%2W*Ms@@hPLM%t4HceRNBmyC6anBPOwoLRGV3kH|7Pmr@}iUxisid{`H6XS8ID zd~Nuril}!3_os^Tz!(M;@dqR`6%!@u@_Ky7Fga6l#->_r?og+e4D;S^quuh~(;*oZ zn?RDbN%YBCQ!ndqJ(@+>1+Pdc`Zm-v-tW4pcDtE87e)d`o3iK~wUIDZkR3Z0To1Z? z%_fPS-b;P^BT&oiP2D`9WHLr(Y>mGv{!uOF_J{8Bd38_Ix->}h$Y{7se%)t?$lpl! zp{|C5zQsPww*JM1v~tMdv;U%i%uVS$cpKYL9i<^@m~t&AI#7q3-)Mv> zb7x}nsZ?(juXLN{(16%fj!-1)gSUCau8o}C6Lr$JP`!FM(HlrbH0~rlvltwHz)!!+ znkL16UOZl#yWFhKLQNFbja~fFi2^A@B^p-bs1?-nxE#5VDH4QCQGQTa(A{sFAhUDt zz|d)6ugnYRwA&w=S9THAdZtBX7Wmb3CpPjeq&hTR!k!KfS)MZ!_jq-;DXjP5K~JX1r~sQwI%l)aPB# zxL2du_wW+qRb0fIe*-|Ei#*u6!Q%>dh4T2d^6X^Gz>J}wpk{$sbceL~=Lnly>^l@& z%}a0QgJf?k@&xx@Cpq^>()%o;*~E|`ti>3VbHcl&w@Lue@qgW$A&x=O-c?M6WmQIt znrD89@DEbrdNH=_R&^7^G1M`SUfGerXjCw6YB0263~xUxsRG`^Lcn{NpQhg+tN%Ew z*#&GM7oAb>xzPNh0DXJwRbE(Ml*MoNv^L+LJ$Fj&Nexe%Eli>#WfguQ3U`6yM?!E+ zr%>Etj~Rr_s8yHotdmHKCYNj{<#OI|wl>a3NPav=g?nWCGefEvx`um2~OJ{9`%k z0<=f(w(dLFcw&XbpM^4|kD$&SdlKwhBjoS$p;p^MzCrfP)$)5*rGAcSSj+>9v{YwD zZlgIj&0L41+YFPWP$`MtKoCe5_Vb9s9lUHs#M_m5+n3B{o%EuRsB618A3kS8@Xt9i z4X1i1<$C+b5ovG+ZqyGqW0M#*7EH zU$_V)8)RzAYM@wPV|&05cg`rji0sO7+^h&aDr1(1R0$TYmtiJ!Hw)j_>6Z%Qn8$(x`AuN@j-Jy&?X z{mpWAzWRt#Sq$RH^Yym(Ktt?_${8bSjFsnpJvdz9^>IbEVF@%&99v`hj>8*+B*2@G zoW{LgKRC4|(Jv7F6Of0Pea9=~h+OZSHy*fOXxiLRXX;GGAQh` zs*%m>%ub;@p6 zoxlG5O`R0OCML02>mk?#M~v}}{7$d}a8KDf848mdalNM8!o@1Rw5A2_)M(5=ehk%< zBhSIRqeLSjS_#(BVAb-n>m0{n7lX+d6^Y%0ZMTUQX!eZ!?6pQ61+&{sS@pk)4(z9l z5slS|_GA0YJK@iqDH@2$n`G*!8#?a3Y^Ho}i8lWq9$~M({;jYhc}N4uG=TscW~e+L znSz%^3^0GclXqqUro5KAAG`jR_q$Ow&7;Z;y%69BlPfm=Crrghy8I`gcwCZ^-xgga-GN{spiM1<8JP=Qo=>5J9fF%N3iI zGjNBN2s>HsR%o0p=a;SXw!y8?4XWj$ivfe*X_)#p$7Fv8{nB=h2w*Qth4H0`(LqWq z?SBwBB%uF_#GWi>Q&4_UL9~)Rt4ILU5+GeP>3nnb^PL`vR^br?nE`QMb#N606SXte z?&E8}RW0PJqh7Mw8sV<>i|~kbh+fbHScq=@b6~=5slO15H>Abpaw-zeNmG|nZM@otnfh|Nf*BU+NrJ=(Cd|oiE9D7>Ivwz9Sa zV$(au@``O%{%T$E^Ubh?)$8}KBB%Vt|C>HCbKluw(&$4&ntx5n+$UG~50TH=K(Jjk zI>kcIerbRoItlI3JjER^h_XA1_5%J{>+uQXLL_q?9d_))Aul2dPb zKoxUB-C*lv2J;qRIGM?ns13P@=%|)}TPy*6Es7Don>cwU699Db$q~C=-zSh0dOLdZ4EJP4&ZiB7pDH^8nGF+?y@r$rqNfNxT?mY3bP zl=dCP9>9(Z5)-#wQyj!$CgQ*EoD?b7i^s9%yuNE*?EW3(Yz-^0nfBb{Eh?{Il!UN` zHesTQ_~twy!^o94)_up+gxOYlWZR;!%+_T_S6A{;Ewzo`_2$Ob^qzU z52kr7haNX_vh6m+h0Xrn>=E$=u6vpZX&sfuGR7y4&viUa)fwYFPII_)q4dfle9QKmjZi19p;o|MU1f`;69Kpa0|B@&DX4(*;+) zA2@KR#OT&d>;K$;`TxJn`~Oeikzc3R0t4=Ue}uyB-oaOU)dZV0^J(_LHPSb|h1GR= F`d@tU_2vKo literal 0 HcmV?d00001 diff --git a/nginx-spa.conf b/nginx-spa.conf index e9879c4..3c29bef 100644 --- a/nginx-spa.conf +++ b/nginx-spa.conf @@ -6,7 +6,7 @@ server { # Proxy API to backend container location /api/ { - proxy_pass http://poster-picker-backend-1:5000/api/; + proxy_pass http://backend:5000/api/; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/package-lock.json b/package-lock.json index a7e500a..6f109f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "frontend", - "version": "0.1.0", + "name": "poster-picker", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "frontend", - "version": "0.1.0", + "name": "poster-picker", + "version": "1.0.0", "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", @@ -19,6 +19,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.7.6", + "jszip": "^3.10.1", "lucide-react": "^0.453.0", "material-ui-dropzone": "^3.5.0", "query-string": "^9.1.0", @@ -56,57 +57,45 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { - "@babel/highlight": "^7.24.7", - "picocolors": "^1.0.0" + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.25.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", - "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -167,15 +156,16 @@ } }, "node_modules/@babel/generator": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", - "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.6", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" @@ -207,14 +197,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -303,6 +293,15 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { "version": "7.24.8", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", @@ -317,28 +316,27 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "license": "MIT", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -429,27 +427,27 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -470,40 +468,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", - "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "license": "MIT", "dependencies": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", - "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.25.6" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -2139,46 +2122,45 @@ } }, "node_modules/@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", - "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.6", - "@babel/parser": "^7.25.6", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.6", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.25.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", - "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -2649,16 +2631,19 @@ "license": "MIT" }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } @@ -3603,17 +3588,23 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -3625,15 +3616,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", @@ -3645,15 +3627,15 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -5260,12 +5242,12 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", - "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "license": "MIT", "dependencies": { - "@babel/types": "^7.20.7" + "@babel/types": "^7.28.2" } }, "node_modules/@types/body-parser": { @@ -5316,10 +5298,20 @@ "@types/json-schema": "*" } }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, "node_modules/@types/express": { @@ -5772,9 +5764,9 @@ "license": "MIT" }, "node_modules/@types/semver": { - "version": "7.5.8", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", - "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", "license": "MIT" }, "node_modules/@types/send": { @@ -6103,148 +6095,148 @@ "license": "ISC" }, "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "license": "MIT", "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "license": "MIT", "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, @@ -6281,9 +6273,9 @@ } }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -6314,13 +6306,16 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, "peerDependencies": { - "acorn": "^8" + "acorn": "^8.14.0" } }, "node_modules/acorn-jsx": { @@ -7175,6 +7170,18 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.21.tgz", + "integrity": "sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -7329,9 +7336,9 @@ "license": "BSD-2-Clause" }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "funding": [ { "type": "opencollective", @@ -7348,10 +7355,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", - "node-releases": "^2.0.18", - "update-browserslist-db": "^1.1.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -7468,9 +7476,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001655", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz", - "integrity": "sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==", + "version": "1.0.30001790", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz", + "integrity": "sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw==", "funding": [ { "type": "opencollective", @@ -8382,12 +8390,12 @@ } }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -8826,9 +8834,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.13", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", - "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", + "version": "1.5.344", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz", + "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==", "license": "ISC" }, "node_modules/email-addresses": { @@ -8875,13 +8883,13 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", + "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" @@ -9046,12 +9054,6 @@ "node": ">= 0.4" } }, - "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", - "license": "MIT" - }, "node_modules/es-object-atoms": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", @@ -11399,6 +11401,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/immer": { "version": "9.0.21", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", @@ -14375,15 +14383,15 @@ } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -14577,6 +14585,54 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -14663,6 +14719,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", @@ -14679,12 +14744,16 @@ "license": "MIT" }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", "license": "MIT", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/loader-utils": { @@ -15051,9 +15120,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/multicast-dns": { @@ -15157,9 +15226,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", "license": "MIT" }, "node_modules/normalize-path": { @@ -15519,6 +15588,12 @@ "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -18496,9 +18571,9 @@ } }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -18546,12 +18621,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -18686,6 +18755,12 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -19621,12 +19696,16 @@ } }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "license": "MIT", "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/temp-dir": { @@ -19703,16 +19782,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-UYhptBwhWvfIjKd/UuFo6D8uq9xpGLDK+z8EDsj/zWhrTaH34cKEbrkMKfV5YWqGBvAYA3tlzZbs2R+qYrbQJA==", "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -19736,15 +19814,50 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 10.13.0" @@ -19825,15 +19938,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "license": "BSD-3-Clause" }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -20218,9 +20322,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "funding": [ { "type": "opencollective", @@ -20237,8 +20341,8 @@ ], "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" @@ -20267,12 +20371,12 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", - "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/util-deprecate": { @@ -20381,9 +20485,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", @@ -20418,34 +20522,35 @@ } }, "node_modules/webpack": { - "version": "5.94.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", - "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "version": "5.106.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz", + "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.5", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", - "acorn": "^8.7.1", - "acorn-import-attributes": "^1.9.5", - "browserslist": "^4.21.10", + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", + "loader-runner": "^4.3.1", + "mime-db": "^1.54.0", "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" }, "bin": { "webpack": "bin/webpack.js" @@ -20605,14 +20710,48 @@ } }, "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.4.0.tgz", + "integrity": "sha512-gHwIe1cgBvvfLeu1Yz/dcFpmHfKDVxxyqI+kzqmuxZED81z2ChxpyqPaWcNqigPywhaEke7AjSGga+kxY55gjQ==", "license": "MIT", "engines": { "node": ">=10.13.0" } }, + "node_modules/webpack/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/webpack/node_modules/es-module-lexer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "license": "MIT" + }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -20635,15 +20774,31 @@ "node": ">=4.0" } }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/webpack/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 10.13.0" diff --git a/package.json b/package.json index 318be52..c53a71f 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.7.6", + "jszip": "^3.10.1", "lucide-react": "^0.453.0", "material-ui-dropzone": "^3.5.0", "query-string": "^9.1.0", diff --git a/src/App.js b/src/App.js index ce48591..1268e5b 100644 --- a/src/App.js +++ b/src/App.js @@ -1,33 +1,36 @@ -import React from "react"; import "./styles/App.scss"; -import { HashRouter as Router, Route, Routes } from "react-router-dom"; +import { HashRouter as Router, Route, Routes, useLocation } from "react-router-dom"; import { Provider } from "react-redux"; import { PersistGate } from "redux-persist/integration/react"; import { store, persistor } from "./services/store"; -import PosterSelector from "./components/PosterSelector"; -import PosterGallery from "./components/PosterGallery"; -import UploadDiary from "./components/UploadDiary"; -import Cart from "./components/Cart"; +import Header from "./components/Header"; +import PosterSelector from "./pages/PosterSelector"; +import PosterGallery from "./pages/PosterGallery"; +import UploadDiary from "./pages/UploadDiary"; +import SelectionRecap from "./pages/SelectionRecap"; + +function AppContent() { + const location = useLocation(); + return ( +

+ ); +} function App() { return ( - { - -
- - } /> - } /> - } - /> - } /> - -
-
- } + + +
); diff --git a/src/components/Cart.js b/src/components/Cart.js deleted file mode 100644 index d1829df..0000000 --- a/src/components/Cart.js +++ /dev/null @@ -1,204 +0,0 @@ -import React, { useState } from "react"; -import { useSelector, useDispatch } from "react-redux"; -import { useNavigate } from "react-router-dom"; -import { removeAllPosters, removePoster } from "../services/action"; -import DeleteIcon from "@material-ui/icons/Delete"; -import { - Container, - Box, - Typography, - Button, - Dialog, - DialogTitle, - DialogContent, - DialogContentText, - DialogActions, -} from "@material-ui/core"; - -const apiUrl = process.env.REACT_APP_API_URL; - -const Cart = () => { - const dispatch = useDispatch(); - const navigate = useNavigate(); - const selectedPosters = useSelector((state) => { - const selections = state.posterSelections; - return Object.entries(selections).flatMap(([movieId, posters]) => - posters.map((poster) => ({ - movieId, - posterId: typeof poster === "string" ? poster : poster.posterId, - watchedDate: poster.watchedDate, - })) - ); - }); - - const [downloadFormat, setDownloadFormat] = useState("zip"); - - // Nouvel état pour la confirmation de suppression globale - const [openConfirmClear, setOpenConfirmClear] = useState(false); - - const handleRemovePoster = (movieId, posterId) => { - if (movieId && posterId) dispatch(removePoster(movieId, posterId)); - }; - - // Ouvre la dialog de confirmation (appelé par le bouton "Clear All Posters") - const handleOpenConfirmClear = () => { - setOpenConfirmClear(true); - }; - - // Ferme la dialog sans supprimer - const handleCloseConfirmClear = () => { - setOpenConfirmClear(false); - }; - - // Confirme et supprime tous les posters - const handleConfirmClearAllPosters = () => { - dispatch(removeAllPosters()); - setOpenConfirmClear(false); - }; - - const handleDownload = async () => { - try { - const response = await fetch(`${apiUrl}/api/download-posters`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - posters: selectedPosters, - format: downloadFormat, - }), - }); - - if (!response.ok) throw new Error("Download failed"); - - const contentType = response.headers.get("content-type"); - if (contentType && contentType.includes("application/zip")) { - const blob = await response.blob(); - const url = window.URL.createObjectURL(blob); - const link = document.createElement("a"); - link.href = url; - link.download = "posters.zip"; - document.body.appendChild(link); - link.click(); - link.remove(); - window.URL.revokeObjectURL(url); - } else { - throw new Error("Invalid response format"); - } - } catch (error) { - console.error("Failed to download posters:", error); - } - }; - - return ( - <> - -
-
- {selectedPosters.length === 0 ? ( - - - Your poster collection is empty. Start exploring movies to add - some posters! - - - - ) : ( - <> - {selectedPosters.map((poster) => ( -
- Movie poster -
- -
-
- ))} -
navigate("/")}> - + -
- - )} -
- -
-

Download Options

- - - -
- - - Confirm deletion - - - Are you sure you want to delete all the posters in your collection? - This action cannot be undone. - - - - - - - -
-
- - ); -}; - -export default Cart; diff --git a/src/components/DownloadButton.js b/src/components/DownloadButton.js deleted file mode 100644 index c95cd2a..0000000 --- a/src/components/DownloadButton.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from "react"; -import { useSelector } from "react-redux"; -import { Fab, makeStyles } from "@material-ui/core"; -import GetAppIcon from "@material-ui/icons/GetApp"; - -const useStyles = makeStyles((theme) => ({ - downloadFab: { - backgroundColor: "#1caff2", - position: "fixed", - bottom: theme.spacing(4), - left: "50%", - transform: "translateX(-50%)", - zIndex: 1000, - }, -})); - -const GlobalDownloadButton = () => { - const classes = useStyles(); - const selectedPosters = useSelector((state) => { - const selections = state.posterSelections; - return Object.values(selections).flat(); - }); - - const totalSelected = selectedPosters.length; - - if (totalSelected === 0) return null; - - const handleDownload = () => { - // Implement your download logic here - console.log("Downloading", totalSelected, "posters"); - }; - - return ( - - - Download ({totalSelected}) - - ); -}; - -export default GlobalDownloadButton; diff --git a/src/components/Header.js b/src/components/Header.js new file mode 100644 index 0000000..b0f7de9 --- /dev/null +++ b/src/components/Header.js @@ -0,0 +1,252 @@ +import React, { useState } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { useNavigate, useMatch } from "react-router-dom"; +import { createSelector } from "reselect"; +import { AppBar, Toolbar, Typography, Button, IconButton, LinearProgress, Box, Tooltip, Link, Menu, MenuItem } from "@material-ui/core"; +import { Snackbar, Alert } from "@mui/material"; +import GetAppIcon from "@material-ui/icons/GetApp"; +import ExitToAppIcon from "@material-ui/icons/ExitToApp"; +import ViewModuleIcon from "@material-ui/icons/ViewModule"; +import SyncIcon from "@material-ui/icons/Sync"; +import MoreVertIcon from "@material-ui/icons/MoreVert"; +import JSZip from "jszip"; +import api from "../services/api"; +import { removeAllPosters } from "../services/action"; + +const selectSelectedPosters = createSelector( + (state) => state.posterSelections, + (posterSelections) => + Object.entries(posterSelections).flatMap(([movieId, posters]) => + (posters || []).map((p) => ({ movieId, posterId: p.posterId, watchedDate: p.watchedDate })) + ) +); + +const Header = () => { + const navigate = useNavigate(); + const dispatch = useDispatch(); + const galleryMatch = useMatch("/posters/:movieName/:movieYear"); + const movieName = galleryMatch ? decodeURIComponent(galleryMatch.params.movieName) : null; + + const [downloadState, setDownloadState] = useState(null); // null | { current, total } + const [syncState, setSyncState] = useState(null); // null | 'syncing' | { added: number } + const [menuAnchor, setMenuAnchor] = useState(null); + const [snackbar, setSnackbar] = useState(null); // null | { message, severity } + + const selectedPosters = useSelector(selectSelectedPosters); + const username = useSelector((state) => state.username); + + const count = selectedPosters.length; + + const handleDownload = async () => { + if (downloadState) return; + const total = selectedPosters.length; + setDownloadState({ current: 0, total }); + + try { + const zip = new JSZip(); + + for (let i = 0; i < selectedPosters.length; i++) { + const { movieId, posterId, watchedDate } = selectedPosters[i]; + const url = `https://image.tmdb.org/t/p/original${posterId}`; + const res = await fetch(url); + const blob = await res.blob(); + zip.file(`${watchedDate}_${movieId}.jpg`, blob); + setDownloadState({ current: i + 1, total }); + } + + const content = await zip.generateAsync({ type: "blob" }); + const objectUrl = URL.createObjectURL(content); + const link = document.createElement("a"); + link.href = objectUrl; + link.download = "posters.zip"; + document.body.appendChild(link); + link.click(); + link.remove(); + URL.revokeObjectURL(objectUrl); + } catch (err) { + console.error("Download error:", err); + } finally { + setDownloadState(null); + } + }; + + const handleRssSync = async () => { + if (!username || syncState === 'syncing') return; + setSyncState('syncing'); + try { + const res = await api.post("/api/sync-rss", { username }); + setSyncState({ added: res.data.added }); + setTimeout(() => setSyncState(null), 3000); + if (res.data.added > 0) { + setSnackbar({ message: `${res.data.added} new film${res.data.added > 1 ? 's' : ''} added to your diary`, severity: 'success' }); + window.dispatchEvent(new Event('diary-synced')); + } else { + setSnackbar({ message: 'Already up to date', severity: 'info' }); + } + } catch { + setSyncState(null); + setSnackbar({ message: 'Sync failed. Please try again.', severity: 'error' }); + } + }; + + const handleReset = async () => { + try { + await api.delete("/api/delete-csv"); + dispatch(removeAllPosters()); + navigate("/"); + } catch (err) { + console.error("Reset error:", err); + } + }; + + const isDownloading = !!downloadState; + const progress = isDownloading + ? Math.round((downloadState.current / downloadState.total) * 100) + : 0; + + return ( + <> + setSnackbar(null)} + anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} + > + setSnackbar(null)} severity={snackbar?.severity} sx={{ width: '100%' }}> + {snackbar?.message} + + + + +
navigate("/PosterSelector")}> + logo + + Poster Picker + +
+ +
+ {movieName ? ( + + {movieName} + + ) : username ? ( + (e.currentTarget.style.opacity = 1)} + onMouseLeave={(e) => (e.currentTarget.style.opacity = 0.7)} + > + {username} + + ) : null} +
+ {/* Desktop actions */} +
+ {username && ( + 0 ? `+${syncState.added} new film${syncState.added > 1 ? 's' : ''}` + : `Sync from ${username}'s RSS` + }> + + + + + + + )} + + + + + + + + {count > 0 && ( + navigate("/recap")} disabled={isDownloading}> + + + )} + {count > 0 && ( + + )} +
+ + {/* Mobile actions — collapsed into a menu */} + setMenuAnchor(e.currentTarget)}> + + + setMenuAnchor(null)} + PaperProps={{ className: "header-menu-paper" }} + getContentAnchorEl={null} + anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }} + transformOrigin={{ vertical: 'top', horizontal: 'right' }} + > + {username && ( + { handleRssSync(); setMenuAnchor(null); }} + disabled={isDownloading || syncState === 'syncing'} + className="header-menu-item" + > + + {syncState === 'syncing' ? 'Syncing…' + : syncState?.added === 0 ? 'Up to date' + : syncState?.added > 0 ? `+${syncState.added} new film${syncState.added > 1 ? 's' : ''}` + : 'Sync RSS'} + + )} + {count > 0 && ( + { navigate('/recap'); setMenuAnchor(null); }} className="header-menu-item"> + + My selections ({count}) + + )} + {count > 0 && ( + { handleDownload(); setMenuAnchor(null); }} + disabled={isDownloading} + className="header-menu-item" + > + + {isDownloading + ? `Downloading… ${downloadState.current}/${downloadState.total}` + : `Download (${count})`} + + )} + { handleReset(); setMenuAnchor(null); }} className="header-menu-item header-menu-item--danger"> + + Reset diary + + +
+
+ + ); +}; + +export default Header; diff --git a/src/components/NavBar.js b/src/components/NavBar.js deleted file mode 100644 index 4c51c06..0000000 --- a/src/components/NavBar.js +++ /dev/null @@ -1,74 +0,0 @@ -import React, { useMemo } from "react"; -import axios from "axios"; -import { useSelector } from "react-redux"; -import { AppBar, Toolbar, IconButton, Badge, Button } from "@material-ui/core"; -import ShoppingCartIcon from "@material-ui/icons/ShoppingCart"; -import ArrowBackIcon from "@material-ui/icons/ArrowBack"; -import { useNavigate, useLocation } from "react-router-dom"; - -const apiUrl = process.env.REACT_APP_API_URL; - -const selectPosterSelections = (state) => state.posterSelections; - -const NavBar = () => { - const navigate = useNavigate(); - const location = useLocation(); - - const posterSelections = useSelector(selectPosterSelections); - const selectedPosters = useMemo(() => { - return Object.values(posterSelections).flat(); - }, [posterSelections]); - - const totalSelected = selectedPosters.length; - - const handleBack = () => { - navigate(-1); - }; - - const handleCart = () => { - navigate("/Cart"); - }; - - const handleResetprofile = async () => { - try { - await axios.delete(`${apiUrl}/api/delete-csv`, { withCredentials: true }); - navigate("/"); - } catch (error) { - console.error("Error deleting CSV file:", error); - } - }; - - const isPosterSelectorPage = location.pathname !== "/PosterSelector"; - - return ( - - - {isPosterSelectorPage ? ( - - - - ) : ( - - {/* Disabled IconButton */} - - )} -
- - - - - - -
-
-
- ); -}; - -export default NavBar; diff --git a/src/components/PosterGallery.js b/src/components/PosterGallery.js deleted file mode 100644 index c2f1169..0000000 --- a/src/components/PosterGallery.js +++ /dev/null @@ -1,167 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { useLocation, useParams } from "react-router-dom"; -import axios from "axios"; -import { useSelector, useDispatch } from "react-redux"; -import { selectPoster, deselectPoster } from "../services/action"; -import { - Container, - Typography, - Grid, - Card, - CardMedia, - CircularProgress, - Box, - Paper, - DialogContent, - DialogTitle, - DialogActions, - Button, -} from "@material-ui/core"; -import CheckIcon from "@material-ui/icons/Check"; -import NavBar from "./NavBar"; -import { Dialog } from "@mui/material"; - -const apiUrl = process.env.REACT_APP_API_URL; - -const PosterGallery = () => { - const location = useLocation(); - const { watchedDate } = location.state || {}; - const { movieName, movieYear } = useParams(); - const [posters, setPosters] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [dialogOpen, setDialogOpen] = useState(false); - const dispatch = useDispatch(); - - const movieId = `${movieName}-${movieYear}`; - - const selectedPosters = useSelector( - (state) => state.posterSelections[movieId] || [] - ); - - useEffect(() => { - const fetchPosters = async () => { - try { - setLoading(true); - setError(null); - - const encodedName = encodeURIComponent(movieName); - const encodedYear = encodeURIComponent(movieYear); - - const response = await axios.get( - `${apiUrl}/api/posters/${encodedName}/${encodedYear}` - ); - - if (response.data && response.data.posters) { - setPosters(response.data.posters); - } else { - throw new Error("No posters data in response"); - } - } catch (error) { - console.error("Error fetching posters:", error); - } finally { - setLoading(false); - } - }; - - if (movieName && movieYear) { - fetchPosters(); - } - }, [movieName, movieYear]); - - const isPosterSelected = (posterId) => { - return selectedPosters.some((poster) => poster.posterId === posterId); - }; - - const handlePosterSelect = (posterId) => { - const isSelected = isPosterSelected(posterId); - - if (selectedPosters.length > 0 && !isSelected) { - setDialogOpen(true); - return; - } - if (isSelected) { - dispatch(deselectPoster(movieName, movieYear, posterId)); - } else { - dispatch(selectPoster(movieName, movieYear, posterId, watchedDate)); - } - }; - - const handleDialogClose = () => { - setDialogOpen(false); - }; - - if (error) { - return ( - - - - {error} - - - - - ); - } - - return ( - <> - - - - Posters for {movieName} ({movieYear}) - - {loading ? ( - - - - ) : posters.length > 0 ? ( - - {posters.map((poster, index) => { - const isSelected = isPosterSelected(poster.file_path); - return ( - - handlePosterSelect(poster.file_path)} - > - - {isSelected && } - - - ); - })} - - ) : ( - - No posters found for this movie. - - )} - - - - - Poster already selected - - - You can only select one poster per movie. Please deselect the - current poster first. - - - - - - - - ); -}; - -export default PosterGallery; diff --git a/src/components/PosterSelector.js b/src/components/PosterSelector.js deleted file mode 100644 index c583e04..0000000 --- a/src/components/PosterSelector.js +++ /dev/null @@ -1,256 +0,0 @@ -import React, { useState, useEffect } from "react"; -import axios from "axios"; -import { useNavigate, useLocation } from "react-router-dom"; -import { - Container, - List, - ListItem, - Grid, - Typography, - LinearProgress, - Box, - Paper, - Button, -} from "@material-ui/core"; -import Pagination from "@mui/material/Pagination"; -import { createTheme, ThemeProvider } from "@mui/material/styles"; -import queryString from "query-string"; -import pulpGif from "../static/images/pulp.gif"; -import NavBar from "./NavBar"; - -const apiUrl = process.env.REACT_APP_API_URL; - -const paginationTheme = createTheme({ - palette: { - mode: "dark", - primary: { - main: "#667788", - }, - text: { - primary: "#ffffff", - secondary: "#667788", - }, - action: { - hover: "rgba(102, 119, 136, 0.2)", - }, - }, - components: { - MuiPaginationItem: { - styleOverrides: { - root: { - "&.Mui-selected": { - backgroundColor: "#667788", - color: "#ffffff", - "&:hover": { - backgroundColor: "#778899", - }, - }, - }, - }, - }, - }, -}); - -const PosterSelector = () => { - const [movies, setMovies] = useState([]); - const [loading, setLoading] = useState(true); - const [page, setPage] = useState(1); - const [totalPages, setTotalPages] = useState(1); - //const [progress, setProgress] = useState(0); - const [username, setUsername] = useState(""); - const navigate = useNavigate(); - const location = useLocation(); - const moviesPerPage = 8; - - useEffect(() => { - const parsed = queryString.parse(location.search); - const pageNumber = parsed.page ? parseInt(parsed.page, 10) : 1; - setPage(pageNumber); - - fetchMovies(pageNumber); - }, [location.search]); - - // const fetchUsername = async () => { - // try { - // const response = await axios.get(`${apiUrl}/api/username`); - // setUsername(response.data.username); - // } catch (error) { - // console.error("Error fetching username:", error); - // } - // }; - - const fetchMovies = async (pageNumber) => { - try { - setLoading(true); - // setProgress(0); - - const response = await axios.get( - `${apiUrl}/api/movies?page=${pageNumber}&limit=${moviesPerPage}`, - { withCredentials: true } - ); - - if (response.data.movies) { - setMovies(response.data.movies); - setTotalPages(Math.ceil(response.data.total / moviesPerPage)); - } - } catch (error) { - console.error("Error fetching movies:", error); - } finally { - setLoading(false); - } - }; - - const handleMovieClick = (movieName, movieYear, watchedDate) => { - navigate( - `/posters/${encodeURIComponent(movieName)}/${encodeURIComponent( - movieYear - )}`, - { state: { watchedDate } } - ); - }; - - const handlePageChange = (event, value) => { - setPage(value); - navigate(`?page=${value}`); - }; - - const handleGoBack = () => { - navigate("/"); - }; - - const formatWatchedDate = (dateString) => { - const date = new Date(dateString); - const day = date.getDate().toString().padStart(2, "0"); - const month = date - .toLocaleString("default", { month: "short" }) - .toUpperCase(); - return { day, month }; - }; - - return ( - <> - - - - Your diary - - - {username && ( - - Letterboxd User: {username} - - )} - - {loading ? ( - - - - Downloading movies... - - - ) : movies.length > 0 ? ( - <> - - {movies.map((movie, index) => ( - - handleMovieClick( - movie.Name, - movie.Year, - movie["Watched Date"] - ) - } - className="movie-item" - > - - - {movie.Name} - - - - {movie.Name} - - - {movie.Year} - - - - - {(() => { - const { day, month } = formatWatchedDate( - movie["Watched Date"] - ); - return ( - <> - - {day} - - - {month} - - - ); - })()} - - - - - ))} - - - - - - - - ) : ( - -
-
- Confused reaction -
- - No movies found in your diary. Please check your Letterboxd - username or CSV file. - - -
- )} -
-
- - - ); -}; - -export default PosterSelector; diff --git a/src/components/UploadDiary.js b/src/components/UploadDiary.js deleted file mode 100644 index 3073dc9..0000000 --- a/src/components/UploadDiary.js +++ /dev/null @@ -1,192 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { useNavigate } from "react-router-dom"; -import { - Container, - Grid, - Typography, - Button, - CircularProgress, - TextField, - Box, - Link, -} from "@material-ui/core"; -import { CloudUploadOutlined } from "@material-ui/icons"; -import CloudDoneOutlinedIcon from "@mui/icons-material/CloudDoneOutlined"; -import { useDropzone } from "react-dropzone"; -import axios from "axios"; - -const apiUrl = process.env.REACT_APP_API_URL; -const VERSION = require("../../package.json").version; - -const UploadDiary = () => { - const [file, setFile] = useState(null); - const [uploading, setUploading] = useState(false); - const [username, setUsername] = useState(""); - const navigate = useNavigate(); - - useEffect(() => { - const checkCSVFile = async () => { - try { - const response = await axios.get(`${apiUrl}/api/check-csv`, { - withCredentials: true, - }); - if (response.data.fileExists) { - navigate("/PosterSelector"); - } - } catch (error) { - console.error("Error checking CSV file:", error); - } - }; - checkCSVFile(); - }, [navigate]); - - const { getRootProps, getInputProps } = useDropzone({ - onDrop: (acceptedFiles) => { - setFile(acceptedFiles[0]); - }, - maxFiles: 1, - accept: ".csv", - }); - - const handleUpload = async () => { - if (username || file) { - setUploading(true); - try { - if (username) { - const response = await axios.post( - `${apiUrl}/api/fetch-diary`, - { username }, - { withCredentials: true } - ); - - if (response.data && response.data.success) { - await new Promise((resolve) => setTimeout(resolve, 500)); - navigate("/PosterSelector"); - } else { - console.error("Failed to fetch diary", response.data); - } - } else if (file) { - const formData = new FormData(); - formData.append("file", file); - - await axios.post(`${apiUrl}/api/upload-csv`, formData, { - headers: { "Content-Type": "multipart/form-data" }, - withCredentials: true, - }); - - navigate("/PosterSelector"); - } - } catch (error) { - console.error("Error processing diary:", error); - } finally { - setUploading(false); - } - } - }; - - return ( - - -
-
- logo -
- - Upload Letterboxd Diary - - setUsername(e.target.value)} - onKeyUp={(e) => { - if (e.key === "Enter") handleUpload(); - }} - placeholder="Enter your Letterboxd username" - /> - - Or upload a CSV file: - -
- - {file ? ( - <> - - - Your file has been uploaded: {file.name} - - - ) : ( - <> - - - Drag and drop a CSV file here or click to select - - - )} -
- - {uploading ? ( - - ) : ( - - )} - -
- - This tool allows you to easily import your Letterboxd diary and select - your favorite movie poster. Follow these simple steps: -
- 1. Enter Your Letterboxd Username: Type your - Letterboxd username to automatically fetch your diary. -
- 2. Or Upload Your CSV File: If you prefer, you can - still upload your diary.csv file directly. -
- 3. Automatic Processing: Once submitted, the - application will process your diary. -
- 4. Poster Selection: After processing, you'll be - redirected to the Poster Selector page, where you can browse and - choose your favorite movie poster based on your Letterboxd diary. -
-
- - - - v{VERSION} - - - • - - (e.currentTarget.style.opacity = 1)} - onMouseLeave={(e) => (e.currentTarget.style.opacity = 0.7)} - > - Hugyouu - - - -
- ); -}; - -export default UploadDiary; diff --git a/src/index.js b/src/index.js index d665ff2..4080a97 100644 --- a/src/index.js +++ b/src/index.js @@ -5,11 +5,7 @@ import App from "./App"; import reportWebVitals from "./reportWebVitals"; const root = ReactDOM.createRoot(document.getElementById("root")); -root.render( - - - -); +root.render(); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) diff --git a/src/pages/PosterGallery.js b/src/pages/PosterGallery.js new file mode 100644 index 0000000..16bac5c --- /dev/null +++ b/src/pages/PosterGallery.js @@ -0,0 +1,150 @@ +import React, { useState, useEffect } from "react"; +import { useLocation, useParams, useNavigate } from "react-router-dom"; +import api from "../services/api"; +import { useSelector, useDispatch } from "react-redux"; +import { selectPoster, deselectPoster } from "../services/action"; +import { + Container, + Typography, + Grid, + Card, + Box, + Paper, +} from "@material-ui/core"; +import { ToggleButton, ToggleButtonGroup } from "@mui/material"; +import CheckIcon from "@material-ui/icons/Check"; + +const LANG_FILTERS = [ + { value: "all", label: "All" }, + { value: "en", label: "EN" }, + { value: "fr", label: "FR" }, + { value: "ja", label: "JA" }, + { value: "none", label: "—" }, +]; + +const PosterCard = ({ poster, movieName, isSelected, onClick }) => { + const [imgLoaded, setImgLoaded] = useState(false); + + return ( + +
+
+ {movieName} setImgLoaded(true)} + /> +
+ {isSelected && } + + ); +}; + +const PosterGallery = () => { + const location = useLocation(); + const { watchedDate } = location.state || {}; + const { movieName, movieYear } = useParams(); + const navigate = useNavigate(); + const [posters, setPosters] = useState([]); + const [loading, setLoading] = useState(true); + const [langFilter, setLangFilter] = useState("all"); + const dispatch = useDispatch(); + + const movieId = `${movieName}-${movieYear}`; + const selectedPosters = useSelector( + (state) => state.posterSelections[movieId] || [] + ); + + useEffect(() => { + const fetchPosters = async () => { + try { + setLoading(true); + const response = await api.get( + `/api/posters/${encodeURIComponent(movieName)}/${encodeURIComponent(movieYear)}` + ); + if (response.data?.posters) setPosters(response.data.posters); + } catch (error) { + console.error("Error fetching posters:", error); + } finally { + setLoading(false); + } + }; + if (movieName && movieYear) fetchPosters(); + }, [movieName, movieYear]); + + const filteredPosters = posters.filter((p) => { + if (langFilter === "all") return true; + if (langFilter === "none") return !p.iso_639_1; + return p.iso_639_1 === langFilter; + }); + + const isPosterSelected = (posterId) => + selectedPosters.some((p) => p.posterId === posterId); + + const handlePosterSelect = (posterId) => { + if (isPosterSelected(posterId)) { + dispatch(deselectPoster(movieName, movieYear, posterId)); + } else { + dispatch(selectPoster(movieName, movieYear, posterId, watchedDate)); + navigate(-1); + } + }; + + const visiblePosters = loading + ? Array.from({ length: 8 }).map((_, i) => ({ _skeleton: true, file_path: `sk-${i}` })) + : filteredPosters; + + return ( + + + + v && setLangFilter(v)} + className="lang-toggle-group" + > + {LANG_FILTERS.map(({ value, label }) => ( + + {label} + + ))} + + + + {!loading && filteredPosters.length === 0 ? ( + + No posters for this language. + + ) : ( + + {visiblePosters.map((poster, index) => + poster._skeleton ? ( + +
+
+
+ + ) : ( + + handlePosterSelect(poster.file_path)} + /> + + ) + )} + + )} + + + ); +}; + +export default PosterGallery; diff --git a/src/pages/PosterSelector.js b/src/pages/PosterSelector.js new file mode 100644 index 0000000..04df6a1 --- /dev/null +++ b/src/pages/PosterSelector.js @@ -0,0 +1,388 @@ +import React, { useState, useEffect, useRef, useCallback } from "react"; +import api from "../services/api"; +import { useNavigate, useSearchParams } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { + Container, + Typography, + LinearProgress, + Box, + Paper, + Button, + IconButton, + Select, + MenuItem, + Popover, +} from "@material-ui/core"; +// LinearProgress kept for the initializing state only +import ChevronLeftIcon from "@material-ui/icons/ChevronLeft"; +import ChevronRightIcon from "@material-ui/icons/ChevronRight"; +import CheckIcon from "@material-ui/icons/Check"; +import pulpGif from "../static/images/pulp.gif"; + +const CURRENT_YEAR = new Date().getFullYear(); +const YEARS = Array.from({ length: CURRENT_YEAR - 2000 + 1}, (_, i) => 2000 + i).reverse(); + +const DAY_LABELS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + +const MONTH_NAMES = [ + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", +]; + +const PosterSelector = () => { + const [currentMonth, setCurrentMonth] = useState(null); + const [moviesByDate, setMoviesByDate] = useState({}); + const [loading, setLoading] = useState(true); + const [initializing, setInitializing] = useState(true); + const [popoverAnchor, setPopoverAnchor] = useState(null); + const [popoverMovies, setPopoverMovies] = useState([]); + const navigate = useNavigate(); + const [searchParams, setSearchParams] = useSearchParams(); + const posterSelections = useSelector((state) => state.posterSelections); + const dataSource = useSelector((state) => state.dataSource); + + useEffect(() => { + const init = async () => { + try { + const csvCheck = await api.get("/api/check-csv"); + if (!csvCheck.data.fileExists) { + navigate("/"); + return; + } + + const urlMonth = searchParams.get("month"); + if (urlMonth) { + const [year, month] = urlMonth.split("-").map(Number); + setCurrentMonth({ year, month: month - 1 }); + setInitializing(false); + return; + } + + const res = await api.get("/api/movies?page=1&limit=1"); + if (res.data.movies?.length > 0) { + const latestDate = res.data.movies[0]["Watched Date"]; + const [year, month] = latestDate.split("-").map(Number); + setCurrentMonth({ year, month: month - 1 }); + } else { + const now = new Date(); + setCurrentMonth({ year: now.getFullYear(), month: now.getMonth() }); + } + } catch { + navigate("/"); + } finally { + setInitializing(false); + } + }; + init(); + }, [navigate, searchParams]); + + useEffect(() => { + if (!currentMonth) return; + fetchMoviesForMonth(currentMonth.year, currentMonth.month); + }, [currentMonth]); + + const fetchMoviesForMonth = useCallback(async (year, month) => { + setLoading(true); + try { + const monthStr = `${year}-${String(month + 1).padStart(2, "0")}`; + const res = await api.get(`/api/movies?month=${monthStr}`); + const map = {}; + res.data.movies?.forEach((movie) => { + const date = movie["Watched Date"]; + if (!map[date]) map[date] = []; + map[date].push(movie); + }); + setMoviesByDate(map); + } catch (err) { + console.error("Error fetching movies:", err); + } finally { + setLoading(false); + } + }, []); + + const currentMonthRef = useRef(currentMonth); + useEffect(() => { currentMonthRef.current = currentMonth; }, [currentMonth]); + + useEffect(() => { + const handleSync = () => { + const m = currentMonthRef.current; + if (m) fetchMoviesForMonth(m.year, m.month); + }; + window.addEventListener('diary-synced', handleSync); + return () => window.removeEventListener('diary-synced', handleSync); + }, [fetchMoviesForMonth]); + + const updateMonth = (year, month) => { + setCurrentMonth({ year, month }); + setSearchParams({ month: `${year}-${String(month + 1).padStart(2, "0")}` }); + }; + + const goToPrevMonth = () => { + const { year, month } = currentMonth; + if (month === 0) updateMonth(year - 1, 11); + else updateMonth(year, month - 1); + }; + + const goToNextMonth = () => { + const { year, month } = currentMonth; + if (month === 11) updateMonth(year + 1, 0); + else updateMonth(year, month + 1); + }; + + const goToToday = () => { + const now = new Date(); + updateMonth(now.getFullYear(), now.getMonth()); + }; + + const handleGoToUpload = async () => { + try { + await api.delete("/api/delete-csv"); + } catch { + // server may have restarted, proceed anyway + } + navigate("/"); + }; + + const handleMovieClick = (movie) => { + navigate( + `/posters/${encodeURIComponent(movie.Name)}/${encodeURIComponent(movie.Year)}`, + { state: { watchedDate: movie["Watched Date"] } } + ); + }; + + const handleCellClick = (e, movies) => { + if (!movies.length) return; + if (movies.length === 1) { + handleMovieClick(movies[0]); + } else { + setPopoverMovies(movies); + setPopoverAnchor(e.currentTarget); + } + }; + + const buildCalendarCells = () => { + if (!currentMonth) return []; + const { year, month } = currentMonth; + const firstDayOfWeek = new Date(year, month, 1).getDay(); + const startOffset = (firstDayOfWeek + 6) % 7; // Mon=0 + const daysInMonth = new Date(year, month + 1, 0).getDate(); + + const cells = []; + for (let i = 0; i < startOffset; i++) cells.push(null); + for (let d = 1; d <= daysInMonth; d++) cells.push(d); + return cells; + }; + + if (initializing) { + return ( + <> + + + + + + + + + ); + } + + const cells = buildCalendarCells(); + const hasAnyMovie = Object.keys(moviesByDate).length > 0; + const today = new Date(); + const isCurrentMonth = + currentMonth.year === today.getFullYear() && + currentMonth.month === today.getMonth(); + const isFutureMonth = + currentMonth.year > today.getFullYear() || + (currentMonth.year === today.getFullYear() && currentMonth.month > today.getMonth()); + + return ( + <> + + + + + + + + + + {currentMonth + ? `${MONTH_NAMES[currentMonth.month]} ${currentMonth.year}` + : ""} + + + + + + + + + + + + {dataSource === 'rss' && ( + + + RSS sync — showing your last ~50 films only.{' '} + + Upload a CSV + {' '} + for your full history. + + )} + +
+
+ {DAY_LABELS.map((label) => ( +
{label}
+ ))} + {cells.map((day, i) => { + if (!day) { + return
; + } + + const { year, month } = currentMonth; + const dateStr = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`; + const isFuture = isFutureMonth || (isCurrentMonth && day > today.getDate()); + const isToday = isCurrentMonth && day === today.getDate(); + const movies = isFuture ? [] : (moviesByDate[dateStr] || []); + const movie = movies[0]; + + const movieId = movie ? `${movie.Name}-${movie.Year}` : null; + const selection = movieId ? posterSelections[movieId]?.[0] : null; + const posterSrc = selection + ? `https://image.tmdb.org/t/p/w200${selection.posterId}` + : movie?.Poster + ? `https://image.tmdb.org/t/p/w200${movie.Poster}` + : null; + + return ( +
!loading && movies.length > 0 && handleCellClick(e, movies)} + > + {day} + {loading && !isFuture && ( +
+
+
+ )} + {!loading && movie && posterSrc && ( +
+ {movie.Name} +
+ {movie.Name} + {movie.Year} +
+ {selection && ( + + + + )} + {movies.length > 1 && ( + +{movies.length - 1} + )} +
+ )} +
+ ); + })} +
+ + {!loading && !hasAnyMovie && !isFutureMonth && ( + +
+
+ No movies +
+ + No movies watched this month. + + +
+ )} +
+ + + + setPopoverAnchor(null)} + anchorOrigin={{ vertical: "bottom", horizontal: "center" }} + transformOrigin={{ vertical: "top", horizontal: "center" }} + PaperProps={{ className: "multi-movie-popover" }} + > + {popoverMovies.map((movie) => { + const movieId = `${movie.Name}-${movie.Year}`; + const selection = posterSelections[movieId]?.[0]; + const posterSrc = selection + ? `https://image.tmdb.org/t/p/w200${selection.posterId}` + : movie.Poster + ? `https://image.tmdb.org/t/p/w200${movie.Poster}` + : null; + return ( +
{ handleMovieClick(movie); setPopoverAnchor(null); }} + > + {posterSrc ? ( + {movie.Name} + ) : ( +
+ )} +
+ {movie.Name} + {movie.Year} +
+ {selection && } +
+ ); + })} + + + ); +}; + +export default PosterSelector; diff --git a/src/pages/SelectionRecap.js b/src/pages/SelectionRecap.js new file mode 100644 index 0000000..40e85ad --- /dev/null +++ b/src/pages/SelectionRecap.js @@ -0,0 +1,173 @@ +import React, { useState } from "react"; +import { useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { + Container, + Typography, + Box, + Button, + IconButton, + LinearProgress, +} from "@material-ui/core"; +import GetAppIcon from "@material-ui/icons/GetApp"; +import ArrowBackIcon from "@material-ui/icons/ArrowBack"; +import CheckIcon from "@material-ui/icons/Check"; +import JSZip from "jszip"; + +const MONTH_NAMES = [ + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", +]; + +const SelectionRecap = () => { + const navigate = useNavigate(); + const posterSelections = useSelector((state) => state.posterSelections); + const [downloadState, setDownloadState] = useState(null); + + const entries = Object.entries(posterSelections) + .flatMap(([movieId, posters]) => { + if (!posters?.length) return []; + const match = movieId.match(/^(.+)-(\d{4})$/); + if (!match) return []; + return posters.map((p) => ({ + movieId, + movieName: match[1], + movieYear: match[2], + posterId: p.posterId, + watchedDate: p.watchedDate || "", + })); + }) + .sort((a, b) => a.watchedDate.localeCompare(b.watchedDate)); + + const groups = entries.reduce((acc, entry) => { + const key = entry.watchedDate.slice(0, 7); + if (!acc[key]) acc[key] = []; + acc[key].push(entry); + return acc; + }, {}); + + const sortedMonths = Object.keys(groups).sort().reverse(); + + const handleDownload = async () => { + if (downloadState) return; + setDownloadState({ current: 0, total: entries.length }); + try { + const zip = new JSZip(); + for (let i = 0; i < entries.length; i++) { + const { movieId, posterId, watchedDate } = entries[i]; + const res = await fetch(`https://image.tmdb.org/t/p/original${posterId}`); + const blob = await res.blob(); + zip.file(`${watchedDate}_${movieId}.jpg`, blob); + setDownloadState({ current: i + 1, total: entries.length }); + } + const content = await zip.generateAsync({ type: "blob" }); + const url = URL.createObjectURL(content); + const link = document.createElement("a"); + link.href = url; + link.download = "posters.zip"; + document.body.appendChild(link); + link.click(); + link.remove(); + URL.revokeObjectURL(url); + } catch (err) { + console.error("Download error:", err); + } finally { + setDownloadState(null); + } + }; + + if (entries.length === 0) { + return ( + + + No posters selected yet. + + + + ); + } + + const isDownloading = !!downloadState; + const progress = isDownloading + ? Math.round((downloadState.current / downloadState.total) * 100) + : 0; + + return ( + + + navigate(-1)}> + + + + Selected Posters ({entries.length}) + + + + + {sortedMonths.map((monthKey) => { + const [year, month] = monthKey.split("-").map(Number); + const monthLabel = `${MONTH_NAMES[month - 1]} ${year}`; + return ( + + {monthLabel} +
+ {groups[monthKey].map((entry) => ( +
+ navigate( + `/posters/${encodeURIComponent(entry.movieName)}/${encodeURIComponent(entry.movieYear)}`, + { state: { watchedDate: entry.watchedDate } } + ) + } + > +
+ {entry.movieName} +
+ {entry.movieName} + {entry.movieYear} +
+ + + +
+
+ ))} +
+
+ ); + })} +
+ ); +}; + +export default SelectionRecap; diff --git a/src/pages/UploadDiary.js b/src/pages/UploadDiary.js new file mode 100644 index 0000000..d5b1bfb --- /dev/null +++ b/src/pages/UploadDiary.js @@ -0,0 +1,236 @@ +import React, { useState, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { useDispatch, useSelector } from "react-redux"; +import { + Container, + Grid, + Typography, + CircularProgress, + Box, + Link, + TextField, + InputAdornment, +} from "@material-ui/core"; +import { Button } from "@mui/material"; +import { CloudUploadOutlined } from "@material-ui/icons"; +import CloudDoneOutlinedIcon from "@mui/icons-material/CloudDoneOutlined"; +import SyncIcon from "@material-ui/icons/Sync"; +import { useDropzone } from "react-dropzone"; +import api from "../services/api"; +import { setUsername, setDataSource } from "../services/action"; +const VERSION = require("../../package.json").version; + +const UploadDiary = () => { + const [file, setFile] = useState(null); + const [uploading, setUploading] = useState(false); + const [usernameInput, setUsernameInput] = useState(""); + const [syncing, setSyncing] = useState(false); + const [syncError, setSyncError] = useState(null); + const navigate = useNavigate(); + const dispatch = useDispatch(); + const storedUsername = useSelector((state) => state.username); + + useEffect(() => { + if (storedUsername) setUsernameInput(storedUsername); + }, [storedUsername]); + + useEffect(() => { + const checkCSVFile = async () => { + try { + const response = await api.get("/api/check-csv"); + if (response.data.fileExists) { + navigate("/PosterSelector"); + } + } catch (error) { + console.error("Error checking CSV file:", error); + } + }; + checkCSVFile(); + }, [navigate]); + + const { getRootProps, getInputProps } = useDropzone({ + onDrop: (acceptedFiles) => { + setFile(acceptedFiles[0]); + }, + maxFiles: 1, + accept: ".csv", + }); + + const handleUpload = async () => { + if (!file) return; + setUploading(true); + try { + const formData = new FormData(); + formData.append("file", file); + await api.post("/api/upload-csv", formData, { + headers: { "Content-Type": "multipart/form-data" }, + }); + dispatch(setDataSource('csv')); + navigate("/PosterSelector"); + } catch (error) { + console.error("Error processing diary:", error); + } finally { + setUploading(false); + } + }; + + const handleRssSync = async () => { + const username = usernameInput.trim(); + if (!username) return; + setSyncing(true); + setSyncError(null); + try { + const res = await api.post("/api/sync-rss", { username }); + dispatch(setUsername(username)); + if (res.data.fresh) dispatch(setDataSource('rss')); + navigate("/PosterSelector"); + } catch (err) { + const msg = err.response?.data?.error || "Could not fetch RSS feed."; + setSyncError(msg); + } finally { + setSyncing(false); + } + }; + + return ( + + +
+
+
+ logo +
+ + {/* CSV upload */} + + Upload Letterboxd Diary + +
+ + {file ? ( + <> + + + Your file has been uploaded: {file.name} + + + ) : ( + <> + + + Drag and drop a CSV file here or click to select + + + )} +
+ + {uploading ? ( + + ) : ( + + )} + + + {/* Divider */} +
+ + or + +
+ + {/* RSS sync */} + + Sync from Letterboxd RSS + +
+ { setUsernameInput(e.target.value); setSyncError(null); }} + onKeyDown={(e) => e.key === "Enter" && handleRssSync()} + InputProps={{ + startAdornment: ( + + letterboxd.com/ + + ), + }} + /> + +
+ {syncError && ( + {syncError} + )} + + Syncs your ~50 most recent diary entries via the public RSS feed. + Use CSV export for full history. + +
+
+ + + This tool allows you to easily import your Letterboxd diary and select + your favorite movie poster. Follow these simple steps: +
+ 1. Export your diary:{" "} + + Export your Letterboxd data + {" "} + and extract the diary.csv file from the ZIP. +
+ 2. Upload the CSV: Drop your diary.csv{" "} + file above or click to select it. +
+ 3. Poster Selection: After processing, you'll be + redirected to the calendar where you can browse and choose your + favorite poster for each movie. +
+
+ + + + v{VERSION} + + + • + + (e.currentTarget.style.opacity = 1)} + onMouseLeave={(e) => (e.currentTarget.style.opacity = 0.7)} + > + Hugyouu + + + +
+ ); +}; + +export default UploadDiary; diff --git a/src/services/action.js b/src/services/action.js index df1181e..e1d8d98 100644 --- a/src/services/action.js +++ b/src/services/action.js @@ -1,3 +1,10 @@ +export const SET_USERNAME = "SET_USERNAME"; +export const setUsername = (username) => ({ type: SET_USERNAME, payload: username }); + +export const SET_DATA_SOURCE = "SET_DATA_SOURCE"; +// source: 'csv' | 'rss' +export const setDataSource = (source) => ({ type: SET_DATA_SOURCE, payload: source }); + export const SELECT_POSTER = "SELECT_POSTER"; export const DESELECT_POSTER = "DESELECT_POSTER"; export const REMOVE_POSTER = "REMOVE_POSTER"; diff --git a/src/services/api.js b/src/services/api.js index b053a9d..f8261f3 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -1,7 +1,22 @@ import axios from "axios"; -const API_BASE_URL = "http://localhost:5000/api"; +export const getUserId = () => { + let id = localStorage.getItem("poster_picker_uid"); + if (!id) { + id = crypto.randomUUID(); + localStorage.setItem("poster_picker_uid", id); + } + return id; +}; -export const fetchMovies = () => axios.get(`${API_BASE_URL}/movies`); -export const downloadPoster = (movieId) => - axios.post(`${API_BASE_URL}/download-poster`, { movie_id: movieId }); +const api = axios.create({ + baseURL: process.env.REACT_APP_API_URL, + withCredentials: true, +}); + +api.interceptors.request.use((config) => { + config.headers["X-User-ID"] = getUserId(); + return config; +}); + +export default api; diff --git a/src/services/store.js b/src/services/store.js index 210f953..86f4c6f 100644 --- a/src/services/store.js +++ b/src/services/store.js @@ -1,7 +1,7 @@ import { legacy_createStore as createStore, combineReducers } from "redux"; import { persistStore, persistReducer } from "redux-persist"; import storage from "redux-persist/lib/storage"; -import {DESELECT_POSTER, REMOVE_ALL_POSTERS, REMOVE_POSTER, SELECT_POSTER} from "./action"; +import { DESELECT_POSTER, REMOVE_ALL_POSTERS, REMOVE_POSTER, SELECT_POSTER, SET_USERNAME, SET_DATA_SOURCE } from "./action"; const posterSelectionReducer = (state = {}, action) => { switch (action.type) { @@ -9,7 +9,7 @@ const posterSelectionReducer = (state = {}, action) => { const { movieId, posterId, watchedDate } = action.payload; return { ...state, - [movieId]: [...(state[movieId] || []), { posterId, watchedDate }], + [movieId]: [{ posterId, watchedDate }], }; } case DESELECT_POSTER: { @@ -38,9 +38,22 @@ const posterSelectionReducer = (state = {}, action) => { } }; +const usernameReducer = (state = "", action) => { + switch (action.type) { + case SET_USERNAME: return action.payload; + default: return state; + } +}; + +const dataSourceReducer = (state = null, action) => { + if (action.type === SET_DATA_SOURCE) return action.payload; + return state; +}; + const rootReducer = combineReducers({ posterSelections: posterSelectionReducer, - // other reducers... + username: usernameReducer, + dataSource: dataSourceReducer, }); const persistConfig = { diff --git a/src/styles/App.scss b/src/styles/App.scss index 632a0ff..11258aa 100644 --- a/src/styles/App.scss +++ b/src/styles/App.scss @@ -1,6 +1,7 @@ @use "./scss/uploadDiary"; @use "./scss/posterSelector"; @use "./scss/posterGallery"; +@use "./scss/selectionRecap"; @use "./scss/cart"; @use "./scss/navbar"; diff --git a/src/styles/scss/_navbar.scss b/src/styles/scss/_navbar.scss index 74095ac..fa847ee 100644 --- a/src/styles/scss/_navbar.scss +++ b/src/styles/scss/_navbar.scss @@ -1,32 +1,206 @@ -// _navbar.scss +@use "sass:color"; -// Variables de couleur et autres constantes $background-color: #1c1f23; -$icon-color: white; -$margin-right: 8px; // Correspondant à theme.spacing(2) +$primary-color: #00a346; +$text-white: #ffffff; +$text-gray: #667788; -.navbar { - top: auto !important; - bottom: 0; +.app-header { background-color: $background-color !important; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.08) !important; - .toolbar { + .header-toolbar { display: flex; justify-content: space-between; + align-items: center; + padding: 0 1.5rem; } - .back-button, - .cart-button, - .refresh-button { - color: $icon-color; + .header-left { + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + flex-shrink: 0; + + &:hover .header-title { + color: $primary-color; + } } - .refresh-button { - margin-right: $margin-right; + .header-logo { + width: 28px; + height: 28px; + border-radius: 50%; + object-fit: cover; + flex-shrink: 0; } - .back-button[disabled] { - opacity: 0.3; - pointer-events: none; + .header-title { + color: $text-white; + font-weight: 700; + letter-spacing: 0.02em; + transition: color 0.15s ease; + } + + .header-center { + position: absolute; + left: 50%; + transform: translateX(-50%); + max-width: 40%; + } + + .header-movie-name { + color: $text-white; + font-weight: 400; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .header-username { + color: $text-gray; + font-size: 0.85rem !important; + letter-spacing: 0.03em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + &::before { + content: '@'; + opacity: 0.6; + } + } + + .header-actions { + display: flex; + align-items: center; + gap: 0.5rem; + } + + .header-menu-btn { + display: none !important; + color: $text-gray !important; + &:hover { color: $text-white !important; } + } + + @media (max-width: 640px) { + .header-actions { display: none !important; } + .header-menu-btn { display: flex !important; } + .header-title { font-size: 1rem !important; } + .header-center { display: none; } + } + + .header-reset-btn { + color: $text-gray !important; + + &:hover { + color: $text-white !important; + } + } + + .header-recap-btn { + color: $text-gray !important; + + &:hover { + color: $text-white !important; + } + } + + .header-sync-btn { + color: $text-gray !important; + + &:hover { color: $text-white !important; } + + &--active { color: $primary-color !important; } + } + + .spin { + animation: spin 1s linear infinite; + } + + @keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } + } + + .header-download-btn { + background-color: $primary-color !important; + color: $text-white !important; + font-weight: 600; + padding: 0.4rem 1rem; + border-radius: 4px; + text-transform: none; + font-size: 0.875rem; + + &:hover { + background-color: color.adjust($primary-color, $lightness: -8%) !important; + } + + &--loading { + background-color: color.adjust($primary-color, $lightness: -12%) !important; + cursor: default !important; + min-width: 140px; + } + } + + .header-download-progress { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + width: 120px; + + span { + font-size: 0.8rem; + font-weight: 600; + color: $text-white; + line-height: 1; + } + } + + .header-progress-bar { + width: 100%; + border-radius: 2px; + height: 4px !important; + + .MuiLinearProgress-bar { + background-color: $text-white !important; + } + + &.MuiLinearProgress-root { + background-color: rgba(255, 255, 255, 0.3) !important; + } } } + +// Menu mobile (portail MUI, hors de .app-header) +.header-menu-paper { + background-color: #1c1f23 !important; + border: 1px solid #2c3038 !important; + border-radius: 6px !important; + min-width: 200px; + + .MuiList-root { padding: 4px 0; } +} + +.header-menu-item { + color: #ffffff !important; + font-size: 0.875rem !important; + gap: 10px; + padding: 10px 16px !important; + + &:hover { background-color: rgba(102, 119, 136, 0.2) !important; } + &.Mui-disabled { opacity: 0.4 !important; color: #ffffff !important; } + + &--danger { + color: #ef9a9a !important; + margin-top: 4px; + border-top: 1px solid #2c3038; + } +} + +.header-menu-icon { + opacity: 0.6; + flex-shrink: 0; +} diff --git a/src/styles/scss/_posterGallery.scss b/src/styles/scss/_posterGallery.scss index cb8cb1f..73a5652 100644 --- a/src/styles/scss/_posterGallery.scss +++ b/src/styles/scss/_posterGallery.scss @@ -6,7 +6,7 @@ $hover-scale: 1.05; $loading-height: 50vh; .poster-gallery { - margin-top: 2rem; + margin-top: 5rem; margin-bottom: 2rem; .content-paper { @@ -15,6 +15,49 @@ $loading-height: 50vh; background-color: $background-dark; } + .lang-filter-bar { + display: flex; + justify-content: flex-end; + margin-bottom: 1.25rem; + } + + .lang-toggle-group { + border: 1px solid #2c3038 !important; + border-radius: 4px !important; + overflow: hidden; + } + + .lang-toggle-btn { + color: #667788 !important; + border: none !important; + border-left: 1px solid #2c3038 !important; + padding: 4px 14px !important; + font-size: 0.75rem !important; + font-weight: 600 !important; + text-transform: none !important; + min-width: unset !important; + background-color: transparent !important; + + &:first-child { + border-left: none !important; + } + + &:hover { + color: #ffffff !important; + background-color: rgba(102, 119, 136, 0.15) !important; + } + + &.Mui-selected { + color: #ffffff !important; + background-color: #667788 !important; + } + } + + .no-posters-msg { + color: #667788; + padding: 3rem 0; + } + .title { color: $text-white; margin-bottom: 1.5rem; @@ -24,30 +67,19 @@ $loading-height: 50vh; .poster-card { position: relative; - height: 100%; - display: flex; - flex-direction: column; background-color: transparent; box-shadow: none; cursor: pointer; - transition: all 0.3s ease; + transition: transform 0.3s ease; + border-radius: 4px; + overflow: hidden; &:hover { transform: scale($hover-scale); } &.selected-poster { - border: 4px solid $primary-color; - border-radius: 4px; - } - - .poster-image { - padding-top: 150%; - background-size: contain; - background-position: center; - background-repeat: no-repeat; - border-radius: 4px; - transition: transform 0.3s ease-in-out; + outline: 4px solid $primary-color; } .check-icon { @@ -61,6 +93,46 @@ $loading-height: 50vh; } } + .poster-aspect-wrapper { + position: relative; + padding-top: 150%; + border-radius: 4px; + overflow: hidden; + } + + .poster-skeleton { + position: absolute; + inset: 0; + background: linear-gradient(90deg, #1c1f23 25%, #2a2e33 50%, #1c1f23 75%); + background-size: 200% 100%; + animation: skeleton-shimmer 1.4s infinite; + transition: opacity 0.3s ease; + + &--hidden { + opacity: 0; + pointer-events: none; + } + } + + .poster-image-img { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + opacity: 0; + transition: opacity 0.4s ease; + + &--loaded { + opacity: 1; + } + } + + @keyframes skeleton-shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } + } + .loading-container { display: flex; justify-content: center; diff --git a/src/styles/scss/_posterSelector.scss b/src/styles/scss/_posterSelector.scss index 4d5101f..a055f94 100644 --- a/src/styles/scss/_posterSelector.scss +++ b/src/styles/scss/_posterSelector.scss @@ -1,134 +1,314 @@ @use "sass:color"; -// Variables $primary-color: #00a346; $secondary-color: #667788; $background-dark: #1c1f23; $text-white: #ffffff; $text-gray: #667788; -$border-color: #667788; +$border-color: #2c3038; $hover-color: rgba(102, 119, 136, 0.2); .poster-selector { - margin-top: 2rem; + margin-top: 5rem; margin-bottom: 2rem; .content-paper { padding: 1.5rem; border-radius: 4px; - background-color: $background-dark !important; + background-color: $background-dark; + max-width: calc((120vh - 340px) * 7 / 9 + 3rem); + margin: 0 auto; } .title { - margin-bottom: 1.5rem; + margin-bottom: 1rem; color: $text-white; } - .username { - color: $primary-color; - margin-bottom: 1rem; - } - - .movies-list { - max-height: 800px; - min-height: 900px; - padding: 0; - } - - .movie-item { - padding: 0 2rem; - margin-bottom: 1rem; - transition: background-color 0.3s ease; + // Calendar navigation + .calendar-nav { display: flex; align-items: center; justify-content: space-between; - flex-wrap: nowrap; + margin-bottom: 1.5rem; + } + + .calendar-nav-center { + display: flex; + align-items: center; + gap: 0.25rem; + } + + .calendar-nav-btn { + color: $text-white !important; &:hover { - background-color: $hover-color; - } - - &:not(:last-child) { - border-bottom: 1px solid $border-color; - padding-top: 0.25rem; - padding-bottom: 0.25rem; - } - - // Override MUI ListItem styles - &.MuiListItem-root { - padding: 0.5rem !important; - } - - .MuiGrid-container { - display: flex; - justify-content: space-between; - width: 100%; - } - - .movie-poster { - width: 50px; - height: 75px; - object-fit: cover; - margin-right: 1rem; - border-radius: 4px; - } - - .movie-info { - flex-grow: 1; - max-width: calc(100% - 110px); - } - - .movie-title { - color: $text-white; - font-family: "TiemposTextWeb-Semibold, Georgia, serif"; - font-size: 1.38461538rem; - font-weight: 400; - transition: color 0.3s ease; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - - &:hover { - color: $primary-color; - } - } - - .movie-year { - color: $text-gray; - } - - .watched-date { - color: $text-gray; - } - - .watched-day { - font-size: 2rem; - line-height: 1; - margin-bottom: 0.25rem; - } - - .watched-month { - font-size: 0.875rem; + background-color: $hover-color !important; } } - .pagination-container { + .calendar-month-label { + color: $text-white; + min-width: 160px; + text-align: center; + } + + .calendar-today-btn { + color: $text-gray !important; + font-size: 0.75rem !important; + text-transform: none !important; + padding: 2px 8px !important; + border: 1px solid $text-gray !important; + border-radius: 4px !important; + min-width: unset !important; + + &:hover { + color: $text-white !important; + border-color: $text-white !important; + } + } + + .calendar-month-selects { display: flex; - justify-content: center; - margin-top: 1.5rem; + align-items: center; + gap: 4px; + border: 1px solid $border-color; + border-radius: 4px; + padding: 0 4px; - // Override MUI Pagination styles - .MuiPagination-root { - .MuiPaginationItem-root { + &:hover { + border-color: $text-gray; + } + } + + .calendar-select { + color: $text-gray !important; + font-size: 0.8rem !important; + + .MuiSelect-root { + padding: 4px 24px 4px 6px !important; + } + + .MuiSelect-icon { + color: $text-gray; + } + + &:hover { + color: $text-white !important; + } + } + + .calendar-select-menu { + background-color: #1c1f23 !important; + color: $text-white !important; + border: 1px solid $border-color; + + .MuiMenuItem-root { + font-size: 0.85rem; + color: $text-gray; + + &:hover, &.Mui-selected { + background-color: rgba(102, 119, 136, 0.2) !important; color: $text-white; - - &.Mui-selected { - background-color: $secondary-color; - } } } } + .rss-notice { + display: flex; + align-items: center; + gap: 6px; + font-size: 0.75rem; + color: $text-gray; + background-color: rgba(102, 119, 136, 0.08); + border: 1px solid rgba(102, 119, 136, 0.2); + border-radius: 4px; + padding: 6px 10px; + margin-bottom: 1rem; + } + + .rss-notice-icon { + font-size: 0.8rem; + flex-shrink: 0; + } + + .rss-notice-link { + color: $primary-color; + cursor: pointer; + text-decoration: underline; + + &:hover { color: color.scale($primary-color, $lightness: 15%); } + } + + // Calendar grid + .calendar-grid-wrapper { + position: relative; + } + + .calendar-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 6px; + max-width: calc((120vh - 340px) * 7 / 9); + min-width: 280px; + margin: 0 auto; + } + + .calendar-day-header { + color: $text-gray; + font-size: 0.75rem; + font-weight: 600; + text-align: center; + text-transform: uppercase; + padding: 0.25rem 0; + letter-spacing: 0.05em; + } + + .calendar-cell { + position: relative; + border-radius: 4px; + border: 1px solid $border-color; + overflow: hidden; + background-color: #14181c; + aspect-ratio: 2/3; + min-height: 14vh; + + &--empty { + border-color: transparent; + background-color: transparent; + } + + &--future { + background-color: rgba(255, 255, 255, 0.02); + border-color: rgba(255, 255, 255, 0.04); + + .calendar-day-number { + opacity: 0.25; + } + } + + &--today { + border-color: $secondary-color; + + .calendar-day-number { + color: $text-white; + font-weight: 700; + } + } + + &--has-movie { + cursor: pointer; + border-color: transparent; + + &:hover .calendar-poster-overlay { + opacity: 1; + } + + &:hover .calendar-day-number { + color: $text-white; + text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8); + } + } + } + + .calendar-day-number { + position: absolute; + top: 4px; + left: 6px; + font-size: 0.7rem; + color: $text-gray; + z-index: 2; + line-height: 1; + } + + .calendar-poster-wrapper { + position: absolute; + inset: 0; + } + + .calendar-cell-skeleton { + position: absolute; + inset: 0; + background: linear-gradient(90deg, #1c1f23 25%, #252930 50%, #1c1f23 75%); + background-size: 200% 100%; + animation: calendar-shimmer 1.4s infinite; + } + + @keyframes calendar-shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } + } + + .calendar-poster { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } + + .calendar-poster-overlay { + position: absolute; + inset: 0; + background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, rgba(0, 0, 0, 0.2) 60%, transparent 100%); + display: flex; + flex-direction: column; + justify-content: flex-end; + padding: 6px; + opacity: 0; + transition: opacity 0.2s ease; + } + + .calendar-movie-title { + color: $text-white; + font-size: 0.65rem; + font-weight: 600; + line-height: 1.2; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + + .calendar-movie-year { + color: $text-gray; + font-size: 0.6rem; + margin-top: 2px; + } + + .calendar-more-badge { + position: absolute; + top: 4px; + right: 4px; + background-color: $primary-color; + color: $text-white; + font-size: 0.6rem; + font-weight: 700; + padding: 1px 4px; + border-radius: 3px; + z-index: 2; + } + + .calendar-selected-badge { + position: absolute; + bottom: 4px; + right: 4px; + background-color: $primary-color; + color: $text-white; + width: 18px; + height: 18px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + z-index: 2; + } + + .calendar-cell--selected { + box-shadow: 0 0 0 2px $primary-color; + border-radius: 4px; + } + + // Shared states .progress-container { display: flex; justify-content: center; @@ -148,12 +328,23 @@ $hover-color: rgba(102, 119, 136, 0.2); } .no-movies-container { + position: absolute; + inset: 0; + z-index: 10; display: flex; flex-direction: column; align-items: center; justify-content: center; - height: 400px; color: $text-white; + background: rgba(28, 31, 35, 0.7); + backdrop-filter: blur(6px); + border-radius: 4px; + animation: fade-in 0.3s ease; + } + + @keyframes fade-in { + from { opacity: 0; } + to { opacity: 1; } } .gif-container { @@ -193,3 +384,142 @@ $hover-color: rgba(102, 119, 136, 0.2); } } } + +// Responsive +@media (max-width: 768px) { + .poster-selector { + margin-top: 3.5rem !important; + padding-left: 0.5rem !important; + padding-right: 0.5rem !important; + } + + .poster-selector .content-paper { + padding: 0.75rem !important; + max-width: 100% !important; + } + + // Nav : deux rangées + // Rangée 1 : Today (gauche) | chevrons + label (droite) + // Rangée 2 : selects centrés + .poster-selector .calendar-nav { + flex-wrap: wrap; + row-gap: 8px; + margin-bottom: 0.75rem; + } + + .poster-selector .calendar-today-btn { + order: 1; + } + + .poster-selector .calendar-nav-center { + order: 2; + flex: 1; + justify-content: flex-end; + } + + .poster-selector .calendar-month-label { + min-width: unset !important; + font-size: 0.9rem !important; + } + + .poster-selector .calendar-month-selects { + order: 3; + width: 100%; + justify-content: center !important; + border: none !important; + border-top: 1px solid #2c3038 !important; + border-radius: 0 !important; + padding: 6px 0 0 !important; + gap: 8px !important; + } + + // Grille + .poster-selector .calendar-grid { + gap: 3px; + max-width: 100%; + min-width: unset; + } + + .poster-selector .calendar-day-header { + font-size: 0.6rem; + padding: 0.15rem 0; + } + + .poster-selector .calendar-cell { + min-height: unset; // aspect-ratio 2/3 contrôle la hauteur + border-radius: 3px; + } + + .poster-selector .calendar-day-number { + font-size: 0.6rem; + } + + .poster-selector .calendar-movie-title { + font-size: 0.55rem; + } + + .poster-selector .calendar-more-badge, + .poster-selector .calendar-selected-badge { + display: none; + } +} + +// Popover multi-films (rendu en portal, hors de .poster-selector) +.multi-movie-popover { + background-color: #1c1f23 !important; + border: 1px solid #2c3038; + border-radius: 6px !important; + overflow: hidden; + min-width: 220px; +} + +.multi-movie-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 12px; + cursor: pointer; + border-bottom: 1px solid #2c3038; + + &:last-child { border-bottom: none; } + + &:hover { background-color: rgba(102, 119, 136, 0.2); } +} + +.multi-movie-poster { + width: 36px; + height: 54px; + object-fit: cover; + border-radius: 2px; + flex-shrink: 0; + + &--empty { background-color: #2c3038; } +} + +.multi-movie-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.multi-movie-title { + color: #ffffff; + font-size: 0.8rem; + font-weight: 500; + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.multi-movie-year { + color: #667788; + font-size: 0.7rem; +} + +.multi-movie-check { + color: #00a346 !important; + flex-shrink: 0; +} diff --git a/src/styles/scss/_selectionRecap.scss b/src/styles/scss/_selectionRecap.scss new file mode 100644 index 0000000..29cf704 --- /dev/null +++ b/src/styles/scss/_selectionRecap.scss @@ -0,0 +1,187 @@ +@use "sass:color"; + +$primary-color: #00a346; +$background-dark: #1c1f23; +$text-white: #ffffff; +$text-gray: #667788; +$border-color: #2c3038; + +.selection-recap { + margin-top: 5rem; + margin-bottom: 3rem; + + .recap-header { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 2.5rem; + } + + .recap-back-btn { + color: $text-gray !important; + &:hover { color: $text-white !important; } + } + + .recap-title { + flex: 1; + color: $text-white; + font-weight: 600; + } + + .recap-download-btn { + background-color: $primary-color !important; + color: $text-white !important; + font-weight: 600; + text-transform: none !important; + padding: 0.4rem 1rem; + border-radius: 4px; + font-size: 0.875rem; + white-space: nowrap; + + &:hover { background-color: color.adjust($primary-color, $lightness: -8%) !important; } + + &--loading { + background-color: color.adjust($primary-color, $lightness: -12%) !important; + cursor: default !important; + min-width: 150px; + } + + &.Mui-disabled { + color: $text-white !important; + opacity: 0.8 !important; + } + } + + .recap-download-progress { + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + width: 120px; + + span { + font-size: 0.8rem; + font-weight: 600; + color: $text-white; + line-height: 1; + } + } + + .recap-progress-bar { + width: 100%; + border-radius: 2px; + height: 4px !important; + + .MuiLinearProgress-bar { background-color: $text-white !important; } + &.MuiLinearProgress-root { background-color: rgba(255, 255, 255, 0.3) !important; } + } + + .recap-month-section { + margin-bottom: 2.5rem; + } + + .recap-month-label { + color: $text-gray; + font-size: 0.8rem !important; + font-weight: 700 !important; + text-transform: uppercase; + letter-spacing: 0.08em; + margin-bottom: 1rem !important; + padding-bottom: 0.5rem; + border-bottom: 1px solid $border-color; + } + + .recap-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); + gap: 10px; + } + + .recap-card { + cursor: pointer; + border-radius: 4px; + overflow: hidden; + + &:hover .recap-overlay { opacity: 1; } + &:hover .recap-poster { transform: scale(1.04); } + } + + .recap-poster-wrapper { + position: relative; + padding-top: 150%; + overflow: hidden; + border-radius: 4px; + background-color: #14181c; + } + + .recap-poster { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.25s ease; + } + + .recap-overlay { + position: absolute; + inset: 0; + background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, rgba(0, 0, 0, 0.15) 55%, transparent 100%); + display: flex; + flex-direction: column; + justify-content: flex-end; + padding: 8px; + opacity: 0; + transition: opacity 0.2s ease; + } + + .recap-movie-title { + color: $text-white; + font-size: 0.68rem; + font-weight: 600; + line-height: 1.2; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + } + + .recap-movie-year { + color: rgba(255, 255, 255, 0.55); + font-size: 0.62rem; + margin-top: 2px; + } + + .recap-check-badge { + position: absolute; + top: 6px; + right: 6px; + background-color: $primary-color; + color: $text-white; + width: 18px; + height: 18px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + z-index: 2; + } + + .recap-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 60vh; + gap: 1.5rem; + color: $text-gray; + + .MuiTypography-root { color: $text-gray; } + } + + .recap-go-btn { + background-color: $primary-color !important; + color: $text-white !important; + text-transform: none !important; + } +} diff --git a/src/styles/scss/_uploadDiary.scss b/src/styles/scss/_uploadDiary.scss index 778be6d..6505ece 100644 --- a/src/styles/scss/_uploadDiary.scss +++ b/src/styles/scss/_uploadDiary.scss @@ -1,12 +1,36 @@ -.upload-diary { - padding: 3rem; +.upload-page { + min-height: 100vh; + display: flex; + flex-direction: column; background-color: #14181c; - min-height: calc(100vh - 60px); +} + +.upload-diary { + flex: 1; + padding: 3rem; + padding-top: 5rem; display: flex !important; flex-direction: column; align-items: center; justify-content: center; + @media (max-width: 600px) { + padding: 1.25rem; + padding-top: 4.5rem; + justify-content: flex-start; + } + + .upload-card-wrapper { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + + @media (max-width: 600px) { + min-height: calc(100svh - 80px); + } + } + .upload-card { width: 100%; max-width: 600px; @@ -17,6 +41,11 @@ padding-top: 4rem; position: relative; + @media (max-width: 600px) { + padding: 1.25rem; + padding-top: 3rem; + } + .logo-container { position: absolute; top: -80px; @@ -24,11 +53,20 @@ transform: translate(-50%); z-index: 10; + @media (max-width: 600px) { + top: -55px; + } + .logo { width: 150px; height: 150px; border-radius: 50%; object-fit: cover; + + @media (max-width: 600px) { + width: 100px; + height: 100px; + } } } @@ -36,35 +74,11 @@ margin-bottom: 1.5rem; font-weight: bold; color: #ffffff; - } - .username-field { - margin-bottom: 1.5rem; - - .MuiOutlinedInput-root { - color: #ffffff; - - fieldset { - border-color: #00a346 !important; - } - - &:hover fieldset { - border-color: #00a346 !important; - } - - &.Mui-focused fieldset { - border-color: #1caff2 !important; - } + @media (max-width: 600px) { + font-size: 1.4rem !important; + margin-bottom: 1rem; } - - .MuiInputLabel-root { - color: #ffffff !important; - } - } - - .or-text { - color: #ffffff; - margin-bottom: 1rem; } .dropzone { @@ -74,6 +88,10 @@ text-align: center; cursor: pointer; + @media (max-width: 600px) { + padding: 1.25rem; + } + &:hover { background-color: #1f252a; } @@ -82,11 +100,20 @@ margin-bottom: 1rem; color: #00a346; font-size: 4rem; + + @media (max-width: 600px) { + font-size: 2.5rem; + margin-bottom: 0.5rem; + } } .dropzone-text { margin-bottom: 1rem; color: #ffffff; + + @media (max-width: 600px) { + font-size: 0.9rem !important; + } } } @@ -94,6 +121,101 @@ margin-top: 1rem; } + .upload-divider { + display: flex; + align-items: center; + gap: 0.75rem; + margin: 1.75rem 0 1.25rem; + + @media (max-width: 600px) { + margin: 1.25rem 0 1rem; + } + } + + .upload-divider-line { + flex: 1; + height: 1px; + background-color: #2c3038; + } + + .upload-divider-text { + color: #526e89; + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.08em; + } + + .rss-label { + color: #ffffff !important; + font-size: 0.9rem !important; + font-weight: 600 !important; + margin-bottom: 0.75rem !important; + } + + .rss-row { + display: flex; + gap: 8px; + align-items: center; + } + + .rss-input { + flex: 1; + min-width: 0; + + .MuiOutlinedInput-root { + background-color: #14181c; + border-radius: 4px; + color: #ffffff; + font-size: 0.875rem; + + fieldset { border-color: #2c3038; } + &:hover fieldset { border-color: #526e89; } + &.Mui-focused fieldset { border-color: #00a346; } + + input { padding: 8px 10px 8px 4px; color: #ffffff; } + input::placeholder { color: #526e89; } + } + } + + .rss-url-prefix { + color: #526e89; + font-size: 0.8rem; + white-space: nowrap; + user-select: none; + + @media (max-width: 400px) { + display: none; + } + } + + .rss-sync-button { + background-color: #00a346 !important; + color: #ffffff !important; + font-weight: 700 !important; + text-transform: none !important; + padding: 7px 16px !important; + border-radius: 4px !important; + white-space: nowrap; + font-size: 0.875rem !important; + + &:hover { background-color: #008a39 !important; } + &:disabled { opacity: 0.6 !important; } + } + + .rss-note { + color: #526e89 !important; + font-size: 0.72rem !important; + margin-top: 0.5rem !important; + line-height: 1.4 !important; + } + + .rss-error { + color: #e57373 !important; + font-size: 0.78rem !important; + margin-top: 0.4rem !important; + } + .submit-button { color: #fff; font-size: 0.8rem; @@ -125,6 +247,15 @@ color: #456; margin-top: 2rem; padding: 1.5rem; + max-width: 600px; + width: 100%; + + @media (max-width: 600px) { + padding: 1rem 0.25rem; + margin-top: 1.25rem; + font-size: 0.85rem !important; + // display: block; + } } } @@ -142,7 +273,7 @@ .link { text-decoration: none; - color: inherit + color: inherit; } } -} \ No newline at end of file +}