feat(design): HNS/예측/구조 탭 디자인 시스템 폰트 및 색상 토큰 전환

This commit is contained in:
leedano 2026-04-02 16:21:58 +09:00
부모 a0be19d060
커밋 c7b0b7a3c2
20개의 변경된 파일8780개의 추가작업 그리고 4141개의 파일을 삭제

4
.gitignore vendored
파일 보기

@ -99,3 +99,7 @@ frontend/public/hns-manual/images/
# Lock files (keep for reproducible builds) # Lock files (keep for reproducible builds)
!frontend/package-lock.json !frontend/package-lock.json
!backend/package-lock.json !backend/package-lock.json
# mcp
.mcp.json

파일 보기

@ -7,6 +7,158 @@ WING-OPS UI 디자인 시스템의 비주얼 레퍼런스 카탈로그.
--- ---
## 버전 히스토리
### v1.1 — 시맨틱 토큰 & 테마 시스템 (2026-03-28~)
> 브랜치: `feature/predict-develop`, `feature/design-system-font`
v1.0의 축약형 토큰 시스템을 시맨틱 네이밍으로 전면 전환하고, 다크/라이트 테마 전환 기능을 도입한 구조적 리팩토링.
#### 변경된 점
| 항목 | v1.0 | v1.1 |
|------|------|------|
| 토큰 네이밍 | 축약형 (`--bg0`, `--t1`, `--cyan`) | 시맨틱 (`--bg-base`, `--fg-default`, `--color-accent`) |
| 폰트 | Outfit + Noto Sans KR + JetBrains Mono (3종) | PretendardGOV 단일 폰트 (4웨이트) |
| 테마 | 다크 모드 전용 (하드코딩) | 다크/라이트 전환 지원 (`data-theme` 속성) |
| 프리미티브 팔레트 | 7그룹 11단계 (Navy/Cyan/Blue/Red/Green/Orange/Yellow, 00~100) | 6그룹 10단계 (Gray/Blue/Green/Yellow/Red/Purple, 100~1000) |
| 텍스트 대비 | `--t2: #b0b8cc`, `--t3: #8690a6` | `--fg-sub: #c0c8dc`, `--fg-disabled: #9ba3b8` (대비 향상) |
| 버튼 스타일 | `.prd-btn.pri` 그라데이션 (cyan→blue) | 아웃라인/고스트 스타일 |
| Tailwind 컬러 키 | 하드코딩 hex (`bg.0`, `text.1`, `primary.cyan`) | CSS 변수 참조 (`bg.base`, `fg.DEFAULT`, `color.accent`) |
| 폰트 유틸리티 | 하드코딩 (`font-family: 'JetBrains Mono'`) | CSS 변수 경유 (`font-family: var(--font-mono)`) |
#### 토큰 마이그레이션 매핑
| v1.0 | v1.1 | 설명 |
|------|------|------|
| `--bg0` | `--bg-base` | 페이지 배경 |
| `--bg1` | `--bg-surface` | 사이드바, 패널 |
| `--bg2` | `--bg-elevated` | 테이블 헤더, 상위 요소 |
| `--bg3` | `--bg-card` | 카드 배경 |
| `--bgH` | `--bg-surface-hover` | 호버 상태 |
| `--bd` | `--stroke-default` | 기본 구분선 |
| `--bdL` | `--stroke-light` | 연한 구분선 |
| `--t1` | `--fg-default` | 기본 텍스트 |
| `--t2` | `--fg-sub` | 보조 텍스트 |
| `--t3` | `--fg-disabled` | 비활성 텍스트 |
| `--cyan` | `--color-accent` | 주요 강조 |
| `--blue` | `--color-info` | 정보, 링크 |
| `--red` | `--color-danger` | 위험, 삭제 |
| `--orange` | `--color-warning` | 주의 |
| `--yellow` | `--color-caution` | 경고 |
| `--green` | `--color-success` | 성공, 정상 |
| `--purple` | `--color-tertiary` | 3차 강조 |
| `--boom` | `--color-boom` | 오일붐 전용 |
| `--fK` | `--font-korean` | 한국어 폰트 스택 |
| `--fM` | `--font-mono` | 모노 폰트 스택 |
| `--rS` | `--radius-sm` | 소형 border radius |
| `--rM` | `--radius-md` | 중형 border radius |
#### 추가된 기능
- **다크/라이트 테마 전환**: `themeStore.ts` (Zustand) + `data-theme` DOM 속성 + FOUC 방지 인라인 스크립트
- **시맨틱 오버레이 토큰**: `--hover-overlay`, `--dropdown-bg` (테마별 불투명도 차별화)
- **Navy 액센트 토큰**: `--color-navy`, `--color-navy-hover`, `--color-accent-muted`
- **정적 컬러 토큰**: `--static-black`, `--static-white` (테마 무관 고정값)
- **타이포그래피 스케일 (17개 토큰)**: Display 3종, Heading 3종, Title 6종, Body 2종, Label 2종, Caption 1종
- **Letter-spacing 토큰 5종**: `--letter-spacing-display` (0.06em) ~ `--letter-spacing-label` (0.04em)
- **Font weight 토큰 4종**: thin(300) / regular(400) / medium(500) / bold(700)
- **Line height 토큰 4종**: tight(1.3) / snug(1.4) / normal(1.5) / relaxed(1.6)
- **Tailwind tracking 유틸리티**: `tracking-display`, `tracking-heading`, `tracking-body`, `tracking-navigation`, `tracking-label`
- **라이트 테마 컴포넌트 오버라이드**: CCTV 팝업, 날짜피커, 타임라인, 콤보박스, 좌표 표시 등
- **폰트 렌더링 최적화**: `-webkit-font-smoothing: antialiased`, `text-rendering: optimizeLegibility`
#### 업데이트된 부분
- 92개+ 컴포넌트 파일의 토큰 마이그레이션 (축약형 → 시맨틱)
- Stitch MCP 프로젝트 참조 제거 (독립 토큰 시스템으로 전환)
- DESIGN-SYSTEM.md 문서 전면 재작성
---
### v1.0 — 초기 디자인 시스템 (2026-03-24~25)
> 브랜치: `feature/stitch-mcp` | 도구: Google Stitch MCP
WING-OPS 전용 디자인 시스템의 첫 구축. CSS 변수 기반 토큰 시스템, `.wing-*` 컴포넌트 클래스, 라이브 카탈로그 뷰어를 도입.
#### 컬러 시스템
- **프리미티브 팔레트**: 7개 그룹 (Navy/Cyan/Blue/Red/Green/Orange/Yellow), 각 11단계 스케일 (00~100)
- **시맨틱 컬러**: 축약형 CSS 변수 — Background(`--bg0`~`--bgH`), Text(`--t1`~`--t3`), Border(`--bd`, `--bdL`)
- **액센트 컬러**: `--cyan`(#06b6d4), `--blue`(#3b82f6), `--red`, `--green`, `--orange`, `--yellow`, `--purple`
- **특수 컬러**: `--boom`(#f59e0b) — 오일붐 전용 Amber
#### 타이포그래피
- **3종 폰트 패밀리**: Outfit (영문 본문), Noto Sans KR (한국어), JetBrains Mono (좌표/수치)
- **10개 `.wing-*` 타이포 클래스**: `.wing-title`(15px) ~ `.wing-badge`(9px)
#### 컴포넌트 클래스
| 카테고리 | 클래스 |
|----------|--------|
| 레이아웃 셸 | `.wing-panel`, `.wing-panel-scroll`, `.wing-header-bar`, `.wing-sidebar` |
| 컨테이너 | `.wing-card`, `.wing-card-sm`, `.wing-section` |
| 버튼 | `.wing-btn`, `.wing-btn-primary` (cyan→blue 그라데이션), `-secondary`, `-outline`, `-pdf`, `-danger` |
| 입력 | `.wing-input` (cyan 포커스 링) |
| 테이블 | `.wing-table`, `.wing-th`, `.wing-td`, `.wing-tr-hover` |
| 탭 | `.wing-tab-bar`, `.wing-tab` (cyan 틴트 + 보더 활성) |
| 모달 | `.wing-overlay` (블러 백드롭), `.wing-modal`, `.wing-modal-header` |
| 유틸리티 | `.wing-divider`, `.wing-kv-row`, `.wing-kv-label`, `.wing-kv-value` |
| 뱃지/아이콘 | `.wing-badge`, `.wing-icon-badge`, `.wing-icon-badge-sm` |
#### 특수 컴포넌트
| 영역 | 클래스 접두사 | 설명 |
|------|--------------|------|
| 예측 패널 | `.prd-*` | 폼 입력, 버튼, 맵 버튼 |
| 콤보박스 | `.combo-*` | 커스텀 드롭다운 (검색 + 리스트) |
| 타임라인 | `.tlb`, `.tlt`, `.tlr`, `.tlth` | 지도 하단 재생 컨트롤 |
| 레이어 트리 | `.lyr-*` | 3단계 접이식 레이어 (색상 스와치 + 투명도) |
| 오일붐 | `.boom-*` | 오일붐 드로잉 인디케이터 |
| 역추적 | `.bt-*` | 역추적 리플레이 마커 + 속도 버튼 |
| HNS | `.hns-scn-card` | HNS 시나리오 선택 카드 |
| 모델 칩 | `.prd-mc` | 모델 선택 칩 (활성 `✓` 인디케이터) |
#### 레이아웃
- **데스크톱 전용** (≥ 1280px), Tablet/Mobile 미지원
- **7단계 z-index 레이어**: Base(0) ~ Tooltip(60)
- **Spacing**: Tailwind 기본 스케일 사용 (`gap-1`~`gap-8`)
#### Border Radius
- `rounded-sm`: **6px** (커스텀 오버라이드)
- `rounded-md`: **10px** (커스텀 오버라이드)
- 나머지 Tailwind 기본값 유지
#### 애니메이션
`fadeIn`, `fadeSlideDown`, `pulse-dot`, `pulse-border`, `comboIn`, `lyrPopIn`, `bt-collision-pulse`
#### 디자인 카탈로그
`/design` 라우트에 라이브 카탈로그 뷰어 배포:
- **Foundations 탭**: Color Palette, Typography, Radius, Layout, Overview
- **Components 탭**: Button, TextField, Overview (Button Catalog, Card, Icon Badge 섹션)
- **다크/라이트 모드 토글** 내장
- **테마 엔진**: `designTheme.ts` — 타입 안전한 `DesignTheme` 인터페이스
#### SVG 아이콘 에셋
23개 커스텀 아이콘 (`wing-` 접두사): `wing-anchor`, `wing-cargo`, `wing-chart-bar`, `wing-color-palette`, `wing-documentation`, `wing-elevation`, `wing-foundations`, `wing-layout-grid`, `wing-notification`, `wing-pdf-file`, `wing-settings`, `wing-typography`, `wing-wave-graph`
#### Stitch MCP 연동
Google Stitch 프로젝트 7개 스크린 참조:
- Design Tokens, Component Catalog (Buttons/Badges), Form Components
- Table & List Patterns, Modal Catalog, Operational Shell (Layout)
- Container & Navigation
---
## 테마 (Theme) ## 테마 (Theme)
### 테마 전환 메커니즘 ### 테마 전환 메커니즘
@ -121,6 +273,7 @@ Primitive Tokens (정적) Semantic Tokens (테마 반응형)
| CSS 변수 | Tailwind 클래스 | Hex | 용도 | | CSS 변수 | Tailwind 클래스 | Hex | 용도 |
|----------|----------------|-----|------| |----------|----------------|-----|------|
| `--color-accent` | `text-color-accent` | `#06b6d4` | 주요 강조 (Cyan) | | `--color-accent` | `text-color-accent` | `#06b6d4` | 주요 강조 (Cyan) |
| `--color-accent-muted` | `bg-color-accent-muted` | `#0e7490` / `#0891b2`(light) | 차분한 강조 (버튼 배경 등) |
| `--color-info` | `text-color-info` | `#3b82f6` | 정보, 링크 (Blue) | | `--color-info` | `text-color-info` | `#3b82f6` | 정보, 링크 (Blue) |
| `--color-tertiary` | `text-color-tertiary` | `#a855f7` | 3차 강조 (Purple) | | `--color-tertiary` | `text-color-tertiary` | `#a855f7` | 3차 강조 (Purple) |
| `--color-danger` | `text-color-danger` | `#ef4444` | 위험, 삭제 (Red) | | `--color-danger` | `text-color-danger` | `#ef4444` | 위험, 삭제 (Red) |
@ -129,6 +282,8 @@ Primitive Tokens (정적) Semantic Tokens (테마 반응형)
| `--color-success` | `text-color-success` | `#22c55e` | 성공, 정상 (Green) | | `--color-success` | `text-color-success` | `#22c55e` | 성공, 정상 (Green) |
| `--color-boom` | `text-color-boom` | `#f59e0b` | 오일붐 전용 (Amber) | | `--color-boom` | `text-color-boom` | `#f59e0b` | 오일붐 전용 (Amber) |
| `--color-boom-hover` | — | `#fbbf24` | 오일붐 호버 | | `--color-boom-hover` | — | `#fbbf24` | 오일붐 호버 |
| `--color-navy` | `bg-color-navy` | `#1e40af` / `#1d4ed8`(light) | Navy 버튼 배경 |
| `--color-navy-hover` | `bg-color-navy-hover` | `#1d4ed8` / `#2563eb`(light) | Navy 호버 |
#### Static Colors #### Static Colors

파일 보기

@ -1,5 +1,5 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="ko">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
@ -10,7 +10,7 @@
href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;600;700;900&family=JetBrains+Mono:wght@400;500;600&family=Outfit:wght@300;400;500;600;700;800&display=swap" href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;600;700;900&family=JetBrains+Mono:wght@400;500;600&family=Outfit:wght@300;400;500;600;700;800&display=swap"
rel="stylesheet" rel="stylesheet"
/> />
<title>frontend</title> <title>해양환경 위기대응 통합지원 시스템</title>
<script> <script>
document.documentElement.setAttribute( document.documentElement.setAttribute(
'data-theme', 'data-theme',

파일 보기

@ -547,7 +547,9 @@ function MapCaptureSetup({
composite.width = Math.round(src.width * scale); composite.width = Math.round(src.width * scale);
composite.height = Math.round(src.height * scale); composite.height = Math.round(src.height * scale);
const ctx = composite.getContext('2d')!; const ctx = composite.getContext('2d')!;
ctx.fillStyle = '#0f1117'; ctx.fillStyle =
getComputedStyle(document.documentElement).getPropertyValue('--bg-base').trim() ||
'#0f1117';
ctx.fillRect(0, 0, composite.width, composite.height); ctx.fillRect(0, 0, composite.width, composite.height);
ctx.drawImage(src, 0, 0, composite.width, composite.height); ctx.drawImage(src, 0, 0, composite.width, composite.height);
resolve(composite.toDataURL('image/jpeg', 0.82)); resolve(composite.toDataURL('image/jpeg', 0.82));
@ -1037,14 +1039,6 @@ export function MapView({
...dispersionHeatmap.filter((p) => p.concentration > 0.01).map((p) => p.concentration), ...dispersionHeatmap.filter((p) => p.concentration > 0.01).map((p) => p.concentration),
); );
const filtered = dispersionHeatmap.filter((p) => p.concentration > 0.01); const filtered = dispersionHeatmap.filter((p) => p.concentration > 0.01);
console.log(
'[MapView] HNS 히트맵:',
dispersionHeatmap.length,
'→ filtered:',
filtered.length,
'maxConc:',
maxConc.toFixed(2),
);
if (filtered.length > 0) { if (filtered.length > 0) {
// 경위도 바운드 계산 // 경위도 바운드 계산
@ -1108,10 +1102,11 @@ export function MapView({
ctx.fill(); ctx.fill();
} }
const imageUrl = canvas.toDataURL();
result.push( result.push(
new BitmapLayer({ new BitmapLayer({
id: 'hns-dispersion-bitmap', id: 'hns-dispersion-bitmap',
image: canvas, image: imageUrl,
bounds: [minLon, minLat, maxLon, maxLat], bounds: [minLon, minLat, maxLon, maxLat],
opacity: 1.0, opacity: 1.0,
pickable: false, pickable: false,
@ -1690,21 +1685,15 @@ function MapControls({ center, zoom }: { center: [number, number]; zoom: number
return ( return (
<div className="absolute top-[80px] left-[10px] z-10"> <div className="absolute top-[80px] left-[10px] z-10">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<button <button onClick={() => map?.zoomIn()} className="map-ctrl-btn">
onClick={() => map?.zoomIn()}
className="w-[28px] h-[28px] bg-[rgba(18,25,41,0.65)] backdrop-blur-sm border border-[rgba(30,42,66,0.5)] rounded-sm text-fg-sub flex items-center justify-center hover:bg-bg-surface-hover hover:text-fg transition-all text-xs"
>
+ +
</button> </button>
<button <button onClick={() => map?.zoomOut()} className="map-ctrl-btn">
onClick={() => map?.zoomOut()}
className="w-[28px] h-[28px] bg-[rgba(18,25,41,0.65)] backdrop-blur-sm border border-[rgba(30,42,66,0.5)] rounded-sm text-fg-sub flex items-center justify-center hover:bg-bg-surface-hover hover:text-fg transition-all text-xs"
>
</button> </button>
<button <button
onClick={() => map?.flyTo({ center: [center[1], center[0]], zoom, duration: 1000 })} onClick={() => map?.flyTo({ center: [center[1], center[0]], zoom, duration: 1000 })}
className="w-[28px] h-[28px] bg-[rgba(18,25,41,0.65)] backdrop-blur-sm border border-[rgba(30,42,66,0.5)] rounded-sm text-fg-sub flex items-center justify-center hover:text-fg transition-all text-[10px]" className="map-ctrl-btn text-[10px]"
> >
&#x1F3AF; &#x1F3AF;
</button> </button>
@ -1732,16 +1721,16 @@ function MapLegend({
if (dispersionResult && incidentCoord) { if (dispersionResult && incidentCoord) {
return ( return (
<div className="absolute top-4 right-4 bg-[rgba(18,25,41,0.95)] backdrop-blur-xl border border-stroke rounded-lg min-w-[200px] z-[20]"> <div className="absolute top-4 right-4 bg-bg-surface/95 backdrop-blur-xl border border-stroke rounded-lg min-w-[200px] z-[20]">
{/* 헤더 + 최소화 버튼 */} {/* 헤더 + 최소화 버튼 */}
<div <div
className="flex items-center justify-between px-3.5 pt-3 pb-1 cursor-pointer" className="flex items-center justify-between px-3.5 pt-3 pb-1 cursor-pointer"
onClick={() => setMinimized(!minimized)} onClick={() => setMinimized(!minimized)}
> >
<span className="text-[10px] font-bold text-fg-disabled uppercase tracking-wider"> <span className="text-label-2 font-bold text-fg-disabled uppercase tracking-label">
</span> </span>
<span className="text-[10px] text-fg-disabled hover:text-fg transition-colors"> <span className="text-label-2 text-fg-disabled hover:text-fg transition-colors">
{minimized ? '▶' : '▼'} {minimized ? '▶' : '▼'}
</span> </span>
</div> </div>
@ -1750,53 +1739,49 @@ function MapLegend({
<div className="flex items-center gap-1.5 mb-2.5"> <div className="flex items-center gap-1.5 mb-2.5">
<div className="text-base">📍</div> <div className="text-base">📍</div>
<div> <div>
<h4 className="text-[11px] font-bold text-color-warning"> </h4> <h4 className="text-label-2 font-bold text-color-warning"> </h4>
<div className="text-[8px] text-fg-disabled font-mono"> <div className="text-caption text-fg-disabled font-mono">
{incidentCoord.lat.toFixed(4)}°N, {incidentCoord.lon.toFixed(4)}°E {incidentCoord.lat.toFixed(4)}°N, {incidentCoord.lon.toFixed(4)}°E
</div> </div>
</div> </div>
</div> </div>
<div <div
className="text-[9px] text-fg-sub mb-2 rounded" className="text-caption text-fg-sub mb-2 rounded"
style={{ background: 'rgba(249,115,22,0.08)', padding: '8px' }} style={{ background: 'rgba(249,115,22,0.08)', padding: '8px' }}
> >
<div className="flex justify-between mb-[3px]"> <div className="flex justify-between mb-[3px]">
<span className="text-fg-disabled"></span> <span className="text-fg-disabled"></span>
<span className="font-semibold text-color-warning"> <span className="font-medium text-color-warning">{dispersionResult.substance}</span>
{dispersionResult.substance}
</span>
</div> </div>
<div className="flex justify-between mb-[3px]"> <div className="flex justify-between mb-[3px]">
<span className="text-fg-disabled"></span> <span className="text-fg-disabled"></span>
<span className="font-semibold font-mono"> <span className="font-medium font-mono">SW {dispersionResult.windDirection}°</span>
SW {dispersionResult.windDirection}°
</span>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-fg-disabled"> </span> <span className="text-fg-disabled"> </span>
<span className="font-semibold text-color-accent"> <span className="font-medium text-color-accent">
{dispersionResult.zones.length} {dispersionResult.zones.length}
</span> </span>
</div> </div>
</div> </div>
<div> <div>
<h5 className="text-[9px] font-bold text-fg-disabled mb-2"> </h5> <h5 className="text-caption font-bold text-fg-disabled mb-2"> </h5>
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
<div className="flex items-center gap-2 text-[10px] text-fg-sub"> <div className="flex items-center gap-2 text-label-2 text-fg-sub">
<div <div
className="w-3 h-3 rounded-full" className="w-3 h-3 rounded-full"
style={{ background: 'rgba(239,68,68,0.7)' }} style={{ background: 'rgba(239,68,68,0.7)' }}
/> />
<span> (AEGL-3)</span> <span> (AEGL-3)</span>
</div> </div>
<div className="flex items-center gap-2 text-[10px] text-fg-sub"> <div className="flex items-center gap-2 text-label-2 text-fg-sub">
<div <div
className="w-3 h-3 rounded-full" className="w-3 h-3 rounded-full"
style={{ background: 'rgba(249,115,22,0.7)' }} style={{ background: 'rgba(249,115,22,0.7)' }}
/> />
<span> (AEGL-2)</span> <span> (AEGL-2)</span>
</div> </div>
<div className="flex items-center gap-2 text-[10px] text-fg-sub"> <div className="flex items-center gap-2 text-label-2 text-fg-sub">
<div <div
className="w-3 h-3 rounded-full" className="w-3 h-3 rounded-full"
style={{ background: 'rgba(234,179,8,0.7)' }} style={{ background: 'rgba(234,179,8,0.7)' }}
@ -1809,8 +1794,8 @@ function MapLegend({
className="flex items-center gap-1.5 mt-2 rounded" className="flex items-center gap-1.5 mt-2 rounded"
style={{ padding: '6px', background: 'rgba(168,85,247,0.08)' }} style={{ padding: '6px', background: 'rgba(168,85,247,0.08)' }}
> >
<div className="text-xs">🧭</div> <div className="text-label-1">🧭</div>
<span className="text-[9px] text-fg-disabled"> ()</span> <span className="text-caption text-fg-disabled"> ()</span>
</div> </div>
</div> </div>
)} )}
@ -1821,7 +1806,7 @@ function MapLegend({
if (oilTrajectory.length > 0) { if (oilTrajectory.length > 0) {
return ( return (
<div <div
className="absolute top-4 right-4 bg-[rgba(18,25,41,0.92)] backdrop-blur-xl border border-stroke rounded-md z-[20]" className="absolute top-4 right-4 bg-bg-surface/95 backdrop-blur-xl border border-stroke rounded-md z-[20]"
style={{ minWidth: 155 }} style={{ minWidth: 155 }}
> >
{/* 헤더 + 접기/펼치기 */} {/* 헤더 + 접기/펼치기 */}
@ -1829,8 +1814,8 @@ function MapLegend({
className="flex items-center justify-between px-3 py-2 cursor-pointer select-none" className="flex items-center justify-between px-3 py-2 cursor-pointer select-none"
onClick={() => setMinimized(!minimized)} onClick={() => setMinimized(!minimized)}
> >
<span className="text-[10px] font-bold text-fg-sub font-korean"></span> <span className="text-label-2 font-bold text-fg-sub font-korean"></span>
<span className="text-[9px] text-fg-disabled hover:text-fg transition-colors ml-3"> <span className="text-caption text-fg-disabled hover:text-fg transition-colors ml-3">
{minimized ? '▶' : '▼'} {minimized ? '▶' : '▼'}
</span> </span>
</div> </div>
@ -1839,7 +1824,7 @@ function MapLegend({
<div className="px-3 pb-2.5 flex flex-col gap-[5px]"> <div className="px-3 pb-2.5 flex flex-col gap-[5px]">
{/* 모델별 색상 */} {/* 모델별 색상 */}
{Array.from(selectedModels).map((model) => ( {Array.from(selectedModels).map((model) => (
<div key={model} className="flex items-center gap-2 text-[10px] text-fg-sub"> <div key={model} className="flex items-center gap-2 text-label-2 text-fg-sub">
<div <div
className="w-[14px] h-[3px] rounded-sm" className="w-[14px] h-[3px] rounded-sm"
style={{ background: MODEL_COLORS[model] }} style={{ background: MODEL_COLORS[model] }}
@ -1848,14 +1833,14 @@ function MapLegend({
</div> </div>
))} ))}
{/* 앙상블 */} {/* 앙상블 */}
<div className="flex items-center gap-2 text-[10px] text-fg-sub"> <div className="flex items-center gap-2 text-label-2 text-fg-sub">
<div className="w-[14px] h-[3px] rounded-sm" style={{ background: '#a855f7' }} /> <div className="w-[14px] h-[3px] rounded-sm" style={{ background: '#a855f7' }} />
<span className="font-korean"></span> <span className="font-korean"></span>
</div> </div>
{/* 오일펜스 라인 */} {/* 오일펜스 라인 */}
<div className="h-px bg-border my-0.5" /> <div className="h-px bg-border my-0.5" />
<div className="flex items-center gap-2 text-[10px] text-fg-sub"> <div className="flex items-center gap-2 text-label-2 text-fg-sub">
<div className="flex gap-px"> <div className="flex gap-px">
<div className="w-[4px] h-[4px] rounded-full bg-[#f97316]" /> <div className="w-[4px] h-[4px] rounded-full bg-[#f97316]" />
<div className="w-[4px] h-[4px] rounded-full bg-[#f97316]" /> <div className="w-[4px] h-[4px] rounded-full bg-[#f97316]" />
@ -1866,19 +1851,19 @@ function MapLegend({
{/* 도달시간별 선종 */} {/* 도달시간별 선종 */}
<div className="h-px bg-border my-0.5" /> <div className="h-px bg-border my-0.5" />
<div className="flex items-center gap-2 text-[10px] text-fg-sub"> <div className="flex items-center gap-2 text-label-2 text-fg-sub">
<div className="w-[14px] h-[3px] rounded-sm bg-[#ef4444]" /> <div className="w-[14px] h-[3px] rounded-sm bg-[#ef4444]" />
<span className="font-korean"> (&lt;6h)</span> <span className="font-korean"> (&lt;6h)</span>
</div> </div>
<div className="flex items-center gap-2 text-[10px] text-fg-sub"> <div className="flex items-center gap-2 text-label-2 text-fg-sub">
<div className="w-[14px] h-[3px] rounded-sm bg-[#f97316]" /> <div className="w-[14px] h-[3px] rounded-sm bg-[#f97316]" />
<span className="font-korean"> (6~12h)</span> <span className="font-korean"> (6~12h)</span>
</div> </div>
<div className="flex items-center gap-2 text-[10px] text-fg-sub"> <div className="flex items-center gap-2 text-label-2 text-fg-sub">
<div className="w-[14px] h-[3px] rounded-sm bg-[#eab308]" /> <div className="w-[14px] h-[3px] rounded-sm bg-[#eab308]" />
<span className="font-korean"> (12~24h)</span> <span className="font-korean"> (12~24h)</span>
</div> </div>
<div className="flex items-center gap-2 text-[10px] text-fg-sub"> <div className="flex items-center gap-2 text-label-2 text-fg-sub">
<div className="w-[14px] h-[3px] rounded-sm bg-[#22c55e]" /> <div className="w-[14px] h-[3px] rounded-sm bg-[#22c55e]" />
<span className="font-korean"></span> <span className="font-korean"></span>
</div> </div>

파일 보기

@ -46,6 +46,7 @@
/* color — Palette */ /* color — Palette */
--color-info: #3b82f6; --color-info: #3b82f6;
--color-accent: #06b6d4; --color-accent: #06b6d4;
--color-accent-muted: #0e7490;
--color-danger: #ef4444; --color-danger: #ef4444;
--color-warning: #f97316; --color-warning: #f97316;
--color-caution: #eab308; --color-caution: #eab308;
@ -53,6 +54,8 @@
--color-tertiary: #a855f7; --color-tertiary: #a855f7;
--color-boom: #f59e0b; --color-boom: #f59e0b;
--color-boom-hover: #fbbf24; --color-boom-hover: #fbbf24;
--color-navy: #1e40af;
--color-navy-hover: #1d4ed8;
/* font */ /* font */
--font-korean: --font-korean:
'PretendardGOV', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo', 'PretendardGOV', -apple-system, BlinkMacSystemFont, 'Apple SD Gothic Neo',
@ -197,6 +200,9 @@
--fg-disabled: #94a3b8; --fg-disabled: #94a3b8;
--hover-overlay: rgba(0, 0, 0, 0.04); --hover-overlay: rgba(0, 0, 0, 0.04);
--dropdown-bg: rgba(255, 255, 255, 0.97); --dropdown-bg: rgba(255, 255, 255, 0.97);
--color-accent-muted: #0891b2;
--color-navy: #1d4ed8;
--color-navy-hover: #2563eb;
} }
* { * {

파일 보기

@ -372,6 +372,41 @@
box-shadow: 0 0 0 1px rgba(6, 182, 212, 0.3); box-shadow: 0 0 0 1px rgba(6, 182, 212, 0.3);
} }
/* Map Zoom/Reset Controls */
.map-ctrl-btn {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-sm);
font-size: 12px;
font-weight: 600;
transition: all 0.15s;
background: rgba(15, 21, 36, 0.75);
backdrop-filter: blur(6px);
border: 1px solid rgba(255, 255, 255, 0.12);
color: rgba(255, 255, 255, 0.85);
}
.map-ctrl-btn:hover {
background: rgba(15, 21, 36, 0.9);
color: #ffffff;
border-color: rgba(255, 255, 255, 0.25);
}
[data-theme='light'] .map-ctrl-btn {
background: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(0, 0, 0, 0.1);
color: rgba(0, 0, 0, 0.7);
}
[data-theme='light'] .map-ctrl-btn:hover {
background: rgba(255, 255, 255, 0.95);
color: rgba(0, 0, 0, 0.9);
border-color: rgba(0, 0, 0, 0.2);
}
/* ═══ Coordinate Display ═══ */ /* ═══ Coordinate Display ═══ */
.cod { .cod {
position: absolute; position: absolute;
@ -951,7 +986,7 @@
background: transparent; background: transparent;
color: #8b949e; color: #8b949e;
font-family: var(--font-korean); font-family: var(--font-korean);
font-size: 9px; font-size: var(--font-size-label-2);
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
text-align: center; text-align: center;

파일 보기

@ -15,9 +15,9 @@ const RISK_LABEL: Record<string, string> = {
}; };
const RISK_STYLE: Record<string, { bg: string; color: string }> = { const RISK_STYLE: Record<string, { bg: string; color: string }> = {
CRITICAL: { bg: 'rgba(239,68,68,0.2)', color: 'var(--color-danger)' }, CRITICAL: { bg: 'rgba(239,68,68,0.15)', color: 'var(--color-danger)' },
HIGH: { bg: 'rgba(239,68,68,0.15)', color: 'var(--color-danger)' }, HIGH: { bg: 'rgba(249,115,22,0.15)', color: 'var(--color-warning)' },
MEDIUM: { bg: 'rgba(249,115,22,0.15)', color: 'var(--color-warning)' }, MEDIUM: { bg: 'rgba(234,179,8,0.15)', color: 'var(--color-caution)' },
LOW: { bg: 'rgba(34,197,94,0.15)', color: 'var(--color-success)' }, LOW: { bg: 'rgba(34,197,94,0.15)', color: 'var(--color-success)' },
}; };
@ -101,31 +101,28 @@ export function HNSAnalysisListTable({ onTabChange, onSelectAnalysis }: HNSAnaly
return ( return (
<div className="flex flex-col h-full bg-bg-base"> <div className="flex flex-col h-full bg-bg-base">
{/* Header */} {/* Header */}
<div className="px-5 py-4 border-b border-stroke flex justify-between items-center bg-bg-surface"> <div className="flex items-center justify-between px-5 py-4 border-b border-stroke">
<div>
<h1 className="text-heading-3 font-bold text-fg">HNS </h1>
<p className="text-title-3 text-fg-disabled mt-1"> {analyses.length}</p>
</div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="text-base font-bold flex items-center gap-2"> <div className="relative">
<span className="text-[18px]">📋</span>
HNS
</div>
<span className="text-[10px] text-fg-disabled bg-bg-card font-mono px-[10px] py-1 rounded-xl">
{analyses.length}
</span>
</div>
<div className="flex gap-2 items-center">
<input <input
type="text" type="text"
placeholder="검색..." placeholder="검색..."
className="bg-bg-card border border-stroke rounded-sm text-[11px] px-3 py-2 w-[200px]" className="w-64 px-4 py-2 text-title-3 bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none"
/> />
</div>
<button <button
onClick={() => onTabChange('analysis')} onClick={() => onTabChange('analysis')}
className="text-color-warning text-[11px] font-semibold cursor-pointer flex items-center gap-1 px-4 py-2 rounded-sm" className="px-4 py-2 text-title-3 font-semibold rounded-sm cursor-pointer text-color-accent"
style={{ style={{
border: '1px solid rgba(249,115,22,0.3)', border: '1px solid rgba(6,182,212,.3)',
background: 'linear-gradient(135deg, rgba(249,115,22,0.15), rgba(239,68,68,0.1))', background: 'rgba(6,182,212,.08)',
}} }}
> >
<span className="text-sm">+</span> +
</button> </button>
</div> </div>
</div> </div>
@ -133,69 +130,69 @@ export function HNSAnalysisListTable({ onTabChange, onSelectAnalysis }: HNSAnaly
{/* Table */} {/* Table */}
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{loading ? ( {loading ? (
<div className="text-center text-fg-disabled text-[12px] py-20"> ...</div> <div className="text-center text-fg-disabled text-label-1 py-20"> ...</div>
) : ( ) : (
<table className="w-full text-[11px] border-collapse"> <table className="w-full text-label-2 border-collapse">
<thead className="sticky top-0 z-10"> <thead className="sticky top-0 z-10">
<tr className="bg-bg-elevated border-b border-stroke"> <tr className="bg-bg-elevated border-b border-stroke">
<th className="text-center text-[10px] font-semibold text-fg-sub px-4 py-3 w-[50px]"> <th className="text-center text-label-1 font-bold text-fg-disabled px-4 py-3">
</th> </th>
<th className="text-left text-[10px] font-semibold text-fg-sub px-4 py-3 min-w-[180px]"> <th className="text-left text-label-1 font-bold text-fg-disabled px-4 py-3">
</th> </th>
<th className="text-center text-[10px] font-semibold text-fg-sub px-4 py-3 min-w-[100px]"> <th className="text-center text-label-1 font-bold text-fg-disabled px-4 py-3">
</th> </th>
<th className="text-center text-[10px] font-semibold text-fg-sub px-4 py-3 min-w-[130px]"> <th className="text-center text-label-1 font-bold text-fg-disabled px-4 py-3">
</th> </th>
<th className="text-center text-[10px] font-semibold text-fg-sub px-4 py-3 min-w-[100px]"> <th className="text-center text-label-1 font-bold text-fg-disabled px-4 py-3">
</th> </th>
<th className="text-center text-[10px] font-semibold text-fg-sub px-4 py-3 min-w-[120px]"> <th className="text-center text-label-1 font-bold text-fg-disabled px-4 py-3">
</th> </th>
<th className="text-center text-[10px] font-semibold text-fg-sub px-4 py-3 min-w-[90px]"> <th className="text-center text-label-1 font-bold text-fg-disabled px-4 py-3">
</th> </th>
<th className="text-center text-[10px] font-semibold text-fg-sub px-4 py-3 min-w-[80px]"> <th className="text-center text-label-1 font-bold text-fg-disabled px-4 py-3">
</th> </th>
<th className="text-center text-[10px] font-semibold text-fg-sub px-4 py-3 w-[70px]"> <th className="text-center text-label-1 font-bold text-fg-disabled px-4 py-3">
</th> </th>
<th className="text-center text-[10px] font-semibold text-fg-sub px-4 py-3 w-[70px]"> <th className="text-center text-label-1 font-bold text-fg-disabled px-4 py-3">
<div className="flex flex-col items-center gap-0.5"> <div className="flex flex-col items-center gap-0.5">
<span>AEGL-3</span> <span>AEGL-3</span>
<span className="text-[8px] text-fg-disabled"></span> <span className="text-caption text-fg-disabled"></span>
</div> </div>
</th> </th>
<th className="text-center text-[10px] font-semibold text-fg-sub px-4 py-3 w-[70px]"> <th className="text-center text-label-1 font-bold text-fg-disabled px-4 py-3">
<div className="flex flex-col items-center gap-0.5"> <div className="flex flex-col items-center gap-0.5">
<span>AEGL-2</span> <span>AEGL-2</span>
<span className="text-[8px] text-fg-disabled"></span> <span className="text-caption text-fg-disabled"></span>
</div> </div>
</th> </th>
<th className="text-center text-[10px] font-semibold text-fg-sub px-4 py-3 w-[70px]"> <th className="text-center text-label-1 font-bold text-fg-disabled px-4 py-3">
<div className="flex flex-col items-center gap-0.5"> <div className="flex flex-col items-center gap-0.5">
<span>AEGL-1</span> <span>AEGL-1</span>
<span className="text-[8px] text-fg-disabled"></span> <span className="text-caption text-fg-disabled"></span>
</div> </div>
</th> </th>
<th className="text-center text-[10px] font-semibold text-fg-sub px-4 py-3 min-w-[80px]"> <th className="text-center text-label-1 font-bold text-fg-disabled px-4 py-3">
</th> </th>
<th className="text-center text-[10px] font-semibold text-fg-sub px-4 py-3 min-w-[90px]"> <th className="text-center text-label-1 font-bold text-fg-disabled px-4 py-3">
</th> </th>
<th className="text-center text-[10px] font-semibold text-fg-sub px-4 py-3 min-w-[120px]"> <th className="text-center text-label-1 font-bold text-fg-disabled px-4 py-3">
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{analyses.map((item, index) => { {analyses.map((item) => {
const rslt = item.rsltData as Record<string, unknown> | null; const rslt = item.rsltData as Record<string, unknown> | null;
const isLocal = !!(item as HnsAnalysisItem & { _isLocal?: boolean })._isLocal; const isLocal = !!(item as HnsAnalysisItem & { _isLocal?: boolean })._isLocal;
const riskLabel = RISK_LABEL[item.riskCd || ''] || item.riskCd || '—'; const riskLabel = RISK_LABEL[item.riskCd || ''] || item.riskCd || '—';
@ -213,19 +210,10 @@ export function HNSAnalysisListTable({ onTabChange, onSelectAnalysis }: HNSAnaly
return ( return (
<tr <tr
key={item.hnsAnlysSn} key={item.hnsAnlysSn}
className="border-b border-stroke cursor-pointer" className="hover:bg-bg-elevated transition-colors cursor-pointer group"
onClick={() => onClick={() =>
onSelectAnalysis?.(item.hnsAnlysSn, isLocal && rslt ? rslt : undefined) onSelectAnalysis?.(item.hnsAnlysSn, isLocal && rslt ? rslt : undefined)
} }
style={{
transition: 'background 0.15s',
background: index % 2 === 0 ? 'transparent' : 'rgba(255,255,255,0.01)',
}}
onMouseEnter={(e) => (e.currentTarget.style.background = 'var(--bg-elevated)')}
onMouseLeave={(e) =>
(e.currentTarget.style.background =
index % 2 === 0 ? 'transparent' : 'rgba(255,255,255,0.01)')
}
> >
<td className="text-center text-fg-disabled font-mono px-4 py-3"> <td className="text-center text-fg-disabled font-mono px-4 py-3">
{item.hnsAnlysSn} {item.hnsAnlysSn}
@ -233,23 +221,23 @@ export function HNSAnalysisListTable({ onTabChange, onSelectAnalysis }: HNSAnaly
<td className="font-medium px-4 py-3">{item.anlysNm}</td> <td className="font-medium px-4 py-3">{item.anlysNm}</td>
<td className="text-center px-4 py-3"> <td className="text-center px-4 py-3">
<span <span
className="text-[9px] font-semibold text-color-warning px-2 py-1 rounded" className="text-caption font-semibold text-color-accent px-2 py-1 rounded"
style={{ background: 'rgba(249,115,22,0.12)' }} style={{ background: 'rgba(6,182,212,0.12)' }}
> >
{substanceTag(item.sbstNm)} {substanceTag(item.sbstNm)}
</span> </span>
</td> </td>
<td className="text-center text-fg-sub font-mono text-[10px] px-4 py-3"> <td className="text-center text-fg-sub font-mono text-label-2 px-4 py-3">
{formatDate(item.acdntDtm, 'full')} {formatDate(item.acdntDtm, 'full')}
</td> </td>
<td className="text-center text-fg-disabled font-mono text-[10px] px-4 py-3"> <td className="text-center text-fg-disabled font-mono text-label-2 px-4 py-3">
{formatDate(item.regDtm, 'date')} {formatDate(item.regDtm, 'date')}
</td> </td>
<td className="text-center text-fg-sub px-4 py-3">{item.locNm || '—'}</td> <td className="text-center text-fg-sub px-4 py-3">{item.locNm || '—'}</td>
<td className="text-center text-fg-sub font-mono px-4 py-3">{amount}</td> <td className="text-center text-fg-sub font-mono px-4 py-3">{amount}</td>
<td className="text-center px-4 py-3"> <td className="text-center px-4 py-3">
<span <span
className="text-[9px] font-semibold text-color-accent px-2 py-1 rounded" className="text-caption font-semibold text-color-accent px-2 py-1 rounded"
style={{ background: 'rgba(6,182,212,0.12)' }} style={{ background: 'rgba(6,182,212,0.12)' }}
> >
{item.algoCd || '—'} {item.algoCd || '—'}
@ -262,7 +250,7 @@ export function HNSAnalysisListTable({ onTabChange, onSelectAnalysis }: HNSAnaly
<div <div
className="w-6 h-6 rounded-full mx-auto" className="w-6 h-6 rounded-full mx-auto"
style={{ style={{
background: aegl3 ? 'rgba(239,68,68,0.8)' : 'rgba(255,255,255,0.1)', background: aegl3 ? 'var(--color-danger)' : 'rgba(255,255,255,0.1)',
border: aegl3 ? 'none' : '1px solid var(--stroke-default)', border: aegl3 ? 'none' : '1px solid var(--stroke-default)',
}} }}
/> />
@ -271,7 +259,7 @@ export function HNSAnalysisListTable({ onTabChange, onSelectAnalysis }: HNSAnaly
<div <div
className="w-6 h-6 rounded-full mx-auto" className="w-6 h-6 rounded-full mx-auto"
style={{ style={{
background: aegl2 ? 'rgba(249,115,22,0.8)' : 'rgba(255,255,255,0.1)', background: aegl2 ? 'var(--color-warning)' : 'rgba(255,255,255,0.1)',
border: aegl2 ? 'none' : '1px solid var(--stroke-default)', border: aegl2 ? 'none' : '1px solid var(--stroke-default)',
}} }}
/> />
@ -280,21 +268,21 @@ export function HNSAnalysisListTable({ onTabChange, onSelectAnalysis }: HNSAnaly
<div <div
className="w-6 h-6 rounded-full mx-auto" className="w-6 h-6 rounded-full mx-auto"
style={{ style={{
background: aegl1 ? 'rgba(234,179,8,0.8)' : 'rgba(255,255,255,0.1)', background: aegl1 ? 'var(--color-caution)' : 'rgba(255,255,255,0.1)',
border: aegl1 ? 'none' : '1px solid var(--stroke-default)', border: aegl1 ? 'none' : '1px solid var(--stroke-default)',
}} }}
/> />
</td> </td>
<td className="text-center px-4 py-3"> <td className="text-center px-4 py-3">
<span <span
className="text-[9px] font-semibold px-[10px] py-1 rounded" className="text-caption font-semibold px-[10px] py-1 rounded"
style={{ background: riskStyle.bg, color: riskStyle.color }} style={{ background: riskStyle.bg, color: riskStyle.color }}
> >
{riskLabel} {riskLabel}
</span> </span>
</td> </td>
<td className="text-center text-fg-sub font-mono px-4 py-3">{damageRadius}</td> <td className="text-center text-fg-sub font-mono px-4 py-3">{damageRadius}</td>
<td className="text-center text-fg-disabled text-[10px] px-4 py-3"> <td className="text-center text-fg-disabled text-label-2 px-4 py-3">
{item.analystNm || '—'} {item.analystNm || '—'}
</td> </td>
</tr> </tr>
@ -305,7 +293,7 @@ export function HNSAnalysisListTable({ onTabChange, onSelectAnalysis }: HNSAnaly
)} )}
{!loading && analyses.length === 0 && ( {!loading && analyses.length === 0 && (
<div className="text-center text-fg-disabled text-[12px] py-20"> <div className="text-center text-fg-disabled text-label-1 py-20">
. .
</div> </div>
)} )}

파일 보기

@ -70,6 +70,9 @@ export function HNSLeftPanel({
}: HNSLeftPanelProps) { }: HNSLeftPanelProps) {
const [incidents, setIncidents] = useState<IncidentListItem[]>([]); const [incidents, setIncidents] = useState<IncidentListItem[]>([]);
const [selectedIncidentSn, setSelectedIncidentSn] = useState(''); const [selectedIncidentSn, setSelectedIncidentSn] = useState('');
const [expandedSections, setExpandedSections] = useState({ accident: true, params: true });
const toggleSection = (key: 'accident' | 'params') =>
setExpandedSections((prev) => ({ ...prev, [key]: !prev[key] }));
const [accidentName, setAccidentName] = useState(''); const [accidentName, setAccidentName] = useState('');
const [accidentDate, setAccidentDate] = useState<string>(() => { const [accidentDate, setAccidentDate] = useState<string>(() => {
@ -218,40 +221,35 @@ export function HNSLeftPanel({
return ( return (
<div className="w-80 min-w-[320px] flex flex-col h-full bg-bg-surface border-r border-stroke overflow-hidden"> <div className="w-80 min-w-[320px] flex flex-col h-full bg-bg-surface border-r border-stroke overflow-hidden">
{/* Scrollable Content */} {/* Scrollable Content */}
<div className="flex-1 overflow-y-auto scrollbar-thin scrollbar-thumb-border-light scrollbar-track-transparent bg-bg-base">
{activeSubTab === 'analysis' && (
<div className="p-4">
{/* Header */}
<div className="flex items-center justify-between mb-[14px]">
<div className="flex items-center gap-2.5">
<div <div
className="w-9 h-9 rounded-md flex items-center justify-center text-[18px]" className="flex-1 overflow-y-scroll scrollbar-thin scrollbar-thumb-border-light scrollbar-track-transparent bg-bg-base"
style={{ style={{ scrollbarGutter: 'stable' }}
background:
'linear-gradient(135deg, rgba(249,115,22,0.15), rgba(168,85,247,0.1))',
border: '1px solid rgba(249,115,22,0.25)',
}}
> >
🧪 {activeSubTab === 'analysis' && (
</div>
<div> <div>
<div className="text-[13px] font-bold text-fg-sub font-korean"> {/* 헤더 */}
<div className="p-4 border-b border-stroke">
<div className="text-title-4 font-bold text-fg-sub font-korean">
HNS HNS
</div> </div>
<div className="text-[10px] text-fg-disabled"> <div className="text-label-2 text-fg-disabled mt-0.5">
ALOHA/CAMEO ALOHA/CAMEO
</div> </div>
</div> </div>
</div>
</div>
{/* Single Column Layout */}
<div className="flex flex-col gap-3">
{/* 사고 기본정보 */} {/* 사고 기본정보 */}
<div> <div className="border-b border-stroke">
<div className="text-[13px] font-bold text-fg-sub font-korean mb-3 flex items-center gap-1.5"> <div
📋 onClick={() => toggleSection('accident')}
className="flex items-center justify-between p-4 cursor-pointer hover:bg-[rgba(255,255,255,0.02)]"
>
<h3 className="text-title-4 font-bold text-fg-sub font-korean"> </h3>
<span className="text-label-2 text-fg-disabled">
{expandedSections.accident ? '▼' : '▶'}
</span>
</div> </div>
{expandedSections.accident && (
<div className="px-4 pb-4">
<div className="flex flex-col gap-[6px]"> <div className="flex flex-col gap-[6px]">
{/* 사고명 직접 입력 */} {/* 사고명 직접 입력 */}
<input <input
@ -275,7 +273,7 @@ export function HNSLeftPanel({
{/* 사고 발생 일시 */} {/* 사고 발생 일시 */}
<div> <div>
<label className="text-[10px] text-fg-disabled block mb-0.5"> <label className="text-label-2 text-fg-disabled block mb-0.5">
</label> </label>
<div className="grid grid-cols-2 gap-1"> <div className="grid grid-cols-2 gap-1">
@ -328,7 +326,7 @@ export function HNSLeftPanel({
{/* DMS 표시 */} {/* DMS 표시 */}
<div <div
className="text-[9px] text-fg-disabled font-mono border border-stroke bg-bg-base" className="text-caption text-fg-disabled font-mono border border-stroke bg-bg-base"
style={{ padding: '4px 8px', borderRadius: 'var(--radius-sm)' }} style={{ padding: '4px 8px', borderRadius: 'var(--radius-sm)' }}
> >
{incidentCoord {incidentCoord
@ -417,7 +415,7 @@ export function HNSLeftPanel({
{/* 알고리즘 선택 */} {/* 알고리즘 선택 */}
<div className="grid grid-cols-2 gap-1"> <div className="grid grid-cols-2 gap-1">
<div> <div>
<label className="text-[10px] text-fg-disabled block mb-0.5"> <label className="text-label-2 text-fg-disabled block mb-0.5">
</label> </label>
<ComboBox <ComboBox
@ -433,7 +431,7 @@ export function HNSLeftPanel({
/> />
</div> </div>
<div> <div>
<label className="text-[10px] text-fg-disabled block mb-0.5"> <label className="text-label-2 text-fg-disabled block mb-0.5">
</label> </label>
<ComboBox <ComboBox
@ -451,18 +449,29 @@ export function HNSLeftPanel({
</div> </div>
</div> </div>
</div> </div>
)}
</div>
{/* 모델 파라미터 & 물질 정보 */} {/* 모델 파라미터 & 물질 정보 */}
<div> <div className="border-b border-stroke">
<div className="text-[13px] font-bold text-fg-sub font-korean mb-3 flex items-center gap-1.5"> <div
🧪{' '} onClick={() => toggleSection('params')}
className="flex items-center justify-between p-4 cursor-pointer hover:bg-[rgba(255,255,255,0.02)]"
>
<h3 className="text-title-4 font-bold text-fg-sub font-korean">
{releaseType === '연속 유출' {releaseType === '연속 유출'
? 'Plume' ? 'Plume'
: releaseType === '순간 유출' : releaseType === '순간 유출'
? 'Puff' ? 'Puff'
: 'Dense Gas'}{' '} : 'Dense Gas'}{' '}
</h3>
<span className="text-label-2 text-fg-disabled">
{expandedSections.params ? '▼' : '▶'}
</span>
</div> </div>
{expandedSections.params && (
<div className="px-4 pb-4">
<div className="flex flex-col gap-[6px]"> <div className="flex flex-col gap-[6px]">
{/* 모델별 입력 파라미터 */} {/* 모델별 입력 파라미터 */}
<div <div
@ -477,7 +486,7 @@ export function HNSLeftPanel({
<div className="flex flex-col gap-[6px]"> <div className="flex flex-col gap-[6px]">
<div className="grid grid-cols-2 gap-1"> <div className="grid grid-cols-2 gap-1">
<div> <div>
<label className="text-[10px] text-fg-disabled block mb-0.5"> <label className="text-label-2 text-fg-disabled block mb-0.5">
(g/s) (g/s)
</label> </label>
<input <input
@ -490,7 +499,7 @@ export function HNSLeftPanel({
/> />
</div> </div>
<div> <div>
<label className="text-[10px] text-fg-disabled block mb-0.5"> <label className="text-label-2 text-fg-disabled block mb-0.5">
(s) (s)
</label> </label>
<input <input
@ -504,7 +513,7 @@ export function HNSLeftPanel({
</div> </div>
</div> </div>
<div> <div>
<label className="text-[10px] text-fg-disabled block mb-0.5"> <label className="text-label-2 text-fg-disabled block mb-0.5">
(m) (m)
</label> </label>
<input <input
@ -523,7 +532,7 @@ export function HNSLeftPanel({
{releaseType === '순간 유출' && ( {releaseType === '순간 유출' && (
<div className="flex flex-col gap-[6px]"> <div className="flex flex-col gap-[6px]">
<div> <div>
<label className="text-[10px] text-fg-disabled block mb-0.5"> <label className="text-label-2 text-fg-disabled block mb-0.5">
(g) (g)
</label> </label>
<input <input
@ -536,7 +545,7 @@ export function HNSLeftPanel({
/> />
</div> </div>
<div> <div>
<label className="text-[10px] text-fg-disabled block mb-0.5"> <label className="text-label-2 text-fg-disabled block mb-0.5">
(m) (m)
</label> </label>
<input <input
@ -556,7 +565,7 @@ export function HNSLeftPanel({
<div className="flex flex-col gap-[6px]"> <div className="flex flex-col gap-[6px]">
<div className="grid grid-cols-2 gap-1"> <div className="grid grid-cols-2 gap-1">
<div> <div>
<label className="text-[10px] text-fg-disabled block mb-0.5"> <label className="text-label-2 text-fg-disabled block mb-0.5">
(g/s) (g/s)
</label> </label>
<input <input
@ -569,7 +578,7 @@ export function HNSLeftPanel({
/> />
</div> </div>
<div> <div>
<label className="text-[10px] text-fg-disabled block mb-0.5"> <label className="text-label-2 text-fg-disabled block mb-0.5">
(m) (m)
</label> </label>
<input <input
@ -583,7 +592,7 @@ export function HNSLeftPanel({
</div> </div>
</div> </div>
<div> <div>
<label className="text-[10px] text-fg-disabled block mb-0.5"> <label className="text-label-2 text-fg-disabled block mb-0.5">
(m) (m)
</label> </label>
<input <input
@ -599,7 +608,7 @@ export function HNSLeftPanel({
)} )}
{/* 모델 설명 */} {/* 모델 설명 */}
<div className="text-[9px] text-fg-disabled mt-1 leading-[1.4]"> <div className="text-caption text-fg-disabled mt-1 leading-[1.4]">
{releaseType === '연속 유출' && {releaseType === '연속 유출' &&
'정상상태 연속 배출. 바람 방향으로 플룸이 형성됩니다.'} '정상상태 연속 배출. 바람 방향으로 플룸이 형성됩니다.'}
{releaseType === '순간 유출' && {releaseType === '순간 유출' &&
@ -613,14 +622,14 @@ export function HNSLeftPanel({
<div <div
className="p-2 rounded-sm mt-0.5" className="p-2 rounded-sm mt-0.5"
style={{ style={{
background: 'rgba(249,115,22,0.05)', background: 'rgba(6,182,212,0.05)',
border: '1px solid rgba(249,115,22,0.12)', border: '1px solid rgba(6,182,212,0.12)',
}} }}
> >
<div className="text-[10px] font-bold text-color-warning mb-1"> <div className="text-label-2 font-bold text-color-accent mb-1">
</div> </div>
<div className="grid grid-cols-2 gap-[3px] text-[9px]"> <div className="grid grid-cols-2 gap-[3px] text-caption">
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-fg-disabled"></span> <span className="text-fg-disabled"></span>
<span className="font-mono">{tox.mw} g/mol</span> <span className="font-mono">{tox.mw} g/mol</span>
@ -635,7 +644,7 @@ export function HNSLeftPanel({
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-fg-disabled">IDLH</span> <span className="text-fg-disabled">IDLH</span>
<span className="text-color-danger font-semibold font-mono"> <span className="text-color-caution font-semibold font-mono">
{tox.idlh} ppm {tox.idlh} ppm
</span> </span>
</div> </div>
@ -646,14 +655,14 @@ export function HNSLeftPanel({
<div <div
className="p-2 rounded-sm" className="p-2 rounded-sm"
style={{ style={{
background: 'rgba(168,85,247,0.05)', background: 'rgba(6,182,212,0.05)',
border: '1px solid rgba(168,85,247,0.12)', border: '1px solid rgba(6,182,212,0.12)',
}} }}
> >
<div className="text-[10px] font-bold text-color-tertiary mb-1"> <div className="text-label-2 font-bold text-color-accent mb-1">
📊 (AEGL) 📊 (AEGL)
</div> </div>
<div className="flex flex-col gap-0.5 text-[9px]"> <div className="flex flex-col gap-0.5 text-caption">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<div <div
className="w-2 h-2 rounded-[2px]" className="w-2 h-2 rounded-[2px]"
@ -666,7 +675,7 @@ export function HNSLeftPanel({
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<div <div
className="w-2 h-2 rounded-[2px]" className="w-2 h-2 rounded-[2px]"
style={{ background: 'rgba(249,115,22,0.7)' }} style={{ background: 'rgba(6,182,212,0.7)' }}
></div> ></div>
<span className="text-fg-disabled"> <span className="text-fg-disabled">
AEGL-2 () {tox.aegl2} ppm AEGL-2 () {tox.aegl2} ppm
@ -675,29 +684,32 @@ export function HNSLeftPanel({
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<div <div
className="w-2 h-2 rounded-[2px]" className="w-2 h-2 rounded-[2px]"
style={{ background: 'rgba(234,179,8,0.7)' }} style={{ background: 'rgba(6,182,212,0.7)' }}
></div> ></div>
<span className="text-fg-disabled">AEGL-1 () {tox.aegl1} ppm</span> <span className="text-fg-disabled">
AEGL-1 () {tox.aegl1} ppm
</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
)}
</div> </div>
{/* 실행 버튼 */} {/* 실행 버튼 */}
<div className="flex flex-col gap-1 mt-2"> <div className="flex flex-col gap-1 px-4 py-3">
<button <button
className="prd-btn pri" className="prd-btn pri"
style={{ padding: '7px', fontSize: '11px' }} style={{ padding: '7px', fontSize: 'var(--font-size-label-2)' }}
onClick={onRunPrediction} onClick={onRunPrediction}
disabled={isRunningPrediction} disabled={isRunningPrediction}
> >
{isRunningPrediction ? '⏳ 실행 중...' : '🧪 대기확산 예측 실행'} {isRunningPrediction ? '⏳ 실행 중...' : '대기확산 예측 실행'}
</button> </button>
<button <button
className="prd-btn sec" className="prd-btn sec"
style={{ padding: '7px', fontSize: '11px' }} style={{ padding: '7px', fontSize: 'var(--font-size-label-2)' }}
onClick={handleReset} onClick={handleReset}
> >
@ -711,29 +723,29 @@ export function HNSLeftPanel({
{/* Header */} {/* Header */}
<div className="flex items-center gap-2.5 mb-[14px]"> <div className="flex items-center gap-2.5 mb-[14px]">
<div <div
className="w-9 h-9 rounded-md flex items-center justify-center text-[18px]" className="w-9 h-9 rounded-md flex items-center justify-center text-title-1"
style={{ style={{
background: 'linear-gradient(135deg, rgba(6,182,212,0.15), rgba(168,85,247,0.1))', background: 'var(--bg-elevated)',
border: '1px solid rgba(6,182,212,0.25)', border: '1px solid rgba(6,182,212,0.25)',
}} }}
> >
📋 📋
</div> </div>
<div> <div>
<div className="text-[13px] font-bold text-fg-sub font-korean"> </div> <div className="text-title-4 font-bold text-fg-sub font-korean"> </div>
<div className="text-[10px] text-fg-disabled"> </div> <div className="text-label-2 text-fg-disabled"> </div>
</div> </div>
</div> </div>
{/* 필터 섹션 */} {/* 필터 섹션 */}
<div className="bg-bg-card border border-stroke rounded-md p-[14px] mb-3"> <div className="bg-bg-card border border-stroke rounded-md p-[14px] mb-3">
<div className="text-[13px] font-bold text-fg-sub font-korean mb-3 flex items-center gap-1.5"> <div className="text-title-4 font-bold text-fg-sub font-korean mb-3 flex items-center gap-1.5">
🔍 🔍
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{/* 기간 선택 */} {/* 기간 선택 */}
<div> <div>
<label className="text-[10px] text-fg-disabled block mb-0.5"></label> <label className="text-label-2 text-fg-disabled block mb-0.5"></label>
<ComboBox <ComboBox
value="최근 7일" value="최근 7일"
onChange={() => {}} onChange={() => {}}
@ -748,7 +760,7 @@ export function HNSLeftPanel({
{/* 물질 분류 */} {/* 물질 분류 */}
<div> <div>
<label className="text-[10px] text-fg-disabled block mb-0.5"> </label> <label className="text-label-2 text-fg-disabled block mb-0.5"> </label>
<ComboBox <ComboBox
value="전체" value="전체"
onChange={() => {}} onChange={() => {}}
@ -764,7 +776,7 @@ export function HNSLeftPanel({
{/* 위험도 */} {/* 위험도 */}
<div> <div>
<label className="text-[10px] text-fg-disabled block mb-0.5"></label> <label className="text-label-2 text-fg-disabled block mb-0.5"></label>
<ComboBox <ComboBox
value="전체" value="전체"
onChange={() => {}} onChange={() => {}}
@ -781,21 +793,21 @@ export function HNSLeftPanel({
{/* 통계 요약 */} {/* 통계 요약 */}
<div className="bg-bg-card border border-stroke rounded-md p-[14px]"> <div className="bg-bg-card border border-stroke rounded-md p-[14px]">
<div className="text-[13px] font-bold text-fg-sub font-korean mb-3 flex items-center gap-1.5"> <div className="text-title-4 font-bold text-fg-sub font-korean mb-3 flex items-center gap-1.5">
📊 📊
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex justify-between items-center p-2 bg-bg-base rounded"> <div className="flex justify-between items-center p-2 bg-bg-base rounded">
<span className="text-[10px] text-fg-disabled"> </span> <span className="text-label-2 text-fg-disabled"> </span>
<span className="text-sm font-bold text-color-accent font-mono">8</span> <span className="text-sm font-bold text-color-accent font-mono">8</span>
</div> </div>
<div className="flex justify-between items-center p-2 bg-bg-base rounded"> <div className="flex justify-between items-center p-2 bg-bg-base rounded">
<span className="text-[10px] text-fg-disabled"> (AEGL-3)</span> <span className="text-label-2 text-fg-disabled"> (AEGL-3)</span>
<span className="text-sm font-bold text-color-danger font-mono">3</span> <span className="text-sm font-bold text-color-caution font-mono">3</span>
</div> </div>
<div className="flex justify-between items-center p-2 bg-bg-base rounded"> <div className="flex justify-between items-center p-2 bg-bg-base rounded">
<span className="text-[10px] text-fg-disabled"> (AEGL-2)</span> <span className="text-label-2 text-fg-disabled"> (AEGL-2)</span>
<span className="text-sm font-bold text-color-warning font-mono">5</span> <span className="text-sm font-bold text-color-accent font-mono">5</span>
</div> </div>
</div> </div>
</div> </div>

파일 보기

@ -82,20 +82,20 @@ export function HNSRecalcModal({ isOpen, onClose, onSubmit, currentParams }: HNS
{/* Header */} {/* Header */}
<div className="px-5 py-4 border-b border-stroke flex items-center gap-3"> <div className="px-5 py-4 border-b border-stroke flex items-center gap-3">
<div <div
className="w-9 h-9 rounded-[10px] border border-[rgba(249,115,22,0.3)] flex items-center justify-center text-base shrink-0" className="w-9 h-9 rounded-[10px] border border-[rgba(6,182,212,0.3)] flex items-center justify-center text-base shrink-0"
style={{ style={{
background: 'linear-gradient(135deg, rgba(249,115,22,0.2), rgba(239,68,68,0.15))', background: 'linear-gradient(135deg, rgba(6,182,212,0.2), rgba(59,130,246,0.15))',
}} }}
> >
🔄 🔄
</div> </div>
<div className="flex-1"> <div className="flex-1">
<h2 className="text-[15px] font-bold m-0"> </h2> <h2 className="text-title-2 font-bold m-0"> </h2>
<div className="text-[10px] text-fg-disabled mt-0.5"> </div> <div className="text-label-2 text-fg-disabled mt-0.5"> </div>
</div> </div>
<button <button
onClick={onClose} onClick={onClose}
className="w-7 h-7 rounded-md border border-stroke bg-bg-card text-fg-disabled text-xs cursor-pointer flex items-center justify-center shrink-0" className="w-7 h-7 rounded-md border border-stroke bg-bg-card text-fg-disabled text-label-1 cursor-pointer flex items-center justify-center shrink-0"
> >
</button> </button>
@ -183,16 +183,16 @@ export function HNSRecalcModal({ isOpen, onClose, onSubmit, currentParams }: HNS
<div className="px-5 py-[14px] border-t border-stroke flex gap-2"> <div className="px-5 py-[14px] border-t border-stroke flex gap-2">
<button <button
onClick={onClose} onClick={onClose}
className="flex-1 py-2.5 text-xs font-semibold rounded-md cursor-pointer bg-bg-card border border-stroke text-fg-sub" className="flex-1 py-2.5 text-label-1 font-semibold rounded-md cursor-pointer bg-bg-card border border-stroke text-fg-sub"
> >
</button> </button>
<button <button
onClick={handleRun} onClick={handleRun}
className="flex-[2] py-2.5 text-xs font-bold rounded-md text-white" className="flex-[2] py-2.5 text-label-1 font-bold rounded-md text-white"
style={{ style={{
cursor: 'pointer', cursor: 'pointer',
background: 'linear-gradient(135deg, var(--color-warning), #ef4444)', background: 'linear-gradient(135deg, var(--color-accent), #ef4444)',
border: 'none', border: 'none',
}} }}
> >

파일 보기

@ -36,7 +36,7 @@ export function HNSRightPanel({
if (!dispersionResult) { if (!dispersionResult) {
return ( return (
<div className="w-[300px] bg-bg-surface border-l border-stroke p-4 overflow-auto"> <div className="w-[300px] bg-bg-surface border-l border-stroke p-4 overflow-auto">
<div className="flex flex-col gap-3 items-center justify-center h-full text-fg-disabled text-xs"> <div className="flex flex-col gap-3 items-center justify-center h-full text-fg-disabled text-label-1">
<div style={{ fontSize: '32px', opacity: 0.3 }}>📊</div> <div style={{ fontSize: '32px', opacity: 0.3 }}>📊</div>
<div> </div> <div> </div>
</div> </div>
@ -67,13 +67,13 @@ export function HNSRightPanel({
width: '6px', width: '6px',
height: '6px', height: '6px',
borderRadius: '50%', borderRadius: '50%',
background: 'var(--color-warning)', background: 'var(--color-accent)',
animation: 'pulse 1.5s infinite', animation: 'pulse 1.5s infinite',
}} }}
></div> ></div>
<h3 className="text-[13px] font-bold m-0"> </h3> <h3 className="text-title-4 font-bold m-0"> </h3>
</div> </div>
<div className="text-[10px] text-fg-disabled font-mono"> <div className="text-label-2 text-fg-disabled font-mono">
{dispersionResult.substance} · {modelLabel} {dispersionResult.substance} · {modelLabel}
</div> </div>
</div> </div>
@ -81,89 +81,80 @@ export function HNSRightPanel({
{/* KPI Cards */} {/* KPI Cards */}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{/* 최대 농도 */} {/* 최대 농도 */}
<div className="p-3 bg-bg-card border border-[rgba(239,68,68,0.2)] rounded-[var(--radius-sm)]"> <div className="p-3 bg-bg-card border border-stroke rounded-[var(--radius-sm)]">
<div className="text-[10px] text-fg-disabled mb-1.5"> </div> <div className="text-label-2 text-fg-disabled mb-1.5"> </div>
<div className="text-[20px] font-bold font-mono text-color-danger"> <div className="text-title-1 font-bold font-mono text-color-caution">
{maxConc > 0 ? maxConc.toFixed(1) : '—'}{' '} {maxConc > 0 ? maxConc.toFixed(1) : '—'}{' '}
<span className="text-[10px] font-medium">ppm</span> <span className="text-label-2 font-medium">ppm</span>
</div> </div>
</div> </div>
{/* 확산 면적 */} {/* 확산 면적 */}
<div className="p-3 bg-bg-card border border-[rgba(6,182,212,0.2)] rounded-[var(--radius-sm)]"> <div className="p-3 bg-bg-card border border-stroke rounded-[var(--radius-sm)]">
<div className="text-[10px] text-fg-disabled mb-1.5">AEGL-1 </div> <div className="text-label-2 text-fg-disabled mb-1.5">AEGL-1 </div>
<div className="text-[20px] font-bold font-mono text-color-accent"> <div className="text-title-1 font-bold font-mono text-color-accent">
{area > 0 ? area.toFixed(2) : '—'} <span className="text-[10px] font-medium">km²</span> {area > 0 ? area.toFixed(2) : '—'} <span className="text-label-2 font-medium">km²</span>
</div> </div>
</div> </div>
{/* 풍속 */} {/* 풍속 */}
<div className="p-3 bg-bg-card border border-stroke rounded-[var(--radius-sm)]"> <div className="p-3 bg-bg-card border border-stroke rounded-[var(--radius-sm)]">
<div className="text-[10px] text-fg-disabled mb-1.5"></div> <div className="text-label-2 text-fg-disabled mb-1.5"></div>
<div className="text-[20px] font-bold font-mono"> <div className="text-title-1 font-bold font-mono">
{windSpd.toFixed(1)} <span className="text-[10px] font-medium">m/s</span> {windSpd.toFixed(1)} <span className="text-label-2 font-medium">m/s</span>
</div> </div>
</div> </div>
{/* 풍향 */} {/* 풍향 */}
<div className="p-3 bg-bg-card border border-stroke rounded-[var(--radius-sm)]"> <div className="p-3 bg-bg-card border border-stroke rounded-[var(--radius-sm)]">
<div className="text-[10px] text-fg-disabled mb-1.5"></div> <div className="text-label-2 text-fg-disabled mb-1.5"></div>
<div className="text-[20px] font-bold font-mono"> <div className="text-title-1 font-bold font-mono">
{windDirToCompass(windDir)} <span className="text-[10px] font-medium">{windDir}°</span> {windDirToCompass(windDir)} <span className="text-label-2 font-medium">{windDir}°</span>
</div> </div>
</div> </div>
</div> </div>
{/* AEGL Zone Details */} {/* AEGL Zone Details */}
<div> <div>
<h4 className="text-[11px] font-semibold text-fg-sub mt-0 mb-2.5">AEGL </h4> <h4 className="text-label-2 font-semibold text-fg-sub mt-0 mb-2.5">AEGL </h4>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{/* AEGL-3 */} {/* AEGL-3 */}
<div <div className="py-2.5 px-3 bg-bg-elevated rounded-[var(--radius-sm)]">
className="py-2.5 px-3 bg-bg-elevated rounded-[var(--radius-sm)]"
style={{ borderLeft: '3px solid rgba(239,68,68,1)' }}
>
<div className="flex justify-between items-center mb-1"> <div className="flex justify-between items-center mb-1">
<span className="text-[11px] font-semibold">AEGL-3 ()</span> <span className="text-label-2 font-semibold">AEGL-3 ()</span>
<span className="text-[10px] font-mono text-fg-disabled"> <span className="text-label-2 font-mono text-fg-disabled">
{computedResult?.aeglDistances.aegl3 || 0}m {computedResult?.aeglDistances.aegl3 || 0}m
</span> </span>
</div> </div>
<div className="flex justify-between text-[10px] text-fg-disabled"> <div className="flex justify-between text-label-2 text-fg-disabled">
<span>{dispersionResult.concentration['AEGL-3']}</span> <span>{dispersionResult.concentration['AEGL-3']}</span>
<span className="font-mono">{computedResult?.aeglAreas.aegl3 ?? 0} km²</span> <span className="font-mono">{computedResult?.aeglAreas.aegl3 ?? 0} km²</span>
</div> </div>
</div> </div>
{/* AEGL-2 */} {/* AEGL-2 */}
<div <div className="py-2.5 px-3 bg-bg-elevated rounded-[var(--radius-sm)]">
className="py-2.5 px-3 bg-bg-elevated rounded-[var(--radius-sm)]"
style={{ borderLeft: '3px solid rgba(249,115,22,1)' }}
>
<div className="flex justify-between items-center mb-1"> <div className="flex justify-between items-center mb-1">
<span className="text-[11px] font-semibold">AEGL-2 ()</span> <span className="text-label-2 font-semibold">AEGL-2 ()</span>
<span className="text-[10px] font-mono text-fg-disabled"> <span className="text-label-2 font-mono text-fg-disabled">
{computedResult?.aeglDistances.aegl2 || 0}m {computedResult?.aeglDistances.aegl2 || 0}m
</span> </span>
</div> </div>
<div className="flex justify-between text-[10px] text-fg-disabled"> <div className="flex justify-between text-label-2 text-fg-disabled">
<span>{dispersionResult.concentration['AEGL-2']}</span> <span>{dispersionResult.concentration['AEGL-2']}</span>
<span className="font-mono">{computedResult?.aeglAreas.aegl2 ?? 0} km²</span> <span className="font-mono">{computedResult?.aeglAreas.aegl2 ?? 0} km²</span>
</div> </div>
</div> </div>
{/* AEGL-1 */} {/* AEGL-1 */}
<div <div className="py-2.5 px-3 bg-bg-elevated rounded-[var(--radius-sm)]">
className="py-2.5 px-3 bg-bg-elevated rounded-[var(--radius-sm)]"
style={{ borderLeft: '3px solid rgba(234,179,8,1)' }}
>
<div className="flex justify-between items-center mb-1"> <div className="flex justify-between items-center mb-1">
<span className="text-[11px] font-semibold">AEGL-1 ()</span> <span className="text-label-2 font-semibold">AEGL-1 ()</span>
<span className="text-[10px] font-mono text-fg-disabled"> <span className="text-label-2 font-mono text-fg-disabled">
{computedResult?.aeglDistances.aegl1 || 0}m {computedResult?.aeglDistances.aegl1 || 0}m
</span> </span>
</div> </div>
<div className="flex justify-between text-[10px] text-fg-disabled"> <div className="flex justify-between text-label-2 text-fg-disabled">
<span>{dispersionResult.concentration['AEGL-1']}</span> <span>{dispersionResult.concentration['AEGL-1']}</span>
<span className="font-mono">{computedResult?.aeglAreas.aegl1 ?? 0} km²</span> <span className="font-mono">{computedResult?.aeglAreas.aegl1 ?? 0} km²</span>
</div> </div>
@ -174,10 +165,10 @@ export function HNSRightPanel({
{/* 시간 정보 (puff/dense_gas) */} {/* 시간 정보 (puff/dense_gas) */}
{computedResult && computedResult.modelType !== 'plume' && ( {computedResult && computedResult.modelType !== 'plume' && (
<div className="p-2.5 bg-bg-card border border-stroke rounded-[var(--radius-sm)]"> <div className="p-2.5 bg-bg-card border border-stroke rounded-[var(--radius-sm)]">
<div className="text-[10px] text-fg-disabled mb-1"> </div> <div className="text-label-2 text-fg-disabled mb-1"> </div>
<div className="text-[14px] font-bold font-mono text-color-accent"> <div className="text-title-3 font-bold font-mono text-color-accent">
t = {computedResult.timeStep}s t = {computedResult.timeStep}s
<span className="text-[10px] font-normal text-fg-disabled ml-1.5"> <span className="text-label-2 font-normal text-fg-disabled ml-1.5">
({(computedResult.timeStep / 60).toFixed(1)}) ({(computedResult.timeStep / 60).toFixed(1)})
</span> </span>
</div> </div>
@ -185,7 +176,7 @@ export function HNSRightPanel({
)} )}
{/* Timestamp */} {/* Timestamp */}
<div className="mt-auto pt-3 border-t border-stroke text-[10px] text-fg-disabled font-mono"> <div className="mt-auto pt-3 border-t border-stroke text-label-2 text-fg-disabled font-mono">
: {new Date(dispersionResult.timestamp).toLocaleString('ko-KR')} : {new Date(dispersionResult.timestamp).toLocaleString('ko-KR')}
</div> </div>
@ -193,19 +184,19 @@ export function HNSRightPanel({
<div className="flex gap-1.5 pt-3 border-t border-stroke"> <div className="flex gap-1.5 pt-3 border-t border-stroke">
<button <button
onClick={onSave} onClick={onSave}
className="flex-1 py-2 px-1 rounded text-[11px] font-semibold bg-gradient-to-r from-boom to-[#d97706] text-black font-korean" className="flex-1 py-2 px-1 rounded text-label-2 font-semibold bg-color-navy text-static-white hover:bg-color-navy-hover font-korean"
> >
💾 💾
</button> </button>
<button <button
onClick={onOpenRecalc} onClick={onOpenRecalc}
className="flex-1 py-2 px-1 rounded text-[11px] font-semibold bg-[rgba(249,115,22,0.1)] border border-[rgba(249,115,22,0.3)] text-color-warning font-korean" className="flex-1 py-2 px-1 rounded text-label-2 font-semibold bg-bg-elevated border border-stroke text-fg font-korean"
> >
🔄 🔄
</button> </button>
<button <button
onClick={onOpenReport} onClick={onOpenReport}
className="flex-1 py-2 px-1 rounded text-[11px] font-semibold bg-gradient-to-r from-primary-cyan to-primary-blue text-white font-korean" className="flex-1 py-2 px-1 rounded text-label-2 font-semibold bg-color-navy text-static-white hover:bg-color-navy-hover font-korean"
> >
📄 📄
</button> </button>

파일 보기

@ -49,10 +49,10 @@ interface HnsMaterial {
} }
const SEVERITY_STYLE: Record<Severity, { bg: string; color: string }> = { const SEVERITY_STYLE: Record<Severity, { bg: string; color: string }> = {
CRITICAL: { bg: 'rgba(239,68,68,0.2)', color: '#f87171' }, CRITICAL: { bg: 'rgba(239,68,68,0.15)', color: 'var(--color-danger)' },
HIGH: { bg: 'rgba(249,115,22,0.15)', color: '#fb923c' }, HIGH: { bg: 'rgba(249,115,22,0.15)', color: 'var(--color-warning)' },
MEDIUM: { bg: 'rgba(251,191,36,0.15)', color: '#fbbf24' }, MEDIUM: { bg: 'rgba(234,179,8,0.15)', color: 'var(--color-caution)' },
RESOLVED: { bg: 'rgba(34,197,94,0.15)', color: '#22c55e' }, RESOLVED: { bg: 'rgba(34,197,94,0.15)', color: 'var(--color-success)' },
}; };
// ─── Mock Data (시나리오 시뮬레이션 엔진 미구현 — 프론트 상수 유지) ── // ─── Mock Data (시나리오 시뮬레이션 엔진 미구현 — 프론트 상수 유지) ──
@ -352,8 +352,8 @@ export function HNSScenarioView() {
<div className="flex items-center gap-2.5"> <div className="flex items-center gap-2.5">
<span className="text-base">📊</span> <span className="text-base">📊</span>
<div> <div>
<div className="text-sm font-bold">HNS </div> <div className="text-title-4 font-bold">HNS </div>
<div className="text-[10px] text-fg-disabled"> <div className="text-label-2 text-fg-disabled">
· · · ·
</div> </div>
</div> </div>
@ -362,7 +362,7 @@ export function HNSScenarioView() {
<select <select
value={selectedIncident} value={selectedIncident}
onChange={(e) => setSelectedIncident(Number(e.target.value))} onChange={(e) => setSelectedIncident(Number(e.target.value))}
className="prd-i w-[280px] text-[11px]" className="prd-i w-[280px] text-label-2"
> >
{incidents.length === 0 ? ( {incidents.length === 0 ? (
<option value={0}> </option> <option value={0}> </option>
@ -376,10 +376,10 @@ export function HNSScenarioView() {
</select> </select>
<button <button
onClick={() => setModalOpen(true)} onClick={() => setModalOpen(true)}
className="cursor-pointer whitespace-nowrap font-bold text-color-warning text-[11px] px-[14px] py-1.5 rounded-sm" className="cursor-pointer whitespace-nowrap font-semibold text-color-accent text-label-2 px-[14px] py-1.5 rounded-sm"
style={{ style={{
background: 'rgba(249,115,22,0.12)', border: '1px solid rgba(6,182,212,.3)',
border: '1px solid rgba(249,115,22,0.3)', background: 'rgba(6,182,212,.08)',
}} }}
> >
+ +
@ -395,18 +395,18 @@ export function HNSScenarioView() {
style={{ width: '370px', minWidth: '370px' }} style={{ width: '370px', minWidth: '370px' }}
> >
<div className="flex items-center justify-between border-b border-stroke px-[14px] py-2.5"> <div className="flex items-center justify-between border-b border-stroke px-[14px] py-2.5">
<span className="text-[11px] font-bold text-fg-disabled"> <span className="text-label-2 font-bold text-fg-disabled">
</span> </span>
<div className="flex gap-1"> <div className="flex gap-1">
{['시간순', '위험도순'].map((label, i) => ( {['시간순', '위험도순'].map((label, i) => (
<button <button
key={i} key={i}
className="cursor-pointer px-2 py-[3px] text-[9px] font-semibold rounded-sm border border-stroke" className={`cursor-pointer px-2 py-[3px] text-caption font-semibold rounded-sm border border-stroke ${
style={{ i === 0
background: i === 0 ? 'rgba(249,115,22,0.08)' : 'var(--bg-card)', ? 'bg-[rgba(6,182,212,0.08)] text-color-accent'
color: i === 0 ? 'var(--color-warning)' : 'var(--fg-disabled)', : 'bg-bg-card text-fg-disabled'
}} }`}
> >
{label} {label}
</button> </button>
@ -439,14 +439,14 @@ export function HNSScenarioView() {
checked={checked.has(idx)} checked={checked.has(idx)}
onChange={() => toggleCheck(idx)} onChange={() => toggleCheck(idx)}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
style={{ accentColor: 'var(--color-warning)' }} style={{ accentColor: 'var(--color-accent)' }}
/> />
<span className="text-[12px] font-bold"> <span className="text-label-1 font-bold">
{scn.id} {scn.name} {scn.id} {scn.name}
</span> </span>
</div> </div>
<span <span
className="font-bold px-2 py-[2px] rounded-lg text-[8px]" className="font-bold px-2 py-[2px] rounded-lg text-caption"
style={{ background: sev.bg, color: sev.color }} style={{ background: sev.bg, color: sev.color }}
> >
{scn.severity} {scn.severity}
@ -456,25 +456,25 @@ export function HNSScenarioView() {
{/* Time row */} {/* Time row */}
<div className="flex items-center gap-1.5 mb-1.5"> <div className="flex items-center gap-1.5 mb-1.5">
<span <span
className="font-bold font-mono text-color-warning text-[9px] px-1.5 py-[2px] rounded-[3px]" className="font-bold font-mono text-color-accent text-caption px-1.5 py-[2px] rounded-[3px]"
style={{ background: 'rgba(249,115,22,0.1)' }} style={{ background: 'rgba(6,182,212,0.1)' }}
> >
{scn.timeStep} {scn.timeStep}
</span> </span>
<span className="text-[9px] text-fg-disabled font-mono">{scn.datetime}</span> <span className="text-caption text-fg-disabled font-mono">{scn.datetime}</span>
<span className="ml-auto text-fg-disabled text-[8px]">{scn.wind}</span> <span className="ml-auto text-fg-disabled text-caption">{scn.wind}</span>
</div> </div>
{/* Metrics grid */} {/* Metrics grid */}
<div className="grid grid-cols-4 gap-1 font-mono text-[8px]"> <div className="grid grid-cols-4 gap-1 font-mono text-caption">
{[ {[
{ label: '최대농도', value: scn.maxConc, color: '#f87171' }, { label: '최대농도', value: scn.maxConc, color: 'var(--color-accent)' },
{ label: 'IDLH반경', value: scn.idlhRadius, color: '#f87171' }, { label: 'IDLH반경', value: scn.idlhRadius, color: 'var(--color-accent)' },
{ label: 'ERPG-2', value: scn.erpg2, color: '#f97316' }, { label: 'ERPG-2', value: scn.erpg2, color: 'var(--color-accent)' },
{ label: '영향인구', value: scn.population, color: '#f87171' }, { label: '영향인구', value: scn.population, color: 'var(--color-accent)' },
].map((m, i) => ( ].map((m, i) => (
<div key={i} className="text-center p-[3px] bg-bg-base rounded-[3px]"> <div key={i} className="text-center p-[3px] bg-bg-base rounded-[3px]">
<div className="text-fg-disabled text-[7px]">{m.label}</div> <div className="text-fg-disabled text-caption">{m.label}</div>
<div className="font-bold" style={{ color: m.color }}> <div className="font-bold" style={{ color: m.color }}>
{m.value} {m.value}
</div> </div>
@ -483,7 +483,7 @@ export function HNSScenarioView() {
</div> </div>
{/* Description */} {/* Description */}
<div className="text-fg-sub mt-1.5 text-[8px] leading-[1.4]"> <div className="text-fg-sub mt-1.5 text-caption leading-[1.4]">
{scn.description} {scn.description}
</div> </div>
</div> </div>
@ -495,15 +495,11 @@ export function HNSScenarioView() {
<div className="flex gap-2 border-t border-stroke px-[14px] py-2.5"> <div className="flex gap-2 border-t border-stroke px-[14px] py-2.5">
<button <button
onClick={() => setActiveView(1)} onClick={() => setActiveView(1)}
className="flex-1 cursor-pointer font-bold text-color-warning text-[11px] p-2 rounded-sm" className="flex-1 cursor-pointer font-bold text-static-white text-label-2 p-2 rounded-sm bg-color-navy hover:bg-color-navy-hover"
style={{
background: 'rgba(249,115,22,0.1)',
border: '1px solid rgba(249,115,22,0.3)',
}}
> >
📊 📊
</button> </button>
<button className="cursor-pointer font-semibold text-fg-sub text-[11px] px-[14px] py-2 rounded-sm bg-bg-card border border-stroke"> <button className="cursor-pointer font-semibold text-fg-sub text-label-2 px-[14px] py-2 rounded-sm bg-bg-card border border-stroke">
📄 📄
</button> </button>
</div> </div>
@ -563,23 +559,13 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
style={{ scrollbarWidth: 'thin' }} style={{ scrollbarWidth: 'thin' }}
> >
{/* Hero card */} {/* Hero card */}
<div <div className="rounded-md p-4 bg-bg-card border border-stroke">
className="relative overflow-hidden rounded-md p-4"
style={{
background: 'linear-gradient(135deg, rgba(249,115,22,0.06), rgba(239,68,68,0.04))',
border: '1px solid rgba(249,115,22,0.2)',
}}
>
<div
className="absolute top-0 left-0 right-0 h-0.5"
style={{ background: 'linear-gradient(90deg, #f97316, #ef4444, #a855f7)' }}
/>
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<span className="text-sm font-bold"> <span className="text-title-4 font-bold">
{scenario.id} {scenario.name} {scenario.id} {scenario.name}
</span> </span>
<span <span
className="font-bold px-2 py-[2px] rounded-lg text-[9px]" className="font-bold px-2 py-[2px] rounded-lg text-caption"
style={{ style={{
background: SEVERITY_STYLE[scenario.severity].bg, background: SEVERITY_STYLE[scenario.severity].bg,
color: SEVERITY_STYLE[scenario.severity].color, color: SEVERITY_STYLE[scenario.severity].color,
@ -587,31 +573,27 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
> >
{scenario.severity} {scenario.severity}
</span> </span>
<span className="ml-auto text-[10px] text-fg-disabled font-mono"> <span className="ml-auto text-label-2 text-fg-disabled font-mono">
{scenario.datetime} {scenario.datetime}
</span> </span>
</div> </div>
<div className="grid gap-2" style={{ gridTemplateColumns: 'repeat(6, 1fr)' }}> <div className="grid gap-2" style={{ gridTemplateColumns: 'repeat(6, 1fr)' }}>
{[ {[
{ label: '최대농도', value: d.maxConc, color: '#f87171' }, { label: '최대농도', value: d.maxConc, color: 'var(--color-accent)' },
{ label: 'IDLH 반경', value: d.idlhRadius, color: '#f87171' }, { label: 'IDLH 반경', value: d.idlhRadius, color: 'var(--color-accent)' },
{ label: 'ERPG-2', value: d.erpg2, color: '#f97316' }, { label: 'ERPG-2', value: d.erpg2, color: 'var(--color-accent)' },
{ {
label: '풍향/풍속', label: '풍향/풍속',
value: `${d.windDir}\n${d.windSpeed}`, value: `${d.windDir}\n${d.windSpeed}`,
color: 'var(--color-accent)', color: 'var(--color-accent)',
}, },
{ label: '영향인구', value: d.population, color: '#f87171' }, { label: '영향인구', value: d.population, color: 'var(--color-accent)' },
{ label: '유출량', value: d.spillAmount, color: 'var(--color-warning)' }, { label: '유출량', value: d.spillAmount, color: 'var(--color-accent)' },
].map((m, i) => ( ].map((m, i) => (
<div key={i} className="text-center rounded-sm p-2 bg-bg-base">
<div className="text-fg-disabled text-caption">{m.label}</div>
<div <div
key={i} className="text-title-3 font-bold font-mono whitespace-pre-line mt-[2px]"
className="text-center rounded-sm p-2"
style={{ background: 'rgba(0,0,0,0.15)' }}
>
<div className="text-fg-disabled text-[8px]">{m.label}</div>
<div
className="text-base font-bold font-mono whitespace-pre-line mt-[2px]"
style={{ color: m.color, lineHeight: 1.2 }} style={{ color: m.color, lineHeight: 1.2 }}
> >
{m.value} {m.value}
@ -625,21 +607,29 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
{/* Threat Zones */} {/* Threat Zones */}
<div className="rounded-md border border-stroke bg-bg-elevated p-[14px]"> <div className="rounded-md border border-stroke bg-bg-elevated p-[14px]">
<h4 className="text-[12px] font-bold mb-2.5"> </h4> <h4 className="text-label-1 font-bold mb-2.5"> </h4>
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
{[ {[
{ label: 'IDLH (즉시위험)', value: scenario.zones.idlh, color: '#ef4444' }, {
{ label: 'ERPG-2 (대피권고)', value: scenario.zones.erpg2, color: '#f97316' }, label: 'IDLH (즉시위험)',
{ label: 'ERPG-1 (주의권고)', value: scenario.zones.erpg1, color: '#fbbf24' }, value: scenario.zones.idlh,
{ label: 'TWA (작업허용)', value: scenario.zones.twa, color: '#22c55e' }, color: 'var(--color-danger)',
},
{
label: 'ERPG-2 (대피권고)',
value: scenario.zones.erpg2,
color: 'var(--color-warning)',
},
{
label: 'ERPG-1 (주의권고)',
value: scenario.zones.erpg1,
color: 'var(--color-caution)',
},
{ label: 'TWA (작업허용)', value: scenario.zones.twa, color: 'var(--color-success)' },
].map((z, i) => ( ].map((z, i) => (
<div <div key={i} className="flex justify-between items-center bg-bg-base rounded-sm">
key={i} <span className="text-label-2 text-fg-sub">{z.label}</span>
className="flex justify-between items-center bg-bg-base rounded-sm" <span className="text-label-2 font-bold font-mono" style={{ color: z.color }}>
style={{ padding: '6px 8px', borderLeft: `3px solid ${z.color}` }}
>
<span className="text-[10px] text-fg-sub">{z.label}</span>
<span className="text-[11px] font-bold font-mono" style={{ color: z.color }}>
{z.value} {z.value}
</span> </span>
</div> </div>
@ -649,14 +639,14 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
{/* Actions */} {/* Actions */}
<div className="rounded-md border border-stroke bg-bg-elevated p-[14px]"> <div className="rounded-md border border-stroke bg-bg-elevated p-[14px]">
<h4 className="text-[12px] font-bold mb-2.5">🛡 </h4> <h4 className="text-label-1 font-bold mb-2.5">🛡 </h4>
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
{scenario.actions.map((action, i) => ( {scenario.actions.map((action, i) => (
<div <div
key={i} key={i}
className="flex items-start gap-1.5 text-[10px] text-fg-sub bg-bg-base rounded-sm leading-[1.4] py-[5px] px-2" className="flex items-start gap-1.5 text-label-2 text-fg-sub bg-bg-base rounded-sm leading-[1.4] py-[5px] px-2"
> >
<span className="text-color-warning font-bold shrink-0"></span> <span className="text-color-accent font-bold shrink-0"></span>
{action} {action}
</div> </div>
))} ))}
@ -666,7 +656,7 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
{/* Weather */} {/* Weather */}
<div className="rounded-md border border-stroke bg-bg-elevated p-[14px]"> <div className="rounded-md border border-stroke bg-bg-elevated p-[14px]">
<h4 className="text-[12px] font-bold mb-2.5">🌊 </h4> <h4 className="text-label-1 font-bold mb-2.5">🌊 </h4>
<div className="grid gap-2" style={{ gridTemplateColumns: 'repeat(6, 1fr)' }}> <div className="grid gap-2" style={{ gridTemplateColumns: 'repeat(6, 1fr)' }}>
{[ {[
{ label: '풍향', value: scenario.weather.dir, icon: '🌬' }, { label: '풍향', value: scenario.weather.dir, icon: '🌬' },
@ -677,9 +667,9 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
{ label: '혼합층', value: scenario.weather.mixHeight, icon: '📏' }, { label: '혼합층', value: scenario.weather.mixHeight, icon: '📏' },
].map((w, i) => ( ].map((w, i) => (
<div key={i} className="text-center p-2 rounded-sm bg-bg-base"> <div key={i} className="text-center p-2 rounded-sm bg-bg-base">
<div className="text-sm mb-0.5">{w.icon}</div> <div className="text-title-3 mb-0.5">{w.icon}</div>
<div className="text-[12px] font-bold font-mono">{w.value}</div> <div className="text-label-1 font-bold font-mono">{w.value}</div>
<div className="text-fg-disabled mt-0.5 text-[8px]">{w.label}</div> <div className="text-fg-disabled mt-0.5 text-caption">{w.label}</div>
</div> </div>
))} ))}
</div> </div>
@ -744,10 +734,10 @@ const CHART_DATA = [
]; ];
const SEV_COLOR: Record<Severity, string> = { const SEV_COLOR: Record<Severity, string> = {
CRITICAL: '#f87171', CRITICAL: 'var(--color-danger)',
HIGH: '#fb923c', HIGH: 'var(--color-warning)',
MEDIUM: '#fbbf24', MEDIUM: 'var(--color-caution)',
RESOLVED: '#22c55e', RESOLVED: 'var(--color-success)',
}; };
function ScenarioComparison() { function ScenarioComparison() {
@ -776,13 +766,13 @@ function ScenarioComparison() {
style={{ scrollbarWidth: 'thin', scrollbarColor: 'var(--stroke-light) transparent' }} style={{ scrollbarWidth: 'thin', scrollbarColor: 'var(--stroke-light) transparent' }}
> >
{/* Title */} {/* Title */}
<div className="text-[13px] font-bold mb-0.5"> <div className="text-title-4 font-bold mb-0.5">
📊 📊
</div> </div>
{/* ── Chart 1: 최대 지표면 농도 추이 (Line + Area) ── */} {/* ── Chart 1: 최대 지표면 농도 추이 (Line + Area) ── */}
<div className="rounded-md border border-stroke bg-bg-card p-[14px]"> <div className="rounded-md border border-stroke bg-bg-card p-[14px]">
<div className="text-[11px] font-bold mb-2.5"> (ppm) </div> <div className="text-label-2 font-bold mb-2.5"> (ppm) </div>
<svg viewBox="0 0 500 140" className="w-full" style={{ height: '130px' }}> <svg viewBox="0 0 500 140" className="w-full" style={{ height: '130px' }}>
<defs> <defs>
<linearGradient id="hnsGrad" x1="0" y1="0" x2="0" y2="1"> <linearGradient id="hnsGrad" x1="0" y1="0" x2="0" y2="1">
@ -799,7 +789,7 @@ function ScenarioComparison() {
y1={10 + (1 - 500 / concMax) * 110} y1={10 + (1 - 500 / concMax) * 110}
x2="480" x2="480"
y2={10 + (1 - 500 / concMax) * 110} y2={10 + (1 - 500 / concMax) * 110}
stroke="rgba(239,68,68,.2)" stroke="rgba(59,130,246,.2)"
strokeWidth={0.5} strokeWidth={0.5}
strokeDasharray="4,3" strokeDasharray="4,3"
/> />
@ -817,7 +807,7 @@ function ScenarioComparison() {
y1={10 + (1 - 300 / concMax) * 110} y1={10 + (1 - 300 / concMax) * 110}
x2="480" x2="480"
y2={10 + (1 - 300 / concMax) * 110} y2={10 + (1 - 300 / concMax) * 110}
stroke="rgba(249,115,22,.2)" stroke="rgba(6,182,212,.2)"
strokeWidth={0.5} strokeWidth={0.5}
strokeDasharray="4,3" strokeDasharray="4,3"
/> />
@ -874,7 +864,7 @@ function ScenarioComparison() {
<div className="grid grid-cols-2 gap-[14px]"> <div className="grid grid-cols-2 gap-[14px]">
{/* Chart 2: 위험 반경 변화 (Multi-line) */} {/* Chart 2: 위험 반경 변화 (Multi-line) */}
<div className="rounded-md border border-stroke bg-bg-card p-[14px]"> <div className="rounded-md border border-stroke bg-bg-card p-[14px]">
<div className="text-[11px] font-bold mb-2.5"> (km) </div> <div className="text-label-2 font-bold mb-2.5"> (km) </div>
<svg viewBox="0 0 260 100" className="w-full" style={{ height: '85px' }}> <svg viewBox="0 0 260 100" className="w-full" style={{ height: '85px' }}>
<line x1="30" y1="10" x2="30" y2="85" stroke="#21262d" strokeWidth={0.5} /> <line x1="30" y1="10" x2="30" y2="85" stroke="#21262d" strokeWidth={0.5} />
<line x1="30" y1="85" x2="230" y2="85" stroke="#21262d" strokeWidth={0.5} /> <line x1="30" y1="85" x2="230" y2="85" stroke="#21262d" strokeWidth={0.5} />
@ -946,7 +936,7 @@ function ScenarioComparison() {
{/* Chart 3: 영향 인구 변화 (Bar) */} {/* Chart 3: 영향 인구 변화 (Bar) */}
<div className="rounded-md border border-stroke bg-bg-card p-[14px]"> <div className="rounded-md border border-stroke bg-bg-card p-[14px]">
<div className="text-[11px] font-bold mb-2.5"> () </div> <div className="text-label-2 font-bold mb-2.5"> () </div>
<svg viewBox="0 0 240 100" className="w-full" style={{ height: '85px' }}> <svg viewBox="0 0 240 100" className="w-full" style={{ height: '85px' }}>
<line x1="30" y1="10" x2="30" y2="85" stroke="#21262d" strokeWidth={0.5} /> <line x1="30" y1="10" x2="30" y2="85" stroke="#21262d" strokeWidth={0.5} />
<line x1="30" y1="85" x2="230" y2="85" stroke="#21262d" strokeWidth={0.5} /> <line x1="30" y1="85" x2="230" y2="85" stroke="#21262d" strokeWidth={0.5} />
@ -987,14 +977,14 @@ function ScenarioComparison() {
{/* ── Chart 4: 시나리오 비교표 ── */} {/* ── Chart 4: 시나리오 비교표 ── */}
<div className="rounded-md border border-stroke overflow-x-auto bg-bg-card p-[14px]"> <div className="rounded-md border border-stroke overflow-x-auto bg-bg-card p-[14px]">
<div className="text-[11px] font-bold mb-2.5">📋 </div> <div className="text-label-2 font-bold mb-2.5">📋 </div>
<table className="w-full text-[10px] border-collapse"> <table className="w-full text-label-2 border-collapse">
<thead> <thead>
<tr className="bg-bg-base"> <tr className="bg-bg-base">
{['지표', ...D.map((d) => `${d.id} (${d.label})`)].map((h, i) => ( {['지표', ...D.map((d) => `${d.id} (${d.label})`)].map((h, i) => (
<th <th
key={i} key={i}
className="text-fg-disabled border-b border-stroke text-[9px] px-[10px] py-2" className="text-fg-disabled border-b border-stroke text-caption px-[10px] py-2"
style={{ textAlign: i === 0 ? 'left' : 'center' }} style={{ textAlign: i === 0 ? 'left' : 'center' }}
> >
{h} {h}
@ -1023,7 +1013,7 @@ function ScenarioComparison() {
<td <td
key={d.id} key={d.id}
className="text-center font-mono font-semibold p-1.5" className="text-center font-mono font-semibold p-1.5"
style={{ color: d.idlh > 0 ? '#f87171' : '#22c55e' }} style={{ color: d.idlh > 0 ? 'var(--color-danger)' : 'var(--color-success)' }}
> >
{d.idlh || 0} {d.idlh || 0}
</td> </td>
@ -1036,7 +1026,7 @@ function ScenarioComparison() {
<td <td
key={d.id} key={d.id}
className="text-center font-mono font-semibold p-1.5" className="text-center font-mono font-semibold p-1.5"
style={{ color: d.erpg2 > 0 ? '#f97316' : '#22c55e' }} style={{ color: d.erpg2 > 0 ? 'var(--color-warning)' : 'var(--color-success)' }}
> >
{d.erpg2 || 0} {d.erpg2 || 0}
</td> </td>
@ -1089,7 +1079,7 @@ function ScenarioMapOverlay() {
return ( return (
<div className="flex-1 flex items-center justify-center flex-col gap-4"> <div className="flex-1 flex items-center justify-center flex-col gap-4">
<div <div
className="flex items-center justify-center rounded-md border border-stroke text-fg-disabled text-[13px] bg-bg-elevated" className="flex items-center justify-center rounded-md border border-stroke text-fg-disabled text-title-4 bg-bg-elevated"
style={{ style={{
width: '80%', width: '80%',
maxWidth: '600px', maxWidth: '600px',
@ -1100,14 +1090,14 @@ function ScenarioMapOverlay() {
</div> </div>
<div className="flex gap-4"> <div className="flex gap-4">
{[ {[
{ label: 'T+0h SW방향', color: '#ef4444' }, { label: 'T+0h SW방향', color: 'var(--color-danger)' },
{ label: 'T+1h SE 전환', color: '#f97316' }, { label: 'T+1h SE 전환', color: 'var(--color-warning)' },
{ label: 'T+3h S방향', color: '#fbbf24' }, { label: 'T+3h S방향', color: 'var(--color-caution)' },
{ label: 'T+6h 차단 후', color: '#22c55e' }, { label: 'T+6h 차단 후', color: 'var(--color-success)' },
].map((item, i) => ( ].map((item, i) => (
<div key={i} className="flex items-center gap-1.5"> <div key={i} className="flex items-center gap-1.5">
<div className="w-3 h-3 rounded-full opacity-50" style={{ background: item.color }} /> <div className="w-3 h-3 rounded-full opacity-50" style={{ background: item.color }} />
<span className="text-[10px] text-fg-sub">{item.label}</span> <span className="text-label-2 text-fg-sub">{item.label}</span>
</div> </div>
))} ))}
</div> </div>
@ -1172,24 +1162,18 @@ function NewScenarioModal({
> >
{/* Header */} {/* Header */}
<div className="flex items-center gap-3 border-b border-stroke px-5 py-4"> <div className="flex items-center gap-3 border-b border-stroke px-5 py-4">
<div <div className="flex items-center justify-center text-base w-9 h-9 rounded-[10px] bg-bg-elevated border border-stroke">
className="flex items-center justify-center text-base w-9 h-9 rounded-[10px]"
style={{
background: 'linear-gradient(135deg, rgba(249,115,22,0.2), rgba(239,68,68,0.15))',
border: '1px solid rgba(249,115,22,0.3)',
}}
>
🧪 🧪
</div> </div>
<div className="flex-1"> <div className="flex-1">
<h2 className="text-[15px] font-bold m-0"> HNS </h2> <h2 className="text-title-2 font-bold m-0"> HNS </h2>
<div className="text-[10px] text-fg-disabled mt-0.5"> <div className="text-label-2 text-fg-disabled mt-0.5">
·· ··
</div> </div>
</div> </div>
<button <button
onClick={onClose} onClick={onClose}
className="flex items-center justify-center w-7 h-7 cursor-pointer text-fg-disabled text-[12px] rounded-sm border border-stroke bg-bg-card" className="flex items-center justify-center w-7 h-7 cursor-pointer text-fg-disabled text-label-1 rounded-sm border border-stroke bg-bg-card"
> >
</button> </button>
@ -1244,12 +1228,10 @@ function NewScenarioModal({
</ModalField> </ModalField>
{/* Material properties card */} {/* Material properties card */}
<div <div
className="grid p-2 rounded-sm" className="grid p-2 rounded-sm bg-bg-elevated border border-stroke"
style={{ style={{
gridTemplateColumns: 'repeat(5, 1fr)', gridTemplateColumns: 'repeat(5, 1fr)',
gap: '4px', gap: '4px',
background: 'rgba(249,115,22,0.04)',
border: '1px solid rgba(249,115,22,0.15)',
}} }}
> >
{[ {[
@ -1260,8 +1242,8 @@ function NewScenarioModal({
{ label: 'ERPG-2', value: mat.erpg2 }, { label: 'ERPG-2', value: mat.erpg2 },
].map((p, i) => ( ].map((p, i) => (
<div key={i} className="text-center"> <div key={i} className="text-center">
<div className="text-fg-disabled text-[8px]">{p.label}</div> <div className="text-fg-disabled text-caption">{p.label}</div>
<div className="text-[10px] font-bold text-color-warning font-mono"> <div className="text-label-2 font-bold text-color-accent font-mono">
{p.value} {p.value}
</div> </div>
</div> </div>
@ -1406,17 +1388,15 @@ function NewScenarioModal({
<div className="flex gap-2 border-t border-stroke px-5 py-[14px]"> <div className="flex gap-2 border-t border-stroke px-5 py-[14px]">
<button <button
onClick={onClose} onClick={onClose}
className="flex-1 text-[12px] font-semibold cursor-pointer rounded-md text-fg-sub p-[10px] bg-bg-card border border-stroke" className="flex-1 text-label-1 font-semibold cursor-pointer rounded-md text-fg-sub p-[10px] bg-bg-card border border-stroke"
> >
</button> </button>
<button <button
onClick={handleSubmit} onClick={handleSubmit}
className="cursor-pointer rounded-md text-[12px] font-bold text-white p-[10px]" className="cursor-pointer rounded-md text-label-1 font-bold text-static-white p-[10px] bg-color-navy hover:bg-color-navy-hover"
style={{ style={{
flex: 2, flex: 2,
background: 'linear-gradient(135deg, var(--color-warning), #ef4444)',
border: 'none',
opacity: name.trim() ? 1 : 0.5, opacity: name.trim() ? 1 : 0.5,
}} }}
> >
@ -1432,10 +1412,7 @@ function NewScenarioModal({
function ModalSection({ title, children }: { title: string; children: React.ReactNode }) { function ModalSection({ title, children }: { title: string; children: React.ReactNode }) {
return ( return (
<div> <div>
<div <div className="text-label-2 font-bold text-color-accent mb-2 pb-1 border-b border-stroke">
className="text-[11px] font-bold text-color-warning mb-2 pb-1"
style={{ borderBottom: '1px solid rgba(249,115,22,0.15)' }}
>
{title} {title}
</div> </div>
<div className="flex flex-col gap-2">{children}</div> <div className="flex flex-col gap-2">{children}</div>
@ -1446,7 +1423,7 @@ function ModalSection({ title, children }: { title: string; children: React.Reac
function ModalField({ label, children }: { label: string; children: React.ReactNode }) { function ModalField({ label, children }: { label: string; children: React.ReactNode }) {
return ( return (
<div> <div>
<div className="text-[9px] font-semibold text-fg-disabled mb-1">{label}</div> <div className="text-caption font-semibold text-fg-disabled mb-1">{label}</div>
{children} {children}
</div> </div>
); );

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다. Load Diff

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다. Load Diff

파일 보기

@ -44,7 +44,7 @@ function HNSManualViewer() {
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<div> <div>
<div className="text-base font-bold">📖 HNS </div> <div className="text-base font-bold">📖 HNS </div>
<div className="text-[10px] text-fg-disabled mt-0.5"> <div className="text-label-2 text-fg-disabled mt-0.5">
Marine HNS Response Manual Bonn Agreement / HELCOM / REMPEC (WestMOPoCo 2024 Marine HNS Response Manual Bonn Agreement / HELCOM / REMPEC (WestMOPoCo 2024
) )
</div> </div>
@ -70,19 +70,19 @@ function HNSManualViewer() {
icon: '🔬', icon: '🔬',
title: '3. HNS 거동 및 유해요소', title: '3. HNS 거동 및 유해요소',
desc: 'SEBC 거동분류 · MSDS · GESAMP · 물리화학적 특성', desc: 'SEBC 거동분류 · MSDS · GESAMP · 물리화학적 특성',
color: 'var(--color-tertiary)', color: 'var(--color-accent)',
}, },
{ {
icon: '🛡️', icon: '🛡️',
title: '4. 대비', title: '4. 대비',
desc: '위험 평가 · 비상 계획 · 교육훈련 · 장비 비축', desc: '위험 평가 · 비상 계획 · 교육훈련 · 장비 비축',
color: 'var(--color-warning)', color: 'var(--color-accent)',
}, },
{ {
icon: '🚨', icon: '🚨',
title: '5. 대응', title: '5. 대응',
desc: '최초 조치 · 안전구역 · PPE · 모니터링 · 대응 기술', desc: '최초 조치 · 안전구역 · PPE · 모니터링 · 대응 기술',
color: 'var(--color-danger)', color: 'var(--color-info)',
}, },
{ {
icon: '🔄', icon: '🔄',
@ -114,18 +114,18 @@ function HNSManualViewer() {
}} }}
> >
<div className="text-[20px] mb-1.5">{ch.icon}</div> <div className="text-[20px] mb-1.5">{ch.icon}</div>
<div className="text-[11px] font-bold">{ch.title}</div> <div className="text-label-2 font-bold">{ch.title}</div>
<div className="text-[9px] text-fg-disabled mt-1 leading-[1.4]">{ch.desc}</div> <div className="text-caption text-fg-disabled mt-1 leading-[1.4]">{ch.desc}</div>
</div> </div>
))} ))}
</div> </div>
{/* SEBC 거동 분류 */} {/* SEBC 거동 분류 */}
<div className={`${card} bg-bg-card border border-stroke`}> <div className={`${card} bg-bg-card border border-stroke`}>
<div className="text-[13px] font-bold mb-2.5"> <div className="text-title-4 font-bold mb-2.5">
SEBC (Standard European Behaviour Classification) SEBC (Standard European Behaviour Classification)
</div> </div>
<div className="text-[9px] text-fg-disabled mb-2.5 leading-normal"> <div className="text-caption text-fg-disabled mb-2.5 leading-normal">
· (, , , ) 5 · (, , , ) 5
+ 7 + 7
</div> </div>
@ -141,7 +141,7 @@ function HNSManualViewer() {
icon: '🌫️', icon: '🌫️',
label: 'E — 증발', label: 'E — 증발',
desc: '수면→대기 증발\n증기압 > 3kPa\n예: 벤젠, 톨루엔', desc: '수면→대기 증발\n증기압 > 3kPa\n예: 벤젠, 톨루엔',
color: 'rgba(249,115,22', color: 'rgba(6,182,212',
}, },
{ {
icon: '🟡', icon: '🟡',
@ -168,7 +168,7 @@ function HNSManualViewer() {
style={{ background: `${s.color},.08)`, border: `1px solid ${s.color},.2)` }} style={{ background: `${s.color},.08)`, border: `1px solid ${s.color},.2)` }}
> >
<div className="text-[22px] mb-1">{s.icon}</div> <div className="text-[22px] mb-1">{s.icon}</div>
<div className="text-[11px] font-bold" style={{ color: `${s.color},1)` }}> <div className="text-label-2 font-bold" style={{ color: `${s.color},1)` }}>
{s.label} {s.label}
</div> </div>
<div className="text-fg-disabled whitespace-pre-line text-[8px] mt-[3px] leading-[1.3]"> <div className="text-fg-disabled whitespace-pre-line text-[8px] mt-[3px] leading-[1.3]">
@ -225,10 +225,10 @@ function DispersionTimeSlider({
> >
<button <button
onClick={onPlayPause} onClick={onPlayPause}
className="flex items-center justify-center w-7 h-7 rounded-full text-[14px]" className="flex items-center justify-center w-7 h-7 rounded-full text-title-3"
style={{ style={{
background: isPlaying ? 'rgba(239,68,68,0.2)' : 'rgba(6,182,212,0.2)', background: isPlaying ? 'rgba(59,130,246,0.2)' : 'rgba(6,182,212,0.2)',
border: `1px solid ${isPlaying ? 'rgba(239,68,68,0.4)' : 'rgba(6,182,212,0.4)'}`, border: `1px solid ${isPlaying ? 'rgba(59,130,246,0.4)' : 'rgba(6,182,212,0.4)'}`,
cursor: 'pointer', cursor: 'pointer',
}} }}
> >
@ -236,7 +236,7 @@ function DispersionTimeSlider({
</button> </button>
<div className="flex-1 flex flex-col gap-1"> <div className="flex-1 flex flex-col gap-1">
<div className="flex items-center justify-between text-[9px]"> <div className="flex items-center justify-between text-caption">
<span className="text-color-accent font-mono font-bold">t = {currentTime}s</span> <span className="text-color-accent font-mono font-bold">t = {currentTime}s</span>
<span className="text-fg-disabled font-mono">{endTime}s</span> <span className="text-fg-disabled font-mono">{endTime}s</span>
</div> </div>
@ -251,7 +251,7 @@ function DispersionTimeSlider({
/> />
</div> </div>
<div className="text-[9px] text-fg-disabled font-mono whitespace-nowrap"> <div className="text-caption text-fg-disabled font-mono whitespace-nowrap">
{currentFrame + 1}/{totalFrames} {currentFrame + 1}/{totalFrames}
</div> </div>
</div> </div>
@ -777,19 +777,19 @@ export function HNSView() {
zones: [ zones: [
{ {
level: 'AEGL-3', level: 'AEGL-3',
color: 'rgba(239,68,68,0.4)', color: 'rgba(59,130,246,0.4)',
radius: resultForZones.aeglDistances.aegl3, radius: resultForZones.aeglDistances.aegl3,
angle: meteo.windDirDeg, angle: meteo.windDirDeg,
}, },
{ {
level: 'AEGL-2', level: 'AEGL-2',
color: 'rgba(249,115,22,0.3)', color: 'rgba(6,182,212,0.3)',
radius: resultForZones.aeglDistances.aegl2, radius: resultForZones.aeglDistances.aegl2,
angle: meteo.windDirDeg, angle: meteo.windDirDeg,
}, },
{ {
level: 'AEGL-1', level: 'AEGL-1',
color: 'rgba(234,179,8,0.25)', color: 'rgba(6,182,212,0.25)',
radius: resultForZones.aeglDistances.aegl1, radius: resultForZones.aeglDistances.aegl1,
angle: meteo.windDirDeg, angle: meteo.windDirDeg,
}, },

파일 보기

@ -36,25 +36,25 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
switch (status) { switch (status) {
case 'completed': case 'completed':
return ( return (
<span className="px-2 py-1 text-[10px] font-semibold rounded-md bg-[rgba(34,197,94,0.15)] text-green-400"> <span className="px-2 py-1 text-label-2 font-medium rounded-md bg-[rgba(34,197,94,0.15)] text-color-success">
</span> </span>
); );
case 'running': case 'running':
return ( return (
<span className="px-2 py-1 text-[10px] font-semibold rounded-md bg-[rgba(249,115,22,0.15)] text-orange-400"> <span className="px-2 py-1 text-label-2 font-medium rounded-md bg-[rgba(249,115,22,0.15)] text-color-warning">
</span> </span>
); );
case 'pending': case 'pending':
return ( return (
<span className="px-2 py-1 text-[10px] font-semibold rounded-md bg-[rgba(138,150,168,0.15)] text-fg-disabled"> <span className="px-2 py-1 text-label-2 font-medium rounded-md bg-[rgba(138,150,168,0.15)] text-fg-disabled">
</span> </span>
); );
case 'error': case 'error':
return ( return (
<span className="px-2 py-1 text-[10px] font-semibold rounded-md bg-[rgba(239,68,68,0.15)] text-color-danger"> <span className="px-2 py-1 text-label-2 font-medium rounded-md bg-[rgba(239,68,68,0.15)] text-color-danger">
</span> </span>
); );
@ -106,7 +106,7 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
<button <button
key={page} key={page}
onClick={() => setCurrentPage(page as number)} onClick={() => setCurrentPage(page as number)}
className={`px-3 py-1 text-sm font-medium rounded transition-colors ${ className={`px-3 py-1 text-title-3 font-medium rounded transition-colors ${
currentPage === page ? 'bg-color-accent text-bg-0' : 'text-fg-sub hover:bg-bg-elevated' currentPage === page ? 'bg-color-accent text-bg-0' : 'text-fg-sub hover:bg-bg-elevated'
}`} }`}
> >
@ -121,8 +121,8 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
{/* 헤더 */} {/* 헤더 */}
<div className="flex items-center justify-between px-5 py-4 border-b border-stroke"> <div className="flex items-center justify-between px-5 py-4 border-b border-stroke">
<div> <div>
<h1 className="text-xl font-bold text-fg"> </h1> <h1 className="text-heading-3 font-bold text-fg"> </h1>
<p className="text-sm text-fg-disabled mt-1"> {analyses.length}</p> <p className="text-title-3 text-fg-disabled mt-1"> {analyses.length}</p>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="relative"> <div className="relative">
@ -131,12 +131,16 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
placeholder="검색..." placeholder="검색..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="w-64 px-4 py-2 text-sm bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none" className="w-64 px-4 py-2 text-title-3 bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none"
/> />
</div> </div>
<button <button
onClick={() => onTabChange('analysis')} onClick={() => onTabChange('analysis')}
className="px-4 py-2 text-sm font-semibold rounded-md bg-color-accent text-bg-0 hover:shadow-[0_0_16px_rgba(6,182,212,0.3)] transition-all" className="px-4 py-2 text-title-3 font-semibold rounded-sm cursor-pointer text-color-accent"
style={{
border: '1px solid rgba(6,182,212,.3)',
background: 'rgba(6,182,212,.08)',
}}
> >
+ +
</button> </button>
@ -146,64 +150,66 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
{/* 테이블 */} {/* 테이블 */}
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{loading ? ( {loading ? (
<div className="text-center py-20 text-fg-disabled text-sm"> ...</div> <div className="text-center py-20 text-fg-disabled text-title-3"> ...</div>
) : ( ) : (
<table className="w-full"> <table className="w-full">
<thead className="sticky top-0 bg-bg-surface border-b border-stroke z-10"> <thead className="sticky top-0 bg-bg-surface border-b border-stroke z-10">
<tr> <tr>
<th className="px-4 py-3 text-left text-xs font-bold text-fg-disabled uppercase tracking-wider"> <th className="px-4 py-3 text-left text-label-1 font-bold text-fg-disabled uppercase tracking-label">
</th> </th>
<th className="px-4 py-3 text-left text-xs font-bold text-fg-disabled uppercase tracking-wider"> <th className="px-4 py-3 text-left text-label-1 font-bold text-fg-disabled uppercase tracking-label">
</th> </th>
<th className="px-4 py-3 text-left text-xs font-bold text-fg-disabled uppercase tracking-wider"> <th className="px-4 py-3 text-left text-label-1 font-bold text-fg-disabled uppercase tracking-label">
</th> </th>
<th className="px-4 py-3 text-left text-xs font-bold text-fg-disabled uppercase tracking-wider"> <th className="px-4 py-3 text-left text-label-1 font-bold text-fg-disabled uppercase tracking-label">
</th> </th>
<th className="px-4 py-3 text-left text-xs font-bold text-fg-disabled uppercase tracking-wider"> <th className="px-4 py-3 text-left text-label-1 font-bold text-fg-disabled uppercase tracking-label">
</th> </th>
<th className="px-4 py-3 text-left text-xs font-bold text-fg-disabled uppercase tracking-wider"> <th className="px-4 py-3 text-left text-label-1 font-bold text-fg-disabled uppercase tracking-label">
</th> </th>
<th className="px-4 py-3 text-right text-xs font-bold text-fg-disabled uppercase tracking-wider"> <th className="px-4 py-3 text-right text-label-1 font-bold text-fg-disabled uppercase tracking-label">
</th> </th>
<th className="px-4 py-3 text-center text-xs font-bold text-fg-disabled uppercase tracking-wider"> <th className="px-4 py-3 text-center text-label-1 font-bold text-fg-disabled uppercase tracking-label">
KOSPS KOSPS
</th> </th>
<th className="px-4 py-3 text-center text-xs font-bold text-fg-disabled uppercase tracking-wider"> <th className="px-4 py-3 text-center text-label-1 font-bold text-fg-disabled uppercase tracking-label">
POSEIDON POSEIDON
</th> </th>
<th className="px-4 py-3 text-center text-xs font-bold text-fg-disabled uppercase tracking-wider"> <th className="px-4 py-3 text-center text-label-1 font-bold text-fg-disabled uppercase tracking-label">
OpenDrift OpenDrift
</th> </th>
<th className="px-4 py-3 text-center text-xs font-bold text-fg-disabled uppercase tracking-wider"> <th className="px-4 py-3 text-center text-label-1 font-bold text-fg-disabled uppercase tracking-label">
</th> </th>
<th className="px-4 py-3 text-left text-xs font-bold text-fg-disabled uppercase tracking-wider"> <th className="px-4 py-3 text-left text-label-1 font-bold text-fg-disabled uppercase tracking-label">
</th> </th>
<th className="px-4 py-3 text-left text-xs font-bold text-fg-disabled uppercase tracking-wider"> <th className="px-4 py-3 text-left text-label-1 font-bold text-fg-disabled uppercase tracking-label">
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-border"> <tbody className="divide-y divide-stroke">
{currentAnalyses.map((analysis) => ( {currentAnalyses.map((analysis) => (
<tr <tr
key={analysis.predRunSn ?? analysis.acdntSn} key={analysis.predRunSn ?? analysis.acdntSn}
className="hover:bg-bg-elevated transition-colors cursor-pointer group" className="hover:bg-bg-elevated transition-colors cursor-pointer group"
> >
<td className="px-4 py-3 text-sm text-fg-sub font-mono">{analysis.acdntSn}</td> <td className="px-4 py-3 text-title-3 text-fg-sub font-mono">
{analysis.acdntSn}
</td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-color-danger animate-pulse" /> <span className="w-2 h-2 rounded-full bg-color-danger animate-pulse" />
<span <span
className="text-sm font-semibold text-color-accent hover:underline transition-all cursor-pointer" className="text-title-3 font-medium text-color-accent hover:underline transition-all cursor-pointer"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
if (onSelectAnalysis) { if (onSelectAnalysis) {
@ -215,7 +221,7 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
</span> </span>
</div> </div>
</td> </td>
<td className="px-4 py-3 text-sm text-fg-sub font-mono"> <td className="px-4 py-3 text-title-3 text-fg-sub font-mono">
{analysis.occurredAt {analysis.occurredAt
? new Date(analysis.occurredAt).toLocaleString('ko-KR', { ? new Date(analysis.occurredAt).toLocaleString('ko-KR', {
month: '2-digit', month: '2-digit',
@ -225,7 +231,7 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
}) })
: '—'} : '—'}
</td> </td>
<td className="px-4 py-3 text-sm text-fg-sub font-mono"> <td className="px-4 py-3 text-title-3 text-fg-sub font-mono">
{analysis.runDtm {analysis.runDtm
? new Date(analysis.runDtm).toLocaleString('ko-KR', { ? new Date(analysis.runDtm).toLocaleString('ko-KR', {
month: '2-digit', month: '2-digit',
@ -235,9 +241,11 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
}) })
: '—'} : '—'}
</td> </td>
<td className="px-4 py-3 text-sm text-fg-sub font-mono">{analysis.duration}</td> <td className="px-4 py-3 text-title-3 text-fg-sub font-mono">
<td className="px-4 py-3 text-sm text-fg-sub">{analysis.oilType}</td> {analysis.duration}
<td className="px-4 py-3 text-sm text-fg font-mono text-right font-semibold"> </td>
<td className="px-4 py-3 text-title-3 text-fg-sub">{analysis.oilType}</td>
<td className="px-4 py-3 text-title-3 text-fg font-mono text-right font-medium">
{analysis.volume != null ? analysis.volume.toFixed(2) : '—'} {analysis.volume != null ? analysis.volume.toFixed(2) : '—'}
</td> </td>
<td className="px-4 py-3 text-center">{getStatusBadge(analysis.kospsStatus)}</td> <td className="px-4 py-3 text-center">{getStatusBadge(analysis.kospsStatus)}</td>
@ -250,8 +258,8 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
<td className="px-4 py-3 text-center"> <td className="px-4 py-3 text-center">
{getStatusBadge(analysis.backtrackStatus)} {getStatusBadge(analysis.backtrackStatus)}
</td> </td>
<td className="px-4 py-3 text-sm text-fg-sub">{analysis.analyst}</td> <td className="px-4 py-3 text-title-3 text-fg-sub">{analysis.analyst}</td>
<td className="px-4 py-3 text-sm text-fg-sub">{analysis.officeName}</td> <td className="px-4 py-3 text-title-3 text-fg-sub">{analysis.officeName}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
@ -259,7 +267,9 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
)} )}
{!loading && analyses.length === 0 && ( {!loading && analyses.length === 0 && (
<div className="text-center py-20 text-fg-disabled text-sm"> .</div> <div className="text-center py-20 text-fg-disabled text-title-3">
.
</div>
)} )}
</div> </div>
@ -268,14 +278,14 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
<button <button
onClick={() => setCurrentPage(1)} onClick={() => setCurrentPage(1)}
disabled={currentPage === 1} disabled={currentPage === 1}
className="px-3 py-1 text-sm font-medium text-fg-sub hover:bg-bg-elevated rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed" className="px-3 py-1 text-title-3 font-medium text-fg-sub hover:bg-bg-elevated rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
> >
</button> </button>
<button <button
onClick={() => setCurrentPage((prev) => Math.max(1, prev - 1))} onClick={() => setCurrentPage((prev) => Math.max(1, prev - 1))}
disabled={currentPage === 1} disabled={currentPage === 1}
className="px-3 py-1 text-sm font-medium text-fg-sub hover:bg-bg-elevated rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed" className="px-3 py-1 text-title-3 font-medium text-fg-sub hover:bg-bg-elevated rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
> >
</button> </button>
@ -285,14 +295,14 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
<button <button
onClick={() => setCurrentPage((prev) => Math.min(totalPages, prev + 1))} onClick={() => setCurrentPage((prev) => Math.min(totalPages, prev + 1))}
disabled={currentPage === totalPages} disabled={currentPage === totalPages}
className="px-3 py-1 text-sm font-medium text-fg-sub hover:bg-bg-elevated rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed" className="px-3 py-1 text-title-3 font-medium text-fg-sub hover:bg-bg-elevated rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
> >
</button> </button>
<button <button
onClick={() => setCurrentPage(totalPages)} onClick={() => setCurrentPage(totalPages)}
disabled={currentPage === totalPages} disabled={currentPage === totalPages}
className="px-3 py-1 text-sm font-medium text-fg-sub hover:bg-bg-elevated rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed" className="px-3 py-1 text-title-3 font-medium text-fg-sub hover:bg-bg-elevated rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
> >
</button> </button>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다. Load Diff

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다. Load Diff

파일 보기

@ -1291,7 +1291,7 @@ export function OilSpillView() {
<div <div
className="absolute bottom-0 left-0 right-0 h-[72px] flex items-center px-5 gap-4" className="absolute bottom-0 left-0 right-0 h-[72px] flex items-center px-5 gap-4"
style={{ style={{
background: 'rgba(15,21,36,0.95)', background: 'var(--bg-surface)',
backdropFilter: 'blur(16px)', backdropFilter: 'blur(16px)',
borderTop: '1px solid var(--stroke-default)', borderTop: '1px solid var(--stroke-default)',
zIndex: 1100, zIndex: 1100,

파일 보기

@ -185,15 +185,21 @@ function TopInfoBar({ activeType }: { activeType: AccidentType }) {
}, []); }, []);
return ( return (
<div className="h-9 bg-gradient-to-r from-bg-0 to-bg-2 border-b border-stroke flex items-center px-4 gap-3.5 flex-shrink-0"> <div className="h-9 bg-bg-base border-b border-stroke flex items-center px-4 gap-3.5 flex-shrink-0">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="text-sm"></span> <span className="text-title-3"></span>
<span className="text-[12px] font-bold text-fg font-korean"></span> <span className="text-label-1 font-bold text-fg font-korean"></span>
</div> </div>
<div className="px-3.5 py-0.5 bg-[rgba(239,68,68,0.15)] border border-[rgba(239,68,68,0.35)] rounded-xl text-[10px] font-bold text-color-danger font-korean"> <div
className="px-3.5 py-0.5 rounded-xl text-label-2 font-bold text-color-danger font-korean"
style={{
background: 'color-mix(in srgb, var(--color-danger) 15%, transparent)',
border: '1px solid color-mix(in srgb, var(--color-danger) 35%, transparent)',
}}
>
: {d.incident} : {d.incident}
</div> </div>
<div className="flex gap-3 text-[9px] font-mono text-fg-disabled ml-auto"> <div className="flex gap-3 text-caption font-mono text-fg-disabled ml-auto">
<span> <span>
: <b className="text-fg">{d.survivors}</b>/{d.total} : <b className="text-fg">{d.survivors}</b>/{d.total}
</span> </span>
@ -210,8 +216,8 @@ function TopInfoBar({ activeType }: { activeType: AccidentType }) {
: <b className="text-color-warning">{d.oilRate}</b> : <b className="text-color-warning">{d.oilRate}</b>
</span> </span>
</div> </div>
<div className="text-[13px] font-bold text-fg font-mono">{clock}</div> <div className="text-title-4 font-bold text-fg font-mono">{clock}</div>
<div className="text-[9px] text-fg-disabled font-korean">👤 Cmdr. KIM</div> <div className="text-caption text-fg-disabled font-korean">👤 Cmdr. KIM</div>
</div> </div>
); );
} }
@ -227,7 +233,7 @@ function LeftPanel({
return ( return (
<div className="w-[208px] min-w-[208px] bg-bg-base border-r border-stroke flex flex-col overflow-y-auto scrollbar-thin p-2 gap-0.5"> <div className="w-[208px] min-w-[208px] bg-bg-base border-r border-stroke flex flex-col overflow-y-auto scrollbar-thin p-2 gap-0.5">
{/* 사고유형 제목 */} {/* 사고유형 제목 */}
<div className="text-[9px] font-bold text-[var(--color-info)] font-korean mb-0.5 tracking-wider"> <div className="text-caption font-bold text-color-info font-korean mb-0.5 tracking-wider">
(INCIDENT TYPE) (INCIDENT TYPE)
</div> </div>
@ -242,32 +248,32 @@ function LeftPanel({
: 'bg-bg-card border-stroke hover:border-[var(--stroke-light)] hover:bg-bg-surface-hover' : 'bg-bg-card border-stroke hover:border-[var(--stroke-light)] hover:bg-bg-surface-hover'
}`} }`}
> >
<span className="text-[12px]">{t.icon}</span> <span className="text-label-1">{t.icon}</span>
<div> <div>
<div <div
className={`text-[10px] font-bold font-korean ${activeType === t.id ? 'text-[var(--color-accent)]' : 'text-fg'}`} className={`text-label-2 font-bold font-korean ${activeType === t.id ? 'text-color-accent' : 'text-fg'}`}
> >
{t.label} ({t.eng}) {t.label} ({t.eng})
</div> </div>
<div className="text-[7px] text-fg-disabled font-korean mt-px">{t.desc}</div> <div className="text-caption text-fg-disabled font-korean mt-px">{t.desc}</div>
</div> </div>
</button> </button>
))} ))}
{/* 긴급 경고 */} {/* 긴급 경고 */}
<div className="text-[9px] font-bold text-fg-disabled font-korean mt-2.5 mb-1"> <div className="text-caption font-bold text-fg-disabled font-korean mt-2.5 mb-1">
(CRITICAL ALERTS) (CRITICAL ALERTS)
</div> </div>
<div className="py-1.5 px-2.5 bg-[rgba(239,68,68,0.15)] border-l-[3px] border-l-[var(--color-danger)] rounded-r text-[9px] font-bold text-color-danger font-korean"> <div className="py-1.5 px-2.5 bg-[color-mix(in_srgb,var(--color-danger)_15%,transparent)] border-l-[3px] border-l-[var(--color-danger)] rounded-r text-caption font-bold text-color-danger font-korean">
GM GM
</div> </div>
<div className="py-1.5 px-2.5 bg-[rgba(239,68,68,0.15)] border-l-[3px] border-l-[var(--color-danger)] rounded-r text-[9px] font-bold text-color-danger font-korean"> <div className="py-1.5 px-2.5 bg-[color-mix(in_srgb,var(--color-danger)_15%,transparent)] border-l-[3px] border-l-[var(--color-danger)] rounded-r text-caption font-bold text-color-danger font-korean">
5 5
</div> </div>
<div className="py-1.5 px-2.5 bg-[rgba(249,115,22,0.12)] border-l-[3px] border-l-[var(--color-warning)] rounded-r text-[9px] font-bold text-color-warning font-korean"> <div className="py-1.5 px-2.5 bg-[color-mix(in_srgb,var(--color-warning)_12%,transparent)] border-l-[3px] border-l-[var(--color-warning)] rounded-r text-caption font-bold text-color-warning font-korean">
- -
</div> </div>
<div className="py-1.5 px-2.5 bg-[rgba(251,191,36,0.1)] border-l-[3px] border-l-[var(--color-caution)] rounded-r text-[9px] font-bold text-color-caution font-korean"> <div className="py-1.5 px-2.5 bg-[color-mix(in_srgb,var(--color-caution)_10%,transparent)] border-l-[3px] border-l-[var(--color-caution)] rounded-r text-caption font-bold text-color-caution font-korean">
88% 88%
</div> </div>
@ -279,8 +285,8 @@ function LeftPanel({
background: 'repeating-linear-gradient(0deg, rgba(255,0,0,.03), transparent 2px)', background: 'repeating-linear-gradient(0deg, rgba(255,0,0,.03), transparent 2px)',
}} }}
/> />
<div className="text-[9px] text-[rgba(255,60,60,0.35)] font-mono">CCTV FEED #1</div> <div className="text-caption text-[rgba(255,60,60,0.35)] font-mono">CCTV FEED #1</div>
<div className="absolute top-1 left-1.5 text-[7px] text-[rgba(255,60,60,0.5)] font-mono"> <div className="absolute top-1 left-1.5 text-caption text-[rgba(255,60,60,0.5)] font-mono">
REC REC
</div> </div>
</div> </div>
@ -324,8 +330,8 @@ function CenterMap({ activeType }: { activeType: AccidentType }) {
/> />
{/* 사고 해역 정보 */} {/* 사고 해역 정보 */}
<div className="absolute top-2.5 left-2.5 z-20 bg-[var(--dropdown-bg)] border border-stroke rounded-md px-3 py-2 backdrop-blur-sm font-mono text-[9px] text-fg-disabled"> <div className="absolute top-2.5 left-2.5 z-20 bg-[var(--dropdown-bg)] border border-stroke rounded-md px-3 py-2 backdrop-blur-sm font-mono text-caption text-fg-disabled">
<div className="text-[10px] font-bold text-fg font-korean mb-1"> </div> <div className="text-label-2 font-bold text-fg font-korean mb-1"> </div>
<div className="grid gap-x-1.5 gap-y-px" style={{ gridTemplateColumns: '32px 1fr' }}> <div className="grid gap-x-1.5 gap-y-px" style={{ gridTemplateColumns: '32px 1fr' }}>
<span></span> <span></span>
<b className="text-fg">37°28'N, 126°15'E</b> <b className="text-fg">37°28'N, 126°15'E</b>
@ -348,11 +354,11 @@ function CenterMap({ activeType }: { activeType: AccidentType }) {
}} }}
> >
<div <div
className="absolute w-0.5 h-[7px] bg-[#888] rounded-[1px]" className="absolute w-0.5 h-[7px] bg-fg-disabled rounded-[1px]"
style={{ top: '-3px', left: '60%' }} style={{ top: '-3px', left: '60%' }}
/> />
</div> </div>
<div className="text-[7px] text-center mt-0.5 font-mono text-[rgba(255,200,150,0.5)]"> <div className="text-caption text-center mt-0.5 font-mono text-[rgba(255,200,150,0.5)]">
M/V SEA GUARDIAN M/V SEA GUARDIAN
</div> </div>
</div> </div>
@ -371,28 +377,28 @@ function CenterMap({ activeType }: { activeType: AccidentType }) {
}} }}
/> />
{/* 구역 라벨 */} {/* 구역 라벨 */}
<div className="absolute z-[6] text-[8px] font-korean font-semibold tracking-wider uppercase whitespace-pre-line text-[rgba(6,182,212,0.45)] top-1/2 left-[36%]"> <div className="absolute z-[6] text-caption font-korean font-semibold tracking-wider uppercase whitespace-pre-line text-[rgba(6,182,212,0.45)] top-1/2 left-[36%]">
{d.zone.replace('\\n', '\n')} {d.zone.replace('\\n', '\n')}
</div> </div>
{/* SAR 자산 */} {/* SAR 자산 */}
<div className="absolute z-10 text-[7px] font-mono text-[rgba(200,220,255,0.35)] top-[10%] left-[42%]"> <div className="absolute z-10 text-caption font-mono text-[rgba(200,220,255,0.35)] top-[10%] left-[42%]">
ETA 5 MIN ETA 5 MIN
</div> </div>
<div className="absolute z-10 text-[7px] font-mono text-[rgba(200,220,255,0.35)] top-[14%] left-[56%]"> <div className="absolute z-10 text-caption font-mono text-[rgba(200,220,255,0.35)] top-[14%] left-[56%]">
ETA 15 MIN ETA 15 MIN
</div> </div>
<div className="absolute z-[12] text-sm opacity-60 top-[7%] left-[52%] -rotate-[30deg]"> <div className="absolute z-[12] text-title-3 opacity-60 top-[7%] left-[52%] -rotate-[30deg]">
🚁 🚁
</div> </div>
<div className="absolute z-[12] text-[8px] font-mono text-[rgba(200,220,255,0.45)] top-[20%] left-[60%]"> <div className="absolute z-[12] text-caption font-mono text-[rgba(200,220,255,0.45)] top-[20%] left-[60%]">
6M 6M
</div> </div>
<div className="absolute z-[12] text-[11px] opacity-45 top-[28%] left-[54%]">🚢</div> <div className="absolute z-[12] text-label-2 opacity-45 top-[28%] left-[54%]">🚢</div>
{/* 환경 민감 구역 */} {/* 환경 민감 구역 */}
<div className="absolute z-10 px-3.5 py-2 rounded bg-[rgba(34,197,94,0.06)] border border-[rgba(34,197,94,0.2)] bottom-[6%] right-[6%]"> <div className="absolute z-10 px-3.5 py-2 rounded bg-[rgba(34,197,94,0.06)] border border-[rgba(34,197,94,0.2)] bottom-[6%] right-[6%]">
<div className="text-[9px] font-bold font-serif uppercase tracking-wider leading-snug text-[rgba(34,197,94,0.55)]"> <div className="text-caption font-bold font-serif uppercase tracking-wider leading-snug text-[rgba(34,197,94,0.55)]">
ENVIRONMENTALLY SENSITIVE ENVIRONMENTALLY SENSITIVE
<br /> <br />
AREA: AQUACULTURE FARM AREA: AQUACULTURE FARM
@ -404,7 +410,7 @@ function CenterMap({ activeType }: { activeType: AccidentType }) {
{['🗺', '🔍', '📐'].map((ico, i) => ( {['🗺', '🔍', '📐'].map((ico, i) => (
<button <button
key={i} key={i}
className="w-7 h-7 bg-[rgba(13,17,23,0.85)] border border-stroke rounded text-fg-disabled text-[12px] flex items-center justify-center cursor-pointer hover:text-fg" className="w-7 h-7 bg-[rgba(13,17,23,0.85)] border border-stroke rounded text-fg-disabled text-label-1 flex items-center justify-center cursor-pointer hover:text-fg"
> >
{ico} {ico}
</button> </button>
@ -412,7 +418,7 @@ function CenterMap({ activeType }: { activeType: AccidentType }) {
</div> </div>
{/* 스케일 바 */} {/* 스케일 바 */}
<div className="absolute bottom-2.5 left-2.5 z-20 bg-[rgba(13,17,23,0.8)] border border-stroke rounded px-2.5 py-1 text-[8px] text-fg-disabled font-mono"> <div className="absolute bottom-2.5 left-2.5 z-20 bg-[rgba(13,17,23,0.8)] border border-stroke rounded px-2.5 py-1 text-caption text-fg-disabled font-mono">
<div <div
className="w-[70px] h-0.5 mb-0.5" className="w-[70px] h-0.5 mb-0.5"
style={{ background: 'linear-gradient(90deg, #e4e8f1 50%, var(--stroke-default) 50%)' }} style={{ background: 'linear-gradient(90deg, #e4e8f1 50%, var(--stroke-default) 50%)' }}
@ -422,11 +428,55 @@ function CenterMap({ activeType }: { activeType: AccidentType }) {
{/* 사고 유형 표시 */} {/* 사고 유형 표시 */}
<div className="absolute bottom-2.5 right-2.5 z-20 bg-[rgba(13,17,23,0.85)] border border-stroke rounded px-3 py-1.5"> <div className="absolute bottom-2.5 right-2.5 z-20 bg-[rgba(13,17,23,0.85)] border border-stroke rounded px-3 py-1.5">
<div className="text-[8px] text-fg-disabled font-korean"> </div> <div className="text-caption text-fg-disabled font-korean"> </div>
<div className="text-[11px] font-bold font-korean text-color-accent"> <div className="text-label-2 font-bold font-korean text-color-accent">
{at.icon} {at.label} ({at.eng}) {at.icon} {at.label} ({at.eng})
</div> </div>
</div> </div>
{/* 타임라인 시뮬레이션 컨트롤 */}
<div className="absolute bottom-2.5 left-1/2 -translate-x-1/2 z-20 bg-[rgba(13,17,23,0.9)] border border-stroke rounded-md px-4 py-2 flex items-center gap-4 backdrop-blur-sm">
<div className="text-caption font-bold text-fg font-korean whitespace-nowrap">TIMELINE</div>
<div className="flex items-center gap-1.5 text-caption text-fg-disabled font-mono">
<span>[-6h]</span>
<span className="font-bold text-fg">[NOW]</span>
<span>[+6H]</span>
<span>[+12H]</span>
<span>[+24H]</span>
</div>
<div className="relative w-24 h-1.5 bg-bg-surface-hover rounded-sm">
<div
className="absolute rounded-full border-2 border-bg-0 bg-color-accent"
style={{
left: '35%',
top: '-3px',
width: '12px',
height: '12px',
boxShadow: '0 0 8px rgba(6,182,212,.4)',
}}
/>
</div>
<div className="flex items-center gap-1.5">
<button className="w-6 h-6 bg-bg-card border border-stroke rounded-full text-fg-disabled text-label-2 flex items-center justify-center cursor-pointer hover:text-fg">
</button>
<button
className="w-8 h-8 rounded-full text-color-accent text-title-4 flex items-center justify-center cursor-pointer hover:brightness-125"
style={{
background: 'color-mix(in srgb, var(--color-accent) 15%, transparent)',
border: '1px solid color-mix(in srgb, var(--color-accent) 30%, transparent)',
}}
>
</button>
<button className="w-6 h-6 bg-bg-card border border-stroke rounded-full text-fg-disabled text-label-2 flex items-center justify-center cursor-pointer hover:text-fg">
</button>
</div>
<div className="text-caption text-fg-disabled font-mono whitespace-nowrap">
<b className="text-color-accent">10:45</b> KST
</div>
</div>
</div> </div>
); );
} }
@ -449,9 +499,9 @@ function RightPanel({
<button <button
key={tab.id} key={tab.id}
onClick={() => onAnalysisChange(tab.id)} onClick={() => onAnalysisChange(tab.id)}
className={`flex-1 py-2 text-[9px] font-semibold font-korean cursor-pointer text-center transition-all border-b-2 ${ className={`flex-1 py-2 text-caption font-semibold font-korean cursor-pointer text-center transition-all border-b-2 ${
activeAnalysis === tab.id activeAnalysis === tab.id
? 'text-[var(--color-accent)] border-b-[var(--color-accent)] bg-[rgba(6,182,212,0.04)]' ? 'text-color-accent border-b-[var(--color-accent)] bg-[rgba(6,182,212,0.04)]'
: 'text-fg-disabled border-b-transparent hover:text-fg' : 'text-fg-disabled border-b-transparent hover:text-fg'
}`} }`}
> >
@ -469,13 +519,19 @@ function RightPanel({
{/* Bottom Action Buttons */} {/* Bottom Action Buttons */}
<div className="flex gap-1.5 p-3 border-t border-stroke flex-shrink-0"> <div className="flex gap-1.5 p-3 border-t border-stroke flex-shrink-0">
<button className="flex-1 py-2 px-1 rounded text-[11px] font-semibold bg-gradient-to-r from-boom to-[#d97706] text-black font-korean"> <button
className="flex-1 py-2 px-1 rounded text-label-2 font-semibold text-color-accent font-korean cursor-pointer"
style={{ border: '1px solid rgba(6,182,212,.3)', background: 'rgba(6,182,212,.08)' }}
>
💾 💾
</button> </button>
<button className="flex-1 py-2 px-1 rounded text-[11px] font-semibold bg-[rgba(249,115,22,0.1)] border border-[rgba(249,115,22,0.3)] text-color-warning font-korean"> <button className="flex-1 py-2 px-1 rounded text-label-2 font-semibold bg-bg-elevated border border-stroke text-fg font-korean cursor-pointer">
🔄 🔄
</button> </button>
<button className="flex-1 py-2 px-1 rounded text-[11px] font-semibold bg-gradient-to-r from-primary-cyan to-primary-blue text-white font-korean"> <button
className="flex-1 py-2 px-1 rounded text-label-2 font-semibold text-color-accent font-korean cursor-pointer"
style={{ border: '1px solid rgba(6,182,212,.3)', background: 'rgba(6,182,212,.08)' }}
>
📄 📄
</button> </button>
</div> </div>
@ -490,14 +546,20 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
return ( return (
<div className="flex flex-col p-2.5 gap-2"> <div className="flex flex-col p-2.5 gap-2">
<div className="text-[12px] font-bold text-fg font-korean"> (RESCUE ANALYSIS)</div> <div className="text-label-1 font-bold text-fg font-korean"> (RESCUE ANALYSIS)</div>
<div className="text-[9px] text-[var(--color-info)] font-korean px-2 py-1 bg-[rgba(88,166,255,0.08)] border border-[rgba(88,166,255,0.2)] rounded"> <div
className="text-caption text-color-info font-korean px-2 py-1 rounded"
style={{
background: 'color-mix(in srgb, var(--color-info) 8%, transparent)',
border: '1px solid color-mix(in srgb, var(--color-info) 20%, transparent)',
}}
>
📌 : {at.label} ({at.eng}) 📌 : {at.label} ({at.eng})
</div> </div>
{/* 선박 단면도 SVG */} {/* 선박 단면도 SVG */}
<div className="bg-bg-card border border-stroke rounded-md p-2 relative"> <div className="bg-bg-card border border-stroke rounded-md p-2 relative">
<div className="absolute top-1.5 right-2 text-[7px] text-fg-disabled font-mono"> <div className="absolute top-1.5 right-2 text-caption text-fg-disabled font-mono">
VESSEL STATUS VESSEL STATUS
</div> </div>
<svg viewBox="0 0 260 80" className="w-full" style={{ height: '65px' }}> <svg viewBox="0 0 260 80" className="w-full" style={{ height: '65px' }}>
@ -537,7 +599,7 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
fill="rgba(239,68,68,.5)" fill="rgba(239,68,68,.5)"
fontSize="4.5" fontSize="4.5"
textAnchor="middle" textAnchor="middle"
fontFamily="monospace" fontFamily="var(--font-mono)"
> >
FLOODED FLOODED
</text> </text>
@ -556,7 +618,7 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
fill="var(--color-danger)" fill="var(--color-danger)"
fontSize="4" fontSize="4"
textAnchor="middle" textAnchor="middle"
fontFamily="monospace" fontFamily="var(--font-mono)"
> >
IMPACT IMPACT
</text> </text>
@ -569,7 +631,13 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
strokeWidth=".4" strokeWidth=".4"
strokeDasharray="3,2" strokeDasharray="3,2"
/> />
<text x="205" y="55" fill="var(--fg-disabled)" fontSize="4.5" fontFamily="monospace"> <text
x="205"
y="55"
fill="var(--fg-disabled)"
fontSize="4.5"
fontFamily="var(--font-mono)"
>
WL WL
</text> </text>
<line <line
@ -619,12 +687,12 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
subColor={trimColor(d.trim)} subColor={trimColor(d.trim)}
/> />
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[8px] text-fg-disabled font-korean"> <div className="text-caption text-fg-disabled font-korean">
(Reserve Buoyancy) (Reserve Buoyancy)
</div> </div>
<div className="text-xl font-bold font-mono" style={{ color: buoyColor(d.buoy) }}> <div className="text-title-2 font-bold font-mono" style={{ color: buoyColor(d.buoy) }}>
{d.buoy} {d.buoy}
<span className="text-[10px]">%</span> <span className="text-label-2">%</span>
</div> </div>
<div className="h-[5px] bg-bg-surface-hover rounded-sm mt-0.5"> <div className="h-[5px] bg-bg-surface-hover rounded-sm mt-0.5">
<div <div
@ -636,7 +704,7 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
</div> </div>
{/* 긴급 조치 버튼 */} {/* 긴급 조치 버튼 */}
<div className="text-[10px] font-bold text-fg-disabled font-korean"> <div className="text-label-2 font-bold text-fg-disabled font-korean">
(EMERGENCY ACTIONS) (EMERGENCY ACTIONS)
</div> </div>
<div className="grid grid-cols-2 gap-1.5"> <div className="grid grid-cols-2 gap-1.5">
@ -654,8 +722,8 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
border: `1px solid color-mix(in srgb, ${btn.color} 25%, transparent)`, border: `1px solid color-mix(in srgb, ${btn.color} 25%, transparent)`,
}} }}
> >
<div className="text-[10px] font-bold text-fg font-mono">{btn.en}</div> <div className="text-label-2 font-bold text-fg font-mono">{btn.en}</div>
<div className="text-[8px] font-korean" style={{ color: btn.color }}> <div className="text-caption font-korean" style={{ color: btn.color }}>
{btn.ko} {btn.ko}
</div> </div>
</button> </button>
@ -664,10 +732,10 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
{/* 구난 의사결정 프로세스 */} {/* 구난 의사결정 프로세스 */}
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[9px] font-bold text-fg font-korean mb-1.5"> <div className="text-caption font-bold text-fg font-korean mb-1.5">
(KRISO Decision Support) (KRISO Decision Support)
</div> </div>
<div className="flex flex-col gap-0.5 text-[7px] font-korean"> <div className="flex flex-col gap-0.5 text-caption font-korean">
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{[ {[
{ label: '① 상태평가', color: 'var(--color-accent)' }, { label: '① 상태평가', color: 'var(--color-accent)' },
@ -719,10 +787,10 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
{/* 유체 정역학 */} {/* 유체 정역학 */}
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[9px] font-bold text-fg font-korean mb-1.5"> <div className="text-caption font-bold text-fg font-korean mb-1.5">
(Hydrostatics) (Hydrostatics)
</div> </div>
<div className="grid grid-cols-2 gap-1 text-[8px] font-mono"> <div className="grid grid-cols-2 gap-1 text-caption font-mono">
{[ {[
{ label: '배수량(Δ)', value: '12,450 ton' }, { label: '배수량(Δ)', value: '12,450 ton' },
{ label: '흘수(Draft)', value: '7.2 m' }, { label: '흘수(Draft)', value: '7.2 m' },
@ -732,7 +800,7 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
{ label: 'MTC', value: '185 t·m' }, { label: 'MTC', value: '185 t·m' },
].map((r, i) => ( ].map((r, i) => (
<div key={i} className="px-1.5 py-1 bg-bg-base rounded-sm"> <div key={i} className="px-1.5 py-1 bg-bg-base rounded-sm">
<span className="text-fg-disabled font-korean text-[7px]">{r.label}</span> <span className="text-fg-disabled font-korean text-caption">{r.label}</span>
<br /> <br />
<b className="text-fg">{r.value}</b> <b className="text-fg">{r.value}</b>
</div> </div>
@ -742,10 +810,10 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
{/* 예인력/이초력 */} {/* 예인력/이초력 */}
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[9px] font-bold text-fg font-korean mb-1.5"> <div className="text-caption font-bold text-fg font-korean mb-1.5">
/ (Towing & Refloating) / (Towing & Refloating)
</div> </div>
<div className="grid grid-cols-2 gap-1 text-[8px] font-mono"> <div className="grid grid-cols-2 gap-1 text-caption font-mono">
{[ {[
{ label: '필요 예인력', value: '285 kN', color: 'var(--color-caution)' }, { label: '필요 예인력', value: '285 kN', color: 'var(--color-caution)' },
{ label: '비상 예인력', value: '420 kN', color: 'var(--color-danger)' }, { label: '비상 예인력', value: '420 kN', color: 'var(--color-danger)' },
@ -753,30 +821,30 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
{ label: '인양 안전성', value: 'FAIL', color: 'var(--color-danger)' }, { label: '인양 안전성', value: 'FAIL', color: 'var(--color-danger)' },
].map((r, i) => ( ].map((r, i) => (
<div key={i} className="px-1.5 py-1 bg-bg-base rounded-sm"> <div key={i} className="px-1.5 py-1 bg-bg-base rounded-sm">
<span className="text-fg-disabled font-korean text-[7px]">{r.label}</span> <span className="text-fg-disabled font-korean text-caption">{r.label}</span>
<br /> <br />
<b style={{ color: r.color }}>{r.value}</b> <b style={{ color: r.color }}>{r.value}</b>
</div> </div>
))} ))}
</div> </div>
<div className="text-[7px] text-fg-disabled font-korean mt-1"> <div className="text-caption text-fg-disabled font-korean mt-1">
IMO Salvage Manual / Resistance Increase Ratio IMO Salvage Manual / Resistance Increase Ratio
</div> </div>
</div> </div>
{/* 유출량 추정 */} {/* 유출량 추정 */}
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[9px] font-bold text-fg font-korean mb-1.5"> <div className="text-caption font-bold text-fg font-korean mb-1.5">
(Oil Outflow Estimation) (Oil Outflow Estimation)
</div> </div>
<div className="grid grid-cols-3 gap-1 text-[8px] font-mono"> <div className="grid grid-cols-3 gap-1 text-caption font-mono">
{[ {[
{ label: '현재 유출률', value: d.oilRate, color: 'var(--color-warning)' }, { label: '현재 유출률', value: d.oilRate, color: 'var(--color-warning)' },
{ label: '누적 유출량', value: '6.8 kL', color: 'var(--color-danger)' }, { label: '누적 유출량', value: '6.8 kL', color: 'var(--color-danger)' },
{ label: '24h 예측', value: '145 kL', color: 'var(--color-danger)' }, { label: '24h 예측', value: '145 kL', color: 'var(--color-danger)' },
].map((r, i) => ( ].map((r, i) => (
<div key={i} className="px-1.5 py-1 bg-bg-base rounded-sm text-center"> <div key={i} className="px-1.5 py-1 bg-bg-base rounded-sm text-center">
<span className="text-fg-disabled font-korean text-[7px]">{r.label}</span> <span className="text-fg-disabled font-korean text-caption">{r.label}</span>
<br /> <br />
<b style={{ color: r.color }}>{r.value}</b> <b style={{ color: r.color }}>{r.value}</b>
</div> </div>
@ -791,14 +859,14 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
}} }}
/> />
</div> </div>
<div className="text-[7px] text-fg-disabled font-korean mt-0.5"> <div className="text-caption text-fg-disabled font-korean mt-0.5">
연료유: 210 kL | 잔량: 68% 연료유: 210 kL | 잔량: 68%
</div> </div>
</div> </div>
{/* CBR 사례기반 추론 */} {/* CBR 사례기반 추론 */}
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[9px] font-bold text-fg font-korean mb-1.5"> <div className="text-caption font-bold text-fg font-korean mb-1.5">
CBR (Case-Based Reasoning) CBR (Case-Based Reasoning)
</div> </div>
<div className="flex flex-col gap-0.5"> <div className="flex flex-col gap-0.5">
@ -831,7 +899,7 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
}} }}
> >
<div <div
className="w-7 h-4 rounded-sm flex items-center justify-center text-[7px] font-bold font-mono" className="w-7 h-4 rounded-sm flex items-center justify-center text-caption font-bold font-mono"
style={{ style={{
background: `color-mix(in srgb, ${c.color} 20%, transparent)`, background: `color-mix(in srgb, ${c.color} 20%, transparent)`,
color: c.color, color: c.color,
@ -839,7 +907,7 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
> >
{c.pct} {c.pct}
</div> </div>
<div className="flex-1 text-[7px] font-korean"> <div className="flex-1 text-caption font-korean">
<b className="text-fg">{c.name}</b> <b className="text-fg">{c.name}</b>
<br /> <br />
<span className="text-fg-disabled">{c.desc}</span> <span className="text-fg-disabled">{c.desc}</span>
@ -850,11 +918,17 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
</div> </div>
{/* 위험도 평가 */} {/* 위험도 평가 */}
<div className="bg-[rgba(239,68,68,0.04)] border border-[rgba(239,68,68,0.15)] rounded-md p-2"> <div
<div className="text-[9px] font-bold text-color-danger font-korean mb-1.5"> className="rounded-md p-2"
style={{
background: 'color-mix(in srgb, var(--color-danger) 4%, transparent)',
border: '1px solid color-mix(in srgb, var(--color-danger) 15%, transparent)',
}}
>
<div className="text-caption font-bold text-color-danger font-korean mb-1.5">
2 2
</div> </div>
<div className="flex flex-col gap-0.5 text-[7px] font-korean"> <div className="flex flex-col gap-0.5 text-caption font-korean">
{[ {[
{ label: '침수 확대 → 전복', level: 'HIGH', color: 'var(--color-danger)' }, { label: '침수 확대 → 전복', level: 'HIGH', color: 'var(--color-danger)' },
{ label: '유류 대량 유출 → 해양오염', level: 'HIGH', color: 'var(--color-warning)' }, { label: '유류 대량 유출 → 해양오염', level: 'HIGH', color: 'var(--color-warning)' },
@ -870,7 +944,7 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
> >
<span className="text-fg">{r.label}</span> <span className="text-fg">{r.label}</span>
<span <span
className="px-1.5 py-px rounded-lg text-[7px] font-bold" className="px-1.5 py-px rounded-lg text-caption font-bold"
style={{ style={{
background: `color-mix(in srgb, ${r.color} 20%, transparent)`, background: `color-mix(in srgb, ${r.color} 20%, transparent)`,
color: r.color, color: r.color,
@ -885,10 +959,10 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
{/* 해상 e-Call */} {/* 해상 e-Call */}
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[9px] font-bold text-fg font-korean mb-1.5"> <div className="text-caption font-bold text-fg font-korean mb-1.5">
e-Call (GMDSS / VHF-DSC) e-Call (GMDSS / VHF-DSC)
</div> </div>
<div className="flex flex-col gap-0.5 text-[7px] font-mono text-fg-disabled"> <div className="flex flex-col gap-0.5 text-caption font-mono text-fg-disabled">
{[ {[
{ label: 'MMSI', value: '440123456' }, { label: 'MMSI', value: '440123456' },
{ label: 'Nature of Distress', value: 'COLLISION', color: 'var(--color-danger)' }, { label: 'Nature of Distress', value: 'COLLISION', color: 'var(--color-danger)' },
@ -902,7 +976,13 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
</div> </div>
))} ))}
</div> </div>
<button className="w-full mt-1 py-1 bg-[rgba(239,68,68,0.12)] border border-[rgba(239,68,68,0.3)] rounded text-[8px] font-bold text-color-danger cursor-pointer font-korean hover:bg-[rgba(239,68,68,0.2)]"> <button
className="w-full mt-1 py-1 rounded text-caption font-bold text-color-danger cursor-pointer font-korean"
style={{
background: 'color-mix(in srgb, var(--color-danger) 12%, transparent)',
border: '1px solid color-mix(in srgb, var(--color-danger) 30%, transparent)',
}}
>
📡 DISTRESS RELAY 📡 DISTRESS RELAY
</button> </button>
</div> </div>
@ -915,10 +995,10 @@ function RescuePanel({ activeType }: { activeType: AccidentType }) {
function DamageStabilityPanel(_props: { activeType: AccidentType }) { function DamageStabilityPanel(_props: { activeType: AccidentType }) {
return ( return (
<div className="flex flex-col p-2.5 gap-2"> <div className="flex flex-col p-2.5 gap-2">
<div className="text-[12px] font-bold text-fg font-korean"> <div className="text-label-1 font-bold text-fg font-korean">
(DAMAGE STABILITY) (DAMAGE STABILITY)
</div> </div>
<div className="text-[9px] text-fg-disabled font-korean leading-snug"> <div className="text-caption text-fg-disabled font-korean leading-snug">
<br /> <br />
IMO A.749(18) / SOLAS Ch.II-1 IMO A.749(18) / SOLAS Ch.II-1
@ -926,7 +1006,7 @@ function DamageStabilityPanel(_props: { activeType: AccidentType }) {
{/* GZ Curve SVG */} {/* GZ Curve SVG */}
<div className="bg-bg-card border border-stroke rounded-md p-2.5"> <div className="bg-bg-card border border-stroke rounded-md p-2.5">
<div className="text-[9px] font-bold text-fg font-korean mb-1.5"> <div className="text-caption font-bold text-fg font-korean mb-1.5">
GZ (Righting Lever Curve) GZ (Righting Lever Curve)
</div> </div>
<svg viewBox="0 0 260 120" className="w-full" style={{ height: '100px' }}> <svg viewBox="0 0 260 120" className="w-full" style={{ height: '100px' }}>
@ -984,37 +1064,79 @@ function DamageStabilityPanel(_props: { activeType: AccidentType }) {
strokeWidth=".6" strokeWidth=".6"
strokeDasharray="4,2" strokeDasharray="4,2"
/> />
<text x="240" y="76" fill="var(--color-caution)" fontSize="4.5" fontFamily="monospace"> <text
x="240"
y="76"
fill="var(--color-caution)"
fontSize="4.5"
fontFamily="var(--font-mono)"
>
IMO MIN IMO MIN
</text> </text>
<text x="5" y="14" fill="var(--fg-disabled)" fontSize="5" fontFamily="monospace"> <text x="5" y="14" fill="var(--fg-disabled)" fontSize="5" fontFamily="var(--font-mono)">
GZ(m) GZ(m)
</text> </text>
<text x="5" y="35" fill="var(--fg-disabled)" fontSize="4.5" fontFamily="monospace"> <text x="5" y="35" fill="var(--fg-disabled)" fontSize="4.5" fontFamily="var(--font-mono)">
0.6 0.6
</text> </text>
<text x="5" y="58" fill="var(--fg-disabled)" fontSize="4.5" fontFamily="monospace"> <text x="5" y="58" fill="var(--fg-disabled)" fontSize="4.5" fontFamily="var(--font-mono)">
0.4 0.4
</text> </text>
<text x="5" y="82" fill="var(--fg-disabled)" fontSize="4.5" fontFamily="monospace"> <text x="5" y="82" fill="var(--fg-disabled)" fontSize="4.5" fontFamily="var(--font-mono)">
0.2 0.2
</text> </text>
<text x="5" y="104" fill="var(--fg-disabled)" fontSize="4.5" fontFamily="monospace"> <text
x="5"
y="104"
fill="var(--fg-disabled)"
fontSize="4.5"
fontFamily="var(--font-mono)"
>
0 0
</text> </text>
<text x="30" y="112" fill="var(--fg-disabled)" fontSize="4.5" fontFamily="monospace"> <text
x="30"
y="112"
fill="var(--fg-disabled)"
fontSize="4.5"
fontFamily="var(--font-mono)"
>
0° 0°
</text> </text>
<text x="80" y="112" fill="var(--fg-disabled)" fontSize="4.5" fontFamily="monospace"> <text
x="80"
y="112"
fill="var(--fg-disabled)"
fontSize="4.5"
fontFamily="var(--font-mono)"
>
15° 15°
</text> </text>
<text x="130" y="112" fill="var(--fg-disabled)" fontSize="4.5" fontFamily="monospace"> <text
x="130"
y="112"
fill="var(--fg-disabled)"
fontSize="4.5"
fontFamily="var(--font-mono)"
>
30° 30°
</text> </text>
<text x="180" y="112" fill="var(--fg-disabled)" fontSize="4.5" fontFamily="monospace"> <text
x="180"
y="112"
fill="var(--fg-disabled)"
fontSize="4.5"
fontFamily="var(--font-mono)"
>
45° 45°
</text> </text>
<text x="230" y="112" fill="var(--fg-disabled)" fontSize="4.5" fontFamily="monospace"> <text
x="230"
y="112"
fill="var(--fg-disabled)"
fontSize="4.5"
fontFamily="var(--font-mono)"
>
60° 60°
</text> </text>
</svg> </svg>
@ -1041,27 +1163,33 @@ function DamageStabilityPanel(_props: { activeType: AccidentType }) {
</div> </div>
<div className="grid grid-cols-2 gap-1.5"> <div className="grid grid-cols-2 gap-1.5">
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[8px] text-fg-disabled font-korean"> </div> <div className="text-caption text-fg-disabled font-korean"> </div>
<div className="text-lg font-bold text-color-danger font-mono"> <div className="text-title-3 font-bold text-color-danger font-mono">
2 <span className="text-[9px]"></span> 2 <span className="text-caption"></span>
</div> </div>
<div className="text-[7px] text-fg-disabled font-korean">#1 / #3 </div> <div className="text-caption text-fg-disabled font-korean">#1 / #3 </div>
</div> </div>
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[8px] text-fg-disabled font-korean">Margin Line </div> <div className="text-caption text-fg-disabled font-korean">Margin Line </div>
<div className="text-lg font-bold text-color-danger font-mono"> <div className="text-title-3 font-bold text-color-danger font-mono">
0.12 <span className="text-[9px]">m</span> 0.12 <span className="text-caption">m</span>
</div> </div>
<div className="text-[7px] text-color-danger font-korean"> </div> <div className="text-caption text-color-danger font-korean"> </div>
</div> </div>
</div> </div>
{/* SOLAS 판정 */} {/* SOLAS 판정 */}
<div className="bg-[rgba(239,68,68,0.06)] border border-[rgba(239,68,68,0.2)] rounded-md p-2.5"> <div
<div className="text-[10px] font-bold text-color-danger font-korean mb-1"> className="rounded-md p-2.5"
style={{
background: 'color-mix(in srgb, var(--color-danger) 6%, transparent)',
border: '1px solid color-mix(in srgb, var(--color-danger) 20%, transparent)',
}}
>
<div className="text-label-2 font-bold text-color-danger font-korean mb-1">
SOLAS 판정: 부적합 (FAIL) SOLAS 판정: 부적합 (FAIL)
</div> </div>
<div className="text-[8px] text-fg-disabled font-korean leading-snug"> <div className="text-caption text-fg-disabled font-korean leading-snug">
· Area(0~θ_f): 0.028 m·rad ( 0.015 ) · Area(0~θ_f): 0.028 m·rad ( 0.015 )
<br /> <br />
· GZ_max 0.1m: 0.25m | θ_max 15°: 28° <br />·{' '} · GZ_max 0.1m: 0.25m | θ_max 15°: 28° <br />·{' '}
@ -1073,10 +1201,10 @@ function DamageStabilityPanel(_props: { activeType: AccidentType }) {
{/* 좌초시 복원성 */} {/* 좌초시 복원성 */}
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[9px] font-bold text-fg font-korean mb-1"> <div className="text-caption font-bold text-fg font-korean mb-1">
(Grounded Stability) (Grounded Stability)
</div> </div>
<div className="grid grid-cols-2 gap-1 text-[8px] font-mono"> <div className="grid grid-cols-2 gap-1 text-caption font-mono">
{[ {[
{ label: '지반반력', value: '1,240 kN', color: 'var(--color-caution)' }, { label: '지반반력', value: '1,240 kN', color: 'var(--color-caution)' },
{ label: '접촉 면적', value: '12.5 m²' }, { label: '접촉 면적', value: '12.5 m²' },
@ -1084,7 +1212,7 @@ function DamageStabilityPanel(_props: { activeType: AccidentType }) {
{ label: '좌초 GM', value: '0.65 m', color: 'var(--color-caution)' }, { label: '좌초 GM', value: '0.65 m', color: 'var(--color-caution)' },
].map((r, i) => ( ].map((r, i) => (
<div key={i} className="px-1.5 py-1 bg-bg-base rounded-sm"> <div key={i} className="px-1.5 py-1 bg-bg-base rounded-sm">
<span className="text-fg-disabled font-korean text-[7px]">{r.label}</span> <span className="text-fg-disabled font-korean text-caption">{r.label}</span>
<br /> <br />
<b style={{ color: r.color }}>{r.value}</b> <b style={{ color: r.color }}>{r.value}</b>
</div> </div>
@ -1094,10 +1222,10 @@ function DamageStabilityPanel(_props: { activeType: AccidentType }) {
{/* 탱크 상태 */} {/* 탱크 상태 */}
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[9px] font-bold text-fg font-korean mb-1"> <div className="text-caption font-bold text-fg font-korean mb-1">
(Tank Volume Status) (Tank Volume Status)
</div> </div>
<div className="flex flex-col gap-0.5 text-[7px] font-korean"> <div className="flex flex-col gap-0.5 text-caption font-korean">
{[ {[
{ name: '#1 FP Tank', pct: 100, status: '침수', color: 'var(--color-danger)' }, { name: '#1 FP Tank', pct: 100, status: '침수', color: 'var(--color-danger)' },
{ name: '#3 Port Tank', pct: 85, status: '85%', color: 'var(--color-danger)' }, { name: '#3 Port Tank', pct: 85, status: '85%', color: 'var(--color-danger)' },
@ -1130,10 +1258,10 @@ function DamageStabilityPanel(_props: { activeType: AccidentType }) {
function LongStrengthPanel(_props: { activeType: AccidentType }) { function LongStrengthPanel(_props: { activeType: AccidentType }) {
return ( return (
<div className="flex flex-col p-2.5 gap-2"> <div className="flex flex-col p-2.5 gap-2">
<div className="text-[12px] font-bold text-fg font-korean"> <div className="text-label-1 font-bold text-fg font-korean">
(LONGITUDINAL STRENGTH) (LONGITUDINAL STRENGTH)
</div> </div>
<div className="text-[9px] text-fg-disabled font-korean leading-snug"> <div className="text-caption text-fg-disabled font-korean leading-snug">
<br /> <br />
IACS CSR / Classification Society IACS CSR / Classification Society
@ -1141,7 +1269,7 @@ function LongStrengthPanel(_props: { activeType: AccidentType }) {
{/* 전단력 분포 SVG */} {/* 전단력 분포 SVG */}
<div className="bg-bg-card border border-stroke rounded-md p-2.5"> <div className="bg-bg-card border border-stroke rounded-md p-2.5">
<div className="text-[9px] font-bold text-fg font-korean mb-1.5"> <div className="text-caption font-bold text-fg font-korean mb-1.5">
(Shear Force Distribution) (Shear Force Distribution)
</div> </div>
<svg viewBox="0 0 260 90" className="w-full" style={{ height: '75px' }}> <svg viewBox="0 0 260 90" className="w-full" style={{ height: '75px' }}>
@ -1184,22 +1312,22 @@ function LongStrengthPanel(_props: { activeType: AccidentType }) {
<text x="80" y="12" fill="var(--color-danger)" fontSize="4.5"> <text x="80" y="12" fill="var(--color-danger)" fontSize="4.5">
SF SF
</text> </text>
<text x="2" y="14" fill="var(--fg-disabled)" fontSize="4" fontFamily="monospace"> <text x="2" y="14" fill="var(--fg-disabled)" fontSize="4" fontFamily="var(--font-mono)">
+SF +SF
</text> </text>
<text x="2" y="48" fill="var(--fg-disabled)" fontSize="4" fontFamily="monospace"> <text x="2" y="48" fill="var(--fg-disabled)" fontSize="4" fontFamily="var(--font-mono)">
0 0
</text> </text>
<text x="2" y="78" fill="var(--fg-disabled)" fontSize="4" fontFamily="monospace"> <text x="2" y="78" fill="var(--fg-disabled)" fontSize="4" fontFamily="var(--font-mono)">
-SF -SF
</text> </text>
<text x="25" y="88" fill="var(--fg-disabled)" fontSize="4" fontFamily="monospace"> <text x="25" y="88" fill="var(--fg-disabled)" fontSize="4" fontFamily="var(--font-mono)">
AP AP
</text> </text>
<text x="130" y="88" fill="var(--fg-disabled)" fontSize="4" fontFamily="monospace"> <text x="130" y="88" fill="var(--fg-disabled)" fontSize="4" fontFamily="var(--font-mono)">
MID MID
</text> </text>
<text x="245" y="88" fill="var(--fg-disabled)" fontSize="4" fontFamily="monospace"> <text x="245" y="88" fill="var(--fg-disabled)" fontSize="4" fontFamily="var(--font-mono)">
FP FP
</text> </text>
</svg> </svg>
@ -1207,7 +1335,7 @@ function LongStrengthPanel(_props: { activeType: AccidentType }) {
{/* 굽힘모멘트 분포 SVG */} {/* 굽힘모멘트 분포 SVG */}
<div className="bg-bg-card border border-stroke rounded-md p-2.5"> <div className="bg-bg-card border border-stroke rounded-md p-2.5">
<div className="text-[9px] font-bold text-fg font-korean mb-1.5"> <div className="text-caption font-bold text-fg font-korean mb-1.5">
(Bending Moment Distribution) (Bending Moment Distribution)
</div> </div>
<svg viewBox="0 0 260 90" className="w-full" style={{ height: '75px' }}> <svg viewBox="0 0 260 90" className="w-full" style={{ height: '75px' }}>
@ -1241,19 +1369,19 @@ function LongStrengthPanel(_props: { activeType: AccidentType }) {
<text x="115" y="8" fill="var(--color-warning)" fontSize="4.5"> <text x="115" y="8" fill="var(--color-warning)" fontSize="4.5">
BM BM
</text> </text>
<text x="2" y="14" fill="var(--fg-disabled)" fontSize="4" fontFamily="monospace"> <text x="2" y="14" fill="var(--fg-disabled)" fontSize="4" fontFamily="var(--font-mono)">
+BM +BM
</text> </text>
<text x="2" y="48" fill="var(--fg-disabled)" fontSize="4" fontFamily="monospace"> <text x="2" y="48" fill="var(--fg-disabled)" fontSize="4" fontFamily="var(--font-mono)">
0 0
</text> </text>
<text x="25" y="88" fill="var(--fg-disabled)" fontSize="4" fontFamily="monospace"> <text x="25" y="88" fill="var(--fg-disabled)" fontSize="4" fontFamily="var(--font-mono)">
AP AP
</text> </text>
<text x="130" y="88" fill="var(--fg-disabled)" fontSize="4" fontFamily="monospace"> <text x="130" y="88" fill="var(--fg-disabled)" fontSize="4" fontFamily="var(--font-mono)">
MID MID
</text> </text>
<text x="245" y="88" fill="var(--fg-disabled)" fontSize="4" fontFamily="monospace"> <text x="245" y="88" fill="var(--fg-disabled)" fontSize="4" fontFamily="var(--font-mono)">
FP FP
</text> </text>
</svg> </svg>
@ -1262,18 +1390,18 @@ function LongStrengthPanel(_props: { activeType: AccidentType }) {
{/* 종강도 지표 */} {/* 종강도 지표 */}
<div className="grid grid-cols-2 gap-1.5"> <div className="grid grid-cols-2 gap-1.5">
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[8px] text-fg-disabled font-korean">SF / </div> <div className="text-caption text-fg-disabled font-korean">SF / </div>
<div className="text-lg font-bold text-color-caution font-mono"> <div className="text-title-3 font-bold text-color-caution font-mono">
88<span className="text-[9px]">%</span> 88<span className="text-caption">%</span>
</div> </div>
<div className="h-[5px] bg-bg-surface-hover rounded-sm mt-0.5"> <div className="h-[5px] bg-bg-surface-hover rounded-sm mt-0.5">
<div className="h-full rounded-sm w-[88%] bg-color-caution" /> <div className="h-full rounded-sm w-[88%] bg-color-caution" />
</div> </div>
</div> </div>
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[8px] text-fg-disabled font-korean">BM / </div> <div className="text-caption text-fg-disabled font-korean">BM / </div>
<div className="text-lg font-bold text-color-warning font-mono"> <div className="text-title-3 font-bold text-color-warning font-mono">
92<span className="text-[9px]">%</span> 92<span className="text-caption">%</span>
</div> </div>
<div className="h-[5px] bg-bg-surface-hover rounded-sm mt-0.5"> <div className="h-[5px] bg-bg-surface-hover rounded-sm mt-0.5">
<div className="h-full rounded-sm w-[92%] bg-color-warning" /> <div className="h-full rounded-sm w-[92%] bg-color-warning" />
@ -1282,23 +1410,29 @@ function LongStrengthPanel(_props: { activeType: AccidentType }) {
</div> </div>
<div className="grid grid-cols-2 gap-1.5"> <div className="grid grid-cols-2 gap-1.5">
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[8px] text-fg-disabled font-korean">Section Modulus </div> <div className="text-caption text-fg-disabled font-korean">Section Modulus </div>
<div className="text-lg font-bold text-color-success font-mono">1.08</div> <div className="text-title-3 font-bold text-color-success font-mono">1.08</div>
<div className="text-[7px] text-color-success font-korean">Req'd: 1.00 </div> <div className="text-caption text-color-success font-korean">Req'd: 1.00 </div>
</div> </div>
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[8px] text-fg-disabled font-korean">Hull Girder ULS</div> <div className="text-caption text-fg-disabled font-korean">Hull Girder ULS</div>
<div className="text-lg font-bold text-color-caution font-mono">1.12</div> <div className="text-title-3 font-bold text-color-caution font-mono">1.12</div>
<div className="text-[7px] text-color-caution font-korean">Req'd: 1.10 </div> <div className="text-caption text-color-caution font-korean">Req'd: 1.10 </div>
</div> </div>
</div> </div>
{/* 판정 */} {/* 판정 */}
<div className="bg-[rgba(251,191,36,0.06)] border border-[rgba(251,191,36,0.2)] rounded-md p-2.5"> <div
<div className="text-[10px] font-bold text-color-caution font-korean mb-1"> className="rounded-md p-2.5"
style={{
background: 'color-mix(in srgb, var(--color-caution) 6%, transparent)',
border: '1px solid color-mix(in srgb, var(--color-caution) 20%, transparent)',
}}
>
<div className="text-label-2 font-bold text-color-caution font-korean mb-1">
판정: 주의 (CAUTION) 판정: 주의 (CAUTION)
</div> </div>
<div className="text-[8px] text-fg-disabled font-korean leading-snug"> <div className="text-caption text-fg-disabled font-korean leading-snug">
· SF 최대: 허용치의 88% <span className="text-color-caution"> </span> · SF 최대: 허용치의 88% <span className="text-color-caution"> </span>
<br />· BM 최대: 허용치의 92% <span className="text-color-warning"> </span> <br />· BM 최대: 허용치의 92% <span className="text-color-warning"> </span>
<br /> <br />
@ -1315,9 +1449,9 @@ function BottomBar() {
return ( return (
<div className="h-[145px] min-h-[145px] border-t border-stroke flex bg-bg-base flex-shrink-0"> <div className="h-[145px] min-h-[145px] border-t border-stroke flex bg-bg-base flex-shrink-0">
{/* 이벤트 로그 */} {/* 이벤트 로그 */}
<div className="flex-1 flex flex-col overflow-hidden border-r border-stroke"> <div className="flex-1 flex flex-col overflow-hidden">
<div className="flex items-center justify-between px-3 py-1 border-b border-stroke flex-shrink-0"> <div className="flex items-center justify-between px-3 py-1 border-b border-stroke flex-shrink-0">
<span className="text-[9px] font-bold text-fg-disabled font-korean"> <span className="text-caption font-bold text-fg-disabled font-korean">
/ (EVENT LOG / COMMUNICATION TRANSCRIPT) / (EVENT LOG / COMMUNICATION TRANSCRIPT)
</span> </span>
<div className="flex gap-0.5"> <div className="flex gap-0.5">
@ -1328,7 +1462,7 @@ function BottomBar() {
].map((f, i) => ( ].map((f, i) => (
<button <button
key={i} key={i}
className="px-2 py-px rounded-sm text-[8px] font-bold cursor-pointer font-korean" className="px-2 py-px rounded-sm text-caption font-bold cursor-pointer font-korean"
style={{ style={{
background: `color-mix(in srgb, ${f.color} 15%, transparent)`, background: `color-mix(in srgb, ${f.color} 15%, transparent)`,
border: `1px solid color-mix(in srgb, ${f.color} 30%, transparent)`, border: `1px solid color-mix(in srgb, ${f.color} 30%, transparent)`,
@ -1340,7 +1474,7 @@ function BottomBar() {
))} ))}
</div> </div>
</div> </div>
<div className="flex-1 overflow-y-auto px-3 py-1 font-mono text-[9px] leading-[1.7] scrollbar-thin"> <div className="flex-1 overflow-y-auto px-3 py-1 font-mono text-caption leading-[1.7] scrollbar-thin">
{[ {[
{ {
time: '10:35', time: '10:35',
@ -1395,46 +1529,6 @@ function BottomBar() {
))} ))}
</div> </div>
</div> </div>
{/* 타임라인 시뮬레이션 */}
<div className="w-[330px] min-w-[330px] flex flex-col px-3.5 py-2 gap-1.5">
<div className="text-[9px] font-bold text-fg font-korean">
(TIMELINE SIMULATION CONTROL)
</div>
<div className="flex items-center gap-1.5 text-[8px] text-fg-disabled font-mono">
<span>[-6h]</span>
<span className="flex-1 text-center font-bold text-fg">[CURRENT]</span>
<span>[+6H]</span>
<span>[+12H]</span>
<span>[+24H]</span>
</div>
<div className="relative h-1.5 bg-bg-surface-hover rounded-sm mx-1">
<div
className="absolute rounded-full border-2 border-bg-0 bg-color-accent"
style={{
left: '35%',
top: '-3px',
width: '12px',
height: '12px',
boxShadow: '0 0 8px rgba(6,182,212,.4)',
}}
/>
</div>
<div className="flex items-center justify-center gap-2 mt-0.5">
<button className="w-7 h-7 bg-bg-card border border-stroke rounded-full text-fg-disabled text-[11px] flex items-center justify-center cursor-pointer hover:text-fg">
</button>
<button className="w-[34px] h-[34px] bg-[rgba(6,182,212,0.15)] border border-[rgba(6,182,212,0.3)] rounded-full text-[var(--color-accent)] text-[13px] flex items-center justify-center cursor-pointer hover:brightness-125">
</button>
<button className="w-7 h-7 bg-bg-card border border-stroke rounded-full text-fg-disabled text-[11px] flex items-center justify-center cursor-pointer hover:text-fg">
</button>
</div>
<div className="text-center text-[8px] text-fg-disabled font-mono">
: <b className="text-color-accent">10:45 KST</b>
</div>
</div>
</div> </div>
); );
} }
@ -1457,12 +1551,12 @@ function MetricCard({
}) { }) {
return ( return (
<div className="bg-bg-card border border-stroke rounded-md p-2"> <div className="bg-bg-card border border-stroke rounded-md p-2">
<div className="text-[8px] text-fg-disabled font-korean">{label}</div> <div className="text-caption text-fg-disabled font-korean">{label}</div>
<div className="text-xl font-bold font-mono" style={{ color }}> <div className="text-title-2 font-bold font-mono" style={{ color }}>
{value} {value}
<span className="text-[10px]"> {unit}</span> <span className="text-label-2"> {unit}</span>
</div> </div>
<div className="text-[8px] font-korean" style={{ color: subColor }}> <div className="text-caption font-korean" style={{ color: subColor }}>
{sub} {sub}
</div> </div>
</div> </div>
@ -1520,35 +1614,41 @@ function RescueListView() {
return ( return (
<div className="flex flex-col flex-1 overflow-hidden"> <div className="flex flex-col flex-1 overflow-hidden">
<div className="px-5 py-4 flex items-center justify-between border-b border-stroke"> <div className="px-5 py-4 flex items-center justify-between border-b border-stroke">
<span className="text-sm font-bold font-korean"> </span> <span className="text-title-3 font-bold font-korean"> </span>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<input <input
type="text" type="text"
placeholder="선박명 / 사고번호 검색..." placeholder="선박명 / 사고번호 검색..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="px-3 py-1.5 bg-bg-base border border-stroke rounded-md text-fg-sub font-korean text-[11px] w-[200px] outline-none focus:border-[var(--color-accent)]" className="px-3 py-1.5 bg-bg-base border border-stroke rounded-md text-fg-sub font-korean text-label-2 w-[200px] outline-none focus:border-[var(--color-accent)]"
/> />
<button className="px-3.5 py-1.5 bg-[rgba(6,182,212,0.12)] border border-[rgba(6,182,212,0.3)] rounded-md text-[var(--color-accent)] text-[11px] font-semibold cursor-pointer font-korean hover:bg-[rgba(6,182,212,0.2)]"> <button
className="rounded-sm text-label-2 font-semibold cursor-pointer text-color-accent px-3.5 py-1.5 font-korean"
style={{
border: '1px solid rgba(6,182,212,.3)',
background: 'rgba(6,182,212,.08)',
}}
>
+ +
</button> </button>
</div> </div>
</div> </div>
<div className="flex-1 overflow-y-auto px-5 pb-4"> <div className="flex-1 overflow-y-auto px-5 pb-4">
{loading ? ( {loading ? (
<div className="text-center py-20 text-fg-disabled text-sm"> ...</div> <div className="text-center py-20 text-fg-disabled text-title-3"> ...</div>
) : opsList.length === 0 ? ( ) : opsList.length === 0 ? (
<div className="text-center py-20 text-fg-disabled text-sm"> <div className="text-center py-20 text-fg-disabled text-title-3">
. .
</div> </div>
) : ( ) : (
<table className="w-full border-collapse text-[11px] mt-3"> <table className="w-full border-collapse text-label-2 mt-3">
<thead> <thead>
<tr className="bg-bg-card border-b border-stroke"> <tr className="bg-bg-card border-b border-stroke">
{['상태', '사고번호', '선박명', '사고유형', '발생일시', '위치', '인명'].map((h) => ( {['상태', '사고번호', '선박명', '사고유형', '발생일시', '위치', '인명'].map((h) => (
<th <th
key={h} key={h}
className="py-2 px-2.5 text-left font-korean font-semibold text-fg-disabled text-[10px]" className="py-2 px-2.5 text-left font-korean font-semibold text-fg-disabled text-label-2"
> >
{h} {h}
</th> </th>
@ -1565,7 +1665,7 @@ function RescueListView() {
> >
<td className="py-2 px-2.5"> <td className="py-2 px-2.5">
<span <span
className="px-2 py-0.5 rounded-xl text-[9px] font-bold" className="px-2 py-0.5 rounded-xl text-caption font-bold"
style={{ style={{
background: `color-mix(in srgb, ${status.color} 15%, transparent)`, background: `color-mix(in srgb, ${status.color} 15%, transparent)`,
color: status.color, color: status.color,
@ -1574,7 +1674,7 @@ function RescueListView() {
{status.label} {status.label}
</span> </span>
</td> </td>
<td className="py-2 px-2.5 font-mono text-[var(--color-accent)] font-semibold"> <td className="py-2 px-2.5 font-mono text-color-accent font-semibold">
{r.opsCd} {r.opsCd}
</td> </td>
<td className="py-2 px-2.5 font-korean text-fg font-semibold">{r.vesselNm}</td> <td className="py-2 px-2.5 font-korean text-fg font-semibold">{r.vesselNm}</td>
@ -1582,7 +1682,7 @@ function RescueListView() {
<td className="py-2 px-2.5 font-mono text-fg-disabled"> <td className="py-2 px-2.5 font-mono text-fg-disabled">
{r.regDtm ? new Date(r.regDtm).toLocaleString('ko-KR') : '—'} {r.regDtm ? new Date(r.regDtm).toLocaleString('ko-KR') : '—'}
</td> </td>
<td className="py-2 px-2.5 font-mono text-fg-disabled text-[10px]"> <td className="py-2 px-2.5 font-mono text-fg-disabled text-label-2">
{r.locDc ?? '—'} {r.locDc ?? '—'}
</td> </td>
<td className="py-2 px-2.5 font-mono"> <td className="py-2 px-2.5 font-mono">

파일 보기

@ -22,6 +22,7 @@ export default {
}, },
color: { color: {
accent: 'var(--color-accent)', accent: 'var(--color-accent)',
'accent-muted': 'var(--color-accent-muted)',
info: 'var(--color-info)', info: 'var(--color-info)',
tertiary: 'var(--color-tertiary)', tertiary: 'var(--color-tertiary)',
danger: 'var(--color-danger)', danger: 'var(--color-danger)',
@ -32,6 +33,10 @@ export default {
DEFAULT: 'var(--color-boom)', DEFAULT: 'var(--color-boom)',
hover: 'var(--color-boom-hover)', hover: 'var(--color-boom-hover)',
}, },
navy: {
DEFAULT: 'var(--color-navy)',
hover: 'var(--color-navy-hover)',
},
}, },
}, },
fontSize: { fontSize: {