HTML&CSS/실전 스타일링

실습: 반응형 블로그 메인 페이지 만들기 완벽 가이드

코딩하는 패션이과생 2025. 5. 11. 18:41
반응형

현대 웹사이트에서 반응형 디자인은 선택이 아닌 필수입니다. 사용자들은 데스크톱, 태블릿, 모바일 등 다양한 기기에서 웹사이트를 방문하며, 모든 화면 크기에서 최적의 경험을 제공해야 합니다. 이 튜토리얼에서는 HTML, CSS, 약간의 JavaScript를 사용하여 모든 기기에 완벽하게 대응하는 블로그 메인 페이지를 처음부터 만들어보겠습니다.

이 실습을 통해 여러분은 반응형 웹 디자인의 핵심 원칙을 배우고, 실제 프로젝트에 적용할 수 있는 기술을 습득하게 될 것입니다. 코드를 직접 작성하며 따라오시면, 자신만의 반응형 블로그를 완성할 수 있습니다!

최종 결과물 미리보기

이 튜토리얼을 완료하면 아래와 같은 반응형 블로그 메인 페이지를 만들 수 있습니다:

  • 데스크톱: 3단 레이아웃 (메인 콘텐츠 + 사이드바)
  • 태블릿: 2단 레이아웃 (메인 콘텐츠 간략화 + 사이드바 하단으로)
  • 모바일: 1단 레이아웃 (모든 요소가 세로로 나열)

주요 기능:

  • 햄버거 메뉴 토글 (모바일)
  • 블로그 포스트 카드 디자인
  • 인기 포스트 및 카테고리 위젯
  • 뉴스레터 구독 폼
  • 다크 모드 전환 옵션
  • 최적화된 로딩 성능

준비물 및 프로젝트 설정

필요한 도구

  • 텍스트 에디터 (VS Code, Sublime Text, Atom 등)
  • 웹 브라우저 (Chrome, Firefox, Edge 등)
  • 기본적인 HTML, CSS, JavaScript 지식

프로젝트 폴더 구조 만들기

먼저, 프로젝트 폴더와 필요한 파일들을 생성합니다:

responsive-blog/
│
├── index.html
├── css/
│   ├── style.css
│   └── normalize.css
├── js/
│   └── script.js
└── images/
    ├── logo.svg
    ├── hero-bg.jpg
    └── blog-posts/
        ├── post-1.jpg
        ├── post-2.jpg
        └── post-3.jpg

normalize.css 추가하기

브라우저 간 일관된 스타일을 위해 normalize.css를 추가합니다. normalize.css에서 다운로드하거나 CDN을 사용할 수 있습니다.

HTML 기본 구조 작성

index.html 파일을 열고 기본 HTML 구조를 작성합니다:

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Responsive Blog</title>
    <meta name="description" content="모든 기기에 최적화된 반응형 블로그 웹사이트">

    <!-- CSS 파일 연결 -->
    <link rel="stylesheet" href="css/normalize.css">
    <link rel="stylesheet" href="css/style.css">

    <!-- 구글 폰트 -->
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700&display=swap" rel="stylesheet">

    <!-- 아이콘 라이브러리 (Font Awesome) -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
</head>
<body>
    <!-- 헤더 영역 -->
    <header class="header">
        <!-- 내비게이션 및 로고 영역 -->
    </header>

    <!-- 히어로 섹션 -->
    <section class="hero">
        <!-- 히어로 콘텐츠 -->
    </section>

    <!-- 메인 콘텐츠 -->
    <main class="main-content">
        <div class="container">
            <div class="content-wrapper">
                <!-- 블로그 포스트 영역 -->
                <section class="blog-posts">
                    <!-- 블로그 포스트 카드들 -->
                </section>

                <!-- 사이드바 -->
                <aside class="sidebar">
                    <!-- 위젯 영역 -->
                </aside>
            </div>
        </div>
    </main>

    <!-- 푸터 영역 -->
    <footer class="footer">
        <!-- 푸터 콘텐츠 -->
    </footer>

    <!-- JavaScript 연결 -->
    <script src="js/script.js"></script>
</body>
</html>

CSS 기본 스타일 설정

style.css 파일을 열고 기본 스타일을 설정합니다:

/* 기본 스타일 설정 */
:root {
    /* 컬러 변수 */
    --primary-color: #4a6bdf;
    --secondary-color: #f8595d;
    --dark-color: #333333;
    --light-color: #f4f4f4;
    --bg-color: #ffffff;
    --text-color: #333333;
    --gray-color: #666666;
    --light-gray-color: #dddddd;

    /* 타이포그래피 */
    --font-family: 'Noto Sans KR', sans-serif;
    --h1-size: 2.5rem;
    --h2-size: 2rem;
    --h3-size: 1.5rem;
    --h4-size: 1.25rem;
    --body-size: 1rem;
    --small-size: 0.875rem;

    /* 간격 */
    --spacing-xs: 0.5rem;
    --spacing-sm: 1rem;
    --spacing-md: 1.5rem;
    --spacing-lg: 2rem;
    --spacing-xl: 3rem;

    /* 테두리 */
    --border-radius: 0.5rem;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: var(--font-family);
    font-size: var(--body-size);
    line-height: 1.6;
    color: var(--text-color);
    background-color: var(--bg-color);
}

