diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d29575 --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/build + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/package.json b/package.json new file mode 100644 index 0000000..2cdb7d5 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "10time", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.14.1", + "@testing-library/react": "^13.0.0", + "@testing-library/user-event": "^13.2.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.16.0", + "react-scripts": "5.0.1", + "styled-components": "^6.0.8", + "styled-reset": "^4.5.1", + "web-vitals": "^2.1.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..624688b Binary files /dev/null and b/public/favicon.png differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..3a0b9a0 --- /dev/null +++ b/public/index.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + 1만 시간의 법칙 + + + +
+ + + \ No newline at end of file diff --git a/src/App.js b/src/App.js new file mode 100644 index 0000000..0bf14d7 --- /dev/null +++ b/src/App.js @@ -0,0 +1,34 @@ +import { createGlobalStyle } from 'styled-components'; +import reset from 'styled-reset'; +import Router from './router/Router'; + +const GlobalStyle = createGlobalStyle` + ${reset} + + body { + background-color: #5B2386; + } + + button { + cursor: pointer; + } + + @font-face { + font-family: 'tvn'; + src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_two@1.0/OTEnjoystoriesBA.woff') format('woff'); + font-weight: normal; + font-style: normal; + } + +`; + +function App() { + return ( + <> + + + + ); +} + +export default App; diff --git a/src/components/Footer/Footer.jsx b/src/components/Footer/Footer.jsx new file mode 100644 index 0000000..c0a968f --- /dev/null +++ b/src/components/Footer/Footer.jsx @@ -0,0 +1,16 @@ +import React from 'react'; +import wenivLogo from '../../images/weniv.svg'; +import { FooterStyle, FooterText, WenivLogo } from './footer.styled'; + +export default function Footer() { + return ( + + + + ※ 본 서비스 내 이미지 및 콘텐츠의 저작권은 주식회사 WeNiv에 있습니다. +
+ 수정 및 재배포, 무단 도용 시 법적인 문제가 발생할 수 있습니다. +
+
+ ); +} diff --git a/src/components/Footer/footer.styled.js b/src/components/Footer/footer.styled.js new file mode 100644 index 0000000..5b61155 --- /dev/null +++ b/src/components/Footer/footer.styled.js @@ -0,0 +1,37 @@ +import styled from 'styled-components'; + +export const FooterStyle = styled.footer` + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; + + padding-top: 130px; + padding-bottom: 116px; + + @media (max-width: 767px) { + padding-top: 83px; + padding-bottom: 32px; + } +`; + +export const WenivLogo = styled.img` + @media (max-width: 767px) { + width: 91px; + height: 18px; + } +`; + +export const FooterText = styled.p` + font-family: Noto Sans KR, sans-serif; + text-align: center; + font-size: 12px; + color: #fff; + font-weight: 400; + line-height: 18px; + + @media (max-width: 767px) { + color: rgba(255, 255, 255, 0.7); + font-size: 9px; + } +`; diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx new file mode 100644 index 0000000..7c9b6f1 --- /dev/null +++ b/src/components/Header/Header.jsx @@ -0,0 +1,29 @@ +import React from 'react'; +import logo from '../../images/logo.svg'; +import logoCircle from '../../images/logoCircle.svg'; +import { + HeaderComponent, + Logo, + LogoCircle, + Quote, + QuoteBox, + StrongSpan, + Title, +} from './header.styled'; + +export default function Header() { + return ( + + + + “연습은 어제의 당신보다 당신을 더 낫게 만든다.” + + + 1만 시간의 법칙은 + + 어떤 분야의 전문가가 되기 위해서는 + 최소한 1만 시간의 훈련이 필요하다는 법칙이다. + + + ); +} diff --git a/src/components/Header/header.styled.js b/src/components/Header/header.styled.js new file mode 100644 index 0000000..c4be84a --- /dev/null +++ b/src/components/Header/header.styled.js @@ -0,0 +1,112 @@ +import styled from 'styled-components'; +import leftQuote from '../../images/leftQuote.svg'; +import rightQuote from '../../images/rightQuote.svg'; + +export const HeaderComponent = styled.header` + display: flex; + align-items: center; + flex-direction: column; + + padding-top: 140px; + + @media (max-width: 767px) { + padding-top: 41px; + } +`; + +export const Logo = styled.img` + padding-top: 54px; + + @media (max-width: 767px) { + padding-top: 41px; + width: 267px; + height: 53px; + } +`; + +export const LogoCircle = styled.img` + position: absolute; + + @media (max-width: 767px) { + width: 125px; + height: 126px; + } +`; + +export const Title = styled.h1` + padding-top: 125px; + + font-family: tvn; + font-size: 36px; + color: #f5df4d; + font-weight: 700; + + @media (max-width: 767px) { + padding-top: 69px; + font-size: 22px; + } +`; + +export const QuoteBox = styled.div` + padding-top: 78px; + + position: relative; + + @media (max-width: 767px) { + padding-top: 62px; + } + + &::after { + content: ''; + background-image: url(${rightQuote}); + position: absolute; + top: 91px; + right: -51px; + width: 37px; + height: 32px; + + @media (max-width: 767px) { + top: 85px; + right: -50px; + } + } + + &::before { + content: ''; + background-image: url(${leftQuote}); + position: absolute; + top: 91px; + left: -51px; + width: 37px; + height: 32px; + + @media (max-width: 767px) { + top: 85px; + left: -50px; + } + } +`; + +export const Quote = styled.h2` + padding-bottom: 18px; + + font-family: gmarketMedium; + font-size: 18px; + color: #fff; + text-align: center; + font-weight: 400; + + @media (max-width: 767px) { + font-size: 14px; + } +`; + +export const StrongSpan = styled.span` + font-family: gmarketBold; + font-size: 24px; + font-weight: 700; + + @media (max-width: 767px) { + font-size: 14px; + } +`; diff --git a/src/components/Input/Input.jsx b/src/components/Input/Input.jsx new file mode 100644 index 0000000..6cbe075 --- /dev/null +++ b/src/components/Input/Input.jsx @@ -0,0 +1,76 @@ +import React, { useState } from 'react'; +import { Words, InputTag, Form, Btn, FingerImg, BtnBox } from './Input.styled'; +import finger from '../../images/finger.svg'; +import Output from '../Output/Output'; + +export default function Input() { + const [job, setJob] = useState({ input: '', display: '' }); + const [time, setTime] = useState({ input: '', display: '' }); + const [viewState, setViewState] = useState(false); + + const onDisplayInfo = (e) => { + e.preventDefault(); + if (job.input === '') return alert('직업을 입력해주세요'); + if (time.input === '') return alert('시간을 입력해주세요'); + + setJob({ ...job, display: job.input }); + setTime({ ...time, display: parseInt(10000 / time.input) }); + setViewState(true); + }; + + return ( + <> +
onDisplayInfo(e)}> + + 나는 + setJob({ ...job, input: e.target.value })} + /> + 전문가가 될 것이다. + + {window.innerWidth > 768 ? ( + <> + + 그래서 앞으로 매일 하루에 + setTime({ ...time, input: e.target.value })} + /> + 시간씩 훈련할 것이다. + + + 나는 며칠 동안 훈련을 해야 1만 시간이 될까? + + + + ) : ( + <> + 그래서 앞으로 매일 하루에 + + setTime({ ...time, input: e.target.value })} + /> + 시간씩 훈련할 것이다. + + + + 나는 며칠 동안 훈련을 해야 +
1만 시간이 될까? +
+ +
+ + )} +
+ + + ); +} diff --git a/src/components/Input/Input.styled.js b/src/components/Input/Input.styled.js new file mode 100644 index 0000000..50065f6 --- /dev/null +++ b/src/components/Input/Input.styled.js @@ -0,0 +1,100 @@ +import styled from 'styled-components'; +import finger from '../../images/finger.svg'; + +export const Form = styled.form` + display: flex; + flex-direction: column; + align-items: center; + + padding-top: 84px; + gap: 27px; + + @media (max-width: 767px) { + padding-top: 62px; + gap: 18px; + } +`; + +export const Words = styled.p` + display: flex; + align-items: center; + justify-content: center; + + font-family: gmarketMedium; + font-size: 24px; + color: #fff; + font-weight: 400; + + @media (max-width: 767px) { + font-size: 14px; + white-space: pre-wrap; + } +`; + +export const InputTag = styled.input` + width: 228px; + height: 57px; + margin: 0 17px; + + font-family: gmarketMedium; + font-size: 24px; + font-weight: 400; + text-align: center; + + border-radius: 7px; + border: none; + + @media (max-width: 767px) { + width: 156px; + height: 37px; + + font-size: 14px; + margin: 0 4px; + } + + &::placeholder { + color: #babcbe; + + @media (max-width: 767px) { + font-size: 14px; + } + } +`; + +export const BtnBox = styled.div` + display: flex; + margin-top: 88px; + + @media (max-width: 767px) { + margin-top: 49px; + } +`; + +export const Btn = styled.button` + padding: 21px 49px; + background: #fcee21; + + font-family: gmarketBold; + text-align: center; + font-size: 24px; + color: #5b2386; + font-weight: 700; + + border-radius: 56px; + box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.5); + + @media (max-width: 767px) { + font-size: 14px; + padding: 18px 36.5px; + border-radius: 13px; + } +`; + +export const FingerImg = styled.img` + padding-top: 14px; + padding-left: 7px; + + @media (max-width: 767px) { + padding-top: 30px; + } +`; diff --git a/src/components/Output/Modal/Modal.jsx b/src/components/Output/Modal/Modal.jsx new file mode 100644 index 0000000..6291ce9 --- /dev/null +++ b/src/components/Output/Modal/Modal.jsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Btn, LicatImg, ModalBackUp, ModalContainer, SubText, Title } from './Modal.styled'; +import licat from '../../../images/licat.svg'; + +export default function Modal({ setModalState }) { + const onCloseModal = () => { + setModalState(false); + }; + + const handleNoneClick = (e) => { + e.stopPropagation(); + }; + + return ( + onCloseModal()}> + handleNoneClick(e)}> + 화이팅!!♥♥♥ + 당신의 꿈을 응원합니다! + + {window.innerWidth > 768 ? ( + onCloseModal()}>종료하고 진짜 훈련하러 가기 GO!GO! + ) : ( + onCloseModal()}> + 종료하고 진짜 +
훈련하러 가기 GO!GO! +
+ )} +
+
+ ); +} diff --git a/src/components/Output/Modal/Modal.styled.js b/src/components/Output/Modal/Modal.styled.js new file mode 100644 index 0000000..af7071f --- /dev/null +++ b/src/components/Output/Modal/Modal.styled.js @@ -0,0 +1,95 @@ +import styled from 'styled-components'; + +export const ModalContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + + background-color: #fff; + border-radius: 30px; + + width: 800px; + height: 800px; + + font-family: tvn; + font-size: 96px; + color: #5b2386; + font-weight: 700; + box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.5); + + position: absolute; + + @media (max-width: 767px) { + width: 330px; + height: 500px; + } +`; + +export const ModalBackUp = styled.div` + display: flex; + justify-content: center; + align-items: center; + + background-color: rgba(0, 0, 0, 0.4); + border-radius: 10px; + + position: fixed; + z-index: 1; + top: 0; + left: 0; + right: 0; + bottom: 0; +`; + +export const Title = styled.p` + font-size: 96px; + + padding-top: 76px; + padding-bottom: 4px; + + @media (max-width: 767px) { + padding-top: 40px; + padding-bottom: 0; + color: #5b2386; + font-size: 64px; + } +`; +export const SubText = styled.p` + font-size: 36px; + + padding-bottom: 33px; + + @media (max-width: 767px) { + font-size: 36px; + } +`; + +export const LicatImg = styled.img` + padding-bottom: 74px; + + @media (max-width: 767px) { + width: 300px; + height: 200px; + padding-bottom: 25px; + } +`; + +export const Btn = styled.button` + color: #5b2386; + text-align: center; + font-family: gmarketBold; + font-size: 24px; + font-weight: 700; + border: none; + + padding: 21px 49px; + border-radius: 56px; + background: #fcee21; + box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.5); + + @media (max-width: 767px) { + font-size: 14px; + padding: 18px 45px; + border-radius: 13px; + } +`; diff --git a/src/components/Output/Output.jsx b/src/components/Output/Output.jsx new file mode 100644 index 0000000..308bc97 --- /dev/null +++ b/src/components/Output/Output.jsx @@ -0,0 +1,36 @@ +import React, { useState } from 'react'; +import { + BtnBox, + GoBtn, + OutputWords, + Section, + ShareBtn, + StrongText, + WordsBox, +} from './output.styled'; +import Modal from './Modal/Modal'; + +export default function Output({ job, time, view }) { + const [modalState, setModalState] = useState(false); + return ( + <> + {modalState && } + {view && ( +
+ + + 당신은 {job}전문가가 되기 위해 + + + 대략 {time}일 이상 훈련하셔야 합니다! :) + + + + setModalState(true)}>훈련하러 가기 GO!GO! + alert('공유 완료!')}>공유하기 + +
+ )} + + ); +} diff --git a/src/components/Output/output.styled.js b/src/components/Output/output.styled.js new file mode 100644 index 0000000..ebecf15 --- /dev/null +++ b/src/components/Output/output.styled.js @@ -0,0 +1,77 @@ +import styled from 'styled-components'; +import { Words } from '../Input/Input.styled'; + +export const Section = styled.section` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + padding-top: 147px; + + @media (max-width: 767px) { + padding-top: 64px; + } +`; + +export const WordsBox = styled.div` + padding-bottom: 127px; + + @media (max-width: 767px) { + padding-bottom: 55px; + } +`; + +export const OutputWords = styled(Words)` + padding-bottom: 17px; + + @media (max-width: 767px) { + font-size: 14px; + font-weight: 400; + } +`; + +export const StrongText = styled.strong` + padding: 0 12px; + + font-family: gmarketMedium; + font-size: 72px; + color: #fff; + font-weight: 700; + + @media (max-width: 767px) { + font-size: 24px; + } +`; + +export const BtnBox = styled.div` + display: flex; + column-gap: 18px; +`; + +export const GoBtn = styled.button` + background: #fcee21; + padding: 21px 49px; + + font-family: gmarketBold; + font-size: 24px; + color: #5b2386; + font-weight: 700; + + border-radius: 56px; + box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.5); + + @media (max-width: 767px) { + font-size: 14px; + padding: 13px 17.5px; + } +`; + +export const ShareBtn = styled(GoBtn)` + background-color: #fff; + + @media (max-width: 767px) { + font-size: 14px; + padding: 13px 19px; + } +`; diff --git a/src/fonts/GmarketSansBold.otf b/src/fonts/GmarketSansBold.otf new file mode 100644 index 0000000..9335b26 Binary files /dev/null and b/src/fonts/GmarketSansBold.otf differ diff --git a/src/fonts/GmarketSansLight.otf b/src/fonts/GmarketSansLight.otf new file mode 100644 index 0000000..c588d3e Binary files /dev/null and b/src/fonts/GmarketSansLight.otf differ diff --git a/src/fonts/GmarketSansMedium.otf b/src/fonts/GmarketSansMedium.otf new file mode 100644 index 0000000..af2cfc3 Binary files /dev/null and b/src/fonts/GmarketSansMedium.otf differ diff --git a/src/fonts/font.css b/src/fonts/font.css new file mode 100644 index 0000000..20493d9 --- /dev/null +++ b/src/fonts/font.css @@ -0,0 +1,20 @@ +@font-face { + font-family: 'gmarketLight'; + src: url('./GmarketSansLight.otf') format('woff'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'gmarketMedium'; + src: url('./GmarketSansMedium.otf') format('woff'); + font-style: normal; + font-weight: normal; +} + +@font-face { + font-family: 'gmarketBold'; + src: url('./GmarketSansBold.otf') format('woff'); + font-weight: normal; + font-style: normal; +} diff --git a/src/images/finger.svg b/src/images/finger.svg new file mode 100644 index 0000000..890fb59 --- /dev/null +++ b/src/images/finger.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/images/leftQuote.svg b/src/images/leftQuote.svg new file mode 100644 index 0000000..5c851f6 --- /dev/null +++ b/src/images/leftQuote.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/images/licat.svg b/src/images/licat.svg new file mode 100644 index 0000000..cdf0871 --- /dev/null +++ b/src/images/licat.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/logo.svg b/src/images/logo.svg new file mode 100644 index 0000000..9dc5416 --- /dev/null +++ b/src/images/logo.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/images/logoCircle.svg b/src/images/logoCircle.svg new file mode 100644 index 0000000..42a3d18 --- /dev/null +++ b/src/images/logoCircle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/images/rightQuote.svg b/src/images/rightQuote.svg new file mode 100644 index 0000000..8855dfa --- /dev/null +++ b/src/images/rightQuote.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/images/weniv.svg b/src/images/weniv.svg new file mode 100644 index 0000000..360f357 --- /dev/null +++ b/src/images/weniv.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..bc50b13 --- /dev/null +++ b/src/index.js @@ -0,0 +1,12 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import { BrowserRouter } from 'react-router-dom'; +import './fonts/font.css'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + +); diff --git a/src/pages/Mainpage.jsx b/src/pages/Mainpage.jsx new file mode 100644 index 0000000..8437acd --- /dev/null +++ b/src/pages/Mainpage.jsx @@ -0,0 +1,14 @@ +import React from 'react'; +import Header from '../components/Header/Header'; +import Input from '../components/Input/Input'; +import Footer from '../components/Footer/Footer'; + +export default function Mainpage() { + return ( + <> +
+ +