-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature] - 랜딩 페이지 구현 #539
Changes from 16 commits
96cb45d
cd9ecbd
ab1eb3e
4d4e915
651a67d
1bdc713
4b6114b
295ad29
3b33142
f7c93da
9df1e6c
b4065e0
a53276f
f3854b2
ba1b69c
600bd57
3b229c7
39d5a9a
ae1dde5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,7 @@ | ||
export { default as ExcitedTturi } from "./excitedTturi.webp"; | ||
export { default as BigTturi } from "./bigTturi.webp"; | ||
export { default as MainPageImage } from "./mainPage.webp"; | ||
export { default as SpeechBubbleLeft } from "./speechBubbleLeft.webp"; | ||
export { default as SpeechBubbleRight } from "./speechBubbleRight.webp"; | ||
export { default as TravelogueDetailPageImage } from "./travelogueDetailPage.webp"; | ||
export { default as travelPlanDetailPageImage } from "./travelPlanDetailPage.webp"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,10 @@ import styled from "@emotion/styled"; | |
|
||
import type { TextProps } from "./Text"; | ||
|
||
export const Text = styled.p<{ $textType: TextProps["textType"] }>` | ||
export const Text = styled.p<{ | ||
$textType: TextProps["textType"]; | ||
$isInline: TextProps["isInline"]; | ||
}>` | ||
${({ theme, $textType = "body" }) => theme.typography.mobile[$textType]} | ||
${({ $isInline }) => ($isInline === true ? "display: inline" : "")} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. inline 여부에 따라 inline 과 block 식으로 해도 괜찮을 거 같은데 이 부분은 참고만 해요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그게 더 깔끔하겠네요 수정햇습니다 |
||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { css, keyframes } from "@emotion/react"; | ||
|
||
export const fadeIn = keyframes` | ||
0% { | ||
opacity: 0; | ||
transform: translateY(1rem); | ||
} | ||
100% { | ||
opacity: 1; | ||
transform: translateY(0); | ||
} | ||
`; | ||
|
||
const ANIMATION_MAP = { | ||
fadeIn: fadeIn, | ||
}; | ||
|
||
export const createAnimation = ({ | ||
shouldAnimate, | ||
animation, | ||
delay = 300, | ||
}: { | ||
shouldAnimate: boolean; | ||
animation: keyof typeof ANIMATION_MAP; | ||
delay?: number; | ||
}) => css` | ||
opacity: 0; | ||
|
||
animation: ${shouldAnimate ? ANIMATION_MAP[animation] : "none"} 0.5s ease-in-out ${delay}ms | ||
forwards; | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { css } from "@emotion/react"; | ||
import styled from "@emotion/styled"; | ||
|
||
import theme from "@styles/theme"; | ||
import { PRIMITIVE_COLORS } from "@styles/tokens"; | ||
|
||
export const Layout = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
justify-content: space-between; | ||
align-items: center; | ||
position: relative; | ||
width: 100%; | ||
height: 100vh; | ||
max-width: 48rem; | ||
padding: ${theme.spacing.l} ${theme.spacing.l} 0 ${theme.spacing.l}; | ||
|
||
background-color: ${PRIMITIVE_COLORS.blue[50]}; | ||
`; | ||
export const Image = styled.img` | ||
position: absolute; | ||
bottom: 0; | ||
z-index: 0; | ||
width: 100%; | ||
`; | ||
export const titleStyle = css` | ||
margin-top: ${theme.spacing.xxl}; | ||
|
||
color: ${theme.colors.primary}; | ||
`; | ||
export const Container = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
gap: ${({ theme }) => theme.spacing.l}; | ||
`; | ||
export const buttonStyle = css` | ||
background-color: ${PRIMITIVE_COLORS.white}; | ||
|
||
color: ${theme.colors.primary}; | ||
|
||
box-shadow: 0 4px 10px rgb(0 0 0 / 5%); | ||
transition: all 0.2s ease-in-out; | ||
|
||
&:hover { | ||
background-color: ${theme.colors.primary}; | ||
|
||
color: ${PRIMITIVE_COLORS.white}; | ||
} | ||
`; | ||
export const Gradient = styled.div` | ||
position: absolute; | ||
bottom: 0; | ||
left: 0; | ||
z-index: 10; | ||
width: 100%; | ||
height: 20rem; | ||
background: linear-gradient(to bottom, rgb(255 255 255 / 0%) 0%, rgb(255 255 255 / 50%) 100%); | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { useNavigate } from "react-router-dom"; | ||
|
||
import { Button, Text } from "@components/common"; | ||
|
||
import { ROUTE_PATHS_MAP } from "@constants/route"; | ||
|
||
import { BigTturi } from "@assets/webp"; | ||
|
||
import { createAnimation } from "../Animation.styled"; | ||
import useAnimationObserver from "../hook/useAnimationObserver"; | ||
import * as S from "./FirstPage.styled"; | ||
|
||
const FirstPage = () => { | ||
const navigate = useNavigate(); | ||
const handleClick = () => { | ||
navigate(ROUTE_PATHS_MAP.main); | ||
}; | ||
const { registerElement, getAnimationState } = useAnimationObserver(); | ||
|
||
return ( | ||
<S.Layout> | ||
<S.Container> | ||
<div | ||
ref={registerElement("Title1")} | ||
css={createAnimation({ | ||
animation: "fadeIn", | ||
shouldAnimate: getAnimationState("Title1").shouldAnimate, | ||
})} | ||
> | ||
<Text textType="heading" css={S.titleStyle}> | ||
To your route, 투룻! | ||
</Text> | ||
</div> | ||
|
||
<div | ||
ref={registerElement("Button1")} | ||
css={createAnimation({ | ||
animation: "fadeIn", | ||
shouldAnimate: getAnimationState("Button1").shouldAnimate, | ||
delay: 600, | ||
})} | ||
> | ||
<Button onClick={handleClick} css={S.buttonStyle}> | ||
투룻 사용하기 | ||
</Button> | ||
</div> | ||
</S.Container> | ||
<S.Gradient /> | ||
<S.Image src={BigTturi} alt="" /> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. alt 값을 일부러 비우신 거 같은데 의도하신 바가 무엇인가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 단순히 장식용 이미지라 빈 alt값을 주었습니다! |
||
</S.Layout> | ||
); | ||
}; | ||
export default FirstPage; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { css } from "@emotion/react"; | ||
import styled from "@emotion/styled"; | ||
|
||
import theme from "@styles/theme"; | ||
|
||
export const Layout = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
width: 100%; | ||
max-width: 48rem; | ||
padding: ${theme.spacing.m}; | ||
gap: ${theme.spacing.xxl}; | ||
`; | ||
|
||
export const largeText = css` | ||
font-size: 1.4rem; | ||
`; | ||
|
||
export const titleStyle = css` | ||
text-align: center; | ||
`; | ||
|
||
export const subtitleStyle = css` | ||
margin-bottom: ${theme.spacing.xxl}; | ||
|
||
text-align: center; | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import { Text } from "@components/common"; | ||
|
||
import { createAnimation } from "../Animation.styled"; | ||
import useAnimationObserver from "../hook/useAnimationObserver"; | ||
import * as S from "./FourthPage.styled"; | ||
import SpeechBubble from "./SpeechBubble/SpeechBubble"; | ||
|
||
const FourthPage = () => { | ||
const { registerElement, getAnimationState } = useAnimationObserver(); | ||
|
||
return ( | ||
<S.Layout> | ||
<Text textType="title" css={S.titleStyle}> | ||
투룻을 써본 사람들은 이런걸 좋아했어요! 💙 | ||
</Text> | ||
|
||
<div | ||
ref={registerElement("SpeechBubble1")} | ||
css={createAnimation({ | ||
shouldAnimate: getAnimationState("SpeechBubble1").shouldAnimate, | ||
animation: "fadeIn", | ||
})} | ||
> | ||
<SpeechBubble variants="left" name="🦹🏻️ 이OO"> | ||
<Text textType="detailBold" css={S.largeText}> | ||
여자 친구랑 가는 여행 계획 세우는 게 너무 힘들었는데~ 다른 사람의 여행을 쉽게 여행 | ||
계획으로 가져올 수 있어서 좋아요! | ||
</Text> | ||
</SpeechBubble> | ||
</div> | ||
|
||
<div | ||
ref={registerElement("SpeechBubble2")} | ||
css={createAnimation({ | ||
shouldAnimate: getAnimationState("SpeechBubble2").shouldAnimate, | ||
animation: "fadeIn", | ||
delay: 300, | ||
})} | ||
> | ||
<SpeechBubble variants="right" name="🙋🏻♀️ 최OO"> | ||
<Text textType="detailBold" css={S.largeText}> | ||
친구가 여행 정보 좀 알려달라고 할 때 일일이 알려주기 귀찮았는데 투룻을 사용하니 잘 | ||
정리된 정보로 전달할 수 있어서 좋았어요 | ||
</Text> | ||
</SpeechBubble> | ||
</div> | ||
|
||
<div | ||
ref={registerElement("SpeechBubble3")} | ||
css={createAnimation({ | ||
shouldAnimate: getAnimationState("SpeechBubble3").shouldAnimate, | ||
animation: "fadeIn", | ||
delay: 600, | ||
})} | ||
> | ||
<SpeechBubble variants="left" name="👨🏼🏭 심OO"> | ||
<Text textType="detailBold" css={S.largeText}> | ||
나의 여행을 기록한 것이 <br /> | ||
다른 사람에게도 도움이 되었다는 점이 뿌듯해요 | ||
</Text> | ||
</SpeechBubble> | ||
</div> | ||
|
||
<div | ||
ref={registerElement("Text1")} | ||
css={[ | ||
S.subtitleStyle, | ||
createAnimation({ | ||
shouldAnimate: getAnimationState("Text1").shouldAnimate, | ||
animation: "fadeIn", | ||
}), | ||
]} | ||
> | ||
<Text textType="subTitle">투룻 서비스를 이용해보세요! 😘</Text> | ||
</div> | ||
</S.Layout> | ||
); | ||
}; | ||
|
||
export default FourthPage; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { css } from "@emotion/react"; | ||
import styled from "@emotion/styled"; | ||
|
||
import { PRIMITIVE_COLORS } from "@styles/tokens"; | ||
|
||
export const Layout = styled.section` | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
position: relative; | ||
width: 100%; | ||
`; | ||
export const Image = styled.img` | ||
width: 100%; | ||
height: 220px; | ||
margin: -4rem 0; | ||
`; | ||
export const TextWrapper = styled.div<{ $variants: "left" | "right" }>` | ||
position: absolute; | ||
top: 50%; | ||
transform: translateY(-40%); | ||
left: 10%; | ||
max-width: 80%; | ||
|
||
word-break: keep-all; | ||
word-wrap: break-word; | ||
|
||
color: ${({ theme, $variants }) => | ||
$variants === "left" ? PRIMITIVE_COLORS.white : theme.colors.text.primary}; | ||
`; | ||
export const NameWrapper = styled.div<{ $variants: "left" | "right" }>` | ||
display: flex; | ||
justify-content: ${({ $variants }) => ($variants === "left" ? "flex-start" : "flex-end")}; | ||
width: 100%; | ||
${({ $variants }) => ($variants === "left" ? "padding-left: 10%;" : "padding-right: 10%;")} | ||
`; | ||
export const fontWeightMediumStyle = css` | ||
font-weight: 500; | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Text } from "@components/common"; | ||
|
||
import { SpeechBubbleLeft, SpeechBubbleRight } from "@assets/webp"; | ||
|
||
import * as S from "./SpeechBubble.styled"; | ||
|
||
export interface SpeechBubbleProps { | ||
variants: "left" | "right"; | ||
name: string; | ||
} | ||
|
||
const SpeechBubble = ({ variants, name, children }: React.PropsWithChildren<SpeechBubbleProps>) => { | ||
return ( | ||
<S.Layout> | ||
<S.NameWrapper $variants={variants}> | ||
<Text css={S.fontWeightMediumStyle}>{name}</Text> | ||
</S.NameWrapper> | ||
<S.TextWrapper $variants={variants}>{children}</S.TextWrapper> | ||
<S.Image src={variants === "left" ? SpeechBubbleLeft : SpeechBubbleRight} /> | ||
</S.Layout> | ||
); | ||
}; | ||
|
||
export default SpeechBubble; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { css } from "@emotion/react"; | ||
import styled from "@emotion/styled"; | ||
|
||
export const Layout = styled.div` | ||
display: flex; | ||
flex-direction: column; | ||
width: 100vw; | ||
overflow-y: auto; | ||
`; | ||
|
||
export const PageWrapper = styled.div` | ||
position: relative; | ||
width: 100%; | ||
height: 260vh; | ||
`; | ||
|
||
export const firstPageStyle = css` | ||
position: sticky; | ||
top: 0; | ||
z-index: 1; | ||
`; | ||
|
||
export const secondPageStyle = css` | ||
position: absolute; | ||
top: 100vh; | ||
z-index: 2; | ||
width: 100%; | ||
transition: transform 0.1s ease-out; | ||
`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
빅뚜리