a {
    text-decoration: none;
    color: var(--primary-color);
    transition: color 0.3s ease;
}

a:hover {
    color: var(--secondary-color);
}

img {
    max-width: 100%;
    height: auto;
    display: block;
}

ul {
    list-style: none;
}

h1, h2, h3, h4, h5, h6 {
    font-weight: 700;
    line-height: 1.3;
    margin-bottom: var(--spacing-sm);
}

h1 { font-size: var(--h1-size); }
h2 { font-size: var(--h2-size); }
h3 { font-size: var(--h3-size); }
h4 { font-size: var(--h4-size); }

p {
    margin-bottom: var(--spacing-md);
}

button, .button {
    display: inline-block;
    background-color: var(--primary-color);
    color: white;
    padding: 0.75rem 1.5rem;
    border: none;
    border-radius: var(--border-radius);
    font-family: var(--font-family);
    font-size: var(--body-size);
    cursor: pointer;
    transition: background-color 0.3s ease;
}

button:hover, .button:hover {
    background-color: var(--secondary-color);
}

/* 유틸리티 클래스 */
.container {
    width: 90%;
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 var(--spacing-sm);
}

.text-center {
    text-align: center;
}

.text-primary {
    color: var(--primary-color);
}

.bg-light {
    background-color: var(--light-color);
}

.mb-1 { margin-bottom: var(--spacing-xs); }
.mb-2 { margin-bottom: var(--spacing-sm); }
.mb-3 { margin-bottom: var(--spacing-md); }
.mb-4 { margin-bottom: var(--spacing-lg); }
.mb-5 { margin-bottom: var(--spacing-xl); }

헤더 및 내비게이션 구현

HTML 구조

헤더와 내비게이션 영역의 HTML을 작성합니다:

<header class="header">
    <div class="container">
        <nav class="navbar">
            <div class="navbar-logo">
                <a href="index.html">
                    <img src="images/logo.svg" alt="My Blog Logo" class="logo">
                </a>
            </div>

            <button class="navbar-toggle" aria-label="메뉴 열기">
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>

            <div class="navbar-menu">
                <ul class="navbar-nav">
                    <li class="nav-item active"><a href="#" class="nav-link">홈</a></li>
                    <li class="nav-item"><a href="#" class="nav-link">카테고리</a></li>
                    <li class="nav-item"><a href="#" class="nav-link">인기 포스트</a></li>
                    <li class="nav-item"><a href="#" class="nav-link">소개</a></li>
                    <li class="nav-item"><a href="#" class="nav-link">연락처</a></li>
                </ul>
            </div>

            <div class="navbar-actions">
                <button class="theme-toggle" aria-label="다크 모드 전환">
                    <i class="fas fa-moon"></i>
                </button>
                <div class="search-box">
                    <input type="text" placeholder="검색..." class="search-input">
                    <button class="search-button">
                        <i class="fas fa-search"></i>
                    </button>
                </div>
            </div>
        </nav>
    </div>
</header>

CSS 스타일링

헤더와 내비게이션의 CSS를 작성합니다:

/* 헤더 스타일 */
.header {
    position: sticky;
    top: 0;
    z-index: 1000;
    background-color: var(--bg-color);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    padding: 1rem 0;
}

.navbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
}

.navbar-logo {
    display: flex;
    align-items: center;
}

.logo {
    height: 40px;
}

.navbar-toggle {
    display: none;
    background: none;
    border: none;
    padding: 0.5rem;
    cursor: pointer;
}

.icon-bar {
    display: block;
    width: 25px;
    height: 3px;
    margin: 5px auto;
    background-color: var(--dark-color);
    transition: all 0.3s;
}

.navbar-menu {
    display: flex;
    align-items: center;
}

.navbar-nav {
    display: flex;
    margin: 0;
    padding: 0;
}

.nav-item {
    margin: 0 1rem;
}

.nav-link {
    color: var(--dark-color);
    font-weight: 500;
    padding: 0.5rem 0;
    position: relative;
}

.nav-link::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    width: 0;
    height: 2px;
    background-color: var(--primary-color);
    transition: width 0.3s ease;
}

.nav-link:hover::after,
.nav-item.active .nav-link::after {
    width: 100%;
}

.navbar-actions {
    display: flex;
    align-items: center;
}

.theme-toggle {
    background: none;
    border: none;
    color: var(--dark-color);
    font-size: 1.2rem;
    margin-right: 1rem;
    cursor: pointer;
    padding: 0.5rem;
}

.search-box {
    position: relative;
    display: flex;
    align-items: center;
}

.search-input {
    padding: 0.5rem 2.5rem 0.5rem 1rem;
    border: 1px solid var(--light-gray-color);
    border-radius: 20px;
    font-size: var(--small-size);
    width: 180px;
    transition: width 0.3s;
}

.search-input:focus {
    outline: none;
    width: 220px;
    border-color: var(--primary-color);
}

.search-button {
    position: absolute;
    right: 0.5rem;
    background: none;
    border: none;
    padding: 0.5rem;
    color: var(--gray-color);
    font-size: 0.9rem;
}

히어로 섹션 만들기

HTML 구조

