feat(design): 디자인 시스템 폰트 업스케일 및 전체 탭 토큰 적용 #169

병합
dnlee feature/design-system-etc 에서 develop 로 4 commits 를 머지했습니다 2026-04-14 11:23:11 +09:00
91개의 변경된 파일1725개의 추가작업 그리고 1341개의 파일을 삭제

파일 보기

@ -127,6 +127,28 @@ wing/
## 진행 중 작업 (완료 후 삭제) ## 진행 중 작업 (완료 후 삭제)
### 폰트 크기 업스케일 작업 (진행 중)
반드시 `memory/font-upscale-plan.md`를 읽고 Phase 진행 상황을 확인할 것.
**토큰 변경 매핑 (이름 유지, 값만 변경):**
- `caption`/`label-2`/`title-6`: 11px → **12px** (0.75rem)
- `label-1`/`title-5`: 12px → **13px** (0.8125rem)
- `body-2`/`title-4`: 13px → **14px** (0.875rem)
- `body-1`/`title-3`: 14px → **16px** (1rem)
**네비게이션 클래스 교체:**
- TopBar 메인탭: `text-title-4``text-title-2` (16px)
- SubMenuBar 서브탭: `text-title-5``text-title-4` (14px)
**작업 범위:**
- Phase 1: tailwind.config.js + base.css 토큰 값 수정
- Phase 2: components.css 하드코딩(27곳) + wing.css `.wing-tab` text-xs→text-caption
- Phase 3: TopBar.tsx + SubMenuBar.tsx 클래스 교체
- Phase 4: text-xs→text-caption, text-sm→text-body-2 스크립트 교체 (design 페이지 제외, 608건)
- Phase 5: prediction 탭 인라인 fontSize 수정
- Phase 6 (보류): wing-header-bar 패딩 — 폰트 변경 후 유저 확인 후 진행
### 디자인 시스템 폰트+색상 통일 작업 ### 디자인 시스템 폰트+색상 통일 작업
compact 후 반드시 `memory/design-system-work.md`를 읽고 작업 상태(완료/미완료 컴포넌트)를 확인할 것. compact 후 반드시 `memory/design-system-work.md`를 읽고 작업 상태(완료/미완료 컴포넌트)를 확인할 것.

파일 보기

@ -5,9 +5,13 @@
## [Unreleased] ## [Unreleased]
### 추가 ### 추가
- 디자인 시스템: HNS·사건사고·확산예측·SCAT·기상 탭 디자인 시스템 토큰 전면 적용
- 관리자: 비식별화조치 메뉴 및 패널 추가 - 관리자: 비식별화조치 메뉴 및 패널 추가
- 긴급구난/예측도 OSM 지도 적용 및 관리자 패널 추가 - 긴급구난/예측도 OSM 지도 적용 및 관리자 패널 추가
### 변경
- 디자인 시스템: 폰트 업스케일 토큰 값 변경 및 전체 탭 색상·폰트 통일
## [2026-04-13] ## [2026-04-13]
### 추가 ### 추가

파일 보기

@ -120,7 +120,7 @@ export function LoginPage() {
</label> </label>
<div className="relative"> <div className="relative">
<span <span
className="absolute text-sm text-fg-disabled pointer-events-none" className="absolute text-body-2 text-fg-disabled pointer-events-none"
style={{ left: 12, top: '50%', transform: 'translateY(-50%)' }} style={{ left: 12, top: '50%', transform: 'translateY(-50%)' }}
> >
<svg <svg
@ -174,7 +174,7 @@ export function LoginPage() {
</label> </label>
<div className="relative"> <div className="relative">
<span <span
className="absolute text-sm text-fg-disabled pointer-events-none" className="absolute text-body-2 text-fg-disabled pointer-events-none"
style={{ left: 12, top: '50%', transform: 'translateY(-50%)' }} style={{ left: 12, top: '50%', transform: 'translateY(-50%)' }}
> >
<svg <svg
@ -249,7 +249,7 @@ export function LoginPage() {
color: '#67e8f9', color: '#67e8f9',
}} }}
> >
<span className="text-sm shrink-0 mt-px"> <span className="text-body-2 shrink-0 mt-px">
<svg <svg
width="14" width="14"
height="14" height="14"
@ -303,7 +303,7 @@ export function LoginPage() {
<button <button
type="submit" type="submit"
disabled={isLoading} disabled={isLoading}
className="w-full text-color-accent text-sm font-bold rounded-md border" className="w-full text-color-accent text-body-2 font-bold rounded-md border"
style={{ style={{
padding: '12px', padding: '12px',
background: isLoading background: isLoading

파일 보기

@ -21,7 +21,7 @@ export function SubMenuBar({ activeMainTab }: SubMenuBarProps) {
key={item.id} key={item.id}
onClick={() => setActiveSubTab(item.id)} onClick={() => setActiveSubTab(item.id)}
className={` className={`
px-4 py-2.5 text-title-5 font-medium transition-all duration-200 px-4 py-2.5 text-title-4 font-medium transition-all duration-200
font-korean tracking-navigation font-korean tracking-navigation
${activeSubTab === item.id ? 'text-color-accent' : 'text-fg-sub hover:text-fg'} ${activeSubTab === item.id ? 'text-color-accent' : 'text-fg-sub hover:text-fg'}
`} `}

파일 보기

@ -56,11 +56,7 @@ export function TopBar({ activeTab, onTabChange }: TopBarProps) {
className="flex items-center hover:opacity-80 transition-opacity cursor-pointer" className="flex items-center hover:opacity-80 transition-opacity cursor-pointer"
title="홈으로 이동" title="홈으로 이동"
> >
<img <img src="/wing_logo_white.svg" alt="WING 해양환경 위기대응" className="h-5 wing-logo" />
src="/wing_logo_white.svg"
alt="WING 해양환경 위기대응"
className="h-3.5 wing-logo"
/>
</button> </button>
{/* Divider */} {/* Divider */}
@ -87,7 +83,7 @@ export function TopBar({ activeTab, onTabChange }: TopBarProps) {
onClick={handleClick} onClick={handleClick}
title={tab.label} title={tab.label}
className={` className={`
px-2.5 xl:px-4 py-2 text-title-4 font-bold transition-all duration-200 px-2.5 xl:px-4 py-2 text-title-2 font-bold transition-all duration-200
font-korean tracking-navigation border-b-2 border-transparent font-korean tracking-navigation border-b-2 border-transparent
${isIncident ? 'ml-1' : ''} ${isIncident ? 'ml-1' : ''}
${isMonitor ? 'ml-1 flex items-center gap-1.5' : ''} ${isMonitor ? 'ml-1 flex items-center gap-1.5' : ''}
@ -127,7 +123,7 @@ export function TopBar({ activeTab, onTabChange }: TopBarProps) {
{/* Right Section */} {/* Right Section */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{/* Status Badge */} {/* Status Badge */}
{/* <div className="flex items-center gap-2 px-3 py-1.5 bg-[rgba(239,68,68,0.1)] border border-[rgba(239,68,68,0.2)] rounded-sm text-xs font-medium text-color-danger animate-pulse"> {/* <div className="flex items-center gap-2 px-3 py-1.5 bg-[rgba(239,68,68,0.1)] border border-[rgba(239,68,68,0.2)] rounded-sm text-caption font-medium text-color-danger animate-pulse">
<div className="w-1.5 h-1.5 rounded-full bg-color-danger animate-pulse" /> <div className="w-1.5 h-1.5 rounded-full bg-color-danger animate-pulse" />
</div> */} </div> */}

파일 보기

@ -141,7 +141,7 @@ export function BacktrackReplayBar({
className="w-2 h-2 rounded-full bg-color-tertiary" className="w-2 h-2 rounded-full bg-color-tertiary"
style={{ boxShadow: '0 0 8px rgba(168,85,247,0.5)' }} style={{ boxShadow: '0 0 8px rgba(168,85,247,0.5)' }}
/> />
<span className="text-xs font-bold"> </span> <span className="text-caption font-bold"> </span>
</div> </div>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
@ -180,7 +180,7 @@ export function BacktrackReplayBar({
{/* Play/Pause */} {/* Play/Pause */}
<button <button
onClick={handlePlayClick} onClick={handlePlayClick}
className="shrink-0 w-9 h-9 rounded-full flex items-center justify-center text-sm cursor-pointer" className="shrink-0 w-9 h-9 rounded-full flex items-center justify-center text-body-2 cursor-pointer"
style={{ style={{
background: isPlaying ? 'var(--color-tertiary)' : 'rgba(168,85,247,0.15)', background: isPlaying ? 'var(--color-tertiary)' : 'rgba(168,85,247,0.15)',
border: `2px solid ${isPlaying ? 'var(--color-tertiary)' : 'rgba(168,85,247,0.4)'}`, border: `2px solid ${isPlaying ? 'var(--color-tertiary)' : 'rgba(168,85,247,0.4)'}`,

파일 보기

@ -414,7 +414,10 @@ export function MapView({
longitude: lng, longitude: lng,
latitude: lat, latitude: lat,
content: ( content: (
<div className="text-xs font-korean" style={{ minWidth: '180px', maxWidth: '260px' }}> <div
className="text-caption font-korean"
style={{ minWidth: '180px', maxWidth: '260px' }}
>
<div className="font-semibold mb-1.5 pb-1 border-b border-[rgba(0,0,0,0.12)]"> <div className="font-semibold mb-1.5 pb-1 border-b border-[rgba(0,0,0,0.12)]">
{String(category ?? '민감자원')} {String(category ?? '민감자원')}
</div> </div>
@ -535,7 +538,7 @@ export function MapView({
longitude: d.lon, longitude: d.lon,
latitude: d.lat, latitude: d.lat,
content: ( content: (
<div className="text-xs"> <div className="text-caption">
<strong> <strong>
{modelKey} #{(d.particle ?? 0) + 1} {modelKey} #{(d.particle ?? 0) + 1}
</strong> </strong>
@ -604,7 +607,7 @@ export function MapView({
longitude: info.coordinate?.[0] ?? 0, longitude: info.coordinate?.[0] ?? 0,
latitude: info.coordinate?.[1] ?? 0, latitude: info.coordinate?.[1] ?? 0,
content: ( content: (
<div className="text-xs" style={{ minWidth: '140px' }}> <div className="text-caption" style={{ minWidth: '140px' }}>
<strong style={{ color: PRIORITY_COLORS[d.priority] }}>{d.name}</strong> <strong style={{ color: PRIORITY_COLORS[d.priority] }}>{d.name}</strong>
<br /> <br />
: {PRIORITY_LABELS[d.priority] || d.priority} : {PRIORITY_LABELS[d.priority] || d.priority}
@ -919,7 +922,7 @@ export function MapView({
longitude: info.coordinate[0], longitude: info.coordinate[0],
latitude: info.coordinate[1], latitude: info.coordinate[1],
content: ( content: (
<div className="text-xs leading-relaxed" style={{ minWidth: 180 }}> <div className="text-caption leading-relaxed" style={{ minWidth: 180 }}>
<strong className="text-color-warning"> <strong className="text-color-warning">
{dispersionResult.substance} {dispersionResult.substance}
</strong> </strong>
@ -1009,7 +1012,7 @@ export function MapView({
longitude: d.lon, longitude: d.lon,
latitude: d.lat, latitude: d.lat,
content: ( content: (
<div className="text-xs" style={{ minWidth: '130px' }}> <div className="text-caption" style={{ minWidth: '130px' }}>
<div className="flex items-center gap-1 mb-1"> <div className="flex items-center gap-1 mb-1">
<span>{SENSITIVE_ICONS[d.type]}</span> <span>{SENSITIVE_ICONS[d.type]}</span>
<strong style={{ color: SENSITIVE_COLORS[d.type] }}>{d.name}</strong> <strong style={{ color: SENSITIVE_COLORS[d.type] }}>{d.name}</strong>
@ -1458,19 +1461,19 @@ function MapControls({ center, zoom }: { center: [number, number]; zoom: number
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<button <button
onClick={() => map?.zoomIn()} 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" className="w-[28px] h-[28px] bg-[color-mix(in_srgb,var(--bg-elevated)_85%,transparent)] backdrop-blur-sm border border-stroke rounded-sm text-fg-sub flex items-center justify-center hover:bg-bg-surface-hover hover:text-fg transition-all text-caption"
> >
+ +
</button> </button>
<button <button
onClick={() => map?.zoomOut()} 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" className="w-[28px] h-[28px] bg-[color-mix(in_srgb,var(--bg-elevated)_85%,transparent)] backdrop-blur-sm border border-stroke rounded-sm text-fg-sub flex items-center justify-center hover:bg-bg-surface-hover hover:text-fg transition-all text-caption"
> >
</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-caption" className="w-[28px] h-[28px] bg-[color-mix(in_srgb,var(--bg-elevated)_85%,transparent)] backdrop-blur-sm border border-stroke rounded-sm text-fg-sub flex items-center justify-center hover:text-fg transition-all text-caption"
> >
&#x1F3AF; &#x1F3AF;
</button> </button>
@ -1575,7 +1578,7 @@ 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-caption">🧭</div>
<span className="text-caption text-fg-disabled"> ()</span> <span className="text-caption text-fg-disabled"> ()</span>
</div> </div>
</div> </div>
@ -1888,7 +1891,9 @@ function BacktrackReplayBar({
minWidth: '340px', minWidth: '340px',
}} }}
> >
<div className="text-sm text-color-tertiary font-mono font-bold">{progress.toFixed(0)}%</div> <div className="text-body-2 text-color-tertiary font-mono font-bold">
{progress.toFixed(0)}%
</div>
<div className="flex-1 h-1 bg-border relative rounded-[2px]"> <div className="flex-1 h-1 bg-border relative rounded-[2px]">
<div <div
className="h-full rounded-[2px]" className="h-full rounded-[2px]"

파일 보기

@ -79,15 +79,15 @@
--font-size-title-1: 1.125rem; --font-size-title-1: 1.125rem;
--font-size-subtitle: 0.9375rem; --font-size-subtitle: 0.9375rem;
--font-size-title-2: 1rem; --font-size-title-2: 1rem;
--font-size-title-3: 0.875rem; --font-size-title-3: 1rem;
--font-size-title-4: 0.8125rem; --font-size-title-4: 0.875rem;
--font-size-title-5: 0.75rem; --font-size-title-5: 0.8125rem;
--font-size-title-6: 0.6875rem; --font-size-title-6: 0.75rem;
--font-size-body-1: 0.875rem; --font-size-body-1: 1rem;
--font-size-body-2: 0.8125rem; --font-size-body-2: 0.875rem;
--font-size-label-1: 0.75rem; --font-size-label-1: 0.8125rem;
--font-size-label-2: 0.6875rem; --font-size-label-2: 0.75rem;
--font-size-caption: 0.6875rem; --font-size-caption: 0.75rem;
/* typography — font-weight */ /* typography — font-weight */
--font-weight-thin: 300; --font-weight-thin: 300;
--font-weight-regular: 400; --font-weight-regular: 400;

파일 보기

@ -45,6 +45,22 @@
background: transparent; background: transparent;
} }
/* ═══ Incidents 사고 팝업 ✕ 버튼 — 라이트 지도 기준 검은색 고정 ═══ */
.incident-popup .maplibregl-popup-close-button {
color: #1a1d21;
background: transparent;
width: 16px;
height: 16px;
font-size: 16px;
line-height: 16px;
top: 6px;
right: 6px;
}
.incident-popup .maplibregl-popup-close-button:hover {
color: #000;
background: transparent;
}
/* ═══ Scrollbar ═══ */ /* ═══ Scrollbar ═══ */
.scrollbar-thin { .scrollbar-thin {
scrollbar-width: thin; scrollbar-width: thin;
@ -78,7 +94,7 @@
border-radius: 6px; border-radius: 6px;
color: var(--fg-default); color: var(--fg-default);
font-family: var(--font-korean); font-family: var(--font-korean);
font-size: 11px; font-size: 0.75rem;
outline: none; outline: none;
} }
@ -103,7 +119,7 @@
.prd-date-input, .prd-date-input,
.prd-time-input { .prd-time-input {
font-size: 10px; font-size: 0.75rem;
color-scheme: dark; color-scheme: dark;
} }
@ -111,7 +127,7 @@
.prd-time-input::-webkit-datetime-edit { .prd-time-input::-webkit-datetime-edit {
color: var(--fg-sub); color: var(--fg-sub);
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 10px; font-size: 0.75rem;
letter-spacing: 0.3px; letter-spacing: 0.3px;
} }
@ -191,7 +207,7 @@
background: #1a1f2e; background: #1a1f2e;
color: var(--fg-default); color: var(--fg-default);
padding: 10px; padding: 10px;
font-size: 11px; font-size: 0.75rem;
font-family: var(--font-korean); font-family: var(--font-korean);
} }
@ -278,7 +294,7 @@
.combo-item { .combo-item {
padding: 7px 10px; padding: 7px 10px;
font-size: 11px; font-size: 0.75rem;
font-family: var(--font-korean); font-family: var(--font-korean);
color: var(--fg-sub); color: var(--fg-sub);
cursor: pointer; cursor: pointer;
@ -309,7 +325,7 @@
gap: 4px; gap: 4px;
padding: 5px 4px; padding: 5px 4px;
border-radius: 5px; border-radius: 5px;
font-size: 9px; font-size: 0.6875rem;
font-weight: 600; font-weight: 600;
font-family: var(--font-korean); font-family: var(--font-korean);
cursor: pointer; cursor: pointer;
@ -328,7 +344,7 @@
/* .prd-mc.on::before { /* .prd-mc.on::before {
content: '✓ '; content: '✓ ';
font-size: 9px; font-size: 0.6875rem;
color: var(--color-accent); color: var(--color-accent);
} */ } */
@ -370,7 +386,7 @@
border: 1px solid rgba(6, 182, 212, 0.2); border: 1px solid rgba(6, 182, 212, 0.2);
border-radius: 6px; border-radius: 6px;
color: var(--color-accent); color: var(--color-accent);
font-size: 9px; font-size: 0.6875rem;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
@ -551,7 +567,7 @@
} }
.tll { .tll {
font-size: 10px; font-size: 0.75rem;
color: var(--fg-disabled); color: var(--fg-disabled);
font-family: var(--font-mono); font-family: var(--font-mono);
} }
@ -620,7 +636,7 @@
border: 1px solid var(--color-boom); border: 1px solid var(--color-boom);
border-radius: 4px; border-radius: 4px;
padding: 4px 8px; padding: 4px 8px;
font-size: 10px; font-size: 0.75rem;
color: var(--color-boom); color: var(--color-boom);
white-space: nowrap; white-space: nowrap;
font-family: var(--font-korean); font-family: var(--font-korean);
@ -671,7 +687,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 5px; gap: 5px;
font-size: 11px; font-size: 0.75rem;
} }
.tlsl { .tlsl {
@ -699,7 +715,7 @@
border: 1px solid var(--stroke-default); border: 1px solid var(--stroke-default);
background: var(--bg-card); background: var(--bg-card);
color: var(--fg-sub); color: var(--fg-sub);
font-size: 11px; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
font-family: var(--font-korean); font-family: var(--font-korean);
@ -750,7 +766,7 @@
padding: 6px 8px; padding: 6px 8px;
cursor: pointer; cursor: pointer;
transition: background 0.15s; transition: background 0.15s;
font-size: 11px; font-size: 0.75rem;
color: var(--fg-sub); color: var(--fg-sub);
font-family: var(--font-korean); font-family: var(--font-korean);
} }
@ -835,7 +851,7 @@
} }
.layer-count { .layer-count {
font-size: 10px; font-size: 0.75rem;
color: var(--fg-disabled); color: var(--fg-disabled);
font-family: var(--font-mono); font-family: var(--font-mono);
} }
@ -856,7 +872,7 @@
border: 1px solid rgba(245, 158, 11, 0.4); border: 1px solid rgba(245, 158, 11, 0.4);
border-radius: 8px; border-radius: 8px;
padding: 8px 16px; padding: 8px 16px;
font-size: 11px; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
color: var(--color-boom); color: var(--color-boom);
font-family: var(--font-korean); font-family: var(--font-korean);
@ -889,7 +905,7 @@
border-radius: 4px; border-radius: 4px;
color: var(--color-accent); color: var(--color-accent);
font-family: var(--font-mono); font-family: var(--font-mono);
font-size: 11px; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
text-align: right; text-align: right;
outline: none; outline: none;
@ -926,7 +942,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 11px; font-size: 0.75rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
} }
@ -936,7 +952,7 @@
border: 1px solid var(--stroke-default); border: 1px solid var(--stroke-default);
background: var(--bg-card); background: var(--bg-card);
color: var(--fg-disabled); color: var(--fg-disabled);
font-size: 10px; font-size: 0.75rem;
font-weight: 700; font-weight: 700;
cursor: pointer; cursor: pointer;
font-family: var(--font-mono); font-family: var(--font-mono);
@ -1123,7 +1139,7 @@
.lyr-h1-cnt { .lyr-h1-cnt {
margin-left: auto; margin-left: auto;
font-size: 10px; font-size: 0.75rem;
font-weight: 500; font-weight: 500;
color: var(--fg-disabled); color: var(--fg-disabled);
font-family: var(--font-mono); font-family: var(--font-mono);
@ -1152,7 +1168,7 @@
cursor: pointer; cursor: pointer;
border-radius: 3px; border-radius: 3px;
transition: background 0.15s; transition: background 0.15s;
font-size: 11px; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
color: var(--fg-sub); color: var(--fg-sub);
font-family: var(--font-korean); font-family: var(--font-korean);
@ -1176,7 +1192,7 @@
.lyr-h2-cnt { .lyr-h2-cnt {
margin-left: auto; margin-left: auto;
font-size: 10px; font-size: 0.75rem;
font-weight: 500; font-weight: 500;
color: var(--fg-disabled); color: var(--fg-disabled);
font-family: var(--font-mono); font-family: var(--font-mono);
@ -1199,7 +1215,7 @@
gap: 8px; gap: 8px;
padding: 4px 8px; padding: 4px 8px;
cursor: pointer; cursor: pointer;
font-size: 11px; font-size: 0.75rem;
color: var(--fg-sub); color: var(--fg-sub);
transition: transition:
color 0.15s, color 0.15s,
@ -1215,7 +1231,7 @@
.lyr-cnt { .lyr-cnt {
margin-left: auto; margin-left: auto;
font-size: 10px; font-size: 0.75rem;
font-weight: 400; font-weight: 400;
color: var(--fg-disabled); color: var(--fg-disabled);
font-family: var(--font-mono); font-family: var(--font-mono);
@ -1329,7 +1345,7 @@
} }
.lyr-ccustom label { .lyr-ccustom label {
font-size: 9px; font-size: 0.6875rem;
color: var(--fg-disabled); color: var(--fg-disabled);
font-family: var(--font-korean); font-family: var(--font-korean);
} }
@ -1353,7 +1369,7 @@
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
} }
.lyr-style-label { .lyr-style-label {
font-size: 9px; font-size: 0.6875rem;
font-weight: 700; font-weight: 700;
color: var(--fg-disabled); color: var(--fg-disabled);
font-family: var(--font-korean); font-family: var(--font-korean);
@ -1370,7 +1386,7 @@
margin-top: 6px; margin-top: 6px;
} }
.lyr-style-name { .lyr-style-name {
font-size: 10px; font-size: 0.75rem;
color: var(--fg-disabled); color: var(--fg-disabled);
font-family: var(--font-korean); font-family: var(--font-korean);
min-width: 32px; min-width: 32px;
@ -1395,7 +1411,7 @@
cursor: pointer; cursor: pointer;
} }
.lyr-style-val { .lyr-style-val {
font-size: 9px; font-size: 0.6875rem;
color: var(--fg-disabled); color: var(--fg-disabled);
font-family: var(--font-mono); font-family: var(--font-mono);
min-width: 28px; min-width: 28px;

파일 보기

@ -181,7 +181,7 @@
} }
.wing-tab { .wing-tab {
@apply flex-1 py-2 px-1 text-xs font-semibold rounded-md text-center cursor-pointer font-korean; @apply flex-1 py-2 px-1 text-caption font-semibold rounded-md text-center cursor-pointer font-korean;
transition: all 0.15s; transition: all 0.15s;
color: var(--fg-disabled); color: var(--fg-disabled);
background: transparent; background: transparent;

파일 보기

@ -148,7 +148,7 @@ export const ButtonContent = ({ theme }: ButtonContentProps) => {
{/* ── 섹션 1: 헤더 ── */} {/* ── 섹션 1: 헤더 ── */}
<div className="pb-10 mb-12 border-b border-solid" style={{ borderColor: dividerColor }}> <div className="pb-10 mb-12 border-b border-solid" style={{ borderColor: dividerColor }}>
<p <p
className="font-mono text-sm uppercase tracking-widest mb-3" className="font-mono text-body-2 uppercase tracking-widest mb-3"
style={{ color: t.textAccent }} style={{ color: t.textAccent }}
> >
Components Components
@ -244,7 +244,7 @@ export const ButtonContent = ({ theme }: ButtonContentProps) => {
</span> </span>
</div> </div>
</div> </div>
<span className="text-xs font-mono" style={{ color: t.textMuted }}> <span className="text-caption font-mono" style={{ color: t.textMuted }}>
+ +
</span> </span>
</div> </div>
@ -298,7 +298,7 @@ export const ButtonContent = ({ theme }: ButtonContentProps) => {
</span> </span>
</div> </div>
</div> </div>
<span className="text-xs font-mono" style={{ color: t.textMuted }}> <span className="text-caption font-mono" style={{ color: t.textMuted }}>
</span> </span>
</div> </div>
@ -339,7 +339,7 @@ export const ButtonContent = ({ theme }: ButtonContentProps) => {
<div key={size.label} className="flex items-center justify-between gap-8"> <div key={size.label} className="flex items-center justify-between gap-8">
{/* 라벨 */} {/* 라벨 */}
<span <span
className="font-mono text-sm w-36 shrink-0" className="font-mono text-body-2 w-36 shrink-0"
style={{ color: t.textSecondary }} style={{ color: t.textSecondary }}
> >
{size.label} {size.label}
@ -349,7 +349,7 @@ export const ButtonContent = ({ theme }: ButtonContentProps) => {
<div className="flex-1 flex items-center"> <div className="flex-1 flex items-center">
<button <button
type="button" type="button"
className="rounded-md font-semibold text-sm" className="rounded-md font-semibold text-body-2"
style={{ style={{
height: `${size.heightPx}px`, height: `${size.heightPx}px`,
paddingLeft: `${size.px}px`, paddingLeft: `${size.px}px`,
@ -380,7 +380,10 @@ export const ButtonContent = ({ theme }: ButtonContentProps) => {
<div className="flex flex-col gap-8"> <div className="flex flex-col gap-8">
{/* Flexible */} {/* Flexible */}
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<span className="font-mono text-sm font-bold" style={{ color: t.textPrimary }}> <span
className="font-mono text-body-2 font-bold"
style={{ color: t.textPrimary }}
>
Flexible Flexible
</span> </span>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
@ -461,7 +464,7 @@ export const ButtonContent = ({ theme }: ButtonContentProps) => {
</span> </span>
</div> </div>
</div> </div>
<span className="font-mono text-xs" style={{ color: t.textSecondary }}> <span className="font-mono text-caption" style={{ color: t.textSecondary }}>
. .
</span> </span>
</div> </div>
@ -469,7 +472,10 @@ export const ButtonContent = ({ theme }: ButtonContentProps) => {
{/* Fixed */} {/* Fixed */}
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<span className="font-mono text-sm font-bold" style={{ color: t.textPrimary }}> <span
className="font-mono text-body-2 font-bold"
style={{ color: t.textPrimary }}
>
Fixed Fixed
</span> </span>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
@ -511,7 +517,10 @@ export const ButtonContent = ({ theme }: ButtonContentProps) => {
<div style={{ height: '1px', flex: 1, backgroundColor: annotationColor }} /> <div style={{ height: '1px', flex: 1, backgroundColor: annotationColor }} />
</div> </div>
</div> </div>
<span className="font-mono text-xs ml-4" style={{ color: t.textSecondary }}> <span
className="font-mono text-caption ml-4"
style={{ color: t.textSecondary }}
>
. .
</span> </span>
</div> </div>
@ -533,7 +542,7 @@ export const ButtonContent = ({ theme }: ButtonContentProps) => {
{ resolution: '해상도 320', width: '248px', maxWidth: '248px', padding: 16 }, { resolution: '해상도 320', width: '248px', maxWidth: '248px', padding: 16 },
].map((item) => ( ].map((item) => (
<div key={item.resolution} className="flex flex-col gap-3"> <div key={item.resolution} className="flex flex-col gap-3">
<span className="font-mono text-sm" style={{ color: t.textSecondary }}> <span className="font-mono text-body-2" style={{ color: t.textSecondary }}>
{item.resolution} {item.resolution}
</span> </span>
<div className="flex items-center gap-6"> <div className="flex items-center gap-6">
@ -597,7 +606,7 @@ export const ButtonContent = ({ theme }: ButtonContentProps) => {
{VARIANTS.map((variant) => ( {VARIANTS.map((variant) => (
<th <th
key={variant} key={variant}
className="font-mono text-xs font-semibold text-center pb-4" className="font-mono text-caption font-semibold text-center pb-4"
style={{ style={{
color: t.textSecondary, color: t.textSecondary,
padding: '8px 12px', padding: '8px 12px',
@ -615,7 +624,7 @@ export const ButtonContent = ({ theme }: ButtonContentProps) => {
<tr key={row.state}> <tr key={row.state}>
{/* 상태 라벨 */} {/* 상태 라벨 */}
<td <td
className="font-mono text-xs font-medium" className="font-mono text-caption font-medium"
style={{ style={{
color: t.textSecondary, color: t.textSecondary,
padding: rowIdx === 0 ? '8px 12px 8px 0' : '8px 12px 8px 0', padding: rowIdx === 0 ? '8px 12px 8px 0' : '8px 12px 8px 0',

파일 보기

@ -347,7 +347,7 @@ const TransparencyRow = ({
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className="font-bold text-sm" style={{ color: isDark ? '#dfe2f3' : '#0f172a' }}> <span className="font-bold text-body-2" style={{ color: isDark ? '#dfe2f3' : '#0f172a' }}>
{label} {label}
</span> </span>
<div className="rounded-xl p-4" style={{ backgroundColor: sectionCardBg }}> <div className="rounded-xl p-4" style={{ backgroundColor: sectionCardBg }}>
@ -453,7 +453,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
{/* ── 섹션 1: 헤더 ── */} {/* ── 섹션 1: 헤더 ── */}
<div className="pb-10 mb-12 border-b border-solid" style={{ borderColor: dividerColor }}> <div className="pb-10 mb-12 border-b border-solid" style={{ borderColor: dividerColor }}>
<p <p
className="font-mono text-sm uppercase tracking-widest mb-3" className="font-mono text-body-2 uppercase tracking-widest mb-3"
style={{ color: t.textAccent }} style={{ color: t.textAccent }}
> >
Foundations Foundations
@ -476,7 +476,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<button <button
key={tab} key={tab}
onClick={() => setActiveColorTab(tab)} onClick={() => setActiveColorTab(tab)}
className="px-5 py-2.5 text-sm font-semibold border-none cursor-pointer bg-transparent" className="px-5 py-2.5 text-body-2 font-semibold border-none cursor-pointer bg-transparent"
style={{ style={{
color: isActive ? t.textAccent : t.textMuted, color: isActive ? t.textAccent : t.textMuted,
borderBottom: isActive ? `2px solid ${t.textAccent}` : '2px solid transparent', borderBottom: isActive ? `2px solid ${t.textAccent}` : '2px solid transparent',
@ -505,7 +505,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
> >
--{'{property}'}-{'{role}'}[-{'{variant}'}] --{'{property}'}-{'{role}'}[-{'{variant}'}]
</div> </div>
<p className="text-sm leading-relaxed" style={{ color: t.textSecondary }}> <p className="text-body-2 leading-relaxed" style={{ color: t.textSecondary }}>
{' '} {' '}
<strong style={{ color: t.textPrimary }}>Property-Role-Variant</strong> 3 <strong style={{ color: t.textPrimary }}>Property-Role-Variant</strong> 3
. Property는 CSS , Role은 , Variant는 . Property는 CSS , Role은 , Variant는
@ -551,17 +551,17 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
}} }}
> >
<span <span
className="font-mono text-sm font-bold shrink-0 w-16" className="font-mono text-body-2 font-bold shrink-0 w-16"
style={{ color: t.textAccent }} style={{ color: t.textAccent }}
> >
{row.prop} {row.prop}
</span> </span>
<div className="flex-1"> <div className="flex-1">
<div className="text-sm" style={{ color: t.textSecondary }}> <div className="text-body-2" style={{ color: t.textSecondary }}>
{row.desc} {row.desc}
</div> </div>
</div> </div>
<span className="font-mono text-xs" style={{ color: t.textMuted }}> <span className="font-mono text-caption" style={{ color: t.textMuted }}>
{row.example} {row.example}
</span> </span>
</div> </div>
@ -581,7 +581,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
style={{ border: `1px solid ${dividerColor}` }} style={{ border: `1px solid ${dividerColor}` }}
> >
<div <div
className="px-5 py-3 text-xs font-bold uppercase tracking-wider" className="px-5 py-3 text-caption font-bold uppercase tracking-wider"
style={{ style={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9', backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted, color: t.textMuted,
@ -608,12 +608,12 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
}} }}
> >
<span <span
className="font-mono text-sm font-semibold" className="font-mono text-body-2 font-semibold"
style={{ color: t.textPrimary }} style={{ color: t.textPrimary }}
> >
{row.name} {row.name}
</span> </span>
<span className="text-xs" style={{ color: t.textMuted }}> <span className="text-caption" style={{ color: t.textMuted }}>
{row.desc} {row.desc}
</span> </span>
</div> </div>
@ -626,7 +626,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
style={{ border: `1px solid ${dividerColor}` }} style={{ border: `1px solid ${dividerColor}` }}
> >
<div <div
className="px-5 py-3 text-xs font-bold uppercase tracking-wider" className="px-5 py-3 text-caption font-bold uppercase tracking-wider"
style={{ style={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9', backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted, color: t.textMuted,
@ -650,12 +650,12 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
}} }}
> >
<span <span
className="font-mono text-sm font-semibold" className="font-mono text-body-2 font-semibold"
style={{ color: t.textPrimary }} style={{ color: t.textPrimary }}
> >
{row.name} {row.name}
</span> </span>
<span className="text-xs" style={{ color: t.textMuted }}> <span className="text-caption" style={{ color: t.textMuted }}>
{row.desc} {row.desc}
</span> </span>
</div> </div>
@ -672,7 +672,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}> <h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}>
Semantic Tokens Semantic Tokens
</h2> </h2>
<p className="text-sm mb-6" style={{ color: t.textSecondary }}> <p className="text-body-2 mb-6" style={{ color: t.textSecondary }}>
. . . .
</p> </p>
@ -745,7 +745,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
}, },
].map((group) => ( ].map((group) => (
<div key={group.title} className="mb-8"> <div key={group.title} className="mb-8">
<h3 className="text-sm font-bold mb-3" style={{ color: t.textAccent }}> <h3 className="text-body-2 font-bold mb-3" style={{ color: t.textAccent }}>
{group.title} {group.title}
</h3> </h3>
<div <div
@ -754,7 +754,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
> >
{/* 헤더 */} {/* 헤더 */}
<div <div
className="grid grid-cols-[80px_1fr_1fr_80px_1fr] gap-2 px-4 py-2.5 text-xs font-bold uppercase tracking-wider" className="grid grid-cols-[80px_1fr_1fr_80px_1fr] gap-2 px-4 py-2.5 text-caption font-bold uppercase tracking-wider"
style={{ style={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9', backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted, color: t.textMuted,
@ -776,21 +776,21 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
}} }}
> >
<span <span
className="font-mono text-xs line-through" className="font-mono text-caption line-through"
style={{ color: t.textMuted }} style={{ color: t.textMuted }}
> >
{tk.legacy} {tk.legacy}
</span> </span>
<span <span
className="font-mono text-xs font-semibold" className="font-mono text-caption font-semibold"
style={{ color: t.textPrimary }} style={{ color: t.textPrimary }}
> >
{tk.name} {tk.name}
</span> </span>
<span className="text-xs" style={{ color: t.textMuted }}> <span className="text-caption" style={{ color: t.textMuted }}>
{tk.desc} {tk.desc}
</span> </span>
<span className="font-mono text-xs" style={{ color: t.textSecondary }}> <span className="font-mono text-caption" style={{ color: t.textSecondary }}>
{tk.value} {tk.value}
</span> </span>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -804,7 +804,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
flexShrink: 0, flexShrink: 0,
}} }}
/> />
<span className="font-mono text-xs" style={{ color: t.textMuted }}> <span className="font-mono text-caption" style={{ color: t.textMuted }}>
{hexToRgb(tk.value)} {hexToRgb(tk.value)}
</span> </span>
</div> </div>
@ -823,7 +823,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}> <h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}>
Palette Tokens Palette Tokens
</h2> </h2>
<p className="text-sm mb-6" style={{ color: t.textSecondary }}> <p className="text-body-2 mb-6" style={{ color: t.textSecondary }}>
fg · bg · stroke . Property {' '} fg · bg · stroke . Property {' '}
<code className="font-mono" style={{ color: t.textAccent }}> <code className="font-mono" style={{ color: t.textAccent }}>
--color-* --color-*
@ -836,7 +836,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
style={{ border: `1px solid ${dividerColor}` }} style={{ border: `1px solid ${dividerColor}` }}
> >
<div <div
className="grid grid-cols-[80px_1fr_80px_1fr_1fr] gap-2 px-4 py-2.5 text-xs font-bold uppercase tracking-wider" className="grid grid-cols-[80px_1fr_80px_1fr_1fr] gap-2 px-4 py-2.5 text-caption font-bold uppercase tracking-wider"
style={{ style={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9', backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted, color: t.textMuted,
@ -882,16 +882,19 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
backgroundColor: isDark ? 'rgba(255,255,255,0.02)' : '#fafafa', backgroundColor: isDark ? 'rgba(255,255,255,0.02)' : '#fafafa',
}} }}
> >
<span className="font-mono text-xs line-through" style={{ color: t.textMuted }}> <span
className="font-mono text-caption line-through"
style={{ color: t.textMuted }}
>
{tk.legacy} {tk.legacy}
</span> </span>
<span <span
className="font-mono text-xs font-semibold" className="font-mono text-caption font-semibold"
style={{ color: t.textPrimary }} style={{ color: t.textPrimary }}
> >
{tk.name} {tk.name}
</span> </span>
<span className="font-mono text-xs" style={{ color: t.textSecondary }}> <span className="font-mono text-caption" style={{ color: t.textSecondary }}>
{tk.value} {tk.value}
</span> </span>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -905,11 +908,11 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
flexShrink: 0, flexShrink: 0,
}} }}
/> />
<span className="font-mono text-xs" style={{ color: t.textMuted }}> <span className="font-mono text-caption" style={{ color: t.textMuted }}>
{hexToRgb(tk.value)} {hexToRgb(tk.value)}
</span> </span>
</div> </div>
<span className="text-xs" style={{ color: t.textMuted }}> <span className="text-caption" style={{ color: t.textMuted }}>
{tk.desc} {tk.desc}
</span> </span>
</div> </div>
@ -925,7 +928,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}> <h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}>
Non-color Tokens Non-color Tokens
</h2> </h2>
<p className="text-sm mb-6" style={{ color: t.textSecondary }}> <p className="text-body-2 mb-6" style={{ color: t.textSecondary }}>
, . , .
</p> </p>
@ -934,7 +937,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
style={{ border: `1px solid ${dividerColor}` }} style={{ border: `1px solid ${dividerColor}` }}
> >
<div <div
className="grid grid-cols-[80px_1fr_1fr_1fr] gap-2 px-4 py-2.5 text-xs font-bold uppercase tracking-wider" className="grid grid-cols-[80px_1fr_1fr_1fr] gap-2 px-4 py-2.5 text-caption font-bold uppercase tracking-wider"
style={{ style={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9', backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted, color: t.textMuted,
@ -979,17 +982,20 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
backgroundColor: isDark ? 'rgba(255,255,255,0.02)' : '#fafafa', backgroundColor: isDark ? 'rgba(255,255,255,0.02)' : '#fafafa',
}} }}
> >
<span className="font-mono text-xs line-through" style={{ color: t.textMuted }}> <span
className="font-mono text-caption line-through"
style={{ color: t.textMuted }}
>
{tk.legacy} {tk.legacy}
</span> </span>
<span <span
className="font-mono text-xs font-semibold" className="font-mono text-caption font-semibold"
style={{ color: t.textPrimary }} style={{ color: t.textPrimary }}
> >
{tk.name} {tk.name}
</span> </span>
<span <span
className="text-xs font-mono px-2 py-0.5 rounded" className="text-caption font-mono px-2 py-0.5 rounded"
style={{ style={{
color: t.textAccent, color: t.textAccent,
backgroundColor: isDark ? 'rgba(6,182,212,0.05)' : 'rgba(6,182,212,0.08)', backgroundColor: isDark ? 'rgba(6,182,212,0.05)' : 'rgba(6,182,212,0.08)',
@ -997,7 +1003,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
> >
{tk.category} {tk.category}
</span> </span>
<span className="text-xs" style={{ color: t.textMuted }}> <span className="text-caption" style={{ color: t.textMuted }}>
{tk.desc} {tk.desc}
</span> </span>
</div> </div>
@ -1015,7 +1021,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<h2 className="text-xl font-bold mb-3" style={{ color: t.textPrimary }}> <h2 className="text-xl font-bold mb-3" style={{ color: t.textPrimary }}>
(primary color) (primary color)
</h2> </h2>
<p className="mb-8 text-sm" style={{ color: t.textSecondary }}> <p className="mb-8 text-body-2" style={{ color: t.textSecondary }}>
Primary . Cyan~Blue Primary . Cyan~Blue
. .
</p> </p>
@ -1023,7 +1029,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
{/* Light Mode */} {/* Light Mode */}
<div className="rounded-xl p-6" style={{ backgroundColor: '#f5f5f5' }}> <div className="rounded-xl p-6" style={{ backgroundColor: '#f5f5f5' }}>
<p className="font-bold text-sm mb-4" style={{ color: '#1e293b' }}> <p className="font-bold text-body-2 mb-4" style={{ color: '#1e293b' }}>
Light Mode Light Mode
</p> </p>
<ColorScaleBar <ColorScaleBar
@ -1041,7 +1047,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
{/* Dark Mode */} {/* Dark Mode */}
<div className="rounded-xl p-6" style={{ backgroundColor: '#1a1a2e' }}> <div className="rounded-xl p-6" style={{ backgroundColor: '#1a1a2e' }}>
<p className="font-bold text-sm mb-4" style={{ color: '#fff' }}> <p className="font-bold text-body-2 mb-4" style={{ color: '#fff' }}>
Dark Mode Dark Mode
</p> </p>
<ColorScaleBar <ColorScaleBar
@ -1067,7 +1073,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<h2 className="text-xl font-bold mb-3" style={{ color: t.textPrimary }}> <h2 className="text-xl font-bold mb-3" style={{ color: t.textPrimary }}>
(secondary color) (secondary color)
</h2> </h2>
<p className="mb-8 text-sm" style={{ color: t.textSecondary }}> <p className="mb-8 text-body-2" style={{ color: t.textSecondary }}>
Secondary UI의 . Navy Secondary UI의 . Navy
. .
</p> </p>
@ -1075,7 +1081,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
{/* Light Mode */} {/* Light Mode */}
<div className="rounded-xl p-6" style={{ backgroundColor: '#f5f5f5' }}> <div className="rounded-xl p-6" style={{ backgroundColor: '#f5f5f5' }}>
<p className="font-bold text-sm mb-4" style={{ color: '#1e293b' }}> <p className="font-bold text-body-2 mb-4" style={{ color: '#1e293b' }}>
Light Mode Light Mode
</p> </p>
<ColorScaleBar <ColorScaleBar
@ -1093,7 +1099,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
{/* Dark Mode */} {/* Dark Mode */}
<div className="rounded-xl p-6" style={{ backgroundColor: '#1a1a2e' }}> <div className="rounded-xl p-6" style={{ backgroundColor: '#1a1a2e' }}>
<p className="font-bold text-sm mb-4" style={{ color: '#fff' }}> <p className="font-bold text-body-2 mb-4" style={{ color: '#fff' }}>
Dark Mode Dark Mode
</p> </p>
<ColorScaleBar <ColorScaleBar
@ -1119,11 +1125,11 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<h2 className="text-xl font-bold mb-3" style={{ color: t.textPrimary }}> <h2 className="text-xl font-bold mb-3" style={{ color: t.textPrimary }}>
(gray color) / , (gray color) / ,
</h2> </h2>
<p className="mb-2 text-sm" style={{ color: t.textSecondary }}> <p className="mb-2 text-body-2" style={{ color: t.textSecondary }}>
Gray , , , Gray , , ,
. .
</p> </p>
<p className="mb-8 text-sm" style={{ color: t.textSecondary }}> <p className="mb-8 text-body-2" style={{ color: t.textSecondary }}>
. .
</p> </p>
@ -1141,7 +1147,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<h2 className="text-2xl font-bold mb-2" style={{ color: t.textPrimary }}> <h2 className="text-2xl font-bold mb-2" style={{ color: t.textPrimary }}>
Transparent Transparent
</h2> </h2>
<p className="mb-8 text-sm" style={{ color: t.textSecondary }}> <p className="mb-8 text-body-2" style={{ color: t.textSecondary }}>
. 65% . 65%
. .
</p> </p>
@ -1174,12 +1180,12 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<div className="mb-16"> <div className="mb-16">
<div className="mb-6"> <div className="mb-6">
<p <p
className="font-mono text-sm uppercase tracking-widest mb-1" className="font-mono text-body-2 uppercase tracking-widest mb-1"
style={{ color: t.textAccent }} style={{ color: t.textAccent }}
> >
Primitive Primitive
</p> </p>
<p className="text-sm" style={{ color: t.textMuted }}> <p className="text-body-2" style={{ color: t.textMuted }}>
UI . . UI . .
</p> </p>
</div> </div>
@ -1214,17 +1220,17 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
/> />
{/* 토큰명 */} {/* 토큰명 */}
<span <span
className="font-mono text-sm font-semibold ml-4" className="font-mono text-body-2 font-semibold ml-4"
style={{ color: t.textPrimary }} style={{ color: t.textPrimary }}
> >
{token.name} {token.name}
</span> </span>
{/* HEX + RGB */} {/* HEX + RGB */}
<div className="ml-auto text-right"> <div className="ml-auto text-right">
<div className="font-mono text-sm" style={{ color: t.textPrimary }}> <div className="font-mono text-body-2" style={{ color: t.textPrimary }}>
{token.hex} {token.hex}
</div> </div>
<div className="font-mono text-xs" style={{ color: t.textMuted }}> <div className="font-mono text-caption" style={{ color: t.textMuted }}>
{hexToRgb(token.hex)} {hexToRgb(token.hex)}
</div> </div>
</div> </div>

파일 보기

@ -13,7 +13,7 @@ export const ComponentsContent = () => {
> >
</h1> </h1>
<p className="text-[#bcc9cd] font-korean text-sm leading-5 font-medium max-w-2xl"> <p className="text-[#bcc9cd] font-korean text-body-2 leading-5 font-medium max-w-2xl">
WING-OPS . WING-OPS .
. .
</p> </p>

파일 보기

@ -256,7 +256,7 @@ const ComponentsOverview = ({ theme, onNavigate }: ComponentsOverviewProps) => {
{/* ── 헤더 영역 ── */} {/* ── 헤더 영역 ── */}
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<span <span
className="font-mono text-xs font-semibold uppercase" className="font-mono text-caption font-semibold uppercase"
style={{ letterSpacing: '1.4px', color: t.textAccent }} style={{ letterSpacing: '1.4px', color: t.textAccent }}
> >
Components Components
@ -264,7 +264,7 @@ const ComponentsOverview = ({ theme, onNavigate }: ComponentsOverviewProps) => {
<h1 className="font-sans text-4xl font-bold leading-tight" style={{ color: t.textPrimary }}> <h1 className="font-sans text-4xl font-bold leading-tight" style={{ color: t.textPrimary }}>
Overview Overview
</h1> </h1>
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-6" style={{ color: t.textSecondary }}>
UI . UI .
</p> </p>
</div> </div>
@ -307,7 +307,10 @@ const ComponentsOverview = ({ theme, onNavigate }: ComponentsOverviewProps) => {
{/* 카드 라벨 */} {/* 카드 라벨 */}
<div className="px-5 py-4"> <div className="px-5 py-4">
<span className="font-sans text-sm font-semibold" style={{ color: t.textPrimary }}> <span
className="font-sans text-body-2 font-semibold"
style={{ color: t.textPrimary }}
>
{card.label} {card.label}
</span> </span>
</div> </div>

파일 보기

@ -154,7 +154,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
style={{ backgroundColor: t.textAccent, boxShadow: t.systemActiveShadow }} style={{ backgroundColor: t.textAccent, boxShadow: t.systemActiveShadow }}
/> />
<span <span
className="font-mono text-xs leading-4 uppercase" className="font-mono text-caption leading-4 uppercase"
style={{ letterSpacing: '1.2px', color: t.textAccent }} style={{ letterSpacing: '1.2px', color: t.textAccent }}
> >
System Active System Active
@ -192,11 +192,14 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
/> />
{/* 정보 */} {/* 정보 */}
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<span className="font-mono text-xs leading-4" style={{ color: t.textAccent }}> <span
className="font-mono text-caption leading-4"
style={{ color: t.textAccent }}
>
{item.token} {item.token}
</span> </span>
<span <span
className="font-korean text-sm leading-5 font-bold" className="font-korean text-body-2 leading-5 font-bold"
style={{ color: t.textPrimary }} style={{ color: t.textPrimary }}
> >
{item.hex} {item.hex}
@ -233,7 +236,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
{item.token} {item.token}
</span> </span>
<span <span
className="font-korean text-sm font-bold pb-2" className="font-korean text-body-2 font-bold pb-2"
style={{ color: t.textPrimary }} style={{ color: t.textPrimary }}
> >
{item.hex} {item.hex}
@ -263,7 +266,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
{item.token} {item.token}
</span> </span>
<span className={item.sampleClass}>{item.sampleText}</span> <span className={item.sampleClass}>{item.sampleText}</span>
<span className="font-korean text-xs" style={{ color: item.descColor }}> <span className="font-korean text-caption" style={{ color: item.descColor }}>
{item.desc} {item.desc}
</span> </span>
</div> </div>
@ -297,7 +300,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
/> />
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<span <span
className="font-korean text-sm font-bold" className="font-korean text-body-2 font-bold"
style={{ color: t.textPrimary }} style={{ color: t.textPrimary }}
> >
{item.name} {item.name}
@ -435,7 +438,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
<div className="grid grid-cols-2 gap-8"> <div className="grid grid-cols-2 gap-8">
{/* radius-sm */} {/* radius-sm */}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className="font-mono text-xs leading-4" style={{ color: t.textMuted }}> <span className="font-mono text-caption leading-4" style={{ color: t.textMuted }}>
{t.radiusSmLabel} {t.radiusSmLabel}
</span> </span>
<div <div
@ -453,7 +456,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
Small Elements Small Elements
</span> </span>
<p <p
className="font-korean text-xs leading-[19.5px] mt-1" className="font-korean text-caption leading-[19.5px] mt-1"
style={{ color: t.radiusDescText }} style={{ color: t.radiusDescText }}
> >
Applied to tactical buttons, search inputs, and micro-cards for a precise, sharp Applied to tactical buttons, search inputs, and micro-cards for a precise, sharp
@ -463,7 +466,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
</div> </div>
{/* radius-md */} {/* radius-md */}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className="font-mono text-xs leading-4" style={{ color: t.textMuted }}> <span className="font-mono text-caption leading-4" style={{ color: t.textMuted }}>
{t.radiusMdLabel} {t.radiusMdLabel}
</span> </span>
<div <div
@ -481,7 +484,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
Structural Panels Structural Panels
</span> </span>
<p <p
className="font-korean text-xs leading-[19.5px] mt-1" className="font-korean text-caption leading-[19.5px] mt-1"
style={{ color: t.radiusDescText }} style={{ color: t.radiusDescText }}
> >
Applied to telemetry cards, floating modals, and primary operational panels to Applied to telemetry cards, floating modals, and primary operational panels to

파일 보기

@ -50,7 +50,10 @@ export const FloatContent = ({ theme }: FloatContentProps) => {
<h1 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}> <h1 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
Float Float
</h1> </h1>
<p className="font-korean text-sm leading-5 mt-1" style={{ color: t.textSecondary }}> <p
className="font-korean text-body-2 leading-5 mt-1"
style={{ color: t.textSecondary }}
>
UI Modal, Dropdown, Overlay, Toast UI Modal, Dropdown, Overlay, Toast
</p> </p>
</div> </div>
@ -70,7 +73,7 @@ export const FloatContent = ({ theme }: FloatContentProps) => {
}} }}
> >
<span <span
className="font-sans text-sm font-bold leading-5" className="font-sans text-body-2 font-bold leading-5"
style={{ color: isActive ? t.textAccent : t.textMuted }} style={{ color: isActive ? t.textAccent : t.textMuted }}
> >
{label} {label}

파일 보기

@ -174,7 +174,7 @@ const FoundationsOverview = ({ theme, onNavigate }: FoundationsOverviewProps) =>
{/* ── 헤더 영역 ── */} {/* ── 헤더 영역 ── */}
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<span <span
className="font-mono text-xs font-semibold uppercase" className="font-mono text-caption font-semibold uppercase"
style={{ letterSpacing: '1.4px', color: t.textAccent }} style={{ letterSpacing: '1.4px', color: t.textAccent }}
> >
Foundations Foundations
@ -182,7 +182,7 @@ const FoundationsOverview = ({ theme, onNavigate }: FoundationsOverviewProps) =>
<h1 className="font-sans text-4xl font-bold leading-tight" style={{ color: t.textPrimary }}> <h1 className="font-sans text-4xl font-bold leading-tight" style={{ color: t.textPrimary }}>
Overview Overview
</h1> </h1>
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-6" style={{ color: t.textSecondary }}>
. .
</p> </p>
</div> </div>
@ -225,7 +225,10 @@ const FoundationsOverview = ({ theme, onNavigate }: FoundationsOverviewProps) =>
{/* 카드 라벨 */} {/* 카드 라벨 */}
<div className="px-5 py-4"> <div className="px-5 py-4">
<span className="font-sans text-sm font-semibold" style={{ color: t.textPrimary }}> <span
className="font-sans text-body-2 font-semibold"
style={{ color: t.textPrimary }}
>
{card.label} {card.label}
</span> </span>
</div> </div>

파일 보기

@ -324,7 +324,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
<h1 className="font-sans text-3xl leading-9 font-bold" style={{ color: t.textPrimary }}> <h1 className="font-sans text-3xl leading-9 font-bold" style={{ color: t.textPrimary }}>
Layout Layout
</h1> </h1>
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-6" style={{ color: t.textSecondary }}>
WING-OPS는 . WING-OPS는 .
(100vh), flex . KRDS (100vh), flex . KRDS
xlarge / xxlarge . xlarge / xxlarge .
@ -364,7 +364,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}> <h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
Breakpoint Breakpoint
</h2> </h2>
<p className="font-korean text-sm leading-5" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
. WING-OPS . WING-OPS
(xl, 2xl) cyan으로 . (xl, 2xl) cyan으로 .
</p> </p>
@ -499,7 +499,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
borderColor: isDark ? 'rgba(76,215,246,0.45)' : 'rgba(6,182,212,0.40)', borderColor: isDark ? 'rgba(76,215,246,0.45)' : 'rgba(6,182,212,0.40)',
}} }}
/> />
<span className="font-korean text-xs" style={{ color: t.textSecondary }}> <span className="font-korean text-caption" style={{ color: t.textSecondary }}>
WING-OPS WING-OPS
</span> </span>
</div> </div>
@ -511,7 +511,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
borderColor: isDark ? 'rgba(140,144,159,0.25)' : 'rgba(148,163,184,0.30)', borderColor: isDark ? 'rgba(140,144,159,0.25)' : 'rgba(148,163,184,0.30)',
}} }}
/> />
<span className="font-korean text-xs" style={{ color: t.textSecondary }}> <span className="font-korean text-caption" style={{ color: t.textSecondary }}>
(1280px ) (1280px )
</span> </span>
</div> </div>
@ -525,7 +525,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}> <h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
Grid Grid
</h2> </h2>
<p className="font-korean text-sm leading-5" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
, , . xl / 2xl , , . xl / 2xl
. .
</p> </p>
@ -548,7 +548,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
style={{ borderColor: isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0' }} style={{ borderColor: isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0' }}
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="font-korean text-xs" style={{ color: t.textMuted }}> <span className="font-korean text-caption" style={{ color: t.textMuted }}>
Breakpoint Breakpoint
</span> </span>
<span <span
@ -562,10 +562,10 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
</span> </span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="font-korean text-xs" style={{ color: t.textMuted }}> <span className="font-korean text-caption" style={{ color: t.textMuted }}>
Width Width
</span> </span>
<span className="font-mono text-sm" style={{ color: t.textPrimary }}> <span className="font-mono text-body-2" style={{ color: t.textPrimary }}>
{spec.width} {spec.width}
</span> </span>
</div> </div>
@ -683,7 +683,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
<span className="font-mono text-base" style={{ color: '#f97316' }}> <span className="font-mono text-base" style={{ color: '#f97316' }}>
</span> </span>
<span className="font-korean text-xs" style={{ color: t.textSecondary }}> <span className="font-korean text-caption" style={{ color: t.textSecondary }}>
<strong style={{ color: '#f97316' }}>1280px </strong> Mobile / Tablet <strong style={{ color: '#f97316' }}>1280px </strong> Mobile / Tablet
(xs / s / md / lg) . (xs / s / md / lg) .
</span> </span>
@ -696,7 +696,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}> <h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
App Shell App Shell
</h2> </h2>
<p className="font-korean text-sm leading-5" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
WING-OPS KRDS Sub-page . WING-OPS KRDS Sub-page .
</p> </p>
</div> </div>
@ -720,7 +720,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
}} }}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="font-mono text-sm font-bold" style={{ color: '#3b82f6' }}> <span className="font-mono text-body-2 font-bold" style={{ color: '#3b82f6' }}>
TopBar TopBar
</span> </span>
<span <span
@ -932,7 +932,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}> <h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
Spacing Spacing
</h2> </h2>
<p className="font-korean text-sm leading-5" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
UI . Tailwind UI . Tailwind
spacing , px . spacing , px .
</p> </p>
@ -1000,7 +1000,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}> <h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
4pt Grid 4pt Grid
</h2> </h2>
<p className="font-korean text-sm leading-5" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
4point . , 4point . ,
2 . 2 .
</p> </p>
@ -1048,7 +1048,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
> >
{item.label} {item.label}
</span> </span>
<span className="font-korean text-xs" style={{ color: t.textPrimary }}> <span className="font-korean text-caption" style={{ color: t.textPrimary }}>
{item.text} {item.text}
</span> </span>
</div> </div>
@ -1145,7 +1145,10 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
minWidth: '180px', minWidth: '180px',
}} }}
> >
<span className="font-korean text-xs font-bold" style={{ color: t.textPrimary }}> <span
className="font-korean text-caption font-bold"
style={{ color: t.textPrimary }}
>
</span> </span>
<span className="font-korean text-caption" style={{ color: t.textSecondary }}> <span className="font-korean text-caption" style={{ color: t.textSecondary }}>
@ -1185,7 +1188,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
</span> </span>
</div> </div>
<p className="font-korean text-sm leading-5" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
UI . z-index가 , UI . z-index가 ,
. .
</p> </p>
@ -1216,7 +1219,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
className="w-2 h-2 rounded-full shrink-0" className="w-2 h-2 rounded-full shrink-0"
style={{ backgroundColor: layer.color }} style={{ backgroundColor: layer.color }}
/> />
<span className="font-mono text-xs font-bold" style={{ color: layer.color }}> <span className="font-mono text-caption font-bold" style={{ color: layer.color }}>
{layer.name} {layer.name}
</span> </span>
<span className="font-korean text-caption" style={{ color: t.textSecondary }}> <span className="font-korean text-caption" style={{ color: t.textSecondary }}>
@ -1234,7 +1237,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}> <h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
Reference Reference
</h2> </h2>
<p className="font-korean text-sm leading-5" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
App Shell CSS KRDS Grid . App Shell CSS KRDS Grid .
</p> </p>
</div> </div>

파일 보기

@ -70,17 +70,17 @@ export const RadiusContent = ({ theme }: RadiusContentProps) => {
<h1 className="font-sans text-3xl leading-9 font-bold" style={{ color: t.textPrimary }}> <h1 className="font-sans text-3xl leading-9 font-bold" style={{ color: t.textPrimary }}>
Radius Radius
</h1> </h1>
<p className="font-korean text-sm leading-5" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
Radius는 . Radius는 .
</p> </p>
</div> </div>
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-6" style={{ color: t.textSecondary }}>
Radius는 UI . Radius는 UI .
Radius , , Radius , ,
. .
</p> </p>
<ul <ul
className="flex flex-col gap-1 list-disc list-inside font-korean text-sm" className="flex flex-col gap-1 list-disc list-inside font-korean text-body-2"
style={{ color: t.textSecondary }} style={{ color: t.textSecondary }}
> >
<li> <li>
@ -203,7 +203,7 @@ export const RadiusContent = ({ theme }: RadiusContentProps) => {
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}> <h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
</h2> </h2>
<p className="font-korean text-sm leading-5" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
wing.css Radius . wing.css Radius .
</p> </p>
</div> </div>
@ -233,7 +233,7 @@ export const RadiusContent = ({ theme }: RadiusContentProps) => {
{/* 정보 */} {/* 정보 */}
<div className="flex flex-col gap-1.5 flex-1"> <div className="flex flex-col gap-1.5 flex-1">
<span className="font-mono text-xs font-bold" style={{ color: t.textPrimary }}> <span className="font-mono text-caption font-bold" style={{ color: t.textPrimary }}>
{item.className} {item.className}
</span> </span>
<div className="flex flex-row flex-wrap gap-2"> <div className="flex flex-row flex-wrap gap-2">

파일 보기

@ -207,7 +207,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* ── 섹션 1: 헤더 ── */} {/* ── 섹션 1: 헤더 ── */}
<div className="pb-10 mb-12 border-b border-solid" style={{ borderColor: dividerColor }}> <div className="pb-10 mb-12 border-b border-solid" style={{ borderColor: dividerColor }}>
<p <p
className="font-mono text-sm uppercase tracking-widest mb-3" className="font-mono text-body-2 uppercase tracking-widest mb-3"
style={{ color: t.textAccent }} style={{ color: t.textAccent }}
> >
Components Components
@ -225,7 +225,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
<h2 className="text-2xl font-bold mb-2" style={{ color: t.textPrimary }}> <h2 className="text-2xl font-bold mb-2" style={{ color: t.textPrimary }}>
Input Field Input Field
</h2> </h2>
<p className="text-sm" style={{ color: t.textSecondary }}> <p className="text-body-2" style={{ color: t.textSecondary }}>
. .
</p> </p>
</div> </div>
@ -246,7 +246,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* Prefix label */} {/* Prefix label */}
<div className="flex flex-col items-center gap-0.5" style={{ width: '70px' }}> <div className="flex flex-col items-center gap-0.5" style={{ width: '70px' }}>
<span <span
className="font-mono text-xs font-semibold" className="font-mono text-caption font-semibold"
style={{ color: annotationColor }} style={{ color: annotationColor }}
> >
Prefix Prefix
@ -272,7 +272,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* Input label */} {/* Input label */}
<div className="flex flex-col items-center gap-0.5"> <div className="flex flex-col items-center gap-0.5">
<span <span
className="font-mono text-xs font-semibold" className="font-mono text-caption font-semibold"
style={{ color: annotationColor }} style={{ color: annotationColor }}
> >
Input Input
@ -295,7 +295,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* Suffix label */} {/* Suffix label */}
<div className="flex flex-col items-center gap-0.5" style={{ width: '70px' }}> <div className="flex flex-col items-center gap-0.5" style={{ width: '70px' }}>
<span <span
className="font-mono text-xs font-semibold" className="font-mono text-caption font-semibold"
style={{ color: annotationColor }} style={{ color: annotationColor }}
> >
Suffix Suffix
@ -377,7 +377,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* Suffix 텍스트 */} {/* Suffix 텍스트 */}
<span <span
className="shrink-0 ml-2 font-mono text-sm" className="shrink-0 ml-2 font-mono text-body-2"
style={{ color: fieldPlaceholder }} style={{ color: fieldPlaceholder }}
> >
@ -402,7 +402,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
}} }}
> >
<span <span
className="font-mono text-xs font-semibold" className="font-mono text-caption font-semibold"
style={{ color: annotationColor }} style={{ color: annotationColor }}
> >
Container Container
@ -437,7 +437,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
style={{ width: '1px', height: '10px', backgroundColor: annotationColor }} style={{ width: '1px', height: '10px', backgroundColor: annotationColor }}
/> />
<span <span
className="font-mono text-xs font-semibold" className="font-mono text-caption font-semibold"
style={{ color: annotationColor }} style={{ color: annotationColor }}
> >
Clear Button Clear Button
@ -468,7 +468,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Container Container
</h3> </h3>
</div> </div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}> <p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. , , . . , , .
</p> </p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}> <div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
@ -519,13 +519,13 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
</div> </div>
<div> <div>
<p className="font-mono text-xs" style={{ color: t.textSecondary }}> <p className="font-mono text-caption" style={{ color: t.textSecondary }}>
height: 44px (Medium) height: 44px (Medium)
</p> </p>
<p className="font-mono text-xs mt-1" style={{ color: t.textSecondary }}> <p className="font-mono text-caption mt-1" style={{ color: t.textSecondary }}>
padding: 12px () padding: 12px ()
</p> </p>
<p className="font-mono text-xs mt-1" style={{ color: t.textSecondary }}> <p className="font-mono text-caption mt-1" style={{ color: t.textSecondary }}>
border-radius: 6px border-radius: 6px
</p> </p>
</div> </div>
@ -543,14 +543,14 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Placeholder Placeholder
</h3> </h3>
</div> </div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}> <p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. . . .
</p> </p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}> <div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
<div className="flex gap-6"> <div className="flex gap-6">
{/* 플레이스홀더 있는 필드 */} {/* 플레이스홀더 있는 필드 */}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className="text-xs font-mono" style={{ color: t.textSecondary }}> <span className="text-caption font-mono" style={{ color: t.textSecondary }}>
</span> </span>
<div <div
@ -570,7 +570,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* 빈 필드 (플레이스홀더 없음) */} {/* 빈 필드 (플레이스홀더 없음) */}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className="text-xs font-mono" style={{ color: t.textSecondary }}> <span className="text-caption font-mono" style={{ color: t.textSecondary }}>
</span> </span>
<div <div
@ -597,14 +597,17 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Label Label
</h3> </h3>
</div> </div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}> <p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. * . . * .
</p> </p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}> <div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
<div className="flex gap-8"> <div className="flex gap-8">
{/* 일반 라벨 */} {/* 일반 라벨 */}
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<span className="text-sm font-semibold mb-1.5" style={{ color: t.textPrimary }}> <span
className="text-body-2 font-semibold mb-1.5"
style={{ color: t.textPrimary }}
>
</span> </span>
<div <div
@ -624,7 +627,10 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* 필수 라벨 */} {/* 필수 라벨 */}
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<span className="text-sm font-semibold mb-1.5" style={{ color: t.textPrimary }}> <span
className="text-body-2 font-semibold mb-1.5"
style={{ color: t.textPrimary }}
>
<span style={{ color: '#ef4444' }}>*</span> <span style={{ color: '#ef4444' }}>*</span>
</span> </span>
<div <div
@ -655,7 +661,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Input Text Input Text
</h3> </h3>
</div> </div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}> <p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. . . .
</p> </p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}> <div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
@ -673,7 +679,10 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
> >
</div> </div>
<div className="flex gap-6 text-xs font-mono" style={{ color: t.textSecondary }}> <div
className="flex gap-6 text-caption font-mono"
style={{ color: t.textSecondary }}
>
<span>font-size: 14px</span> <span>font-size: 14px</span>
<span>color: textPrimary</span> <span>color: textPrimary</span>
<span>font-weight: 400</span> <span>font-weight: 400</span>
@ -692,14 +701,14 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Clear Icon Clear Icon
</h3> </h3>
</div> </div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}> <p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. . . .
</p> </p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}> <div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
<div className="flex gap-6"> <div className="flex gap-6">
{/* 텍스트 입력 + Clear 아이콘 표시 */} {/* 텍스트 입력 + Clear 아이콘 표시 */}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className="text-xs font-mono" style={{ color: t.textSecondary }}> <span className="text-caption font-mono" style={{ color: t.textSecondary }}>
(Clear ) (Clear )
</span> </span>
<div <div
@ -736,7 +745,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* 빈 상태 (Clear 미표시) */} {/* 빈 상태 (Clear 미표시) */}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className="text-xs font-mono" style={{ color: t.textSecondary }}> <span className="text-caption font-mono" style={{ color: t.textSecondary }}>
(Clear ) (Clear )
</span> </span>
<div <div
@ -767,7 +776,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Helper Text Helper Text
</h3> </h3>
</div> </div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}> <p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. . . .
</p> </p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}> <div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
@ -787,7 +796,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
> >
</div> </div>
<span className="text-xs" style={{ color: t.textMuted }}> <span className="text-caption" style={{ color: t.textMuted }}>
, 8 , 8
</span> </span>
</div> </div>
@ -807,7 +816,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
> >
</div> </div>
<span className="text-xs" style={{ color: '#ef4444' }}> <span className="text-caption" style={{ color: '#ef4444' }}>
. .
</span> </span>
</div> </div>
@ -828,7 +837,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
<div key={row.state} className="flex items-center gap-8"> <div key={row.state} className="flex items-center gap-8">
{/* 왼쪽: State 라벨 + 뱃지 */} {/* 왼쪽: State 라벨 + 뱃지 */}
<div className="flex items-center gap-2 shrink-0" style={{ width: '200px' }}> <div className="flex items-center gap-2 shrink-0" style={{ width: '200px' }}>
<span className="font-mono text-xs" style={{ color: t.textSecondary }}> <span className="font-mono text-caption" style={{ color: t.textSecondary }}>
State State
</span> </span>
<span <span
@ -891,7 +900,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
<h2 className="text-2xl font-bold mb-2" style={{ color: t.textPrimary }}> <h2 className="text-2xl font-bold mb-2" style={{ color: t.textPrimary }}>
Text Area Text Area
</h2> </h2>
<p className="text-sm" style={{ color: t.textSecondary }}> <p className="text-body-2" style={{ color: t.textSecondary }}>
. .
</p> </p>
</div> </div>
@ -911,7 +920,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* Input Area label */} {/* Input Area label */}
<div className="flex flex-col items-center gap-0.5" style={{ width: '80px' }}> <div className="flex flex-col items-center gap-0.5" style={{ width: '80px' }}>
<span <span
className="font-mono text-xs font-semibold" className="font-mono text-caption font-semibold"
style={{ color: annotationColor }} style={{ color: annotationColor }}
> >
Input Area Input Area
@ -933,7 +942,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* Placeholder label */} {/* Placeholder label */}
<div className="flex flex-col items-center gap-0.5"> <div className="flex flex-col items-center gap-0.5">
<span <span
className="font-mono text-xs font-semibold" className="font-mono text-caption font-semibold"
style={{ color: annotationColor }} style={{ color: annotationColor }}
> >
Placeholder Placeholder
@ -955,7 +964,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* Character Counter label */} {/* Character Counter label */}
<div className="flex flex-col items-center gap-0.5" style={{ width: '110px' }}> <div className="flex flex-col items-center gap-0.5" style={{ width: '110px' }}>
<span <span
className="font-mono text-xs font-semibold" className="font-mono text-caption font-semibold"
style={{ color: annotationColor }} style={{ color: annotationColor }}
> >
Character Counter Character Counter
@ -1042,7 +1051,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
}} }}
> >
<span <span
className="font-mono text-xs font-semibold" className="font-mono text-caption font-semibold"
style={{ color: annotationColor }} style={{ color: annotationColor }}
> >
Container Container
@ -1075,7 +1084,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
style={{ width: '1px', height: '10px', backgroundColor: annotationColor }} style={{ width: '1px', height: '10px', backgroundColor: annotationColor }}
/> />
<span <span
className="font-mono text-xs font-semibold" className="font-mono text-caption font-semibold"
style={{ color: annotationColor }} style={{ color: annotationColor }}
> >
Resize Handle Resize Handle
@ -1103,7 +1112,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Container Container
</h3> </h3>
</div> </div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}> <p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. 112px이며 . 112px이며
. .
</p> </p>
@ -1154,16 +1163,16 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
</div> </div>
<div className="pt-2"> <div className="pt-2">
<p className="font-mono text-xs" style={{ color: t.textSecondary }}> <p className="font-mono text-caption" style={{ color: t.textSecondary }}>
height: 112px (default) height: 112px (default)
</p> </p>
<p className="font-mono text-xs mt-1" style={{ color: t.textSecondary }}> <p className="font-mono text-caption mt-1" style={{ color: t.textSecondary }}>
padding: 12px padding: 12px
</p> </p>
<p className="font-mono text-xs mt-1" style={{ color: t.textSecondary }}> <p className="font-mono text-caption mt-1" style={{ color: t.textSecondary }}>
border-radius: 6px border-radius: 6px
</p> </p>
<p className="font-mono text-xs mt-1" style={{ color: t.textSecondary }}> <p className="font-mono text-caption mt-1" style={{ color: t.textSecondary }}>
resize: vertical resize: vertical
</p> </p>
</div> </div>
@ -1181,14 +1190,14 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Placeholder Placeholder
</h3> </h3>
</div> </div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}> <p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. .
</p> </p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}> <div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
<div className="flex gap-6"> <div className="flex gap-6">
{/* 플레이스홀더 있는 TextArea */} {/* 플레이스홀더 있는 TextArea */}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className="text-xs font-mono" style={{ color: t.textSecondary }}> <span className="text-caption font-mono" style={{ color: t.textSecondary }}>
</span> </span>
<div <div
@ -1209,7 +1218,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* 빈 TextArea */} {/* 빈 TextArea */}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className="text-xs font-mono" style={{ color: t.textSecondary }}> <span className="text-caption font-mono" style={{ color: t.textSecondary }}>
</span> </span>
<div <div
@ -1236,14 +1245,17 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Label Label
</h3> </h3>
</div> </div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}> <p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. .
</p> </p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}> <div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
<div className="flex gap-8"> <div className="flex gap-8">
{/* 기본 라벨 */} {/* 기본 라벨 */}
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<span className="text-sm font-semibold mb-1.5" style={{ color: t.textPrimary }}> <span
className="text-body-2 font-semibold mb-1.5"
style={{ color: t.textPrimary }}
>
</span> </span>
<div <div
@ -1264,7 +1276,10 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* 필수(*) 라벨 */} {/* 필수(*) 라벨 */}
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<span className="text-sm font-semibold mb-1.5" style={{ color: t.textPrimary }}> <span
className="text-body-2 font-semibold mb-1.5"
style={{ color: t.textPrimary }}
>
<span style={{ color: '#ef4444' }}>*</span> <span style={{ color: '#ef4444' }}>*</span>
</span> </span>
<div <div
@ -1296,7 +1311,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Input Text Input Text
</h3> </h3>
</div> </div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}> <p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. .
</p> </p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}> <div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
@ -1316,7 +1331,10 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
> >
{'오늘 점검 내용을 기록합니다.\n상세 내용은 아래와 같습니다.'} {'오늘 점검 내용을 기록합니다.\n상세 내용은 아래와 같습니다.'}
</div> </div>
<div className="flex gap-6 text-xs font-mono" style={{ color: t.textSecondary }}> <div
className="flex gap-6 text-caption font-mono"
style={{ color: t.textSecondary }}
>
<span>font-size: 14px</span> <span>font-size: 14px</span>
<span>color: textPrimary</span> <span>color: textPrimary</span>
<span>line-height: 1.6</span> <span>line-height: 1.6</span>
@ -1335,14 +1353,14 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Clear Icon Clear Icon
</h3> </h3>
</div> </div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}> <p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. . . .
</p> </p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}> <div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
<div className="flex gap-6"> <div className="flex gap-6">
{/* 텍스트 있는 상태 (Clear 표시) */} {/* 텍스트 있는 상태 (Clear 표시) */}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className="text-xs font-mono" style={{ color: t.textSecondary }}> <span className="text-caption font-mono" style={{ color: t.textSecondary }}>
(Clear ) (Clear )
</span> </span>
<div <div
@ -1380,7 +1398,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* 빈 상태 (Clear 미표시) */} {/* 빈 상태 (Clear 미표시) */}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className="text-xs font-mono" style={{ color: t.textSecondary }}> <span className="text-caption font-mono" style={{ color: t.textSecondary }}>
(Clear ) (Clear )
</span> </span>
<div <div
@ -1412,7 +1430,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Helper Text Helper Text
</h3> </h3>
</div> </div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}> <p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. .
</p> </p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}> <div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
@ -1434,10 +1452,10 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
</div> </div>
<div className="flex items-center justify-between" style={{ width: '240px' }}> <div className="flex items-center justify-between" style={{ width: '240px' }}>
<span className="text-xs" style={{ color: t.textMuted }}> <span className="text-caption" style={{ color: t.textMuted }}>
</span> </span>
<span className="text-xs font-mono" style={{ color: t.textMuted }}> <span className="text-caption font-mono" style={{ color: t.textMuted }}>
0/500 0/500
</span> </span>
</div> </div>
@ -1459,7 +1477,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
> >
</div> </div>
<span className="text-xs" style={{ color: '#ef4444' }}> <span className="text-caption" style={{ color: '#ef4444' }}>
. .
</span> </span>
</div> </div>
@ -1480,7 +1498,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
<div key={`ta-${row.state}`} className="flex items-start gap-8"> <div key={`ta-${row.state}`} className="flex items-start gap-8">
{/* 왼쪽: State 라벨 + 뱃지 */} {/* 왼쪽: State 라벨 + 뱃지 */}
<div className="flex items-center gap-2 shrink-0 pt-3" style={{ width: '200px' }}> <div className="flex items-center gap-2 shrink-0 pt-3" style={{ width: '200px' }}>
<span className="font-mono text-xs" style={{ color: t.textSecondary }}> <span className="font-mono text-caption" style={{ color: t.textSecondary }}>
State State
</span> </span>
<span <span

파일 보기

@ -307,18 +307,18 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
<h1 className="font-sans text-3xl leading-9 font-bold" style={{ color: t.textPrimary }}> <h1 className="font-sans text-3xl leading-9 font-bold" style={{ color: t.textPrimary }}>
Typography Typography
</h1> </h1>
<p className="font-korean text-sm leading-5" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
WING-OPS . , , WING-OPS . , ,
. .
</p> </p>
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<h3 className="font-korean text-sm font-bold" style={{ color: t.textPrimary }}> <h3 className="font-korean text-body-2 font-bold" style={{ color: t.textPrimary }}>
</h3> </h3>
<ul <ul
className="flex flex-col gap-1 list-disc list-inside font-korean text-sm" className="flex flex-col gap-1 list-disc list-inside font-korean text-body-2"
style={{ color: t.textSecondary }} style={{ color: t.textSecondary }}
> >
<li> <li>
@ -337,7 +337,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}> <h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
</h2> </h2>
<p className="font-korean text-sm leading-5" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
, . , .
UI에 . UI에 .
</p> </p>
@ -352,7 +352,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
}} }}
> >
<pre <pre
className="font-mono text-sm leading-6" className="font-mono text-body-2 leading-6"
style={{ color: isDark ? '#c0c8dc' : '#475569' }} style={{ color: isDark ? '#c0c8dc' : '#475569' }}
> >
<span style={{ color: t.textAccent }}>font-family</span> <span style={{ color: t.textAccent }}>font-family</span>
@ -390,7 +390,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
</div> </div>
<div className="px-5 py-5 flex flex-col gap-4"> <div className="px-5 py-5 flex flex-col gap-4">
<div <div
className="font-mono text-xs leading-5 rounded px-3 py-2" className="font-mono text-caption leading-5 rounded px-3 py-2"
style={{ style={{
color: isDark ? '#9ba3b8' : '#64748b', color: isDark ? '#9ba3b8' : '#64748b',
backgroundColor: isDark ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.02)', backgroundColor: isDark ? 'rgba(255,255,255,0.02)' : 'rgba(0,0,0,0.02)',
@ -398,7 +398,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
> >
{font.stack} {font.stack}
</div> </div>
<p className="font-korean text-xs leading-5" style={{ color: t.textSecondary }}>{font.usage}</p> <p className="font-korean text-caption leading-5" style={{ color: t.textSecondary }}>{font.usage}</p>
<div className="flex flex-col gap-3 pt-2"> <div className="flex flex-col gap-3 pt-2">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<span className="font-mono text-caption uppercase" style={{ letterSpacing: '1px', color: t.textMuted }}>Regular</span> <span className="font-mono text-caption uppercase" style={{ letterSpacing: '1px', color: t.textMuted }}>Regular</span>
@ -421,7 +421,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}> <h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
</h2> </h2>
<p className="font-korean text-sm leading-5" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-5" style={{ color: t.textSecondary }}>
Display, Heading, Body, Navigation, Label의 5 Display, Heading, Body, Navigation, Label의 5
. .
</p> </p>
@ -434,7 +434,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
<h3 className="font-sans text-lg font-bold" style={{ color: t.textPrimary }}> <h3 className="font-sans text-lg font-bold" style={{ color: t.textPrimary }}>
{category.name} {category.name}
</h3> </h3>
<p className="font-korean text-xs leading-5" style={{ color: t.textSecondary }}> <p className="font-korean text-caption leading-5" style={{ color: t.textSecondary }}>
{category.description} {category.description}
</p> </p>
</div> </div>
@ -445,7 +445,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
style={{ border: `1px solid ${isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0'}` }} style={{ border: `1px solid ${isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0'}` }}
> >
<div <div
className="grid grid-cols-[1fr_80px_80px_80px_90px_140px] gap-2 px-4 py-2.5 text-xs font-bold uppercase tracking-wider" className="grid grid-cols-[1fr_80px_80px_80px_90px_140px] gap-2 px-4 py-2.5 text-caption font-bold uppercase tracking-wider"
style={{ style={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9', backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted, color: t.textMuted,
@ -467,20 +467,23 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
backgroundColor: isDark ? 'rgba(255,255,255,0.02)' : '#fafafa', backgroundColor: isDark ? 'rgba(255,255,255,0.02)' : '#fafafa',
}} }}
> >
<span className="font-mono text-xs font-semibold" style={{ color: t.textAccent }}> <span
className="font-mono text-caption font-semibold"
style={{ color: t.textAccent }}
>
{row.token} {row.token}
</span> </span>
<span className="font-mono text-xs" style={{ color: t.textPrimary }}> <span className="font-mono text-caption" style={{ color: t.textPrimary }}>
{row.px} {row.px}
</span> </span>
<span className="font-mono text-xs" style={{ color: t.textPrimary }}> <span className="font-mono text-caption" style={{ color: t.textPrimary }}>
{row.weight} {row.weight}
</span> </span>
<span <span
className="font-mono text-xs" className="font-mono text-caption"
style={{ color: t.textMuted }} style={{ color: t.textMuted }}
>{`${(row.lineHeight * 100).toFixed(0)}%`}</span> >{`${(row.lineHeight * 100).toFixed(0)}%`}</span>
<span className="font-mono text-xs" style={{ color: t.textMuted }}> <span className="font-mono text-caption" style={{ color: t.textMuted }}>
{row.letterSpacing} {row.letterSpacing}
</span> </span>
<span <span
@ -510,7 +513,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
> >
<div className="shrink-0 w-[100px]"> <div className="shrink-0 w-[100px]">
<div <div
className="font-mono text-xs font-semibold" className="font-mono text-caption font-semibold"
style={{ color: t.textAccent }} style={{ color: t.textAccent }}
> >
{row.token} {row.token}
@ -533,7 +536,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
{row.sample} {row.sample}
</span> </span>
</div> </div>
<div className="shrink-0 text-xs font-korean" style={{ color: t.textMuted }}> <div className="shrink-0 text-caption font-korean" style={{ color: t.textMuted }}>
{row.role} {row.role}
</div> </div>
</div> </div>
@ -551,7 +554,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
<h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}> <h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}>
</h2> </h2>
<p className="text-sm" style={{ color: t.textSecondary }}> <p className="text-body-2" style={{ color: t.textSecondary }}>
Regular(400), Bold(700). Medium(500) , Thin(300) Regular(400), Bold(700). Medium(500) , Thin(300)
. .
</p> </p>
@ -561,7 +564,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
style={{ border: `1px solid ${isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0'}` }} style={{ border: `1px solid ${isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0'}` }}
> >
<div <div
className="grid grid-cols-[1fr_80px_1fr_1fr] gap-2 px-4 py-2.5 text-xs font-bold uppercase tracking-wider" className="grid grid-cols-[1fr_80px_1fr_1fr] gap-2 px-4 py-2.5 text-caption font-bold uppercase tracking-wider"
style={{ style={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9', backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted, color: t.textMuted,
@ -606,17 +609,20 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
backgroundColor: isDark ? 'rgba(255,255,255,0.02)' : '#fafafa', backgroundColor: isDark ? 'rgba(255,255,255,0.02)' : '#fafafa',
}} }}
> >
<span className="font-mono text-xs font-semibold" style={{ color: t.textAccent }}> <span
className="font-mono text-caption font-semibold"
style={{ color: t.textAccent }}
>
{row.token} {row.token}
</span> </span>
<span className="font-mono text-xs" style={{ color: t.textPrimary }}> <span className="font-mono text-caption" style={{ color: t.textPrimary }}>
{row.value} {row.value}
</span> </span>
<span className="text-xs" style={{ color: t.textSecondary }}> <span className="text-caption" style={{ color: t.textSecondary }}>
{row.name} {row.name}
</span> </span>
<span <span
className="font-korean text-sm" className="font-korean text-body-2"
style={{ color: t.textPrimary, fontWeight: Number(row.value) }} style={{ color: t.textPrimary, fontWeight: Number(row.value) }}
> >
{row.preview} {row.preview}
@ -632,7 +638,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
<h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}> <h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}>
</h2> </h2>
<p className="text-sm" style={{ color: t.textSecondary }}> <p className="text-body-2" style={{ color: t.textSecondary }}>
(1.3), (1.6). (1.3), (1.6).
. .
</p> </p>
@ -642,7 +648,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
style={{ border: `1px solid ${isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0'}` }} style={{ border: `1px solid ${isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0'}` }}
> >
<div <div
className="grid grid-cols-[1fr_80px_100px_1fr] gap-2 px-4 py-2.5 text-xs font-bold uppercase tracking-wider" className="grid grid-cols-[1fr_80px_100px_1fr] gap-2 px-4 py-2.5 text-caption font-bold uppercase tracking-wider"
style={{ style={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9', backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted, color: t.textMuted,
@ -677,16 +683,19 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
backgroundColor: isDark ? 'rgba(255,255,255,0.02)' : '#fafafa', backgroundColor: isDark ? 'rgba(255,255,255,0.02)' : '#fafafa',
}} }}
> >
<span className="font-mono text-xs font-semibold" style={{ color: t.textAccent }}> <span
className="font-mono text-caption font-semibold"
style={{ color: t.textAccent }}
>
{row.token} {row.token}
</span> </span>
<span className="font-mono text-xs" style={{ color: t.textPrimary }}> <span className="font-mono text-caption" style={{ color: t.textPrimary }}>
{row.value} {row.value}
</span> </span>
<span className="font-mono text-xs" style={{ color: t.textMuted }}> <span className="font-mono text-caption" style={{ color: t.textMuted }}>
{row.pct} {row.pct}
</span> </span>
<span className="text-xs" style={{ color: t.textSecondary }}> <span className="text-caption" style={{ color: t.textSecondary }}>
{row.desc} {row.desc}
</span> </span>
</div> </div>
@ -700,7 +709,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
<h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}> <h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}>
</h2> </h2>
<p className="text-sm" style={{ color: t.textSecondary }}> <p className="text-body-2" style={{ color: t.textSecondary }}>
. Display는 , Body는 . . Display는 , Body는 .
</p> </p>
</div> </div>
@ -709,7 +718,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
style={{ border: `1px solid ${isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0'}` }} style={{ border: `1px solid ${isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0'}` }}
> >
<div <div
className="grid grid-cols-[1fr_80px_140px_1fr] gap-2 px-4 py-2.5 text-xs font-bold uppercase tracking-wider" className="grid grid-cols-[1fr_80px_140px_1fr] gap-2 px-4 py-2.5 text-caption font-bold uppercase tracking-wider"
style={{ style={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9', backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted, color: t.textMuted,
@ -760,10 +769,13 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
backgroundColor: isDark ? 'rgba(255,255,255,0.02)' : '#fafafa', backgroundColor: isDark ? 'rgba(255,255,255,0.02)' : '#fafafa',
}} }}
> >
<span className="font-mono text-xs font-semibold" style={{ color: t.textAccent }}> <span
className="font-mono text-caption font-semibold"
style={{ color: t.textAccent }}
>
{row.token} {row.token}
</span> </span>
<span className="font-mono text-xs" style={{ color: t.textPrimary }}> <span className="font-mono text-caption" style={{ color: t.textPrimary }}>
{row.value} {row.value}
</span> </span>
<span <span
@ -776,7 +788,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
> >
{row.tw} {row.tw}
</span> </span>
<span className="text-xs" style={{ color: t.textSecondary }}> <span className="text-caption" style={{ color: t.textSecondary }}>
{row.category} {row.category}
</span> </span>
</div> </div>

파일 보기

@ -167,7 +167,7 @@ export const ButtonCatalogSection = () => {
<div className="border-b border-solid border-[#1e2a42] pt-4 pr-6 pb-4 pl-6 flex flex-row gap-3 items-center justify-start self-stretch shrink-0 relative"> <div className="border-b border-solid border-[#1e2a42] pt-4 pr-6 pb-4 pl-6 flex flex-row gap-3 items-center justify-start self-stretch shrink-0 relative">
<div className="bg-[#06b6d4] rounded-xl shrink-0 w-1 h-4 relative" /> <div className="bg-[#06b6d4] rounded-xl shrink-0 w-1 h-4 relative" />
<div <div
className="text-[#22d3ee] text-left font-korean text-xs leading-4 font-medium uppercase relative flex items-center justify-start" className="text-[#22d3ee] text-left font-korean text-caption leading-4 font-medium uppercase relative flex items-center justify-start"
style={{ letterSpacing: '1.2px' }} style={{ letterSpacing: '1.2px' }}
> >
인터페이스: 버튼 인터페이스: 버튼
@ -185,7 +185,7 @@ export const ButtonCatalogSection = () => {
className="pt-px pr-2 pb-[17.5px] pl-2 flex flex-col gap-0 items-start justify-start flex-1 min-w-0 relative" className="pt-px pr-2 pb-[17.5px] pl-2 flex flex-col gap-0 items-start justify-start flex-1 min-w-0 relative"
> >
<div <div
className="text-[#64748b] text-left font-korean text-xs font-medium uppercase relative flex items-center justify-start" className="text-[#64748b] text-left font-korean text-caption font-medium uppercase relative flex items-center justify-start"
style={{ letterSpacing: '-0.55px' }} style={{ letterSpacing: '-0.55px' }}
> >
{header} {header}
@ -207,7 +207,7 @@ export const ButtonCatalogSection = () => {
> >
{/* 버튼 유형 레이블 */} {/* 버튼 유형 레이블 */}
<div className="pt-[31.5px] pr-2 pb-[31.5px] pl-2 flex flex-col gap-0 items-start justify-start flex-1 min-w-0 relative"> <div className="pt-[31.5px] pr-2 pb-[31.5px] pl-2 flex flex-col gap-0 items-start justify-start flex-1 min-w-0 relative">
<div className="text-[#bcc9cd] text-left font-korean text-xs font-medium relative flex items-center justify-start"> <div className="text-[#bcc9cd] text-left font-korean text-caption font-medium relative flex items-center justify-start">
{row.label} {row.label}
</div> </div>
</div> </div>

파일 보기

@ -129,7 +129,7 @@ export const CardSection = () => {
<div className="text-white text-left font-sans font-bold text-4xl leading-10 relative flex items-center justify-start"> <div className="text-white text-left font-sans font-bold text-4xl leading-10 relative flex items-center justify-start">
24.8 24.8
</div> </div>
<div className="text-[#64748b] text-left font-sans font-semibold text-sm leading-5 relative flex items-center justify-start"> <div className="text-[#64748b] text-left font-sans font-semibold text-body-2 leading-5 relative flex items-center justify-start">
(knots) (knots)
</div> </div>
</div> </div>

파일 보기

@ -61,7 +61,7 @@ export const IconBadgeSection = () => {
<div className="bg-[#e89337] rounded-xl shrink-0 w-1 h-4 relative"></div> <div className="bg-[#e89337] rounded-xl shrink-0 w-1 h-4 relative"></div>
<div className="flex flex-col gap-0 items-start justify-start shrink-0 relative"> <div className="flex flex-col gap-0 items-start justify-start shrink-0 relative">
<div <div
className="text-[#22d3ee] text-left font-korean text-xs leading-4 font-medium uppercase relative flex items-center justify-start" className="text-[#22d3ee] text-left font-korean text-caption leading-4 font-medium uppercase relative flex items-center justify-start"
style={{ letterSpacing: '1.2px' }} style={{ letterSpacing: '1.2px' }}
> >
컨트롤: 아이콘 컨트롤: 아이콘
@ -113,7 +113,7 @@ export const IconBadgeSection = () => {
<div className="bg-[#93000a] rounded-xl shrink-0 w-1 h-4 relative"></div> <div className="bg-[#93000a] rounded-xl shrink-0 w-1 h-4 relative"></div>
<div className="flex flex-col gap-0 items-start justify-start shrink-0 relative"> <div className="flex flex-col gap-0 items-start justify-start shrink-0 relative">
<div <div
className="text-[#22d3ee] text-left font-korean text-xs leading-4 font-medium uppercase relative flex items-center justify-start" className="text-[#22d3ee] text-left font-korean text-caption leading-4 font-medium uppercase relative flex items-center justify-start"
style={{ letterSpacing: '1.2px' }} style={{ letterSpacing: '1.2px' }}
> >
컨트롤: 아이콘 컨트롤: 아이콘

파일 보기

@ -36,10 +36,10 @@ export const FloatDropdownContent = ({ theme }: FloatDropdownContentProps) => {
<h2 className="font-sans text-xl font-bold" style={{ color: t.textPrimary }}> <h2 className="font-sans text-xl font-bold" style={{ color: t.textPrimary }}>
Dropdown Dropdown
</h2> </h2>
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-6" style={{ color: t.textSecondary }}>
{' '} {' '}
<code <code
className="font-mono text-xs px-1.5 py-0.5 rounded" className="font-mono text-caption px-1.5 py-0.5 rounded"
style={{ style={{
backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)', backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)',
color: t.textAccent, color: t.textAccent,
@ -50,7 +50,7 @@ export const FloatDropdownContent = ({ theme }: FloatDropdownContentProps) => {
. 5 . . 5 .
{' '} {' '}
<code <code
className="font-mono text-xs px-1.5 py-0.5 rounded" className="font-mono text-caption px-1.5 py-0.5 rounded"
style={{ style={{
backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)', backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)',
color: t.textAccent, color: t.textAccent,
@ -84,7 +84,7 @@ export const FloatDropdownContent = ({ theme }: FloatDropdownContentProps) => {
> >
<div className="grid grid-cols-2 gap-6"> <div className="grid grid-cols-2 gap-6">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className="font-korean text-xs" style={{ color: t.textMuted }}> <span className="font-korean text-caption" style={{ color: t.textMuted }}>
</span> </span>
<ComboBox <ComboBox
@ -98,7 +98,7 @@ export const FloatDropdownContent = ({ theme }: FloatDropdownContentProps) => {
</span> </span>
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<span className="font-korean text-xs" style={{ color: t.textMuted }}> <span className="font-korean text-caption" style={{ color: t.textMuted }}>
</span> </span>
<ComboBox <ComboBox
@ -112,7 +112,7 @@ export const FloatDropdownContent = ({ theme }: FloatDropdownContentProps) => {
</span> </span>
</div> </div>
</div> </div>
<p className="font-korean text-xs" style={{ color: t.textMuted }}> <p className="font-korean text-caption" style={{ color: t.textMuted }}>
{' '} {' '}
<code className="font-mono" style={{ color: t.textAccent }}> <code className="font-mono" style={{ color: t.textAccent }}>
@common/components/ui/ComboBox @common/components/ui/ComboBox
@ -373,7 +373,7 @@ export const FloatDropdownContent = ({ theme }: FloatDropdownContentProps) => {
</span> </span>
</div> </div>
<div className="py-2.5 px-4"> <div className="py-2.5 px-4">
<span className="font-korean text-xs" style={{ color: t.textSecondary }}> <span className="font-korean text-caption" style={{ color: t.textSecondary }}>
{row.desc} {row.desc}
</span> </span>
</div> </div>
@ -423,10 +423,16 @@ export const FloatDropdownContent = ({ theme }: FloatDropdownContentProps) => {
: t.cardBorder, : t.cardBorder,
}} }}
> >
<span className="font-korean text-sm font-medium" style={{ color: t.textPrimary }}> <span
className="font-korean text-body-2 font-medium"
style={{ color: t.textPrimary }}
>
{item.title} {item.title}
</span> </span>
<span className="font-korean text-xs leading-5" style={{ color: t.textSecondary }}> <span
className="font-korean text-caption leading-5"
style={{ color: t.textSecondary }}
>
{item.desc} {item.desc}
</span> </span>
</div> </div>

파일 보기

@ -88,9 +88,9 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
<h2 className="font-sans text-xl font-bold" style={{ color: t.textPrimary }}> <h2 className="font-sans text-xl font-bold" style={{ color: t.textPrimary }}>
Modal Modal
</h2> </h2>
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-6" style={{ color: t.textSecondary }}>
<code <code
className="font-mono text-xs px-1.5 py-0.5 rounded" className="font-mono text-caption px-1.5 py-0.5 rounded"
style={{ style={{
backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)', backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)',
color: t.textAccent, color: t.textAccent,
@ -156,7 +156,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
</button> </button>
))} ))}
</div> </div>
<span className="font-korean text-xs" style={{ color: t.textMuted }}> <span className="font-korean text-caption" style={{ color: t.textMuted }}>
{SIZE_CONFIG[activeSize].desc} {SIZE_CONFIG[activeSize].desc}
</span> </span>
</div> </div>
@ -166,7 +166,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
<button <button
type="button" type="button"
onClick={() => setIsModalOpen(true)} onClick={() => setIsModalOpen(true)}
className="px-4 py-2 rounded border border-solid font-korean text-sm font-medium transition-opacity hover:opacity-80" className="px-4 py-2 rounded border border-solid font-korean text-body-2 font-medium transition-opacity hover:opacity-80"
style={{ style={{
backgroundColor: isDark ? 'rgba(76,215,246,0.15)' : 'rgba(6,182,212,0.12)', backgroundColor: isDark ? 'rgba(76,215,246,0.15)' : 'rgba(6,182,212,0.12)',
borderColor: t.textAccent, borderColor: t.textAccent,
@ -178,7 +178,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
<button <button
type="button" type="button"
onClick={() => setIsConfirmOpen(true)} onClick={() => setIsConfirmOpen(true)}
className="px-4 py-2 rounded border border-solid font-korean text-sm font-medium transition-opacity hover:opacity-80" className="px-4 py-2 rounded border border-solid font-korean text-body-2 font-medium transition-opacity hover:opacity-80"
style={{ style={{
backgroundColor: isDark ? 'rgba(239,68,68,0.10)' : 'rgba(239,68,68,0.06)', backgroundColor: isDark ? 'rgba(239,68,68,0.10)' : 'rgba(239,68,68,0.06)',
borderColor: 'rgba(239,68,68,0.40)', borderColor: 'rgba(239,68,68,0.40)',
@ -200,7 +200,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
className="rounded-lg border border-solid p-5 flex flex-col gap-3" className="rounded-lg border border-solid p-5 flex flex-col gap-3"
style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder }} style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder }}
> >
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-6" style={{ color: t.textSecondary }}>
Confirm은 <strong>Modal의 variant</strong>. + Confirm은 <strong>Modal의 variant</strong>. +
+ / 2 . (, ) . + / 2 . (, ) .
</p> </p>
@ -423,7 +423,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder }} style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder }}
> >
<span <span
className="font-mono text-sm rounded border border-solid px-2 py-0.5 shrink-0" className="font-mono text-body-2 rounded border border-solid px-2 py-0.5 shrink-0"
style={{ color: t.textAccent, borderColor: t.cardBorder }} style={{ color: t.textAccent, borderColor: t.cardBorder }}
> >
{row.range} {row.range}
@ -434,7 +434,10 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
> >
{row.status} {row.status}
</span> </span>
<span className="font-korean text-xs leading-5" style={{ color: t.textSecondary }}> <span
className="font-korean text-caption leading-5"
style={{ color: t.textSecondary }}
>
{row.desc} {row.desc}
</span> </span>
</div> </div>
@ -480,7 +483,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
}} }}
> >
<div className="py-2.5 px-4"> <div className="py-2.5 px-4">
<span className="font-mono text-xs" style={{ color: t.textPrimary }}> <span className="font-mono text-caption" style={{ color: t.textPrimary }}>
{item.component} {item.component}
</span> </span>
</div> </div>
@ -496,7 +499,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
</span> </span>
</div> </div>
<div className="py-2.5 px-4"> <div className="py-2.5 px-4">
<span className="font-korean text-xs" style={{ color: t.textSecondary }}> <span className="font-korean text-caption" style={{ color: t.textSecondary }}>
{item.trigger} {item.trigger}
</span> </span>
</div> </div>
@ -533,7 +536,10 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
className="flex items-center justify-between px-5 py-4 border-b border-solid shrink-0" className="flex items-center justify-between px-5 py-4 border-b border-solid shrink-0"
style={{ borderColor: modalBorder }} style={{ borderColor: modalBorder }}
> >
<span className="font-korean text-sm font-medium" style={{ color: t.textPrimary }}> <span
className="font-korean text-body-2 font-medium"
style={{ color: t.textPrimary }}
>
Modal Preview {SIZE_CONFIG[activeSize].label} ({SIZE_CONFIG[activeSize].width}) Modal Preview {SIZE_CONFIG[activeSize].label} ({SIZE_CONFIG[activeSize].width})
</span> </span>
<button <button
@ -542,16 +548,16 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
className="w-7 h-7 rounded flex items-center justify-center hover:opacity-70 transition-opacity" className="w-7 h-7 rounded flex items-center justify-center hover:opacity-70 transition-opacity"
style={{ backgroundColor: isDark ? 'rgba(66,71,84,0.25)' : '#f1f5f9' }} style={{ backgroundColor: isDark ? 'rgba(66,71,84,0.25)' : '#f1f5f9' }}
> >
<span className="font-mono text-sm" style={{ color: t.textMuted }}> <span className="font-mono text-body-2" style={{ color: t.textMuted }}>
</span> </span>
</button> </button>
</div> </div>
<div className="px-5 py-5 flex flex-col gap-3 overflow-y-auto"> <div className="px-5 py-5 flex flex-col gap-3 overflow-y-auto">
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-6" style={{ color: t.textSecondary }}>
{' '} {' '}
<code <code
className="font-mono text-xs px-1 rounded" className="font-mono text-caption px-1 rounded"
style={{ style={{
backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)', backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)',
color: t.textAccent, color: t.textAccent,
@ -568,7 +574,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
className="rounded border border-solid px-3 py-2.5" className="rounded border border-solid px-3 py-2.5"
style={{ borderColor: t.cardBorder }} style={{ borderColor: t.cardBorder }}
> >
<span className="font-korean text-xs" style={{ color: t.textMuted }}> <span className="font-korean text-caption" style={{ color: t.textMuted }}>
{label} {label}
</span> </span>
</div> </div>
@ -582,7 +588,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
<button <button
type="button" type="button"
onClick={() => setIsModalOpen(false)} onClick={() => setIsModalOpen(false)}
className="px-4 py-2 rounded border border-solid font-korean text-sm transition-opacity hover:opacity-70" className="px-4 py-2 rounded border border-solid font-korean text-body-2 transition-opacity hover:opacity-70"
style={{ borderColor: t.cardBorder, color: t.textMuted }} style={{ borderColor: t.cardBorder, color: t.textMuted }}
> >
@ -590,7 +596,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
<button <button
type="button" type="button"
onClick={() => setIsModalOpen(false)} onClick={() => setIsModalOpen(false)}
className="px-4 py-2 rounded font-korean text-sm font-medium transition-opacity hover:opacity-80" className="px-4 py-2 rounded font-korean text-body-2 font-medium transition-opacity hover:opacity-80"
style={{ style={{
backgroundColor: isDark ? 'rgba(76,215,246,0.18)' : 'rgba(6,182,212,0.14)', backgroundColor: isDark ? 'rgba(76,215,246,0.18)' : 'rgba(6,182,212,0.14)',
color: t.textAccent, color: t.textAccent,
@ -624,13 +630,16 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span style={{ color: '#ef4444', fontSize: '16px' }}></span> <span style={{ color: '#ef4444', fontSize: '16px' }}></span>
<span className="font-korean text-sm font-medium" style={{ color: t.textPrimary }}> <span
className="font-korean text-body-2 font-medium"
style={{ color: t.textPrimary }}
>
? ?
</span> </span>
</div> </div>
</div> </div>
<div className="px-5 py-4"> <div className="px-5 py-4">
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-6" style={{ color: t.textSecondary }}>
. ? . ?
</p> </p>
</div> </div>
@ -641,7 +650,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
<button <button
type="button" type="button"
onClick={() => setIsConfirmOpen(false)} onClick={() => setIsConfirmOpen(false)}
className="px-4 py-2 rounded border border-solid font-korean text-sm transition-opacity hover:opacity-70" className="px-4 py-2 rounded border border-solid font-korean text-body-2 transition-opacity hover:opacity-70"
style={{ borderColor: t.cardBorder, color: t.textMuted }} style={{ borderColor: t.cardBorder, color: t.textMuted }}
> >
@ -649,7 +658,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
<button <button
type="button" type="button"
onClick={() => setIsConfirmOpen(false)} onClick={() => setIsConfirmOpen(false)}
className="px-4 py-2 rounded font-korean text-sm font-medium transition-opacity hover:opacity-80" className="px-4 py-2 rounded font-korean text-body-2 font-medium transition-opacity hover:opacity-80"
style={{ style={{
backgroundColor: isDark ? 'rgba(239,68,68,0.18)' : 'rgba(239,68,68,0.12)', backgroundColor: isDark ? 'rgba(239,68,68,0.18)' : 'rgba(239,68,68,0.12)',
color: '#ef4444', color: '#ef4444',

파일 보기

@ -63,10 +63,10 @@ export const FloatOverlayContent = ({ theme }: FloatOverlayContentProps) => {
<h2 className="font-sans text-xl font-bold" style={{ color: t.textPrimary }}> <h2 className="font-sans text-xl font-bold" style={{ color: t.textPrimary }}>
Overlay Overlay
</h2> </h2>
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-6" style={{ color: t.textSecondary }}>
<code <code
className="font-mono text-xs px-1.5 py-0.5 mx-1 rounded" className="font-mono text-caption px-1.5 py-0.5 mx-1 rounded"
style={{ style={{
backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)', backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)',
color: t.textAccent, color: t.textAccent,
@ -133,12 +133,12 @@ export const FloatOverlayContent = ({ theme }: FloatOverlayContentProps) => {
</span> </span>
</div> </div>
<div className="py-2.5 px-4"> <div className="py-2.5 px-4">
<span className="font-korean text-xs" style={{ color: t.textSecondary }}> <span className="font-korean text-caption" style={{ color: t.textSecondary }}>
{row.overlay} {row.overlay}
</span> </span>
</div> </div>
<div className="py-2.5 px-4"> <div className="py-2.5 px-4">
<span className="font-korean text-xs" style={{ color: t.textSecondary }}> <span className="font-korean text-caption" style={{ color: t.textSecondary }}>
{row.modal} {row.modal}
</span> </span>
</div> </div>
@ -284,7 +284,7 @@ export const FloatOverlayContent = ({ theme }: FloatOverlayContentProps) => {
className="rounded-lg border border-solid p-5 flex flex-col gap-4" className="rounded-lg border border-solid p-5 flex flex-col gap-4"
style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder }} style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder }}
> >
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-6" style={{ color: t.textSecondary }}>
<strong>ScatPopup</strong> . Modal(fixed <strong>ScatPopup</strong> . Modal(fixed
) , · ) , ·
. .
@ -307,7 +307,7 @@ export const FloatOverlayContent = ({ theme }: FloatOverlayContentProps) => {
<span className="font-mono text-caption" style={{ color: t.textMuted }}> <span className="font-mono text-caption" style={{ color: t.textMuted }}>
{item.label} {item.label}
</span> </span>
<span className="font-mono text-xs" style={{ color: t.textAccent }}> <span className="font-mono text-caption" style={{ color: t.textAccent }}>
{item.value} {item.value}
</span> </span>
<span className="font-korean text-caption" style={{ color: t.textSecondary }}> <span className="font-korean text-caption" style={{ color: t.textSecondary }}>
@ -323,7 +323,7 @@ export const FloatOverlayContent = ({ theme }: FloatOverlayContentProps) => {
borderColor: 'rgba(234,179,8,0.25)', borderColor: 'rgba(234,179,8,0.25)',
}} }}
> >
<span className="font-korean text-xs" style={{ color: '#eab308' }}> <span className="font-korean text-caption" style={{ color: '#eab308' }}>
주의: ScatPopup은 MapLibre GL JS의 Popup/Marker React DOM으로 . 주의: ScatPopup은 MapLibre GL JS의 Popup/Marker React DOM으로 .
position: absolute로 . position: absolute로 .
</span> </span>
@ -377,7 +377,7 @@ export const FloatOverlayContent = ({ theme }: FloatOverlayContentProps) => {
}} }}
> >
<div className="py-2.5 px-3"> <div className="py-2.5 px-3">
<span className="font-mono text-xs" style={{ color: t.textPrimary }}> <span className="font-mono text-caption" style={{ color: t.textPrimary }}>
{item.component} {item.component}
</span> </span>
</div> </div>
@ -413,7 +413,10 @@ export const FloatOverlayContent = ({ theme }: FloatOverlayContentProps) => {
</span> </span>
</div> </div>
<div className="py-2.5 px-3"> <div className="py-2.5 px-3">
<span className="font-korean text-xs leading-5" style={{ color: t.textSecondary }}> <span
className="font-korean text-caption leading-5"
style={{ color: t.textSecondary }}
>
{item.desc} {item.desc}
</span> </span>
</div> </div>

파일 보기

@ -84,10 +84,10 @@ export const FloatToastContent = ({ theme }: FloatToastContentProps) => {
</span> </span>
</div> </div>
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-6" style={{ color: t.textSecondary }}>
. .
<code <code
className="font-mono text-xs px-1.5 py-0.5 mx-1 rounded" className="font-mono text-caption px-1.5 py-0.5 mx-1 rounded"
style={{ style={{
backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)', backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)',
color: t.textAccent, color: t.textAccent,
@ -97,7 +97,7 @@ export const FloatToastContent = ({ theme }: FloatToastContentProps) => {
</code> </code>
. {' '} . {' '}
<code <code
className="font-mono text-xs px-1.5 py-0.5 mx-1 rounded" className="font-mono text-caption px-1.5 py-0.5 mx-1 rounded"
style={{ style={{
backgroundColor: isDark ? 'rgba(239,68,68,0.08)' : 'rgba(239,68,68,0.05)', backgroundColor: isDark ? 'rgba(239,68,68,0.08)' : 'rgba(239,68,68,0.05)',
color: '#ef4444', color: '#ef4444',
@ -129,7 +129,7 @@ export const FloatToastContent = ({ theme }: FloatToastContentProps) => {
className="rounded-lg border border-solid p-5 flex flex-col gap-4" className="rounded-lg border border-solid p-5 flex flex-col gap-4"
style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder }} style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder }}
> >
<p className="font-korean text-xs" style={{ color: t.textMuted }}> <p className="font-korean text-caption" style={{ color: t.textMuted }}>
Toast가 . 3 . Toast가 . 3 .
</p> </p>
<div className="flex gap-2 flex-wrap"> <div className="flex gap-2 flex-wrap">
@ -280,7 +280,7 @@ export const FloatToastContent = ({ theme }: FloatToastContentProps) => {
<span className="font-mono text-lg" style={{ color: cfg.color }}> <span className="font-mono text-lg" style={{ color: cfg.color }}>
{cfg.icon} {cfg.icon}
</span> </span>
<span className="font-mono text-sm font-bold" style={{ color: cfg.color }}> <span className="font-mono text-body-2 font-bold" style={{ color: cfg.color }}>
{cfg.label} {cfg.label}
</span> </span>
</div> </div>
@ -311,7 +311,7 @@ export const FloatToastContent = ({ theme }: FloatToastContentProps) => {
className="rounded-lg border border-solid p-5 flex flex-col gap-4" className="rounded-lg border border-solid p-5 flex flex-col gap-4"
style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder }} style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder }}
> >
<p className="font-korean text-sm leading-6" style={{ color: t.textSecondary }}> <p className="font-korean text-body-2 leading-6" style={{ color: t.textSecondary }}>
Toast는 <strong>Zustand store + useToast hook</strong>{' '} Toast는 <strong>Zustand store + useToast hook</strong>{' '}
. ToastContainer는 App.tsx . . ToastContainer는 App.tsx .
</p> </p>
@ -387,7 +387,7 @@ export const FloatToastContent = ({ theme }: FloatToastContentProps) => {
<span className="font-mono text-base shrink-0" style={{ color: cfg.color }}> <span className="font-mono text-base shrink-0" style={{ color: cfg.color }}>
{cfg.icon} {cfg.icon}
</span> </span>
<span className="font-korean text-sm flex-1" style={{ color: t.textPrimary }}> <span className="font-korean text-body-2 flex-1" style={{ color: t.textPrimary }}>
{toast.message} {toast.message}
</span> </span>
<button <button

파일 보기

@ -6,7 +6,7 @@ interface AdminPlaceholderProps {
const AdminPlaceholder = ({ label }: AdminPlaceholderProps) => ( const AdminPlaceholder = ({ label }: AdminPlaceholderProps) => (
<div className="flex flex-col items-center justify-center h-full gap-3"> <div className="flex flex-col items-center justify-center h-full gap-3">
<div className="text-4xl opacity-20">🚧</div> <div className="text-4xl opacity-20">🚧</div>
<div className="text-sm font-korean text-fg-sub font-semibold">{label}</div> <div className="text-body-2 font-korean text-fg-sub font-semibold">{label}</div>
<div className="text-label-2 font-korean text-fg-disabled"> .</div> <div className="text-label-2 font-korean text-fg-disabled"> .</div>
</div> </div>
); );

파일 보기

@ -107,7 +107,7 @@ const AdminSidebar = ({ activeMenu, onSelect }: AdminSidebarProps) => {
> >
{/* 헤더 */} {/* 헤더 */}
<div className="px-4 py-3 border-b border-stroke bg-bg-elevated shrink-0"> <div className="px-4 py-3 border-b border-stroke bg-bg-elevated shrink-0">
<div className="text-xs font-bold text-fg font-korean flex items-center gap-1.5"> <div className="text-caption font-bold text-fg font-korean flex items-center gap-1.5">
<span></span> <span></span>
</div> </div>
</div> </div>
@ -129,7 +129,7 @@ const AdminSidebar = ({ activeMenu, onSelect }: AdminSidebarProps) => {
color: hasActiveChild ? 'var(--color-accent)' : 'var(--fg-default)', color: hasActiveChild ? 'var(--color-accent)' : 'var(--fg-default)',
}} }}
> >
<span className="text-sm">{section.icon}</span> <span className="text-body-2">{section.icon}</span>
<span className="flex-1 text-left">{section.label}</span> <span className="flex-1 text-left">{section.label}</span>
<span <span
className="text-caption text-fg-disabled transition-transform" className="text-caption text-fg-disabled transition-transform"

파일 보기

@ -103,7 +103,7 @@ function AssetUploadPanel() {
{/* 헤더 */} {/* 헤더 */}
<div className="px-6 py-4 border-b border-stroke flex-shrink-0"> <div className="px-6 py-4 border-b border-stroke flex-shrink-0">
<h1 className="text-lg font-bold text-fg font-korean"> </h1> <h1 className="text-lg font-bold text-fg font-korean"> </h1>
<p className="text-xs text-fg-disabled mt-1 font-korean"> <p className="text-caption text-fg-disabled mt-1 font-korean">
</p> </p>
</div> </div>
@ -115,7 +115,7 @@ function AssetUploadPanel() {
<div className="flex-1 max-w-[560px] space-y-4"> <div className="flex-1 max-w-[560px] space-y-4">
<div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden"> <div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden">
<div className="px-5 py-3 border-b border-stroke"> <div className="px-5 py-3 border-b border-stroke">
<h2 className="text-sm font-bold text-fg font-korean"> </h2> <h2 className="text-body-2 font-bold text-fg font-korean"> </h2>
</div> </div>
<div className="px-5 py-4 space-y-4"> <div className="px-5 py-4 space-y-4">
{/* 드롭존 */} {/* 드롭존 */}
@ -135,12 +135,12 @@ function AssetUploadPanel() {
> >
<div className="text-3xl mb-2 opacity-40">📁</div> <div className="text-3xl mb-2 opacity-40">📁</div>
{selectedFile ? ( {selectedFile ? (
<div className="text-xs font-semibold text-color-accent font-korean mb-1"> <div className="text-caption font-semibold text-color-accent font-korean mb-1">
{selectedFile.name} {selectedFile.name}
</div> </div>
) : ( ) : (
<> <>
<div className="text-xs font-semibold text-fg-sub font-korean mb-1"> <div className="text-caption font-semibold text-fg-sub font-korean mb-1">
</div> </div>
<div className="text-caption text-fg-disabled font-korean mb-3"> <div className="text-caption text-fg-disabled font-korean mb-3">
@ -148,7 +148,7 @@ function AssetUploadPanel() {
</div> </div>
<button <button
type="button" type="button"
className="px-4 py-1.5 text-xs font-semibold rounded-md bg-color-accent text-bg-0 className="px-4 py-1.5 text-caption font-semibold rounded-md bg-color-accent text-bg-0
hover:shadow-[0_0_12px_rgba(6,182,212,0.3)] transition-all font-korean" hover:shadow-[0_0_12px_rgba(6,182,212,0.3)] transition-all font-korean"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@ -176,7 +176,7 @@ function AssetUploadPanel() {
<select <select
value={assetCategory} value={assetCategory}
onChange={(e) => setAssetCategory(e.target.value)} onChange={(e) => setAssetCategory(e.target.value)}
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md
text-fg focus:border-color-accent focus:outline-none font-korean" text-fg focus:border-color-accent focus:outline-none font-korean"
> >
{ASSET_CATEGORIES.map((c) => ( {ASSET_CATEGORIES.map((c) => (
@ -195,7 +195,7 @@ function AssetUploadPanel() {
<select <select
value={jurisdiction} value={jurisdiction}
onChange={(e) => setJurisdiction(e.target.value)} onChange={(e) => setJurisdiction(e.target.value)}
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md
text-fg focus:border-color-accent focus:outline-none font-korean" text-fg focus:border-color-accent focus:outline-none font-korean"
> >
{JURISDICTIONS.map((j) => ( {JURISDICTIONS.map((j) => (
@ -212,7 +212,7 @@ function AssetUploadPanel() {
</label> </label>
<div className="flex gap-4"> <div className="flex gap-4">
<label className="flex items-center gap-1.5 cursor-pointer text-xs text-fg-sub font-korean"> <label className="flex items-center gap-1.5 cursor-pointer text-caption text-fg-sub font-korean">
<input <input
type="radio" type="radio"
checked={uploadMode === 'add'} checked={uploadMode === 'add'}
@ -221,7 +221,7 @@ function AssetUploadPanel() {
/> />
( + ) ( + )
</label> </label>
<label className="flex items-center gap-1.5 cursor-pointer text-xs text-fg-sub font-korean"> <label className="flex items-center gap-1.5 cursor-pointer text-caption text-fg-sub font-korean">
<input <input
type="radio" type="radio"
checked={uploadMode === 'replace'} checked={uploadMode === 'replace'}
@ -238,7 +238,7 @@ function AssetUploadPanel() {
type="button" type="button"
onClick={handleUpload} onClick={handleUpload}
disabled={!selectedFile || uploaded} disabled={!selectedFile || uploaded}
className={`w-full py-2.5 text-xs font-semibold rounded-md transition-all font-korean disabled:opacity-50 ${ className={`w-full py-2.5 text-caption font-semibold rounded-md transition-all font-korean disabled:opacity-50 ${
uploaded uploaded
? 'bg-[rgba(34,197,94,0.15)] text-color-success border border-status-green/30' ? 'bg-[rgba(34,197,94,0.15)] text-color-success border border-status-green/30'
: 'bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)]' : 'bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)]'
@ -255,7 +255,7 @@ function AssetUploadPanel() {
{/* 수정 권한 체계 */} {/* 수정 권한 체계 */}
<div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden"> <div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden">
<div className="px-5 py-3 border-b border-stroke"> <div className="px-5 py-3 border-b border-stroke">
<h2 className="text-sm font-bold text-fg font-korean"> </h2> <h2 className="text-body-2 font-bold text-fg font-korean"> </h2>
</div> </div>
<div className="px-5 py-4 space-y-2"> <div className="px-5 py-4 space-y-2">
{PERM_ITEMS.map((p) => ( {PERM_ITEMS.map((p) => (
@ -264,13 +264,15 @@ function AssetUploadPanel() {
className="flex items-center gap-3 px-4 py-3 bg-bg-elevated border border-stroke rounded-md" className="flex items-center gap-3 px-4 py-3 bg-bg-elevated border border-stroke rounded-md"
> >
<div <div
className="w-8 h-8 rounded-full flex items-center justify-center text-sm flex-shrink-0" className="w-8 h-8 rounded-full flex items-center justify-center text-body-2 flex-shrink-0"
style={{ background: p.bg }} style={{ background: p.bg }}
> >
{p.icon} {p.icon}
</div> </div>
<div> <div>
<div className={`text-xs font-bold font-korean ${p.color}`}>{p.role}</div> <div className={`text-caption font-bold font-korean ${p.color}`}>
{p.role}
</div>
<div className="text-caption text-fg-disabled font-korean mt-0.5"> <div className="text-caption text-fg-disabled font-korean mt-0.5">
{p.desc} {p.desc}
</div> </div>
@ -283,7 +285,7 @@ function AssetUploadPanel() {
{/* 최근 업로드 이력 */} {/* 최근 업로드 이력 */}
<div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden"> <div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden">
<div className="px-5 py-3 border-b border-stroke"> <div className="px-5 py-3 border-b border-stroke">
<h2 className="text-sm font-bold text-fg font-korean"> </h2> <h2 className="text-body-2 font-bold text-fg font-korean"> </h2>
</div> </div>
<div className="px-5 py-4 space-y-2"> <div className="px-5 py-4 space-y-2">
{uploadHistory.length === 0 ? ( {uploadHistory.length === 0 ? (
@ -297,7 +299,9 @@ function AssetUploadPanel() {
className="flex justify-between items-center px-4 py-3 bg-bg-elevated border border-stroke rounded-md" className="flex justify-between items-center px-4 py-3 bg-bg-elevated border border-stroke rounded-md"
> >
<div> <div>
<div className="text-xs font-semibold text-fg font-korean">{h.fileNm}</div> <div className="text-caption font-semibold text-fg font-korean">
{h.fileNm}
</div>
<div className="text-caption text-fg-disabled mt-0.5 font-korean"> <div className="text-caption text-fg-disabled mt-0.5 font-korean">
{formatDate(h.regDtm)} · {h.uploaderNm} · {h.uploadCnt.toLocaleString()} {formatDate(h.regDtm)} · {h.uploaderNm} · {h.uploadCnt.toLocaleString()}
</div> </div>

파일 보기

@ -119,8 +119,8 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
<div className="flex flex-col h-full overflow-hidden"> <div className="flex flex-col h-full overflow-hidden">
{/* 헤더 */} {/* 헤더 */}
<div className="flex items-center justify-between px-5 py-3 border-b border-stroke-1"> <div className="flex items-center justify-between px-5 py-3 border-b border-stroke-1">
<h2 className="text-sm font-semibold text-fg"> </h2> <h2 className="text-body-2 font-semibold text-fg"> </h2>
<span className="text-xs text-fg-disabled"> {data?.totalCount ?? 0}</span> <span className="text-caption text-fg-disabled"> {data?.totalCount ?? 0}</span>
</div> </div>
{/* 카테고리 탭 + 검색 */} {/* 카테고리 탭 + 검색 */}
@ -130,7 +130,7 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
<button <button
key={tab.code} key={tab.code}
onClick={() => handleCategoryChange(tab.code)} onClick={() => handleCategoryChange(tab.code)}
className={`px-3 py-1 text-xs rounded-full transition-colors ${ className={`px-3 py-1 text-caption rounded-full transition-colors ${
activeCategory === tab.code activeCategory === tab.code
? 'bg-blue-500/20 text-blue-400 font-medium' ? 'bg-blue-500/20 text-blue-400 font-medium'
: 'text-fg-disabled hover:text-fg-sub hover:bg-bg-elevated' : 'text-fg-disabled hover:text-fg-sub hover:bg-bg-elevated'
@ -146,11 +146,11 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
value={searchInput} value={searchInput}
onChange={(e) => setSearchInput(e.target.value)} onChange={(e) => setSearchInput(e.target.value)}
placeholder="제목/작성자 검색" placeholder="제목/작성자 검색"
className="px-2 py-1 text-xs rounded bg-bg-elevated border border-stroke-1 text-fg placeholder:text-text-4 w-48" className="px-2 py-1 text-caption rounded bg-bg-elevated border border-stroke-1 text-fg placeholder:text-text-4 w-48"
/> />
<button <button
type="submit" type="submit"
className="px-2 py-1 text-xs rounded bg-bg-elevated border border-stroke-1 text-fg-sub hover:bg-bg-card" className="px-2 py-1 text-caption rounded bg-bg-elevated border border-stroke-1 text-fg-sub hover:bg-bg-card"
> >
</button> </button>
@ -162,7 +162,7 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
<button <button
onClick={handleDelete} onClick={handleDelete}
disabled={selected.size === 0 || deleting} disabled={selected.size === 0 || deleting}
className="px-3 py-1 text-xs rounded bg-red-500/20 text-red-400 hover:bg-red-500/30 disabled:opacity-40 disabled:cursor-not-allowed" className="px-3 py-1 text-caption rounded bg-red-500/20 text-red-400 hover:bg-red-500/30 disabled:opacity-40 disabled:cursor-not-allowed"
> >
{deleting ? '삭제 중...' : `선택 삭제 (${selected.size})`} {deleting ? '삭제 중...' : `선택 삭제 (${selected.size})`}
</button> </button>
@ -170,7 +170,7 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
{/* 테이블 */} {/* 테이블 */}
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
<table className="w-full text-xs"> <table className="w-full text-caption">
<thead className="sticky top-0 bg-bg-surface z-10"> <thead className="sticky top-0 bg-bg-surface z-10">
<tr className="border-b border-stroke-1 text-fg-disabled"> <tr className="border-b border-stroke-1 text-fg-disabled">
<th className="w-8 py-2 text-center"> <th className="w-8 py-2 text-center">
@ -222,7 +222,7 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
<button <button
onClick={() => setPage((p) => Math.max(1, p - 1))} onClick={() => setPage((p) => Math.max(1, p - 1))}
disabled={page <= 1} disabled={page <= 1}
className="px-2 py-1 text-xs rounded text-fg-disabled hover:bg-bg-elevated disabled:opacity-30" className="px-2 py-1 text-caption rounded text-fg-disabled hover:bg-bg-elevated disabled:opacity-30"
> >
&lt; &lt;
</button> </button>
@ -234,7 +234,7 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
<button <button
key={p} key={p}
onClick={() => setPage(p)} onClick={() => setPage(p)}
className={`w-7 h-7 text-xs rounded ${ className={`w-7 h-7 text-caption rounded ${
p === page p === page
? 'bg-blue-500/20 text-blue-400 font-medium' ? 'bg-blue-500/20 text-blue-400 font-medium'
: 'text-fg-disabled hover:bg-bg-elevated' : 'text-fg-disabled hover:bg-bg-elevated'
@ -247,7 +247,7 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
<button <button
onClick={() => setPage((p) => Math.min(totalPages, p + 1))} onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
disabled={page >= totalPages} disabled={page >= totalPages}
className="px-2 py-1 text-xs rounded text-fg-disabled hover:bg-bg-elevated disabled:opacity-30" className="px-2 py-1 text-caption rounded text-fg-disabled hover:bg-bg-elevated disabled:opacity-30"
> >
&gt; &gt;
</button> </button>

파일 보기

@ -99,13 +99,15 @@ function CleanupEquipPanel() {
<div className="flex items-center justify-between px-6 py-4 border-b border-stroke"> <div className="flex items-center justify-between px-6 py-4 border-b border-stroke">
<div> <div>
<h1 className="text-lg font-bold text-fg font-korean"> </h1> <h1 className="text-lg font-bold text-fg font-korean"> </h1>
<p className="text-xs text-fg-disabled mt-1 font-korean"> {filtered.length} </p> <p className="text-caption text-fg-disabled mt-1 font-korean">
{filtered.length}
</p>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<select <select
value={regionFilter} value={regionFilter}
onChange={handleFilterChange(setRegionFilter)} onChange={handleFilterChange(setRegionFilter)}
className="px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean" className="px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean"
> >
<option value="전체"> </option> <option value="전체"> </option>
<option value="남해"></option> <option value="남해"></option>
@ -117,7 +119,7 @@ function CleanupEquipPanel() {
<select <select
value={typeFilter} value={typeFilter}
onChange={handleFilterChange(setTypeFilter)} onChange={handleFilterChange(setTypeFilter)}
className="px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean" className="px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean"
> >
<option value="전체"> </option> <option value="전체"> </option>
{typeOptions.map((t) => ( {typeOptions.map((t) => (
@ -129,7 +131,7 @@ function CleanupEquipPanel() {
<select <select
value={equipFilter} value={equipFilter}
onChange={handleFilterChange(setEquipFilter)} onChange={handleFilterChange(setEquipFilter)}
className="px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean" className="px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean"
> >
<option value="전체"> </option> <option value="전체"> </option>
<option value="방제선"></option> <option value="방제선"></option>
@ -146,11 +148,11 @@ function CleanupEquipPanel() {
setSearchTerm(e.target.value); setSearchTerm(e.target.value);
setCurrentPage(1); setCurrentPage(1);
}} }}
className="w-56 px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean" className="w-56 px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean"
/> />
<button <button
onClick={load} onClick={load}
className="px-4 py-2 text-xs font-semibold rounded-md bg-bg-elevated border border-stroke text-fg-sub hover:border-color-accent hover:text-color-accent transition-all font-korean" className="px-4 py-2 text-caption font-semibold rounded-md bg-bg-elevated border border-stroke text-fg-sub hover:border-color-accent hover:text-color-accent transition-all font-korean"
> >
</button> </button>
@ -160,7 +162,7 @@ function CleanupEquipPanel() {
{/* 테이블 */} {/* 테이블 */}
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{loading ? ( {loading ? (
<div className="flex items-center justify-center h-32 text-fg-disabled text-sm font-korean"> <div className="flex items-center justify-center h-32 text-fg-disabled text-body-2 font-korean">
... ...
</div> </div>
) : ( ) : (
@ -217,7 +219,7 @@ function CleanupEquipPanel() {
<tr> <tr>
<td <td
colSpan={11} colSpan={11}
className="px-6 py-10 text-center text-xs text-fg-disabled font-korean" className="px-6 py-10 text-center text-caption text-fg-disabled font-korean"
> >
. .
</td> </td>

파일 보기

@ -211,7 +211,7 @@ const HEADERS = [
function HrTable({ rows, loading }: { rows: HrCollectItem[]; loading: boolean }) { function HrTable({ rows, loading }: { rows: HrCollectItem[]; loading: boolean }) {
return ( return (
<div className="overflow-auto"> <div className="overflow-auto">
<table className="w-full text-xs border-collapse"> <table className="w-full text-caption border-collapse">
<thead> <thead>
<tr className="bg-bg-elevated text-t3 uppercase tracking-wide"> <tr className="bg-bg-elevated text-t3 uppercase tracking-wide">
{HEADERS.map((h) => ( {HEADERS.map((h) => (
@ -317,10 +317,10 @@ export default function CollectHrPanel() {
<div className="flex flex-col h-full overflow-hidden"> <div className="flex flex-col h-full overflow-hidden">
{/* 헤더 */} {/* 헤더 */}
<div className="flex items-center justify-between px-5 py-3 border-b border-stroke-1 shrink-0"> <div className="flex items-center justify-between px-5 py-3 border-b border-stroke-1 shrink-0">
<h2 className="text-sm font-semibold text-t1"> </h2> <h2 className="text-body-2 font-semibold text-t1"> </h2>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{lastUpdate && ( {lastUpdate && (
<span className="text-xs text-t3"> <span className="text-caption text-t3">
:{' '} :{' '}
{lastUpdate.toLocaleTimeString('ko-KR', { {lastUpdate.toLocaleTimeString('ko-KR', {
hour: '2-digit', hour: '2-digit',
@ -332,7 +332,7 @@ export default function CollectHrPanel() {
<button <button
onClick={fetchData} onClick={fetchData}
disabled={loading} disabled={loading}
className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded bg-bg-elevated hover:bg-bg-card text-t2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" className="flex items-center gap-1.5 px-3 py-1.5 text-caption rounded bg-bg-elevated hover:bg-bg-card text-t2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
> >
<svg <svg
className={`w-3.5 h-3.5 ${loading ? 'animate-spin' : ''}`} className={`w-3.5 h-3.5 ${loading ? 'animate-spin' : ''}`}
@ -354,11 +354,11 @@ export default function CollectHrPanel() {
{/* 상태 표시줄 */} {/* 상태 표시줄 */}
<div className="flex items-center gap-3 px-5 py-2 shrink-0 border-b border-stroke-1 bg-bg-base"> <div className="flex items-center gap-3 px-5 py-2 shrink-0 border-b border-stroke-1 bg-bg-base">
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-emerald-500/10 text-emerald-400"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-caption bg-emerald-500/10 text-emerald-400">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" /> <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
{completedCount} {completedCount}
</span> </span>
<span className="text-xs text-t3"> <span className="text-caption text-t3">
{rows.length} (: {activeCount} / : {rows.length - activeCount}) {rows.length} (: {activeCount} / : {rows.length - activeCount})
</span> </span>
</div> </div>

파일 보기

@ -130,7 +130,9 @@ const DispersingZonePanel = () => {
onClick={() => handleToggleExpand(zone)} onClick={() => handleToggleExpand(zone)}
> >
<span className={`w-3 h-3 rounded-sm shrink-0 ${swatchColor}`} /> <span className={`w-3 h-3 rounded-sm shrink-0 ${swatchColor}`} />
<span className="flex-1 text-xs font-semibold text-fg font-korean">{info.label}</span> <span className="flex-1 text-caption font-semibold text-fg font-korean">
{info.label}
</span>
{/* 토글 스위치 */} {/* 토글 스위치 */}
<button <button
onClick={(e) => { onClick={(e) => {
@ -209,7 +211,7 @@ const DispersingZonePanel = () => {
<div className="w-[280px] bg-bg-surface border-l border-stroke flex flex-col overflow-hidden shrink-0"> <div className="w-[280px] bg-bg-surface border-l border-stroke flex flex-col overflow-hidden shrink-0">
{/* 헤더 */} {/* 헤더 */}
<div className="px-4 py-4 border-b border-stroke shrink-0"> <div className="px-4 py-4 border-b border-stroke shrink-0">
<h1 className="text-sm font-bold text-fg font-korean"> </h1> <h1 className="text-body-2 font-bold text-fg font-korean"> </h1>
<p className="text-label-2 text-fg-disabled mt-0.5 font-korean"> </p> <p className="text-label-2 text-fg-disabled mt-0.5 font-korean"> </p>
</div> </div>

파일 보기

@ -186,7 +186,7 @@ const LayerFormModal = ({ mode, initialData, onClose, onSaved }: LayerFormModalP
}; };
const inputCls = const inputCls =
'w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none'; 'w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none';
const labelCls = 'block text-label-2 font-semibold text-fg-sub font-korean mb-1.5'; const labelCls = 'block text-label-2 font-semibold text-fg-sub font-korean mb-1.5';
return ( return (
@ -194,7 +194,7 @@ const LayerFormModal = ({ mode, initialData, onClose, onSaved }: LayerFormModalP
<div className="bg-bg-surface border border-stroke rounded-lg shadow-lg w-[480px] max-h-[90vh] flex flex-col"> <div className="bg-bg-surface border border-stroke rounded-lg shadow-lg w-[480px] max-h-[90vh] flex flex-col">
{/* 헤더 */} {/* 헤더 */}
<div className="flex items-center justify-between px-6 py-4 border-b border-stroke shrink-0"> <div className="flex items-center justify-between px-6 py-4 border-b border-stroke shrink-0">
<h2 className="text-sm font-bold text-fg font-korean"> <h2 className="text-body-2 font-bold text-fg font-korean">
{mode === 'create' ? '레이어 등록' : '레이어 수정'} {mode === 'create' ? '레이어 등록' : '레이어 수정'}
</h2> </h2>
<button onClick={onClose} className="text-fg-disabled hover:text-fg transition-colors"> <button onClick={onClose} className="text-fg-disabled hover:text-fg transition-colors">
@ -214,7 +214,7 @@ const LayerFormModal = ({ mode, initialData, onClose, onSaved }: LayerFormModalP
? handleParentChange(e.target.value) ? handleParentChange(e.target.value)
: handleField('upLayerCd', e.target.value) : handleField('upLayerCd', e.target.value)
} }
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none"
> >
<option value="">()</option> <option value="">()</option>
{options {options
@ -311,7 +311,7 @@ const LayerFormModal = ({ mode, initialData, onClose, onSaved }: LayerFormModalP
<select <select
value={form.useYn} value={form.useYn}
onChange={(e) => handleField('useYn', e.target.value)} onChange={(e) => handleField('useYn', e.target.value)}
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none"
> >
<option value="Y"></option> <option value="Y"></option>
<option value="N"></option> <option value="N"></option>
@ -329,14 +329,14 @@ const LayerFormModal = ({ mode, initialData, onClose, onSaved }: LayerFormModalP
<button <button
type="button" type="button"
onClick={onClose} onClick={onClose}
className="px-3 py-1.5 text-xs border border-stroke text-fg-disabled rounded hover:bg-[rgba(255,255,255,0.04)] transition-all font-korean" className="px-3 py-1.5 text-caption border border-stroke text-fg-disabled rounded hover:bg-[rgba(255,255,255,0.04)] transition-all font-korean"
> >
</button> </button>
<button <button
type="submit" type="submit"
disabled={saving} disabled={saving}
className="px-3 py-1.5 text-xs bg-color-accent text-bg-0 rounded hover:opacity-90 disabled:opacity-50 transition-all font-korean" className="px-3 py-1.5 text-caption bg-color-accent text-bg-0 rounded hover:opacity-90 disabled:opacity-50 transition-all font-korean"
> >
{saving ? '저장 중...' : mode === 'create' ? '등록' : '저장'} {saving ? '저장 중...' : mode === 'create' ? '등록' : '저장'}
</button> </button>
@ -449,11 +449,11 @@ const LayerPanel = () => {
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<div> <div>
<h1 className="text-lg font-bold text-fg font-korean"> </h1> <h1 className="text-lg font-bold text-fg font-korean"> </h1>
<p className="text-xs text-fg-disabled mt-1 font-korean"> {total}</p> <p className="text-caption text-fg-disabled mt-1 font-korean"> {total}</p>
</div> </div>
<button <button
onClick={() => setModal({ mode: 'create' })} onClick={() => setModal({ mode: 'create' })}
className="px-3 py-1.5 text-xs font-semibold bg-color-accent text-bg-0 rounded hover:opacity-90 transition-opacity font-korean" className="px-3 py-1.5 text-caption font-semibold bg-color-accent text-bg-0 rounded hover:opacity-90 transition-opacity font-korean"
> >
</button> </button>
@ -465,12 +465,12 @@ const LayerPanel = () => {
onChange={(e) => setSearchInput(e.target.value)} onChange={(e) => setSearchInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()} onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
placeholder="레이어코드 / 레이어명 검색" placeholder="레이어코드 / 레이어명 검색"
className="flex-1 px-3 py-1.5 text-xs bg-bg-elevated border border-stroke rounded text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean" className="flex-1 px-3 py-1.5 text-caption bg-bg-elevated border border-stroke rounded text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean"
/> />
<select <select
value={filterUseYn} value={filterUseYn}
onChange={(e) => setFilterUseYn(e.target.value)} onChange={(e) => setFilterUseYn(e.target.value)}
className="px-2 py-1.5 text-xs bg-bg-elevated border border-stroke rounded text-fg focus:border-color-accent focus:outline-none font-korean" className="px-2 py-1.5 text-caption bg-bg-elevated border border-stroke rounded text-fg focus:border-color-accent focus:outline-none font-korean"
> >
<option value=""></option> <option value=""></option>
<option value="Y"></option> <option value="Y"></option>
@ -478,7 +478,7 @@ const LayerPanel = () => {
</select> </select>
<button <button
onClick={handleSearch} onClick={handleSearch}
className="px-3 py-1.5 text-xs border border-stroke text-fg-sub rounded hover:bg-[rgba(255,255,255,0.04)] transition-all font-korean" className="px-3 py-1.5 text-caption border border-stroke text-fg-sub rounded hover:bg-[rgba(255,255,255,0.04)] transition-all font-korean"
> >
</button> </button>
@ -487,7 +487,7 @@ const LayerPanel = () => {
{/* 오류 메시지 */} {/* 오류 메시지 */}
{error && ( {error && (
<div className="px-6 py-2 text-xs text-red-400 bg-[rgba(239,68,68,0.05)] border-b border-stroke shrink-0 font-korean"> <div className="px-6 py-2 text-caption text-red-400 bg-[rgba(239,68,68,0.05)] border-b border-stroke shrink-0 font-korean">
{error} {error}
</div> </div>
)} )}
@ -495,7 +495,7 @@ const LayerPanel = () => {
{/* 테이블 영역 */} {/* 테이블 영역 */}
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{loading ? ( {loading ? (
<div className="flex items-center justify-center h-full text-fg-disabled text-sm font-korean"> <div className="flex items-center justify-center h-full text-fg-disabled text-body-2 font-korean">
... ...
</div> </div>
) : ( ) : (
@ -539,7 +539,7 @@ const LayerPanel = () => {
<tr> <tr>
<td <td
colSpan={10} colSpan={10}
className="px-4 py-12 text-center text-fg-disabled text-sm font-korean" className="px-4 py-12 text-center text-fg-disabled text-body-2 font-korean"
> >
. .
</td> </td>
@ -551,15 +551,15 @@ const LayerPanel = () => {
className="border-b border-stroke hover:bg-[rgba(255,255,255,0.02)] transition-colors" className="border-b border-stroke hover:bg-[rgba(255,255,255,0.02)] transition-colors"
> >
{/* 번호 */} {/* 번호 */}
<td className="px-4 py-3 text-xs text-fg-disabled font-mono"> <td className="px-4 py-3 text-caption text-fg-disabled font-mono">
{(page - 1) * PAGE_SIZE + idx + 1} {(page - 1) * PAGE_SIZE + idx + 1}
</td> </td>
{/* 레이어코드 */} {/* 레이어코드 */}
<td className="px-4 py-3 text-label-2 text-fg-sub font-mono">{item.layerCd}</td> <td className="px-4 py-3 text-label-2 text-fg-sub font-mono">{item.layerCd}</td>
{/* 레이어명 */} {/* 레이어명 */}
<td className="px-4 py-3 text-xs text-fg font-korean">{item.layerNm}</td> <td className="px-4 py-3 text-caption text-fg font-korean">{item.layerNm}</td>
{/* 레이어전체명 */} {/* 레이어전체명 */}
<td className="px-4 py-3 text-xs text-fg-sub font-korean max-w-[200px]"> <td className="px-4 py-3 text-caption text-fg-sub font-korean max-w-[200px]">
<span className="block truncate" title={item.layerFullNm}> <span className="block truncate" title={item.layerFullNm}>
{item.layerFullNm} {item.layerFullNm}
</span> </span>
@ -575,7 +575,7 @@ const LayerPanel = () => {
{item.wmsLayerNm ?? <span className="text-fg-disabled">-</span>} {item.wmsLayerNm ?? <span className="text-fg-disabled">-</span>}
</td> </td>
{/* 정렬순서 */} {/* 정렬순서 */}
<td className="px-4 py-3 text-xs text-fg-disabled text-center font-mono"> <td className="px-4 py-3 text-caption text-fg-disabled text-center font-mono">
{item.sortOrd} {item.sortOrd}
</td> </td>
{/* 등록일시 */} {/* 등록일시 */}
@ -614,13 +614,13 @@ const LayerPanel = () => {
<div className="flex items-center justify-center gap-1.5 flex-nowrap"> <div className="flex items-center justify-center gap-1.5 flex-nowrap">
<button <button
onClick={() => setModal({ mode: 'edit', data: item })} onClick={() => setModal({ mode: 'edit', data: item })}
className="px-3 py-1 text-xs rounded bg-[rgba(6,182,212,0.15)] text-color-accent hover:bg-[rgba(6,182,212,0.25)] font-korean whitespace-nowrap" className="px-3 py-1 text-caption rounded bg-[rgba(6,182,212,0.15)] text-color-accent hover:bg-[rgba(6,182,212,0.25)] font-korean whitespace-nowrap"
> >
</button> </button>
<button <button
onClick={() => handleDelete(item.layerCd)} onClick={() => handleDelete(item.layerCd)}
className="px-3 py-1 text-xs rounded bg-red-500/20 text-red-400 hover:bg-red-500/30 font-korean whitespace-nowrap" className="px-3 py-1 text-caption rounded bg-red-500/20 text-red-400 hover:bg-red-500/30 font-korean whitespace-nowrap"
> >
</button> </button>

파일 보기

@ -78,7 +78,7 @@ function MapBaseModal({
<div className="bg-bg-surface border border-stroke rounded-lg shadow-lg w-[520px] max-h-[90vh] flex flex-col"> <div className="bg-bg-surface border border-stroke rounded-lg shadow-lg w-[520px] max-h-[90vh] flex flex-col">
{/* 모달 헤더 */} {/* 모달 헤더 */}
<div className="flex items-center justify-between px-6 py-4 border-b border-stroke"> <div className="flex items-center justify-between px-6 py-4 border-b border-stroke">
<h2 className="text-sm font-bold text-fg font-korean"> <h2 className="text-body-2 font-bold text-fg font-korean">
{isEdit ? '지도 수정' : '지도 등록'} {isEdit ? '지도 수정' : '지도 등록'}
</h2> </h2>
<button onClick={onClose} className="text-fg-disabled hover:text-fg transition-colors"> <button onClick={onClose} className="text-fg-disabled hover:text-fg transition-colors">
@ -108,7 +108,7 @@ function MapBaseModal({
value={form.mapNm} value={form.mapNm}
onChange={(e) => setField('mapNm', e.target.value)} onChange={(e) => setField('mapNm', e.target.value)}
placeholder="지도 이름을 입력하세요" placeholder="지도 이름을 입력하세요"
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean"
/> />
</div> </div>
@ -123,7 +123,7 @@ function MapBaseModal({
onChange={(e) => setField('mapKey', e.target.value)} onChange={(e) => setField('mapKey', e.target.value)}
placeholder="고유 식별 키 (영문/숫자)" placeholder="고유 식별 키 (영문/숫자)"
disabled={isEdit} disabled={isEdit}
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono disabled:opacity-50 disabled:cursor-not-allowed" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono disabled:opacity-50 disabled:cursor-not-allowed"
/> />
</div> </div>
@ -135,7 +135,7 @@ function MapBaseModal({
<select <select
value={form.mapLevelCd} value={form.mapLevelCd}
onChange={(e) => setField('mapLevelCd', e.target.value)} onChange={(e) => setField('mapLevelCd', e.target.value)}
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean"
> >
<option value=""></option> <option value=""></option>
{MAP_LEVEL_OPTIONS.map((opt) => ( {MAP_LEVEL_OPTIONS.map((opt) => (
@ -156,7 +156,7 @@ function MapBaseModal({
value={form.mapSrc} value={form.mapSrc}
onChange={(e) => setField('mapSrc', e.target.value)} onChange={(e) => setField('mapSrc', e.target.value)}
placeholder="타일 URL 또는 파일 경로" placeholder="타일 URL 또는 파일 경로"
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono"
/> />
</div> </div>
@ -170,7 +170,7 @@ function MapBaseModal({
value={form.mapDc} value={form.mapDc}
onChange={(e) => setField('mapDc', e.target.value)} onChange={(e) => setField('mapDc', e.target.value)}
placeholder="지도에 대한 설명을 입력하세요" placeholder="지도에 대한 설명을 입력하세요"
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean resize-none" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean resize-none"
/> />
</div> </div>
@ -193,7 +193,7 @@ function MapBaseModal({
}`} }`}
/> />
</button> </button>
<span className="text-xs text-fg-sub font-korean"> <span className="text-caption text-fg-sub font-korean">
{form.useYn === 'Y' ? '사용' : '미사용'} {form.useYn === 'Y' ? '사용' : '미사용'}
</span> </span>
</div> </div>
@ -208,14 +208,14 @@ function MapBaseModal({
<button <button
type="button" type="button"
onClick={onClose} onClick={onClose}
className="px-4 py-2 text-xs border border-stroke text-fg-sub rounded-md hover:bg-[rgba(255,255,255,0.04)] transition-all font-korean" className="px-4 py-2 text-caption border border-stroke text-fg-sub rounded-md hover:bg-[rgba(255,255,255,0.04)] transition-all font-korean"
> >
</button> </button>
<button <button
type="submit" type="submit"
disabled={saving} disabled={saving}
className="px-4 py-2 text-xs font-semibold rounded-md bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)] transition-all disabled:opacity-50 font-korean" className="px-4 py-2 text-caption font-semibold rounded-md bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)] transition-all disabled:opacity-50 font-korean"
> >
{saving ? '저장 중...' : isEdit ? '수정' : '등록'} {saving ? '저장 중...' : isEdit ? '수정' : '등록'}
</button> </button>
@ -350,11 +350,11 @@ function MapBasePanel() {
<div className="flex items-center justify-between px-6 py-4 border-b border-stroke"> <div className="flex items-center justify-between px-6 py-4 border-b border-stroke">
<div> <div>
<h1 className="text-lg font-bold text-fg font-korean"> </h1> <h1 className="text-lg font-bold text-fg font-korean"> </h1>
<p className="text-xs text-fg-disabled mt-1 font-korean"> {total}</p> <p className="text-caption text-fg-disabled mt-1 font-korean"> {total}</p>
</div> </div>
<button <button
onClick={() => openModal(null)} onClick={() => openModal(null)}
className="px-4 py-2 text-xs font-semibold rounded-md bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)] transition-all font-korean" className="px-4 py-2 text-caption font-semibold rounded-md bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)] transition-all font-korean"
> >
+ +
</button> </button>
@ -375,7 +375,7 @@ function MapBasePanel() {
{/* 테이블 영역 */} {/* 테이블 영역 */}
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
<table className="w-full text-xs"> <table className="w-full text-caption">
<thead className="sticky top-0 bg-bg-surface z-10"> <thead className="sticky top-0 bg-bg-surface z-10">
<tr className="border-b border-stroke text-fg-disabled"> <tr className="border-b border-stroke text-fg-disabled">
<th className="w-12 py-3 text-center"></th> <th className="w-12 py-3 text-center"></th>
@ -433,7 +433,7 @@ function MapBasePanel() {
<td className="py-3 text-center"> <td className="py-3 text-center">
<button <button
onClick={() => openModal(item)} onClick={() => openModal(item)}
className="px-3 py-1 text-xs rounded bg-[rgba(6,182,212,0.15)] text-color-accent hover:bg-[rgba(6,182,212,0.25)]" className="px-3 py-1 text-caption rounded bg-[rgba(6,182,212,0.15)] text-color-accent hover:bg-[rgba(6,182,212,0.25)]"
> >
</button> </button>
@ -441,7 +441,7 @@ function MapBasePanel() {
<td className="py-3 text-center"> <td className="py-3 text-center">
<button <button
onClick={() => handleDelete(item)} onClick={() => handleDelete(item)}
className="px-3 py-1 text-xs rounded bg-red-500/20 text-red-400 hover:bg-red-500/30" className="px-3 py-1 text-caption rounded bg-red-500/20 text-red-400 hover:bg-red-500/30"
> >
</button> </button>
@ -452,7 +452,7 @@ function MapBasePanel() {
</tbody> </tbody>
</table> </table>
{!loading && items.length === 0 && ( {!loading && items.length === 0 && (
<div className="flex items-center justify-center h-32 text-xs text-fg-disabled font-korean"> <div className="flex items-center justify-center h-32 text-caption text-fg-disabled font-korean">
. .
</div> </div>
)} )}
@ -464,7 +464,7 @@ function MapBasePanel() {
<button <button
onClick={() => setPage((p) => Math.max(1, p - 1))} onClick={() => setPage((p) => Math.max(1, p - 1))}
disabled={page <= 1} disabled={page <= 1}
className="px-2 py-1 text-xs rounded text-fg-disabled hover:bg-bg-elevated disabled:opacity-30" className="px-2 py-1 text-caption rounded text-fg-disabled hover:bg-bg-elevated disabled:opacity-30"
> >
&lt; &lt;
</button> </button>
@ -476,7 +476,7 @@ function MapBasePanel() {
<button <button
key={p} key={p}
onClick={() => setPage(p)} onClick={() => setPage(p)}
className={`w-7 h-7 text-xs rounded ${ className={`w-7 h-7 text-caption rounded ${
p === page p === page
? 'bg-blue-500/20 text-blue-400 font-medium' ? 'bg-blue-500/20 text-blue-400 font-medium'
: 'text-fg-disabled hover:bg-bg-elevated' : 'text-fg-disabled hover:bg-bg-elevated'
@ -489,7 +489,7 @@ function MapBasePanel() {
<button <button
onClick={() => setPage((p) => Math.min(totalPages, p + 1))} onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
disabled={page >= totalPages} disabled={page >= totalPages}
className="px-2 py-1 text-xs rounded text-fg-disabled hover:bg-bg-elevated disabled:opacity-30" className="px-2 py-1 text-caption rounded text-fg-disabled hover:bg-bg-elevated disabled:opacity-30"
> >
&gt; &gt;
</button> </button>

파일 보기

@ -124,7 +124,7 @@ function MenusPanel() {
if (loading) { if (loading) {
return ( return (
<div className="flex items-center justify-center h-full"> <div className="flex items-center justify-center h-full">
<div className="text-fg-disabled text-sm font-korean"> ...</div> <div className="text-fg-disabled text-body-2 font-korean"> ...</div>
</div> </div>
); );
} }
@ -136,14 +136,14 @@ function MenusPanel() {
<div className="flex items-center justify-between px-6 py-4 border-b border-stroke"> <div className="flex items-center justify-between px-6 py-4 border-b border-stroke">
<div> <div>
<h1 className="text-lg font-bold text-fg font-korean"> </h1> <h1 className="text-lg font-bold text-fg font-korean"> </h1>
<p className="text-xs text-fg-disabled mt-1 font-korean"> <p className="text-caption text-fg-disabled mt-1 font-korean">
, , , , , ,
</p> </p>
</div> </div>
<button <button
onClick={handleSave} onClick={handleSave}
disabled={!hasChanges || saving} disabled={!hasChanges || saving}
className={`px-4 py-2 text-xs font-semibold rounded-md transition-all font-korean ${ className={`px-4 py-2 text-caption font-semibold rounded-md transition-all font-korean ${
hasChanges && !saving hasChanges && !saving
? 'bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)]' ? 'bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)]'
: 'bg-bg-card text-fg-disabled cursor-not-allowed' : 'bg-bg-card text-fg-disabled cursor-not-allowed'
@ -188,7 +188,7 @@ function MenusPanel() {
<DragOverlay> <DragOverlay>
{activeMenu ? ( {activeMenu ? (
<div className="flex items-center gap-3 px-4 py-3 rounded-md border border-color-accent bg-bg-surface shadow-lg opacity-90 max-w-[700px]"> <div className="flex items-center gap-3 px-4 py-3 rounded-md border border-color-accent bg-bg-surface shadow-lg opacity-90 max-w-[700px]">
<span className="text-fg-disabled text-xs"></span> <span className="text-fg-disabled text-caption"></span>
<span className="text-title-2">{activeMenu.icon}</span> <span className="text-title-2">{activeMenu.icon}</span>
<span className="text-title-4 font-semibold text-fg font-korean"> <span className="text-title-4 font-semibold text-fg font-korean">
{activeMenu.label} {activeMenu.label}

파일 보기

@ -45,24 +45,24 @@ function formatTime(iso: string | null): string {
function StatusCell({ row }: { row: NumericalDataStatus }) { function StatusCell({ row }: { row: NumericalDataStatus }) {
if (row.lastStatus === 'COMPLETED') { if (row.lastStatus === 'COMPLETED') {
return <span className="text-emerald-400 text-xs"></span>; return <span className="text-emerald-400 text-caption"></span>;
} }
if (row.lastStatus === 'FAILED') { if (row.lastStatus === 'FAILED') {
return ( return (
<span className="text-red-400 text-xs"> <span className="text-red-400 text-caption">
{row.consecutiveFailures > 0 ? ` (${row.consecutiveFailures}회 연속)` : ''} {row.consecutiveFailures > 0 ? ` (${row.consecutiveFailures}회 연속)` : ''}
</span> </span>
); );
} }
if (row.lastStatus === 'STARTED') { if (row.lastStatus === 'STARTED') {
return ( return (
<span className="inline-flex items-center gap-1 text-cyan-400 text-xs"> <span className="inline-flex items-center gap-1 text-cyan-400 text-caption">
<span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" /> <span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" />
</span> </span>
); );
} }
return <span className="text-t3 text-xs">-</span>; return <span className="text-t3 text-caption">-</span>;
} }
function StatusBadge({ function StatusBadge({
@ -76,7 +76,7 @@ function StatusBadge({
}) { }) {
if (loading) { if (loading) {
return ( return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-bg-elevated text-t2"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-caption bg-bg-elevated text-t2">
<span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" /> <span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" />
... ...
</span> </span>
@ -84,7 +84,7 @@ function StatusBadge({
} }
if (errorCount === total && total > 0) { if (errorCount === total && total > 0) {
return ( return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-red-500/10 text-red-400"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-caption bg-red-500/10 text-red-400">
<span className="w-1.5 h-1.5 rounded-full bg-red-400" /> <span className="w-1.5 h-1.5 rounded-full bg-red-400" />
</span> </span>
@ -92,14 +92,14 @@ function StatusBadge({
} }
if (errorCount > 0) { if (errorCount > 0) {
return ( return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-yellow-500/10 text-yellow-400"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-caption bg-yellow-500/10 text-yellow-400">
<span className="w-1.5 h-1.5 rounded-full bg-yellow-400" /> <span className="w-1.5 h-1.5 rounded-full bg-yellow-400" />
({errorCount}/{total}) ({errorCount}/{total})
</span> </span>
); );
} }
return ( return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-emerald-500/10 text-emerald-400"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-caption bg-emerald-500/10 text-emerald-400">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" /> <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
</span> </span>
@ -118,7 +118,7 @@ const TABLE_HEADERS = [
function ForecastTable({ rows, loading }: { rows: NumericalDataStatus[]; loading: boolean }) { function ForecastTable({ rows, loading }: { rows: NumericalDataStatus[]; loading: boolean }) {
return ( return (
<div className="overflow-auto"> <div className="overflow-auto">
<table className="w-full text-xs border-collapse"> <table className="w-full text-caption border-collapse">
<thead> <thead>
<tr className="bg-bg-elevated text-t3 uppercase tracking-wide"> <tr className="bg-bg-elevated text-t3 uppercase tracking-wide">
{TABLE_HEADERS.map((h) => ( {TABLE_HEADERS.map((h) => (
@ -193,10 +193,10 @@ export default function MonitorForecastPanel() {
<div className="flex flex-col h-full overflow-hidden"> <div className="flex flex-col h-full overflow-hidden">
{/* 헤더 */} {/* 헤더 */}
<div className="flex items-center justify-between px-5 py-3 border-b border-stroke-1 shrink-0"> <div className="flex items-center justify-between px-5 py-3 border-b border-stroke-1 shrink-0">
<h2 className="text-sm font-semibold text-t1"> </h2> <h2 className="text-body-2 font-semibold text-t1"> </h2>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{lastUpdate && ( {lastUpdate && (
<span className="text-xs text-t3"> <span className="text-caption text-t3">
:{' '} :{' '}
{lastUpdate.toLocaleTimeString('ko-KR', { {lastUpdate.toLocaleTimeString('ko-KR', {
hour: '2-digit', hour: '2-digit',
@ -208,7 +208,7 @@ export default function MonitorForecastPanel() {
<button <button
onClick={() => void fetchData()} onClick={() => void fetchData()}
disabled={loading} disabled={loading}
className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded bg-bg-elevated hover:bg-bg-card text-t2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" className="flex items-center gap-1.5 px-3 py-1.5 text-caption rounded bg-bg-elevated hover:bg-bg-card text-t2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
> >
<svg <svg
className={`w-3.5 h-3.5 ${loading ? 'animate-spin' : ''}`} className={`w-3.5 h-3.5 ${loading ? 'animate-spin' : ''}`}
@ -234,7 +234,7 @@ export default function MonitorForecastPanel() {
<button <button
key={tab.id} key={tab.id}
onClick={() => setActiveTab(tab.id)} onClick={() => setActiveTab(tab.id)}
className={`px-4 py-2.5 text-xs font-medium border-b-2 transition-colors ${ className={`px-4 py-2.5 text-caption font-medium border-b-2 transition-colors ${
activeTab === tab.id activeTab === tab.id
? 'border-cyan-400 text-cyan-400' ? 'border-cyan-400 text-cyan-400'
: 'border-transparent text-t3 hover:text-t2' : 'border-transparent text-t3 hover:text-t2'
@ -248,7 +248,9 @@ export default function MonitorForecastPanel() {
{/* 상태 표시줄 */} {/* 상태 표시줄 */}
<div className="flex items-center gap-3 px-5 py-2 shrink-0 border-b border-stroke-1 bg-bg-base"> <div className="flex items-center gap-3 px-5 py-2 shrink-0 border-b border-stroke-1 bg-bg-base">
<StatusBadge loading={loading} errorCount={errorCount} total={totalCount} /> <StatusBadge loading={loading} errorCount={errorCount} total={totalCount} />
{!loading && totalCount > 0 && <span className="text-xs text-t3"> {totalCount}</span>} {!loading && totalCount > 0 && (
<span className="text-caption text-t3"> {totalCount}</span>
)}
</div> </div>
{/* 테이블 */} {/* 테이블 */}

파일 보기

@ -84,7 +84,7 @@ function StatusBadge({
}) { }) {
if (loading) { if (loading) {
return ( return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-bg-elevated text-t2"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-caption bg-bg-elevated text-t2">
<span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" /> <span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" />
... ...
</span> </span>
@ -92,7 +92,7 @@ function StatusBadge({
} }
if (errorCount === total) { if (errorCount === total) {
return ( return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-red-500/10 text-red-400"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-caption bg-red-500/10 text-red-400">
<span className="w-1.5 h-1.5 rounded-full bg-red-400" /> <span className="w-1.5 h-1.5 rounded-full bg-red-400" />
</span> </span>
@ -100,14 +100,14 @@ function StatusBadge({
} }
if (errorCount > 0) { if (errorCount > 0) {
return ( return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-yellow-500/10 text-yellow-400"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-caption bg-yellow-500/10 text-yellow-400">
<span className="w-1.5 h-1.5 rounded-full bg-yellow-400" /> <span className="w-1.5 h-1.5 rounded-full bg-yellow-400" />
({errorCount}/{total}) ({errorCount}/{total})
</span> </span>
); );
} }
return ( return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-emerald-500/10 text-emerald-400"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-caption bg-emerald-500/10 text-emerald-400">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" /> <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
</span> </span>
@ -130,7 +130,7 @@ function KhoaTable({ rows, loading }: { rows: KhoaRow[]; loading: boolean }) {
return ( return (
<div className="overflow-auto"> <div className="overflow-auto">
<table className="w-full text-xs border-collapse"> <table className="w-full text-caption border-collapse">
<thead> <thead>
<tr className="bg-bg-elevated text-t3 uppercase tracking-wide"> <tr className="bg-bg-elevated text-t3 uppercase tracking-wide">
{headers.map((h) => ( {headers.map((h) => (
@ -172,11 +172,11 @@ function KhoaTable({ rows, loading }: { rows: KhoaRow[]; loading: boolean }) {
<td className="px-3 py-2 text-t2">{fmt(row.data?.tide_level, 0)}</td> <td className="px-3 py-2 text-t2">{fmt(row.data?.tide_level, 0)}</td>
<td className="px-3 py-2"> <td className="px-3 py-2">
{row.error ? ( {row.error ? (
<span className="text-red-400 text-xs"></span> <span className="text-red-400 text-caption"></span>
) : row.data ? ( ) : row.data ? (
<span className="text-emerald-400 text-xs"></span> <span className="text-emerald-400 text-caption"></span>
) : ( ) : (
<span className="text-t3 text-xs">-</span> <span className="text-t3 text-caption">-</span>
)} )}
</td> </td>
</tr> </tr>
@ -201,7 +201,7 @@ function KmaUltraTable({ rows, loading }: { rows: KmaUltraRow[]; loading: boolea
return ( return (
<div className="overflow-auto"> <div className="overflow-auto">
<table className="w-full text-xs border-collapse"> <table className="w-full text-caption border-collapse">
<thead> <thead>
<tr className="bg-bg-elevated text-t3 uppercase tracking-wide"> <tr className="bg-bg-elevated text-t3 uppercase tracking-wide">
{headers.map((h) => ( {headers.map((h) => (
@ -241,11 +241,11 @@ function KmaUltraTable({ rows, loading }: { rows: KmaUltraRow[]; loading: boolea
<td className="px-3 py-2 text-t2">{fmt(row.data?.humidity, 0)}</td> <td className="px-3 py-2 text-t2">{fmt(row.data?.humidity, 0)}</td>
<td className="px-3 py-2"> <td className="px-3 py-2">
{row.error ? ( {row.error ? (
<span className="text-red-400 text-xs"></span> <span className="text-red-400 text-caption"></span>
) : row.data ? ( ) : row.data ? (
<span className="text-emerald-400 text-xs"></span> <span className="text-emerald-400 text-caption"></span>
) : ( ) : (
<span className="text-t3 text-xs">-</span> <span className="text-t3 text-caption">-</span>
)} )}
</td> </td>
</tr> </tr>
@ -261,7 +261,7 @@ function MarineTable({ rows, loading }: { rows: MarineRow[]; loading: boolean })
return ( return (
<div className="overflow-auto"> <div className="overflow-auto">
<table className="w-full text-xs border-collapse"> <table className="w-full text-caption border-collapse">
<thead> <thead>
<tr className="bg-bg-elevated text-t3 uppercase tracking-wide"> <tr className="bg-bg-elevated text-t3 uppercase tracking-wide">
{headers.map((h) => ( {headers.map((h) => (
@ -294,11 +294,11 @@ function MarineTable({ rows, loading }: { rows: MarineRow[]; loading: boolean })
<td className="px-3 py-2 text-t2">{fmt(row.data?.temperature)}</td> <td className="px-3 py-2 text-t2">{fmt(row.data?.temperature)}</td>
<td className="px-3 py-2"> <td className="px-3 py-2">
{row.error ? ( {row.error ? (
<span className="text-red-400 text-xs"></span> <span className="text-red-400 text-caption"></span>
) : row.data ? ( ) : row.data ? (
<span className="text-emerald-400 text-xs"></span> <span className="text-emerald-400 text-caption"></span>
) : ( ) : (
<span className="text-t3 text-xs">-</span> <span className="text-t3 text-caption">-</span>
)} )}
</td> </td>
</tr> </tr>
@ -441,10 +441,10 @@ export default function MonitorRealtimePanel() {
<div className="flex flex-col h-full overflow-hidden"> <div className="flex flex-col h-full overflow-hidden">
{/* 헤더 */} {/* 헤더 */}
<div className="flex items-center justify-between px-5 py-3 border-b border-stroke-1 shrink-0"> <div className="flex items-center justify-between px-5 py-3 border-b border-stroke-1 shrink-0">
<h2 className="text-sm font-semibold text-t1"> </h2> <h2 className="text-body-2 font-semibold text-t1"> </h2>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{lastUpdate && ( {lastUpdate && (
<span className="text-xs text-t3"> <span className="text-caption text-t3">
:{' '} :{' '}
{lastUpdate.toLocaleTimeString('ko-KR', { {lastUpdate.toLocaleTimeString('ko-KR', {
hour: '2-digit', hour: '2-digit',
@ -456,7 +456,7 @@ export default function MonitorRealtimePanel() {
<button <button
onClick={handleRefresh} onClick={handleRefresh}
disabled={isLoading} disabled={isLoading}
className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded bg-bg-elevated hover:bg-bg-card text-t2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" className="flex items-center gap-1.5 px-3 py-1.5 text-caption rounded bg-bg-elevated hover:bg-bg-card text-t2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
> >
<svg <svg
className={`w-3.5 h-3.5 ${isLoading ? 'animate-spin' : ''}`} className={`w-3.5 h-3.5 ${isLoading ? 'animate-spin' : ''}`}
@ -482,7 +482,7 @@ export default function MonitorRealtimePanel() {
<button <button
key={tab.id} key={tab.id}
onClick={() => setActiveTab(tab.id)} onClick={() => setActiveTab(tab.id)}
className={`px-4 py-2.5 text-xs font-medium border-b-2 transition-colors ${ className={`px-4 py-2.5 text-caption font-medium border-b-2 transition-colors ${
activeTab === tab.id activeTab === tab.id
? 'border-cyan-400 text-cyan-400' ? 'border-cyan-400 text-cyan-400'
: 'border-transparent text-t3 hover:text-t2' : 'border-transparent text-t3 hover:text-t2'
@ -496,7 +496,7 @@ export default function MonitorRealtimePanel() {
{/* 상태 표시줄 */} {/* 상태 표시줄 */}
<div className="flex items-center gap-3 px-5 py-2 shrink-0 border-b border-stroke-1 bg-bg-base"> <div className="flex items-center gap-3 px-5 py-2 shrink-0 border-b border-stroke-1 bg-bg-base">
<StatusBadge loading={isLoading} errorCount={errorCount} total={totalCount} /> <StatusBadge loading={isLoading} errorCount={errorCount} total={totalCount} />
<span className="text-xs text-t3"> <span className="text-caption text-t3">
{activeTab === 'khoa' && `관측소 ${totalCount}`} {activeTab === 'khoa' && `관측소 ${totalCount}`}
{activeTab === 'kma-ultra' && `지점 ${totalCount}`} {activeTab === 'kma-ultra' && `지점 ${totalCount}`}
{activeTab === 'kma-marine' && `해역 ${totalCount}`} {activeTab === 'kma-marine' && `해역 ${totalCount}`}

파일 보기

@ -300,7 +300,7 @@ function StatusBadge({
}) { }) {
if (loading) { if (loading) {
return ( return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-bg-elevated text-t2"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-caption bg-bg-elevated text-t2">
<span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" /> <span className="w-1.5 h-1.5 rounded-full bg-cyan-400 animate-pulse" />
... ...
</span> </span>
@ -309,7 +309,7 @@ function StatusBadge({
const offCount = total - onCount; const offCount = total - onCount;
if (offCount === total) { if (offCount === total) {
return ( return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-red-500/10 text-red-400"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-caption bg-red-500/10 text-red-400">
<span className="w-1.5 h-1.5 rounded-full bg-red-400" /> <span className="w-1.5 h-1.5 rounded-full bg-red-400" />
OFF OFF
</span> </span>
@ -317,14 +317,14 @@ function StatusBadge({
} }
if (offCount > 0) { if (offCount > 0) {
return ( return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-yellow-500/10 text-yellow-400"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-caption bg-yellow-500/10 text-yellow-400">
<span className="w-1.5 h-1.5 rounded-full bg-yellow-400" /> <span className="w-1.5 h-1.5 rounded-full bg-yellow-400" />
OFF ({offCount}/{total}) OFF ({offCount}/{total})
</span> </span>
); );
} }
return ( return (
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs bg-emerald-500/10 text-emerald-400"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-caption bg-emerald-500/10 text-emerald-400">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" /> <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
</span> </span>
@ -376,7 +376,7 @@ const HEADERS = [
function VesselTable({ rows, loading }: { rows: VesselMonitorRow[]; loading: boolean }) { function VesselTable({ rows, loading }: { rows: VesselMonitorRow[]; loading: boolean }) {
return ( return (
<div className="overflow-auto"> <div className="overflow-auto">
<table className="w-full text-xs border-collapse"> <table className="w-full text-caption border-collapse">
<thead> <thead>
<tr className="bg-bg-elevated text-t3 uppercase tracking-wide"> <tr className="bg-bg-elevated text-t3 uppercase tracking-wide">
{HEADERS.map((h) => ( {HEADERS.map((h) => (
@ -462,10 +462,10 @@ export default function MonitorVesselPanel() {
<div className="flex flex-col h-full overflow-hidden"> <div className="flex flex-col h-full overflow-hidden">
{/* 헤더 */} {/* 헤더 */}
<div className="flex items-center justify-between px-5 py-3 border-b border-stroke-1 shrink-0"> <div className="flex items-center justify-between px-5 py-3 border-b border-stroke-1 shrink-0">
<h2 className="text-sm font-semibold text-t1"> </h2> <h2 className="text-body-2 font-semibold text-t1"> </h2>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{lastUpdate && ( {lastUpdate && (
<span className="text-xs text-t3"> <span className="text-caption text-t3">
:{' '} :{' '}
{lastUpdate.toLocaleTimeString('ko-KR', { {lastUpdate.toLocaleTimeString('ko-KR', {
hour: '2-digit', hour: '2-digit',
@ -477,7 +477,7 @@ export default function MonitorVesselPanel() {
<button <button
onClick={fetchData} onClick={fetchData}
disabled={loading} disabled={loading}
className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded bg-bg-elevated hover:bg-bg-card text-t2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors" className="flex items-center gap-1.5 px-3 py-1.5 text-caption rounded bg-bg-elevated hover:bg-bg-card text-t2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
> >
<svg <svg
className={`w-3.5 h-3.5 ${loading ? 'animate-spin' : ''}`} className={`w-3.5 h-3.5 ${loading ? 'animate-spin' : ''}`}
@ -500,7 +500,7 @@ export default function MonitorVesselPanel() {
{/* 상태 표시줄 */} {/* 상태 표시줄 */}
<div className="flex items-center gap-3 px-5 py-2 shrink-0 border-b border-stroke-1 bg-bg-base"> <div className="flex items-center gap-3 px-5 py-2 shrink-0 border-b border-stroke-1 bg-bg-base">
<StatusBadge loading={loading} onCount={onCount} total={rows.length} /> <StatusBadge loading={loading} onCount={onCount} total={rows.length} />
<span className="text-xs text-t3"> <span className="text-caption text-t3">
{rows.length} (ON: {onCount} / OFF: {rows.length - onCount}) {rows.length} (ON: {onCount} / OFF: {rows.length - onCount})
</span> </span>
</div> </div>

파일 보기

@ -557,7 +557,7 @@ function RolePermTab({
</table> </table>
</div> </div>
) : ( ) : (
<div className="flex-1 flex items-center justify-center text-fg-disabled text-sm font-korean"> <div className="flex-1 flex items-center justify-center text-fg-disabled text-body-2 font-korean">
</div> </div>
)} )}
@ -567,7 +567,7 @@ function RolePermTab({
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"> <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="w-[400px] bg-bg-surface rounded-lg border border-stroke shadow-2xl"> <div className="w-[400px] bg-bg-surface rounded-lg border border-stroke shadow-2xl">
<div className="px-5 py-4 border-b border-stroke"> <div className="px-5 py-4 border-b border-stroke">
<h3 className="text-sm font-bold text-fg font-korean"> </h3> <h3 className="text-body-2 font-bold text-fg font-korean"> </h3>
</div> </div>
<div className="px-5 py-4 flex flex-col gap-3"> <div className="px-5 py-4 flex flex-col gap-3">
<div> <div>
@ -581,7 +581,7 @@ function RolePermTab({
setNewRoleCode(e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, '')) setNewRoleCode(e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, ''))
} }
placeholder="CUSTOM_ROLE" placeholder="CUSTOM_ROLE"
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono"
/> />
<p className="text-caption text-fg-disabled mt-1 font-korean"> <p className="text-caption text-fg-disabled mt-1 font-korean">
, , ( ) , , ( )
@ -596,7 +596,7 @@ function RolePermTab({
value={newRoleName} value={newRoleName}
onChange={(e) => setNewRoleName(e.target.value)} onChange={(e) => setNewRoleName(e.target.value)}
placeholder="사용자 정의 역할" placeholder="사용자 정의 역할"
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean"
/> />
</div> </div>
<div> <div>
@ -608,7 +608,7 @@ function RolePermTab({
value={newRoleDesc} value={newRoleDesc}
onChange={(e) => setNewRoleDesc(e.target.value)} onChange={(e) => setNewRoleDesc(e.target.value)}
placeholder="역할에 대한 설명" placeholder="역할에 대한 설명"
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean"
/> />
</div> </div>
{createError && ( {createError && (
@ -620,14 +620,14 @@ function RolePermTab({
<div className="px-5 py-3 border-t border-stroke flex justify-end gap-2"> <div className="px-5 py-3 border-t border-stroke flex justify-end gap-2">
<button <button
onClick={() => setShowCreateForm(false)} onClick={() => setShowCreateForm(false)}
className="px-4 py-2 text-xs text-fg-disabled border border-stroke rounded-md hover:bg-bg-surface-hover font-korean" className="px-4 py-2 text-caption text-fg-disabled border border-stroke rounded-md hover:bg-bg-surface-hover font-korean"
> >
</button> </button>
<button <button
onClick={handleCreateRole} onClick={handleCreateRole}
disabled={!newRoleCode || !newRoleName || creating} disabled={!newRoleCode || !newRoleName || creating}
className="px-4 py-2 text-xs font-semibold rounded-md bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)] transition-all font-korean disabled:opacity-50" className="px-4 py-2 text-caption font-semibold rounded-md bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)] transition-all font-korean disabled:opacity-50"
> >
{creating ? '생성 중...' : '생성'} {creating ? '생성 중...' : '생성'}
</button> </button>
@ -815,7 +815,7 @@ function UserPermTab({ roles, permTree, rolePerms }: UserPermTabProps) {
onFocus={() => setShowDropdown(true)} onFocus={() => setShowDropdown(true)}
placeholder={loadingUsers ? '불러오는 중...' : '이름, 계정, 조직으로 검색...'} placeholder={loadingUsers ? '불러오는 중...' : '이름, 계정, 조직으로 검색...'}
disabled={loadingUsers} disabled={loadingUsers}
className="w-full max-w-sm px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean disabled:opacity-50" className="w-full max-w-sm px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean disabled:opacity-50"
/> />
{showDropdown && filteredUsers.length > 0 && ( {showDropdown && filteredUsers.length > 0 && (
<div className="absolute left-0 top-full mt-1 w-full max-w-sm bg-bg-surface border border-stroke rounded-md shadow-xl z-20 overflow-auto max-h-52"> <div className="absolute left-0 top-full mt-1 w-full max-w-sm bg-bg-surface border border-stroke rounded-md shadow-xl z-20 overflow-auto max-h-52">
@ -826,7 +826,7 @@ function UserPermTab({ roles, permTree, rolePerms }: UserPermTabProps) {
className="w-full px-3 py-2 text-left hover:bg-bg-surface-hover transition-colors flex items-center gap-2" className="w-full px-3 py-2 text-left hover:bg-bg-surface-hover transition-colors flex items-center gap-2"
> >
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="text-xs font-semibold text-fg font-korean truncate"> <div className="text-caption font-semibold text-fg font-korean truncate">
{user.name} {user.name}
{user.rank && ( {user.rank && (
<span className="ml-1 text-caption text-fg-disabled font-korean"> <span className="ml-1 text-caption text-fg-disabled font-korean">
@ -848,7 +848,7 @@ function UserPermTab({ roles, permTree, rolePerms }: UserPermTabProps) {
</div> </div>
)} )}
{showDropdown && !loadingUsers && filteredUsers.length === 0 && searchQuery && ( {showDropdown && !loadingUsers && filteredUsers.length === 0 && searchQuery && (
<div className="absolute left-0 top-full mt-1 w-full max-w-sm bg-bg-surface border border-stroke rounded-md shadow-xl z-20 px-3 py-2 text-xs text-fg-disabled font-korean"> <div className="absolute left-0 top-full mt-1 w-full max-w-sm bg-bg-surface border border-stroke rounded-md shadow-xl z-20 px-3 py-2 text-caption text-fg-disabled font-korean">
</div> </div>
)} )}
@ -954,13 +954,13 @@ function UserPermTab({ roles, permTree, rolePerms }: UserPermTabProps) {
</table> </table>
</div> </div>
) : ( ) : (
<div className="flex-1 flex items-center justify-center text-fg-disabled text-sm font-korean"> <div className="flex-1 flex items-center justify-center text-fg-disabled text-body-2 font-korean">
</div> </div>
)} )}
</> </>
) : ( ) : (
<div className="flex-1 flex items-center justify-center text-fg-disabled text-sm font-korean"> <div className="flex-1 flex items-center justify-center text-fg-disabled text-body-2 font-korean">
</div> </div>
)} )}
@ -1180,7 +1180,7 @@ function PermissionsPanel() {
if (loading) { if (loading) {
return ( return (
<div className="flex items-center justify-center h-32 text-fg-disabled text-sm font-korean"> <div className="flex items-center justify-center h-32 text-fg-disabled text-body-2 font-korean">
... ...
</div> </div>
); );
@ -1194,7 +1194,7 @@ function PermissionsPanel() {
style={{ flexShrink: 0 }} style={{ flexShrink: 0 }}
> >
<div> <div>
<h1 className="text-sm font-bold text-fg font-korean"> </h1> <h1 className="text-body-2 font-bold text-fg font-korean"> </h1>
<p className="text-caption text-fg-disabled mt-0.5 font-korean"> <p className="text-caption text-fg-disabled mt-0.5 font-korean">
× CRUD × CRUD
</p> </p>
@ -1203,7 +1203,7 @@ function PermissionsPanel() {
<div className="flex items-center gap-1 p-1 bg-bg-elevated rounded-lg border border-stroke"> <div className="flex items-center gap-1 p-1 bg-bg-elevated rounded-lg border border-stroke">
<button <button
onClick={() => setActiveTab('role')} onClick={() => setActiveTab('role')}
className={`px-4 py-1.5 text-xs font-semibold rounded-md transition-all font-korean ${ className={`px-4 py-1.5 text-caption font-semibold rounded-md transition-all font-korean ${
activeTab === 'role' activeTab === 'role'
? 'bg-color-accent text-bg-0 shadow-[0_0_8px_rgba(6,182,212,0.25)]' ? 'bg-color-accent text-bg-0 shadow-[0_0_8px_rgba(6,182,212,0.25)]'
: 'text-fg-disabled hover:text-fg-sub' : 'text-fg-disabled hover:text-fg-sub'
@ -1213,7 +1213,7 @@ function PermissionsPanel() {
</button> </button>
<button <button
onClick={() => setActiveTab('user')} onClick={() => setActiveTab('user')}
className={`px-4 py-1.5 text-xs font-semibold rounded-md transition-all font-korean ${ className={`px-4 py-1.5 text-caption font-semibold rounded-md transition-all font-korean ${
activeTab === 'user' activeTab === 'user'
? 'bg-color-accent text-bg-0 shadow-[0_0_8px_rgba(6,182,212,0.25)]' ? 'bg-color-accent text-bg-0 shadow-[0_0_8px_rgba(6,182,212,0.25)]'
: 'text-fg-disabled hover:text-fg-sub' : 'text-fg-disabled hover:text-fg-sub'

파일 보기

@ -136,7 +136,7 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<div> <div>
<h1 className="text-lg font-bold text-fg font-korean">{title}</h1> <h1 className="text-lg font-bold text-fg font-korean">{title}</h1>
<p className="text-xs text-fg-disabled mt-1 font-korean"> {total}</p> <p className="text-caption text-fg-disabled mt-1 font-korean"> {total}</p>
</div> </div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -146,12 +146,12 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
onChange={(e) => setSearchInput(e.target.value)} onChange={(e) => setSearchInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()} onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
placeholder="레이어코드 / 레이어명 검색" placeholder="레이어코드 / 레이어명 검색"
className="flex-1 px-3 py-1.5 text-xs bg-bg-elevated border border-stroke rounded text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean" className="flex-1 px-3 py-1.5 text-caption bg-bg-elevated border border-stroke rounded text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean"
/> />
<select <select
value={filterUseYn} value={filterUseYn}
onChange={(e) => setFilterUseYn(e.target.value)} onChange={(e) => setFilterUseYn(e.target.value)}
className="px-2 py-1.5 text-xs bg-bg-elevated border border-stroke rounded text-fg focus:border-color-accent focus:outline-none font-korean" className="px-2 py-1.5 text-caption bg-bg-elevated border border-stroke rounded text-fg focus:border-color-accent focus:outline-none font-korean"
> >
<option value=""></option> <option value=""></option>
<option value="Y"></option> <option value="Y"></option>
@ -159,7 +159,7 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
</select> </select>
<button <button
onClick={handleSearch} onClick={handleSearch}
className="px-3 py-1.5 text-xs border border-stroke text-fg-sub rounded hover:bg-[rgba(255,255,255,0.04)] transition-all font-korean" className="px-3 py-1.5 text-caption border border-stroke text-fg-sub rounded hover:bg-[rgba(255,255,255,0.04)] transition-all font-korean"
> >
</button> </button>
@ -168,7 +168,7 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
{/* 오류 메시지 */} {/* 오류 메시지 */}
{error && ( {error && (
<div className="px-6 py-2 text-xs text-red-400 bg-[rgba(239,68,68,0.05)] border-b border-stroke shrink-0 font-korean"> <div className="px-6 py-2 text-caption text-red-400 bg-[rgba(239,68,68,0.05)] border-b border-stroke shrink-0 font-korean">
{error} {error}
</div> </div>
)} )}
@ -176,7 +176,7 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
{/* 테이블 영역 */} {/* 테이블 영역 */}
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{loading ? ( {loading ? (
<div className="flex items-center justify-center h-full text-fg-disabled text-sm font-korean"> <div className="flex items-center justify-center h-full text-fg-disabled text-body-2 font-korean">
... ...
</div> </div>
) : ( ) : (
@ -217,7 +217,7 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
<tr> <tr>
<td <td
colSpan={9} colSpan={9}
className="px-4 py-12 text-center text-fg-disabled text-sm font-korean" className="px-4 py-12 text-center text-fg-disabled text-body-2 font-korean"
> >
. .
</td> </td>
@ -228,12 +228,12 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
key={item.layerCd} key={item.layerCd}
className="border-b border-stroke hover:bg-[rgba(255,255,255,0.02)] transition-colors" className="border-b border-stroke hover:bg-[rgba(255,255,255,0.02)] transition-colors"
> >
<td className="px-4 py-3 text-xs text-fg-disabled font-mono"> <td className="px-4 py-3 text-caption text-fg-disabled font-mono">
{(page - 1) * PAGE_SIZE + idx + 1} {(page - 1) * PAGE_SIZE + idx + 1}
</td> </td>
<td className="px-4 py-3 text-label-2 text-fg-sub font-mono">{item.layerCd}</td> <td className="px-4 py-3 text-label-2 text-fg-sub font-mono">{item.layerCd}</td>
<td className="px-4 py-3 text-xs text-fg font-korean">{item.layerNm}</td> <td className="px-4 py-3 text-caption text-fg font-korean">{item.layerNm}</td>
<td className="px-4 py-3 text-xs text-fg-sub font-korean max-w-[200px]"> <td className="px-4 py-3 text-caption text-fg-sub font-korean max-w-[200px]">
<span className="block truncate" title={item.layerFullNm}> <span className="block truncate" title={item.layerFullNm}>
{item.layerFullNm} {item.layerFullNm}
</span> </span>
@ -246,7 +246,7 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
<td className="px-4 py-3 text-label-2 text-fg-sub font-mono"> <td className="px-4 py-3 text-label-2 text-fg-sub font-mono">
{item.wmsLayerNm ?? <span className="text-fg-disabled">-</span>} {item.wmsLayerNm ?? <span className="text-fg-disabled">-</span>}
</td> </td>
<td className="px-4 py-3 text-xs text-fg-disabled text-center font-mono"> <td className="px-4 py-3 text-caption text-fg-disabled text-center font-mono">
{item.sortOrd} {item.sortOrd}
</td> </td>
<td className="px-4 py-3 text-label-2 text-fg-disabled font-mono"> <td className="px-4 py-3 text-label-2 text-fg-disabled font-mono">

파일 보기

@ -54,7 +54,7 @@ function SettingsPanel() {
if (loading) { if (loading) {
return ( return (
<div className="flex items-center justify-center h-32 text-fg-disabled text-sm font-korean"> <div className="flex items-center justify-center h-32 text-fg-disabled text-body-2 font-korean">
... ...
</div> </div>
); );
@ -64,7 +64,7 @@ function SettingsPanel() {
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
<div className="px-6 py-4 border-b border-stroke"> <div className="px-6 py-4 border-b border-stroke">
<h1 className="text-lg font-bold text-fg font-korean"> </h1> <h1 className="text-lg font-bold text-fg font-korean"> </h1>
<p className="text-xs text-fg-disabled mt-1 font-korean"> <p className="text-caption text-fg-disabled mt-1 font-korean">
</p> </p>
</div> </div>
@ -74,7 +74,7 @@ function SettingsPanel() {
{/* 사용자 등록 설정 */} {/* 사용자 등록 설정 */}
<div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden"> <div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden">
<div className="px-5 py-3 border-b border-stroke"> <div className="px-5 py-3 border-b border-stroke">
<h2 className="text-sm font-bold text-fg font-korean"> </h2> <h2 className="text-body-2 font-bold text-fg font-korean"> </h2>
<p className="text-label-2 text-fg-disabled mt-0.5 font-korean"> <p className="text-label-2 text-fg-disabled mt-0.5 font-korean">
</p> </p>
@ -140,7 +140,7 @@ function SettingsPanel() {
{/* OAuth 설정 */} {/* OAuth 설정 */}
<div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden"> <div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden">
<div className="px-5 py-3 border-b border-stroke"> <div className="px-5 py-3 border-b border-stroke">
<h2 className="text-sm font-bold text-fg font-korean">Google OAuth </h2> <h2 className="text-body-2 font-bold text-fg font-korean">Google OAuth </h2>
<p className="text-label-2 text-fg-disabled mt-0.5 font-korean"> <p className="text-label-2 text-fg-disabled mt-0.5 font-korean">
Google Google
</p> </p>
@ -162,7 +162,7 @@ function SettingsPanel() {
value={oauthDomainInput} value={oauthDomainInput}
onChange={(e) => setOauthDomainInput(e.target.value)} onChange={(e) => setOauthDomainInput(e.target.value)}
placeholder="gcsc.co.kr, example.com" placeholder="gcsc.co.kr, example.com"
className="flex-1 px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono" className="flex-1 px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono"
/> />
<button <button
onClick={async () => { onClick={async () => {
@ -183,7 +183,7 @@ function SettingsPanel() {
savingOAuth || savingOAuth ||
oauthDomainInput.trim() === (oauthSettings?.autoApproveDomains || '') oauthDomainInput.trim() === (oauthSettings?.autoApproveDomains || '')
} }
className={`px-4 py-2 text-xs font-semibold rounded-md transition-all font-korean whitespace-nowrap ${ className={`px-4 py-2 text-caption font-semibold rounded-md transition-all font-korean whitespace-nowrap ${
oauthDomainInput.trim() !== (oauthSettings?.autoApproveDomains || '') oauthDomainInput.trim() !== (oauthSettings?.autoApproveDomains || '')
? 'bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)]' ? 'bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)]'
: 'bg-bg-card text-fg-disabled cursor-not-allowed' : 'bg-bg-card text-fg-disabled cursor-not-allowed'
@ -220,7 +220,7 @@ function SettingsPanel() {
{/* 현재 설정 상태 요약 */} {/* 현재 설정 상태 요약 */}
<div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden"> <div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden">
<div className="px-5 py-3 border-b border-stroke"> <div className="px-5 py-3 border-b border-stroke">
<h2 className="text-sm font-bold text-fg font-korean"> </h2> <h2 className="text-body-2 font-bold text-fg font-korean"> </h2>
</div> </div>
<div className="px-5 py-4"> <div className="px-5 py-4">
<div className="flex flex-col gap-3 text-label-1 font-korean"> <div className="flex flex-col gap-3 text-label-1 font-korean">

파일 보기

@ -71,7 +71,7 @@ function SortableMenuItem({
<circle cx="9" cy="14" r="1.5" /> <circle cx="9" cy="14" r="1.5" />
</svg> </svg>
</button> </button>
<span className="text-fg-disabled text-xs font-mono w-6 text-center shrink-0"> <span className="text-fg-disabled text-caption font-mono w-6 text-center shrink-0">
{idx + 1} {idx + 1}
</span> </span>
{isEditing ? ( {isEditing ? (
@ -152,14 +152,14 @@ function SortableMenuItem({
<button <button
onClick={() => onMove(idx, -1)} onClick={() => onMove(idx, -1)}
disabled={idx === 0} disabled={idx === 0}
className="w-7 h-7 rounded border border-stroke bg-bg-elevated text-fg-disabled text-xs flex items-center justify-center hover:bg-bg-surface-hover hover:text-fg transition-all disabled:opacity-30 disabled:cursor-not-allowed" className="w-7 h-7 rounded border border-stroke bg-bg-elevated text-fg-disabled text-caption flex items-center justify-center hover:bg-bg-surface-hover hover:text-fg transition-all disabled:opacity-30 disabled:cursor-not-allowed"
> >
</button> </button>
<button <button
onClick={() => onMove(idx, 1)} onClick={() => onMove(idx, 1)}
disabled={idx === totalCount - 1} disabled={idx === totalCount - 1}
className="w-7 h-7 rounded border border-stroke bg-bg-elevated text-fg-disabled text-xs flex items-center justify-center hover:bg-bg-surface-hover hover:text-fg transition-all disabled:opacity-30 disabled:cursor-not-allowed" className="w-7 h-7 rounded border border-stroke bg-bg-elevated text-fg-disabled text-caption flex items-center justify-center hover:bg-bg-surface-hover hover:text-fg transition-all disabled:opacity-30 disabled:cursor-not-allowed"
> >
</button> </button>

파일 보기

@ -87,7 +87,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
<div className="bg-bg-surface border border-stroke rounded-lg shadow-lg w-[480px] max-h-[90vh] flex flex-col"> <div className="bg-bg-surface border border-stroke rounded-lg shadow-lg w-[480px] max-h-[90vh] flex flex-col">
{/* 헤더 */} {/* 헤더 */}
<div className="flex items-center justify-between px-6 py-4 border-b border-stroke"> <div className="flex items-center justify-between px-6 py-4 border-b border-stroke">
<h2 className="text-sm font-bold text-fg font-korean"> </h2> <h2 className="text-body-2 font-bold text-fg font-korean"> </h2>
<button onClick={onClose} className="text-fg-disabled hover:text-fg transition-colors"> <button onClick={onClose} className="text-fg-disabled hover:text-fg transition-colors">
<svg <svg
width="16" width="16"
@ -115,7 +115,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
value={account} value={account}
onChange={(e) => setAccount(e.target.value)} onChange={(e) => setAccount(e.target.value)}
placeholder="로그인 계정 ID" placeholder="로그인 계정 ID"
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono"
/> />
</div> </div>
@ -129,7 +129,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
placeholder="초기 비밀번호" placeholder="초기 비밀번호"
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono"
/> />
</div> </div>
@ -143,7 +143,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
placeholder="실명" placeholder="실명"
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean"
/> />
</div> </div>
@ -157,7 +157,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
value={rank} value={rank}
onChange={(e) => setRank(e.target.value)} onChange={(e) => setRank(e.target.value)}
placeholder="예: 팀장, 주임 등" placeholder="예: 팀장, 주임 등"
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean"
/> />
</div> </div>
@ -169,7 +169,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
<select <select
value={orgSn} value={orgSn}
onChange={(e) => setOrgSn(e.target.value !== '' ? Number(e.target.value) : '')} onChange={(e) => setOrgSn(e.target.value !== '' ? Number(e.target.value) : '')}
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean"
> >
<option value=""> </option> <option value=""> </option>
{allOrgs.map((org) => ( {allOrgs.map((org) => (
@ -191,7 +191,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
placeholder="이메일 주소" placeholder="이메일 주소"
className="w-full px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono" className="w-full px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono"
/> />
</div> </div>
@ -217,7 +217,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
onChange={() => toggleRole(role.sn)} onChange={() => toggleRole(role.sn)}
style={{ accentColor: color }} style={{ accentColor: color }}
/> />
<span className="text-xs font-korean" style={{ color }}> <span className="text-caption font-korean" style={{ color }}>
{role.name} {role.name}
</span> </span>
<span className="text-caption text-fg-disabled font-mono">{role.code}</span> <span className="text-caption text-fg-disabled font-mono">{role.code}</span>
@ -237,14 +237,14 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
<button <button
type="button" type="button"
onClick={onClose} onClick={onClose}
className="px-4 py-2 text-xs border border-stroke text-fg-sub rounded-md hover:bg-[rgba(255,255,255,0.04)] transition-all font-korean" className="px-4 py-2 text-caption border border-stroke text-fg-sub rounded-md hover:bg-[rgba(255,255,255,0.04)] transition-all font-korean"
> >
</button> </button>
<button <button
type="submit" type="submit"
disabled={submitting} disabled={submitting}
className="px-4 py-2 text-xs font-semibold rounded-md bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)] transition-all disabled:opacity-50 font-korean" className="px-4 py-2 text-caption font-semibold rounded-md bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)] transition-all disabled:opacity-50 font-korean"
> >
{submitting ? '등록 중...' : '등록'} {submitting ? '등록 중...' : '등록'}
</button> </button>
@ -332,7 +332,7 @@ function UserDetailModal({ user, allOrgs, onClose, onUpdated }: UserDetailModalP
{/* 헤더 */} {/* 헤더 */}
<div className="flex items-center justify-between px-6 py-4 border-b border-stroke"> <div className="flex items-center justify-between px-6 py-4 border-b border-stroke">
<div> <div>
<h2 className="text-sm font-bold text-fg font-korean"> </h2> <h2 className="text-body-2 font-bold text-fg font-korean"> </h2>
<p className="text-caption text-fg-disabled font-mono mt-0.5">{user.account}</p> <p className="text-caption text-fg-disabled font-mono mt-0.5">{user.account}</p>
</div> </div>
<button onClick={onClose} className="text-fg-disabled hover:text-fg transition-colors"> <button onClick={onClose} className="text-fg-disabled hover:text-fg transition-colors">
@ -364,7 +364,7 @@ function UserDetailModal({ user, allOrgs, onClose, onUpdated }: UserDetailModalP
type="text" type="text"
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
className="w-full px-3 py-1.5 text-xs bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean" className="w-full px-3 py-1.5 text-caption bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean"
/> />
</div> </div>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
@ -377,7 +377,7 @@ function UserDetailModal({ user, allOrgs, onClose, onUpdated }: UserDetailModalP
value={rank} value={rank}
onChange={(e) => setRank(e.target.value)} onChange={(e) => setRank(e.target.value)}
placeholder="예: 팀장" placeholder="예: 팀장"
className="w-full px-3 py-1.5 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean" className="w-full px-3 py-1.5 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean"
/> />
</div> </div>
<div> <div>
@ -387,7 +387,7 @@ function UserDetailModal({ user, allOrgs, onClose, onUpdated }: UserDetailModalP
<select <select
value={orgSn} value={orgSn}
onChange={(e) => setOrgSn(e.target.value !== '' ? Number(e.target.value) : '')} onChange={(e) => setOrgSn(e.target.value !== '' ? Number(e.target.value) : '')}
className="w-full px-3 py-1.5 text-xs bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean" className="w-full px-3 py-1.5 text-caption bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean"
> >
<option value=""> </option> <option value=""> </option>
{allOrgs.map((org) => ( {allOrgs.map((org) => (
@ -427,7 +427,7 @@ function UserDetailModal({ user, allOrgs, onClose, onUpdated }: UserDetailModalP
value={newPassword} value={newPassword}
onChange={(e) => setNewPassword(e.target.value)} onChange={(e) => setNewPassword(e.target.value)}
placeholder="새 비밀번호 입력" placeholder="새 비밀번호 입력"
className="w-full px-3 py-1.5 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono" className="w-full px-3 py-1.5 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-mono"
/> />
</div> </div>
<button <button
@ -535,7 +535,7 @@ function UserDetailModal({ user, allOrgs, onClose, onUpdated }: UserDetailModalP
<div className="flex items-center justify-end px-6 py-3 border-t border-stroke"> <div className="flex items-center justify-end px-6 py-3 border-t border-stroke">
<button <button
onClick={onClose} onClick={onClose}
className="px-4 py-2 text-xs border border-stroke text-fg-sub rounded-md hover:bg-[rgba(255,255,255,0.04)] transition-all font-korean" className="px-4 py-2 text-caption border border-stroke text-fg-sub rounded-md hover:bg-[rgba(255,255,255,0.04)] transition-all font-korean"
> >
</button> </button>
@ -680,7 +680,7 @@ function UsersPanel() {
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div> <div>
<h1 className="text-lg font-bold text-fg font-korean"> </h1> <h1 className="text-lg font-bold text-fg font-korean"> </h1>
<p className="text-xs text-fg-disabled mt-1 font-korean"> <p className="text-caption text-fg-disabled mt-1 font-korean">
{filteredUsers.length} {filteredUsers.length}
</p> </p>
</div> </div>
@ -698,7 +698,7 @@ function UsersPanel() {
setOrgFilter(e.target.value); setOrgFilter(e.target.value);
setCurrentPage(1); setCurrentPage(1);
}} }}
className="px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean" className="px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean"
> >
<option value=""> </option> <option value=""> </option>
{allOrgs.map((org) => ( {allOrgs.map((org) => (
@ -711,7 +711,7 @@ function UsersPanel() {
<select <select
value={statusFilter} value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)} onChange={(e) => setStatusFilter(e.target.value)}
className="px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean" className="px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean"
> >
<option value=""> </option> <option value=""> </option>
<option value="PENDING"></option> <option value="PENDING"></option>
@ -726,11 +726,11 @@ function UsersPanel() {
placeholder="이름, 계정 검색..." placeholder="이름, 계정 검색..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="w-56 px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean" className="w-56 px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean"
/> />
<button <button
onClick={() => setShowRegisterModal(true)} onClick={() => setShowRegisterModal(true)}
className="px-4 py-2 text-xs font-semibold rounded-md bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)] transition-all font-korean" className="px-4 py-2 text-caption font-semibold rounded-md bg-color-accent text-bg-0 hover:shadow-[0_0_12px_rgba(6,182,212,0.3)] transition-all font-korean"
> >
+ +
</button> </button>
@ -740,7 +740,7 @@ function UsersPanel() {
{/* 테이블 */} {/* 테이블 */}
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{loading ? ( {loading ? (
<div className="flex items-center justify-center h-32 text-fg-disabled text-sm font-korean"> <div className="flex items-center justify-center h-32 text-fg-disabled text-body-2 font-korean">
... ...
</div> </div>
) : ( ) : (
@ -781,7 +781,7 @@ function UsersPanel() {
<tr> <tr>
<td <td
colSpan={9} colSpan={9}
className="px-6 py-10 text-center text-xs text-fg-disabled font-korean" className="px-6 py-10 text-center text-caption text-fg-disabled font-korean"
> >
. .
</td> </td>
@ -897,7 +897,7 @@ function UsersPanel() {
onChange={() => toggleRoleSelection(role.sn)} onChange={() => toggleRoleSelection(role.sn)}
style={{ accentColor: color }} style={{ accentColor: color }}
/> />
<span className="text-xs font-korean" style={{ color }}> <span className="text-caption font-korean" style={{ color }}>
{role.name} {role.name}
</span> </span>
<span className="text-caption text-fg-disabled font-mono"> <span className="text-caption text-fg-disabled font-mono">

파일 보기

@ -90,7 +90,7 @@ function VesselMaterialsPanel() {
<div className="flex items-center justify-between px-6 py-4 border-b border-stroke"> <div className="flex items-center justify-between px-6 py-4 border-b border-stroke">
<div> <div>
<h1 className="text-lg font-bold text-fg font-korean"> </h1> <h1 className="text-lg font-bold text-fg font-korean"> </h1>
<p className="text-xs text-fg-disabled mt-1 font-korean"> <p className="text-caption text-fg-disabled mt-1 font-korean">
{filtered.length} ( ) {filtered.length} ( )
</p> </p>
</div> </div>
@ -98,7 +98,7 @@ function VesselMaterialsPanel() {
<select <select
value={regionFilter} value={regionFilter}
onChange={handleFilterChange(setRegionFilter)} onChange={handleFilterChange(setRegionFilter)}
className="px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean" className="px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean"
> >
<option value="전체"> </option> <option value="전체"> </option>
<option value="남해"></option> <option value="남해"></option>
@ -110,7 +110,7 @@ function VesselMaterialsPanel() {
<select <select
value={typeFilter} value={typeFilter}
onChange={handleFilterChange(setTypeFilter)} onChange={handleFilterChange(setTypeFilter)}
className="px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean" className="px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg focus:border-color-accent focus:outline-none font-korean"
> >
<option value="전체"> </option> <option value="전체"> </option>
{typeOptions.map((t) => ( {typeOptions.map((t) => (
@ -127,11 +127,11 @@ function VesselMaterialsPanel() {
setSearchTerm(e.target.value); setSearchTerm(e.target.value);
setCurrentPage(1); setCurrentPage(1);
}} }}
className="w-56 px-3 py-2 text-xs bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean" className="w-56 px-3 py-2 text-caption bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none font-korean"
/> />
<button <button
onClick={load} onClick={load}
className="px-4 py-2 text-xs font-semibold rounded-md bg-bg-elevated border border-stroke text-fg-sub hover:border-color-accent hover:text-color-accent transition-all font-korean" className="px-4 py-2 text-caption font-semibold rounded-md bg-bg-elevated border border-stroke text-fg-sub hover:border-color-accent hover:text-color-accent transition-all font-korean"
> >
</button> </button>
@ -141,7 +141,7 @@ function VesselMaterialsPanel() {
{/* 테이블 */} {/* 테이블 */}
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{loading ? ( {loading ? (
<div className="flex items-center justify-center h-32 text-fg-disabled text-sm font-korean"> <div className="flex items-center justify-center h-32 text-fg-disabled text-body-2 font-korean">
... ...
</div> </div>
) : ( ) : (
@ -188,7 +188,7 @@ function VesselMaterialsPanel() {
<tr> <tr>
<td <td
colSpan={11} colSpan={11}
className="px-6 py-10 text-center text-xs text-fg-disabled font-korean" className="px-6 py-10 text-center text-caption text-fg-disabled font-korean"
> >
. .
</td> </td>

파일 보기

@ -142,17 +142,17 @@ export default function VesselSignalPanel() {
<div className="flex flex-col h-full overflow-hidden"> <div className="flex flex-col h-full overflow-hidden">
{/* 헤더 */} {/* 헤더 */}
<div className="flex items-center justify-between px-6 py-3 border-b border-stroke-1"> <div className="flex items-center justify-between px-6 py-3 border-b border-stroke-1">
<h2 className="text-sm font-semibold text-fg"> </h2> <h2 className="text-body-2 font-semibold text-fg"> </h2>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<input <input
type="date" type="date"
value={date} value={date}
onChange={(e) => setDate(e.target.value)} onChange={(e) => setDate(e.target.value)}
className="px-2 py-1 text-xs rounded bg-bg-elevated border border-stroke-1 text-fg" className="px-2 py-1 text-caption rounded bg-bg-elevated border border-stroke-1 text-fg"
/> />
<button <button
onClick={load} onClick={load}
className="px-3 py-1 text-xs rounded bg-bg-elevated border border-stroke-1 text-fg-sub hover:bg-bg-card" className="px-3 py-1 text-caption rounded bg-bg-elevated border border-stroke-1 text-fg-sub hover:bg-bg-card"
> >
</button> </button>
@ -163,7 +163,7 @@ export default function VesselSignalPanel() {
<div className="flex-1 overflow-y-auto px-6 py-5"> <div className="flex-1 overflow-y-auto px-6 py-5">
{loading ? ( {loading ? (
<div className="flex items-center justify-center h-full"> <div className="flex items-center justify-center h-full">
<span className="text-xs text-fg-disabled"> ...</span> <span className="text-caption text-fg-disabled"> ...</span>
</div> </div>
) : ( ) : (
<div className="flex gap-2"> <div className="flex gap-2">

파일 보기

@ -589,7 +589,7 @@ export function CctvView() {
{/* 헤더 */} {/* 헤더 */}
<div className="p-3 pb-2.5 border-b border-stroke shrink-0 bg-bg-elevated"> <div className="p-3 pb-2.5 border-b border-stroke shrink-0 bg-bg-elevated">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<div className="text-xs font-bold text-fg font-korean flex items-center gap-1.5"> <div className="text-caption font-bold text-fg font-korean flex items-center gap-1.5">
<span <span
className="w-[7px] h-[7px] rounded-full inline-block animate-pulse" className="w-[7px] h-[7px] rounded-full inline-block animate-pulse"
style={{ background: 'var(--color-danger)' }} style={{ background: 'var(--color-danger)' }}
@ -776,7 +776,7 @@ export function CctvView() {
{/* 뷰어 툴바 */} {/* 뷰어 툴바 */}
<div className="flex items-center justify-between px-4 py-2 border-b border-stroke bg-bg-elevated shrink-0 gap-2.5"> <div className="flex items-center justify-between px-4 py-2 border-b border-stroke bg-bg-elevated shrink-0 gap-2.5">
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">
<div className="text-xs font-bold text-fg font-korean whitespace-nowrap overflow-hidden text-ellipsis"> <div className="text-caption font-bold text-fg font-korean whitespace-nowrap overflow-hidden text-ellipsis">
{selectedCamera ? `📹 ${selectedCamera.cameraNm}` : '📹 카메라를 선택하세요'} {selectedCamera ? `📹 ${selectedCamera.cameraNm}` : '📹 카메라를 선택하세요'}
</div> </div>
{selectedCamera?.sttsCd === 'LIVE' && ( {selectedCamera?.sttsCd === 'LIVE' && (
@ -966,7 +966,7 @@ export function CctvView() {
<div key={srcKey} className="mb-5"> <div key={srcKey} className="mb-5">
{/* 출처 헤더 */} {/* 출처 헤더 */}
<div className="flex items-center gap-2 mb-2 pb-1.5 border-b border-stroke"> <div className="flex items-center gap-2 mb-2 pb-1.5 border-b border-stroke">
<span className="text-sm">{group.icon}</span> <span className="text-body-2">{group.icon}</span>
<span className="text-label-1 font-bold text-fg font-korean"> <span className="text-label-1 font-bold text-fg font-korean">
{group.label} {group.label}
</span> </span>

파일 보기

@ -421,7 +421,7 @@ export function MediaManagement() {
<div className="fixed inset-0 z-[300] bg-black/60 backdrop-blur-sm flex items-center justify-center"> <div className="fixed inset-0 z-[300] bg-black/60 backdrop-blur-sm flex items-center justify-center">
<div className="bg-bg-surface border border-stroke rounded-md p-6 w-72 text-center"> <div className="bg-bg-surface border border-stroke rounded-md p-6 w-72 text-center">
<div className="text-2xl mb-3">📥</div> <div className="text-2xl mb-3">📥</div>
<div className="text-sm font-bold font-korean mb-3"> </div> <div className="text-body-2 font-bold font-korean mb-3"> </div>
<div className="text-title-4 font-korean text-fg-sub mb-1"> <div className="text-title-4 font-korean text-fg-sub mb-1">
<span className="text-color-accent font-bold">{downloadResult.total}</span> <span className="text-color-accent font-bold">{downloadResult.total}</span>
</div> </div>
@ -441,7 +441,7 @@ export function MediaManagement() {
</div> </div>
<button <button
onClick={() => setDownloadResult(null)} onClick={() => setDownloadResult(null)}
className="px-6 py-2 text-sm font-semibold rounded bg-[rgba(6,182,212,0.15)] text-color-accent border border-[rgba(6,182,212,0.3)] hover:bg-[rgba(6,182,212,0.25)] transition-colors font-korean" className="px-6 py-2 text-body-2 font-semibold rounded bg-[rgba(6,182,212,0.15)] text-color-accent border border-[rgba(6,182,212,0.3)] hover:bg-[rgba(6,182,212,0.25)] transition-colors font-korean"
> >
</button> </button>
@ -475,7 +475,7 @@ export function MediaManagement() {
</div> </div>
</div> </div>
<div className="mb-3"> <div className="mb-3">
<label className="block text-xs font-semibold mb-1.5 text-fg-sub font-korean"> <label className="block text-caption font-semibold mb-1.5 text-fg-sub font-korean">
</label> </label>
<select className="prd-i w-full"> <select className="prd-i w-full">
@ -489,7 +489,7 @@ export function MediaManagement() {
</select> </select>
</div> </div>
<div className="mb-3"> <div className="mb-3">
<label className="block text-xs font-semibold mb-1.5 text-fg-sub font-korean"> <label className="block text-caption font-semibold mb-1.5 text-fg-sub font-korean">
</label> </label>
<select className="prd-i w-full"> <select className="prd-i w-full">
@ -499,7 +499,7 @@ export function MediaManagement() {
</select> </select>
</div> </div>
<div className="mb-4"> <div className="mb-4">
<label className="block text-xs font-semibold mb-1.5 text-fg-sub font-korean"> <label className="block text-caption font-semibold mb-1.5 text-fg-sub font-korean">
</label> </label>
<textarea <textarea
@ -508,7 +508,7 @@ export function MediaManagement() {
/> />
</div> </div>
<button <button
className="w-full py-3 rounded-sm text-sm font-bold font-korean cursor-pointer hover:brightness-125 transition-all" className="w-full py-3 rounded-sm text-body-2 font-bold font-korean cursor-pointer hover:brightness-125 transition-all"
style={{ style={{
background: 'rgba(6,182,212,0.15)', background: 'rgba(6,182,212,0.15)',
border: '1px solid rgba(6,182,212,0.3)', border: '1px solid rgba(6,182,212,0.3)',

파일 보기

@ -239,7 +239,7 @@ export function OilAreaAnalysis() {
<div className="flex gap-5 h-full overflow-hidden"> <div className="flex gap-5 h-full overflow-hidden">
{/* ── Left Panel ── */} {/* ── Left Panel ── */}
<div className="w-[280px] min-w-[280px] flex flex-col overflow-y-auto scrollbar-thin"> <div className="w-[280px] min-w-[280px] flex flex-col overflow-y-auto scrollbar-thin">
<div className="text-sm font-bold mb-1 font-korean">🧩 </div> <div className="text-body-2 font-bold mb-1 font-korean">🧩 </div>
<div className="text-label-2 text-fg-disabled mb-4 font-korean"> <div className="text-label-2 text-fg-disabled mb-4 font-korean">
. .
</div> </div>
@ -256,7 +256,7 @@ export function OilAreaAnalysis() {
<button <button
onClick={() => fileInputRef.current?.click()} onClick={() => fileInputRef.current?.click()}
disabled={selectedFiles.length >= MAX_IMAGES || isStitching || isAnalyzing} disabled={selectedFiles.length >= MAX_IMAGES || isStitching || isAnalyzing}
className="w-full py-2 mb-3 border border-dashed border-stroke rounded-sm text-xs font-korean text-fg-sub className="w-full py-2 mb-3 border border-dashed border-stroke rounded-sm text-caption font-korean text-fg-sub
hover:border-color-accent hover:text-color-accent transition-colors cursor-pointer hover:border-color-accent hover:text-color-accent transition-colors cursor-pointer
disabled:opacity-40 disabled:cursor-not-allowed" disabled:opacity-40 disabled:cursor-not-allowed"
> >

파일 보기

@ -177,7 +177,7 @@ export function RealtimeDrone() {
{/* 헤더 */} {/* 헤더 */}
<div className="p-3 pb-2.5 border-b border-stroke shrink-0 bg-bg-elevated"> <div className="p-3 pb-2.5 border-b border-stroke shrink-0 bg-bg-elevated">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<div className="text-xs font-bold text-fg font-korean flex items-center gap-1.5"> <div className="text-caption font-bold text-fg font-korean flex items-center gap-1.5">
<span <span
className="w-[7px] h-[7px] rounded-full inline-block" className="w-[7px] h-[7px] rounded-full inline-block"
style={{ style={{
@ -225,7 +225,7 @@ export function RealtimeDrone() {
> >
<div className="flex items-center justify-between mb-1.5"> <div className="flex items-center justify-between mb-1.5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="text-sm">🚁</div> <div className="text-body-2">🚁</div>
<div> <div>
<div className="text-label-2 font-normal text-fg font-korean"> <div className="text-label-2 font-normal text-fg font-korean">
{stream.shipName}{' '} {stream.shipName}{' '}
@ -300,7 +300,7 @@ export function RealtimeDrone() {
{/* 툴바 */} {/* 툴바 */}
<div className="flex items-center justify-between px-4 py-2 border-b border-stroke bg-bg-elevated shrink-0 gap-2.5"> <div className="flex items-center justify-between px-4 py-2 border-b border-stroke bg-bg-elevated shrink-0 gap-2.5">
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">
<div className="text-xs font-bold text-fg font-korean whitespace-nowrap overflow-hidden text-ellipsis"> <div className="text-caption font-bold text-fg font-korean whitespace-nowrap overflow-hidden text-ellipsis">
{selectedStream ? `🚁 ${selectedStream.shipName}` : '🚁 드론 스트림을 선택하세요'} {selectedStream ? `🚁 ${selectedStream.shipName}` : '🚁 드론 스트림을 선택하세요'}
</div> </div>
{selectedStream?.status === 'streaming' && ( {selectedStream?.status === 'streaming' && (
@ -566,7 +566,7 @@ export function RealtimeDrone() {
style={{ minWidth: 170, background: 'var(--bg-card)', borderRadius: 6 }} style={{ minWidth: 170, background: 'var(--bg-card)', borderRadius: 6 }}
> >
<div className="flex items-center gap-1.5 mb-1"> <div className="flex items-center gap-1.5 mb-1">
<span className="text-sm">🚁</span> <span className="text-body-2">🚁</span>
<div className="text-label-2 font-bold text-fg">{mapPopup.shipName}</div> <div className="text-label-2 font-bold text-fg">{mapPopup.shipName}</div>
</div> </div>
<div className="text-caption text-fg-disabled mb-0.5"> <div className="text-caption text-fg-disabled mb-0.5">
@ -813,7 +813,7 @@ export function RealtimeDrone() {
].map((item, i) => ( ].map((item, i) => (
<div key={i} className="px-2 py-1.5 bg-bg-base rounded text-center"> <div key={i} className="px-2 py-1.5 bg-bg-base rounded text-center">
<div className="text-caption text-fg-disabled font-korean">{item.label}</div> <div className="text-caption text-fg-disabled font-korean">{item.label}</div>
<div className="text-sm font-mono text-fg">{item.value}</div> <div className="text-body-2 font-mono text-fg">{item.value}</div>
</div> </div>
))} ))}
</div> </div>

파일 보기

@ -485,7 +485,7 @@ export function SatelliteRequest() {
<div className="flex items-center gap-3 mb-2 h-9"> <div className="flex items-center gap-3 mb-2 h-9">
<div className="flex items-center gap-2 shrink-0"> <div className="flex items-center gap-2 shrink-0">
<div <div
className="w-7 h-7 rounded-md flex items-center justify-center text-sm" className="w-7 h-7 rounded-md flex items-center justify-center text-body-2"
style={{ style={{
background: 'rgba(6,182,212,0.15)', background: 'rgba(6,182,212,0.15)',
border: '1px solid rgba(6,182,212,0.3)', border: '1px solid rgba(6,182,212,0.3)',
@ -624,7 +624,7 @@ export function SatelliteRequest() {
> >
<div className="text-label-2 font-mono text-fg-sub">{r.id}</div> <div className="text-label-2 font-mono text-fg-sub">{r.id}</div>
<div> <div>
<div className="text-xs font-semibold text-fg font-korean">{r.zone}</div> <div className="text-caption font-semibold text-fg font-korean">{r.zone}</div>
<div className="text-caption text-fg-disabled font-mono mt-0.5"> <div className="text-caption text-fg-disabled font-mono mt-0.5">
{r.zoneCoord} · {r.zoneArea} {r.zoneCoord} · {r.zoneArea}
</div> </div>
@ -776,7 +776,9 @@ export function SatelliteRequest() {
<div className="grid grid-cols-2 gap-3.5"> <div className="grid grid-cols-2 gap-3.5">
{/* 가용 위성 현황 */} {/* 가용 위성 현황 */}
<div className="bg-bg-elevated border border-stroke rounded-md p-4"> <div className="bg-bg-elevated border border-stroke rounded-md p-4">
<div className="text-xs font-bold text-fg font-korean mb-3">🛰 </div> <div className="text-caption font-bold text-fg font-korean mb-3">
🛰
</div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{satellites.map((sat, i) => ( {satellites.map((sat, i) => (
<div <div
@ -807,7 +809,7 @@ export function SatelliteRequest() {
{/* 오늘 촬영 가능 시간 */} {/* 오늘 촬영 가능 시간 */}
<div className="bg-bg-elevated border border-stroke rounded-md p-4"> <div className="bg-bg-elevated border border-stroke rounded-md p-4">
<div className="text-xs font-bold text-fg font-korean mb-3"> <div className="text-caption font-bold text-fg font-korean mb-3">
(KST) (KST)
</div> </div>
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
@ -1133,7 +1135,7 @@ export function SatelliteRequest() {
)} )}
<button <button
onClick={() => setMapSelectedItem(null)} onClick={() => setMapSelectedItem(null)}
className="text-fg-disabled bg-transparent border-none cursor-pointer text-sm" className="text-fg-disabled bg-transparent border-none cursor-pointer text-body-2"
> >
</button> </button>
@ -1213,7 +1215,7 @@ export function SatelliteRequest() {
</span> </span>
</div> </div>
<div> <div>
<div className="text-sm font-bold text-fg font-korean">BlackSky</div> <div className="text-body-2 font-bold text-fg font-korean">BlackSky</div>
<div className="text-caption text-fg-disabled font-korean mt-px"> <div className="text-caption text-fg-disabled font-korean mt-px">
Maxar Electro-Optical API Maxar Electro-Optical API
</div> </div>
@ -1274,7 +1276,7 @@ export function SatelliteRequest() {
</span> </span>
</div> </div>
<div> <div>
<div className="text-sm font-bold text-fg font-korean"> <div className="text-body-2 font-bold text-fg font-korean">
UP42 EO + SAR UP42 EO + SAR
</div> </div>
<div className="text-caption text-fg-disabled font-korean mt-px"> <div className="text-caption text-fg-disabled font-korean mt-px">
@ -1703,13 +1705,13 @@ export function SatelliteRequest() {
</div> </div>
<button <button
onClick={() => setModalPhase('provider')} onClick={() => setModalPhase('provider')}
className="px-5 py-2.5 rounded-lg border border-stroke text-xs font-semibold cursor-pointer font-korean text-fg-sub bg-bg-elevated" className="px-5 py-2.5 rounded-lg border border-stroke text-caption font-semibold cursor-pointer font-korean text-fg-sub bg-bg-elevated"
> >
</button> </button>
<button <button
onClick={() => setModalPhase('none')} onClick={() => setModalPhase('none')}
className="px-7 py-2.5 rounded-lg text-xs font-bold cursor-pointer font-korean text-color-accent" className="px-7 py-2.5 rounded-lg text-caption font-bold cursor-pointer font-korean text-color-accent"
style={{ style={{
background: 'rgba(6,182,212,0.08)', background: 'rgba(6,182,212,0.08)',
}} }}
@ -2098,7 +2100,7 @@ export function SatelliteRequest() {
{urgency} {urgency}
</span> </span>
{up42SelPass === pass.id && ( {up42SelPass === pass.id && (
<span className="text-xs text-fg"></span> <span className="text-caption text-fg"></span>
)} )}
</div> </div>
); );

파일 보기

@ -391,7 +391,7 @@ function Vessel3DModel({ viewMode, status }: { viewMode: string; status: string
{isProcessing && ( {isProcessing && (
<div className="absolute inset-0 flex items-center justify-center"> <div className="absolute inset-0 flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="text-color-accent/40 text-xs font-mono animate-pulse"> <div className="text-color-accent/40 text-caption font-mono animate-pulse">
... ...
</div> </div>
<div className="w-24 h-0.5 bg-bg-card rounded-full mt-2 mx-auto overflow-hidden"> <div className="w-24 h-0.5 bg-bg-card rounded-full mt-2 mx-auto overflow-hidden">
@ -712,7 +712,7 @@ function Pollution3DModel({ viewMode, status }: { viewMode: string; status: stri
{isProcessing && ( {isProcessing && (
<div className="absolute inset-0 flex items-center justify-center"> <div className="absolute inset-0 flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="text-color-danger/40 text-xs font-mono animate-pulse"> <div className="text-color-danger/40 text-caption font-mono animate-pulse">
... ...
</div> </div>
<div className="w-24 h-0.5 bg-bg-card rounded-full mt-2 mx-auto overflow-hidden"> <div className="w-24 h-0.5 bg-bg-card rounded-full mt-2 mx-auto overflow-hidden">
@ -818,7 +818,7 @@ export function SensorAnalysis() {
className="absolute inset-0 flex items-center justify-center" className="absolute inset-0 flex items-center justify-center"
style={{ background: 'var(--bg-base)' }} style={{ background: 'var(--bg-base)' }}
> >
<div className="text-fg-disabled/10 text-xs font-mono"> <div className="text-fg-disabled/10 text-caption font-mono">
{src.label.split(' ')[0]} {src.label.split(' ')[0]}
</div> </div>
</div> </div>
@ -916,7 +916,7 @@ export function SensorAnalysis() {
{ value: '0.023m', label: 'RMS오차' }, { value: '0.023m', label: 'RMS오차' },
].map((s, i) => ( ].map((s, i) => (
<div key={i} className="text-center"> <div key={i} className="text-center">
<div className="font-mono font-bold text-sm text-color-accent">{s.value}</div> <div className="font-mono font-bold text-body-2 text-color-accent">{s.value}</div>
<div className="text-caption text-fg-disabled mt-0.5 font-korean">{s.label}</div> <div className="text-caption text-fg-disabled mt-0.5 font-korean">{s.label}</div>
</div> </div>
))} ))}

파일 보기

@ -47,7 +47,7 @@ export function WingAI() {
<div className="flex items-center gap-3 mb-4 h-9"> <div className="flex items-center gap-3 mb-4 h-9">
<div className="flex items-center gap-2 shrink-0"> <div className="flex items-center gap-2 shrink-0">
<div <div
className="w-7 h-7 rounded-md flex items-center justify-center text-sm" className="w-7 h-7 rounded-md flex items-center justify-center text-body-2"
style={{ style={{
background: 'rgba(6,182,212,0.15)', background: 'rgba(6,182,212,0.15)',
border: '1px solid rgba(6,182,212,0.3)', border: '1px solid rgba(6,182,212,0.3)',
@ -1757,8 +1757,8 @@ function AoiPanel() {
} }
} }
> >
<span className="text-sm shrink-0">{active ? '◉' : '○'}</span> <span className="text-body-2 shrink-0">{active ? '◉' : '○'}</span>
<span className="text-sm shrink-0">{src.icon}</span> <span className="text-body-2 shrink-0">{src.icon}</span>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="text-caption font-bold font-korean"> <div className="text-caption font-bold font-korean">
{src.label} {src.label}

파일 보기

@ -83,7 +83,9 @@ function AssetManagement() {
if (loading) { if (loading) {
return ( return (
<div className="flex items-center justify-center h-full"> <div className="flex items-center justify-center h-full">
<div className="text-fg-disabled text-sm font-korean"> ...</div> <div className="text-fg-disabled text-body-2 font-korean">
...
</div>
</div> </div>
); );
} }
@ -415,7 +417,7 @@ function AssetManagement() {
<aside className="w-[340px] min-w-[340px] bg-bg-surface border-l border-stroke flex flex-col"> <aside className="w-[340px] min-w-[340px] bg-bg-surface border-l border-stroke flex flex-col">
{/* Header */} {/* Header */}
<div className="p-4 border-b border-stroke"> <div className="p-4 border-b border-stroke">
<div className="text-sm font-bold mb-1 font-korean">{selectedOrg.name}</div> <div className="text-body-2 font-bold mb-1 font-korean">{selectedOrg.name}</div>
<div className="text-label-2 text-fg-sub font-semibold font-korean mb-1"> <div className="text-label-2 text-fg-sub font-semibold font-korean mb-1">
{selectedOrg.type} · {regionShort(selectedOrg.jurisdiction)} · {selectedOrg.area} {selectedOrg.type} · {regionShort(selectedOrg.jurisdiction)} · {selectedOrg.area}
</div> </div>
@ -505,7 +507,7 @@ function AssetManagement() {
); );
}) })
) : ( ) : (
<div className="text-center text-fg-disabled text-xs py-8 font-korean"> <div className="text-center text-fg-disabled text-caption py-8 font-korean">
. .
</div> </div>
)} )}
@ -601,10 +603,10 @@ function AssetManagement() {
{/* Bottom Actions */} {/* Bottom Actions */}
{/* <div className="p-3.5 border-t border-stroke flex gap-2"> {/* <div className="p-3.5 border-t border-stroke flex gap-2">
<button className="flex-1 py-2.5 rounded-sm text-xs font-semibold font-korean text-white bg-color-accent border-none cursor-pointer hover:opacity-90 transition-opacity"> <button className="flex-1 py-2.5 rounded-sm text-caption font-semibold font-korean text-white bg-color-accent border-none cursor-pointer hover:opacity-90 transition-opacity">
📥 📥
</button> </button>
<button className="flex-1 py-2.5 rounded-sm text-xs font-semibold font-korean bg-bg-card border border-stroke text-fg-sub cursor-pointer hover:bg-bg-surface-hover transition-colors"> <button className="flex-1 py-2.5 rounded-sm text-caption font-semibold font-korean bg-bg-card border border-stroke text-fg-sub cursor-pointer hover:bg-bg-surface-hover transition-colors">
</button> </button>
</div> */} </div> */}

파일 보기

@ -27,7 +27,7 @@ function AssetUpload() {
{/* Drop Zone */} {/* Drop Zone */}
<div className="border-2 border-dashed border-stroke-light rounded-md py-10 px-5 text-center mb-5 cursor-pointer hover:border-[rgba(6,182,212,0.4)] transition-colors"> <div className="border-2 border-dashed border-stroke-light rounded-md py-10 px-5 text-center mb-5 cursor-pointer hover:border-[rgba(6,182,212,0.4)] transition-colors">
<div className="text-4xl mb-2.5 opacity-50">📁</div> <div className="text-4xl mb-2.5 opacity-50">📁</div>
<div className="text-sm font-semibold mb-1.5 font-korean"> <div className="text-body-2 font-semibold mb-1.5 font-korean">
</div> </div>
<div className="text-label-2 text-fg-disabled mb-4 font-korean"> <div className="text-label-2 text-fg-disabled mb-4 font-korean">
@ -43,7 +43,7 @@ function AssetUpload() {
{/* Asset Classification */} {/* Asset Classification */}
<div className="mb-4"> <div className="mb-4">
<label className="block text-xs font-semibold mb-1.5 text-fg-sub font-korean"> <label className="block text-caption font-semibold mb-1.5 text-fg-sub font-korean">
</label> </label>
<select className="prd-i w-full"> <select className="prd-i w-full">
@ -58,7 +58,7 @@ function AssetUpload() {
{/* Jurisdiction */} {/* Jurisdiction */}
<div className="mb-4"> <div className="mb-4">
<label className="block text-xs font-semibold mb-1.5 text-fg-sub font-korean"> <label className="block text-caption font-semibold mb-1.5 text-fg-sub font-korean">
</label> </label>
<select className="prd-i w-full"> <select className="prd-i w-full">
@ -74,10 +74,10 @@ function AssetUpload() {
{/* Upload Mode */} {/* Upload Mode */}
<div className="mb-5"> <div className="mb-5">
<label className="block text-xs font-semibold mb-1.5 text-fg-sub font-korean"> <label className="block text-caption font-semibold mb-1.5 text-fg-sub font-korean">
</label> </label>
<div className="flex gap-4 text-xs text-fg-sub font-korean"> <div className="flex gap-4 text-caption text-fg-sub font-korean">
<label className="flex items-center gap-1.5 cursor-pointer"> <label className="flex items-center gap-1.5 cursor-pointer">
<input <input
type="radio" type="radio"
@ -102,7 +102,7 @@ function AssetUpload() {
{/* Upload Button */} {/* Upload Button */}
<button <button
onClick={handleUpload} onClick={handleUpload}
className={`w-full py-3.5 rounded-sm text-sm font-bold font-korean border-none cursor-pointer transition-all ${ className={`w-full py-3.5 rounded-sm text-body-2 font-bold font-korean border-none cursor-pointer transition-all ${
uploaded uploaded
? 'bg-[rgba(34,197,94,0.2)] text-color-success border border-status-green' ? 'bg-[rgba(34,197,94,0.2)] text-color-success border border-status-green'
: 'text-white' : 'text-white'
@ -163,7 +163,7 @@ function AssetUpload() {
{p.icon} {p.icon}
</div> </div>
<div> <div>
<div className={`text-xs font-bold font-korean ${p.color}`}>{p.role}</div> <div className={`text-caption font-bold font-korean ${p.color}`}>{p.role}</div>
<div className="text-caption text-fg-disabled font-korean">{p.desc}</div> <div className="text-caption text-fg-disabled font-korean">{p.desc}</div>
</div> </div>
</div> </div>
@ -179,7 +179,7 @@ function AssetUpload() {
className="flex justify-between items-center p-3.5 px-4 bg-bg-card border border-stroke rounded-sm" className="flex justify-between items-center p-3.5 px-4 bg-bg-card border border-stroke rounded-sm"
> >
<div> <div>
<div className="text-xs font-semibold font-korean">{h.fileNm}</div> <div className="text-caption font-semibold font-korean">{h.fileNm}</div>
<div className="text-caption text-fg-disabled mt-0.5 font-korean"> <div className="text-caption text-fg-disabled mt-0.5 font-korean">
{new Date(h.regDtm).toLocaleString('ko-KR')} · {h.uploaderNm} · {h.uploadCnt} {new Date(h.regDtm).toLocaleString('ko-KR')} · {h.uploaderNm} · {h.uploadCnt}
</div> </div>

파일 보기

@ -27,10 +27,8 @@ export function AssetsView() {
<button <button
key={tab.id} key={tab.id}
onClick={() => setActiveTab(tab.id)} onClick={() => setActiveTab(tab.id)}
className={`px-5 py-3.5 text-xs font-semibold transition-all font-korean border-b-2 ${ className={`px-4 py-2.5 text-title-4 font-medium transition-all duration-200 font-korean tracking-navigation ${
activeTab === tab.id activeTab === tab.id ? 'text-color-accent' : 'text-fg-sub hover:text-fg'
? 'text-color-accent border-color-accent'
: 'text-fg-disabled border-transparent hover:text-fg-sub'
}`} }`}
> >
{tab.label} {tab.label}

파일 보기

@ -167,7 +167,7 @@ function ShipInsurance() {
{total > 0 ? `${total.toLocaleString()}` : '데이터 없음'} {total > 0 ? `${total.toLocaleString()}` : '데이터 없음'}
</div> </div>
</div> </div>
<div className="text-xs text-fg-disabled"> <div className="text-caption text-fg-disabled">
</div> </div>
</div> </div>
@ -200,7 +200,7 @@ function ShipInsurance() {
onChange={(e) => setSearch(e.target.value)} onChange={(e) => setSearch(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()} onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
placeholder="선박명, 호출부호, IMO, 선주명" placeholder="선박명, 호출부호, IMO, 선주명"
className="w-full px-3.5 py-2 bg-bg-base border border-stroke rounded-sm text-xs outline-none box-border" className="w-full px-3.5 py-2 bg-bg-base border border-stroke rounded-sm text-caption outline-none box-border"
/> />
</div> </div>
<div> <div>
@ -243,20 +243,20 @@ function ShipInsurance() {
</div> </div>
<button <button
onClick={handleSearch} onClick={handleSearch}
className="px-4 py-2 bg-bg-base text-fg border border-stroke rounded-sm text-xs cursor-pointer" className="px-4 py-2 bg-bg-base text-fg border border-stroke rounded-sm text-caption cursor-pointer"
> >
</button> </button>
<button <button
onClick={handleReset} onClick={handleReset}
className="px-4 py-2 bg-bg-base text-fg border border-stroke rounded-sm text-xs cursor-pointer" className="px-4 py-2 bg-bg-base text-fg border border-stroke rounded-sm text-caption cursor-pointer"
> >
</button> </button>
<button <button
onClick={handleDownload} onClick={handleDownload}
disabled={total === 0} disabled={total === 0}
className="px-4 py-2 text-xs cursor-pointer rounded-sm disabled:opacity-30 disabled:cursor-default bg-bg-base border border-stroke" className="px-4 py-2 text-caption cursor-pointer rounded-sm disabled:opacity-30 disabled:cursor-default bg-bg-base border border-stroke"
> >
</button> </button>
@ -281,8 +281,8 @@ function ShipInsurance() {
{/* 에러 */} {/* 에러 */}
{error && !isLoading && ( {error && !isLoading && (
<div className="flex flex-col items-center justify-center py-16 px-5 bg-bg-card border border-stroke rounded-md"> <div className="flex flex-col items-center justify-center py-16 px-5 bg-bg-card border border-stroke rounded-md">
<div className="text-sm font-bold text-color-danger mb-2"> </div> <div className="text-body-2 font-bold text-color-danger mb-2"> </div>
<div className="text-xs text-fg-disabled">{error}</div> <div className="text-caption text-fg-disabled">{error}</div>
</div> </div>
)} )}
@ -291,7 +291,7 @@ function ShipInsurance() {
<> <>
<div className="border border-stroke rounded-md overflow-hidden mb-3"> <div className="border border-stroke rounded-md overflow-hidden mb-3">
<div className="flex items-center justify-between px-4 py-3 border-b border-stroke"> <div className="flex items-center justify-between px-4 py-3 border-b border-stroke">
<div className="text-xs font-bold"> <div className="text-caption font-bold">
<span className="text-color-accent">{total.toLocaleString()}</span> <span className="text-color-accent">{total.toLocaleString()}</span>
{totalPages > 1 && ( {totalPages > 1 && (
<span className="text-fg-disabled font-normal ml-2"> <span className="text-fg-disabled font-normal ml-2">

파일 보기

@ -219,7 +219,7 @@ 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-full min-w-0 flex flex-col h-full bg-bg-surface border-r border-stroke overflow-hidden">
{/* Scrollable Content */} {/* Scrollable Content */}
<div <div
className="flex-1 overflow-y-scroll scrollbar-thin scrollbar-thumb-border-light scrollbar-track-transparent bg-bg-base" className="flex-1 overflow-y-scroll scrollbar-thin scrollbar-thumb-border-light scrollbar-track-transparent bg-bg-base"
@ -799,15 +799,15 @@ export function HNSLeftPanel({
<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-label-2 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-body-2 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-label-2 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-caution font-mono">3</span> <span className="text-body-2 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-label-2 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-accent font-mono">5</span> <span className="text-body-2 font-bold text-color-accent font-mono">5</span>
</div> </div>
</div> </div>
</div> </div>

파일 보기

@ -35,8 +35,8 @@ export function HNSRightPanel({
}: HNSRightPanelProps) { }: HNSRightPanelProps) {
if (!dispersionResult) { if (!dispersionResult) {
return ( return (
<div className="w-[300px] bg-bg-surface border-l border-stroke p-4 overflow-auto"> <div className="w-full h-full bg-bg-surface border-l border-stroke p-4 overflow-auto flex items-center justify-center">
<div className="flex flex-col gap-3 items-center justify-center h-full text-fg-disabled text-label-1"> <div className="flex flex-col gap-3 items-center 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>
@ -58,7 +58,7 @@ export function HNSRightPanel({
: 'ALOHA'; : 'ALOHA';
return ( return (
<div className="w-[300px] bg-bg-surface border-l border-stroke p-4 overflow-auto flex flex-col gap-4"> <div className="w-full bg-bg-surface border-l border-stroke p-4 overflow-auto flex flex-col gap-4">
{/* Header */} {/* Header */}
<div> <div>
<div className="flex items-center gap-1.5 mb-2"> <div className="flex items-center gap-1.5 mb-2">

파일 보기

@ -792,7 +792,7 @@ ${styles}
<div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card"> <div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[10px]"> <div className="flex items-center justify-between mb-[10px]">
<div> <div>
<span className="text-sm font-mono font-extrabold text-color-accent"> <span className="text-body-2 font-mono font-extrabold text-color-accent">
NH NH
</span>{' '} </span>{' '}
<span className="text-label-1 font-bold"></span> <span className="text-label-1 font-bold"></span>
@ -891,7 +891,7 @@ ${styles}
<div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card"> <div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[10px]"> <div className="flex items-center justify-between mb-[10px]">
<div> <div>
<span className="text-sm font-mono font-extrabold text-color-accent"> <span className="text-body-2 font-mono font-extrabold text-color-accent">
CHOH CHOH
</span>{' '} </span>{' '}
<span className="text-label-1 font-bold"></span> <span className="text-label-1 font-bold"></span>
@ -990,7 +990,9 @@ ${styles}
<div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card"> <div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[10px]"> <div className="flex items-center justify-between mb-[10px]">
<div> <div>
<span className="text-sm font-mono font-extrabold text-color-accent">H</span>{' '} <span className="text-body-2 font-mono font-extrabold text-color-accent">
H
</span>{' '}
<span className="text-label-1 font-bold"></span> <span className="text-label-1 font-bold"></span>
</div> </div>
<div className="flex gap-1"> <div className="flex gap-1">
@ -1048,7 +1050,7 @@ ${styles}
<div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card"> <div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[10px]"> <div className="flex items-center justify-between mb-[10px]">
<div> <div>
<span className="text-sm font-mono font-extrabold text-color-accent"> <span className="text-body-2 font-mono font-extrabold text-color-accent">
CH CH
</span>{' '} </span>{' '}
<span className="text-label-1 font-bold">LNG ()</span> <span className="text-label-1 font-bold">LNG ()</span>
@ -1106,7 +1108,7 @@ ${styles}
<div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card"> <div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[10px]"> <div className="flex items-center justify-between mb-[10px]">
<div> <div>
<span className="text-sm font-mono font-extrabold text-color-accent"> <span className="text-body-2 font-mono font-extrabold text-color-accent">
CHOH CHOH
</span>{' '} </span>{' '}
<span className="text-label-1 font-bold"></span> <span className="text-label-1 font-bold"></span>
@ -1164,7 +1166,7 @@ ${styles}
<div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card"> <div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[10px]"> <div className="flex items-center justify-between mb-[10px]">
<div> <div>
<span className="text-sm font-mono font-extrabold text-color-accent"> <span className="text-body-2 font-mono font-extrabold text-color-accent">
CH CH
</span>{' '} </span>{' '}
<span className="text-label-1 font-bold"></span> <span className="text-label-1 font-bold"></span>

파일 보기

@ -262,6 +262,8 @@ function DispersionTimeSlider({
export function HNSView() { export function HNSView() {
const { activeSubTab, setActiveSubTab } = useSubMenu('hns'); const { activeSubTab, setActiveSubTab } = useSubMenu('hns');
const { user } = useAuthStore(); const { user } = useAuthStore();
const [leftCollapsed, setLeftCollapsed] = useState(false);
const [rightCollapsed, setRightCollapsed] = useState(false);
const [incidentCoord, setIncidentCoord] = useState<{ lon: number; lat: number } | null>(null); const [incidentCoord, setIncidentCoord] = useState<{ lon: number; lat: number } | null>(null);
const [isSelectingLocation, setIsSelectingLocation] = useState(false); const [isSelectingLocation, setIsSelectingLocation] = useState(false);
const [isRunningPrediction, setIsRunningPrediction] = useState(false); const [isRunningPrediction, setIsRunningPrediction] = useState(false);
@ -890,22 +892,66 @@ export function HNSView() {
<div className="flex flex-1 overflow-hidden"> <div className="flex flex-1 overflow-hidden">
{/* Left Panel - 분석 목록일 때는 숨김 */} {/* Left Panel - 분석 목록일 때는 숨김 */}
{activeSubTab === 'analysis' && ( {activeSubTab === 'analysis' && (
<HNSLeftPanel <div className="shrink-0 overflow-hidden" style={{ width: leftCollapsed ? 0 : 320 }}>
activeSubTab={activeSubTab} <HNSLeftPanel
onSubTabChange={setActiveSubTab} activeSubTab={activeSubTab}
incidentCoord={incidentCoord} onSubTabChange={setActiveSubTab}
onCoordChange={setIncidentCoord} incidentCoord={incidentCoord}
onMapSelectClick={() => setIsSelectingLocation(true)} onCoordChange={setIncidentCoord}
onRunPrediction={handleRunPrediction} onMapSelectClick={() => setIsSelectingLocation(true)}
isRunningPrediction={isRunningPrediction} onRunPrediction={handleRunPrediction}
onParamsChange={handleParamsChange} isRunningPrediction={isRunningPrediction}
onReset={handleReset} onParamsChange={handleParamsChange}
loadedParams={loadedParams} onReset={handleReset}
/> loadedParams={loadedParams}
/>
</div>
)} )}
{/* Center - Map/Content Area */} {/* Center - Map/Content Area */}
<div className="flex-1 relative overflow-hidden"> <div className="flex-1 relative overflow-hidden">
{/* Left panel toggle button */}
{activeSubTab === 'analysis' && (
<button
onClick={() => setLeftCollapsed((v) => !v)}
className="absolute z-[500] top-1/2 -translate-y-1/2 flex items-center justify-center text-[10px]"
style={{
left: 0,
width: 18,
height: 40,
background: 'var(--bg-elevated)',
border: '1px solid var(--stroke-default)',
borderLeft: 'none',
borderRadius: '0 6px 6px 0',
color: 'var(--fg-sub)',
cursor: 'pointer',
}}
>
{leftCollapsed ? '▶' : '◀'}
</button>
)}
{/* Right panel toggle button */}
{activeSubTab === 'analysis' && (
<button
onClick={() => setRightCollapsed((v) => !v)}
className="absolute z-[500] top-1/2 -translate-y-1/2 flex items-center justify-center text-[10px]"
style={{
right: 0,
width: 18,
height: 40,
background: 'var(--bg-elevated)',
border: '1px solid var(--stroke-default)',
borderRight: 'none',
borderRadius: '6px 0 0 6px',
color: 'var(--fg-sub)',
cursor: 'pointer',
}}
>
{rightCollapsed ? '◀' : '▶'}
</button>
)}
{activeSubTab === 'list' ? ( {activeSubTab === 'list' ? (
<HNSAnalysisListTable <HNSAnalysisListTable
onTabChange={(v) => onTabChange={(v) =>
@ -942,14 +988,16 @@ export function HNSView() {
{/* Right Panel - 분석 목록일 때는 숨김 */} {/* Right Panel - 분석 목록일 때는 숨김 */}
{activeSubTab === 'analysis' && ( {activeSubTab === 'analysis' && (
<HNSRightPanel <div className="shrink-0 overflow-hidden" style={{ width: rightCollapsed ? 0 : 300 }}>
dispersionResult={dispersionResult} <HNSRightPanel
computedResult={computedResult} dispersionResult={dispersionResult}
weatherData={inputParams?.weather ?? null} computedResult={computedResult}
onOpenRecalc={() => setRecalcModalOpen(true)} weatherData={inputParams?.weather ?? null}
onOpenReport={handleOpenReport} onOpenRecalc={() => setRecalcModalOpen(true)}
onSave={handleSave} onOpenReport={handleOpenReport}
/> onSave={handleSave}
/>
</div>
)} )}
{/* HNS 재계산 모달 */} {/* HNS 재계산 모달 */}

파일 보기

@ -1,4 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { DndContext } from '@dnd-kit/core';
import { useDraggable } from '@dnd-kit/core';
import type { DragEndEvent } from '@dnd-kit/core';
/** /**
* 22 * 22
@ -100,33 +103,30 @@ const RULES: DischargeRule[] = [
]; ];
const ZONE_LABELS = ['~3해리', '3~12해리', '12~25해리', '25~50해리', '50해리+']; const ZONE_LABELS = ['~3해리', '3~12해리', '12~25해리', '25~50해리', '50해리+'];
const ZONE_COLORS = ['#ef4444', '#f97316', '#eab308', '#22c55e', '#64748b']; const ZONE_COLORS = [
'var(--color-danger)',
'var(--color-warning)',
'var(--color-caution)',
'var(--color-success)',
'var(--fg-disabled)',
];
function StatusBadge({ status }: { status: Status }) { function StatusBadge({ status }: { status: Status }) {
if (status === 'forbidden') if (status === 'forbidden')
return ( return (
<span <span
className="text-caption font-bold px-1.5 py-0.5 rounded" className="text-caption px-1.5 py-0.5 rounded"
style={{ background: 'rgba(239,68,68,0.15)', color: 'var(--color-danger)' }} style={{
background: 'color-mix(in srgb, var(--color-danger) 15%, transparent)',
color: 'var(--color-danger)',
}}
> >
</span> </span>
); );
if (status === 'allowed')
return (
<span
className="text-caption font-bold px-1.5 py-0.5 rounded"
style={{ background: 'rgba(34,197,94,0.15)', color: 'var(--color-success)' }}
>
</span>
);
return ( return (
<span <span className="text-caption px-1.5 py-0.5 rounded text-fg-sub">
className="text-caption font-bold px-1.5 py-0.5 rounded" {status === 'allowed' ? '배출가능' : '조건부'}
style={{ background: 'rgba(234,179,8,0.15)', color: 'var(--color-caution)' }}
>
</span> </span>
); );
} }
@ -139,15 +139,36 @@ interface DischargeZonePanelProps {
onClose: () => void; onClose: () => void;
} }
export function DischargeZonePanel({ export function DischargeZonePanel(props: DischargeZonePanelProps) {
const [offset, setOffset] = useState({ x: 0, y: 0 });
function handleDragEnd(event: DragEndEvent) {
setOffset((prev) => ({ x: prev.x + event.delta.x, y: prev.y + event.delta.y }));
}
return (
<DndContext onDragEnd={handleDragEnd}>
<DraggablePanel {...props} offset={offset} />
</DndContext>
);
}
function DraggablePanel({
lat, lat,
lon, lon,
distanceNm, distanceNm,
zoneIndex, zoneIndex,
onClose, onClose,
}: DischargeZonePanelProps) { offset,
}: DischargeZonePanelProps & { offset: { x: number; y: number } }) {
const zoneIdx = zoneIndex; const zoneIdx = zoneIndex;
const [expandedCat, setExpandedCat] = useState<string | null>(null); const [expandedCat, setExpandedCat] = useState<string | null>(null);
const { attributes, listeners, setNodeRef, transform } = useDraggable({
id: 'discharge-panel',
});
const tx = offset.x + (transform?.x ?? 0);
const ty = offset.y + (transform?.y ?? 0);
const categories = [...new Set(RULES.map((r) => r.category))]; const categories = [...new Set(RULES.map((r) => r.category))];
@ -161,22 +182,33 @@ export function DischargeZonePanel({
border: '1px solid var(--stroke-default)', border: '1px solid var(--stroke-default)',
boxShadow: '0 16px 48px rgba(0,0,0,0.5)', boxShadow: '0 16px 48px rgba(0,0,0,0.5)',
backdropFilter: 'blur(12px)', backdropFilter: 'blur(12px)',
transform: `translate(${tx}px, ${ty}px)`,
}} }}
> >
{/* Header */} {/* Header — drag handle */}
<div <div
ref={setNodeRef}
{...listeners}
{...attributes}
className="shrink-0 flex items-center justify-between" className="shrink-0 flex items-center justify-between"
style={{ style={{
padding: '10px 14px', padding: '10px 14px',
borderBottom: '1px solid var(--stroke-default)', borderBottom: '1px solid var(--stroke-default)',
background: 'var(--bg-elevated)', background: 'var(--bg-elevated)',
cursor: 'grab',
userSelect: 'none',
}} }}
> >
<div> <div>
<div className="text-label-2 font-bold text-fg font-korean">🚢 </div> <div className="text-label-2 text-fg font-korean">🚢 </div>
<div className="text-caption text-fg-sub font-korean"> 22</div> <div className="text-caption text-fg-sub font-korean"> 22</div>
</div> </div>
<span onClick={onClose} className="text-title-3 cursor-pointer text-fg-sub hover:text-fg"> <span
onClick={onClose}
onPointerDown={(e) => e.stopPropagation()}
className="text-title-3 cursor-pointer text-fg-sub hover:text-fg"
style={{ pointerEvents: 'all' }}
>
</span> </span>
</div> </div>
@ -194,10 +226,7 @@ export function DischargeZonePanel({
</div> </div>
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<span className="text-caption text-fg-sub font-korean"> </span> <span className="text-caption text-fg-sub font-korean"> </span>
<span <span className="text-label-2 font-mono" style={{ color: ZONE_COLORS[zoneIdx] }}>
className="text-label-2 font-bold font-mono"
style={{ color: ZONE_COLORS[zoneIdx] }}
>
{distanceNm.toFixed(1)} NM {distanceNm.toFixed(1)} NM
</span> </span>
</div> </div>
@ -206,12 +235,10 @@ export function DischargeZonePanel({
{ZONE_LABELS.map((label, i) => ( {ZONE_LABELS.map((label, i) => (
<div <div
key={label} key={label}
className="flex-1 text-center rounded-sm" className="flex-1 text-center rounded-sm text-[10px]"
style={{ style={{
padding: '3px 0', padding: '2px 0',
fontSize: 8, color: i === zoneIdx ? '#000' : 'var(--fg-sub)',
fontWeight: i === zoneIdx ? 700 : 400,
color: i === zoneIdx ? 'var(--fg-default)' : 'var(--fg-sub)',
background: i === zoneIdx ? ZONE_COLORS[i] : 'var(--hover-overlay)', background: i === zoneIdx ? ZONE_COLORS[i] : 'var(--hover-overlay)',
border: i === zoneIdx ? 'none' : '1px solid var(--stroke-light)', border: i === zoneIdx ? 'none' : '1px solid var(--stroke-light)',
}} }}
@ -231,8 +258,7 @@ export function DischargeZonePanel({
const catRules = RULES.filter((r) => r.category === cat); const catRules = RULES.filter((r) => r.category === cat);
const isExpanded = expandedCat === cat; const isExpanded = expandedCat === cat;
const allForbidden = catRules.every((r) => r.zones[zoneIdx] === 'forbidden'); const allForbidden = catRules.every((r) => r.zones[zoneIdx] === 'forbidden');
const allAllowed = catRules.every((r) => r.zones[zoneIdx] === 'allowed'); const summaryColor = allForbidden ? 'var(--color-danger)' : 'var(--fg-sub)';
const summaryColor = allForbidden ? '#ef4444' : allAllowed ? '#22c55e' : '#eab308';
return ( return (
<div key={cat} style={{ borderBottom: '1px solid var(--stroke-light)' }}> <div key={cat} style={{ borderBottom: '1px solid var(--stroke-light)' }}>
@ -245,11 +271,11 @@ export function DischargeZonePanel({
<div <div
style={{ width: 6, height: 6, borderRadius: '50%', background: summaryColor }} style={{ width: 6, height: 6, borderRadius: '50%', background: summaryColor }}
/> />
<span className="text-caption font-bold text-fg font-korean">{cat}</span> <span className="text-caption text-fg font-korean">{cat}</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-caption font-semibold" style={{ color: summaryColor }}> <span className="text-caption" style={{ color: summaryColor }}>
{allForbidden ? '전체 불가' : allAllowed ? '전체 가능' : '항목별 상이'} {allForbidden ? '전체 불가' : '허용'}
</span> </span>
<span className="text-caption text-fg-sub">{isExpanded ? '▾' : '▸'}</span> <span className="text-caption text-fg-sub">{isExpanded ? '▾' : '▸'}</span>
</div> </div>
@ -268,7 +294,7 @@ export function DischargeZonePanel({
borderRadius: 4, borderRadius: 4,
}} }}
> >
<span className="text-caption text-fg font-korean">{rule.item}</span> <span className="text-caption text-fg-sub font-korean">{rule.item}</span>
<StatusBadge status={rule.zones[zoneIdx]} /> <StatusBadge status={rule.zones[zoneIdx]} />
</div> </div>
))} ))}

파일 보기

@ -23,10 +23,10 @@ export function IncidentTable() {
<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-xl font-bold text-fg"> </h1>
<p className="text-sm text-fg-disabled mt-1"> {filteredIncidents.length}</p> <p className="text-body-2 text-fg-disabled mt-1"> {filteredIncidents.length}</p>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<button className="px-4 py-2 text-sm font-semibold border border-stroke rounded-md bg-bg-card text-fg-sub hover:bg-bg-surface-hover hover:text-fg transition-all"> <button className="px-4 py-2 text-body-2 font-semibold border border-stroke rounded-md bg-bg-card text-fg-sub hover:bg-bg-surface-hover hover:text-fg transition-all">
</button> </button>
<div className="relative"> <div className="relative">
@ -35,10 +35,10 @@ export function IncidentTable() {
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-body-2 bg-bg-elevated border border-stroke rounded-md text-fg placeholder-fg-disabled focus:border-color-accent focus:outline-none"
/> />
</div> </div>
<button 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"> <button className="px-4 py-2 text-body-2 font-semibold rounded-md bg-color-accent text-bg-0 hover:shadow-[0_0_16px_rgba(6,182,212,0.3)] transition-all">
+ +
</button> </button>
</div> </div>
@ -49,31 +49,31 @@ export function IncidentTable() {
<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-caption font-bold text-fg-disabled uppercase tracking-wider">
</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-caption font-bold text-fg-disabled uppercase tracking-wider">
</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-caption font-bold text-fg-disabled uppercase tracking-wider">
</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-caption font-bold text-fg-disabled uppercase tracking-wider">
</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-caption font-bold text-fg-disabled uppercase tracking-wider">
</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-caption font-bold text-fg-disabled uppercase tracking-wider">
</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-caption font-bold text-fg-disabled uppercase tracking-wider">
</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-caption font-bold text-fg-disabled uppercase tracking-wider">
</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-caption font-bold text-fg-disabled uppercase tracking-wider">
</th> </th>
</tr> </tr>
@ -84,28 +84,28 @@ export function IncidentTable() {
key={incident.acdntSn} key={incident.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">{incident.acdntSn}</td> <td className="px-4 py-3 text-body-2 text-fg-sub font-mono">{incident.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 className="text-sm font-semibold text-fg group-hover:text-color-accent transition-colors"> <span className="text-body-2 font-semibold text-fg group-hover:text-color-accent transition-colors">
{incident.acdntNm} {incident.acdntNm}
</span> </span>
</div> </div>
</td> </td>
<td className="px-4 py-3 text-sm text-fg-sub font-mono">{incident.occrnDtm}</td> <td className="px-4 py-3 text-body-2 text-fg-sub font-mono">{incident.occrnDtm}</td>
<td className="px-4 py-3 text-sm text-fg-sub">{incident.vesselTp ?? '—'}</td> <td className="px-4 py-3 text-body-2 text-fg-sub">{incident.vesselTp ?? '—'}</td>
<td className="px-4 py-3 text-sm text-fg-sub">{incident.oilTpCd ?? '—'}</td> <td className="px-4 py-3 text-body-2 text-fg-sub">{incident.oilTpCd ?? '—'}</td>
<td className="px-4 py-3 text-sm text-fg font-mono text-right font-semibold"> <td className="px-4 py-3 text-body-2 text-fg font-mono text-right font-semibold">
{incident.spilQty != null ? incident.spilQty.toFixed(2) : '—'} {incident.spilQty != null ? incident.spilQty.toFixed(2) : '—'}
</td> </td>
<td className="px-4 py-3 text-sm text-fg-sub">{incident.acdntTpCd}</td> <td className="px-4 py-3 text-body-2 text-fg-sub">{incident.acdntTpCd}</td>
<td className="px-4 py-3"> <td className="px-4 py-3">
<span className="px-2 py-1 text-xs font-semibold rounded-md bg-[rgba(168,85,247,0.15)] text-purple-400"> <span className="px-2 py-1 text-caption font-semibold rounded-md bg-[rgba(168,85,247,0.15)] text-purple-400">
{incident.phaseCd} {incident.phaseCd}
</span> </span>
</td> </td>
<td className="px-4 py-3 text-sm text-fg-sub">{incident.analystNm ?? '—'}</td> <td className="px-4 py-3 text-body-2 text-fg-sub">{incident.analystNm ?? '—'}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>

파일 보기

@ -215,7 +215,7 @@ export function IncidentsLeftPanel({
{/* Search */} {/* Search */}
<div className="px-4 py-3 border-b border-stroke shrink-0"> <div className="px-4 py-3 border-b border-stroke shrink-0">
<div className="relative"> <div className="relative">
<span className="absolute left-[10px] top-1/2 -translate-y-1/2 text-xs">🔍</span> <span className="absolute left-[10px] top-1/2 -translate-y-1/2 text-caption">🔍</span>
<input <input
type="text" type="text"
placeholder="사고명, 선박명 검색..." placeholder="사고명, 선박명 검색..."
@ -224,7 +224,7 @@ export function IncidentsLeftPanel({
setSearchTerm(e.target.value); setSearchTerm(e.target.value);
resetPage(); resetPage();
}} }}
className="w-full py-2 pr-3 pl-8 bg-bg-base border border-stroke text-xs outline-none" className="w-full py-2 pr-3 pl-8 bg-bg-base border border-stroke text-caption outline-none"
style={{ borderRadius: 'var(--radius-sm)' }} style={{ borderRadius: 'var(--radius-sm)' }}
/> />
</div> </div>
@ -257,11 +257,11 @@ export function IncidentsLeftPanel({
/> />
<button <button
onClick={resetPage} onClick={resetPage}
className="text-label-2 font-semibold cursor-pointer whitespace-nowrap text-white border-none" className="rounded-sm text-label-2 font-semibold cursor-pointer whitespace-nowrap text-color-accent"
style={{ style={{
padding: '5px 12px', padding: '5px 12px',
background: 'linear-gradient(135deg,var(--color-accent),var(--color-info))', border: '1px solid rgba(6,182,212,.3)',
borderRadius: 'var(--radius-sm)', background: 'rgba(6,182,212,.08)',
}} }}
> >
@ -356,7 +356,7 @@ export function IncidentsLeftPanel({
setSelectedStatus(s.id); setSelectedStatus(s.id);
resetPage(); resetPage();
}} }}
className="flex items-center gap-1 text-caption font-semibold cursor-pointer" className="flex items-center gap-1 text-caption cursor-pointer"
style={{ style={{
padding: '4px 10px', padding: '4px 10px',
borderRadius: '12px', borderRadius: '12px',
@ -442,7 +442,7 @@ export function IncidentsLeftPanel({
> >
{/* Row 1: name + status */} {/* Row 1: name + status */}
<div className="flex items-center justify-between mb-[5px]"> <div className="flex items-center justify-between mb-[5px]">
<div className="flex items-center gap-1.5 text-xs font-bold"> <div className="flex items-center gap-1.5 text-caption">
<span <span
className="shrink-0" className="shrink-0"
style={{ style={{
@ -456,7 +456,7 @@ export function IncidentsLeftPanel({
{inc.name} {inc.name}
</div> </div>
<span <span
className="shrink-0 text-caption font-semibold" className="shrink-0 text-caption"
style={{ style={{
padding: '2px 10px', padding: '2px 10px',
borderRadius: '10px', borderRadius: '10px',
@ -470,9 +470,9 @@ export function IncidentsLeftPanel({
{/* Row 2: meta */} {/* Row 2: meta */}
<div className="flex items-center gap-2 text-caption text-fg-disabled mb-[5px]"> <div className="flex items-center gap-2 text-caption text-fg-disabled mb-[5px]">
<span> <span>
📅 {inc.date} {inc.time} {inc.date} {inc.time}
</span> </span>
<span>🏛 {inc.office}</span> <span> {inc.office}</span>
</div> </div>
{/* Row 3: tags + buttons */} {/* Row 3: tags + buttons */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@ -492,12 +492,12 @@ export function IncidentsLeftPanel({
)} )}
{inc.oilType && ( {inc.oilType && (
<span <span
className="text-caption font-medium text-color-warning" className="text-caption font-medium text-fg-sub"
style={{ style={{
padding: '2px 8px', padding: '2px 8px',
borderRadius: '3px', borderRadius: '3px',
background: 'rgba(249,115,22,0.08)', background: 'rgba(100,116,139,0.08)',
border: '1px solid rgba(249,115,22,0.2)', border: '1px solid rgba(100,116,139,0.2)',
}} }}
> >
{inc.oilType} {inc.oilType}
@ -505,12 +505,12 @@ export function IncidentsLeftPanel({
)} )}
{inc.prediction && ( {inc.prediction && (
<span <span
className="text-caption font-medium text-color-success" className="text-caption font-medium text-fg-sub"
style={{ style={{
padding: '2px 8px', padding: '2px 8px',
borderRadius: '3px', borderRadius: '3px',
background: 'rgba(34,197,94,0.08)', background: 'rgba(100,116,139,0.08)',
border: '1px solid rgba(34,197,94,0.2)', border: '1px solid rgba(100,116,139,0.2)',
}} }}
> >
{inc.prediction} {inc.prediction}
@ -535,7 +535,7 @@ export function IncidentsLeftPanel({
transition: '0.15s', transition: '0.15s',
}} }}
> >
🌤
</button> </button>
{(inc.mediaCount ?? 0) > 0 && ( {(inc.mediaCount ?? 0) > 0 && (
<button <button
@ -555,7 +555,7 @@ export function IncidentsLeftPanel({
transition: '0.15s', transition: '0.15s',
}} }}
> >
📹 <span className="text-caption">{inc.mediaCount}</span> <span className="text-caption">{inc.mediaCount}</span>
</button> </button>
)} )}
{inc.hasImgAnalysis && ( {inc.hasImgAnalysis && (
@ -720,13 +720,13 @@ const WeatherPopup = forwardRef<
}} }}
> >
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="text-sm">🌤</span> <span className="text-body-2">🌤</span>
<div> <div>
<div className="text-label-2 font-bold">{data?.locNm || '기상정보 없음'}</div> <div className="text-label-2 font-bold">{data?.locNm || '기상정보 없음'}</div>
<div className="text-fg-disabled font-mono text-caption">{data?.obsDtm || '-'}</div> <div className="text-fg-disabled font-mono text-caption">{data?.obsDtm || '-'}</div>
</div> </div>
</div> </div>
<span onClick={onClose} className="cursor-pointer text-fg-disabled text-sm p-0.5"> <span onClick={onClose} className="cursor-pointer text-fg-disabled text-body-2 p-0.5">
</span> </span>
</div> </div>
@ -761,7 +761,7 @@ const WeatherPopup = forwardRef<
border: '1px solid rgba(59,130,246,0.1)', border: '1px solid rgba(59,130,246,0.1)',
}} }}
> >
<span className="text-xs"></span> <span className="text-caption"></span>
<div> <div>
<div className="text-fg-disabled text-caption"> ()</div> <div className="text-fg-disabled text-caption"> ()</div>
<div className="font-bold font-mono text-caption text-color-info"> <div className="font-bold font-mono text-caption text-color-info">
@ -773,7 +773,7 @@ const WeatherPopup = forwardRef<
className="flex-1 flex items-center gap-1.5 px-2 py-1.5 rounded-md" className="flex-1 flex items-center gap-1.5 px-2 py-1.5 rounded-md"
style={{ background: 'rgba(6,182,212,0.06)', border: '1px solid rgba(6,182,212,0.1)' }} style={{ background: 'rgba(6,182,212,0.06)', border: '1px solid rgba(6,182,212,0.1)' }}
> >
<span className="text-xs"></span> <span className="text-caption"></span>
<div> <div>
<div className="text-fg-disabled text-caption"> ()</div> <div className="text-fg-disabled text-caption"> ()</div>
<div className="text-color-accent font-bold font-mono text-caption"> <div className="text-color-accent font-bold font-mono text-caption">
@ -791,7 +791,7 @@ const WeatherPopup = forwardRef<
{forecast.map((f, i) => ( {forecast.map((f, i) => (
<div key={i} className="text-center"> <div key={i} className="text-center">
<div>{f.hour}</div> <div>{f.hour}</div>
<div className="text-xs my-0.5">{f.icon}</div> <div className="text-caption my-0.5">{f.icon}</div>
<div className="font-semibold">{f.temp}</div> <div className="font-semibold">{f.temp}</div>
</div> </div>
))} ))}

파일 보기

@ -50,31 +50,8 @@ interface AnalysisItem {
checked: boolean; checked: boolean;
} }
/* ── 카테고리별 고유 색상 (목록 순서 인덱스 기반 — 중복 없음) ── */ /* ── 카테고리 → 이모지 매핑 (prediction LeftPanel의 CATEGORY_ICON_MAP 기반, 미사용 보존) ── */
const CATEGORY_PALETTE: [number, number, number][] = [ // eslint-disable-next-line @typescript-eslint/no-unused-vars
[239, 68, 68], // red
[249, 115, 22], // orange
[234, 179, 8], // yellow
[132, 204, 22], // lime
[20, 184, 166], // teal
[6, 182, 212], // cyan
[59, 130, 246], // blue
[99, 102, 241], // indigo
[168, 85, 247], // purple
[236, 72, 153], // pink
[244, 63, 94], // rose
[16, 185, 129], // emerald
[14, 165, 233], // sky
[139, 92, 246], // violet
[217, 119, 6], // amber
[45, 212, 191], // turquoise
];
function getCategoryColor(index: number): [number, number, number] {
return CATEGORY_PALETTE[index % CATEGORY_PALETTE.length];
}
/* ── 카테고리 → 이모지 매핑 (prediction LeftPanel의 CATEGORY_ICON_MAP 기반) ── */
const CATEGORY_ICON: Record<string, string> = { const CATEGORY_ICON: Record<string, string> = {
: '🐟', : '🐟',
: '🦪', : '🦪',
@ -140,8 +117,20 @@ function getActiveModels(p: PredictionAnalysis): string {
/* ── HNS/구난 섹션 (미개발, 고정 구조만 유지) ────── */ /* ── HNS/구난 섹션 (미개발, 고정 구조만 유지) ────── */
const STATIC_SECTIONS = [ const STATIC_SECTIONS = [
{ key: 'hns', icon: '🧪', title: 'HNS 대기확산', color: '#a855f7', colorRgb: '168,85,247' }, {
{ key: 'rsc', icon: '🚨', title: '긴급구난', color: '#06b6d4', colorRgb: '6,182,212' }, key: 'hns',
icon: '🧪',
title: 'HNS 대기확산',
color: 'var(--color-accent)',
colorRgb: '6,182,212',
},
{
key: 'rsc',
icon: '🚨',
title: '긴급구난',
color: 'var(--color-accent)',
colorRgb: '6,182,212',
},
]; ];
/* ── Component ───────────────────────────────────── */ /* ── Component ───────────────────────────────────── */
@ -292,7 +281,7 @@ export function IncidentsRightPanel({
key: 'oil', key: 'oil',
icon: '🛢', icon: '🛢',
title: '유출유 확산예측', title: '유출유 확산예측',
color: '#f97316', color: 'var(--color-accent)',
colorRgb: '249,115,22', colorRgb: '249,115,22',
totalLabel: `전체 ${predItems.length}`, totalLabel: `전체 ${predItems.length}`,
items: predItems.map((p) => { items: predItems.map((p) => {
@ -310,7 +299,7 @@ export function IncidentsRightPanel({
if (!incident) { if (!incident) {
return ( return (
<div className="flex flex-col items-center justify-center bg-bg-surface border-l border-stroke w-[280px] min-w-[280px]"> <div className="flex flex-col items-center justify-center bg-bg-surface border-l border-stroke w-full h-full">
<div className="text-center text-fg-disabled text-label-2"> <div className="text-center text-fg-disabled text-label-2">
<div className="text-[32px] mb-2 opacity-30">📊</div> <div className="text-[32px] mb-2 opacity-30">📊</div>
@ -325,9 +314,9 @@ export function IncidentsRightPanel({
<div className="flex flex-col bg-bg-surface border-l border-stroke overflow-hidden h-full w-[280px] min-w-[280px]"> <div className="flex flex-col bg-bg-surface border-l border-stroke overflow-hidden h-full w-[280px] min-w-[280px]">
{/* Header */} {/* Header */}
<div className="px-[14px] py-2.5 border-b border-stroke shrink-0"> <div className="px-[14px] py-2.5 border-b border-stroke shrink-0">
<div className="text-xs font-bold mb-0.5">🔬 </div> <div className="text-caption font-bold mb-0.5">🔬 </div>
<div className="text-caption text-fg-disabled"> <div className="text-caption text-fg-disabled">
: <b className="text-color-accent">{incident.name}</b> : <span className="text-fg-disabled">{incident.name}</span>
</div> </div>
</div> </div>
@ -344,22 +333,19 @@ export function IncidentsRightPanel({
<div className="bg-bg-elevated border border-stroke rounded-md p-2.5"> <div className="bg-bg-elevated border border-stroke rounded-md p-2.5">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="text-sm">{sec.icon}</span> {/* <span className="text-body-2">{sec.icon}</span> */}
<span className="text-xs font-bold" style={{ color: sec.color }}> <span className="text-caption">{sec.title}</span>
{sec.title}
</span>
</div> </div>
<button <button
className="text-caption font-semibold cursor-pointer" className="text-caption font-semibold cursor-pointer"
style={{ style={{
padding: '3px 10px', padding: '3px 10px',
borderRadius: '4px', borderRadius: '4px',
background: `rgba(${sec.colorRgb},0.1)`, border: '1px solid var(--stroke-default)',
border: `1px solid rgba(${sec.colorRgb},0.25)`,
color: sec.color, color: sec.color,
}} }}
> >
📋
</button> </button>
</div> </div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
@ -374,8 +360,7 @@ export function IncidentsRightPanel({
className="flex items-center gap-1.5" className="flex items-center gap-1.5"
style={{ style={{
padding: '5px 8px', padding: '5px 8px',
background: `rgba(${sec.colorRgb},0.06)`, border: '1px solid var(--stroke-default)',
border: `1px solid rgba(${sec.colorRgb},0.15)`,
borderRadius: '4px', borderRadius: '4px',
}} }}
> >
@ -415,22 +400,19 @@ export function IncidentsRightPanel({
<div key={sec.key} className="bg-bg-elevated border border-stroke rounded-md p-2.5"> <div key={sec.key} className="bg-bg-elevated border border-stroke rounded-md p-2.5">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<span className="text-sm">{sec.icon}</span> {/* <span className="text-body-2">{sec.icon}</span> */}
<span className="text-xs font-bold" style={{ color: sec.color }}> <span className="text-caption">{sec.title}</span>
{sec.title}
</span>
</div> </div>
<button <button
className="text-caption font-semibold cursor-pointer" className="text-caption font-semibold cursor-pointer"
style={{ style={{
padding: '3px 10px', padding: '3px 10px',
borderRadius: '4px', borderRadius: '4px',
background: `rgba(${sec.colorRgb},0.1)`, border: '1px solid var(--stroke-default)',
border: `1px solid rgba(${sec.colorRgb},0.25)`,
color: sec.color, color: sec.color,
}} }}
> >
📋
</button> </button>
</div> </div>
<div className="text-caption text-fg-disabled text-center py-1.5"> </div> <div className="text-caption text-fg-disabled text-center py-1.5"> </div>
@ -443,8 +425,8 @@ export function IncidentsRightPanel({
{/* 민감자원 */} {/* 민감자원 */}
<div className="bg-bg-elevated border border-stroke rounded-md p-2.5"> <div className="bg-bg-elevated border border-stroke rounded-md p-2.5">
<div className="flex items-center gap-1.5 mb-2"> <div className="flex items-center gap-1.5 mb-2">
<span className="text-sm">🐟</span> {/* <span className="text-body-2">🐟</span> */}
<span className="text-xs font-bold text-color-success"></span> <span className="text-caption"></span>
</div> </div>
<div className="flex flex-col gap-[3px]"> <div className="flex flex-col gap-[3px]">
{sensCategories.length === 0 ? ( {sensCategories.length === 0 ? (
@ -452,38 +434,33 @@ export function IncidentsRightPanel({
</div> </div>
) : ( ) : (
sensCategories.map((cat, i) => { sensCategories.map((cat) => {
const icon = CATEGORY_ICON[cat.category] ?? '🌊';
const areaLabel = const areaLabel =
cat.totalArea != null cat.totalArea != null
? `${cat.totalArea.toLocaleString('ko-KR', { maximumFractionDigits: 0 })}ha` ? `${cat.totalArea.toLocaleString('ko-KR', { maximumFractionDigits: 0 })}ha`
: `${cat.count}개소`; : `${cat.count}개소`;
const [r, g, b] = getCategoryColor(i);
const hex = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
return ( return (
<label <label
key={cat.category} key={cat.category}
className="flex items-center cursor-pointer text-caption gap-[5px] rounded-[3px]" className="flex items-center cursor-pointer text-caption gap-[5px]"
style={{ padding: '4px 6px', background: `rgba(${r},${g},${b},0.06)` }} style={{ padding: '3px 0' }}
> >
<input <input
type="checkbox" type="checkbox"
checked={checkedSensCategories.has(cat.category)} checked={checkedSensCategories.has(cat.category)}
onChange={() => toggleSensCategory(cat.category)} onChange={() => toggleSensCategory(cat.category)}
style={{ accentColor: hex }} style={{ accentColor: 'var(--color-accent)' }}
/> />
<span <span
style={{ style={{
width: 8, width: 6,
height: 8, height: 6,
borderRadius: '50%', borderRadius: '50%',
background: hex, background: 'var(--color-accent)',
flexShrink: 0, flexShrink: 0,
display: 'inline-block', display: 'inline-block',
border: `1px solid rgba(${r},${g},${b},0.45)`,
}} }}
/> />
<span>{icon}</span>
<span className="flex-1">{cat.category}</span> <span className="flex-1">{cat.category}</span>
<span className="text-fg-disabled font-mono shrink-0">({areaLabel})</span> <span className="text-fg-disabled font-mono shrink-0">({areaLabel})</span>
</label> </label>
@ -496,10 +473,9 @@ export function IncidentsRightPanel({
{/* 근처 방제자원 */} {/* 근처 방제자원 */}
<div className="bg-bg-elevated border border-stroke rounded-md p-2.5"> <div className="bg-bg-elevated border border-stroke rounded-md p-2.5">
<div className="flex items-center gap-1.5 mb-2"> <div className="flex items-center gap-1.5 mb-2">
<span className="text-sm">🛡</span> <span className="text-caption font-bold text-color-accent"> </span>
<span className="text-xs font-bold text-color-boom"> </span>
{nearbyOrgs.length > 0 && ( {nearbyOrgs.length > 0 && (
<span className="ml-auto text-caption font-mono text-color-boom"> <span className="ml-auto text-caption font-mono text-color-accent">
{nearbyOrgs.length} {nearbyOrgs.length}
</span> </span>
)} )}
@ -519,21 +495,18 @@ export function IncidentsRightPanel({
</div> </div>
) : ( ) : (
<div className="flex flex-col gap-[3px] max-h-[200px] overflow-y-auto"> <div className="flex flex-col max-h-[200px] overflow-y-auto">
{nearbyOrgs.map((org) => ( {nearbyOrgs.map((org) => (
<div <div
key={org.orgSn} key={org.orgSn}
className="flex items-start gap-1.5 rounded-[3px] px-[6px] py-[5px]" className="flex items-start gap-1.5 px-[2px] py-[5px]"
style={{ style={{ borderBottom: '1px solid var(--stroke-default)' }}
background: 'rgba(245,158,11,0.05)',
border: '1px solid rgba(245,158,11,0.08)',
}}
> >
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-1 mb-[2px]"> <div className="flex items-center gap-1 mb-[2px]">
<span <span
className="text-caption px-[4px] py-[1px] rounded-[2px] font-bold shrink-0" className="text-caption px-[4px] py-[1px] rounded-[2px] font-bold shrink-0 text-color-accent"
style={{ background: 'rgba(245,158,11,0.15)', color: '#f59e0b' }} style={{ background: 'rgba(6,182,212,0.1)' }}
> >
{org.orgTp} {org.orgTp}
</span> </span>
@ -544,7 +517,7 @@ export function IncidentsRightPanel({
{org.totalAssets > 0 ? ` · 장비 ${org.totalAssets}` : ''} {org.totalAssets > 0 ? ` · 장비 ${org.totalAssets}` : ''}
</div> </div>
</div> </div>
<span className="text-caption font-mono text-color-boom shrink-0"> <span className="text-caption font-mono text-color-accent shrink-0">
{org.distanceNm.toFixed(1)} nm {org.distanceNm.toFixed(1)} nm
</span> </span>
</div> </div>
@ -553,10 +526,10 @@ export function IncidentsRightPanel({
)} )}
{/* Radius slider */} {/* Radius slider */}
<div className="mt-2 pt-2" style={{ borderTop: '1px solid rgba(245,158,11,0.1)' }}> <div className="mt-2 pt-2" style={{ borderTop: '1px solid var(--stroke-default)' }}>
<div className="flex items-center justify-between mb-[5px]"> <div className="flex items-center justify-between mb-[5px]">
<span className="text-caption text-fg-disabled"> </span> <span className="text-caption text-fg-disabled"> </span>
<span className="text-caption font-bold font-mono text-color-boom"> <span className="text-caption font-bold font-mono text-color-accent">
{nearbyRadius} nm {nearbyRadius} nm
</span> </span>
</div> </div>
@ -572,7 +545,7 @@ export function IncidentsRightPanel({
height: '4px', height: '4px',
background: 'var(--stroke-default)', background: 'var(--stroke-default)',
borderRadius: '2px', borderRadius: '2px',
accentColor: '#f59e0b', accentColor: 'var(--color-accent)',
}} }}
/> />
</div> </div>
@ -605,7 +578,8 @@ export function IncidentsRightPanel({
color: isActive ? 'var(--color-accent)' : 'var(--fg-disabled)', color: isActive ? 'var(--color-accent)' : 'var(--fg-disabled)',
}} }}
> >
{v.icon} {v.label} {/* {v.icon} */}
{v.label}
</button> </button>
); );
})} })}
@ -627,9 +601,7 @@ export function IncidentsRightPanel({
className="w-full text-label-2 font-bold cursor-pointer" className="w-full text-label-2 font-bold cursor-pointer"
style={{ style={{
padding: '8px', padding: '8px',
background: analysisActive background: analysisActive ? 'rgba(239,68,68,0.1)' : 'rgba(6,182,212,0.1)',
? 'linear-gradient(135deg,rgba(239,68,68,0.15),rgba(239,68,68,0.1))'
: 'linear-gradient(135deg,rgba(6,182,212,0.15),rgba(59,130,246,0.1))',
border: analysisActive border: analysisActive
? '1px solid rgba(239,68,68,0.3)' ? '1px solid rgba(239,68,68,0.3)'
: '1px solid rgba(6,182,212,0.3)', : '1px solid rgba(6,182,212,0.3)',

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

파일 보기

@ -112,7 +112,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-title-3 font-medium rounded transition-colors ${ className={`px-3 py-1 text-body-2 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'
}`} }`}
> >
@ -127,8 +127,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-heading-3 font-bold text-fg"> </h1> <h1 className="text-heading-3 text-fg"> </h1>
<p className="text-title-3 text-fg-disabled mt-1"> {analyses.length}</p> <p className="text-body-2 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">
@ -137,12 +137,12 @@ 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-title-3 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-body-2 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-title-3 font-semibold rounded-sm cursor-pointer text-color-accent" className="px-4 py-2 text-body-2 font-semibold rounded-sm cursor-pointer text-color-accent"
style={{ style={{
border: '1px solid rgba(6,182,212,.3)', border: '1px solid rgba(6,182,212,.3)',
background: 'rgba(6,182,212,.08)', background: 'rgba(6,182,212,.08)',
@ -156,7 +156,7 @@ 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-title-3"> ...</div> <div className="text-center py-20 text-fg-disabled text-body-2"> ...</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">
@ -208,14 +208,14 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
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-title-3 text-fg-sub font-mono"> <td className="px-4 py-3 text-body-2 text-fg-sub font-mono">
{analysis.acdntSn} {analysis.acdntSn}
</td> </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-title-3 font-medium text-color-accent hover:underline transition-all cursor-pointer" className="text-body-2 font-medium text-color-accent hover:underline transition-all cursor-pointer"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
if (onSelectAnalysis) { if (onSelectAnalysis) {
@ -227,7 +227,7 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
</span> </span>
</div> </div>
</td> </td>
<td className="px-4 py-3 text-title-3 text-fg-sub font-mono"> <td className="px-4 py-3 text-body-2 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',
@ -237,7 +237,7 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
}) })
: '—'} : '—'}
</td> </td>
<td className="px-4 py-3 text-title-3 text-fg-sub font-mono"> <td className="px-4 py-3 text-body-2 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',
@ -247,11 +247,11 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
}) })
: '—'} : '—'}
</td> </td>
<td className="px-4 py-3 text-title-3 text-fg-sub font-mono"> <td className="px-4 py-3 text-body-2 text-fg-sub font-mono">
{analysis.duration} {analysis.duration}
</td> </td>
<td className="px-4 py-3 text-title-3 text-fg-sub">{analysis.oilType}</td> <td className="px-4 py-3 text-body-2 text-fg-sub">{analysis.oilType}</td>
<td className="px-4 py-3 text-title-3 text-fg font-mono text-right font-medium"> <td className="px-4 py-3 text-body-2 text-fg font-mono text-right font-medium">
{analysis.volume != null {analysis.volume != null
? analysis.volume >= 0.01 ? analysis.volume >= 0.01
? analysis.volume.toFixed(2) ? analysis.volume.toFixed(2)
@ -268,8 +268,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-title-3 text-fg-sub">{analysis.analyst}</td> <td className="px-4 py-3 text-body-2 text-fg-sub">{analysis.analyst}</td>
<td className="px-4 py-3 text-title-3 text-fg-sub">{analysis.officeName}</td> <td className="px-4 py-3 text-body-2 text-fg-sub">{analysis.officeName}</td>
</tr> </tr>
))} ))}
</tbody> </tbody>
@ -277,7 +277,7 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
)} )}
{!loading && analyses.length === 0 && ( {!loading && analyses.length === 0 && (
<div className="text-center py-20 text-fg-disabled text-title-3"> <div className="text-center py-20 text-fg-disabled text-body-2">
. .
</div> </div>
)} )}
@ -288,14 +288,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-title-3 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-body-2 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-title-3 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-body-2 font-medium text-fg-sub hover:bg-bg-elevated rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
> >
</button> </button>
@ -305,14 +305,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-title-3 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-body-2 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-title-3 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-body-2 font-medium text-fg-sub hover:bg-bg-elevated rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
> >
</button> </button>

파일 보기

@ -68,7 +68,7 @@ export function BacktrackModal({
border: '1px solid var(--bd)', border: '1px solid var(--bd)',
borderRadius: '6px', borderRadius: '6px',
color: 'var(--t1)', color: 'var(--t1)',
fontSize: '11px', fontSize: 'var(--font-size-caption)',
fontFamily: 'var(--fM)', fontFamily: 'var(--fM)',
outline: 'none', outline: 'none',
opacity: inputDisabled ? 0.6 : 1, opacity: inputDisabled ? 0.6 : 1,
@ -108,9 +108,8 @@ export function BacktrackModal({
borderRadius: '10px', borderRadius: '10px',
background: 'linear-gradient(135deg, rgba(168,85,247,0.2), rgba(6,182,212,0.2))', background: 'linear-gradient(135deg, rgba(168,85,247,0.2), rgba(6,182,212,0.2))',
border: '1px solid rgba(168,85,247,0.3)', border: '1px solid rgba(168,85,247,0.3)',
fontSize: '18px',
}} }}
className="flex items-center justify-center" className="flex items-center justify-center text-title-1"
> >
🔍 🔍
</div> </div>
@ -127,7 +126,7 @@ export function BacktrackModal({
height: '32px', height: '32px',
borderRadius: '8px', borderRadius: '8px',
background: 'var(--bg-card)', background: 'var(--bg-card)',
fontSize: '14px', fontSize: 'var(--font-size-body-2)',
}} }}
className="border border-stroke text-fg-disabled cursor-pointer flex items-center justify-center" className="border border-stroke text-fg-disabled cursor-pointer flex items-center justify-center"
> >
@ -245,7 +244,7 @@ export function BacktrackModal({
}} }}
> >
<div className="text-caption text-fg-disabled mb-1"> </div> <div className="text-caption text-fg-disabled mb-1"> </div>
<div className="text-sm font-bold text-color-tertiary font-mono"> <div className="text-body-2 font-bold text-color-tertiary font-mono">
{conditions.totalVessels}{' '} {conditions.totalVessels}{' '}
<span className="text-caption font-medium text-fg-disabled">(AIS )</span> <span className="text-caption font-medium text-fg-disabled">(AIS )</span>
</div> </div>
@ -371,7 +370,7 @@ function VesselCard({ vessel }: { vessel: BacktrackVessel }) {
borderRadius: '50%', borderRadius: '50%',
background: `${vessel.color}20`, background: `${vessel.color}20`,
border: `2px solid ${vessel.color}`, border: `2px solid ${vessel.color}`,
fontSize: '12px', fontSize: 'var(--font-size-caption)',
fontWeight: 800, fontWeight: 800,
color: vessel.color, color: vessel.color,
}} }}

파일 보기

@ -1222,7 +1222,7 @@ function FieldApplicationPanel() {
style={{ background: step.bg, border: `1px solid ${step.bd}` }} style={{ background: step.bg, border: `1px solid ${step.bd}` }}
> >
<div <div
className="min-w-[36px] h-[36px] rounded-[9px] flex items-center justify-center font-extrabold text-sm flex-shrink-0" className="min-w-[36px] h-[36px] rounded-[9px] flex items-center justify-center font-extrabold text-body-2 flex-shrink-0"
style={{ style={{
background: step.numBg, background: step.numBg,
border: `1px solid ${step.numBd}`, border: `1px solid ${step.numBd}`,

파일 보기

@ -130,7 +130,7 @@ export function LeftPanel({
}; };
return ( return (
<div className="w-80 min-w-[320px] bg-bg-surface border-r border-stroke flex flex-col"> <div className="w-full min-w-0 h-full bg-bg-surface border-r border-stroke flex flex-col overflow-hidden">
{/* Scrollable Content */} {/* Scrollable Content */}
<div <div
className="flex-1 overflow-y-scroll scrollbar-thin scrollbar-thumb-border-light scrollbar-track-transparent" className="flex-1 overflow-y-scroll scrollbar-thin scrollbar-thumb-border-light scrollbar-track-transparent"

파일 보기

@ -166,6 +166,8 @@ export const ALL_MODELS: PredictionModel[] = ['KOSPS', 'POSEIDON', 'OpenDrift'];
export function OilSpillView() { export function OilSpillView() {
const { activeSubTab, setActiveSubTab } = useSubMenu('prediction'); const { activeSubTab, setActiveSubTab } = useSubMenu('prediction');
const [leftCollapsed, setLeftCollapsed] = useState(false);
const [rightCollapsed, setRightCollapsed] = useState(false);
const [enabledLayers, setEnabledLayers] = useState<Set<string>>(new Set()); const [enabledLayers, setEnabledLayers] = useState<Set<string>>(new Set());
const [incidentCoord, setIncidentCoord] = useState<{ lon: number; lat: number } | null>(null); const [incidentCoord, setIncidentCoord] = useState<{ lon: number; lat: number } | null>(null);
const [flyToCoord, setFlyToCoord] = useState<{ lon: number; lat: number } | undefined>(undefined); const [flyToCoord, setFlyToCoord] = useState<{ lon: number; lat: number } | undefined>(undefined);
@ -1129,88 +1131,132 @@ export function OilSpillView() {
<div className="relative flex flex-1 overflow-hidden"> <div className="relative flex flex-1 overflow-hidden">
{/* Left Sidebar */} {/* Left Sidebar */}
{activeSubTab === 'analysis' && ( {activeSubTab === 'analysis' && (
<LeftPanel <div className="shrink-0 overflow-hidden" style={{ width: leftCollapsed ? 0 : 320 }}>
selectedAnalysis={selectedAnalysis} <LeftPanel
enabledLayers={enabledLayers} selectedAnalysis={selectedAnalysis}
onToggleLayer={handleToggleLayer} enabledLayers={enabledLayers}
accidentTime={accidentTime} onToggleLayer={handleToggleLayer}
onAccidentTimeChange={(v) => { accidentTime={accidentTime}
setAccidentTime(v); onAccidentTimeChange={(v) => {
setValidationErrors((prev) => { setAccidentTime(v);
const n = new Set(prev); setValidationErrors((prev) => {
n.delete('accidentTime'); const n = new Set(prev);
return n; n.delete('accidentTime');
}); return n;
}} });
incidentCoord={incidentCoord} }}
onCoordChange={(v) => { incidentCoord={incidentCoord}
setIncidentCoord(v); onCoordChange={(v) => {
setValidationErrors((prev) => { setIncidentCoord(v);
const n = new Set(prev); setValidationErrors((prev) => {
n.delete('coord'); const n = new Set(prev);
return n; n.delete('coord');
}); return n;
}} });
isSelectingLocation={isSelectingLocation} }}
onMapSelectClick={() => setIsSelectingLocation((prev) => !prev)} isSelectingLocation={isSelectingLocation}
onRunSimulation={handleRunSimulation} onMapSelectClick={() => setIsSelectingLocation((prev) => !prev)}
isRunningSimulation={isRunningSimulation} onRunSimulation={handleRunSimulation}
selectedModels={selectedModels} isRunningSimulation={isRunningSimulation}
onModelsChange={(v) => { selectedModels={selectedModels}
setSelectedModels(v); onModelsChange={(v) => {
setValidationErrors((prev) => { setSelectedModels(v);
const n = new Set(prev); setValidationErrors((prev) => {
n.delete('models'); const n = new Set(prev);
return n; n.delete('models');
}); return n;
}} });
visibleModels={visibleModels} }}
onVisibleModelsChange={setVisibleModels} visibleModels={visibleModels}
hasResults={oilTrajectory.length > 0} onVisibleModelsChange={setVisibleModels}
predictionTime={predictionTime} hasResults={oilTrajectory.length > 0}
onPredictionTimeChange={setPredictionTime} predictionTime={predictionTime}
spillType={spillType} onPredictionTimeChange={setPredictionTime}
onSpillTypeChange={setSpillType} spillType={spillType}
oilType={oilType} onSpillTypeChange={setSpillType}
onOilTypeChange={setOilType} oilType={oilType}
spillAmount={spillAmount} onOilTypeChange={setOilType}
onSpillAmountChange={setSpillAmount} spillAmount={spillAmount}
incidentName={incidentName} onSpillAmountChange={setSpillAmount}
onIncidentNameChange={(v) => { incidentName={incidentName}
setIncidentName(v); onIncidentNameChange={(v) => {
setValidationErrors((prev) => { setIncidentName(v);
const n = new Set(prev); setValidationErrors((prev) => {
n.delete('incidentName'); const n = new Set(prev);
return n; n.delete('incidentName');
}); return n;
}} });
spillUnit={spillUnit} }}
onSpillUnitChange={setSpillUnit} spillUnit={spillUnit}
boomLines={boomLines} onSpillUnitChange={setSpillUnit}
onBoomLinesChange={setBoomLines} boomLines={boomLines}
oilTrajectory={oilTrajectory} onBoomLinesChange={setBoomLines}
algorithmSettings={algorithmSettings} oilTrajectory={oilTrajectory}
onAlgorithmSettingsChange={setAlgorithmSettings} algorithmSettings={algorithmSettings}
isDrawingBoom={isDrawingBoom} onAlgorithmSettingsChange={setAlgorithmSettings}
onDrawingBoomChange={setIsDrawingBoom} isDrawingBoom={isDrawingBoom}
drawingPoints={drawingPoints} onDrawingBoomChange={setIsDrawingBoom}
onDrawingPointsChange={setDrawingPoints} drawingPoints={drawingPoints}
containmentResult={containmentResult} onDrawingPointsChange={setDrawingPoints}
onContainmentResultChange={setContainmentResult} containmentResult={containmentResult}
layerOpacity={layerOpacity} onContainmentResultChange={setContainmentResult}
onLayerOpacityChange={setLayerOpacity} layerOpacity={layerOpacity}
layerBrightness={layerBrightness} onLayerOpacityChange={setLayerOpacity}
onLayerBrightnessChange={setLayerBrightness} layerBrightness={layerBrightness}
layerColors={layerColors} onLayerBrightnessChange={setLayerBrightness}
onLayerColorChange={(id, color) => setLayerColors((prev) => ({ ...prev, [id]: color }))} layerColors={layerColors}
sensitiveResources={sensitiveResourceCategories} onLayerColorChange={(id, color) => setLayerColors((prev) => ({ ...prev, [id]: color }))}
onImageAnalysisResult={handleImageAnalysisResult} sensitiveResources={sensitiveResourceCategories}
validationErrors={validationErrors} onImageAnalysisResult={handleImageAnalysisResult}
/> validationErrors={validationErrors}
/>
</div>
)} )}
{/* Center - Map/Content Area */} {/* Center - Map/Content Area */}
<div className="flex-1 relative overflow-hidden"> <div className="flex-1 relative overflow-hidden">
{/* Left panel toggle button */}
{activeSubTab === 'analysis' && (
<button
onClick={() => setLeftCollapsed((v) => !v)}
className="absolute z-[500] top-1/2 -translate-y-1/2 flex items-center justify-center text-[10px]"
style={{
left: 0,
width: 18,
height: 40,
background: 'var(--bg-elevated)',
border: '1px solid var(--stroke-default)',
borderLeft: 'none',
borderRadius: '0 6px 6px 0',
color: 'var(--fg-sub)',
cursor: 'pointer',
}}
>
{leftCollapsed ? '▶' : '◀'}
</button>
)}
{/* Right panel toggle button */}
{activeSubTab === 'analysis' && (
<button
onClick={() => setRightCollapsed((v) => !v)}
className="absolute z-[500] top-1/2 -translate-y-1/2 flex items-center justify-center text-[10px]"
style={{
right: 0,
width: 18,
height: 40,
background: 'var(--bg-elevated)',
border: '1px solid var(--stroke-default)',
borderRight: 'none',
borderRadius: '6px 0 0 6px',
color: 'var(--fg-sub)',
cursor: 'pointer',
}}
>
{rightCollapsed ? '◀' : '▶'}
</button>
)}
{activeSubTab === 'list' ? ( {activeSubTab === 'list' ? (
<AnalysisListTable <AnalysisListTable
onTabChange={setActiveSubTab} onTabChange={setActiveSubTab}
@ -1332,7 +1378,7 @@ export function OilSpillView() {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
cursor: 'pointer', cursor: 'pointer',
fontSize: '14px', fontSize: 'var(--font-size-body-2)',
transition: '0.2s', transition: '0.2s',
}} }}
> >
@ -1358,7 +1404,7 @@ export function OilSpillView() {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
cursor: 'pointer', cursor: 'pointer',
fontSize: '14px', fontSize: 'var(--font-size-body-2)',
transition: '0.2s', transition: '0.2s',
}} }}
> >
@ -1394,7 +1440,7 @@ export function OilSpillView() {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
cursor: 'pointer', cursor: 'pointer',
fontSize: '12px', fontSize: 'var(--font-size-caption)',
transition: '0.2s', transition: '0.2s',
}} }}
> >
@ -1415,7 +1461,7 @@ export function OilSpillView() {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
cursor: 'pointer', cursor: 'pointer',
fontSize: '11px', fontSize: 'var(--font-size-caption)',
fontWeight: 600, fontWeight: 600,
fontFamily: 'var(--font-mono)', fontFamily: 'var(--font-mono)',
transition: '0.2s', transition: '0.2s',
@ -1439,7 +1485,7 @@ export function OilSpillView() {
position: 'absolute', position: 'absolute',
left: `${pos}%`, left: `${pos}%`,
transform: 'translateX(-50%)', transform: 'translateX(-50%)',
fontSize: '10px', fontSize: 'var(--font-size-caption)',
fontFamily: 'var(--font-mono)', fontFamily: 'var(--font-mono)',
color: isActive ? 'var(--color-accent)' : 'var(--fg-disabled)', color: isActive ? 'var(--color-accent)' : 'var(--fg-disabled)',
fontWeight: isActive ? 600 : 400, fontWeight: isActive ? 600 : 400,
@ -1526,7 +1572,7 @@ export function OilSpillView() {
top: '-18px', top: '-18px',
left: `${bm.pos}%`, left: `${bm.pos}%`,
transform: 'translateX(-50%)', transform: 'translateX(-50%)',
fontSize: '12px', fontSize: 'var(--font-size-caption)',
cursor: 'pointer', cursor: 'pointer',
filter: 'drop-shadow(0 0 4px rgba(245,158,11,0.5))', filter: 'drop-shadow(0 0 4px rgba(245,158,11,0.5))',
}} }}
@ -1569,7 +1615,7 @@ export function OilSpillView() {
> >
<div <div
style={{ style={{
fontSize: '14px', fontSize: 'var(--font-size-body-2)',
fontWeight: 600, fontWeight: 600,
color: 'var(--color-accent)', color: 'var(--color-accent)',
fontFamily: 'var(--font-mono)', fontFamily: 'var(--font-mono)',
@ -1610,7 +1656,7 @@ export function OilSpillView() {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: '5px', gap: '5px',
fontSize: '11px', fontSize: 'var(--font-size-caption)',
}} }}
> >
<span className="text-fg-disabled">{s.label}</span> <span className="text-fg-disabled">{s.label}</span>
@ -1655,44 +1701,46 @@ export function OilSpillView() {
{/* Right Panel */} {/* Right Panel */}
{activeSubTab === 'analysis' && ( {activeSubTab === 'analysis' && (
<RightPanel <div className="shrink-0 overflow-hidden" style={{ width: rightCollapsed ? 0 : 300 }}>
onOpenBacktrack={handleOpenBacktrack} <RightPanel
onOpenRecalc={() => { onOpenBacktrack={handleOpenBacktrack}
if (!selectedAnalysis) { onOpenRecalc={() => {
alert('선택된 사고가 없습니다.\n분석 목록에서 사고를 선택해주세요.'); if (!selectedAnalysis) {
return; alert('선택된 사고가 없습니다.\n분석 목록에서 사고를 선택해주세요.');
return;
}
setRecalcModalOpen(true);
}}
onOpenReport={handleOpenReport}
detail={analysisDetail}
summary={
stepSummariesByModel[windHydrModel]?.[currentStep] ??
summaryByModel[windHydrModel] ??
simulationSummary
} }
setRecalcModalOpen(true); boomBlockedVolume={boomBlockedVolume}
}} displayControls={displayControls}
onOpenReport={handleOpenReport} onDisplayControlsChange={setDisplayControls}
detail={analysisDetail} windHydrModel={windHydrModel}
summary={ windHydrModelOptions={windHydrModelOptions}
stepSummariesByModel[windHydrModel]?.[currentStep] ?? onWindHydrModelChange={setWindHydrModel}
summaryByModel[windHydrModel] ?? analysisTab={analysisTab}
simulationSummary onSwitchAnalysisTab={setAnalysisTab}
} drawAnalysisMode={drawAnalysisMode}
boomBlockedVolume={boomBlockedVolume} analysisPolygonPoints={analysisPolygonPoints}
displayControls={displayControls} circleRadiusNm={circleRadiusNm}
onDisplayControlsChange={setDisplayControls} onCircleRadiusChange={setCircleRadiusNm}
windHydrModel={windHydrModel} analysisResult={analysisResult}
windHydrModelOptions={windHydrModelOptions} incidentCoord={incidentCoord}
onWindHydrModelChange={setWindHydrModel} centerPoints={centerPoints}
analysisTab={analysisTab} predictionTime={predictionTime}
onSwitchAnalysisTab={setAnalysisTab} onStartPolygonDraw={handleStartPolygonDraw}
drawAnalysisMode={drawAnalysisMode} onRunPolygonAnalysis={handleRunPolygonAnalysis}
analysisPolygonPoints={analysisPolygonPoints} onRunCircleAnalysis={handleRunCircleAnalysis}
circleRadiusNm={circleRadiusNm} onCancelAnalysis={handleCancelAnalysis}
onCircleRadiusChange={setCircleRadiusNm} onClearAnalysis={handleClearAnalysis}
analysisResult={analysisResult} />
incidentCoord={incidentCoord} </div>
centerPoints={centerPoints}
predictionTime={predictionTime}
onStartPolygonDraw={handleStartPolygonDraw}
onRunPolygonAnalysis={handleRunPolygonAnalysis}
onRunCircleAnalysis={handleRunCircleAnalysis}
onCancelAnalysis={handleCancelAnalysis}
onClearAnalysis={handleClearAnalysis}
/>
)} )}
{/* 확산 예측 실행 중 로딩 오버레이 */} {/* 확산 예측 실행 중 로딩 오버레이 */}

파일 보기

@ -159,7 +159,7 @@ export function RecalcModal({
borderRadius: '10px', borderRadius: '10px',
background: 'linear-gradient(135deg, rgba(249,115,22,0.2), rgba(6,182,212,0.2))', background: 'linear-gradient(135deg, rgba(249,115,22,0.2), rgba(6,182,212,0.2))',
border: '1px solid rgba(249,115,22,0.3)', border: '1px solid rgba(249,115,22,0.3)',
fontSize: '16px', fontSize: 'var(--font-size-body-1)',
}} }}
className="flex items-center justify-center" className="flex items-center justify-center"
> >
@ -178,7 +178,7 @@ export function RecalcModal({
height: '28px', height: '28px',
borderRadius: '6px', borderRadius: '6px',
background: 'var(--bg-card)', background: 'var(--bg-card)',
fontSize: '12px', fontSize: 'var(--font-size-caption)',
}} }}
className="border border-stroke text-fg-disabled cursor-pointer flex items-center justify-center" className="border border-stroke text-fg-disabled cursor-pointer flex items-center justify-center"
> >
@ -321,7 +321,7 @@ export function RecalcModal({
} }
toggleModel(model); toggleModel(model);
}} }}
style={{ fontSize: '11px', padding: '5px 10px' }} style={{ fontSize: 'var(--font-size-caption)', padding: '5px 10px' }}
> >
<span className="prd-md" style={{ background: color }} /> <span className="prd-md" style={{ background: color }} />
{model} {model}

파일 보기

@ -117,7 +117,7 @@ export function RightPanel({
}, [incidentCoord, centerPoints, summary, predictionTime]); }, [incidentCoord, centerPoints, summary, predictionTime]);
return ( return (
<div className="w-[300px] min-w-[300px] bg-bg-surface border-l border-stroke flex flex-col"> <div className="w-full min-w-0 h-full bg-bg-surface border-l border-stroke flex flex-col overflow-hidden">
{/* Tab Header */} {/* Tab Header */}
<div className="flex border-b border-stroke"> <div className="flex border-b border-stroke">
<button className="flex-1 py-3 text-center text-label-1 font-medium text-color-accent border-b-2 border-color-accent transition-all font-korean"> <button className="flex-1 py-3 text-center text-label-1 font-medium text-color-accent border-b-2 border-color-accent transition-all font-korean">
@ -580,7 +580,7 @@ export function RightPanel({
{/* Bottom Action Buttons */} {/* Bottom Action Buttons */}
<div className="flex gap-1.5 p-3 border-t border-stroke"> <div className="flex gap-1.5 p-3 border-t border-stroke">
<button className="flex-1 py-2 px-1 rounded-sm text-label-2 font-medium border border-color-accent text-color-accent font-korean hover:bg-[rgba(6,182,212,0.08)] transition-colors"> <button className="flex-1 py-2 px-1 rounded-sm text-label-2 font-medium border border-stroke font-korean hover:bg-[rgba(6,182,212,0.08)] transition-colors">
</button> </button>
<button <button
@ -597,7 +597,7 @@ export function RightPanel({
</button> </button>
<button <button
onClick={onOpenBacktrack} onClick={onOpenBacktrack}
className="flex-1 py-2 px-1 rounded-sm text-label-2 font-medium border border-[var(--color-tertiary)] text-[var(--color-tertiary)] font-korean hover:bg-[rgba(168,85,247,0.08)] transition-colors" className="flex-1 py-2 px-1 rounded-sm text-label-2 font-medium border border-stroke font-korean hover:bg-[rgba(168,85,247,0.08)] transition-colors"
> >
</button> </button>

파일 보기

@ -1120,7 +1120,9 @@ function ReportGenerator({ onSave }: ReportGeneratorProps) {
{activeSections.length === 0 && ( {activeSections.length === 0 && (
<div className="flex flex-col items-center justify-center py-20 text-fg-disabled"> <div className="flex flex-col items-center justify-center py-20 text-fg-disabled">
<div className="text-4xl mb-4">📋</div> <div className="text-4xl mb-4">📋</div>
<p className="text-sm font-korean"> </p> <p className="text-body-2 font-korean">
</p>
</div> </div>
)} )}
</div> </div>

파일 보기

@ -171,13 +171,13 @@ export function ReportsView() {
{filteredReports.length === 0 ? ( {filteredReports.length === 0 ? (
<div className="flex flex-col items-center justify-center py-20 text-fg-disabled"> <div className="flex flex-col items-center justify-center py-20 text-fg-disabled">
<div className="text-4xl mb-4">📄</div> <div className="text-4xl mb-4">📄</div>
<p className="text-sm font-korean mb-2"> </p> <p className="text-body-2 font-korean mb-2"> </p>
<button <button
onClick={() => { onClick={() => {
setView({ screen: 'templates' }); setView({ screen: 'templates' });
setActiveSubTab('template'); setActiveSubTab('template');
}} }}
className="px-4 py-2 text-xs font-semibold rounded-md border border-color-accent text-color-accent hover:bg-[color-mix(in_srgb,var(--color-accent)_10%,transparent)] transition-all font-korean mt-4" className="px-4 py-2 text-caption font-semibold rounded-md border border-color-accent text-color-accent hover:bg-[color-mix(in_srgb,var(--color-accent)_10%,transparent)] transition-all font-korean mt-4"
> >
릿 릿
</button> </button>
@ -376,7 +376,7 @@ export function ReportsView() {
<td className="px-3 py-3 text-center"> <td className="px-3 py-3 text-center">
<button <button
onClick={() => handleDelete(report.id)} onClick={() => handleDelete(report.id)}
className="w-7 h-7 rounded flex items-center justify-center text-color-danger hover:bg-[color-mix(in_srgb,var(--color-danger)_10%,transparent)] transition-all text-sm" className="w-7 h-7 rounded flex items-center justify-center text-color-danger hover:bg-[color-mix(in_srgb,var(--color-danger)_10%,transparent)] transition-all text-body-2"
> >
🗑 🗑
</button> </button>

파일 보기

@ -1651,14 +1651,14 @@ function NewScenarioModal({ ops, onClose }: { ops: RescueOpsItem[]; onClose: ()
</div> </div>
<button <button
onClick={onClose} onClick={onClose}
className="px-5 py-2.5 rounded-md border border-stroke bg-bg-card text-fg-sub text-xs font-semibold cursor-pointer" className="px-5 py-2.5 rounded-md border border-stroke bg-bg-card text-fg-sub text-caption font-semibold cursor-pointer"
> >
</button> </button>
{done ? ( {done ? (
<button <button
onClick={onClose} onClick={onClose}
className="px-7 py-2.5 rounded-md border-none text-static-white text-xs font-bold cursor-pointer bg-color-success" className="px-7 py-2.5 rounded-md border-none text-static-white text-caption font-bold cursor-pointer bg-color-success"
> >
</button> </button>
@ -1666,7 +1666,7 @@ function NewScenarioModal({ ops, onClose }: { ops: RescueOpsItem[]; onClose: ()
<button <button
onClick={handleSubmit} onClick={handleSubmit}
disabled={submitting} disabled={submitting}
className={`px-7 py-2.5 rounded-md border-none text-xs font-bold ${ className={`px-7 py-2.5 rounded-md border-none text-caption font-bold ${
submitting submitting
? 'bg-bg-card text-fg-disabled cursor-wait' ? 'bg-bg-card text-fg-disabled cursor-wait'
: 'bg-color-navy text-static-white cursor-pointer hover:bg-color-navy-hover' : 'bg-color-navy text-static-white cursor-pointer hover:bg-color-navy-hover'

파일 보기

@ -3,8 +3,8 @@ function DistributionView() {
<div className="flex w-full h-full bg-bg-base items-center justify-center"> <div className="flex w-full h-full bg-bg-base items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="text-3xl opacity-20 mb-3">🗺</div> <div className="text-3xl opacity-20 mb-3">🗺</div>
<div className="text-sm font-bold text-fg-sub font-korean mb-1"></div> <div className="text-body-2 font-bold text-fg-sub font-korean mb-1"></div>
<div className="text-xs text-fg-disabled font-korean"> <div className="text-caption text-fg-disabled font-korean">
. .
</div> </div>
</div> </div>

파일 보기

@ -16,6 +16,8 @@ import ScatRightPanel from './ScatRightPanel';
// ═══ Main PreScatView ═══ // ═══ Main PreScatView ═══
export function PreScatView() { export function PreScatView() {
const [leftCollapsed, setLeftCollapsed] = useState(false);
const [rightCollapsed, setRightCollapsed] = useState(false);
const [segments, setSegments] = useState<ScatSegment[]>([]); const [segments, setSegments] = useState<ScatSegment[]>([]);
const [zones, setZones] = useState<ApiZoneItem[]>([]); const [zones, setZones] = useState<ApiZoneItem[]>([]);
const [jurisdictions, setJurisdictions] = useState<string[]>([]); const [jurisdictions, setJurisdictions] = useState<string[]>([]);
@ -175,13 +177,13 @@ export function PreScatView() {
if (error) { if (error) {
return ( return (
<div className="flex w-full h-full bg-bg-base items-center justify-center flex-col gap-3"> <div className="flex w-full h-full bg-bg-base items-center justify-center flex-col gap-3">
<div className="text-color-danger text-sm font-korean">{error}</div> <div className="text-color-danger text-body-2 font-korean">{error}</div>
<button <button
onClick={() => { onClick={() => {
setError(null); setError(null);
setLoading(true); setLoading(true);
}} }}
className="px-4 py-1.5 bg-color-accent text-white text-xs rounded font-korean" className="px-4 py-1.5 bg-color-accent text-white text-caption rounded font-korean"
> >
</button> </button>
@ -192,36 +194,77 @@ export function PreScatView() {
if (loading || !selectedSeg) { if (loading || !selectedSeg) {
return ( return (
<div className="flex w-full h-full bg-bg-base items-center justify-center"> <div className="flex w-full h-full bg-bg-base items-center justify-center">
<div className="text-fg-sub text-sm font-korean">SCAT ...</div> <div className="text-fg-sub text-body-2 font-korean">SCAT ...</div>
</div> </div>
); );
} }
return ( return (
<div className="flex w-full h-full bg-bg-base overflow-hidden"> <div className="flex w-full h-full bg-bg-base overflow-hidden">
<ScatLeftPanel {/* Left Panel */}
segments={segments} <div className="shrink-0 overflow-hidden" style={{ width: leftCollapsed ? 0 : 340 }}>
zones={zones} <ScatLeftPanel
jurisdictions={jurisdictions} segments={segments}
offices={offices} zones={zones}
selectedOffice={selectedOffice} jurisdictions={jurisdictions}
onOfficeChange={setSelectedOffice} offices={offices}
selectedSeg={selectedSeg} selectedOffice={selectedOffice}
onSelectSeg={setSelectedSeg} onOfficeChange={setSelectedOffice}
onOpenPopup={handleOpenPopup} selectedSeg={selectedSeg}
jurisdictionFilter={jurisdictionFilter} onSelectSeg={setSelectedSeg}
onJurisdictionChange={setJurisdictionFilter} onOpenPopup={handleOpenPopup}
areaFilter={areaFilter} jurisdictionFilter={jurisdictionFilter}
onAreaChange={setAreaFilter} onJurisdictionChange={setJurisdictionFilter}
phaseFilter={phaseFilter} areaFilter={areaFilter}
onPhaseChange={setPhaseFilter} onAreaChange={setAreaFilter}
statusFilter={statusFilter} phaseFilter={phaseFilter}
onStatusChange={setStatusFilter} onPhaseChange={setPhaseFilter}
searchTerm={searchTerm} statusFilter={statusFilter}
onSearchChange={setSearchTerm} onStatusChange={setStatusFilter}
/> searchTerm={searchTerm}
onSearchChange={setSearchTerm}
/>
</div>
<div className="flex-1 relative overflow-hidden">
{/* Left panel toggle button */}
<button
onClick={() => setLeftCollapsed((v) => !v)}
className="absolute z-[500] top-1/2 -translate-y-1/2 flex items-center justify-center text-[10px]"
style={{
left: 0,
width: 18,
height: 40,
background: 'var(--bg-elevated)',
border: '1px solid var(--stroke-default)',
borderLeft: 'none',
borderRadius: '0 6px 6px 0',
color: 'var(--fg-sub)',
cursor: 'pointer',
}}
>
{leftCollapsed ? '▶' : '◀'}
</button>
{/* Right panel toggle button */}
<button
onClick={() => setRightCollapsed((v) => !v)}
className="absolute z-[500] top-1/2 -translate-y-1/2 flex items-center justify-center text-[10px]"
style={{
right: 0,
width: 18,
height: 40,
background: 'var(--bg-elevated)',
border: '1px solid var(--stroke-default)',
borderRight: 'none',
borderRadius: '6px 0 0 6px',
color: 'var(--fg-sub)',
cursor: 'pointer',
}}
>
{rightCollapsed ? '◀' : '▶'}
</button>
<div className="flex-1 relative">
<ScatMap <ScatMap
segments={segments} segments={segments}
zones={zones} zones={zones}
@ -237,7 +280,10 @@ export function PreScatView() {
/> */} /> */}
</div> </div>
<ScatRightPanel detail={panelDetail} loading={panelLoading} /> {/* Right Panel */}
<div className="shrink-0 overflow-hidden" style={{ width: rightCollapsed ? 0 : 280 }}>
<ScatRightPanel detail={panelDetail} loading={panelLoading} />
</div>
{popupData && ( {popupData && (
<ScatPopup data={popupData} segCode={selectedSeg.code} onClose={handleClosePopup} /> <ScatPopup data={popupData} segCode={selectedSeg.code} onClose={handleClosePopup} />

파일 보기

@ -156,7 +156,7 @@ function ScatLeftPanel({
}, []); }, []);
return ( return (
<div className="w-[340px] min-w-[340px] bg-bg-surface border-r border-stroke flex flex-col overflow-hidden"> <div className="w-full h-full min-w-0 bg-bg-surface border-r border-stroke flex flex-col overflow-hidden">
{/* Filters */} {/* Filters */}
<div className="p-3.5 border-b border-stroke"> <div className="p-3.5 border-b border-stroke">
<div className="flex items-center gap-1.5 text-caption font-bold uppercase tracking-wider text-fg mb-3"> <div className="flex items-center gap-1.5 text-caption font-bold uppercase tracking-wider text-fg mb-3">
@ -269,7 +269,11 @@ function ScatLeftPanel({
rowCount={filtered.length} rowCount={filtered.length}
rowHeight={88} rowHeight={88}
overscanCount={5} overscanCount={5}
style={{ height: listHeight }} style={{
height: listHeight,
scrollbarWidth: 'thin',
scrollbarColor: 'var(--stroke-default) transparent',
}}
rowComponent={SegRow} rowComponent={SegRow}
rowProps={{ rowProps={{
filtered, filtered,

파일 보기

@ -310,7 +310,7 @@ function ScatMap({
</div> </div>
{/* Right info cards */} {/* Right info cards */}
<div className="absolute top-3.5 right-3.5 w-[260px] flex flex-col gap-2 z-[1000] max-h-[calc(100%-100px)] overflow-y-auto scrollbar-thin"> <div className="absolute top-3.5 right-8 w-[260px] flex flex-col gap-2 z-[1000] max-h-[calc(100%-100px)] overflow-y-auto scrollbar-thin">
{/* ESI Legend */} {/* ESI Legend */}
<div className="bg-[color-mix(in_srgb,var(--bg-base)_92%,transparent)] backdrop-blur-xl border border-stroke rounded-md p-3.5 shadow-[0_4px_20px_rgba(0,0,0,0.3)]"> <div className="bg-[color-mix(in_srgb,var(--bg-base)_92%,transparent)] backdrop-blur-xl border border-stroke rounded-md p-3.5 shadow-[0_4px_20px_rgba(0,0,0,0.3)]">
<div className="text-caption font-bold uppercase tracking-wider text-fg-disabled mb-2.5"> <div className="text-caption font-bold uppercase tracking-wider text-fg-disabled mb-2.5">

파일 보기

@ -182,7 +182,7 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
<button <button
key={i} key={i}
onClick={() => setPopTab(i)} onClick={() => setPopTab(i)}
className={`px-5 py-3 text-xs font-semibold font-korean border-b-2 transition-colors cursor-pointer ${ className={`px-5 py-3 text-caption font-semibold font-korean border-b-2 transition-colors cursor-pointer ${
popTab === i popTab === i
? 'text-color-success border-status-green' ? 'text-color-success border-status-green'
: 'text-fg-disabled border-transparent hover:text-fg-sub' : 'text-fg-disabled border-transparent hover:text-fg-sub'
@ -204,7 +204,9 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
{/* Skeleton */} {/* Skeleton */}
{!imgLoaded && !imgError && ( {!imgLoaded && !imgError && (
<div className="w-full aspect-video bg-bg-card animate-pulse flex items-center justify-center"> <div className="w-full aspect-video bg-bg-card animate-pulse flex items-center justify-center">
<span className="text-fg-disabled text-xs font-korean"> ...</span> <span className="text-fg-disabled text-caption font-korean">
...
</span>
</div> </div>
)} )}
<img <img
@ -216,7 +218,7 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
onError={() => setImgError(true)} onError={() => setImgError(true)}
/> />
{imgError && ( {imgError && (
<div className="w-full aspect-video flex flex-col items-center justify-center text-fg-disabled text-xs font-korean"> <div className="w-full aspect-video flex flex-col items-center justify-center text-fg-disabled text-caption font-korean">
{/* <span className="text-[40px]">📷</span> */} {/* <span className="text-[40px]">📷</span> */}
<span> </span> <span> </span>
</div> </div>
@ -266,7 +268,7 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
].map(([k, v, cls], i) => ( ].map(([k, v, cls], i) => (
<div <div
key={i} key={i}
className="flex justify-between py-1.5 text-xs border-b border-stroke last:border-b-0" className="flex justify-between py-1.5 text-caption border-b border-stroke last:border-b-0"
> >
<span className="text-fg-sub font-korean">{k}</span> <span className="text-fg-sub font-korean">{k}</span>
<span className={`text-fg font-korean ${cls}`}>{v}</span> <span className={`text-fg font-korean ${cls}`}>{v}</span>
@ -282,7 +284,7 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
{data.sensitive.map((s, i) => ( {data.sensitive.map((s, i) => (
<div <div
key={i} key={i}
className="flex justify-between py-1.5 text-xs border-b border-stroke last:border-b-0" className="flex justify-between py-1.5 text-caption border-b border-stroke last:border-b-0"
> >
<span className="text-fg-sub font-korean">{s.t}</span> <span className="text-fg-sub font-korean">{s.t}</span>
<span className="text-fg font-korean">{s.v}</span> <span className="text-fg font-korean">{s.v}</span>
@ -344,7 +346,9 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
<div className="w-full aspect-[4/3] bg-bg-base border border-stroke rounded-md mb-4 overflow-hidden relative"> <div className="w-full aspect-[4/3] bg-bg-base border border-stroke rounded-md mb-4 overflow-hidden relative">
{!mapLoaded && ( {!mapLoaded && (
<div className="absolute inset-0 bg-bg-card animate-pulse flex items-center justify-center z-10"> <div className="absolute inset-0 bg-bg-card animate-pulse flex items-center justify-center z-10">
<span className="text-fg-disabled text-xs font-korean"> ...</span> <span className="text-fg-disabled text-caption font-korean">
...
</span>
</div> </div>
)} )}
<PopupMap <PopupMap
@ -390,7 +394,7 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
].map(([k, v], i) => ( ].map(([k, v], i) => (
<div <div
key={i} key={i}
className="flex justify-between py-1.5 text-xs border-b border-stroke last:border-b-0" className="flex justify-between py-1.5 text-caption border-b border-stroke last:border-b-0"
> >
<span className="text-fg-disabled font-korean">{k}</span> <span className="text-fg-disabled font-korean">{k}</span>
<span className="text-color-success font-mono font-medium">{v}</span> <span className="text-color-success font-mono font-medium">{v}</span>
@ -413,7 +417,7 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
].map(([k, v], i) => ( ].map(([k, v], i) => (
<div <div
key={i} key={i}
className="flex justify-between py-1.5 text-xs border-b border-stroke last:border-b-0" className="flex justify-between py-1.5 text-caption border-b border-stroke last:border-b-0"
> >
<span className="text-fg-disabled font-korean">{k}</span> <span className="text-fg-disabled font-korean">{k}</span>
<span className="text-fg font-medium font-korean">{v}</span> <span className="text-fg font-medium font-korean">{v}</span>
@ -441,7 +445,7 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
].map((h, i) => ( ].map((h, i) => (
<div key={i} className="bg-bg-card border border-stroke rounded-md p-4"> <div key={i} className="bg-bg-card border border-stroke rounded-md p-4">
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<span className="text-xs font-bold font-mono">{h.date}</span> <span className="text-caption font-bold font-mono">{h.date}</span>
<span <span
className={`text-caption font-bold px-2 py-0.5 rounded-lg ${ className={`text-caption font-bold px-2 py-0.5 rounded-lg ${
h.type === 'Pre-SCAT' h.type === 'Pre-SCAT'

파일 보기

@ -23,7 +23,7 @@ export default function ScatRightPanel({
if (!detail && !loading) { if (!detail && !loading) {
return ( return (
<div className="flex flex-col items-center justify-center bg-bg-surface border-l border-stroke w-[280px] min-w-[280px] h-full"> <div className="flex flex-col items-center justify-center bg-bg-surface border-l border-stroke w-full h-full">
<div className="text-3xl mb-2">🏖</div> <div className="text-3xl mb-2">🏖</div>
<div className="text-center text-fg-disabled text-label-2 leading-relaxed"> <div className="text-center text-fg-disabled text-label-2 leading-relaxed">
@ -37,7 +37,7 @@ export default function ScatRightPanel({
} }
return ( return (
<div className="flex flex-col bg-bg-surface border-l border-stroke overflow-hidden h-full w-[280px] min-w-[280px]"> <div className="flex flex-col bg-bg-surface border-l border-stroke overflow-hidden h-full w-full">
{/* 헤더 */} {/* 헤더 */}
<div className="px-3.5 py-2.5 border-b border-stroke shrink-0"> <div className="px-3.5 py-2.5 border-b border-stroke shrink-0">
{detail ? ( {detail ? (
@ -49,12 +49,12 @@ export default function ScatRightPanel({
{detail.esi} {detail.esi}
</span> </span>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="text-xs font-bold truncate">{detail.name}</div> <div className="text-caption font-bold truncate">{detail.name}</div>
<div className="text-caption text-fg-disabled">{detail.code}</div> <div className="text-caption text-fg-disabled">{detail.code}</div>
</div> </div>
</div> </div>
) : ( ) : (
<div className="text-xs text-fg-disabled"> ...</div> <div className="text-caption text-fg-disabled"> ...</div>
)} )}
</div> </div>

파일 보기

@ -67,38 +67,38 @@ function ScatTimeline({ segments, currentIdx, onSeek }: ScatTimelineProps) {
<div className="flex gap-1 flex-shrink-0"> <div className="flex gap-1 flex-shrink-0">
<button <button
onClick={() => onSeek(0)} onClick={() => onSeek(0)}
className="w-[34px] h-[34px] rounded-sm border border-stroke bg-bg-card text-fg-sub flex items-center justify-center cursor-pointer hover:bg-bg-surface-hover text-sm" className="w-[34px] h-[34px] rounded-sm border border-stroke bg-bg-card text-fg-sub flex items-center justify-center cursor-pointer hover:bg-bg-surface-hover text-body-2"
> >
</button> </button>
<button <button
onClick={() => onSeek(Math.max(0, currentIdx - 1))} onClick={() => onSeek(Math.max(0, currentIdx - 1))}
className="w-[34px] h-[34px] rounded-sm border border-stroke bg-bg-card text-fg-sub flex items-center justify-center cursor-pointer hover:bg-bg-surface-hover text-sm" className="w-[34px] h-[34px] rounded-sm border border-stroke bg-bg-card text-fg-sub flex items-center justify-center cursor-pointer hover:bg-bg-surface-hover text-body-2"
> >
</button> </button>
<button <button
onClick={togglePlay} onClick={togglePlay}
className={`w-[34px] h-[34px] rounded-sm border flex items-center justify-center cursor-pointer text-sm ${playing ? 'bg-color-success text-black border-status-green' : 'border-stroke bg-bg-card text-fg-sub hover:bg-bg-surface-hover'}`} className={`w-[34px] h-[34px] rounded-sm border flex items-center justify-center cursor-pointer text-body-2 ${playing ? 'bg-color-success text-black border-status-green' : 'border-stroke bg-bg-card text-fg-sub hover:bg-bg-surface-hover'}`}
> >
{playing ? '⏸' : '▶'} {playing ? '⏸' : '▶'}
</button> </button>
<button <button
onClick={() => onSeek(Math.min(total - 1, currentIdx + 1))} onClick={() => onSeek(Math.min(total - 1, currentIdx + 1))}
className="w-[34px] h-[34px] rounded-sm border border-stroke bg-bg-card text-fg-sub flex items-center justify-center cursor-pointer hover:bg-bg-surface-hover text-sm" className="w-[34px] h-[34px] rounded-sm border border-stroke bg-bg-card text-fg-sub flex items-center justify-center cursor-pointer hover:bg-bg-surface-hover text-body-2"
> >
</button> </button>
<button <button
onClick={() => onSeek(total - 1)} onClick={() => onSeek(total - 1)}
className="w-[34px] h-[34px] rounded-sm border border-stroke bg-bg-card text-fg-sub flex items-center justify-center cursor-pointer hover:bg-bg-surface-hover text-sm" className="w-[34px] h-[34px] rounded-sm border border-stroke bg-bg-card text-fg-sub flex items-center justify-center cursor-pointer hover:bg-bg-surface-hover text-body-2"
> >
</button> </button>
<div className="w-2" /> <div className="w-2" />
<button <button
onClick={cycleSpeed} onClick={cycleSpeed}
className="w-[34px] h-[34px] rounded-sm border border-stroke bg-bg-card text-fg-sub flex items-center justify-center cursor-pointer hover:bg-bg-surface-hover text-xs font-mono font-bold" className="w-[34px] h-[34px] rounded-sm border border-stroke bg-bg-card text-fg-sub flex items-center justify-center cursor-pointer hover:bg-bg-surface-hover text-caption font-mono font-bold"
> >
{speed}× {speed}×
</button> </button>
@ -162,7 +162,7 @@ function ScatTimeline({ segments, currentIdx, onSeek }: ScatTimelineProps) {
{/* Info */} {/* Info */}
<div className="flex flex-col items-end gap-1 flex-shrink-0 min-w-[210px]"> <div className="flex flex-col items-end gap-1 flex-shrink-0 min-w-[210px]">
<span className="text-sm font-semibold text-color-success font-mono"> <span className="text-body-2 font-semibold text-color-success font-mono">
{displaySegs[currentIdx]?.code || 'S-001'} / {total} {displaySegs[currentIdx]?.code || 'S-001'} / {total}
</span> </span>
<div className="flex gap-3.5"> <div className="flex gap-3.5">

파일 보기

@ -3,8 +3,8 @@ function SurveyView() {
<div className="flex w-full h-full bg-bg-base items-center justify-center"> <div className="flex w-full h-full bg-bg-base items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="text-3xl opacity-20 mb-3">📋</div> <div className="text-3xl opacity-20 mb-3">📋</div>
<div className="text-sm font-bold text-fg-sub font-korean mb-1"> </div> <div className="text-body-2 font-bold text-fg-sub font-korean mb-1"> </div>
<div className="text-xs text-fg-disabled font-korean"> <div className="text-caption text-fg-disabled font-korean">
. .
</div> </div>
</div> </div>

파일 보기

@ -33,11 +33,11 @@ export function WeatherMapControls({ center, zoom }: WeatherMapControlsProps) {
<div key={tooltip} className="relative group"> <div key={tooltip} className="relative group">
<button <button
onClick={onClick} onClick={onClick}
className="w-[38px] h-[38px] bg-[rgba(18,25,41,0.9)] backdrop-blur-xl border border-stroke rounded-sm text-fg-sub flex items-center justify-center hover:bg-bg-surface-hover hover:text-fg transition-all text-base" className="w-[38px] h-[38px] bg-bg-surface border border-stroke rounded-sm shadow-md text-fg-sub flex items-center justify-center hover:bg-bg-surface-hover hover:text-fg transition-all text-base"
> >
{label} {label}
</button> </button>
<div className="absolute right-full top-1/2 -translate-y-1/2 mr-2 px-2 py-1 text-xs bg-bg-base text-fg border border-stroke rounded whitespace-nowrap opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity z-20"> <div className="absolute right-full top-1/2 -translate-y-1/2 mr-2 px-2 py-1 text-caption bg-bg-base text-fg border border-stroke rounded whitespace-nowrap opacity-0 group-hover:opacity-100 pointer-events-none transition-opacity z-20">
{tooltip} {tooltip}
</div> </div>
</div> </div>

파일 보기

@ -33,26 +33,26 @@ interface WeatherMapOverlayProps {
selectedStationId: string | null; selectedStationId: string | null;
} }
// 풍속에 따른 hex 색상 반환 // 풍속에 따른 색상 반환
function getWindHexColor(speed: number, isSelected: boolean): string { function getWindHexColor(speed: number, isSelected: boolean): string {
if (isSelected) return '#06b6d4'; if (isSelected) return 'var(--color-accent)';
if (speed > 10) return '#ef4444'; if (speed > 10) return 'var(--color-danger)';
if (speed > 7) return '#f59e0b'; if (speed > 7) return 'var(--color-caution)';
return '#3b82f6'; return 'var(--color-info)';
} }
// 파고에 따른 hex 색상 반환 // 파고에 따른 색상 반환
function getWaveHexColor(height: number): string { function getWaveHexColor(height: number): string {
if (height > 2.5) return '#ef4444'; if (height > 2.5) return 'var(--color-danger)';
if (height > 1.5) return '#f59e0b'; if (height > 1.5) return 'var(--color-caution)';
return '#3b82f6'; return 'var(--color-info)';
} }
// 수온에 따른 hex 색상 반환 // 수온에 따른 색상 반환
function getTempHexColor(temp: number): string { function getTempHexColor(temp: number): string {
if (temp > 8) return '#ef4444'; if (temp > 8) return 'var(--color-danger)';
if (temp > 6) return '#f59e0b'; if (temp > 6) return 'var(--color-caution)';
return '#3b82f6'; return 'var(--color-info)';
} }
/** /**
@ -91,15 +91,17 @@ export function WeatherMapOverlay({
width={24} width={24}
height={24} height={24}
viewBox="0 0 24 24" viewBox="0 0 24 24"
style={{ filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.3))' }} style={{ filter: 'drop-shadow(0 2px 6px rgba(0,0,0,0.5))' }}
> >
{/* 위쪽이 바람 방향을 나타내는 삼각형 */} {/* 흰 외곽선 레이어 */}
<polygon points="12,2 4,22 12,16 20,22" fill={color} opacity="0.9" /> <polygon points="12,2 4,22 12,16 20,22" fill="white" opacity="0.9" />
{/* 색상 레이어 */}
<polygon points="12,3 5,21 12,15.5 19,21" fill={color} opacity="0.95" />
</svg> </svg>
</div> </div>
<span <span
style={{ color, textShadow: '0 1px 3px rgba(0,0,0,0.8)' }} className="text-caption font-bold leading-none px-1 py-px rounded-sm bg-bg-base/80"
className="text-xs font-bold leading-none" style={{ color }}
> >
{station.wind.speed.toFixed(1)} {station.wind.speed.toFixed(1)}
</span> </span>
@ -138,7 +140,7 @@ export function WeatherMapOverlay({
textShadow: '1px 1px 3px rgba(0,0,0,0.7)', textShadow: '1px 1px 3px rgba(0,0,0,0.7)',
borderBottom: `1px solid ${isSelected ? 'rgba(0,0,0,0.2)' : 'rgba(255,255,255,0.3)'}`, borderBottom: `1px solid ${isSelected ? 'rgba(0,0,0,0.2)' : 'rgba(255,255,255,0.3)'}`,
}} }}
className="text-center text-xs font-bold pb-1 mb-0.5" className="text-center text-caption font-bold pb-1 mb-0.5"
> >
{station.name} {station.name}
</div> </div>
@ -150,7 +152,7 @@ export function WeatherMapOverlay({
🌡 🌡
</div> </div>
<div className="flex items-baseline gap-0.5"> <div className="flex items-baseline gap-0.5">
<span className="text-sm font-bold text-white" style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}> <span className="text-body-2 font-bold text-white" style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}>
{station.temperature.current.toFixed(1)} {station.temperature.current.toFixed(1)}
</span> </span>
<span className="text-caption text-white opacity-90" style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}>°C</span> <span className="text-caption text-white opacity-90" style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}>°C</span>
@ -164,7 +166,7 @@ export function WeatherMapOverlay({
🌊 🌊
</div> </div>
<div className="flex items-baseline gap-0.5"> <div className="flex items-baseline gap-0.5">
<span className="text-sm font-bold text-white" style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}> <span className="text-body-2 font-bold text-white" style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}>
{station.wave.height.toFixed(1)} {station.wave.height.toFixed(1)}
</span> </span>
<span className="text-caption text-white opacity-90" style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}>m</span> <span className="text-caption text-white opacity-90" style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}>m</span>
@ -178,7 +180,7 @@ export function WeatherMapOverlay({
💨 💨
</div> </div>
<div className="flex items-baseline gap-0.5"> <div className="flex items-baseline gap-0.5">
<span className="text-sm font-bold text-white" style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}> <span className="text-body-2 font-bold text-white" style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}>
{station.wind.speed.toFixed(1)} {station.wind.speed.toFixed(1)}
</span> </span>
<span className="text-caption text-white opacity-90" style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}>m/s</span> <span className="text-caption text-white opacity-90" style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}>m/s</span>
@ -205,7 +207,6 @@ export function useWeatherDeckLayers(
onStationClick: (station: WeatherStation) => void, onStationClick: (station: WeatherStation) => void,
): Layer[] { ): Layer[] {
return useMemo(() => { return useMemo(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result: Layer[] = []; const result: Layer[] = [];
// 파고 분포 ScatterplotLayer (Circle 대체, 반경 = 파고 * 15km) // 파고 분포 ScatterplotLayer (Circle 대체, 반경 = 파고 * 15km)

파일 보기

@ -1,3 +1,5 @@
import { useState } from 'react';
interface WeatherData { interface WeatherData {
stationName: string; stationName: string;
location: { lat: number; lon: number }; location: { lat: number; lon: number };
@ -46,20 +48,14 @@ interface WeatherRightPanelProps {
weatherData: WeatherData | null; weatherData: WeatherData | null;
} }
/** 풍속 등급 색상 */ /** 풍속 텍스트 색상 (2단계 — danger | accent) */
function windColor(speed: number): string { function windTextColor(speed: number): string {
if (speed >= 14) return '#ef4444'; return speed >= 10 ? 'var(--color-danger)' : 'var(--color-accent)';
if (speed >= 10) return '#f97316';
if (speed >= 6) return '#eab308';
return '#22c55e';
} }
/** 파고 등급 색상 */ /** 파고 텍스트 색상 (2단계 — danger | accent) */
function waveColor(height: number): string { function waveTextColor(height: number): string {
if (height >= 3) return '#ef4444'; return height >= 2 ? 'var(--color-danger)' : 'var(--color-accent)';
if (height >= 2) return '#f97316';
if (height >= 1) return '#eab308';
return '#22c55e';
} }
/** 풍향 텍스트 */ /** 풍향 텍스트 */
@ -86,13 +82,38 @@ function windDirText(deg: number): string {
} }
export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) { export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
const [collapsed, setCollapsed] = useState(false);
if (collapsed) {
return (
<div className="flex flex-col bg-bg-surface border-l border-stroke w-8 shrink-0">
<button
onClick={() => setCollapsed(false)}
className="flex-1 flex flex-col items-center justify-start pt-3 gap-1 text-fg-sub hover:text-fg transition-colors"
title="패널 펼치기"
>
<span className="text-heading-2 leading-none"></span>
<span className="text-subtitle text-fg-disabled [writing-mode:vertical-rl]"></span>
</button>
</div>
);
}
if (!weatherData) { if (!weatherData) {
return ( return (
<div className="flex flex-col bg-bg-surface border-l border-stroke overflow-hidden w-[320px] shrink-0"> <div className="flex flex-col bg-bg-surface border-l border-stroke overflow-hidden w-[320px] shrink-0">
<div className="p-6 text-center"> <div className="flex items-center justify-between px-4 py-3 border-b border-stroke bg-bg-elevated">
<p className="text-fg-disabled text-title-4 font-korean"> <p className="text-fg-disabled text-title-4 font-korean">
</p> </p>
<button
onClick={() => setCollapsed(true)}
className="shrink-0 flex items-center gap-0.5 text-fg-sub hover:text-fg transition-colors ml-2"
title="패널 접기"
>
<span className="text-subtitle text-fg-disabled"></span>
<span className="text-heading-2 leading-none"></span>
</button>
</div> </div>
</div> </div>
); );
@ -109,18 +130,30 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
<div className="flex flex-col bg-bg-surface border-l border-stroke overflow-hidden w-[320px] shrink-0"> <div className="flex flex-col bg-bg-surface border-l border-stroke overflow-hidden w-[320px] shrink-0">
{/* 헤더 */} {/* 헤더 */}
<div className="px-4 py-3 border-b border-stroke bg-bg-elevated"> <div className="px-4 py-3 border-b border-stroke bg-bg-elevated">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-start justify-between">
<span className="text-title-4 font-bold text-color-accent font-korean"> <div className="flex-1 min-w-0">
📍 {weatherData.stationName} <div className="flex items-center gap-2 mb-1">
</span> <span className="text-title-4 font-bold text-color-accent font-korean truncate">
<span className="px-1.5 py-px text-label-2 rounded bg-[rgba(6,182,212,0.15)] text-color-accent font-bold"> 📍 {weatherData.stationName}
</span>
</span> <span className="px-1.5 py-px text-label-2 rounded bg-[color-mix(in_srgb,var(--color-accent)_15%,transparent)] text-color-accent font-bold shrink-0">
</span>
</div>
<p className="text-label-2 text-fg-disabled font-mono">
{weatherData.location.lat.toFixed(2)}°N, {weatherData.location.lon.toFixed(2)}°E ·{' '}
{weatherData.currentTime}
</p>
</div>
<button
onClick={() => setCollapsed(true)}
className="shrink-0 flex items-center gap-0.5 text-fg-sub hover:text-fg transition-colors ml-2 mt-0.5"
title="패널 접기"
>
<span className="text-subtitle text-fg-disabled"></span>
<span className="text-heading-2 leading-none"></span>
</button>
</div> </div>
<p className="text-label-2 text-fg-disabled font-mono">
{weatherData.location.lat.toFixed(2)}°N, {weatherData.location.lon.toFixed(2)}°E ·{' '}
{weatherData.currentTime}
</p>
</div> </div>
{/* 스크롤 콘텐츠 */} {/* 스크롤 콘텐츠 */}
@ -131,13 +164,13 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
{/* ── 핵심 지표 3칸 카드 ── */} {/* ── 핵심 지표 3칸 카드 ── */}
<div className="grid grid-cols-3 gap-1 px-3 py-2.5"> <div className="grid grid-cols-3 gap-1 px-3 py-2.5">
<div className="text-center py-2.5 bg-bg-base border border-stroke rounded-md"> <div className="text-center py-2.5 bg-bg-base border border-stroke rounded-md">
<div className="text-[20px] font-bold font-mono" style={{ color: windColor(wSpd) }}> <div className="text-[20px] font-bold font-mono" style={{ color: windTextColor(wSpd) }}>
{wSpd.toFixed(1)} {wSpd.toFixed(1)}
</div> </div>
<div className="text-label-2 text-fg-disabled font-korean"> (m/s)</div> <div className="text-label-2 text-fg-disabled font-korean"> (m/s)</div>
</div> </div>
<div className="text-center py-2.5 bg-bg-base border border-stroke rounded-md"> <div className="text-center py-2.5 bg-bg-base border border-stroke rounded-md">
<div className="text-[20px] font-bold font-mono" style={{ color: waveColor(wHgt) }}> <div className="text-[20px] font-bold font-mono" style={{ color: waveTextColor(wHgt) }}>
{wHgt.toFixed(1)} {wHgt.toFixed(1)}
</div> </div>
<div className="text-label-2 text-fg-disabled font-korean"> (m)</div> <div className="text-label-2 text-fg-disabled font-korean"> (m)</div>
@ -152,9 +185,7 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
{/* ── 바람 상세 ── */} {/* ── 바람 상세 ── */}
<div className="px-3 py-2 border-b border-stroke"> <div className="px-3 py-2 border-b border-stroke">
<div className="text-label-2 font-bold text-fg-disabled font-korean mb-2"> <div className="text-label-2 text-fg-disabled font-korean mb-2">🌬 </div>
🌬
</div>
<div className="flex items-center gap-3 mb-2"> <div className="flex items-center gap-3 mb-2">
{/* 풍향 컴파스 */} {/* 풍향 컴파스 */}
<div className="relative w-[50px] h-[50px] shrink-0"> <div className="relative w-[50px] h-[50px] shrink-0">
@ -202,11 +233,11 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
y1="25" y1="25"
x2={25 + 14 * Math.sin((wind.direction * Math.PI) / 180)} x2={25 + 14 * Math.sin((wind.direction * Math.PI) / 180)}
y2={25 - 14 * Math.cos((wind.direction * Math.PI) / 180)} y2={25 - 14 * Math.cos((wind.direction * Math.PI) / 180)}
stroke={windColor(wSpd)} stroke="var(--color-accent)"
strokeWidth="2" strokeWidth="2"
strokeLinecap="round" strokeLinecap="round"
/> />
<circle cx="25" cy="25" r="3" fill={windColor(wSpd)} /> <circle cx="25" cy="25" r="3" fill="var(--color-accent)" />
</svg> </svg>
</div> </div>
<div className="flex-1 grid grid-cols-2 gap-x-3 gap-y-1.5 text-label-2"> <div className="flex-1 grid grid-cols-2 gap-x-3 gap-y-1.5 text-label-2">
@ -222,19 +253,13 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-fg-disabled">1k </span> <span className="text-fg-disabled">1k </span>
<span <span className="font-mono text-title-4 text-fg">
className="font-mono text-title-4"
style={{ color: windColor(wind.speed_1k) }}
>
{Number(wind.speed_1k).toFixed(1)} {Number(wind.speed_1k).toFixed(1)}
</span> </span>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<span className="text-fg-disabled">3k </span> <span className="text-fg-disabled">3k </span>
<span <span className="font-mono text-title-4 text-fg">
className="font-mono text-title-4"
style={{ color: windColor(wind.speed_3k) }}
>
{Number(wind.speed_3k).toFixed(1)} {Number(wind.speed_3k).toFixed(1)}
</span> </span>
</div> </div>
@ -248,11 +273,8 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex-1 h-[5px] bg-bg-card rounded-full overflow-hidden"> <div className="flex-1 h-[5px] bg-bg-card rounded-full overflow-hidden">
<div <div
className="h-full rounded-full transition-all" className="h-full rounded-full transition-all bg-color-accent"
style={{ style={{ width: `${Math.min((wSpd / 20) * 100, 100)}%` }}
width: `${Math.min((wSpd / 20) * 100, 100)}%`,
background: windColor(wSpd),
}}
/> />
</div> </div>
<span className="text-label-2 font-mono text-fg-disabled shrink-0"> <span className="text-label-2 font-mono text-fg-disabled shrink-0">
@ -263,24 +285,20 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
{/* ── 파도 상세 ── */} {/* ── 파도 상세 ── */}
<div className="px-3 py-2 border-b border-stroke"> <div className="px-3 py-2 border-b border-stroke">
<div className="text-label-2 font-bold text-fg-disabled font-korean mb-2">🌊 </div> <div className="text-label-2 text-fg-disabled font-korean mb-2">🌊 </div>
<div className="grid grid-cols-4 gap-1"> <div className="grid grid-cols-4 gap-1">
<div className="text-center py-2 bg-bg-base border border-stroke rounded"> <div className="text-center py-2 bg-bg-base border border-stroke rounded">
<div className="text-title-3 font-bold font-mono" style={{ color: waveColor(wHgt) }}> <div className="text-title-3 font-bold font-mono text-fg">{wHgt.toFixed(1)}m</div>
{wHgt.toFixed(1)}m
</div>
<div className="text-caption text-fg-disabled"></div> <div className="text-caption text-fg-disabled"></div>
</div> </div>
<div className="text-center py-2 bg-bg-base border border-stroke rounded"> <div className="text-center py-2 bg-bg-base border border-stroke rounded">
<div className="text-title-3 font-bold font-mono text-color-danger"> <div className="text-title-3 font-bold font-mono text-fg">
{wave.maxHeight.toFixed(1)}m {wave.maxHeight.toFixed(1)}m
</div> </div>
<div className="text-caption text-fg-disabled"></div> <div className="text-caption text-fg-disabled"></div>
</div> </div>
<div className="text-center py-2 bg-bg-base border border-stroke rounded"> <div className="text-center py-2 bg-bg-base border border-stroke rounded">
<div className="text-title-3 font-bold font-mono text-color-accent"> <div className="text-title-3 font-bold font-mono text-fg">{wave.period}s</div>
{wave.period}s
</div>
<div className="text-caption text-fg-disabled"></div> <div className="text-caption text-fg-disabled"></div>
</div> </div>
<div className="text-center py-2 bg-bg-base border border-stroke rounded"> <div className="text-center py-2 bg-bg-base border border-stroke rounded">
@ -295,7 +313,7 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
className="h-full rounded-full transition-all" className="h-full rounded-full transition-all"
style={{ style={{
width: `${Math.min((wHgt / 5) * 100, 100)}%`, width: `${Math.min((wHgt / 5) * 100, 100)}%`,
background: waveColor(wHgt), background: 'var(--color-accent)',
}} }}
/> />
</div> </div>
@ -307,14 +325,10 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
{/* ── 수온/공기 ── */} {/* ── 수온/공기 ── */}
<div className="px-3 py-2 border-b border-stroke"> <div className="px-3 py-2 border-b border-stroke">
<div className="text-label-2 font-bold text-fg-disabled font-korean mb-2"> <div className="text-label-2 text-fg-disabled font-korean mb-2">🌡 · </div>
🌡 ·
</div>
<div className="grid grid-cols-3 gap-1"> <div className="grid grid-cols-3 gap-1">
<div className="text-center py-2 bg-bg-base border border-stroke rounded"> <div className="text-center py-2 bg-bg-base border border-stroke rounded">
<div className="text-title-3 font-bold font-mono text-color-accent"> <div className="text-title-3 font-bold font-mono text-fg">{wTemp.toFixed(1)}°</div>
{wTemp.toFixed(1)}°
</div>
<div className="text-caption text-fg-disabled"></div> <div className="text-caption text-fg-disabled"></div>
</div> </div>
<div className="text-center py-2 bg-bg-base border border-stroke rounded"> <div className="text-center py-2 bg-bg-base border border-stroke rounded">
@ -332,9 +346,7 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
{/* ── 시간별 예보 ── */} {/* ── 시간별 예보 ── */}
<div className="px-3 py-2 border-b border-stroke"> <div className="px-3 py-2 border-b border-stroke">
<div className="text-label-2 font-bold text-fg-disabled font-korean mb-2"> <div className="text-label-2 text-fg-disabled font-korean mb-2"> </div>
</div>
<div className="grid grid-cols-5 gap-1"> <div className="grid grid-cols-5 gap-1">
{forecast.map((f, i) => ( {forecast.map((f, i) => (
<div <div
@ -353,9 +365,7 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
{/* ── 천문/조석 ── */} {/* ── 천문/조석 ── */}
{astronomy && ( {astronomy && (
<div className="px-3 py-2 border-b border-stroke"> <div className="px-3 py-2 border-b border-stroke">
<div className="text-label-2 font-bold text-fg-disabled font-korean mb-2"> <div className="text-label-2 text-fg-disabled font-korean mb-2"> · </div>
·
</div>
<div className="grid grid-cols-4 gap-1"> <div className="grid grid-cols-4 gap-1">
{[ {[
{ icon: '🌅', label: '일출', value: astronomy.sunrise }, { icon: '🌅', label: '일출', value: astronomy.sunrise },
@ -371,7 +381,7 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
))} ))}
</div> </div>
<div className="flex items-center gap-2 mt-1.5 px-2 py-1 bg-bg-base border border-stroke rounded text-label-2"> <div className="flex items-center gap-2 mt-1.5 px-2 py-1 bg-bg-base border border-stroke rounded text-label-2">
<span className="text-sm">🌓</span> <span className="text-body-2">🌓</span>
<span className="text-fg-disabled">{astronomy.moonPhase}</span> <span className="text-fg-disabled">{astronomy.moonPhase}</span>
<span className="ml-auto text-fg font-mono"> {astronomy.tidalRange}m</span> <span className="ml-auto text-fg font-mono"> {astronomy.tidalRange}m</span>
</div> </div>
@ -381,17 +391,21 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
{/* ── 날씨 특보 ── */} {/* ── 날씨 특보 ── */}
{alert && ( {alert && (
<div className="px-3 py-2"> <div className="px-3 py-2">
<div className="text-label-2 font-bold text-fg-disabled font-korean mb-2"> <div className="text-label-2 text-fg-disabled font-korean mb-2">🚨 </div>
🚨
</div>
<div <div
className="px-2.5 py-2 rounded border" className="px-2.5 py-2 rounded border"
style={{ background: 'rgba(239,68,68,.06)', borderColor: 'rgba(239,68,68,.2)' }} style={{
background: 'color-mix(in srgb, var(--color-danger) 6%, transparent)',
borderColor: 'color-mix(in srgb, var(--color-danger) 20%, transparent)',
}}
> >
<div className="flex items-center gap-2 text-label-2"> <div className="flex items-center gap-2 text-label-2">
<span <span
className="px-1.5 py-px rounded text-label-2 font-bold" className="px-1.5 py-px rounded text-label-2 font-bold"
style={{ background: 'rgba(239,68,68,.15)', color: 'var(--color-danger)' }} style={{
background: 'color-mix(in srgb, var(--color-danger) 15%, transparent)',
color: 'var(--color-danger)',
}}
> >
</span> </span>

파일 보기

@ -90,7 +90,6 @@ const WEATHER_MAP_CENTER: [number, number] = [127.8, 36.5]; // [lng, lat]
const WEATHER_MAP_ZOOM = 7; const WEATHER_MAP_ZOOM = 7;
// deck.gl 오버레이 컴포넌트 (MapLibre 컨트롤로 등록) // deck.gl 오버레이 컴포넌트 (MapLibre 컨트롤로 등록)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function DeckGLOverlay({ layers }: { layers: Layer[] }) { function DeckGLOverlay({ layers }: { layers: Layer[] }) {
const overlay = useControl<MapboxOverlay>(() => new MapboxOverlay({ interleaved: true })); const overlay = useControl<MapboxOverlay>(() => new MapboxOverlay({ interleaved: true }));
overlay.setProps({ layers }); overlay.setProps({ layers });
@ -178,7 +177,7 @@ function WeatherMapInner({
{/* 핀 꼬리 */} {/* 핀 꼬리 */}
<div className="w-px h-3 bg-color-accent" /> <div className="w-px h-3 bg-color-accent" />
{/* 좌표 라벨 */} {/* 좌표 라벨 */}
<div className="px-2 py-1 bg-bg-base/90 border border-color-accent rounded text-caption text-color-accent whitespace-nowrap backdrop-blur-sm"> <div className="px-2 py-1 bg-bg-base border border-color-accent rounded text-caption text-color-accent whitespace-nowrap shadow-md">
{clickedLocation.lat.toFixed(3)}°N&nbsp;{clickedLocation.lon.toFixed(3)}°E {clickedLocation.lat.toFixed(3)}°N&nbsp;{clickedLocation.lon.toFixed(3)}°E
</div> </div>
</div> </div>
@ -295,13 +294,13 @@ export function WeatherView() {
{/* Main Map Area */} {/* Main Map Area */}
<div className="flex-1 relative flex flex-col overflow-hidden"> <div className="flex-1 relative flex flex-col overflow-hidden">
{/* Tab Navigation */} {/* Tab Navigation */}
<div className="flex items-center border-b border-stroke bg-bg-surface shrink-0"> <div className="flex items-center border-b border-stroke bg-bg-surface shrink-0 pt-2 pb-2">
<div className="flex items-center gap-2 px-6"> <div className="flex items-center gap-2 px-6">
{(['0', '3', '6', '9'] as TimeOffset[]).map((offset) => ( {(['0', '3', '6', '9'] as TimeOffset[]).map((offset) => (
<button <button
key={offset} key={offset}
onClick={() => setTimeOffset(offset)} onClick={() => setTimeOffset(offset)}
className={`px-3 py-2 text-xs font-semibold rounded transition-all ${ className={`px-3 py-2 text-caption font-semibold rounded transition-all ${
timeOffset === offset timeOffset === offset
? 'bg-color-accent text-bg-0' ? 'bg-color-accent text-bg-0'
: 'bg-bg-card border border-stroke text-fg-sub hover:bg-bg-surface-hover' : 'bg-bg-card border border-stroke text-fg-sub hover:bg-bg-surface-hover'
@ -312,7 +311,7 @@ export function WeatherView() {
))} ))}
<div className="flex items-center gap-2 ml-4"> <div className="flex items-center gap-2 ml-4">
<span className="text-xs text-fg-disabled"> <span className="text-caption text-fg-disabled">
{lastUpdate {lastUpdate
? `마지막 업데이트: ${lastUpdate.toLocaleTimeString('ko-KR', { ? `마지막 업데이트: ${lastUpdate.toLocaleTimeString('ko-KR', {
hour: '2-digit', hour: '2-digit',
@ -323,7 +322,7 @@ export function WeatherView() {
{loading && ( {loading && (
<div className="w-4 h-4 border-2 border-color-accent border-t-transparent rounded-full animate-spin" /> <div className="w-4 h-4 border-2 border-color-accent border-t-transparent rounded-full animate-spin" />
)} )}
{error && <span className="text-xs text-color-danger"> {error}</span>} {error && <span className="text-caption text-color-danger"> {error}</span>}
</div> </div>
</div> </div>
@ -356,10 +355,7 @@ export function WeatherView() {
</Map> </Map>
{/* 레이어 컨트롤 */} {/* 레이어 컨트롤 */}
<div <div className="absolute top-4 left-4 bg-bg-surface border border-stroke rounded-md shadow-md z-10 px-2.5 py-1.5">
className="absolute top-4 left-4 bg-bg-surface/85 border border-stroke rounded-md backdrop-blur-sm z-10"
style={{ padding: '6px 10px' }}
>
<div className="text-caption font-semibold text-fg mb-1.5 font-korean"> </div> <div className="text-caption font-semibold text-fg mb-1.5 font-korean"> </div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<label className="flex items-center gap-1.5 cursor-pointer"> <label className="flex items-center gap-1.5 cursor-pointer">
@ -420,17 +416,12 @@ export function WeatherView() {
</div> </div>
{/* 범례 */} {/* 범례 */}
<div <div className="absolute bottom-4 left-4 bg-bg-surface border border-stroke rounded-md shadow-md z-10 px-2.5 py-1.5 max-w-[180px]">
className="absolute bottom-4 left-4 bg-bg-surface/85 border border-stroke rounded-md backdrop-blur-sm z-10" <div className="text-caption text-fg mb-1.5 font-korean"> </div>
style={{ padding: '6px 10px', maxWidth: 180 }} <div className="flex flex-col gap-1.5 text-[8px]">
>
<div className="text-caption font-semibold text-fg mb-1.5 font-korean"> </div>
<div className="flex flex-col gap-1.5" style={{ fontSize: 8 }}>
{/* 바람 */} {/* 바람 */}
<div> <div>
<div className="font-semibold text-fg-sub mb-0.5" style={{ fontSize: 8 }}> <div className="text-fg-sub mb-0.5"> (m/s)</div>
(m/s)
</div>
<div className="flex items-center gap-px h-[6px] rounded-sm overflow-hidden mb-0.5"> <div className="flex items-center gap-px h-[6px] rounded-sm overflow-hidden mb-0.5">
<div className="flex-1 h-full" style={{ background: '#6271b7' }} /> <div className="flex-1 h-full" style={{ background: '#6271b7' }} />
<div className="flex-1 h-full" style={{ background: '#39a0f6' }} /> <div className="flex-1 h-full" style={{ background: '#39a0f6' }} />
@ -441,7 +432,7 @@ export function WeatherView() {
<div className="flex-1 h-full" style={{ background: '#f05421' }} /> <div className="flex-1 h-full" style={{ background: '#f05421' }} />
<div className="flex-1 h-full" style={{ background: '#b41e46' }} /> <div className="flex-1 h-full" style={{ background: '#b41e46' }} />
</div> </div>
<div className="flex justify-between text-fg-disabled" style={{ fontSize: 7 }}> <div className="flex justify-between text-fg-disabled text-[7px]">
<span>3</span> <span>3</span>
<span>5</span> <span>5</span>
<span>7</span> <span>7</span>
@ -453,16 +444,14 @@ export function WeatherView() {
</div> </div>
{/* 해류 */} {/* 해류 */}
<div className="pt-1 border-t border-stroke"> <div className="pt-1 border-t border-stroke">
<div className="font-semibold text-fg-sub mb-0.5" style={{ fontSize: 8 }}> <div className="text-fg-sub mb-0.5"> (m/s)</div>
(m/s)
</div>
<div className="flex items-center gap-px h-[6px] rounded-sm overflow-hidden mb-0.5"> <div className="flex items-center gap-px h-[6px] rounded-sm overflow-hidden mb-0.5">
<div className="flex-1 h-full" style={{ background: 'rgb(59, 130, 246)' }} /> <div className="flex-1 h-full" style={{ background: 'rgb(59, 130, 246)' }} />
<div className="flex-1 h-full" style={{ background: 'rgb(6, 182, 212)' }} /> <div className="flex-1 h-full" style={{ background: 'rgb(6, 182, 212)' }} />
<div className="flex-1 h-full" style={{ background: 'rgb(34, 197, 94)' }} /> <div className="flex-1 h-full" style={{ background: 'rgb(34, 197, 94)' }} />
<div className="flex-1 h-full" style={{ background: 'rgb(249, 115, 22)' }} /> <div className="flex-1 h-full" style={{ background: 'rgb(249, 115, 22)' }} />
</div> </div>
<div className="flex justify-between text-fg-disabled" style={{ fontSize: 7 }}> <div className="flex justify-between text-fg-disabled text-[7px]">
<span>0.2</span> <span>0.2</span>
<span>0.4</span> <span>0.4</span>
<span>0.6</span> <span>0.6</span>
@ -471,23 +460,18 @@ export function WeatherView() {
</div> </div>
{/* 파고 */} {/* 파고 */}
<div className="pt-1 border-t border-stroke"> <div className="pt-1 border-t border-stroke">
<div className="font-semibold text-fg-sub mb-0.5" style={{ fontSize: 8 }}> <div className="text-fg-sub mb-0.5"> (m)</div>
(m)
</div>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<div className="w-2 h-2 rounded-full bg-blue-500" /> <div className="w-2 h-2 rounded-full bg-color-info" />
<span className="text-fg-disabled">&lt;1.5 </span> <span className="text-fg-disabled">&lt;1.5 </span>
<div className="w-2 h-2 rounded-full bg-orange-500 ml-1" /> <div className="w-2 h-2 rounded-full bg-color-warning ml-1" />
<span className="text-fg-disabled">~2.5</span> <span className="text-fg-disabled">~2.5</span>
<div className="w-2 h-2 rounded-full bg-red-500 ml-1" /> <div className="w-2 h-2 rounded-full bg-color-danger ml-1" />
<span className="text-fg-disabled">&gt;2.5</span> <span className="text-fg-disabled">&gt;2.5</span>
</div> </div>
</div> </div>
</div> </div>
<div <div className="mt-1 pt-1 border-t border-stroke text-fg-disabled text-[7px] font-korean">
className="mt-1 pt-1 border-t border-stroke text-fg-disabled font-korean"
style={{ fontSize: 7 }}
>
💡 💡
</div> </div>
</div> </div>

파일 보기

@ -77,29 +77,29 @@ export default {
{ lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-navigation)' }, { lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-navigation)' },
], ],
'title-3': [ 'title-3': [
'0.875rem', '1rem',
{ lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-navigation)' }, { lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-navigation)' },
], ],
'title-4': [ 'title-4': [
'0.8125rem', '0.875rem',
{ lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-navigation)' }, { lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-navigation)' },
], ],
'title-5': [ 'title-5': [
'0.75rem', '0.8125rem',
{ lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-navigation)' }, { lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-navigation)' },
], ],
'title-6': [ 'title-6': [
'0.6875rem', '0.75rem',
{ lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-navigation)' }, { lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-navigation)' },
], ],
'body-1': ['0.875rem', { lineHeight: '1.6', letterSpacing: 'var(--letter-spacing-body)' }], 'body-1': ['1rem', { lineHeight: '1.6', letterSpacing: 'var(--letter-spacing-body)' }],
'body-2': ['0.8125rem', { lineHeight: '1.6', letterSpacing: 'var(--letter-spacing-body)' }], 'body-2': ['0.875rem', { lineHeight: '1.6', letterSpacing: 'var(--letter-spacing-body)' }],
'label-1': ['0.75rem', { lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-label)' }], 'label-1': [
'label-2': [ '0.8125rem',
'0.6875rem',
{ lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-label)' }, { lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-label)' },
], ],
caption: ['0.6875rem', { lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-body)' }], 'label-2': ['0.75rem', { lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-label)' }],
caption: ['0.75rem', { lineHeight: '1.5', letterSpacing: 'var(--letter-spacing-body)' }],
}, },
letterSpacing: { letterSpacing: {
display: 'var(--letter-spacing-display)', display: 'var(--letter-spacing-display)',