# WING-OPS 디자인 시스템 (백업)
Tailwind CSS + @apply 기반 디자인 시스템. `wing-*` CSS 클래스와 React UI 컴포넌트로 구성.
---
## 브랜드 (Brand)
### 로고
해양 환경 위기대응을 위한 통합 솔루션 **WING**의 로고.
- **파일**: `frontend/public/wing_logo_white.svg`
- **네이티브 크기**: 280 × 20px (비율 14:1)
- **색상**: 단색 흰색 (다크 배경 전용)
#### 로고 규격
| 용도 | 높이 | Tailwind | 비고 |
|------|------|----------|------|
| Header | 14px | `h-3.5` | TopBar 52px 높이 내 사용 (현재) |
| Standard | 24px | `h-6` | 일반 UI, 문서 내 |
| Large | 32px | `h-8` | 로그인, 랜딩 화면 |
| **Minimum** | **14px** | `h-3.5` | 이보다 작게 사용 금지 |
#### 여백 규칙 (Clear Space)
- 최소 여백: 로고 높이의 **50%** (상하좌우)
- 로고 주변에 다른 텍스트나 아이콘이 침범하지 않도록 유지
### 테마 컬러
다크 테마 기반. 게시판(Board) 메뉴에서 추출한 컬러 체계.
#### 배경 (Background) — 딥 네이비
| 토큰 | CSS 변수 | HEX | 용도 |
|------|----------|-----|------|
| `bg-bg-0` | `--bg0` | `#0a0e1a` | 최하위 배경 (body, input) |
| `bg-bg-1` | `--bg1` | `#0f1524` | 패널, 모달, 푸터 배경 |
| `bg-bg-2` | `--bg2` | `#121929` | 테이블 헤더, elevated 영역 |
| `bg-bg-3` | `--bg3` | `#1a2236` | 카드, 보조 버튼, 비활성 요소 |
| `bg-hover` | `--bgH` | `#1e2844` | 행 hover |
#### 텍스트 (Text)
| 토큰 | CSS 변수 | HEX | 용도 |
|------|----------|-----|------|
| `text-text-1` | `--t1` | `#edf0f7` | 주 텍스트 (제목, 본문) |
| `text-text-2` | `--t2` | `#b0b8cc` | 보조 텍스트 (라벨, 설명) |
| `text-text-3` | `--t3` | `#8690a6` | 비활성/메타 (날짜, 조회수) |
#### 브랜드 강조색 (Accent)
| 토큰 | HEX | 용도 |
|------|-----|------|
| `primary-cyan` | `#06b6d4` | 주 강조색 — 활성 상태, 링크, CTA |
| `primary-blue` | `#3b82f6` | 보조 강조색 — 그라데이션 끝점 |
| **Primary Gradient** | `linear-gradient(135deg, #06b6d4, #3b82f6)` | Primary 버튼 |
#### 시맨틱 (Semantic)
| 토큰 | HEX | 용도 |
|------|-----|------|
| `red` / `danger` | `#ef4444` | 삭제, 필수 표시(*) |
| `orange` | `#f97316` | 경고 |
| `yellow` | `#eab308` | 주의 |
| `green` | `#22c55e` | 성공, 정상 |
| `purple` | `#a855f7` | 특수 강조 |
#### 테두리 (Border)
| 토큰 | CSS 변수 | HEX | 용도 |
|------|----------|-----|------|
| `border` | `--bd` | `#1e2a42` | 기본 구분선 |
| `border-light` | `--bdL` | `#2a3a5c` | 밝은 테두리 |
#### 오버레이 (Overlay)
| 토큰 | 값 | 용도 |
|------|-----|------|
| `overlay` | `rgba(0, 0, 0, 0.55)` | 모달 배경 오버레이 |
---
## 디자인 원칙
### 컬러 사용 규칙
- **상태 표현** (위험/정상/주의) → 컬러 배지 사용 (red, green, yellow)
- **단순 분류** (공지사항, 자료실, Q&A) → 기본 텍스트 컬러 또는 neutral 배지
- **강조** (고정글, 선택 항목) → accent 컬러 (cyan)
- **원칙**: 색상은 정보를 전달할 때만 사용. 장식 목적 금지.
---
## 1. 토큰
### 컬러 팔레트
| CSS 변수 | 값 | 용도 |
|----------|-----|------|
| `--bg0` | `#0a0e1a` | 최하위 배경 (body, input) |
| `--bg1` | `#0f1524` | 기본 패널 배경 |
| `--bg2` | `#121929` | 테이블 헤더, elevated |
| `--bg3` | `#1a2236` | 카드, 섹션 배경 |
| `--bgH` | `#1e2844` | hover 상태 |
| `--bd` | `#1e2a42` | 기본 테두리 |
| `--bdL` | `#2a3a5c` | 밝은 테두리 |
| `--t1` | `#edf0f7` | 기본 텍스트 (밝음) |
| `--t2` | `#b0b8cc` | 보조 텍스트 |
| `--t3` | `#8690a6` | 비활성/메타 텍스트 |
| `--cyan` | `#06b6d4` | Primary accent |
| `--blue` | `#3b82f6` | Secondary accent |
| `--purple` | `#a855f7` | 특수 강조 |
| `--red` | `#ef4444` | 위험/삭제 |
| `--orange` | `#f97316` | 경고 |
| `--yellow` | `#eab308` | 주의 |
| `--green` | `#22c55e` | 성공/정상 |
### Z-Index 스케일
| 변수 | 값 | 용도 |
|------|-----|------|
| `--z-dropdown` | 100 | 드롭다운, 콤보박스 |
| `--z-sticky` | 200 | sticky 헤더 |
| `--z-overlay` | 1000 | 오버레이 |
| `--z-modal` | 10000 | 모달 |
| `--z-toast` | 10100 | 토스트 알림 |
### 패널 너비
| 변수 | 값 | 용도 |
|------|-----|------|
| `--panel-narrow` | 280px | 좁은 사이드패널 |
| `--panel-default` | 300px | 기본 사이드패널 |
| `--panel-wide` | 340px | 넓은 사이드패널 |
### 타이포그래피 (Tailwind)
| 클래스 | 크기 | 용도 |
|--------|------|------|
| `text-wing-meta` | 9px | 메타 텍스트, 날짜, 부가 정보 |
| `text-wing-caption` | 10px | 캡션, 설명, 라벨 부연 |
| `text-wing-body` | 11px | 본문, 라벨, 값 (가장 많이 사용) |
| `text-wing-heading` | 13px | 섹션 헤더 |
| `text-wing-title` | 15px | 페이지/모달 타이틀 |
---
## 2. CSS 클래스 (`wing-*`)
모든 클래스는 `frontend/src/common/styles/wing.css`에 정의.
### Layout
| 클래스 | 설명 |
|--------|------|
| `wing-panel` | flex column, full height, overflow hidden |
| `wing-panel-scroll` | flex-1, overflow-y-auto, thin scrollbar |
| `wing-panel-right` | 우측 사이드패널 (border-l, 300px) |
| `wing-panel-left` | 좌측 사이드패널 (border-r, 300px) |
| `wing-header-bar` | 헤더 바 (flex between, border-b, px-5) |
| `wing-sidebar` | 사이드바 (flex col, border-r, bg1) |
### Card / Section
| 클래스 | 설명 |
|--------|------|
| `wing-card` | 카드 (rounded-md, p-4, border, bg3) |
| `wing-card-sm` | 작은 카드 (rounded-sm, p-3) |
| `wing-section` | 섹션 (rounded-md, p-4, mb-3) |
| `wing-section-header` | 섹션 제목 (13px bold) |
| `wing-section-desc` | 섹션 설명 (10px, t3 색상) |
### Typography
| 클래스 | 설명 |
|--------|------|
| `wing-title` | 15px bold korean |
| `wing-subtitle` | 10px korean, t3 색상 |
| `wing-label` | 11px semibold korean |
| `wing-value` | 11px semibold mono |
| `wing-meta` | 9px korean, t3 색상 |
### Button
| 클래스 | 설명 |
|--------|------|
| `wing-btn` | 기본 버튼 (px-3, py-1.5, 11px) |
| `wing-btn-primary` | cyan→blue gradient, white |
| `wing-btn-secondary` | bg3, border, t2 색상 |
| `wing-btn-outline` | transparent, border |
| `wing-btn-pdf` | blue 테마 |
| `wing-btn-danger` | red 테마 |
### Input / Select / Textarea
| 클래스 | 설명 |
|--------|------|
| `wing-input` | 기본 입력 (w-full, 11px, cyan focus) |
| `wing-select` | 셀렉트 (커스텀 화살표 포함) |
| `wing-textarea` | 텍스트영역 (resize-vertical, min-h 80px) |
| `wing-input-search` | 검색 입력 (256px 고정) |
### Table
| 클래스 | 설명 |
|--------|------|
| `wing-table` | 테이블 (w-full, 10px, collapse) |
| `wing-table-head` | 헤더 셀 (bg2, t3, bold) |
| `wing-table-cell` | 데이터 셀 (t2, border-b) |
| `wing-table-row` | 행 hover (bgH, cursor-pointer) |
### Badge
| 클래스 | 설명 |
|--------|------|
| `wing-badge` | 기본 배지 (inline-flex, 9px bold) |
| `wing-badge-neutral` | 회색 (단순 분류용 기본값) |
| `wing-badge-red` | 위험/삭제 |
| `wing-badge-blue` | 정보 |
| `wing-badge-green` | 성공/정상 |
| `wing-badge-yellow` | 주의 |
| `wing-badge-purple` | 특수 |
| `wing-badge-cyan` | 주요 |
### Modal
| 클래스 | 설명 |
|--------|------|
| `wing-overlay` | 오버레이 (fixed, blur, z-modal) |
| `wing-modal` | 모달 컨테이너 (rounded-xl, bg1) |
| `wing-modal-header` | 모달 헤더 (flex between, border-b) |
| `wing-modal-body` | 모달 본문 (flex-1, scroll) |
| `wing-modal-footer` | 모달 푸터 (flex end, border-t) |
| `wing-modal-sm` | 400px |
| `wing-modal-md` | 560px |
| `wing-modal-lg` | 720px |
### Tab
| 클래스 | 설명 |
|--------|------|
| `wing-tab-bar` | 탭 바 (flex, rounded-lg, border) |
| `wing-tab` | 탭 아이템 (flex-1, text-center) |
| `wing-tab.active` | 활성 탭 (cyan border/bg/text) |
### Utility
| 클래스 | 설명 |
|--------|------|
| `wing-divider` | 구분선 (1px, full width) |
| `wing-info-row` | 키-값 행 (flex between) |
| `wing-info-label` | 키 라벨 (10px, t3) |
| `wing-info-value` | 값 (11px, semibold mono) |
---
## 3. React 컴포넌트
위치: `frontend/src/common/components/ui/`
### Modal
```tsx
import Modal from '@common/components/ui/Modal';
setIsOpen(false)}
title="제목"
size="md"
footer={
<>
취소
확인
>
}
>
모달 내용
```
| Prop | 타입 | 기본값 | 설명 |
|------|------|--------|------|
| `isOpen` | `boolean` | 필수 | 표시 여부 |
| `onClose` | `() => void` | 필수 | 닫기 콜백 |
| `title` | `string` | 필수 | 헤더 제목 |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 너비 (400/560/720px) |
| `children` | `ReactNode` | 필수 | 본문 |
| `footer` | `ReactNode` | - | 하단 버튼 영역 |
| `closeOnBackdrop` | `boolean` | `true` | 배경 클릭 닫기 |
### Pagination
```tsx
import Pagination from '@common/components/ui/Pagination';
```
| Prop | 타입 | 기본값 | 설명 |
|------|------|--------|------|
| `currentPage` | `number` | 필수 | 현재 페이지 (1-based) |
| `totalPages` | `number` | 필수 | 전체 페이지 수 |
| `onPageChange` | `(page: number) => void` | 필수 | 페이지 변경 콜백 |
| `showFirstLast` | `boolean` | `true` | 처음/끝 버튼 표시 |
### DataTable
```tsx
import DataTable, { Column } from '@common/components/ui/DataTable';
interface Post {
id: number;
title: string;
author: string;
createdAt: string;
}
const columns: Column[] = [
{ key: 'id', label: '번호', width: '60px', align: 'center' },
{ key: 'title', label: '제목' },
{ key: 'author', label: '작성자', width: '100px' },
{ key: 'createdAt', label: '작성일', width: '120px',
render: (val) => new Date(val as string).toLocaleDateString() },
];
navigate(`/board/${post.id}`)}
/>
```
| Prop | 타입 | 기본값 | 설명 |
|------|------|--------|------|
| `columns` | `Column[]` | 필수 | 컬럼 정의 |
| `data` | `T[]` | 필수 | 데이터 배열 |
| `onRowClick` | `(row: T) => void` | - | 행 클릭 콜백 |
| `stickyHeader` | `boolean` | `true` | 헤더 고정 |
| `emptyMessage` | `string` | `'데이터가 없습니다.'` | 빈 상태 메시지 |
### SidePanel
```tsx
import SidePanel from '@common/components/ui/SidePanel';
상세 정보}
footer={
저장
}
>
```
| Prop | 타입 | 기본값 | 설명 |
|------|------|--------|------|
| `position` | `'left' \| 'right'` | 필수 | 배치 방향 |
| `width` | `'narrow' \| 'default' \| 'wide'` | `'default'` | 너비 (280/300/340px) |
| `header` | `ReactNode` | - | 헤더 영역 |
| `footer` | `ReactNode` | - | 하단 영역 |
### Badge
```tsx
import Badge from '@common/components/ui/Badge';
정상 {/* 상태 표현 */}
위험 {/* 상태 표현 */}
공지사항 {/* 단순 분류 → neutral */}
자료실 {/* 단순 분류 → neutral */}
```
| Prop | 타입 | 기본값 | 설명 |
|------|------|--------|------|
| `color` | `'red' \| 'blue' \| 'green' \| 'yellow' \| 'purple' \| 'cyan' \| 'neutral'` | `'neutral'` | 배지 색상 |
---
## 4. 적용 예시: Before → After
### Before (raw Tailwind 복붙)
```tsx
{/* 검색 */}
{/* 테이블 */}
{/* 배지 — 의미 없는 컬러 분화 */}
공지사항
{/* 페이지네이션 — 직접 구현 */}
이전
다음
```
### After (디자인 시스템)
```tsx
{/* 검색 */}
{/* 테이블 — React 컴포넌트 */}
{/* 배지 — neutral로 통일 */}
공지사항
자료실
진행중 {/* 상태만 컬러 */}
{/* 페이지네이션 — React 컴포넌트 */}
```
---
## 5. 마이그레이션 가이드
### 작업 순서
1. 파일 내 raw Tailwind 문자열을 `wing-*` 클래스로 교체
2. 반복되는 행동 패턴 (모달, 페이지네이션, 테이블)을 React 컴포넌트로 교체
3. 의미 없는 컬러 배지를 `` (neutral)로 교체
4. 브라우저에서 시각적 동일성 확인
### 판단 기준
```
순수 시각 + 5회 이상 반복 → wing-* CSS 클래스
동작 포함 + 5회 이상 반복 → React 컴포넌트
1-2회 사용 → 인라인 Tailwind 유지
도메인 전용 시각 → components.css (유지)
```