히어로 섹션의 HTML을 작성합니다:

<section class="hero">
    <div class="hero-overlay"></div>
    <div class="container">
        <div class="hero-content">
            <span class="hero-subtitle">웹 개발 & 디자인</span>
            <h1 class="hero-title">반응형 웹 디자인의 모든 것</h1>
            <p class="hero-description">모바일부터 데스크톱까지 완벽하게 대응하는 반응형 웹사이트 제작 기법을 소개합니다.</p>
            <div class="hero-buttons">
                <a href="#" class="button">최신 포스트 보기</a>
                <a href="#" class="button button-outline">구독하기</a>
            </div>
        </div>
    </div>
</section>

CSS 스타일링

히어로 섹션의 CSS를 작성합니다:

/* 히어로 섹션 스타일 */
.hero {
    position: relative;
    height: 500px;
    background-image: url('../images/hero-bg.jpg');
    background-size: cover;
    background-position: center;
    display: flex;
    align-items: center;
    color: white;
    text-align: center;
    margin-bottom: var(--spacing-xl);
}

.hero-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.6);
    z-index: 1;
}

.hero-content {
    position: relative;
    z-index: 2;
    max-width: 800px;
    margin: 0 auto;
}

.hero-subtitle {
    display: inline-block;
    background-color: var(--primary-color);
    color: white;
    padding: 0.3rem 1rem;
    border-radius: 20px;
    font-size: 0.9rem;
    font-weight: 500;
    margin-bottom: var(--spacing-sm);
}

.hero-title {
    font-size: 3rem;
    margin-bottom: var(--spacing-md);
    text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

.hero-description {
    font-size: 1.2rem;
    margin-bottom: var(--spacing-lg);
    max-width: 600px;
    margin-left: auto;
    margin-right: auto;
}

.hero-buttons {
    display: flex;
    justify-content: center;
    gap: 1rem;
}

.button-outline {
    background-color: transparent;
    border: 2px solid white;
    color: white;
}

.button-outline:hover {
    background-color: white;
    color: var(--primary-color);
}

블로그 포스트 그리드 레이아웃

HTML 구조

블로그 포스트 섹션의 HTML을 작성합니다:

<section class="blog-posts">
    <h2 class="section-title">최신 포스트</h2>

    <div class="post-grid">
        <!-- 포스트 1 -->
        <article class="post-card">
            <div class="post-card-image">
                <img src="images/blog-posts/post-1.jpg" alt="반응형 디자인 기초">
            </div>
            <div class="post-card-content">
                <div class="post-card-category">웹 디자인</div>
                <h3 class="post-card-title">
                    <a href="#">반응형 디자인의 기본 원칙: 모바일 우선 접근법</a>
                </h3>
                <p class="post-card-excerpt">
                    반응형 웹 디자인에서 모바일 우선 접근법이 중요한 이유와 실제 적용 방법에 대해 알아봅니다.
                </p>
                <div class="post-card-meta">
                    <div class="post-card-date">
                        <i class="far fa-calendar"></i> 2023년 5월 10일
                    </div>
                    <div class="post-card-author">
                        <i class="far fa-user"></i> 홍길동
                    </div>
                </div>
            </div>
        </article>

        <!-- 포스트 2 -->
        <article class="post-card">
            <div class="post-card-image">
                <img src="images/blog-posts/post-2.jpg" alt="CSS 그리드 레이아웃">
            </div>
            <div class="post-card-content">
                <div class="post-card-category">CSS</div>
                <h3 class="post-card-title">
                    <a href="#">CSS 그리드로 복잡한 레이아웃 쉽게 구현하기</a>
                </h3>
                <p class="post-card-excerpt">
                    CSS 그리드 레이아웃을 활용하여 복잡한 디자인도 쉽게 구현하는 방법을 단계별로 설명합니다.
                </p>
                <div class="post-card-meta">
                    <div class="post-card-date">
                        <i class="far fa-calendar"></i> 2023년 5월 5일
                    </div>
                    <div class="post-card-author">
                        <i class="far fa-user"></i> 김철수
                    </div>
                </div>
            </div>
        </article>

        <!-- 포스트 3 -->
        <article class="post-card">
            <div class="post-card-image">
                <img src="images/blog-posts/post-3.jpg" alt="JavaScript 팁">
            </div>
            <div class="post-card-content">
                <div class="post-card-category">JavaScript</div>
                <h3 class="post-card-title">
                    <a href="#">실무에서 바로 적용하는 JavaScript 핵심 팁 10가지</a>
                </h3>
                <p class="post-card-excerpt">
                    현직 개발자들이 실무에서 자주 사용하는 유용한 JavaScript 팁 10가지를 소개합니다.
                </p>
                <div class="post-card-meta">
                    <div class="post-card-date">
                        <i class="far fa-calendar"></i> 2023년 4월 28일
                    </div>
                    <div class="post-card-author">
                        <i class="far fa-user"></i> 이영희
                    </div>
                </div>
            </div>
        </article>

        <!-- 더 많은 포스트 카드 추가 -->
        <!-- 포스트 4-6 같은 형식으로 반복 -->
    </div>

    <div class="pagination">
        <a href="#" class="page-link active">1</a>
        <a href="#" class="page-link">2</a>
        <a href="#" class="page-link">3</a>
        <a href="#" class="page-link next">
            <i class="fas fa-arrow-right"></i>
        </a>
    </div>
</section>

CSS 스타일링

블로그 포스트 섹션의 CSS를 작성합니다:

/* 메인 콘텐츠 레이아웃 */
.main-content {
    padding: var(--spacing-lg) 0;
}

.content-wrapper {
    display: grid;
    grid-template-columns: 2fr 1fr;
    gap: 2rem;
}

/* 섹션 타이틀 */
.section-title {
    margin-bottom: var(--spacing-lg);
    position: relative;
    padding-bottom: 0.5rem;
}

.section-title::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    width: 60px;
    height: 3px;
    background-color: var(--primary-color);
}

