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:
AWESOMEDEV 2026-07-16 09:37:33 +09:00
parent 8f1143aee8
commit 3adebdb44a
8 changed files with 2784 additions and 0 deletions

View File

@ -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 />} />

View 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">&lt;/&gt;</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>
);
}

View 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">&lt;html&gt;</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">&lt;div&gt;</span> "이름 없는 그냥 상자"인데,
HTML에는 역할이 이름에 담긴 <strong>시맨틱 태그</strong>들이 있어요.
우리 플랫폼의 실제 뼈대를 보면 이렇게 생겼습니다.
</p>
<Code>{CODE_SEMANTIC}</Code>
<div className="warn">
<b>클릭은 button에게</b> 겉보기엔 <span className="icode">&lt;div onClick=...&gt;</span>
클릭이 되지만, 키보드 <span className="kbd">Tab</span>으로는 접근이 되고 스크린리더도
버튼인 몰라요. "누르는 것" 반드시 <span className="icode">&lt;button&gt;</span>
우리 코드 리뷰에서도 자주 나오는 지적입니다.
</div>
<div className="tip">
<b>직접 확인해 보기</b> 페이지에서 <span className="kbd">F12</span>
<span className="kbd">Elements</span> 탭을 열어 보세요. 접었다 있는 태그 트리가
보일 거예요. <span className="icode">&lt;section class="step-card"&gt;</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>
);
}

View 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>
);
}

View File

@ -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() {

View 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" &gt;&gt; ~/test.log</span>,{' '}
<span className="icode">echo "ERROR: 큰일남" &gt;&gt; ~/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">&amp;</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>
);
}

View 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">&gt;=</span>, <span className="icode">&lt;&gt;</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>
);
}

View 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>
);
}