이모지 아이콘을 SVG로 바꾸다가, 더 큰 원인을 발견해 함께 고쳤다.
■ 폰트: 이름만 부르고 파일은 안 받아오고 있었다
global.css에 font-family: "Pretendard"가 적혀 있었지만 @font-face도 CDN
링크도 없었다. 그래서 이 글꼴이 따로 깔린 사람 외에는 전원 맑은 고딕으로
보고 있었다 — 앱과 학습 문서 19개 전부. 에러가 안 나서 조용히 넘어간 케이스.
- PretendardStdVariable.woff2(285KB)를 public/fonts에 self-host
· CDN 의존 없음 → 외부 서비스가 죽어도, 오프라인에서도 안 깨진다
· variable font → 파일 하나가 굵기 45~920을 전부 담는다
· font-display: swap → 받는 동안 흰 화면(FOIT) 방지
- 전체판(2MB) 대신 Std판(상용 한글 2,350자)을 골랐다. 앱이 쓰는 한글
1,276자가 Std에 전부 있는지 브라우저에서 실측 확인했다(폴백 0자).
- 학습 문서 19개에도 같은 @font-face 적용
■ 아이콘: 이모지 95종 → Lucide 132개
이모지는 OS마다 다른 그림으로 렌더링되고, 굵기·색을 통제할 수 없고,
스크린리더가 "bust in silhouette"처럼 읽는다.
- 카탈로그의 icon: '📜'(문자) → Icon: ScrollText(컴포넌트)로 전환
- strokeWidth={1.5}로 굵기 통일, aria-hidden으로 장식임을 명시
- 개별 import만 사용(트리셰이킹) → gzip +14KB
- slug: null인 '개발 환경 설치 가이드' 항목이 일괄 변환에서 누락돼
Icon이 undefined가 될 뻔한 것을 정합성 검사(항목 수 vs Icon 수)로 잡았다
■ 토큰: 여백·글자 크기·굵기를 :root로 승격
--space-1~7(4의 배수), --text-xs~2xl, --weight-*, --radius-sm/lg/full.
값을 직접 박으면 다크 모드에서 깨지고 화면마다 미묘하게 어긋난다.
CSS 프레임워크는 도입하지 않았다 — 손으로 쓴 global.css와 충돌하고,
교재로서 "왜 이렇게 썼는지"가 사라지기 때문이다.
■ /design: 재료를 고르는 화면
색·글자·여백·모서리를 실제 CSS 변수로 렌더링(다크 모드 자동 반영) +
Lucide 아이콘 1,993개 검색·복사. lucide-react는 아이콘 하나를 이름 3개로
내보내(Rocket/RocketIcon/LucideRocket) 5,981개처럼 보이므로 별칭을 걸렀다.
아이콘 전체를 훑어야 해 무거운 화면이라 lazy로 분리했다
→ 첫 화면 번들은 1.9KB만 증가(575.57 → 577.44KB).
검증: ESLint 통과, Vitest 30개 통과, 프로덕션 빌드 성공, 콘솔 에러 0.
브라우저에서 수습 7카테고리 + 초급~특급 4과정 = 11개 화면 전수 확인
(각 12카드/12아이콘/이모지 0).
문서: DESIGN.md(폰트·토큰·비용), ICONS.md(규약·대응표 132행)
220 lines
13 KiB
Markdown
220 lines
13 KiB
Markdown
# ICONS.md — 아이콘 규약과 대응표
|
|
|
|
> 이 앱의 아이콘은 **Lucide**(SVG) 하나로 통일합니다. 이모지는 UI에 쓰지 않습니다.
|
|
> 아이콘을 새로 넣거나 바꿀 때 이 문서를 먼저 읽으세요.
|
|
> 색·글자·여백 등 나머지 재료는 [DESIGN.md](./DESIGN.md)에, 실물은 앱의 **`/design`** 에 있습니다.
|
|
|
|
---
|
|
|
|
## 1. 왜 이모지를 걷어냈나
|
|
|
|
원래 강좌 카드마다 이모지(📜 💻 🌐 …)가 붙어 있었습니다. 편하지만 세 가지가 발목을 잡습니다.
|
|
|
|
**① 기기마다 다른 그림이 나온다.** 이모지는 글꼴입니다. 윈도우·맥·안드로이드가 각자 다른 그림을 그려서, 내 화면에서 예쁜 게 남의 화면에선 아닐 수 있습니다. **우리가 통제할 수 없는 디자인**이라는 게 핵심 문제입니다.
|
|
|
|
**② 굵기·크기·색을 못 맞춘다.** 어떤 이모지는 선이 두껍고 어떤 건 알록달록합니다. 12개를 한 화면에 모으면 통일감이 안 생깁니다. SVG 아이콘은 `size`·`strokeWidth`·`color`를 코드로 지시할 수 있어 132개가 한 세트처럼 보입니다.
|
|
|
|
**③ 스크린리더가 엉뚱하게 읽는다.** 👤 하나만 있는 링크를 리더는 "bust in silhouette 링크"라고 읽습니다. (이 문제는 `App.jsx` 헤더에서 실제로 겪었습니다.)
|
|
|
|
> 학습 포인트: "그냥 취향 문제 아닌가?"로 보이는 UI 결정에도 **통제 가능성·일관성·접근성**이라는 기술적 근거가 있습니다. 디자인 논쟁은 근거를 대는 쪽이 이깁니다.
|
|
|
|
---
|
|
|
|
## 2. 쓰는 법
|
|
|
|
카탈로그는 아이콘을 **문자가 아니라 컴포넌트**로 들고 있습니다.
|
|
|
|
```jsx
|
|
// courseCatalog.jsx — 데이터
|
|
import { ScrollText } from 'lucide-react';
|
|
|
|
{ slug: 'history', Icon: ScrollText, title: '컴퓨터의 역사', /* … */ }
|
|
```
|
|
|
|
```jsx
|
|
// LearnHubPage.jsx — 화면
|
|
<course.Icon size={26} strokeWidth={1.5} aria-hidden="true" />
|
|
```
|
|
|
|
지켜야 할 규칙 네 가지:
|
|
|
|
| 규칙 | 이유 |
|
|
|---|---|
|
|
| 필드 이름은 `Icon` (대문자 I) | JSX는 **대문자로 시작하는 이름만** 컴포넌트로 취급합니다. 소문자는 `<div>` 같은 HTML 태그로 봅니다. |
|
|
| `strokeWidth={1.5}` 고정 | 굵기가 섞이면 통일감이 깨집니다. 이 값이 우리 앱의 기본 굵기입니다. |
|
|
| `aria-hidden="true"` 필수 | 아이콘은 장식입니다. 의미는 옆의 글자가 전달합니다. |
|
|
| 글자 없이 아이콘만 쓸 땐 `aria-label` | 예: 헤더의 내 정보·비밀번호 버튼. 안 그러면 스크린리더가 읽을 게 없습니다. |
|
|
|
|
**개별 import만 씁니다.** `import * as Icons from 'lucide-react'`처럼 통째로 가져오면 안 쓰는 아이콘까지 전부 번들에 실립니다. 지금처럼 이름을 하나씩 적으면 번들러가 쓰는 것만 남깁니다(트리셰이킹).
|
|
|
|
새 아이콘은 <https://lucide.dev/icons> 에서 고르고, 파일 상단 import 목록에 **알파벳 순서**로 끼워 넣으세요.
|
|
|
|
---
|
|
|
|
## 3. 비용
|
|
|
|
아이콘 132종을 넣고 번들이 얼마나 커졌는지 실제로 재 봤습니다.
|
|
|
|
| | 변경 전 | 변경 후 | 차이 |
|
|
|---|---|---|---|
|
|
| index 청크 | 518.36 kB | 575.57 kB | +57.2 kB |
|
|
| gzip 전송 크기 | 171.96 kB | 186.01 kB | **+14.0 kB** |
|
|
|
|
> 학습 포인트: "라이브러리 넣으면 무거워진다"는 말은 **숫자로 확인하기 전까진 추측**입니다. 실제로 재 보면 아이콘 132개에 gzip 14KB — 사진 한 장보다 작습니다. 빌드 결과를 읽는 습관이 이런 판단을 가능하게 합니다.
|
|
|
|
Lucide를 고른 이유: 아이콘 약 1,600종으로 우리가 필요한 개념(CPU·네트워크·DB·보안·디자인·앱)을 전부 덮고, 굵기가 한 종류라 섞어 써도 통일감이 유지됩니다.
|
|
|
|
⚠️ 브랜드 로고(Figma 등)는 Lucide v1에서 제거됐습니다. 그래서 「피그마와 디자인 툴」은 로고 대신 도구의 성격을 나타내는 `PenTool`을 씁니다.
|
|
|
|
---
|
|
|
|
## 4. 전체 대응표 (132개)
|
|
|
|
<!-- 이 표는 카탈로그에서 자동 생성했습니다. 강좌를 추가하면 이 표에도 한 줄 추가하세요. -->
|
|
|
|
### 수습 트랙(83강좌 + 설치 가이드) — `frontend/src/courseCatalog.jsx`
|
|
|
|
| 강좌 | 이전(이모지) | 이후(Lucide) | 아이콘 |
|
|
|---|:---:|---|---|
|
|
| 컴퓨터의 역사 | 📜 | `ScrollText` | scroll-text |
|
|
| 컴퓨터의 구성 | 💻 | `Computer` | computer |
|
|
| 2진수와 데이터 표현 | 🔢 | `Binary` | binary |
|
|
| CPU 깊이 보기 | 🧠 | `Cpu` | cpu |
|
|
| 메모리와 저장장치 깊이 보기 | 💾 | `HardDrive` | hard-drive |
|
|
| 그래픽카드와 GPU의 세계 | 🎮 | `MemoryStick` | memory-stick |
|
|
| 메인보드와 파워 깊이 보기 | 🔋 | `CircuitBoard` | circuit-board |
|
|
| 데스크탑 조립하기 | 🔧 | `Wrench` | wrench |
|
|
| 운영체제의 이해 | 🪟 | `AppWindow` | app-window |
|
|
| 파일과 폴더의 원리 | 📁 | `Folder` | folder |
|
|
| 노트북과 주변기기 고르기 | 🖥️ | `Laptop` | laptop |
|
|
| 컴퓨터 문제 해결 | 🩺 | `Stethoscope` | stethoscope |
|
|
| 네트워크의 이해 | 🌐 | `Globe` | globe |
|
|
| 네트워크 속도와 대역폭 | 🚗 | `Gauge` | gauge |
|
|
| IP 주소 깊이 보기 | 🔍 | `MapPin` | map-pin |
|
|
| 공유기와 홈 네트워크 실전 | 📡 | `Router` | router |
|
|
| DNS 깊이 보기 | 📖 | `BookMarked` | book-marked |
|
|
| TCP와 UDP | 🤝 | `Handshake` | handshake |
|
|
| HTTP와 HTTPS | 📨 | `ArrowRightLeft` | arrow-right-left |
|
|
| 네트워크 명령어 실전 | 🧰 | `SquareTerminal` | square-terminal |
|
|
| 무선 네트워크와 보안 | 🔐 | `Wifi` | wifi |
|
|
| VPN과 프록시 | 🚇 | `Route` | route |
|
|
| 방화벽과 보안 기초 | 🛡️ | `BrickWall` | brick-wall |
|
|
| 클라우드 네트워크 입문 | ☁️ | `Cloud` | cloud |
|
|
| 서버란 무엇인가 | 🏗️ | `Server` | server |
|
|
| 리눅스 / 터미널 기초 | 🐧 | `Terminal` | terminal |
|
|
| 리눅스 심화 | ⚙️ | `Settings2` | settings-2 |
|
|
| 도커 입문 | 🐳 | `Container` | container |
|
|
| 클라우드와 AWS 입문 | 🌩️ | `CloudLightning` | cloud-lightning |
|
|
| SQL 입문 | 🗄️ | `Database` | database |
|
|
| SQL 중급 | 📊 | `ChartColumn` | chart-column |
|
|
| 데이터 모델링 기초 | 🗂️ | `Table` | table |
|
|
| 캐시와 NoSQL 맛보기 | ⚡ | `Zap` | zap |
|
|
| API의 이해 | 🔌 | `Plug` | plug |
|
|
| CI/CD와 배포 자동화 입문 | 🔁 | `RotateCw` | rotate-cw |
|
|
| 운영과 장애 대응 기초 | 🚨 | `Siren` | siren |
|
|
| 코딩 기초 | ⌨️ | `Keyboard` | keyboard |
|
|
| HTML / CSS 기초 | 🧱 | `FileCode` | file-code |
|
|
| 자바스크립트 기초 | ⚡ | `Braces` | braces |
|
|
| 자료구조 맛보기 | 🧺 | `Boxes` | boxes |
|
|
| React 입문 | ⚛️ | `Atom` | atom |
|
|
| Java 기초 | ☕ | `Coffee` | coffee |
|
|
| Spring Boot 입문 | 🍃 | `Leaf` | leaf |
|
|
| Git 깊이 보기 | 🌳 | `GitBranch` | git-branch |
|
|
| 디버깅과 에러 읽는 법 | 🐞 | `Bug` | bug |
|
|
| 테스트 코드 입문 | 🧪 | `FlaskConical` | flask-conical |
|
|
| 클린 코드와 코드리뷰 | ✨ | `Sparkles` | sparkles |
|
|
| AI 도구와 함께 코딩하기 | 🤖 | `Bot` | bot |
|
|
| UI/UX 기초 | 🎨 | `Palette` | palette |
|
|
| 색 이론 기초 | 🌈 | `SwatchBook` | swatch-book |
|
|
| 타이포그래피 | 🔤 | `Type` | type |
|
|
| 레이아웃과 그리드 | 📐 | `LayoutGrid` | layout-grid |
|
|
| 아이콘과 이미지 | 🖼️ | `Image` | image |
|
|
| 와이어프레임 기초 | ✏️ | `PencilRuler` | pencil-ruler |
|
|
| 피그마와 디자인 툴 | 🖌️ | `PenTool` | pen-tool |
|
|
| 디자인 시스템 입문 | 🧩 | `Component` | component |
|
|
| 모바일 디자인 | 📱 | `Smartphone` | smartphone |
|
|
| 사용자 리서치 기초 | 🔬 | `Microscope` | microscope |
|
|
| 레퍼런스와 무드보드 | 🗃️ | `Images` | images |
|
|
| 포트폴리오 만들기 | 💼 | `Briefcase` | briefcase |
|
|
| 개발 환경 설치 가이드 | 🛠️ | `Wrench` | wrench |
|
|
| 윈도우 단축키 마스터 | ⌨️ | `Command` | command |
|
|
| 윈도우 터미널과 PowerShell | 🖤 | `SquareChevronRight` | square-chevron-right |
|
|
| VS Code 200% 활용 | 📝 | `Code` | code |
|
|
| IntelliJ 꿀팁 모음 | 💡 | `Lightbulb` | lightbulb |
|
|
| 크롬 개발자도구 | 🔎 | `Inspect` | inspect |
|
|
| Gitea 200% 활용 | 🍵 | `GitFork` | git-fork |
|
|
| Docker Desktop 사용법 | 🐳 | `Ship` | ship |
|
|
| DBeaver로 DB 들여다보기 | 🦫 | `DatabaseZap` | database-zap |
|
|
| Postman으로 API 테스트 | 🚀 | `Send` | send |
|
|
| 캡처와 화면 녹화 | 📸 | `Camera` | camera |
|
|
| 노션으로 기록하기 | 🗒️ | `NotebookPen` | notebook-pen |
|
|
| 앱의 지형 | 📱 | `LayoutTemplate` | layout-template |
|
|
| 안드로이드 앱의 구조 | 🤖 | `TabletSmartphone` | tablet-smartphone |
|
|
| Kotlin 입문 | 🟣 | `Blocks` | blocks |
|
|
| iOS 앱의 구조 | 🍎 | `SquareDashedBottomCode` | square-dashed-bottom-code |
|
|
| Swift 입문 | 🐦 | `Bird` | bird |
|
|
| React Native | ⚛️ | `Orbit` | orbit |
|
|
| Flutter | 🦋 | `Feather` | feather |
|
|
| 앱 화면과 이동 | 🧭 | `Compass` | compass |
|
|
| 앱의 상태와 데이터 | 🗃️ | `Layers` | layers |
|
|
| PWA — 웹앱을 앱처럼 | 🌐 | `MonitorSmartphone` | monitor-smartphone |
|
|
| 앱 보안과 권한 | 🔐 | `ShieldCheck` | shield-check |
|
|
| 스토어 배포 | 🚀 | `Rocket` | rocket |
|
|
|
|
### 수준별 과정(초급~특급 48강좌) — `frontend/src/levelCatalog.jsx`
|
|
|
|
| 강좌 | 이전(이모지) | 이후(Lucide) | 아이콘 |
|
|
|---|:---:|---|---|
|
|
| 프로그램이란 무엇일까? | 💡 | `Lightbulb` | lightbulb |
|
|
| 컴퓨터처럼 생각하기: 컴퓨팅 사고 | 🧩 | `Puzzle` | puzzle |
|
|
| 나의 첫 웹페이지: HTML 첫걸음 | 🌐 | `Globe` | globe |
|
|
| 태그로 페이지에 살 붙이기 | 🏗️ | `Blocks` | blocks |
|
|
| 브라우저는 내 코드를 어떻게 읽을까 | 🔍 | `Search` | search |
|
|
| 나의 첫 JavaScript | ✨ | `Sparkles` | sparkles |
|
|
| 변수와 자료형: 이름 붙인 상자 | 📦 | `Package` | package |
|
|
| 조건문: 컴퓨터가 판단하게 하기 | 🚦 | `GitBranch` | git-branch |
|
|
| 반복문: 지루한 일은 컴퓨터에게 | 🔁 | `Repeat` | repeat |
|
|
| 함수: 나만의 명령어 만들기 | 🧰 | `SquareFunction` | square-function |
|
|
| 에러와 친해지기: 빨간 글씨 해독법 | 🩹 | `Bandage` | bandage |
|
|
| 첫 프로젝트: 숫자 맞히기 게임 만들기 | 🚀 | `Rocket` | rocket |
|
|
| 비동기 JavaScript: fetch와 Promise 정복 | ⏳ | `Hourglass` | hourglass |
|
|
| 배열 메서드로 데이터 요리하기 | 🔪 | `ChefHat` | chef-hat |
|
|
| REST API 설계: 주소와 동사의 문법 | 📐 | `Ruler` | ruler |
|
|
| Express로 CRUD API 만들기 | 🛠️ | `Hammer` | hammer |
|
|
| DB 연동과 트랜잭션: 데이터가 사는 집 | 🗄️ | `Database` | database |
|
|
| 로그인 구현: 세션과 비밀번호 해시 | 🔐 | `KeyRound` | key-round |
|
|
| React 상태와 이펙트 실전 | ⚛️ | `Atom` | atom |
|
|
| React Router로 멀티 페이지 SPA | 🧭 | `Compass` | compass |
|
|
| 폼 검증: 사용자를 돕고 서버를 지키기 | ✅ | `CircleCheck` | circle-check |
|
|
| 에러 처리 패턴: 무너지지 않는 서비스 | 🚨 | `TriangleAlert` | triangle-alert |
|
|
| 테스트 작성 실전: 자신 있게 고치는 힘 | 🧪 | `FlaskConical` | flask-conical |
|
|
| 미니 프로젝트: 설계부터 배포까지 | 🚀 | `Rocket` | rocket |
|
|
| 레이어드 아키텍처와 관심사 분리 | 🏛️ | `Layers` | layers |
|
|
| 디자인 패턴 실전 | 🧩 | `Puzzle` | puzzle |
|
|
| DB 인덱스와 실행계획 읽기 | 🔍 | `ListTree` | list-tree |
|
|
| 트랜잭션과 격리 수준 | 🔒 | `Lock` | lock |
|
|
| N+1과 쿼리 최적화 | 🐢 | `Turtle` | turtle |
|
|
| 캐싱 전략 — 로컬과 Redis | ⚡ | `Zap` | zap |
|
|
| 성능 측정과 프로파일링 | 📈 | `TrendingUp` | trending-up |
|
|
| 웹 보안 심화 — OWASP Top 10 | 🛡️ | `Shield` | shield |
|
|
| 인증 아키텍처 — 세션 vs JWT vs OAuth | 🔑 | `Fingerprint` | fingerprint |
|
|
| 로깅·모니터링·알림 설계 | 🔭 | `Telescope` | telescope |
|
|
| 무중단 배포와 롤백 | 🚦 | `Milestone` | milestone |
|
|
| 코드 리뷰와 리팩터링 전략 | 🧭 | `GitPullRequest` | git-pull-request |
|
|
| 분산 시스템 첫걸음 — 왜 서버 한 대로는 안 될까 | 🌐 | `Network` | network |
|
|
| 메시지 큐와 이벤트 드리븐 — 시스템을 느슨하게 잇는 법 | 📨 | `Inbox` | inbox |
|
|
| 모놀리스 vs 마이크로서비스 — 쪼개는 것의 진짜 비용 | 🧩 | `Boxes` | boxes |
|
|
| DB 레플리케이션과 샤딩 — 데이터베이스를 늘리는 두 가지 축 | 🗄️ | `DatabaseBackup` | database-backup |
|
|
| 대용량 트래픽 설계 — 밀려오는 요청 앞에서 무너지지 않기 | 🚦 | `Gauge` | gauge |
|
|
| 쿠버네티스와 클라우드 네이티브 — 선언하면 알아서 굴러가는 인프라 | ☸️ | `Hexagon` | hexagon |
|
|
| 관측성 — 보이지 않으면 고칠 수 없다 | 🔭 | `Activity` | activity |
|
|
| SRE와 장애 대응 — 무너져도 빨리 일어나는 팀 | 🚨 | `Siren` | siren |
|
|
| 시스템 설계 훈련 1 — URL 단축기를 처음부터 끝까지 | 🔗 | `Link` | link |
|
|
| 시스템 설계 훈련 2 — 실시간 채팅 시스템 | 💬 | `MessageSquare` | message-square |
|
|
| 시스템 설계 훈련 3 — 뉴스피드와 타임라인 | 📰 | `Newspaper` | newspaper |
|
|
| 기술 리더십과 ADR — 결정을 설계하고 기록하는 사람 | 🧭 | `Signpost` | signpost |
|
|
|
|
---
|
|
|
|
AWESOMEDEV · 아이콘 규약 v1 (Lucide 기준)
|