/* 블로그 포스트 그리드 */
.post-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 2rem;
    margin-bottom: var(--spacing-xl);
}

.post-card {
    border-radius: var(--border-radius);
    overflow: hidden;
    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
    transition: transform 0.3s, box-shadow 0.3s;
    background-color: var(--bg-color);
}

.post-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
}

.post-card-image {
    height: 200px;
    overflow: hidden;
}

.post-card-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform 0.5s;
}

.post-card:hover .post-card-image img {
    transform: scale(1.1);
}

.post-card-content {
    padding: var(--spacing-md);
}

.post-card-category {
    display: inline-block;
    background-color: var(--primary-color);
    color: white;
    font-size: 0.8rem;
    padding: 0.2rem 0.8rem;
    border-radius: 20px;
    margin-bottom: 0.5rem;
}

.post-card-title {
    font-size: 1.3rem;
    margin-bottom: 0.5rem;
    line-height: 1.4;
}

.post-card-title a {
    color: var(--dark-color);
    transition: color 0.3s;
}

.post-card-title a:hover {
    color: var(--primary-color);
}

.post-card-excerpt {
    font-size: 0.95rem;
    color: var(--gray-color);
    margin-bottom: var(--spacing-sm);
    line-height: 1.6;
}

.post-card-meta {
    display: flex;
    justify-content: space-between;
    font-size: 0.85rem;
    color: var(--gray-color);
}

.post-card-date, .post-card-author {
    display: flex;
    align-items: center;
}

.post-card-meta i {
    margin-right: 0.3rem;
}

/* 페이지네이션 */
.pagination {
    display: flex;
    justify-content: center;
    gap: 0.5rem;
}

.page-link {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background-color: var(--light-color);
    color: var(--dark-color);
    font-weight: 500;
    transition: all 0.3s;
}

.page-link:hover, .page-link.active {
    background-color: var(--primary-color);
    color: white;
}

.page-link.next {
    background-color: var(--dark-color);
    color: white;
}

사이드바 및 위젯 영역

HTML 구조

사이드바와 위젯 영역의 HTML을 작성합니다:

<aside class="sidebar">
    <!-- 프로필 위젯 -->
    <div class="widget widget-profile">
        <div class="widget-profile-image">
            <img src="images/profile.jpg" alt="블로그 운영자 프로필">
        </div>
        <h3 class="widget-profile-name">홍길동</h3>
        <p class="widget-profile-bio">
            프론트엔드 개발자이자 UI/UX 디자이너입니다. 모던 웹 기술과 디자인 트렌드에 관심이 많습니다.
        </p>
        <div class="widget-profile-social">
            <a href="#" class="social-link"><i class="fab fa-twitter"></i></a>
            <a href="#" class="social-link"><i class="fab fa-facebook"></i></a>
            <a href="#" class="social-link"><i class="fab fa-instagram"></i></a>
            <a href="#" class="social-link"><i class="fab fa-github"></i></a>
        </div>
    </div>

    <!-- 인기 포스트 위젯 -->
    <div class="widget widget-popular">
        <h3 class="widget-title">인기 포스트</h3>
        <ul class="popular-posts">
            <li class="popular-post-item">
                <a href="#" class="popular-post-link">
                    <div class="popular-post-image">
                        <img src="images/blog-posts/popular-1.jpg" alt="인기 포스트 1">
                    </div>
                    <div class="popular-post-content">
                        <h4 class="popular-post-title">프론트엔드 개발자를 위한 필수 도구</h4>
                        <span class="popular-post-date">2023년 5월 1일</span>
                    </div>
                </a>
            </li>
            <li class="popular-post-item">
                <a href="#" class="popular-post-link">
                    <div class="popular-post-image">
                        <img src="images/blog-posts/popular-2.jpg" alt="인기 포스트 2">
                    </div>
                    <div class="popular-post-content">
                        <h4 class="popular-post-title">웹 성능 최적화 방법 10가지</h4>
                        <span class="popular-post-date">2023년 4월 25일</span>
                    </div>
                </a>
            </li>
            <li class="popular-post-item">
                <a href="#" class="popular-post-link">
                    <div class="popular-post-image">
                        <img src="images/blog-posts/popular-3.jpg" alt="인기 포스트 3">
                    </div>
                    <div class="popular-post-content">
                        <h4 class="popular-post-title">CSS 변수 활용 가이드</h4>
                        <span class="popular-post-date">2023년 4월 20일</span>
                    </div>
                </a>
            </li>
        </ul>
    </div>

    <!-- 카테고리 위젯 -->
    <div class="widget widget-categories">
        <h3 class="widget-title">카테고리</h3>
        <ul class="category-list">
            <li class="category-item">
                <a href="#" class="category-link">HTML & CSS <span class="category-count">12</span></a>
            </li>
            <li class="category-item">
                <a href="#" class="category-link">JavaScript <span class="category-count">8</span></a>
            </li>
            <li class="category-item">
                <a href="#" class="category-link">반응형 디자인 <span class="category-count">6</span></a>
            </li>
            <li class="category-item">
                <a href="#" class="category-link">성능 최적화 <span class="category-count">4</span></a>
            </li>
            <li class="category-item">
                <a href="#" class="category-link">UI/UX 디자인 <span class="category-count">7</span></a>
            </li>
        </ul>
    </div>

    <!-- 뉴스레터 구독 위젯 -->
    <div class="widget widget-newsletter">
        <h3 class="widget-title">뉴스레터 구독</h3>
        <p class="widget-description">
            최신 웹 개발 및 디자인 트렌드를 이메일로 받아보세요.
        </p>
        <form class="newsletter-form">
            <input type="email" placeholder="이메일 주소" class="newsletter-input" required>
            <button type="submit" class="newsletter-button">구독하기</button>
        </form>
    </div>

    <!-- 태그 클라우드 위젯 -->
    <div class="widget widget-tags">
        <h3 class="widget-title">태그 클라우드</h3>
        <div class="tag-cloud">
            <a href="#" class="tag">HTML</a>
            <a href="#" class="tag">CSS</a>
            <a href="#" class="tag">JavaScript</a>
            <a href="#" class="tag">React</a>
            <a href="#" class="tag">Vue.js</a>
            <a href="#" class="tag">Responsive</a>
            <a href="#" class="tag">Mobile</a>
            <a href="#" class="tag">Performance</a>
            <a href="#" class="tag">UX</a>
            <a href="#" class="tag">Design</a>
        </div>
    </div>
