09 완성 정리
09. 완성 및 오류 정리
최종 파일 목록
1src/2├── main.jsx ✅ 라우터 연결3├── router.js ✅ 라우터 설정4├── App.jsx ✅ 01단계5├── index.css ✅ 각 단계에서 추가6└── components/7 ├── SectionTitle.jsx ✅ 05단계8 ├── Nav.jsx ✅ 03단계9 ├── Hero.jsx ✅ 04단계10 ├── AboutMe.jsx ✅ 05단계11 ├── Projects.jsx ✅ 06단계12 ├── Contact.jsx ✅ 07단계13 └── Footer.jsx ✅ 08단계전체 index.css 최종본
모든 단계에서 추가한 CSS를 하나로 정리한 버전입니다.
1/* ─── 초기화 ─────────────────────────────── */2@import "reset-css/reset.css";3
4body {5 font-family: "Pretendard Variable", Pretendard, "Malgun Gothic", sans-serif;6 background-color: #ffffff;7 color: #1e1e1e;8}9
10a {11 text-decoration: none;12 color: inherit;13}14
15ul {16 list-style: none;17}18
19.container {20 padding: 0 10vw;21 margin: 0 auto;22}23
24/* ─── Nav ───────────────────────────────── */25.nav {26 display: flex;27 align-items: center;28 justify-content: space-between;29 height: 99px;30 padding: 0 24px;31 background-color: #ffffff;32 border-bottom: 1px solid #f0f0f0;33}34
35.nav-list {36 display: flex;37 gap: 8px;38}39
40.nav-item {41 display: inline-block;42 padding: 8px 16px;43 border-radius: 100px;44 font-size: 16px;45 color: #1e1e1e;46 transition: background-color 0.2s;47}48
49.nav-item:hover,50.nav-item--active {51 background-color: #f5f5f5;52}53
54/* ─── Hero ──────────────────────────────── */55.hero {56 height: 524px;57 display: flex;58 flex-direction: column;59 justify-content: center;60 gap: 40px;61 padding: 0 24px;62}63
64.hero-title {65 font-size: 72px;66 font-weight: 700;67 color: #0c0c0d;68 line-height: 1.2;69}70
71.hero-subtitle {72 font-size: 32px;73 color: #0c0c0d;74 margin-top: 8px;75}76
77.hero-buttons {78 display: flex;79 gap: 16px;80}81
82.btn {83 display: inline-flex;84 align-items: center;85 justify-content: center;86 gap: 8px;87 padding: 0 20px;88 height: 40px;89 border-radius: 6px;90 font-size: 16px;91 cursor: pointer;92 transition: opacity 0.2s;93}94
95.btn--light { background-color: #e3e3e3; color: #1e1e1e; }96.btn--dark { background-color: #2c2c2c; color: #f5f5f5; }97.btn:hover { opacity: 0.8; }98
99/* ─── 섹션 제목 ──────────────────────────── */100.section-title {101 margin-bottom: 48px;102}103
104.section-title__row {105 display: flex;106 align-items: center;107 gap: 16px;108 margin-bottom: 8px;109}110
111.section-title__line {112 width: 2px;113 height: 36px;114 background-color: #000;115}116
117.section-title__text {118 font-size: 48px;119 font-weight: 700;120 color: #000;121}122
123.section-title__sub {124 font-size: 16px;125 color: #555555;126 padding-left: 18px;127}128
129/* ─── AboutMe ───────────────────────────── */130.about {131 padding: 80px 24px;132}133
134.about-grid {135 display: grid;136 grid-template-columns: 1fr 1fr;137 gap: 40px;138}139
140.about-block { margin-bottom: 32px; }141
142.about-block__title {143 font-size: 18px;144 font-weight: 600;145 color: #333333;146 margin-bottom: 16px;147 padding-bottom: 8px;148 border-bottom: 1px solid #eeeeee;149}150
151.about-block__item {152 display: flex;153 gap: 16px;154 padding: 12px 0;155 font-size: 16px;156 color: #747474;157 border-bottom: 1px solid #f5f5f5;158}159
160.about-block__date {161 color: #555555;162 white-space: nowrap;163 min-width: 100px;164}165
166.info-row {167 display: flex;168 gap: 16px;169 padding: 8px 0;170 font-size: 16px;171 border-bottom: 1px solid #f5f5f5;172}173
174.info-row__label { width: 70px; color: #3a3a3a; }175.info-row__value { color: #727272; }176
177/* ─── Projects ──────────────────────────── */178.projects {179 padding: 80px 24px;180}181
182.project-item {183 display: grid;184 grid-template-columns: 1fr 1fr;185 margin-bottom: 40px;186 border: 1px solid #f0f0f0;187}188
189.project-desc {190 padding: 40px;191 background-color: #ffffff;192}193
194.project-number {195 display: flex;196 align-items: center;197 gap: 8px;198 margin-bottom: 16px;199}200
201.project-dot {202 display: inline-block;203 width: 7px;204 height: 7px;205 border-radius: 50%;206 background-color: #f26440;207}208
209.project-number__text { font-size: 12px; color: #c7c7c7; }210
211.project-title {212 font-size: 32px;213 font-weight: 700;214 color: #000;215 margin-bottom: 24px;216}217
218.project-info {219 display: flex;220 flex-direction: column;221 gap: 8px;222 margin-bottom: 32px;223}224
225.project-info__row {226 display: flex;227 gap: 8px;228 font-size: 18px;229 color: #555555;230}231
232.project-info__label {233 min-width: 140px;234 flex-shrink: 0;235}236
237.project-buttons { display: flex; gap: 16px; }238
239.project-btn {240 display: inline-flex;241 align-items: center;242 justify-content: center;243 width: 160px;244 height: 52px;245 background-color: #000;246 color: #fff;247 font-size: 18px;248 transition: background-color 0.2s;249}250
251.project-btn:hover { background-color: #333; }252
253.project-image {254 background-color: #e8e8e8;255 min-height: 447px;256}257
258/* ─── Contact ───────────────────────────── */259.contact {260 padding: 80px 24px;261}262
263.contact-form {264 width: 320px;265 display: flex;266 flex-direction: column;267 gap: 20px;268}269
270.form-field {271 display: flex;272 flex-direction: column;273 gap: 6px;274}275
276.form-label {277 font-size: 16px;278 font-weight: 500;279 color: #1e1e1e;280}281
282.form-input,283.form-textarea {284 padding: 0 12px;285 border: 1px solid #d0d0d0;286 border-radius: 4px;287 font-size: 16px;288 color: #1e1e1e;289 outline: none;290 width: 100%;291}292
293.form-input { height: 40px; }294.form-textarea { height: 80px; padding: 10px 12px; resize: none; }295
296.form-input::placeholder,297.form-textarea::placeholder { color: #b3b3b3; }298
299.form-input:focus,300.form-textarea:focus { border-color: #2c2c2c; }301
302.form-buttons { display: flex; gap: 12px; }303
304.form-btn {305 height: 40px;306 padding: 0 20px;307 border: none;308 border-radius: 4px;309 font-size: 16px;310 cursor: pointer;311 transition: opacity 0.2s;312}313
314.form-btn--cancel { background-color: #f0f0f0; color: #303030; }315.form-btn--submit { flex: 1; background-color: #2c2c2c; color: #f5f5f5; }316.form-btn:hover { opacity: 0.8; }317
318/* ─── Footer ───────────────────────────── */319.footer {320 padding: 60px 24px;321 border-top: 1px solid #f0f0f0;322}323
324.footer-top {325 display: flex;326 align-items: center;327 justify-content: space-between;328 margin-bottom: 40px;329}330
331.footer-thanks { font-size: 20px; font-weight: 500; color: #1e1e1e; }332
333.footer-sns {334 display: flex;335 gap: 16px;336 align-items: center;337}338
339.footer-sns__item {340 display: flex;341 align-items: center;342 opacity: 0.7;343 transition: opacity 0.2s;344}345
346.footer-sns__item:hover { opacity: 1; }347
348.footer-copy { font-size: 14px; color: #1e1e1e; }자주 발생하는 오류 모음
1. 화면이 안 바뀌어요
1✅ 체크리스트2□ 파일 저장했나요? (Ctrl+S)3□ 브라우저에서 새로고침했나요?4□ 터미널에 빨간 에러가 있나요?2. 빨간 에러: Module not found
1Error: Cannot find module './components/Nav'원인: 파일 경로나 이름이 틀렸습니다.
1// App.jsx가 src/ 에 있다면2import Nav from './components/Nav' // ✅3import Nav from './Components/Nav' // ❌ 대소문자 주의4import Nav from '../components/Nav' // ❌ 경로 방향 주의3. 빨간 에러: is not a function 또는 클릭해도 반응 없음
1// ❌ 클릭 즉시 실행됨2onClick={handleReset()}3
4// ✅ 클릭할 때만 실행됨5onClick={handleReset}6
7// 인자가 필요한 경우8onClick={() => handleDelete(item.id)}4. 경고: Each child in a list should have a unique "key" prop
1// ❌ key 없음2{items.map((item) => <li>{item}</li>)}3
4// ✅ key 추가5{items.map((item) => <li key={item.id}>{item.name}</li>)}5. 스타일이 적용 안 돼요
1// ❌ HTML 방식2<div class="nav">3
4// ✅ JSX 방식5<div className="nav">핵심 개념 한눈에 보기
| 개념 | 문법 | 사용 예 |
|---|---|---|
| 컴포넌트 | const Nav = () => { return <div/> } | 화면 블록 단위 |
| props | <Title text="Hello" /> | 데이터 전달 |
| useState | const [v, setV] = useState(초기값) | 변하는 값 관리 |
| 조건부 렌더링 | {조건 ? A : B} | 상황에 따라 다른 화면 |
| map | {list.map(item => <li key={item.id}/>)} | 목록 반복 출력 |
| 이벤트 | onClick={함수} | 클릭, 입력 처리 |
| 제어 컴포넌트 | value={state} onChange={핸들러} | 폼 입력 관리 |