Skip to Content
WebCSSCSS 기본기20. 종합 점검: 시안과 나란히 비교하기

이번 편의 결과물: 데스크톱·모바일 모두 시안과 일치하는 완성된 포트폴리오 사이트. 01_학습방향.md의 완료 기준 5가지를 자가 점검합니다. 다루는 개념: 시안 대 결과물 스크린샷 비교, 포커스 표시·색 대비 접근성 점검, DevTools로 최종 확인

이 편에서 만드는 파일

web-practice/portfolio/ └── css/ ├── tokens.css (03~19편 완성분, 이 편에서는 수정하지 않음) └── styles.css (03~19편 완성분, 이 편에서는 수정하지 않음)

새 코드는 없습니다. 지금까지 쌓아 온 두 파일의 정본 전체를 확인하고, 시안과 결과물을 대조해 빠진 부분이 없는지 점검합니다.

개념 정리

01_학습방향.md가 정한 완료 기준은 다섯 가지입니다.

#기준확인 방법
1시안과 색·간격·타이포가 데스크톱/모바일 모두 일치시안 이미지와 브라우저 화면을 나란히 놓고 비교
2css/가 리셋 → 변수 → 컴포넌트 순서로 정리styles.css를 위에서 아래로 훑으며 순서 확인
3내비게이션(Flexbox)·카드(Grid)를 고른 이유를 설명할 수 있음왜 1차원은 Flex, 2차원은 Grid인지 스스로 말해 보기
4미디어 쿼리가 개입하는 지점을 DevTools로 짚을 수 있음반응형 모드에서 폭을 조절하며 브레이크포인트 확인
5transition·animation 값을 조정할 수 있음17·18편에서 바꿔 본 값들을 다시 시도

색 대비는 WCAG AA 기준(본문 텍스트 4.5:1 이상)을 최소선으로 봅니다.