</aside>

CSS 스타일링

사이드바와 위젯 영역의 CSS를 작성합니다:

/* 사이드바 및 위젯 스타일 */
.sidebar {
    position: sticky;
    top: 100px;
}

.widget {
    background-color: var(--bg-color);
    border-radius: var(--border-radius);
    box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
    padding: var(--spacing-md);
    margin-bottom: var(--spacing-lg);
}

.widget-title {
    font-size: 1.3rem;
    margin-bottom: var(--spacing-md);
    position: relative;
    padding-bottom: 0.5rem;
}

.widget-title::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    width: 40px;
    height: 3px;
    background-color: var(--primary-color);
}

/* 프로필 위젯 */
.widget-profile {
    text-align: center;
}

.widget-profile-image {
    width: 120px;
    height: 120px;
    border-radius: 50%;
    overflow: hidden;
    margin: 0 auto var(--spacing-sm);
    border: 3px solid var(--light-color);
}

.widget-profile-name {
    font-size: 1.3rem;
    margin-bottom: var(--spacing-xs);
}

.widget-profile-bio {
    font-size: 0.95rem;
    color: var(--gray-color);
    margin-bottom: var(--spacing-sm);
}

.widget-profile-social {
    display: flex;
    justify-content: center;
    gap: 0.8rem;
}

.social-link {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background-color: var(--light-color);
    color: var(--gray-color);
    transition: all 0.3s;
}

.social-link:hover {
    background-color: var(--primary-color);
    color: white;
}

/* 인기 포스트 위젯 */
.popular-posts {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.popular-post-item {
    border-bottom: 1px solid var(--light-gray-color);
    padding-bottom: 1rem;
}

.popular-post-item:last-child {
    border-bottom: none;
    padding-bottom: 0;
}

.popular-post-link {
    display: flex;
    gap: 1rem;
    color: var(--dark-color);
}

.popular-post-image {
    flex-shrink: 0;
    width: 80px;
    height: 60px;
    border-radius: 4px;
    overflow: hidden;
}

.popular-post-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.popular-post-title {
    font-size: 0.95rem;
    margin-bottom: 0.3rem;
    transition: color 0.3s;
}

.popular-post-link:hover .popular-post-title {
    color: var(--primary-color);
}

.popular-post-date {
    font-size: 0.8rem;
    color: var(--gray-color);
}

/* 카테고리 위젯 */
.category-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.category-link {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.7rem 1rem;
    background-color: var(--light-color);
    border-radius: 4px;
    color: var(--dark-color);
    transition: all 0.3s;
}

.category-link:hover {
    background-color: var(--primary-color);
    color: white;
}

.category-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background-color: white;
    color: var(--dark-color);
    font-size: 0.8rem;
    font-weight: 500;
}

/* 뉴스레터 위젯 */
.widget-description {
    font-size: 0.95rem;
    color: var(--gray-color);
    margin-bottom: var(--spacing-sm);
}

.newsletter-form {
    display: flex;
    flex-direction: column;
    gap: 0.8rem;
}

.newsletter-input {
    padding: 0.8rem 1rem;
    border: 1px solid var(--light-gray-color);
    border-radius: 4px;
    font-family: var(--font-family);
    font-size: 0.95rem;
}

