From 7bd54359e6840427fd9541ee3a09a1a560fcf4f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Leszczyk?= Date: Wed, 6 Apr 2022 11:10:28 +0200 Subject: [PATCH] Dashboard v2 lighthouse scores improvements (#1972) * Metadata improvements * Accessibility improvements * Improve performance on mobile --- packages/dashboard-v2/gatsby-config.js | 4 +- .../src/components/APIKeyList/APIKey.js | 14 ++- .../dashboard-v2/src/components/CopyButton.js | 4 +- .../components/CurrentUsage/CurrentUsage.js | 1 - .../src/components/FileList/FileTable.js | 6 +- .../src/components/Metadata/Metadata.js | 27 +++++ .../src/components/Metadata/index.js | 1 + .../src/components/Modal/Modal.js | 2 +- .../src/components/NavBar/NavBar.js | 1 + .../src/components/Slider/Bullets.js | 1 + .../src/components/Uploader/Uploader.js | 6 +- .../src/components/forms/AddAPIKeyForm.js | 4 +- .../components/forms/AddPublicAPIKeyForm.js | 5 +- .../forms/AddSkylinkToAPIKeyForm.js | 2 +- packages/dashboard-v2/src/pages/auth/login.js | 24 +++-- .../src/pages/auth/reset-password.js | 54 +++++----- .../dashboard-v2/src/pages/auth/signup.js | 4 + packages/dashboard-v2/src/pages/files.js | 32 +++--- packages/dashboard-v2/src/pages/index.js | 98 ++++++++++-------- .../src/pages/settings/api-keys.js | 4 + .../dashboard-v2/src/pages/settings/export.js | 4 + .../dashboard-v2/src/pages/settings/index.js | 4 + .../src/pages/settings/notifications.js | 6 +- packages/dashboard-v2/src/pages/upgrade.js | 4 + .../dashboard-v2/src/pages/user/confirm.js | 36 ++++--- .../dashboard-v2/src/pages/user/recover.js | 60 ++++++----- packages/dashboard-v2/static/favicon.ico | Bin 0 -> 2118 bytes 27 files changed, 258 insertions(+), 150 deletions(-) create mode 100644 packages/dashboard-v2/src/components/Metadata/Metadata.js create mode 100644 packages/dashboard-v2/src/components/Metadata/index.js create mode 100644 packages/dashboard-v2/static/favicon.ico diff --git a/packages/dashboard-v2/gatsby-config.js b/packages/dashboard-v2/gatsby-config.js index ce35de3a..c6096a98 100644 --- a/packages/dashboard-v2/gatsby-config.js +++ b/packages/dashboard-v2/gatsby-config.js @@ -2,8 +2,8 @@ const { createProxyMiddleware } = require("http-proxy-middleware"); module.exports = { siteMetadata: { - title: `Accounts Dashboard`, - siteUrl: `https://www.yourdomain.tld`, + title: "Skynet Account", + siteUrl: `https://account.${process.env.GATSBY_PORTAL_DOMAIN}/`, }, trailingSlash: "never", plugins: [ diff --git a/packages/dashboard-v2/src/components/APIKeyList/APIKey.js b/packages/dashboard-v2/src/components/APIKeyList/APIKey.js index 5cb6680a..3269bb9f 100644 --- a/packages/dashboard-v2/src/components/APIKeyList/APIKey.js +++ b/packages/dashboard-v2/src/components/APIKeyList/APIKey.js @@ -83,13 +83,19 @@ export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => { {isPublic && ( )} - @@ -121,7 +127,11 @@ export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => { {skylink} - diff --git a/packages/dashboard-v2/src/components/CopyButton.js b/packages/dashboard-v2/src/components/CopyButton.js index 479352d2..9cbf43d2 100644 --- a/packages/dashboard-v2/src/components/CopyButton.js +++ b/packages/dashboard-v2/src/components/CopyButton.js @@ -22,7 +22,7 @@ const TooltipContent = styled.div.attrs({ className: "bg-primary-light/10 text-palette-600 py-2 px-4 ", })``; -export const CopyButton = ({ value, className }) => { +export const CopyButton = ({ value, className, ariaLabel = "Copy" }) => { const containerRef = useRef(); const [copied, setCopied] = useState(false); const [timer, setTimer] = useState(null); @@ -39,7 +39,7 @@ export const CopyButton = ({ value, className }) => { return (
- diff --git a/packages/dashboard-v2/src/components/CurrentUsage/CurrentUsage.js b/packages/dashboard-v2/src/components/CurrentUsage/CurrentUsage.js index 44be79ed..081b9cca 100644 --- a/packages/dashboard-v2/src/components/CurrentUsage/CurrentUsage.js +++ b/packages/dashboard-v2/src/components/CurrentUsage/CurrentUsage.js @@ -101,7 +101,6 @@ export default function CurrentUsage() { > UPGRADE {" "} - {/* TODO: proper URL */} {usage.filesLimit}
diff --git a/packages/dashboard-v2/src/components/FileList/FileTable.js b/packages/dashboard-v2/src/components/FileList/FileTable.js index 90c9600f..c2f133d6 100644 --- a/packages/dashboard-v2/src/components/FileList/FileTable.js +++ b/packages/dashboard-v2/src/components/FileList/FileTable.js @@ -84,19 +84,19 @@ export default function FileTable({ items }) { {date}
- + {skylink}
- - diff --git a/packages/dashboard-v2/src/components/Metadata/Metadata.js b/packages/dashboard-v2/src/components/Metadata/Metadata.js new file mode 100644 index 00000000..5bb98330 --- /dev/null +++ b/packages/dashboard-v2/src/components/Metadata/Metadata.js @@ -0,0 +1,27 @@ +import { Helmet } from "react-helmet"; +import { graphql, useStaticQuery } from "gatsby"; + +export const Metadata = ({ children }) => { + const { site } = useStaticQuery( + graphql` + query Q { + site { + siteMetadata { + title + } + } + } + ` + ); + + const { title } = site.siteMetadata; + + return ( + + + + + {children} + + ); +}; diff --git a/packages/dashboard-v2/src/components/Metadata/index.js b/packages/dashboard-v2/src/components/Metadata/index.js new file mode 100644 index 00000000..8abb6696 --- /dev/null +++ b/packages/dashboard-v2/src/components/Metadata/index.js @@ -0,0 +1 @@ +export * from "./Metadata"; diff --git a/packages/dashboard-v2/src/components/Modal/Modal.js b/packages/dashboard-v2/src/components/Modal/Modal.js index c183e190..ac7bd98e 100644 --- a/packages/dashboard-v2/src/components/Modal/Modal.js +++ b/packages/dashboard-v2/src/components/Modal/Modal.js @@ -11,7 +11,7 @@ export const Modal = ({ children, className, onClose }) => (
- {children} diff --git a/packages/dashboard-v2/src/components/NavBar/NavBar.js b/packages/dashboard-v2/src/components/NavBar/NavBar.js index f75030bb..6d2cb9eb 100644 --- a/packages/dashboard-v2/src/components/NavBar/NavBar.js +++ b/packages/dashboard-v2/src/components/NavBar/NavBar.js @@ -94,6 +94,7 @@ export const NavBar = () => { partiallyActive /> (
) : (
- Add, or drop your files here diff --git a/packages/dashboard-v2/src/components/forms/AddAPIKeyForm.js b/packages/dashboard-v2/src/components/forms/AddAPIKeyForm.js index 703d88a0..ccea02a5 100644 --- a/packages/dashboard-v2/src/components/forms/AddAPIKeyForm.js +++ b/packages/dashboard-v2/src/components/forms/AddAPIKeyForm.js @@ -44,7 +44,7 @@ export const AddAPIKeyForm = forwardRef(({ onSuccess, type }, ref) => { {generatedKey} - +
)} @@ -94,7 +94,7 @@ export const AddAPIKeyForm = forwardRef(({ onSuccess, type }, ref) => { {isSubmitting ? ( ) : ( - )} diff --git a/packages/dashboard-v2/src/components/forms/AddPublicAPIKeyForm.js b/packages/dashboard-v2/src/components/forms/AddPublicAPIKeyForm.js index 2184c513..c98daac9 100644 --- a/packages/dashboard-v2/src/components/forms/AddPublicAPIKeyForm.js +++ b/packages/dashboard-v2/src/components/forms/AddPublicAPIKeyForm.js @@ -59,7 +59,7 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => { {generatedKey} - +
)} @@ -137,7 +137,7 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => { touched={skylinksTouched[index]} /> - @@ -160,6 +160,7 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => { /> )} diff --git a/packages/dashboard-v2/src/pages/auth/login.js b/packages/dashboard-v2/src/pages/auth/login.js index 8b0cfbb5..5812fd8f 100644 --- a/packages/dashboard-v2/src/pages/auth/login.js +++ b/packages/dashboard-v2/src/pages/auth/login.js @@ -4,6 +4,7 @@ import { navigate } from "gatsby"; import AuthLayout from "../../layouts/AuthLayout"; import { LoginForm } from "../../components/forms"; import { useUser } from "../../contexts/user"; +import { Metadata } from "../../components/Metadata"; const LoginPage = ({ location }) => { const { user, mutate: refreshUserState } = useUser(); @@ -17,16 +18,21 @@ const LoginPage = ({ location }) => { }, [user, redirectTo]); return ( -
-
- Skynet + <> + + Sign In + +
+
+ Skynet +
+ { + await refreshUserState(); + }} + />
- { - await refreshUserState(); - }} - /> -
+ ); }; diff --git a/packages/dashboard-v2/src/pages/auth/reset-password.js b/packages/dashboard-v2/src/pages/auth/reset-password.js index 8ecbfeaa..88db338a 100644 --- a/packages/dashboard-v2/src/pages/auth/reset-password.js +++ b/packages/dashboard-v2/src/pages/auth/reset-password.js @@ -4,6 +4,7 @@ import AuthLayout from "../../layouts/AuthLayout"; import { RecoveryForm } from "../../components/forms/RecoveryForm"; import HighlightedLink from "../../components/HighlightedLink"; +import { Metadata } from "../../components/Metadata"; const State = { Pure: "PURE", @@ -15,31 +16,36 @@ const ResetPasswordPage = () => { const [state, setState] = useState(State.Pure); return ( -
-
- Skynet + <> + + Reset Password + +
+
+ Skynet +
+ {state !== State.Success && ( + setState(State.Success)} onFailure={() => setState(State.Failure)} /> + )} + + {state === State.Success && ( +

Please check your inbox for further instructions.

+ )} + + {state === State.Failure && ( +

Something went wrong, please try again later.

+ )} + +
+

+ Suddenly remembered your password? Sign in +

+

+ Don't actually have an account? Create one! +

+
- {state !== State.Success && ( - setState(State.Success)} onFailure={() => setState(State.Failure)} /> - )} - - {state === State.Success && ( -

Please check your inbox for further instructions.

- )} - - {state === State.Failure && ( -

Something went wrong, please try again later.

- )} - -
-

- Suddenly remembered your password? Sign in -

-

- Don't actually have an account? Create one! -

-
-
+ ); }; diff --git a/packages/dashboard-v2/src/pages/auth/signup.js b/packages/dashboard-v2/src/pages/auth/signup.js index 2af428f9..b6f0e0ac 100644 --- a/packages/dashboard-v2/src/pages/auth/signup.js +++ b/packages/dashboard-v2/src/pages/auth/signup.js @@ -9,6 +9,7 @@ import HighlightedLink from "../../components/HighlightedLink"; import { SignUpForm } from "../../components/forms/SignUpForm"; import { usePortalSettings } from "../../contexts/portal-settings"; import { PlansProvider, usePlans } from "../../contexts/plans"; +import { Metadata } from "../../components/Metadata"; const FreePortalHeader = () => { const { plans } = usePlans(); @@ -57,6 +58,9 @@ const SignUpPage = () => { return ( + + Sign Up +
Skynet diff --git a/packages/dashboard-v2/src/pages/files.js b/packages/dashboard-v2/src/pages/files.js index 197cc031..f4a47a1d 100644 --- a/packages/dashboard-v2/src/pages/files.js +++ b/packages/dashboard-v2/src/pages/files.js @@ -1,28 +1,34 @@ import * as React from "react"; +import { useSearchParam } from "react-use"; import DashboardLayout from "../layouts/DashboardLayout"; import { Panel } from "../components/Panel"; import { Tab, TabPanel, Tabs } from "../components/Tabs"; +import { Metadata } from "../components/Metadata"; import FileList from "../components/FileList/FileList"; -import { useSearchParam } from "react-use"; const FilesPage = () => { const defaultTab = useSearchParam("tab"); return ( - - - - - - - - - - - - + <> + + My Files + + + + + + + + + + + + + + ); }; diff --git a/packages/dashboard-v2/src/pages/index.js b/packages/dashboard-v2/src/pages/index.js index 1fd88651..15edd552 100644 --- a/packages/dashboard-v2/src/pages/index.js +++ b/packages/dashboard-v2/src/pages/index.js @@ -13,6 +13,7 @@ import CurrentUsage from "../components/CurrentUsage"; import Uploader from "../components/Uploader/Uploader"; import CurrentPlan from "../components/CurrentPlan"; import { FullScreenLoadingIndicator } from "../components/LoadingIndicator"; +import { Metadata } from "../components/Metadata"; import useUpgradeRedirect from "../hooks/useUpgradeRedirect"; const IndexPage = () => { @@ -24,51 +25,60 @@ const IndexPage = () => { } return ( - -
- - - - - - - - - - - - , - - Usage - - } - className="h-[330px]" - > - - , - - Current plan - - } - className="h-[330px]" - > - - , - ]} - /> -
- {showRecentActivity && ( -
- -
+ <> + + Dashboard + + {verifyingSubscription ? ( + + ) : ( + +
+ + + + + + + + + + + + , + + Usage + + } + className="h-[330px]" + > + + , + + Current plan + + } + className="h-[330px]" + > + + , + ]} + /> +
+ {showRecentActivity && ( +
+ +
+ )} +
)} -
+ ); }; diff --git a/packages/dashboard-v2/src/pages/settings/api-keys.js b/packages/dashboard-v2/src/pages/settings/api-keys.js index 1f6c93c5..03486248 100644 --- a/packages/dashboard-v2/src/pages/settings/api-keys.js +++ b/packages/dashboard-v2/src/pages/settings/api-keys.js @@ -7,6 +7,7 @@ import { AddAPIKeyForm, APIKeyType } from "../../components/forms/AddAPIKeyForm" import { APIKeyList } from "../../components/APIKeyList/APIKeyList"; import { Alert } from "../../components/Alert"; import { AddPublicAPIKeyForm } from "../../components/forms/AddPublicAPIKeyForm"; +import { Metadata } from "../../components/Metadata"; const APIKeysPage = () => { const { data: apiKeys = [], mutate: reloadKeys, error } = useSWR("user/apikeys"); @@ -29,6 +30,9 @@ const APIKeysPage = () => { return ( <> + + API Keys +
diff --git a/packages/dashboard-v2/src/pages/settings/export.js b/packages/dashboard-v2/src/pages/settings/export.js index ad05f5ff..7a75bc0c 100644 --- a/packages/dashboard-v2/src/pages/settings/export.js +++ b/packages/dashboard-v2/src/pages/settings/export.js @@ -4,6 +4,7 @@ import UserSettingsLayout from "../../layouts/UserSettingsLayout"; import { Switch } from "../../components/Switch"; import { Button } from "../../components/Button"; +import { Metadata } from "../../components/Metadata"; const useExportOptions = () => { const [pinnedFiles, setPinnedFiles] = useState(false); @@ -29,6 +30,9 @@ const ExportPage = () => { return ( <> + + Export +
diff --git a/packages/dashboard-v2/src/pages/settings/index.js b/packages/dashboard-v2/src/pages/settings/index.js index 358a4b28..23a815e2 100644 --- a/packages/dashboard-v2/src/pages/settings/index.js +++ b/packages/dashboard-v2/src/pages/settings/index.js @@ -7,6 +7,7 @@ import { AccountSettingsForm } from "../../components/forms/AccountSettingsForm" import { Modal } from "../../components/Modal/Modal"; import { AccountRemovalForm } from "../../components/forms/AccountRemovalForm"; import { Alert } from "../../components/Alert"; +import { Metadata } from "../../components/Metadata"; const State = { Pure: "PURE", @@ -39,6 +40,9 @@ const AccountPage = () => { return ( <> + + Settings +
diff --git a/packages/dashboard-v2/src/pages/settings/notifications.js b/packages/dashboard-v2/src/pages/settings/notifications.js index b46a1da4..447e40c8 100644 --- a/packages/dashboard-v2/src/pages/settings/notifications.js +++ b/packages/dashboard-v2/src/pages/settings/notifications.js @@ -1,13 +1,17 @@ import * as React from "react"; +import { StaticImage } from "gatsby-plugin-image"; import UserSettingsLayout from "../../layouts/UserSettingsLayout"; import { Switch } from "../../components/Switch"; -import { StaticImage } from "gatsby-plugin-image"; +import { Metadata } from "../../components/Metadata"; const NotificationsPage = () => { return ( <> + + Notifications +

Notifications

diff --git a/packages/dashboard-v2/src/pages/upgrade.js b/packages/dashboard-v2/src/pages/upgrade.js index 8e47472e..9f69487e 100644 --- a/packages/dashboard-v2/src/pages/upgrade.js +++ b/packages/dashboard-v2/src/pages/upgrade.js @@ -13,6 +13,7 @@ import { Button } from "../components/Button"; import { usePortalSettings } from "../contexts/portal-settings"; import { Alert } from "../components/Alert"; import HighlightedLink from "../components/HighlightedLink"; +import { Metadata } from "../components/Metadata"; const PAID_PORTAL_BREAKPOINTS = [ { @@ -88,6 +89,9 @@ const PlansSlider = () => { return (
+ + Upgrade + {settings.isSubscriptionRequired && !activePlan && (

This Skynet portal requires a paid subscription.

diff --git a/packages/dashboard-v2/src/pages/user/confirm.js b/packages/dashboard-v2/src/pages/user/confirm.js index 9e95e3e3..b4ce6bc1 100644 --- a/packages/dashboard-v2/src/pages/user/confirm.js +++ b/packages/dashboard-v2/src/pages/user/confirm.js @@ -5,6 +5,7 @@ import { AllUsersAuthLayout } from "../../layouts/AuthLayout"; import HighlightedLink from "../../components/HighlightedLink"; import accountsService from "../../services/accountsService"; +import { Metadata } from "../../components/Metadata"; const State = { Pure: "PURE", @@ -52,24 +53,29 @@ const EmailConfirmationPage = ({ location }) => { }, [token]); return ( -
-
- Skynet -
-
- {state === State.Pure &&

Please wait while we verify your account...

} + <> + + Confirm E-mail Address + +
+
+ Skynet +
+
+ {state === State.Pure &&

Please wait while we verify your account...

} - {state === State.Success && ( - <> -

All done!

-

You will be redirected to your dashboard shortly.

- Redirect now. - - )} + {state === State.Success && ( + <> +

All done!

+

You will be redirected to your dashboard shortly.

+ Redirect now. + + )} - {state === State.Failure &&

Something went wrong, please try again later.

} + {state === State.Failure &&

Something went wrong, please try again later.

} +
-
+ ); }; diff --git a/packages/dashboard-v2/src/pages/user/recover.js b/packages/dashboard-v2/src/pages/user/recover.js index 686e677a..d8d02e8a 100644 --- a/packages/dashboard-v2/src/pages/user/recover.js +++ b/packages/dashboard-v2/src/pages/user/recover.js @@ -5,6 +5,7 @@ import AuthLayout from "../../layouts/AuthLayout"; import { ResetPasswordForm } from "../../components/forms/ResetPasswordForm"; import HighlightedLink from "../../components/HighlightedLink"; +import { Metadata } from "../../components/Metadata"; const State = { Pure: "PURE", @@ -19,35 +20,40 @@ const RecoverPage = ({ location }) => { const [state, setState] = useState(State.Pure); return ( -
-
- Skynet -
- {state !== State.Success && ( - { - setState(State.Success); - navigate("/"); - }} - onFailure={() => setState(State.Failure)} - /> - )} + <> + + Recover Your Account + +
+
+ Skynet +
+ {state !== State.Success && ( + { + setState(State.Success); + navigate("/"); + }} + onFailure={() => setState(State.Failure)} + /> + )} - {state === State.Success && ( -

- All done! You will be redirected to your dashboard shortly. + {state === State.Success && ( +

+ All done! You will be redirected to your dashboard shortly. +

+ )} + + {state === State.Failure && ( +

Something went wrong, please try again later.

+ )} + +

+ Suddenly remembered your old password? Sign in

- )} - - {state === State.Failure && ( -

Something went wrong, please try again later.

- )} - -

- Suddenly remembered your old password? Sign in -

-
+
+ ); }; diff --git a/packages/dashboard-v2/static/favicon.ico b/packages/dashboard-v2/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9229fbf780c52d9d25e2183c0effe239dd2728dd GIT binary patch literal 2118 zcmV-M2)Xx(P)RT(~arVF-3X?a8o@+eOO1vEksNT^L4i?pS? zGjpFa_ntF5TNVgSR!EH+O{o}6G@z1bNRVez0u;Kt_nv!ax2u$A6CgkW0YZ2+L`XDR z1#DW{()IiQb7pobr6JWx{@i=coOAyFeE;`9t`LXuTbZsA!xQ~t-Jh2TFH3}{6DQEJ z6zHVn=d$E!>4ZfC^CXFAOg6;-{0rK#2(l(bi6EE4Cxy8zUvSIUSxJyd0Rx1evq8d3 zha9Xa(>gb=iy)OEOqCGRhoRgjg}D-58;WpffF}-SLzdsS%z?raVZb67@GTf{F2-MV z(;;3%66v9mpUF~Vb13?^4dbll7gOv$>{{hgcsXEBn}Asfe+!Jr!P=_3OLR`PC=u47m}Isz}7LsTJhYKlCX6 zBrpm{_G9fwFm8vh)jxegBJUdaX<*jugv4$kh1*sRUB<(^s6k0J8xl1@Q`Ifu62!Wz z+yV4rnwc|67!;s5J#&f^`FEE?D^M;J+P z;%n0lV?YH4c$so)2pvDO1^^yEi{W=B-2JRDm%Hq(_dtH+>c$*)kzLPGfJosn$QKta zdXmew2L_S72C-xyFa#|NKOLOcpAXIQu&HjCTdHZ^SoVznTV2RCE8l@Qznmh#O=J3h z78I6XfMX0$4v)2*l}sob3GuObZu4h&@jgg+93dg9d26nxo0q_V3qw69jAg11h#;Qj zDR}?8_2qtS+zU2;*M?vpB_R1gT_ocp*w^H!af_x%#v;V~H0>t?U?2=j)mQNTdY7PD zZXhCJQB%m#rURBP$K+3N@Hbh3=G!-{-;CwC{F!BN-%?!>mGOpHxy46*@Pyi|a1O0SL$oa(Oxy+yS{A3FTxaVkXbQxJ`r$#~X7z z9fPQnAa>SWcDz`7_lT%XxTM6vdWYOb$_E}95$<@4!#JM08fQ1lVbNqhSso#BHJ{WcsMGC+s#y>%;d%dC-$XAo6C?u463wTSt8Zevs@3$fuj%5eDahUm$bsR1c?k;$bnU{Q6G z%e5hDFGq%yXJFl#IBPZxFwkdI8<>z~c0H(G+CkpL$0@rrh6 z!b;0^Hrf0IDAoPZmRK~kif*_X;|+K=K;SP^z5r6+!1y}IjgWpAu)v^VWaa$>qR}3v zGas{V*m7Xe(PRkxXg2%`;~!Ar2(tJz&iRIrFF>whcV)?!F!w{qpRoSdE^(;fig+sT z)pDGaKyHFu>ZNk7Z#Xh)4S3=J0}u%!EL15d*q8U%n?De0HMc&=a~u3_7^$sr*=Z5=#{DWog{4r;P%E_h zCCAPZsfPA)*!UqQk7VpavYAR>lqbtMjYx2N<{jkw26<%#wZXJgG+ z*N|LpM^^j=o(nl5vj6G{7H?h8Y>jY>(6n6>)&N^XP*^gaKLT)S=|n$IDgsqZlJ*-%E4s_CX$-kA59MkIQa@fv`6?8@k zw^$U8So06Ne!n&3U&N(q*60gNWKkFOY>#U${)_>uw%0_Zh=dv&7HaewVf{yk?y^PX zX*3dZO~zAYCT&K^hMG}%GUjzB)AbYvw~dZ-5; z!<>{o530&fZU3Nf`+3q7K-7m>-d9&P+7gDpAywt9R6xgS?w{vDC|LpBIEbSh$nHyP0=?P zw;tMn89qqS+)N~k&F7e|6B6ir;v;d3??cJ&F#iP?H%Mm!b#eF8VSrZkti zGFk(^_;+E!A%^ce)A_=Ha+r79E$ViFRx$_Ca4go%YaylE-qQ4i87Y>Id~#4Me|QZS zy~FwqZM;@g@x?SS6;hRRY?lL)suBzkoldE#L;6}S+wRy2CSwue3)wHE_`m)L)*x*d wcr!_T1R3R2QHrnKyh^M|E8>b`?IF+j55uL_p0enzz5oCK07*qoM6N<$f=9md3IG5A literal 0 HcmV?d00001