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`를 읽고 작업 상태(완료/미완료 컴포넌트)를 확인할 것.

파일 보기

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

파일 보기

@ -120,7 +120,7 @@ export function LoginPage() {
</label>
<div className="relative">
<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%)' }}
>
<svg
@ -174,7 +174,7 @@ export function LoginPage() {
</label>
<div className="relative">
<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%)' }}
>
<svg
@ -249,7 +249,7 @@ export function LoginPage() {
color: '#67e8f9',
}}
>
<span className="text-sm shrink-0 mt-px">
<span className="text-body-2 shrink-0 mt-px">
<svg
width="14"
height="14"
@ -303,7 +303,7 @@ export function LoginPage() {
<button
type="submit"
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={{
padding: '12px',
background: isLoading

파일 보기

@ -21,7 +21,7 @@ export function SubMenuBar({ activeMainTab }: SubMenuBarProps) {
key={item.id}
onClick={() => setActiveSubTab(item.id)}
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
${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"
title="홈으로 이동"
>
<img
src="/wing_logo_white.svg"
alt="WING 해양환경 위기대응"
className="h-3.5 wing-logo"
/>
<img src="/wing_logo_white.svg" alt="WING 해양환경 위기대응" className="h-5 wing-logo" />
</button>
{/* Divider */}
@ -87,7 +83,7 @@ export function TopBar({ activeTab, onTabChange }: TopBarProps) {
onClick={handleClick}
title={tab.label}
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
${isIncident ? 'ml-1' : ''}
${isMonitor ? 'ml-1 flex items-center gap-1.5' : ''}
@ -127,7 +123,7 @@ export function TopBar({ activeTab, onTabChange }: TopBarProps) {
{/* Right Section */}
<div className="flex items-center gap-3">
{/* 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> */}

파일 보기

@ -141,7 +141,7 @@ export function BacktrackReplayBar({
className="w-2 h-2 rounded-full bg-color-tertiary"
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 className="flex items-center gap-1.5">
@ -180,7 +180,7 @@ export function BacktrackReplayBar({
{/* Play/Pause */}
<button
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={{
background: isPlaying ? 'var(--color-tertiary)' : 'rgba(168,85,247,0.15)',
border: `2px solid ${isPlaying ? 'var(--color-tertiary)' : 'rgba(168,85,247,0.4)'}`,

파일 보기

@ -414,7 +414,10 @@ export function MapView({
longitude: lng,
latitude: lat,
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)]">
{String(category ?? '민감자원')}
</div>
@ -535,7 +538,7 @@ export function MapView({
longitude: d.lon,
latitude: d.lat,
content: (
<div className="text-xs">
<div className="text-caption">
<strong>
{modelKey} #{(d.particle ?? 0) + 1}
</strong>
@ -604,7 +607,7 @@ export function MapView({
longitude: info.coordinate?.[0] ?? 0,
latitude: info.coordinate?.[1] ?? 0,
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>
<br />
: {PRIORITY_LABELS[d.priority] || d.priority}
@ -919,7 +922,7 @@ export function MapView({
longitude: info.coordinate[0],
latitude: info.coordinate[1],
content: (
<div className="text-xs leading-relaxed" style={{ minWidth: 180 }}>
<div className="text-caption leading-relaxed" style={{ minWidth: 180 }}>
<strong className="text-color-warning">
{dispersionResult.substance}
</strong>
@ -1009,7 +1012,7 @@ export function MapView({
longitude: d.lon,
latitude: d.lat,
content: (
<div className="text-xs" style={{ minWidth: '130px' }}>
<div className="text-caption" style={{ minWidth: '130px' }}>
<div className="flex items-center gap-1 mb-1">
<span>{SENSITIVE_ICONS[d.type]}</span>
<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">
<button
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
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
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;
</button>
@ -1575,7 +1578,7 @@ function MapLegend({
className="flex items-center gap-1.5 mt-2 rounded"
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>
</div>
</div>
@ -1888,7 +1891,9 @@ function BacktrackReplayBar({
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="h-full rounded-[2px]"

파일 보기

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

파일 보기

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

파일 보기

@ -181,7 +181,7 @@
}
.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;
color: var(--fg-disabled);
background: transparent;

파일 보기

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

파일 보기

@ -347,7 +347,7 @@ const TransparencyRow = ({
return (
<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}
</span>
<div className="rounded-xl p-4" style={{ backgroundColor: sectionCardBg }}>
@ -453,7 +453,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
{/* ── 섹션 1: 헤더 ── */}
<div className="pb-10 mb-12 border-b border-solid" style={{ borderColor: dividerColor }}>
<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 }}
>
Foundations
@ -476,7 +476,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<button
key={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={{
color: isActive ? t.textAccent : t.textMuted,
borderBottom: isActive ? `2px solid ${t.textAccent}` : '2px solid transparent',
@ -505,7 +505,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
>
--{'{property}'}-{'{role}'}[-{'{variant}'}]
</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
. Property는 CSS , Role은 , Variant는
@ -551,17 +551,17 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
}}
>
<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 }}
>
{row.prop}
</span>
<div className="flex-1">
<div className="text-sm" style={{ color: t.textSecondary }}>
<div className="text-body-2" style={{ color: t.textSecondary }}>
{row.desc}
</div>
</div>
<span className="font-mono text-xs" style={{ color: t.textMuted }}>
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
{row.example}
</span>
</div>
@ -581,7 +581,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
style={{ border: `1px solid ${dividerColor}` }}
>
<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={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted,
@ -608,12 +608,12 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
}}
>
<span
className="font-mono text-sm font-semibold"
className="font-mono text-body-2 font-semibold"
style={{ color: t.textPrimary }}
>
{row.name}
</span>
<span className="text-xs" style={{ color: t.textMuted }}>
<span className="text-caption" style={{ color: t.textMuted }}>
{row.desc}
</span>
</div>
@ -626,7 +626,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
style={{ border: `1px solid ${dividerColor}` }}
>
<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={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted,
@ -650,12 +650,12 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
}}
>
<span
className="font-mono text-sm font-semibold"
className="font-mono text-body-2 font-semibold"
style={{ color: t.textPrimary }}
>
{row.name}
</span>
<span className="text-xs" style={{ color: t.textMuted }}>
<span className="text-caption" style={{ color: t.textMuted }}>
{row.desc}
</span>
</div>
@ -672,7 +672,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}>
Semantic Tokens
</h2>
<p className="text-sm mb-6" style={{ color: t.textSecondary }}>
<p className="text-body-2 mb-6" style={{ color: t.textSecondary }}>
. .
</p>
@ -745,7 +745,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
},
].map((group) => (
<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}
</h3>
<div
@ -754,7 +754,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
>
{/* 헤더 */}
<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={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted,
@ -776,21 +776,21 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
}}
>
<span
className="font-mono text-xs line-through"
className="font-mono text-caption line-through"
style={{ color: t.textMuted }}
>
{tk.legacy}
</span>
<span
className="font-mono text-xs font-semibold"
className="font-mono text-caption font-semibold"
style={{ color: t.textPrimary }}
>
{tk.name}
</span>
<span className="text-xs" style={{ color: t.textMuted }}>
<span className="text-caption" style={{ color: t.textMuted }}>
{tk.desc}
</span>
<span className="font-mono text-xs" style={{ color: t.textSecondary }}>
<span className="font-mono text-caption" style={{ color: t.textSecondary }}>
{tk.value}
</span>
<div className="flex items-center gap-2">
@ -804,7 +804,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
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)}
</span>
</div>
@ -823,7 +823,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}>
Palette Tokens
</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 {' '}
<code className="font-mono" style={{ color: t.textAccent }}>
--color-*
@ -836,7 +836,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
style={{ border: `1px solid ${dividerColor}` }}
>
<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={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted,
@ -882,16 +882,19 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
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}
</span>
<span
className="font-mono text-xs font-semibold"
className="font-mono text-caption font-semibold"
style={{ color: t.textPrimary }}
>
{tk.name}
</span>
<span className="font-mono text-xs" style={{ color: t.textSecondary }}>
<span className="font-mono text-caption" style={{ color: t.textSecondary }}>
{tk.value}
</span>
<div className="flex items-center gap-2">
@ -905,11 +908,11 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
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)}
</span>
</div>
<span className="text-xs" style={{ color: t.textMuted }}>
<span className="text-caption" style={{ color: t.textMuted }}>
{tk.desc}
</span>
</div>
@ -925,7 +928,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}>
Non-color Tokens
</h2>
<p className="text-sm mb-6" style={{ color: t.textSecondary }}>
<p className="text-body-2 mb-6" style={{ color: t.textSecondary }}>
, .
</p>
@ -934,7 +937,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
style={{ border: `1px solid ${dividerColor}` }}
>
<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={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted,
@ -979,17 +982,20 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
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}
</span>
<span
className="font-mono text-xs font-semibold"
className="font-mono text-caption font-semibold"
style={{ color: t.textPrimary }}
>
{tk.name}
</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={{
color: t.textAccent,
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}
</span>
<span className="text-xs" style={{ color: t.textMuted }}>
<span className="text-caption" style={{ color: t.textMuted }}>
{tk.desc}
</span>
</div>
@ -1015,7 +1021,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<h2 className="text-xl font-bold mb-3" style={{ color: t.textPrimary }}>
(primary color)
</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
.
</p>
@ -1023,7 +1029,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<div className="flex flex-col gap-6">
{/* Light Mode */}
<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
</p>
<ColorScaleBar
@ -1041,7 +1047,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
{/* Dark Mode */}
<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
</p>
<ColorScaleBar
@ -1067,7 +1073,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<h2 className="text-xl font-bold mb-3" style={{ color: t.textPrimary }}>
(secondary color)
</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
.
</p>
@ -1075,7 +1081,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<div className="flex flex-col gap-6">
{/* Light Mode */}
<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
</p>
<ColorScaleBar
@ -1093,7 +1099,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
{/* Dark Mode */}
<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
</p>
<ColorScaleBar
@ -1119,11 +1125,11 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<h2 className="text-xl font-bold mb-3" style={{ color: t.textPrimary }}>
(gray color) / ,
</h2>
<p className="mb-2 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-2 text-body-2" style={{ color: t.textSecondary }}>
Gray , , ,
.
</p>
<p className="mb-8 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-8 text-body-2" style={{ color: t.textSecondary }}>
.
</p>
@ -1141,7 +1147,7 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<h2 className="text-2xl font-bold mb-2" style={{ color: t.textPrimary }}>
Transparent
</h2>
<p className="mb-8 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-8 text-body-2" style={{ color: t.textSecondary }}>
. 65%
.
</p>
@ -1174,12 +1180,12 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
<div className="mb-16">
<div className="mb-6">
<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 }}
>
Primitive
</p>
<p className="text-sm" style={{ color: t.textMuted }}>
<p className="text-body-2" style={{ color: t.textMuted }}>
UI . .
</p>
</div>
@ -1214,17 +1220,17 @@ export const ColorPaletteContent = ({ theme }: ColorPaletteContentProps) => {
/>
{/* 토큰명 */}
<span
className="font-mono text-sm font-semibold ml-4"
className="font-mono text-body-2 font-semibold ml-4"
style={{ color: t.textPrimary }}
>
{token.name}
</span>
{/* HEX + RGB */}
<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}
</div>
<div className="font-mono text-xs" style={{ color: t.textMuted }}>
<div className="font-mono text-caption" style={{ color: t.textMuted }}>
{hexToRgb(token.hex)}
</div>
</div>

파일 보기

@ -13,7 +13,7 @@ export const ComponentsContent = () => {
>
</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 .
.
</p>

파일 보기

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

파일 보기

@ -154,7 +154,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
style={{ backgroundColor: t.textAccent, boxShadow: t.systemActiveShadow }}
/>
<span
className="font-mono text-xs leading-4 uppercase"
className="font-mono text-caption leading-4 uppercase"
style={{ letterSpacing: '1.2px', color: t.textAccent }}
>
System Active
@ -192,11 +192,14 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
/>
{/* 정보 */}
<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}
</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 }}
>
{item.hex}
@ -233,7 +236,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
{item.token}
</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 }}
>
{item.hex}
@ -263,7 +266,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
{item.token}
</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}
</span>
</div>
@ -297,7 +300,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
/>
<div className="flex flex-col gap-1">
<span
className="font-korean text-sm font-bold"
className="font-korean text-body-2 font-bold"
style={{ color: t.textPrimary }}
>
{item.name}
@ -435,7 +438,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
<div className="grid grid-cols-2 gap-8">
{/* radius-sm */}
<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}
</span>
<div
@ -453,7 +456,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
Small Elements
</span>
<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 }}
>
Applied to tactical buttons, search inputs, and micro-cards for a precise, sharp
@ -463,7 +466,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
</div>
{/* radius-md */}
<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}
</span>
<div
@ -481,7 +484,7 @@ export const DesignContent = ({ theme }: DesignContentProps) => {
Structural Panels
</span>
<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 }}
>
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 }}>
Float
</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
</p>
</div>
@ -70,7 +73,7 @@ export const FloatContent = ({ theme }: FloatContentProps) => {
}}
>
<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 }}
>
{label}

파일 보기

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

파일 보기

@ -324,7 +324,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
<h1 className="font-sans text-3xl leading-9 font-bold" style={{ color: t.textPrimary }}>
Layout
</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는 .
(100vh), flex . KRDS
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 }}>
Breakpoint
</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
(xl, 2xl) cyan으로 .
</p>
@ -499,7 +499,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
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
</span>
</div>
@ -511,7 +511,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
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 )
</span>
</div>
@ -525,7 +525,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
Grid
</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
.
</p>
@ -548,7 +548,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
style={{ borderColor: isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0' }}
>
<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
</span>
<span
@ -562,10 +562,10 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
</span>
</div>
<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
</span>
<span className="font-mono text-sm" style={{ color: t.textPrimary }}>
<span className="font-mono text-body-2" style={{ color: t.textPrimary }}>
{spec.width}
</span>
</div>
@ -683,7 +683,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
<span className="font-mono text-base" style={{ color: '#f97316' }}>
</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
(xs / s / md / lg) .
</span>
@ -696,7 +696,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
App Shell
</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 .
</p>
</div>
@ -720,7 +720,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
}}
>
<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
</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 }}>
Spacing
</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
spacing , px .
</p>
@ -1000,7 +1000,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
<h2 className="font-sans text-2xl leading-8 font-bold" style={{ color: t.textPrimary }}>
4pt Grid
</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 . ,
2 .
</p>
@ -1048,7 +1048,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
>
{item.label}
</span>
<span className="font-korean text-xs" style={{ color: t.textPrimary }}>
<span className="font-korean text-caption" style={{ color: t.textPrimary }}>
{item.text}
</span>
</div>
@ -1145,7 +1145,10 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
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 className="font-korean text-caption" style={{ color: t.textSecondary }}>
@ -1185,7 +1188,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
</span>
</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가 ,
.
</p>
@ -1216,7 +1219,7 @@ export const LayoutContent = ({ theme }: LayoutContentProps) => {
className="w-2 h-2 rounded-full shrink-0"
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}
</span>
<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 }}>
Reference
</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 .
</p>
</div>

파일 보기

@ -70,17 +70,17 @@ export const RadiusContent = ({ theme }: RadiusContentProps) => {
<h1 className="font-sans text-3xl leading-9 font-bold" style={{ color: t.textPrimary }}>
Radius
</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는 .
</p>
</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 , ,
.
</p>
<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 }}
>
<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>
<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 .
</p>
</div>
@ -233,7 +233,7 @@ export const RadiusContent = ({ theme }: RadiusContentProps) => {
{/* 정보 */}
<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}
</span>
<div className="flex flex-row flex-wrap gap-2">

파일 보기

@ -207,7 +207,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* ── 섹션 1: 헤더 ── */}
<div className="pb-10 mb-12 border-b border-solid" style={{ borderColor: dividerColor }}>
<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 }}
>
Components
@ -225,7 +225,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
<h2 className="text-2xl font-bold mb-2" style={{ color: t.textPrimary }}>
Input Field
</h2>
<p className="text-sm" style={{ color: t.textSecondary }}>
<p className="text-body-2" style={{ color: t.textSecondary }}>
.
</p>
</div>
@ -246,7 +246,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* Prefix label */}
<div className="flex flex-col items-center gap-0.5" style={{ width: '70px' }}>
<span
className="font-mono text-xs font-semibold"
className="font-mono text-caption font-semibold"
style={{ color: annotationColor }}
>
Prefix
@ -272,7 +272,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* Input label */}
<div className="flex flex-col items-center gap-0.5">
<span
className="font-mono text-xs font-semibold"
className="font-mono text-caption font-semibold"
style={{ color: annotationColor }}
>
Input
@ -295,7 +295,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* Suffix label */}
<div className="flex flex-col items-center gap-0.5" style={{ width: '70px' }}>
<span
className="font-mono text-xs font-semibold"
className="font-mono text-caption font-semibold"
style={{ color: annotationColor }}
>
Suffix
@ -377,7 +377,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* Suffix 텍스트 */}
<span
className="shrink-0 ml-2 font-mono text-sm"
className="shrink-0 ml-2 font-mono text-body-2"
style={{ color: fieldPlaceholder }}
>
@ -402,7 +402,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
}}
>
<span
className="font-mono text-xs font-semibold"
className="font-mono text-caption font-semibold"
style={{ color: annotationColor }}
>
Container
@ -437,7 +437,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
style={{ width: '1px', height: '10px', backgroundColor: annotationColor }}
/>
<span
className="font-mono text-xs font-semibold"
className="font-mono text-caption font-semibold"
style={{ color: annotationColor }}
>
Clear Button
@ -468,7 +468,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Container
</h3>
</div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. , , .
</p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
@ -519,13 +519,13 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
</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)
</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 ()
</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
</p>
</div>
@ -543,14 +543,14 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Placeholder
</h3>
</div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. .
</p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
<div className="flex gap-6">
{/* 플레이스홀더 있는 필드 */}
<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>
<div
@ -570,7 +570,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* 빈 필드 (플레이스홀더 없음) */}
<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>
<div
@ -597,14 +597,17 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Label
</h3>
</div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. * .
</p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
<div className="flex gap-8">
{/* 일반 라벨 */}
<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>
<div
@ -624,7 +627,10 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* 필수 라벨 */}
<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>
<div
@ -655,7 +661,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Input Text
</h3>
</div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. .
</p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
@ -673,7 +679,10 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
>
</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>color: textPrimary</span>
<span>font-weight: 400</span>
@ -692,14 +701,14 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Clear Icon
</h3>
</div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. .
</p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
<div className="flex gap-6">
{/* 텍스트 입력 + Clear 아이콘 표시 */}
<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 )
</span>
<div
@ -736,7 +745,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* 빈 상태 (Clear 미표시) */}
<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 )
</span>
<div
@ -767,7 +776,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Helper Text
</h3>
</div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. .
</p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
@ -787,7 +796,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
>
</div>
<span className="text-xs" style={{ color: t.textMuted }}>
<span className="text-caption" style={{ color: t.textMuted }}>
, 8
</span>
</div>
@ -807,7 +816,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
>
</div>
<span className="text-xs" style={{ color: '#ef4444' }}>
<span className="text-caption" style={{ color: '#ef4444' }}>
.
</span>
</div>
@ -828,7 +837,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
<div key={row.state} className="flex items-center gap-8">
{/* 왼쪽: State 라벨 + 뱃지 */}
<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
</span>
<span
@ -891,7 +900,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
<h2 className="text-2xl font-bold mb-2" style={{ color: t.textPrimary }}>
Text Area
</h2>
<p className="text-sm" style={{ color: t.textSecondary }}>
<p className="text-body-2" style={{ color: t.textSecondary }}>
.
</p>
</div>
@ -911,7 +920,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* Input Area label */}
<div className="flex flex-col items-center gap-0.5" style={{ width: '80px' }}>
<span
className="font-mono text-xs font-semibold"
className="font-mono text-caption font-semibold"
style={{ color: annotationColor }}
>
Input Area
@ -933,7 +942,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* Placeholder label */}
<div className="flex flex-col items-center gap-0.5">
<span
className="font-mono text-xs font-semibold"
className="font-mono text-caption font-semibold"
style={{ color: annotationColor }}
>
Placeholder
@ -955,7 +964,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* Character Counter label */}
<div className="flex flex-col items-center gap-0.5" style={{ width: '110px' }}>
<span
className="font-mono text-xs font-semibold"
className="font-mono text-caption font-semibold"
style={{ color: annotationColor }}
>
Character Counter
@ -1042,7 +1051,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
}}
>
<span
className="font-mono text-xs font-semibold"
className="font-mono text-caption font-semibold"
style={{ color: annotationColor }}
>
Container
@ -1075,7 +1084,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
style={{ width: '1px', height: '10px', backgroundColor: annotationColor }}
/>
<span
className="font-mono text-xs font-semibold"
className="font-mono text-caption font-semibold"
style={{ color: annotationColor }}
>
Resize Handle
@ -1103,7 +1112,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Container
</h3>
</div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. 112px이며
.
</p>
@ -1154,16 +1163,16 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
</div>
<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)
</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
</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
</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
</p>
</div>
@ -1181,14 +1190,14 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Placeholder
</h3>
</div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
.
</p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
<div className="flex gap-6">
{/* 플레이스홀더 있는 TextArea */}
<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>
<div
@ -1209,7 +1218,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* 빈 TextArea */}
<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>
<div
@ -1236,14 +1245,17 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Label
</h3>
</div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
.
</p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
<div className="flex gap-8">
{/* 기본 라벨 */}
<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>
<div
@ -1264,7 +1276,10 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* 필수(*) 라벨 */}
<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>
<div
@ -1296,7 +1311,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Input Text
</h3>
</div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
.
</p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
@ -1316,7 +1331,10 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
>
{'오늘 점검 내용을 기록합니다.\n상세 내용은 아래와 같습니다.'}
</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>color: textPrimary</span>
<span>line-height: 1.6</span>
@ -1335,14 +1353,14 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Clear Icon
</h3>
</div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
. .
</p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
<div className="flex gap-6">
{/* 텍스트 있는 상태 (Clear 표시) */}
<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 )
</span>
<div
@ -1380,7 +1398,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
{/* 빈 상태 (Clear 미표시) */}
<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 )
</span>
<div
@ -1412,7 +1430,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
Helper Text
</h3>
</div>
<p className="mb-5 text-sm" style={{ color: t.textSecondary }}>
<p className="mb-5 text-body-2" style={{ color: t.textSecondary }}>
.
</p>
<div className="rounded-xl p-8" style={{ backgroundColor: sectionCardBg }}>
@ -1434,10 +1452,10 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
</div>
<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 className="text-xs font-mono" style={{ color: t.textMuted }}>
<span className="text-caption font-mono" style={{ color: t.textMuted }}>
0/500
</span>
</div>
@ -1459,7 +1477,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
>
</div>
<span className="text-xs" style={{ color: '#ef4444' }}>
<span className="text-caption" style={{ color: '#ef4444' }}>
.
</span>
</div>
@ -1480,7 +1498,7 @@ export const TextFieldContent = ({ theme }: TextFieldContentProps) => {
<div key={`ta-${row.state}`} className="flex items-start gap-8">
{/* 왼쪽: State 라벨 + 뱃지 */}
<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
</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 }}>
Typography
</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 . , ,
.
</p>
</div>
<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>
<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 }}
>
<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>
<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에 .
</p>
@ -352,7 +352,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
}}
>
<pre
className="font-mono text-sm leading-6"
className="font-mono text-body-2 leading-6"
style={{ color: isDark ? '#c0c8dc' : '#475569' }}
>
<span style={{ color: t.textAccent }}>font-family</span>
@ -390,7 +390,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
</div>
<div className="px-5 py-5 flex flex-col gap-4">
<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={{
color: isDark ? '#9ba3b8' : '#64748b',
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}
</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-1">
<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>
<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
.
</p>
@ -434,7 +434,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
<h3 className="font-sans text-lg font-bold" style={{ color: t.textPrimary }}>
{category.name}
</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}
</p>
</div>
@ -445,7 +445,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
style={{ border: `1px solid ${isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0'}` }}
>
<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={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted,
@ -467,20 +467,23 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
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}
</span>
<span className="font-mono text-xs" style={{ color: t.textPrimary }}>
<span className="font-mono text-caption" style={{ color: t.textPrimary }}>
{row.px}
</span>
<span className="font-mono text-xs" style={{ color: t.textPrimary }}>
<span className="font-mono text-caption" style={{ color: t.textPrimary }}>
{row.weight}
</span>
<span
className="font-mono text-xs"
className="font-mono text-caption"
style={{ color: t.textMuted }}
>{`${(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}
</span>
<span
@ -510,7 +513,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
>
<div className="shrink-0 w-[100px]">
<div
className="font-mono text-xs font-semibold"
className="font-mono text-caption font-semibold"
style={{ color: t.textAccent }}
>
{row.token}
@ -533,7 +536,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
{row.sample}
</span>
</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}
</div>
</div>
@ -551,7 +554,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
<h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}>
</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)
.
</p>
@ -561,7 +564,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
style={{ border: `1px solid ${isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0'}` }}
>
<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={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted,
@ -606,17 +609,20 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
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}
</span>
<span className="font-mono text-xs" style={{ color: t.textPrimary }}>
<span className="font-mono text-caption" style={{ color: t.textPrimary }}>
{row.value}
</span>
<span className="text-xs" style={{ color: t.textSecondary }}>
<span className="text-caption" style={{ color: t.textSecondary }}>
{row.name}
</span>
<span
className="font-korean text-sm"
className="font-korean text-body-2"
style={{ color: t.textPrimary, fontWeight: Number(row.value) }}
>
{row.preview}
@ -632,7 +638,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
<h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}>
</h2>
<p className="text-sm" style={{ color: t.textSecondary }}>
<p className="text-body-2" style={{ color: t.textSecondary }}>
(1.3), (1.6).
.
</p>
@ -642,7 +648,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
style={{ border: `1px solid ${isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0'}` }}
>
<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={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted,
@ -677,16 +683,19 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
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}
</span>
<span className="font-mono text-xs" style={{ color: t.textPrimary }}>
<span className="font-mono text-caption" style={{ color: t.textPrimary }}>
{row.value}
</span>
<span className="font-mono text-xs" style={{ color: t.textMuted }}>
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
{row.pct}
</span>
<span className="text-xs" style={{ color: t.textSecondary }}>
<span className="text-caption" style={{ color: t.textSecondary }}>
{row.desc}
</span>
</div>
@ -700,7 +709,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
<h2 className="text-xl font-bold mb-2" style={{ color: t.textPrimary }}>
</h2>
<p className="text-sm" style={{ color: t.textSecondary }}>
<p className="text-body-2" style={{ color: t.textSecondary }}>
. Display는 , Body는 .
</p>
</div>
@ -709,7 +718,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
style={{ border: `1px solid ${isDark ? 'rgba(66,71,84,0.20)' : '#e2e8f0'}` }}
>
<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={{
backgroundColor: isDark ? 'rgba(255,255,255,0.04)' : '#f1f5f9',
color: t.textMuted,
@ -760,10 +769,13 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
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}
</span>
<span className="font-mono text-xs" style={{ color: t.textPrimary }}>
<span className="font-mono text-caption" style={{ color: t.textPrimary }}>
{row.value}
</span>
<span
@ -776,7 +788,7 @@ export const TypographyContent = ({ theme }: TypographyContentProps) => {
>
{row.tw}
</span>
<span className="text-xs" style={{ color: t.textSecondary }}>
<span className="text-caption" style={{ color: t.textSecondary }}>
{row.category}
</span>
</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="bg-[#06b6d4] rounded-xl shrink-0 w-1 h-4 relative" />
<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' }}
>
인터페이스: 버튼
@ -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"
>
<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' }}
>
{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="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}
</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">
24.8
</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)
</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="flex flex-col gap-0 items-start justify-start shrink-0 relative">
<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' }}
>
컨트롤: 아이콘
@ -113,7 +113,7 @@ export const IconBadgeSection = () => {
<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="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' }}
>
컨트롤: 아이콘

파일 보기

@ -36,10 +36,10 @@ export const FloatDropdownContent = ({ theme }: FloatDropdownContentProps) => {
<h2 className="font-sans text-xl font-bold" style={{ color: t.textPrimary }}>
Dropdown
</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
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={{
backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)',
color: t.textAccent,
@ -50,7 +50,7 @@ export const FloatDropdownContent = ({ theme }: FloatDropdownContentProps) => {
. 5 .
{' '}
<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={{
backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)',
color: t.textAccent,
@ -84,7 +84,7 @@ export const FloatDropdownContent = ({ theme }: FloatDropdownContentProps) => {
>
<div className="grid grid-cols-2 gap-6">
<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>
<ComboBox
@ -98,7 +98,7 @@ export const FloatDropdownContent = ({ theme }: FloatDropdownContentProps) => {
</span>
</div>
<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>
<ComboBox
@ -112,7 +112,7 @@ export const FloatDropdownContent = ({ theme }: FloatDropdownContentProps) => {
</span>
</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 }}>
@common/components/ui/ComboBox
@ -373,7 +373,7 @@ export const FloatDropdownContent = ({ theme }: FloatDropdownContentProps) => {
</span>
</div>
<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}
</span>
</div>
@ -423,10 +423,16 @@ export const FloatDropdownContent = ({ theme }: FloatDropdownContentProps) => {
: 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}
</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}
</span>
</div>

파일 보기

@ -88,9 +88,9 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
<h2 className="font-sans text-xl font-bold" style={{ color: t.textPrimary }}>
Modal
</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
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={{
backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)',
color: t.textAccent,
@ -156,7 +156,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
</button>
))}
</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}
</span>
</div>
@ -166,7 +166,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
<button
type="button"
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={{
backgroundColor: isDark ? 'rgba(76,215,246,0.15)' : 'rgba(6,182,212,0.12)',
borderColor: t.textAccent,
@ -178,7 +178,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
<button
type="button"
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={{
backgroundColor: isDark ? 'rgba(239,68,68,0.10)' : 'rgba(239,68,68,0.06)',
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"
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>. +
+ / 2 . (, ) .
</p>
@ -423,7 +423,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
style={{ backgroundColor: t.cardBg, borderColor: t.cardBorder }}
>
<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 }}
>
{row.range}
@ -434,7 +434,10 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
>
{row.status}
</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}
</span>
</div>
@ -480,7 +483,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
}}
>
<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}
</span>
</div>
@ -496,7 +499,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
</span>
</div>
<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}
</span>
</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"
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})
</span>
<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"
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>
</button>
</div>
<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
className="font-mono text-xs px-1 rounded"
className="font-mono text-caption px-1 rounded"
style={{
backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)',
color: t.textAccent,
@ -568,7 +574,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
className="rounded border border-solid px-3 py-2.5"
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}
</span>
</div>
@ -582,7 +588,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
<button
type="button"
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 }}
>
@ -590,7 +596,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
<button
type="button"
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={{
backgroundColor: isDark ? 'rgba(76,215,246,0.18)' : 'rgba(6,182,212,0.14)',
color: t.textAccent,
@ -624,13 +630,16 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
>
<div className="flex items-center gap-2">
<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>
</div>
</div>
<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>
</div>
@ -641,7 +650,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
<button
type="button"
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 }}
>
@ -649,7 +658,7 @@ export const FloatModalContent = ({ theme }: FloatModalContentProps) => {
<button
type="button"
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={{
backgroundColor: isDark ? 'rgba(239,68,68,0.18)' : 'rgba(239,68,68,0.12)',
color: '#ef4444',

파일 보기

@ -63,10 +63,10 @@ export const FloatOverlayContent = ({ theme }: FloatOverlayContentProps) => {
<h2 className="font-sans text-xl font-bold" style={{ color: t.textPrimary }}>
Overlay
</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
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={{
backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)',
color: t.textAccent,
@ -133,12 +133,12 @@ export const FloatOverlayContent = ({ theme }: FloatOverlayContentProps) => {
</span>
</div>
<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}
</span>
</div>
<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}
</span>
</div>
@ -284,7 +284,7 @@ export const FloatOverlayContent = ({ theme }: FloatOverlayContentProps) => {
className="rounded-lg border border-solid p-5 flex flex-col gap-4"
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
) , ·
.
@ -307,7 +307,7 @@ export const FloatOverlayContent = ({ theme }: FloatOverlayContentProps) => {
<span className="font-mono text-caption" style={{ color: t.textMuted }}>
{item.label}
</span>
<span className="font-mono text-xs" style={{ color: t.textAccent }}>
<span className="font-mono text-caption" style={{ color: t.textAccent }}>
{item.value}
</span>
<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)',
}}
>
<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으로 .
position: absolute로 .
</span>
@ -377,7 +377,7 @@ export const FloatOverlayContent = ({ theme }: FloatOverlayContentProps) => {
}}
>
<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}
</span>
</div>
@ -413,7 +413,10 @@ export const FloatOverlayContent = ({ theme }: FloatOverlayContentProps) => {
</span>
</div>
<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}
</span>
</div>