.newsletter-input:focus {
    outline: none;
    border-color: var(--primary-color);
}

.newsletter-button {
    width: 100%;
}

/* 태그 클라우드 위젯 */
.tag-cloud {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
}

.tag {
    display: inline-block;
    padding: 0.3rem 0.8rem;
    background-color: var(--light-color);
    border-radius: 4px;
    color: var(--gray-color);
    font-size: 0.85rem;
    transition: all 0.3s;
}

.tag:hover {
    background-color: var(--primary-color);
    color: white;
}

푸터 영역 디자인

HTML 구조

푸터 영역의 HTML을 작성합니다:

<footer class="footer">
    <div class="container">
        <div class="footer-content">
            <div class="footer-logo">
                <img src="images/logo-white.svg" alt="My Blog Logo" class="logo">
                <p class="footer-description">
                    웹 개발과 디자인에 관한 유용한 정보를 제공하는 블로그입니다.
                </p>
            </div>

            <div class="footer-links">
                <h3 class="footer-title">바로가기</h3>
                <ul class="footer-nav">
                    <li><a href="#">홈</a></li>
                    <li><a href="#">블로그</a></li>
                    <li><a href="#">카테고리</a></li>
                    <li><a href="#">소개</a></li>
                    <li><a href="#">연락처</a></li>
                </ul>
            </div>

            <div class="footer-categories">
                <h3 class="footer-title">카테고리</h3>
                <ul class="footer-nav">
                    <li><a href="#">HTML & CSS</a></li>
                    <li><a href="#">JavaScript</a></li>
                    <li><a href="#">반응형 디자인</a></li>
                    <li><a href="#">성능 최적화</a></li>
                    <li><a href="#">UI/UX 디자인</a></li>
                </ul>
            </div>

            <div class="footer-subscribe">
                <h3 class="footer-title">뉴스레터 구독</h3>
                <p class="footer-description">
                    최신 글과 업데이트 소식을 받아보세요.
                </p>
                <form class="footer-form">
                    <input type="email" placeholder="이메일 주소" class="footer-input" required>
                    <button type="submit" class="footer-button">구독</button>
                </form>
            </div>
        </div>

        <div class="footer-bottom">
            <div class="copyright">
                &copy; 2023 My Blog. All Rights Reserved.
            </div>
            <div class="footer-social">
                <a href="#" class="social-link"><i class="fab fa-twitter"></i></a>
                <a href="#" class="social-link"><i class="fab fa-facebook"></i></a>
                <a href="#" class="social-link"><i class="fab fa-instagram"></i></a>
                <a href="#" class="social-link"><i class="fab fa-github"></i></a>
            </div>
        </div>
    </div>
</footer>

CSS 스타일링

푸터 영역의 CSS를 작성합니다:

/* 푸터 스타일 */
.footer {
    background-color: var(--dark-color);
    color: white;
    padding: var(--spacing-xl) 0 var(--spacing-md);
    margin-top: var(--spacing-xl);
}

.footer-content {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 2rem;
    margin-bottom: var(--spacing-lg);
}

.footer-logo .logo {
    height: 40px;
    margin-bottom: var(--spacing-sm);
}

.footer-description {
    font-size: 0.95rem;
    color: rgba(255, 255, 255, 0.7);
    line-height: 1.6;
}

.footer-title {
    font-size: 1.1rem;
    margin-bottom: var(--spacing-md);
    position: relative;
    padding-bottom: 0.5rem;
}

.footer-title::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    width: 30px;
    height: 2px;
    background-color: var(--primary-color);
}

.footer-nav {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
}

.footer-nav a {
    color: rgba(255, 255, 255, 0.7);
    font-size: 0.95rem;
    transition: color 0.3s;
}

.footer-nav a:hover {
    color: white;
}

.footer-form {
    display: flex;
    margin-top: var(--spacing-sm);
}

.footer-input {
    flex: 1;
    padding: 0.8rem 1rem;
    border: none;
    border-radius: 4px 0 0 4px;
    font-family: var(--font-family);
    font-size: 0.95rem;
}

.footer-button {
    padding: 0 1.5rem;
    background-color: var(--primary-color);
    color: white;
    border: none;
    border-radius: 0 4px 4px 0;
    cursor: pointer;
    transition: background-color 0.3s;
}

.footer-button:hover {
    background-color: var(--secondary-color);
}

.footer-bottom {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-top: var(--spacing-md);
    border-top: 1px solid rgba(255, 255, 255, 0.1);
}

.copyright {
    font-size: 0.9rem;
    color: rgba(255, 255, 255, 0.7);
}

.footer-social {
    display: flex;
    gap: 1rem;
}

.footer-social .social-link {
    background-color: rgba(255, 255, 255, 0.1);
    color: white;
}

.footer-social .social-link:hover {
    background-color: var(--primary-color);
}

미디어 쿼리로 반응형 디자인 구현

이제 다양한 화면 크기에 대응하는 미디어 쿼리를 작성합니다:

/* 반응형 디자인을 위한 미디어 쿼리 */

