From 53947954d8915b8bc6ec1525c05f11facc66063f Mon Sep 17 00:00:00 2001 From: JinHo Kim <81083461+jinhokim98@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:25:16 +0900 Subject: [PATCH 01/14] =?UTF-8?q?refactor:=20=EB=9E=9C=EB=94=A9=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=94=94=EB=A0=89=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD=20(#767)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 랜딩 페이지 디렉토리 구조 변경 * refactor: nav 관련 스타일 nav로 이동 * remove: 사용하지 않는 컴포넌트 제거 * style: div => section, article 태그로 변경 * style: 컴포넌트 이름 조금 더 의미있게 변경 --- client/src/pages/MainPage/MainPage.style.ts | 9 + client/src/pages/MainPage/MainPage.tsx | 40 +- client/src/pages/MainPage/Nav/Nav.style.ts | 38 +- client/src/pages/MainPage/Nav/Nav.tsx | 36 +- client/src/pages/MainPage/Nav/index.ts | 1 + .../MainPage/Section/DescriptionSection.tsx | 52 --- .../DescriptionSection.style.ts | 27 ++ .../DescriptionSection/DescriptionSection.tsx | 21 ++ .../Section/DescriptionSection/index.ts | 1 + .../pages/MainPage/Section/FeatureSection.tsx | 351 ------------------ .../AutoCalculate/AutoCalculate.style.ts | 52 +++ .../AutoCalculate/AutoCalculate.tsx | 25 ++ .../FeatureSection/AutoCalculate/index.ts | 1 + .../CheckDeposit/CheckDeposit.style.ts | 52 +++ .../CheckDeposit/CheckDeposit.tsx | 25 ++ .../FeatureSection/CheckDeposit/index.ts | 1 + .../Section/FeatureSection/FeatureSection.tsx | 19 + .../RecordMemoryWithPhoto.style.ts | 52 +++ .../RecordMemoryWithPhoto.tsx | 25 ++ .../RecordMemoryWithPhoto/index.ts | 1 + .../SimpleShare/SimpleShare.style.ts | 52 +++ .../SimpleShare/SimpleShare.tsx | 25 ++ .../FeatureSection/SimpleShare/index.ts | 1 + .../SimpleTransfer/SimpleTransfer.style.ts | 52 +++ .../SimpleTransfer/SimpleTransfer.tsx | 25 ++ .../FeatureSection/SimpleTransfer/index.ts | 1 + .../MainPage/Section/FeatureSection/index.ts | 1 + .../pages/MainPage/Section/MainSection.tsx | 115 ------ .../Section/MainSection/MainSection.style.ts | 71 ++++ .../Section/MainSection/MainSection.tsx | 51 +++ .../MainPage/Section/MainSection/index.ts | 1 + .../pages/MainPage/Section/ReportSection.tsx | 30 -- 32 files changed, 639 insertions(+), 615 deletions(-) create mode 100644 client/src/pages/MainPage/MainPage.style.ts create mode 100644 client/src/pages/MainPage/Nav/index.ts delete mode 100644 client/src/pages/MainPage/Section/DescriptionSection.tsx create mode 100644 client/src/pages/MainPage/Section/DescriptionSection/DescriptionSection.style.ts create mode 100644 client/src/pages/MainPage/Section/DescriptionSection/DescriptionSection.tsx create mode 100644 client/src/pages/MainPage/Section/DescriptionSection/index.ts delete mode 100644 client/src/pages/MainPage/Section/FeatureSection.tsx create mode 100644 client/src/pages/MainPage/Section/FeatureSection/AutoCalculate/AutoCalculate.style.ts create mode 100644 client/src/pages/MainPage/Section/FeatureSection/AutoCalculate/AutoCalculate.tsx create mode 100644 client/src/pages/MainPage/Section/FeatureSection/AutoCalculate/index.ts create mode 100644 client/src/pages/MainPage/Section/FeatureSection/CheckDeposit/CheckDeposit.style.ts create mode 100644 client/src/pages/MainPage/Section/FeatureSection/CheckDeposit/CheckDeposit.tsx create mode 100644 client/src/pages/MainPage/Section/FeatureSection/CheckDeposit/index.ts create mode 100644 client/src/pages/MainPage/Section/FeatureSection/FeatureSection.tsx create mode 100644 client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.style.ts create mode 100644 client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.tsx create mode 100644 client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/index.ts create mode 100644 client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.style.ts create mode 100644 client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.tsx create mode 100644 client/src/pages/MainPage/Section/FeatureSection/SimpleShare/index.ts create mode 100644 client/src/pages/MainPage/Section/FeatureSection/SimpleTransfer/SimpleTransfer.style.ts create mode 100644 client/src/pages/MainPage/Section/FeatureSection/SimpleTransfer/SimpleTransfer.tsx create mode 100644 client/src/pages/MainPage/Section/FeatureSection/SimpleTransfer/index.ts create mode 100644 client/src/pages/MainPage/Section/FeatureSection/index.ts delete mode 100644 client/src/pages/MainPage/Section/MainSection.tsx create mode 100644 client/src/pages/MainPage/Section/MainSection/MainSection.style.ts create mode 100644 client/src/pages/MainPage/Section/MainSection/MainSection.tsx create mode 100644 client/src/pages/MainPage/Section/MainSection/index.ts delete mode 100644 client/src/pages/MainPage/Section/ReportSection.tsx diff --git a/client/src/pages/MainPage/MainPage.style.ts b/client/src/pages/MainPage/MainPage.style.ts new file mode 100644 index 000000000..072466744 --- /dev/null +++ b/client/src/pages/MainPage/MainPage.style.ts @@ -0,0 +1,9 @@ +import {css} from '@emotion/react'; + +export const mainContainer = css({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + width: '100%', +}); diff --git a/client/src/pages/MainPage/MainPage.tsx b/client/src/pages/MainPage/MainPage.tsx index ccd0439fa..911f32457 100644 --- a/client/src/pages/MainPage/MainPage.tsx +++ b/client/src/pages/MainPage/MainPage.tsx @@ -1,45 +1,17 @@ -import {css} from '@emotion/react'; - import useAmplitude from '@hooks/useAmplitude'; import Nav from './Nav/Nav'; -import MainSection from './Section/MainSection'; -import DescriptionSection from './Section/DescriptionSection'; -import FeatureSection from './Section/FeatureSection'; -import CreatorSection from './Section/CreatorSection'; +import {MainSection} from './Section/MainSection'; +import {DescriptionSection} from './Section/DescriptionSection'; +import {FeatureSection} from './Section/FeatureSection'; +import {mainContainer} from './MainPage.style'; const MainPage = () => { const {trackStartCreateEvent} = useAmplitude(); return ( -
-
-
-
-
+
+
From f98c0eacbd748dd163666e4fdfe947308b65e3f4 Mon Sep 17 00:00:00 2001 From: Soyeon Choe <77609591+soi-ha@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:26:04 +0900 Subject: [PATCH 07/14] =?UTF-8?q?refactor=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0=20:=20Preconnect=20to=20requered=20origins=20(#771)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 성능개선 Preconnect to requered origins (font) * fix: storybook에서 pretendard 폰트가 적용되도록 스토리북 preview.tsx 수정 --- client/.storybook/preview.tsx | 23 +++++++++++++++---- client/index.html | 3 +++ client/src/GlobalStyle.ts | 2 -- .../components/Design/theme/GlobalStyle.ts | 2 -- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/client/.storybook/preview.tsx b/client/.storybook/preview.tsx index a3c83803f..7492d7b9d 100644 --- a/client/.storybook/preview.tsx +++ b/client/.storybook/preview.tsx @@ -2,6 +2,7 @@ import type {Preview} from '@storybook/react'; import {HDesignProvider} from '../src/components/Design'; +import {css, Global} from '@emotion/react'; const preview: Preview = { parameters: { @@ -30,11 +31,23 @@ const preview: Preview = { }, }, decorators: [ - Story => ( - - - - ), + Story => { + return ( +
+ + + + +
+ ); + }, ], }; diff --git a/client/index.html b/client/index.html index 7f96b23c5..610cb08a1 100644 --- a/client/index.html +++ b/client/index.html @@ -32,6 +32,9 @@ + + +
diff --git a/client/src/App.tsx b/client/src/App.tsx index 99e09da87..2872860cd 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -5,7 +5,6 @@ import {ReactQueryDevtools} from '@tanstack/react-query-devtools'; import QueryClientBoundary from '@components/QueryClientBoundary/QueryClientBoundary'; import ErrorCatcher from '@components/AppErrorBoundary/ErrorCatcher'; import ToastContainer from '@components/Toast/ToastContainer'; -import KakaoInitializer from '@components/KakaoInitializer/KakaoInitializer'; import AmplitudeInitializer from '@components/AmplitudeInitializer/AmplitudeInitializer'; import {HDesignProvider} from '@HDesign/index'; @@ -26,9 +25,7 @@ const App: React.FC = () => { - - - + diff --git a/client/src/components/Design/components/Dropdown/ButtonBase.tsx b/client/src/components/Design/components/Dropdown/ButtonBase.tsx index 8c1c553d0..0d4072932 100644 --- a/client/src/components/Design/components/Dropdown/ButtonBase.tsx +++ b/client/src/components/Design/components/Dropdown/ButtonBase.tsx @@ -12,14 +12,20 @@ type ButtonBaseProps = DropdownProps & { isOpen: boolean; setIsOpen: React.Dispatch>; dropdownRef: React.RefObject; + onBaseButtonClick?: () => void; }; -const ButtonBase = ({isOpen, setIsOpen, dropdownRef, baseButtonText, children}: ButtonBaseProps) => { +const ButtonBase = ({isOpen, setIsOpen, dropdownRef, baseButtonText, onBaseButtonClick, children}: ButtonBaseProps) => { const {theme} = useTheme(); + const onClick = () => { + if (onBaseButtonClick) onBaseButtonClick(); + setIsOpen(true); + }; + return ( <> - {isOpen && ( diff --git a/client/src/components/Design/components/Dropdown/Dropdown.tsx b/client/src/components/Design/components/Dropdown/Dropdown.tsx index ab5fe23e4..17420ed87 100644 --- a/client/src/components/Design/components/Dropdown/Dropdown.tsx +++ b/client/src/components/Design/components/Dropdown/Dropdown.tsx @@ -7,7 +7,7 @@ import MeatballBase from './MeatballBase'; import ButtonBase from './ButtonBase'; import {dropdownBaseStyle} from './Dropdown.style'; -const Dropdown = ({base = 'meatballs', baseButtonText, children}: DropdownProps) => { +const Dropdown = ({base = 'meatballs', baseButtonText, onBaseButtonClick, children}: DropdownProps) => { const {isOpen, setIsOpen, baseRef, dropdownRef} = useDropdown(); const isDropdownOpen = isOpen && !!baseRef.current; @@ -21,6 +21,7 @@ const Dropdown = ({base = 'meatballs', baseButtonText, children}: DropdownProps) & { export type DropdownProps = { base?: DropdownBase; baseButtonText?: string; + onBaseButtonClick?: () => void; children: React.ReactElement[]; }; diff --git a/client/src/components/KakaoInitializer/KakaoInitializer.tsx b/client/src/components/KakaoInitializer/KakaoInitializer.tsx deleted file mode 100644 index d3ed82bab..000000000 --- a/client/src/components/KakaoInitializer/KakaoInitializer.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import {useEffect} from 'react'; - -const KakaoInitializer = ({children}: React.PropsWithChildren) => { - useEffect(() => { - if (!window.Kakao) return; - - if (!window.Kakao.isInitialized()) { - window.Kakao.init(process.env.KAKAO_JAVASCRIPT_KEY); - } - }, []); - - return children; -}; - -export default KakaoInitializer; diff --git a/client/src/components/ShareEventButton/MobileShareEventButton.tsx b/client/src/components/ShareEventButton/MobileShareEventButton.tsx index 368dd611a..1c13f80e9 100644 --- a/client/src/components/ShareEventButton/MobileShareEventButton.tsx +++ b/client/src/components/ShareEventButton/MobileShareEventButton.tsx @@ -2,6 +2,8 @@ import toast from '@hooks/useToast/toast'; import {Dropdown, DropdownButton} from '@components/Design'; +import initKakao from '@utils/initKakao'; + type MobileShareEventButtonProps = { copyShare: () => Promise; kakaoShare: () => void; @@ -18,7 +20,7 @@ const MobileShareEventButton = ({copyShare, kakaoShare}: MobileShareEventButtonP return (
- + diff --git a/client/src/utils/initKakao.ts b/client/src/utils/initKakao.ts new file mode 100644 index 000000000..508dc9022 --- /dev/null +++ b/client/src/utils/initKakao.ts @@ -0,0 +1,46 @@ +const kakaoScript = { + url: 'https://t1.kakaocdn.net/kakao_js_sdk/2.7.2/kakao.min.js', + integrity: 'sha384-TiCUE00h649CAMonG018J2ujOgDKW/kVWlChEuu4jK2vxfAAD0eZxzCKakxg55G4', + crossOrigin: 'anonymous', + loaded: false, +}; + +const loadKakaoScript = () => { + return new Promise((resolve, reject) => { + if (kakaoScript.loaded) { + resolve(null); + return; + } + + const script = document.createElement('script'); + script.src = kakaoScript.url; + script.integrity = kakaoScript.integrity; + script.crossOrigin = kakaoScript.crossOrigin; + script.async = true; + + script.onload = () => { + kakaoScript.loaded = true; + resolve(null); + }; + + script.onerror = error => { + reject(error); + }; + + document.head.appendChild(script); + }); +}; + +const initKakao = async () => { + try { + await loadKakaoScript(); + + if (window.Kakao && !window.Kakao.isInitialized()) { + window.Kakao.init(process.env.KAKAO_JAVASCRIPT_KEY); + } + } catch (error) { + console.error('Kakao SDK 로드 중 오류 발생:', error); + } +}; + +export default initKakao; From ccf83275c350a728b8b0516b0455b93fb542ded6 Mon Sep 17 00:00:00 2001 From: TaehunLee <85233397+Todari@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:49:17 +0900 Subject: [PATCH 10/14] =?UTF-8?q?refactor:=20v2.1.1=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=ED=95=9C=20=EB=9E=9C=EB=94=A9=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B0=9C=EC=84=A0=20(#777)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 랜딩페이지 개선 * fix: 첫 스크롤이 이상하게 되던 오류 수정 * design: image가 꽉차게 보이도록 변경 * move: CreatorSection 파일 위치 변경 * fix: IOS 환경에서 svg 렌더를 위해 object tag 로 변경 * fix: import 잘못된 오류 수정 * style: lint 적용 * fix: `useMainPageYScroll.ts`를 FeatureSection 내부에서 호출하도록 변경 * refactor: avatar style 분ㄹ * fix: avatar를 button이 아니라 a태그로 감싸도록 변경 * style: lint 적용 및 사용하지 않는 주석과 코드 제거 * refactor: Avatar 부분 리스트 렌더링으로 변경 * style: fix 적용 * fix: object tag에 alt property 제거 --- client/src/GlobalStyle.ts | 1 + client/src/hooks/useMainPageYScroll.ts | 35 +++++++++ .../hooks/useMainSectionBackgroundScroll.ts | 28 ++++++++ client/src/pages/MainPage/MainPage.tsx | 3 + .../pages/MainPage/Section/CreatorSection.tsx | 72 ------------------- .../Section/CreatorSection/Avatar.style.ts | 17 +++++ .../Section/CreatorSection/Avatar.tsx | 24 +++++++ .../CreatorSection/CreatorSection.style.ts | 39 ++++++++++ .../Section/CreatorSection/CreatorSection.tsx | 59 +++++++++++++++ .../AutoCalculate/AutoCalculate.style.ts | 3 +- .../AutoCalculate/AutoCalculate.tsx | 2 +- .../CheckDeposit/CheckDeposit.style.ts | 3 +- .../CheckDeposit/CheckDeposit.tsx | 2 +- .../Section/FeatureSection/FeatureSection.tsx | 16 ++++- .../RecordMemoryWithPhoto.style.ts | 3 +- .../RecordMemoryWithPhoto.tsx | 2 +- .../SimpleShare/SimpleShare.style.ts | 3 +- .../SimpleShare/SimpleShare.tsx | 2 +- .../SimpleTransfer/SimpleTransfer.style.ts | 3 +- .../SimpleTransfer/SimpleTransfer.tsx | 2 +- .../Section/MainSection/MainSection.style.ts | 12 ++-- .../Section/MainSection/MainSection.tsx | 15 ++-- 22 files changed, 247 insertions(+), 99 deletions(-) create mode 100644 client/src/hooks/useMainPageYScroll.ts create mode 100644 client/src/hooks/useMainSectionBackgroundScroll.ts delete mode 100644 client/src/pages/MainPage/Section/CreatorSection.tsx create mode 100644 client/src/pages/MainPage/Section/CreatorSection/Avatar.style.ts create mode 100644 client/src/pages/MainPage/Section/CreatorSection/Avatar.tsx create mode 100644 client/src/pages/MainPage/Section/CreatorSection/CreatorSection.style.ts create mode 100644 client/src/pages/MainPage/Section/CreatorSection/CreatorSection.tsx diff --git a/client/src/GlobalStyle.ts b/client/src/GlobalStyle.ts index 460d50ca2..f2f5c3cc4 100644 --- a/client/src/GlobalStyle.ts +++ b/client/src/GlobalStyle.ts @@ -124,6 +124,7 @@ export const GlobalStyle = css` } body { + overflow-x: hidden; font-family: 'Pretendard', -apple-system, diff --git a/client/src/hooks/useMainPageYScroll.ts b/client/src/hooks/useMainPageYScroll.ts new file mode 100644 index 000000000..912891357 --- /dev/null +++ b/client/src/hooks/useMainPageYScroll.ts @@ -0,0 +1,35 @@ +import {useEffect, useRef} from 'react'; + +const useMainPageYScroll = () => { + const featureSectionRef = useRef(null); + const translateX = useRef(0); + + useEffect(() => { + const handleScroll = () => { + if (featureSectionRef.current) { + const featureSectionTop = featureSectionRef.current.offsetTop; + const scrollY = window.scrollY; + + if (scrollY >= featureSectionTop && translateX.current < window.innerWidth * 4) { + window.scrollTo(0, featureSectionTop); + translateX.current += scrollY - featureSectionTop; + const newTransform = `translateX(calc(200vw - ${translateX.current > 0 ? translateX.current : 0}px))`; + featureSectionRef.current.style.transform = newTransform; + } + if (scrollY <= featureSectionTop && translateX.current > 0) { + window.scrollTo(0, featureSectionTop); + translateX.current += scrollY - featureSectionTop; + const newTransform = `translateX(calc(200vw - ${translateX.current < window.innerWidth * 4 ? translateX.current : window.innerWidth * 4}px))`; + featureSectionRef.current.style.transform = newTransform; + } + } + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + return {featureSectionRef}; +}; + +export default useMainPageYScroll; diff --git a/client/src/hooks/useMainSectionBackgroundScroll.ts b/client/src/hooks/useMainSectionBackgroundScroll.ts new file mode 100644 index 000000000..42ead493a --- /dev/null +++ b/client/src/hooks/useMainSectionBackgroundScroll.ts @@ -0,0 +1,28 @@ +import {useEffect, useState} from 'react'; +import {useNavigate} from 'react-router-dom'; + +import {ROUTER_URLS} from '@constants/routerUrls'; + +const useMainSectionBackgroundScroll = (trackStartCreateEvent: () => void) => { + const navigate = useNavigate(); + + const handleClick = () => { + trackStartCreateEvent(); + navigate(ROUTER_URLS.createEvent); + }; + + const [isVisible, setIsVisible] = useState(true); + + useEffect(() => { + const handleScroll = () => { + setIsVisible(window.scrollY <= window.innerHeight); + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + return {isVisible, handleClick}; +}; + +export default useMainSectionBackgroundScroll; diff --git a/client/src/pages/MainPage/MainPage.tsx b/client/src/pages/MainPage/MainPage.tsx index 911f32457..f6663f618 100644 --- a/client/src/pages/MainPage/MainPage.tsx +++ b/client/src/pages/MainPage/MainPage.tsx @@ -1,10 +1,12 @@ import useAmplitude from '@hooks/useAmplitude'; +import useMainPageYScroll from '@hooks/useMainPageYScroll'; import Nav from './Nav/Nav'; import {MainSection} from './Section/MainSection'; import {DescriptionSection} from './Section/DescriptionSection'; import {FeatureSection} from './Section/FeatureSection'; import {mainContainer} from './MainPage.style'; +import CreatorSection from './Section/CreatorSection/CreatorSection'; const MainPage = () => { const {trackStartCreateEvent} = useAmplitude(); @@ -15,6 +17,7 @@ const MainPage = () => { +
); }; diff --git a/client/src/pages/MainPage/Section/CreatorSection.tsx b/client/src/pages/MainPage/Section/CreatorSection.tsx deleted file mode 100644 index fd1c8c2d9..000000000 --- a/client/src/pages/MainPage/Section/CreatorSection.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import {css} from '@emotion/react'; - -import Text from '@components/Design/components/Text/Text'; - -const CreatorSection = () => { - return ( -
-
-
- - - 누구와도 간편하게 정산하세요 - -
-
-
- ); -}; - -export default CreatorSection; diff --git a/client/src/pages/MainPage/Section/CreatorSection/Avatar.style.ts b/client/src/pages/MainPage/Section/CreatorSection/Avatar.style.ts new file mode 100644 index 000000000..e03df7d02 --- /dev/null +++ b/client/src/pages/MainPage/Section/CreatorSection/Avatar.style.ts @@ -0,0 +1,17 @@ +import {css} from '@emotion/react'; + +export const avatarStyle = css({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '0.5rem', + '@media (min-width: 1200px)': { + gap: '1rem', + }, +}); + +export const avatarImageStyle = css({ + width: '100%', + height: '100%', + borderRadius: '25%', +}); diff --git a/client/src/pages/MainPage/Section/CreatorSection/Avatar.tsx b/client/src/pages/MainPage/Section/CreatorSection/Avatar.tsx new file mode 100644 index 000000000..d559325e4 --- /dev/null +++ b/client/src/pages/MainPage/Section/CreatorSection/Avatar.tsx @@ -0,0 +1,24 @@ +import {css} from '@emotion/react'; + +import {Text} from '@components/Design'; + +import {avatarImageStyle, avatarStyle} from './Avatar.style'; + +interface Props { + imagePath: string; + name: string; + navigateUrl: string; +} + +const Avatar = ({imagePath, name, navigateUrl}: Props) => { + return ( + + + + {name} + + + ); +}; + +export default Avatar; diff --git a/client/src/pages/MainPage/Section/CreatorSection/CreatorSection.style.ts b/client/src/pages/MainPage/Section/CreatorSection/CreatorSection.style.ts new file mode 100644 index 000000000..55ee71a55 --- /dev/null +++ b/client/src/pages/MainPage/Section/CreatorSection/CreatorSection.style.ts @@ -0,0 +1,39 @@ +import {css} from '@emotion/react'; + +export const sectionStyle = css({ + display: 'flex', + padding: '4rem 0', + minHeight: '100vh', + width: '100vw', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#000000 ', + gap: '2rem', +}); + +export const partStyle = css({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '2rem', +}); + +export const avatarContainerStyle = css({ + display: 'grid', + gridTemplateColumns: 'repeat(2, 1fr)', + gridTemplateRows: 'repeat(2, 1fr)', + width: '100%', + padding: '0 4rem', + gap: '1rem', + maxWidth: '1200px', + '@media (min-width: 640px)': { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + gap: '2rem', + }, + '@media (min-width: 1024px)': { + gap: '6rem', + }, +}); diff --git a/client/src/pages/MainPage/Section/CreatorSection/CreatorSection.tsx b/client/src/pages/MainPage/Section/CreatorSection/CreatorSection.tsx new file mode 100644 index 000000000..6bd279fed --- /dev/null +++ b/client/src/pages/MainPage/Section/CreatorSection/CreatorSection.tsx @@ -0,0 +1,59 @@ +import {css} from '@emotion/react'; +import {useNavigate} from 'react-router-dom'; + +import Text from '@components/Design/components/Text/Text'; + +import Avatar from './Avatar'; +import {avatarContainerStyle, partStyle, sectionStyle} from './CreatorSection.style'; + +const CreatorSection = () => { + const frontEndDevelopers = [ + {imagePath: 'todari', name: '토다리', navigateUrl: 'https://github.com/Todari'}, + {imagePath: 'cookie', name: '쿠키', navigateUrl: 'https://github.com/jinhokim98'}, + {imagePath: 'soha', name: '소하', navigateUrl: 'https://github.com/soi-ha'}, + {imagePath: 'weadie', name: '웨디', navigateUrl: 'https://github.com/pakxe'}, + ]; + const backEndDevelopers = [ + {imagePath: '2sang', name: '이상', navigateUrl: 'https://github.com/kunsanglee'}, + {imagePath: 'baekho', name: '백호', navigateUrl: 'https://github.com/Arachneee'}, + {imagePath: 'mangcho', name: '망쵸', navigateUrl: 'https://github.com/3Juhwan'}, + {imagePath: 'gamja', name: '감자', navigateUrl: 'https://github.com/khabh'}, + ]; + + return ( +
+ + 행동대장을 만든 행동대장들 + +
+ + FRONTEND + +
+ {frontEndDevelopers.map(developer => ( + + ))} +
+
+
+ + BACKEND + +
+ {backEndDevelopers.map(developer => ( + + ))} +
+
+
+ ); +}; + +export default CreatorSection; diff --git a/client/src/pages/MainPage/Section/FeatureSection/AutoCalculate/AutoCalculate.style.ts b/client/src/pages/MainPage/Section/FeatureSection/AutoCalculate/AutoCalculate.style.ts index d847764e8..417901162 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/AutoCalculate/AutoCalculate.style.ts +++ b/client/src/pages/MainPage/Section/FeatureSection/AutoCalculate/AutoCalculate.style.ts @@ -7,7 +7,6 @@ export const sectionStyle = css({ flexDirection: 'column', justifyContent: 'center', alignItems: 'center', - backgroundColor: '#ffffff', }); export const articleStyle = css({ @@ -28,6 +27,8 @@ export const articleStyle = css({ }); export const imageStyle = css({ + objectFit: 'cover', + aspectRatio: '1/1', minWidth: '15rem', maxWidth: '25rem', width: '100%', diff --git a/client/src/pages/MainPage/Section/FeatureSection/AutoCalculate/AutoCalculate.tsx b/client/src/pages/MainPage/Section/FeatureSection/AutoCalculate/AutoCalculate.tsx index b090c4713..697bffe76 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/AutoCalculate/AutoCalculate.tsx +++ b/client/src/pages/MainPage/Section/FeatureSection/AutoCalculate/AutoCalculate.tsx @@ -18,7 +18,7 @@ const AutoCalculate = () => { return (
- 차등 정산 계산을 쉽게 해주는 UI 이미지 +
계산은 저희가 알아서 해드려요 diff --git a/client/src/pages/MainPage/Section/FeatureSection/CheckDeposit/CheckDeposit.style.ts b/client/src/pages/MainPage/Section/FeatureSection/CheckDeposit/CheckDeposit.style.ts index 08e4619c0..4cd328d85 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/CheckDeposit/CheckDeposit.style.ts +++ b/client/src/pages/MainPage/Section/FeatureSection/CheckDeposit/CheckDeposit.style.ts @@ -7,7 +7,6 @@ export const sectionStyle = css({ flexDirection: 'column', justifyContent: 'center', alignItems: 'center', - backgroundColor: '#DFC1FF', }); export const articleStyle = css({ @@ -36,6 +35,8 @@ export const textContainerStyle = css({ }); export const imageStyle = css({ + objectFit: 'cover', + aspectRatio: '1/1', minWidth: '20rem', maxWidth: '30rem', width: '100%', diff --git a/client/src/pages/MainPage/Section/FeatureSection/CheckDeposit/CheckDeposit.tsx b/client/src/pages/MainPage/Section/FeatureSection/CheckDeposit/CheckDeposit.tsx index 73995dd1c..2cc7c4fd7 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/CheckDeposit/CheckDeposit.tsx +++ b/client/src/pages/MainPage/Section/FeatureSection/CheckDeposit/CheckDeposit.tsx @@ -28,7 +28,7 @@ const CheckDeposit = () => { 간편하게 관리할 수 있어요.`}
- 입금 확인 기능 UI 이미지 + ); diff --git a/client/src/pages/MainPage/Section/FeatureSection/FeatureSection.tsx b/client/src/pages/MainPage/Section/FeatureSection/FeatureSection.tsx index 60d34fd95..7c16517f8 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/FeatureSection.tsx +++ b/client/src/pages/MainPage/Section/FeatureSection/FeatureSection.tsx @@ -1,5 +1,8 @@ +import {css} from '@emotion/react'; import {useRef} from 'react'; +import useMainPageYScroll from '@hooks/useMainPageYScroll'; + import {SimpleShare} from './SimpleShare'; import {AutoCalculate} from './AutoCalculate'; import {CheckDeposit} from './CheckDeposit'; @@ -7,16 +10,25 @@ import {SimpleTransfer} from './SimpleTransfer'; import {RecordMemoryWithPhoto} from './RecordMemoryWithPhoto'; const FeatureSection = () => { + const {featureSectionRef} = useMainPageYScroll(); const simpleTransferRef = useRef(null); return ( - <> +
- +
); }; diff --git a/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.style.ts b/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.style.ts index 42f4781ae..cac040ec4 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.style.ts +++ b/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.style.ts @@ -7,7 +7,6 @@ export const sectionStyle = css({ flexDirection: 'column', justifyContent: 'center', alignItems: 'center', - backgroundColor: '#C1CFFF', }); export const articleStyle = css({ @@ -36,6 +35,8 @@ export const textContainerStyle = css({ }); export const imageStyle = css({ + objectFit: 'cover', + aspectRatio: '1/1', minWidth: '20rem', maxWidth: '25rem', width: '100%', diff --git a/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.tsx b/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.tsx index 5ecaa8230..7a3d7f44b 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.tsx +++ b/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.tsx @@ -30,7 +30,7 @@ const RecordMemoryWithPhoto = ({targetRef}: RecordMemoryWithPhotoProps) => { 정산은 투명하게, 추억은 오래오래 간직할 수 있어요.`} - 행사 사진을 저장할 수 있는 UI 이미지 + ); diff --git a/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.style.ts b/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.style.ts index 9d3623870..ad2279f7a 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.style.ts +++ b/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.style.ts @@ -7,7 +7,6 @@ export const sectionStyle = css({ flexDirection: 'column', justifyContent: 'center', alignItems: 'center', - backgroundColor: '#FFA5B8', }); export const articleStyle = css({ @@ -36,6 +35,8 @@ export const textContainerStyle = css({ }); export const imageStyle = css({ + objectFit: 'cover', + aspectRatio: '1/1', minWidth: '15rem', maxWidth: '25rem', width: '100%', diff --git a/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.tsx b/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.tsx index 5e40a8eda..ae2b5b607 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.tsx +++ b/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.tsx @@ -28,7 +28,7 @@ const SimpleAccount = () => { 복잡한 절차 없이, 빠르게 정산을 마치세요.`} - 간편한 공유를 설명하는 UI 이미지 + ); diff --git a/client/src/pages/MainPage/Section/FeatureSection/SimpleTransfer/SimpleTransfer.style.ts b/client/src/pages/MainPage/Section/FeatureSection/SimpleTransfer/SimpleTransfer.style.ts index 368328043..5c996369f 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/SimpleTransfer/SimpleTransfer.style.ts +++ b/client/src/pages/MainPage/Section/FeatureSection/SimpleTransfer/SimpleTransfer.style.ts @@ -7,7 +7,6 @@ export const sectionStyle = css({ flexDirection: 'column', justifyContent: 'center', alignItems: 'center', - backgroundColor: '#ffffff', }); export const articleStyle = css({ @@ -36,6 +35,8 @@ export const textContainerStyle = css({ }); export const imageStyle = css({ + objectFit: 'cover', + aspectRatio: '1/1', minWidth: '20rem', maxWidth: '30rem', width: '100%', diff --git a/client/src/pages/MainPage/Section/FeatureSection/SimpleTransfer/SimpleTransfer.tsx b/client/src/pages/MainPage/Section/FeatureSection/SimpleTransfer/SimpleTransfer.tsx index 380c3a804..dcb3cc425 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/SimpleTransfer/SimpleTransfer.tsx +++ b/client/src/pages/MainPage/Section/FeatureSection/SimpleTransfer/SimpleTransfer.tsx @@ -18,7 +18,7 @@ const SimpleTransfer = ({targetRef}: SimpleTransferProps) => { return (
- 간편 송금을 설명하는 UI 이미지 +
몇 번의 클릭으로 송금 완료! diff --git a/client/src/pages/MainPage/Section/MainSection/MainSection.style.ts b/client/src/pages/MainPage/Section/MainSection/MainSection.style.ts index 8db38b97d..4afea6c80 100644 --- a/client/src/pages/MainPage/Section/MainSection/MainSection.style.ts +++ b/client/src/pages/MainPage/Section/MainSection/MainSection.style.ts @@ -47,12 +47,16 @@ export const backgroundStyle = css({ height: '100vh', top: 0, zIndex: -1, + backgroundColor: '#000000', }); -export const backgroundImageStyle = css({ - height: '100vh', - objectFit: 'cover', -}); +export const backgroundImageStyle = (isVisible: boolean) => + css({ + objectFit: 'cover', + height: '100vh', + width: '100vw', + opacity: isVisible ? 1 : 0, + }); export const sectionStyle = css({ display: 'flex', diff --git a/client/src/pages/MainPage/Section/MainSection/MainSection.tsx b/client/src/pages/MainPage/Section/MainSection/MainSection.tsx index e6e476336..8b2491e60 100644 --- a/client/src/pages/MainPage/Section/MainSection/MainSection.tsx +++ b/client/src/pages/MainPage/Section/MainSection/MainSection.tsx @@ -1,11 +1,9 @@ -import {useNavigate} from 'react-router-dom'; - import Button from '@HDesign/components/Button/Button'; import Text from '@HDesign/components/Text/Text'; -import {Icon} from '@components/Design'; +import useMainSectionBackgroundScroll from '@hooks/useMainSectionBackgroundScroll'; -import {ROUTER_URLS} from '@constants/routerUrls'; +import {Icon} from '@components/Design'; import { animateWithDelay, @@ -21,17 +19,12 @@ type MainSectionProps = { }; const MainSection = ({trackStartCreateEvent}: MainSectionProps) => { - const navigate = useNavigate(); - - const handleClick = () => { - trackStartCreateEvent(); - navigate(ROUTER_URLS.createEvent); - }; + const {isVisible, handleClick} = useMainSectionBackgroundScroll(trackStartCreateEvent); return (
- +
{`행동대장으로 From 86b36ca4903c9570814c9f7172c8d894437c1f9d Mon Sep 17 00:00:00 2001 From: JinHo Kim <81083461+jinhokim98@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:49:47 +0900 Subject: [PATCH 11/14] =?UTF-8?q?fix:=20=EC=9D=B4=EB=B2=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=99=88=20=ED=8E=98=EC=9D=B4=EC=A7=80=EC=97=90=20=EC=9E=88?= =?UTF-8?q?=EC=9D=84=20=EB=95=90=20=ED=86=A0=ED=81=B0=EC=9D=B4=20=EC=9E=88?= =?UTF-8?q?=EC=96=B4=EB=8F=84=20=EC=A7=80=EC=B6=9C=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=91=EA=B7=BC=20=EB=B6=88=EA=B0=80=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20(#781)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/StepList/Step.tsx | 2 +- client/src/pages/EventPage/HomePage/HomePage.tsx | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/client/src/components/StepList/Step.tsx b/client/src/components/StepList/Step.tsx index aead2f37c..b8c0813be 100644 --- a/client/src/components/StepList/Step.tsx +++ b/client/src/components/StepList/Step.tsx @@ -1,5 +1,5 @@ /** @jsxImportSource @emotion/react */ -import {useNavigate} from 'react-router-dom'; +import {useMatch, useNavigate} from 'react-router-dom'; import {css} from '@emotion/react'; import Amount from '@components/Design/components/Amount/Amount'; diff --git a/client/src/pages/EventPage/HomePage/HomePage.tsx b/client/src/pages/EventPage/HomePage/HomePage.tsx index 4314d9592..f8a205ef6 100644 --- a/client/src/pages/EventPage/HomePage/HomePage.tsx +++ b/client/src/pages/EventPage/HomePage/HomePage.tsx @@ -1,6 +1,6 @@ import type {EventPageContextProps} from '../EventPageLayout'; -import {useNavigate, useOutletContext} from 'react-router-dom'; +import {useMatch, useNavigate, useOutletContext} from 'react-router-dom'; import StepList from '@components/StepList/Steps'; import useRequestGetSteps from '@hooks/queries/step/useRequestGetSteps'; @@ -13,10 +13,14 @@ import {Icon, Tab, Tabs, Title} from '@HDesign/index'; import getEventIdByUrl from '@utils/getEventIdByUrl'; +import {ROUTER_URLS} from '@constants/routerUrls'; + import {receiptStyle} from './HomePage.style'; const HomePage = () => { const {isAdmin, eventName} = useOutletContext(); + const isInHomePage = useMatch(ROUTER_URLS.home) !== null; + const {steps} = useRequestGetSteps(); const {totalExpenseAmount} = useTotalExpenseAmountStore(); const {images} = useRequestGetImages(); @@ -38,7 +42,7 @@ const HomePage = () => { /> } /> - } /> + } />
); From 27e2ee3dc9f63de17a65c18ec9645814c43fca83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=A7=84=ED=98=B8?= Date: Wed, 23 Oct 2024 16:51:09 +0900 Subject: [PATCH 12/14] =?UTF-8?q?fix:=20cypress=EC=97=90=EC=84=9C=EB=8A=94?= =?UTF-8?q?=20sideEffects=EB=A5=BC=20tree=20shaking=ED=95=98=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/package.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/package.json b/client/package.json index 2dda6ac09..1a69f8574 100644 --- a/client/package.json +++ b/client/package.json @@ -3,7 +3,13 @@ "version": "1.0.0", "description": "", "type": "module", - "sideEffects": false, + "sideEffects": [ + "cypress/**/*", + "*.cy.ts", + "*.cy.js", + "*.spec.ts", + "*.spec.js" + ], "scripts": { "prod": "NODE_ENV=production webpack server --open --config webpack.prod.mjs", "dev": "NODE_ENV=development webpack server --open --config webpack.dev.mjs", From 1871cd4f39279882c08e835a45599908a7247645 Mon Sep 17 00:00:00 2001 From: Pakxe <64801796+pakxe@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:03:18 +0900 Subject: [PATCH 13/14] =?UTF-8?q?refactor:=20=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0=20:=20Serve=20images=20in=20next-gen=20forma?= =?UTF-8?q?ts=20=20(#784)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 랜딩페이지 개선 * fix: 첫 스크롤이 이상하게 되던 오류 수정 * design: image가 꽉차게 보이도록 변경 * move: CreatorSection 파일 위치 변경 * fix: IOS 환경에서 svg 렌더를 위해 object tag 로 변경 * fix: import 잘못된 오류 수정 * style: lint 적용 * chore: webp포맷의 이미지 추가 * chore: webp타입 추가 * feat: 이미지 호스팅 경로를 생성하는 함수 구현 * feat: src, fallbackSrc를 지정해 대체 이미지를 보여줄 수 있는 Image 컴포넌트 구현 * feat: Image컴포넌트를 사용해 이미지를 불러오도록 수정 * feat: Avatar 컴포넌트에서 이미지 경량화를 위한 Image 컴포넌트를 사용 * chore: 사용하지 않고있는 토스 아이콘 제거 * feat: 용량 큰 이미지를 사용하는 곳에선 webp이미지를 사용하도록 수정 * feat: 이미지 경로를 받아오는 함수가 svg포맷도 받아들일 수 있도록 수정 * fix: 이미지 크기가 넘쳐버리지 않도록 width 속성 추가 * feat: 은행 목록 이미지를 webp로 이미지 호스팅 서버에서 가져오도록 수정 * chore: 사용하지 않는 이미지 제거 * feat: 흔듯콘을 webp로 불러오도록 함 * fix: 흔듯콘에 width부여 * design: 행동개시 행댕이의 크기가 너무 커지지 않도록 maxWidth 속성 추가 --------- Co-authored-by: 이태훈 --- client/src/assets/image/Toss_Symbol_Primary.png | Bin 5800 -> 0 bytes client/src/assets/image/banksprite.png | Bin 48163 -> 0 bytes .../components/BankSelect/BankSelect.style.ts | 4 ++-- .../Design/components/Icon/Icon.style.ts | 1 - .../components/Design/components/Icon/Icon.tsx | 10 +++++++--- .../Design/components/Image/Image.tsx | 15 +++++++++++++++ client/src/components/Logo/Logo.style.ts | 5 +++++ client/src/components/Logo/RunningDogLogo.tsx | 12 ++++++++++-- client/src/components/Logo/StandingDogLogo.tsx | 6 +++++- client/src/global.d.ts | 1 + client/src/pages/MainPage/MainPage.tsx | 1 - .../MainPage/Section/CreatorSection/Avatar.tsx | 2 -- .../Section/CreatorSection/CreatorSection.tsx | 1 - .../DescriptionSection/DescriptionSection.tsx | 13 +++++++++++-- .../RecordMemoryWithPhoto.style.ts | 2 +- .../RecordMemoryWithPhoto.tsx | 4 +--- .../SimpleShare/SimpleShare.style.ts | 2 +- .../FeatureSection/SimpleShare/SimpleShare.tsx | 2 +- .../Section/MainSection/MainSection.tsx | 10 +++++++++- client/src/utils/getImageUrl.ts | 5 +++++ 20 files changed, 74 insertions(+), 22 deletions(-) delete mode 100644 client/src/assets/image/Toss_Symbol_Primary.png delete mode 100644 client/src/assets/image/banksprite.png create mode 100644 client/src/components/Design/components/Image/Image.tsx create mode 100644 client/src/utils/getImageUrl.ts diff --git a/client/src/assets/image/Toss_Symbol_Primary.png b/client/src/assets/image/Toss_Symbol_Primary.png deleted file mode 100644 index 073a2692474bb5fe6dcf8661c078748862760aa7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5800 zcmV;Z7FX$sP)ULopj>z*`kQ7kpX+eWxNITsFPt!XRF@zALI1%Au9ET5ZsGpWJK7C6 zRBYc)^SyI$a*~hFv?eM+kw`gkO>ool6Hq zHp(+YO^<934i3j0A=^oF%eC1{G)sP6hxi3Lc@Y&o0xjVKL@a#)T5)5tOt@5sG%`Be zZQlqSQ-r+XANM`uwyTp5Bg3y{DW65fOptj=Mor*S?j$l-p)3iEpC(+Y0}&36SVs;4 zN1qTaNOsEko0iYJh$m4;y~K#M8SFTd znHdXVI?iRLpXEYT(Oid`_pp@C1L@5lMFKgp`L7LsKy}#;+QD#qLGJL$e1uo!2~4RO zXh%)0xkQ|?EgW^}u#Hcvi-?~DhVM3<_rFaC#3Oe&?743R{mo{0>IIws8^AUwpGlY{ zDrduw=-AsPMT8!_?mNz0dUT!(qxCZ>Wa6@!H|5btGTQhB(=! z5X~!772mC?4G_Q-A*;muAb4A7N}(NMe0i}bwI)1}0)gd66%XB#B3T(?+c^tZJf#m$ zNYNGoB+KvR9@TZAULFKV44rZhoVm%teC7vRK6wweZx12LhG0Vo*-o0^2dtD?i?W>k8$$LwqRz# zdg&51Vw(FBF(E*WENZ!poa1#l+@FpIPd&I%^BlAPBKrX;cU+sjOh@^r(}53bgJ!fM zs`58z3!uXQl&4DKx^gD<)9Qe9nNX(?Bd|RK%?OvmZ&|fz*ca_}d2ucGn--gM^*$A( z&I{!7p6lJbY4+qlI_vWB86XdNfFCj;7hg3vDNprd*-&1QCy<~WB^h0#5z&Zij=~tN z?)efmO>30UoJdv(I$~ztdV!{q@T091dN`LiHrH_+4YZ;Sr$G3@9^B{8dgtoT9+EJz z0fcC8=2O{_Z*U2FniUPM+ujnf@H@4l@xARftNDjC&0c|g1f&B$jO|>nrV%yG=pcf* z4?yBg-@q1>La(FEeA^Q3#E_9>V86F^A(w=atm4%YmLH(&&A%iiCYZ$(Y<>{zlaOJ zeZT|$pfu#7ui!cP$i2r6cjRQ7WqY+Mq%5Uch7hG1&H999((N;NnDTxc`WsOJ- zs2`VDBjin1hH=lax z@}&nN-vcJZF?p*?&G{}XI?)tlG#TtBg>};w)IGMtO%t#lDWM2~WEpTtaWgTX`Y@P| z(42KY5l2>`Otg8jB4IbSGSX`F*`KEm293aoynoxJT$)tA2V}BdfAz|VF5`RhF@&}7 zwn{?;;j}>1%vM>@00OCguPDq~^}AY~@!YVM;U&@)z-6v1SWDZ)rZj3p&GbdV0|AJz zi~xx>eiP1+EuT_UcHUezxB$S79a3rGfHcI~nYZU-JU2uewUOL7M&lT$t~WI*j<^_& zz?$|%zwd%ee3q9k_t{`dHNrzgRG2Un=W6DuxrH){Rz2;Ft-h857FjX|KR>CACnn_e zS3kZ8QfNn0OCkZ*8XQls3Kb7(Pu6X9Y#yo`wUG{hYzS|IU;vs<^U@@} z{2sJ3@*CRaBbwji0od@TMO%9io^#qIdrz1|&J&v~YqnmH4S5Q4)P&a>88vmSe`l=n zsLI0t3c{#Q9G1>9Bo|9^{d(T`ULi8ehCn`65DKaVGVvMU&R8{SU(`-C+vkclG_D7e zT3%~b-;ja8oRnoe3BaDVWb2ri5OsWwOR*TV!@CY59ucL3lZdERYwI1P0HWSbtDzUZ z`d+F4wh&5bLSmf`i2wz%A)%=Qh{Ay@?2IYW_#LyG*iVIa1fe(m6^qj`flZQ{h&2b? zVtU-NM#&l`BIMG4;nL{IEwOT&{I3`fbcj&DV*{ z!hNxUKGLlB>1cf~=wFQMwDZXeSEEPaRNaN$60RE^^9Um-z~VJsw?Aq~-Hkg|jcT z!dQTF{wK}>=_;VPD}PhB=mh9M5TXRbgZS3kV3sLRqKM|ACY3&X4X4kTl&HVO;M+3` zyWa>4n2->cqa+}1WPEN-VTa>5`sygx@;k-oCB0)eio!N}voo_$RO^6jmC)Sz`3w+6 z2S62jYf-I$>U|1O*8(q8MoowuOei05U0W>^`r7pR1fhH^+w|n_j-IzL5h2zfQ9)8g zkD=p@zDH)HX|@QIq;%U^RMAWrae){qCTRpzeU#PD{Y94$*^r>qctjTWX_Mk@l!BBs zwZI<-J@MwOX1+qZ8=-Tpvd|AAg_ye^zW&sSUa6bRsl%+0T&X5bzPXJ;e%DmlMk8zU zlc;Q5u0~Elh^UXU!~)PN^i2o!6GDR2!lJ|my^#bX72yru!r{hEDNB0uW7E3Cy{I`m)c3UzxmiI>8X_+cd~I;*#UKys zw~`gWr3%tXsJ|AzmP`jNVWrmP%ZSmAC=#CaewCg+4v|9}BTbH<@e~2ZL%qS`ZR-j+ z!bGw0^77_KMEIcCj%-0gRXb4oFu3&D3j6RK3(p zR-*|`wT;`;=nIR+tTljI$<%Nud^f!Eftx4)kCllC`Of=fB*xqhBcrgQurgn5v>M;& zM^nRE^O!ZZFflb2usuPZ5ZQoG=gNU!^K*j8$akURS~L)9{c94|7);IcUTL_?_4w6w z8qmVT*P>GIPrmZ<>S>Hky*v$AX9RT^5pwqfb3w~#Ue%00h ztaJZRAe4{WUK>=Qtb!1J`jKyJerkedl$e;1Z}0B^M6CI`>7jasENDuj=Pg03-RI7e zO(L4D5R5$+$SK-L7|pPlRvg$-g{Srid=wI9MeDC4i~zw$aJe?YZe zbdcsBmmVG$i+Ttc;`sHfLnvAZs&t6^Lt_-8n1j#}=>UF)&UVn(E$1R)T5H)#?gnH6Rh z0MirXL%@x}kkMT;)UKVctMi%)%Bpk~M_1S40J}7$03d|)@R0|5OW#?m>iyxMmXh9b z#imEcSbn#$xJj&JWv;G6OFGS2Q_!(PZcTOT=x_E8J{uAFIC^{;EmLXDlZwVk2))WS ztr3mH^TlRcOV`sHExB0!{jP6p`qIIes(^!D{eH*$PkbCq-YS-VCq^DGXV39pTr}q@ zO+VTY0I-fPc45~EdEf8+ztyh1u3ntIMyIk`_>?hG^+I;hyb%oexY~cB=#e<-!=zQGZL81H$>+N2m7{A5!HT;qWXf=348YP)|>^mJ%U4 z50vr!{0YEGFELntgxGyo#^F<|&|HNo)!MGIZtjN+9Pb-}P6Z5$z6rmWTMF_8qy?l02@b9#q%MVZ$v_kU_-iT_-<$2r z@85gp1yCyY%msTL4v{;rT`Qu0l#jj_-6rFh-oC^nm#R(Ml%y3p`d|U%Ud{O zKCnyXzk4VVRTmCBAr&Yu_&v;SS-@N}9?Z>KGn=VClho&`V4J{f6{md~SSp;5V;J45 z&OT*eA61j>d)}+({w*K#jK{w3u>A0VE=dK2c~YWytSCP4)j~UH0265lr0%287JhlI z@E~QlYydl}w;J*{cfNG#UTl|W*~7-*2oiE=c++*mQ`MN;hEMU?k9|MGVD~CC%L_kI z_zWfdx?X@Zo)B0_c%dD}i?|u#bwqPbGQ?tJd%R(3ant2@T_cC>4>gbepI}ailyZgz z`{{si3CriN!YB({y+;6VWCY zb>3^?_#lK7{B!agH|9(>Bv=q4G5U}^ABcj{-%4sD$7TyDF3Auwh~6U<6T!kEGZ0dnl}CQU zA``*g#>l@rNxpjDxm)i#8h&Cu92bOuGYk3HZPF}*h>wWk7kq?#XEh(1<3E3qsUk-ldq+Jb{>8K=H4sdDFyF8(_m3_`5 zjBmZ?i>E$)lv{pKIQ9tQMux_47X<%H;)iN*k2Si>aJ=(}r=Pz2 zSaZ?`iesM;09%(f-@NzH!3EanBGjzFCh05|#;Z%Gj_&;%Nse~=rkG-iDW;fWiYca; mVu~rIm|}`4rkLXYBmNIbBJ~Ny%wuB!00007ax?PG*deb>GIvrCIQ+ibrY$pj9pU3g*ZzQ$yv7Bak~ z%l>QTuYIImzB&~*67pS+AKLRoM|+;E=@yk+z z)N_Eb1Y{?}3)9_D z@_HVNyNk-Iit{SUEe?SKH8=&dNd7T?(gLl^Sj%^-IQsmlBata#O2?w^14{a(y)W1N z+y$0vtTc6_rqVuWe}kf&$BbU}Khk9Ej1Dg_13~C_tL0@qF%Cz+OIWRcmoiroYI!gY zYfuJ`ox%{PPa`5CT)aIPk&z)%wGE^rR}$&3O)b2skoM@Myx$Cl$QZYn7m@9az)f_u zfb11eUjv%TV@NkY`~c|smwIlcr<~>e4pX|%G!FOy!)~@`P9jv$eJfh*aPXU%&GK?m zjX9|>N@f1fDIz#0mb-765UqP=+nPA=R+_U5JiFTQi{a(DVl!A8{ch4f$onAK+z#(| zJ~{l4v>4t8h|%0L4lS~{q<{9RnH7ldvns*?O!6yRr-mi|JwkPs zS;MO@4>*9XzCN7r?%I#6W&0mfwn4ib6v>Kaz+wx(bq{umbq!6!Px`=Lh~6jVW%+rH z%_X&si(!rUVDg+d)zWrO_k+1Ehi0mhx}#s)>KhAZYk$2BdV8qpGt9FVg}6R!rr^g; z(V%GzbTT%{MLIWpH{Woc0-)&hUp_wfknNP$?^?II8gX{c)}YtBJ9YGN7cfhSK9Nlz zdVAsYfdDlbc=30>j8JEX!tSLUPpFu8xW8*CDmEJ9u+{%CO0T8;5={Mbc$RQjnQ~p_ zi-O;m?mg()crsv1jP!+2A;tw9mD(b;~tFqFjIih7?UtO^z6N-%0TloGuW z4@b*b8Syd1{@Td?aueTF0Et=*YC<1o{M5e0l<4`fzkA5kEBdw{Npb!-8K7I9Ob~j@ zj;W=KHgwkEUy5$lAZ>%Uyc0YU7XpPal<&#M-NG=_7;sE)3`BT>{O3Xkv8lC4`O7ScPI?><6(lR3 zrwWwPhUA&L)L9EV_xSQHF2B@hTY!^Z=e-E!qCobwFu;XE{0xYr<1i?gcaRa%nb6*f zI_2hmvsV{+J`#()nfHEkqVV2KHPIS-S?sIq~y4vNM=8N>p zfPU@CknOk04Lz^OikAUKQ^s}HbvqIAgW*lUfT6Cfg{D^M^wfu02PLxUlF|Wwe$K!U zf@yg+4cc|}i|^3WLFN6k3g&jZqf|CAedAPPW!buNeM z;j#t+{00XWnvRLj%3K06{KMdK^E3W~hUJ^>uX#RrftmMW95y~6)TJoQT>KN8lEz&Z zB|bKt$Xc$1r12XG+;}Q+TEWVSfXw>DaH#eVog$2&i(@r$g3X)+%8) z*hp)k#)gZhi$qUu7Pu7_q+`-waGdT+9>k490*z52IjB%x@~snxf*AsJ9}e5j1;en&Y8F_KAmoCv2-mEh60XSpjpL zWxb=Sy#92f)y4rKJR48E@KHXUKE!(+0g!+(^oKPvZs8>&rui2aD1?5JFcl%R?-BaO zkQw43Kpv+cc?=NA#+3Scq4gOnh7?|YZg>ovq= zop<8z)H^i9$ZUO)4USF?m3zCP47avz?Z|N~yHoq9U5@1UX+_ofeO?hk{p^kOJL=hU zc1fO<^aP6IbP~n+yZ44i#q7-7z&s>f$;ld{Qhd-`#hH*#=#B0}i%&C-i$X*y%}%(^ zxGrbCyY{M+yG_!xVHVu!7ceo!&F6s?jz#e1z|D?!VyL-QX6~2g^4!b`?EBt~mP}~u zPn?gb2R)HP@_}_wVtd74?6JEoSy@?j zi&Xf18=AWEQ(ne6{%_b_AF~J{&ZP>60zL>wQ31!908}9bJ7kkzS4Rn$5G{4zRyLAf zm+LIWXi>mykfM<>q*IKd7vc!)#M!)V$ zIsUwHQQ7R){lO2&o#~$#-inH&#{op1=e=KmFI&;ZsMjf$_56-?^aO{V*xq=ZlIQ~> z15q3-m)Iy?*(5@;J1nA8qaiTgj2-K zhe$dfbL^Wd=R~809wz6y?;cwH*wiWUQ~vS`Wj0QM#%g%xjRx*h7Y-* z!U05xNHn%D;7LKBO$m|er|2J=lFa-!mtTz1WndGbp2!^LBBM*yrCv4|VzC^wxM&iS zHRszh3f{e9>IB^qKbD&~qe@@jU4T86Hl9y)<#y^XA{w}OV4`_Gv!7vJ>AYdXZF?E$ zNbx_q7d8=hIC1Z?+VCAS?i96d;*w%BClt6pp67(zFaEj)U&sv-@NflSP-SnHYr&4c zF$OYu<`JBkFwlBzR{zlT@`Wly+4k9rr4fbSPW0O7Pe&9b*x;1hH6OnAi}H)XvGF-@ zyuifl(AFyrvDn{JwK#KkG!uJl=IBpof66kvASV(&WMV|=m}KI?Om_ZdRg*q87LVnx+MA6fJVqb&k-z za`&W7A+=K%yx?UCk)`iC%5Ycyu)HGRR@awRf!g{!i~a5P;`CQY5&9pC8*eN6rv(!E z&IGsOkCo^qaw;gbq~q-)w+>-b&wnl^xHtylwPU)O`?N=!%X;{OCG1%Xu`HD zk8bYTLOupnyDpz`x({uq4{ihG^Y9P8vssy%&Vshy&N_{J8VnD3n+3a*QUmC^knXe> zeNA9Vn?ro82k?Fq`1D#v=B96czW3tt62HQdSSNZd!GRM-T^i;=iGe6OK#^jNVz8yt2#*R8 z-SFk$)N;Dt=g`jMHAt=OF`%7)3p&){E3s>{EkLgpmdBi#c3VvemLIbsF@moMm;*m1 z+%600mfS26oYNr%-~hM1fUfszKY(q+#B0yrN!z%W-2Tg_rEkU>vq?$XzEM29Nuj#O z$R6e-^QlXk0SQ%0nWQOr^TO-(g!b;F5Jt)5InX>9{NWH{y|5_U$qcE;DD4*9A0%Kh zQ2rI~Y}}mVO=FGcT>Wi47lMoA1lLt}?2MZ-^OMzZAoHgz0{fQqZ&vE(O1bP@gguKY0`Jn4~GFGj!l*Sx=nDtY~_i`Oqv z4DCWra!iO^@{49YC))QfFJq~yxm6g$|3FdUiSbqjKqS9%>C_#2=0WACJ!4cy!;ej0 zz-zO~LU6d8i56(lLF})dHeOxDju!vPXVzF&;s@zSY3jr*+f!_87OdY7W}9lNqH>GOlT(NW?_41 z%_eW#U{LH}nug+0`#LAx-e=MR7Dr!KjJPIJ`rHprDYiR-?>u&@3Ett!8gL?im%u%d zz~v?Qyz0Ok`6?DofFR@#TaJjhH0<$>FB?J`(~Fhu5l+9z^(LUps{5B@1wyiKbYHnV z_VWO*c=};&KY7UjZJk+a-HSMPG|bYPx? z#byOqO)E}zEPF#-Y}}KQcgN~oJ01)Lzlt#9P;&Dz?XT*`JQ_c_(e__NHKX(CHj1LJ zf7Qie{_SV2h%aBnBxPMglCScqqH;0x&Iu(U?$auZbEbdrT)eX>sk86&%P+oCxa}s~ zeif(=+<1M~9f-&QXw9S{f1=)S2yCEJAN)j!ltZ9-Lsb6+N^;Ej8#w@CRY>B$NLRca zaP^yfsJCkh=c^A^O9+`Lv{I+K3{*zb1Jk$qW=cMqYsvg-F{xzW2I7e%;-9N13IW4x zj997vj&*M7IXr;IP@+Fgemw9OYx6C1DlESg*~O3)Ge&=kw)XIB=)Ui?T8J<0eQpe% z+Z|mNxD@kPS1-8sO$5ca#9fLj>h2PaYVv5!C8oUnBDMb(8u-1QGlJet4r!t2pP$%U@7*KP-RURm%(TBxo@7J zCMN`F<&Qi|rnBj?$6X?eZ@SzY3)+dhl-VwoUFZby@mD|WTb}*Cd(HBsf;nq-!7=Lt z3-9oVDX={C#!BC|P!UP=y>*#z-fH+Uuu(|o>O-xrqPs%)nqq+*7S-vRGl32||k#tUQGm3Fp;IU;}ng|qEOuZe36$1g% zWbB#inydIVyU+w+{2e{n=qp)4-mEMFbvA~sTRlSgQkSfX@fQlATt#%OuSn7OW5lwb zIyFe|C5>-xPf^o}h+hRuvY*t^8P(9W*=uB9R%i*^k?nQbcBN^k%?DcV@o(F3P3)Jf zfHaR-XtR?-<_AF7W85WMG`bDaP|K#DS0;5JS2;bvm5z6ft#}LU#sR!bl_~n{oNc`H z8a7ABxwg8713pm}>R4S%o*)K1?VfYWpe_)+qzK3{1mEPdtxk=VXBTZ8Fl6(6I5=`` zI++Xq={QXMC%6L=;P^1n%F#@qvW0dL z(uOfxaSwV&>+X7*>QtDIR)g{K@?GBuL!YgBve$(jV_r|YlvSFAMOt_2)~v+K(7RUa zGJHN&dD@oSS@`5emH=pxbRh-%IL`PBDvJ93{;m6EK=A2Pz(m(t*hI1Wwv3{Wlrv`Q zJ?o$R#9%sO3m+T2HxuogZ1q&`tk7f?T6wqqoMRB*&a-{a3I^rPdjpS*>E=n-WgafK(%A^WngS+~$0+ypHk; z2UA#aojgzFSh`D*D91@mnCj>o1%valXYJRWBXw zI*^Y*N#HVHpQu-q;9~UQit^iv5MK+n{N=dJux0MOJ&olQ zY532NUueIEi%B~qSnU+w83^ld#5iu~CfL4Y-CMPovTjPxyjOTTqNA1UNH0i~g9K0{ zjifs{jqhzywVeB#BH(MV>>T^ldQ79)?+$*Vuz&3F(FvS#VIA8W9V#Wdkw&6inmZ=5 zZ$n5Ud>>VC06q}`30|C#E+Ut<>CNS&kAAfPLvcYb6nT`OHQ9RFIX>hYIDPuGa|@w?zxHr0wq_;%{`q9oYsr!Kh40 zb1%n*h&#&C1tXV6Z!vHjViIK6TX&)F+_jA{<+V&k7FgrOg0W+qVk73jzY_kiy=kd` z+Puu}>GIn9xRw$eso8bHPXS=;Og|DPCIA^)Oc3XJ?tmHw)1%;WMZ(ssWoa6cce+NA z^G@NwFKFRV*zL%QAjEIY5d^+rIcHiGFedCg-y2^-i6@hlQBFia+L}My!nlGvMpb^?LBP4KhgYYQFMp0 zLGh({3+O`$NPCgfV?V||gHj1m%qA=dz(Q15S2ZTVbYI$HMxdt)00xdg$c;oY%$Yv# zXBAn>hc=RPtV}Ky{TjcGsXAxe3xWyN0&j?~C~{tz=J~2iwoPTZ`HEl0<`cKQw_?VX zS`dUd(6YO%e&>Ufu|_&0JUiqOsuy#x+P?(`>Kj6@P%Z7ihk%UE_Ws2M+pW3hN=f^( zxa?<&5pJ&V>=T%(uZcw3`sUfRkgMxkyZdNIXZeQyxLwMIaiun%CZ7#PDZUE%hd+sC z5(}Oyk2~KUPBk-bfo@{Ub*1>k6q-nnmo!p*%T35>$9NgNl%qQM&he=}E!jx=HvBw< zhk0OqRPe)UA2=xb5&x{gR=9-5-mj9)-ufgMN6ge;YQXhJNm!xjxC2~OYqR)FGaOei zxBO|7Ni4)-^PGWF=+k_}0;GwZV8~mjF{Dmjy`)snWZ1vnXd_^61Ats{HNB9>=9o2g z_?Xf9P0jf;nUjJ!&HU5%YNqO^7&(|kr1O+fqCC|471gAC;K`izXy3H;C>O3(RyABA zp^FT1{FBp~oTD)nf27h?crR!nJ0~$TbUL!RIg#Hr#{O7_B6f4xLAhL4i9unN(xQ8n zoi@s#JqC0cL8~vA3rOLl>lJkr@L~?Je{@70r2xpKN#~~tWxT!8S5)?2Iwtf&Fz^`U^o~+Yy0rRW9fuqX;42^1)VD&TWzxgC zVEQTXpQ7VTy)BL2$-< zsbq8&rg3KDehGH}fR%>4?O+AD%CChoc&)@{xRq#!4NVi(QOZxhs7T9HMw))!(Yzp! zZpv1!CzN|@$2$|kA1t+} zcbR_ON@#1Io`oVR_6NQtTOtV(A^RKJG#cQ$>crkpOX6^hCjK>;+eBWf;yn_N$E%d9 z&bZ_rC$3?aEfpS=QZ3zJKxw+S;?TmklkVzFI)yaZuv*Vk^xR)9h>~7>OJVa$i`R9% z)?pYFwRoTu1v*9bBWWJ0=%nNNV$HchGl^(;@E5oxcDhD+LzMi*z-0;Q@xrs^5`;1y zGVKF&?dgVKU==P*AF%td?)!yI&p@9zq+t2oIxO1KE-#q>WdT?fR~ojFZ0<&m%~d7H z%tlp&m;^6H_b7zkCUq;I_)lb)t|xl>g*7U%*c z&+{$Si2?;WiJy7$yl;Pe1|J+G^XRlJd)`;tZ4&VOO(Lkm@rNsBmM+qEE#Rx4&0RzR z=UgYdH01W0Jk>>8o0UU-qWbeBdWlbT=JP>(@;QFBo_j4i-vruS@`|U;9p7~Fn;kQ+ z-QP(?%MJOR5kHqF0%ezX;tvxBTJmD~ig?y!$cj z<7Q_*t>?>(6s|tjpLvQ`LA`y|kaQ6!SLof2K?9`Es5_7AskoAcg)w2;fI8j|rBq)zvW_*~+iwWdO)%>Ciu~)= zKrCS^ZP~F}vy;SPtdAGd=aWKowbjOR^>*X4$r20-B$^#YF}QwI;ry()$=v{QKG=JE zR8myrcst?>yY5tUe1>Em+vgatiy6yuU#umCHsD)(N=4CRbu;zW|un} ze)`dz)rCzVIWAXnZnh`CFZ`s;hNeG1S<%9mM1Qd zxgaa(5m71__jE?H{CF=O$&*5iIgxv;%h;R^Tx!tsJr_0f^?#g(P52qd*8dq(AG?3x zI1u8Fj4?RFPR{GW*I3X9K%ZgmoZ4o7we;3VB6=-n6;TjhxUoQjWJi>s2?_4sLw32o zu+&>LePBw3#j+EM-Q!mlRw0&0e{3B4$NOO~2enJ+?Qu5mP6ZPPXtMu>r{sgr@%?A+ zreYg56OqK8HrN57)^j<(5GNR~IddTmgE0f15?xj(!3w`*w0My|KfyLq*T)#_d35z& zHNO0y-VdT2Rt;YmYXxhU?gV@5V}Sd89j~274ZGOBp&cGtbDj>@>cY#Q5!>JibKq8Z zX2q%LP5&caI^)l&mqyIAw{mjuQqPaAfT*(Z2+Jsx_#stRuozmalT7XfrsW<%9ihK> zfvZPF>6MdC%`e^R`QTZ6bc$>P*lRaFP@d_o(inZhJPS9F8A)Br3<|-5efdt^1uyi5 ziWkVh#ayjaFlwX4+<{#mcs87BE8w=A@Z}X_#Fyv4bzXk>0}JQk60yiyUc*yG~9+TYHg5wP^Nv`@uW@v9~a!pO!I6fQihh_;O~7POEmY=Ej?^)X7vk zC{Z`AY*%XfdWvSl(`b)1b?VT8uXueAeogAEmp1H`Ch&f_lL6lK$@t;7?C6@_yMupA z-vIMMjlZRD9DOer)!UB>mj^>gzh2iOL_8Xcd{;pHhbQ2!y!&4oQ?c#{m=}fP%zeTh z`+`M&RRgDg4m6c`?kNe!ZqTRfZf+1e*ZS}NvO^KwzPB*Q`|~USPn8h|g&9>5o319D zZ1C3i)>V}aUC1pnLFZPZ$L&(gICZW?q+{u}(op%c=>e+f#Sias+?c%)HV(+wWDZWn zKc|8a!mq@+X((h4bttzC0;dRs4)#wc)oNAMiG2i9bv~dEc`{@7?uZxUSG2lTjlC1% z-PkWK`Mpz6Qn|A0E;%z5asg*xPy@5`97Hyafg2h|z>O2mg_K)%62pdz%M&V^ou7k) zpIuL|%SajeBwwO_3Fax&Qk$V6PMZmZ@EX7`H8l-R3bjn;bch}>P*vDNLqhjamQekC*<3^~FZc^KTK9g+&wZyA zJL;s;>4Vi1X*XNbIfAS4ssNskIGq-hHYskUTO;6*4@O&J7<5I&Nr0kgf*~n3IDS%! z!9afMGNYzm?jGX!vgH@*`?935a5b6kIemTg&CN^9-5BNVZ0ge+Pq?Q4!F-FxZQu0& z!LTU#f4eC!V_0*#JjX60VlV@o{?5&0QMRk|(tgZlB==Ov3NDo+1p*@-Xw3$zr5Y33 z!6ru(P^lwxNIU@)5^u3RV%-XsMr&Np1w?^n;u7snCr_TXZ7yK`!Jz~FSMjof35x=v z%_{NnHibnc{YzE^(LEeK2(eiOrUDj_<>aUa%!u1^^=)HdSUA8^B%i8hyxch>zp=S6 zOuaeZ7W0oEyJT;`s~BN$iKg?mrC`_Em|(o=ks84zE#EiK!q<`*LR+dAIPCpksDIck zw(Y0sSq_TnRpyj%oWBSXeq`|SANg%^dpQYZQ;ARu55hoQN+LgdkW+iu?YxR`{+9i< zN?fD1^d#Zlpc`>5an_r)_g!8&v7wl1ttytE41qr^*x^*t1)NG6g0PCv@ghUKSt0|S z3BC5XBE&ACYa#YF#bx_L>Qz~5*SPu4L%8!(tM_G%-YxQOMY#@0ls!8Hgb~ zQ1r{1OUE+6nJxAhX2=+LhX7QPucxRtF20aQkm6n-7qILu)hD)rv(>|;Fe8ovxc~U+ zFJAV$GH>wgNA`9wZzkdIG2#9w-bUg?&&I<^7dRwDlW`bxF9jUS_*th3hRVVUlemj_ zB6;}-XlU0oP}j5Yop5pPLVMmUGN#(6N8A z)W@oB{yWwJP6{Yw3N`7mIi)VM$hRQvk4uDo5k1ojG-;Vo^jtghG3e&FwkmgmFg>XR zJE;UO@p+$dXxf-~^TNU7@VTWsBM+8=Zs@|4iq@W}W~lb*{4VK@XtFikgE1N8c<`bH5}UljI;fp~DS*6cLKqx|`Sw5DDA9|1>UMuSeY<*-|H4MU`?AAP z(ax_GRR7-9Kp)=m;Sgrx_kVd)Y)M}vT_VPhCjZ{`AROMYcf;Bbj(^SXgxiKL))V~S zclCyMT*tJg&;QKN#jg3_u+WiJiJjbzn-7%1c z1F=i-?im5ii5G7X8{QJ>g*6WunHCS%}_VX{4F#LJ~1q)91r;YYjAGOH>agB z8!;X~*P$u(4yVEpYQY>?5sEFkMsIW~9+ei929Hm#jDYaKi?g~NKNVs=I%)&NcyrM~ z)9T8bM)mBJ5CcTuSOppfgOncSNXC1-mQA3EUwCU=T zA>sQUn+CwJpHUuYjhulRYqP4FdgFVACG?(U0?NuEeQ!=8CN_3GorVHg^9jta#QYzvSk^;&;0Ql$ycafdOb-Jr%Okr z;7r)uKkb74X$N3=`OwVyV5MO|j3g0g~ckX6vG&lJabMqzsf6(}+kp*Alo}TS}TH0Y0fD z13ooFYt4XANex!-|3H-R1moQfBv|s!<47k}lCRrvXSTkrqC=UETz2utEp}fR^Z(w6 zdFTJYm1Z+J1>^b38~$1So;miG#=wgYZ{W$Lomr0&5Gtys-}s*_^W6~Sd#2m8N}`*) zJ4Y&>+fxPQ``&r|M+~jz6sQ21b8bzkDc8i8*qIBn3=pQGjIVkc(ikJ+y#0YKjBz|nd zQ4AC~W;!*aS_4blBJIXWNjjxcxMep!jQRo`d49f{W(;xgqlJy9xLgCQKs6NMt4|?gK}Ik48Y11uGnaRGOY0I8kKxUv?HMKX3gNG88z?=$~noKWsr&(|xrVLfSFttM05$)E6Sl2OLe{d^}Jy6+V1r-G5zU4Gf{VVc(`Ja8^ ziR4o!89N zN+49`+A}DnCz2+$^;glz913YvgVy-7((Dn#$*~Nn0H*Q+&c`lSe|gd7r*oOnpQ6uX zT%ApBLNHHbvyUHjF%E8YTx%yU1_l`w6X8QCnG=EKDA-8aluI#G-hU^rCteT_kGeT+ z$s~Mf-nHpa-_m*dot;??IBpnw5=CA4EQ9E_65w#$6@o=7-YJxw#Mew7SDhOCBVj_Z zbxkR*lz#=)J$Hhh8)$~s7*dcQ=J7i-Jj63BsVei;niNIvhVFCFY?k`3h13|F)EaV? zT5sIkZ9M~%$_dMXi@!=HW>r~>og`El$#Z`{-ShegniP|GbZ>fh%wVg9qp*B?r7L{T zB0NO$BirukqzQf)XX(W9CvPpo3N6rQa2javTGd%hsk*s%iS1>!4{9F2eC420Jo0u_ zI>2aCEF1^zw);w(ec5}c+IPDXHdX!lLmE$Z_F-4U7tF`jf2tGz12rUU%#3=z=V564nY)kY0*1Xxh zaRP1>7Q5j-mZvTqX^15oF0{Y@#a`Qp0S91*DOddE$z3-TxL)dchO!mq%XKP>O zC=h3}>8`M8nRXq7#Q<1%*IJ_5+jb{HFrk+^I&w@RopMy;*R;|%%CEfJ+(*lVYt;h( z-w+|Z0!dCm)^VbJ#?xXRA6?|F@DBlINkxKI`!Sj~R=P;!vY1QI#M2I8o>k_V1+!-2 zfj_jd(O$QPmMu*U_7nfF@Fv!ZB=~t<09JxGqqr`y+td18g6=d5I8h3%DeBN{dvV0& zM(Gz9b|9%7`+4Wr0Ccfc^0j|j(Pj7ncUqC z3@{~O{g9Rpw>l@&T0-z=lKGpzG$77-5c5t&hx*CJIa@JxRdWLeLdyB?SI(u zq>a2}>~UeWk{DPyNLB`!jxE0ViIY*O+`V!bWG#oHGkln>`eCt@=$0~Csi(R65-bs? z8l{82vHVzOu#_yLpo^Kc4B@Cu7Pt?X$nfxLHNV3K!}V}g7(TUsy9K>hld5`yd8Owe zJ^LpYBQ@loLCu4YH&e&TTd)$;Tii2${0q?XC)M{;V)>(-B~EmJp8~cY3d9ZyGm2Iw zbi7xFxUK{*d0fERKTc0) zQV;a$d0(%vlt5Ufvv7(Y1W1Oc{!UVNTMS7VVS~!}2R1uzckLW{A%(8^aM%*iXtxCl zUq36iI(rHZo-{RWa&JCAonqhVk%~?zMV%Ro!xwAiYd$hwv4uH*MM}IO426#(LPxJ6 zDNuAqHUQ|JT8I`{XX#yyoZKI!G@Y<~i1^Z(fo>CX40m~%C>!VrtR{Ss9&nu^&r7nfWg`_Hbwzb{ zGQXSMI1TD-$rHP6yHgTgZXhNN*NQ)uAQ%q)tDFaQLM+JsJmK7Y9O3?^HL2FX@YwmW zkKc>{*Ay2tm>7I$$(sIhIM>S!O!0I|S^Io8Dnq4!V0#SItq#$Ii;z*@{Y%3dv=-p> zunbHUu2c6@@1f6$sjp?Nxt&c%=b ztgbCi;WA-U#wxLe`;4=JI|u1BNkYjt!^7X#2S@fMd>F zol!vlZVe~dyk%iga()nYDZZ1a-re-M;;CO0rKbTK^}UzxYqr6WxtHcOgr^;cArk~k zjcUA!VfKfr`*7sojCH4hpwU#TJb`hqkZBzaql8did)1;eRp9>1c857J4s3z<0?tAw zzV(;)pnEycWwZ|YaX&PH#y=by>t5uy1+(n<*%&03Oea*s|EKk$|JhC)Y~j}1;HHRi z@ed&)09_a3_EBuQMdjNR5xd00B7JKJ&_xoAO)6T+Kc9sEx}>}Ld}#o`-J6Y~>yGV_ zFTa2dBtlmP-awyW@^9Tx2l#I_O0_0EwAoOKls|C44PKw^sH9Vawb&z(*CF z4V{ev#-%qQnVQ>*uJ6cq16*HCu{eBmq4zJP>lY}4k8)4PEicSWyF3ryg)hJ5&)!_& z#{5=7N`8L_x7i!&l-9YI*&!d(Ub+c1oXBAg{sz^#MG}^MJN7ho>d>y8+TVi#?X`!7 zKMrdiH(#S2eA_@WJxX~}$l8p+w0Lfxv+$aMmbPxr5l4BTz<1{u=tMDanmXQ#*CUd6gSYIFo+xfVXJBrEoWlJPJ( z?^7=~GH&c;v3K12yn4=83Z#|GQ4VT8$Uesgm!prFyOFSNSk-bq(j5DzTR!Q88`ie0 z4{}tbwYQ%fJW>yUYSAh>cH!dWJVzN}UEp2DfbytFgjc3`2t(G^(C z95_UP{0+QRoyp^~v9&8ulR+{ZkPaX6TV|a9m3BjygDD5;^n{9gjQKoW7w3sM@6R<~ zs!*xZL_M89OBigky#1NnxZYBEC>9nyapJk#a7gd7K+q*{HVHLNSN9;iXuLLG4$dpl zfDf`dEuz^`t499(2xOG%so%Ks%J-l$jhilZ1PtGa`=BUljhys!%`tuHoNe;8+>j%y zd1@8V;X{*a6UlI4-_#gF9pT8}dtNK(oHgTwNFbwWC$+>`^vu)J;RLxyty45l>oMe~ zChP-c@|+MJKvxZ3xOu#xuMBMnDPY4Bp;Pe6-X<_j7vSJ?i%bn}wyYP>mA;(jLTe!A znQ*PQqi5LBi}^w8VXg^?ZOdYSx~{ZU{INRf$< znJGSkI!&ZV%ZGYN6mQtTw`#e$f5s3UJ+WAKITE1kKZ#lE0*T z8eP*lNlaa~q}`!lrkUe9)i{M8J-AFePpaCd1q#i0>`P5g4tIQCA3H7m9o?i}m;JDA zD_jjYCH&E+(Imf+>lGZC*A5itN_9=RuYlRqk(OJ!w~|3p>EQnJSOYS6zPtiyTiB5<7$BnFfNAn(hv`$p*Sgx)8Q^&>74#2)S#(y&H1JdDImfwQuFkJB@C;P*kfE<8 zFRXGu&NgJ?;cjx+S_Q?;nSbYeGMYG155dAvXYcpTWXq9h;(PI|0ZVf?;2!J3M?p7v z^yTWJu`1O_q$N?2{Nn$>Y|}O%=3nl63T^D)gJ^eDo^oS_W{qT?}4$=`By(d z%ZS9`q8}cqJ zwuc#;$wikl%QbcvR_;ybS!4v)k5)yT_N=%XFctz3nj(+r-;hx!^g~F5ZPfE$7T}lo zFS5B7AB&dDd7lSD6}v|Hx$KWuOZJqo*tN!CZhVEy=_ox{D!BjK9YgVzT3qt$&{Qdw zUIpG`dpQn_M7V2JdB^#E!&`?wVJMzDuN!<+1fnj_WOyuUHr=>{bpc?^-z$mDluGwz-0p0! zA&RckYuJrdLK*z(c_F%bTX)>$?Uch=M^5op_%>y}sBa z#IQQ=y*8)THKA6co8~-ftuBLy?{a8sTbV}rvjv`t-4ZiU#^qP~KWea%f}qx2o0_A# zGhv)Pa%5Nv;tzw=jkDppRljrG_nGE})ysbL^C#LR(M%SacQmP0cX)3tD({Osel~|Y zR(1Hed%rHlgyc2AmVuNTl1|Kt(e{&S>kb-f(qrb)<`c=~twlu{FP(V2vb@ zTleAImByT~=A;qGj!ZupqWgLrZn>;+%mPdSHr zHfqnSU?{V>as1%@yHG!z`^6Vw6xNl%bM6mm)m^(sij<%(ctgi9k#fLQkH~$Rrc~>1 z5BTr%=84W2cDR3$xB6@$1nc;xtIk@||E_)a39-$cEVY_2Z6utB1O9Wf{kkWjma-My zQsnv8+BN^!I2yg!`_YVyt98{5LLAWH>${_UHl-2&mFMy3N;!FN%T@24ITC$IfH$L7 zpVv(0d786SF=y`Q^ajSQ-@|QsWBzQlY1T$;{i)St_mKYA{}*|08P-e=l$<I0_Qkq$}V&TR2f6>*YR|JChWZqWn$aW7aXa9p%$xkiy0%qTi{Zg;_M1U1EU<_SPlX&LSC`J^E|kRz-kvS)r;4Rr;sP^AL}~q$Q`cSk9Bx(u`}60 zxhtU4X+W(w#&rWptkF4p4#V`>ig&^MNd?FuLh@xQ7@H!7x*#) zezDH)cYr_!N83-m$Ni45LgVTr4}QMJd1^6%T2{68t=3h7nr0J;=h1Q+cL5KtDU5y| zGhox7(g>`I3Y5(LUrR1oDwFbw+s{4XC)Nckp9qQyPN39<&8{1~wXy61@N4d65SCz) zX4^~#>f&teg7o~`>RCdAI{UM_lQjlyvQ-@z?*-Y-FM0K5+_r z+czR;H<3-@EQ7SXsLISKjufO|mdq=6GS%!T7Vr{G{M?FEGF!%eAgH1fBZ+m$2(@vi z$>3Gy3Rj!BCymKHaojh+CxQEeZz@MW4@pO5d9|2t&>sHSVSNiA(uz>cZr}jPVt(6( zX0te*tT;TyU4eHhu_kF#%aZyYfUY#47`CMThnMQEDw!cEXWtt;sQ3}xbHd-Fw(GLL zzH`3v&GFa~+y_F!?)A*xr`$Fw9O%~Awe~}_z-Gl#8=MoBJmmyrh1y7f5{@ENk>FGt zywm+V1Ubw(^O&<3Y)VMz#v%UzKzxjP zvm|fH@?X$4j;?q!IiSZ{3i{>5&8*dvc&IikA~8<0;UCp3+(=D;@lm*vCTr zl|=(w+l(B;$GO$b_cg{`{~p7ub8Pz$L?fX{9O*$)q5I%H+ixkr$5^e(iK6KV0#h;B zo4ejsJ+b*a=3ic&uo8^A_(2L!HG3jc67{A6wdmwl5cSf zpgF^iubw1&1L=@Zn*Sjkk_nJGXr#%LV9%D2{8XI*2?c(<7_*BDDN~DJn~L}iYtfV3 zf&Uc7NC8-xbp2{eB#zEjuq>#B*td+MlF=<)`z%k%i<1C=D1ILRbOl*-`AZ;?qWs^} zK28A8(6RtHktBc<-G5LjcI}s`6!yQ9G6Tne zNXdQjc4$c9jJ0IQMZH;{hXv{$7N`1>Jht4lxg0#x&-|L|5(?x<{;h^F^!Ee+ISzol zNF_4oVJ-c0EDRy>tJWizjg7(P=Kc7s$Gl(v4VCN1`=2z&5dh+7AQSq3N~Yb2W;e2~9l{&Q$Sf z5!}iCVM#Z=&|2>Fg$?3LK>^B+ZHRht_5(cbPe`7c&$JA`_b;4w+jGI#r?Ipa% zq0&o2Ezz9?SvpEn=VUMNqfXGe6X~$5Q*?RVJJE6}Aup4^|47BA`W2)?nE#)YjhSG) z+`EelK<8+7QyV~~%HN|8%UUO$`H$~J3LPF``J5XP3{eff%4D-(~_6I{2 zw+~)Pj_(5!C$Nfg_GfKeRBpI7qU8>u%?R`V1^D5hf}k4znGBOX*Gg_S)Spa{Ldr6{ z!Hp*IAGSZR{sIitnzjcN-au}@m-?Tpi1s2iB}a8+`JjZ+qw&jkj>)7$ZY53$hECR@e7JO5m?jU2XI#TgmiG3Qa~T zzV#*viue)TA3)rnau5sSE9{M3x6EasF^3=OgjYnez=^RX$Zv^XPl{VNfK z8QW5`DUv8YkhO#fkyjYtSw-i171gY4x;~4=4z+1iH=0pWH;BQ6fkOuz$U{=VV z_u0|?o-(z>vJm_&EBxl`r~I+NkIpoKqWR9swN<nHl z7w1DEQ2~gOe_|jt0829Z>|YNsJ_5jzs83ShNB=}gfTtbr<;wnITK+G7T8a21ILxo& zIhq0ahw|XZY#3o(Q7Embftio}>F^%MqC8oR==Oim#*#91N+dPMMer3sJ$>(?43c{& z;;Dip-r!@ZB6OdCjxj@n?S-!&a@r>MkCTBrOpM-%I70<3ZpDF+fR(kKfDnh-VqNIl_k3mY>V(Y>ipfZ$M<-TiXoS)D_0rDJyi zVprjn6!%L;x}*_OTK=x}Lz_GPo4|qB|0z3VwBv)V#0X?$dKJ@sl}XTRd{|E0??M(TnddBO#!o^5tag} zajTmaz+!(&t;6rBot#qVW5*J?#46pu&k(o>89{K$42ot zXu`uGPDM^Sr>no|RD#fgmGx|vaJP>?c$84R)LU4;i%>_|wBY^gb!LCPPBQ9&SY>p8 z$u^NY=`T@VcE+tdW4xI?me5L0gUrAX5mZ3hGYt08vhp+ zF^8_h{}2&t$OHDWxiH`REjU+?lKUD!FwLw&2@z(fgR9;E;9@H-2AZ8A@DendLL0w z08AuybW&mq=#GjlM!&v|*enX|9L>H6xU!jlHAph=0 z-|EK8<4DfNh#rpi0tQKSas=9eN%Lgw&wdz%rKtN)z^e)xsgoNHvxV*`r2frL16~!; z$U*$$ zcelOz33!2@IiktPDa}OP>repUulKKH%tVz?bY*}2gB~9Ws}jV19z&-yIrB~LWL5d{ zUovMefv-9zap^M>8khOvO&T_r+Eg~cNK*-MnGXU|ah$9#FH61x=Of}xNrIAQ;Fxfa zt_*-u150nY)39%5({MFEV-vY^k;utw{&z1Is0>aIIAZiUJ7f9s6m;k(_u&2QNoCTV zv%yR>MYffmY+Os3Py#^gLL+X zF!tmKt8zy`p-LKl(1$kO?nd>*M6q4J&5foxB=K}nTrM~pV}f9-2OMB{SXjGU!PtDe8ZtGbFCp!QX88*402H|3BL z-Qm5%+dmX|L7|($i?z3ZE?oSyvh1g!eh^zq)>FI{;IotBG*R15Cw-(dwtY{n0{0+% znW1cVTLt>t2{X$R^KrL_ZDVz#H20ya8wypCPqH#$N4=2>t+}muiVIGyZJtajWkw-x zcFAc^w2DH`RWHT?6}NT_s>@f`N3vZc==58Lcv}H_5Mkh`_GoG^o2`T_ zzR+7C>kzL)|D4m_VG<_@$?TuzJ(9BRge2`elWmg3F!M0vxrW=uYd3HruLr!!$eYzg#| ztCY}i*$6t`MbKyqbLepox1T{?06Jgrib~&|#TKqL2@}X+%63O_ovpO6_tw1E!~Mnw zY|e6#kU8KaJ1wSVrfuTWe*CJh0FMtOPdoJ+(9D|?au9FY-Mec zTjkGFiX#QgUedxXReh(Ko}=P94cff>_{hHF$Z&(}__ndWf-q1QCi59P9sTm~s2`4- zD2UHUKSmD3V=!B2OT$5^H_I=&27fUd=?938A~M*Y#gdBa^iO?cg+r(tm8I@AJKo=) z-XkTU{~IzxJ>m8k>M4KeZ2LWPb6I|J%5S%n-g~<)XsKLH?xsr&S7x#AP$Nezti66U zeftZEzIgFTMzvl0P!E4VRI=!rD6{?TE$nZEur-i#ym z42{#G4F2aR3{|%!%gP)dhF(y|h*zUu_K*3;*Iv@Ikf|dZI?^nN1^B~%P~CCg2+SpQ zAhr1ICrp>P&gQRhs3^4e?3er^%!$cf^jx%^Gi5gm*MIld_0bJoMa}kqlE@HRqp2#5 zv@PCb_2qqhpYs|Ap=Kf(Glrsmd2dasXo$F9vQ9929ajxZ=mZOPXI(a5ii}L1w+UM; zBLom<-{-3XEO)|6y7p=DSGJoG4zf)iCGANIbyP zJ3-Dv++2w81sF_AKtza!tM2b>sQU)Y+JF$5W`6)^kVw5axJux(RWqiR{W8E~-#kc*$yM zL;Qj2BSYR%$2WDDqNm+Wqh$$KLsL0~{>#R0#O;Me@$wha^Y*+#LT7PG8``>!Wt3bM z3Wp#+4!%T{BU8W?n z&qlT5bx}?JEUbNZml-!`q}=l1_|~;Ny5jHPW7eCWp_*^{J_u|+1wAF4^jlpU44A+W z^i#>Z{oF^HwN|o=>y8KVu~jognRI1!7YE2@pF@yo73ap+{eng%nsi8g%rBKpv101; z`fP>GZ;&Il2_w)BP6r8RbA=@eotK?FR_%paPohp{LSO42Lu`xl?1a%^Pb@<_fqKu9 zD=1&aM7VLbq99p`xR6aHtgu}x+?>V^2+u2{h%{px>l2^>c*3p7MlOpRypCUm!Y3}b zQ`!VXkQ7V8ocSih^jsxlBTWE3O&C${H;@!A6Jm~g@`I|Tw%C-_;1GRc{<*tp~ z;%xQb(J`^+7c8ck4T=m+J}7?c#T`FZDh8Ax>pwY=4lT!qH)+@@_gONa6uv4ouJZ`$ z#^yd$_l`|_Qd75EQ)t3xr4BWAb8w(`%$*Wcg!^!uH+^6<_J%g4skCbsCT+*vmr44* z?8UyaE_PDx;Y22ZEC;&#ngOKZeFgjCtYhF zUwt4SLya|GzMvkNt}l4c&?B@RKQ>&_r!K!7pVMLBwywmk(tbIS>-wQ z6Jy*R?2(#+Kzv#SN=yUHRJRD}R^j(w2pd?le%a#wte7RLai(T*v!mcGB{#%dCa zil$DF{a8@3h0%71zj!VvrD(m{Zx!+PjsX3&S9Ny@65L#>uP^UMW(NGLOjpOvdRK&9P=_y6=d|MwLb9k zLfqZanQihGib9{DJv?E$%H$Z&bZ@KQmuJ9_92Vt#ma|`Y_|jcIMBD ze&jGo1<^Od{HF7o<|r>ec7~q)EGdgc!Et1+>;&_*ExwLx#SsjLm)ezLJsYTOx)Do6 z+GB2`WPV;W<+s|b!!oD`!-miyo~J?^h03y@6sYk5tB{!UllE2fu_X`}2@oO_5v$V^V#Hzvqxz2w4q8&zQ|B!_ctk_ zQ)cb`q>}AX*O{W`m&85W6s}`ZzsEk`eKMu;)B7`Q%hM{jAS$wV5WW%>wj7;5&Y7#D z)V0Gc@vcr6JhBi=AteKqPbG7{i?ihr1tbsrY_%Z}G2<3~6&ZkxT+tBmqn1F8PQ`Y? z_RBqf^Yn$h|36s(Q}N;pn#O`cN>W-V$R-WgG-tp%%Qu%s{1M#7DW%WW$nD-2Q&y*{(boU27JH4Afst8Mr(~2`r?lL;#94ia@1FcV5I)!yLZOuv! zczbA17V9+M(Zi+3s;8P?YqU2?3^P%^kO;was*?Q z2{{0Z6IBWSkv5)vW9~DQai6#ZMlNpp9b{=ImM3{&2l|U+;pnFJ`nD!|1PkFjR}Lx_ zDPc?E!aH)LGAuKimRRETK7>u}m!ePDd199Em5tlLq+QZBXQn_c^P`=~imDnWr0~K{ zL;cc3w|4POZ|yHYHvQ(^5%EXQRedXXCV(G`lT+{`Mpk%8qO~@=zkHq>;3`hqvqrm5 z8kzUxHBFkid(?Zs0z~T0O+niy$-Gl0TF?o$b`23JQtI#iUY~<2rNz^0EJV9e{HPJ} zCGxh!;PD<%sUQ4I%K2jp{8|+_=&6AsftgpdaJZspwvg8K0>{0`} z_hcC_RM@*Osh9<8t{=R+rJ8q@wfk2D2odo33`hYj@lEw!|nwRMU#9PXnQ+2Lx#PB2cY+Du6WAF<=SKE!aI)XqZ;h; z@_edb!YhZ=0{s0+vyaSSKKfYC8Yi$m`&gC5Z$XBR)T@G2EL%xPegu%i(g@sTpL)fx z_gUaaxi%OZjI{4}*Sf-9Slv?%Su&V%UAi5dHc3MJJjL1vu*a&C`|sz3-H&&+<#bQ? zJO#}T>eC@x4z`LSmq{o_#`LNrP;-7kgx~K~#p19VTlVaRQ7b_%69OS1N{-{mQsM~< zwwjz+szd9nGkyxaJv!wp8l#>$Ev|!Q{u*-6jpCgO^{I+J`$~YP0bq8WGKAC0VyU}U zUPdn8Y7~r5WVlJ6K_eR?+nD$2g4z#1nIcnT)94Q~oP6@%tv}?tUmZm5j}GoAU=dwE zQ<5C*m&rvj`tl3)uS%07_wB@ie48JXgb7aLZPyrQh3~Fl4`(qAJ)EAOc$I#@dafnu zX!5f->*o*p6qR(6>X;a0s(1zx$M%Mc7nX0T?Q*b~P4HpA{ex`s)KQ(bqwdxM0ybqN zC2j0$m~O;YV+i=81<4<$)ApH6e2QT4Xv%r)q@2%YihGOI13jF<+3g%6^M$YTyzPP5 z2jfTa0B_d^UadYtplS`!c|G5Hx4otxmuGpJZ?N8IIN~)>u)H~CacMB8xlx!*|sw-iR z@3~b~7=DzP)CCDqFmmynXJHsCSe&l~5304FeR`n~cRb}Uvm~0yEsJ#kcca}JojL|; zh>n6(l5QJs1Aa=bZ8X`q>yK_lB)M(*pbzK=G6f?s(b;&12;o&}zqDiAJ~t(msTOZS zfZtrKI{7O;25eMR@+O%Ifldar)zd|}{<26b2&r~+kaj}sR%vr1WTO-t2>XUN#5;J3 zoR18r7q`5KS>43DSI72JOg8{e4jD&(PszNJgh7#`^!(l9{ zQ|@x%hRX||lBZ0#rx=K3678Q=Hq^)%^A?MJs2wjYig%^TY~@o6i&(#UD3RL~LqC-zU-a*rY@!rw-DHAmeAe zFddmJ_laNYM}ppqC2nh$wQHXXys}B_IG)1}hXs$d<35Eq6d(m(rX7z*@_6@ghk-{e z?Atc(mX}fhCA8#SF`MZg; zQBPqylPpebCWHh@M3!yuHF9VkInIjL$)`8iO3bG@aHXAI`;86DtG=oQZLMb{qzzo% z_#+EPK)VRgmN24y`Miw^E~@R+zSY|Go04|c z3CYmdiS<5y@I}b9etkfwO29j7aj#f$453aCBuYa114HNz`&RFaWRJ{1wX0M*f~4K^ zl}JeVSrn=LchN&7eUY=!-SjCu+IPQ0i7nnf zrS-)%0S%S=SvtG+K;o~8%!DSfnXPmJD+qneaee(Xv(O4xMPYG+S&|jFAwMiG59cJ} z0!1Oam0`XXP)pqrJh2 zK2KxXA3%v8^2C?-v_Y zZ=biII)i$V(4=(UfX6La)tQCt>zOCj1NAIRN=2Ay*qwo+Va}ihyw?S}uFy zn-FRtwY{h&4Egv|=Rm2|`!x#sOET$@iD_GUuF%o2$dTlZgPv)dGbZ(QjhU$_;k+QQ z!C9RS7+hvJ5ZK@(0s`vRe?$UPX!%E3qf5aalm62&wC3fR8)tK60B0HfUU~cS!_FJc zH(GoXws#6aZMUR3BHn9=hOLJXgQRwJ=r;jB@haGelh#`)n|_Cm%Kpsu<11C=mN#rE zITM5>?%q)p$sb>_o(cmUD)P8opCExc2-vWL*vq_|;Xcr?tbetJ5(uhZ>(Rl#YR*8d zQ{g0$X>_zR&Qr6gYn*(MaeKBP#&?giERHd>6F^;fwb%D8%Tq6)tI4B*umU{Q* zSp02Oj(yQ%tngeLNhYmw>~I$;>qa`no0f~>29sgX7d7X_tg3rHh0VQYX}byFQ&4mC zePaD~*XOL?Rs+&0qd`uC>6Lm^IGR7wLKRZu^dE>QCfsKT(SRa)R(R1cm%mxsqkP+> z*Fb@+t%5Yr_v7_99SnpSX=0I9K^V4)Bp>SWUJ@Jtk%U@NXd)+1K*M1!d*zJxMaHFn z1k?UnM;h2vt3jgNdxl!N`n2+$(&Z$EKc`;A4UzEk?>HN&8QWWLopVxs{%tAx^ry8x z^=236`)At2Ad;Yw8VR3!f}{=Y+7D}|l&B@6Q<{~`l3JTQqW87?$JSyO@l!mms}T?h zz=6E}oUUDXuP?%*!+|h+%2%ea4HWWbUSVG}3|E%u8SHrIPo3wL^N~cZei5sxw>3NE zc^~G%I#W152K|}epbI|{X7sDE>Nd? zKgu^$I@I>%CcwKn=_LIEQY|@}VYal>LEcF`t+DJfrmRGLfsz(}F<7OBJqqxl9w4t1 z1!SR8rkc}$n*M2BF#vkuD03rQ0g{5}*q4H3gNidcNxl`7it3YxWEeC!kxowKl?Jvw zK$6uDP{+7DsuAQvnDKO8?$`kMiPsS!B?URd8?Y=+D|(82xXHh1Njw$+lhL!nKvhr4h{ z{M%X1T_eX#B#v+EJ%6${*aMmktRro)))?h4_7l#Ad$Q>EsQbzToL?aj=lb@bBI*d!Z2*^0ARM zzi-cQ!ht;I7XiYIZ*^|o0)P}IaDJaB*{Xfh7-u~)YtBi3^i;R=pGqOGAL#@7B3l^$ zdf=Z8@38-~;hn($tKr@Mg-<7A`P_Y~H2w;GB@TIj2eXtf4UGO4^g|_xs%C*<03J1T z?DhbXPHNYJm3R0;5YSBcn>-%QP?#YsgX-x5OwL)?gY|VB0Uyi zk@lL$=o6|UW|p?^3mdWZcm-30g+LnwpWm*6(X?N~=<}=jW%?xlYL~Y$f@IwTthe&Z zd+3Vnp}*-|%*V5{^35)8pdHkYM$+efut5z~W$EU|cjX`Q@5z&QB>90KOof3DUr=kA zdvC0hcz-*wY$m^k^0a&!ZsDB)?AeMEx@J`x-yM%^X!40sT zn(J@XwUcDaat{LEmAAN;x z&C6lWj>uKxm{*!w+gs+oiBQC0Q6*P=k#Cb$-nmAzb%2qD?oJWr0sG0ND?9aP#Lqr z9=XNOdLB+nKxxdfc^9xY>7;)f7<17*34?Nqd)l$Z^~F6elfZ+XQq~A=D2G4lO;sjn zR(VcND)~`Z$37_u7-4NMDBQwIR^{(ez609D)zB8Ji@IY7>IYj{Ep60m7kWmQ{GQKo z)~C(?=>Zt=Or56y^$^DVxAo-$dM=h%`U4!RiOv>pC!n>p%*VelWWTk_t$`EvQxf28 zQK#xx3%lJo0TC8;mB0THXz?ebXL?@2{&WE!?)mA;PLy?fK{_5P!cpQAU~qV1?WdEmOBoatK7j?N#AW?+5* zeUcLVKMnPYXi9rp?&}BKvR*y4(_epr1B3I6oTKN3xB-VR7305;wR5M{>w}&sq$^;rhmlw&|*k4u+|jhpyu%S<%d!bVztvkOxzW@Z3H(l;9U)#n0-=minZn$|Yp zoD$wg!v1-z12DMzt%8%Px4Jg3fD+&uD&D;;Q~_LX&#-?VSiWXs`+gvD{0qd+0{H-h zNn;@X{r@nSqWaCpiq_qAjO4Ttsia`BU%IsXb{AN2ZBAM~@w!~Bsd{oKlK-MCxs5Q% z%4s}zjDOi^)uQ*^oSJy`G4RuXp24?-65PjzqwIa$+(lUZD-)fZ7S59zKG zHA0P9*2h<*M7H)nX~94R&v~$?a2N6?SNNU73d;$Ysgi>@qmp)4Xe)v5qWu9ZU^yFi zFqxT;*SHC1Up8fgW}!KKT2k%+h9_{W4=o8$749G&YOj;Vb?4LHikx3WWx)8T0nL1l z=ZTgPS$ObcKieBkMaVjf5#=*tL|XJ|u$AObi492>c*5(#@CNfjvAKtIsJklNHxWbdq$(_i3auc3r9wLE)iyb%unK zk@vW@iL#1lBA)Tt*zR)OpO^S*nc%^<82sdT>5_yGT$44Boz~_z)hG&#)LtzqKY7t_XY9=ec3AS`D>)m7Q0_~bb&*C%(N+;Z9U}K}-GUX+F`UHw z3Ju3^|1iAmOmOuv8qRXSgFoYMy3etdN@l9RKa3s3UDgW-}Wt_mz2NYY=+m; z6S<3MDl&3ld_1Y2!9DOHukN@p#&z{}*ZS@NQ5uzg#@J>}-%8cg|A7Mb*YpWbj1JDU z3hY`{_F7%v;H19YWjM^bZYo;f+Fb>Q;f%j{*BILFu-G+w+KV!%ps1$3BCU*ib4G{S zhLU7ODGeP`F#dib=&}dmSb}^fNT)=SIkjCicOQ5bYgSJa0?m@x!;YPMB#L=Fi&0X# z0y*w>BluU~3hIpRTP#mR-$NV4#^X3{S59r$#I{mNGg`mnjqqS7g5m-mVYi<27EGxS z`HXFiWwEBHKpRc`NJmAAWcV6nFoESJ5u~!a>L`ZRf2;y-RPFj+fDw!EdL@-TJ)jSb zQKB`rlIxZ)n)J-!~1!wukI6^oObN~#HRd6XyS~apxBl}c3w(=m09jqX`VT8Hz3Ht=Cbx( zfsb7njAum|#+gC5?EO^8=jKC;(UFfFlp!Lx z{dAjjlglmFZ2ae`u)5TROE^0SF)m7vt-oz&;JTY#ExVSdN(fqfu*<6U9TbiUsfpho z21n~aYV3`_x*BeUuo%JxZDuC-;%z*clRv`S!`!2qwDccd(&RA2f7e|;=9@LQK3`Jg+mIv*WZGbaPMfI|2iQSy9Y~f-oHn{3v0n z0_OGTgapF{VK_gT9p^W0rygN2-Z?0kU))#7Q1lX_PfTk1uoxPpu4#SSTQ0E~ycuR^ zYri!}Sj>?@Gnt2gTijg*t;Xu&>u=5Lv@JF~bD4C?MTmFCcFp6Pi^Q*J#g>r*clgWO zturQXSA#7ubI$E*cXgAuW?XlKYdN23E+RKp7x&n7G3GbNVU~4F*`tGPe(ZxrwzNMo zyf7(Z$hkO*^D_4{C)ehgr2xwpvkIce8qk=gdF?N7(4|NUc?>Yn<6L@8Y^;pqOsAh50MgDQvZd5EZoJXO7QUSZ3`ExV zgTo;;Z`fU;l?5sfzCis!&tRlO(q_1Wy5S%tZvoJX>O`;iNVD=q<@v4c*F#x|UOK~6$JsvR{qa7mfY@xm-S{`;d_z%;RPkeTV|}bN zYy(;<)2nA2vuWRqiW_tHV5)krR!e?s%y;R6%w5|u+vSFRS+DRu@CKb6#d`&b;(J{w zp)Q{923TN;2-JG+VXac3Nuo*lO~zHLCz3UhNcrZN&$WhP+O$+YL<`U2NYr#;UIOdTQvo zv1B&u@6H4foz0k*F!i+NO3rE~Av^T5{U+3YlNbuW?CnWDy0?MP^#=NW$f1Z_1)=v? zqR_esx9&?XoWGBwfe#8oX0RG*b`IosL5NeEymnZ6?Ny-KQ`6;R(*DOe4qw~=d!Lkf zIOue~?(I(RLylVMBBy7xD}GgNE|FCGSh~vkDv)Yl?a@F%(T)bOQ)`tr{F+~X(=yjS z!AC1bDkEq_?aQ}G#6E%h=2DqzI#GrU%DIv^<~}l&H};*_Y8(e{@Jrjmp~S1H^rRdv z8hN{RqF)%M_ydA#!lAvg@OHE}w-xh=9U-UYj z2X)UH8szPHdU-E=@D+|E3E2FKUv+G}`tw~D@{0QUa$CHR6fwp!72rY|$=$SkH=+r1W`tC1>kK7QyN0P$u}|YZ>Wrf7A~+)?g@0+B8t0sNg(r zH`LoCu@Wqmz?&P-lffYY-iSiUyyQuE=AxAiY#a(uuIBj(44=c}kWVKuQXl4SuApwO z3$8nMYhKB<aT0Pi#O(iyCl7Xh4=G%}svR2FaLyw9POpPa{pe7PK1v(t zzNLnK^_f#? z>DClXggPtRWMN)eB81EN50&-&57LX?)>+8{{eIGsqd0>L8oIG(jjY|M46Lado*Qw+ zPRxt$Q}Lga!;&-jEOuLSP(>yV&Xj^gJ3nw zY=;Efd1_e_o09&2F87=epILtM(|37%XM%s}VV{USXQei$IJ^$qu-`_GqD2Vo)1^@7 zn=cY4S=iXoxR@Z<_Ft-xCBnYhk9$n`62=$1&-Jpa3B=c}F7TO6Xh*CCpIDg<=S@|ql33QUo$+yUJ8Syw+Gckv1^xmph#*O z?Esf7{8TqH!lMHgF&c1Npa@($EP1H!85%BnLYc7)e{$7mFz$<7Ir3b&r?>GBoNf+8 z!X-6WfrgA%QssAb{)&!BKFZE%ppnr)AK$7ehlu9fOWEY+nk?WHeYx-Xwt8A|@p)^- zKr7kliSWxj?0iDgDk+$X*DGT~cAi(h>|P=m0u+Iok0+roJOsj*?{WOK`nQKE5+>!2XAH_Y_sj;RP6 z_rd6pN)ODcnAzL@ZrNDB<3WK=la)4Fa@KUni0ZW@@;K*J#Vnft1y^c)Y$b4ga8Dds z=G`y^9~#_EwEPx5_*jnoqd8l3N{_nMJ$F20JCv9he#44u^1y_%L*u&+!WlW{62hN69eSbNA&VyK8ea%5h6_~q-wT(w+RE~x1 zSf3kRiYEG>NNRqTa5|kHyY(Mjo@V!DG1y^#O^$BSMO&G3`1@jgYoCEtt^7ob#n7c9 z`vct>>Xahb*KxT_so{GV8PZy+>*FsJtPP^Rn!dTq*uS29WzZO z`tbR3x^3ra#xJ!Jbbq^-X%6Np*@d;x35*U#mF;7osn-t4ds=eG@S((LNZ*ETM}bl9 zH{OvIy@R#oOR(!xw#Cx|dw*md4b1MB!S9pC3Zctt2a6eQ@iA+wvb2E;mCni{HoiKG z?d_4s7UQe)OjEy(8Wm^HUkK28OpXoKE1)3hrqK@zj2fl(CpV6HcPrhLQit6pH^zC9 zgM99_RLi^}k(T+usbP_0cr9$vgfYl}S&=p;pSzkr0uqgWL)RmW7sJ4I5|V@6_9j}! z>w)P-Q@dJXrk7~gvg4s7H=eqGXKqN+?OB5k=*X{_Ny0>-Wl4V07_U?z^SxoB2yb(I znC#eN8iNk)AeErfg^Xm^FmH(exW0=4m1+!4F{SK~Wu^`Odm$%tpwn$#lg%?aO>a(e z8;>ZJY$jyl)r6^%OoC!XU~JpCAS9HD3YmkYozX{?{zu8XkB?IPyi9fpxqkbK= z1s&`SLMQXVI~PB%;D6HQhgPh&9YJe91m)w8q~}vwq__L~?!o4WW3=g7tBHQkO1(3# zM^3kiTLVpC_nJ}&P;4V+S!QZ~aWGa%*-d=ch!&`Zf}e{{v+_D`1zjQ+=T#7X(`NoY z|F$w#;;C!Ru%gj(RsjgXRH4cQ72f@FThjp)UAQSETQ2CTqwM-MOkH;qG$G1FKFSe> zxW4-pc8S}0_`63@tPcYNEGN!Q2M z(SKe>_~=zb-;(R00T7eKADi~}%uNy}EE(n!%a^OOUk0Ss!%RV%xjQw~r#cPO_C$lxcwFpffm3F4^^;OKJpunI2VV==p>fySN25<+lkg1ft0@Gyt(n|tfdt*NP+fAeGbLsj?b z(`WBKXV=PMoSlF#;@RV#=is#o9FNj3P+a=A&- zspZ)QkC$D<8T)pp3h^o>7SwL11{}mspQbM8pUti~^M1v&vU(O~DlHUYCMx|tPo-}I zl)1*DBqhERSbT9FBvDD3+U}&OoE5M}Z=#+Dt`!anXp8S^8xG#f;8!jJ->|)KKCvDev zu}snHTXX?FsPN)8Ddhw1`}50j+QV7n{deIU^Cc<^cK1e7Q_jL7zE^3Ua>YM*n`>PJ ze$T!BK}ZU2nLhwj?#8Ez{Zx*R)xrVANlvMvvVxw^^YAim+zTzkIt#pqCb+C?3X3n{Y5y^ z%dojlsaaP_A)k5{NGp+4jHq@4(VK+HYzW9<*;}&Opl_p<@EOg|m$6@(4t8G@xt(?XMf!Ul#ml~ly=O#penO~>%DinJ6ZgFA z?2R4DR(<_)gTsiTb>ubmN*#3UEOf+u!ISv@@0Fjx!VX>OpOEdIC@RQ1HWk8k@W9qANOKEL8{DohSceU zY;X%qVu?w!toay27%EOY1cVHhsVCRWw~aK7q7wADJ`k2nPaM1UOz+?ds1RTW7K(Y_-L@q+(Dib;H`^Uyiv0pY;E!@~0)fxV-TYD2< zZB1neA9B?JF687IvC2Zfn2j}+!HqrtK6^kwx3uxcf{C5~R~%(nIWx!ajkJ0JE`xPE+u@zc1s$DJFhDoE^< z{iLB>CkGG58?8o4>(lyh+LOo8U0frEqeb;6z?KGa%oC<~Y~46lweQptTOL|Yp}Qqh-j7VCR5mvvw5YyXV#9}M9QyXwKB|W zB3n&YNqQl%$>>b(+(YUJ)=COyTCJ$aaroI$ggONB_REuE;4lGe@2YqBA_G0^s*W*C zjee)E;T-#k6#0o17NZmv)fD23a6*};LnFL6<>R}**fA=r6qym`k|f%b{Vx#{^&U3| z^w2ko#!)>ddn2a71zVZql~Qv*^MM~YV7rVEY@ZpI5pee4>w)8GFY|tE?n|-IvRaU+ z`%9t?A~CO&;RTmphhFs~pV|D&O1*E6?Mr4}uePzPccHjn* zxbp`^pR~&|`<|My3{T@;`7bb2wC$x?!dm-W%9SS+z2Cy)A%tK}wh*o2cbYM^N9R5+-;0ETT8fyy=SWT0%QToTo;tD!13jx`n5^TtLW(@>9?>+ z_m_yCJ^@xaf=~PcDOCujz5*AK`+-QiG~4(zpx=swD{Ow-+q#)Y8#<@`oX5z|nW;jm zk&^e2UfZJ8Az5{L65LpQaCbK&Ke~(8S_xjbNu48O7^JM}RD*hf3 z;plGpdcj#XUD@2J|71nNvGdKi*sC`buS$@uYe-GJ43gb{QXN=m;4`Y_EYy}NJl2-Z zk2PH(*)QjJ?yhgn_G%YPm8}oF8p-p6?fCz0O7X6R*j{o%Dk=(-akIJ3d?dqs_9H?; zD@J>;HlO230-&q~%d$h%KSq=}`-I@pb`pbji&Z9|tpnavB8B-oDQ6EJsI7ZXDL_t4 zZ?nul2LyZt-_VTF+iydW=TElxUI~hpH)*}TT+9Krd5t`&d8BpeCD7N&7g}8R00g(o zh!iYdO!wTJbpXm^7zrY}z-+%~%v!QJIHA%kvpJ2`t;3hNCOpu}TjxCAVj{J*R=?jV z{N6SGrlr%rsWez5fbL23k<=D%YiG=$y|UDZiY5-6Ea2&-6EDr1LuJ#zmFib^nk~sd za{Jb%G1%X2Z0zdbk8uL2oCcNzHvHLU9ebSfn}P>cm$wqlg-=HQ-r0|?OlMS5Tvd_s zMN@O?NUsNYvp~8ca3rwB4j{!?9%MN}Bg*fqPt8vR8_Im8{7Kq!4Z~{UqAma@$MOXT z=>BGbxaD{Z)3iIfF6VF4 z*KdYPJ5G@JPA#5&g>f7+FO#_1EH)?Dk6@4)Ikxyo(boSO4*xYabH00V`~%`FemBNB z4xk6#HVO!(+mxg0wD>!VM?pE7*z7GpfDYsEai(b>8|JTYQyxL=zL9V#(ddTG zf6=IaVeQEO25bNKV*tw@M3sxCv2`pnHhHye-G@aSXfxe^Qo_3bmz419(TT7p@t1-; z-%KoQIsiJ?4q!f9YfAwdkhG?I*IGJe#YUUG%^wTq@qoOPaxFxvrq;#-yD|BEfdMG7 zoMJWzbC;ReY$E+1J_@+gptettZam+EalR_s_0ybGBTtBl)hKhY|8^kg5exvrfY8Y! z7aVn6g`;nI@$V&k2J95k+k(DNnXGzxPfVm3KBCFq+sC4vE2=753Qmpbs8-T3fTkYB z&!JKPD%I|#f~Ee;)BxITT-)R3vk~=XCf4|s1<|7_)x!idRC^9aZx%RorvDBn8ABHL&L(`AKueu)oa2-Feemx_*vG$aXh5IMWZ0}UXD`sft!e!aI>ibQ z&zr&Nst@29SCN@+b0n4ZRa>sdo#!F=@1#TaLa z)Jzf{eDq%V&=zh$vPju$GYuPn%%B*`Dn&*-Q#UssHuilr%rl%Rsl_G3T$vkcn)AGB zX^BIwdt(I7%zN-i{)1%(fMK@iMt&`6JZ$)`FYhH>cS7e&)TP&xb;)%C28t+WM+3dK z+2NHWfHX^s6FoEE;u>5a`>z*Jo_=bshago8u=@%d+_SgU)F9lT$F}f)X5pJQH9y9X z_z^(xNX{6=J@)$m`&Ga>iSyAp>CgI$kMWe)zV7!8u<9sB|_19Zec$?tQ!!@BGd3&*}c9d4C_yc84Ty~6*mU;xW~_7i^EJD$@~O)m?-{Hs2iXz_~EnV*gWl_uDQuXF8?QR81SC; zu9!^wpTID`N2@w1xL$&T|EX)p+=kwrKV2oR{-B#3&Xf^G7}&fowsY-wkM4>TbNaub1_ytj4ukPZ@PoD$Z_UM7(#D94`g z%;~*_(7S!m3ma!*T;v|u=D(d3q*f^q1OW9%1oTmfOc3oV!1}SpK@itpk7v6G>E{=| z|F9&Ck<|h8=srOH^97|~BwSyjgkFXJ6tDx{e)9j{4i4Uc@Lc8t28usIa2fLO1?-VF ziV2hGWphr6dpsfv4DFS?8NrfT^nRHB2T!n_lGa<1lIByEo?y;Nyvwn9dY7;v{ZiS~C?y9KvG8u$e_dvKUO}1R z1xgAQd_J10sS4-&F3MAv>JQI2QI=vUCSR8KcbrLI{JF8Gv5wPD`ZQtE2m5x!g_B7R6(b8>$l_ZiK*lv_*Bz-;9<7%j=&ucKdI{qxC)Av-z&uEy6 zz-`5=(>TIkaV_H4S{Ew7Cj%~y8`lwCYv@12cZk1mTA|rsBWo?KMA!ldXiC0M>hqoX|ftNu*1e-qB@z7XE(x8DM~W&B}K zfL|^Za8b8TFMi~gDH5yVH566nZ&$JR_$sfa{};m98+1&B6~<46#py4&MM%37Jf>VZGi6A&5ogC% zj)ToXOesW`Ab&f-KrVB~QDgnLXNtrT`z3tVfkQ275oZvMue_d~DqWV3W+yVfN5a6+ z!8Vq_y9BJEAKB6dF9jc-5~Z$CWB;b|(_rTF^Pm1J>;5q&cnE9oHR86K80p~iCKxTm zxdfZG7P>Z}Rm88Pr|ssOJ(&mh3+7!*V*a^> zqjeizw-%j|N^}0HQy@{#)_>?Nv2;2=nI-oJi$1j-xiL1Z{NA3 zOQE)tf*SqCIzL!@OqOR3-3Kl|1_Ij|lS=gs$JMDA+{DxkNK7D(j3##w~i`kLl%j{WgRQj zmntZ31J``85hDVoH&|HPI!GS~gcUkZQ~q%VW=av+Q+@wb_KFs>ELrGH-DTeCbyq;rWDc?S-+<>SsRl z#utO(6+3d8>n!q#PudIB$sM!OiBzM5n{tnoZ3T2B6iUdEapS5E=x7ehIL^w-SB7-q z)5o#uW?;$I$C|cQeANZPl{hEot4W;~^zf_We~55MeASM8?28vDJ4A%!=Tg%s*lLuj zs93$z972}qAxV_c`;;_`Z*`3u>c;m3YG?B+Ie|Vo{2;Tv)zFEe)6t-RSaHDg(gTBt z{vS#lQ2DE+&VmxL;h=S)Lw~kXu^K7ojK}D#k-p|__OtWZ>YP0lhL2?>i=y;-p0{t_ zaA;jmO9-~swl}oK9tv(T$042Y`v8jqFkvOYgatoN*vDX?ATrbp(;QlxIgfSo3sd%w z^&}NaVt$?Xp@4lkWBY2h{iEf-y!c}~A)tT0T8HL7Y=#K<|AC#oHZLv} zh)a%(UhZ1!yc!ShK}-|AC^TXEGWc9>>BSRCSJ`eYY3#WtD1|=-;7gb^Us#nXaN&uR zj>eQ^;JvDij4d)0fC*W;eos32&_y-1v5^|Hwj?7az}acC4>^oS^4>>j*hhwACYzE< zA=$-GtMQ*3bDN()?y$o9A=#$9Vr1ucs~`Qeny>}|h0Tl%s{E^y@^F%Yh2eK)kaj=3 z>0^Q8v8muuKBV7{H+f@Vpnv(kMIxp>(^$`F1me7v#re5G4}lxXerVgy#J7b8#bVUG zb7_AFE6+D^gtYTw&EL=*kLd=>sxqyAow53o>aNVo zR$pLb;v3gmB&Jd#QJIy!ITCX-@{PU?d$1gIV|tUUOrqXT-?PZi2Y9iUB}C{O{KH7{ zbK{kR|AYtAw!P$M9mukY;n+0T1Ez))Jh1?}4x*O#3rEEzYZ=|ce5RQ@h&>JuDc}q~ zqYfFP3MSq(1pTl=1st!l-bJ)C+dxT{DK!6xGRT89f{N>l$&gVa+jeYahM@n zH%JIKLG3|pm=UQbaCRLrV8Gu1zh&h47f-Tua6XF$i$0YhSqk6P3bOcKUH`p!1WDTG zY1MRAlRPD*rCiR6m2k5tF1#<;-P40C1VRq3sTz@Vl4m^Tm3ZZPd4J=jRhz4kb6bN4 zmPB*(BSoT}VkOey_9#z)WiF3@YRM4%=FETQrS!#1)UdoI!?`v0{NjiC^4U6t>tp0+5evDFT*sDa*tjz zf8QMy>@SUK(ixoa^r-ngL7$;7VYNKLzdYT~G}+9t{4P~zeBxnj>Dl+tp1$cidHDkL zNpW1?z&0Fx#91`Kq58@*zAg0oWO_QKE&ILr3eu>7`#2M`U-wAe$riw^Rhl+-^txrX z9s;_ii$wm(nA^VJOO6VRXhhwf4axomPF6jI&vvcs7wKZP;bJ#o^n11)j=~^y?z8HG z%M|ti+7nDf^*ZPDn78WnZJ(3+_Uv^|Q{V>6eEs!E|55|^9&`A;jPw>o`EXU;+T4}L zrNgTi5;x9rV-{=P4r$4C)VHxxre^;@Z-g^k11!ySU^8Da_icq>{9zWi|B+n zEBLMP;Vz5lQ*WUNymBbaVW;k}{W%&U;9w)WWU|R?vgD#PAM`L)R$tjncs1NJR0y=O z+^?nHBM~^yq?6oB!hfh<1-Vg3>5m7C!u*~m7JozyqQHFB8XP??Q&<7Mus>rqKFbz8zQM?C9+u2F)kJhO zA4>bf&KBQWfP&0$7bFjxS(J0rh|{oZmN zFoGs`TqJ{lTubZHxy)tF__C6GSevH0XKgAM_B(Nt%W34c|&Bts2gm;T-=UnoW zlykV7bYKJ|k=W?sr>-Bglx=jRbC%=rwfaf#ZSZU%!ph+UnvXhC}$;%MEY1 zut0g9mc%$qVNr^BVTDoT6vBhCE9V7@1+c_EKf7=zV}gJxyx>md)pFD~ukrW_noo@p zGV|znn>*|LLgl%|IMp2AOWfvOBHo`-MT!Ufc|$U-z8L@N`n;aE z0Pz<8{BJMY>Da(VbZC41o(yoy!lL-J;BE(nS4LZyMV@+`+v#r@G&`Ke`eRJd-VP+H zj#0I7>@FF3^R`cX$VrNoW@6M(7x4;2$}#jSIo$$77MHwT-6fefcj!yj1Lf1xS4ivT#Xd(t zSjgcl!By0*e#pfyswGS~*GnEI4Yo^ zd^LN+`TLKQHUehfj~Gu99Z{KtXE^V-lH?d>UdboycFUBM6OC_1OZmyQ>}aq29UJE5 z44T3b4^Vc>nxBmL+pf?bpD}Lyrv&muzq=;Rg>x;hrcNS9_&k!C3{3P{lz=IL8hNGB zs#y(CUZQvjM+rYnkuSQY06cSF>RRYZ=<)AR@4R z0Vn(U=bTRlq@F@SrMbMOe*#)_C(~_a7^v9+le=OrTQjszgoJ@=WYM|A9AI+a&JE|B zZ(A+7yl_;CT>J;{{A<8!(*0mmNQf=QYtqj6$f{5THP^K|hz=P=fG|0@2~E{1&kre_ z*p*pHN+`y$lv=v^PvHG+@DWR#OJ>rk9HMVReQ$BeAl20)nIid{!&LpmkIh+<;`^*! z3c4p|`nQc=R>^q{Argnz=OpuX&`iaQq}|df;McW-g*q=?u{oPSuYMml539MY%@VwJ zYu2K!E7D*et3k$vvumYtnxoY`<6}jK5;Cem6&ET#A3*~5D}#gfNA5^qQ`nE8)MNl#^cS-zJ18#>|NY#Fag3=4 zL|_~)kNYU~roA{tMB2FL0T==z6OHT;e*Y`YXw^!4smlF~1Q~>`gzxGBy3MRE-1E@l zI-e7+%&O@-93Gl0o+z$IOu#2(Ma8gH`@WOGO}c;`N&*mAHd^uPZ*!19VfR;G@pX&e z`Tr^Lxb{@yYwaSJ%wjf-L2tNo+}M4oZfNp@#&o4VFo?<95ebWV|0x=XAy5aYTU37_h4D6$X*HQ$3Q^TYuR@TNTnQF-T~<{PQWePcz`*3^qS6) ziC>o+Tpr;N3T1c|J79voEQ&+`Li%e4NcuD_6>abg?1k&*>igib7_ZB)% zj~?7$T0(e7_3_AQ3wo^CB`+CL+l&@i3CxPf_`LMCxd^B#nXLT}m z(+Z~ql}~ZWRhIHVPYXXy9=vxlW(I_}bGtrJgvi;;I(9vDb6%^Yxj>^|=;-EP3rjX-mf{spJ8) z&&2tYfVIb$N~BUNs>`&{Wjw2PgbUtJZ2_@@bI{qkn_w*(l*e}TPhz`l0!(IXIKU|g zn4)4HTPxs6V7P1G(vb@~LYKQ+Phr|}EYIK#HOm{DzEuv6Gerqd0DLsH$LlZCiw$;M zP>=obd;%0bvY}kyr*1$f_yEGl$rC!qvmKoZB-xg<$jx{);D{rxKm1wsc)Ll@{u%B~ zNO>gf+UE+Rz~Rr(qdK|R`F(r743D|^9woI@tJaX%Q<(u7Kbgjh6JVJC_D9Fj=c_)oZ)CSpTTa)adW_0f z;0BLYzhJ>N`C0bg*EhwBn0G$AFFB2~Hv1(rA=xk&j}wmu#MZMtNgGZ=Krt;^cQOMZp5dW`bX_K?*99NmEs$`e5zB3Fsj^*-gtx z!pU*!b1m=s;kS#8+4Rs88kjwaThYuIIk5$(y~N@=)6&CM5xXyn45)N(!zHZdo4XD8i$`m6 zw>Ia8X}Gq%@`s#AU!ZuHAP(U2^}vG@3;N^IisOHxD{$OO5P^V;HP35%`@MYjHVg3m z4HNbJBNoDIitWGgs-S$2vjDzGOXS$c)4$ffwsopEa@Z)z`C$)VLX-h2i@?i9ZccRTTVllzThJvQv z%_E;$fGrFgdHr-^lrdt&AIoQVn1N3+yH#x=-L;YPvc!J=fMfc}GDMg%(>Vd*`j;G->sy?}lXKGXBjXB2)l2ckl)8eDaoD`g>fnhx?!3(a=A?tbj5#Q-VP7x%UcC z)Nh%X{?|VRoSXD~ZX$Bg1h3vI5rOzoLVPUX99nmlp+&E^pm8J?lQ z^#w(UqB1c6n=|6`8A1<>{5147UZlFf7bvakYRUkfTnu~r{(im?>)MkXH3w?VsGEaD z2F~;T=#;L9aQc`*&Ebx=`8jti{p>>I)^P?cRoo>?A7M&~0lMpFa}7r6NBB0A(^XQ_ zvbLP`d9m{NQ7`9zbN!H{hdBEbVI>0m`*$zGaFCfgS(1@~ zsef!1MNuJ3K@L&hXpP(HiHLTLqj^jcB?ZjC5L|UAgMCY6yP;w=M_a9l0h$u-hAy;= zPOH7`rW?OiBwZ8QJWJ%KOXYWU9ban7oqZ<__WD@+0!0bPhEet2Z=+TOBOlWUp zUkH(s7<@@)Lus$Zj=|{MBjf!h3}ljkgga-y+NACa`uuln*LMZP6x2C-Q{!(OIOyTx zKhh~fp2^7?A#O*Xe?i@bxuPorq8M`Cu_CErt#jrqOg^F}BhRj{@Woi{cW^X~E zcNzUEfV@gu*WO_%jJxqY^RLlC#ERRepNVlrVFp^)Y)`;GBr(C#XrIubrC)A;P_Lb@ z;P!4OXN;CMrl|(bQcUqx=7&vRxfu2gEwfvZ&2+5a|%>i(O9AN!BzHdRz zgrbsEfC(Ge!z19!bN};y6{i5@A4O)F%UCobEF}>UgRXEh5m9QUH-e8@RFm=Vg?c+# zibN6l+V-5sUiyqv+1n|IwDnZjP!h=U#LZh>tZ$m8Ak>6bZ=6_of&re zCjcNsS2HNCYj{LYwfN?j|GNKQK=}m_Jem<7i6jA{-Yy~p?>07PTk6NbbtHXddW-F< ze&BFc&IL-hKRjjJm2NFGtAitXS-Z0zB+2%GH>XZl;zWJ-NTstpu&ht=N*%TmedgVz zyZnBMQHFG?$TsG3>fD%=g$=4q*Zk(SEF6ykbf}2LJ2$-~rg+P^Ep=URmTR~^dNqVw zoc2%Na2Q_YGCA_3It1KHn&7;IK6LdUR`@?LReu@62a^8gJuiBl_)xBcgBj%jg)7jm z@GEsa=!?`y%H9hT%~gG&%A}D(2XiS=8bz~bHEuxK*YKKQ%CC8c|@- zsYqFWys=c6nUA3$+XxkX?a6ER%$Q&()}Sh4BJOn98y)+xY|VnxytDw?;)r^-1x}N% zhI+UwJ1OP6(a-?eSCMmvHY4M1?j0?(aoV0;;U3OTMlp&OqObiGt-WeT>})*Dl^H}( zirNk~r1bL8poybNEi?K3E8(9!-xj!M@w)uZ&PybHnD1z6fX~mpHCg5HHD~2?6{Ps# z%CMelc?$o93p9bNr2JhG z4XA|gCQAmBDI&132h{g1rFH9xG{M6u(Ng4mz)8r$e~#{WiKY52fZnD0MT4db+|q^H zMngGCUV$woDUPzZ$^CewF);kO`NIpwHo#n$P0-wFOfrHcxf@vFOgtAdPcSICO7~CctPGnd?BEypz%(I?KpTy=A~)?uHBqI-byl z#nAO`hoRPeY|C{C-3_v=lcI}nR7;p;cz&IrsYMtcf%f#s9Q$qjenJba>$xDU^ css({ width: '80px', height: '80px', - background: `url(${ImageSprite}) ${position}`, + background: `url(${getImageUrl('banksprite', 'webp')}) ${position}`, }); export const bankSelectStyle = css({ diff --git a/client/src/components/Design/components/Icon/Icon.style.ts b/client/src/components/Design/components/Icon/Icon.style.ts index 7f1b89705..1ffd6f473 100644 --- a/client/src/components/Design/components/Icon/Icon.style.ts +++ b/client/src/components/Design/components/Icon/Icon.style.ts @@ -15,7 +15,6 @@ const ICON_DEFAULT_COLOR: Record = { check: 'primary', x: 'gray', pencilMini: 'gray', - toss: 'white', meatballs: 'black', editPencil: 'gray', heundeut: 'gray', diff --git a/client/src/components/Design/components/Icon/Icon.tsx b/client/src/components/Design/components/Icon/Icon.tsx index bc4dc4bee..4f55abcac 100644 --- a/client/src/components/Design/components/Icon/Icon.tsx +++ b/client/src/components/Design/components/Icon/Icon.tsx @@ -1,6 +1,5 @@ /** @jsxImportSource @emotion/react */ -import Toss from '@assets/image/Toss_Symbol_Primary.png'; import InputDelete from '@assets/image/inputDelete.svg'; import Error from '@assets/image/error.svg'; import Confirm from '@assets/image/confirm.svg'; @@ -18,6 +17,10 @@ import {IconProps} from '@HDcomponents/Icon/Icon.type'; import {useTheme} from '@theme/HDesignProvider'; import ChevronDownLarge from '@assets/image/chevronDownLarge.svg'; +import getImageUrl from '@utils/getImageUrl'; + +import Image from '../Image/Image'; + import {iconStyle} from './Icon.style'; export const ICON = { @@ -31,10 +34,11 @@ export const ICON = { check: , x: , pencilMini: , - toss: toss icon, meatballs: , editPencil: , - heundeut: , + heundeut: ( + + ), photoButton: , chevronDown: , } as const; diff --git a/client/src/components/Design/components/Image/Image.tsx b/client/src/components/Design/components/Image/Image.tsx new file mode 100644 index 000000000..d8a303bc0 --- /dev/null +++ b/client/src/components/Design/components/Image/Image.tsx @@ -0,0 +1,15 @@ +type ImageProps = React.ComponentProps<'img'> & { + src: string; + fallbackSrc?: string; +}; + +const Image = ({src, fallbackSrc, ...htmlProps}: ImageProps) => { + return ( + + + + + ); +}; + +export default Image; diff --git a/client/src/components/Logo/Logo.style.ts b/client/src/components/Logo/Logo.style.ts index 89f97ede0..ee3b6ee99 100644 --- a/client/src/components/Logo/Logo.style.ts +++ b/client/src/components/Logo/Logo.style.ts @@ -6,3 +6,8 @@ export const logoStyle = css({ width: '100%', }); + +export const logoImageStyle = css({ + maxWidth: '300px', + width: '100%', +}); diff --git a/client/src/components/Logo/RunningDogLogo.tsx b/client/src/components/Logo/RunningDogLogo.tsx index 602ef24dc..f28daa676 100644 --- a/client/src/components/Logo/RunningDogLogo.tsx +++ b/client/src/components/Logo/RunningDogLogo.tsx @@ -1,9 +1,17 @@ -import {logoStyle} from './Logo.style'; +import Image from '@components/Design/components/Image/Image'; + +import getImageUrl from '@utils/getImageUrl'; + +import {logoImageStyle, logoStyle} from './Logo.style'; const RunningDogLogo = () => { return (
- +
); }; diff --git a/client/src/components/Logo/StandingDogLogo.tsx b/client/src/components/Logo/StandingDogLogo.tsx index 1cfd54029..cb6a1350e 100644 --- a/client/src/components/Logo/StandingDogLogo.tsx +++ b/client/src/components/Logo/StandingDogLogo.tsx @@ -1,9 +1,13 @@ +import Image from '@components/Design/components/Image/Image'; + +import getImageUrl from '@utils/getImageUrl'; + import {logoStyle} from './Logo.style'; const StandingDogLogo = () => { return (
- +
); }; diff --git a/client/src/global.d.ts b/client/src/global.d.ts index 109234ddc..8b3136371 100644 --- a/client/src/global.d.ts +++ b/client/src/global.d.ts @@ -1,5 +1,6 @@ declare module '*.svg'; declare module '*.png'; +declare module '*.webp'; declare namespace NodeJS { interface ProcessEnv { diff --git a/client/src/pages/MainPage/MainPage.tsx b/client/src/pages/MainPage/MainPage.tsx index f6663f618..d608eaf2d 100644 --- a/client/src/pages/MainPage/MainPage.tsx +++ b/client/src/pages/MainPage/MainPage.tsx @@ -1,5 +1,4 @@ import useAmplitude from '@hooks/useAmplitude'; -import useMainPageYScroll from '@hooks/useMainPageYScroll'; import Nav from './Nav/Nav'; import {MainSection} from './Section/MainSection'; diff --git a/client/src/pages/MainPage/Section/CreatorSection/Avatar.tsx b/client/src/pages/MainPage/Section/CreatorSection/Avatar.tsx index d559325e4..8cdf3fe5d 100644 --- a/client/src/pages/MainPage/Section/CreatorSection/Avatar.tsx +++ b/client/src/pages/MainPage/Section/CreatorSection/Avatar.tsx @@ -1,5 +1,3 @@ -import {css} from '@emotion/react'; - import {Text} from '@components/Design'; import {avatarImageStyle, avatarStyle} from './Avatar.style'; diff --git a/client/src/pages/MainPage/Section/CreatorSection/CreatorSection.tsx b/client/src/pages/MainPage/Section/CreatorSection/CreatorSection.tsx index 6bd279fed..1c6682f2a 100644 --- a/client/src/pages/MainPage/Section/CreatorSection/CreatorSection.tsx +++ b/client/src/pages/MainPage/Section/CreatorSection/CreatorSection.tsx @@ -1,5 +1,4 @@ import {css} from '@emotion/react'; -import {useNavigate} from 'react-router-dom'; import Text from '@components/Design/components/Text/Text'; diff --git a/client/src/pages/MainPage/Section/DescriptionSection/DescriptionSection.tsx b/client/src/pages/MainPage/Section/DescriptionSection/DescriptionSection.tsx index 63ed09eb9..a962e7cb9 100644 --- a/client/src/pages/MainPage/Section/DescriptionSection/DescriptionSection.tsx +++ b/client/src/pages/MainPage/Section/DescriptionSection/DescriptionSection.tsx @@ -1,9 +1,12 @@ import {useRef} from 'react'; import Text from '@HDesign/components/Text/Text'; +import Image from '@components/Design/components/Image/Image'; import useImageLazyLoading from '@hooks/useImageLazyLoading'; +import getImageUrl from '@utils/getImageUrl'; + import {descriptionSectionStyle, imgStyle} from './DescriptionSection.style'; const DescriptionSection = () => { @@ -11,13 +14,19 @@ const DescriptionSection = () => { const {imageSrc} = useImageLazyLoading({ targetRef: descriptionRef, - src: `${process.env.IMAGE_URL}/standingDog.svg`, + src: `${process.env.IMAGE_URL}/standingDog.webp`, threshold: 0.05, }); return (
- 행댕이 - 행동대장 마스코트 + 행댕이 - 행동대장 마스코트 + {`행동대장들을 위해 행동대장을 준비했어요 `} diff --git a/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.style.ts b/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.style.ts index cac040ec4..8aa212844 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.style.ts +++ b/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.style.ts @@ -35,7 +35,7 @@ export const textContainerStyle = css({ }); export const imageStyle = css({ - objectFit: 'cover', + objectFit: 'contain', aspectRatio: '1/1', minWidth: '20rem', maxWidth: '25rem', diff --git a/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.tsx b/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.tsx index 7a3d7f44b..f7dca8191 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.tsx +++ b/client/src/pages/MainPage/Section/FeatureSection/RecordMemoryWithPhoto/RecordMemoryWithPhoto.tsx @@ -1,5 +1,3 @@ -import {useRef} from 'react'; - import useImageLazyLoading from '@hooks/useImageLazyLoading'; import {Text} from '@components/Design'; @@ -13,7 +11,7 @@ type RecordMemoryWithPhotoProps = { const RecordMemoryWithPhoto = ({targetRef}: RecordMemoryWithPhotoProps) => { const {imageSrc} = useImageLazyLoading({ targetRef, - src: `${process.env.IMAGE_URL}/feature5.svg`, + src: `${process.env.IMAGE_URL}/feature5.webp`, threshold: 0.05, }); diff --git a/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.style.ts b/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.style.ts index ad2279f7a..e53b5d4cd 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.style.ts +++ b/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.style.ts @@ -35,7 +35,7 @@ export const textContainerStyle = css({ }); export const imageStyle = css({ - objectFit: 'cover', + objectFit: 'contain', aspectRatio: '1/1', minWidth: '15rem', maxWidth: '25rem', diff --git a/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.tsx b/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.tsx index ae2b5b607..a8956df71 100644 --- a/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.tsx +++ b/client/src/pages/MainPage/Section/FeatureSection/SimpleShare/SimpleShare.tsx @@ -11,7 +11,7 @@ const SimpleAccount = () => { const {imageSrc} = useImageLazyLoading({ targetRef: sectionRef, - src: `${process.env.IMAGE_URL}/feature1.svg`, + src: `${process.env.IMAGE_URL}/feature1.webp`, threshold: 0.05, }); diff --git a/client/src/pages/MainPage/Section/MainSection/MainSection.tsx b/client/src/pages/MainPage/Section/MainSection/MainSection.tsx index 8b2491e60..a4422c4ab 100644 --- a/client/src/pages/MainPage/Section/MainSection/MainSection.tsx +++ b/client/src/pages/MainPage/Section/MainSection/MainSection.tsx @@ -1,10 +1,13 @@ import Button from '@HDesign/components/Button/Button'; import Text from '@HDesign/components/Text/Text'; +import Image from '@components/Design/components/Image/Image'; import useMainSectionBackgroundScroll from '@hooks/useMainSectionBackgroundScroll'; import {Icon} from '@components/Design'; +import getImageUrl from '@utils/getImageUrl'; + import { animateWithDelay, backgroundImageStyle, @@ -24,7 +27,12 @@ const MainSection = ({trackStartCreateEvent}: MainSectionProps) => { return (
- +
{`행동대장으로 diff --git a/client/src/utils/getImageUrl.ts b/client/src/utils/getImageUrl.ts new file mode 100644 index 000000000..f1efb0307 --- /dev/null +++ b/client/src/utils/getImageUrl.ts @@ -0,0 +1,5 @@ +const getImageUrl = (name: string, format: 'png' | 'webp' | 'svg') => { + return `${process.env.IMAGE_URL}/${name}.${format}`; +}; + +export default getImageUrl; From 1306d4da7c804d06c9e2c8fba81cf2234ee94699 Mon Sep 17 00:00:00 2001 From: Soyeon Choe <77609591+soi-ha@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:04:02 +0900 Subject: [PATCH 14/14] =?UTF-8?q?feat:=20QR=EC=BD=94=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EC=B4=88=EB=8C=80=ED=95=98=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#783)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: DropDown에 Tap 글씨가 위에 있는 에러 수정 * feat: QR코드로 초대하기 페이지 디자인 구현 및 navigate 추가 * feat: qrcode.react 라이브러리를 활용하여 행사 접속 QR코드 생성 구현 * feat: 데스크탑 초대하기를 DropDown으로 변경하여 QR코드 초대 기능 추가하기 --- client/package-lock.json | 9 ++++ client/package.json | 1 + client/src/components/Design/token/zIndex.ts | 2 +- .../DesktopShareEventButton.tsx | 24 ++++++++--- .../MobileShareEventButton.tsx | 11 +++++ client/src/constants/routerUrls.ts | 1 + .../src/pages/EventPage/EventPageLayout.tsx | 2 +- .../src/pages/QRCodePage/QRCodePage.style.ts | 8 ++++ client/src/pages/QRCodePage/QRCodePage.tsx | 41 +++++++++++++++++++ client/src/router.tsx | 5 +++ 10 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 client/src/pages/QRCodePage/QRCodePage.style.ts create mode 100644 client/src/pages/QRCodePage/QRCodePage.tsx diff --git a/client/package-lock.json b/client/package-lock.json index db19fe1bc..2410cd49a 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -13,6 +13,7 @@ "@emotion/react": "^11.11.4", "@sentry/react": "^8.25.0", "@tanstack/react-query": "^5.51.23", + "qrcode.react": "^4.1.0", "react": "^18.3.1", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.3.1", @@ -19591,6 +19592,14 @@ } ] }, + "node_modules/qrcode.react": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/qrcode.react/-/qrcode.react-4.1.0.tgz", + "integrity": "sha512-uqXVIIVD/IPgWLYxbOczCNAQw80XCM/LulYDADF+g2xDsPj5OoRwSWtIS4jGyp295wyjKstfG1qIv/I2/rNWpQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", diff --git a/client/package.json b/client/package.json index 059f75d8a..5b88a1330 100644 --- a/client/package.json +++ b/client/package.json @@ -89,6 +89,7 @@ "@emotion/react": "^11.11.4", "@sentry/react": "^8.25.0", "@tanstack/react-query": "^5.51.23", + "qrcode.react": "^4.1.0", "react": "^18.3.1", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.3.1", diff --git a/client/src/components/Design/token/zIndex.ts b/client/src/components/Design/token/zIndex.ts index fc32d97bc..f951dbae8 100644 --- a/client/src/components/Design/token/zIndex.ts +++ b/client/src/components/Design/token/zIndex.ts @@ -13,7 +13,7 @@ const BOTTOM_SHEET_DIMMED_LAYER = NUMBER_KEYBOARD_BOTTOM_SHEET + ABOVE; const BOTTOM_SHEET_CONTAINER = BOTTOM_SHEET_DIMMED_LAYER + ABOVE; const TOAST = BOTTOM_SHEET_CONTAINER + ABOVE; const SELECT_OPTION = ABOVE; -const DROPDOWN_LIST = BASE + ABOVE; +const DROPDOWN_LIST = TAB_TEXT + ABOVE; export const ZINDEX = { bottomSheetDimmedLayer: BOTTOM_SHEET_DIMMED_LAYER, diff --git a/client/src/components/ShareEventButton/DesktopShareEventButton.tsx b/client/src/components/ShareEventButton/DesktopShareEventButton.tsx index cf7408ad3..c66942f80 100644 --- a/client/src/components/ShareEventButton/DesktopShareEventButton.tsx +++ b/client/src/components/ShareEventButton/DesktopShareEventButton.tsx @@ -1,12 +1,16 @@ +import {useNavigate} from 'react-router-dom'; + import toast from '@hooks/useToast/toast'; -import {Button} from '@components/Design'; +import {Dropdown, DropdownButton} from '@components/Design'; + +import getEventIdByUrl from '@utils/getEventIdByUrl'; type DesktopShareEventButtonProps = React.PropsWithChildren> & { onCopy: () => Promise; }; -const DesktopShareEventButton = ({onCopy, children, ...buttonProps}: DesktopShareEventButtonProps) => { +const DesktopShareEventButton = ({onCopy}: DesktopShareEventButtonProps) => { const copyAndToast = async () => { await onCopy(); toast.confirm('링크가 복사되었어요 :) \n참여자들에게 링크를 공유해 주세요!', { @@ -15,10 +19,20 @@ const DesktopShareEventButton = ({onCopy, children, ...buttonProps}: DesktopShar }); }; + const navigate = useNavigate(); + const eventId = getEventIdByUrl(); + + const navigateQRPage = () => { + navigate(`/event/${eventId}/qrcode`); + }; + return ( - +
+ + + + +
); }; diff --git a/client/src/components/ShareEventButton/MobileShareEventButton.tsx b/client/src/components/ShareEventButton/MobileShareEventButton.tsx index 1c13f80e9..dd26725d6 100644 --- a/client/src/components/ShareEventButton/MobileShareEventButton.tsx +++ b/client/src/components/ShareEventButton/MobileShareEventButton.tsx @@ -1,7 +1,10 @@ +import {useNavigate} from 'react-router-dom'; + import toast from '@hooks/useToast/toast'; import {Dropdown, DropdownButton} from '@components/Design'; +import getEventIdByUrl from '@utils/getEventIdByUrl'; import initKakao from '@utils/initKakao'; type MobileShareEventButtonProps = { @@ -18,10 +21,18 @@ const MobileShareEventButton = ({copyShare, kakaoShare}: MobileShareEventButtonP }); }; + const navigate = useNavigate(); + const eventId = getEventIdByUrl(); + + const navigateQRPage = () => { + navigate(`/event/${eventId}/qrcode`); + }; + return (
+
diff --git a/client/src/constants/routerUrls.ts b/client/src/constants/routerUrls.ts index e4cecc9d5..af3305706 100644 --- a/client/src/constants/routerUrls.ts +++ b/client/src/constants/routerUrls.ts @@ -11,4 +11,5 @@ export const ROUTER_URLS = { images: '/event/:eventId/images', addImages: '/event/:eventId/admin/add-images', send: 'event/:eventId/:memberId/send', + qrCode: 'event/:eventId/qrcode', }; diff --git a/client/src/pages/EventPage/EventPageLayout.tsx b/client/src/pages/EventPage/EventPageLayout.tsx index b8ae60a25..d46df5b11 100644 --- a/client/src/pages/EventPage/EventPageLayout.tsx +++ b/client/src/pages/EventPage/EventPageLayout.tsx @@ -65,7 +65,7 @@ const EventPageLayout = () => { {isMobile ? ( ) : ( - 정산 초대하기 + )} diff --git a/client/src/pages/QRCodePage/QRCodePage.style.ts b/client/src/pages/QRCodePage/QRCodePage.style.ts new file mode 100644 index 000000000..aa8603b1a --- /dev/null +++ b/client/src/pages/QRCodePage/QRCodePage.style.ts @@ -0,0 +1,8 @@ +import {css} from '@emotion/react'; + +export const QRCodeStyle = () => css` + position: relative; + display: flex; + justify-content: center; + align-items: center; +`; diff --git a/client/src/pages/QRCodePage/QRCodePage.tsx b/client/src/pages/QRCodePage/QRCodePage.tsx new file mode 100644 index 000000000..3cbcb37d9 --- /dev/null +++ b/client/src/pages/QRCodePage/QRCodePage.tsx @@ -0,0 +1,41 @@ +import {css} from '@emotion/react'; +import {QRCodeSVG} from 'qrcode.react'; + +import {MainLayout, Top, TopNav} from '@components/Design'; +import {useTheme} from '@components/Design'; + +import getEventPageUrlByEnvironment from '@utils/getEventPageUrlByEnvironment'; +import getEventIdByUrl from '@utils/getEventIdByUrl'; + +import {QRCodeStyle} from './QRCodePage.style'; + +const QRCodePage = () => { + const {theme} = useTheme(); + const eventId = getEventIdByUrl(); + + return ( + + + + +
+ + + + +
+
+ +
+
+ ); +}; + +export default QRCodePage; diff --git a/client/src/router.tsx b/client/src/router.tsx index 9969ce139..aae7b0f63 100644 --- a/client/src/router.tsx +++ b/client/src/router.tsx @@ -24,6 +24,7 @@ const EditBillPage = lazy(() => import('@pages/EditBillPage/EditBillPage')); const Account = lazy(() => import('@pages/AccountPage/Account')); const ImagesPage = lazy(() => import('@pages/ImagesPage/ImagesPage')); const AddImagesPage = lazy(() => import('@pages/AddImagesPage/AddImagesPage')); +const QRCodePage = lazy(() => import('@pages/QRCodePage/QRCodePage')); const router = createBrowserRouter([ { @@ -92,6 +93,10 @@ const router = createBrowserRouter([ element: , errorElement: , }, + { + path: ROUTER_URLS.qrCode, + element: , + }, { path: '*', element: ,