K-BOOKS ORIGINAL / E03

별빛 파도 페스티벌
28단계 독학 교재

빈 HTML·CSS·JS 파일과 이미지만 받은 뒤 한 폴더를 끝까지 누적 작성합니다. 아래 내용을 순서대로 직접 입력하세요.

학생용 시작 ZIP
T01 / 28

학생 지급 ZIP을 풀고 빈 프로젝트 열기

빈 파일 3개와 이미지 7개를 확인하고 VS Code로 한 폴더를 엽니다.

정상 결과: 아직 홈페이지는 나타나지 않습니다. 빈 파일이 정상입니다.

완성본·START/FINISH는 학생용 ZIP에 들어 있지 않습니다. 한 번만 다운로드하세요.

1. Windows · VS Code

프로젝트 준비

① E03 학생 지급 ZIP 1개를 다운로드합니다.
② ZIP을 우클릭 → [모두 압축 풀기]를 선택합니다.
③ Visual Studio Code → [파일] → [폴더 열기] → KBOOKS_E03_MOCK 폴더 선택
④ index.html · css/style.css · js/script.js가 빈 파일인지 확인
⑤ images 폴더의 7개 이미지, source/content.txt, 문제지.txt를 확인
⑥ T02부터 끝까지 이 한 프로젝트만 계속 수정합니다.
※ 기존에 작성한 폴더 위에 ZIP을 다시 압축 해제하지 마세요.

왜 사용하는가

오류 복구

파일이 없어요 / ZIP을 열기만 하고 압축 해제하지 않았거나 상위 폴더를 열었습니다. / KBOOKS_E03_MOCK 폴더 자체를 열고 파일 목록을 확인하세요.

직접 확인하기

T01 모바일 결과

T02 / 28

index.html 기본 문서와 조립 위치 만들기

HTML 기본 태그, CSS·JS 경로, 앞으로 채울 각 영역의 자리를 작성합니다.

정상 결과: 텍스트 없이 비어 보이는 영역이 있어도 정상입니다.

이 단계에서만 빈 index.html 전체를 작성합니다. 이후에는 E03 표시 주석만 하나씩 교체합니다.

1. index.html

빈 파일 전체에 입력

<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>별빛 파도 페스티벌 | K-BOOKS 자체 제작 실기</title>
  <meta name="description" content="K-BOOKS 자체 제작 웹디자인개발기능사 E03 연습 완성 참고본">
  <link rel="stylesheet" href="css/style.css">
</head>
<body class="festival">
<div class="site-shell" id="home">

<div class="main-column">
<header class="top-header" id="about"><!-- E03:BRAND --><!-- E03:MOBILE --><!-- E03:NAV --></header>
<main>
  <section class="visual" aria-label="메인 이미지 슬라이드">
    <div class="slide-window"><div class="slide-track" id="slideTrack">
          <!-- E03:SLIDE1 -->
          <!-- E03:SLIDE2 -->
          <!-- E03:SLIDE3 -->
    </div></div>
    <!-- E03:CONTROLS -->
  </section>
  <section class="content-grid" id="program" aria-label="공지와 안내">
    <div class="news-panel" id="notice"><!-- E03:TABS -->
      <!-- E03:NOTICE --><!-- E03:GALLERY -->
    </div>
    <!-- E03:STORY -->
    <!-- E03:QUICK -->
  </section>
</main>
<!-- E03:FOOTER -->
</div>
</div>
<!-- E03:POPUP -->
<script src="js/script.js"></script>
</body>
</html>

왜 사용하는가

오류 복구

브라우저가 빈 화면이에요 / 로고와 슬라이드 등은 아직 주석으로 비워 두었습니다. / E03 표시 주석이 있는지 확인하고 T03으로 진행합니다.

직접 확인하기

T02 모바일 결과

T03 / 28

CSS 기본값과 표시 주석 입력

박스 크기·폰트·기본 여백을 설정합니다.

정상 결과: 브라우저 기본 여백이 제거됩니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. css/style.css

빈 파일 전체에 입력