/* 태블릿 (768px ~ 1023px) */
@media (max-width: 1023px) {
    :root {
        --h1-size: 2.2rem;
        --h2-size: 1.8rem;
        --h3-size: 1.4rem;
        --h4-size: 1.2rem;
    }

    .hero-title {
        font-size: 2.5rem;
    }

    .content-wrapper {
        grid-template-columns: 1fr;
        gap: 3rem;
    }

    .sidebar {
        position: static;
        top: auto;
    }

    .post-grid {
        grid-template-columns: repeat(2, 1fr);
    }

    .footer-content {
        grid-template-columns: repeat(2, 1fr);
    }
}

/* 모바일 (767px 이하) */
@media (max-width: 767px) {
    :root {
        --h1-size: 2rem;
        --h2-size: 1.6rem;
        --h3-size: 1.3rem;
        --h4-size: 1.1rem;
        --spacing-xl: 2.5rem;
        --spacing-lg: 1.5rem;
    }

    .navbar {
        flex-wrap: nowrap;
    }

    .navbar-toggle {
        display: block;
        order: 3;
    }

    .navbar-menu {
        position: fixed;
        top: 70px;
        left: 0;
        width: 100%;
        height: 0;
        background-color: var(--bg-color);
        overflow: hidden;
        transition: height 0.3s ease;
        box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
        flex-direction: column;
        z-index: 1000;
    }

    .navbar-menu.active {
        height: auto;
        padding: 1rem 0;
    }

    .navbar-nav {
        flex-direction: column;
        width: 100%;
        text-align: center;
    }

    .nav-item {
        margin: 0.8rem 0;
    }

    .search-box {
        display: none;
    }

    .hero {
        height: 400px;
    }

    .hero-title {
        font-size: 2rem;
    }

    .hero-description {
        font-size: 1rem;
    }

    .hero-buttons {
        flex-direction: column;
        gap: 0.8rem;
    }

    .post-grid {
        grid-template-columns: 1fr;
    }

    .footer-content {
        grid-template-columns: 1fr;
    }

    .footer-bottom {
        flex-direction: column;
        gap: 1rem;
        text-align: center;
    }
}

/* 작은 모바일 (480px 이하) */
@media (max-width: 480px) {
    .container {
        width: 95%;
    }

    .hero-title {
        font-size: 1.8rem;
    }

    .pagination {
        flex-wrap: wrap;
    }
}

JavaScript 기능 추가

이제 필요한 JavaScript 기능을 추가합니다. script.js 파일을 열고 다음 코드를 작성합니다:

document.addEventListener('DOMContentLoaded', function() {
    // 모바일 메뉴 토글
    const navbarToggle = document.querySelector('.navbar-toggle');
    const navbarMenu = document.querySelector('.navbar-menu');

    navbarToggle.addEventListener('click', function() {
        navbarMenu.classList.toggle('active');

        // 햄버거 아이콘 애니메이션
        this.classList.toggle('active');

        if (this.classList.contains('active')) {
            // X 모양으로 변경
            this.querySelector('.icon-bar:nth-child(1)').style.transform = 'translateY(8px) rotate(45deg)';
            this.querySelector('.icon-bar:nth-child(2)').style.opacity = '0';
            this.querySelector('.icon-bar:nth-child(3)').style.transform = 'translateY(-8px) rotate(-45deg)';
        } else {
            // 원래 햄버거 모양으로 복원
            this.querySelector('.icon-bar:nth-child(1)').style.transform = 'none';
            this.querySelector('.icon-bar:nth-child(2)').style.opacity = '1';
            this.querySelector('.icon-bar:nth-child(3)').style.transform = 'none';
        }
    });

    // 스크롤 시 헤더 스타일 변경
    const header = document.querySelector('.header');

    window.addEventListener('scroll', function() {
        if (window.scrollY > 50) {
            header.classList.add('scrolled');
        } else {
            header.classList.remove('scrolled');
        }
    });

    // 뉴스레터 폼 제출
    const newsletterForm = document.querySelector('.newsletter-form');

    if (newsletterForm) {
        newsletterForm.addEventListener('submit', function(e) {
            e.preventDefault();

            const emailInput = this.querySelector('input[type="email"]');
            const email = emailInput.value.trim();

            if (email) {
                // 실제 구현에서는 서버로 데이터 전송
                alert('뉴스레터 구독 신청이 완료되었습니다!');
                emailInput.value = '';
            }
        });
    }

    // 푸터 폼 제출
    const footerForm = document.querySelector('.footer-form');

    if (footerForm) {
        footerForm.addEventListener('submit', function(e) {
            e.preventDefault();

            const emailInput = this.querySelector('input[type="email"]');
            const email = emailInput.value.trim();

            if (email) {
                // 실제 구현에서는 서버로 데이터 전송
                alert('뉴스레터 구독 신청이 완료되었습니다!');
                emailInput.value = '';
            }
        });
    }
});

헤더 스타일 변경을 위한 CSS를 추가합니다:

/* 스크롤 시 헤더 스타일 */
.header.scrolled {
    background-color: rgba(255, 255, 255, 0.95);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    padding: 0.5rem 0;
    transition: all 0.3s;
}

다크 모드 구현

이제 다크 모드 기능을 추가합니다. 먼저 CSS 변수에 다크 모드 스타일을 추가합니다:

:root {
    /* 라이트 모드 (기본) */
    --primary-color: #4a6bdf;
    --secondary-color: #f8595d;
    --dark-color: #333333;
    --light-color: #f4f4f4;
    --bg-color: #ffffff;
    --text-color: #333333;
    --gray-color: #666666;
    --light-gray-color: #dddddd;

    /* 기타 변수들... */
}

/* 다크 모드 */
[data-theme="dark"] {
    --primary-color: #5d7ce7;
    --secondary-color: #ff6b6e;
    --dark-color: #f4f4f4;
    --light-color: #444444;
    --bg-color: #121212;
    --text-color: #f4f4f4;
    --gray-color: #aaaaaa;
    --light-gray-color: #444444;
}

JavaScript에 다크 모드 전환 기능을 추가합니다:

// 다크 모드 토글
const themeToggle = document.querySelector('.theme-toggle');

// 선호 색 테마 감지
const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
const currentTheme = localStorage.getItem('theme');

// 저장된 테마가 있거나 사용자가 다크 모드를 선호하는 경우
if (currentTheme === 'dark' || (!currentTheme && prefersDarkScheme.matches)) {
    document.documentElement.setAttribute('data-theme', 'dark');
    themeToggle.innerHTML = '<i class="fas fa-sun"></i>';
} else {
    document.documentElement.setAttribute('data-theme', 'light');
    themeToggle.innerHTML = '<i class="fas fa-moon"></i>';
}

themeToggle.addEventListener('click', function() {
    const currentTheme = document.documentElement.getAttribute('data-theme');

    if (currentTheme === 'light') {
        document.documentElement.setAttribute('data-theme', 'dark');
        localStorage.setItem('theme', 'dark');
        this.innerHTML = '<i class="fas fa-sun"></i>';
    } else {
        document.documentElement.setAttribute('data-theme', 'light');
        localStorage.setItem('theme', 'light');
        this.innerHTML = '<i class="fas fa-moon"></i>';
    }
});

성능 최적화 팁

반응형 블로그 페이지의 성능을 최적화하기 위한 몇 가지 팁:

  1. 이미지 최적화: 모든 이미지를 압축하고 적절한 크기로 조정합니다. WebP 형식의 이미지를 사용하면 좋습니다.
  2. 지연 로딩: 이미지와 비디오에 지연 로딩을 적용합니다:
<img src="placeholder.jpg" data-src="actual-image.jpg" class="lazy" alt="이미지 설명">
// 이미지 지연 로딩
document.addEventListener('DOMContentLoaded', function() {
    const lazyImages = [].slice.call(document.querySelectorAll('img.lazy'));

    if ('IntersectionObserver' in window) {
        let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
            entries.forEach(function(entry) {
                if (entry.isIntersecting) {
                    let lazyImage = entry.target;
                    lazyImage.src = lazyImage.dataset.src;
                    lazyImage.classList.remove('lazy');
                    lazyImageObserver.unobserve(lazyImage);
                }
            });
        });

        lazyImages.forEach(function(lazyImage) {
            lazyImageObserver.observe(lazyImage);
        });
    }
});
  1. CSS 최적화: 사용하지 않는 CSS를 제거하고, 중복되는 스타일을 통합합니다.
  2. 폰트 최적화: 필요한 폰트 웨이트만 로드하고, font-display 속성을 사용합니다:
@font-face {
    font-family: 'Noto Sans KR';
    src: url('noto-sans-kr.woff2') format('woff2');
    font-weight: 400;
    font-display: swap;
}
  1. 스크립트 최적화: JavaScript 파일은 가능한 작게 유지하고, 필요한 경우 비동기적으로 로드합니다:
<script src="js/script.js" async></script>

마무리 및 다음 단계

축하합니다! 이제 완전히 반응형인 블로그 메인 페이지를 만들었습니다. 이 페이지는 모바일, 태블릿, 데스크톱 등 모든 화면 크기에서 잘 작동합니다.

다음 단계로 도전해볼 만한 것들:

  1. 블로그 상세 페이지 만들기: 글 내용, 댓글 섹션, 관련 글 등을 포함한 상세 페이지를 만듭니다.
  2. 카테고리 페이지 구현: 특정 카테고리에 속한 글들만 보여주는 페이지를 만듭니다.
  3. 검색 기능 추가: 사용자가 원하는 내용을 검색할 수 있는 기능을 추가합니다.
  4. 소셜 공유 버튼 추가: 각 글을 소셜 미디어에 공유할 수 있는 버튼을 추가합니다.
  5. 댓글 시스템 구현: 사용자가 블로그 글에 댓글을 남길 수 있는 기능을 구현합니다.
  6. 백엔드 연동: 실제 블로그 데이터를 관리할 수 있는 백엔드 시스템과 연동합니다.

반응형 웹 디자인은 현대 웹 개발의 필수 요소입니다. 이 튜토리얼을 통해 반응형 디자인의 기본 원칙을 배우고, 실제 프로젝트에 적용하는 방법을 알아보았습니다. 더 발전된 기능이나 스타일링에 도전해보며, 여러분만의 블로그를 완성해보세요!

질문이나 의견이 있으시면 댓글로 남겨주세요. 행복한 코딩 되세요! 🚀