조합대비 확인 대상
--color-text(#1f2328) on --color-bg(#ffffff)본문 텍스트
--color-text-muted(#59636e) on --color-bg부제·도움말 문구
#ffffff on --color-primary(#0969da)기본 버튼 글자

DevTools의 Elements 패널에서 텍스트 색상 값을 클릭하면 대비 비율과 AA/AAA 통과 여부를 바로 보여줍니다.

실습

1. tokens.css 정본 확인

/* web-practice/portfolio/css/tokens.css */ :root { /* 색 */ --color-bg: #ffffff; --color-surface: #f6f7f9; --color-text: #1f2328; --color-text-muted: #59636e; --color-primary: #0969da; --color-primary-hover: #0550ae; --color-border: #d0d7de; --color-accent: #bf3989; /* 타이포 */ --font-sans: "Pretendard", "Noto Sans KR", system-ui, sans-serif; --font-mono: "JetBrains Mono", ui-monospace, monospace; --text-xs: 0.75rem; --text-sm: 0.875rem; --text-base: 1rem; --text-lg: 1.25rem; --text-xl: 1.5rem; --text-2xl: 2rem; --text-3xl: 3rem; --leading: 1.6; /* 간격 (4px 배수) */ --space-1: 0.25rem; --space-2: 0.5rem; --space-3: 0.75rem; --space-4: 1rem; --space-6: 1.5rem; --space-8: 2rem; --space-12: 3rem; --space-16: 4rem; /* 레이아웃 */ --container-max: 72rem; --radius: 0.5rem; --shadow: 0 1px 3px rgb(0 0 0 / 0.1), 0 4px 12px rgb(0 0 0 / 0.06); --header-height: 4rem; }

이 파일은 04편 이후 한 줄도 바뀌지 않았습니다. 다크 모드용 값(--color-bg: #0d1117 등)은 modern_css가 다음 과목에서 같은 파일에 추가합니다.

2. styles.css 정본 확인

/* web-practice/portfolio/css/styles.css */ /* ======================================== 1. 리셋 (03편) ======================================== */ *, *::before, *::after { box-sizing: border-box; } body, h1, h2, h3, p, ul, ol, figure { margin: 0; } ul, ol { padding: 0; list-style: none; } img { max-width: 100%; display: block; } a { color: inherit; text-decoration: none; } button, input, textarea, select { font: inherit; color: inherit; } /* ======================================== 2. 공통 레이아웃 ======================================== */ body { font-family: var(--font-sans); font-size: var(--text-base); line-height: var(--leading); color: var(--color-text); background-color: var(--color-bg); } .container { max-width: var(--container-max); margin-inline: auto; padding-inline: var(--space-6); } /* ======================================== 3. 타이포그래피 (05편) ======================================== */ h1, h2, h3 { font-weight: 700; line-height: 1.2; color: var(--color-text); } h1 { font-size: var(--text-3xl); } h2 { font-size: var(--text-2xl); margin-bottom: var(--space-4); } h3 { font-size: var(--text-lg); } p { margin-bottom: var(--space-4); } .logo { font-size: var(--text-lg); font-weight: 700; } /* ======================================== 4. 색·배경 (06편) ======================================== */ a { color: var(--color-primary); } a.logo, .site-nav a, .project-card a { color: inherit; } .hero { background: linear-gradient(180deg, var(--color-surface), var(--color-bg)); } /* ======================================== 5. 박스 모델·여백 (07편) ======================================== */ .about, .skills, .career, .project-grid, .contact-form { padding-block: var(--space-12); } .about + .skills, .skills + .career { border-top: 1px solid var(--color-border); } /* ======================================== 6. 버튼 (06, 17편) ======================================== */ .button { display: inline-block; border-radius: var(--radius); padding: var(--space-2) var(--space-6); font-weight: 700; background-color: var(--color-primary); color: #ffffff; border: 1px solid transparent; transition: background-color 200ms ease, border-color 200ms ease, transform 100ms ease; } .button:hover { background-color: var(--color-primary-hover); } .button-secondary { background-color: transparent; border-color: var(--color-border); color: var(--color-text); } .button-secondary:hover { background-color: transparent; border-color: var(--color-primary); color: var(--color-primary); } .button:active { transform: translateY(1px); } /* ======================================== 7. 헤더 & 내비게이션 (08~10, 13편) ======================================== */ .site-header { position: sticky; top: 0; z-index: 10; display: flex; align-items: center; justify-content: space-between; height: var(--header-height); padding-inline: var(--space-6); background-color: var(--color-bg); border-bottom: 1px solid var(--color-border); } .site-nav { display: none; gap: var(--space-6); } .site-nav a[aria-current='page'] { color: var(--color-primary); font-weight: 700; } .menu-toggle { display: inline-flex; align-items: center; justify-content: center; width: 2.5rem; height: 2.5rem; border: 1px solid var(--color-border); border-radius: var(--radius); background: var(--color-bg); cursor: pointer; } /* ======================================== 8. 모바일 메뉴 popover (13, 19편) ======================================== */ #mobile-menu { display: flex; flex-direction: column; gap: var(--space-4); position: fixed; inset: var(--header-height) var(--space-4) auto var(--space-4); margin: 0; padding: var(--space-4); border: 1px solid var(--color-border); border-radius: var(--radius); background: var(--color-bg); box-shadow: var(--shadow); } #mobile-menu a { display: block; font-size: var(--text-lg); } #mobile-menu:popover-open { animation: overlay-enter 150ms ease-out; } /* ======================================== 9. 히어로 (06, 18편) ======================================== */ .hero { padding-block: var(--space-16); text-align: center; } .hero h1 { font-size: var(--text-3xl); } .hero p { font-size: var(--text-lg); color: var(--color-text-muted); } .hero > * { animation: hero-enter 600ms ease-out both; } .hero > *:nth-child(2) { animation-delay: 150ms; } .hero > *:nth-child(3) { animation-delay: 300ms; } .hero > *:nth-child(4) { animation-delay: 400ms; } @keyframes hero-enter { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } } @media (prefers-reduced-motion: reduce) { .hero > * { animation: none; } } /* ======================================== 10. 소개 / 기술 스택 / 경력 (07, 09편) ======================================== */ .about { display: flex; flex-wrap: wrap; gap: var(--space-8); } .about img { width: 12rem; border-radius: var(--radius); } .about p { line-height: 1.8; } .skills ul { display: flex; flex-wrap: wrap; gap: var(--space-4); } .skills li { padding: var(--space-2) var(--space-4); background-color: var(--color-surface); border-radius: var(--radius); font-size: var(--text-sm); } .career table { width: 100%; border-collapse: collapse; } .career th { background-color: var(--color-surface); text-align: left; font-weight: 700; padding: var(--space-3) var(--space-4); } .career td { padding: var(--space-3) var(--space-4); border-bottom: 1px solid var(--color-border); } /* ======================================== 11. 프로젝트 카드 그리드 (11~12, 17편) ======================================== */ .project-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-6); } .project-card { display: flex; flex-direction: column; background-color: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius); overflow: hidden; transition: transform 200ms ease, box-shadow 200ms ease; } .project-card:hover { transform: translateY(-2px); box-shadow: var(--shadow); } .project-card img { aspect-ratio: 3 / 2; object-fit: cover; } .project-card h2 { font-size: var(--text-lg); padding-inline: var(--space-4); margin-top: var(--space-4); } .project-card p { padding-inline: var(--space-4); color: var(--color-text-muted); font-size: var(--text-sm); } .project-card .tags { display: flex; flex-wrap: wrap; gap: var(--space-2); padding-inline: var(--space-4); } .project-card .tags li { padding: var(--space-1) var(--space-3); background-color: var(--color-bg); border: 1px solid var(--color-border); border-radius: 999px; font-size: var(--text-xs); } .project-card button[data-open-project] { margin: var(--space-4); margin-top: auto; } /* ======================================== 12. 프로젝트 상세 dialog (19편) ======================================== */ #project-dialog { width: min(32rem, 90vw); border: none; border-radius: var(--radius); padding: var(--space-8); background-color: var(--color-bg); box-shadow: var(--shadow); } #project-dialog::backdrop { background-color: rgb(15 23 32 / 0.5); } #project-dialog h3 { margin-bottom: var(--space-4); } #project-dialog p + p { margin-top: var(--space-3); } #project-dialog[open] { animation: overlay-enter 200ms ease-out; } @keyframes overlay-enter { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } } /* ======================================== 13. 연락 폼 (15~17편) ======================================== */ .contact-form { display: flex; flex-direction: column; gap: var(--space-6); max-width: 32rem; } .field { display: flex; flex-direction: column; gap: var(--space-2); } .field label { font-size: var(--text-sm); font-weight: 700; color: var(--color-text); } .field input, .field select, .field textarea { width: 100%; padding: var(--space-3); border: 1px solid var(--color-border); border-radius: var(--radius); transition: border-color 150ms ease, box-shadow 150ms ease; } .field input:focus, .field select:focus, .field textarea:focus { outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 2px rgb(9 105 218 / 0.2); } .field input:invalid:not(:placeholder-shown) { border-color: var(--color-accent); } .field-checkbox { flex-direction: row; align-items: center; gap: var(--space-2); } .field .help { font-size: var(--text-xs); color: var(--color-text-muted); } .contact-form button { display: inline-flex; align-items: center; justify-content: center; height: 2.75rem; padding-inline: var(--space-6); border-radius: var(--radius); font-size: var(--text-base); font-weight: 700; cursor: pointer; border: 1px solid transparent; background: var(--color-primary); color: #ffffff; transition: background-color 200ms ease, transform 100ms ease; } .contact-form button:hover { background: var(--color-primary-hover); } .contact-form button:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; } .contact-form button:active { transform: translateY(1px); } .contact-form button:disabled { opacity: 0.5; cursor: not-allowed; } .contact-form button:disabled:hover { background: var(--color-primary); } /* ======================================== 14. FAQ details (19편) ======================================== */ .faq details { border-bottom: 1px solid var(--color-border); padding-block: var(--space-4); } .faq summary { display: flex; justify-content: space-between; align-items: center; cursor: pointer; font-weight: 700; list-style: none; } .faq summary::-webkit-details-marker { display: none; } .faq summary::after { content: "+"; font-size: var(--text-lg); color: var(--color-text-muted); transition: transform 200ms ease; } .faq details[open] summary::after { transform: rotate(45deg); } .faq details[open] summary { margin-bottom: var(--space-2); } /* ======================================== 15. 푸터 ======================================== */ .site-footer { padding-block: var(--space-8); border-top: 1px solid var(--color-border); color: var(--color-text-muted); font-size: var(--text-sm); } .site-footer .container { display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--space-4); } /* ======================================== 16. 반응형 (13~14편) ======================================== */ @media (max-width: 767px) { .hero h1 { font-size: var(--text-2xl); } .project-card h2 { font-size: var(--text-base); } } @media (min-width: 768px) { .project-grid { grid-template-columns: repeat(2, 1fr); } } @media (min-width: 1024px) { .site-nav { display: flex; } .menu-toggle { display: none; } .project-grid { grid-template-columns: repeat(3, 1fr); } }

3. 데스크톱 시안과 비교

public/practice/css_fundamentals/starter.zipdesign/desktop-index.png·desktop-projects.png·desktop-contact.png를 열고, 브라우저 창을 1024px 이상으로 넓혀 세 페이지를 나란히 놓습니다. 헤더 내비게이션 위치, 히어로 여백, 카드 3열 배치, 폼 필드 순서를 하나씩 짚습니다.

4. 모바일 시안과 비교

개발자 도구의 반응형 모드(Ctrl + Shift + M)에서 폭 375px 안팎으로 맞추고 mobile-*.png 시안과 비교합니다. 내비게이션이 햄버거 버튼으로 바뀌는지, 카드가 1열로 쌓이는지 확인합니다.

5. 접근성 점검

Tab 키만으로 헤더 → 히어로 버튼 → 카드 → 폼까지 이동해 봅니다. 포커스 링이 항상 보이는지, contact.html의 필수 입력에 오류 시 --color-accent 테두리가 보이는지 확인합니다.

6. 최종 확인

  • 데스크톱·모바일 모두 시안과 색·간격·타이포가 일치합니다.
  • styles.css가 리셋(1) → 공통 레이아웃(2) → 타이포(3) → 색(4) → 나머지 컴포넌트 순서로 정리되어 있습니다.
  • 헤더 내비게이션은 Flexbox, 프로젝트 카드는 Grid로 구현된 이유를 말할 수 있습니다.
  • DevTools 반응형 모드에서 768px, 1024px 두 지점에서 레이아웃이 바뀌는 것을 확인했습니다.
  • 17·18편에서 만든 transition·animation 값을 하나 이상 바꿔 다른 느낌으로 조정해 봤습니다.

직접 해보기

  1. styles.css에서 var()를 쓰지 않고 직접 색상 코드(#0969da 등)를 적은 곳이 있는지 검색(Ctrl+F)해 찾아보고, 있다면 토큰으로 바꿔 보세요.
  2. 브라우저 창 폭을 1023px에서 1025px로 1px씩 옮기며 내비게이션이 정확히 어느 지점에서 바뀌는지 DevTools 눈금자로 확인해 보세요.

정답 확인

  1. 매직 넘버가 남아 있다면 var(--color-primary)처럼 토큰으로 교체합니다. modern_css에서 다크 모드를 추가할 때 토큰만 바꿔도 전체 색상이 바뀌게 하려면 이 작업이 꼭 필요합니다.
  2. min-width: 1024px이므로 1024px에서부터 데스크톱 스타일이 적용되고, 1023px까지는 모바일/태블릿 스타일이 유지됩니다.

자주 하는 실수

증상원인고치는 법
시안과 색이 미묘하게 다르다토큰 대신 비슷한 색을 직접 입력했다tokens.css--color-* 값을 그대로 검색해 일치 여부 확인
모바일에서 카드 사이 간격이 시안보다 좁다.project-gridgapvar(--space-6)이 아니라 다른 값으로 남아 있다그리드·플렉스 gap 값을 전부 토큰으로 통일
포커스 링이 일부 요소에서 안 보인다outline: none만 적용하고 대체 표시를 빠뜨린 요소가 있다모든 상호작용 요소가 :focus 상태에서 box-shadow 또는 outline을 갖는지 전수 확인
DevTools에서 --color-* 변수가 계산되지 않는다tokens.cssstyles.css보다 나중에 연결됐다index.html 등에서 <link> 순서를 tokens.cssstyles.css로 확인

확인 문제

문제 14지선다
이 포트폴리오의 styles.css에서 리셋 다음으로 오는 섹션은?
문제 24지선다
데스크톱 내비게이션이 가로 메뉴로 바뀌고 카드가 3열이 되는 공통 기준 폭은?
문제 34지선다
완료 기준 중 매직 넘버 대신 변수를 쓰는 습관을 점검하는 방법으로 옳은 것은?
문제 44지선다
포커스 링 점검에서 확인해야 할 것으로 옳은 것은?

참고 자료

Last updated on