처음 웹 개발을 배울 때 제일 먼저 드는 생각이 뭔지 아는가?
"이거 언제 실제로 써먹냐"는 것이다.
JavaScript도 모르고, React는 이름만 들어봤고, 그냥 HTML이랑 CSS만 손에 쥐어진 상태에서 뭔가 그럴듯한 걸 만들어보고 싶었다..
시작은 무식하게
처음엔 그냥 index.html 파일 하나 만들고 <h1>안녕하세요</h1> 찍어보는 것부터 시작했다.
그다음엔 <div> 박스 이리저리 쌓으면서 레이아웃을 잡아보기 시작했고, CSS 파일을 연결해서 색깔도 넣어봤다. background-color: #f0f0f0 같은 걸 처음 써봤을 때 "아 이게 그거구나" 싶었다.
Flexbox 활용
이전에는 요소 가운데 정렬 하나 하려고 margin: 0 auto에 position: absolute에 transform: translate(-50%, -50%)까지 막 갖다 붙이고 있었다. 왜 작동이 되는지 모르면서 말이다.
.container {
display: flex;
justify-content: center;
align-items: center;
}
CSS
복사
justify-content는 가로 방향, align-items는 세로 방향 — 이 두 개만 기억해도 레이아웃의 절반은 해결된다.
실제로 만든 것들
•
상단 내비게이션 바
•
소개 섹션 (사진이랑 텍스트 나란히)
•
프로젝트 카드 그리드
•
하단 연락처
각각이 어떤 CSS 속성으로 잡혔는지 간단히 설명하자면,
내비게이션 바는 display: flex에 justify-content: space-between으로 로고는 왼쪽, 메뉴는 오른쪽에 붙였고요. position: sticky; top: 0을 추가해서 스크롤을 해도 따라오게 했다.
소개 섹션도 Flexbox로 이미지랑 텍스트를 나란히 세웠다. gap 속성이 생각보다 편했다. margin 이렇게 저렇게 주는 것보다 훨씬 깔끔하게 간격이 잡혔다.
프로젝트 카드는 CSS Grid를 써봤다.
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}
CSS
복사
CSS 변수 활용
색상을 여러 곳에서 반복해서 쓰다 보니까, 나중에 바꾸려면 일일이 다 찾아서 바꿔야 했다. 그래서 CSS 변수를 활용해봤다.
:root {
--color-primary: #3b82f6;
--color-text: #1f2937;
--color-bg: #ffffff;
--border-radius: 8px;
}
.button {
background-color: var(--color-primary);
border-radius: var(--border-radius);
}
CSS
복사
나중에 디자인 변경할 때 :root 안에서만 고치면 된다. 다크 모드 대응도 이 방식으로 하면 나름 괜찮다.
실제 구현 코드 전체
위에서 설명한 내비게이션 바, 소개 섹션, 프로젝트 카드 그리드, 푸터를 모두 합친 실제 포트폴리오 페이지 코드다.
파일 하나로 바로 실행된다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Portfolio</title>
<style>
/* ── 리셋 ── */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* ── CSS 변수 ── */
:root {
--color-primary: #3b82f6;
--color-text: #1f2937;
--color-sub: #6b7280;
--color-bg: #f9fafb;
--color-card: #ffffff;
--border-radius: 12px;
--max-width: 960px;
}
body {
font-family: 'Segoe UI', sans-serif;
background-color: var(--color-bg);
color: var(--color-text);
line-height: 1.7;
}
/* ── 내비게이션 바 ── */
header {
position: sticky;
top: 0;
background-color: #ffffff;
border-bottom: 1px solid #e5e7eb;
z-index: 100;
}
nav {
max-width: var(--max-width);
margin: 0 auto;
padding: 0 20px;
height: 60px;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 1.2rem;
font-weight: 700;
color: var(--color-primary);
}
.nav-links {
display: flex;
gap: 24px;
list-style: none;
}
.nav-links a {
text-decoration: none;
color: var(--color-sub);
font-size: 0.95rem;
transition: color 0.2s;
}
.nav-links a:hover {
color: var(--color-primary);
}
/* ── 공통 섹션 ── */
section {
padding: 80px 0;
}
.inner {
max-width: var(--max-width);
margin: 0 auto;
padding: 0 20px;
}
.section-title {
font-size: 1.6rem;
font-weight: 700;
margin-bottom: 40px;
}
/* ── 소개 섹션 ── */
.hero {
background-color: #ffffff;
}
.hero-content {
display: flex;
align-items: center;
gap: 48px;
}
.hero-avatar {
width: 140px;
height: 140px;
border-radius: 50%;
background-color: var(--color-primary);
display: flex;
align-items: center;
justify-content: center;
font-size: 3rem;
flex-shrink: 0;
}
.hero-text h1 {
font-size: 2rem;
font-weight: 800;
margin-bottom: 8px;
}
.hero-text p {
color: var(--color-sub);
font-size: 1rem;
margin-bottom: 20px;
}
.btn {
display: inline-block;
padding: 10px 24px;
background-color: var(--color-primary);
color: #ffffff;
border-radius: var(--border-radius);
text-decoration: none;
font-size: 0.95rem;
transition: opacity 0.2s;
}
.btn:hover {
opacity: 0.85;
}
/* ── 프로젝트 카드 그리드 ── */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 24px;
}
.card {
background-color: var(--color-card);
border-radius: var(--border-radius);
padding: 28px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.07);
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1);
}
.card-emoji {
font-size: 2rem;
margin-bottom: 12px;
}
.card h3 {
font-size: 1.05rem;
font-weight: 700;
margin-bottom: 8px;
}
.card p {
font-size: 0.9rem;
color: var(--color-sub);
}
.tag {
display: inline-block;
margin-top: 14px;
padding: 4px 10px;
background-color: #eff6ff;
color: var(--color-primary);
border-radius: 999px;
font-size: 0.8rem;
}
/* ── 푸터 ── */
footer {
background-color: #1f2937;
color: #9ca3af;
text-align: center;
padding: 32px 20px;
font-size: 0.9rem;
}
</style>
</head>
<body>
<!-- 내비게이션 바 -->
<header>
<nav>
<span class="logo">codesche.dev</span>
<ul class="nav-links">
<li><a href="#about">소개</a></li>
<li><a href="#projects">프로젝트</a></li>
<li><a href="#contact">연락처</a></li>
</ul>
</nav>
</header>
<!-- 소개 섹션 -->
<section class="hero" id="about">
<div class="inner">
<div class="hero-content">
<div class="hero-avatar">👨💻</div>
<div class="hero-text">
<h1>안녕하세요, codesche입니다.</h1>
<p>HTML과 CSS로 시작해서 조금씩 쌓아가는 중입니다.<br />
백엔드 개발자인데 프론트도 직접 만들어보고 싶어서 공부 중이에요.</p>
<a href="#projects" class="btn">프로젝트 보기</a>
</div>
</div>
</div>
</section>
<!-- 프로젝트 섹션 -->
<section id="projects">
<div class="inner">
<h2 class="section-title">프로젝트</h2>
<div class="card-grid">
<div class="card">
<div class="card-emoji">💬</div>
<h3>DungeonTalk</h3>
<p>WebSocket과 Redis를 활용한 실시간 채팅 시스템. 채널 기반 구조로 설계했다.</p>
<span class="tag">Spring Boot</span>
<span class="tag">Redis</span>
</div>
<div class="card">
<div class="card-emoji">📒</div>
<h3>MoodBook</h3>
<p>기분을 기록하는 풀스택 앱. Spring Boot + React로 만든 첫 번째 프로젝트다.</p>
<span class="tag">React</span>
<span class="tag">MySQL</span>
</div>
<div class="card">
<div class="card-emoji">🏥</div>
<h3>EMR 시스템</h3>
<p>실무에서 유지보수 중인 전자의무기록 시스템. Delphi + Spring Boot 스택이다.</p>
<span class="tag">Delphi</span>
<span class="tag">Oracle</span>
</div>
</div>
</div>
</section>
<!-- 푸터 -->
<footer id="contact">
© 2026 codesche · GitHub · Notion Blog
</footer>
</body>
</html>
HTML
복사