파일 보기

@ -84,10 +84,10 @@ export const FloatToastContent = ({ theme }: FloatToastContentProps) => {
</span>
</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
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={{
backgroundColor: isDark ? 'rgba(76,215,246,0.08)' : 'rgba(6,182,212,0.06)',
color: t.textAccent,
@ -97,7 +97,7 @@ export const FloatToastContent = ({ theme }: FloatToastContentProps) => {
</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={{
backgroundColor: isDark ? 'rgba(239,68,68,0.08)' : 'rgba(239,68,68,0.05)',
color: '#ef4444',
@ -129,7 +129,7 @@ export const FloatToastContent = ({ theme }: FloatToastContentProps) => {
className="rounded-lg border border-solid p-5 flex flex-col gap-4"
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 .
</p>
<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 }}>
{cfg.icon}
</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}
</span>
</div>
@ -311,7 +311,7 @@ export const FloatToastContent = ({ theme }: FloatToastContentProps) => {
className="rounded-lg border border-solid p-5 flex flex-col gap-4"
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>{' '}
. ToastContainer는 App.tsx .
</p>
@ -387,7 +387,7 @@ export const FloatToastContent = ({ theme }: FloatToastContentProps) => {
<span className="font-mono text-base shrink-0" style={{ color: cfg.color }}>
{cfg.icon}
</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}
</span>
<button

파일 보기

@ -6,7 +6,7 @@ interface AdminPlaceholderProps {
const AdminPlaceholder = ({ label }: AdminPlaceholderProps) => (
<div className="flex flex-col items-center justify-center h-full gap-3">
<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>
);

파일 보기

@ -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="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>
</div>
</div>
@ -129,7 +129,7 @@ const AdminSidebar = ({ activeMenu, onSelect }: AdminSidebarProps) => {
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="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">
<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>
</div>
@ -115,7 +115,7 @@ function AssetUploadPanel() {
<div className="flex-1 max-w-[560px] space-y-4">
<div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden">
<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 className="px-5 py-4 space-y-4">
{/* 드롭존 */}
@ -135,12 +135,12 @@ function AssetUploadPanel() {
>
<div className="text-3xl mb-2 opacity-40">📁</div>
{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}
</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 className="text-caption text-fg-disabled font-korean mb-3">
@ -148,7 +148,7 @@ function AssetUploadPanel() {
</div>
<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"
onClick={(e) => {
e.stopPropagation();
@ -176,7 +176,7 @@ function AssetUploadPanel() {
<select
value={assetCategory}
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"
>
{ASSET_CATEGORIES.map((c) => (
@ -195,7 +195,7 @@ function AssetUploadPanel() {
<select
value={jurisdiction}
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"
>
{JURISDICTIONS.map((j) => (
@ -212,7 +212,7 @@ function AssetUploadPanel() {
</label>
<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
type="radio"
checked={uploadMode === 'add'}
@ -221,7 +221,7 @@ function AssetUploadPanel() {
/>
( + )
</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
type="radio"
checked={uploadMode === 'replace'}
@ -238,7 +238,7 @@ function AssetUploadPanel() {
type="button"
onClick={handleUpload}
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
? '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)]'
@ -255,7 +255,7 @@ function AssetUploadPanel() {
{/* 수정 권한 체계 */}
<div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden">
<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 className="px-5 py-4 space-y-2">
{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"
>
<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 }}
>
{p.icon}
</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">
{p.desc}
</div>
@ -283,7 +285,7 @@ function AssetUploadPanel() {
{/* 최근 업로드 이력 */}
<div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden">
<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 className="px-5 py-4 space-y-2">
{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"
>
<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">
{formatDate(h.regDtm)} · {h.uploaderNm} · {h.uploadCnt.toLocaleString()}
</div>

파일 보기

@ -119,8 +119,8 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
<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">
<h2 className="text-sm font-semibold text-fg"> </h2>
<span className="text-xs text-fg-disabled"> {data?.totalCount ?? 0}</span>
<h2 className="text-body-2 font-semibold text-fg"> </h2>
<span className="text-caption text-fg-disabled"> {data?.totalCount ?? 0}</span>
</div>
{/* 카테고리 탭 + 검색 */}
@ -130,7 +130,7 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
<button
key={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
? 'bg-blue-500/20 text-blue-400 font-medium'
: 'text-fg-disabled hover:text-fg-sub hover:bg-bg-elevated'
@ -146,11 +146,11 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
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
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>
@ -162,7 +162,7 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
<button
onClick={handleDelete}
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})`}
</button>
@ -170,7 +170,7 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
{/* 테이블 */}
<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">
<tr className="border-b border-stroke-1 text-fg-disabled">
<th className="w-8 py-2 text-center">
@ -222,7 +222,7 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
<button
onClick={() => setPage((p) => Math.max(1, p - 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;
</button>
@ -234,7 +234,7 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
<button
key={p}
onClick={() => setPage(p)}
className={`w-7 h-7 text-xs rounded ${
className={`w-7 h-7 text-caption rounded ${
p === page
? 'bg-blue-500/20 text-blue-400 font-medium'
: 'text-fg-disabled hover:bg-bg-elevated'
@ -247,7 +247,7 @@ export default function BoardMgmtPanel({ initialCategory = '' }: BoardMgmtPanelP
<button
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
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;
</button>

파일 보기

@ -99,13 +99,15 @@ function CleanupEquipPanel() {
<div className="flex items-center justify-between px-6 py-4 border-b border-stroke">
<div>
<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 className="flex items-center gap-3">
<select
value={regionFilter}
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>
@ -117,7 +119,7 @@ function CleanupEquipPanel() {
<select
value={typeFilter}
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>
{typeOptions.map((t) => (
@ -129,7 +131,7 @@ function CleanupEquipPanel() {
<select
value={equipFilter}
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>
@ -146,11 +148,11 @@ function CleanupEquipPanel() {
setSearchTerm(e.target.value);
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
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>
@ -160,7 +162,7 @@ function CleanupEquipPanel() {
{/* 테이블 */}
<div className="flex-1 overflow-auto">
{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>
) : (
@ -217,7 +219,7 @@ function CleanupEquipPanel() {
<tr>
<td
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>

파일 보기

@ -211,7 +211,7 @@ const HEADERS = [
function HrTable({ rows, loading }: { rows: HrCollectItem[]; loading: boolean }) {
return (
<div className="overflow-auto">
<table className="w-full text-xs border-collapse">
<table className="w-full text-caption border-collapse">
<thead>
<tr className="bg-bg-elevated text-t3 uppercase tracking-wide">
{HEADERS.map((h) => (
@ -317,10 +317,10 @@ export default function CollectHrPanel() {
<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">
<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">
{lastUpdate && (
<span className="text-xs text-t3">
<span className="text-caption text-t3">
:{' '}
{lastUpdate.toLocaleTimeString('ko-KR', {
hour: '2-digit',
@ -332,7 +332,7 @@ export default function CollectHrPanel() {
<button
onClick={fetchData}
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
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">
<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" />
{completedCount}
</span>
<span className="text-xs text-t3">
<span className="text-caption text-t3">
{rows.length} (: {activeCount} / : {rows.length - activeCount})
</span>
</div>

파일 보기

@ -130,7 +130,9 @@ const DispersingZonePanel = () => {
onClick={() => handleToggleExpand(zone)}
>
<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
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="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>
</div>

파일 보기

@ -186,7 +186,7 @@ const LayerFormModal = ({ mode, initialData, onClose, onSaved }: LayerFormModalP
};
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';
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="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' ? '레이어 등록' : '레이어 수정'}
</h2>
<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)
: 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>
{options
@ -311,7 +311,7 @@ const LayerFormModal = ({ mode, initialData, onClose, onSaved }: LayerFormModalP
<select
value={form.useYn}
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="N"></option>
@ -329,14 +329,14 @@ const LayerFormModal = ({ mode, initialData, onClose, onSaved }: LayerFormModalP
<button
type="button"
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
type="submit"
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' ? '등록' : '저장'}
</button>
@ -449,11 +449,11 @@ const LayerPanel = () => {
<div className="flex items-center justify-between mb-3">
<div>
<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>
<button
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>
@ -465,12 +465,12 @@ const LayerPanel = () => {
onChange={(e) => setSearchInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
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
value={filterUseYn}
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="Y"></option>
@ -478,7 +478,7 @@ const LayerPanel = () => {
</select>
<button
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>
@ -487,7 +487,7 @@ const LayerPanel = () => {
{/* 오류 메시지 */}
{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}
</div>
)}
@ -495,7 +495,7 @@ const LayerPanel = () => {
{/* 테이블 영역 */}
<div className="flex-1 overflow-auto">
{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>
) : (
@ -539,7 +539,7 @@ const LayerPanel = () => {
<tr>
<td
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>
@ -551,15 +551,15 @@ const LayerPanel = () => {
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}
</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}>
{item.layerFullNm}
</span>
@ -575,7 +575,7 @@ const LayerPanel = () => {
{item.wmsLayerNm ?? <span className="text-fg-disabled">-</span>}
</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}
</td>
{/* 등록일시 */}
@ -614,13 +614,13 @@ const LayerPanel = () => {
<div className="flex items-center justify-center gap-1.5 flex-nowrap">
<button
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
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>

파일 보기

@ -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="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 ? '지도 수정' : '지도 등록'}
</h2>
<button onClick={onClose} className="text-fg-disabled hover:text-fg transition-colors">
@ -108,7 +108,7 @@ function MapBaseModal({
value={form.mapNm}
onChange={(e) => setField('mapNm', e.target.value)}
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>
@ -123,7 +123,7 @@ function MapBaseModal({
onChange={(e) => setField('mapKey', e.target.value)}
placeholder="고유 식별 키 (영문/숫자)"
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>
@ -135,7 +135,7 @@ function MapBaseModal({
<select
value={form.mapLevelCd}
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>
{MAP_LEVEL_OPTIONS.map((opt) => (
@ -156,7 +156,7 @@ function MapBaseModal({
value={form.mapSrc}
onChange={(e) => setField('mapSrc', e.target.value)}
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>
@ -170,7 +170,7 @@ function MapBaseModal({
value={form.mapDc}
onChange={(e) => setField('mapDc', e.target.value)}
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>
@ -193,7 +193,7 @@ function MapBaseModal({
}`}
/>
</button>
<span className="text-xs text-fg-sub font-korean">
<span className="text-caption text-fg-sub font-korean">
{form.useYn === 'Y' ? '사용' : '미사용'}
</span>
</div>
@ -208,14 +208,14 @@ function MapBaseModal({
<button
type="button"
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
type="submit"
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 ? '수정' : '등록'}
</button>
@ -350,11 +350,11 @@ function MapBasePanel() {
<div className="flex items-center justify-between px-6 py-4 border-b border-stroke">
<div>
<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>
<button
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>
@ -375,7 +375,7 @@ function MapBasePanel() {
{/* 테이블 영역 */}
<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">
<tr className="border-b border-stroke text-fg-disabled">
<th className="w-12 py-3 text-center"></th>
@ -433,7 +433,7 @@ function MapBasePanel() {
<td className="py-3 text-center">
<button
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>
@ -441,7 +441,7 @@ function MapBasePanel() {
<td className="py-3 text-center">
<button
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>
@ -452,7 +452,7 @@ function MapBasePanel() {
</tbody>
</table>
{!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>
)}
@ -464,7 +464,7 @@ function MapBasePanel() {
<button
onClick={() => setPage((p) => Math.max(1, p - 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;
</button>
@ -476,7 +476,7 @@ function MapBasePanel() {
<button
key={p}
onClick={() => setPage(p)}
className={`w-7 h-7 text-xs rounded ${
className={`w-7 h-7 text-caption rounded ${
p === page
? 'bg-blue-500/20 text-blue-400 font-medium'
: 'text-fg-disabled hover:bg-bg-elevated'
@ -489,7 +489,7 @@ function MapBasePanel() {
<button
onClick={() => setPage((p) => Math.min(totalPages, p + 1))}
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;
</button>

파일 보기

@ -124,7 +124,7 @@ function MenusPanel() {
if (loading) {
return (
<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>
);
}
@ -136,14 +136,14 @@ function MenusPanel() {
<div className="flex items-center justify-between px-6 py-4 border-b border-stroke">
<div>
<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>
</div>
<button
onClick={handleSave}
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
? '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'
@ -188,7 +188,7 @@ function MenusPanel() {
<DragOverlay>
{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]">
<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-4 font-semibold text-fg font-korean">
{activeMenu.label}

파일 보기

@ -45,24 +45,24 @@ function formatTime(iso: string | null): string {
function StatusCell({ row }: { row: NumericalDataStatus }) {
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') {
return (
<span className="text-red-400 text-xs">
<span className="text-red-400 text-caption">
{row.consecutiveFailures > 0 ? ` (${row.consecutiveFailures}회 연속)` : ''}
</span>
);
}
if (row.lastStatus === 'STARTED') {
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>
);
}
return <span className="text-t3 text-xs">-</span>;
return <span className="text-t3 text-caption">-</span>;
}
function StatusBadge({
@ -76,7 +76,7 @@ function StatusBadge({
}) {
if (loading) {
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>
@ -84,7 +84,7 @@ function StatusBadge({
}
if (errorCount === total && total > 0) {
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>
@ -92,14 +92,14 @@ function StatusBadge({
}
if (errorCount > 0) {
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" />
({errorCount}/{total})
</span>
);
}
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>
@ -118,7 +118,7 @@ const TABLE_HEADERS = [
function ForecastTable({ rows, loading }: { rows: NumericalDataStatus[]; loading: boolean }) {
return (
<div className="overflow-auto">
<table className="w-full text-xs border-collapse">
<table className="w-full text-caption border-collapse">
<thead>
<tr className="bg-bg-elevated text-t3 uppercase tracking-wide">
{TABLE_HEADERS.map((h) => (
@ -193,10 +193,10 @@ export default function MonitorForecastPanel() {
<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">
<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">
{lastUpdate && (
<span className="text-xs text-t3">
<span className="text-caption text-t3">
:{' '}
{lastUpdate.toLocaleTimeString('ko-KR', {
hour: '2-digit',
@ -208,7 +208,7 @@ export default function MonitorForecastPanel() {
<button
onClick={() => void fetchData()}
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
className={`w-3.5 h-3.5 ${loading ? 'animate-spin' : ''}`}
@ -234,7 +234,7 @@ export default function MonitorForecastPanel() {
<button
key={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
? 'border-cyan-400 text-cyan-400'
: '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">
<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>
{/* 테이블 */}

파일 보기

@ -84,7 +84,7 @@ function StatusBadge({
}) {
if (loading) {
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>
@ -92,7 +92,7 @@ function StatusBadge({
}
if (errorCount === total) {
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>
@ -100,14 +100,14 @@ function StatusBadge({
}
if (errorCount > 0) {
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" />
({errorCount}/{total})
</span>
);
}
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>
@ -130,7 +130,7 @@ function KhoaTable({ rows, loading }: { rows: KhoaRow[]; loading: boolean }) {
return (
<div className="overflow-auto">
<table className="w-full text-xs border-collapse">
<table className="w-full text-caption border-collapse">
<thead>
<tr className="bg-bg-elevated text-t3 uppercase tracking-wide">
{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">
{row.error ? (
<span className="text-red-400 text-xs"></span>
<span className="text-red-400 text-caption"></span>
) : 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>
</tr>
@ -201,7 +201,7 @@ function KmaUltraTable({ rows, loading }: { rows: KmaUltraRow[]; loading: boolea
return (
<div className="overflow-auto">
<table className="w-full text-xs border-collapse">
<table className="w-full text-caption border-collapse">
<thead>
<tr className="bg-bg-elevated text-t3 uppercase tracking-wide">
{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">
{row.error ? (
<span className="text-red-400 text-xs"></span>
<span className="text-red-400 text-caption"></span>
) : 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>
</tr>
@ -261,7 +261,7 @@ function MarineTable({ rows, loading }: { rows: MarineRow[]; loading: boolean })
return (
<div className="overflow-auto">
<table className="w-full text-xs border-collapse">
<table className="w-full text-caption border-collapse">
<thead>
<tr className="bg-bg-elevated text-t3 uppercase tracking-wide">
{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">
{row.error ? (
<span className="text-red-400 text-xs"></span>
<span className="text-red-400 text-caption"></span>
) : 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>
</tr>
@ -441,10 +441,10 @@ export default function MonitorRealtimePanel() {
<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">
<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">
{lastUpdate && (
<span className="text-xs text-t3">
<span className="text-caption text-t3">
:{' '}
{lastUpdate.toLocaleTimeString('ko-KR', {
hour: '2-digit',
@ -456,7 +456,7 @@ export default function MonitorRealtimePanel() {
<button
onClick={handleRefresh}
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
className={`w-3.5 h-3.5 ${isLoading ? 'animate-spin' : ''}`}
@ -482,7 +482,7 @@ export default function MonitorRealtimePanel() {
<button
key={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
? 'border-cyan-400 text-cyan-400'
: '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">
<StatusBadge loading={isLoading} errorCount={errorCount} total={totalCount} />
<span className="text-xs text-t3">
<span className="text-caption text-t3">
{activeTab === 'khoa' && `관측소 ${totalCount}`}
{activeTab === 'kma-ultra' && `지점 ${totalCount}`}
{activeTab === 'kma-marine' && `해역 ${totalCount}`}

파일 보기

@ -300,7 +300,7 @@ function StatusBadge({
}) {
if (loading) {
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>
@ -309,7 +309,7 @@ function StatusBadge({
const offCount = total - onCount;
if (offCount === total) {
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" />
OFF
</span>
@ -317,14 +317,14 @@ function StatusBadge({
}
if (offCount > 0) {
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" />
OFF ({offCount}/{total})
</span>
);
}
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>
@ -376,7 +376,7 @@ const HEADERS = [
function VesselTable({ rows, loading }: { rows: VesselMonitorRow[]; loading: boolean }) {
return (
<div className="overflow-auto">
<table className="w-full text-xs border-collapse">
<table className="w-full text-caption border-collapse">
<thead>
<tr className="bg-bg-elevated text-t3 uppercase tracking-wide">
{HEADERS.map((h) => (
@ -462,10 +462,10 @@ export default function MonitorVesselPanel() {
<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">
<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">
{lastUpdate && (
<span className="text-xs text-t3">
<span className="text-caption text-t3">
:{' '}
{lastUpdate.toLocaleTimeString('ko-KR', {
hour: '2-digit',
@ -477,7 +477,7 @@ export default function MonitorVesselPanel() {
<button
onClick={fetchData}
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
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">
<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})
</span>
</div>

파일 보기

@ -557,7 +557,7 @@ function RolePermTab({
</table>
</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>
)}
@ -567,7 +567,7 @@ function RolePermTab({
<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="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 className="px-5 py-4 flex flex-col gap-3">
<div>
@ -581,7 +581,7 @@ function RolePermTab({
setNewRoleCode(e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, ''))
}
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">
, , ( )
@ -596,7 +596,7 @@ function RolePermTab({
value={newRoleName}
onChange={(e) => setNewRoleName(e.target.value)}
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>
@ -608,7 +608,7 @@ function RolePermTab({
value={newRoleDesc}
onChange={(e) => setNewRoleDesc(e.target.value)}
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>
{createError && (
@ -620,14 +620,14 @@ function RolePermTab({
<div className="px-5 py-3 border-t border-stroke flex justify-end gap-2">
<button
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
onClick={handleCreateRole}
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 ? '생성 중...' : '생성'}
</button>
@ -815,7 +815,7 @@ function UserPermTab({ roles, permTree, rolePerms }: UserPermTabProps) {
onFocus={() => setShowDropdown(true)}
placeholder={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 && (
<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"
>
<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.rank && (
<span className="ml-1 text-caption text-fg-disabled font-korean">
@ -848,7 +848,7 @@ function UserPermTab({ roles, permTree, rolePerms }: UserPermTabProps) {
</div>
)}
{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>
)}
@ -954,13 +954,13 @@ function UserPermTab({ roles, permTree, rolePerms }: UserPermTabProps) {
</table>
</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 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>
)}
@ -1180,7 +1180,7 @@ function PermissionsPanel() {
if (loading) {
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>
);
@ -1194,7 +1194,7 @@ function PermissionsPanel() {
style={{ flexShrink: 0 }}
>
<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">
× CRUD
</p>
@ -1203,7 +1203,7 @@ function PermissionsPanel() {
<div className="flex items-center gap-1 p-1 bg-bg-elevated rounded-lg border border-stroke">
<button
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'
? 'bg-color-accent text-bg-0 shadow-[0_0_8px_rgba(6,182,212,0.25)]'
: 'text-fg-disabled hover:text-fg-sub'
@ -1213,7 +1213,7 @@ function PermissionsPanel() {
</button>
<button
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'
? 'bg-color-accent text-bg-0 shadow-[0_0_8px_rgba(6,182,212,0.25)]'
: '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>
<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 className="flex items-center gap-2">
@ -146,12 +146,12 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
onChange={(e) => setSearchInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
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
value={filterUseYn}
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="Y"></option>
@ -159,7 +159,7 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
</select>
<button
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>
@ -168,7 +168,7 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
{/* 오류 메시지 */}
{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}
</div>
)}
@ -176,7 +176,7 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
{/* 테이블 영역 */}
<div className="flex-1 overflow-auto">
{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>
) : (
@ -217,7 +217,7 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
<tr>
<td
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>
@ -228,12 +228,12 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
key={item.layerCd}
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}
</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-xs text-fg-sub font-korean max-w-[200px]">
<td className="px-4 py-3 text-caption text-fg font-korean">{item.layerNm}</td>
<td className="px-4 py-3 text-caption text-fg-sub font-korean max-w-[200px]">
<span className="block truncate" title={item.layerFullNm}>
{item.layerFullNm}
</span>
@ -246,7 +246,7 @@ const SensitiveLayerPanel = ({ categoryCode, title }: SensitiveLayerPanelProps)
<td className="px-4 py-3 text-label-2 text-fg-sub font-mono">
{item.wmsLayerNm ?? <span className="text-fg-disabled">-</span>}
</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}
</td>
<td className="px-4 py-3 text-label-2 text-fg-disabled font-mono">

파일 보기

@ -54,7 +54,7 @@ function SettingsPanel() {
if (loading) {
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>
);
@ -64,7 +64,7 @@ function SettingsPanel() {
<div className="flex flex-col h-full">
<div className="px-6 py-4 border-b border-stroke">
<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>
</div>
@ -74,7 +74,7 @@ function SettingsPanel() {
{/* 사용자 등록 설정 */}
<div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden">
<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>
@ -140,7 +140,7 @@ function SettingsPanel() {
{/* OAuth 설정 */}
<div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden">
<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">
Google
</p>
@ -162,7 +162,7 @@ function SettingsPanel() {
value={oauthDomainInput}
onChange={(e) => setOauthDomainInput(e.target.value)}
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
onClick={async () => {
@ -183,7 +183,7 @@ function SettingsPanel() {
savingOAuth ||
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 || '')
? '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'
@ -220,7 +220,7 @@ function SettingsPanel() {
{/* 현재 설정 상태 요약 */}
<div className="rounded-lg border border-stroke bg-bg-surface overflow-hidden">
<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 className="px-5 py-4">
<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" />
</svg>
</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}
</span>
{isEditing ? (
@ -152,14 +152,14 @@ function SortableMenuItem({
<button
onClick={() => onMove(idx, -1)}
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
onClick={() => onMove(idx, 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>

파일 보기

@ -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="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">
<svg
width="16"
@ -115,7 +115,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
value={account}
onChange={(e) => setAccount(e.target.value)}
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>
@ -129,7 +129,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
value={password}
onChange={(e) => setPassword(e.target.value)}
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>
@ -143,7 +143,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
value={name}
onChange={(e) => setName(e.target.value)}
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>
@ -157,7 +157,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
value={rank}
onChange={(e) => setRank(e.target.value)}
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>
@ -169,7 +169,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
<select
value={orgSn}
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>
{allOrgs.map((org) => (
@ -191,7 +191,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
value={email}
onChange={(e) => setEmail(e.target.value)}
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>
@ -217,7 +217,7 @@ function RegisterModal({ allRoles, allOrgs, onClose, onSuccess }: RegisterModalP
onChange={() => toggleRole(role.sn)}
style={{ accentColor: color }}
/>
<span className="text-xs font-korean" style={{ color }}>
<span className="text-caption font-korean" style={{ color }}>
{role.name}
</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
type="button"
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
type="submit"
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 ? '등록 중...' : '등록'}
</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>
<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>
</div>
<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"
value={name}
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 className="grid grid-cols-2 gap-3">
@ -377,7 +377,7 @@ function UserDetailModal({ user, allOrgs, onClose, onUpdated }: UserDetailModalP
value={rank}
onChange={(e) => setRank(e.target.value)}
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>
@ -387,7 +387,7 @@ function UserDetailModal({ user, allOrgs, onClose, onUpdated }: UserDetailModalP
<select
value={orgSn}
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>
{allOrgs.map((org) => (
@ -427,7 +427,7 @@ function UserDetailModal({ user, allOrgs, onClose, onUpdated }: UserDetailModalP
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
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>
<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">
<button
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>
@ -680,7 +680,7 @@ function UsersPanel() {
<div className="flex items-center gap-3">
<div>
<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}
</p>
</div>
@ -698,7 +698,7 @@ function UsersPanel() {
setOrgFilter(e.target.value);
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>
{allOrgs.map((org) => (
@ -711,7 +711,7 @@ function UsersPanel() {
<select
value={statusFilter}
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="PENDING"></option>
@ -726,11 +726,11 @@ function UsersPanel() {
placeholder="이름, 계정 검색..."
value={searchTerm}
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
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>
@ -740,7 +740,7 @@ function UsersPanel() {
{/* 테이블 */}
<div className="flex-1 overflow-auto">
{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>
) : (
@ -781,7 +781,7 @@ function UsersPanel() {
<tr>
<td
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>
@ -897,7 +897,7 @@ function UsersPanel() {
onChange={() => toggleRoleSelection(role.sn)}
style={{ accentColor: color }}
/>
<span className="text-xs font-korean" style={{ color }}>
<span className="text-caption font-korean" style={{ color }}>
{role.name}
</span>
<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>
<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} ( )
</p>
</div>
@ -98,7 +98,7 @@ function VesselMaterialsPanel() {
<select
value={regionFilter}
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>
@ -110,7 +110,7 @@ function VesselMaterialsPanel() {
<select
value={typeFilter}
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>
{typeOptions.map((t) => (
@ -127,11 +127,11 @@ function VesselMaterialsPanel() {
setSearchTerm(e.target.value);
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
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>
@ -141,7 +141,7 @@ function VesselMaterialsPanel() {
{/* 테이블 */}
<div className="flex-1 overflow-auto">
{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>
) : (
@ -188,7 +188,7 @@ function VesselMaterialsPanel() {
<tr>
<td
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>

파일 보기

@ -142,17 +142,17 @@ export default function VesselSignalPanel() {
<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">
<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">
<input
type="date"
value={date}
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
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>
@ -163,7 +163,7 @@ export default function VesselSignalPanel() {
<div className="flex-1 overflow-y-auto px-6 py-5">
{loading ? (
<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 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="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
className="w-[7px] h-[7px] rounded-full inline-block animate-pulse"
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 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}` : '📹 카메라를 선택하세요'}
</div>
{selectedCamera?.sttsCd === 'LIVE' && (
@ -966,7 +966,7 @@ export function CctvView() {
<div key={srcKey} className="mb-5">
{/* 출처 헤더 */}
<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">
{group.label}
</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="bg-bg-surface border border-stroke rounded-md p-6 w-72 text-center">
<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">
<span className="text-color-accent font-bold">{downloadResult.total}</span>
</div>
@ -441,7 +441,7 @@ export function MediaManagement() {
</div>
<button
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>
@ -475,7 +475,7 @@ export function MediaManagement() {
</div>
</div>
<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>
<select className="prd-i w-full">
@ -489,7 +489,7 @@ export function MediaManagement() {
</select>
</div>
<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>
<select className="prd-i w-full">
@ -499,7 +499,7 @@ export function MediaManagement() {
</select>
</div>
<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>
<textarea
@ -508,7 +508,7 @@ export function MediaManagement() {
/>
</div>
<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={{
background: 'rgba(6,182,212,0.15)',
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">
{/* ── Left Panel ── */}
<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>
@ -256,7 +256,7 @@ export function OilAreaAnalysis() {
<button
onClick={() => fileInputRef.current?.click()}
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
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="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
className="w-[7px] h-[7px] rounded-full inline-block"
style={{
@ -225,7 +225,7 @@ export function RealtimeDrone() {
>
<div className="flex items-center justify-between mb-1.5">
<div className="flex items-center gap-2">
<div className="text-sm">🚁</div>
<div className="text-body-2">🚁</div>
<div>
<div className="text-label-2 font-normal text-fg font-korean">
{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 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}` : '🚁 드론 스트림을 선택하세요'}
</div>
{selectedStream?.status === 'streaming' && (
@ -566,7 +566,7 @@ export function RealtimeDrone() {
style={{ minWidth: 170, background: 'var(--bg-card)', borderRadius: 6 }}
>
<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>
<div className="text-caption text-fg-disabled mb-0.5">
@ -813,7 +813,7 @@ export function RealtimeDrone() {
].map((item, i) => (
<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-sm font-mono text-fg">{item.value}</div>
<div className="text-body-2 font-mono text-fg">{item.value}</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-2 shrink-0">
<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={{
background: 'rgba(6,182,212,0.15)',
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>
<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">
{r.zoneCoord} · {r.zoneArea}
</div>
@ -776,7 +776,9 @@ export function SatelliteRequest() {
<div className="grid grid-cols-2 gap-3.5">
{/* 가용 위성 현황 */}
<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">
{satellites.map((sat, i) => (
<div
@ -807,7 +809,7 @@ export function SatelliteRequest() {
{/* 오늘 촬영 가능 시간 */}
<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)
</div>
<div className="flex flex-col gap-1.5">
@ -1133,7 +1135,7 @@ export function SatelliteRequest() {
)}
<button
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>
@ -1213,7 +1215,7 @@ export function SatelliteRequest() {
</span>
</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">
Maxar Electro-Optical API
</div>
@ -1274,7 +1276,7 @@ export function SatelliteRequest() {
</span>
</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
</div>
<div className="text-caption text-fg-disabled font-korean mt-px">
@ -1703,13 +1705,13 @@ export function SatelliteRequest() {
</div>
<button
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
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={{
background: 'rgba(6,182,212,0.08)',
}}
@ -2098,7 +2100,7 @@ export function SatelliteRequest() {
{urgency}
</span>
{up42SelPass === pass.id && (
<span className="text-xs text-fg"></span>
<span className="text-caption text-fg"></span>
)}
</div>
);

파일 보기

@ -391,7 +391,7 @@ function Vessel3DModel({ viewMode, status }: { viewMode: string; status: string
{isProcessing && (
<div className="absolute inset-0 flex items-center justify-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 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 && (
<div className="absolute inset-0 flex items-center justify-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 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"
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]}
</div>
</div>
@ -916,7 +916,7 @@ export function SensorAnalysis() {
{ value: '0.023m', label: 'RMS오차' },
].map((s, i) => (
<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>
))}

파일 보기

@ -47,7 +47,7 @@ export function WingAI() {
<div className="flex items-center gap-3 mb-4 h-9">
<div className="flex items-center gap-2 shrink-0">
<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={{
background: 'rgba(6,182,212,0.15)',
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-sm shrink-0">{src.icon}</span>
<span className="text-body-2 shrink-0">{active ? '◉' : '○'}</span>
<span className="text-body-2 shrink-0">{src.icon}</span>
<div className="flex-1 min-w-0">
<div className="text-caption font-bold font-korean">
{src.label}

파일 보기

@ -83,7 +83,9 @@ function AssetManagement() {
if (loading) {
return (
<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>
);
}
@ -415,7 +417,7 @@ function AssetManagement() {
<aside className="w-[340px] min-w-[340px] bg-bg-surface border-l border-stroke flex flex-col">
{/* Header */}
<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">
{selectedOrg.type} · {regionShort(selectedOrg.jurisdiction)} · {selectedOrg.area}
</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>
)}
@ -601,10 +603,10 @@ function AssetManagement() {
{/* Bottom Actions */}
{/* <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 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>
</div> */}

파일 보기

@ -27,7 +27,7 @@ function AssetUpload() {
{/* 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="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 className="text-label-2 text-fg-disabled mb-4 font-korean">
@ -43,7 +43,7 @@ function AssetUpload() {
{/* Asset Classification */}
<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>
<select className="prd-i w-full">
@ -58,7 +58,7 @@ function AssetUpload() {
{/* Jurisdiction */}
<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>
<select className="prd-i w-full">
@ -74,10 +74,10 @@ function AssetUpload() {
{/* Upload Mode */}
<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>
<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">
<input
type="radio"
@ -102,7 +102,7 @@ function AssetUpload() {
{/* Upload Button */}
<button
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
? 'bg-[rgba(34,197,94,0.2)] text-color-success border border-status-green'
: 'text-white'
@ -163,7 +163,7 @@ function AssetUpload() {
{p.icon}
</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>
</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"
>
<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">
{new Date(h.regDtm).toLocaleString('ko-KR')} · {h.uploaderNm} · {h.uploadCnt}
</div>

파일 보기

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

파일 보기

@ -167,7 +167,7 @@ function ShipInsurance() {
{total > 0 ? `${total.toLocaleString()}` : '데이터 없음'}
</div>
</div>
<div className="text-xs text-fg-disabled">
<div className="text-caption text-fg-disabled">
</div>
</div>
@ -200,7 +200,7 @@ function ShipInsurance() {
onChange={(e) => setSearch(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
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>
@ -243,20 +243,20 @@ function ShipInsurance() {
</div>
<button
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
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
onClick={handleDownload}
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>
@ -281,8 +281,8 @@ function ShipInsurance() {
{/* 에러 */}
{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="text-sm font-bold text-color-danger mb-2"> </div>
<div className="text-xs text-fg-disabled">{error}</div>
<div className="text-body-2 font-bold text-color-danger mb-2"> </div>
<div className="text-caption text-fg-disabled">{error}</div>
</div>
)}
@ -291,7 +291,7 @@ function ShipInsurance() {
<>
<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="text-xs font-bold">
<div className="text-caption font-bold">
<span className="text-color-accent">{total.toLocaleString()}</span>
{totalPages > 1 && (
<span className="text-fg-disabled font-normal ml-2">

파일 보기

@ -219,7 +219,7 @@ export function HNSLeftPanel({
};
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 */}
<div
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 justify-between items-center p-2 bg-bg-base rounded">
<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 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-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 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-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>

파일 보기

@ -35,8 +35,8 @@ export function HNSRightPanel({
}: HNSRightPanelProps) {
if (!dispersionResult) {
return (
<div className="w-[300px] bg-bg-surface border-l border-stroke p-4 overflow-auto">
<div className="flex flex-col gap-3 items-center justify-center h-full text-fg-disabled text-label-1">
<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 text-fg-disabled text-label-1">
<div style={{ fontSize: '32px', opacity: 0.3 }}>📊</div>
<div> </div>
</div>
@ -58,7 +58,7 @@ export function HNSRightPanel({
: 'ALOHA';
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 */}
<div>
<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="flex items-center justify-between mb-[10px]">
<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
</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="flex items-center justify-between mb-[10px]">
<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
</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="flex items-center justify-between mb-[10px]">
<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>
</div>
<div className="flex gap-1">
@ -1048,7 +1050,7 @@ ${styles}
<div className="rounded-[10px] p-[14px] border border-stroke bg-bg-card">
<div className="flex items-center justify-between mb-[10px]">
<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
</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="flex items-center justify-between mb-[10px]">
<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
</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="flex items-center justify-between mb-[10px]">
<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
</span>{' '}
<span className="text-label-1 font-bold"></span>

파일 보기

@ -262,6 +262,8 @@ function DispersionTimeSlider({
export function HNSView() {
const { activeSubTab, setActiveSubTab } = useSubMenu('hns');
const { user } = useAuthStore();
const [leftCollapsed, setLeftCollapsed] = useState(false);
const [rightCollapsed, setRightCollapsed] = useState(false);
const [incidentCoord, setIncidentCoord] = useState<{ lon: number; lat: number } | null>(null);
const [isSelectingLocation, setIsSelectingLocation] = useState(false);
const [isRunningPrediction, setIsRunningPrediction] = useState(false);
@ -890,6 +892,7 @@ export function HNSView() {
<div className="flex flex-1 overflow-hidden">
{/* Left Panel - 분석 목록일 때는 숨김 */}
{activeSubTab === 'analysis' && (
<div className="shrink-0 overflow-hidden" style={{ width: leftCollapsed ? 0 : 320 }}>
<HNSLeftPanel
activeSubTab={activeSubTab}
onSubTabChange={setActiveSubTab}
@ -902,10 +905,53 @@ export function HNSView() {
onReset={handleReset}
loadedParams={loadedParams}
/>
</div>
)}
{/* Center - Map/Content Area */}
<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' ? (
<HNSAnalysisListTable
onTabChange={(v) =>
@ -942,6 +988,7 @@ export function HNSView() {
{/* Right Panel - 분석 목록일 때는 숨김 */}
{activeSubTab === 'analysis' && (
<div className="shrink-0 overflow-hidden" style={{ width: rightCollapsed ? 0 : 300 }}>
<HNSRightPanel
dispersionResult={dispersionResult}
computedResult={computedResult}
@ -950,6 +997,7 @@ export function HNSView() {
onOpenReport={handleOpenReport}
onSave={handleSave}
/>
</div>
)}
{/* HNS 재계산 모달 */}

파일 보기

@ -1,4 +1,7 @@
import { useState } from 'react';
import { DndContext } from '@dnd-kit/core';
import { useDraggable } from '@dnd-kit/core';
import type { DragEndEvent } from '@dnd-kit/core';
/**
* 22
@ -100,33 +103,30 @@ const RULES: DischargeRule[] = [
];
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 }) {
if (status === 'forbidden')
return (
<span
className="text-caption font-bold px-1.5 py-0.5 rounded"
style={{ background: 'rgba(239,68,68,0.15)', color: 'var(--color-danger)' }}
className="text-caption px-1.5 py-0.5 rounded"
style={{
background: 'color-mix(in srgb, var(--color-danger) 15%, transparent)',
color: 'var(--color-danger)',
}}
>
</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 (
<span
className="text-caption font-bold px-1.5 py-0.5 rounded"
style={{ background: 'rgba(234,179,8,0.15)', color: 'var(--color-caution)' }}
>
<span className="text-caption px-1.5 py-0.5 rounded text-fg-sub">
{status === 'allowed' ? '배출가능' : '조건부'}
</span>
);
}
@ -139,15 +139,36 @@ interface DischargeZonePanelProps {
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,
lon,
distanceNm,
zoneIndex,
onClose,
}: DischargeZonePanelProps) {
offset,
}: DischargeZonePanelProps & { offset: { x: number; y: number } }) {
const zoneIdx = zoneIndex;
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))];
@ -161,22 +182,33 @@ export function DischargeZonePanel({
border: '1px solid var(--stroke-default)',
boxShadow: '0 16px 48px rgba(0,0,0,0.5)',
backdropFilter: 'blur(12px)',
transform: `translate(${tx}px, ${ty}px)`,
}}
>
{/* Header */}
{/* Header — drag handle */}
<div
ref={setNodeRef}
{...listeners}
{...attributes}
className="shrink-0 flex items-center justify-between"
style={{
padding: '10px 14px',
borderBottom: '1px solid var(--stroke-default)',
background: 'var(--bg-elevated)',
cursor: 'grab',
userSelect: 'none',
}}
>
<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>
<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>
</div>
@ -194,10 +226,7 @@ export function DischargeZonePanel({
</div>
<div className="flex items-center justify-between mb-2">
<span className="text-caption text-fg-sub font-korean"> </span>
<span
className="text-label-2 font-bold font-mono"
style={{ color: ZONE_COLORS[zoneIdx] }}
>
<span className="text-label-2 font-mono" style={{ color: ZONE_COLORS[zoneIdx] }}>
{distanceNm.toFixed(1)} NM
</span>
</div>
@ -206,12 +235,10 @@ export function DischargeZonePanel({
{ZONE_LABELS.map((label, i) => (
<div
key={label}
className="flex-1 text-center rounded-sm"
className="flex-1 text-center rounded-sm text-[10px]"
style={{
padding: '3px 0',
fontSize: 8,
fontWeight: i === zoneIdx ? 700 : 400,
color: i === zoneIdx ? 'var(--fg-default)' : 'var(--fg-sub)',
padding: '2px 0',
color: i === zoneIdx ? '#000' : 'var(--fg-sub)',
background: i === zoneIdx ? ZONE_COLORS[i] : 'var(--hover-overlay)',
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 isExpanded = expandedCat === cat;
const allForbidden = catRules.every((r) => r.zones[zoneIdx] === 'forbidden');
const allAllowed = catRules.every((r) => r.zones[zoneIdx] === 'allowed');
const summaryColor = allForbidden ? '#ef4444' : allAllowed ? '#22c55e' : '#eab308';
const summaryColor = allForbidden ? 'var(--color-danger)' : 'var(--fg-sub)';
return (
<div key={cat} style={{ borderBottom: '1px solid var(--stroke-light)' }}>
@ -245,11 +271,11 @@ export function DischargeZonePanel({
<div
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 className="flex items-center gap-2">
<span className="text-caption font-semibold" style={{ color: summaryColor }}>
{allForbidden ? '전체 불가' : allAllowed ? '전체 가능' : '항목별 상이'}
<span className="text-caption" style={{ color: summaryColor }}>
{allForbidden ? '전체 불가' : '허용'}
</span>
<span className="text-caption text-fg-sub">{isExpanded ? '▾' : '▸'}</span>
</div>
@ -268,7 +294,7 @@ export function DischargeZonePanel({
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]} />
</div>
))}

파일 보기

@ -23,10 +23,10 @@ export function IncidentTable() {
<div className="flex items-center justify-between px-5 py-4 border-b border-stroke">
<div>
<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 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>
<div className="relative">
@ -35,10 +35,10 @@ export function IncidentTable() {
placeholder="검색..."
value={searchTerm}
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>
<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>
</div>
@ -49,31 +49,31 @@ export function IncidentTable() {
<table className="w-full">
<thead className="sticky top-0 bg-bg-surface border-b border-stroke z-10">
<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 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 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 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 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 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 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 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 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>
</tr>
@ -84,28 +84,28 @@ export function IncidentTable() {
key={incident.acdntSn}
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">
<div className="flex items-center gap-2">
<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}
</span>
</div>
</td>
<td className="px-4 py-3 text-sm 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-sm 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-sub font-mono">{incident.occrnDtm}</td>
<td className="px-4 py-3 text-body-2 text-fg-sub">{incident.vesselTp ?? '—'}</td>
<td className="px-4 py-3 text-body-2 text-fg-sub">{incident.oilTpCd ?? '—'}</td>
<td className="px-4 py-3 text-body-2 text-fg font-mono text-right font-semibold">
{incident.spilQty != null ? incident.spilQty.toFixed(2) : '—'}
</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">
<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}
</span>
</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>
))}
</tbody>

파일 보기

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

파일 보기

@ -50,31 +50,8 @@ interface AnalysisItem {
checked: boolean;
}
/* ── 카테고리별 고유 색상 (목록 순서 인덱스 기반 — 중복 없음) ── */
const CATEGORY_PALETTE: [number, number, number][] = [
[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 기반) ── */
/* ── 카테고리 → 이모지 매핑 (prediction LeftPanel의 CATEGORY_ICON_MAP 기반, 미사용 보존) ── */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const CATEGORY_ICON: Record<string, string> = {
: '🐟',
: '🦪',
@ -140,8 +117,20 @@ function getActiveModels(p: PredictionAnalysis): string {
/* ── HNS/구난 섹션 (미개발, 고정 구조만 유지) ────── */
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 ───────────────────────────────────── */
@ -292,7 +281,7 @@ export function IncidentsRightPanel({
key: 'oil',
icon: '🛢',
title: '유출유 확산예측',
color: '#f97316',
color: 'var(--color-accent)',
colorRgb: '249,115,22',
totalLabel: `전체 ${predItems.length}`,
items: predItems.map((p) => {
@ -310,7 +299,7 @@ export function IncidentsRightPanel({
if (!incident) {
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-[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]">
{/* Header */}
<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">
: <b className="text-color-accent">{incident.name}</b>
: <span className="text-fg-disabled">{incident.name}</span>
</div>
</div>
@ -344,22 +333,19 @@ export function IncidentsRightPanel({
<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 gap-1.5">
<span className="text-sm">{sec.icon}</span>
<span className="text-xs font-bold" style={{ color: sec.color }}>
{sec.title}
</span>
{/* <span className="text-body-2">{sec.icon}</span> */}
<span className="text-caption">{sec.title}</span>
</div>
<button
className="text-caption font-semibold cursor-pointer"
style={{
padding: '3px 10px',
borderRadius: '4px',
background: `rgba(${sec.colorRgb},0.1)`,
border: `1px solid rgba(${sec.colorRgb},0.25)`,
border: '1px solid var(--stroke-default)',
color: sec.color,
}}
>
📋
</button>
</div>
<div className="flex flex-col gap-1">
@ -374,8 +360,7 @@ export function IncidentsRightPanel({
className="flex items-center gap-1.5"
style={{
padding: '5px 8px',
background: `rgba(${sec.colorRgb},0.06)`,
border: `1px solid rgba(${sec.colorRgb},0.15)`,
border: '1px solid var(--stroke-default)',
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 className="flex items-center justify-between mb-2">
<div className="flex items-center gap-1.5">
<span className="text-sm">{sec.icon}</span>
<span className="text-xs font-bold" style={{ color: sec.color }}>
{sec.title}
</span>
{/* <span className="text-body-2">{sec.icon}</span> */}
<span className="text-caption">{sec.title}</span>
</div>
<button
className="text-caption font-semibold cursor-pointer"
style={{
padding: '3px 10px',
borderRadius: '4px',
background: `rgba(${sec.colorRgb},0.1)`,
border: `1px solid rgba(${sec.colorRgb},0.25)`,
border: '1px solid var(--stroke-default)',
color: sec.color,
}}
>
📋
</button>
</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="flex items-center gap-1.5 mb-2">
<span className="text-sm">🐟</span>
<span className="text-xs font-bold text-color-success"></span>
{/* <span className="text-body-2">🐟</span> */}
<span className="text-caption"></span>
</div>
<div className="flex flex-col gap-[3px]">
{sensCategories.length === 0 ? (
@ -452,38 +434,33 @@ export function IncidentsRightPanel({
</div>
) : (
sensCategories.map((cat, i) => {
const icon = CATEGORY_ICON[cat.category] ?? '🌊';
sensCategories.map((cat) => {
const areaLabel =
cat.totalArea != null
? `${cat.totalArea.toLocaleString('ko-KR', { maximumFractionDigits: 0 })}ha`
: `${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 (
<label
key={cat.category}
className="flex items-center cursor-pointer text-caption gap-[5px] rounded-[3px]"
style={{ padding: '4px 6px', background: `rgba(${r},${g},${b},0.06)` }}
className="flex items-center cursor-pointer text-caption gap-[5px]"
style={{ padding: '3px 0' }}
>
<input
type="checkbox"
checked={checkedSensCategories.has(cat.category)}
onChange={() => toggleSensCategory(cat.category)}
style={{ accentColor: hex }}
style={{ accentColor: 'var(--color-accent)' }}
/>
<span
style={{
width: 8,
height: 8,
width: 6,
height: 6,
borderRadius: '50%',
background: hex,
background: 'var(--color-accent)',
flexShrink: 0,
display: 'inline-block',
border: `1px solid rgba(${r},${g},${b},0.45)`,
}}
/>
<span>{icon}</span>
<span className="flex-1">{cat.category}</span>
<span className="text-fg-disabled font-mono shrink-0">({areaLabel})</span>
</label>
@ -496,10 +473,9 @@ export function IncidentsRightPanel({
{/* 근처 방제자원 */}
<div className="bg-bg-elevated border border-stroke rounded-md p-2.5">
<div className="flex items-center gap-1.5 mb-2">
<span className="text-sm">🛡</span>
<span className="text-xs font-bold text-color-boom"> </span>
<span className="text-caption font-bold text-color-accent"> </span>
{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}
</span>
)}
@ -519,21 +495,18 @@ export function IncidentsRightPanel({
</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) => (
<div
key={org.orgSn}
className="flex items-start gap-1.5 rounded-[3px] px-[6px] py-[5px]"
style={{
background: 'rgba(245,158,11,0.05)',
border: '1px solid rgba(245,158,11,0.08)',
}}
className="flex items-start gap-1.5 px-[2px] py-[5px]"
style={{ borderBottom: '1px solid var(--stroke-default)' }}
>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-1 mb-[2px]">
<span
className="text-caption px-[4px] py-[1px] rounded-[2px] font-bold shrink-0"
style={{ background: 'rgba(245,158,11,0.15)', color: '#f59e0b' }}
className="text-caption px-[4px] py-[1px] rounded-[2px] font-bold shrink-0 text-color-accent"
style={{ background: 'rgba(6,182,212,0.1)' }}
>
{org.orgTp}
</span>
@ -544,7 +517,7 @@ export function IncidentsRightPanel({
{org.totalAssets > 0 ? ` · 장비 ${org.totalAssets}` : ''}
</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
</span>
</div>
@ -553,10 +526,10 @@ export function IncidentsRightPanel({
)}
{/* 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]">
<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
</span>
</div>
@ -572,7 +545,7 @@ export function IncidentsRightPanel({
height: '4px',
background: 'var(--stroke-default)',
borderRadius: '2px',
accentColor: '#f59e0b',
accentColor: 'var(--color-accent)',
}}
/>
</div>
@ -605,7 +578,8 @@ export function IncidentsRightPanel({
color: isActive ? 'var(--color-accent)' : 'var(--fg-disabled)',
}}
>
{v.icon} {v.label}
{/* {v.icon} */}
{v.label}
</button>
);
})}
@ -627,9 +601,7 @@ export function IncidentsRightPanel({
className="w-full text-label-2 font-bold cursor-pointer"
style={{
padding: '8px',
background: analysisActive
? '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))',
background: analysisActive ? 'rgba(239,68,68,0.1)' : 'rgba(6,182,212,0.1)',
border: analysisActive
? '1px solid rgba(239,68,68,0.3)'
: '1px solid rgba(6,182,212,0.3)',

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

파일 보기

@ -112,7 +112,7 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
<button
key={page}
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'
}`}
>
@ -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>
<h1 className="text-heading-3 font-bold text-fg"> </h1>
<p className="text-title-3 text-fg-disabled mt-1"> {analyses.length}</p>
<h1 className="text-heading-3 text-fg"> </h1>
<p className="text-body-2 text-fg-disabled mt-1"> {analyses.length}</p>
</div>
<div className="flex items-center gap-3">
<div className="relative">
@ -137,12 +137,12 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
placeholder="검색..."
value={searchTerm}
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>
<button
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={{
border: '1px solid rgba(6,182,212,.3)',
background: 'rgba(6,182,212,.08)',
@ -156,7 +156,7 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
{/* 테이블 */}
<div className="flex-1 overflow-auto">
{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">
<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}
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}
</td>
<td className="px-4 py-3">
<div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-color-danger animate-pulse" />
<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) => {
e.stopPropagation();
if (onSelectAnalysis) {
@ -227,7 +227,7 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
</span>
</div>
</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
? new Date(analysis.occurredAt).toLocaleString('ko-KR', {
month: '2-digit',
@ -237,7 +237,7 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
})
: '—'}
</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
? new Date(analysis.runDtm).toLocaleString('ko-KR', {
month: '2-digit',
@ -247,11 +247,11 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
})
: '—'}
</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}
</td>
<td className="px-4 py-3 text-title-3 text-fg-sub">{analysis.oilType}</td>
<td className="px-4 py-3 text-title-3 text-fg font-mono text-right font-medium">
<td className="px-4 py-3 text-body-2 text-fg-sub">{analysis.oilType}</td>
<td className="px-4 py-3 text-body-2 text-fg font-mono text-right font-medium">
{analysis.volume != null
? analysis.volume >= 0.01
? analysis.volume.toFixed(2)
@ -268,8 +268,8 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
<td className="px-4 py-3 text-center">
{getStatusBadge(analysis.backtrackStatus)}
</td>
<td className="px-4 py-3 text-title-3 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.analyst}</td>
<td className="px-4 py-3 text-body-2 text-fg-sub">{analysis.officeName}</td>
</tr>
))}
</tbody>
@ -277,7 +277,7 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
)}
{!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>
)}
@ -288,14 +288,14 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
<button
onClick={() => setCurrentPage(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
onClick={() => setCurrentPage((prev) => Math.max(1, prev - 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>
@ -305,14 +305,14 @@ export function AnalysisListTable({ onTabChange, onSelectAnalysis }: AnalysisLis
<button
onClick={() => setCurrentPage((prev) => Math.min(totalPages, prev + 1))}
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
onClick={() => setCurrentPage(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>

파일 보기

@ -68,7 +68,7 @@ export function BacktrackModal({
border: '1px solid var(--bd)',
borderRadius: '6px',
color: 'var(--t1)',
fontSize: '11px',
fontSize: 'var(--font-size-caption)',
fontFamily: 'var(--fM)',
outline: 'none',
opacity: inputDisabled ? 0.6 : 1,
@ -108,9 +108,8 @@ export function BacktrackModal({
borderRadius: '10px',
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)',
fontSize: '18px',
}}
className="flex items-center justify-center"
className="flex items-center justify-center text-title-1"
>
🔍
</div>
@ -127,7 +126,7 @@ export function BacktrackModal({
height: '32px',
borderRadius: '8px',
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"
>
@ -245,7 +244,7 @@ export function BacktrackModal({
}}
>
<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}{' '}
<span className="text-caption font-medium text-fg-disabled">(AIS )</span>
</div>
@ -371,7 +370,7 @@ function VesselCard({ vessel }: { vessel: BacktrackVessel }) {
borderRadius: '50%',
background: `${vessel.color}20`,
border: `2px solid ${vessel.color}`,
fontSize: '12px',
fontSize: 'var(--font-size-caption)',
fontWeight: 800,
color: vessel.color,
}}

파일 보기

@ -1222,7 +1222,7 @@ function FieldApplicationPanel() {
style={{ background: step.bg, border: `1px solid ${step.bd}` }}
>
<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={{
background: step.numBg,
border: `1px solid ${step.numBd}`,

파일 보기

@ -130,7 +130,7 @@ export function LeftPanel({
};
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 */}
<div
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() {
const { activeSubTab, setActiveSubTab } = useSubMenu('prediction');
const [leftCollapsed, setLeftCollapsed] = useState(false);
const [rightCollapsed, setRightCollapsed] = useState(false);
const [enabledLayers, setEnabledLayers] = useState<Set<string>>(new Set());
const [incidentCoord, setIncidentCoord] = useState<{ lon: number; lat: number } | null>(null);
const [flyToCoord, setFlyToCoord] = useState<{ lon: number; lat: number } | undefined>(undefined);
@ -1129,6 +1131,7 @@ export function OilSpillView() {
<div className="relative flex flex-1 overflow-hidden">
{/* Left Sidebar */}
{activeSubTab === 'analysis' && (
<div className="shrink-0 overflow-hidden" style={{ width: leftCollapsed ? 0 : 320 }}>
<LeftPanel
selectedAnalysis={selectedAnalysis}
enabledLayers={enabledLayers}
@ -1207,10 +1210,53 @@ export function OilSpillView() {
onImageAnalysisResult={handleImageAnalysisResult}
validationErrors={validationErrors}
/>
</div>
)}
{/* Center - Map/Content Area */}
<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' ? (
<AnalysisListTable
onTabChange={setActiveSubTab}
@ -1332,7 +1378,7 @@ export function OilSpillView() {
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
fontSize: '14px',
fontSize: 'var(--font-size-body-2)',
transition: '0.2s',
}}
>
@ -1358,7 +1404,7 @@ export function OilSpillView() {
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
fontSize: '14px',
fontSize: 'var(--font-size-body-2)',
transition: '0.2s',
}}
>
@ -1394,7 +1440,7 @@ export function OilSpillView() {
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
fontSize: '12px',
fontSize: 'var(--font-size-caption)',
transition: '0.2s',
}}
>
@ -1415,7 +1461,7 @@ export function OilSpillView() {
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
fontSize: '11px',
fontSize: 'var(--font-size-caption)',
fontWeight: 600,
fontFamily: 'var(--font-mono)',
transition: '0.2s',
@ -1439,7 +1485,7 @@ export function OilSpillView() {
position: 'absolute',
left: `${pos}%`,
transform: 'translateX(-50%)',
fontSize: '10px',
fontSize: 'var(--font-size-caption)',
fontFamily: 'var(--font-mono)',
color: isActive ? 'var(--color-accent)' : 'var(--fg-disabled)',
fontWeight: isActive ? 600 : 400,
@ -1526,7 +1572,7 @@ export function OilSpillView() {
top: '-18px',
left: `${bm.pos}%`,
transform: 'translateX(-50%)',
fontSize: '12px',
fontSize: 'var(--font-size-caption)',
cursor: 'pointer',
filter: 'drop-shadow(0 0 4px rgba(245,158,11,0.5))',
}}
@ -1569,7 +1615,7 @@ export function OilSpillView() {
>
<div
style={{
fontSize: '14px',
fontSize: 'var(--font-size-body-2)',
fontWeight: 600,
color: 'var(--color-accent)',
fontFamily: 'var(--font-mono)',
@ -1610,7 +1656,7 @@ export function OilSpillView() {
display: 'flex',
alignItems: 'center',
gap: '5px',
fontSize: '11px',
fontSize: 'var(--font-size-caption)',
}}
>
<span className="text-fg-disabled">{s.label}</span>
@ -1655,6 +1701,7 @@ export function OilSpillView() {
{/* Right Panel */}
{activeSubTab === 'analysis' && (
<div className="shrink-0 overflow-hidden" style={{ width: rightCollapsed ? 0 : 300 }}>
<RightPanel
onOpenBacktrack={handleOpenBacktrack}
onOpenRecalc={() => {
@ -1693,6 +1740,7 @@ export function OilSpillView() {
onCancelAnalysis={handleCancelAnalysis}
onClearAnalysis={handleClearAnalysis}
/>
</div>
)}
{/* 확산 예측 실행 중 로딩 오버레이 */}

파일 보기

@ -159,7 +159,7 @@ export function RecalcModal({
borderRadius: '10px',
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)',
fontSize: '16px',
fontSize: 'var(--font-size-body-1)',
}}
className="flex items-center justify-center"
>
@ -178,7 +178,7 @@ export function RecalcModal({
height: '28px',
borderRadius: '6px',
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"
>
@ -321,7 +321,7 @@ export function RecalcModal({
}
toggleModel(model);
}}
style={{ fontSize: '11px', padding: '5px 10px' }}
style={{ fontSize: 'var(--font-size-caption)', padding: '5px 10px' }}
>
<span className="prd-md" style={{ background: color }} />
{model}

파일 보기

@ -117,7 +117,7 @@ export function RightPanel({
}, [incidentCoord, centerPoints, summary, predictionTime]);
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 */}
<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">
@ -580,7 +580,7 @@ export function RightPanel({
{/* Bottom Action Buttons */}
<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
@ -597,7 +597,7 @@ export function RightPanel({
</button>
<button
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>

파일 보기

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

파일 보기

@ -171,13 +171,13 @@ export function ReportsView() {
{filteredReports.length === 0 ? (
<div className="flex flex-col items-center justify-center py-20 text-fg-disabled">
<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
onClick={() => {
setView({ screen: 'templates' });
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>
@ -376,7 +376,7 @@ export function ReportsView() {
<td className="px-3 py-3 text-center">
<button
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>

파일 보기

@ -1651,14 +1651,14 @@ function NewScenarioModal({ ops, onClose }: { ops: RescueOpsItem[]; onClose: ()
</div>
<button
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>
{done ? (
<button
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>
@ -1666,7 +1666,7 @@ function NewScenarioModal({ ops, onClose }: { ops: RescueOpsItem[]; onClose: ()
<button
onClick={handleSubmit}
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
? 'bg-bg-card text-fg-disabled cursor-wait'
: '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="text-center">
<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-xs text-fg-disabled font-korean">
<div className="text-body-2 font-bold text-fg-sub font-korean mb-1"></div>
<div className="text-caption text-fg-disabled font-korean">
.
</div>
</div>

파일 보기

@ -16,6 +16,8 @@ import ScatRightPanel from './ScatRightPanel';
// ═══ Main PreScatView ═══
export function PreScatView() {
const [leftCollapsed, setLeftCollapsed] = useState(false);
const [rightCollapsed, setRightCollapsed] = useState(false);
const [segments, setSegments] = useState<ScatSegment[]>([]);
const [zones, setZones] = useState<ApiZoneItem[]>([]);
const [jurisdictions, setJurisdictions] = useState<string[]>([]);
@ -175,13 +177,13 @@ export function PreScatView() {
if (error) {
return (
<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
onClick={() => {
setError(null);
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>
@ -192,13 +194,15 @@ export function PreScatView() {
if (loading || !selectedSeg) {
return (
<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>
);
}
return (
<div className="flex w-full h-full bg-bg-base overflow-hidden">
{/* Left Panel */}
<div className="shrink-0 overflow-hidden" style={{ width: leftCollapsed ? 0 : 340 }}>
<ScatLeftPanel
segments={segments}
zones={zones}
@ -220,8 +224,47 @@ export function PreScatView() {
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
segments={segments}
zones={zones}
@ -237,7 +280,10 @@ export function PreScatView() {
/> */}
</div>
{/* Right Panel */}
<div className="shrink-0 overflow-hidden" style={{ width: rightCollapsed ? 0 : 280 }}>
<ScatRightPanel detail={panelDetail} loading={panelLoading} />
</div>
{popupData && (
<ScatPopup data={popupData} segCode={selectedSeg.code} onClose={handleClosePopup} />

파일 보기

@ -156,7 +156,7 @@ function ScatLeftPanel({
}, []);
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 */}
<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">
@ -269,7 +269,11 @@ function ScatLeftPanel({
rowCount={filtered.length}
rowHeight={88}
overscanCount={5}
style={{ height: listHeight }}
style={{
height: listHeight,
scrollbarWidth: 'thin',
scrollbarColor: 'var(--stroke-default) transparent',
}}
rowComponent={SegRow}
rowProps={{
filtered,

파일 보기

@ -310,7 +310,7 @@ function ScatMap({
</div>
{/* 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 */}
<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">

파일 보기

@ -182,7 +182,7 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
<button
key={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
? 'text-color-success border-status-green'
: 'text-fg-disabled border-transparent hover:text-fg-sub'
@ -204,7 +204,9 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
{/* Skeleton */}
{!imgLoaded && !imgError && (
<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>
)}
<img
@ -216,7 +218,7 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
onError={() => setImgError(true)}
/>
{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> </span>
</div>
@ -266,7 +268,7 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
].map(([k, v, cls], i) => (
<div
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 font-korean ${cls}`}>{v}</span>
@ -282,7 +284,7 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
{data.sensitive.map((s, i) => (
<div
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 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">
{!mapLoaded && (
<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>
)}
<PopupMap
@ -390,7 +394,7 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
].map(([k, v], i) => (
<div
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-color-success font-mono font-medium">{v}</span>
@ -413,7 +417,7 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
].map(([k, v], i) => (
<div
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 font-medium font-korean">{v}</span>
@ -441,7 +445,7 @@ function ScatPopup({ data, segCode, onClose }: ScatPopupProps) {
].map((h, i) => (
<div key={i} className="bg-bg-card border border-stroke rounded-md p-4">
<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
className={`text-caption font-bold px-2 py-0.5 rounded-lg ${
h.type === 'Pre-SCAT'

파일 보기

@ -23,7 +23,7 @@ export default function ScatRightPanel({
if (!detail && !loading) {
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-center text-fg-disabled text-label-2 leading-relaxed">
@ -37,7 +37,7 @@ export default function ScatRightPanel({
}
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">
{detail ? (
@ -49,12 +49,12 @@ export default function ScatRightPanel({
{detail.esi}
</span>
<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>
</div>
) : (
<div className="text-xs text-fg-disabled"> ...</div>
<div className="text-caption text-fg-disabled"> ...</div>
)}
</div>

파일 보기

@ -67,38 +67,38 @@ function ScatTimeline({ segments, currentIdx, onSeek }: ScatTimelineProps) {
<div className="flex gap-1 flex-shrink-0">
<button
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
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
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 ? '⏸' : '▶'}
</button>
<button
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
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>
<div className="w-2" />
<button
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}×
</button>
@ -162,7 +162,7 @@ function ScatTimeline({ segments, currentIdx, onSeek }: ScatTimelineProps) {
{/* Info */}
<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}
</span>
<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="text-center">
<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-xs text-fg-disabled font-korean">
<div className="text-body-2 font-bold text-fg-sub font-korean mb-1"> </div>
<div className="text-caption text-fg-disabled font-korean">
.
</div>
</div>

파일 보기

@ -33,11 +33,11 @@ export function WeatherMapControls({ center, zoom }: WeatherMapControlsProps) {
<div key={tooltip} className="relative group">
<button
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}
</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}
</div>
</div>

파일 보기

@ -33,26 +33,26 @@ interface WeatherMapOverlayProps {
selectedStationId: string | null;
}
// 풍속에 따른 hex 색상 반환
// 풍속에 따른 색상 반환
function getWindHexColor(speed: number, isSelected: boolean): string {
if (isSelected) return '#06b6d4';
if (speed > 10) return '#ef4444';
if (speed > 7) return '#f59e0b';
return '#3b82f6';
if (isSelected) return 'var(--color-accent)';
if (speed > 10) return 'var(--color-danger)';
if (speed > 7) return 'var(--color-caution)';
return 'var(--color-info)';
}
// 파고에 따른 hex 색상 반환
// 파고에 따른 색상 반환
function getWaveHexColor(height: number): string {
if (height > 2.5) return '#ef4444';
if (height > 1.5) return '#f59e0b';
return '#3b82f6';
if (height > 2.5) return 'var(--color-danger)';
if (height > 1.5) return 'var(--color-caution)';
return 'var(--color-info)';
}
// 수온에 따른 hex 색상 반환
// 수온에 따른 색상 반환
function getTempHexColor(temp: number): string {
if (temp > 8) return '#ef4444';
if (temp > 6) return '#f59e0b';
return '#3b82f6';
if (temp > 8) return 'var(--color-danger)';
if (temp > 6) return 'var(--color-caution)';
return 'var(--color-info)';
}
/**
@ -91,15 +91,17 @@ export function WeatherMapOverlay({
width={24}
height={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>
</div>
<span
style={{ color, textShadow: '0 1px 3px rgba(0,0,0,0.8)' }}
className="text-xs font-bold leading-none"
className="text-caption font-bold leading-none px-1 py-px rounded-sm bg-bg-base/80"
style={{ color }}
>
{station.wind.speed.toFixed(1)}
</span>
@ -138,7 +140,7 @@ export function WeatherMapOverlay({
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)'}`,
}}
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}
</div>
@ -150,7 +152,7 @@ export function WeatherMapOverlay({
🌡
</div>
<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)}
</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 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)}
</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 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)}
</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,
): Layer[] {
return useMemo(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const result: Layer[] = [];
// 파고 분포 ScatterplotLayer (Circle 대체, 반경 = 파고 * 15km)

파일 보기

@ -1,3 +1,5 @@
import { useState } from 'react';
interface WeatherData {
stationName: string;
location: { lat: number; lon: number };
@ -46,20 +48,14 @@ interface WeatherRightPanelProps {
weatherData: WeatherData | null;
}
/** 풍속 등급 색상 */
function windColor(speed: number): string {
if (speed >= 14) return '#ef4444';
if (speed >= 10) return '#f97316';
if (speed >= 6) return '#eab308';
return '#22c55e';
/** 풍속 텍스트 색상 (2단계 — danger | accent) */
function windTextColor(speed: number): string {
return speed >= 10 ? 'var(--color-danger)' : 'var(--color-accent)';
}
/** 파고 등급 색상 */
function waveColor(height: number): string {
if (height >= 3) return '#ef4444';
if (height >= 2) return '#f97316';
if (height >= 1) return '#eab308';
return '#22c55e';
/** 파고 텍스트 색상 (2단계 — danger | accent) */
function waveTextColor(height: number): string {
return height >= 2 ? 'var(--color-danger)' : 'var(--color-accent)';
}
/** 풍향 텍스트 */
@ -86,13 +82,38 @@ function windDirText(deg: number): string {
}
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) {
return (
<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>
<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>
);
@ -109,11 +130,13 @@ 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="px-4 py-3 border-b border-stroke bg-bg-elevated">
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<span className="text-title-4 font-bold text-color-accent font-korean">
<span className="text-title-4 font-bold text-color-accent font-korean truncate">
📍 {weatherData.stationName}
</span>
<span className="px-1.5 py-px text-label-2 rounded bg-[rgba(6,182,212,0.15)] text-color-accent font-bold">
<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>
@ -122,6 +145,16 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
{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>
{/* 스크롤 콘텐츠 */}
<div
@ -131,13 +164,13 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
{/* ── 핵심 지표 3칸 카드 ── */}
<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-[20px] font-bold font-mono" style={{ color: windColor(wSpd) }}>
<div className="text-[20px] font-bold font-mono" style={{ color: windTextColor(wSpd) }}>
{wSpd.toFixed(1)}
</div>
<div className="text-label-2 text-fg-disabled font-korean"> (m/s)</div>
</div>
<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)}
</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="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="flex items-center gap-3 mb-2">
{/* 풍향 컴파스 */}
<div className="relative w-[50px] h-[50px] shrink-0">
@ -202,11 +233,11 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
y1="25"
x2={25 + 14 * Math.sin((wind.direction * Math.PI) / 180)}
y2={25 - 14 * Math.cos((wind.direction * Math.PI) / 180)}
stroke={windColor(wSpd)}
stroke="var(--color-accent)"
strokeWidth="2"
strokeLinecap="round"
/>
<circle cx="25" cy="25" r="3" fill={windColor(wSpd)} />
<circle cx="25" cy="25" r="3" fill="var(--color-accent)" />
</svg>
</div>
<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 className="flex justify-between">
<span className="text-fg-disabled">1k </span>
<span
className="font-mono text-title-4"
style={{ color: windColor(wind.speed_1k) }}
>
<span className="font-mono text-title-4 text-fg">
{Number(wind.speed_1k).toFixed(1)}
</span>
</div>
<div className="flex justify-between">
<span className="text-fg-disabled">3k </span>
<span
className="font-mono text-title-4"
style={{ color: windColor(wind.speed_3k) }}
>
<span className="font-mono text-title-4 text-fg">
{Number(wind.speed_3k).toFixed(1)}
</span>
</div>
@ -248,11 +273,8 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
<div className="flex items-center gap-2">
<div className="flex-1 h-[5px] bg-bg-card rounded-full overflow-hidden">
<div
className="h-full rounded-full transition-all"
style={{
width: `${Math.min((wSpd / 20) * 100, 100)}%`,
background: windColor(wSpd),
}}
className="h-full rounded-full transition-all bg-color-accent"
style={{ width: `${Math.min((wSpd / 20) * 100, 100)}%` }}
/>
</div>
<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="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="text-center py-2 bg-bg-base border border-stroke rounded">
<div className="text-title-3 font-bold font-mono" style={{ color: waveColor(wHgt) }}>
{wHgt.toFixed(1)}m
</div>
<div className="text-title-3 font-bold font-mono text-fg">{wHgt.toFixed(1)}m</div>
<div className="text-caption text-fg-disabled"></div>
</div>
<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
</div>
<div className="text-caption text-fg-disabled"></div>
</div>
<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">
{wave.period}s
</div>
<div className="text-title-3 font-bold font-mono text-fg">{wave.period}s</div>
<div className="text-caption text-fg-disabled"></div>
</div>
<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"
style={{
width: `${Math.min((wHgt / 5) * 100, 100)}%`,
background: waveColor(wHgt),
background: 'var(--color-accent)',
}}
/>
</div>
@ -307,14 +325,10 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
{/* ── 수온/공기 ── */}
<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-3 gap-1">
<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">
{wTemp.toFixed(1)}°
</div>
<div className="text-title-3 font-bold font-mono text-fg">{wTemp.toFixed(1)}°</div>
<div className="text-caption text-fg-disabled"></div>
</div>
<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="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-5 gap-1">
{forecast.map((f, i) => (
<div
@ -353,9 +365,7 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
{/* ── 천문/조석 ── */}
{astronomy && (
<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">
{[
{ icon: '🌅', label: '일출', value: astronomy.sunrise },
@ -371,7 +381,7 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
))}
</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">
<span className="text-sm">🌓</span>
<span className="text-body-2">🌓</span>
<span className="text-fg-disabled">{astronomy.moonPhase}</span>
<span className="ml-auto text-fg font-mono"> {astronomy.tidalRange}m</span>
</div>
@ -381,17 +391,21 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
{/* ── 날씨 특보 ── */}
{alert && (
<div className="px-3 py-2">
<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="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">
<span
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>

파일 보기

@ -90,7 +90,6 @@ const WEATHER_MAP_CENTER: [number, number] = [127.8, 36.5]; // [lng, lat]
const WEATHER_MAP_ZOOM = 7;
// deck.gl 오버레이 컴포넌트 (MapLibre 컨트롤로 등록)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function DeckGLOverlay({ layers }: { layers: Layer[] }) {
const overlay = useControl<MapboxOverlay>(() => new MapboxOverlay({ interleaved: true }));
overlay.setProps({ layers });
@ -178,7 +177,7 @@ function WeatherMapInner({
{/* 핀 꼬리 */}
<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
</div>
</div>
@ -295,13 +294,13 @@ export function WeatherView() {
{/* Main Map Area */}
<div className="flex-1 relative flex flex-col overflow-hidden">
{/* 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">
{(['0', '3', '6', '9'] as TimeOffset[]).map((offset) => (
<button
key={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
? 'bg-color-accent text-bg-0'
: '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">
<span className="text-xs text-fg-disabled">
<span className="text-caption text-fg-disabled">
{lastUpdate
? `마지막 업데이트: ${lastUpdate.toLocaleTimeString('ko-KR', {
hour: '2-digit',
@ -323,7 +322,7 @@ export function WeatherView() {
{loading && (
<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>
@ -356,10 +355,7 @@ export function WeatherView() {
</Map>
{/* 레이어 컨트롤 */}
<div
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="absolute top-4 left-4 bg-bg-surface border border-stroke rounded-md shadow-md z-10 px-2.5 py-1.5">
<div className="text-caption font-semibold text-fg mb-1.5 font-korean"> </div>
<div className="flex flex-col gap-1">
<label className="flex items-center gap-1.5 cursor-pointer">
@ -420,17 +416,12 @@ export function WeatherView() {
</div>
{/* 범례 */}
<div
className="absolute bottom-4 left-4 bg-bg-surface/85 border border-stroke rounded-md backdrop-blur-sm z-10"
style={{ padding: '6px 10px', maxWidth: 180 }}
>
<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 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]">
<div className="text-caption text-fg mb-1.5 font-korean"> </div>
<div className="flex flex-col gap-1.5 text-[8px]">
{/* 바람 */}
<div>
<div className="font-semibold text-fg-sub mb-0.5" style={{ fontSize: 8 }}>
(m/s)
</div>
<div className="text-fg-sub mb-0.5"> (m/s)</div>
<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: '#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: '#b41e46' }} />
</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>5</span>
<span>7</span>
@ -453,16 +444,14 @@ export function WeatherView() {
</div>
{/* 해류 */}
<div className="pt-1 border-t border-stroke">
<div className="font-semibold text-fg-sub mb-0.5" style={{ fontSize: 8 }}>
(m/s)
</div>
<div className="text-fg-sub mb-0.5"> (m/s)</div>
<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(6, 182, 212)' }} />
<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>
<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.4</span>
<span>0.6</span>
@ -471,23 +460,18 @@ export function WeatherView() {
</div>
{/* 파고 */}
<div className="pt-1 border-t border-stroke">
<div className="font-semibold text-fg-sub mb-0.5" style={{ fontSize: 8 }}>
(m)
</div>
<div className="text-fg-sub mb-0.5"> (m)</div>
<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>
<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>
<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>
</div>
</div>
</div>
<div
className="mt-1 pt-1 border-t border-stroke text-fg-disabled font-korean"
style={{ fontSize: 7 }}
>
<div className="mt-1 pt-1 border-t border-stroke text-fg-disabled text-[7px] font-korean">
💡
</div>
</div>

파일 보기

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