+ * ========================================================= */
+
+ /* Display (H1) — 100px 데스크탑 / 48px 모바일 */
+ --text-display: 100px;
+ --text-display--line-height: 1;
+ --text-display--letter-spacing: -1px;
+ --text-display--font-weight: 700;
+
+ --text-display-mobile: 48px;
+ --text-display-mobile--line-height: 1.05;
+ --text-display-mobile--letter-spacing: -1px;
+ --text-display-mobile--font-weight: 700;
+
+ /* H2 (Section) */
+ --text-h2: 56px;
+ --text-h2--line-height: 1.05;
+ --text-h2--letter-spacing: -0.2px;
+ --text-h2--font-weight: 700;
+
+ --text-h2-mobile: 42px;
+ --text-h2-mobile--line-height: 1.1;
+ --text-h2-mobile--letter-spacing: -0.2px;
+ --text-h2-mobile--font-weight: 700;
+
+ /* H3 (Card / Block) */
+ --text-h3: 38px;
+ --text-h3--line-height: 1.1;
+ --text-h3--letter-spacing: -0.2px;
+ --text-h3--font-weight: 700;
+
+ /* H4 */
+ --text-h4: 32px;
+ --text-h4--line-height: 1.2;
+ --text-h4--letter-spacing: -0.8px;
+ --text-h4--font-weight: 700;
+
+ /* H5 */
+ --text-h5: 24px;
+ --text-h5--line-height: 1.3;
+ --text-h5--letter-spacing: -0.2px;
+ --text-h5--font-weight: 700;
+
+ /* H6 */
+ --text-h6: 20px;
+ --text-h6--line-height: 1.4;
+ --text-h6--letter-spacing: -0.1px;
+ --text-h6--font-weight: 700;
+
+ /* Rich text (큰 본문 / 강조) */
+ --text-rich: 20px;
+ --text-rich--line-height: 1.5;
+ --text-rich--font-weight: 500;
+
+ /* Body 1 (기본 본문) */
+ --text-body: 16px;
+ --text-body--line-height: 1.2;
+ --text-body--letter-spacing: -0.01em;
+ --text-body--font-weight: 500;
+
+ /* Body 2 / Button (작은 본문 / 버튼) */
+ --text-button: 14px;
+ --text-button--line-height: 1.2;
+ --text-button--font-weight: 600;
+
+ /* Caption */
+ --text-caption: 12px;
+ --text-caption--line-height: 1.4;
+ --text-caption--font-weight: 400;
+
+ /* =========================================================
+ * 8. Radius
+ * ========================================================= */
+ --radius-sm: 4px;
+ --radius-md: 8px;
+ --radius-lg: 12px;
+ --radius-xl: 16px;
+ --radius-2xl: 24px;
+ --radius-pill: 9999px;
+
+ /* =========================================================
+ * 9. Shadow / Elevation (sage-tint)
+ * shadow-sm, shadow-md, shadow-lg, shadow-focus-ring
+ * ========================================================= */
+ --shadow-sm: 0 1px 2px rgba(31, 39, 27, 0.06);
+ --shadow-md:
+ 0 4px 6px -1px rgba(31, 39, 27, 0.10),
+ 0 2px 4px -2px rgba(31, 39, 27, 0.06);
+ --shadow-lg:
+ 0 10px 15px -3px rgba(31, 39, 27, 0.10),
+ 0 4px 6px -4px rgba(31, 39, 27, 0.05);
+ --shadow-focus-ring: 0 0 0 3px rgba(98, 110, 92, 0.45);
+
+ /* =========================================================
+ * 10. Motion
+ * duration-fast/normal/slow, ease-standard/decelerate/accelerate
+ * ========================================================= */
+ --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
+ --ease-decelerate: cubic-bezier(0, 0, 0.2, 1);
+ --ease-accelerate: cubic-bezier(0.4, 0, 1, 1);
+
+ --duration-fast: 120ms;
+ --duration-normal: 200ms;
+ --duration-slow: 320ms;
+
+ /* =========================================================
+ * 11. Z-Index Scale
+ * `--z-*` 는 Tailwind 자동 namespace 가 아니므로 utility 미생성.
+ * 사용 — style={{ zIndex: 'var(--z-modal)' }} 또는 className="z-[var(--z-modal)]"
+ * ========================================================= */
+ --z-base: 0;
+ --z-raised: 10;
+ --z-dropdown: 1000;
+ --z-sticky: 1100;
+ --z-modal-backdrop: 1200;
+ --z-modal: 1300;
+ --z-popover: 1400;
+ --z-tooltip: 1500;
+ --z-toast: 1600;
+
+ /* =========================================================
+ * 12. Container max-widths (Tailwind v4 max-w-*)
+ * max-w-readable, max-w-content, max-w-app
+ * ========================================================= */
+ --container-readable: 65ch; /* 본문 / 프로즈 카드 */
+ --container-content: 1280px; /* TopNav 안쪽 메인 영역 */
+ --container-app: 1440px; /* 앱 전체 최대 폭 */
+
+ /* =========================================================
+ * 13. Breakpoints
+ * Tailwind v4 default 사용 — sm 640 / md 768 / lg 1024 / xl 1280 / 2xl 1536.
+ * 재정의 필요 시 --breakpoint-* 로 오버라이드.
+ * ========================================================= */
+}
diff --git a/frontend/src/index.css b/frontend/src/index.css
deleted file mode 100644
index 5fb33130..00000000
--- a/frontend/src/index.css
+++ /dev/null
@@ -1,111 +0,0 @@
-:root {
- --text: #6b6375;
- --text-h: #08060d;
- --bg: #fff;
- --border: #e5e4e7;
- --code-bg: #f4f3ec;
- --accent: #aa3bff;
- --accent-bg: rgba(170, 59, 255, 0.1);
- --accent-border: rgba(170, 59, 255, 0.5);
- --social-bg: rgba(244, 243, 236, 0.5);
- --shadow:
- rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
-
- --sans: system-ui, 'Segoe UI', Roboto, sans-serif;
- --heading: system-ui, 'Segoe UI', Roboto, sans-serif;
- --mono: ui-monospace, Consolas, monospace;
-
- font: 18px/145% var(--sans);
- letter-spacing: 0.18px;
- color-scheme: light dark;
- color: var(--text);
- background: var(--bg);
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-
- @media (max-width: 1024px) {
- font-size: 16px;
- }
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --text: #9ca3af;
- --text-h: #f3f4f6;
- --bg: #16171d;
- --border: #2e303a;
- --code-bg: #1f2028;
- --accent: #c084fc;
- --accent-bg: rgba(192, 132, 252, 0.15);
- --accent-border: rgba(192, 132, 252, 0.5);
- --social-bg: rgba(47, 48, 58, 0.5);
- --shadow:
- rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
- }
-
- #social .button-icon {
- filter: invert(1) brightness(2);
- }
-}
-
-#root {
- width: 1126px;
- max-width: 100%;
- margin: 0 auto;
- text-align: center;
- border-inline: 1px solid var(--border);
- min-height: 100svh;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
-}
-
-body {
- margin: 0;
-}
-
-h1,
-h2 {
- font-family: var(--heading);
- font-weight: 500;
- color: var(--text-h);
-}
-
-h1 {
- font-size: 56px;
- letter-spacing: -1.68px;
- margin: 32px 0;
- @media (max-width: 1024px) {
- font-size: 36px;
- margin: 20px 0;
- }
-}
-h2 {
- font-size: 24px;
- line-height: 118%;
- letter-spacing: -0.24px;
- margin: 0 0 8px;
- @media (max-width: 1024px) {
- font-size: 20px;
- }
-}
-p {
- margin: 0;
-}
-
-code,
-.counter {
- font-family: var(--mono);
- display: inline-flex;
- border-radius: 4px;
- color: var(--text-h);
-}
-
-code {
- font-size: 15px;
- line-height: 135%;
- padding: 4px 8px;
- background: var(--code-bg);
-}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index bef5202a..037249fa 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,6 +1,6 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
-import './index.css'
+import '@/app/styles'
import App from './App.tsx'
createRoot(document.getElementById('root')!).render(
diff --git a/frontend/src/pages/DesignSystem/DesignSystemDemo.tsx b/frontend/src/pages/DesignSystem/DesignSystemDemo.tsx
new file mode 100644
index 00000000..ea90c99f
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/DesignSystemDemo.tsx
@@ -0,0 +1,122 @@
+import { useState } from 'react'
+import { NAV, type SectionId } from './nav'
+import { Code } from './primitives'
+import {
+ ColorsSection,
+ ContainerSection,
+ MotionSection,
+ OverviewSection,
+ RadiusSection,
+ ShadowSection,
+ SpacingSection,
+ TypographySection,
+ ZIndexSection,
+} from './sections'
+
+function Sidebar({
+ active,
+ onSelect,
+}: {
+ active: SectionId
+ onSelect: (id: SectionId) => void
+}) {
+ return (
+
+ )
+}
+
+function Stat({ label, value }: { label: string; value: string }) {
+ return (
+
+
+ {label}
+
+
+ {value}
+
+
+ )
+}
+
+function Hero() {
+ return (
+
+
+ frontend / app / styles / tokens.css
+
+
+ Design System
+
+
+ StackUp 프론트엔드의 시각·행동 규약. tokens.css 의{' '}
+ @theme 블록을 SSOT 로 한 Tailwind v4 토큰 시스템.
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default function DesignSystemDemo() {
+ const [active, setActive] = useState('overview')
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/frontend/src/pages/DesignSystem/index.ts b/frontend/src/pages/DesignSystem/index.ts
new file mode 100644
index 00000000..d7fa05b3
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/index.ts
@@ -0,0 +1 @@
+export { default } from './DesignSystemDemo'
diff --git a/frontend/src/pages/DesignSystem/lib.ts b/frontend/src/pages/DesignSystem/lib.ts
new file mode 100644
index 00000000..868c8698
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/lib.ts
@@ -0,0 +1,13 @@
+export function luminance(hex: string): number {
+ const h = hex.replace('#', '')
+ const r = parseInt(h.substring(0, 2), 16) / 255
+ const g = parseInt(h.substring(2, 4), 16) / 255
+ const b = parseInt(h.substring(4, 6), 16) / 255
+ const lin = (v: number) =>
+ v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)
+ return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b)
+}
+
+export function readableFg(hex: string): string {
+ return luminance(hex) > 0.45 ? '#1f271b' : '#ffffff'
+}
diff --git a/frontend/src/pages/DesignSystem/nav.ts b/frontend/src/pages/DesignSystem/nav.ts
new file mode 100644
index 00000000..228cc570
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/nav.ts
@@ -0,0 +1,22 @@
+export type SectionId =
+ | 'overview'
+ | 'colors'
+ | 'typography'
+ | 'radius'
+ | 'shadow'
+ | 'spacing'
+ | 'motion'
+ | 'z-index'
+ | 'container'
+
+export const NAV: { id: SectionId; label: string }[] = [
+ { id: 'overview', label: 'Overview' },
+ { id: 'colors', label: 'Colors' },
+ { id: 'typography', label: 'Typography' },
+ { id: 'radius', label: 'Radius' },
+ { id: 'shadow', label: 'Shadow' },
+ { id: 'spacing', label: 'Spacing' },
+ { id: 'motion', label: 'Motion' },
+ { id: 'z-index', label: 'Z-Index' },
+ { id: 'container', label: 'Container' },
+]
diff --git a/frontend/src/pages/DesignSystem/primitives.tsx b/frontend/src/pages/DesignSystem/primitives.tsx
new file mode 100644
index 00000000..d5e3008a
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/primitives.tsx
@@ -0,0 +1,116 @@
+import { type ReactNode } from 'react'
+import type { SectionId } from './nav'
+import { readableFg } from './lib'
+
+/**
+ * 데모 페이지 전용 UI 프리미티브.
+ * - Section / Sub : 섹션 / 하위 섹션 헤딩
+ * - Swatch : 컬러 스와치 카드
+ * - Code : 인라인 코드 / 토큰 배지
+ * - TableHead : 공통 테이블 헤더
+ *
+ * 모두 디자인 시스템 토큰만 사용 — 하드코딩된 색상 없음.
+ */
+
+export function Section({
+ id,
+ label,
+ title,
+ description,
+ children,
+}: {
+ id: SectionId
+ label: string
+ title: string
+ description?: string
+ children: ReactNode
+}) {
+ return (
+
+
+
+ {label}
+
+
+ {title}
+
+ {description && (
+ {description}
+ )}
+
+ {children}
+
+ )
+}
+
+export function Sub({
+ title,
+ action,
+ children,
+}: {
+ title: string
+ action?: ReactNode
+ children: ReactNode
+}) {
+ return (
+
+
+
+ {title}
+
+ {action}
+
+ {children}
+
+ )
+}
+
+export function Swatch({
+ token,
+ hex,
+ sub,
+}: {
+ token: string
+ hex: string
+ sub?: string
+}) {
+ const fg = readableFg(hex)
+ return (
+
+
+ {hex.toUpperCase()}
+
+
+
{token}
+ {sub && (
+
{sub}
+ )}
+
+
+ )
+}
+
+export function Code({ children }: { children: ReactNode }) {
+ return (
+
+ {children}
+
+ )
+}
+
+export function TableHead({ cols }: { cols: string[] }) {
+ return (
+
+
+ {cols.map((c) => (
+ |
+ {c}
+ |
+ ))}
+
+
+ )
+}
diff --git a/frontend/src/pages/DesignSystem/sections/Colors.tsx b/frontend/src/pages/DesignSystem/sections/Colors.tsx
new file mode 100644
index 00000000..d9399766
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/sections/Colors.tsx
@@ -0,0 +1,260 @@
+import { Code, Section, Sub, Swatch, TableHead } from '../primitives'
+import { readableFg } from '../lib'
+
+const SAGE_SCALE = [
+ { token: 'sage-50', hex: '#e8e7e1' },
+ { token: 'sage-100', hex: '#d4cfcb' },
+ { token: 'sage-200', hex: '#c9ccc8' },
+ { token: 'sage-300', hex: '#b4bdaf' },
+ { token: 'sage-400', hex: '#a0a89d' },
+ { token: 'sage-500', hex: '#626e5c' },
+ { token: 'sage-600', hex: '#3e4739' },
+ { token: 'sage-700', hex: '#2b3625' },
+ { token: 'sage-800', hex: '#1f271b' },
+ { token: 'sage-900', hex: '#181e15' },
+ { token: 'sage-950', hex: '#141a11' },
+]
+
+const SEMANTIC_ROWS = [
+ { token: 'bg', alias: 'background', hex: '#e9e8e7', usage: '앱 기본 배경' },
+ { token: 'surface', alias: 'sage-50', hex: '#e8e7e1', usage: '컴포넌트 배경' },
+ { token: 'surface-raised', alias: 'white', hex: '#ffffff', usage: '카드 · 모달' },
+ { token: 'border', alias: 'sage-100', hex: '#d4cfcb', usage: '기본 보더' },
+ { token: 'border-strong', alias: 'sage-200', hex: '#c9ccc8', usage: '강조 보더' },
+ { token: 'fg', alias: 'sage-950', hex: '#141a11', usage: '기본 텍스트' },
+ { token: 'fg-strong', alias: 'sage-800', hex: '#1f271b', usage: '헤딩' },
+ { token: 'fg-muted', alias: 'sage-400', hex: '#a0a89d', usage: '보조 텍스트' },
+ { token: 'fg-subtle', alias: 'sage-300', hex: '#b4bdaf', usage: '흐린 텍스트' },
+ { token: 'fg-disabled', alias: 'sage-200', hex: '#c9ccc8', usage: '비활성' },
+ { token: 'primary', alias: 'sage-500', hex: '#626e5c', usage: 'Primary 액션' },
+ { token: 'primary-hover', alias: 'sage-600', hex: '#3e4739', usage: 'hover' },
+ { token: 'primary-pressed', alias: 'sage-700', hex: '#2b3625', usage: 'active' },
+]
+
+const STATUS_COLORS = [
+ {
+ key: 'success',
+ name: 'Success',
+ example: 'COMPLETED · ANALYZED',
+ shades: [
+ { label: '-50', hex: '#e8efe1' },
+ { label: '-500', hex: '#5b7c47' },
+ { label: '-700', hex: '#3f5731' },
+ ],
+ bgLight: '#e8efe1',
+ textLight: '#3f5731',
+ bgSolid: '#5b7c47',
+ },
+ {
+ key: 'warning',
+ name: 'Warning',
+ example: 'IN_PROGRESS · ANALYZING',
+ shades: [
+ { label: '-50', hex: '#f4e8d4' },
+ { label: '-500', hex: '#b88840' },
+ { label: '-700', hex: '#8a6529' },
+ ],
+ bgLight: '#f4e8d4',
+ textLight: '#8a6529',
+ bgSolid: '#b88840',
+ },
+ {
+ key: 'danger',
+ name: 'Danger',
+ example: 'FAILED · 삭제',
+ shades: [
+ { label: '-50', hex: '#f4e0d8' },
+ { label: '-500', hex: '#a8503c' },
+ { label: '-700', hex: '#803a2a' },
+ ],
+ bgLight: '#f4e0d8',
+ textLight: '#803a2a',
+ bgSolid: '#a8503c',
+ },
+ {
+ key: 'info',
+ name: 'Info',
+ example: '정보 토스트 · 안내',
+ shades: [
+ { label: '-50', hex: '#dde4ea' },
+ { label: '-500', hex: '#4d6878' },
+ { label: '-700', hex: '#36475a' },
+ ],
+ bgLight: '#dde4ea',
+ textLight: '#36475a',
+ bgSolid: '#4d6878',
+ },
+]
+
+const DOMAIN_JOBS = [
+ { token: 'job-frontend', hex: '#5e8a98', name: 'Frontend', tone: 'teal' },
+ { token: 'job-backend', hex: '#7d6c93', name: 'Backend', tone: 'plum' },
+ { token: 'job-infra', hex: '#b06c70', name: 'Infra', tone: 'rose' },
+ { token: 'job-dba', hex: '#b89c5e', name: 'DBA', tone: 'gold' },
+]
+
+const DOMAIN_TYPES = [
+ { token: 'type-personality', hex: '#6f9978', name: 'Personality', tone: 'mint' },
+ { token: 'type-technical', hex: '#6c8294', name: 'Technical', tone: 'slate' },
+ { token: 'type-live-coding', hex: '#a87385', name: 'Live Coding', tone: 'mauve' },
+ { token: 'type-integrated', hex: '#8a7896', name: 'Integrated', tone: 'violet' },
+]
+
+function DomainCard({
+ token,
+ hex,
+ name,
+ tone,
+}: {
+ token: string
+ hex: string
+ name: string
+ tone: string
+}) {
+ return (
+
+
+ {hex.toUpperCase()}
+
+
+
+ {name}
+
+
+ {token} · {tone}
+
+
+
+ )
+}
+
+export function ColorsSection() {
+ return (
+
+
+
+ {SAGE_SCALE.map((c) => (
+
+ ))}
+
+
+
+
+
+
+
+
+ {SEMANTIC_ROWS.map((row) => (
+
+ |
+ {row.token}
+ |
+
+ {row.alias}
+ |
+
+
+
+
+ {row.hex}
+
+
+ |
+ {row.usage} |
+
+ ))}
+
+
+
+
+
+
+
+ {STATUS_COLORS.map((s) => (
+
+
+
+ {s.name}
+
+
+ {s.example}
+
+
+
+
+ {s.shades.map((sh) => (
+
+ ))}
+
+
+
+ Light Badge
+
+
+ Solid Badge
+
+
+
+ {`bg-${s.key}-50 text-${s.key}-700`}
+ {`bg-${s.key} text-white`}
+
+
+
+ ))}
+
+
+
+
+
+
+
+ 직군 (Jobs)
+
+
+ {DOMAIN_JOBS.map((d) => (
+
+ ))}
+
+
+
+
+ 면접 유형 (Interview Types)
+
+
+ {DOMAIN_TYPES.map((d) => (
+
+ ))}
+
+
+
+
+
+ )
+}
diff --git a/frontend/src/pages/DesignSystem/sections/Container.tsx b/frontend/src/pages/DesignSystem/sections/Container.tsx
new file mode 100644
index 00000000..cbdd9aa1
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/sections/Container.tsx
@@ -0,0 +1,89 @@
+import { Code, Section, Sub } from '../primitives'
+
+const CONTAINERS = [
+ {
+ token: '--container-readable',
+ utility: 'max-w-readable',
+ value: '65ch',
+ pct: 45,
+ usage: '본문 · 프로즈 카드',
+ },
+ {
+ token: '--container-content',
+ utility: 'max-w-content',
+ value: '1280px',
+ pct: 89,
+ usage: 'TopNav 안 메인 영역',
+ },
+ {
+ token: '--container-app',
+ utility: 'max-w-app',
+ value: '1440px',
+ pct: 100,
+ usage: '앱 전체 최대 폭',
+ },
+]
+
+const BREAKPOINTS = [
+ { label: 'sm', value: '640px' },
+ { label: 'md', value: '768px' },
+ { label: 'lg', value: '1024px' },
+ { label: 'xl', value: '1280px' },
+ { label: '2xl', value: '1536px' },
+]
+
+export function ContainerSection() {
+ return (
+
+
+
+ {CONTAINERS.map((c) => (
+
+
+
+ {c.utility}
+
+ {c.value}
+
+
+
{c.usage}
+
+
+
+ ))}
+
+
+
+
+
+ {BREAKPOINTS.map((bp) => (
+
+
+ {bp.label}
+
+
+ {bp.value}
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/frontend/src/pages/DesignSystem/sections/Motion.tsx b/frontend/src/pages/DesignSystem/sections/Motion.tsx
new file mode 100644
index 00000000..8d2cf8f3
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/sections/Motion.tsx
@@ -0,0 +1,97 @@
+import { useState } from 'react'
+import { Code, Section } from '../primitives'
+
+const DURATIONS = [
+ { token: 'duration-fast', value: '120ms', usage: 'hover · focus' },
+ { token: 'duration-normal', value: '200ms', usage: '기본 transition' },
+ { token: 'duration-slow', value: '320ms', usage: '모달 enter / exit' },
+]
+
+const EASINGS = [
+ { token: 'ease-standard', curve: 'cubic-bezier(0.4, 0, 0.2, 1)', usage: '기본' },
+ { token: 'ease-decelerate', curve: 'cubic-bezier(0, 0, 0.2, 1)', usage: 'enter' },
+ { token: 'ease-accelerate', curve: 'cubic-bezier(0.4, 0, 1, 1)', usage: 'exit' },
+]
+
+function MotionDemo() {
+ const [hover, setHover] = useState(false)
+ return (
+ setHover(true)}
+ onMouseLeave={() => setHover(false)}
+ >
+
+
+ hover → 200ms · ease-standard
+
+
+ )
+}
+
+export function MotionSection() {
+ return (
+
+
+
+
+ Duration
+
+
+ {DURATIONS.map((d) => (
+
+
{d.token}
+
+
+ {d.value}
+
+
+ {d.usage}
+
+
+
+ ))}
+
+
+
+
+ Easing
+
+
+ {EASINGS.map((e) => (
+
+
{e.token}
+
+
+ {e.curve}
+
+
{e.usage}
+
+
+ ))}
+
+
+
+
+
+ )
+}
diff --git a/frontend/src/pages/DesignSystem/sections/Overview.tsx b/frontend/src/pages/DesignSystem/sections/Overview.tsx
new file mode 100644
index 00000000..e7564323
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/sections/Overview.tsx
@@ -0,0 +1,38 @@
+import { Section } from '../primitives'
+
+const ROWS: [string, string][] = [
+ ['Sage Scale', 'sage-50 … sage-950 (11단계)'],
+ ['Semantic', '14개 별칭 — fg / bg / surface / border / primary'],
+ ['Status', 'success · warning · danger · info × 3 shades'],
+ ['Domain', '직군 4 + 면접 유형 4 (muted jewel tone)'],
+ ['Typography', 'Fira / Geist / Inter 3-Tier · 10단계 텍스트 스케일'],
+ ['Radius', 'sm → 2xl + pill 6단계'],
+ ['Shadow', 'sm · md · lg · focus-ring (sage-tint)'],
+ ['Spacing', '4px grid — Tailwind 기본 scale'],
+ ['Motion', '120 / 200 / 320ms + 3 easing'],
+ ['Z-Index', '9단계 (0 → 1600)'],
+ ['Container', 'readable 65ch · content 1280px · app 1440px'],
+]
+
+export function OverviewSection() {
+ return (
+
+
+ {ROWS.map(([k, v]) => (
+
+
- {k}
+ - {v}
+
+ ))}
+
+
+ )
+}
diff --git a/frontend/src/pages/DesignSystem/sections/Radius.tsx b/frontend/src/pages/DesignSystem/sections/Radius.tsx
new file mode 100644
index 00000000..efe78e94
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/sections/Radius.tsx
@@ -0,0 +1,35 @@
+import { Code, Section } from '../primitives'
+
+const RADII = [
+ { token: 'sm', value: '4px', cls: 'rounded-sm' },
+ { token: 'md', value: '8px', cls: 'rounded-md' },
+ { token: 'lg', value: '12px', cls: 'rounded-lg' },
+ { token: 'xl', value: '16px', cls: 'rounded-xl' },
+ { token: '2xl', value: '24px', cls: 'rounded-2xl' },
+ { token: 'pill', value: '9999px', cls: 'rounded-pill' },
+]
+
+export function RadiusSection() {
+ return (
+
+
+ {RADII.map((r) => (
+
+
+
+ {r.cls}
+
+
{r.value}
+
+ ))}
+
+
+ )
+}
diff --git a/frontend/src/pages/DesignSystem/sections/Shadow.tsx b/frontend/src/pages/DesignSystem/sections/Shadow.tsx
new file mode 100644
index 00000000..af5c25d6
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/sections/Shadow.tsx
@@ -0,0 +1,34 @@
+import { Code, Section } from '../primitives'
+
+const SHADOWS = [
+ { cls: 'shadow-sm', label: 'shadow-sm', desc: '카드 기본' },
+ { cls: 'shadow-md', label: 'shadow-md', desc: '드롭다운' },
+ { cls: 'shadow-lg', label: 'shadow-lg', desc: '모달' },
+ { cls: 'shadow-focus-ring', label: 'shadow-focus-ring', desc: 'Focus ring' },
+]
+
+export function ShadowSection() {
+ return (
+
+
+ {SHADOWS.map((s) => (
+
+ ))}
+
+
+ )
+}
diff --git a/frontend/src/pages/DesignSystem/sections/Spacing.tsx b/frontend/src/pages/DesignSystem/sections/Spacing.tsx
new file mode 100644
index 00000000..6141e807
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/sections/Spacing.tsx
@@ -0,0 +1,31 @@
+import { Section } from '../primitives'
+
+const SCALE = [1, 2, 3, 4, 5, 6, 8, 10, 12, 16]
+
+export function SpacingSection() {
+ return (
+
+
+ {SCALE.map((n) => (
+
+
+ space-{n}
+
+
+
+ {n * 4}px
+
+
+ ))}
+
+
+ )
+}
diff --git a/frontend/src/pages/DesignSystem/sections/Typography.tsx b/frontend/src/pages/DesignSystem/sections/Typography.tsx
new file mode 100644
index 00000000..a922a348
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/sections/Typography.tsx
@@ -0,0 +1,102 @@
+import { Code, Section, Sub } from '../primitives'
+
+const SAMPLE = 'StackUp · 빠른 면접, 깊은 피드백'
+
+const FONT_FAMILIES = [
+ {
+ token: '--font-heading / display',
+ utility: 'font-heading',
+ name: 'Fira Sans Extra Condensed',
+ usage: 'H1 · H2 · H3 (Uppercase)',
+ cls: 'font-heading text-h4 uppercase font-bold',
+ },
+ {
+ token: '--font-subheading',
+ utility: 'font-subheading',
+ name: 'Geist',
+ usage: 'H4 · H5 · H6 · Caption',
+ cls: 'font-subheading text-h4 font-bold',
+ },
+ {
+ token: '--font-sans / body',
+ utility: 'font-sans',
+ name: 'Inter',
+ usage: '본문 · 버튼',
+ cls: 'font-sans text-h4',
+ },
+ {
+ token: '--font-mono',
+ utility: 'font-mono',
+ name: 'Geist Mono',
+ usage: '코드 · 숫자',
+ cls: 'font-mono text-h5',
+ },
+]
+
+const TEXT_SCALE = [
+ { token: 'text-display', cls: 'font-display text-display-mobile lg:text-display uppercase', size: '100 / 48', weight: 700, usage: '랜딩 타이틀' },
+ { token: 'text-h2', cls: 'font-heading text-h2-mobile lg:text-h2 uppercase', size: '56 / 42', weight: 700, usage: '섹션 제목' },
+ { token: 'text-h3', cls: 'font-heading text-h3 uppercase', size: '38', weight: 700, usage: '카드 제목' },
+ { token: 'text-h4', cls: 'font-subheading text-h4', size: '32', weight: 700, usage: '서브헤딩 L' },
+ { token: 'text-h5', cls: 'font-subheading text-h5', size: '24', weight: 700, usage: '서브헤딩 M' },
+ { token: 'text-h6', cls: 'font-subheading text-h6', size: '20', weight: 700, usage: '서브헤딩 S' },
+ { token: 'text-rich', cls: 'font-subheading text-rich', size: '20', weight: 500, usage: '큰 본문 / 강조' },
+ { token: 'text-body', cls: 'font-sans text-body', size: '16', weight: 500, usage: '기본 본문' },
+ { token: 'text-button', cls: 'font-sans text-button', size: '14', weight: 600, usage: '버튼 · 작은 본문' },
+ { token: 'text-caption', cls: 'font-subheading text-caption', size: '12', weight: 400, usage: '캡션 · 메타' },
+]
+
+export function TypographySection() {
+ return (
+
+
+
+ {FONT_FAMILIES.map((f) => (
+
+
+ {f.name}
+
+
+
+ {f.utility}
+
+
{f.usage}
+
+
+ ))}
+
+
+
+
+
+ {TEXT_SCALE.map((t) => (
+
+
{SAMPLE}
+
+ {t.token}
+
+ {t.size}px
+
+
+ w{t.weight}
+
+ {t.usage}
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/frontend/src/pages/DesignSystem/sections/ZIndex.tsx b/frontend/src/pages/DesignSystem/sections/ZIndex.tsx
new file mode 100644
index 00000000..5b2218fc
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/sections/ZIndex.tsx
@@ -0,0 +1,74 @@
+import { Section, TableHead } from '../primitives'
+
+const Z_INDEX = [
+ { token: '--z-toast', value: 1600, usage: 'Toast' },
+ { token: '--z-tooltip', value: 1500, usage: 'Tooltip' },
+ { token: '--z-popover', value: 1400, usage: 'Popover' },
+ { token: '--z-modal', value: 1300, usage: '모달 본체' },
+ { token: '--z-modal-backdrop', value: 1200, usage: '모달 dim' },
+ { token: '--z-sticky', value: 1100, usage: 'Sticky TopNav' },
+ { token: '--z-dropdown', value: 1000, usage: 'Select · Menu' },
+ { token: '--z-raised', value: 10, usage: '카드 hover' },
+ { token: '--z-base', value: 0, usage: '기본 레이어' },
+]
+
+export function ZIndexSection() {
+ return (
+
+
+
+
+
+
+ {Z_INDEX.map((z) => (
+
+ |
+ {z.token}
+ |
+
+ {z.value}
+ |
+ {z.usage} |
+
+ ))}
+
+
+
+
+
+
+ Stacking Order
+
+
+ {[...Z_INDEX].reverse().map((z, i) => {
+ const intensity = 0.07 + i * 0.085
+ return (
+
0.5 ? '#ffffff' : '#1f271b',
+ }}
+ >
+
+ {z.value}
+
+ {z.usage}
+
+ )
+ })}
+
+
+
+
+ )
+}
diff --git a/frontend/src/pages/DesignSystem/sections/index.ts b/frontend/src/pages/DesignSystem/sections/index.ts
new file mode 100644
index 00000000..779b4e36
--- /dev/null
+++ b/frontend/src/pages/DesignSystem/sections/index.ts
@@ -0,0 +1,9 @@
+export { OverviewSection } from './Overview'
+export { ColorsSection } from './Colors'
+export { TypographySection } from './Typography'
+export { RadiusSection } from './Radius'
+export { ShadowSection } from './Shadow'
+export { SpacingSection } from './Spacing'
+export { MotionSection } from './Motion'
+export { ZIndexSection } from './ZIndex'
+export { ContainerSection } from './Container'
diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json
index af516fcc..b2a52c6b 100644
--- a/frontend/tsconfig.app.json
+++ b/frontend/tsconfig.app.json
@@ -22,7 +22,21 @@
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
- "noUncheckedSideEffectImports": true
+ "noUncheckedSideEffectImports": true,
+
+ /* Path mapping */
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"],
+ "@/shared/*": ["src/shared/*"],
+ "@/entities/*": ["src/domain/*"],
+ "@/features/*": ["src/features/*"],
+ "@/pages/*": ["src/pages/*"],
+ "@/app/*": ["src/app/*"],
+ },
+
+
+
},
"include": ["src"]
}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 8b0f57b9..bb1d7b51 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -1,7 +1,14 @@
import { defineConfig } from 'vite'
+import path from 'node:path'
import react from '@vitejs/plugin-react'
+import tailwindcss from '@tailwindcss/vite'
// https://vite.dev/config/
export default defineConfig({
- plugins: [react()],
+ plugins: [react(), tailwindcss()],
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ },
+ },
})