/* E03 - K-BOOKS 실기 180분 완성 참고 CSS */
:root{--primary:#252c61;--accent:#e97cae;--soft:#f9effb;--ink:#2a2f48;--hero-height:300px}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;background:#e9edf0;color:var(--ink);font:14px/1.6 system-ui,"Malgun Gothic",Arial,sans-serif}
a{color:inherit;text-decoration:none}
button{font:inherit;cursor:pointer}
button:focus-visible,a:focus-visible{outline:3px solid #fdaf45;outline-offset:3px}
[hidden]{display:none!important}
/* E03:CSS_SHELL *//* E03:CSS_HEADER *//* E03:CSS_HERO *//* E03:CSS_CONTENT *//* E03:CSS_FOOTPOP *//* E03:CSS_RESPONSIVE */

왜 사용하는가

오류 복구

CSS가 적용 안 돼요 / 파일 위치 또는 <link>의 경로가 다릅니다. / index.html 옆 css 폴더에 style.css가 있는지 확인합니다.

직접 확인하기

T03 모바일 결과

T04 / 28

모바일 화면 폭 규칙 먼저 작성

작은 화면에서 카드·메뉴가 겹치지 않도록 반응형 규칙을 준비합니다.

정상 결과: 390px에서도 화면 밖으로 가로로 넘어가지 않습니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. css/style.css

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: /* E03:CSS_RESPONSIVE */

@media(max-width:950px){.site-shell{max-width:100%}.content-grid{grid-template-columns:minmax(0,1.35fr) repeat(2,minmax(0,.85fr))}.story-card,.quick-card{padding-left:16px;padding-right:16px}}
@media(max-width:760px){
.site-shell{display:block;min-height:0}
.side-brand{min-height:0;padding:12px 17px;display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;gap:10px}
.side-brand .brand{margin:0;flex:1;min-width:0}
.side-brand .brand small{font-size:8px}
.side-brand .sidebar-foot{display:none}
.top-header{height:auto;min-height:76px;padding:14px 16px;flex-wrap:wrap}
.top-header .brand{flex:1}
.mobile-menu{display:inline-block;white-space:nowrap}
.site-nav{display:none;width:100%;flex:0 0 100%;order:10;max-width:none!important}
.site-nav.open{display:block}
.top-header .site-nav>ul{display:block}
.top-header .nav-row{justify-content:space-between}
.top-header .nav-link{text-align:left}
.top-header .submenu{position:static;box-shadow:none}
.site-nav .nav-item{border-bottom:1px solid #ffffff33}
.site-nav .nav-link{padding:8px}
.masthead{height:auto;min-height:55px;padding:12px 18px}
.masthead strong{font-size:12px}
.content-grid{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}
.news-panel{grid-column:1/-1}
.story-card,.quick-card{min-height:150px;padding:25px 17px}
.site-footer{flex-wrap:wrap;padding:22px 19px}
}
@media(max-width:420px){
.slide-copy{left:18px;top:57px;right:15px}.slide-copy h2{font-size:24px}.slide-copy p{font-size:11px}
.slide-no{left:18px;bottom:29px;font-size:18px}
.slider-controls{right:12px;bottom:14px}
.slider-controls>button{min-width:23px;height:26px}
.news-panel{padding:19px 16px}
.story-card,.quick-card{padding:20px 13px}
.story-card strong,.quick-card strong{font-size:14px}
.site-footer{gap:10px}.footer-links{justify-content:flex-start}.footer-links small{text-align:left}
}
@media(prefers-reduced-motion:reduce){.slide-track{transition:none}html{scroll-behavior:auto}}

왜 사용하는가

오류 복구

390px에서 화면이 옆으로 밀려요 / PC용 너비나 이미지의 자연 크기가 그대로 적용됐습니다. / @media와 max-width 규칙을 확인하세요.

직접 확인하기

T04 모바일 결과

T05 / 28

상단 로고·홈페이지 제목 HTML 작성

가로형 헤더 왼쪽에 로고와 사이트명을 추가합니다.

정상 결과: 상단에 별빛 파도 페스티벌 로고가 준비됩니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. index.html

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: <!-- E03:BRAND -->

<a class="brand" href="#home"><img src="images/logo.svg" alt="별빛 파도 페스티벌 심볼"><span><strong>별빛 파도 페스티벌</strong><small>OFFICIAL WEBSITE</small></span></a>

왜 사용하는가

오류 복구

로고가 안 보여요 / 이미지 경로가 달라졌습니다. / images/logo.svg가 index.html과 같은 프로젝트의 images에 있는지 확인합니다.

직접 확인하기

T05 모바일 결과

T06 / 28

모바일 햄버거 메뉴 버튼 작성

작은 화면에서 메뉴를 열 버튼을 만듭니다.

정상 결과: ☰ 메뉴 버튼이 HTML에 추가됩니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. index.html

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: <!-- E03:MOBILE -->

<button type="button" class="mobile-menu" aria-expanded="false" aria-controls="siteNav">☰ 메뉴</button>

왜 사용하는가

오류 복구

버튼이 아직 안 눌려요 / JavaScript 동작 코드를 아직 작성하지 않았습니다. / 현재는 버튼 태그만 확인하고 T26에서 작동을 확인하세요.

직접 확인하기

T06 모바일 결과

T07 / 28

상단 4개 주메뉴·하위 메뉴 작성

축제 소개·프로그램·참가 안내·커뮤니티 메뉴를 채웁니다.

정상 결과: 상단 내비게이션의 메뉴와 하위 메뉴가 HTML에 준비됩니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. index.html

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: <!-- E03:NAV -->

<nav class="site-nav" id="siteNav" aria-label="주 메뉴"><ul>      <li class="nav-item"><div class="nav-row"><a class="nav-link" href="#about">축제 소개</a><button class="submenu-toggle" type="button" aria-expanded="false" aria-controls="sub0" aria-label="축제 소개 하위 메뉴 열기">+</button></div><ul id="sub0" class="submenu" hidden><li><a href="#about">축제 소개 안내</a></li><li><a href="#notice">소식 확인</a></li></ul></li>
      <li class="nav-item"><div class="nav-row"><a class="nav-link" href="#program">프로그램</a><button class="submenu-toggle" type="button" aria-expanded="false" aria-controls="sub1" aria-label="프로그램 하위 메뉴 열기">+</button></div><ul id="sub1" class="submenu" hidden><li><a href="#program">프로그램 안내</a></li><li><a href="#notice">소식 확인</a></li></ul></li>
      <li class="nav-item"><div class="nav-row"><a class="nav-link" href="#reserve">참가 안내</a><button class="submenu-toggle" type="button" aria-expanded="false" aria-controls="sub2" aria-label="참가 안내 하위 메뉴 열기">+</button></div><ul id="sub2" class="submenu" hidden><li><a href="#reserve">참가 안내 안내</a></li><li><a href="#notice">소식 확인</a></li></ul></li>
      <li class="nav-item"><div class="nav-row"><a class="nav-link" href="#community">커뮤니티</a><button class="submenu-toggle" type="button" aria-expanded="false" aria-controls="sub3" aria-label="커뮤니티 하위 메뉴 열기">+</button></div><ul id="sub3" class="submenu" hidden><li><a href="#community">커뮤니티 안내</a></li><li><a href="#notice">소식 확인</a></li></ul></li></ul></nav>

왜 사용하는가

오류 복구

하위 메뉴가 보이지 않아요 / 현재 hidden 상태이며 동작 스크립트는 뒤에서 작성합니다. / sub0~sub3의 ID가 올바른지 먼저 확인하세요.

직접 확인하기

T07 모바일 결과

T08 / 28

슬라이드 영역 CSS부터 작성

슬라이드가 들어오기 전에 300px 높이와 숨김 영역을 만듭니다.

정상 결과: 이미지 없이도 슬라이드 틀이 잡히고 모바일 넘침을 방지합니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. css/style.css

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: /* E03:CSS_HERO */

.visual{position:relative;height:var(--hero-height);overflow:hidden;background:var(--primary)}
.slide-window{height:var(--hero-height);overflow:hidden}
.slide-track{height:100%;display:flex;flex-direction:column;transition:transform .62s cubic-bezier(.22,.66,.19,1);will-change:transform}
.slide{height:var(--hero-height);min-height:var(--hero-height);flex:0 0 var(--hero-height);width:100%;position:relative;overflow:hidden;background:var(--primary);color:white}
.slide::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,#08180d91,transparent 70%);pointer-events:none}
.slide img{display:block;width:100%;height:100%;object-fit:cover}
.slide-copy{position:absolute;z-index:1;top:48px;left:34px;max-width:420px}
.slide-copy .eyebrow{font-size:10px;letter-spacing:.18em;font-weight:800;color:#e2e5e6}
.slide-copy h2{font-size:clamp(25px,4vw,35px);line-height:1.25;margin:19px 0 12px;letter-spacing:-.05em}
.slide-copy p{max-width:410px;font-size:12px;margin:0;color:#f4f4f4}
.slide-no{position:absolute;z-index:1;left:34px;bottom:28px;color:#fff8;font-size:27px;letter-spacing:.14em;font-weight:350}
.slider-controls{position:absolute;z-index:2;bottom:22px;right:27px;display:flex;align-items:center;gap:6px}
.slider-controls>button{border:1px solid #ffffff7d;background:#10271e9a;color:white;border-radius:5px;min-width:27px;height:29px;font-size:15px}
#pauseSlide{font-size:10px;padding:0 7px}
.slider-dots{display:flex;gap:5px;margin-left:8px}
.slider-dots button{width:10px;height:10px;border-radius:99px;border:1px solid white;background:#ffffff65;padding:0}
.slider-dots button[aria-current=true]{width:23px;background:var(--accent)}

왜 사용하는가

오류 복구

슬라이드 이미지가 화면 아래로 길게 보여요 / 슬라이드 영역의 높이나 overflow:hidden이 없습니다. / slide-window와 .slide-track flex-direction:column을 확인합니다.

직접 확인하기

T08 모바일 결과

T09 / 28

슬라이드 첫 번째 이미지 HTML

첫 번째 축제 사진과 텍스트를 입력합니다.

정상 결과: 첫 번째 사진과 제목이 슬라이드 안에 표시됩니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. index.html

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: <!-- E03:SLIDE1 -->

<article class="slide"><img src="images/slide1.jpg" alt="별빛 파도 페스티벌 슬라이드 1"><div class="slide-copy"><span class="eyebrow">STARWAVE FESTIVAL · 01</span><h2>밤을 밝히는 순간</h2><p>음악과 빛이 만나는 도시 축제 안내 홈페이지</p></div><span class="slide-no">01 / 03</span></article>

왜 사용하는가

오류 복구

그림이 보이지 않아요 / images/slide1.jpg 경로 또는 부모 구조가 다릅니다. / 이미지 이름을 확인하고 #slideTrack 안에 추가하세요.

직접 확인하기

T09 모바일 결과

T10 / 28

두 번째 슬라이드 이미지 추가

첫 사진 아래에 두 번째 이미지를 추가합니다.

정상 결과: 슬라이드 부모에 2개의 자식이 들어갑니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. index.html

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: <!-- E03:SLIDE2 -->

<article class="slide"><img src="images/slide2.jpg" alt="별빛 파도 페스티벌 슬라이드 2"><div class="slide-copy"><span class="eyebrow">STARWAVE FESTIVAL · 02</span><h2>밤의 리듬을 경험하세요</h2><p>음악과 빛이 만나는 도시 축제 안내 홈페이지</p></div><span class="slide-no">02 / 03</span></article>

왜 사용하는가

오류 복구

두 장이 동시에 보여요 / overflow:hidden 또는 각 slide 높이가 빠졌습니다. / T08 CSS의 높이와 overflow 규칙을 확인하세요.

직접 확인하기

T10 모바일 결과

T11 / 28

세 번째 슬라이드 이미지 추가

세 번째 사진과 문구를 입력해 3장의 구성을 마칩니다.

정상 결과: 슬라이드 3장이 같은 부모 안에 준비됩니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. index.html

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: <!-- E03:SLIDE3 -->

<article class="slide"><img src="images/slide3.jpg" alt="별빛 파도 페스티벌 슬라이드 3"><div class="slide-copy"><span class="eyebrow">STARWAVE FESTIVAL · 03</span><h2>밤의 리듬을 경험하세요</h2><p>음악과 빛이 만나는 도시 축제 안내 홈페이지</p></div><span class="slide-no">03 / 03</span></article>

왜 사용하는가

오류 복구

세 번째 그림이 안 보여요 / 현재 첫 사진만 보이는 것이 정상입니다. / T25에서 상하 이동 기능을 작성한 뒤 확인하세요.

직접 확인하기

T11 모바일 결과

T12 / 28

이전·다음·일시정지·점 버튼 작성

슬라이드 제어 버튼을 HTML에 추가합니다.

정상 결과: 왼쪽/오른쪽 버튼, 일시정지, 페이지 점 3개가 준비됩니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. index.html

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: <!-- E03:CONTROLS -->

<div class="slider-controls" aria-label="슬라이드 컨트롤"><button type="button" id="prevSlide" aria-label="이전 슬라이드">‹</button><button type="button" id="pauseSlide" aria-pressed="false">일시정지</button><button type="button" id="nextSlide" aria-label="다음 슬라이드">›</button><div class="slider-dots" aria-label="슬라이드 번호"><button type="button" data-index="0" aria-label="첫 번째 슬라이드" aria-current="true"></button><button type="button" data-index="1" aria-label="두 번째 슬라이드"></button><button type="button" data-index="2" aria-label="세 번째 슬라이드"></button></div></div>

왜 사용하는가

오류 복구

버튼을 눌러도 이미지가 안 바뀌어요 / 기능 스크립트는 T25에서 연결합니다. / 현재는 button ID와 data-index를 먼저 검사하세요.

직접 확인하기

T12 모바일 결과

T13 / 28

공지·갤러리 탭 버튼

두 탭과 선택 상태를 구성합니다.

정상 결과: 공지사항 탭이 선택된 형태의 HTML이 준비됩니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. index.html

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: <!-- E03:TABS -->

<div class="tab-buttons" role="tablist" aria-label="공지와 갤러리"><button type="button" id="noticeTab" role="tab" aria-selected="true" aria-controls="noticePanel">공지사항</button><button type="button" id="galleryTab" role="tab" aria-selected="false" aria-controls="galleryPanel">갤러리</button></div>

왜 사용하는가

오류 복구

갤러리 탭을 눌러도 변화가 없어요 / 탭 전환 스크립트는 T27에서 작성합니다. / 탭 ID와 연결 패널 이름을 확인하고 계속 진행하세요.

직접 확인하기

T13 모바일 결과

T14 / 28

공지사항 3건 추가

공지 제목과 날짜, 클릭용 버튼을 작성합니다.

정상 결과: 공지 3개가 들어갑니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. index.html

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: <!-- E03:NOTICE -->

<section id="noticePanel" role="tabpanel" aria-labelledby="noticeTab"><ul class="notice-list">
              <li><button type="button" class="notice-open" data-title="2026 페스티벌 일정 발표"><span>2026 페스티벌 일정 발표</span><time datetime="2026-11-02">2026.11</time></button></li>
              <li><button type="button" class="notice-open" data-title="아티스트 라인업 1차 공개"><span>아티스트 라인업 1차 공개</span><time datetime="2026-11-02">2026.11</time></button></li>
              <li><button type="button" class="notice-open" data-title="자원활동가 모집 공지"><span>자원활동가 모집 공지</span><time datetime="2026-11-02">2026.11</time></button></li>
      </ul></section>

왜 사용하는가

오류 복구

공지를 눌러도 팝업이 안 나와요 / 레이어와 JavaScript를 아직 추가하지 않았습니다. / T19와 T27을 마친 뒤 테스트합니다.

직접 확인하기

T14 모바일 결과

T15 / 28

갤러리 이미지 3장 작성

공지가 아닌 갤러리 탭에 이미지 세 개를 연결합니다.

정상 결과: 사진 3장이 갤러리 패널 안에 준비됩니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. index.html

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: <!-- E03:GALLERY -->

<section id="galleryPanel" role="tabpanel" aria-labelledby="galleryTab" hidden><div class="gallery-grid">
              <a href="#program" class="gallery-item"><img src="images/gallery1.jpg" alt="메인 스테이지 사진"><span>메인 스테이지</span></a>
              <a href="#program" class="gallery-item"><img src="images/gallery2.jpg" alt="빛의 거리 사진"><span>빛의 거리</span></a>
              <a href="#program" class="gallery-item"><img src="images/gallery3.jpg" alt="아트 마켓 사진"><span>아트 마켓</span></a>
      </div></section>

왜 사용하는가

오류 복구

갤러리 그림이 안 보여요 / 처음에 hidden인 것은 정상입니다. / T27에서 탭 코드를 만든 뒤 갤러리를 열어 확인합니다.

직접 확인하기

T15 모바일 결과

T16 / 28

콘텐츠 가운데 홍보 카드 작성

아티스트 라인업 안내 카드 HTML을 입력합니다.

정상 결과: FEATURED STORY 카드가 생깁니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. index.html

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: <!-- E03:STORY -->

<a href="#community" class="story-card" id="reserve"><span>FEATURED STORY</span><strong>아티스트 라인업</strong><small>음악과 빛이 만나는 도시 축제 안내 홈페이지</small><span class="card-arrow">자세히 보기 ↗</span></a>

왜 사용하는가

오류 복구

카드가 예쁘게 배치되지 않아요 / 아직 3열 Grid CSS를 작성하지 않았습니다. / T22에서 콘텐츠 Grid를 적용합니다.

직접 확인하기

T16 모바일 결과

T17 / 28

콘텐츠 오른쪽 바로가기 카드 작성

참가 정보로 이동하는 카드를 작성합니다.

정상 결과: QUICK ACCESS 카드가 준비됩니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. index.html

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: <!-- E03:QUICK -->

<a href="#community" class="quick-card"><span>QUICK ACCESS</span><strong>티켓 바로가기</strong><small>관련 정보를 확인해 보세요</small><span class="card-arrow">바로가기 ↗</span></a>

왜 사용하는가

오류 복구

오른쪽 카드가 아래로 내려가요 / 3열 콘텐츠 CSS가 아직 적용되지 않았습니다. / T22에서 3열 Grid 배치를 확인합니다.

직접 확인하기

T17 모바일 결과

T18 / 28

푸터 정보 작성

사이트명과 링크·저작권 정보를 HTML로 작성합니다.

정상 결과: 페이지 아래쪽에 푸터 내용이 생깁니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. index.html

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: <!-- E03:FOOTER -->

<footer id="community" class="site-footer"><div><strong>별빛 파도 페스티벌</strong><small>K-BOOKS ORIGINAL PRACTICE · 2026</small></div><div class="footer-links"><a href="#about">이용약관</a><a href="#program">프로그램</a><a href="#notice">문의하기</a><small>© K-BOOKS educational mock project.</small></div></footer>

왜 사용하는가

오류 복구

푸터가 높아 보여요 / 푸터 높이·정렬 CSS는 T23에서 완성됩니다. / HTML을 지우지 말고 T23으로 진행합니다.

직접 확인하기

T18 모바일 결과

T19 / 28

공지 팝업 HTML을 숨긴 상태로 작성

공지사항을 눌렀을 때 보여줄 레이어를 준비합니다.

정상 결과: noticePopup이 HTML에 존재하며 아직 숨겨집니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. index.html

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: <!-- E03:POPUP -->

<div id="noticePopup" class="popup-backdrop" role="dialog" aria-modal="true" aria-labelledby="popupTitle" hidden><div class="popup-card"><button type="button" class="popup-close" aria-label="팝업 닫기">×</button><span class="popup-label">NOTICE</span><h2 id="popupTitle"></h2><p id="popupBody">별빛 파도 페스티벌에서 안내하는 연습용 공지사항입니다.</p><button type="button" class="popup-ok">확인</button></div></div>

왜 사용하는가

오류 복구

팝업이 처음부터 떠 있어요 / hidden 속성이 빠졌습니다. / noticePopup에 hidden과 CSS의 [hidden] 규칙이 있는지 확인합니다.

직접 확인하기

T19 모바일 결과

T20 / 28

1200px 가로형 바깥 배치 CSS 작성

사이트 최대 너비와 기본 화면 영역을 정리합니다.

정상 결과: PC에서 1200px 너비의 홈페이지 외곽이 잡힙니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. css/style.css

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: /* E03:CSS_SHELL */

.site-shell{max-width:1200px;min-height:700px;margin:0 auto;background:white;display:block;box-shadow:0 12px 45px #1d26301c}
.main-column{min-width:0}
.brand{display:flex;align-items:center;gap:12px;font-weight:900}
.brand img{width:27px;height:27px;object-fit:contain}
.brand strong{font-size:17px;letter-spacing:-.04em}
.brand small{display:block;font-size:10px;letter-spacing:.16em;font-weight:650;opacity:.64}
.mobile-menu{display:none;background:transparent;border:1px solid currentColor;border-radius:6px;padding:7px 11px}
.site-nav ul{padding:0;margin:0;list-style:none}
.nav-item{position:relative;min-width:0}
.nav-row{display:flex;align-items:center;min-width:0}
.nav-link{display:block;font-weight:750;font-size:13px;flex:1;min-width:0}
.submenu-toggle{border:0;background:transparent;color:inherit;padding:12px;opacity:.7}
.submenu{padding:4px 12px 12px!important;list-style:none;background:#ffffff16}
.submenu li a{display:block;padding:6px 5px;font-size:11px;opacity:.88}
.submenu a:hover,.nav-link:hover{text-decoration:underline;text-underline-offset:4px}
.side-brand{background:var(--primary);color:white;padding:32px 16px 18px;display:flex;flex-direction:column;min-height:700px}
.side-brand .brand{flex-wrap:wrap;gap:6px 9px;line-height:1.5;margin-bottom:40px}
.side-brand .brand small{width:100%;padding-left:0}
.side-brand .nav-item{border-bottom:1px solid #ffffff29}
.side-brand .nav-row{min-height:55px}
.side-brand .nav-link{padding:12px 5px}
.sidebar-foot{margin:auto 0 0;color:#cce7d8;font-size:9px;letter-spacing:.13em}

왜 사용하는가

오류 복구

화면이 옆으로 넘쳐요 / max-width가 아닌 고정 width만 사용했습니다. / site-shell의 max-width와 반응형 규칙을 확인합니다.

직접 확인하기

T20 모바일 결과

T21 / 28

상단 가로 메뉴 100px 디자인

로고·메뉴를 가로 한 줄로 정렬합니다.

정상 결과: 문제지 상단 100px 헤더에 가까운 모습이 됩니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. css/style.css

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: /* E03:CSS_HEADER */

.top-header{height:100px;background:var(--primary);color:white;display:flex;align-items:center;justify-content:space-between;gap:22px;padding:0 32px}
.top-header .site-nav{flex:1;max-width:660px}
.top-header .site-nav>ul{display:flex;align-items:center;justify-content:space-between;gap:14px}
.top-header .nav-item{flex:1;min-width:0}
.top-header .nav-row{justify-content:center}
.top-header .nav-link{text-align:center;white-space:nowrap}
.top-header .submenu{position:absolute;z-index:8;top:100%;min-width:145px;background:var(--primary);box-shadow:0 9px 14px #13223627}
.masthead{height:100px;background:linear-gradient(100deg,#f4f9f2,#e5eee2);color:var(--primary);padding:24px 30px;display:flex;align-items:center;justify-content:space-between;gap:12px}
.masthead span{letter-spacing:.16em;font-size:11px;font-weight:750}
.masthead strong{font-size:17px;letter-spacing:-.04em}

왜 사용하는가

오류 복구

주메뉴가 아래로 밀려요 / 부모의 display:flex가 적용되지 않았거나 여백이 큽니다. / top-header와 .site-nav 스타일을 확인합니다.

직접 확인하기

T21 모바일 결과

T22 / 28

공지·배너·바로가기 3열 CSS 작성

콘텐츠 200px 안에서 3개 카드 영역을 나눕니다.

정상 결과: PC에서 공지·가운데 카드·오른쪽 카드가 가로 3열로 표시됩니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. css/style.css

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: /* E03:CSS_CONTENT */

.content-grid{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(0,.8fr) minmax(0,.8fr);min-height:200px}
.news-panel{min-width:0;padding:20px 24px}
.tab-buttons{display:flex;gap:19px;border-bottom:1px solid #e1e5ea;margin-bottom:12px}
.tab-buttons button{background:transparent;border:0;border-bottom:3px solid transparent;padding:0 1px 10px;font-weight:850;color:#89949f}
.tab-buttons button[aria-selected=true]{color:var(--primary);border-color:var(--accent)}
.notice-list{padding:0;margin:0;list-style:none}
.notice-open{display:flex;align-items:center;justify-content:space-between;gap:12px;border:0;width:100%;background:transparent;padding:7px 0;text-align:left;font-size:11px;color:#344251}
.notice-open span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.notice-open time{color:#9cabb6;white-space:nowrap;font-size:10px}
.notice-open:hover span{text-decoration:underline}
.gallery-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
.gallery-item{min-width:0;display:block;font-size:10px;text-align:center}
.gallery-item img{width:100%;aspect-ratio:4/3;object-fit:cover;display:block;border-radius:4px}
.gallery-item span{display:block;margin-top:4px;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}
.story-card,.quick-card{padding:43px 23px 15px;display:flex;flex-direction:column;min-width:0;min-height:200px}
.story-card{background:var(--soft)}
.quick-card{background:var(--primary);color:white}
.story-card>span:first-child,.quick-card>span:first-child{font-weight:800;letter-spacing:.14em;font-size:10px;opacity:.72}
.story-card strong,.quick-card strong{font-size:17px;margin:8px 0 9px;letter-spacing:-.05em}
.story-card small,.quick-card small{font-size:11px;opacity:.76}
.card-arrow{font-size:10px;opacity:.8;margin-top:auto!important}

왜 사용하는가

오류 복구

모바일에서 카드가 너무 좁아요 / PC의 3열 규칙을 좁은 화면에 그대로 적용했습니다. / T04의 media 쿼리 1~2열 전환 규칙을 확인합니다.

직접 확인하기

T22 모바일 결과

T23 / 28

푸터·레이어 팝업 CSS 작성

푸터 높이 100px과 숨김·열림 팝업 디자인을 마칩니다.

정상 결과: 전체 화면의 푸터가 정리되고 팝업 틀이 준비됩니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. css/style.css

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: /* E03:CSS_FOOTPOP */

.site-footer{display:flex;gap:15px;justify-content:space-between;align-items:center;min-height:100px;padding:23px 32px;background:#f6f8fb;border-top:1px solid #e9edf0}
.site-footer strong{font-size:17px;display:block}
.site-footer small{font-size:10px;color:#96a3b0;display:block}
.footer-links{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:8px 15px;max-width:370px;font-size:10px;color:#8b9aab}
.footer-links small{width:100%;text-align:right}
.popup-backdrop{position:fixed;inset:0;background:#0a142bbf;z-index:100;display:grid;place-items:center;padding:20px}
.popup-card{position:relative;background:white;width:min(460px,100%);border-radius:8px;padding:34px;box-shadow:0 25px 75px #0005}
.popup-card h2{font-size:21px;margin:11px 0}
.popup-card p{color:#586977;font-size:13px}
.popup-label{color:var(--primary);font-weight:900;letter-spacing:.14em;font-size:10px}
.popup-close{position:absolute;right:18px;top:12px;background:transparent;border:0;font-size:26px}
.popup-ok{background:var(--primary);color:white;border:0;padding:9px 20px;border-radius:5px;margin-top:10px}

왜 사용하는가

오류 복구

팝업이 콘텐츠 밑에 숨어요 / z-index 또는 position:fixed가 없습니다. / popup-backdrop 스타일과 hidden 속성을 확인합니다.

직접 확인하기

T23 모바일 결과

T24 / 28

빈 script.js에 실행 뼈대 작성

각 기능을 넣을 표시 주석 네 개와 닫는 괄호를 작성합니다.

정상 결과: script.js가 실행 가능한 빈 함수 형태로 준비됩니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. js/script.js

빈 파일 전체에 입력

/* E03 - K-BOOKS 자체 제작 실기 참고 JS: 상하 슬라이드 */
(()=>{
  'use strict';
  // E03:JS_SLIDER
  // E03:JS_MENU
  // E03:JS_TABS
  // E03:JS_POPUP
})();

왜 사용하는가

오류 복구

JavaScript에 빨간 오류가 뜹니다 / 마지막 닫는 괄호가 빠졌거나 주석을 잘못 붙였습니다. / 시작 (()=>{ 와 끝 })();를 확인합니다.

직접 확인하기

T24 모바일 결과

T25 / 28

상하 슬라이드 JavaScript 작성

translateY로 3장 사진을 세로로 이동시키고 자동재생·버튼을 연결합니다.

정상 결과: 2.6초마다 사진이 아래에서 위로 이동하고 버튼도 작동합니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. js/script.js

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: // E03:JS_SLIDER

  const track=document.querySelector('#slideTrack');
  const slides=[...document.querySelectorAll('.slide')];
  const dots=[...document.querySelectorAll('.slider-dots button')];
  const pauseButton=document.querySelector('#pauseSlide');
  let current=0,paused=window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  let timer=null;
  function showSlide(next){
    current=(next+slides.length)%slides.length;
    // E03 과제의 핵심: 세로는 슬라이드 실제 높이만큼 translateY 이동
    const offset=`translateY(-${current*slides[0].getBoundingClientRect().height}px)`;
    track.style.transform=offset;
    dots.forEach((dot,i)=>{if(i===current)dot.setAttribute('aria-current','true');else dot.removeAttribute('aria-current');});
  }
  function stopTimer(){if(timer!==null)clearInterval(timer);timer=null;}
  function startTimer(){stopTimer();if(!paused&&slides.length>1)timer=setInterval(()=>showSlide(current+1),2600);}
  pauseButton.addEventListener('click',()=>{paused=!paused;pauseButton.setAttribute('aria-pressed',String(paused));pauseButton.textContent=paused?'재생':'일시정지';startTimer();});
  document.querySelector('#prevSlide').addEventListener('click',()=>showSlide(current-1));
  document.querySelector('#nextSlide').addEventListener('click',()=>showSlide(current+1));
  dots.forEach((dot,i)=>dot.addEventListener('click',()=>showSlide(i)));
  document.addEventListener('visibilitychange',()=>{if(document.hidden)stopTimer();else startTimer();});
  window.addEventListener('resize',()=>showSlide(current));
  pauseButton.setAttribute('aria-pressed',String(paused));
  pauseButton.textContent=paused?'재생':'일시정지';
  showSlide(0);startTimer();

왜 사용하는가

오류 복구

사진이 가로로 넘어가요 / translateX를 사용했거나 slideTrack의 flex-direction이 row입니다. / translateY와 flex-direction:column을 다시 확인합니다.

직접 확인하기

T25 모바일 결과

T26 / 28

모바일 메뉴·하위 메뉴 JavaScript

햄버거 및 각 하위 메뉴 펼치기를 연결합니다.

정상 결과: 모바일에서 메뉴를 열고 닫을 수 있습니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. js/script.js

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: // E03:JS_MENU

  const menuButton=document.querySelector('.mobile-menu'),menu=document.querySelector('#siteNav');
  menuButton.addEventListener('click',()=>{const open=menu.classList.toggle('open');menuButton.setAttribute('aria-expanded',String(open));});
  document.querySelectorAll('.submenu-toggle').forEach(button=>{
    button.addEventListener('click',()=>{const target=document.getElementById(button.getAttribute('aria-controls'));const open=button.getAttribute('aria-expanded')!=='true';button.setAttribute('aria-expanded',String(open));target.hidden=!open;});
  });

왜 사용하는가

오류 복구

모바일에서 메뉴가 안 열려요 / mobile-menu 클래스나 siteNav ID가 코드와 다릅니다. / HTML의 class/ID와 JS querySelector를 비교합니다.

직접 확인하기

T26 모바일 결과

T27 / 28

공지·갤러리 탭과 팝업 JavaScript

두 탭 전환과 공지 레이어 열기·닫기·Esc 기능을 완성합니다.

정상 결과: 갤러리 탭과 공지 팝업까지 전부 정상 작동합니다.

이전 단계 내용을 지우지 말고 표시 주석만 찾아 바꾸세요. Ctrl+S 저장 후 Chrome을 새로고침하세요.

1. js/script.js

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: // E03:JS_TABS

  const tabs=[document.querySelector('#noticeTab'),document.querySelector('#galleryTab')];
  const panels=[document.querySelector('#noticePanel'),document.querySelector('#galleryPanel')];
  tabs.forEach((tab,i)=>tab.addEventListener('click',()=>{
    tabs.forEach((t,k)=>{t.setAttribute('aria-selected',String(i===k));t.tabIndex=i===k?0:-1;panels[k].hidden=i!==k;});
  }));
2. js/script.js

Ctrl+F로 표시 주석 찾기 → 교체

찾을 주석: // E03:JS_POPUP

  const popup=document.querySelector('#noticePopup'),popupTitle=document.querySelector('#popupTitle');
  const closeButton=document.querySelector('.popup-close');let opener=null;
  function closePopup(){popup.hidden=true;if(opener)opener.focus();}
  document.querySelectorAll('.notice-open').forEach(button=>button.addEventListener('click',()=>{opener=button;popupTitle.textContent=button.dataset.title||button.textContent.trim();popup.hidden=false;closeButton.focus();}));
  closeButton.addEventListener('click',closePopup);
  document.querySelector('.popup-ok').addEventListener('click',closePopup);
  popup.addEventListener('click',event=>{if(event.target===popup)closePopup();});
  document.addEventListener('keydown',event=>{if(event.key==='Escape'&&!popup.hidden)closePopup();});

왜 사용하는가

오류 복구

갤러리가 표시되지 않거나 팝업이 안 닫혀요 / HTML의 ID와 JS 선택자가 다르거나 hidden 처리가 누락됐습니다. / noticeTab/galleryTab과 popup-close, popup-ok 이름을 비교합니다.

직접 확인하기

T27 모바일 결과

T28 / 28

1200px·모바일·오프라인 제출 최종 검사

목표 화면과 파일 구조·상하 슬라이드·작동을 직접 검증합니다.

정상 결과: PC 1200px: 헤더100/슬라이드300/콘텐츠200/푸터100. 모바일에서도 겹침이 없습니다.

180분 모의시험에서는 단계별 코드가 나오지 않습니다.

1. Chrome · VS Code · 제출 체크

최종 검사

① 빈 파일 3개에 직접 작성한 코드가 모두 저장됐는지 확인
② index.html → css/style.css·js/script.js 상대경로 확인
③ images 안의 7개 파일과 모든 이미지가 보이는지 확인
④ PC 1200px: 상단 메뉴100·슬라이드300·콘텐츠200·푸터100을 비교
⑤ 상하 이동 translateY, 2.6초 자동 전환, 이전/다음/일시정지 확인
⑥ 모바일 메뉴·하위 메뉴·공지/갤러리 탭·팝업/닫기 확인
⑦ 인터넷을 끈 뒤 index.html을 직접 열어 기능 확인
⑧ 제출 폴더 10MB 이하·시험장에서 지시한 경로 준수
⑨ 준비가 되면 별도 힌트 없는 180분 모의시험에 도전

왜 사용하는가

오류 복구

다른 PC에서 이미지가 안 나와요 / index.html만 복사하고 css/js/images를 빠뜨렸습니다. / 프로젝트 폴더 전체를 제출하고 상대경로를 유지하세요.

직접 확인하기

T28 모바일 결과