feat: 학습 센터 10강좌 완성 — 리눅스·SQL·HTML/CSS·자바스크립트·UI/UX·피그마 추가
- 서버와 데이터: 리눅스/터미널 기초(SSH까지), SQL 입문(실제 우리 테이블·psql 실습) - 소프트웨어: HTML/CSS 기초(global.css 해부), 자바스크립트 기초(콘솔·fetch 실습) - 디자인(신설): UI/UX 기초(우리 화면 비평), 피그마와 디자인 툴(모작·핸드오프) - 코스 간 상호 연결: UI/UX 비평 → 피그마 시안 → 디자인 티켓(DZ-02) 흐름 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
8f1143aee8
commit
3adebdb44a
@ -11,6 +11,12 @@ import CodingBasicsPage from './pages/CodingBasicsPage';
|
||||
import HardwarePage from './pages/HardwarePage';
|
||||
import AssemblyPage from './pages/AssemblyPage';
|
||||
import NetworkPage from './pages/NetworkPage';
|
||||
import LinuxPage from './pages/LinuxPage';
|
||||
import SqlPage from './pages/SqlPage';
|
||||
import HtmlCssPage from './pages/HtmlCssPage';
|
||||
import JavascriptPage from './pages/JavascriptPage';
|
||||
import UiUxPage from './pages/UiUxPage';
|
||||
import FigmaPage from './pages/FigmaPage';
|
||||
import DocsPage from './pages/DocsPage';
|
||||
import DocViewerPage from './pages/DocViewerPage';
|
||||
import AssignmentsPage from './pages/AssignmentsPage';
|
||||
@ -111,6 +117,12 @@ export default function App() {
|
||||
<Route path="/learn/hardware" element={<HardwarePage />} />
|
||||
<Route path="/learn/assembly" element={<AssemblyPage />} />
|
||||
<Route path="/learn/network" element={<NetworkPage />} />
|
||||
<Route path="/learn/linux" element={<LinuxPage />} />
|
||||
<Route path="/learn/sql" element={<SqlPage />} />
|
||||
<Route path="/learn/htmlcss" element={<HtmlCssPage />} />
|
||||
<Route path="/learn/javascript" element={<JavascriptPage />} />
|
||||
<Route path="/learn/uiux" element={<UiUxPage />} />
|
||||
<Route path="/learn/figma" element={<FigmaPage />} />
|
||||
<Route path="/setup" element={<SetupGuidePage />} />
|
||||
<Route path="/docs" element={<DocsPage />} />
|
||||
<Route path="/docs/:slug" element={<DocViewerPage />} />
|
||||
|
||||
407
frontend/src/pages/FigmaPage.jsx
Normal file
407
frontend/src/pages/FigmaPage.jsx
Normal file
@ -0,0 +1,407 @@
|
||||
// 이 파일이 하는 일: "피그마와 디자인 툴" 코스 — 피그마가 뭔지부터 시작해
|
||||
// 화면 그리기 → 오토레이아웃 → 컴포넌트 → 프로토타입 → 개발자 핸드오프까지
|
||||
// 8개 섹션으로 안내하는 정적 학습 페이지. 디자인 수습생이 주인공이지만,
|
||||
// 섹션 7(핸드오프)은 개발 수습생도 꼭 읽어야 하는 공용 구간이에요.
|
||||
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 global.css의
|
||||
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용한다 — 색을 여기서 하드코딩하지 않는다.
|
||||
// 표·텍스트 다이어그램은 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// ── 텍스트 표 · 다이어그램 상수들 ──
|
||||
|
||||
const CODE_TOOL_ROLES = `디자인 툴들의 역할 분담 — 다 같은 '그림 툴'이 아니에요
|
||||
|
||||
툴 한 줄 역할
|
||||
──────────────────────────────────────────────────
|
||||
피그마 화면(UI) 설계 + 팀 협업 — 우리의 메인 작업대
|
||||
포토샵 사진 보정·합성 — 픽셀을 만지는 붓
|
||||
일러스트 로고·아이콘 같은 벡터 그래픽 제작 — 정밀한 펜
|
||||
스케치 피그마 이전 세대의 UI 툴 — Mac 전용이라 협업이 불편했음
|
||||
|
||||
→ 앱·웹 화면을 그린다 = 요즘은 거의 무조건 피그마입니다.`;
|
||||
|
||||
const CODE_SHORTCUTS = `꼭 외울 단축키 8개 — 이것만 알아도 손이 3배 빨라져요
|
||||
|
||||
키 하는 일
|
||||
──────────────────────────────────────────────
|
||||
V 이동(선택) 도구 — 기본 손
|
||||
F 프레임(Frame) — '화면' 만들기
|
||||
R 사각형(Rectangle)
|
||||
T 텍스트(Text)
|
||||
O 원(Oval)
|
||||
Ctrl + D 복제(Duplicate) — 방금 만든 걸 하나 더
|
||||
Ctrl + G 그룹 묶기
|
||||
Shift + A 오토레이아웃 씌우기 (섹션 4의 주인공!)
|
||||
|
||||
보너스: Space 누른 채 드래그 = 캔버스 이동,
|
||||
Ctrl + 스크롤 = 확대/축소. 지도 앱 조작과 똑같아요.`;
|
||||
|
||||
const CODE_LOGIN_STEPS = `로그인 화면 모작 — 따라 하기 순서
|
||||
|
||||
1) F → 우측 패널에서 "Desktop" 프리셋 클릭 → 1440×1024 프레임 생성
|
||||
2) R → 프레임 가운데에 세로로 긴 사각형 (로그인 카드)
|
||||
→ 우측 패널에서 모서리 둥글기(Corner radius) 12 정도
|
||||
3) T → "로그인" 제목, "이메일", "비밀번호" 라벨 입력
|
||||
4) R → 납작한 사각형 2개 (입력창 2개) — Ctrl+D로 복제하면 빠름
|
||||
5) R → 파란색 사각형 1개 + T로 "로그인" 글자 → 버튼 완성
|
||||
6) 우리 플랫폼 로그인 화면을 옆에 띄워 두고
|
||||
간격·크기·색을 눈으로 비교하며 다듬기
|
||||
|
||||
완성 기준: 멀리서 보면 진짜 우리 로그인 화면과 헷갈릴 정도!`;
|
||||
|
||||
const CODE_AUTOLAYOUT = `오토레이아웃 = 피그마의 flexbox
|
||||
|
||||
오토레이아웃 없는 버튼 오토레이아웃 있는 버튼
|
||||
───────────────────── ─────────────────────
|
||||
글자를 바꾸면... 글자를 바꾸면...
|
||||
[ 로그인 ] ← 상자는 그대로 [ 비밀번호 재설정 ] ← 상자가
|
||||
글자만 삐져나옴! 알아서 늘어남!
|
||||
|
||||
우측 패널에 나타나는 값들 (CSS와 짝지어 보세요):
|
||||
방향(가로/세로) ↔ flex-direction: row / column
|
||||
간격(Spacing) ↔ gap
|
||||
패딩(Padding) ↔ padding
|
||||
정렬(Alignment) ↔ justify-content / align-items
|
||||
|
||||
HTML/CSS 코스에서 배운 flex 그대로예요.
|
||||
디자이너와 개발자가 같은 개념을 다른 화면에서 만지는 것뿐!`;
|
||||
|
||||
const CODE_COLOR_STYLES = `색 스타일로 등록할 우리 브랜드 팔레트 (예시)
|
||||
|
||||
이름 값 쓰임새
|
||||
──────────────────────────────────────────
|
||||
Brand/Primary #2159C5 메인 버튼, 링크, 강조
|
||||
Brand/Dark #17408F 버튼 hover, 진한 강조
|
||||
Gray/Text #222222 본문 글자
|
||||
Gray/Sub #777777 보조 설명 글자
|
||||
Gray/Line #E5E5E5 구분선, 테두리
|
||||
BG/Base #FFFFFF 기본 배경
|
||||
|
||||
→ 우리 global.css가 색을 CSS 변수(var(--...))로 한곳에 모아두는 것과
|
||||
완전히 같은 발상이에요. "색을 바꿀 땐 한 군데만 고친다."`;
|
||||
|
||||
const CODE_HANDOFF_SPEC = `핸드오프 스펙 문서에 꼭 적을 것 — 그림만으론 부족해요
|
||||
|
||||
1) 상태(State)
|
||||
버튼: 기본 / hover / 눌림 / 비활성(disabled) — 4가지 다 그렸나?
|
||||
입력창: 기본 / 포커스 / 에러(빨간 테두리+메시지)
|
||||
|
||||
2) 인터랙션
|
||||
"이 버튼 누르면 어디로 가나요?" "로딩 중엔 뭐가 보이나요?"
|
||||
|
||||
3) 엣지케이스
|
||||
글자가 아주 길면? (말줄임? 줄바꿈?)
|
||||
목록이 0개면? (빈 화면에 뭘 보여주나?)
|
||||
이미지가 안 불러와지면?
|
||||
|
||||
→ 개발자가 물어보기 전에 디자이너가 먼저 적어 두는 것,
|
||||
그게 '일 잘하는 핸드오프'입니다.`;
|
||||
|
||||
const CODE_FINAL_MISSION = `졸업 과제 3단계 — 우리 플랫폼을 재료로
|
||||
|
||||
STEP 1. 모작 (관찰력 훈련)
|
||||
우리 플랫폼 화면 중 하나를 골라 피그마로 그대로 옮기기
|
||||
(추천: 로그인, 코스 목록, 이 페이지 같은 학습 페이지)
|
||||
체크: 프레임 사용? 오토레이아웃 사용? 색 스타일 등록?
|
||||
|
||||
STEP 2. 개선안 (제안력 훈련)
|
||||
UI/UX 기초 코스에서 적어 둔 "우리 플랫폼 아쉬운 점" 목록을 꺼내서
|
||||
모작한 화면 옆에 개선 버전을 나란히 만들기
|
||||
체크: 뭘 왜 바꿨는지 한 줄 설명을 화면 옆에 메모로 남겼나?
|
||||
|
||||
STEP 3. 공유 (협업 훈련)
|
||||
우측 상단 Share → "링크가 있는 모든 사용자 - 보기 가능" 설정
|
||||
→ 링크를 멘토에게 보내고 코멘트(C키) 피드백 받기
|
||||
|
||||
이 3단계가 실제 디자이너의 하루 축소판이에요:
|
||||
관찰하고 → 제안하고 → 공유해서 피드백 받기.`;
|
||||
|
||||
// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
|
||||
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. props만 바꿔 재사용.
|
||||
// (섹션 5에서 배울 피그마 '컴포넌트'와 정확히 같은 발상이에요!)
|
||||
function Section({ n, title, sub, children }) {
|
||||
return (
|
||||
<section className="step-card" id={`sec-${n}`}>
|
||||
<div className="step-head">
|
||||
<span className="step-num">{n}</span>
|
||||
<div>
|
||||
<h3>{title}</h3>
|
||||
{sub && <span className="step-sub">{sub}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="step-body">{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Code({ children }) {
|
||||
return <pre className="code-block"><code>{children}</code></pre>;
|
||||
}
|
||||
|
||||
const SECTIONS = [
|
||||
{ n: 1, label: '왜 피그마인가' },
|
||||
{ n: 2, label: '화면 구성 익히기' },
|
||||
{ n: 3, label: '프레임과 도형' },
|
||||
{ n: 4, label: '오토레이아웃' },
|
||||
{ n: 5, label: '컴포넌트와 스타일' },
|
||||
{ n: 6, label: '프로토타입' },
|
||||
{ n: 7, label: '개발자에게 넘기기' },
|
||||
{ n: 8, label: '직접 해보기' },
|
||||
];
|
||||
|
||||
export default function FigmaPage() {
|
||||
return (
|
||||
<div>
|
||||
{/* 히어로: 이 코스가 누구를 위한 것이고 어디까지 가는지 */}
|
||||
<div className="hero">
|
||||
<div className="eyebrow">Course · 디자인</div>
|
||||
<h1>피그마와 디자인 툴<br />— 디자이너의 작업대</h1>
|
||||
<p>
|
||||
개발자의 작업대가 에디터(VS Code)라면, 디자이너의 작업대는 <strong>피그마</strong>예요.
|
||||
빈 캔버스에 첫 사각형을 그리는 것부터, 완성한 시안을 개발자에게 넘기는 순간까지
|
||||
한 바퀴를 돕니다. 각 섹션의 <strong>"직접 확인해 보기"</strong>는 꼭 피그마를 열고
|
||||
손으로 따라 하세요 — 디자인 툴은 눈으로 배우는 게 아니라 손으로 배웁니다.
|
||||
</p>
|
||||
<div className="chip-row">
|
||||
<span className="chip">예상 소요 90분</span>
|
||||
<span className="chip">무료 계정으로 충분</span>
|
||||
<span className="chip">준비물: 브라우저 하나</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 섹션 바로가기 */}
|
||||
<nav className="pill-nav">
|
||||
{SECTIONS.map((s) => (
|
||||
<a key={s.n} href={`#sec-${s.n}`}>
|
||||
<b>{s.n}</b>
|
||||
{s.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<Section n={1} title="왜 피그마인가" sub="설치도 없이, 같은 파일을 동시에, 무료로">
|
||||
<p>
|
||||
피그마가 표준이 된 이유는 세 가지예요. 첫째, <strong>브라우저에서 돌아갑니다</strong> —
|
||||
설치 없이 링크만 열면 어떤 컴퓨터에서든 같은 파일이 열려요. 둘째,
|
||||
<strong> 실시간 협업</strong> — 구글 문서처럼 여러 명이 한 화면에 동시에 들어와
|
||||
서로의 커서가 움직이는 걸 보며 작업합니다. AWESOMEDEV에서도 디자이너가 그리는 화면에
|
||||
개발자가 들어와 "이 간격 몇이에요?" 하고 바로 확인해요. 셋째, <strong>무료</strong> —
|
||||
학생·개인은 핵심 기능을 전부 공짜로 씁니다.
|
||||
</p>
|
||||
<p>
|
||||
"포토샵으로 하면 안 돼요?"라는 질문이 많은데, 툴마다 역할이 달라요.
|
||||
요리로 치면 붓·펜·작업대를 용도에 맞게 나눠 쓰는 거예요.
|
||||
</p>
|
||||
<Code>{CODE_TOOL_ROLES}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> <span className="icode">figma.com</span>에 접속해서
|
||||
학교 이메일로 가입하세요(학생은 무료). 로그인 후
|
||||
<span className="kbd">New design file</span> 버튼을 누르면 빈 캔버스가 열려요 —
|
||||
이 빈 화면이 앞으로 90분간 여러분의 작업대입니다. 탭을 닫지 말고 다음 섹션으로!
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={2} title="화면 구성 익히기" sub="왼쪽 레이어, 가운데 캔버스, 오른쪽 속성 — 3분할만 알면 끝">
|
||||
<p>
|
||||
피그마 화면은 크게 셋으로 나뉘어요. <strong>왼쪽은 레이어 패널</strong> —
|
||||
지금까지 만든 모든 것의 목차예요. 책의 차례처럼, 캔버스에서 안 보이는 것도
|
||||
여기선 다 찾을 수 있어요. <strong>가운데는 캔버스</strong> — 무한히 넓은 책상이에요.
|
||||
확대·축소·이동이 자유롭죠. <strong>오른쪽은 속성 패널</strong> — 지금 선택한 것의
|
||||
크기·색·글꼴 같은 세부 정보를 보고 고치는 곳입니다.
|
||||
</p>
|
||||
<p>
|
||||
"왼쪽에서 <strong>찾고</strong>, 가운데서 <strong>그리고</strong>, 오른쪽에서
|
||||
<strong> 다듬는다</strong>" — 이 한 문장이 피그마 전부예요. 그리고 도구는
|
||||
마우스로 아이콘을 누르는 대신 단축키로 바꾸는 습관을 처음부터 들이세요.
|
||||
</p>
|
||||
<Code>{CODE_SHORTCUTS}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 새 파일에서 <span className="kbd">R</span>을 누르고 드래그해
|
||||
사각형 하나를 그린 뒤 — 왼쪽 레이어 패널에 <span className="icode">Rectangle 1</span>이
|
||||
생기는지, 오른쪽 속성 패널에 W(너비)·H(높이) 숫자가 뜨는지 확인하세요.
|
||||
그 다음 <span className="kbd">Ctrl</span>+<span className="kbd">D</span>를 세 번 —
|
||||
사각형 4개가 됐다면 3분할 구조를 다 쓴 겁니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={3} title="프레임과 도형" sub="프레임 = 화면 한 장, 그리고 첫 모작">
|
||||
<p>
|
||||
<strong>프레임(Frame)</strong>은 '화면 한 장'이에요. 다른 툴에서는 아트보드라고
|
||||
부르죠. 스케치북 낱장이라고 생각하면 됩니다 — 캔버스(책상) 위에 스케치북 종이(프레임)를
|
||||
여러 장 깔아 놓고, 종이마다 화면 하나씩 그리는 거예요.
|
||||
<span className="kbd">F</span>를 누르면 오른쪽 패널에
|
||||
<strong> iPhone·Android·Desktop</strong> 같은 실제 기기 크기 프리셋이 떠요 —
|
||||
직접 치수를 잴 필요 없이 클릭 한 번이면 됩니다.
|
||||
</p>
|
||||
<p>
|
||||
이제 첫 실전. 새로운 화면을 상상해서 그리는 건 어렵지만, <strong>있는 화면을 보고
|
||||
똑같이 옮기는 것(모작)</strong>은 오늘 시작한 사람도 할 수 있어요. 그림 연습을
|
||||
명화 따라 그리기로 시작하듯, UI 연습은 실제 서비스 모작으로 시작합니다.
|
||||
재료는 가장 가까운 것 — <strong>우리 학습 플랫폼의 로그인 화면</strong>이요.
|
||||
</p>
|
||||
<Code>{CODE_LOGIN_STEPS}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 위 순서대로 로그인 화면 모작을 완성해 보세요.
|
||||
비교하기 쉽게 브라우저 창을 반으로 나눠 왼쪽엔 우리 플랫폼 로그아웃 상태 화면,
|
||||
오른쪽엔 피그마를 띄우면 좋아요. 색은 다음 섹션 전까진 눈대중이어도 괜찮아요 —
|
||||
정확한 색 추출은 섹션 5에서 배웁니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={4} title="오토레이아웃" sub="피그마의 flexbox — 상자가 내용에 맞춰 스스로 늘어난다">
|
||||
<p>
|
||||
섹션 3에서 만든 버튼엔 약점이 있어요. 글자를 "로그인"에서 "비밀번호 재설정"으로
|
||||
바꾸면 <strong>글자가 상자를 삐져나갑니다</strong>. 상자 크기를 손으로 다시 맞춰야 하죠.
|
||||
<strong> 오토레이아웃(Auto layout)</strong>은 이 문제를 없애 줍니다 —
|
||||
내용물에 맞춰 상자가 스스로 늘어나고 줄어들어요.
|
||||
</p>
|
||||
<Code>{CODE_AUTOLAYOUT}</Code>
|
||||
<p>
|
||||
눈치챘나요? 이건 HTML/CSS 코스에서 배운 <strong>flexbox와 같은 개념</strong>이에요.
|
||||
개발자가 <span className="icode">display: flex; gap: 8px; padding: 12px 24px;</span>라고
|
||||
코드로 쓰는 것을, 디자이너는 오토레이아웃 패널에서 숫자로 입력하는 것뿐.
|
||||
그래서 오토레이아웃으로 만든 시안은 개발자가 CSS로 옮기기도 훨씬 쉬워요 —
|
||||
두 직군이 같은 언어로 대화하게 되는 거죠.
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 섹션 3에서 만든 로그인 버튼(사각형+글자)을 선택하고
|
||||
<span className="kbd">Shift</span>+<span className="kbd">A</span>를 누르세요.
|
||||
그 다음 버튼 글자를 더블클릭해 "비밀번호를 잊으셨나요? 이메일로 재설정 링크 받기"처럼
|
||||
아주 길게 바꿔 보세요 — 상자가 글자를 따라 스스로 늘어나면 성공!
|
||||
내친김에 입력창 2개+버튼을 모두 선택해 한 번 더 <span className="kbd">Shift</span>+
|
||||
<span className="kbd">A</span> — 세로 목록 전체가 간격을 유지한 채 움직이는 걸 보세요.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={5} title="컴포넌트와 스타일" sub="한 번 만들고, 한 곳에서 고친다">
|
||||
<p>
|
||||
로그인 버튼과 똑같이 생긴 버튼이 회원가입·글쓰기·설정 화면에도 필요하다면?
|
||||
복사해 붙이면 당장은 되지만, 나중에 "버튼 색 바꿔 주세요" 한 마디에 화면 40개를
|
||||
일일이 고치는 지옥이 열려요. <strong>컴포넌트(Component)</strong>는 이걸 해결합니다 —
|
||||
버튼을 컴포넌트로 만들어 두면(<span className="kbd">Ctrl</span>+<span className="kbd">Alt</span>+<span className="kbd">K</span>),
|
||||
복사본(인스턴스)이 원본과 연결돼서 <strong>원본 하나만 고치면 전부 한꺼번에 바뀝니다</strong>.
|
||||
도장을 하나 파 두고 여기저기 찍는 것과 같아요 — 도장을 다시 파면 다음 찍는 것부터가 아니라,
|
||||
이미 찍힌 모든 자국이 함께 바뀌는 마법 도장이죠.
|
||||
</p>
|
||||
<p>
|
||||
색도 마찬가지예요. <strong>색 스타일(Color style)</strong>로 우리 브랜드 색에
|
||||
이름을 붙여 등록해 두면, 색을 바꿀 때 스타일 하나만 수정하면 됩니다.
|
||||
</p>
|
||||
<Code>{CODE_COLOR_STYLES}</Code>
|
||||
<p>
|
||||
이 발상, 어디서 봤죠? 이 페이지가 쓰는 <span className="icode">global.css</span>도
|
||||
색을 CSS 변수로 한곳에 모아 두고 모든 화면이 가져다 써요. 그리고 이 페이지의 8개 섹션
|
||||
카드도 사실 <span className="icode">Section</span>이라는 React 컴포넌트 하나를 8번
|
||||
재사용한 거예요(파일 위쪽 코드를 열어 보면 진짜 있어요!). <strong>"반복되는 건 하나로
|
||||
만들어 재사용한다"</strong> — 피그마도 CSS도 React도 전부 같은 철학입니다.
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 로그인 버튼을 선택해 컴포넌트로 만들고
|
||||
(<span className="kbd">Ctrl</span>+<span className="kbd">Alt</span>+<span className="kbd">K</span>),
|
||||
<span className="kbd">Ctrl</span>+<span className="kbd">D</span>로 인스턴스를 3개 복제하세요.
|
||||
이제 <strong>원본</strong> 버튼의 색을 <span className="icode">#2159C5</span>에서 아무 색으로
|
||||
바꿔 보세요 — 복제본 3개가 동시에 따라 바뀌면, 방금 여러분은 디자인 시스템의
|
||||
첫 조각을 만든 겁니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={6} title="프로토타입" sub="그림을 '눌러 볼 수 있는 가짜 앱'으로">
|
||||
<p>
|
||||
지금까지 만든 건 멈춰 있는 그림이에요. <strong>프로토타입(Prototype)</strong> 기능은
|
||||
화면과 화면을 화살표로 이어서, 코드 한 줄 없이 <strong>진짜 앱처럼 눌러 볼 수 있게</strong>
|
||||
만들어 줍니다. 영화로 치면 촬영 전에 장면 순서를 붙여 보는 스토리보드예요 —
|
||||
만들기 전에 "이 흐름이 자연스러운가?"를 공짜로 시험해 보는 거죠.
|
||||
실제 현장에서도 개발에 들어가기 전에 프로토타입으로 흐름을 먼저 검증해요.
|
||||
코드로 만들고 나서 "어, 이 흐름 이상한데?" 하면 고치는 비용이 수십 배거든요.
|
||||
</p>
|
||||
<ol className="olist">
|
||||
<li>로그인 화면 옆에 프레임을 하나 더 만들고, 간단한 대시보드(제목+카드 몇 개)를 그려요.</li>
|
||||
<li>오른쪽 패널 상단에서 <span className="kbd">Prototype</span> 탭으로 전환해요.</li>
|
||||
<li>로그인 버튼을 선택하면 오른쪽 가장자리에 동그라미(+)가 생겨요 — 그걸 잡아 대시보드 프레임까지 드래그! 파란 화살표가 두 화면을 잇습니다.</li>
|
||||
<li>우측 상단 <span className="kbd">▶ Present</span>(재생 버튼)를 누르면 실행 모드가 열려요.</li>
|
||||
</ol>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 실행 모드에서 로그인 버튼을 클릭해 보세요.
|
||||
화면이 대시보드로 넘어가면 — 방금 여러분은 코드 없이 '동작하는 앱의 뼈대'를 만든 거예요.
|
||||
이 상태로 폰에서도 열 수 있어요(피그마 모바일 앱) — 친구에게 눌러 보게 하고
|
||||
"로그인 버튼인 줄 알았어?"라고 물어보는 게 가장 값싼 사용성 테스트입니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={7} title="핸드오프 — 개발자에게 넘기기" sub="개발 수습생도 이 섹션은 필독!">
|
||||
<p>
|
||||
시안이 완성됐다고 끝이 아니에요. 개발자가 그 시안을 <strong>정확한 숫자로</strong>
|
||||
읽어낼 수 있어야 진짜 끝입니다. 이 넘겨주는 과정을 <strong>핸드오프(handoff)</strong>라고
|
||||
해요 — 건축가가 그림만 주는 게 아니라 치수가 적힌 설계도를 시공팀에 넘기는 것과 같죠.
|
||||
</p>
|
||||
<p>
|
||||
피그마엔 이를 위한 <strong>Dev Mode</strong>(구버전에선 Inspect 탭)가 있어요.
|
||||
우측 상단의 <span className="kbd"></></span> 토글을 켜고 요소를 클릭하면 —
|
||||
색상 코드(<span className="icode">#2159C5</span>), 글꼴과 크기, 요소 사이 간격(px),
|
||||
모서리 둥글기까지 전부 숫자로 표시되고, CSS 코드 형태로도 보여 줍니다.
|
||||
<strong> 개발 수습생 여러분</strong> — 디자이너에게 "이 파란색 뭐예요?"라고 물어보기 전에
|
||||
Dev Mode에서 직접 읽는 것, 그게 협업 매너이자 여러분의 속도예요. 요소를 선택하고
|
||||
<span className="kbd">Alt</span>를 누른 채 다른 요소에 마우스를 올리면 둘 사이 간격도
|
||||
바로 표시됩니다.
|
||||
</p>
|
||||
<p>
|
||||
아이콘·이미지가 필요하면 <strong>Export</strong>를 써요. 요소를 선택하고 우측 패널
|
||||
맨 아래 <span className="kbd">Export</span>에서 PNG(사진류)나 SVG(아이콘류)로 내보내면
|
||||
개발자가 바로 프로젝트에 넣을 수 있는 파일이 나옵니다.
|
||||
</p>
|
||||
<p>그런데 숫자만으론 부족해요. 그림에 안 보이는 것들을 글로 남겨야 합니다.</p>
|
||||
<Code>{CODE_HANDOFF_SPEC}</Code>
|
||||
<div className="warn">
|
||||
<b>수습 3주차 핸드오프 과제 예고</b> 3주차에 디자인 수습생과 개발 수습생이 짝을 지어
|
||||
실제 핸드오프를 합니다 — 디자인 쪽은 위 스펙 3종(상태·인터랙션·엣지케이스)이 담긴
|
||||
시안을 넘기고, 개발 쪽은 Dev Mode만 보고 질문 없이 화면을 구현하는 게 목표예요.
|
||||
"질문이 안 나오는 핸드오프"가 만점입니다.
|
||||
</div>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 섹션 3~5에서 만든 내 로그인 시안에서 Dev Mode를 켜고,
|
||||
버튼을 클릭해 보세요. 내가 만든 버튼의 배경색·패딩·둥글기가 CSS 코드로 나오죠?
|
||||
그 값과 실제 우리 플랫폼 로그인 버튼(브라우저에서 <span className="kbd">F12</span> →
|
||||
요소 검사)의 CSS를 나란히 비교해 보세요 — 얼마나 비슷하게 모작했는지 숫자로 채점됩니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={8} title="직접 해보기 — 모작에서 개선안까지" sub="배운 걸 전부 꺼내 쓰는 졸업 과제">
|
||||
<p>
|
||||
이제 도구 사용법은 다 배웠어요. 마지막은 <strong>진짜 디자이너의 일 흐름</strong>을
|
||||
한 바퀴 도는 과제입니다. 재료는 계속 그래왔듯 우리 플랫폼이에요 —
|
||||
매일 쓰는 화면이라 아쉬운 점이 눈에 잘 보이고, 개선안을 멘토에게 보여주면
|
||||
<strong> 진짜로 반영될 수도</strong> 있으니까요.
|
||||
</p>
|
||||
<Code>{CODE_FINAL_MISSION}</Code>
|
||||
<div className="tip">
|
||||
<b>공유 전 셀프 체크</b> 레이어 패널이 <span className="icode">Rectangle 47</span>,
|
||||
<span className="icode">Frame 12</span> 같은 이름투성이라면 잠깐 멈추세요.
|
||||
<span className="icode">btn-login</span>, <span className="icode">card-course</span>처럼
|
||||
이름을 붙여 정리하는 것까지가 작업이에요 — 개발자가 변수 이름을 정성껏 짓는 것과
|
||||
같은 이유입니다. 남이 열어 봤을 때 이해되는 파일이 프로의 파일이에요.
|
||||
</div>
|
||||
<div className="warn">
|
||||
<b>주의: 개선안은 근거와 함께</b> "예뻐서 바꿨어요"는 디자인이 아니라 취향이에요.
|
||||
"버튼이 화면 하단에 있어서 스크롤 없이 안 보임 → 상단 고정으로 변경"처럼
|
||||
<strong> 문제 → 해결</strong> 형식으로 한 줄씩 근거를 다세요.
|
||||
UI/UX 기초 코스에서 배운 원칙들이 그 근거의 재료입니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<div className="step-card">
|
||||
<div className="step-body">
|
||||
<h3 style={{ marginBottom: 6 }}>🎨 여기까지 왔다면</h3>
|
||||
<p className="muted">
|
||||
이제 여러분의 작업대(피그마)가 생겼어요. 도구는 손에 익었으니, 다음은 그 도구로
|
||||
<strong> 무엇을, 왜</strong> 그릴지 배울 차례 —{' '}
|
||||
<Link to="/learn/uiux"><strong>UI/UX 기초</strong></Link> 코스에서 좋은 화면의
|
||||
원칙을 배우고 돌아오면, 섹션 8의 개선안 과제가 훨씬 깊어질 거예요.
|
||||
더 많은 연습 문제는 <strong>디자이너 트랙 과제집</strong>에서 이어집니다 —
|
||||
멘토에게 과제집 링크를 요청하세요.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
466
frontend/src/pages/HtmlCssPage.jsx
Normal file
466
frontend/src/pages/HtmlCssPage.jsx
Normal file
@ -0,0 +1,466 @@
|
||||
// 이 파일이 하는 일: "HTML / CSS 기초" 코스 — 웹페이지가 HTML(뼈대)과 CSS(옷)로
|
||||
// 만들어진다는 사실에서 출발해, 우리 플랫폼의 실제 코드(global.css, 상단 네비)를
|
||||
// 해부하고, 마지막엔 자기소개 페이지 한 장을 직접 만들어 보는 8섹션 정적 학습 페이지.
|
||||
// (웹의 정체 → HTML 구조 → CSS 첫걸음 → 박스 모델 → 색·글꼴 → Flexbox → 반응형 → 직접 해보기)
|
||||
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 global.css의
|
||||
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용한다 — 색을 여기서 하드코딩하지 않는다.
|
||||
// 코드 예시는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// ── 코드 예시 · 텍스트 다이어그램 상수들 ──
|
||||
|
||||
const CODE_THREE_LAYERS = `웹페이지 한 장 = 세 가지 재료의 합작
|
||||
|
||||
HTML → 뼈대 "여기 제목이 있고, 그 아래 버튼이 있다" (구조)
|
||||
CSS → 옷 "제목은 크고 파랗게, 버튼은 둥글게" (모양)
|
||||
JS → 움직임 "버튼을 누르면 창이 뜬다" (동작)
|
||||
|
||||
사람으로 치면: HTML은 골격, CSS는 옷과 화장, JS는 근육.
|
||||
같은 뼈대(HTML)에 옷(CSS)만 갈아입혀도 완전히 다른 사이트처럼 보여요.`;
|
||||
|
||||
const CODE_HTML_ANATOMY = `<button class="btn">저장</button>
|
||||
└┬────┘ └┬──────────┘└┬─┘└┬──────┘
|
||||
여는 태그 속성(attribute) 내용 닫는 태그
|
||||
|
||||
태그는 <상자>처럼 열고 닫으며, 상자 안에 상자를 넣을 수 있어요(중첩):
|
||||
|
||||
<section> ← 큰 상자
|
||||
<h3>제목</h3> ← 그 안의 작은 상자
|
||||
<p>본문 문단</p>
|
||||
</section>
|
||||
|
||||
규칙 하나만 기억: 나중에 연 상자를 먼저 닫는다.
|
||||
<a><b>...</a></b> ← 이렇게 엇갈리면 안 돼요!`;
|
||||
|
||||
const CODE_SEMANTIC = `우리 학습 플랫폼의 실제 뼈대 (요약 발췌)
|
||||
|
||||
<header class="topnav"> ← "여긴 상단 바야"
|
||||
<nav>...메뉴들...</nav> ← "여긴 이동 메뉴야"
|
||||
</header>
|
||||
<main> ← "여기부터 본문이야"
|
||||
<section class="step-card"> ← "이건 한 덩어리 섹션이야"
|
||||
<h3>랜선의 구조</h3>
|
||||
<p>...</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
전부 <div>로 써도 화면은 똑같이 나와요. 그런데 왜 이름을 나눌까?
|
||||
1) 사람이 코드를 읽기 쉬움 — div 100개보다 header/main/section이 지도처럼 보임
|
||||
2) 검색엔진·스크린리더가 구조를 이해함 (시각장애인용 낭독기가 "본문 시작!"을 앎)
|
||||
3) 클릭되는 건 <button> — div에 onClick을 달면 키보드로는 못 누릅니다`;
|
||||
|
||||
const CODE_CSS_BASIC = `CSS 문장의 생김새: "누구에게(선택자) { 무엇을: 어떻게; }"
|
||||
|
||||
h1 { color: blue; } ← 태그 선택자: 모든 <h1>에게
|
||||
.card { padding: 20px; } ← 클래스 선택자: class="card"인 애들에게
|
||||
#top-logo { font-size: 15px; } ← id 선택자: id="top-logo"인 딱 하나에게
|
||||
|
||||
셋 중 실무에서 제일 많이 쓰는 건 압도적으로 클래스(.)예요.
|
||||
태그는 너무 광범위하고, id는 페이지에 하나만 허용되거든요.`;
|
||||
|
||||
const CODE_CARD_RULE = `우리 global.css에 실제로 있는 규칙 — .card 해부
|
||||
|
||||
.card {
|
||||
background: var(--card); /* 배경색: 변수에서 가져옴 (섹션 5 예고!) */
|
||||
border: 1px solid var(--line); /* 테두리: 1px 실선 */
|
||||
border-radius: var(--radius); /* 모서리 둥글기 (= 14px) */
|
||||
box-shadow: var(--shadow); /* 그림자: 살짝 떠 있는 느낌 */
|
||||
padding: 20px; /* 안쪽 여백 (섹션 4 예고!) */
|
||||
}
|
||||
|
||||
.card + .card {
|
||||
margin-top: 14px; /* 카드가 연달아 오면 사이를 14px 띄움 */
|
||||
}
|
||||
|
||||
읽는 법: "class가 card인 요소는 → 배경·테두리·둥근 모서리·그림자·안쪽 여백을 가져라"
|
||||
지금 여러분이 보고 있는 이 페이지의 흰 상자들이 전부 이 다섯 줄의 작품이에요.`;
|
||||
|
||||
const CODE_BOX_MODEL = `박스 모델 — 모든 HTML 요소는 사실 네 겹의 상자다
|
||||
|
||||
┌─ margin (바깥 여백: 옆 상자와의 거리) ─────────┐
|
||||
│ ┌─ border (테두리: 상자의 벽) ─────────────┐ │
|
||||
│ │ ┌─ padding (안쪽 여백: 벽과 내용 사이) ─┐ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ │ content (내용: 글자·이미지) │ │ │
|
||||
│ │ │ │ │ │
|
||||
│ │ └──────────────────────────────────┘ │ │
|
||||
│ └────────────────────────────────────────┘ │
|
||||
└──────────────────────────────────────────────┘
|
||||
|
||||
택배 상자 비유:
|
||||
content = 물건, padding = 뽁뽁이, border = 상자 벽, margin = 옆 상자와의 간격
|
||||
|
||||
.card의 padding: 20px → "벽과 글자 사이를 20px 띄워라"
|
||||
.card + .card의 margin-top: 14px → "상자끼리 14px 떨어져라"`;
|
||||
|
||||
const CODE_COLOR_VARS = `색은 사실 숫자예요 — #RRGGBB (빨강·초록·파랑 각 00~FF)
|
||||
|
||||
#2159C5 ← 우리 AWESOMEDEV 브랜드 블루!
|
||||
││││└┴─ C5 = 파랑 많이
|
||||
││└┴─── 59 = 초록 조금
|
||||
└┴───── 21 = 빨강 아주 조금 → 섞으면 진한 파랑
|
||||
|
||||
그런데 이 색을 파일 곳곳에 300번 복붙하면? 색 바꿀 때 300군데 수정...
|
||||
그래서 우리 global.css는 맨 위 :root에 '색 이름표'를 붙여 둡니다:
|
||||
|
||||
:root {
|
||||
--primary: #2159C5; /* 브랜드 블루 */
|
||||
--bg: #FAFBFD; /* 페이지 배경 */
|
||||
--ink: #101A28; /* 글자색 */
|
||||
}
|
||||
.btn { background: var(--primary); } /* 쓸 때는 var(이름표) */
|
||||
|
||||
다크 모드의 정체가 바로 이거예요 — 이름표의 '값'만 갈아끼우면 끝:
|
||||
|
||||
@media (prefers-color-scheme: dark) { /* PC가 다크 모드면 */
|
||||
:root {
|
||||
--bg: #0B1017; /* 배경은 어둡게 */
|
||||
--ink: #E8EDF4; /* 글자는 밝게 */
|
||||
--primary: #6AA0F0; /* 파랑도 어두운 배경에서 잘 보이게 살짝 밝게 */
|
||||
}
|
||||
}
|
||||
|
||||
컴포넌트 코드는 한 줄도 안 바꿨는데 사이트 전체가 옷을 갈아입습니다.`;
|
||||
|
||||
const CODE_FLEX = `Flexbox — 상자들을 한 줄로 착착 정렬하는 도구
|
||||
|
||||
부모에게 display: flex 를 선언하면, 자식들이 가로로 한 줄에 서요.
|
||||
|
||||
우리 플랫폼 상단 네비의 실제 코드 (global.css의 .topnav-inner):
|
||||
|
||||
.topnav-inner {
|
||||
display: flex; /* 자식들아, 한 줄로 서! */
|
||||
align-items: center; /* 세로 방향은 가운데 맞춰 */
|
||||
gap: 20px; /* 서로 20px씩 떨어져 */
|
||||
height: 56px;
|
||||
max-width: 960px;
|
||||
margin: 0 auto; /* 좌우 여백 자동 = 화면 가운데 정렬 */
|
||||
}
|
||||
|
||||
자주 쓰는 3총사:
|
||||
justify-content → 가로(진행 방향) 정렬: flex-start / center / space-between
|
||||
align-items → 세로(교차 방향) 정렬: center가 국룰
|
||||
gap → 자식들 사이 간격 (margin 노가다 탈출!)
|
||||
|
||||
grid 한 입: flex가 '한 줄 정렬'이라면 grid는 '바둑판 배치'예요.
|
||||
display: grid; grid-template-columns: 1fr 1fr; → 두 칸짜리 표 완성.
|
||||
카드 목록처럼 행과 열이 둘 다 필요할 때 grid를 씁니다.`;
|
||||
|
||||
const CODE_MEDIA = `반응형 — 화면 크기에 따라 옷을 갈아입는 CSS
|
||||
|
||||
@media (max-width: 640px) { /* 화면 폭이 640px 이하일 때만 */
|
||||
.card-grid {
|
||||
grid-template-columns: 1fr; /* 두 칸짜리 바둑판을 한 칸으로 */
|
||||
}
|
||||
}
|
||||
|
||||
'모바일 우선(mobile-first)' 사고방식:
|
||||
1) 기본 CSS는 제일 좁은 화면(폰) 기준으로 작성
|
||||
2) @media (min-width: ...) 로 "화면이 넓어지면 이렇게 확장"을 추가
|
||||
|
||||
이유: 좁은 화면이 제약이 많아서, 좁은 것부터 설계하면 넓히기는 쉬운데
|
||||
넓은 것부터 만들면 좁힐 때 여기저기 터져요. 옷도 줄이는 수선이 더 어렵죠.`;
|
||||
|
||||
const CODE_MINI_PAGE = `<!-- me.html — VS Code에 새 파일로 저장하고 더블클릭으로 열어 보세요 -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>나의 소개</title>
|
||||
<style>
|
||||
:root { --brand: #2159C5; } /* 우리처럼 색을 변수로! */
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
background: #FAFBFD;
|
||||
display: flex; /* 섹션 6에서 배운 flex */
|
||||
justify-content: center;
|
||||
padding: 40px 16px;
|
||||
}
|
||||
.card { /* 섹션 3·4에서 배운 카드 */
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #E4E8F0;
|
||||
border-radius: 14px;
|
||||
padding: 24px;
|
||||
max-width: 420px;
|
||||
}
|
||||
h1 { color: var(--brand); margin: 0 0 8px; }
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: #E5EDFB;
|
||||
color: var(--brand);
|
||||
border-radius: 999px;
|
||||
padding: 4px 12px;
|
||||
font-size: 13px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="card">
|
||||
<h1>홍길동</h1>
|
||||
<p>미림마이스터고 3학년, AWESOMEDEV 수습 개발자입니다.</p>
|
||||
<span class="tag">React</span>
|
||||
<span class="tag">Spring Boot</span>
|
||||
<span class="tag">PostgreSQL</span>
|
||||
</main>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
|
||||
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. props만 바꿔 재사용.
|
||||
function Section({ n, title, sub, children }) {
|
||||
return (
|
||||
<section className="step-card" id={`sec-${n}`}>
|
||||
<div className="step-head">
|
||||
<span className="step-num">{n}</span>
|
||||
<div>
|
||||
<h3>{title}</h3>
|
||||
{sub && <span className="step-sub">{sub}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="step-body">{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Code({ children }) {
|
||||
return <pre className="code-block"><code>{children}</code></pre>;
|
||||
}
|
||||
|
||||
const SECTIONS = [
|
||||
{ n: 1, label: '웹페이지의 정체' },
|
||||
{ n: 2, label: 'HTML 구조' },
|
||||
{ n: 3, label: 'CSS 첫걸음' },
|
||||
{ n: 4, label: '박스 모델' },
|
||||
{ n: 5, label: '색과 글꼴' },
|
||||
{ n: 6, label: 'Flexbox' },
|
||||
{ n: 7, label: '반응형' },
|
||||
{ n: 8, label: '직접 해보기' },
|
||||
];
|
||||
|
||||
export default function HtmlCssPage() {
|
||||
return (
|
||||
<div>
|
||||
{/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
|
||||
<div className="hero">
|
||||
<div className="eyebrow">Course · 소프트웨어</div>
|
||||
<h1>HTML / CSS 기초<br />— 웹페이지의 뼈대와 옷</h1>
|
||||
<p>
|
||||
지금 보고 있는 이 화면도 결국 HTML과 CSS로 만들어졌어요. 이 코스에서는
|
||||
웹페이지의 재료를 하나씩 뜯어 보고, <strong>우리 플랫폼의 진짜 코드</strong>를
|
||||
해부한 뒤, 마지막엔 자기소개 페이지 한 장을 직접 만들어 봅니다.
|
||||
각 섹션의 <strong>"직접 확인해 보기"</strong>는 꼭 손으로 해보세요 —
|
||||
CSS는 눈으로 읽는 과목이 아니라 만져 보는 과목이에요.
|
||||
</p>
|
||||
<div className="chip-row">
|
||||
<span className="chip">예상 소요 60분</span>
|
||||
<span className="chip">우리 코드 해부 3회</span>
|
||||
<span className="chip">준비물: 브라우저 + VS Code</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 섹션 바로가기 */}
|
||||
<nav className="pill-nav">
|
||||
{SECTIONS.map((s) => (
|
||||
<a key={s.n} href={`#sec-${s.n}`}>
|
||||
<b>{s.n}</b>
|
||||
{s.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<Section n={1} title="웹페이지의 정체" sub="모든 웹은 HTML + CSS + JS, 딱 세 가지로 만든다">
|
||||
<p>
|
||||
유튜브도, 네이버도, 우리 학습 플랫폼도 — 세상 모든 웹페이지는 결국
|
||||
<strong> 딱 세 가지 언어</strong>로 만들어져요. 브라우저가 읽을 수 있는 언어가
|
||||
이 셋뿐이거든요.
|
||||
</p>
|
||||
<Code>{CODE_THREE_LAYERS}</Code>
|
||||
<p>
|
||||
이번 코스에서는 앞의 두 가지, <strong>뼈대(HTML)</strong>와 <strong>옷(CSS)</strong>을
|
||||
배웁니다. 움직임(JS)은 다음 코스에서 이어져요.
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 아무 웹사이트에서나 마우스 <span className="kbd">우클릭</span> →
|
||||
<span className="kbd">페이지 소스 보기</span>를 눌러 보세요. 화려한 화면 뒤에 숨어 있던
|
||||
진짜 재료 — <span className="icode"><html></span>로 시작하는 텍스트 —
|
||||
가 나타납니다. 어떤 사이트든 예외가 없어요. 웹의 '주방 공개'인 셈이죠.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={2} title="HTML 구조" sub="태그, 중첩, 그리고 이름이 있는 상자들">
|
||||
<p>
|
||||
HTML은 <strong>태그(tag)</strong>라는 꺾쇠 괄호로 내용을 감싸는 언어예요.
|
||||
"여기부터 여기까지는 제목이야", "이건 버튼이야" 하고 <strong>이름표가 붙은
|
||||
상자</strong>에 내용을 담는 거죠.
|
||||
</p>
|
||||
<Code>{CODE_HTML_ANATOMY}</Code>
|
||||
<p>
|
||||
여기서 초보와 실무자를 가르는 포인트 하나 — <strong>div만 쓰지 않기</strong>예요.
|
||||
<span className="icode"><div></span>는 "이름 없는 그냥 상자"인데,
|
||||
HTML에는 역할이 이름에 담긴 <strong>시맨틱 태그</strong>들이 있어요.
|
||||
우리 플랫폼의 실제 뼈대를 보면 이렇게 생겼습니다.
|
||||
</p>
|
||||
<Code>{CODE_SEMANTIC}</Code>
|
||||
<div className="warn">
|
||||
<b>클릭은 button에게</b> 겉보기엔 <span className="icode"><div onClick=...></span>도
|
||||
클릭이 되지만, 키보드 <span className="kbd">Tab</span>으로는 접근이 안 되고 스크린리더도
|
||||
버튼인 줄 몰라요. "누르는 것"은 반드시 <span className="icode"><button></span> —
|
||||
우리 코드 리뷰에서도 자주 나오는 지적입니다.
|
||||
</div>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 이 페이지에서 <span className="kbd">F12</span> →
|
||||
<span className="kbd">Elements</span> 탭을 열어 보세요. 접었다 펼 수 있는 태그 트리가
|
||||
보일 거예요. <span className="icode"><section class="step-card"></span>를 찾아서 —
|
||||
네, 바로 지금 읽고 있는 이 카드예요! — 그 안에 어떤 상자들이 중첩돼 있는지 세어 보세요.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={3} title="CSS 첫걸음" sub="누구에게, 무엇을, 어떻게 — 선택자와 속성">
|
||||
<p>
|
||||
CSS는 문법이 단순해요. <strong>"누구에게(선택자) 무엇을(속성) 어떻게(값)"</strong> —
|
||||
이 한 문장 구조가 전부입니다. 미용실 주문서와 같아요:
|
||||
"앞머리는(선택자) 길이를(속성) 눈썹 위로(값)".
|
||||
</p>
|
||||
<Code>{CODE_CSS_BASIC}</Code>
|
||||
<p>
|
||||
말로만 배우면 심심하니, <strong>우리 플랫폼의 진짜 CSS</strong>를 해부해 봅시다.
|
||||
여러분이 매일 보는 흰 카드 상자의 정체예요.
|
||||
</p>
|
||||
<Code>{CODE_CARD_RULE}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> <span className="kbd">F12</span> → 개발자 도구 왼쪽 위의
|
||||
<span className="kbd">화살표 아이콘(요소 선택)</span>을 누르고 이 카드를 클릭해 보세요.
|
||||
오른쪽 <span className="kbd">Styles</span> 패널에 방금 본 <span className="icode">.card</span> 나
|
||||
<span className="icode"> .step-card</span> 규칙이 그대로 떠요. 거기서
|
||||
<span className="icode"> padding: 20px</span>을 <span className="icode">60px</span>로
|
||||
바꿔 보세요 — 화면이 즉시 변합니다! (새로고침하면 원래대로 돌아오니 마음껏.)
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={4} title="박스 모델" sub="모든 요소는 네 겹의 택배 상자다">
|
||||
<p>
|
||||
CSS에서 제일 중요한 개념 하나만 꼽으라면 <strong>박스 모델</strong>이에요.
|
||||
화면의 모든 요소 — 글자 한 줄, 버튼 하나, 이미지 하나 — 는 전부
|
||||
<strong> 네 겹짜리 상자</strong>로 취급됩니다.
|
||||
</p>
|
||||
<Code>{CODE_BOX_MODEL}</Code>
|
||||
<p>
|
||||
헷갈리기 쉬운 <strong>padding vs margin</strong> — 뽁뽁이(padding)는 상자
|
||||
<strong> 안</strong>에 있고, 상자 사이 간격(margin)은 <strong>밖</strong>에 있어요.
|
||||
"내용물이 벽에 닿아서 답답하다" → padding, "상자끼리 너무 붙었다" → margin.
|
||||
이 구분만 되면 CSS 레이아웃 고민의 절반이 사라집니다.
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> <span className="kbd">F12</span>로 이 카드를 선택한 상태에서
|
||||
<span className="kbd">Styles</span> 패널을 아래로 스크롤하면 <strong>알록달록한 상자
|
||||
그림</strong>이 나와요 — 주황(margin), 노랑(border), 초록(padding), 파랑(content).
|
||||
브라우저가 위 다이어그램을 실제 숫자와 함께 그려 주는 거예요. 각 겹에 마우스를
|
||||
올리면 화면에서 해당 영역이 색으로 표시됩니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={5} title="색과 글꼴" sub="#2159C5의 비밀, 그리고 다크 모드의 원리">
|
||||
<p>
|
||||
<Link to="/learn/computer">컴퓨터의 구성</Link> 코스의 <strong>"색이 숫자"</strong> 파트를
|
||||
기억하나요? 컴퓨터에게 색은 빨강·초록·파랑의 양을 적은 숫자였죠. CSS에서는 그 숫자를
|
||||
<span className="icode"> #RRGGBB</span> 형태의 <strong>hex 코드</strong>로 씁니다.
|
||||
</p>
|
||||
<Code>{CODE_COLOR_VARS}</Code>
|
||||
<p>
|
||||
글꼴도 같은 원리로 관리해요. <span className="icode">font-family</span>로 글꼴,
|
||||
<span className="icode"> font-size</span>로 크기, <span className="icode">font-weight</span>로
|
||||
굵기(400=보통, 700=굵게)를 정합니다. 우리 플랫폼이 다크 모드에서도 코드를 한 줄도
|
||||
안 고치고 멀쩡한 이유 — <strong>모든 색이 :root 변수 한 곳에 모여 있기 때문</strong> —
|
||||
를 이제 설명할 수 있어야 해요.
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> PC의 테마를 바꿔 보세요.
|
||||
<span className="kbd">Windows 설정 → 개인 설정 → 색 → 다크</span>로 전환하고
|
||||
이 페이지로 돌아오면 — 새로고침 없이도 옷을 갈아입습니다.
|
||||
<span className="icode"> @media (prefers-color-scheme: dark)</span> 블록이
|
||||
이름표 값을 통째로 바꿔치기한 순간이에요.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={6} title="레이아웃: Flexbox" sub="우리 상단 네비가 한 줄로 서 있는 이유">
|
||||
<p>
|
||||
박스 모델이 상자 <strong>한 개</strong>의 해부학이라면, <strong>Flexbox</strong>는
|
||||
상자 <strong>여러 개</strong>를 줄 세우는 기술이에요. 부모가
|
||||
<span className="icode"> display: flex</span> 하고 구령을 외치면 자식들이 정렬합니다.
|
||||
지금 화면 맨 위의 네비게이션 바 — 로고와 메뉴가 나란히 서 있는 것도 flex의 작품이에요.
|
||||
</p>
|
||||
<Code>{CODE_FLEX}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> <span className="kbd">F12</span>로 상단 네비의
|
||||
<span className="icode"> .topnav-inner</span>를 선택하면 <span className="kbd">Elements</span> 탭에서
|
||||
태그 옆에 작은 <span className="kbd">flex</span> 배지가 붙어 있어요. 배지를 클릭하면
|
||||
flex 컨테이너가 화면에 표시되고, <span className="kbd">Styles</span> 패널에서
|
||||
<span className="icode"> gap: 20px</span>을 <span className="icode">60px</span>으로 바꾸면
|
||||
메뉴 사이가 즉시 벌어집니다. flex를 글이 아니라 손으로 익히는 순간!
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={7} title="반응형" sub="폰에서도, 모니터에서도 — @media 하나로">
|
||||
<p>
|
||||
같은 사이트가 폰에서는 한 줄로, 모니터에서는 여러 칸으로 보이죠?
|
||||
비밀은 <span className="icode">@media</span> — <strong>"화면이 이런 조건일 때만
|
||||
이 CSS를 적용해"</strong>라는 조건문이에요. 화면 크기에 따라 다른 옷을 꺼내 입는 겁니다.
|
||||
</p>
|
||||
<Code>{CODE_MEDIA}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> <span className="kbd">F12</span> → 왼쪽 위
|
||||
<span className="kbd">폰·태블릿 아이콘(기기 툴바)</span>을 누르면 이 페이지를
|
||||
모바일 화면 크기로 볼 수 있어요. 위쪽에서 <span className="kbd">iPhone</span> 등
|
||||
기종을 골라 가며 우리 플랫폼의 레이아웃이 어떻게 접히는지 관찰해 보세요.
|
||||
</div>
|
||||
<div className="warn">
|
||||
<b>티켓 풀 연결 (FE-06)</b> 수습 티켓 풀의 <strong>FE-06 "모바일 레이아웃 깨짐 수정"</strong>이
|
||||
바로 이 섹션의 실전판이에요. 방금 배운 기기 툴바로 깨지는 화면을 재현하고,
|
||||
@media 블록을 다듬어 고치는 티켓입니다. 이 코스를 마치면 도전해 보세요.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={8} title="직접 해보기 — 자기소개 페이지 한 장" sub="배운 것 전부를 30줄에 담기">
|
||||
<p>
|
||||
이제 진짜로 만들어 봅시다. <span className="kbd">VS Code</span>에서 새 파일
|
||||
<span className="icode"> me.html</span>을 만들고, 아래 코드를 <strong>복붙 말고
|
||||
한 줄씩 따라 쳐 보세요</strong>. 타자로 친 코드만 손에 남아요. 다 쳤으면 파일을
|
||||
저장하고 더블클릭 — 브라우저에 내 페이지가 뜹니다. 서버도, 설치도 필요 없어요.
|
||||
</p>
|
||||
<Code>{CODE_MINI_PAGE}</Code>
|
||||
<ol className="olist">
|
||||
<li>일단 그대로 완성해서 브라우저로 열어 본다. (뜨는 것 자체가 첫 성공!)</li>
|
||||
<li>이름·소개 문구를 진짜 내 것으로 바꾼다.</li>
|
||||
<li><span className="icode">--brand</span> 값을 내가 좋아하는 색 hex 코드로 바꿔 본다 —
|
||||
변수 하나 바꿨는데 제목과 태그가 한꺼번에 바뀌는 걸 확인!</li>
|
||||
<li><span className="icode">border-radius</span>를 <span className="icode">0</span>과
|
||||
<span className="icode"> 30px</span>으로 바꿔 보며 카드 인상이 어떻게 달라지는지 느껴 본다.</li>
|
||||
<li>(도전) <span className="icode">.tag</span>를 하나 더 추가하고,
|
||||
@media로 화면이 좁을 때 카드 padding을 줄여 본다.</li>
|
||||
</ol>
|
||||
<div className="tip">
|
||||
<b>막히면</b> 화면이 하얗게만 나오면 십중팔구 <strong>태그를 안 닫았거나 중괄호 짝이
|
||||
안 맞는</strong> 거예요. <span className="kbd">F12</span> → <span className="kbd">Console</span>에
|
||||
단서가 있고, VS Code의 색 표시가 이상해지는 지점이 범인 근처입니다.
|
||||
그래도 안 되면 멘토 채널로 — 에러 화면 캡처와 함께!
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<div className="step-card">
|
||||
<div className="step-body">
|
||||
<h3 style={{ marginBottom: 6 }}>🎨 여기까지 왔다면</h3>
|
||||
<p className="muted">
|
||||
이제 웹페이지의 <strong>뼈대(HTML)</strong>를 세우고 <strong>옷(CSS)</strong>을
|
||||
입힐 수 있게 됐어요. 남은 재료는 하나 — 페이지를 움직이게 하는{' '}
|
||||
<strong>근육</strong>이죠. <Link to="/learn/javascript"><strong>자바스크립트 기초</strong></Link> 코스에서
|
||||
버튼이 눌리고, 데이터가 바뀌고, 화면이 반응하는 원리를 이어서 배워 보세요.
|
||||
거기까지 가면 우리 React 코드가 읽히기 시작합니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
508
frontend/src/pages/JavascriptPage.jsx
Normal file
508
frontend/src/pages/JavascriptPage.jsx
Normal file
@ -0,0 +1,508 @@
|
||||
// 이 파일이 하는 일: "자바스크립트 기초" 코스 — 브라우저 F12 콘솔을 실습장 삼아
|
||||
// JS 언어 문법을 실전 위주로 8개 섹션으로 안내하는 정적 학습 페이지.
|
||||
// (콘솔 → 변수/타입 → 함수 → 배열/객체 → 조건/반복 → DOM/이벤트 → 비동기/fetch → React로 가는 다리)
|
||||
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 global.css의
|
||||
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용한다 — 색을 여기서 하드코딩하지 않는다.
|
||||
// 코드 예제는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.
|
||||
// (예제 코드 안에 백틱이 필요하면 \`처럼 이스케이프한다.)
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// ── 코드 예제 상수들 (전부 "콘솔에 붙여넣어 확인" 용) ──
|
||||
|
||||
const CODE_CONSOLE_START = `// F12 → Console 탭에 한 줄씩 붙여넣고 Enter!
|
||||
|
||||
console.log("안녕, 자바스크립트!"); // 콘솔에 글자 찍기
|
||||
|
||||
1 + 2 * 3 // 7 — 수학 시간과 똑같이 곱셈 먼저
|
||||
10 % 3 // 1 — 나머지 연산 (짝수/홀수 판별의 단골)
|
||||
"어썸" + "데브" // "어썸데브" — 문자열은 +로 이어 붙어요
|
||||
|
||||
// 콘솔은 "계산기 + 대화창"이에요. 입력하면 바로 결과를 돌려줍니다.
|
||||
// 이 코스의 모든 예제는 이렇게 콘솔에 붙여넣으면 그 자리에서 확인돼요.`;
|
||||
|
||||
const CODE_CONSOLE_TRAP = `// 콘솔의 재미있는 함정 하나 — 붙여넣어 보세요:
|
||||
"1" + 1 // "11" ?! 숫자가 아니라 문자열 이어붙이기가 됐어요
|
||||
"1" - 1 // 0 ?! 빼기는 문자열에 없어서 숫자로 바꿔 계산
|
||||
|
||||
// 교훈: JS는 타입을 자동으로 바꿔치기해요(형변환).
|
||||
// 그래서 "지금 이 값의 타입이 뭐지?"를 늘 의식해야 합니다. → 다음 섹션!`;
|
||||
|
||||
const CODE_VARIABLES = `// const = 바꿀 수 없는 이름표, let = 바꿀 수 있는 이름표
|
||||
const school = "미림마이스터고";
|
||||
let level = 1;
|
||||
|
||||
level = 2; // OK — let은 재할당 가능
|
||||
// school = "다른학교"; // 에러! const는 재할당 불가
|
||||
|
||||
// 기본 규칙: 일단 전부 const로 쓰고, 정말 바뀌어야 할 때만 let.
|
||||
// var는? 옛날 문법이에요. 선언 전에 써도 에러가 안 나는 등
|
||||
// 버그를 숨기는 성질이 있어서 현대 코드에서는 쓰지 않습니다.`;
|
||||
|
||||
const CODE_TYPES = `// 콘솔에서 typeof로 타입을 직접 물어보세요:
|
||||
typeof "어썸데브" // "string" — 문자열
|
||||
typeof 90 // "number" — 숫자 (정수/실수 구분 없음!)
|
||||
typeof true // "boolean" — 참/거짓
|
||||
typeof undefined // "undefined" — "값을 아직 안 넣음"
|
||||
typeof null // "object" — null인데 object?! (유명한 역사적 버그)
|
||||
|
||||
// undefined = 아직 아무것도 안 넣은 빈 사물함
|
||||
// null = "여긴 비워 뒀음"이라고 일부러 붙여 둔 팻말
|
||||
// 둘 다 "없음"이지만, null은 사람이 의도적으로 넣은 없음이에요.`;
|
||||
|
||||
const CODE_TEMPLATE = `// 템플릿 문자열 — 따옴표 대신 백틱(\`)을 쓰면 문자열 안에 값을 끼워 넣어요
|
||||
const name = "김미림";
|
||||
const score = 95;
|
||||
|
||||
// 옛날 방식: + 로 이어 붙이기 (띄어쓰기 실수 단골)
|
||||
console.log(name + "님의 점수는 " + score + "점");
|
||||
|
||||
// 요즘 방식: \${ } 자리에 값이 쏙
|
||||
console.log(\`\${name}님의 점수는 \${score}점\`);
|
||||
|
||||
// React 코드에서도 클래스명 조합 등에 정말 자주 나와요.`;
|
||||
|
||||
const CODE_FUNCTIONS = `// 함수 = 코드에 이름을 붙인 자판기. 넣으면(매개변수) 나온다(반환값).
|
||||
|
||||
// 1) 함수 선언식 — 교과서 스타일
|
||||
function add(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
add(3, 4); // 7
|
||||
|
||||
// 2) 화살표 함수 — 요즘 스타일. "(입력) => 출력"
|
||||
const addArrow = (a, b) => {
|
||||
return a + b;
|
||||
};
|
||||
|
||||
// 3) 한 줄이면 return과 중괄호도 생략 가능!
|
||||
const addShort = (a, b) => a + b;
|
||||
addShort(3, 4); // 7`;
|
||||
|
||||
const CODE_ARROW_WHY = `// 우리 React 코드가 화살표 함수 천지인 이유:
|
||||
// "그 자리에서 만들어 바로 넘기는 작은 함수"가 정말 많기 때문이에요.
|
||||
|
||||
const scores = [70, 85, 95];
|
||||
|
||||
// 배열의 각 요소를 변환할 때 — 함수를 '재료'로 넘깁니다
|
||||
scores.map((s) => s + 5); // [75, 90, 100]
|
||||
|
||||
// React 버튼에서도 (지금은 모양만 눈에 익혀 두세요):
|
||||
// <button onClick={() => setOpen(true)}>열기</button>
|
||||
// "클릭되면 실행할 함수"를 즉석에서 만들어 붙이는 거예요.`;
|
||||
|
||||
const CODE_ARRAY = `// 배열 = 순서 있는 목록. 대괄호 [ ]
|
||||
const courses = ["네트워크", "코딩 기초", "자바스크립트"];
|
||||
|
||||
courses[0]; // "네트워크" — 번호는 0부터!
|
||||
courses.length; // 3
|
||||
courses.push("HTML/CSS"); // 끝에 추가 → 이제 4개
|
||||
|
||||
// 3대장 메서드 — 콘솔에서 하나씩 실행해 보세요:
|
||||
courses.map((c) => c + " 코스"); // 전원 변환 → 새 배열
|
||||
courses.filter((c) => c.length >= 4); // 조건 통과만 골라내기
|
||||
courses.find((c) => c === "코딩 기초"); // 첫 번째 한 개만 찾기
|
||||
|
||||
// 우리 학습 플랫폼의 코스 카드 목록도 정확히 이 패턴이에요:
|
||||
// courses.map((c) => <Card ... />) ← 배열이 화면이 되는 마법`;
|
||||
|
||||
const CODE_OBJECT = `// 객체 = 이름표 붙은 서랍장. 중괄호 { }
|
||||
const student = {
|
||||
name: "김미림",
|
||||
grade: 3,
|
||||
major: "소프트웨어",
|
||||
};
|
||||
|
||||
student.name; // "김미림" — 점으로 서랍 열기
|
||||
student["major"]; // "소프트웨어" — 대괄호로도 가능
|
||||
|
||||
// 구조분해 한 입 — 서랍에서 꺼내면서 바로 변수로!
|
||||
const { name, grade } = student;
|
||||
console.log(name, grade); // "김미림" 3
|
||||
|
||||
// 서버가 주는 데이터(JSON)도, React의 props도 전부 객체예요.
|
||||
// 구조분해는 React 코드 첫 줄마다 나오니 눈에 꼭 익혀 두세요.`;
|
||||
|
||||
const CODE_IF = `// if — 조건에 따라 갈림길
|
||||
const score = 85;
|
||||
|
||||
if (score >= 90) {
|
||||
console.log("최고예요!");
|
||||
} else if (score >= 70) {
|
||||
console.log("잘하고 있어요");
|
||||
} else {
|
||||
console.log("같이 복습해 봐요");
|
||||
}
|
||||
|
||||
// 삼항 연산자 — "조건 ? 참일 때 : 거짓일 때" 한 줄 버전
|
||||
const message = score >= 70 ? "통과" : "재도전";
|
||||
// React의 JSX 안에서는 if문을 못 써서 삼항이 특히 자주 등장해요:
|
||||
// {isLogin ? <MyPage /> : <LoginButton />}`;
|
||||
|
||||
const CODE_LOOP = `// for — 고전 반복문. 횟수를 직접 셀 때
|
||||
for (let i = 1; i <= 3; i++) {
|
||||
console.log(\`\${i}번째 바퀴\`);
|
||||
}
|
||||
|
||||
// for...of — 배열을 하나씩 꺼낼 때 (요즘은 이쪽이 편해요)
|
||||
const courses = ["네트워크", "코딩 기초", "자바스크립트"];
|
||||
for (const c of courses) {
|
||||
console.log(c);
|
||||
}
|
||||
|
||||
// 그럼 언제 map, 언제 for?
|
||||
// map : 배열을 → "새 배열"로 변환할 때 (화면 그리기, 데이터 가공)
|
||||
// for : 변환 결과가 필요 없고 그냥 "각각에 대해 뭔가 할" 때,
|
||||
// 또는 중간에 break로 멈추고 싶을 때
|
||||
// React 화면 그리기는 "배열 → JSX 배열" 변환이라 거의 map입니다.`;
|
||||
|
||||
const CODE_DOM = `// DOM = 브라우저가 HTML을 읽어 만든 "조작 가능한 문서 모형"
|
||||
// JS는 이 모형을 통해 화면을 실시간으로 바꿀 수 있어요.
|
||||
|
||||
// 지금 이 페이지에서 콘솔에 붙여넣어 보세요:
|
||||
const hero = document.querySelector(".hero"); // CSS 선택자로 요소 찾기
|
||||
hero.style.background = "hotpink"; // 히어로가 핑크로!
|
||||
|
||||
// 상단 제목 글자 바꿔치기:
|
||||
document.querySelector("h1").textContent = "내가 접수한 페이지";
|
||||
|
||||
// 이벤트 = "~하면 ~해라" 예약 걸기
|
||||
document.querySelector(".hero").addEventListener("click", () => {
|
||||
alert("히어로를 클릭했어요!");
|
||||
}); // 이제 히어로 영역을 클릭해 보세요`;
|
||||
|
||||
const CODE_DOM_RESET = `// 실컷 어질러 놨다면 → F5(새로고침)!
|
||||
// 모든 게 원래대로 돌아옵니다. 왜일까요?
|
||||
|
||||
// 콘솔에서 바꾼 건 "브라우저가 들고 있는 모형(DOM)"이지
|
||||
// 서버에 있는 원본 코드가 아니기 때문이에요.
|
||||
// 새로고침 = 서버에서 원본을 다시 받아 모형을 처음부터 다시 만들기.
|
||||
// (그러니 마음껏 실험해도 안전합니다 — 부술 수 있는 건 내 화면뿐!)`;
|
||||
|
||||
const CODE_PROMISE = `// 서버 응답은 "즉시"가 아니에요 — 택배처럼 기다려야 합니다.
|
||||
// Promise = 택배 운장장. "지금은 물건이 없지만, 도착하면 알려줄게"라는 약속.
|
||||
|
||||
// 주문(요청)하면 운송장(Promise)이 먼저 나오고,
|
||||
// .then()에 "도착하면 할 일"을 적어 둡니다:
|
||||
fetch("/api/auth/me")
|
||||
.then((res) => res.json())
|
||||
.then((data) => console.log(data));
|
||||
|
||||
// async/await — 같은 일을 "기다렸다가 받기"처럼 읽히게 쓰는 문법
|
||||
// (콘솔은 최상위 await를 허용해서 이렇게 바로 실행돼요)
|
||||
const res = await fetch("/api/auth/me");
|
||||
const me = await res.json();
|
||||
console.log(me); // 내 계정 정보가 객체로!`;
|
||||
|
||||
const CODE_FETCH_REAL = `// 진짜로 해 보세요 — 지금 이 플랫폼에 로그인돼 있으니 가능합니다!
|
||||
// F12 콘솔에 붙여넣기:
|
||||
|
||||
const res = await fetch("/api/auth/me");
|
||||
console.log(res.status); // 200 이면 성공 (네트워크 코스에서 본 그 200 OK!)
|
||||
const me = await res.json();
|
||||
console.log(me.name); // 내 이름이 나오나요?
|
||||
|
||||
// 방금 일어난 일:
|
||||
// 브라우저(JS) → 우리 Spring Boot 서버 → PostgreSQL에서 내 정보 조회 → JSON 응답
|
||||
// 여러분이 매일 쓰는 이 플랫폼의 모든 화면이 정확히 이 과정으로 그려집니다.`;
|
||||
|
||||
const CODE_REACT_BRIDGE = `// 지금까지 배운 JS가 React에서 이렇게 그대로 쓰여요:
|
||||
|
||||
// 1) 컴포넌트 = 그냥 "JSX를 반환하는 함수" (섹션 3!)
|
||||
function Welcome({ name }) { // ← 매개변수 자리의 구조분해 (섹션 4!)
|
||||
return <h1>안녕하세요, {name}님</h1>;
|
||||
}
|
||||
|
||||
// 2) JSX 안의 { } = JS 표현식 자리 (삼항, map... 전부 여기서)
|
||||
// {courses.map((c) => <Card title={c} />)} ← 섹션 4의 map!
|
||||
// {isLogin ? "환영해요" : "로그인하세요"} ← 섹션 5의 삼항!
|
||||
|
||||
// 3) useState가 그냥 변수와 다른 점 한 입:
|
||||
// let count = 0; count++; ← 값은 바뀌지만 화면은 그대로예요.
|
||||
// const [count, setCount] = useState(0);
|
||||
// setCount(count + 1); ← "값 바꾸고 화면도 다시 그려줘!"까지가 한 세트.
|
||||
// React는 setXxx가 불려야만 화면을 갱신합니다.`;
|
||||
|
||||
// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
|
||||
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. props만 바꿔 재사용.
|
||||
function Section({ n, title, sub, children }) {
|
||||
return (
|
||||
<section className="step-card" id={`sec-${n}`}>
|
||||
<div className="step-head">
|
||||
<span className="step-num">{n}</span>
|
||||
<div>
|
||||
<h3>{title}</h3>
|
||||
{sub && <span className="step-sub">{sub}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="step-body">{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Code({ children }) {
|
||||
return <pre className="code-block"><code>{children}</code></pre>;
|
||||
}
|
||||
|
||||
const SECTIONS = [
|
||||
{ n: 1, label: '콘솔에서 시작' },
|
||||
{ n: 2, label: '변수와 타입' },
|
||||
{ n: 3, label: '함수' },
|
||||
{ n: 4, label: '배열과 객체' },
|
||||
{ n: 5, label: '조건과 반복' },
|
||||
{ n: 6, label: 'DOM과 이벤트' },
|
||||
{ n: 7, label: '비동기와 fetch' },
|
||||
{ n: 8, label: 'React로 가는 다리' },
|
||||
];
|
||||
|
||||
export default function JavascriptPage() {
|
||||
return (
|
||||
<div>
|
||||
{/* 히어로: 준비물은 브라우저 하나 — F12 콘솔이 곧 실습장 */}
|
||||
<div className="hero">
|
||||
<div className="eyebrow">Course · 소프트웨어</div>
|
||||
<h1>자바스크립트 기초<br />— 웹을 움직이는 언어</h1>
|
||||
<p>
|
||||
HTML이 뼈대, CSS가 옷이라면 자바스크립트는 <strong>움직임</strong>이에요.
|
||||
설치할 것도 없습니다 — 지금 이 페이지에서 <span className="kbd">F12</span>를 누르고{' '}
|
||||
<span className="kbd">Console</span> 탭을 열면 그게 바로 실습장이에요.
|
||||
이 코스의 모든 예제는 <strong>콘솔에 붙여넣으면 그 자리에서 실행</strong>됩니다.
|
||||
</p>
|
||||
<div className="chip-row">
|
||||
<span className="chip">예상 소요 90분</span>
|
||||
<span className="chip">선수 코스: 코딩 기초</span>
|
||||
<span className="chip">선수 코스: HTML/CSS</span>
|
||||
<span className="chip">준비물: F12 콘솔</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 섹션 바로가기 */}
|
||||
<nav className="pill-nav">
|
||||
{SECTIONS.map((s) => (
|
||||
<a key={s.n} href={`#sec-${s.n}`}>
|
||||
<b>{s.n}</b>
|
||||
{s.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<Section n={1} title="콘솔에서 시작" sub="설치 없이, 지금 바로 첫 코드">
|
||||
<p>
|
||||
<span className="kbd">F12</span>(맥은 <span className="kbd">Cmd+Option+I</span>)를 누르고{' '}
|
||||
<span className="kbd">Console</span> 탭을 여세요. 커서가 깜빡이는 곳이 자바스크립트와의
|
||||
대화창입니다. 뭐든 입력하고 Enter를 치면 <strong>즉시 답</strong>이 돌아와요 —
|
||||
계산기이자, 언어를 배우는 가장 빠른 놀이터예요.
|
||||
</p>
|
||||
<Code>{CODE_CONSOLE_START}</Code>
|
||||
<p>
|
||||
<span className="icode">console.log(...)</span>는 "괄호 안의 값을 콘솔에 찍어라"는
|
||||
함수예요. 앞으로 개발하면서 <strong>가장 많이 치게 될 코드</strong>입니다 —
|
||||
"이 변수에 지금 뭐가 들어 있지?"를 확인하는 손전등이거든요.
|
||||
</p>
|
||||
<Code>{CODE_CONSOLE_TRAP}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 위 예제를 전부 콘솔에 붙여넣고, 마지막으로
|
||||
내 이름으로 <span className="icode">console.log("OO의 첫 JS!")</span>를 찍어 보세요.
|
||||
빨간 에러가 나도 겁내지 마세요 — 콘솔에서는 <strong>부서지는 게 없어요</strong>.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={2} title="변수와 타입" sub="값에 이름표 붙이기 — const가 기본, let은 가끔, var는 안녕">
|
||||
<p>
|
||||
변수는 값에 붙이는 <strong>이름표</strong>예요. 사물함에 물건을 넣고 이름을 써 붙이는 것과
|
||||
같죠. 자바스크립트에는 이름표가 세 종류(<span className="icode">const</span>,{' '}
|
||||
<span className="icode">let</span>, <span className="icode">var</span>) 있는데,
|
||||
실전에서 쓰는 건 사실상 두 개뿐입니다.
|
||||
</p>
|
||||
<Code>{CODE_VARIABLES}</Code>
|
||||
<p>
|
||||
이름표에 붙는 값에는 <strong>타입</strong>(종류)이 있어요. 콘솔에서{' '}
|
||||
<span className="icode">typeof</span>로 직접 물어볼 수 있습니다.
|
||||
</p>
|
||||
<Code>{CODE_TYPES}</Code>
|
||||
<p>
|
||||
그리고 문자열을 다루는 가장 편한 도구, <strong>템플릿 문자열</strong>.
|
||||
따옴표 대신 <strong>백틱(`)</strong>으로 감싸면 문자열 안에{' '}
|
||||
<span className="icode">{'${값}'}</span>을 끼워 넣을 수 있어요.
|
||||
</p>
|
||||
<Code>{CODE_TEMPLATE}</Code>
|
||||
<div className="warn">
|
||||
<b>왜 var를 안 쓰나요?</b> var는 선언하기 전 줄에서 써도 에러가 안 나고,
|
||||
블록(중괄호) 밖으로 새어 나가는 등 <strong>실수를 조용히 삼키는</strong> 옛 문법이에요.
|
||||
우리 회사 코드를 포함해 현대 자바스크립트에서는 const/let만 씁니다.
|
||||
옛날 코드나 인터넷 예제에서 var를 보면 "아, 오래된 글이구나" 하고 알아채는 용도로만!
|
||||
</div>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 콘솔에서 <span className="icode">const me = "내이름"</span>을
|
||||
선언하고, 템플릿 문자열로 <span className="icode">{'`${me}는 미림 3학년`'}</span>을
|
||||
출력해 보세요. 그다음 <span className="icode">me = "다른이름"</span>을 시도해서
|
||||
const가 정말 재할당을 막는지 에러 메시지를 직접 읽어 보세요.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={3} title="함수" sub="코드에 이름을 붙인 자판기 — 그리고 화살표라는 요즘 문법">
|
||||
<p>
|
||||
함수는 <strong>자판기</strong>예요. 동전(매개변수)을 넣으면 음료(반환값)가 나오죠.
|
||||
같은 코드를 복사-붙여넣기하는 대신, 이름을 붙여 두고 필요할 때마다 호출합니다.
|
||||
</p>
|
||||
<Code>{CODE_FUNCTIONS}</Code>
|
||||
<p>
|
||||
두 문법은 대부분의 상황에서 같은 일을 해요. 그런데 우리 플랫폼의 React 코드를 열어 보면
|
||||
온통 화살표 함수(<span className="icode">{'=>'}</span>)입니다. 왜일까요?
|
||||
</p>
|
||||
<Code>{CODE_ARROW_WHY}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 콘솔에서 인사 자판기를 만들어 보세요:{' '}
|
||||
<span className="icode">{'const greet = (name) => `안녕, ${name}!`'}</span> 를 선언하고{' '}
|
||||
<span className="icode">greet("미림")</span>을 호출! 그다음 우리 플랫폼 코드(예:{' '}
|
||||
<span className="icode">frontend/src/pages</span> 폴더 아무 파일)를 열어{' '}
|
||||
<span className="icode">{'=>'}</span>가 몇 번 나오는지 에디터 검색(
|
||||
<span className="kbd">Ctrl+F</span>)으로 세어 보세요 — 정말 천지라는 걸 확인하게 될 거예요.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={4} title="배열과 객체" sub="순서 있는 목록과 이름표 붙은 서랍장 — 데이터의 두 그릇">
|
||||
<p>
|
||||
실전 데이터는 두 가지 그릇에 담겨 다녀요. <strong>배열</strong>은 순서 있는 목록
|
||||
(코스 목록, 학생 명단), <strong>객체</strong>는 이름표 붙은 서랍장(학생 한 명의 정보)입니다.
|
||||
</p>
|
||||
<Code>{CODE_ARRAY}</Code>
|
||||
<p>
|
||||
특히 <span className="icode">map</span>은 꼭 기억하세요. 우리 플랫폼의 코스 대시보드에
|
||||
카드가 주르륵 나오는 것, 네트워크 코스의 섹션 바로가기 버튼 8개가 그려지는 것 — 전부
|
||||
<strong> "배열.map(항목 → 화면조각)"</strong> 한 줄이에요. 실제로 이 페이지의 소스에도{' '}
|
||||
<span className="icode">{'SECTIONS.map((s) => ...)'}</span>가 있습니다.
|
||||
</p>
|
||||
<Code>{CODE_OBJECT}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 콘솔에서 우리 반 배열을 만들어 보세요:{' '}
|
||||
<span className="icode">const crew = ["나", "짝꿍", "멘토님"]</span> →{' '}
|
||||
<span className="icode">{'crew.map((c) => c + " 안녕!")'}</span>. 그다음 7섹션 예고편:{' '}
|
||||
<span className="icode">await (await fetch("/api/auth/me")).json()</span>을 실행하면
|
||||
서버가 주는 진짜 <strong>객체</strong>를 만날 수 있어요 — 점(.)으로 서랍을 열어 보세요!
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={5} title="조건과 반복" sub="갈림길과 되돌이표 — 그리고 '언제 map, 언제 for?'">
|
||||
<p>
|
||||
프로그램의 흐름은 딱 두 가지 도구로 조종해요. <strong>조건문</strong>은 갈림길
|
||||
("점수가 90 이상이면 이쪽"), <strong>반복문</strong>은 되돌이표("목록 끝까지 반복")입니다.
|
||||
</p>
|
||||
<Code>{CODE_IF}</Code>
|
||||
<Code>{CODE_LOOP}</Code>
|
||||
<p>
|
||||
정리하면 — <strong>결과물(새 배열)이 필요하면 map, 행동만 필요하면 for</strong>.
|
||||
React에서 화면을 그리는 건 "데이터 배열을 JSX 배열로 바꾸는 일"이라 map이 표준이고,
|
||||
단순히 로그를 찍거나 중간에 멈추고 싶을 땐 for가 자연스러워요.
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 콘솔에서 구구단 3단을 for로 출력해 보세요:{' '}
|
||||
<span className="icode">{'for (let i = 1; i <= 9; i++) console.log(`3 x ${i} = ${3 * i}`)'}</span>.
|
||||
그다음 같은 결과를 map으로:{' '}
|
||||
<span className="icode">{'[1,2,3,4,5,6,7,8,9].map((i) => `3 x ${i} = ${3 * i}`)'}</span>.
|
||||
for는 한 줄씩 "찍고" 끝나지만, map은 <strong>배열을 돌려준다</strong>는 차이를 눈으로 비교!
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={6} title="DOM과 이벤트" sub="화면을 손에 쥐기 — 지금 이 페이지로 장난쳐 보기">
|
||||
<p>
|
||||
브라우저는 HTML을 읽어 <strong>DOM</strong>이라는 "조작 가능한 문서 모형"을 만들어요.
|
||||
자바스크립트가 이 모형을 만지면 화면이 <strong>실시간으로</strong> 바뀝니다.
|
||||
레고 설명서(HTML)와 조립된 레고(DOM)의 관계 — 우리는 조립된 쪽을 만지는 거예요.
|
||||
</p>
|
||||
<Code>{CODE_DOM}</Code>
|
||||
<p>
|
||||
<span className="icode">document.querySelector("선택자")</span>는 HTML/CSS 코스에서 배운
|
||||
<strong> CSS 선택자 문법 그대로</strong> 요소를 찾아 줘요. 그리고{' '}
|
||||
<span className="icode">addEventListener("click", 함수)</span>는 "클릭되면 이 함수를
|
||||
실행해 줘"라고 <strong>예약</strong>을 걸어 두는 것 — 섹션 3에서 배운 화살표 함수가
|
||||
여기서 재료로 쓰입니다.
|
||||
</p>
|
||||
<Code>{CODE_DOM_RESET}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 위 예제로 지금 이 페이지의 히어로를 핑크로 만들고, 제목을
|
||||
내 마음대로 바꿔 보세요. 실컷 어지른 다음 <span className="kbd">F5</span> —
|
||||
전부 원상복구! <strong>학습 포인트:</strong> 콘솔의 변경은 서버의 원본이 아니라
|
||||
내 브라우저 속 모형(DOM)만 바꾸기 때문이에요. 새로고침은 원본을 다시 받아 오는 일이고요.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={7} title="비동기와 fetch" sub="서버 응답은 기다려야 한다 — 우리 API가 곧 실습장">
|
||||
<p>
|
||||
지금까지의 코드는 전부 <strong>즉시</strong> 결과가 나왔어요. 그런데 서버에 데이터를
|
||||
요청하면? 네트워크 코스에서 배웠듯 요청은 랜선과 바다를 건너 다녀옵니다 —
|
||||
아무리 빨라도 <strong>기다림</strong>이 생겨요. 이 기다림을 다루는 도구가{' '}
|
||||
<strong>Promise</strong>입니다.
|
||||
</p>
|
||||
<p>
|
||||
Promise는 <strong>택배 운송장</strong>이에요. 주문 버튼을 누르면 물건이 아니라 운송장이
|
||||
먼저 나오죠. "물건은 아직 없지만, 도착하면 알려줄게"라는 <strong>약속</strong> —
|
||||
그 운송장에 "도착하면 할 일"을 미리 적어 두는 게 <span className="icode">.then()</span>이고,
|
||||
"도착할 때까지 여기서 기다렸다 받기"처럼 자연스럽게 읽히게 해 주는 문법이{' '}
|
||||
<span className="icode">async/await</span>입니다.
|
||||
</p>
|
||||
<Code>{CODE_PROMISE}</Code>
|
||||
<p>
|
||||
이제 진짜를 해 볼 시간이에요. 여러분은 지금 이 플랫폼에 <strong>로그인돼 있으니</strong>,
|
||||
내 정보를 주는 우리 API를 콘솔에서 직접 호출할 수 있습니다.
|
||||
</p>
|
||||
<Code>{CODE_FETCH_REAL}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 위 fetch 예제를 실행하고, <span className="kbd">F12</span>의{' '}
|
||||
<span className="kbd">Network</span> 탭에서 방금 나간{' '}
|
||||
<span className="icode">/api/auth/me</span> 요청을 찾아 보세요. 네트워크 코스에서 본
|
||||
Status 200과, 응답(Response) 탭의 JSON이 콘솔에서 받은 객체와 <strong>같은 것</strong>임을
|
||||
확인하면 — 두 코스가 하나로 이어집니다.
|
||||
</div>
|
||||
<div className="warn">
|
||||
<b>주의</b> 콘솔 fetch는 "읽기" 연습까지만! 조회(GET) 말고 데이터를 바꾸는 요청을
|
||||
장난으로 보내면 진짜 데이터가 바뀔 수 있어요. 실습은 항상 멘토가 안내한 범위 안에서.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={8} title="React로 가는 다리" sub="오늘 배운 JS가 그대로 React가 된다">
|
||||
<p>
|
||||
축하해요 — 사실 여러분은 이미 React를 절반쯤 배웠습니다. React는 새로운 언어가 아니라
|
||||
<strong> 오늘 배운 자바스크립트를 화면 만들기에 특화해 쓰는 방법</strong>이거든요.
|
||||
</p>
|
||||
<Code>{CODE_REACT_BRIDGE}</Code>
|
||||
<ol className="olist">
|
||||
<li>
|
||||
<strong>컴포넌트 = 함수</strong> (섹션 3) — 이 페이지 자체도{' '}
|
||||
<span className="icode">JavascriptPage</span>라는 함수 하나예요.
|
||||
</li>
|
||||
<li>
|
||||
<strong>{'JSX의 { } = JS 표현식'}</strong> — 그 안에서 섹션 4의 map, 섹션 5의 삼항이
|
||||
매일 일합니다.
|
||||
</li>
|
||||
<li>
|
||||
<strong>useState ≠ 그냥 변수</strong> (섹션 2) — 값만 바꾸면 화면은 모릅니다.
|
||||
"바꿨으니 다시 그려!"까지 해 주는 게 setXxx예요.
|
||||
</li>
|
||||
</ol>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기 (미니 과제)</b> 에디터로{' '}
|
||||
<span className="icode">frontend/src/pages/JavascriptPage.jsx</span> — 바로 이 파일! —
|
||||
를 열어 보세요. 백틱 문자열 상수(섹션 2), 화살표 함수(섹션 3),{' '}
|
||||
<span className="icode">SECTIONS.map</span>(섹션 4), 구조분해{' '}
|
||||
<span className="icode">{'{ n, title, sub, children }'}</span>(섹션 4)을 전부 찾아
|
||||
형광펜을 쳐 보세요. 찾은 목록은 <span className="icode">STUDY.md</span> 미니 과제에
|
||||
기록해서 멘토에게 공유!
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<div className="step-card">
|
||||
<div className="step-body">
|
||||
<h3 style={{ marginBottom: 6 }}>⚡ 여기까지 왔다면</h3>
|
||||
<p className="muted">
|
||||
콘솔 한 줄에서 시작해 우리 서버의 API 호출까지 — 웹을 <strong>움직이는</strong> 언어의
|
||||
뼈대를 다 만졌어요. 개념이 흔들리면 <Link to="/learn/coding"><strong>코딩 기초</strong></Link>{' '}
|
||||
코스로 돌아가 복습하고, 이제 내 PC에 진짜 개발 환경을 갖출 준비가 됐다면{' '}
|
||||
<Link to="/setup"><strong>개발 환경 설치 가이드</strong></Link>로 넘어가세요.
|
||||
다음 정거장은 React — 오늘 놓은 다리를 건너기만 하면 됩니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -38,6 +38,26 @@ const COURSES = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: '서버와 데이터',
|
||||
desc: '백엔드 개발자가 매일 쓰는 도구들.',
|
||||
items: [
|
||||
{
|
||||
to: '/learn/linux',
|
||||
icon: '🐧',
|
||||
title: '리눅스 / 터미널 기초',
|
||||
desc: '세상의 서버 대부분은 리눅스로 돌아가요. 터미널 명령어부터 SSH로 서버 접속까지.',
|
||||
level: '입문',
|
||||
},
|
||||
{
|
||||
to: '/learn/sql',
|
||||
icon: '🗄️',
|
||||
title: 'SQL 입문',
|
||||
desc: '데이터베이스에게 말 거는 언어. 우리 플랫폼의 진짜 테이블로 SELECT부터 JOIN까지.',
|
||||
level: '입문',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: '소프트웨어',
|
||||
desc: '이제 코드를 읽고 쓰는 세계로.',
|
||||
@ -49,6 +69,20 @@ const COURSES = [
|
||||
desc: '변수·함수·조건·반복을 이 플랫폼의 실제 코드로 배워요. 여러분이 누른 로그인 버튼도 코드예요.',
|
||||
level: '입문',
|
||||
},
|
||||
{
|
||||
to: '/learn/htmlcss',
|
||||
icon: '🧱',
|
||||
title: 'HTML / CSS 기초',
|
||||
desc: '웹페이지의 뼈대와 옷. 박스 모델·플렉스 레이아웃을 우리 플랫폼의 실제 스타일로 배워요.',
|
||||
level: '입문',
|
||||
},
|
||||
{
|
||||
to: '/learn/javascript',
|
||||
icon: '⚡',
|
||||
title: '자바스크립트 기초',
|
||||
desc: '웹을 움직이게 하는 언어. 변수에서 이벤트·fetch까지 — React를 배우기 전 필수 코스.',
|
||||
level: '입문',
|
||||
},
|
||||
{
|
||||
to: '/setup',
|
||||
icon: '🛠️',
|
||||
@ -58,6 +92,26 @@ const COURSES = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: '디자인',
|
||||
desc: '보기 좋은 것을 넘어, 쓰기 좋은 것을 만드는 법.',
|
||||
items: [
|
||||
{
|
||||
to: '/learn/uiux',
|
||||
icon: '🎨',
|
||||
title: 'UI/UX 기초',
|
||||
desc: '사용자는 왜 헤맬까? 좋은 화면의 원칙들을 우리 플랫폼 화면을 뜯어보며 배워요.',
|
||||
level: '입문',
|
||||
},
|
||||
{
|
||||
to: '/learn/figma',
|
||||
icon: '🖌️',
|
||||
title: '피그마와 디자인 툴',
|
||||
desc: '디자이너의 작업대. 프레임·오토레이아웃·컴포넌트, 그리고 개발자에게 넘기는 핸드오프까지.',
|
||||
level: '실습',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default function LearnHubPage() {
|
||||
|
||||
427
frontend/src/pages/LinuxPage.jsx
Normal file
427
frontend/src/pages/LinuxPage.jsx
Normal file
@ -0,0 +1,427 @@
|
||||
// 이 파일이 하는 일: "리눅스 / 터미널 기초" 코스 — 세상 서버 대부분이 왜 리눅스인지에서 출발해
|
||||
// 터미널 명령어(길 찾기·파일 다루기·읽기), 권한, 프로세스, 그리고 SSH 원격 접속까지
|
||||
// 8개 섹션으로 안내하는 정적 학습 페이지.
|
||||
// (왜 리눅스 → 터미널 첫걸음 → 길 찾기 → 파일 다루기 → 파일 읽기 → 권한/sudo → 프로세스 → SSH)
|
||||
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 global.css의
|
||||
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용한다 — 색을 여기서 하드코딩하지 않는다.
|
||||
// 명령어 예시·출력·표는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// ── 명령어 예시 · 출력 · 표 상수들 ──
|
||||
|
||||
const CODE_GUI_CLI = `윈도우 데스크톱 (GUI) 리눅스 서버 (CLI)
|
||||
────────────────────── ──────────────────────
|
||||
마우스로 아이콘 더블클릭 키보드로 명령어 입력
|
||||
화면·그래픽카드 필요 화면이 아예 없어도 됨
|
||||
사람 한 명이 한 대씩 조작 명령을 파일(스크립트)로 저장해
|
||||
서버 100대에 똑같이 자동 실행
|
||||
켜 두면 무거움 24시간 365일 가볍게 동작
|
||||
|
||||
→ 서버실의 서버에는 모니터가 안 붙어 있어요.
|
||||
"화면 없는 컴퓨터와 대화하는 법" = 오늘 배우는 터미널입니다.`;
|
||||
|
||||
const CODE_PROMPT = `Git Bash를 열면 이런 한 줄이 기다리고 있어요 — 프롬프트(prompt)라고 합니다.
|
||||
|
||||
user@DESKTOP-A1B2C3 MINGW64 ~
|
||||
$
|
||||
│ │ │ │
|
||||
│ │ │ └ $ = "명령을 입력하세요" 신호 (관리자면 #)
|
||||
│ │ └ ~ = 지금 내가 있는 위치 (~는 내 홈 폴더)
|
||||
│ └ 컴퓨터 이름 (host)
|
||||
└ 사용자 이름 (user)
|
||||
|
||||
리눅스 서버라면: ubuntu@ip-172-31-5-10:~$
|
||||
읽는 법은 똑같아요 — "누가 @ 어느 컴퓨터에서 : 어디에 있다 $"`;
|
||||
|
||||
const CODE_NAVIGATE = `$ pwd # Print Working Directory — "나 지금 어디지?"
|
||||
/c/Users/mirim # ← Windows의 C:\\Users\\mirim 을 이렇게 표기해요
|
||||
|
||||
$ ls # List — "여기 뭐가 있지?"
|
||||
Desktop Documents Downloads ...
|
||||
|
||||
$ ls -l # 자세히 (권한·크기·날짜까지)
|
||||
$ ls -a # 숨김 파일(.으로 시작)까지 전부
|
||||
|
||||
$ cd Documents # Change Directory — 이동!
|
||||
$ cd .. # 한 칸 위(부모 폴더)로
|
||||
$ cd ~ # 어디에 있든 홈으로 순간이동
|
||||
$ cd /c/awesomedev # 절대 경로로 바로 이동`;
|
||||
|
||||
const CODE_PATH = `절대 경로 vs 상대 경로 — 주소를 말하는 두 가지 방법
|
||||
|
||||
절대 경로: 루트(/)부터 전부 다 말하기
|
||||
/c/awesomedev/mirim-app/frontend
|
||||
→ "서울시 ○○구 ○○로 12, 3층" — 어디서 말해도 같은 곳
|
||||
|
||||
상대 경로: 지금 위치 기준으로 말하기
|
||||
./frontend → "여기서 frontend 폴더로" (.은 현재 위치)
|
||||
../ → "한 칸 위로"
|
||||
../../platform → "두 칸 위로 갔다가 platform으로"
|
||||
→ "여기서 두 블록 직진 후 우회전" — 내 위치에 따라 달라짐
|
||||
|
||||
특별 기호 정리:
|
||||
~ 내 홈 폴더 . 현재 폴더
|
||||
/ 루트(최상위) .. 부모 폴더(한 칸 위)`;
|
||||
|
||||
const CODE_FILES = `$ mkdir practice # Make Directory — 폴더 만들기
|
||||
$ cd practice
|
||||
$ touch memo.txt # 빈 파일 만들기
|
||||
$ cp memo.txt memo2.txt # Copy — 복사 (원본 → 사본)
|
||||
$ mv memo2.txt note.txt # Move — 이동인데, 같은 폴더 안에서 쓰면 '이름 바꾸기'
|
||||
$ mv note.txt ../ # 진짜 이동 — 한 칸 위 폴더로 보내기
|
||||
$ rm memo.txt # Remove — 삭제 (⚠ 휴지통 없음!)
|
||||
$ cd ..
|
||||
$ rm -r practice # 폴더는 -r(recursive, 안의 것까지 재귀적으로)을 붙여야 지워져요`;
|
||||
|
||||
const CODE_READ = `$ cat app.log # 파일 내용을 한 번에 좌르륵 출력 (짧은 파일용)
|
||||
$ less app.log # 긴 파일을 한 페이지씩 (↑↓ 스크롤, q로 나가기)
|
||||
$ head -n 20 app.log # 앞에서 20줄만
|
||||
$ tail -n 20 app.log # 뒤에서 20줄만 — 로그는 최신이 아래에 쌓이니까!
|
||||
|
||||
$ tail -f app.log # ★ Follow — 파일에 새 줄이 쌓이는 걸 실시간 중계
|
||||
# (서버 개발자가 하루 종일 켜 두는 화면. Ctrl+C로 종료)
|
||||
|
||||
$ grep "ERROR" app.log # 파일에서 "ERROR"가 들어간 줄만 골라내기
|
||||
$ grep -i "error" app.log # -i: 대소문자 무시
|
||||
$ grep -n "ERROR" app.log # -n: 몇 번째 줄인지 번호도 같이
|
||||
|
||||
# ★★ 실무에서 제일 많이 치는 조합 — 파이프(|)로 두 명령을 연결:
|
||||
$ tail -f app.log | grep ERROR
|
||||
# "실시간으로 흘러오는 로그 중에서, ERROR 줄만 보여줘"
|
||||
# 장애가 났을 때 서버 개발자가 제일 먼저 치는 한 줄입니다.`;
|
||||
|
||||
const CODE_PERMISSION = `$ ls -l
|
||||
-rwxr-xr-- 1 ubuntu dev 1024 Jul 16 10:00 deploy.sh
|
||||
│└┬┘└┬┘└┬┘ └──┬─┘ └┬┘
|
||||
│ │ │ │ │ └ 그룹 이름
|
||||
│ │ │ │ └ 주인(owner) 이름
|
||||
│ │ │ └ 그 외 모두(others)의 권한: r-- (읽기만)
|
||||
│ │ └ 그룹(group)의 권한: r-x (읽기·실행)
|
||||
│ └ 주인(owner)의 권한: rwx (읽기·쓰기·실행)
|
||||
└ 첫 글자: - 파일 / d 폴더
|
||||
|
||||
r = read(읽기) w = write(쓰기) x = execute(실행)
|
||||
→ 세 글자씩 세 묶음: [주인][그룹][모두] — 집 열쇠를
|
||||
"우리 가족 / 친척 / 지나가는 사람"에게 각각 다르게 주는 것과 같아요.
|
||||
|
||||
$ chmod +x deploy.sh # 이 파일에 실행(x) 권한 부여
|
||||
# "다운받은 스크립트가 실행이 안 돼요"의 단골 해결책
|
||||
|
||||
$ sudo apt update # sudo = SuperUser DO, 이 명령 한 번만 관리자로 실행`;
|
||||
|
||||
const CODE_PROCESS = `$ ps # 지금 내 터미널에서 돌고 있는 프로세스 목록
|
||||
$ ps aux # 시스템 전체 프로세스 (서버에서 자주 씀)
|
||||
$ top # 실시간 모니터링 — 작업관리자의 터미널판! (q로 나가기)
|
||||
|
||||
PID USER %CPU %MEM COMMAND
|
||||
1234 ubuntu 2.1 5.3 java -jar app.jar ← 우리 백엔드!
|
||||
1301 ubuntu 0.5 1.2 node server.js
|
||||
└ PID = 프로세스마다 붙는 고유 번호(주민번호 같은 것)
|
||||
|
||||
$ kill 1234 # PID 1234에게 "정리하고 종료해 줘" 정중히 요청
|
||||
$ kill -9 1234 # 강제 종료 — 말 안 들을 때 최후의 수단 (저장 못 하고 죽음)
|
||||
|
||||
$ ./run.sh & # 맨 뒤 & = 백그라운드 실행
|
||||
# 터미널을 붙잡지 않고 뒤에서 돌게 하기 —
|
||||
# 서버 프로그램이 24시간 도는 원리의 출발점이에요.`;
|
||||
|
||||
const CODE_SSH = `$ ssh ubuntu@13.125.x.x # "13.125.x.x 서버에 ubuntu 사용자로 들어갈게"
|
||||
ubuntu@ip-172-31-5-10:~$ # ← 프롬프트가 바뀌었다 = 이제 내가 치는 명령은
|
||||
# 전부 그 서버에서 실행된다!
|
||||
|
||||
# 비밀번호 대신 열쇠 파일(키)을 쓰는 게 표준이에요:
|
||||
$ ssh -i mykey.pem ubuntu@13.125.x.x
|
||||
# └ .pem = 나만 가진 열쇠 파일. 이게 곧 서버 출입증이라
|
||||
# 절대 카톡·깃허브·공유폴더에 올리면 안 됩니다!
|
||||
|
||||
$ exit # 서버에서 나와 내 PC로 복귀`;
|
||||
|
||||
const CODE_CHEATSHEET = `━━━ 오늘 배운 명령어 치트시트 (한 장 요약) ━━━
|
||||
|
||||
[길 찾기] [파일 다루기] [파일 읽기]
|
||||
pwd 나 어디? mkdir 폴더 생성 cat 전체 출력
|
||||
ls 뭐 있지? touch 빈 파일 less 페이지 넘기며
|
||||
ls -l 자세히 cp 복사 head 앞부분만
|
||||
cd 이동 mv 이동/이름변경 tail 뒷부분만
|
||||
cd ~ 홈으로 rm 삭제(휴지통X!) tail -f 실시간 중계
|
||||
cd .. 위로 rm -r 폴더 삭제 grep 골라내기
|
||||
|
||||
[권한] [프로세스]
|
||||
ls -l rwx 확인 ps / top 목록 / 실시간
|
||||
chmod +x 실행권한 부여 kill PID 종료 요청
|
||||
sudo 관리자로 한 번 실행 명령 뒤 & 백그라운드 실행
|
||||
|
||||
[원격 접속]
|
||||
ssh -i 키.pem user@서버주소 서버 접속
|
||||
exit 접속 종료
|
||||
|
||||
[조합의 마법]
|
||||
tail -f app.log | grep ERROR 실시간 로그에서 에러만!`;
|
||||
|
||||
// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
|
||||
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. props만 바꿔 재사용.
|
||||
function Section({ n, title, sub, children }) {
|
||||
return (
|
||||
<section className="step-card" id={`sec-${n}`}>
|
||||
<div className="step-head">
|
||||
<span className="step-num">{n}</span>
|
||||
<div>
|
||||
<h3>{title}</h3>
|
||||
{sub && <span className="step-sub">{sub}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="step-body">{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Code({ children }) {
|
||||
return <pre className="code-block"><code>{children}</code></pre>;
|
||||
}
|
||||
|
||||
const SECTIONS = [
|
||||
{ n: 1, label: '왜 리눅스인가' },
|
||||
{ n: 2, label: '터미널 첫걸음' },
|
||||
{ n: 3, label: '길 찾기 명령' },
|
||||
{ n: 4, label: '파일 다루기' },
|
||||
{ n: 5, label: '파일 읽기' },
|
||||
{ n: 6, label: '권한과 sudo' },
|
||||
{ n: 7, label: '프로세스와 서비스' },
|
||||
{ n: 8, label: 'SSH — 원격 접속' },
|
||||
];
|
||||
|
||||
export default function LinuxPage() {
|
||||
return (
|
||||
<div>
|
||||
{/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
|
||||
<div className="hero">
|
||||
<div className="eyebrow">Course · 서버와 데이터</div>
|
||||
<h1>리눅스 / 터미널 기초<br />— 서버의 세계로</h1>
|
||||
<p>
|
||||
여러분이 매일 쓰는 우리 서비스도, 유튜브도, 게임 서버도 — 전부{' '}
|
||||
<strong>리눅스 서버</strong> 위에서 돌아가요. 그런데 서버에는 마우스도 화면도 없습니다.
|
||||
키보드로 컴퓨터와 대화하는 법, <strong>터미널</strong>을 오늘 배워요.
|
||||
각 섹션의 <strong>"직접 확인해 보기"</strong>는 꼭 손으로 쳐 보세요 —
|
||||
명령어는 눈이 아니라 손가락이 외웁니다.
|
||||
</p>
|
||||
<div className="chip-row">
|
||||
<span className="chip">예상 소요 60분</span>
|
||||
<span className="chip">준비물: Git Bash</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 섹션 바로가기 */}
|
||||
<nav className="pill-nav">
|
||||
{SECTIONS.map((s) => (
|
||||
<a key={s.n} href={`#sec-${s.n}`}>
|
||||
<b>{s.n}</b>
|
||||
{s.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<Section n={1} title="왜 리눅스인가" sub="세상 서버의 대부분이 리눅스인 이유">
|
||||
<p>
|
||||
전 세계 서버의 대부분은 <strong>리눅스</strong>로 돌아가요. 이유는 세 가지입니다.
|
||||
</p>
|
||||
<ol className="olist">
|
||||
<li><strong>무료</strong> — 서버 1,000대를 운영해도 운영체제 값이 0원이에요. 윈도우 서버는 대수만큼 돈을 냅니다.</li>
|
||||
<li><strong>안정</strong> — 한 번 켜면 몇 년씩 재부팅 없이 도는 게 목표인 운영체제예요. 24시간 장사하는 편의점 같은 서버에 딱 맞죠.</li>
|
||||
<li><strong>자동화</strong> — 모든 작업을 명령어로 하니까, 명령어를 파일로 저장하면 <strong>사람 없이 반복 실행</strong>이 됩니다. 서버 100대에 같은 설정을 1분 만에 뿌릴 수 있어요.</li>
|
||||
</ol>
|
||||
<p>
|
||||
우리 학습 플랫폼도 <strong>AWS의 리눅스 서버</strong> 위에서 돌아갑니다.
|
||||
여러분이 지금 보는 이 페이지도 리눅스 서버가 보내 준 거예요.
|
||||
</p>
|
||||
<p>
|
||||
그런데 결정적 차이가 하나 있어요 — <strong>서버엔 화면이 없습니다!</strong>
|
||||
</p>
|
||||
<Code>{CODE_GUI_CLI}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 지금 쓰는 웹 서비스 아무거나 하나 떠올려 보세요
|
||||
(유튜브, 카톡, 게임...). "이것도 어딘가의 화면 없는 리눅스 서버에서 돌고 있겠구나"
|
||||
하고 생각해 보면, 오늘 배우는 내용이 왜 필요한지 감이 옵니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={2} title="터미널 첫걸음" sub="키보드로 컴퓨터와 대화하기">
|
||||
<p>
|
||||
<strong>터미널</strong>은 키보드로 컴퓨터와 대화하는 창구예요. 내가 한 줄 말하면(명령),
|
||||
컴퓨터가 한 줄 답하는(출력) <strong>채팅창</strong>이라고 생각하면 됩니다.
|
||||
단, 상대가 사람이 아니라서 <strong>정확한 문법</strong>으로만 알아들어요 —
|
||||
오타 하나에 "그런 명령 몰라요(command not found)"라고 답하죠.
|
||||
</p>
|
||||
<p>
|
||||
좋은 소식: 여러분 PC에는 이미 연습장이 깔려 있어요. Git을 설치할 때 같이 들어온{' '}
|
||||
<strong>Git Bash</strong>가 윈도우 위에서 리눅스 명령어를 흉내내 줍니다.
|
||||
<span className="kbd">시작 메뉴</span>에서 <span className="kbd">Git Bash</span>를
|
||||
검색해 열어 보세요.
|
||||
</p>
|
||||
<p>열자마자 보이는 한 줄, <strong>프롬프트</strong> 읽는 법부터 배웁니다.</p>
|
||||
<Code>{CODE_PROMPT}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> Git Bash를 열고 프롬프트에서 내 사용자 이름과 컴퓨터 이름을
|
||||
찾아 읽어 보세요. 그리고 <span className="icode">echo 안녕</span>을 쳐 보세요 —
|
||||
컴퓨터가 그대로 따라 말하면, 첫 대화 성공입니다!
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={3} title="길 찾기 명령 — pwd · ls · cd" sub="내가 어딨지? 뭐가 있지? 저기로 가자!">
|
||||
<p>
|
||||
터미널에는 폴더 창이 없으니, 항상 세 가지 질문으로 움직여요.{' '}
|
||||
<strong>내가 어디에 있지?</strong>(<span className="icode">pwd</span>)
|
||||
— <strong>여긴 뭐가 있지?</strong>(<span className="icode">ls</span>)
|
||||
— <strong>저기로 가자!</strong>(<span className="icode">cd</span>).
|
||||
낯선 건물에서 "현 위치" 지도를 보고, 층별 안내를 읽고, 계단으로 이동하는 것과 똑같습니다.
|
||||
</p>
|
||||
<Code>{CODE_NAVIGATE}</Code>
|
||||
<p>이동하려면 <strong>주소(경로)</strong>를 말하는 법을 알아야 해요.</p>
|
||||
<Code>{CODE_PATH}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> Git Bash에서 <span className="icode">cd /c/awesomedev/mirim-app</span>으로
|
||||
우리 프로젝트 폴더까지 이동한 뒤, <span className="icode">pwd</span>로 위치를 확인하고{' '}
|
||||
<span className="icode">ls</span>로 안에 뭐가 있는지 구경해 보세요.
|
||||
<span className="icode">cd fro</span>까지 치고 <span className="kbd">Tab</span> 키를 누르면
|
||||
자동완성이 됩니다 — 터미널 고수의 필수 습관!
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={4} title="파일 다루기 — mkdir · touch · cp · mv · rm" sub="만들고, 복사하고, 옮기고, 지우고">
|
||||
<p>
|
||||
마우스 우클릭으로 하던 일을 전부 명령어로 할 수 있어요. 다섯 개면 충분합니다.
|
||||
</p>
|
||||
<Code>{CODE_FILES}</Code>
|
||||
<div className="warn">
|
||||
<b>rm에는 휴지통이 없다!</b> 윈도우에서 지운 파일은 휴지통에서 되살릴 수 있지만,{' '}
|
||||
<span className="icode">rm</span>은 <strong>즉시, 영구히</strong> 삭제합니다.
|
||||
특히 <span className="icode">rm -rf</span>(강제로+폴더째)는 서버 전체를 한 줄에 날릴 수
|
||||
있는 명령이에요. 실제로 회사들이 이 명령 실수로 서비스 전체를 잃은 사고가 여럿 있습니다.
|
||||
지우기 전에 <span className="icode">pwd</span>로 위치 확인, <span className="icode">ls</span>로
|
||||
대상 확인 — 두 번 보는 습관을 들이세요.
|
||||
</div>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 홈 폴더(<span className="icode">cd ~</span>)에서{' '}
|
||||
<span className="icode">mkdir practice</span>로 연습 폴더를 만들고, 위 예시를 한 줄씩
|
||||
그대로 따라 쳐 보세요. 한 줄 칠 때마다 <span className="icode">ls</span>로 결과를 확인하고,
|
||||
마지막에 <span className="icode">rm -r practice</span>로 깨끗이 정리하면 실습 끝!
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={5} title="파일 읽기 — cat · less · tail · grep" sub="서버 개발자의 일상: 로그 읽기">
|
||||
<p>
|
||||
서버엔 메모장이 없으니 파일도 명령어로 읽어요. 특히 서버가 남기는 일기장인{' '}
|
||||
<strong>로그(log) 파일</strong>을 읽는 건 서버 개발자의 매일 하는 일과입니다.
|
||||
"서비스가 이상해요!" 신고가 오면 제일 먼저 하는 일이 로그 열어 보기거든요.
|
||||
</p>
|
||||
<Code>{CODE_READ}</Code>
|
||||
<p>
|
||||
<span className="icode">tail -f</span>는 로그 파일에 새 줄이 쌓이는 걸{' '}
|
||||
<strong>실시간으로 중계</strong>해 줘요. CCTV 생중계 화면처럼요. 그리고{' '}
|
||||
<span className="icode">grep</span>은 수만 줄에서 원하는 단어가 든 줄만 골라내는{' '}
|
||||
<strong>형광펜</strong>입니다. 이 둘을 파이프(<span className="icode">|</span>)로 이으면 —
|
||||
"실시간 CCTV에 형광펜 필터 달기"가 되죠. 실무에서 제일 많이 쓰는 조합이에요.
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> Git Bash에서 연습해 봐요. 창을 두 개 열고,
|
||||
첫 창에서 <span className="icode">tail -f ~/test.log</span>를 켜 두세요.
|
||||
두 번째 창에서 <span className="icode">echo "hello" >> ~/test.log</span>,{' '}
|
||||
<span className="icode">echo "ERROR: 큰일남" >> ~/test.log</span>를 치면 —
|
||||
첫 창에 실시간으로 나타납니다! <span className="icode">grep ERROR ~/test.log</span>로
|
||||
에러 줄만 골라내는 것까지 해 보면, 방금 서버 개발자의 하루를 체험한 거예요.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={6} title="권한과 sudo" sub="rwx 세 글자와 관리자 모자">
|
||||
<p>
|
||||
리눅스 서버는 여러 사람(그리고 여러 프로그램)이 같이 쓰는 컴퓨터예요.
|
||||
그래서 모든 파일에 <strong>"누가 뭘 할 수 있는지"</strong> 딱지가 붙어 있습니다.{' '}
|
||||
<span className="icode">ls -l</span>로 그 딱지를 읽을 수 있어요.
|
||||
</p>
|
||||
<Code>{CODE_PERMISSION}</Code>
|
||||
<p>
|
||||
<span className="icode">sudo</span>는 <strong>관리자 모자를 잠깐 쓰는 것</strong>이에요.
|
||||
평소엔 일반 사원 권한으로 일하다가, 시스템 설정처럼 큰 권한이 필요한 일만
|
||||
모자를 쓰고 처리하고, 바로 벗습니다. 처음부터 끝까지 관리자로 사는 게 아니라요.
|
||||
</p>
|
||||
<div className="warn">
|
||||
<b>sudo를 함부로 쓰지 않기</b> <span className="icode">sudo</span>를 붙이면 리눅스는
|
||||
"정말 아는 사람이구나" 하고 <strong>아무 확인 없이</strong> 시키는 대로 다 합니다.
|
||||
섹션 4의 <span className="icode">rm -rf</span>에 <span className="icode">sudo</span>까지
|
||||
붙으면 시스템 파일도 지워져요. 원칙: <strong>에러가 나서 인터넷에서 찾은 명령에{' '}
|
||||
sudo가 붙어 있으면, 실행 전에 그 명령이 뭘 하는지 먼저 이해하기.</strong>
|
||||
회사 서버에서는 멘토에게 물어본 뒤에!
|
||||
</div>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> Git Bash에서 <span className="icode">ls -l</span>을 쳐 보세요.
|
||||
아무 줄이나 골라 첫 10글자를 소리 내어 해석해 보기 —
|
||||
"폴더고, 주인은 다 되고, 그룹은 읽기·실행만..." 이렇게 읽어내면 통과입니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={7} title="프로세스와 서비스" sub="작업관리자의 터미널판">
|
||||
<p>
|
||||
실행 중인 프로그램 하나하나를 <strong>프로세스</strong>라고 불러요.
|
||||
윈도우에서 <span className="kbd">Ctrl+Shift+Esc</span>로 여는 작업관리자,
|
||||
그 터미널판이 <span className="icode">ps</span>와 <span className="icode">top</span>입니다.
|
||||
서버가 느려지면 "누가 CPU를 다 먹고 있지?" 하고 <span className="icode">top</span>부터 열죠.
|
||||
</p>
|
||||
<Code>{CODE_PROCESS}</Code>
|
||||
<p>
|
||||
백그라운드 실행(<span className="icode">&</span>)이 중요한 이유 — 서버 프로그램은
|
||||
내가 터미널을 꺼도 계속 돌아야 하잖아요. "뒤에서 돌게 하기"가 그 출발점입니다.
|
||||
그리고 하나 더: 요즘 서버에서 많이 쓰는 <strong>도커 컨테이너</strong>도, 겉보기엔
|
||||
특별해 보이지만 서버 입장에선 결국 <strong>프로세스 하나</strong>예요.{' '}
|
||||
<span className="icode">ps</span>에 다 잡힙니다 — 나중에 도커를 배울 때 이 문장을 떠올려 보세요.
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> Git Bash에서 <span className="icode">ps</span>를 쳐 보세요 —
|
||||
지금 떠 있는 bash 자신이 보일 거예요. 더 실감나게는 윈도우 작업관리자
|
||||
(<span className="kbd">Ctrl+Shift+Esc</span>)를 옆에 띄워 놓고 "아, 이 목록의
|
||||
터미널 버전이 ps구나" 하고 비교해 보세요. PID 열이 양쪽에 다 있습니다!
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={8} title="SSH — 원격 서버 접속" sub="지구 반대편 서버의 터미널을 내 앞으로">
|
||||
<p>
|
||||
드디어 오늘의 최종 보스. AWS 서버는 데이터센터 어딘가에 있는데, 우리는 어떻게
|
||||
그 서버에 명령을 칠까요? <strong>SSH</strong>(Secure Shell)는 <strong>원격 서버의
|
||||
터미널을 암호화된 통로로 내 화면에 끌어오는</strong> 기술이에요.
|
||||
접속한 순간부터 내가 치는 명령은 전부 <strong>그 서버에서</strong> 실행됩니다.
|
||||
</p>
|
||||
<Code>{CODE_SSH}</Code>
|
||||
<p>
|
||||
비밀번호 대신 <strong>키 파일(.pem)</strong>을 쓰는 게 표준이에요. 서버에는 자물쇠(공개키),
|
||||
나에게는 열쇠(개인키)가 있어서, 열쇠 파일을 가진 사람만 들어갈 수 있습니다.
|
||||
비밀번호보다 훨씬 안전하지만 — 뒤집으면 <strong>열쇠 파일이 유출되면 누구든 우리 서버에
|
||||
들어올 수 있다</strong>는 뜻이기도 해요.
|
||||
</p>
|
||||
<div className="warn">
|
||||
<b>실제 접속은 멘토와 함께</b> 우리 회사 AWS 서버도 이렇게 SSH로 관리합니다.
|
||||
다만 운영 서버는 실수 한 번이 실제 서비스 장애로 이어지는 곳이라, 접속 권한과 키 발급은{' '}
|
||||
<strong>멘토와 함께 보안 안내를 받은 뒤에</strong> 진행해요. 그리고 .pem 파일은
|
||||
절대 메신저·저장소·공유폴더에 올리지 않기 — 서버 열쇠니까요.
|
||||
</div>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> Git Bash에서 <span className="icode">ssh -V</span>를 쳐 보세요.
|
||||
버전이 나오면 여러분 PC에 이미 SSH가 준비돼 있다는 뜻입니다.
|
||||
지금은 연결할 서버가 없어도, 명령이 손에 익어 있으면 멘토와 첫 접속하는 날 훨씬 수월해요.
|
||||
</div>
|
||||
<p>마지막으로, 오늘 배운 것 전부를 한 장에 담았어요. 인쇄해서 모니터 옆에 붙여 두세요.</p>
|
||||
<Code>{CODE_CHEATSHEET}</Code>
|
||||
</Section>
|
||||
|
||||
<div className="step-card">
|
||||
<div className="step-body">
|
||||
<h3 style={{ marginBottom: 6 }}>🐧 여기까지 왔다면</h3>
|
||||
<p className="muted">
|
||||
이제 화면 없는 서버와 키보드로 대화하고, 로그를 읽고, 원격으로 들어가는 법까지
|
||||
알게 됐어요. 서버에 들어갔으니 이제 <strong>데이터</strong>를 만나러 갈 차례 —{' '}
|
||||
<Link to="/learn/sql"><strong>SQL 입문</strong></Link> 코스에서 서버 안에 잠들어 있는
|
||||
데이터베이스에게 질문하는 법을 이어서 배워 보세요.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
525
frontend/src/pages/SqlPage.jsx
Normal file
525
frontend/src/pages/SqlPage.jsx
Normal file
@ -0,0 +1,525 @@
|
||||
// 이 파일이 하는 일: "SQL 입문" 코스 — 데이터베이스가 뭔지부터 시작해
|
||||
// SELECT/WHERE/ORDER BY/쓰기 3형제/JOIN/JPA와의 관계/psql 실습까지 8개 섹션으로 안내하는 정적 학습 페이지.
|
||||
// 특징: 예제 테이블이 전부 '이 학습 플랫폼의 진짜 스키마'다 —
|
||||
// users / assignment / submission / checklist_item / progress.
|
||||
// 여러분이 체크리스트를 하나 누를 때마다 progress 테이블에 행이 하나 생긴다. 그걸 직접 조회해 본다.
|
||||
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 global.css의
|
||||
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용한다 — 색을 여기서 하드코딩하지 않는다.
|
||||
// SQL 예제·표는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// ── SQL 예제 · 표 상수들 ──
|
||||
|
||||
const CODE_TABLES = `우리 학습 플랫폼의 진짜 테이블 5개 (PostgreSQL, JPA가 생성)
|
||||
|
||||
테이블 무엇을 담나 주요 열
|
||||
──────────────────────────────────────────────────────────────────
|
||||
users 계정 (여러분!) id, username, password_hash,
|
||||
name, role, track, created_at
|
||||
assignment 과제 id, week, day, kind, title, summary
|
||||
submission 과제 제출물 id, assignment_id, user_id, content,
|
||||
link, submitted_at, feedback, status
|
||||
checklist_item 주차별 체크리스트 항목 id, week, day, label, sort_order
|
||||
progress "체크했다"는 기록 id, user_id, item_id, checked_at
|
||||
|
||||
→ 엑셀로 치면: 파일 하나(데이터베이스) 안에 시트 5장(테이블).
|
||||
시트의 행 하나 = 데이터 한 건, 열 하나 = 속성.`;
|
||||
|
||||
const CODE_SELECT_BASIC = `-- 기본형: SELECT 보고싶은열 FROM 테이블;
|
||||
|
||||
-- users 테이블에서 이름과 트랙만 보기
|
||||
SELECT name, track FROM users;
|
||||
|
||||
name | track
|
||||
----------+--------
|
||||
김미림 | FE
|
||||
이마이 | BE
|
||||
...
|
||||
|
||||
-- * 는 "열 전부 다"라는 뜻 (편하지만 실무에선 필요한 열만!)
|
||||
SELECT * FROM users;`;
|
||||
|
||||
const CODE_SELECT_HASH = `-- password_hash 열을 구경해 보면...
|
||||
SELECT username, password_hash FROM users;
|
||||
|
||||
username | password_hash
|
||||
----------+--------------------------------------
|
||||
mirim01 | $2a$10$N9qo8uLOickgx2ZMRZoMye...
|
||||
|
||||
-- 비밀번호가 아니라 뜻 모를 문자열이 저장돼 있어요.
|
||||
-- 원래 비밀번호는 DB 어디에도 없습니다 — 해시만 저장!
|
||||
-- (보안 코스에서 배운 "비밀번호는 해시로"가 바로 이 열이에요.)`;
|
||||
|
||||
const CODE_WHERE = `-- WHERE = 조건에 맞는 행만 골라내기 (엑셀의 '필터'와 같은 역할)
|
||||
|
||||
-- 1주차 과제만
|
||||
SELECT title FROM assignment WHERE week = 1;
|
||||
|
||||
-- MAIN 과제만 (문자열은 작은따옴표!)
|
||||
SELECT title FROM assignment WHERE kind = 'MAIN';
|
||||
|
||||
-- 1주차이면서 MAIN인 과제 (AND = 둘 다 만족)
|
||||
SELECT title FROM assignment WHERE week = 1 AND kind = 'MAIN';
|
||||
|
||||
-- 1주차 또는 2주차 (OR = 하나만 맞아도)
|
||||
SELECT title FROM assignment WHERE week = 1 OR week = 2;
|
||||
|
||||
-- 특정 학생(user_id가 3번)의 제출물만
|
||||
SELECT content, status FROM submission WHERE user_id = 3;`;
|
||||
|
||||
const CODE_WHERE_OPS = `자주 쓰는 비교 연산자
|
||||
|
||||
연산자 뜻 예시
|
||||
─────────────────────────────────────────────────────
|
||||
= 같다 WHERE week = 1
|
||||
<> 같지 않다 WHERE status <> 'DONE'
|
||||
> < 크다/작다 WHERE week > 2
|
||||
>= <= 이상/이하 WHERE week <= 3
|
||||
LIKE 패턴 검색 WHERE title LIKE '%Git%'
|
||||
(% = 아무 글자나 몇 개든)
|
||||
|
||||
-- LIKE 예: 제목에 'Git'이 들어간 과제 전부
|
||||
SELECT title FROM assignment WHERE title LIKE '%Git%';`;
|
||||
|
||||
const CODE_ORDER_LIMIT = `-- ORDER BY = 정렬, LIMIT = 위에서 N개만 자르기
|
||||
|
||||
-- 제출물을 최신순으로 (DESC = 내림차순, ASC = 오름차순이 기본)
|
||||
SELECT id, user_id, submitted_at
|
||||
FROM submission
|
||||
ORDER BY submitted_at DESC;
|
||||
|
||||
-- "최근 제출 5건" — 정렬하고 5개만!
|
||||
SELECT id, user_id, submitted_at
|
||||
FROM submission
|
||||
ORDER BY submitted_at DESC
|
||||
LIMIT 5;
|
||||
|
||||
-- 조합 순서 기억: FROM → WHERE → ORDER BY → LIMIT
|
||||
-- (골라내고 → 정렬하고 → 자른다)
|
||||
SELECT title FROM assignment
|
||||
WHERE kind = 'MAIN'
|
||||
ORDER BY week ASC
|
||||
LIMIT 3;`;
|
||||
|
||||
const CODE_WRITE = `-- 쓰기 3형제: INSERT(추가) / UPDATE(수정) / DELETE(삭제)
|
||||
|
||||
-- INSERT: 체크리스트 항목 하나 추가
|
||||
INSERT INTO checklist_item (week, day, label, sort_order)
|
||||
VALUES (1, 1, 'SQL 입문 코스 읽기', 10);
|
||||
|
||||
-- UPDATE: 3번 제출물의 상태를 바꾸기 (WHERE 필수!!)
|
||||
UPDATE submission SET status = 'REVIEWED' WHERE id = 3;
|
||||
|
||||
-- DELETE: 방금 실수로 넣은 항목 지우기 (WHERE 필수!!)
|
||||
DELETE FROM checklist_item WHERE label = 'SQL 입문 코스 읽기';`;
|
||||
|
||||
const CODE_TX = `-- 트랜잭션 = "일단 해보고, 마음에 안 들면 되돌리기"
|
||||
|
||||
BEGIN; -- 되돌리기 지점 만들기
|
||||
UPDATE submission SET status = 'DONE'; -- 앗, WHERE를 빼먹었다!
|
||||
SELECT status FROM submission LIMIT 5; -- 전부 DONE이 돼버림...
|
||||
ROLLBACK; -- 휴, 전부 원상복구!
|
||||
|
||||
-- 반대로 확정하고 싶으면 COMMIT;
|
||||
-- 실무에서 UPDATE/DELETE 전에 BEGIN부터 치는 습관 =
|
||||
-- 게임에서 보스전 앞 세이브 포인트 만드는 습관이에요.`;
|
||||
|
||||
const CODE_JOIN_PROBLEM = `-- submission만 조회하면 이렇게 보여요:
|
||||
SELECT id, user_id, status FROM submission;
|
||||
|
||||
id | user_id | status
|
||||
----+---------+--------
|
||||
1 | 3 | DONE
|
||||
2 | 5 | PENDING
|
||||
|
||||
-- user_id = 3... 이게 누구지? 숫자만 있으니 알 수가 없죠.
|
||||
-- 이름은 users 테이블에 있으니까, 두 테이블을 '이어야' 합니다.`;
|
||||
|
||||
const CODE_JOIN_TWO = `-- JOIN: "submission의 user_id와 users의 id가 같은 행끼리 붙여라"
|
||||
|
||||
SELECT s.id, u.name, s.status
|
||||
FROM submission s
|
||||
JOIN users u ON s.user_id = u.id;
|
||||
|
||||
id | name | status
|
||||
----+--------+--------
|
||||
1 | 김미림 | DONE
|
||||
2 | 이마이 | PENDING
|
||||
|
||||
-- s, u 는 테이블의 별명(alias) — 매번 긴 이름 쓰기 귀찮으니까.
|
||||
-- ON 뒤가 '잇는 조건': 명찰(user_id)과 학생부(users.id)를 맞춰보는 것.`;
|
||||
|
||||
const CODE_JOIN_THREE = `-- 3테이블 JOIN: 제출물 + 학생 이름 + 과제 제목까지 한 번에!
|
||||
|
||||
SELECT u.name AS 학생,
|
||||
a.title AS 과제,
|
||||
s.status AS 상태,
|
||||
s.submitted_at
|
||||
FROM submission s
|
||||
JOIN users u ON s.user_id = u.id
|
||||
JOIN assignment a ON s.assignment_id = a.id
|
||||
ORDER BY s.submitted_at DESC;
|
||||
|
||||
학생 | 과제 | 상태 | submitted_at
|
||||
--------+-------------------+---------+---------------
|
||||
김미림 | Git 브랜치 실습 | DONE | 2026-07-15 ...
|
||||
이마이 | 리눅스 명령어 정리 | PENDING | 2026-07-14 ...
|
||||
|
||||
-- 이게 관리자 화면 '제출물 목록'의 정체입니다.
|
||||
-- 화면에 이름과 제목이 나란히 보이는 건, 뒤에서 이런 JOIN이 돌기 때문!`;
|
||||
|
||||
const CODE_JPA = `// 우리 백엔드(SubmissionRepository.java)엔 SQL이 한 줄도 없어요:
|
||||
List<Submission> findByUserIdOrderBySubmittedAtDesc(Long userId);
|
||||
|
||||
// 그런데 Hibernate(JPA)가 메서드 이름을 읽고 SQL을 '지어서' 실행합니다:
|
||||
// findBy UserId → WHERE user_id = ?
|
||||
// OrderBy SubmittedAt → ORDER BY submitted_at
|
||||
// Desc → DESC
|
||||
|
||||
SELECT * FROM submission
|
||||
WHERE user_id = ?
|
||||
ORDER BY submitted_at DESC;
|
||||
|
||||
-- 메서드 이름이 곧 SQL의 설계도인 셈이에요.
|
||||
-- 번역기를 쓰더라도 원문(SQL)을 읽을 줄 알아야
|
||||
-- "번역이 이상한데?"를 알아챌 수 있습니다.`;
|
||||
|
||||
const CODE_PSQL = `# 1) 로컬 개발 DB 컨테이너 안의 psql 실행 (Git Bash나 PowerShell에서)
|
||||
docker exec -it mirim-postgres psql -U mirim
|
||||
|
||||
# 2) 접속되면 프롬프트가 이렇게 바뀌어요:
|
||||
mirim=#
|
||||
|
||||
# 3) 테이블 목록 보기 (백슬래시 명령은 psql 전용!)
|
||||
mirim=# \\dt
|
||||
|
||||
List of relations
|
||||
Schema | Name | Type | Owner
|
||||
--------+-----------------+-------+-------
|
||||
public | assignment | table | mirim
|
||||
public | checklist_item | table | mirim
|
||||
public | progress | table | mirim
|
||||
public | submission | table | mirim
|
||||
public | users | table | mirim
|
||||
|
||||
# 4) 오늘 배운 SELECT 실행 — SQL 끝엔 세미콜론(;) 필수!
|
||||
mirim=# SELECT name, track FROM users;
|
||||
|
||||
# 5) 내 계정 행 찾아보기 (username을 본인 것으로)
|
||||
mirim=# SELECT * FROM users WHERE username = '내아이디';
|
||||
|
||||
# 6) 내가 누른 체크가 진짜 저장됐나? (앱에서 체크 하나 누르고 실행!)
|
||||
mirim=# SELECT * FROM progress ORDER BY checked_at DESC LIMIT 5;
|
||||
|
||||
# 나가기: \\q`;
|
||||
|
||||
// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
|
||||
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. props만 바꿔 재사용.
|
||||
function Section({ n, title, sub, children }) {
|
||||
return (
|
||||
<section className="step-card" id={`sec-${n}`}>
|
||||
<div className="step-head">
|
||||
<span className="step-num">{n}</span>
|
||||
<div>
|
||||
<h3>{title}</h3>
|
||||
{sub && <span className="step-sub">{sub}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="step-body">{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Code({ children }) {
|
||||
return <pre className="code-block"><code>{children}</code></pre>;
|
||||
}
|
||||
|
||||
const SECTIONS = [
|
||||
{ n: 1, label: 'DB가 뭐길래' },
|
||||
{ n: 2, label: 'SELECT' },
|
||||
{ n: 3, label: 'WHERE' },
|
||||
{ n: 4, label: 'ORDER BY / LIMIT' },
|
||||
{ n: 5, label: '쓰기 3형제' },
|
||||
{ n: 6, label: 'JOIN' },
|
||||
{ n: 7, label: 'JPA와 SQL' },
|
||||
{ n: 8, label: 'psql 실습' },
|
||||
];
|
||||
|
||||
export default function SqlPage() {
|
||||
return (
|
||||
<div>
|
||||
{/* 히어로: 이 코스에서 다룰 데이터가 '남의 예제'가 아니라 '내 데이터'임을 강조 */}
|
||||
<div className="hero">
|
||||
<div className="eyebrow">Course · 서버와 데이터</div>
|
||||
<h1>SQL 입문<br />— 데이터베이스에게 말 거는 법</h1>
|
||||
<p>
|
||||
여러분이 이 플랫폼에서 체크리스트를 하나 누를 때마다, 어딘가의 테이블에
|
||||
행이 하나 생깁니다. 이 코스에선 그 <strong>"어딘가"</strong>를 직접 열어 봐요 —
|
||||
예제가 전부 <strong>지금 쓰고 있는 이 앱의 진짜 테이블</strong>이라서,
|
||||
오늘 배우는 SQL이 곧 내 데이터를 읽는 열쇠가 됩니다.
|
||||
마지막 섹션에선 내 PC의 개발 DB에 접속해 <strong>내 계정 행</strong>을 직접 찾아봅니다.
|
||||
</p>
|
||||
<div className="chip-row">
|
||||
<span className="chip">예상 소요 60분</span>
|
||||
<span className="chip">선수 지식: 리눅스 기초 권장</span>
|
||||
<span className="chip">준비물: 로컬 개발 환경(docker)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 섹션 바로가기 */}
|
||||
<nav className="pill-nav">
|
||||
{SECTIONS.map((s) => (
|
||||
<a key={s.n} href={`#sec-${s.n}`}>
|
||||
<b>{s.n}</b>
|
||||
{s.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<Section n={1} title="데이터베이스가 뭐길래" sub="엑셀과 닮았지만, 엑셀이 못 하는 일을 한다">
|
||||
<p>
|
||||
데이터베이스(DB)는 <strong>표 모양으로 데이터를 쌓아 두는 창고</strong>예요.
|
||||
엑셀에 비유하면 딱 맞아떨어집니다 — <strong>시트 = 테이블</strong>,
|
||||
<strong> 행(row) = 데이터 한 건</strong>, <strong>열(column) = 속성</strong>.
|
||||
"학생 명단" 시트에 한 줄이 학생 한 명이고, 열이 이름·트랙·가입일인 것처럼요.
|
||||
</p>
|
||||
<p>그런데 왜 엑셀 대신 DB를 쓸까요? 엑셀이 못 하는 세 가지 때문이에요.</p>
|
||||
<ol className="olist">
|
||||
<li>
|
||||
<strong>동시 접속</strong> — 엑셀 파일은 한 명이 열면 다른 사람은 읽기 전용.
|
||||
DB는 수습생 전원이 동시에 체크리스트를 눌러도 한 건씩 안전하게 처리해요.
|
||||
</li>
|
||||
<li>
|
||||
<strong>무결성</strong> — 엑셀은 나이 칸에 "스무살"이라고 써도 말리지 않죠.
|
||||
DB는 "이 열은 숫자만", "이 값은 반드시 users에 있는 id여야 함" 같은
|
||||
규칙을 걸어서 이상한 데이터가 아예 못 들어오게 막아요.
|
||||
</li>
|
||||
<li>
|
||||
<strong>속도</strong> — 행이 100만 개여도 조건에 맞는 한 줄을 순식간에 찾아요.
|
||||
엑셀이라면 스크롤하다 하루가 갑니다.
|
||||
</li>
|
||||
</ol>
|
||||
<p>
|
||||
우리 앱의 DB(PostgreSQL) 안에는 테이블이 5개 있어요. 이번 코스 내내
|
||||
이 다섯 개만 갖고 놉니다.
|
||||
</p>
|
||||
<Code>{CODE_TABLES}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 이 표를 눈에 익혀 두세요. 특히
|
||||
<span className="icode">submission.user_id</span>와 <span className="icode">progress.item_id</span> 처럼
|
||||
<strong> 다른 테이블을 가리키는 숫자 열</strong>에 동그라미를 쳐 보세요 —
|
||||
섹션 6(JOIN)에서 이 동그라미들이 주인공이 됩니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={2} title="SELECT — 조회의 기본" sub="DB에게 건네는 첫 마디: '이것 좀 보여줘'">
|
||||
<p>
|
||||
SQL은 DB에게 말을 거는 언어예요. 그중 가장 많이 쓰는 문장이
|
||||
<strong> SELECT</strong> — "이 테이블에서 이 열들 좀 보여줘"입니다.
|
||||
문장 구조가 거의 영어 그대로라 읽기 쉬워요.
|
||||
</p>
|
||||
<Code>{CODE_SELECT_BASIC}</Code>
|
||||
<p>
|
||||
<span className="icode">*</span>는 "열 전부"라는 와일드카드예요. 구경할 땐 편하지만,
|
||||
실무 코드에선 <strong>필요한 열만 콕 집어</strong> 가져오는 게 예의입니다 —
|
||||
열이 수십 개인 테이블에서 <span className="icode">*</span>를 남발하면
|
||||
쓸데없는 데이터가 네트워크를 타고 우르르 넘어오거든요.
|
||||
</p>
|
||||
<p>
|
||||
users 테이블을 조회하다 보면 <span className="icode">password_hash</span> 열에서
|
||||
이상한 문자열을 만나게 돼요.
|
||||
</p>
|
||||
<Code>{CODE_SELECT_HASH}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 아직 DB 접속 전이니, 종이에 SELECT 문장을 지어 보세요.
|
||||
"assignment 테이블에서 title과 summary만 보고 싶다"를 SQL로 쓰면?
|
||||
(답: <span className="icode">SELECT title, summary FROM assignment;</span> —
|
||||
세미콜론까지가 한 문장이에요.) 섹션 8에서 진짜로 실행해 봅니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={3} title="WHERE — 조건 걸기" sub="전체가 아니라 '조건에 맞는 행만'">
|
||||
<p>
|
||||
SELECT만 쓰면 테이블 전체가 쏟아져요. 실제로 궁금한 건 보통
|
||||
"<strong>1주차</strong> 과제만", "<strong>내</strong> 제출물만"처럼 일부죠.
|
||||
그 필터가 <strong>WHERE</strong>입니다. 엑셀에서 열 머리의 필터 버튼을 눌러
|
||||
조건을 거는 것과 똑같은 일이에요.
|
||||
</p>
|
||||
<Code>{CODE_WHERE}</Code>
|
||||
<p>연산자는 이 정도만 알면 당분간 충분해요.</p>
|
||||
<Code>{CODE_WHERE_OPS}</Code>
|
||||
<div className="warn">
|
||||
<b>따옴표 주의</b> 문자열은 <strong>작은따옴표</strong>(<span className="icode">'MAIN'</span>)로
|
||||
감싸요. 큰따옴표(<span className="icode">"..."</span>)는 SQL에서
|
||||
열·테이블 이름용이라 뜻이 달라집니다. 숫자는 따옴표 없이 그냥
|
||||
<span className="icode">week = 1</span>.
|
||||
</div>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 말로 된 조건을 SQL로 바꾸는 연습 세 개 —
|
||||
① "2주차 이상의 과제 제목" ② "status가 PENDING이 아닌 제출물"
|
||||
③ "label에 'Git'이 들어간 체크리스트 항목". 각각
|
||||
<span className="icode">>=</span>, <span className="icode"><></span>,
|
||||
<span className="icode">LIKE</span>를 쓰면 됩니다. 답을 메모해 뒀다가 섹션 8에서 실행!
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={4} title="ORDER BY / LIMIT — 정렬과 자르기" sub="최신순으로 5개만 주세요">
|
||||
<p>
|
||||
DB는 기본적으로 행을 <strong>순서 보장 없이</strong> 돌려줘요. "최근 것부터"를 원하면
|
||||
<strong> ORDER BY</strong>로 정렬하고, "다 말고 위에서 몇 개만"은
|
||||
<strong> LIMIT</strong>로 자릅니다. 유튜브 검색 결과를 '최신순 정렬'하고
|
||||
첫 페이지만 보는 것과 같은 조합이죠.
|
||||
</p>
|
||||
<Code>{CODE_ORDER_LIMIT}</Code>
|
||||
<p>
|
||||
우리 앱 관리자 화면의 "최근 제출물" 목록이 정확히 이 쿼리예요 —
|
||||
<span className="icode">ORDER BY submitted_at DESC LIMIT 5</span>.
|
||||
화면에 보이는 모든 목록 뒤에는 이런 정렬·자르기가 숨어 있습니다.
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> "체크리스트 항목을 week, day, sort_order 순으로 정렬"은
|
||||
어떻게 쓸까요? ORDER BY엔 열을 쉼표로 여러 개 나열할 수 있어요:
|
||||
<span className="icode">ORDER BY week, day, sort_order</span> —
|
||||
앞 열이 같을 때 다음 열로 순서를 가리는, '가나다순 → 같으면 나이순' 방식입니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={5} title="INSERT / UPDATE / DELETE — 쓰기 3형제" sub="읽기만 하던 손님에서, 쓰는 사람으로">
|
||||
<p>
|
||||
지금까지는 구경(SELECT)만 했다면, 이제 데이터를 <strong>넣고(INSERT)·고치고(UPDATE)·
|
||||
지우는(DELETE)</strong> 차례예요. 문장은 짧지만 파괴력은 SELECT와 비교가 안 됩니다.
|
||||
</p>
|
||||
<Code>{CODE_WRITE}</Code>
|
||||
<div className="warn">
|
||||
<b>실무 사고 원인 1위: WHERE 없는 UPDATE/DELETE</b>{' '}
|
||||
<span className="icode">UPDATE submission SET status = 'DONE';</span> —
|
||||
WHERE를 빼먹은 이 한 줄은 <strong>테이블의 모든 행</strong>을 DONE으로 바꿔 버립니다.
|
||||
DELETE도 마찬가지로 전멸이에요. "3번만 바꿔야지" 하고 친 명령이 전교생 성적을
|
||||
갈아치우는 셈이죠. 습관 하나로 예방합니다: <strong>UPDATE/DELETE를 치기 전에,
|
||||
같은 WHERE로 SELECT를 먼저 돌려서</strong> 바뀔 행이 몇 개인지 눈으로 확인하세요.
|
||||
</div>
|
||||
<p>
|
||||
그리고 안전벨트가 하나 더 있어요 — <strong>트랜잭션</strong>.
|
||||
<span className="icode">BEGIN</span>으로 시작하면 그 뒤의 변경은 '가안' 상태라서,
|
||||
<span className="icode">ROLLBACK</span> 한 방에 전부 되돌릴 수 있습니다.
|
||||
</p>
|
||||
<Code>{CODE_TX}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 섹션 8에서 로컬 DB에 접속하면
|
||||
<span className="icode">BEGIN;</span> → WHERE 없는 UPDATE → <span className="icode">SELECT</span>로
|
||||
참사 확인 → <span className="icode">ROLLBACK;</span> → 다시 SELECT로 원상복구 확인 —
|
||||
이 '사고와 복구' 시나리오를 일부러 한 번 겪어 보세요. 로컬 DB니까 안전합니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={6} title="JOIN — 테이블 두 개 잇기" sub="user_id 3번... 그래서 그게 누군데?">
|
||||
<p>
|
||||
submission 테이블엔 학생 <strong>이름이 없어요</strong>.
|
||||
<span className="icode">user_id</span>라는 숫자만 있죠.
|
||||
</p>
|
||||
<Code>{CODE_JOIN_PROBLEM}</Code>
|
||||
<p>
|
||||
왜 이름을 안 넣었을까요? 이름을 제출물마다 복사해 두면, 개명이라도 하는 순간
|
||||
수십 곳을 고쳐야 하고 하나라도 빼먹으면 데이터가 어긋나요. 그래서 DB는
|
||||
<strong> 이름은 users에 한 번만 두고, 다른 테이블은 번호표(id)로 가리키게</strong> 합니다.
|
||||
이 번호표를 <strong>외래키</strong>(foreign key)라고 불러요 — "다른(외) 테이블의
|
||||
열쇠(키)"라는 뜻이에요.
|
||||
</p>
|
||||
<p>
|
||||
그리고 번호표를 실제 이름으로 바꿔 보는 순간이 <strong>JOIN</strong>입니다.
|
||||
명찰의 학번을 학생부와 맞춰 보며 "아, 3번은 김미림이구나" 하는 거죠.
|
||||
</p>
|
||||
<Code>{CODE_JOIN_TWO}</Code>
|
||||
<p>
|
||||
한 번 이었으면 두 번도 이을 수 있어요. 제출물엔
|
||||
<span className="icode">assignment_id</span>도 있으니, 과제 제목까지 붙여 봅시다 —
|
||||
테이블 3개를 한 문장으로.
|
||||
</p>
|
||||
<Code>{CODE_JOIN_THREE}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> progress 테이블로 같은 걸 스스로 지어 보세요 —
|
||||
"누가(users.name) 어떤 항목(checklist_item.label)을 언제(checked_at) 체크했나".
|
||||
잇는 조건은 <span className="icode">progress.user_id = users.id</span>와{' '}
|
||||
<span className="icode">progress.item_id = checklist_item.id</span>.
|
||||
섹션 8에서 실행해 보면, 여러분이 누른 체크가 이름과 함께 나옵니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={7} title="JPA와 SQL의 관계" sub="백엔드 코드엔 SQL이 없는데, 누가 쿼리를 날리지?">
|
||||
<p>
|
||||
여기서 이상한 점 하나 — 우리 백엔드(Spring Boot) 코드를 열어 보면
|
||||
<strong> SQL이 거의 안 보입니다</strong>. 대신 <strong>JPA(Hibernate)</strong>라는
|
||||
번역기가 있어요. 자바 메서드 이름을 읽고, 알맞은 SQL을 자동으로 지어서
|
||||
DB에 대신 말을 걸어 줍니다.
|
||||
</p>
|
||||
<Code>{CODE_JPA}</Code>
|
||||
<p>
|
||||
"그럼 SQL 몰라도 되는 거 아냐?" — 반대예요. 번역기를 쓸수록 원문을 알아야 합니다.
|
||||
이유는 두 가지예요.
|
||||
</p>
|
||||
<ol className="olist">
|
||||
<li>
|
||||
<strong>느린 쿼리는 SQL로만 보인다</strong> — 화면이 느릴 때 범인은 대부분
|
||||
JPA가 지어낸 비효율적인 쿼리예요. 로그에 찍힌 SQL을 읽을 줄 알아야
|
||||
"아, 여기서 JOIN이 100번 도는구나"를 잡아낼 수 있습니다.
|
||||
(티켓 풀의 <strong>BE-06</strong>이 바로 이런 '느린 쿼리 잡기' 과제예요.)
|
||||
</li>
|
||||
<li>
|
||||
<strong>번역이 항상 완벽하진 않다</strong> — 복잡한 조회는 JPA에게 맡기기보다
|
||||
SQL을 직접 쓰는 게 나을 때가 있어요. 그 판단도 SQL을 알아야 할 수 있죠.
|
||||
</li>
|
||||
</ol>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 백엔드 저장소에서{' '}
|
||||
<span className="icode">SubmissionRepository</span> 파일을 열어 메서드 이름들을 읽고,
|
||||
각각 어떤 SQL로 번역될지 오늘 배운 문법으로 추리해 보세요.
|
||||
<span className="icode">findByStatus</span>는? <span className="icode">findByUserIdAndStatus</span>는?
|
||||
메서드 이름이 문장처럼 읽히기 시작하면 성공입니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={8} title="직접 해보기 — 내 PC에서 psql 접속" sub="오늘 배운 전부를 진짜 DB에 쏘기">
|
||||
<p>
|
||||
이제 진짜 DB에 접속할 시간이에요. 로컬 개발 환경에서 docker로 띄운
|
||||
PostgreSQL 컨테이너 안에는 <strong>psql</strong>이라는 SQL 대화창이 들어 있습니다.
|
||||
Git Bash(또는 PowerShell)를 열고 따라 해 보세요.
|
||||
</p>
|
||||
<Code>{CODE_PSQL}</Code>
|
||||
<p>
|
||||
6번이 이 코스의 하이라이트예요 — 브라우저에서 체크리스트 하나를 체크하고,
|
||||
곧바로 psql에서 progress를 조회하면 <strong>방금 그 클릭이 행 하나로</strong> 나타납니다.
|
||||
화면의 체크박스와 DB의 행이 같은 것이라는 감각, 그게 오늘의 목표였어요.
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>여긴 놀이터예요</b> 지금 접속한 건 <strong>내 PC의 로컬 개발 DB</strong> —
|
||||
망가뜨려도 아무 일도 안 일어나고, 컨테이너를 지우고 다시 만들면 그만이에요.
|
||||
WHERE 없는 UPDATE도, DELETE도, 트랜잭션 롤백도 마음껏 실험해 보세요.
|
||||
많이 부숴 본 사람이 안 부수는 법을 압니다.
|
||||
</div>
|
||||
<div className="warn">
|
||||
<b>단, 운영 DB는 절대 아닙니다</b> 실제 서비스가 쓰는 운영 DB에서는
|
||||
실험 쿼리를 날리지 않아요 — 거기 있는 행 하나하나가 진짜 사용자의 데이터입니다.
|
||||
나중에 운영 DB에 접근할 일이 생기면 반드시 멘토와 함께, 읽기(SELECT)부터,
|
||||
그리고 쓰기 전엔 항상 <span className="icode">BEGIN</span>부터. 이 감각의 차이가
|
||||
수습과 실무자를 가릅니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<div className="step-card">
|
||||
<div className="step-body">
|
||||
<h3 style={{ marginBottom: 6 }}>🗄️ 여기까지 왔다면</h3>
|
||||
<p className="muted">
|
||||
이제 화면 뒤의 데이터가 어디에 어떤 모양으로 사는지, 그리고 그 데이터에게
|
||||
직접 말을 거는 법(SELECT부터 JOIN까지)을 알게 됐어요. 다음은 그 데이터를
|
||||
화면으로 바꾸는 <strong>코드</strong> 차례 —{' '}
|
||||
<Link to="/learn/coding"><strong>코딩 기초</strong></Link> 코스로 이어 가세요.
|
||||
그리고 <span className="icode">STUDY.md</span>의 미니 과제
|
||||
(오늘 배운 3테이블 JOIN을 progress 버전으로 직접 작성해 제출)로
|
||||
배운 것을 굳혀 보세요.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
385
frontend/src/pages/UiUxPage.jsx
Normal file
385
frontend/src/pages/UiUxPage.jsx
Normal file
@ -0,0 +1,385 @@
|
||||
// 이 파일이 하는 일: "UI/UX 기초" 코스 — UI와 UX의 차이에서 출발해
|
||||
// 시각적 위계·일관성·피드백·색·여백·휴리스틱을 거쳐 '화면 비평' 실습까지
|
||||
// 8개 섹션으로 안내하는 정적 학습 페이지. 개발·디자인 수습생 모두 대상.
|
||||
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 global.css의
|
||||
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용한다 — 색을 여기서 하드코딩하지 않는다.
|
||||
// 텍스트 표·예시는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
// ── 텍스트 표 · 예시 상수들 ──
|
||||
|
||||
const CODE_UI_VS_UX = `UI vs UX — 케첩병으로 이해하기
|
||||
|
||||
유리 케첩병 거꾸로 세우는 플라스틱병
|
||||
─────────────────────────────────────────────────────────
|
||||
UI(보이는 것) 반짝반짝, 고급스러움 투박함
|
||||
UX(겪는 것) 탁탁 쳐도 안 나옴, 튐 뚜껑 열면 바로 나옴
|
||||
|
||||
→ UI = 화면의 생김새 (색, 글꼴, 버튼 모양, 배치)
|
||||
→ UX = 그걸 쓰면서 겪는 전체 경험 (쉬운가? 빠른가? 짜증나는가?)
|
||||
|
||||
좋은 제품 = 예쁜데(UI) 쓰기도 편한(UX) 제품.
|
||||
둘 중 하나만 좋으면 "예쁜 쓰레기" 또는 "못생긴 명작"이 됩니다.`;
|
||||
|
||||
const CODE_F_PATTERN = `사용자의 눈이 움직이는 길 — F패턴
|
||||
|
||||
■■■■■■■■■■■■■■■ ← ① 맨 윗줄은 끝까지 읽고
|
||||
■■■■■■■■
|
||||
■■■■■ ← ② 그 아래는 점점 짧게 훑다가
|
||||
■■■
|
||||
■■ ← ③ 왼쪽 가장자리만 쭉 내려가며 스캔
|
||||
■
|
||||
|
||||
→ 눈길 모양이 알파벳 F를 닮았다고 해서 F패턴.
|
||||
→ 결론: 중요한 것일수록 '위쪽·왼쪽·크게' 둬야 합니다.`;
|
||||
|
||||
const CODE_HIERARCHY = `우리 학습 플랫폼 대시보드의 시각적 위계 (눈이 가는 순서)
|
||||
|
||||
1순위 [ 72% ] ← 제일 크고 진한 진행률 숫자
|
||||
2순위 [과제] [코스] [공지] ← 그다음 크기의 탭
|
||||
3순위 · 과제 목록 항목들 ← 작고 촘촘한 텍스트
|
||||
|
||||
왜 이 순서로 눈이 갈까?
|
||||
→ 크기가 다르고(72%가 제일 큼), 굵기가 다르고, 여백으로 떨어져 있어서.
|
||||
→ 디자이너가 "이 순서로 봐 주세요"라고 화면에 써 둔 무언의 안내입니다.`;
|
||||
|
||||
const CODE_FIVE_STATES = `버튼·화면이 가져야 하는 5가지 상태 (티켓 풀 FE-01·FE-02와 연결!)
|
||||
|
||||
상태 예시 없으면 생기는 일
|
||||
──────────────────────────────────────────────────────────
|
||||
로딩 "제출 중..." + 버튼 잠금 연타 → 중복 제출 사고
|
||||
성공 "제출 완료!" 초록 메시지 됐는지 몰라서 또 누름
|
||||
실패 "제출 실패: 다시 시도해 주세요" 조용히 실패 → 사용자는 낸 줄 앎
|
||||
빈 상태 "아직 과제가 없어요" 버그인지 원래 없는 건지 헷갈림
|
||||
비활성 회색 버튼 + 못 누름 마감 지났는데 눌러서 에러`;
|
||||
|
||||
const CODE_SUBMIT_BUTTON = `우리 과제 제출 버튼 해부 — 이런 코드가 상태를 만듭니다
|
||||
|
||||
<button disabled={submitting}>
|
||||
{submitting ? '제출 중...' : '제출하기'}
|
||||
</button>
|
||||
|
||||
submitting이 true인 동안:
|
||||
1) 글자가 "제출 중..."으로 바뀜 → 사용자에게 "일하는 중" 신호
|
||||
2) disabled로 잠김 → 연타로 두 번 제출되는 사고 방지
|
||||
|
||||
버튼 하나에 UX 두 가지(피드백 + 실수 방지)가 들어 있어요.`;
|
||||
|
||||
const CODE_COLOR_ROLES = `색은 장식이 아니라 정보 — 색마다 맡은 역할이 있어요
|
||||
|
||||
색 역할 우리 플랫폼 예시
|
||||
────────────────────────────────────────────────
|
||||
파랑 행동 (눌러도 됨) 제출하기, 저장 버튼, 링크
|
||||
빨강 위험 · 오류 삭제 버튼, "제출 실패" 메시지
|
||||
초록/청록 성공 · 완료 "제출 완료!", 완료 체크 표시
|
||||
회색 비활성 · 보조 못 누르는 버튼, 부가 설명
|
||||
|
||||
규칙 하나: 같은 색 = 같은 의미.
|
||||
빨강을 오늘은 '삭제'에, 내일은 '추천'에 쓰면 색의 언어가 무너집니다.`;
|
||||
|
||||
const CODE_SPACING = `여백은 낭비가 아니라 그룹핑 — 근접성의 법칙
|
||||
|
||||
나쁜 예 (전부 같은 간격) 좋은 예 (관련 있는 것끼리 붙임)
|
||||
|
||||
제목 제목
|
||||
설명 설명
|
||||
← 큰 여백 = "여기서 단락 바뀜"
|
||||
버튼A 버튼A 버튼B ← 붙어 있음 = 한 세트
|
||||
버튼B
|
||||
라벨 ← 큰 여백
|
||||
입력칸 라벨
|
||||
입력칸 ← 라벨과 입력칸은 한 몸
|
||||
|
||||
→ 가까이 있는 것은 관련 있어 보인다. 이게 근접성(proximity)입니다.
|
||||
→ 그리고 왼쪽 정렬선 하나만 맞춰도 화면이 갑자기 정돈돼 보여요.`;
|
||||
|
||||
const CODE_HEURISTICS = `니엘슨 사용성 어림법(휴리스틱) — 10개 중 오늘은 5개만
|
||||
|
||||
어림법 쉬운 말 우리 플랫폼에서는?
|
||||
─────────────────────────────────────────────────────────────
|
||||
① 시스템 상태 보여주기 "지금 뭐 하는 중인지 말해줘" "제출 중..." 로딩 표시
|
||||
② 사용자의 언어 "개발자 말고 사람 말로" "Error 500" 대신
|
||||
"잠시 후 다시 시도해 주세요"
|
||||
③ 실수 방지 "사고 나기 전에 막아줘" 삭제 전 "정말 삭제할까요?"
|
||||
확인창, 마감 후 버튼 비활성
|
||||
④ 일관성 "같은 건 같게 생겨라" 모든 페이지가 같은
|
||||
step-card·버튼 스타일
|
||||
⑤ 미니멀리즘 "필요 없는 건 빼라" 대시보드 첫 화면엔
|
||||
진행률·과제만 (설정은 뒤로)`;
|
||||
|
||||
const CODE_CRITIQUE = `화면 비평 연습장 — 이대로 채워서 멘토에게 보여 주세요
|
||||
|
||||
고른 화면: (예: 대시보드 / 과제 제출 페이지 / 로그인 화면)
|
||||
|
||||
[좋은 점 3가지] — 오늘 배운 용어로!
|
||||
1. 예) 진행률 숫자가 제일 커서 시각적 위계가 명확하다 (섹션 2)
|
||||
2.
|
||||
3.
|
||||
|
||||
[아쉬운 점 3가지] — "왜" 아쉬운지 근거까지
|
||||
1. 예) 제출 버튼과 삭제 버튼이 같은 색이라 색의 역할이 겹친다 (섹션 5)
|
||||
2.
|
||||
3.
|
||||
|
||||
→ 아쉬운 점에는 반드시 근거(어느 섹션의 어떤 원칙?)를 붙이세요.
|
||||
"그냥 별로예요"는 비평이 아니라 투정입니다.
|
||||
→ 근거 있는 아쉬운 점은 그대로 디자인 티켓(DZ-02)이 됩니다!`;
|
||||
|
||||
// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
|
||||
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. props만 바꿔 재사용.
|
||||
function Section({ n, title, sub, children }) {
|
||||
return (
|
||||
<section className="step-card" id={`sec-${n}`}>
|
||||
<div className="step-head">
|
||||
<span className="step-num">{n}</span>
|
||||
<div>
|
||||
<h3>{title}</h3>
|
||||
{sub && <span className="step-sub">{sub}</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="step-body">{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function Code({ children }) {
|
||||
return <pre className="code-block"><code>{children}</code></pre>;
|
||||
}
|
||||
|
||||
const SECTIONS = [
|
||||
{ n: 1, label: 'UI vs UX' },
|
||||
{ n: 2, label: '스캔과 위계' },
|
||||
{ n: 3, label: '일관성' },
|
||||
{ n: 4, label: '피드백과 상태' },
|
||||
{ n: 5, label: '색의 역할' },
|
||||
{ n: 6, label: '여백과 정렬' },
|
||||
{ n: 7, label: '사용성 어림법' },
|
||||
{ n: 8, label: '화면 비평' },
|
||||
];
|
||||
|
||||
export default function UiUxPage() {
|
||||
return (
|
||||
<div>
|
||||
{/* 히어로: 이 코스가 누구를 위한 것이고 무엇을 얻어 가는지 */}
|
||||
<div className="hero">
|
||||
<div className="eyebrow">Course · 디자인</div>
|
||||
<h1>UI/UX 기초<br />— 쓰기 좋은 화면의 비밀</h1>
|
||||
<p>
|
||||
"이 화면 뭔가 불편한데 왜인지 모르겠어" — 그 <strong>왜</strong>를 말로 설명할 수
|
||||
있게 되는 코스예요. 디자인 수습생은 물론, 화면을 직접 만드는 개발 수습생에게도
|
||||
필수입니다. 각 섹션의 <strong>"직접 확인해 보기"</strong>는 우리 플랫폼의 진짜
|
||||
화면과 코드로 해 보세요 — 남의 앱 비평보다 내가 만들 앱 비평이 백배 남습니다.
|
||||
</p>
|
||||
<div className="chip-row">
|
||||
<span className="chip">예상 소요 60분</span>
|
||||
<span className="chip">개발자도 필수</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 섹션 바로가기 */}
|
||||
<nav className="pill-nav">
|
||||
{SECTIONS.map((s) => (
|
||||
<a key={s.n} href={`#sec-${s.n}`}>
|
||||
<b>{s.n}</b>
|
||||
{s.label}
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<Section n={1} title="UI와 UX는 다르다" sub="보이는 것과 겪는 것">
|
||||
<p>
|
||||
<strong>UI</strong>(User Interface)는 화면에 <strong>보이는 것</strong> — 색, 글꼴,
|
||||
버튼 모양, 배치예요. <strong>UX</strong>(User Experience)는 그걸 쓰면서{' '}
|
||||
<strong>겪는 것</strong> — 쉬운지, 빠른지, 짜증 나는지의 전체 경험이고요.
|
||||
케첩병이 딱 좋은 비유입니다.
|
||||
</p>
|
||||
<Code>{CODE_UI_VS_UX}</Code>
|
||||
<p>
|
||||
유리병은 식탁 위에서 예쁘지만(UI 승) 케첩이 안 나와서 바닥을 탁탁 쳐야 하죠(UX 패).
|
||||
거꾸로 세워 두는 플라스틱병은 투박하지만 뚜껑만 열면 바로 나옵니다.
|
||||
<strong> 좋은 제품은 둘 다</strong>를 잡아요 — 예쁜데 편하기까지 한 것.
|
||||
그리고 둘이 충돌하면, 대부분의 경우 <strong>편함(UX)이 이겨야</strong> 합니다.
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 오늘 쓴 앱 중에서 "예쁜데 불편한 것" 하나,
|
||||
"안 예쁜데 편한 것" 하나를 찾아 보세요. 그리고 우리 학습 플랫폼은
|
||||
어느 쪽에 가까운지 첫인상을 메모해 두세요 — 섹션 8에서 그 메모가 재료가 됩니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={2} title="사용자는 읽지 않는다 — 스캔한다" sub="F패턴과 시각적 위계">
|
||||
<p>
|
||||
충격적인 사실: 사용자는 화면의 글을 <strong>읽지 않아요</strong>. 시험지 훑듯이{' '}
|
||||
<strong>스캔</strong>합니다. 눈길이 움직이는 대표적인 모양이 <strong>F패턴</strong>이에요.
|
||||
</p>
|
||||
<Code>{CODE_F_PATTERN}</Code>
|
||||
<p>
|
||||
그래서 화면에는 <strong>시각적 위계</strong>(visual hierarchy)가 필요해요 —
|
||||
중요한 것부터 눈에 들어오도록 <strong>크기·굵기·색·위치</strong>로 순서를 매기는 것.
|
||||
신문 1면을 떠올려 보세요. 제일 큰 헤드라인 → 중간 제목 → 본문 순서로
|
||||
자연스럽게 눈이 가죠. 그건 우연이 아니라 설계입니다.
|
||||
</p>
|
||||
<Code>{CODE_HIERARCHY}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 우리 플랫폼 대시보드를 열고 3초만 보고 눈을 감아 보세요.
|
||||
뭐가 기억나나요? 아마 큰 진행률 숫자일 거예요. 그다음 다시 열어서
|
||||
내 눈이 <strong>어떤 순서로</strong> 움직이는지 관찰해 보세요 —
|
||||
큰 숫자 → 탭 → 목록. 그 순서를 만든 게 크기·굵기·여백입니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={3} title="일관성 — 같은 것은 같게" sub="디자인 시스템이라는 약속">
|
||||
<p>
|
||||
파란 버튼이 이 페이지에서는 '저장'인데 저 페이지에서는 모양도 색도 다르다면?
|
||||
사용자는 페이지마다 <strong>사용법을 다시 배워야</strong> 해요. 반대로 모든 화면에서
|
||||
버튼·카드·경고창이 같은 모양이면, 한 번 배운 걸 어디서든 써먹을 수 있죠.
|
||||
이게 <strong>일관성</strong>입니다. 학교의 모든 교실 문손잡이가 같은 방식으로
|
||||
열리는 것과 같아요 — 아무도 그걸 '배웠다'고 느끼지 않지만, 다 다르면 매일 고생합니다.
|
||||
</p>
|
||||
<p>
|
||||
이 약속을 문서와 코드로 정리한 게 <strong>디자인 시스템</strong>이에요.
|
||||
"우리 서비스의 버튼은 이 색·이 크기, 경고는 이 모양" 하고 부품을 정해 두고
|
||||
모든 화면이 그 부품만 조립해서 쓰는 거죠. 거창하게 들리지만 —
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 우리 프로젝트의{' '}
|
||||
<span className="icode">frontend/src/styles/global.css</span>를 열어 보세요.
|
||||
지금 보고 있는 이 페이지의 <span className="icode">step-card</span>,{' '}
|
||||
<span className="icode">tip</span>, <span className="icode">chip</span> 클래스가
|
||||
거기 있어요. 네트워크 코스도, 이 코스도 <strong>같은 클래스</strong>를 씁니다 —
|
||||
이게 바로 우리의 <strong>미니 디자인 시스템</strong>이에요. 새 페이지를 만들 때
|
||||
CSS를 새로 짜지 않고 이 부품을 재사용하는 것, 그게 일관성을 지키는 방법입니다.
|
||||
</div>
|
||||
<div className="warn">
|
||||
<b>흔한 실수</b> "이 페이지만 특별하니까 버튼 색을 살짝 다르게..." — 그 '살짝'이
|
||||
쌓이면 화면마다 다른 앱처럼 보여요. 다르게 만들고 싶다면 먼저 물어볼 것:
|
||||
<strong> 정말 의미가 다른가?</strong> 의미가 같으면 모양도 같아야 합니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={4} title="피드백과 상태 — 눌렀으면 반응이 있어야 한다" sub="화면의 5가지 상태">
|
||||
<p>
|
||||
엘리베이터 버튼을 눌렀는데 불이 안 들어오면? 또 누르고, 또 누르죠.
|
||||
화면도 똑같아요. 사용자가 뭔가 하면 <strong>즉시 반응</strong>이 있어야 하고,
|
||||
화면은 지금 상태를 늘 말해 줘야 합니다. 잘 만든 화면은 다음 5가지 상태를
|
||||
전부 준비해 둬요.
|
||||
</p>
|
||||
<Code>{CODE_FIVE_STATES}</Code>
|
||||
<p>
|
||||
개발 수습생이라면 티켓 풀의 <strong>FE-01(로딩 상태)·FE-02(빈 상태)</strong>가
|
||||
정확히 이 이야기예요. 우리 과제 제출 버튼을 해부해 보면 이렇게 생겼습니다.
|
||||
</p>
|
||||
<Code>{CODE_SUBMIT_BUTTON}</Code>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 우리 플랫폼에서 과제를 하나 제출해 보면서 관찰하세요.
|
||||
버튼이 <strong>"제출 중..."</strong>으로 바뀌는 순간, 성공 메시지가 뜨는 순간을
|
||||
눈으로 확인! 그리고 과제가 하나도 없는 계정 화면(빈 상태)은 어떻게 생겼는지도
|
||||
찾아 보세요. 5가지 상태 중 우리 플랫폼에 <strong>빠진 상태</strong>가 있다면 —
|
||||
축하해요, 방금 개선 티켓 하나를 발견한 겁니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={5} title="색의 역할" sub="색은 장식이 아니라 정보다">
|
||||
<p>
|
||||
신호등을 생각해 보세요. 빨강=멈춤, 초록=가도 됨 — 색 자체가 <strong>정보</strong>죠.
|
||||
화면의 색도 똑같이 역할이 정해져 있어요.
|
||||
</p>
|
||||
<Code>{CODE_COLOR_ROLES}</Code>
|
||||
<p>
|
||||
두 가지를 더 챙겨야 해요. 첫째, <strong>명도 대비</strong> — 연회색 바탕에 흰 글씨처럼
|
||||
바탕과 글자의 밝기 차이가 작으면 읽기 힘들어요. 예쁜 색이어도 <strong>읽히지 않으면
|
||||
탈락</strong>입니다. 둘째, <strong>접근성</strong> — 색약인 사용자는 빨강과 초록을
|
||||
구분하기 어려울 수 있어요. 그래서 색 <strong>하나에만</strong> 의존하면 안 되고,
|
||||
"실패"라는 <strong>글자</strong>나 아이콘을 함께 줘야 합니다.
|
||||
신호등도 색만이 아니라 <strong>위치</strong>(위=빨강)로 한 번 더 알려 주잖아요.
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 우리 플랫폼에서 파란 요소만 골라 세어 보세요 —
|
||||
전부 '누르면 뭔가 일어나는 것'인가요? 만약 눌리지도 않는 것이 파랗다면,
|
||||
색의 언어를 어기고 있는 거예요. 그리고 화면 밝기를 최저로 낮춰서
|
||||
여전히 다 읽히는지도 확인해 보세요 (간이 명도 대비 테스트!).
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={6} title="여백과 정렬" sub="여백은 낭비가 아니라 그룹핑이다">
|
||||
<p>
|
||||
초보가 가장 많이 하는 실수: "빈 데가 아까우니까 뭘 더 채우자."
|
||||
하지만 여백은 낭비가 아니라 <strong>정보를 묶어 주는 도구</strong>예요.
|
||||
사람 눈은 <strong>가까이 있는 것들을 한 덩어리로</strong> 봅니다 —
|
||||
이걸 <strong>근접성</strong>(proximity)이라고 해요. 문장 사이 띄어쓰기가 없으면
|
||||
못읽는것과같은원리예요. 방금 불편했죠? 그게 여백의 힘입니다.
|
||||
</p>
|
||||
<Code>{CODE_SPACING}</Code>
|
||||
<p>
|
||||
<strong>정렬</strong>은 여백의 단짝이에요. 요소들의 왼쪽 끝을 가상의 세로선 하나에
|
||||
맞추기만 해도 화면이 갑자기 정돈돼 보입니다. 공책에 줄이 있으면 글씨가
|
||||
가지런해 보이는 것과 같아요. 들쭉날쭉한 화면의 범인은 대부분
|
||||
"정렬선이 여러 개"인 경우입니다.
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 지금 이 페이지에서 <span className="kbd">F12</span> 개발자
|
||||
도구를 열고, 이 카드(<span className="icode">step-card</span>)에 마우스를 올려
|
||||
보세요. 파란색(콘텐츠)과 초록/주황색(패딩·마진)이 표시돼요 —
|
||||
카드 안쪽의 그 투명한 띠가 전부 <strong>일부러 설계된 여백</strong>입니다.
|
||||
제목과 본문, 카드와 카드 사이의 간격이 서로 다른 것도 관찰해 보세요.
|
||||
간격의 크기 차이가 곧 "얼마나 가까운 사이인지"의 표현이에요.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={7} title="사용성 어림법" sub="니엘슨 휴리스틱, 오늘은 5개만">
|
||||
<p>
|
||||
UX 학자 제이콥 니엘슨이 정리한 <strong>사용성 어림법(휴리스틱) 10가지</strong>는
|
||||
화면을 평가하는 체크리스트로 전 세계에서 쓰여요. 오늘은 그중 우리에게 가장
|
||||
자주 필요한 5개만, 우리 플랫폼 사례와 짝지어 봅니다.
|
||||
</p>
|
||||
<Code>{CODE_HEURISTICS}</Code>
|
||||
<p>
|
||||
눈치챘겠지만 ①은 섹션 4(피드백), ③도 섹션 4(제출 버튼 잠금), ④는 섹션 3(일관성)에서
|
||||
이미 배운 내용이에요. 휴리스틱은 새로운 지식이 아니라, 오늘 배운 것들에
|
||||
<strong> 이름표를 붙여 체크리스트로</strong> 만든 것에 가깝습니다.
|
||||
</p>
|
||||
<div className="tip">
|
||||
<b>직접 확인해 보기</b> 우리 플랫폼에서 일부러 틀려 보세요 — 로그인할 때 비밀번호를
|
||||
틀리게 넣거나, 필수 입력칸을 비우고 제출해 보기. 그때 뜨는 메시지가
|
||||
② <strong>사용자의 언어</strong>인가요, 개발자의 언어(에러 코드)인가요?
|
||||
사람 말로 "무엇이 잘못됐고 어떻게 하면 되는지" 알려 주면 합격입니다.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section n={8} title="직접 해보기: 화면 비평" sub="좋은 점 3, 아쉬운 점 3 — 배운 용어로">
|
||||
<p>
|
||||
이제 오늘 배운 걸 전부 꺼내 쓸 시간이에요. 우리 플랫폼의 화면 <strong>하나</strong>를
|
||||
골라(대시보드, 과제 제출, 로그인 — 뭐든 좋아요) 아래 틀대로 비평을 써 보세요.
|
||||
핵심 규칙은 하나: <strong>느낌이 아니라 용어로</strong> 말하기.
|
||||
"왠지 답답해요" 대신 "여백이 부족해서 그룹핑이 안 보여요(섹션 6)"처럼요.
|
||||
</p>
|
||||
<Code>{CODE_CRITIQUE}</Code>
|
||||
<div className="warn">
|
||||
<b>비평의 예의</b> 비평은 화면을 향하는 것이지, 만든 사람을 향하는 게 아니에요.
|
||||
"이 버튼은 위계가 약하다"는 비평, "누가 이렇게 만들었냐"는 공격.
|
||||
여러분이 만든 화면도 곧 누군가의 비평 대상이 됩니다 — 받고 싶은 방식으로 하세요.
|
||||
</div>
|
||||
<div className="tip">
|
||||
<b>이 실습이 진짜인 이유</b> 여기서 나온 근거 있는 아쉬운 점은 그대로
|
||||
디자인 티켓 <strong>DZ-02</strong>의 재료가 돼요. 수습 기간에 여러분의 비평이
|
||||
실제 서비스 개선으로 배포될 수 있다는 뜻입니다. 멘토에게 비평을 보여 주고,
|
||||
함께 티켓으로 다듬어 보세요.
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<div className="step-card">
|
||||
<div className="step-body">
|
||||
<h3 style={{ marginBottom: 6 }}>🎨 여기까지 왔다면</h3>
|
||||
<p className="muted">
|
||||
이제 "뭔가 불편해"를 <strong>용어와 근거</strong>로 말할 수 있게 됐어요.
|
||||
다음은 그 비평을 <strong>시안으로 만들 차례</strong> —{' '}
|
||||
<Link to="/learn/figma"><strong>피그마 기초</strong></Link> 코스에서
|
||||
섹션 8의 아쉬운 점을 직접 고친 화면을 그려 보세요. 말로 하는 비평보다
|
||||
그림으로 보여 주는 개선안이 백배 힘이 셉니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user