diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 71dbfdeb..00000000 --- a/package-lock.json +++ /dev/null @@ -1,2 +0,0 @@ - "lockfileVersion": 3, - "nanoid": "4.0.2", diff --git a/src/assets/icons/ic_ellipse_blue.svg b/src/assets/icons/ic_ellipse_blue.svg new file mode 100644 index 00000000..4064419b --- /dev/null +++ b/src/assets/icons/ic_ellipse_blue.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/assets/images/img_intro_card1.png b/src/assets/images/img_intro_card1.png new file mode 100644 index 00000000..c336b345 Binary files /dev/null and b/src/assets/images/img_intro_card1.png differ diff --git a/src/assets/images/img_intro_card2.png b/src/assets/images/img_intro_card2.png new file mode 100644 index 00000000..a86f6841 Binary files /dev/null and b/src/assets/images/img_intro_card2.png differ diff --git a/src/assets/images/img_intro_card3.png b/src/assets/images/img_intro_card3.png new file mode 100644 index 00000000..7afad9ae Binary files /dev/null and b/src/assets/images/img_intro_card3.png differ diff --git a/src/components/Footer/MakersNForm/style.ts b/src/components/Footer/MakersNForm/style.ts index 82bf913a..d91424af 100644 --- a/src/components/Footer/MakersNForm/style.ts +++ b/src/components/Footer/MakersNForm/style.ts @@ -15,7 +15,7 @@ const FooterForm = styled.div<{ hide: boolean }>` background-color: #1c1d1e; transition: transform 0.3s; - z-index: 10; + z-index: 99; ${({ hide }) => hide diff --git a/src/lib/constants/main.ts b/src/lib/constants/main.ts new file mode 100644 index 00000000..5dd518a3 --- /dev/null +++ b/src/lib/constants/main.ts @@ -0,0 +1,30 @@ +import { default as ImgIntroCard1 } from '@src/assets/images/img_intro_card1.png'; +import { default as ImgIntroCard2 } from '@src/assets/images/img_intro_card2.png'; +import { default as ImgIntroCard3 } from '@src/assets/images/img_intro_card3.png'; + +export const INTRO_CONTENT_LIST = [ + { + id: 1, + title: '열정이 이끄는\n최고 수준의 몰입', + detail: + '열정 하나로 뭉친 SOPT는 끊임 없이 집중하며 성장합니다.\n언제나 어떤 일에 대해서든 최고 수준의 몰입을 유지합니다.', + src: ImgIntroCard1.src, + }, + { + id: 2, + title: '가진 것은 무엇이든\n나누는 문화', + detail: + 'SOPT에서는 모두가 자신의 지식과 경험을\n적극적으로 나눕니다. 이를 통해 다양한 관점에서\n세상의 문제를 해결할 수 있습니다.', + src: ImgIntroCard2.src, + }, + { + id: 3, + title: '함께이기 때문에\n가능한 도전', + detail: + '200여 명의 활동 회원, 3000여 명의 명예 회원들과 함께이기에\n그 어떤 목표에도 용기 내어 도전할 수 있습니다.', + src: ImgIntroCard3.src, + }, +]; + +export const FIRST_INTRO_CONTENT = 1; +export const LAST_INTRO_CONTENT = 3; diff --git a/src/lib/types/main.ts b/src/lib/types/main.ts new file mode 100644 index 00000000..051af9d6 --- /dev/null +++ b/src/lib/types/main.ts @@ -0,0 +1,6 @@ +export type IntroContentType = { + id: number; + title: string; + detail: string; + src: string; +}; diff --git a/src/views/MainPage/MainPage.tsx b/src/views/MainPage/MainPage.tsx index ed8bddbf..981d1191 100644 --- a/src/views/MainPage/MainPage.tsx +++ b/src/views/MainPage/MainPage.tsx @@ -1,5 +1,6 @@ import DummyDiv from '@src/components/common/DummyDiv'; import PageLayout from '@src/components/common/PageLayout'; +import IntroSection from '@src/views/MainPage/components/IntroSection'; import Banner from './components/Banner'; import Introduce from './components/Introduce'; import ScrollInteractiveLogo from './components/ScrollInteractiveLogo'; @@ -9,6 +10,7 @@ function MainPage() { + diff --git a/src/views/MainPage/components/IntroSection/IntroContent/index.tsx b/src/views/MainPage/components/IntroSection/IntroContent/index.tsx new file mode 100644 index 00000000..8924fc1e --- /dev/null +++ b/src/views/MainPage/components/IntroSection/IntroContent/index.tsx @@ -0,0 +1,82 @@ +import { motion } from 'framer-motion'; +import { useState } from 'react'; +import { ReactComponent as IcEllipseBlue } from '@src/assets/icons/ic_ellipse_blue.svg'; +import { FIRST_INTRO_CONTENT, LAST_INTRO_CONTENT } from '@src/lib/constants/main'; +import { IntroContentType } from '@src/lib/types/main'; +import * as S from './style'; + +interface IntroContentProps { + content: IntroContentType; +} + +export default function IntroContent({ content }: IntroContentProps) { + const contentDraw = { + initial: { opacity: 0 }, + visible: { opacity: 1, transition: { opacity: { delay: 0.2, duration: 0.7 } } }, + }; + + const upMotion = { + initial: { y: 10 }, + visible: { + y: 0, + transition: { type: 'spring', damping: 20, stiffness: 120, y: { delay: 0.3, duration: 0.8 } }, + }, + }; + + const lineDraw = { + initial: { pathLength: 0, opacity: 0 }, + visible: { + pathLength: 1, + opacity: 1, + transition: { + pathLength: { delay: 0.8, type: 'spring', duration: 3, bounce: 0, ease: 'easeInOut' }, + opacity: { delay: 0.8, duration: 0.4 }, + }, + }, + }; + + const [isContentVisible, setIsContentVisible] = useState(false); + + return ( + + {content.id === FIRST_INTRO_CONTENT && } + + {content.id !== LAST_INTRO_CONTENT && ( + + + + )} + setIsContentVisible(true)} + onViewportLeave={() => setIsContentVisible(false)} + > + + + + + {content.title} + {content.detail} + + + + + + {content.id === LAST_INTRO_CONTENT && } + + ); +} diff --git a/src/views/MainPage/components/IntroSection/IntroContent/style.ts b/src/views/MainPage/components/IntroSection/IntroContent/style.ts new file mode 100644 index 00000000..77245a85 --- /dev/null +++ b/src/views/MainPage/components/IntroSection/IntroContent/style.ts @@ -0,0 +1,231 @@ +import styled from '@emotion/styled'; +import { motion } from 'framer-motion'; +import Image from 'next/image'; + +export const IntroWrapper = styled(motion.div)` + display: flex; + justify-content: center; + align-items: center; + position: relative; + width: 100vw; + height: 100vh; +`; + +export const Shadow = styled.div` + position: absolute; + z-index: 90; + width: 100%; + height: 120px; +`; + +export const Header = styled(Shadow)` + top: 0; + background: linear-gradient(180deg, #0f1012 0%, rgba(15, 16, 16, 0) 100%); +`; + +export const Footer = styled(Shadow)` + bottom: 0; + background: linear-gradient(360deg, #0f1012 0%, rgba(15, 16, 16, 0) 100%); +`; + +export const Intro = styled.div` + position: relative; + padding: 0 60px 0 104px; + + @media (max-width: 768px) { + padding: 0 30px; + } +`; + +export const AnimatedLine = styled(motion.svg)` + position: absolute; + top: 46px; + left: 57px; + z-index: -1; + + width: 20px; + height: calc(100vh + 7px); + stroke-width: 4px; + stroke-linecap: round; + + @media (max-width: 768px) { + top: 285px; + left: 42px; + } +`; + +export const Circle = styled(motion.div)` + display: flex; + justify-content: center; + align-items: center; + + position: absolute; + top: -160px; + left: -300.91px; + z-index: 10; + + width: 735.833px; + height: 430px; + transform: rotate(-21.134deg); + flex-shrink: 0; + + border-radius: 735.833px; + background: radial-gradient( + 50% 50% at 50% 50%, + rgba(46, 57, 60, 0.35) 0%, + rgba(7, 9, 12, 0) 100% + ); + + & > svg { + width: 130px; + height: 130px; + } + + @media (max-width: 1440px) and (min-width: 769px) { + grid-template-rows: auto 1fr; + grid-gap: 24px 0px; + top: -170px; + left: -300.91px; + } + + @media (max-width: 768px) { + top: 80.19px; + left: -315.83px; + + & > svg { + width: 100px; + height: 100px; + } + } +`; + +export const Content = styled(motion.div)` + display: grid; + grid-template-areas: + 'title image' + 'desc image'; + grid-template-rows: auto 1fr; + grid-template-columns: 590px minmax(323px, 1fr); + grid-gap: 24px 57px; + + position: relative; + z-index: 20; + + @media (max-width: 1440px) and (min-width: 769px) { + grid-template-columns: minmax(400px, 1fr) minmax(auto, 1fr); + grid-gap: 24px 36px; + } + + @media (max-width: 768px) { + grid-template-areas: + 'image' + 'title' + 'desc'; + grid-template-rows: auto auto 103px; + grid-template-columns: none; + grid-gap: 16px; + font-size: 28px; + } +`; + +export const ContentTitle = styled(motion.h2)` + grid-area: title; + margin-top: 38px; + + background: linear-gradient(93deg, #e1edf0 78.65%, #fff 128.82%, #e6eff2 137.19%); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + + font-family: SUIT; + font-size: 48px; + font-style: normal; + font-weight: 700; + line-height: normal; + white-space: pre-wrap; + + @media (max-width: 1440px) and (min-width: 769px) { + margin-top: 28px; + font-size: 40px; + } + + @media (max-width: 768px) { + width: fit-content; + margin-top: 48px; + margin-left: 58px; + font-size: 28px; + } +`; + +export const ContentDetail = styled(motion.h3)` + grid-area: desc; + + color: #b9c7c8; + font-family: SUIT; + font-size: 24px; + font-style: normal; + font-weight: 500; + line-height: 160%; /* 38.4px */ + letter-spacing: -0.48px; + white-space: pre-wrap; + + @media (max-width: 1440px) and (min-width: 769px) { + font-size: 20px; + letter-spacing: -0.4px; + word-break: keep-all; + } + + @media (max-width: 1091px) and (min-width: 769px) { + white-space: normal; + } + + @media (max-width: 768px) { + max-width: 259px; + margin-left: 58px; + font-size: 16px; + letter-spacing: -0.32px; + white-space: normal; + word-break: keep-all; + } +`; + +export const ContentImage = styled(Image)` + grid-area: image; + width: 100%; + height: auto; + border-radius: 50px; + object-fit: cover; + + @media (max-width: 1440px) and (min-width: 769px) { + max-width: 540px; + min-height: 270px; + border-radius: 36px; + } + + @media (max-width: 768px) { + max-width: 323px; + min-width: 264px; + height: 215px; + border-radius: 25px; + box-shadow: 0px 0px 64px 0px rgba(15, 16, 18, 0.9); + } +`; + +export const BackLight = styled.div<{ isContentVisible: boolean }>` + position: absolute; + top: -114.94px; + left: 385.37px; + z-index: -99; + + width: 1954px; + height: 1084px; + transform: rotate(7.639deg); + background: radial-gradient(45.16% 45.16% at 50% 50%, #2c3242 0%, rgba(15, 15, 18, 0) 100%); + + opacity: ${({ isContentVisible }) => (isContentVisible ? '1' : '0')}; + transition: opacity 0.3s ease-in-out; + + @media (max-width: 768px) { + opacity: 0; + } +`; diff --git a/src/views/MainPage/components/IntroSection/index.tsx b/src/views/MainPage/components/IntroSection/index.tsx new file mode 100644 index 00000000..68fc0810 --- /dev/null +++ b/src/views/MainPage/components/IntroSection/index.tsx @@ -0,0 +1,17 @@ +import { INTRO_CONTENT_LIST } from '@src/lib/constants/main'; +import IntroContent from '@src/views/MainPage/components/IntroSection/IntroContent'; +import * as S from './style'; + +export default function IntroCardList() { + return ( + + +
+ {INTRO_CONTENT_LIST.map((content) => ( + + ))} +
+ +
+ ); +} diff --git a/src/views/MainPage/components/IntroSection/style.ts b/src/views/MainPage/components/IntroSection/style.ts new file mode 100644 index 00000000..83669ffe --- /dev/null +++ b/src/views/MainPage/components/IntroSection/style.ts @@ -0,0 +1,23 @@ +import styled from '@emotion/styled'; + +export const IntroSection = styled.section` + position: relative; + height: calc(300vh + 240px); +`; + +export const Shadow = styled.div` + position: sticky; + z-index: 90; + width: 100%; + height: 120px; +`; + +export const Header = styled(Shadow)` + top: 0; + background: linear-gradient(180deg, #0f1012 0%, rgba(15, 16, 16, 0) 100%); +`; + +export const Footer = styled(Shadow)` + bottom: 0; + background: linear-gradient(360deg, #0f1012 0%, rgba(15, 16, 16, 0) 100%); +`;