develop #58

병합
htlee develop 에서 main 로 6 commits 를 머지했습니다 2026-03-01 12:32:01 +09:00
38개의 변경된 파일2196개의 추가작업 그리고 2843개의 파일을 삭제
Showing only changes of commit 3fc8f03238 - Show all commits

파일 보기

@ -55,10 +55,7 @@ export function LoginPage() {
}
return (
<div style={{
width: '100vw', height: '100vh', display: 'flex',
background: '#001028', overflow: 'hidden', position: 'relative',
}}>
<div className="w-screen h-screen flex overflow-hidden relative" style={{ background: '#001028' }}>
{/* Background image */}
<div style={{
position: 'absolute', inset: 0,
@ -78,14 +75,10 @@ export function LoginPage() {
}} />
{/* Center: Login Form */}
<div style={{
width: '100%', display: 'flex', flexDirection: 'column',
alignItems: 'flex-start', justifyContent: 'center',
padding: '40px 50px 40px 120px', position: 'relative', zIndex: 1,
}}>
<div className="w-full flex flex-col items-start justify-center relative z-[1]" style={{ padding: '40px 50px 40px 120px' }}>
<div style={{ width: '100%', maxWidth: 360 }}>
{/* Logo */}
<div style={{ textAlign: 'center', marginBottom: 36 }}>
<div className="text-center" style={{ marginBottom: 36 }}>
<img
src="/wing_logo_text_white.svg"
alt="WING 해양환경 위기대응 통합시스템"
@ -105,17 +98,11 @@ export function LoginPage() {
<form onSubmit={handleSubmit}>
{/* User ID */}
<div style={{ marginBottom: 16 }}>
<label style={{
display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)',
marginBottom: 6, letterSpacing: '0.3px',
}}>
<label className="block text-[10px] font-semibold text-text-3" style={{ marginBottom: 6, letterSpacing: '0.3px' }}>
</label>
<div style={{ position: 'relative' }}>
<span style={{
position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)',
fontSize: 14, color: 'var(--t3)', pointerEvents: 'none',
}}>
<div className="relative">
<span className="absolute text-sm text-text-3" style={{ left: 12, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none' }}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
</span>
<input
@ -125,11 +112,9 @@ export function LoginPage() {
placeholder="사용자 아이디 입력"
autoComplete="username"
autoFocus
className="w-full bg-bg-2 border border-border rounded-md text-[13px] outline-none"
style={{
width: '100%', padding: '11px 14px 11px 38px',
background: 'var(--bg2)', border: '1px solid var(--bd)',
borderRadius: 8, fontSize: 13,
outline: 'none',
padding: '11px 14px 11px 38px',
transition: 'border-color 0.2s, box-shadow 0.2s',
}}
onFocus={(e) => {
@ -146,17 +131,11 @@ export function LoginPage() {
{/* Password */}
<div style={{ marginBottom: 20 }}>
<label style={{
display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)',
marginBottom: 6, letterSpacing: '0.3px',
}}>
<label className="block text-[10px] font-semibold text-text-3" style={{ marginBottom: 6, letterSpacing: '0.3px' }}>
</label>
<div style={{ position: 'relative' }}>
<span style={{
position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)',
fontSize: 14, color: 'var(--t3)', pointerEvents: 'none',
}}>
<div className="relative">
<span className="absolute text-sm text-text-3" style={{ left: 12, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none' }}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
</span>
<input
@ -165,11 +144,9 @@ export function LoginPage() {
onChange={(e) => { setPassword(e.target.value); clearError() }}
placeholder="비밀번호 입력"
autoComplete="current-password"
className="w-full bg-bg-2 border border-border rounded-md text-[13px] outline-none"
style={{
width: '100%', padding: '11px 14px 11px 38px',
background: 'var(--bg2)', border: '1px solid var(--bd)',
borderRadius: 8, fontSize: 13,
outline: 'none',
padding: '11px 14px 11px 38px',
transition: 'border-color 0.2s, box-shadow 0.2s',
}}
onFocus={(e) => {
@ -185,14 +162,8 @@ export function LoginPage() {
</div>
{/* Remember + Forgot */}
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
marginBottom: 20,
}}>
<label style={{
display: 'flex', alignItems: 'center', gap: 6,
fontSize: 11, color: 'var(--t3)', cursor: 'pointer',
}}>
<div className="flex items-center justify-between" style={{ marginBottom: 20 }}>
<label className="flex items-center gap-1.5 text-[11px] text-text-3 cursor-pointer">
<input
type="checkbox"
checked={remember}
@ -201,11 +172,8 @@ export function LoginPage() {
/>
</label>
<button type="button" style={{
fontSize: 11, color: 'var(--cyan)',
background: 'none', border: 'none', cursor: 'pointer',
textDecoration: 'none',
}}
<button type="button" className="text-[11px] text-primary-cyan cursor-pointer"
style={{ background: 'none', border: 'none', textDecoration: 'none' }}
onMouseEnter={(e) => e.currentTarget.style.textDecoration = 'underline'}
onMouseLeave={(e) => e.currentTarget.style.textDecoration = 'none'}
>
@ -215,13 +183,12 @@ export function LoginPage() {
{/* Pending approval */}
{pendingMessage && (
<div style={{
padding: '10px 12px', marginBottom: 16, borderRadius: 6,
<div className="flex items-start gap-2 text-[11px] rounded-sm" style={{
padding: '10px 12px', marginBottom: 16,
background: 'rgba(6,182,212,0.08)', border: '1px solid rgba(6,182,212,0.2)',
fontSize: 11, color: '#67e8f9',
display: 'flex', alignItems: 'flex-start', gap: 8,
color: '#67e8f9',
}}>
<span style={{ fontSize: 14, flexShrink: 0, marginTop: 1 }}>
<span className="text-sm shrink-0" style={{ marginTop: 1 }}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
</span>
<span>{pendingMessage}</span>
@ -230,13 +197,12 @@ export function LoginPage() {
{/* Error */}
{error && (
<div style={{
padding: '8px 12px', marginBottom: 16, borderRadius: 6,
<div className="flex items-center gap-1.5 text-[11px] rounded-sm" style={{
padding: '8px 12px', marginBottom: 16,
background: 'rgba(239,68,68,0.08)', border: '1px solid rgba(239,68,68,0.2)',
fontSize: 11, color: '#f87171',
display: 'flex', alignItems: 'center', gap: 6,
color: '#f87171',
}}>
<span style={{ fontSize: 13 }}>
<span className="text-[13px]">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" x2="12" y1="9" y2="13"/><line x1="12" x2="12.01" y1="17" y2="17"/></svg>
</span>
{error}
@ -244,17 +210,17 @@ export function LoginPage() {
)}
{/* Login button */}
<button type="submit" disabled={isLoading} style={{
width: '100%', padding: '12px',
background: isLoading
? 'rgba(6,182,212,0.15)'
: 'linear-gradient(135deg, rgba(6,182,212,0.2), rgba(59,130,246,0.15))',
border: '1px solid rgba(6,182,212,0.3)',
borderRadius: 8, color: 'var(--cyan)',
fontSize: 14, fontWeight: 700, cursor: isLoading ? 'wait' : 'pointer',
transition: 'all 0.2s',
boxShadow: '0 4px 16px rgba(6,182,212,0.1)',
}}
<button type="submit" disabled={isLoading} className="w-full text-primary-cyan text-sm font-bold rounded-md border"
style={{
padding: '12px',
background: isLoading
? 'rgba(6,182,212,0.15)'
: 'linear-gradient(135deg, rgba(6,182,212,0.2), rgba(59,130,246,0.15))',
borderColor: 'rgba(6,182,212,0.3)',
cursor: isLoading ? 'wait' : 'pointer',
transition: 'all 0.2s',
boxShadow: '0 4px 16px rgba(6,182,212,0.1)',
}}
onMouseEnter={(e) => {
if (!isLoading) {
e.currentTarget.style.background = 'linear-gradient(135deg, rgba(6,182,212,0.3), rgba(59,130,246,0.2))'
@ -269,7 +235,7 @@ export function LoginPage() {
}}
>
{isLoading ? (
<span style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8 }}>
<span className="flex items-center justify-center gap-2">
<span style={{
width: 14, height: 14, border: '2px solid rgba(6,182,212,0.3)',
borderTop: '2px solid var(--cyan)', borderRadius: '50%',
@ -282,21 +248,16 @@ export function LoginPage() {
</form>
{/* Divider */}
<div style={{
display: 'flex', alignItems: 'center', gap: 12, margin: '24px 0',
}}>
<div style={{ flex: 1, height: 1, background: 'var(--bd)' }} />
<span style={{ fontSize: 9, color: 'var(--t3)' }}></span>
<div style={{ flex: 1, height: 1, background: 'var(--bd)' }} />
<div className="flex items-center gap-3" style={{ margin: '24px 0' }}>
<div className="flex-1 bg-border" style={{ height: 1 }} />
<span className="text-[9px] text-text-3"></span>
<div className="flex-1 bg-border" style={{ height: 1 }} />
</div>
{/* Google / Certificate */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<div className="flex flex-col gap-2">
{GOOGLE_ENABLED && (
<div style={{
display: 'flex', justifyContent: 'center',
borderRadius: 8, overflow: 'hidden',
}}>
<div className="flex justify-center rounded-md overflow-hidden">
<GoogleLogin
onSuccess={handleGoogleSuccess}
onError={() => { /* 팝업 닫힘 등 — 별도 처리 불필요 */ }}
@ -307,14 +268,11 @@ export function LoginPage() {
/>
</div>
)}
<button type="button" style={{
width: '100%', padding: '10px', borderRadius: 8,
background: 'var(--bg3)', border: '1px solid var(--bd)',
color: 'var(--t2)', fontSize: 11, fontWeight: 600,
cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
transition: 'background 0.15s',
}}
<button type="button" className="w-full rounded-md bg-bg-3 border border-border text-text-2 text-[11px] font-semibold cursor-pointer flex items-center justify-center gap-1.5"
style={{
padding: '10px',
transition: 'background 0.15s',
}}
onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bgH)'}
onMouseLeave={(e) => e.currentTarget.style.background = 'var(--bg3)'}
>
@ -325,29 +283,29 @@ export function LoginPage() {
{/* Demo accounts info (DEV only) */}
{import.meta.env.DEV && (
<div style={{
marginTop: 24, padding: '10px 12px', borderRadius: 8,
<div className="rounded-md" style={{
marginTop: 24, padding: '10px 12px',
background: 'rgba(6,182,212,0.04)', border: '1px solid rgba(6,182,212,0.08)',
}}>
<div style={{ fontSize: 9, fontWeight: 700, color: 'var(--cyan)', marginBottom: 6 }}>
<div className="text-[9px] font-bold text-primary-cyan" style={{ marginBottom: 6 }}>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
<div className="flex flex-col" style={{ gap: 3 }}>
{DEMO_ACCOUNTS.map((acc) => (
<div key={acc.id}
onClick={() => { setUserId(acc.id); setPassword(acc.password); clearError() }}
className="flex justify-between items-center cursor-pointer"
style={{
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
padding: '4px 6px', borderRadius: 4, cursor: 'pointer',
padding: '4px 6px', borderRadius: 4,
transition: 'background 0.15s',
}}
onMouseEnter={(e) => e.currentTarget.style.background = 'rgba(6,182,212,0.06)'}
onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}
>
<span style={{ fontSize: 9, color: 'var(--t2)', fontFamily: 'var(--fM)' }}>
<span className="text-[9px] text-text-2 font-mono">
{acc.id} / {acc.password}
</span>
<span style={{ fontSize: 8, color: 'var(--t3)' }}>
<span className="text-[8px] text-text-3">
{acc.label}
</span>
</div>
@ -358,10 +316,7 @@ export function LoginPage() {
</div>{/* end form card */}
{/* Footer */}
<div style={{
marginTop: 24, textAlign: 'center', fontSize: 9,
color: 'var(--t3)', lineHeight: 1.6,
}}>
<div className="text-center text-[9px] text-text-3" style={{ marginTop: 24, lineHeight: 1.6 }}>
<div>WING V2.0 | </div>
<div style={{ marginTop: 2, color: 'rgba(134,144,166,0.6)' }}>
&copy; 2026 Korea Coast Guard. All rights reserved.

파일 보기

@ -25,19 +25,14 @@ export function LayerTree({ layers, enabledLayers, onToggleLayer, layerColors =
}
return (
<div style={{ padding: '0 4px' }}>
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '4px 8px 8px', marginBottom: '4px',
borderBottom: '1px solid var(--bd)',
}}>
<span style={{ fontSize: '10px', fontWeight: 600, color: 'var(--t3)' }}>
<div className="px-1">
<div className="flex items-center justify-between px-2 pt-1 pb-2 mb-1 border-b border-border">
<span className="text-[10px] font-semibold text-text-3">
</span>
<div
className={`lyr-sw ${allEnabled ? 'on' : ''}`}
className={`lyr-sw ${allEnabled ? 'on' : ''} cursor-pointer`}
onClick={handleToggleAll}
style={{ cursor: 'pointer' }}
/>
</div>
@ -125,7 +120,7 @@ function LayerNode({ layer, enabledLayers, onToggleLayer, layerColors, onColorCh
{hasChildren ? (
<span className={`lyr-arr ${expanded ? 'open' : ''}`}></span>
) : (
<span className="lyr-arr" style={{ visibility: 'hidden' }}></span>
<span className="lyr-arr invisible"></span>
)}
<div className={`lyr-sw ${isEnabled ? 'on' : ''}`} onClick={handleSwitchClick} />
{layer.icon && <span>{layer.icon}</span>}
@ -153,7 +148,7 @@ function LayerNode({ layer, enabledLayers, onToggleLayer, layerColors, onColorCh
<div className="lyr-t" onClick={(e) => { if (!(e.target as HTMLElement).closest('.lyr-csw, .lyr-cpop')) handleSwitchClick(e) }}>
<div className={`lyr-sw ${isEnabled ? 'on' : ''}`} />
{layer.icon && <span>{layer.icon}</span>}
<span style={{ flex: 1 }}>{layer.name}</span>
<span className="flex-1">{layer.name}</span>
{layer.count !== undefined && <span className="lyr-cnt">{layer.count.toLocaleString()}</span>}
{onColorChange && (
<ColorSwatch color={layerColors[layer.id]} onChange={(c) => onColorChange(layer.id, c)} />
@ -168,7 +163,7 @@ function LayerNode({ layer, enabledLayers, onToggleLayer, layerColors, onColorCh
{hasChildren ? (
<span className={`lyr-arr ${expanded ? 'open' : ''}`}></span>
) : (
<span className="lyr-arr" style={{ visibility: 'hidden' }}></span>
<span className="lyr-arr invisible"></span>
)}
<div className={`lyr-sw ${isEnabled ? 'on' : ''}`} onClick={handleSwitchClick} />
{layer.icon && <span>{layer.icon}</span>}
@ -192,7 +187,7 @@ function LayerNode({ layer, enabledLayers, onToggleLayer, layerColors, onColorCh
<div className="lyr-t" onClick={(e) => { if (!(e.target as HTMLElement).closest('.lyr-csw, .lyr-cpop')) handleSwitchClick(e) }}>
<div className={`lyr-sw ${isEnabled ? 'on' : ''}`} />
{layer.icon && <span>{layer.icon}</span>}
<span style={{ flex: 1 }}>{layer.name}</span>
<span className="flex-1">{layer.name}</span>
{layer.count !== undefined && <span className="lyr-cnt">{layer.count.toLocaleString()}</span>}
{onColorChange && (
<ColorSwatch color={layerColors[layer.id]} onChange={(c) => onColorChange(layer.id, c)} />
@ -204,15 +199,15 @@ function LayerNode({ layer, enabledLayers, onToggleLayer, layerColors, onColorCh
// depth 2+ with children
return (
<div>
<div className="lyr-t" style={{ gap: '6px' }}>
<span className={`lyr-arr ${expanded ? 'open' : ''}`} onClick={handleHeaderClick} style={{ cursor: 'pointer', fontSize: '7px', width: '10px', textAlign: 'center' }}></span>
<div className="lyr-t gap-1.5">
<span className={`lyr-arr ${expanded ? 'open' : ''} cursor-pointer text-[7px] w-[10px] text-center`} onClick={handleHeaderClick}></span>
<div className={`lyr-sw ${isEnabled ? 'on' : ''}`} onClick={handleSwitchClick} />
{layer.icon && <span>{layer.icon}</span>}
<span onClick={handleHeaderClick} style={{ cursor: 'pointer', flex: 1 }}>{layer.name}</span>
<span onClick={handleHeaderClick} className="cursor-pointer flex-1">{layer.name}</span>
{layer.count !== undefined && <span className="lyr-cnt">{layer.count.toLocaleString()}</span>}
</div>
{expanded && (
<div style={{ paddingLeft: '16px' }}>
<div className="pl-4">
{layer.children!.map(child => (
<LayerNode key={child.id} layer={child} enabledLayers={enabledLayers} onToggleLayer={onToggleLayer} layerColors={layerColors} onColorChange={onColorChange} depth={depth + 1} />
))}
@ -237,7 +232,7 @@ function ColorSwatch({ color, onChange }: { color?: string; onChange: (c: string
}, [open])
return (
<div ref={ref} style={{ position: 'relative', flexShrink: 0 }}>
<div ref={ref} className="relative shrink-0">
<div
className={`lyr-csw ${color ? 'has-color' : ''}`}
style={color ? { borderColor: color, background: color } : {}}

파일 보기

@ -14,7 +14,7 @@ export function SubMenuBar({ activeMainTab }: SubMenuBarProps) {
}
return (
<div className="border-b border-border bg-bg-1" style={{ flexShrink: 0 }}>
<div className="border-b border-border bg-bg-1 shrink-0">
<div className="flex px-5">
{subMenuConfig.map((item) => (
<button

파일 보기

@ -42,27 +42,30 @@ export function BacktrackReplayBar({
}
return (
<div style={{
position: 'absolute', bottom: '80px', left: '50%', transform: 'translateX(-50%)',
minWidth: '480px', maxWidth: '680px', width: '60%',
background: 'rgba(10,15,25,0.92)', backdropFilter: 'blur(12px)',
border: '1px solid rgba(168,85,247,0.3)', borderRadius: '12px',
padding: '12px 18px', zIndex: 1200,
display: 'flex', flexDirection: 'column', gap: '10px',
}}>
<div
className="absolute flex flex-col"
style={{
bottom: '80px', left: '50%', transform: 'translateX(-50%)',
minWidth: '480px', maxWidth: '680px', width: '60%',
background: 'rgba(10,15,25,0.92)', backdropFilter: 'blur(12px)',
border: '1px solid rgba(168,85,247,0.3)', borderRadius: '12px',
padding: '12px 18px', zIndex: 1200,
gap: '10px',
}}
>
{/* Header row */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<div style={{
width: '8px', height: '8px', borderRadius: '50%',
background: 'var(--purple)', boxShadow: '0 0 8px rgba(168,85,247,0.5)',
}} />
<span style={{ fontSize: '12px', fontWeight: 700 }}>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div
className="w-2 h-2 rounded-full bg-primary-purple"
style={{ boxShadow: '0 0 8px rgba(168,85,247,0.5)' }}
/>
<span className="text-xs font-bold">
</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<div className="flex items-center gap-1.5">
{/* Speed buttons */}
{[1, 2, 4].map((spd) => (
<button
@ -74,15 +77,15 @@ export function BacktrackReplayBar({
</button>
))}
<div style={{ width: '8px' }} />
<div className="w-2" />
{/* Close button */}
<button
onClick={onClose}
className="text-status-red cursor-pointer font-bold"
style={{
padding: '4px 10px', borderRadius: '6px', fontSize: '10px', fontWeight: 700,
padding: '4px 10px', borderRadius: '6px', fontSize: '10px',
background: 'rgba(239,68,68,0.1)', border: '1px solid rgba(239,68,68,0.3)',
color: 'var(--red)', cursor: 'pointer',
}}
>
@ -91,52 +94,50 @@ export function BacktrackReplayBar({
</div>
{/* Controls row */}
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<div className="flex items-center gap-3">
{/* Play/Pause */}
<button
onClick={onTogglePlay}
className="shrink-0 w-9 h-9 rounded-full flex items-center justify-center text-sm cursor-pointer"
style={{
width: '36px', height: '36px', borderRadius: '50%', flexShrink: 0,
background: isPlaying ? 'var(--purple)' : 'rgba(168,85,247,0.15)',
border: `2px solid ${isPlaying ? 'var(--purple)' : 'rgba(168,85,247,0.4)'}`,
color: isPlaying ? '#fff' : 'var(--purple)',
fontSize: '14px', cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}
>
{isPlaying ? '⏸' : '▶'}
</button>
{/* Timeline */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: '4px' }}>
<div className="flex-1 flex flex-col gap-1">
{/* Progress bar */}
<div
style={{
position: 'relative', height: '20px', display: 'flex', alignItems: 'center',
cursor: 'pointer',
}}
className="relative h-5 flex items-center cursor-pointer"
onClick={handleSeekClick}
>
<div style={{
width: '100%', height: '4px', background: 'var(--bd)', borderRadius: '2px',
position: 'relative', overflow: 'visible',
}}>
<div
className="w-full h-1 bg-border relative overflow-visible"
style={{ borderRadius: '2px' }}
>
{/* Fill */}
<div style={{
position: 'absolute', top: 0, left: 0,
width: `${progress}%`, height: '100%',
background: 'linear-gradient(90deg, var(--purple), var(--cyan))',
borderRadius: '2px', transition: 'width 0.05s',
}} />
<div
className="absolute top-0 left-0 h-full"
style={{
width: `${progress}%`,
background: 'linear-gradient(90deg, var(--purple), var(--cyan))',
borderRadius: '2px', transition: 'width 0.05s',
}}
/>
{/* Collision marker */}
{collisionEvent && (
<div style={{
position: 'absolute', top: '-14px',
left: `${collisionEvent.progressPercent}%`,
transform: 'translateX(-50%)',
fontSize: '10px', cursor: 'pointer',
}}
<div
className="absolute text-[10px] cursor-pointer"
style={{
top: '-14px',
left: `${collisionEvent.progressPercent}%`,
transform: 'translateX(-50%)',
}}
title={collisionEvent.timeLabel}
>
💥
@ -145,32 +146,33 @@ export function BacktrackReplayBar({
</div>
{/* Thumb */}
<div style={{
position: 'absolute', left: `${progress}%`, top: '50%',
transform: 'translate(-50%, -50%)',
width: '14px', height: '14px',
background: '#fff', borderRadius: '50%',
border: '3px solid var(--purple)',
boxShadow: '0 0 8px rgba(168,85,247,0.4)',
zIndex: 2, transition: 'left 0.05s',
}} />
<div
className="absolute top-1/2 w-3.5 h-3.5 bg-white rounded-full"
style={{
left: `${progress}%`,
transform: 'translate(-50%, -50%)',
border: '3px solid var(--purple)',
boxShadow: '0 0 8px rgba(168,85,247,0.4)',
zIndex: 2, transition: 'left 0.05s',
}}
/>
</div>
{/* Time labels */}
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '9px', fontFamily: 'var(--fM)' }}>
<span style={{ color: 'var(--t3)' }}>18:30</span>
<span style={{ color: 'var(--purple)', fontWeight: 600 }}>{currentTimeLabel}</span>
<span style={{ color: 'var(--t3)' }}>06:30</span>
<div className="flex justify-between text-[9px] font-mono">
<span className="text-text-3">18:30</span>
<span className="font-semibold text-primary-purple">{currentTimeLabel}</span>
<span className="text-text-3">06:30</span>
</div>
</div>
</div>
{/* Legend row */}
<div style={{ display: 'flex', alignItems: 'center', gap: '14px', paddingTop: '4px', borderTop: '1px solid var(--bd)' }}>
<div className="flex items-center gap-[14px] pt-1 border-t border-border">
{replayShips.map((ship) => (
<div key={ship.vesselName} style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<div style={{ width: '16px', height: '3px', background: ship.color, borderRadius: '1px' }} />
<span style={{ fontSize: '9px', color: 'var(--t2)', fontFamily: 'var(--fM)' }}>
<div key={ship.vesselName} className="flex items-center gap-1.5">
<div className="w-4 h-[3px]" style={{ background: ship.color, borderRadius: '1px' }} />
<span className="text-[9px] text-text-2 font-mono">
{ship.vesselName}
</span>
</div>

파일 보기

@ -439,11 +439,11 @@ export function MapView({
latitude: d.lat,
content: (
<div className="text-xs" style={{ minWidth: '130px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '4px' }}>
<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>
</div>
<div style={{ fontSize: '10px', color: '#666' }}>
<div className="text-[10px]" style={{ color: '#666' }}>
: {d.radiusM}m<br />
: <strong style={{ color: d.arrivalTimeH <= 6 ? '#ef4444' : '#f97316' }}>{d.arrivalTimeH}h</strong>
</div>
@ -544,7 +544,8 @@ export function MapView({
zoom: zoom,
}}
mapStyle={BASE_STYLE}
style={{ width: '100%', height: '100%', cursor: isSelectingLocation ? 'crosshair' : 'grab' }}
className="w-full h-full"
style={{ cursor: isSelectingLocation ? 'crosshair' : 'grab' }}
onClick={handleMapClick}
attributionControl={false}
>
@ -578,9 +579,10 @@ export function MapView({
{incidentCoord && !isNaN(incidentCoord.lat) && !isNaN(incidentCoord.lon) && (
<Marker longitude={incidentCoord.lon} latitude={incidentCoord.lat} anchor="bottom">
<div
className="w-6 h-6 bg-primary-cyan border-2 border-white"
style={{
width: 24, height: 24, background: 'var(--cyan)', borderRadius: '50% 50% 50% 0',
transform: 'rotate(-45deg)', border: '2px solid #fff',
borderRadius: '50% 50% 50% 0',
transform: 'rotate(-45deg)',
boxShadow: '0 2px 8px rgba(6,182,212,0.5)',
}}
/>
@ -599,7 +601,7 @@ export function MapView({
{decimalToDMS(incidentCoord.lon, false)}
</span>
<br />
<span className="text-xs" style={{ fontFamily: 'monospace', color: '#888' }}>
<span className="text-xs font-mono" style={{ color: '#888' }}>
({incidentCoord.lat.toFixed(4)}°, {incidentCoord.lon.toFixed(4)}°)
</span>
</div>
@ -670,7 +672,7 @@ function MapControls({ center, zoom }: { center: [number, number]; zoom: number
const { current: map } = useMap()
return (
<div style={{ position: 'absolute', top: 16, left: 16, zIndex: 10 }}>
<div className="absolute top-4 left-4" style={{ zIndex: 10 }}>
<div className="flex flex-col gap-2">
<button
onClick={() => map?.zoomIn()}
@ -708,27 +710,27 @@ function MapLegend({ dispersionResult, incidentCoord, oilTrajectory = [], boomLi
if (dispersionResult && incidentCoord) {
return (
<div className="absolute top-4 right-4 bg-[rgba(18,25,41,0.95)] backdrop-blur-xl border border-border rounded-lg p-3.5 min-w-[200px]" style={{ zIndex: 20 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '10px' }}>
<div style={{ fontSize: '16px' }}>📍</div>
<div className="flex items-center gap-1.5 mb-2.5">
<div className="text-base">📍</div>
<div>
<h4 className="text-[11px] font-bold text-primary-orange"> </h4>
<div className="text-[8px] text-text-3" style={{ fontFamily: 'var(--fM)' }}>
<div className="text-[8px] text-text-3 font-mono">
{incidentCoord.lat.toFixed(4)}°N, {incidentCoord.lon.toFixed(4)}°E
</div>
</div>
</div>
<div style={{ background: 'rgba(249,115,22,0.08)', padding: '8px', borderRadius: '6px', marginBottom: '8px', fontSize: '9px', color: 'var(--t2)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '3px' }}>
<span style={{ color: 'var(--t3)' }}></span>
<span style={{ fontWeight: 600, color: 'var(--orange)' }}>{dispersionResult.substance}</span>
<div className="text-[9px] text-text-2 mb-2" style={{ background: 'rgba(249,115,22,0.08)', padding: '8px', borderRadius: '6px' }}>
<div className="flex justify-between mb-[3px]">
<span className="text-text-3"></span>
<span className="font-semibold text-status-orange">{dispersionResult.substance}</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '3px' }}>
<span style={{ color: 'var(--t3)' }}></span>
<span style={{ fontWeight: 600, fontFamily: 'var(--fM)' }}>SW {dispersionResult.windDirection}°</span>
<div className="flex justify-between mb-[3px]">
<span className="text-text-3"></span>
<span className="font-semibold font-mono">SW {dispersionResult.windDirection}°</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ color: 'var(--t3)' }}> </span>
<span style={{ fontWeight: 600, color: 'var(--cyan)' }}>{dispersionResult.zones.length}</span>
<div className="flex justify-between">
<span className="text-text-3"> </span>
<span className="font-semibold text-primary-cyan">{dispersionResult.zones.length}</span>
</div>
</div>
<div>
@ -748,8 +750,8 @@ function MapLegend({ dispersionResult, incidentCoord, oilTrajectory = [], boomLi
</div>
</div>
</div>
<div style={{ marginTop: '8px', padding: '6px', background: 'rgba(168,85,247,0.08)', borderRadius: '4px', display: 'flex', alignItems: 'center', gap: '6px' }}>
<div style={{ fontSize: '12px' }}>🧭</div>
<div className="flex items-center gap-1.5 mt-2" style={{ padding: '6px', background: 'rgba(168,85,247,0.08)', borderRadius: '4px' }}>
<div className="text-xs">🧭</div>
<span className="text-[9px] text-text-3"> ()</span>
</div>
</div>
@ -772,14 +774,14 @@ function MapLegend({ dispersionResult, incidentCoord, oilTrajectory = [], boomLi
<span className="font-korean">( )</span>
</div>
)}
<div style={{ height: '1px', background: 'var(--bd)', margin: '4px 0' }} />
<div className="h-px bg-border my-1" />
<div className="flex items-center gap-2 text-xs text-text-2">
<div className="w-3.5 h-3.5 rounded-full bg-primary-cyan" />
<span className="font-korean"> </span>
</div>
{boomLines.length > 0 && (
<>
<div style={{ height: '1px', background: 'var(--bd)', margin: '4px 0' }} />
<div className="h-px bg-border my-1" />
<div className="flex items-center gap-2 text-xs text-text-2">
<div style={{ width: '14px', height: '3px', background: '#ef4444', borderRadius: '1px' }} />
<span className="font-korean"> </span>
@ -948,25 +950,33 @@ function WeatherInfoPanel({ position }: { position: [number, number] }) {
function BacktrackReplayBar({ replayFrame, totalFrames, ships }: { replayFrame: number; totalFrames: number; ships: ReplayShip[] }) {
const progress = (replayFrame / totalFrames) * 100
return (
<div style={{
position: 'absolute', bottom: 80, left: '50%', transform: 'translateX(-50%)',
background: 'rgba(10,14,26,0.92)', backdropFilter: 'blur(12px)',
border: '1px solid var(--bdL)', borderRadius: '10px',
padding: '12px 18px', zIndex: 50,
display: 'flex', alignItems: 'center', gap: '16px', minWidth: '340px',
}}>
<div style={{ fontSize: '14px', color: 'var(--purple)', fontFamily: 'var(--fM)', fontWeight: 700 }}>
<div
className="absolute flex items-center gap-4"
style={{
bottom: 80, left: '50%', transform: 'translateX(-50%)',
background: 'rgba(10,14,26,0.92)', backdropFilter: 'blur(12px)',
border: '1px solid var(--bdL)', borderRadius: '10px',
padding: '12px 18px', zIndex: 50,
minWidth: '340px',
}}
>
<div className="text-sm text-primary-purple font-mono font-bold">
{progress.toFixed(0)}%
</div>
<div style={{ flex: 1, height: '4px', background: 'var(--bd)', borderRadius: '2px', position: 'relative' }}>
<div style={{ width: `${progress}%`, height: '100%', background: 'linear-gradient(90deg, var(--purple), var(--cyan))', borderRadius: '2px', transition: 'width 0.05s' }} />
<div className="flex-1 h-1 bg-border relative" style={{ borderRadius: '2px' }}>
<div
className="h-full"
style={{ width: `${progress}%`, background: 'linear-gradient(90deg, var(--purple), var(--cyan))', borderRadius: '2px', transition: 'width 0.05s' }}
/>
</div>
<div style={{ display: 'flex', gap: '6px' }}>
<div className="flex gap-1.5">
{ships.map(s => (
<div key={s.vesselName} style={{
width: 8, height: 8, borderRadius: '50%', background: s.color,
border: '1px solid rgba(255,255,255,0.3)',
}} title={s.vesselName} />
<div
key={s.vesselName}
className="w-2 h-2 rounded-full border border-white/30"
style={{ background: s.color }}
title={s.vesselName}
/>
))}
</div>
</div>

파일 보기

@ -32,44 +32,34 @@ export function ComboBox({ value, onChange, options, placeholder, className }: C
const displayText = selectedOption?.label || placeholder || '선택'
return (
<div ref={containerRef} style={{ position: 'relative' }}>
<div ref={containerRef} className="relative">
<div
className={className}
className={`cursor-pointer flex items-center justify-between pr-2 ${className ?? ''}`}
onClick={() => setIsOpen(!isOpen)}
style={{
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
paddingRight: '8px'
}}
>
<span>{displayText}</span>
<span style={{
fontSize: '8px',
color: 'var(--t3)',
transition: 'transform 0.2s',
transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)'
}}>
<span
className="text-[8px] text-text-3"
style={{
transition: 'transform 0.2s',
transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)'
}}
>
</span>
</div>
{isOpen && (
<div style={{
position: 'absolute',
top: 'calc(100% + 2px)',
left: 0,
right: 0,
background: 'var(--bg0)',
border: '1px solid var(--bd)',
borderRadius: 'var(--rS)',
maxHeight: '200px',
overflowY: 'auto',
zIndex: 1000,
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
animation: 'fadeSlideDown 0.15s ease-out'
}}>
<div
className="absolute left-0 right-0 bg-bg-0 border border-border overflow-y-auto z-[1000]"
style={{
top: 'calc(100% + 2px)',
borderRadius: 'var(--rS)',
maxHeight: '200px',
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
animation: 'fadeSlideDown 0.15s ease-out'
}}
>
{options.map((option) => (
<div
key={option.value}
@ -77,12 +67,11 @@ export function ComboBox({ value, onChange, options, placeholder, className }: C
onChange(option.value)
setIsOpen(false)
}}
className="text-[11px] cursor-pointer"
style={{
padding: '8px 10px',
fontSize: '11px',
color: option.value === String(value) ? 'var(--cyan)' : 'var(--t2)',
background: option.value === String(value) ? 'rgba(6,182,212,0.1)' : 'transparent',
cursor: 'pointer',
transition: '0.1s',
borderLeft: option.value === String(value) ? '2px solid var(--cyan)' : '2px solid transparent'
}}

파일 보기

@ -28,32 +28,30 @@ export function AerialTheoryView() {
const [activePanel, setActivePanel] = useState(0)
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', width: '100%', flex: 1, overflow: 'hidden', background: 'var(--bg0)' }}>
<div style={{ flex: 1, overflowY: 'auto', padding: '20px 24px' }}>
<div className="flex flex-col h-full w-full flex-1 overflow-hidden bg-bg-0">
<div className="flex-1 overflow-y-auto px-6 py-5">
{/* 헤더 */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '20px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<div style={{ width: '42px', height: '42px', borderRadius: '10px', background: 'linear-gradient(135deg,rgba(249,115,22,.2),rgba(234,179,8,.15))', border: '1px solid rgba(249,115,22,.3)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '20px' }}>📐</div>
<div className="flex items-center justify-between mb-5">
<div className="flex items-center gap-3">
<div className="w-[42px] h-[42px] rounded-[10px] flex items-center justify-center text-xl border" style={{ background: 'linear-gradient(135deg,rgba(249,115,22,.2),rgba(234,179,8,.15))', borderColor: 'rgba(249,115,22,.3)' }}>📐</div>
<div>
<div style={{ fontSize: '16px', fontWeight: 700 }}> · </div>
<div style={{ fontSize: '10px', color: 'var(--t3)', marginTop: '2px' }}> · · ESI · 10-1567431 </div>
<div className="text-base font-bold"> · </div>
<div className="text-[10px] text-text-3 mt-0.5"> · · ESI · 10-1567431 </div>
</div>
</div>
</div>
{/* 내부 네비게이션 */}
<div style={{ display: 'flex', gap: '3px', background: 'var(--bg3)', borderRadius: '8px', padding: '4px', marginBottom: '20px', border: '1px solid var(--bd)' }}>
<div className="flex gap-[3px] bg-bg-3 rounded-lg p-1 mb-5 border border-border">
{panels.map(p => (
<button
key={p.id}
onClick={() => setActivePanel(p.id)}
style={{
flex: 1, padding: '8px', fontSize: '10px',
borderRadius: '6px', border: 'none', cursor: 'pointer',
background: activePanel === p.id ? 'rgba(6,182,212,.15)' : 'transparent',
color: activePanel === p.id ? 'var(--cyan)' : 'var(--t3)',
fontWeight: activePanel === p.id ? 700 : 400,
}}
className={`flex-1 py-2 text-[10px] rounded-md border-none cursor-pointer transition-colors ${
activePanel === p.id
? 'bg-[rgba(6,182,212,.15)] text-primary-cyan font-bold'
: 'bg-transparent text-text-3 font-normal'
}`}
>
{p.icon} {p.label}
</button>

파일 보기

@ -138,7 +138,7 @@ export function SatelliteRequest() {
const bsInputStyle = { border: '1px solid #21262d', background: '#161b22', color: '#e2e8f0' }
return (
<div className="overflow-y-auto" style={{ padding: '20px 24px' }}>
<div className="overflow-y-auto py-5 px-6">
{/* 헤더 */}
<div className="flex items-center justify-between mb-5">
<div className="flex items-center gap-3">
@ -401,7 +401,7 @@ export function SatelliteRequest() {
{/* 하단 */}
<div className="px-7 pb-5 flex items-center justify-between">
<div className="text-[9px] text-text-3 font-korean leading-relaxed">💡 촬영: BlackSky (90 ) · /악천후: UP42 SAR </div>
<button onClick={() => setModalPhase('none')} className="px-4 py-2 rounded-lg border text-[11px] font-semibold cursor-pointer font-korean" style={{ borderColor: 'var(--bd)', background: 'var(--bg3)', color: 'var(--t2)' }}></button>
<button onClick={() => setModalPhase('none')} className="px-4 py-2 rounded-lg border text-[11px] font-semibold cursor-pointer font-korean bg-bg-3 text-text-2 border-border"></button>
</div>
</div>
)}

파일 보기

@ -351,7 +351,7 @@ function AssetManagement() {
{/* Bottom Actions */}
<div className="p-3.5 border-t border-border flex gap-2">
<button className="flex-1 py-2.5 rounded-sm text-xs font-semibold font-korean text-white border-none cursor-pointer" style={{ background: 'linear-gradient(135deg, var(--cyan), var(--blue))' }}>
<button className="flex-1 py-2.5 rounded-sm text-xs font-semibold font-korean text-white border-none cursor-pointer" style={{ background: 'linear-gradient(135deg, var(--cyan), var(--blue))' }} >
📥
</button>
<button className="flex-1 py-2.5 rounded-sm text-xs font-semibold font-korean bg-bg-3 border border-border text-text-2 cursor-pointer hover:bg-bg-hover transition-colors">

파일 보기

@ -114,7 +114,7 @@ function AssetMap({
<Map
initialViewState={{ longitude: 127.8, latitude: 35.9, zoom: 7 }}
mapStyle={BASE_STYLE}
style={{ width: '100%', height: '100%' }}
className="w-full h-full"
attributionControl={false}
>
<DeckGLOverlay layers={[markerLayer]} />

파일 보기

@ -145,65 +145,54 @@ const TAG_COLORS: Record<string, { bg: string; bd: string; fg: string }> = {
function TheoryCard({ section }: { section: TheorySection }) {
const badgeBg = section.bgTint.replace(/[\d.]+\)$/, '0.15)')
return (
<div style={{
background: 'var(--bg3)', border: '1px solid var(--bd)',
borderRadius: 'var(--rM, 10px)', overflow: 'hidden',
}}>
<div className="bg-bg-3 border border-border rounded-md overflow-hidden">
{/* Section Header */}
<div style={{
padding: '12px 16px', background: section.bgTint,
borderBottom: '1px solid var(--bd)',
display: 'flex', alignItems: 'center', gap: '8px',
}}>
<span style={{ fontSize: '14px' }}>{section.icon}</span>
<span style={{ fontSize: '12px', fontWeight: 700, color: section.color }}>
<div className="px-4 py-3 border-b border-border flex items-center gap-2" style={{ background: section.bgTint }}>
<span className="text-sm">{section.icon}</span>
<span className="text-xs font-bold" style={{ color: section.color }}>
{section.title}
</span>
</div>
{/* Items */}
<div style={{ padding: '14px 16px', display: 'flex', flexDirection: 'column', gap: '8px', fontSize: '9px' }}>
<div className="px-4 py-3.5 flex flex-col gap-2 text-[9px]">
{section.items.map((item, i) => (
<div key={i}>
{/* Divider */}
{section.dividerAfter !== undefined && i === section.dividerAfter + 1 && (
<div style={{ borderTop: '1px dashed var(--bd)', margin: '4px 0 12px', paddingTop: '8px' }}>
<div style={{ fontSize: '8px', fontWeight: 700, color: section.color, marginBottom: '6px', opacity: 0.7 }}>
<div className="mt-1 mb-3 pt-2" style={{ borderTop: '1px dashed var(--bd)' }}>
<div className="text-[8px] font-bold mb-1.5 opacity-70" style={{ color: section.color }}>
{section.dividerLabel}
</div>
</div>
)}
<div style={{
display: 'grid', gridTemplateColumns: '24px 1fr', gap: '8px',
padding: '8px 10px', background: 'var(--bg0)', borderRadius: '6px',
<div className="grid gap-2 px-2.5 py-2 bg-bg-0 rounded-md" style={{
gridTemplateColumns: '24px 1fr',
borderLeft: item.highlight ? `2px solid ${section.color}` : undefined,
}}>
{/* Number badge */}
<div style={{
width: '20px', height: '20px', borderRadius: '4px',
background: badgeBg,
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: '9px', flexShrink: 0,
fontWeight: item.highlight ? 700 : 400,
color: item.highlight ? section.color : undefined,
}}>
<div className="w-5 h-5 rounded flex items-center justify-center text-[9px] shrink-0"
style={{
background: badgeBg,
fontWeight: item.highlight ? 700 : 400,
color: item.highlight ? section.color : undefined,
}}>
{['①','②','③','④','⑤','⑥','⑦','⑧','⑨','⑩'][i]}
</div>
<div>
<div style={{ fontWeight: 700, marginBottom: '2px' }}>
<div className="font-bold mb-0.5">
{item.title}
</div>
<div style={{ color: 'var(--t3)', lineHeight: '1.6' }}>
<div className="text-text-3 leading-[1.6]">
{item.source}
</div>
{/* Tags */}
{item.tags && (
<div style={{ marginTop: '3px', display: 'flex', flexWrap: 'wrap', gap: '3px' }}>
<div className="mt-0.5 flex flex-wrap gap-0.5">
{item.tags.map((tag, ti) => {
const tc = TAG_COLORS[tag.color] || { bg: 'rgba(107,114,128,0.08)', bd: 'rgba(107,114,128,0.2)', fg: '#6b7280' }
return (
<span key={ti} style={{
padding: '1px 5px', borderRadius: '3px', fontSize: '8px',
<span key={ti} className="px-1 py-px rounded text-[8px]" style={{
color: tc.fg, background: tc.bg, border: `1px solid ${tc.bd}`,
}}>
{tag.label}
@ -212,7 +201,7 @@ function TheoryCard({ section }: { section: TheorySection }) {
})}
</div>
)}
<div style={{ marginTop: '2px', color: 'var(--t2)' }}>
<div className="mt-0.5 text-text-2">
{item.desc}
</div>
</div>
@ -226,23 +215,23 @@ function TheoryCard({ section }: { section: TheorySection }) {
function AssetTheory() {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '0' }}>
<div style={{ fontSize: '18px', fontWeight: 700, marginBottom: '4px' }}>
<div className="flex flex-col gap-0">
<div className="text-[18px] font-bold mb-1">
📚
</div>
<div style={{ fontSize: '12px', color: 'var(--t3)', marginBottom: '24px' }}>
<div className="text-xs text-text-3 mb-6">
· ·
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '18px', alignItems: 'start' }}>
<div className="grid gap-[18px] items-start" style={{ gridTemplateColumns: '1fr 1fr' }}>
{/* Left column */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
<div className="flex flex-col gap-3.5">
{THEORY_SECTIONS.slice(0, 2).map((sec) => (
<TheoryCard key={sec.title} section={sec} />
))}
</div>
{/* Right column */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '14px' }}>
<div className="flex flex-col gap-3.5">
{THEORY_SECTIONS.slice(2).map((sec) => (
<TheoryCard key={sec.title} section={sec} />
))}

파일 보기

@ -17,7 +17,7 @@ export function AssetsView() {
return (
<div className="flex flex-col h-full w-full bg-bg-0">
{/* Tab Navigation */}
<div className="flex items-center justify-between border-b border-border bg-bg-1" style={{ flexShrink: 0 }}>
<div className="flex items-center justify-between border-b border-border bg-bg-1 shrink-0">
<div className="flex">
{([
{ id: 'management' as const, icon: '🗂', label: '자산 관리' },

파일 보기

@ -86,50 +86,49 @@ function ShipInsurance() {
const expiredList = resultData.filter(r => getStatus(r.expiry) === 'expired')
return (
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, overflow: 'auto' }}>
<div className="flex flex-col flex-1 overflow-auto">
{/* ── 헤더 ── */}
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: 20 }}>
<div className="flex items-start justify-between mb-5">
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
<div style={{ fontSize: 18, fontWeight: 700 }}>🛡 </div>
<div style={{
display: 'flex', alignItems: 'center', gap: 5, padding: '3px 10px', borderRadius: 10,
fontSize: 10, fontWeight: 700,
background: apiConnected ? 'rgba(34,197,94,.12)' : 'rgba(239,68,68,.12)',
color: apiConnected ? 'var(--green)' : 'var(--red)',
border: `1px solid ${apiConnected ? 'rgba(34,197,94,.25)' : 'rgba(239,68,68,.25)'}`,
}}>
<span style={{ width: 6, height: 6, borderRadius: '50%', background: apiConnected ? 'var(--green)' : 'var(--red)', display: 'inline-block' }} />
<div className="flex items-center gap-2.5 mb-1">
<div className="text-[18px] font-bold">🛡 </div>
<div className="flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-[10px] font-bold"
style={{
background: apiConnected ? 'rgba(34,197,94,.12)' : 'rgba(239,68,68,.12)',
color: apiConnected ? 'var(--green)' : 'var(--red)',
border: `1px solid ${apiConnected ? 'rgba(34,197,94,.25)' : 'rgba(239,68,68,.25)'}`,
}}>
<span className="w-1.5 h-1.5 rounded-full inline-block" style={{ background: apiConnected ? 'var(--green)' : 'var(--red)' }} />
{apiConnected ? 'API 연결됨' : 'API 미연결'}
</div>
</div>
<div style={{ fontSize: 12, color: 'var(--t3)' }}>(KSA) Open API · P&I </div>
<div className="text-xs text-text-3">(KSA) Open API · P&I </div>
</div>
<div style={{ display: 'flex', gap: 8 }}>
<button onClick={handleTestConnect} style={{ padding: '8px 16px', background: 'rgba(6,182,212,.12)', color: 'var(--cyan)', border: '1px solid rgba(6,182,212,.3)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>🔌 </button>
<button onClick={() => setShowConfig(v => !v)} style={{ padding: '8px 16px', background: 'var(--bg3)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}> API </button>
<div className="flex gap-2">
<button onClick={handleTestConnect} className="px-4 py-2 text-xs font-semibold cursor-pointer rounded-sm" style={{ background: 'rgba(6,182,212,.12)', color: 'var(--cyan)', border: '1px solid rgba(6,182,212,.3)' }}>🔌 </button>
<button onClick={() => setShowConfig(v => !v)} className="px-4 py-2 text-xs font-semibold cursor-pointer rounded-sm bg-bg-3 text-text-2 border border-border"> API </button>
</div>
</div>
{/* ── API 설정 패널 ── */}
{showConfig && (
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)', padding: '20px 24px', marginBottom: 20 }}>
<div style={{ fontSize: 13, fontWeight: 700, marginBottom: 14, color: 'var(--cyan)' }}> API </div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginBottom: 16 }}>
<div className="bg-bg-3 border border-border rounded-md p-5 mb-5">
<div className="text-[13px] font-bold mb-3.5 text-primary-cyan"> API </div>
<div className="grid gap-3 mb-4" style={{ gridTemplateColumns: '1fr 1fr' }}>
<div>
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 5 }}>API Endpoint URL</label>
<label className="block text-[11px] font-semibold text-text-2 mb-1.5">API Endpoint URL</label>
<input type="text" value={configEndpoint} onChange={e => setConfigEndpoint(e.target.value)} placeholder="https://api.haewoon.or.kr/v1/..."
style={{ width: '100%', padding: '9px 12px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontFamily: 'var(--fM)', fontSize: 12, outline: 'none', boxSizing: 'border-box' }} />
className="w-full px-3 py-2 bg-bg-0 border border-border rounded-sm font-mono text-xs outline-none box-border" />
</div>
<div>
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 5 }}>API Key</label>
<label className="block text-[11px] font-semibold text-text-2 mb-1.5">API Key</label>
<input type="password" value={configApiKey} onChange={e => setConfigApiKey(e.target.value)} placeholder="발급받은 API Key 입력"
style={{ width: '100%', padding: '9px 12px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontFamily: 'var(--fM)', fontSize: 12, outline: 'none', boxSizing: 'border-box' }} />
className="w-full px-3 py-2 bg-bg-0 border border-border rounded-sm font-mono text-xs outline-none box-border" />
</div>
<div>
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 5 }}> </label>
<select value={configKeyType} onChange={e => setConfigKeyType(e.target.value)} className="prd-i" style={{ borderColor: 'var(--bd)', width: '100%' }}>
<label className="block text-[11px] font-semibold text-text-2 mb-1.5"> </label>
<select value={configKeyType} onChange={e => setConfigKeyType(e.target.value)} className="prd-i w-full" style={{ borderColor: 'var(--bd)' }}>
<option value="mmsi">MMSI</option>
<option value="imo">IMO </option>
<option value="shipname"></option>
@ -137,20 +136,20 @@ function ShipInsurance() {
</select>
</div>
<div>
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 5 }}> </label>
<select value={configRespType} onChange={e => setConfigRespType(e.target.value)} className="prd-i" style={{ borderColor: 'var(--bd)', width: '100%' }}>
<label className="block text-[11px] font-semibold text-text-2 mb-1.5"> </label>
<select value={configRespType} onChange={e => setConfigRespType(e.target.value)} className="prd-i w-full" style={{ borderColor: 'var(--bd)' }}>
<option value="json">JSON</option>
<option value="xml">XML</option>
</select>
</div>
</div>
<div style={{ display: 'flex', gap: 8 }}>
<button onClick={handleSaveConfig} style={{ padding: '9px 20px', background: 'linear-gradient(135deg, var(--cyan), var(--blue))', color: '#fff', border: 'none', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 700, cursor: 'pointer' }}>💾 </button>
<button onClick={() => setShowConfig(false)} style={{ padding: '9px 16px', background: 'var(--bg0)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: 12, cursor: 'pointer' }}></button>
<div className="flex gap-2">
<button onClick={handleSaveConfig} className="px-5 py-2 text-white border-none rounded-sm text-xs font-bold cursor-pointer" style={{ background: 'linear-gradient(135deg, var(--cyan), var(--blue))' }}>💾 </button>
<button onClick={() => setShowConfig(false)} className="px-4 py-2 bg-bg-0 text-text-2 border border-border rounded-sm text-xs cursor-pointer"></button>
</div>
{/* API 연동 안내 */}
<div style={{ marginTop: 16, padding: '12px 16px', background: 'rgba(6,182,212,.05)', border: '1px solid rgba(6,182,212,.15)', borderRadius: 'var(--rS)', fontSize: 10, color: 'var(--t3)', lineHeight: 1.8 }}>
<span style={{ color: 'var(--cyan)', fontWeight: 700 }}>📋 API </span><br />
<div className="mt-4 px-4 py-3 rounded-sm text-[10px] text-text-3 leading-[1.8]" style={{ background: 'rgba(6,182,212,.05)', border: '1px solid rgba(6,182,212,.15)' }}>
<span className="text-primary-cyan font-bold">📋 API </span><br />
IT지원팀에 API <br />
<br />
데이터: P&I , , , , ,
@ -159,26 +158,26 @@ function ShipInsurance() {
)}
{/* ── 검색 영역 ── */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)', padding: '18px 20px', marginBottom: 16 }}>
<div style={{ fontSize: 12, fontWeight: 700, marginBottom: 12, color: 'var(--t2)' }}>🔍 </div>
<div style={{ display: 'flex', gap: 8, alignItems: 'flex-end', flexWrap: 'wrap' }}>
<div className="bg-bg-3 border border-border rounded-md px-5 py-4 mb-4">
<div className="text-xs font-bold mb-3 text-text-2">🔍 </div>
<div className="flex gap-2 items-end flex-wrap">
<div>
<label style={{ display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)', marginBottom: 4 }}> </label>
<select value={searchType} onChange={e => setSearchType(e.target.value)} className="prd-i" style={{ borderColor: 'var(--bd)', minWidth: 120 }}>
<label className="block text-[10px] font-semibold text-text-3 mb-1"> </label>
<select value={searchType} onChange={e => setSearchType(e.target.value)} className="prd-i min-w-[120px]" style={{ borderColor: 'var(--bd)' }}>
<option value="mmsi">MMSI</option>
<option value="imo">IMO </option>
<option value="shipname"></option>
<option value="callsign"></option>
</select>
</div>
<div style={{ flex: 1, minWidth: 220 }}>
<label style={{ display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)', marginBottom: 4 }}></label>
<div className="flex-1 min-w-[220px]">
<label className="block text-[10px] font-semibold text-text-3 mb-1"></label>
<input type="text" value={searchVal} onChange={e => setSearchVal(e.target.value)} placeholder={placeholderMap[searchType]}
style={{ width: '100%', padding: '9px 14px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontFamily: 'var(--fM)', fontSize: 13, outline: 'none', boxSizing: 'border-box' }} />
className="w-full px-3.5 py-2 bg-bg-0 border border-border rounded-sm font-mono text-[13px] outline-none box-border" />
</div>
<div>
<label style={{ display: 'block', fontSize: 10, fontWeight: 600, color: 'var(--t3)', marginBottom: 4 }}> </label>
<select value={insTypeFilter} onChange={e => setInsTypeFilter(e.target.value)} className="prd-i" style={{ borderColor: 'var(--bd)', minWidth: 140 }}>
<label className="block text-[10px] font-semibold text-text-3 mb-1"> </label>
<select value={insTypeFilter} onChange={e => setInsTypeFilter(e.target.value)} className="prd-i min-w-[140px]" style={{ borderColor: 'var(--bd)' }}>
<option></option>
<option>P&I </option>
<option></option>
@ -186,8 +185,8 @@ function ShipInsurance() {
<option></option>
</select>
</div>
<button onClick={handleQuery} style={{ padding: '9px 24px', background: 'linear-gradient(135deg, var(--cyan), var(--blue))', color: '#fff', border: 'none', borderRadius: 'var(--rS)', fontSize: 13, fontWeight: 700, cursor: 'pointer', flexShrink: 0 }}>🔍 </button>
<button onClick={handleBatchQuery} style={{ padding: '9px 18px', background: 'rgba(168,85,247,.12)', color: 'var(--purple)', border: '1px solid rgba(168,85,247,.3)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer', flexShrink: 0 }}>📋 </button>
<button onClick={handleQuery} className="px-6 py-2 text-white border-none rounded-sm text-[13px] font-bold cursor-pointer shrink-0" style={{ background: 'linear-gradient(135deg, var(--cyan), var(--blue))' }}>🔍 </button>
<button onClick={handleBatchQuery} className="px-4 py-2 text-[12px] font-semibold cursor-pointer shrink-0 rounded-sm" style={{ background: 'rgba(168,85,247,.12)', color: 'var(--purple)', border: '1px solid rgba(168,85,247,.3)' }}>📋 </button>
</div>
</div>
@ -195,26 +194,26 @@ function ShipInsurance() {
{/* 초기 안내 상태 */}
{viewState === 'empty' && (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '60px 20px', background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)' }}>
<div style={{ fontSize: 48, marginBottom: 16, opacity: 0.3 }}>🛡</div>
<div style={{ fontSize: 14, fontWeight: 700, color: 'var(--t2)', marginBottom: 8 }}> API </div>
<div style={{ fontSize: 12, color: 'var(--t3)', textAlign: 'center', lineHeight: 1.8 }}>
<div className="flex flex-col items-center justify-center py-16 px-5 bg-bg-3 border border-border rounded-md">
<div className="text-[48px] mb-4 opacity-30">🛡</div>
<div className="text-sm font-bold text-text-2 mb-2"> API </div>
<div className="text-xs text-text-3 text-center leading-[1.8]">
API API Key를 <br />
MMSI·IMO· .<br />
<span style={{ color: 'var(--cyan)' }}> </span> .
<span className="text-primary-cyan"> </span> .
</div>
<div style={{ marginTop: 20, display: 'flex', gap: 10 }}>
<button onClick={() => setShowConfig(true)} style={{ padding: '10px 20px', background: 'rgba(6,182,212,.12)', color: 'var(--cyan)', border: '1px solid rgba(6,182,212,.3)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}> API </button>
<button onClick={loadDemoData} style={{ padding: '10px 20px', background: 'var(--bg0)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}>📊 </button>
<div className="mt-5 flex gap-2.5">
<button onClick={() => setShowConfig(true)} className="px-5 py-2.5 text-xs font-semibold cursor-pointer rounded-sm" style={{ background: 'rgba(6,182,212,.12)', color: 'var(--cyan)', border: '1px solid rgba(6,182,212,.3)' }}> API </button>
<button onClick={loadDemoData} className="px-5 py-2.5 bg-bg-0 text-text-2 border border-border rounded-sm text-xs font-semibold cursor-pointer">📊 </button>
</div>
</div>
)}
{/* 로딩 */}
{viewState === 'loading' && (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 60, background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)' }}>
<div style={{ width: 36, height: 36, border: '3px solid var(--bd)', borderTopColor: 'var(--cyan)', borderRadius: '50%', animation: 'spin 0.8s linear infinite', marginBottom: 14 }} />
<div style={{ fontSize: 13, color: 'var(--t2)' }}> API ...</div>
<div className="flex flex-col items-center justify-center p-16 bg-bg-3 border border-border rounded-md">
<div className="w-9 h-9 rounded-full mb-3.5" style={{ border: '3px solid var(--bd)', borderTopColor: 'var(--cyan)', animation: 'spin 0.8s linear infinite' }} />
<div className="text-[13px] text-text-2"> API ...</div>
</div>
)}
@ -222,33 +221,33 @@ function ShipInsurance() {
{viewState === 'result' && (
<>
{/* 요약 카드 */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 10, marginBottom: 14 }}>
<div className="grid gap-2.5 mb-3.5" style={{ gridTemplateColumns: 'repeat(4, 1fr)' }}>
{[
{ label: '전체', val: resultData.length, color: 'var(--cyan)', bg: 'rgba(6,182,212,.08)' },
{ label: '유효', val: validCount, color: 'var(--green)', bg: 'rgba(34,197,94,.08)' },
{ label: '만료임박(30일)', val: soonList.length, color: 'var(--yellow)', bg: 'rgba(234,179,8,.08)' },
{ label: '만료/미가입', val: resultData.length - validCount, color: 'var(--red)', bg: 'rgba(239,68,68,.08)' },
].map((c, i) => (
<div key={i} style={{ padding: '14px 16px', background: c.bg, border: `1px solid ${c.color}33`, borderRadius: 'var(--rS)', textAlign: 'center' }}>
<div style={{ fontSize: 22, fontWeight: 800, color: c.color, fontFamily: 'var(--fM)' }}>{c.val}</div>
<div style={{ fontSize: 10, color: 'var(--t3)', marginTop: 2 }}>{c.label}</div>
<div key={i} className="px-4 py-3.5 text-center rounded-sm" style={{ background: c.bg, border: `1px solid ${c.color}33` }}>
<div className="text-[22px] font-extrabold font-mono" style={{ color: c.color }}>{c.val}</div>
<div className="text-[10px] text-text-3 mt-0.5">{c.label}</div>
</div>
))}
</div>
{/* 테이블 */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rM)', overflow: 'hidden', marginBottom: 12 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px 16px', borderBottom: '1px solid var(--bd)' }}>
<div style={{ fontSize: 12, fontWeight: 700 }}> <span style={{ color: 'var(--cyan)' }}>{resultData.length}</span></div>
<div style={{ display: 'flex', gap: 6 }}>
<button onClick={() => alert('엑셀 내보내기 기능은 실제 API 연동 후 활성화됩니다.')} style={{ padding: '5px 12px', background: 'rgba(34,197,94,.1)', color: 'var(--green)', border: '1px solid rgba(34,197,94,.25)', borderRadius: 'var(--rS)', fontSize: 11, fontWeight: 600, cursor: 'pointer' }}>📥 </button>
<button onClick={handleQuery} style={{ padding: '5px 12px', background: 'var(--bg0)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: 11, cursor: 'pointer' }}>🔄 </button>
<div className="bg-bg-3 border border-border rounded-md overflow-hidden mb-3">
<div className="flex items-center justify-between px-4 py-3 border-b border-border">
<div className="text-xs font-bold"> <span className="text-primary-cyan">{resultData.length}</span></div>
<div className="flex gap-1.5">
<button onClick={() => alert('엑셀 내보내기 기능은 실제 API 연동 후 활성화됩니다.')} className="px-3 py-1 text-[11px] font-semibold cursor-pointer rounded-sm" style={{ background: 'rgba(34,197,94,.1)', color: 'var(--green)', border: '1px solid rgba(34,197,94,.25)' }}>📥 </button>
<button onClick={handleQuery} className="px-3 py-1 bg-bg-0 text-text-2 border border-border rounded-sm text-[11px] cursor-pointer">🔄 </button>
</div>
</div>
<div style={{ overflowX: 'auto' }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 11 }}>
<div className="overflow-x-auto">
<table className="w-full text-[11px] border-collapse">
<thead>
<tr style={{ background: 'var(--bg0)' }}>
<tr className="bg-bg-0">
{[
{ label: '선박명', align: 'left' },
{ label: 'MMSI', align: 'center' },
@ -260,7 +259,7 @@ function ShipInsurance() {
{ label: '보상한도', align: 'right' },
{ label: '상태', align: 'center' },
].map((h, i) => (
<th key={i} style={{ padding: '10px 14px', textAlign: h.align as 'left' | 'center' | 'right', fontWeight: 700, color: 'var(--t2)', borderBottom: '1px solid var(--bd)', whiteSpace: 'nowrap' }}>{h.label}</th>
<th key={i} className="px-3.5 py-2.5 font-bold text-text-2 border-b border-border whitespace-nowrap" style={{ textAlign: h.align as 'left' | 'center' | 'right' }}>{h.label}</th>
))}
</tr>
</thead>
@ -270,18 +269,17 @@ function ShipInsurance() {
const isExp = st === 'expired'
const isSoon = st === 'soon'
return (
<tr key={i} style={{ borderBottom: '1px solid var(--bd)', background: isExp ? 'rgba(239,68,68,.03)' : undefined }}>
<td style={{ padding: '10px 14px', fontWeight: 600 }}>{r.shipName}</td>
<td style={{ padding: '10px 14px', textAlign: 'center', fontFamily: 'var(--fM)', fontSize: 11 }}>{r.mmsi || '—'}</td>
<td style={{ padding: '10px 14px', textAlign: 'center', fontFamily: 'var(--fM)', fontSize: 11 }}>{r.imo || '—'}</td>
<td style={{ padding: '10px 14px', textAlign: 'center' }}>{r.insType}</td>
<td style={{ padding: '10px 14px', textAlign: 'center' }}>{r.insurer}</td>
<td style={{ padding: '10px 14px', textAlign: 'center', fontFamily: 'var(--fM)', fontSize: 10, color: 'var(--t3)' }}>{r.policyNo}</td>
<td style={{ padding: '10px 14px', textAlign: 'center', fontFamily: 'var(--fM)', fontSize: 11, color: isExp ? 'var(--red)' : isSoon ? 'var(--yellow)' : undefined, fontWeight: isExp || isSoon ? 700 : undefined }}>{r.start} ~ {r.expiry}</td>
<td style={{ padding: '10px 14px', textAlign: 'right', fontWeight: 700, fontFamily: 'var(--fM)' }}>{r.limit}</td>
<td style={{ padding: '10px 14px', textAlign: 'center' }}>
<span style={{
padding: '3px 10px', borderRadius: 10, fontSize: 10, fontWeight: 600,
<tr key={i} className="border-b border-border" style={{ background: isExp ? 'rgba(239,68,68,.03)' : undefined }}>
<td className="px-3.5 py-2.5 font-semibold">{r.shipName}</td>
<td className="px-3.5 py-2.5 text-center font-mono text-[11px]">{r.mmsi || '—'}</td>
<td className="px-3.5 py-2.5 text-center font-mono text-[11px]">{r.imo || '—'}</td>
<td className="px-3.5 py-2.5 text-center">{r.insType}</td>
<td className="px-3.5 py-2.5 text-center">{r.insurer}</td>
<td className="px-3.5 py-2.5 text-center font-mono text-[10px] text-text-3">{r.policyNo}</td>
<td className="px-3.5 py-2.5 text-center font-mono text-[11px]" style={{ color: isExp ? 'var(--red)' : isSoon ? 'var(--yellow)' : undefined, fontWeight: isExp || isSoon ? 700 : undefined }}>{r.start} ~ {r.expiry}</td>
<td className="px-3.5 py-2.5 text-right font-bold font-mono">{r.limit}</td>
<td className="px-3.5 py-2.5 text-center">
<span className="px-2.5 py-0.5 rounded-full text-[10px] font-semibold" style={{
background: isExp ? 'rgba(239,68,68,.15)' : isSoon ? 'rgba(234,179,8,.15)' : 'rgba(34,197,94,.15)',
color: isExp ? 'var(--red)' : isSoon ? 'var(--yellow)' : 'var(--green)',
}}>
@ -298,12 +296,12 @@ function ShipInsurance() {
{/* 경고 */}
{(expiredList.length > 0 || soonList.length > 0) && (
<div style={{ padding: '12px 16px', background: 'rgba(234,179,8,.06)', border: '1px solid rgba(234,179,8,.25)', borderRadius: 'var(--rS)', fontSize: 12, color: 'var(--t2)', marginBottom: 12 }}>
<div className="px-4 py-3 text-xs text-text-2 mb-3 rounded-sm" style={{ background: 'rgba(234,179,8,.06)', border: '1px solid rgba(234,179,8,.25)' }}>
{expiredList.length > 0 && (
<><span style={{ color: 'var(--red)', fontWeight: 700 }}> {expiredList.length}:</span> {expiredList.map(r => r.shipName).join(', ')}<br /></>
<><span className="text-status-red font-bold"> {expiredList.length}:</span> {expiredList.map(r => r.shipName).join(', ')}<br /></>
)}
{soonList.length > 0 && (
<><span style={{ color: 'var(--yellow)', fontWeight: 700 }}> (30) {soonList.length}:</span> {soonList.map(r => r.shipName).join(', ')}</>
<><span className="font-bold" style={{ color: 'var(--yellow)' }}> (30) {soonList.length}:</span> {soonList.map(r => r.shipName).join(', ')}</>
)}
</div>
)}
@ -311,15 +309,15 @@ function ShipInsurance() {
)}
{/* ── API 연동 정보 푸터 ── */}
<div style={{ marginTop: 16, padding: '12px 16px', background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ fontSize: 10, color: 'var(--t3)', lineHeight: 1.7 }}>
<span style={{ color: 'var(--t2)', fontWeight: 700 }}> :</span> (KSA) · haewoon.or.kr<br />
<span style={{ color: 'var(--t2)', fontWeight: 700 }}> :</span> REST API (JSON) · · TTL 1
<div className="mt-4 px-4 py-3 bg-bg-3 border border-border rounded-sm flex items-center justify-between">
<div className="text-[10px] text-text-3 leading-[1.7]">
<span className="text-text-2 font-bold"> :</span> (KSA) · haewoon.or.kr<br />
<span className="text-text-2 font-bold"> :</span> REST API (JSON) · · TTL 1
</div>
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
<span style={{ fontSize: 10, color: 'var(--t3)' }}> :</span>
<span style={{ fontSize: 10, color: 'var(--t2)', fontFamily: 'var(--fM)' }}>{lastSync}</span>
<button onClick={handleFullSync} style={{ padding: '4px 10px', background: 'var(--bg0)', color: 'var(--t2)', border: '1px solid var(--bd)', borderRadius: 4, fontSize: 10, cursor: 'pointer' }}> </button>
<div className="flex gap-1.5 items-center">
<span className="text-[10px] text-text-3"> :</span>
<span className="text-[10px] text-text-2 font-mono">{lastSync}</span>
<button onClick={handleFullSync} className="px-2.5 py-1 bg-bg-0 text-text-2 border border-border rounded text-[10px] cursor-pointer"> </button>
</div>
</div>
</div>

파일 보기

@ -375,30 +375,29 @@ export function BoardView() {
{/* 업로드 모달 */}
{showUploadModal && (
<div style={{ position: 'fixed', inset: 0, zIndex: 9999, display: 'flex', alignItems: 'center', justifyContent: 'center', background: 'rgba(0,0,0,.55)' }}
<div className="fixed inset-0 z-[9999] flex items-center justify-center" style={{ background: 'rgba(0,0,0,.55)' }}
onClick={() => { setShowUploadModal(false); setEditingManualId(null) }}>
<div style={{ width: 480, background: 'var(--bg1)', border: '1px solid var(--bd)', borderRadius: 12, overflow: 'hidden' }}
<div className="w-[480px] bg-bg-1 border border-border rounded-xl overflow-hidden"
onClick={e => e.stopPropagation()}>
<div style={{ padding: '16px 20px', borderBottom: '1px solid var(--bd)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ fontSize: 16 }}>{editingManualId ? '✏️' : '📤'}</span>
<span style={{ fontSize: 14, fontWeight: 700 }}>{editingManualId ? '매뉴얼 수정' : '매뉴얼 업로드'}</span>
<div className="px-5 py-4 border-b border-border flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-base">{editingManualId ? '✏️' : '📤'}</span>
<span className="text-sm font-bold">{editingManualId ? '매뉴얼 수정' : '매뉴얼 업로드'}</span>
</div>
<span onClick={() => { setShowUploadModal(false); setEditingManualId(null) }}
style={{ cursor: 'pointer', color: 'var(--t3)', fontSize: 16, lineHeight: 1 }}></span>
className="cursor-pointer text-text-3 text-base leading-none"></span>
</div>
<div style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 16 }}>
<div className="p-5 flex flex-col gap-4">
<div>
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 6 }}></label>
<div style={{ display: 'flex', gap: 6 }}>
<label className="block text-[11px] font-semibold text-text-2 mb-1.5"></label>
<div className="flex gap-1.5">
{['방제매뉴얼', '대응매뉴얼', '교육자료', '법령·규정'].map(cat => {
const cc = catColor(cat)
const isActive = uploadForm.category === cat
return (
<button key={cat} onClick={() => setUploadForm(prev => ({ ...prev, category: cat }))}
className="flex-1 py-2 px-1 rounded-md text-[11px] font-semibold cursor-pointer"
style={{
flex: 1, padding: '8px 4px', borderRadius: 6, fontSize: 11, fontWeight: 600,
cursor: 'pointer',
background: isActive ? cc.bg : 'var(--bg3)',
border: isActive ? `1px solid ${cc.text}40` : '1px solid var(--bd)',
color: isActive ? cc.text : 'var(--t3)',
@ -410,23 +409,20 @@ export function BoardView() {
</div>
</div>
<div>
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 6 }}> </label>
<label className="block text-[11px] font-semibold text-text-2 mb-1.5"> </label>
<input type="text" placeholder="매뉴얼 제목을 입력하세요" value={uploadForm.title}
onChange={e => setUploadForm(prev => ({ ...prev, title: e.target.value }))}
style={{ width: '100%', padding: '10px 12px', borderRadius: 6, fontSize: 12, background: 'var(--bg2)', border: '1px solid var(--bd)', outline: 'none', boxSizing: 'border-box' }} />
className="w-full px-3 py-2.5 rounded-md text-xs bg-bg-2 border border-border outline-none" style={{ boxSizing: 'border-box' }} />
</div>
<div>
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 6 }}></label>
<label className="block text-[11px] font-semibold text-text-2 mb-1.5"></label>
<input type="text" placeholder="예: v1.0" value={uploadForm.version}
onChange={e => setUploadForm(prev => ({ ...prev, version: e.target.value }))}
style={{ width: '100%', padding: '10px 12px', borderRadius: 6, fontSize: 12, background: 'var(--bg2)', border: '1px solid var(--bd)', outline: 'none', boxSizing: 'border-box' }} />
className="w-full px-3 py-2.5 rounded-md text-xs bg-bg-2 border border-border outline-none" style={{ boxSizing: 'border-box' }} />
</div>
<div>
<label style={{ display: 'block', fontSize: 11, fontWeight: 600, color: 'var(--t2)', marginBottom: 6 }}> </label>
<div style={{
border: '2px dashed var(--bd)', borderRadius: 8, padding: '24px 16px', textAlign: 'center',
background: 'var(--bg2)', cursor: 'pointer', position: 'relative',
}}
<label className="block text-[11px] font-semibold text-text-2 mb-1.5"> </label>
<div className="border-2 border-dashed border-border rounded-md py-6 px-4 text-center bg-bg-2 cursor-pointer relative"
onClick={() => {
const input = document.createElement('input')
input.type = 'file'
@ -441,28 +437,28 @@ export function BoardView() {
input.click()
}}>
{uploadForm.fileName ? (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8 }}>
<span style={{ fontSize: 20 }}>📄</span>
<div style={{ textAlign: 'left' }}>
<div style={{ fontSize: 12, fontWeight: 600 }}>{uploadForm.fileName}</div>
<div style={{ fontSize: 10, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{uploadForm.fileSize}</div>
<div className="flex items-center justify-center gap-2">
<span className="text-xl">📄</span>
<div className="text-left">
<div className="text-xs font-semibold">{uploadForm.fileName}</div>
<div className="text-[10px] text-text-3 font-mono">{uploadForm.fileSize}</div>
</div>
<span onClick={(e) => { e.stopPropagation(); setUploadForm(prev => ({ ...prev, fileName: '', fileSize: '' })) }}
style={{ fontSize: 12, color: 'var(--t3)', cursor: 'pointer', marginLeft: 8 }}></span>
className="text-xs text-text-3 cursor-pointer ml-2"></span>
</div>
) : (
<>
<div style={{ fontSize: 28, opacity: 0.3, marginBottom: 6 }}>📁</div>
<div style={{ fontSize: 11, color: 'var(--t3)' }}> </div>
<div style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fM)', marginTop: 4 }}>PDF, DOC, HWP, XLSX ( 100MB)</div>
<div className="text-[28px] opacity-30 mb-1.5">📁</div>
<div className="text-[11px] text-text-3"> </div>
<div className="text-[9px] text-text-3 font-mono mt-1">PDF, DOC, HWP, XLSX ( 100MB)</div>
</>
)}
</div>
</div>
</div>
<div style={{ padding: '12px 20px', borderTop: '1px solid var(--bd)', display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
<div className="px-5 py-3 border-t border-border flex justify-end gap-2">
<button onClick={() => { setShowUploadModal(false); setEditingManualId(null) }}
style={{ padding: '8px 20px', borderRadius: 6, fontSize: 12, fontWeight: 600, background: 'var(--bg3)', border: '1px solid var(--bd)', color: 'var(--t3)', cursor: 'pointer' }}>
className="px-5 py-2 rounded-md text-xs font-semibold bg-bg-3 border border-border text-text-3 cursor-pointer">
</button>
<button onClick={async () => {
@ -495,7 +491,7 @@ export function BoardView() {
alert((err as { message?: string })?.message || '저장에 실패했습니다.')
}
}}
style={{ padding: '8px 24px', borderRadius: 6, fontSize: 12, fontWeight: 600, background: 'rgba(6,182,212,.2)', border: '1px solid rgba(6,182,212,.35)', color: '#22d3ee', cursor: 'pointer' }}>
className="px-6 py-2 rounded-md text-xs font-semibold cursor-pointer" style={{ background: 'rgba(6,182,212,.2)', border: '1px solid rgba(6,182,212,.35)', color: '#22d3ee' }}>
{editingManualId ? '✏️ 수정' : '📤 업로드'}
</button>
</div>

파일 보기

@ -55,122 +55,78 @@ export function HNSAnalysisListTable({ onTabChange }: HNSAnalysisListTableProps)
}, [loadData])
return (
<div style={{
display: 'flex',
flexDirection: 'column',
height: '100%',
background: 'var(--bg0)',
}}>
<div className="flex flex-col h-full bg-bg-0">
{/* Header */}
<div style={{
padding: '16px 20px',
borderBottom: '1px solid var(--bd)',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
background: 'var(--bg1)'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
<div style={{
fontSize: '16px',
fontWeight: 700,
display: 'flex',
alignItems: 'center',
gap: '8px'
}}>
<span style={{ fontSize: '18px' }}>📋</span>
<div className="px-5 py-4 border-b border-border flex justify-between items-center bg-bg-1">
<div className="flex items-center gap-3">
<div className="text-base font-bold flex items-center gap-2">
<span className="text-[18px]">📋</span>
HNS
</div>
<span style={{
fontSize: '10px',
color: 'var(--t3)',
background: 'var(--bg3)',
padding: '4px 10px',
borderRadius: '12px',
fontFamily: 'var(--fM)'
}}>
<span className="text-[10px] text-text-3 bg-bg-3 font-mono" style={{ padding: '4px 10px', borderRadius: '12px' }}>
{analyses.length}
</span>
</div>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<div className="flex gap-2 items-center">
<input
type="text"
placeholder="검색..."
style={{
padding: '8px 12px',
background: 'var(--bg3)',
border: '1px solid var(--bd)',
borderRadius: '6px',
fontSize: '11px',
width: '200px',
}}
className="bg-bg-3 border border-border rounded-sm text-[11px]"
style={{ padding: '8px 12px', width: '200px' }}
/>
<button
onClick={() => onTabChange('analysis')}
className="text-status-orange text-[11px] font-semibold cursor-pointer flex items-center gap-1"
style={{
padding: '8px 16px',
borderRadius: '6px',
border: '1px solid rgba(249,115,22,0.3)',
background: 'linear-gradient(135deg, rgba(249,115,22,0.15), rgba(239,68,68,0.1))',
color: 'var(--orange)',
fontSize: '11px',
fontWeight: 600,
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
gap: '4px'
}}
>
<span style={{ fontSize: '14px' }}>+</span>
<span className="text-sm">+</span>
</button>
</div>
</div>
{/* Table */}
<div style={{ flex: 1, overflow: 'auto' }}>
<div className="flex-1 overflow-auto">
{loading ? (
<div style={{ textAlign: 'center', padding: '80px 0', color: 'var(--t3)', fontSize: '12px' }}> ...</div>
<div className="text-center text-text-3 text-[12px]" style={{ padding: '80px 0' }}> ...</div>
) : (
<table style={{
width: '100%',
borderCollapse: 'collapse',
fontSize: '11px'
}}>
<thead style={{ position: 'sticky', top: 0, zIndex: 10 }}>
<tr style={{
background: 'var(--bg2)',
borderBottom: '1px solid var(--bd)'
}}>
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', width: '50px' }}></th>
<th style={{ padding: '12px 16px', textAlign: 'left', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', minWidth: '180px' }}></th>
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', minWidth: '100px' }}></th>
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', minWidth: '130px' }}></th>
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', minWidth: '100px' }}></th>
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', minWidth: '120px' }}></th>
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', minWidth: '90px' }}></th>
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', minWidth: '80px' }}></th>
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', width: '70px' }}></th>
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', width: '70px' }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px' }}>
<table className="w-full text-[11px]" style={{ borderCollapse: 'collapse' }}>
<thead className="sticky top-0 z-10">
<tr className="bg-bg-2 border-b border-border">
<th className="text-center text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', width: '50px' }}></th>
<th className="text-left text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', minWidth: '180px' }}></th>
<th className="text-center text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', minWidth: '100px' }}></th>
<th className="text-center text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', minWidth: '130px' }}></th>
<th className="text-center text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', minWidth: '100px' }}></th>
<th className="text-center text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', minWidth: '120px' }}></th>
<th className="text-center text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', minWidth: '90px' }}></th>
<th className="text-center text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', minWidth: '80px' }}></th>
<th className="text-center text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', width: '70px' }}></th>
<th className="text-center text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', width: '70px' }}>
<div className="flex flex-col items-center gap-0.5">
<span>AEGL-3</span>
<span style={{ fontSize: '8px', color: 'var(--t3)' }}></span>
<span className="text-[8px] text-text-3"></span>
</div>
</th>
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', width: '70px' }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px' }}>
<th className="text-center text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', width: '70px' }}>
<div className="flex flex-col items-center gap-0.5">
<span>AEGL-2</span>
<span style={{ fontSize: '8px', color: 'var(--t3)' }}></span>
<span className="text-[8px] text-text-3"></span>
</div>
</th>
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', width: '70px' }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '2px' }}>
<th className="text-center text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', width: '70px' }}>
<div className="flex flex-col items-center gap-0.5">
<span>AEGL-1</span>
<span style={{ fontSize: '8px', color: 'var(--t3)' }}></span>
<span className="text-[8px] text-text-3"></span>
</div>
</th>
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', minWidth: '80px' }}></th>
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', minWidth: '90px' }}></th>
<th style={{ padding: '12px 16px', textAlign: 'center', fontSize: '10px', fontWeight: 600, color: 'var(--t2)', minWidth: '120px' }}></th>
<th className="text-center text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', minWidth: '80px' }}></th>
<th className="text-center text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', minWidth: '90px' }}></th>
<th className="text-center text-[10px] font-semibold text-text-2" style={{ padding: '12px 16px', minWidth: '120px' }}></th>
</tr>
</thead>
<tbody>
@ -187,90 +143,74 @@ export function HNSAnalysisListTable({ onTabChange }: HNSAnalysisListTableProps)
return (
<tr
key={item.hnsAnlysSn}
className="border-b border-border cursor-pointer"
style={{
borderBottom: '1px solid var(--bd)',
cursor: 'pointer',
transition: 'background 0.15s',
background: index % 2 === 0 ? 'transparent' : 'rgba(255,255,255,0.01)'
}}
onMouseEnter={(e) => e.currentTarget.style.background = 'var(--bg2)'}
onMouseLeave={(e) => e.currentTarget.style.background = index % 2 === 0 ? 'transparent' : 'rgba(255,255,255,0.01)'}
>
<td style={{ padding: '12px 16px', textAlign: 'center', color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{item.hnsAnlysSn}</td>
<td style={{ padding: '12px 16px', fontWeight: 500 }}>{item.anlysNm}</td>
<td style={{ padding: '12px 16px', textAlign: 'center' }}>
<span style={{
padding: '4px 8px',
borderRadius: '4px',
fontSize: '9px',
fontWeight: 600,
background: 'rgba(249,115,22,0.12)',
color: 'var(--orange)'
}}>
<td className="text-center text-text-3 font-mono" style={{ padding: '12px 16px' }}>{item.hnsAnlysSn}</td>
<td className="font-medium" style={{ padding: '12px 16px' }}>{item.anlysNm}</td>
<td className="text-center" style={{ padding: '12px 16px' }}>
<span
className="text-[9px] font-semibold text-status-orange"
style={{ padding: '4px 8px', borderRadius: '4px', background: 'rgba(249,115,22,0.12)' }}
>
{substanceTag(item.sbstNm)}
</span>
</td>
<td style={{ padding: '12px 16px', textAlign: 'center', color: 'var(--t2)', fontFamily: 'var(--fM)', fontSize: '10px' }}>{formatDate(item.acdntDtm, 'full')}</td>
<td style={{ padding: '12px 16px', textAlign: 'center', color: 'var(--t3)', fontFamily: 'var(--fM)', fontSize: '10px' }}>{formatDate(item.regDtm, 'date')}</td>
<td style={{ padding: '12px 16px', textAlign: 'center', color: 'var(--t2)' }}>{item.locNm || '—'}</td>
<td style={{ padding: '12px 16px', textAlign: 'center', color: 'var(--t2)', fontFamily: 'var(--fM)' }}>{amount}</td>
<td style={{ padding: '12px 16px', textAlign: 'center' }}>
<span style={{
padding: '4px 8px',
borderRadius: '4px',
fontSize: '9px',
fontWeight: 600,
background: 'rgba(6,182,212,0.12)',
color: 'var(--cyan)'
}}>
<td className="text-center text-text-2 font-mono text-[10px]" style={{ padding: '12px 16px' }}>{formatDate(item.acdntDtm, 'full')}</td>
<td className="text-center text-text-3 font-mono text-[10px]" style={{ padding: '12px 16px' }}>{formatDate(item.regDtm, 'date')}</td>
<td className="text-center text-text-2" style={{ padding: '12px 16px' }}>{item.locNm || '—'}</td>
<td className="text-center text-text-2 font-mono" style={{ padding: '12px 16px' }}>{amount}</td>
<td className="text-center" style={{ padding: '12px 16px' }}>
<span
className="text-[9px] font-semibold text-primary-cyan"
style={{ padding: '4px 8px', borderRadius: '4px', background: 'rgba(6,182,212,0.12)' }}
>
{item.algoCd || '—'}
</span>
</td>
<td style={{ padding: '12px 16px', textAlign: 'center', color: 'var(--t2)', fontFamily: 'var(--fM)' }}>{item.fcstHr ? `${item.fcstHr}H` : '—'}</td>
<td style={{ padding: '12px 16px', textAlign: 'center' }}>
<div style={{
width: '24px',
height: '24px',
borderRadius: '50%',
background: aegl3 ? 'rgba(239,68,68,0.8)' : 'rgba(255,255,255,0.1)',
margin: '0 auto',
border: aegl3 ? 'none' : '1px solid var(--bd)'
}} />
<td className="text-center text-text-2 font-mono" style={{ padding: '12px 16px' }}>{item.fcstHr ? `${item.fcstHr}H` : '—'}</td>
<td className="text-center" style={{ padding: '12px 16px' }}>
<div
className="w-6 h-6 rounded-full mx-auto"
style={{
background: aegl3 ? 'rgba(239,68,68,0.8)' : 'rgba(255,255,255,0.1)',
border: aegl3 ? 'none' : '1px solid var(--bd)'
}}
/>
</td>
<td style={{ padding: '12px 16px', textAlign: 'center' }}>
<div style={{
width: '24px',
height: '24px',
borderRadius: '50%',
background: aegl2 ? 'rgba(249,115,22,0.8)' : 'rgba(255,255,255,0.1)',
margin: '0 auto',
border: aegl2 ? 'none' : '1px solid var(--bd)'
}} />
<td className="text-center" style={{ padding: '12px 16px' }}>
<div
className="w-6 h-6 rounded-full mx-auto"
style={{
background: aegl2 ? 'rgba(249,115,22,0.8)' : 'rgba(255,255,255,0.1)',
border: aegl2 ? 'none' : '1px solid var(--bd)'
}}
/>
</td>
<td style={{ padding: '12px 16px', textAlign: 'center' }}>
<div style={{
width: '24px',
height: '24px',
borderRadius: '50%',
background: aegl1 ? 'rgba(234,179,8,0.8)' : 'rgba(255,255,255,0.1)',
margin: '0 auto',
border: aegl1 ? 'none' : '1px solid var(--bd)'
}} />
<td className="text-center" style={{ padding: '12px 16px' }}>
<div
className="w-6 h-6 rounded-full mx-auto"
style={{
background: aegl1 ? 'rgba(234,179,8,0.8)' : 'rgba(255,255,255,0.1)',
border: aegl1 ? 'none' : '1px solid var(--bd)'
}}
/>
</td>
<td style={{ padding: '12px 16px', textAlign: 'center' }}>
<span style={{
padding: '4px 10px',
borderRadius: '4px',
fontSize: '9px',
fontWeight: 600,
background: riskStyle.bg,
color: riskStyle.color,
}}>
<td className="text-center" style={{ padding: '12px 16px' }}>
<span
className="text-[9px] font-semibold"
style={{ padding: '4px 10px', borderRadius: '4px', background: riskStyle.bg, color: riskStyle.color }}
>
{riskLabel}
</span>
</td>
<td style={{ padding: '12px 16px', textAlign: 'center', color: 'var(--t2)', fontFamily: 'var(--fM)' }}>{damageRadius}</td>
<td style={{ padding: '12px 16px', textAlign: 'center', color: 'var(--t3)', fontSize: '10px' }}>{item.analystNm || '—'}</td>
<td className="text-center text-text-2 font-mono" style={{ padding: '12px 16px' }}>{damageRadius}</td>
<td className="text-center text-text-3 text-[10px]" style={{ padding: '12px 16px' }}>{item.analystNm || '—'}</td>
</tr>
)
})}
@ -279,7 +219,7 @@ export function HNSAnalysisListTable({ onTabChange }: HNSAnalysisListTableProps)
)}
{!loading && analyses.length === 0 && (
<div style={{ textAlign: 'center', padding: '80px 0', color: 'var(--t3)', fontSize: '12px' }}> .</div>
<div className="text-center text-text-3 text-[12px]" style={{ padding: '80px 0' }}> .</div>
)}
</div>
</div>

파일 보기

@ -51,28 +51,24 @@ export function HNSLeftPanel({
return (
<div className="flex flex-col h-full bg-bg-1 border-r border-border overflow-hidden">
{/* Scrollable Content */}
<div className="flex-1 overflow-y-auto scrollbar-thin scrollbar-thumb-border-light scrollbar-track-transparent" style={{ background: 'var(--bg0)' }}>
<div className="flex-1 overflow-y-auto scrollbar-thin scrollbar-thumb-border-light scrollbar-track-transparent bg-bg-0">
{activeSubTab === 'analysis' && (
<div style={{ padding: '16px' }}>
<div className="p-4">
{/* Header */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '14px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<div style={{
width: '36px',
height: '36px',
borderRadius: '8px',
background: 'linear-gradient(135deg, rgba(249,115,22,0.15), rgba(168,85,247,0.1))',
border: '1px solid rgba(249,115,22,0.25)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '18px'
}}>🧪</div>
<div className="flex items-center justify-between mb-[14px]">
<div className="flex items-center gap-2.5">
<div
className="w-9 h-9 rounded-md flex items-center justify-center text-[18px]"
style={{
background: 'linear-gradient(135deg, rgba(249,115,22,0.15), rgba(168,85,247,0.1))',
border: '1px solid rgba(249,115,22,0.25)',
}}
>🧪</div>
<div>
<div style={{ fontSize: '14px', fontWeight: 700 }}>
<div className="text-sm font-bold">
HNS
</div>
<div style={{ fontSize: '9px', color: 'var(--t3)' }}>
<div className="text-[9px] text-text-3">
ALOHA/CAMEO
</div>
</div>
@ -80,20 +76,20 @@ export function HNSLeftPanel({
</div>
{/* Single Column Layout */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
<div className="flex flex-col gap-3">
{/* 사고 기본정보 */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px' }}>
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--cyan)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
<div className="bg-bg-3 border border-border rounded-md p-[14px]">
<div className="text-[11px] font-bold text-primary-cyan mb-3 flex items-center gap-1.5">
📋
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div className="flex flex-col gap-2">
{/* 사고명 */}
<div>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}></label>
<label className="hns-lbl text-[8px] text-text-3 block mb-1"></label>
<input
className="hns-inp"
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
className="hns-inp w-full"
style={{ padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
value={accidentName}
onChange={(e) => setAccidentName(e.target.value)}
/>
@ -102,16 +98,16 @@ export function HNSLeftPanel({
{/* 사고일시 + 예측시간 */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
<div>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}></label>
<label className="hns-lbl text-[8px] text-text-3 block mb-1"></label>
<input
className="hns-inp"
className="hns-inp w-full"
type="datetime-local"
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
style={{ padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
defaultValue="2025-02-11T05:02"
/>
</div>
<div>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}></label>
<label className="hns-lbl text-[8px] text-text-3 block mb-1"></label>
<ComboBox
className="hns-inp"
value={predictionTime}
@ -128,21 +124,21 @@ export function HNSLeftPanel({
</div>
{/* 사고지점 */}
<div style={{ padding: '10px', background: 'var(--bg0)', border: '1px solid rgba(6,182,212,0.2)', borderRadius: '8px' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', marginBottom: 0 }}>📍 </label>
<div
className="p-[10px] rounded-md"
style={{ background: 'var(--bg0)', border: '1px solid rgba(6,182,212,0.2)' }}
>
<div className="flex items-center justify-between mb-2">
<label className="hns-lbl text-[8px] text-text-3">📍 </label>
<button
onClick={onMapSelectClick}
className="text-primary-cyan text-[8px] font-bold cursor-pointer"
style={{
padding: '4px 10px',
borderRadius: '4px',
border: '1px solid rgba(6,182,212,0.3)',
background: 'rgba(6,182,212,0.08)',
color: 'var(--cyan)',
fontSize: '8px',
fontWeight: 700,
cursor: 'pointer',
transition: '0.15s'
transition: '0.15s'
}}
>
🗺
@ -150,29 +146,29 @@ export function HNSLeftPanel({
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px', marginBottom: '6px' }}>
<div>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}></label>
<label className="hns-lbl text-[8px] text-text-3 block mb-1"></label>
<input
className="hns-inp"
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
className="hns-inp w-full"
style={{ padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
value={incidentCoord.lat.toFixed(4)}
onChange={(e) => onCoordChange({ ...incidentCoord, lat: parseFloat(e.target.value) || 0 })}
/>
</div>
<div>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}></label>
<label className="hns-lbl text-[8px] text-text-3 block mb-1"></label>
<input
className="hns-inp"
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
className="hns-inp w-full"
style={{ padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
value={incidentCoord.lon.toFixed(4)}
onChange={(e) => onCoordChange({ ...incidentCoord, lon: parseFloat(e.target.value) || 0 })}
/>
</div>
</div>
<div>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}> </label>
<label className="hns-lbl text-[8px] text-text-3 block mb-1"> </label>
<input
className="hns-inp"
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
className="hns-inp w-full"
style={{ padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
value={locationName}
onChange={(e) => setLocationName(e.target.value)}
/>
@ -180,40 +176,43 @@ export function HNSLeftPanel({
</div>
{/* 기상 정보 */}
<div style={{ padding: '10px', background: 'linear-gradient(135deg, rgba(168,85,247,0.04), rgba(6,182,212,0.03))', border: '1px solid rgba(168,85,247,0.15)', borderRadius: '8px' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<div style={{ width: '6px', height: '6px', borderRadius: '50%', background: 'var(--green)' }}></div>
<span className="hns-lbl" style={{ fontSize: '8px', color: 'var(--purple)', marginBottom: 0 }}>🌬 ()</span>
<div
className="p-[10px] rounded-md"
style={{ background: 'linear-gradient(135deg, rgba(168,85,247,0.04), rgba(6,182,212,0.03))', border: '1px solid rgba(168,85,247,0.15)' }}
>
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-[5px]">
<div className="w-1.5 h-1.5 rounded-full bg-status-green"></div>
<span className="hns-lbl text-[8px] text-primary-purple">🌬 ()</span>
</div>
<span style={{ fontSize: '7px', color: 'var(--t3)', fontFamily: 'var(--fM)' }}>KMA API · AWS</span>
<span className="text-[7px] text-text-3 font-mono">KMA API · AWS</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: '6px', marginBottom: '6px' }}>
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--cyan)' }}>5.2</div>
<div style={{ fontSize: '7px', color: 'var(--t3)' }}>(m/s)</div>
<div className="text-center bg-bg-0 rounded border border-border" style={{ padding: '6px 2px' }}>
<div className="text-[12px] font-extrabold font-mono text-primary-cyan">5.2</div>
<div className="text-[7px] text-text-3">(m/s)</div>
</div>
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--cyan)' }}>SW 225°</div>
<div style={{ fontSize: '7px', color: 'var(--t3)' }}></div>
<div className="text-center bg-bg-0 rounded border border-border" style={{ padding: '6px 2px' }}>
<div className="text-[12px] font-extrabold font-mono text-primary-cyan">SW 225°</div>
<div className="text-[7px] text-text-3"></div>
</div>
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--orange)' }}>8.5°C</div>
<div style={{ fontSize: '7px', color: 'var(--t3)' }}></div>
<div className="text-center bg-bg-0 rounded border border-border" style={{ padding: '6px 2px' }}>
<div className="text-[12px] font-extrabold font-mono text-status-orange">8.5°C</div>
<div className="text-[7px] text-text-3"></div>
</div>
<div style={{ textAlign: 'center', padding: '6px 2px', background: 'var(--bg0)', borderRadius: '4px', border: '1px solid var(--bd)' }}>
<div style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--blue)' }}>62%</div>
<div style={{ fontSize: '7px', color: 'var(--t3)' }}></div>
<div className="text-center bg-bg-0 rounded border border-border" style={{ padding: '6px 2px' }}>
<div className="text-[12px] font-extrabold font-mono text-primary-blue">62%</div>
<div className="text-[7px] text-text-3"></div>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 6px', background: 'var(--bg0)', borderRadius: '3px', fontSize: '8px' }}>
<span style={{ color: 'var(--t3)' }}></span>
<span style={{ fontWeight: 600 }}>D ()</span>
<div className="flex justify-between bg-bg-0 text-[8px]" style={{ padding: '3px 6px', borderRadius: '3px' }}>
<span className="text-text-3"></span>
<span className="font-semibold">D ()</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '3px 6px', background: 'var(--bg0)', borderRadius: '3px', fontSize: '8px' }}>
<span style={{ color: 'var(--t3)' }}> </span>
<span style={{ fontWeight: 600 }}></span>
<div className="flex justify-between bg-bg-0 text-[8px]" style={{ padding: '3px 6px', borderRadius: '3px' }}>
<span className="text-text-3"> </span>
<span className="font-semibold"></span>
</div>
</div>
</div>
@ -221,7 +220,7 @@ export function HNSLeftPanel({
{/* 알고리즘 선택 */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
<div>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}> </label>
<label className="hns-lbl text-[8px] text-text-3 block mb-1"> </label>
<ComboBox
className="hns-inp"
value={algorithm}
@ -235,7 +234,7 @@ export function HNSLeftPanel({
/>
</div>
<div>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}> </label>
<label className="hns-lbl text-[8px] text-text-3 block mb-1"> </label>
<ComboBox
className="hns-inp"
value={criteriaModel}
@ -253,14 +252,14 @@ export function HNSLeftPanel({
</div>
{/* 물질 정보 */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px' }}>
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--orange)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
<div className="bg-bg-3 border border-border rounded-md p-[14px]">
<div className="text-[11px] font-bold text-status-orange mb-3 flex items-center gap-1.5">
🧪
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div className="flex flex-col gap-2">
{/* 물질 분류 */}
<div>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}> </label>
<label className="hns-lbl text-[8px] text-text-3 block mb-1"> </label>
<ComboBox
className="hns-inp"
value={materialCategory}
@ -278,7 +277,7 @@ export function HNSLeftPanel({
{/* 물질명 */}
<div>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}></label>
<label className="hns-lbl text-[8px] text-text-3 block mb-1"></label>
<ComboBox
className="hns-inp"
value={substance}
@ -296,17 +295,17 @@ export function HNSLeftPanel({
{/* UN번호 / CAS번호 */}
<div>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}>UN번호 / CAS번호</label>
<label className="hns-lbl text-[8px] text-text-3 block mb-1">UN번호 / CAS번호</label>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
<input
className="hns-inp"
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
className="hns-inp w-full"
style={{ padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
value={unNumber}
readOnly
/>
<input
className="hns-inp"
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
className="hns-inp w-full"
style={{ padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
value={casNumber}
readOnly
/>
@ -316,18 +315,18 @@ export function HNSLeftPanel({
{/* 유출량 + 단위 */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
<div>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}></label>
<label className="hns-lbl text-[8px] text-text-3 block mb-1"></label>
<input
className="hns-inp"
className="hns-inp w-full"
type="number"
style={{ width: '100%', padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
style={{ padding: '8px 10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontSize: '11px' }}
value={amount}
onChange={(e) => setAmount(e.target.value)}
step="0.1"
/>
</div>
<div>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}></label>
<label className="hns-lbl text-[8px] text-text-3 block mb-1"></label>
<ComboBox
className="hns-inp"
value={unit}
@ -344,7 +343,7 @@ export function HNSLeftPanel({
{/* 유출 형태 */}
<div>
<label className="hns-lbl" style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}> </label>
<label className="hns-lbl text-[8px] text-text-3 block mb-1"> </label>
<ComboBox
className="hns-inp"
value={releaseType}
@ -358,55 +357,61 @@ export function HNSLeftPanel({
</div>
{/* 물질 위험 특성 */}
<div style={{ padding: '8px', background: 'rgba(249,115,22,0.05)', border: '1px solid rgba(249,115,22,0.12)', borderRadius: '6px', marginTop: '2px' }}>
<div style={{ fontSize: '8px', fontWeight: 700, color: 'var(--orange)', marginBottom: '4px' }}>
<div
className="p-2 rounded-sm mt-0.5"
style={{ background: 'rgba(249,115,22,0.05)', border: '1px solid rgba(249,115,22,0.12)' }}
>
<div className="text-[8px] font-bold text-status-orange mb-1">
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '3px', fontSize: '8px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ color: 'var(--t3)' }}></span>
<span style={{ color: 'var(--red)', fontWeight: 600, fontFamily: 'var(--fM)' }}>4°C</span>
<div className="text-[8px]" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '3px' }}>
<div className="flex justify-between">
<span className="text-text-3"></span>
<span className="text-status-red font-semibold font-mono">4°C</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ color: 'var(--t3)' }}></span>
<span style={{ fontFamily: 'var(--fM)' }}>0.867</span>
<div className="flex justify-between">
<span className="text-text-3"></span>
<span className="font-mono">0.867</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ color: 'var(--t3)' }}></span>
<span style={{ fontFamily: 'var(--fM)' }}>22 mmHg</span>
<div className="flex justify-between">
<span className="text-text-3"></span>
<span className="font-mono">22 mmHg</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ color: 'var(--t3)' }}>IDLH</span>
<span style={{ color: 'var(--red)', fontWeight: 600, fontFamily: 'var(--fM)' }}>500 ppm</span>
<div className="flex justify-between">
<span className="text-text-3">IDLH</span>
<span className="text-status-red font-semibold font-mono">500 ppm</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ color: 'var(--t3)' }}>TWA</span>
<span style={{ fontFamily: 'var(--fM)' }}>50 ppm</span>
<div className="flex justify-between">
<span className="text-text-3">TWA</span>
<span className="font-mono">50 ppm</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span style={{ color: 'var(--t3)' }}>AEGL-2(1h)</span>
<span style={{ color: 'var(--orange)', fontWeight: 600, fontFamily: 'var(--fM)' }}>150 ppm</span>
<div className="flex justify-between">
<span className="text-text-3">AEGL-2(1h)</span>
<span className="text-status-orange font-semibold font-mono">150 ppm</span>
</div>
</div>
</div>
{/* AEGL 등급 범례 */}
<div style={{ padding: '8px', background: 'rgba(168,85,247,0.05)', border: '1px solid rgba(168,85,247,0.12)', borderRadius: '6px' }}>
<div style={{ fontSize: '8px', fontWeight: 700, color: 'var(--purple)', marginBottom: '4px' }}>
<div
className="p-2 rounded-sm"
style={{ background: 'rgba(168,85,247,0.05)', border: '1px solid rgba(168,85,247,0.12)' }}
>
<div className="text-[8px] font-bold text-primary-purple mb-1">
📊 (AEGL)
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '2px', fontSize: '8px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
<div style={{ width: '8px', height: '8px', borderRadius: '2px', background: 'rgba(239,68,68,0.7)' }}></div>
<span style={{ color: 'var(--t3)' }}>AEGL-3 () 500 ppm</span>
<div className="flex flex-col gap-0.5 text-[8px]">
<div className="flex items-center gap-1">
<div className="w-2 h-2" style={{ borderRadius: '2px', background: 'rgba(239,68,68,0.7)' }}></div>
<span className="text-text-3">AEGL-3 () 500 ppm</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
<div style={{ width: '8px', height: '8px', borderRadius: '2px', background: 'rgba(249,115,22,0.7)' }}></div>
<span style={{ color: 'var(--t3)' }}>AEGL-2 () 150 ppm</span>
<div className="flex items-center gap-1">
<div className="w-2 h-2" style={{ borderRadius: '2px', background: 'rgba(249,115,22,0.7)' }}></div>
<span className="text-text-3">AEGL-2 () 150 ppm</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
<div style={{ width: '8px', height: '8px', borderRadius: '2px', background: 'rgba(234,179,8,0.7)' }}></div>
<span style={{ color: 'var(--t3)' }}>AEGL-1 () 37 ppm</span>
<div className="flex items-center gap-1">
<div className="w-2 h-2" style={{ borderRadius: '2px', background: 'rgba(234,179,8,0.7)' }}></div>
<span className="text-text-3">AEGL-1 () 37 ppm</span>
</div>
</div>
</div>
@ -415,22 +420,19 @@ export function HNSLeftPanel({
</div>
{/* 실행 버튼 */}
<div style={{ display: 'flex', gap: '8px', marginTop: '14px', justifyContent: 'center' }}>
<div className="flex gap-2 mt-[14px] justify-center">
<button
onClick={onRunPrediction}
disabled={isRunningPrediction}
className="text-white text-[13px] font-bold rounded-md"
style={{
padding: '12px 40px',
background: isRunningPrediction
? 'var(--t4)'
: 'linear-gradient(135deg, var(--orange), var(--red))',
border: 'none',
borderRadius: '8px',
color: '#fff',
fontSize: '13px',
fontWeight: 700,
cursor: isRunningPrediction ? 'not-allowed' : 'pointer',
transition: '0.2s',
transition: '0.2s',
boxShadow: isRunningPrediction ? 'none' : '0 4px 16px rgba(249,115,22,0.25)'
}}
>
@ -438,16 +440,8 @@ export function HNSLeftPanel({
</button>
<button
onClick={handleReset}
style={{
padding: '12px 24px',
background: 'var(--bg3)',
border: '1px solid var(--bd)',
borderRadius: '8px',
color: 'var(--t2)',
fontSize: '12px',
fontWeight: 600,
cursor: 'pointer',
}}
className="bg-bg-3 border border-border rounded-md text-text-2 text-[12px] font-semibold cursor-pointer"
style={{ padding: '12px 24px' }}
>
🔄
</button>
@ -456,39 +450,35 @@ export function HNSLeftPanel({
)}
{activeSubTab === 'list' && (
<div style={{ padding: '16px' }}>
<div className="p-4">
{/* Header */}
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '14px' }}>
<div style={{
width: '36px',
height: '36px',
borderRadius: '8px',
background: 'linear-gradient(135deg, rgba(6,182,212,0.15), rgba(168,85,247,0.1))',
border: '1px solid rgba(6,182,212,0.25)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '18px'
}}>📋</div>
<div className="flex items-center gap-2.5 mb-[14px]">
<div
className="w-9 h-9 rounded-md flex items-center justify-center text-[18px]"
style={{
background: 'linear-gradient(135deg, rgba(6,182,212,0.15), rgba(168,85,247,0.1))',
border: '1px solid rgba(6,182,212,0.25)',
}}
>📋</div>
<div>
<div style={{ fontSize: '14px', fontWeight: 700 }}>
<div className="text-sm font-bold">
</div>
<div style={{ fontSize: '9px', color: 'var(--t3)' }}>
<div className="text-[9px] text-text-3">
</div>
</div>
</div>
{/* 필터 섹션 */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px', marginBottom: '12px' }}>
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--cyan)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
<div className="bg-bg-3 border border-border rounded-md p-[14px] mb-3">
<div className="text-[11px] font-bold text-primary-cyan mb-3 flex items-center gap-1.5">
🔍
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div className="flex flex-col gap-2">
{/* 기간 선택 */}
<div>
<label style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}></label>
<label className="text-[8px] text-text-3 block mb-1"></label>
<ComboBox
value="최근 7일"
onChange={() => {}}
@ -503,7 +493,7 @@ export function HNSLeftPanel({
{/* 물질 분류 */}
<div>
<label style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}> </label>
<label className="text-[8px] text-text-3 block mb-1"> </label>
<ComboBox
value="전체"
onChange={() => {}}
@ -519,7 +509,7 @@ export function HNSLeftPanel({
{/* 위험도 */}
<div>
<label style={{ fontSize: '8px', color: 'var(--t3)', display: 'block', marginBottom: '4px' }}></label>
<label className="text-[8px] text-text-3 block mb-1"></label>
<ComboBox
value="전체"
onChange={() => {}}
@ -535,22 +525,22 @@ export function HNSLeftPanel({
</div>
{/* 통계 요약 */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '10px', padding: '14px' }}>
<div style={{ fontSize: '11px', fontWeight: 700, color: 'var(--purple)', marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '6px' }}>
<div className="bg-bg-3 border border-border rounded-md p-[14px]">
<div className="text-[11px] font-bold text-primary-purple mb-3 flex items-center gap-1.5">
📊
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: 'var(--rS)' }}>
<span style={{ fontSize: '10px', color: 'var(--t3)' }}> </span>
<span style={{ fontSize: '14px', fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>8</span>
<div className="flex flex-col gap-2">
<div className="flex justify-between items-center p-2 bg-bg-0" style={{ borderRadius: 'var(--rS)' }}>
<span className="text-[10px] text-text-3"> </span>
<span className="text-sm font-bold text-primary-cyan font-mono">8</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: 'var(--rS)' }}>
<span style={{ fontSize: '10px', color: 'var(--t3)' }}> (AEGL-3)</span>
<span style={{ fontSize: '14px', fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fM)' }}>3</span>
<div className="flex justify-between items-center p-2 bg-bg-0" style={{ borderRadius: 'var(--rS)' }}>
<span className="text-[10px] text-text-3"> (AEGL-3)</span>
<span className="text-sm font-bold text-status-red font-mono">3</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: 'var(--rS)' }}>
<span style={{ fontSize: '10px', color: 'var(--t3)' }}> (AEGL-2)</span>
<span style={{ fontSize: '14px', fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fM)' }}>5</span>
<div className="flex justify-between items-center p-2 bg-bg-0" style={{ borderRadius: 'var(--rS)' }}>
<span className="text-[10px] text-text-3"> (AEGL-2)</span>
<span className="text-sm font-bold text-status-orange font-mono">5</span>
</div>
</div>
</div>

파일 보기

@ -79,61 +79,36 @@ export function HNSRecalcModal({ isOpen, onClose, onSubmit }: HNSRecalcModalProp
return (
<div
ref={backdropRef}
style={{
position: 'fixed', inset: 0, zIndex: 9999,
background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(4px)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}
className="fixed inset-0 z-[9999] flex items-center justify-center"
style={{ background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(4px)' }}
>
<div style={{
width: '400px', maxHeight: 'calc(100vh - 100px)',
background: 'var(--bg1)', border: '1px solid var(--bd)',
borderRadius: '14px', overflow: 'hidden',
display: 'flex', flexDirection: 'column',
boxShadow: '0 20px 60px rgba(0,0,0,0.5)',
}}>
<div className="w-[400px] max-h-[calc(100vh-100px)] bg-bg-1 border border-border rounded-[14px] overflow-hidden flex flex-col"
style={{ boxShadow: '0 20px 60px rgba(0,0,0,0.5)' }}>
{/* Header */}
<div style={{
padding: '16px 20px', borderBottom: '1px solid var(--bd)',
display: 'flex', alignItems: 'center', gap: '12px',
}}>
<div style={{
width: '36px', height: '36px', borderRadius: '10px',
background: 'linear-gradient(135deg, rgba(249,115,22,0.2), rgba(239,68,68,0.15))',
border: '1px solid rgba(249,115,22,0.3)',
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '16px',
}}>🔄</div>
<div style={{ flex: 1 }}>
<h2 style={{ fontSize: '15px', fontWeight: 700, margin: 0 }}>
<div className="px-5 py-4 border-b border-border flex items-center gap-3">
<div className="w-9 h-9 rounded-[10px] border border-[rgba(249,115,22,0.3)] flex items-center justify-center text-base shrink-0"
style={{ background: 'linear-gradient(135deg, rgba(249,115,22,0.2), rgba(239,68,68,0.15))' }}>🔄</div>
<div className="flex-1">
<h2 className="text-[15px] font-bold m-0">
HNS
</h2>
<div style={{ fontSize: '10px', color: 'var(--t3)', marginTop: '2px' }}>
<div className="text-[10px] text-text-3 mt-0.5">
·
</div>
</div>
<button onClick={onClose} style={{
width: '28px', height: '28px', borderRadius: '6px',
border: '1px solid var(--bd)', background: 'var(--bg3)',
color: 'var(--t3)', fontSize: '12px', cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}></button>
<button onClick={onClose} className="w-7 h-7 rounded-md border border-border bg-bg-3 text-text-3 text-xs cursor-pointer flex items-center justify-center shrink-0"></button>
</div>
{/* Content */}
<div style={{
flex: 1, overflowY: 'auto', padding: '16px 20px',
display: 'flex', flexDirection: 'column', gap: '14px',
scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent',
}}>
<div className="flex-1 overflow-y-auto px-5 py-4 flex flex-col gap-[14px]"
style={{ scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
{/* 현재 분석 정보 */}
<div style={{
padding: '10px 12px', background: 'rgba(249,115,22,0.04)',
border: '1px solid rgba(249,115,22,0.15)', borderRadius: '8px',
}}>
<div style={{ fontSize: '9px', fontWeight: 700, color: 'var(--orange)', marginBottom: '6px' }}>
<div className="py-2.5 px-3 border border-[rgba(249,115,22,0.15)] rounded-md"
style={{ background: 'rgba(249,115,22,0.04)' }}>
<div className="text-[9px] font-bold text-status-orange mb-1.5">
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px', fontSize: '9px' }}>
<div className="grid grid-cols-2 gap-1 text-[9px]">
<InfoRow label="사고명" value="울산 온산항 톨루엔 유출" />
<InfoRow label="물질" value="톨루엔 (Toluene)" />
<InfoRow label="유출량" value="2.5 ton" />
@ -149,16 +124,16 @@ export function HNSRecalcModal({ isOpen, onClose, onSubmit }: HNSRecalcModalProp
</FG>
{/* 유출 유형 + 유출량 */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }}>
<div className="grid grid-cols-2 gap-[10px]">
<FG label="유출 유형">
<select className="prd-i" value={releaseType} onChange={e => setReleaseType(e.target.value)}>
{RELEASE_TYPES.map(r => <option key={r} value={r}>{r}</option>)}
</select>
</FG>
<FG label="유출량">
<div style={{ display: 'flex', gap: '4px' }}>
<input className="prd-i" type="number" value={amount} onChange={e => setAmount(Number(e.target.value))} step={0.1} style={{ flex: 1 }} />
<select className="prd-i" value={unit} onChange={e => setUnit(e.target.value as typeof unit)} style={{ width: '55px' }}>
<div className="flex gap-1">
<input className="prd-i flex-1" type="number" value={amount} onChange={e => setAmount(Number(e.target.value))} step={0.1} />
<select className="prd-i w-[55px]" value={unit} onChange={e => setUnit(e.target.value as typeof unit)}>
{['t', 'kg', 'm³', 'L'].map(u => <option key={u} value={u}>{u}</option>)}
</select>
</div>
@ -166,7 +141,7 @@ export function HNSRecalcModal({ isOpen, onClose, onSubmit }: HNSRecalcModalProp
</div>
{/* 풍향 / 풍속 / 기온 */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '10px' }}>
<div className="grid grid-cols-3 gap-[10px]">
<FG label="풍향">
<select className="prd-i" value={windDir} onChange={e => setWindDir(e.target.value)}>
{['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'].map(d => <option key={d} value={d}>{d}</option>)}
@ -181,7 +156,7 @@ export function HNSRecalcModal({ isOpen, onClose, onSubmit }: HNSRecalcModalProp
</div>
{/* 대기안정도 + 확산 모델 */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px' }}>
<div className="grid grid-cols-2 gap-[10px]">
<FG label="대기안정도 (Pasquill)">
<select className="prd-i" value={stability} onChange={e => setStability(e.target.value)}>
{STABILITIES.map(s => <option key={s} value={s}>{s}</option>)}
@ -203,37 +178,32 @@ export function HNSRecalcModal({ isOpen, onClose, onSubmit }: HNSRecalcModalProp
{/* 유출 위치 */}
<FG label="유출 위치 (좌표)">
<div style={{ display: 'flex', gap: '6px' }}>
<div style={{ flex: 1 }}>
<div style={{ fontSize: '8px', color: 'var(--t3)', marginBottom: '3px' }}> (N)</div>
<input className="prd-i" type="number" value={lat} step={0.0001} onChange={e => setLat(Number(e.target.value))} style={{ fontFamily: 'var(--fM)' }} />
<div className="flex gap-1.5">
<div className="flex-1">
<div className="text-[8px] text-text-3 mb-[3px]"> (N)</div>
<input className="prd-i font-mono" type="number" value={lat} step={0.0001} onChange={e => setLat(Number(e.target.value))} />
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: '8px', color: 'var(--t3)', marginBottom: '3px' }}> (E)</div>
<input className="prd-i" type="number" value={lon} step={0.0001} onChange={e => setLon(Number(e.target.value))} style={{ fontFamily: 'var(--fM)' }} />
<div className="flex-1">
<div className="text-[8px] text-text-3 mb-[3px]"> (E)</div>
<input className="prd-i font-mono" type="number" value={lon} step={0.0001} onChange={e => setLon(Number(e.target.value))} />
</div>
</div>
</FG>
</div>
{/* Footer */}
<div style={{ padding: '14px 20px', borderTop: '1px solid var(--bd)', display: 'flex', gap: '8px' }}>
<div className="px-5 py-[14px] border-t border-border flex gap-2">
<button
onClick={onClose}
disabled={phase !== 'editing'}
style={{
flex: 1, padding: '10px', fontSize: '12px', fontWeight: 600,
borderRadius: '8px', cursor: 'pointer',
background: 'var(--bg3)', border: '1px solid var(--bd)',
color: 'var(--t2)', opacity: phase !== 'editing' ? 0.5 : 1,
}}
className="flex-1 py-2.5 text-xs font-semibold rounded-md cursor-pointer bg-bg-3 border border-border text-text-2"
style={{ opacity: phase !== 'editing' ? 0.5 : 1 }}
></button>
<button
onClick={handleRun}
disabled={phase !== 'editing'}
className="flex-[2] py-2.5 text-xs font-bold rounded-md"
style={{
flex: 2, padding: '10px', fontSize: '12px', fontWeight: 700,
borderRadius: '8px',
cursor: phase === 'editing' ? 'pointer' : 'wait',
background: phase === 'done'
? 'rgba(34,197,94,0.15)'

파일 보기

@ -22,23 +22,8 @@ interface HNSRightPanelProps {
export function HNSRightPanel({ dispersionResult, onOpenRecalc, onOpenReport }: HNSRightPanelProps) {
if (!dispersionResult) {
return (
<div style={{
width: '300px',
background: 'var(--bg1)',
borderLeft: '1px solid var(--bd)',
padding: '16px',
overflow: 'auto',
}}>
<div style={{
display: 'flex',
flexDirection: 'column',
gap: '12px',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
color: 'var(--t3)',
fontSize: '12px'
}}>
<div className="w-[300px] bg-bg-1 border-l border-border p-4 overflow-auto">
<div className="flex flex-col gap-3 items-center justify-center h-full text-text-3 text-xs">
<div style={{ fontSize: '32px', opacity: 0.3 }}>📊</div>
<div> </div>
</div>
@ -47,24 +32,10 @@ export function HNSRightPanel({ dispersionResult, onOpenRecalc, onOpenReport }:
}
return (
<div style={{
width: '300px',
background: 'var(--bg1)',
borderLeft: '1px solid var(--bd)',
padding: '16px',
overflow: 'auto',
display: 'flex',
flexDirection: 'column',
gap: '16px'
}}>
<div className="w-[300px] bg-bg-1 border-l border-border p-4 overflow-auto flex flex-col gap-4">
{/* Header */}
<div>
<div style={{
display: 'flex',
alignItems: 'center',
gap: '6px',
marginBottom: '8px'
}}>
<div className="flex items-center gap-1.5 mb-2">
<div style={{
width: '6px',
height: '6px',
@ -72,161 +43,77 @@ export function HNSRightPanel({ dispersionResult, onOpenRecalc, onOpenReport }:
background: 'var(--orange)',
animation: 'pulse 1.5s infinite'
}}></div>
<h3 style={{
fontSize: '13px',
fontWeight: 700,
margin: 0
}}>
<h3 className="text-[13px] font-bold m-0">
</h3>
</div>
<div style={{
fontSize: '10px',
color: 'var(--t3)',
fontFamily: 'var(--fM)'
}}>
<div className="text-[10px] text-text-3 font-mono">
{dispersionResult.substance} · ALOHA v5.4.7
</div>
</div>
{/* KPI Cards */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div style={{
padding: '12px',
background: 'var(--bg3)',
border: '1px solid rgba(6,182,212,0.2)',
borderRadius: 'var(--rS)'
}}>
<div style={{
fontSize: '10px',
color: 'var(--t3)',
marginBottom: '6px'
}}>
<div className="flex flex-col gap-2">
<div className="p-3 bg-bg-3 border border-[rgba(6,182,212,0.2)] rounded-[var(--rS)]">
<div className="text-[10px] text-text-3 mb-1.5">
</div>
<div style={{
fontSize: '20px',
fontWeight: 700,
fontFamily: 'var(--fM)',
color: 'var(--cyan)'
}}>
8.2 <span style={{ fontSize: '10px', fontWeight: 500 }}>km²</span>
<div className="text-[20px] font-bold font-mono text-primary-cyan">
8.2 <span className="text-[10px] font-medium">km²</span>
</div>
</div>
<div style={{
padding: '12px',
background: 'var(--bg3)',
border: '1px solid rgba(249,115,22,0.2)',
borderRadius: 'var(--rS)'
}}>
<div style={{
fontSize: '10px',
color: 'var(--t3)',
marginBottom: '6px'
}}>
<div className="p-3 bg-bg-3 border border-[rgba(249,115,22,0.2)] rounded-[var(--rS)]">
<div className="text-[10px] text-text-3 mb-1.5">
</div>
<div style={{
fontSize: '20px',
fontWeight: 700,
fontFamily: 'var(--fM)',
color: 'var(--orange)'
}}>
<div className="text-[20px] font-bold font-mono text-status-orange">
2
</div>
</div>
<div style={{
padding: '12px',
background: 'var(--bg3)',
border: '1px solid var(--bd)',
borderRadius: 'var(--rS)'
}}>
<div style={{
fontSize: '10px',
color: 'var(--t3)',
marginBottom: '6px'
}}>
<div className="p-3 bg-bg-3 border border-border rounded-[var(--rS)]">
<div className="text-[10px] text-text-3 mb-1.5">
</div>
<div style={{
fontSize: '20px',
fontWeight: 700,
fontFamily: 'var(--fM)',
}}>
5.2 <span style={{ fontSize: '10px', fontWeight: 500 }}>m/s</span>
<div className="text-[20px] font-bold font-mono">
5.2 <span className="text-[10px] font-medium">m/s</span>
</div>
</div>
<div style={{
padding: '12px',
background: 'var(--bg3)',
border: '1px solid var(--bd)',
borderRadius: 'var(--rS)'
}}>
<div style={{
fontSize: '10px',
color: 'var(--t3)',
marginBottom: '6px'
}}>
<div className="p-3 bg-bg-3 border border-border rounded-[var(--rS)]">
<div className="text-[10px] text-text-3 mb-1.5">
</div>
<div style={{
fontSize: '20px',
fontWeight: 700,
fontFamily: 'var(--fM)',
}}>
SW <span style={{ fontSize: '10px', fontWeight: 500 }}>225°</span>
<div className="text-[20px] font-bold font-mono">
SW <span className="text-[10px] font-medium">225°</span>
</div>
</div>
</div>
{/* Zone Details */}
<div>
<h4 style={{
fontSize: '11px',
fontWeight: 600,
color: 'var(--t2)',
margin: '0 0 10px 0'
}}>
<h4 className="text-[11px] font-semibold text-text-2 mt-0 mb-2.5">
</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div className="flex flex-col gap-2">
{dispersionResult.zones.map((zone, idx) => (
<div
key={idx}
className="py-2.5 px-3 bg-bg-2 rounded-[var(--rS)]"
style={{
padding: '10px 12px',
background: 'var(--bg2)',
borderRadius: 'var(--rS)',
borderLeft: `3px solid ${zone.color.replace('0.4', '1').replace('0.3', '1').replace('0.25', '1')}`
}}
>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '4px'
}}>
<span style={{
fontSize: '11px',
fontWeight: 600,
}}>
<div className="flex justify-between items-center mb-1">
<span className="text-[11px] font-semibold">
{zone.level}
</span>
<span style={{
fontSize: '10px',
fontFamily: 'var(--fM)',
color: 'var(--t3)'
}}>
<span className="text-[10px] font-mono text-text-3">
{zone.radius}m
</span>
</div>
<div style={{
fontSize: '10px',
color: 'var(--t3)'
}}>
<div className="text-[10px] text-text-3">
{dispersionResult.concentration[zone.level as keyof typeof dispersionResult.concentration]}
</div>
</div>
@ -235,19 +122,12 @@ export function HNSRightPanel({ dispersionResult, onOpenRecalc, onOpenReport }:
</div>
{/* Timestamp */}
<div style={{
marginTop: 'auto',
paddingTop: '12px',
borderTop: '1px solid var(--bd)',
fontSize: '10px',
color: 'var(--t3)',
fontFamily: 'var(--fM)'
}}>
<div className="mt-auto pt-3 border-t border-border text-[10px] text-text-3 font-mono">
: {new Date(dispersionResult.timestamp).toLocaleString('ko-KR')}
</div>
{/* Bottom Action Buttons */}
<div style={{ display: 'flex', gap: '6px', padding: '12px 0 0', borderTop: '1px solid var(--bd)' }}>
<div className="flex gap-1.5 pt-3 border-t border-border">
<button className="flex-1 py-2 px-1 rounded text-[11px] font-semibold bg-gradient-to-r from-boom to-[#d97706] text-black font-korean">
💾
</button>

파일 보기

@ -130,24 +130,21 @@ export function HNSScenarioView() {
}
return (
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, width: '100%', height: '100%', overflow: 'hidden', background: 'var(--bg0)' }}>
<div className="flex flex-col flex-1 w-full h-full overflow-hidden" style={{ background: 'var(--bg0)' }}>
{/* Header */}
<div style={{
padding: '14px 20px', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
borderBottom: '1px solid var(--bd)', flexShrink: 0, background: 'var(--bg1)',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
<span style={{ fontSize: '16px' }}>📊</span>
<div className="flex items-center justify-between shrink-0 border-b border-border" style={{ padding: '14px 20px', background: 'var(--bg1)' }}>
<div className="flex items-center gap-2.5">
<span className="text-base">📊</span>
<div>
<div style={{ fontSize: '14px', fontWeight: 700 }}>
<div className="text-sm font-bold">
HNS
</div>
<div style={{ fontSize: '10px', color: 'var(--t3)' }}>
<div className="text-[10px] text-text-3">
· ·
</div>
</div>
</div>
<div style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
<div className="flex gap-2 items-center">
<select
value={selectedIncident}
onChange={(e) => setSelectedIncident(Number(e.target.value))}
@ -165,11 +162,11 @@ export function HNSScenarioView() {
</select>
<button
onClick={() => setModalOpen(true)}
className="cursor-pointer whitespace-nowrap font-bold text-status-orange"
style={{
padding: '6px 14px', background: 'rgba(249,115,22,0.12)',
border: '1px solid rgba(249,115,22,0.3)', borderRadius: '6px',
color: 'var(--orange)', fontSize: '11px', fontWeight: 700,
cursor: 'pointer', whiteSpace: 'nowrap',
fontSize: '11px',
}}
>
+
@ -178,24 +175,18 @@ export function HNSScenarioView() {
</div>
{/* Body: Left list + Right detail */}
<div style={{ display: 'flex', flex: 1, overflow: 'hidden' }}>
<div className="flex flex-1 overflow-hidden">
{/* ── Left: Scenario List ── */}
<div style={{
width: '370px', minWidth: '370px', borderRight: '1px solid var(--bd)',
display: 'flex', flexDirection: 'column', overflow: 'hidden', background: 'var(--bg1)',
}}>
<div style={{
padding: '10px 14px', borderBottom: '1px solid var(--bd)',
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
}}>
<span style={{ fontSize: '11px', fontWeight: 700, color: 'var(--t3)' }}>
<div className="flex flex-col overflow-hidden shrink-0 border-r border-border" style={{ width: '370px', minWidth: '370px', background: 'var(--bg1)' }}>
<div className="flex items-center justify-between border-b border-border" style={{ padding: '10px 14px' }}>
<span className="text-[11px] font-bold text-text-3">
</span>
<div style={{ display: 'flex', gap: '4px' }}>
<div className="flex gap-1">
{['시간순', '위험도순'].map((label, i) => (
<button key={i} style={{
<button key={i} className="cursor-pointer" style={{
padding: '3px 8px', fontSize: '9px', fontWeight: 600,
borderRadius: '4px', cursor: 'pointer',
borderRadius: '4px',
border: '1px solid var(--bd)', background: i === 0 ? 'rgba(249,115,22,0.08)' : 'var(--bg3)',
color: i === 0 ? 'var(--orange)' : 'var(--t3)',
}}>
@ -206,11 +197,7 @@ export function HNSScenarioView() {
</div>
{/* Scrollable list */}
<div style={{
flex: 1, overflowY: 'auto', padding: '8px',
display: 'flex', flexDirection: 'column', gap: '6px',
scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent',
}}>
<div className="flex-1 overflow-y-auto flex flex-col gap-1.5" style={{ padding: '8px', scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
{scenarios.map((scn, idx) => {
const sev = SEVERITY_STYLE[scn.severity]
const isSel = selectedIdx === idx
@ -221,8 +208,8 @@ export function HNSScenarioView() {
onClick={() => { setSelectedIdx(idx); setActiveView(0) }}
>
{/* Title + badge */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '6px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<div className="flex items-center justify-between mb-1.5">
<div className="flex items-center gap-1.5">
<input
type="checkbox"
checked={checked.has(idx)}
@ -230,50 +217,41 @@ export function HNSScenarioView() {
onClick={(e) => e.stopPropagation()}
style={{ accentColor: 'var(--orange)' }}
/>
<span style={{ fontSize: '12px', fontWeight: 700 }}>
<span className="text-[12px] font-bold">
{scn.id} {scn.name}
</span>
</div>
<span style={{
padding: '2px 8px', background: sev.bg, borderRadius: '8px',
fontSize: '8px', fontWeight: 700, color: sev.color,
}}>
<span className="font-bold" style={{ padding: '2px 8px', background: sev.bg, borderRadius: '8px', fontSize: '8px', color: sev.color }}>
{scn.severity}
</span>
</div>
{/* Time row */}
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '6px' }}>
<span style={{
padding: '2px 6px', background: 'rgba(249,115,22,0.1)', borderRadius: '3px',
fontSize: '9px', color: 'var(--orange)', fontWeight: 700, fontFamily: 'var(--fM)',
}}>
<div className="flex items-center gap-1.5 mb-1.5">
<span className="font-bold font-mono text-status-orange" style={{ padding: '2px 6px', background: 'rgba(249,115,22,0.1)', borderRadius: '3px', fontSize: '9px' }}>
{scn.timeStep}
</span>
<span style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{scn.datetime}</span>
<span style={{ marginLeft: 'auto', fontSize: '8px', color: 'var(--t3)' }}>{scn.wind}</span>
<span className="text-[9px] text-text-3 font-mono">{scn.datetime}</span>
<span className="ml-auto text-text-3" style={{ fontSize: '8px' }}>{scn.wind}</span>
</div>
{/* Metrics grid */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: '4px', fontSize: '8px', fontFamily: 'var(--fM)' }}>
<div className="grid font-mono" style={{ gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: '4px', fontSize: '8px' }}>
{[
{ label: '최대농도', value: scn.maxConc, color: '#f87171' },
{ label: 'IDLH반경', value: scn.idlhRadius, color: '#f87171' },
{ label: 'ERPG-2', value: scn.erpg2, color: '#f97316' },
{ label: '영향인구', value: scn.population, color: '#f87171' },
].map((m, i) => (
<div key={i} style={{ textAlign: 'center', padding: '3px', background: 'var(--bg0)', borderRadius: '3px' }}>
<div style={{ color: 'var(--t3)', fontSize: '7px' }}>{m.label}</div>
<div style={{ color: m.color, fontWeight: 700 }}>{m.value}</div>
<div key={i} className="text-center" style={{ padding: '3px', background: 'var(--bg0)', borderRadius: '3px' }}>
<div className="text-text-3" style={{ fontSize: '7px' }}>{m.label}</div>
<div className="font-bold" style={{ color: m.color }}>{m.value}</div>
</div>
))}
</div>
{/* Description */}
<div style={{
marginTop: '6px', fontSize: '8px', color: 'var(--t2)',
lineHeight: 1.4,
}}>
<div className="text-text-2 mt-1.5" style={{ fontSize: '8px', lineHeight: 1.4 }}>
{scn.description}
</div>
</div>
@ -282,24 +260,22 @@ export function HNSScenarioView() {
</div>
{/* Bottom buttons */}
<div style={{
padding: '10px 14px', borderTop: '1px solid var(--bd)',
display: 'flex', gap: '8px',
}}>
<div className="flex gap-2 border-t border-border" style={{ padding: '10px 14px' }}>
<button
onClick={() => setActiveView(1)}
className="flex-1 cursor-pointer font-bold text-status-orange"
style={{
flex: 1, padding: '8px', borderRadius: '6px', cursor: 'pointer',
padding: '8px', borderRadius: '6px',
background: 'rgba(249,115,22,0.1)', border: '1px solid rgba(249,115,22,0.3)',
color: 'var(--orange)', fontSize: '11px', fontWeight: 700,
fontSize: '11px',
}}
>
📊
</button>
<button style={{
padding: '8px 14px', borderRadius: '6px', cursor: 'pointer',
<button className="cursor-pointer font-semibold text-text-2" style={{
padding: '8px 14px', borderRadius: '6px',
background: 'var(--bg3)', border: '1px solid var(--bd)',
color: 'var(--t2)', fontSize: '11px', fontWeight: 600,
fontSize: '11px',
}}>
📄
</button>
@ -307,12 +283,9 @@ export function HNSScenarioView() {
</div>
{/* ── Right: Detail Views ── */}
<div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
<div className="flex-1 min-w-0 flex flex-col overflow-hidden">
{/* View Tabs */}
<div style={{
display: 'flex', borderBottom: '1px solid var(--bd)', flexShrink: 0,
padding: '0 16px', background: 'var(--bg1)',
}}>
<div className="flex border-b border-border shrink-0 px-4" style={{ background: 'var(--bg1)' }}>
{['📋 시나리오 상세', '📊 비교 차트', '🗺 확산범위 오버레이'].map((label, i) => (
<button
key={i}
@ -358,25 +331,25 @@ export function HNSScenarioView() {
function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
const d = scenario.detail
return (
<div style={{ flex: 1, overflowY: 'auto', padding: '16px', display: 'flex', flexDirection: 'column', gap: '14px', scrollbarWidth: 'thin' }}>
<div className="flex-1 overflow-y-auto flex flex-col gap-3.5" style={{ padding: '16px', scrollbarWidth: 'thin' }}>
{/* Hero card */}
<div style={{
<div className="relative overflow-hidden rounded-md" style={{
background: 'linear-gradient(135deg, rgba(249,115,22,0.06), rgba(239,68,68,0.04))',
border: '1px solid rgba(249,115,22,0.2)', borderRadius: '10px',
padding: '16px', position: 'relative', overflow: 'hidden',
border: '1px solid rgba(249,115,22,0.2)',
padding: '16px',
}}>
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: '2px', background: 'linear-gradient(90deg, #f97316, #ef4444, #a855f7)' }} />
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '12px' }}>
<span style={{ fontSize: '14px', fontWeight: 700 }}>
<div className="absolute top-0 left-0 right-0 h-0.5" style={{ background: 'linear-gradient(90deg, #f97316, #ef4444, #a855f7)' }} />
<div className="flex items-center gap-2 mb-3">
<span className="text-sm font-bold">
{scenario.id} {scenario.name}
</span>
<span style={{
padding: '2px 8px', borderRadius: '8px', fontSize: '9px', fontWeight: 700,
<span className="font-bold" style={{
padding: '2px 8px', borderRadius: '8px', fontSize: '9px',
background: SEVERITY_STYLE[scenario.severity].bg, color: SEVERITY_STYLE[scenario.severity].color,
}}>
{scenario.severity}
</span>
<span style={{ marginLeft: 'auto', fontSize: '10px', color: 'var(--t3)', fontFamily: 'var(--fM)' }}>
<span className="ml-auto text-[10px] text-text-3 font-mono">
{scenario.datetime}
</span>
</div>
@ -389,11 +362,9 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
{ label: '영향인구', value: d.population, color: '#f87171' },
{ label: '유출량', value: d.spillAmount, color: 'var(--orange)' },
].map((m, i) => (
<div key={i} style={{
background: 'rgba(0,0,0,0.15)', borderRadius: '6px', padding: '8px', textAlign: 'center',
}}>
<div style={{ fontSize: '8px', color: 'var(--t3)' }}>{m.label}</div>
<div style={{ fontSize: '16px', fontWeight: 700, color: m.color, fontFamily: 'var(--fM)', whiteSpace: 'pre-line', lineHeight: 1.2, marginTop: '2px' }}>{m.value}</div>
<div key={i} className="text-center rounded-sm" style={{ background: 'rgba(0,0,0,0.15)', padding: '8px' }}>
<div className="text-text-3" style={{ fontSize: '8px' }}>{m.label}</div>
<div className="text-base font-bold font-mono whitespace-pre-line" style={{ color: m.color, lineHeight: 1.2, marginTop: '2px' }}>{m.value}</div>
</div>
))}
</div>
@ -402,38 +373,37 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
{/* Two-column section */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
{/* Threat Zones */}
<div style={{ background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
<h4 style={{ fontSize: '12px', fontWeight: 700, marginBottom: '10px' }}>
<div className="rounded-md border border-border" style={{ background: 'var(--bg2)', padding: '14px' }}>
<h4 className="text-[12px] font-bold mb-2.5">
</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<div className="flex flex-col gap-1.5">
{[
{ label: 'IDLH (즉시위험)', value: scenario.zones.idlh, color: '#ef4444' },
{ label: 'ERPG-2 (대피권고)', value: scenario.zones.erpg2, color: '#f97316' },
{ label: 'ERPG-1 (주의권고)', value: scenario.zones.erpg1, color: '#fbbf24' },
{ label: 'TWA (작업허용)', value: scenario.zones.twa, color: '#22c55e' },
].map((z, i) => (
<div key={i} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '6px 8px', background: 'var(--bg0)', borderRadius: '4px', borderLeft: `3px solid ${z.color}` }}>
<span style={{ fontSize: '10px', color: 'var(--t2)' }}>{z.label}</span>
<span style={{ fontSize: '11px', fontWeight: 700, color: z.color, fontFamily: 'var(--fM)' }}>{z.value}</span>
<div key={i} className="flex justify-between items-center" style={{ padding: '6px 8px', background: 'var(--bg0)', borderRadius: '4px', borderLeft: `3px solid ${z.color}` }}>
<span className="text-[10px] text-text-2">{z.label}</span>
<span className="text-[11px] font-bold font-mono" style={{ color: z.color }}>{z.value}</span>
</div>
))}
</div>
</div>
{/* Actions */}
<div style={{ background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
<h4 style={{ fontSize: '12px', fontWeight: 700, marginBottom: '10px' }}>
<div className="rounded-md border border-border" style={{ background: 'var(--bg2)', padding: '14px' }}>
<h4 className="text-[12px] font-bold mb-2.5">
🛡
</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '5px' }}>
<div className="flex flex-col gap-1.5">
{scenario.actions.map((action, i) => (
<div key={i} style={{
display: 'flex', alignItems: 'flex-start', gap: '6px',
<div key={i} className="flex items-start gap-1.5 text-[10px] text-text-2" style={{
padding: '5px 8px', background: 'var(--bg0)', borderRadius: '4px',
fontSize: '10px', color: 'var(--t2)', lineHeight: 1.4,
lineHeight: 1.4,
}}>
<span style={{ color: 'var(--orange)', fontWeight: 700, flexShrink: 0 }}></span>
<span className="text-status-orange font-bold shrink-0"></span>
{action}
</div>
))}
@ -442,8 +412,8 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
</div>
{/* Weather */}
<div style={{ background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
<h4 style={{ fontSize: '12px', fontWeight: 700, marginBottom: '10px' }}>
<div className="rounded-md border border-border" style={{ background: 'var(--bg2)', padding: '14px' }}>
<h4 className="text-[12px] font-bold mb-2.5">
🌊
</h4>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)', gap: '8px' }}>
@ -455,10 +425,10 @@ function ScenarioDetail({ scenario }: { scenario: HnsScenario }) {
{ label: '습도', value: scenario.weather.humidity, icon: '💧' },
{ label: '혼합층', value: scenario.weather.mixHeight, icon: '📏' },
].map((w, i) => (
<div key={i} style={{ textAlign: 'center', padding: '8px', background: 'var(--bg0)', borderRadius: '6px' }}>
<div style={{ fontSize: '14px', marginBottom: '2px' }}>{w.icon}</div>
<div style={{ fontSize: '12px', fontWeight: 700, fontFamily: 'var(--fM)' }}>{w.value}</div>
<div style={{ fontSize: '8px', color: 'var(--t3)', marginTop: '2px' }}>{w.label}</div>
<div key={i} className="text-center p-2 rounded-sm" style={{ background: 'var(--bg0)' }}>
<div className="text-sm mb-0.5">{w.icon}</div>
<div className="text-[12px] font-bold font-mono">{w.value}</div>
<div className="text-text-3 mt-0.5" style={{ fontSize: '8px' }}>{w.label}</div>
</div>
))}
</div>
@ -500,15 +470,15 @@ function ScenarioComparison() {
const barX = [30, 70, 110, 150, 190]
return (
<div style={{ flex: 1, overflowY: 'auto', padding: '16px 20px', display: 'flex', flexDirection: 'column', gap: '14px', scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
<div className="flex-1 overflow-y-auto flex flex-col gap-3.5" style={{ padding: '16px 20px', scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
{/* Title */}
<div style={{ fontSize: '13px', fontWeight: 700, marginBottom: '2px' }}>
<div className="text-[13px] font-bold mb-0.5">
📊
</div>
{/* ── Chart 1: 최대 지표면 농도 추이 (Line + Area) ── */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
<div style={{ fontSize: '11px', fontWeight: 700, marginBottom: '10px' }}>
<div className="rounded-md border border-border" style={{ background: 'var(--bg3)', padding: '14px' }}>
<div className="text-[11px] font-bold mb-2.5">
(ppm)
</div>
<svg viewBox="0 0 500 140" style={{ width: '100%', height: '130px' }}>
@ -544,8 +514,8 @@ function ScenarioComparison() {
{/* ── Charts 2 & 3: 2-column grid ── */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '14px' }}>
{/* Chart 2: 위험 반경 변화 (Multi-line) */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
<div style={{ fontSize: '11px', fontWeight: 700, marginBottom: '10px' }}>
<div className="rounded-md border border-border" style={{ background: 'var(--bg3)', padding: '14px' }}>
<div className="text-[11px] font-bold mb-2.5">
(km)
</div>
<svg viewBox="0 0 260 100" style={{ width: '100%', height: '85px' }}>
@ -574,8 +544,8 @@ function ScenarioComparison() {
</div>
{/* Chart 3: 영향 인구 변화 (Bar) */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px' }}>
<div style={{ fontSize: '11px', fontWeight: 700, marginBottom: '10px' }}>
<div className="rounded-md border border-border" style={{ background: 'var(--bg3)', padding: '14px' }}>
<div className="text-[11px] font-bold mb-2.5">
()
</div>
<svg viewBox="0 0 240 100" style={{ width: '100%', height: '85px' }}>
@ -601,17 +571,17 @@ function ScenarioComparison() {
</div>
{/* ── Chart 4: 시나리오 비교표 ── */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: '8px', padding: '14px', overflowX: 'auto' }}>
<div style={{ fontSize: '11px', fontWeight: 700, marginBottom: '10px' }}>
<div className="rounded-md border border-border overflow-x-auto" style={{ background: 'var(--bg3)', padding: '14px' }}>
<div className="text-[11px] font-bold mb-2.5">
📋
</div>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '10px' }}>
<table className="w-full text-[10px]" style={{ borderCollapse: 'collapse' }}>
<thead>
<tr style={{ background: 'var(--bg0)' }}>
{['지표', ...D.map(d => `${d.id} (${d.label})`)].map((h, i) => (
<th key={i} style={{
<th key={i} className="text-text-3 border-b border-border" style={{
padding: '8px 10px', textAlign: i === 0 ? 'left' : 'center',
color: 'var(--t3)', fontSize: '9px', borderBottom: '1px solid var(--bd)',
fontSize: '9px',
}}>
{h}
</th>
@ -620,45 +590,45 @@ function ScenarioComparison() {
</thead>
<tbody>
{/* 최대농도 */}
<tr style={{ borderBottom: '1px solid var(--bd)' }}>
<td style={{ padding: '6px 10px', color: 'var(--t2)' }}> (ppm)</td>
<tr className="border-b border-border">
<td className="text-text-2" style={{ padding: '6px 10px' }}> (ppm)</td>
{D.map(d => (
<td key={d.id} style={{ padding: '6px', textAlign: 'center', fontFamily: 'var(--fM)', color: SEV_COLOR[d.severity], fontWeight: 600 }}>{d.conc}</td>
<td key={d.id} className="text-center font-mono font-semibold" style={{ padding: '6px', color: SEV_COLOR[d.severity] }}>{d.conc}</td>
))}
</tr>
{/* IDLH 반경 */}
<tr style={{ borderBottom: '1px solid var(--bd)' }}>
<td style={{ padding: '6px 10px', color: 'var(--t2)' }}>IDLH (km)</td>
<tr className="border-b border-border">
<td className="text-text-2" style={{ padding: '6px 10px' }}>IDLH (km)</td>
{D.map(d => (
<td key={d.id} style={{ padding: '6px', textAlign: 'center', fontFamily: 'var(--fM)', color: d.idlh > 0 ? '#f87171' : '#22c55e', fontWeight: 600 }}>{d.idlh || 0}</td>
<td key={d.id} className="text-center font-mono font-semibold" style={{ padding: '6px', color: d.idlh > 0 ? '#f87171' : '#22c55e' }}>{d.idlh || 0}</td>
))}
</tr>
{/* ERPG-2 반경 */}
<tr style={{ borderBottom: '1px solid var(--bd)' }}>
<td style={{ padding: '6px 10px', color: 'var(--t2)' }}>ERPG-2 (km)</td>
<tr className="border-b border-border">
<td className="text-text-2" style={{ padding: '6px 10px' }}>ERPG-2 (km)</td>
{D.map(d => (
<td key={d.id} style={{ padding: '6px', textAlign: 'center', fontFamily: 'var(--fM)', color: d.erpg2 > 0 ? '#f97316' : '#22c55e', fontWeight: 600 }}>{d.erpg2 || 0}</td>
<td key={d.id} className="text-center font-mono font-semibold" style={{ padding: '6px', color: d.erpg2 > 0 ? '#f97316' : '#22c55e' }}>{d.erpg2 || 0}</td>
))}
</tr>
{/* 영향인구 */}
<tr style={{ borderBottom: '1px solid var(--bd)' }}>
<td style={{ padding: '6px 10px', color: 'var(--t2)' }}> ()</td>
<tr className="border-b border-border">
<td className="text-text-2" style={{ padding: '6px 10px' }}> ()</td>
{D.map(d => (
<td key={d.id} style={{ padding: '6px', textAlign: 'center', fontFamily: 'var(--fM)', color: SEV_COLOR[d.severity], fontWeight: 600 }}>{d.pop.toLocaleString()}</td>
<td key={d.id} className="text-center font-mono font-semibold" style={{ padding: '6px', color: SEV_COLOR[d.severity] }}>{d.pop.toLocaleString()}</td>
))}
</tr>
{/* 풍향/풍속 */}
<tr style={{ borderBottom: '1px solid var(--bd)' }}>
<td style={{ padding: '6px 10px', color: 'var(--t2)' }}> / </td>
<tr className="border-b border-border">
<td className="text-text-2" style={{ padding: '6px 10px' }}> / </td>
{D.map(d => (
<td key={d.id} style={{ padding: '6px', textAlign: 'center', fontFamily: 'var(--fM)', color: 'var(--cyan)' }}>{d.wind}</td>
<td key={d.id} className="text-center font-mono text-primary-cyan" style={{ padding: '6px' }}>{d.wind}</td>
))}
</tr>
{/* 위험 등급 */}
<tr>
<td style={{ padding: '6px 10px', color: 'var(--t2)' }}> </td>
<td className="text-text-2" style={{ padding: '6px 10px' }}> </td>
{D.map(d => (
<td key={d.id} style={{ padding: '6px', textAlign: 'center', color: SEV_COLOR[d.severity], fontWeight: 700 }}>{d.severity}</td>
<td key={d.id} className="text-center font-bold" style={{ padding: '6px', color: SEV_COLOR[d.severity] }}>{d.severity}</td>
))}
</tr>
</tbody>
@ -671,25 +641,23 @@ function ScenarioComparison() {
// ─── View 2: Map Overlay ─────────────────────────────────
function ScenarioMapOverlay() {
return (
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: '16px' }}>
<div style={{
<div className="flex-1 flex items-center justify-center flex-col gap-4">
<div className="flex items-center justify-center rounded-md border border-border text-text-3 text-[13px]" style={{
width: '80%', maxWidth: '600px', height: '300px',
background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: '10px',
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: 'var(--t3)', fontSize: '13px',
background: 'var(--bg2)',
}}>
[ ]
</div>
<div style={{ display: 'flex', gap: '16px' }}>
<div className="flex gap-4">
{[
{ label: 'T+0h SW방향', color: '#ef4444' },
{ label: 'T+1h SE 전환', color: '#f97316' },
{ label: 'T+3h S방향', color: '#fbbf24' },
{ label: 'T+6h 차단 후', color: '#22c55e' },
].map((item, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<div style={{ width: '12px', height: '12px', borderRadius: '50%', background: item.color, opacity: 0.5 }} />
<span style={{ fontSize: '10px', color: 'var(--t2)' }}>{item.label}</span>
<div key={i} className="flex items-center gap-1.5">
<div className="rounded-full opacity-50" style={{ width: '12px', height: '12px', background: item.color }} />
<span className="text-[10px] text-text-2">{item.label}</span>
</div>
))}
</div>
@ -734,50 +702,33 @@ function NewScenarioModal({ isOpen, onClose, onSubmit }: {
}
return (
<div ref={backdropRef} style={{
position: 'fixed', inset: 0, zIndex: 9999,
background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(4px)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<div style={{
<div ref={backdropRef} className="fixed inset-0 z-[9999] flex items-center justify-center" style={{ background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(4px)' }}>
<div className="flex flex-col overflow-hidden" style={{
width: '520px', maxHeight: 'calc(100vh - 80px)',
background: 'var(--bg1)', border: '1px solid var(--bd)',
borderRadius: '14px', overflow: 'hidden',
display: 'flex', flexDirection: 'column',
borderRadius: '14px',
boxShadow: '0 20px 60px rgba(0,0,0,0.5)',
}}>
{/* Header */}
<div style={{
padding: '16px 20px', borderBottom: '1px solid var(--bd)',
display: 'flex', alignItems: 'center', gap: '12px',
}}>
<div style={{
<div className="flex items-center gap-3 border-b border-border" style={{ padding: '16px 20px' }}>
<div className="flex items-center justify-center text-base" style={{
width: '36px', height: '36px', borderRadius: '10px',
background: 'linear-gradient(135deg, rgba(249,115,22,0.2), rgba(239,68,68,0.15))',
border: '1px solid rgba(249,115,22,0.3)',
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '16px',
}}>🧪</div>
<div style={{ flex: 1 }}>
<h2 style={{ fontSize: '15px', fontWeight: 700, margin: 0 }}>
<div className="flex-1">
<h2 className="text-[15px] font-bold m-0">
HNS
</h2>
<div style={{ fontSize: '10px', color: 'var(--t3)', marginTop: '2px' }}>
<div className="text-[10px] text-text-3 mt-0.5">
··
</div>
</div>
<button onClick={onClose} style={{
width: '28px', height: '28px', borderRadius: '6px',
border: '1px solid var(--bd)', background: 'var(--bg3)',
color: 'var(--t3)', fontSize: '12px', cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}></button>
<button onClick={onClose} className="flex items-center justify-center cursor-pointer text-text-3 text-[12px] rounded-sm border border-border" style={{ width: '28px', height: '28px', background: 'var(--bg3)' }}></button>
</div>
{/* Scrollable content */}
<div style={{
flex: 1, overflowY: 'auto', padding: '16px 20px',
display: 'flex', flexDirection: 'column', gap: '14px',
}}>
<div className="flex-1 overflow-y-auto flex flex-col gap-3.5" style={{ padding: '16px 20px' }}>
{/* 기본 정보 */}
<ModalSection title="기본 정보">
<ModalField label="시나리오명">
@ -815,9 +766,9 @@ function NewScenarioModal({ isOpen, onClose, onSubmit }: {
{ label: 'IDLH', value: mat.idlh },
{ label: 'ERPG-2', value: mat.erpg2 },
].map((p, i) => (
<div key={i} style={{ textAlign: 'center' }}>
<div style={{ fontSize: '8px', color: 'var(--t3)' }}>{p.label}</div>
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--orange)', fontFamily: 'var(--fM)' }}>{p.value}</div>
<div key={i} className="text-center">
<div className="text-text-3" style={{ fontSize: '8px' }}>{p.label}</div>
<div className="text-[10px] font-bold text-status-orange font-mono">{p.value}</div>
</div>
))}
</div>
@ -830,8 +781,8 @@ function NewScenarioModal({ isOpen, onClose, onSubmit }: {
</select>
</ModalField>
<ModalField label="유출량">
<div style={{ display: 'flex', gap: '4px' }}>
<input className="prd-i" type="number" value={amount} onChange={e => setAmount(e.target.value)} style={{ flex: 1 }} />
<div className="flex gap-1">
<input className="prd-i flex-1" type="number" value={amount} onChange={e => setAmount(e.target.value)} />
<select className="prd-i" value={unit} onChange={e => setUnit(e.target.value)} style={{ width: '60px' }}>
{['t', 'kg', 'm³', 'L'].map(u => <option key={u} value={u}>{u}</option>)}
</select>
@ -876,17 +827,12 @@ function NewScenarioModal({ isOpen, onClose, onSubmit }: {
</div>
{/* Footer */}
<div style={{ padding: '14px 20px', borderTop: '1px solid var(--bd)', display: 'flex', gap: '8px' }}>
<button onClick={onClose} style={{
flex: 1, padding: '10px', fontSize: '12px', fontWeight: 600,
borderRadius: '8px', cursor: 'pointer',
background: 'var(--bg3)', border: '1px solid var(--bd)', color: 'var(--t2)',
}}></button>
<button onClick={handleSubmit} style={{
flex: 2, padding: '10px', fontSize: '12px', fontWeight: 700,
borderRadius: '8px', cursor: 'pointer',
<div className="flex gap-2 border-t border-border" style={{ padding: '14px 20px' }}>
<button onClick={onClose} className="flex-1 text-[12px] font-semibold cursor-pointer rounded-md text-text-2" style={{ padding: '10px', background: 'var(--bg3)', border: '1px solid var(--bd)' }}></button>
<button onClick={handleSubmit} className="cursor-pointer rounded-md text-[12px] font-bold text-white" style={{
flex: 2, padding: '10px',
background: 'linear-gradient(135deg, var(--orange), #ef4444)',
border: 'none', color: '#fff',
border: 'none',
opacity: name.trim() ? 1 : 0.5,
}}>
🧪
@ -901,12 +847,8 @@ function NewScenarioModal({ isOpen, onClose, onSubmit }: {
function ModalSection({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div>
<div style={{
fontSize: '11px', fontWeight: 700, color: 'var(--orange)',
marginBottom: '8px',
paddingBottom: '4px', borderBottom: '1px solid rgba(249,115,22,0.15)',
}}>{title}</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>{children}</div>
<div className="text-[11px] font-bold text-status-orange mb-2 pb-1" style={{ borderBottom: '1px solid rgba(249,115,22,0.15)' }}>{title}</div>
<div className="flex flex-col gap-2">{children}</div>
</div>
)
}
@ -914,7 +856,7 @@ function ModalSection({ title, children }: { title: string; children: React.Reac
function ModalField({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div>
<div style={{ fontSize: '9px', fontWeight: 600, color: 'var(--t3)', marginBottom: '4px' }}>{label}</div>
<div className="text-[9px] font-semibold text-text-3 mb-1">{label}</div>
{children}
</div>
)

파일 보기

@ -179,35 +179,37 @@ ${styles}
]
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', width: '100%', flex: 1, overflow: 'hidden', background: 'var(--bg0)' }}>
<div ref={contentRef} style={{ flex: 1, overflowY: 'auto', scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent', padding: 20 }}>
<div className="flex flex-col h-full w-full flex-1 overflow-hidden" style={{ background: 'var(--bg0)' }}>
<div ref={contentRef} className="flex-1 overflow-y-auto p-5" style={{ scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
{/* 헤더 */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 20 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{ width: 42, height: 42, borderRadius: 10, background: 'linear-gradient(135deg,rgba(249,115,22,.2),rgba(239,68,68,.15))', border: '1px solid rgba(249,115,22,.3)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 20 }}>🧬</div>
<div className="flex items-center justify-between mb-5">
<div className="flex items-center gap-3">
<div className="flex items-center justify-center text-[20px] shrink-0" style={{ width: 42, height: 42, borderRadius: 10, background: 'linear-gradient(135deg,rgba(249,115,22,.2),rgba(239,68,68,.15))', border: '1px solid rgba(249,115,22,.3)' }}>🧬</div>
<div>
<div style={{ fontSize: 16, fontWeight: 700 }}>HNS </div>
<div style={{ fontSize: 10, color: 'var(--t3)', marginTop: 2 }}>SEBC · CHRIS/CAMEO DB · AEGL/ERPG/IDLH · 6,500+ </div>
<div className="text-base font-bold">HNS </div>
<div className="text-[10px] text-text-3 mt-0.5">SEBC · CHRIS/CAMEO DB · AEGL/ERPG/IDLH · 6,500+ </div>
</div>
</div>
<div style={{ display: 'flex', gap: 6 }} data-html2pdf-ignore>
<div className="flex gap-1.5" data-html2pdf-ignore>
<input
type="text"
placeholder="물질명 또는 CAS 번호 검색..."
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
style={{ padding: '6px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg3)', fontSize: 10, width: 200, outline: 'none' }}
className="rounded-sm border border-border text-[10px] outline-none"
style={{ padding: '6px 12px', background: 'var(--bg3)', width: 200 }}
/>
<button
onClick={handleExportPDF}
style={{ padding: '6px 14px', borderRadius: 6, border: '1px solid rgba(249,115,22,.3)', background: 'rgba(249,115,22,.08)', color: 'var(--orange)', fontSize: 10, fontWeight: 600, cursor: 'pointer' }}
className="rounded-sm text-[10px] font-semibold cursor-pointer text-status-orange"
style={{ padding: '6px 14px', border: '1px solid rgba(249,115,22,.3)', background: 'rgba(249,115,22,.08)' }}
>📥 DB </button>
</div>
</div>
{/* 서브탭 */}
<div style={{ display: 'flex', gap: 3, background: 'var(--bg3)', borderRadius: 8, padding: 4, marginBottom: 20, border: '1px solid var(--bd)' }} data-html2pdf-ignore>
<div className="flex rounded-md border border-border mb-5" style={{ gap: 3, background: 'var(--bg3)', padding: 4 }} data-html2pdf-ignore>
{tabLabels.map((tab, idx) => (
<button
key={idx}
@ -221,66 +223,66 @@ ${styles}
{/* ═══ MAT PANEL 0: SEBC 거동분류 ═══ */}
{activeTab === 0 && (
<div>
<div style={{ background: 'linear-gradient(135deg,rgba(249,115,22,.05),rgba(6,182,212,.03))', border: '1px solid rgba(249,115,22,.2)', borderRadius: 12, padding: 16, marginBottom: 16, position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 3, background: 'linear-gradient(90deg,var(--orange),var(--cyan),var(--green),var(--purple))' }} />
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
<span style={{ fontSize: 13, fontWeight: 700, color: 'var(--orange)' }}>SEBC </span>
<span style={{ padding: '2px 8px', borderRadius: 10, background: 'rgba(249,115,22,.1)', fontSize: 8, color: 'var(--orange)', fontWeight: 600 }}>Standard European Behaviour Classification</span>
<div className="rounded-[12px] p-4 mb-4 relative overflow-hidden" style={{ background: 'linear-gradient(135deg,rgba(249,115,22,.05),rgba(6,182,212,.03))', border: '1px solid rgba(249,115,22,.2)' }}>
<div className="absolute top-0 left-0 right-0" style={{ height: 3, background: 'linear-gradient(90deg,var(--orange),var(--cyan),var(--green),var(--purple))' }} />
<div className="flex items-center gap-2 mb-3">
<span className="text-[13px] font-bold text-status-orange">SEBC </span>
<span className="text-[8px] font-semibold text-status-orange" style={{ padding: '2px 8px', borderRadius: 10, background: 'rgba(249,115,22,.1)' }}>Standard European Behaviour Classification</span>
</div>
<div style={{ fontSize: 10, color: 'var(--t2)', lineHeight: 1.7, marginBottom: 14 }}>
<div className="text-[10px] text-text-2 leading-[1.7] mb-[14px]">
HNS <b style={{ color: 'var(--orange)' }}>· </b> . , , 5 , .
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5,1fr)', gap: 8, marginBottom: 14 }}>
{/* G: Gas */}
<div style={{ padding: 12, background: 'rgba(168,85,247,.06)', border: '1px solid rgba(168,85,247,.2)', borderRadius: 8, textAlign: 'center' }}>
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(168,85,247,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}>💨</div>
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--purple)', fontFamily: 'var(--fM)' }}>G</div>
<div style={{ fontSize: 11, fontWeight: 700, margin: '4px 0' }}>Gas</div>
<div style={{ fontSize: 8, color: 'var(--t2)', lineHeight: 1.5 }}> . </div>
<div style={{ marginTop: 6, padding: 3, background: 'rgba(168,85,247,.08)', borderRadius: 3, fontSize: 7, color: 'var(--purple)', fontWeight: 600 }}> </div>
<div className="p-3 rounded-md text-center" style={{ background: 'rgba(168,85,247,.06)', border: '1px solid rgba(168,85,247,.2)' }}>
<div className="flex items-center justify-center text-[18px] mx-auto mb-2" style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(168,85,247,.15)' }}>💨</div>
<div className="text-[13px] font-mono text-primary-purple" style={{ fontWeight: 800 }}>G</div>
<div className="text-[11px] font-bold" style={{ margin: '4px 0' }}>Gas</div>
<div className="text-[8px] text-text-2 leading-normal"> . </div>
<div className="mt-1.5 text-[7px] font-semibold text-primary-purple" style={{ padding: 3, background: 'rgba(168,85,247,.08)', borderRadius: 3 }}> </div>
</div>
{/* E: Evaporator */}
<div style={{ padding: 12, background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.2)', borderRadius: 8, textAlign: 'center' }}>
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(239,68,68,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}>🔥</div>
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--red)', fontFamily: 'var(--fM)' }}>E</div>
<div style={{ fontSize: 11, fontWeight: 700, margin: '4px 0' }}>Evaporator</div>
<div style={{ fontSize: 8, color: 'var(--t2)', lineHeight: 1.5 }}> . </div>
<div style={{ marginTop: 6, padding: 3, background: 'rgba(239,68,68,.08)', borderRadius: 3, fontSize: 7, color: 'var(--red)', fontWeight: 600 }}>+ </div>
<div className="p-3 rounded-md text-center" style={{ background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.2)' }}>
<div className="flex items-center justify-center text-[18px] mx-auto mb-2" style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(239,68,68,.15)' }}>🔥</div>
<div className="text-[13px] font-mono text-status-red" style={{ fontWeight: 800 }}>E</div>
<div className="text-[11px] font-bold" style={{ margin: '4px 0' }}>Evaporator</div>
<div className="text-[8px] text-text-2 leading-normal"> . </div>
<div className="mt-1.5 text-[7px] font-semibold text-status-red" style={{ padding: 3, background: 'rgba(239,68,68,.08)', borderRadius: 3 }}>+ </div>
</div>
{/* F: Floater */}
<div style={{ padding: 12, background: 'rgba(234,179,8,.06)', border: '1px solid rgba(234,179,8,.2)', borderRadius: 8, textAlign: 'center' }}>
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(234,179,8,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}>🟡</div>
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--yellow)', fontFamily: 'var(--fM)' }}>F</div>
<div style={{ fontSize: 11, fontWeight: 700, margin: '4px 0' }}>Floater</div>
<div style={{ fontSize: 8, color: 'var(--t2)', lineHeight: 1.5 }}>{'해수면 위에 부유. 비중 < 1.0, 불용성 물질'}</div>
<div style={{ marginTop: 6, padding: 3, background: 'rgba(234,179,8,.08)', borderRadius: 3, fontSize: 7, color: 'var(--yellow)', fontWeight: 600 }}> </div>
<div className="p-3 rounded-md text-center" style={{ background: 'rgba(234,179,8,.06)', border: '1px solid rgba(234,179,8,.2)' }}>
<div className="flex items-center justify-center text-[18px] mx-auto mb-2" style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(234,179,8,.15)' }}>🟡</div>
<div className="text-[13px] font-mono text-status-yellow" style={{ fontWeight: 800 }}>F</div>
<div className="text-[11px] font-bold" style={{ margin: '4px 0' }}>Floater</div>
<div className="text-[8px] text-text-2 leading-normal">{'해수면 위에 부유. 비중 < 1.0, 불용성 물질'}</div>
<div className="mt-1.5 text-[7px] font-semibold text-status-yellow" style={{ padding: 3, background: 'rgba(234,179,8,.08)', borderRadius: 3 }}> </div>
</div>
{/* D: Dissolver */}
<div style={{ padding: 12, background: 'rgba(6,182,212,.06)', border: '1px solid rgba(6,182,212,.2)', borderRadius: 8, textAlign: 'center' }}>
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(6,182,212,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}>💧</div>
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>D</div>
<div style={{ fontSize: 11, fontWeight: 700, margin: '4px 0' }}>Dissolver</div>
<div style={{ fontSize: 8, color: 'var(--t2)', lineHeight: 1.5 }}> . </div>
<div style={{ marginTop: 6, padding: 3, background: 'rgba(6,182,212,.08)', borderRadius: 3, fontSize: 7, color: 'var(--cyan)', fontWeight: 600 }}> </div>
<div className="p-3 rounded-md text-center" style={{ background: 'rgba(6,182,212,.06)', border: '1px solid rgba(6,182,212,.2)' }}>
<div className="flex items-center justify-center text-[18px] mx-auto mb-2" style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(6,182,212,.15)' }}>💧</div>
<div className="text-[13px] font-mono text-primary-cyan" style={{ fontWeight: 800 }}>D</div>
<div className="text-[11px] font-bold" style={{ margin: '4px 0' }}>Dissolver</div>
<div className="text-[8px] text-text-2 leading-normal"> . </div>
<div className="mt-1.5 text-[7px] font-semibold text-primary-cyan" style={{ padding: 3, background: 'rgba(6,182,212,.08)', borderRadius: 3 }}> </div>
</div>
{/* S: Sinker */}
<div style={{ padding: 12, background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.2)', borderRadius: 8, textAlign: 'center' }}>
<div style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(34,197,94,.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 8px', fontSize: 18 }}></div>
<div style={{ fontSize: 13, fontWeight: 800, color: 'var(--green)', fontFamily: 'var(--fM)' }}>S</div>
<div style={{ fontSize: 11, fontWeight: 700, margin: '4px 0' }}>Sinker</div>
<div style={{ fontSize: 8, color: 'var(--t2)', lineHeight: 1.5 }}>{'해저로 침강. 비중 > 1.0, 저층 오염 축적'}</div>
<div style={{ marginTop: 6, padding: 3, background: 'rgba(34,197,94,.08)', borderRadius: 3, fontSize: 7, color: 'var(--green)', fontWeight: 600 }}> 3D </div>
<div className="p-3 rounded-md text-center" style={{ background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.2)' }}>
<div className="flex items-center justify-center text-[18px] mx-auto mb-2" style={{ width: 36, height: 36, borderRadius: '50%', background: 'rgba(34,197,94,.15)' }}></div>
<div className="text-[13px] font-mono text-status-green" style={{ fontWeight: 800 }}>S</div>
<div className="text-[11px] font-bold" style={{ margin: '4px 0' }}>Sinker</div>
<div className="text-[8px] text-text-2 leading-normal">{'해저로 침강. 비중 > 1.0, 저층 오염 축적'}</div>
<div className="mt-1.5 text-[7px] font-semibold text-status-green" style={{ padding: 3, background: 'rgba(34,197,94,.08)', borderRadius: 3 }}> 3D </div>
</div>
</div>
{/* 복합 거동 */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 8, padding: 12 }}>
<div style={{ fontSize: 10, fontWeight: 700, marginBottom: 8 }}>🔀 </div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5,1fr)', gap: 6, fontSize: 8, textAlign: 'center' }}>
<div style={{ padding: 6, background: 'var(--bg0)', borderRadius: 4 }}><span style={{ fontWeight: 700, color: 'var(--purple)' }}>GD</span><br/><span style={{ color: 'var(--t3)' }}>+</span></div>
<div style={{ padding: 6, background: 'var(--bg0)', borderRadius: 4 }}><span style={{ fontWeight: 700, color: 'var(--red)' }}>ED</span><br/><span style={{ color: 'var(--t3)' }}>+</span></div>
<div style={{ padding: 6, background: 'var(--bg0)', borderRadius: 4 }}><span style={{ fontWeight: 700, color: 'var(--yellow)' }}>FE</span><br/><span style={{ color: 'var(--t3)' }}>+</span></div>
<div style={{ padding: 6, background: 'var(--bg0)', borderRadius: 4 }}><span style={{ fontWeight: 700, color: 'var(--cyan)' }}>FED</span><br/><span style={{ color: 'var(--t3)' }}>++</span></div>
<div style={{ padding: 6, background: 'var(--bg0)', borderRadius: 4 }}><span style={{ fontWeight: 700, color: 'var(--green)' }}>SD</span><br/><span style={{ color: 'var(--t3)' }}>+</span></div>
<div className="rounded-md p-3 border border-border" style={{ background: 'var(--bg3)' }}>
<div className="text-[10px] font-bold mb-2">🔀 </div>
<div className="text-center" style={{ display: 'grid', gridTemplateColumns: 'repeat(5,1fr)', gap: 6, fontSize: 8 }}>
<div className="rounded" style={{ padding: 6, background: 'var(--bg0)' }}><span className="font-bold text-primary-purple">GD</span><br/><span className="text-text-3">+</span></div>
<div className="rounded" style={{ padding: 6, background: 'var(--bg0)' }}><span className="font-bold text-status-red">ED</span><br/><span className="text-text-3">+</span></div>
<div className="rounded" style={{ padding: 6, background: 'var(--bg0)' }}><span className="font-bold text-status-yellow">FE</span><br/><span className="text-text-3">+</span></div>
<div className="rounded" style={{ padding: 6, background: 'var(--bg0)' }}><span className="font-bold text-primary-cyan">FED</span><br/><span className="text-text-3">++</span></div>
<div className="rounded" style={{ padding: 6, background: 'var(--bg0)' }}><span className="font-bold text-status-green">SD</span><br/><span className="text-text-3">+</span></div>
</div>
</div>
</div>
@ -291,11 +293,12 @@ ${styles}
{activeTab === 1 && (
<div>
{/* 카테고리 필터 */}
<div style={{ display: 'flex', gap: 6, marginBottom: 14 }} data-html2pdf-ignore>
<div className="flex gap-1.5 mb-[14px]" data-html2pdf-ignore>
{categories.map(cat => (
<button key={cat.id} onClick={() => setSelectedCategory(cat.id)}
className="rounded-sm text-[10px] font-semibold cursor-pointer"
style={{
padding: '6px 12px', borderRadius: 6, fontSize: 10, fontWeight: 600, cursor: 'pointer',
padding: '6px 12px',
border: selectedCategory === cat.id ? '1px solid var(--orange)' : '1px solid var(--bd)',
color: selectedCategory === cat.id ? 'var(--orange)' : 'var(--t3)',
background: selectedCategory === cat.id ? 'rgba(249,115,22,.08)' : 'var(--bg3)',
@ -309,125 +312,125 @@ ${styles}
{/* 암모니아 */}
{filtered.find(s => s.casNumber === '7664-41-7') && (
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--purple)' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--purple)', fontFamily: 'var(--fM)' }}>NH</span> <span style={{ fontSize: 12, fontWeight: 700 }}></span></div>
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(168,85,247,.1)', fontSize: 8, color: 'var(--purple)', fontWeight: 600 }}>G/GD</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(239,68,68,.1)', fontSize: 8, color: 'var(--red)', fontWeight: 600 }}></span></div>
<div className="rounded-[10px] p-[14px] border border-border" style={{ background: 'var(--bg3)', borderLeft: '4px solid var(--purple)' }}>
<div className="flex items-center justify-between mb-[10px]">
<div><span className="text-sm font-mono text-primary-purple" style={{ fontWeight: 800 }}>NH</span> <span className="text-xs font-bold"></span></div>
<div className="flex gap-1"><span className="text-[8px] font-semibold text-primary-purple" style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(168,85,247,.1)' }}>G/GD</span><span className="text-[8px] font-semibold text-status-red" style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(239,68,68,.1)' }}></span></div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, marginBottom: 8 }}>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ fontFamily: 'var(--fM)' }}>7664-41-7</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ fontFamily: 'var(--fM)' }}>17.03</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>-33.4°C</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ fontFamily: 'var(--fM)' }}>0.73</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ fontFamily: 'var(--fM)' }}>N/A ()</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}> </span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">CAS:</span> <span className="font-mono">7664-41-7</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono">17.03</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono text-primary-cyan">-33.4°C</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono">0.73</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono">N/A ()</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono text-primary-cyan"> </span></div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4, fontSize: 7, textAlign: 'center' }}>
<div style={{ padding: 4, background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.12)', borderRadius: 3 }}><span style={{ color: 'var(--green)' }}>AEGL-2</span><br/><b>160 ppm</b></div>
<div style={{ padding: 4, background: 'rgba(249,115,22,.06)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 3 }}><span style={{ color: 'var(--orange)' }}>ERPG-2</span><br/><b>150 ppm</b></div>
<div style={{ padding: 4, background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 3 }}><span style={{ color: 'var(--red)' }}>IDLH</span><br/><b>300 ppm</b></div>
<div className="text-center" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4, fontSize: 7 }}>
<div style={{ padding: 4, background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.12)', borderRadius: 3 }}><span className="text-status-green">AEGL-2</span><br/><b>160 ppm</b></div>
<div style={{ padding: 4, background: 'rgba(249,115,22,.06)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 3 }}><span className="text-status-orange">ERPG-2</span><br/><b>150 ppm</b></div>
<div style={{ padding: 4, background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 3 }}><span className="text-status-red">IDLH</span><br/><b>300 ppm</b></div>
</div>
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', lineHeight: 1.5 }}> . . .</div>
<div className="mt-1.5 text-[8px] text-text-3 leading-normal"> . . .</div>
</div>
)}
{/* 메탄올 */}
{filtered.find(s => s.casNumber === '67-56-1') && (
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--cyan)' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>CHOH</span> <span style={{ fontSize: 12, fontWeight: 700 }}></span></div>
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(6,182,212,.1)', fontSize: 8, color: 'var(--cyan)', fontWeight: 600 }}>ED</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(249,115,22,.1)', fontSize: 8, color: 'var(--orange)', fontWeight: 600 }}></span></div>
<div className="rounded-[10px] p-[14px] border border-border" style={{ background: 'var(--bg3)', borderLeft: '4px solid var(--cyan)' }}>
<div className="flex items-center justify-between mb-[10px]">
<div><span className="text-sm font-mono text-primary-cyan" style={{ fontWeight: 800 }}>CHOH</span> <span className="text-xs font-bold"></span></div>
<div className="flex gap-1"><span className="text-[8px] font-semibold text-primary-cyan" style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(6,182,212,.1)' }}>ED</span><span className="text-[8px] font-semibold text-status-orange" style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(249,115,22,.1)' }}></span></div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, marginBottom: 8 }}>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ fontFamily: 'var(--fM)' }}>67-56-1</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ fontFamily: 'var(--fM)' }}>32.04</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>64.7°C</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ fontFamily: 'var(--fM)' }}>0.79</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>11°C</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}> </span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">CAS:</span> <span className="font-mono">67-56-1</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono">32.04</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono text-primary-cyan">64.7°C</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono">0.79</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono text-status-orange">11°C</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono text-primary-cyan"> </span></div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4, fontSize: 7, textAlign: 'center' }}>
<div style={{ padding: 4, background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.12)', borderRadius: 3 }}><span style={{ color: 'var(--green)' }}>AEGL-2</span><br/><b>2,100 ppm</b></div>
<div style={{ padding: 4, background: 'rgba(249,115,22,.06)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 3 }}><span style={{ color: 'var(--orange)' }}>ERPG-2</span><br/><b>1,000 ppm</b></div>
<div style={{ padding: 4, background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 3 }}><span style={{ color: 'var(--red)' }}>IDLH</span><br/><b>6,000 ppm</b></div>
<div className="text-center" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 4, fontSize: 7 }}>
<div style={{ padding: 4, background: 'rgba(34,197,94,.06)', border: '1px solid rgba(34,197,94,.12)', borderRadius: 3 }}><span className="text-status-green">AEGL-2</span><br/><b>2,100 ppm</b></div>
<div style={{ padding: 4, background: 'rgba(249,115,22,.06)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 3 }}><span className="text-status-orange">ERPG-2</span><br/><b>1,000 ppm</b></div>
<div style={{ padding: 4, background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 3 }}><span className="text-status-red">IDLH</span><br/><b>6,000 ppm</b></div>
</div>
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', lineHeight: 1.5 }}> . . . 2007 FODDANGER호 95L .</div>
<div className="mt-1.5 text-[8px] text-text-3 leading-normal"> . . . 2007 FODDANGER호 95L .</div>
</div>
)}
{/* 수소 */}
{filtered.find(s => s.casNumber === '1333-74-0') && (
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--red)' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--red)', fontFamily: 'var(--fM)' }}>H</span> <span style={{ fontSize: 12, fontWeight: 700 }}></span></div>
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(168,85,247,.1)', fontSize: 8, color: 'var(--purple)', fontWeight: 600 }}>G</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(239,68,68,.1)', fontSize: 8, color: 'var(--red)', fontWeight: 600 }}></span></div>
<div className="rounded-[10px] p-[14px] border border-border" style={{ background: 'var(--bg3)', borderLeft: '4px solid var(--red)' }}>
<div className="flex items-center justify-between mb-[10px]">
<div><span className="text-sm font-mono text-status-red" style={{ fontWeight: 800 }}>H</span> <span className="text-xs font-bold"></span></div>
<div className="flex gap-1"><span className="text-[8px] font-semibold text-primary-purple" style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(168,85,247,.1)' }}>G</span><span className="text-[8px] font-semibold text-status-red" style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(239,68,68,.1)' }}></span></div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, marginBottom: 8 }}>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ fontFamily: 'var(--fM)' }}>1333-74-0</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ fontFamily: 'var(--fM)' }}>2.016</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>-252.9°C</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>():</span> <span style={{ fontFamily: 'var(--fM)' }}>0.07</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>LFL:</span> <span style={{ color: 'var(--red)', fontFamily: 'var(--fM)' }}>4.0%</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>UFL:</span> <span style={{ color: 'var(--red)', fontFamily: 'var(--fM)' }}>75.0%</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">CAS:</span> <span className="font-mono">1333-74-0</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono">2.016</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono text-primary-cyan">-252.9°C</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">():</span> <span className="font-mono">0.07</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">LFL:</span> <span className="font-mono text-status-red">4.0%</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">UFL:</span> <span className="font-mono text-status-red">75.0%</span></div>
</div>
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', lineHeight: 1.5 }}> (4~75%). · . BLEVE . .</div>
<div className="mt-1.5 text-[8px] text-text-3 leading-normal"> (4~75%). · . BLEVE . .</div>
</div>
)}
{/* LNG */}
{filtered.find(s => s.nameEn === 'LPG (Propane/Butane)' || s.casNumber === '74-82-8') && (
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--orange)' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--orange)', fontFamily: 'var(--fM)' }}>CH</span> <span style={{ fontSize: 12, fontWeight: 700 }}>LNG ()</span></div>
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(168,85,247,.1)', fontSize: 8, color: 'var(--purple)', fontWeight: 600 }}>G</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(249,115,22,.1)', fontSize: 8, color: 'var(--orange)', fontWeight: 600 }}>/</span></div>
<div className="rounded-[10px] p-[14px] border border-border" style={{ background: 'var(--bg3)', borderLeft: '4px solid var(--orange)' }}>
<div className="flex items-center justify-between mb-[10px]">
<div><span className="text-sm font-mono text-status-orange" style={{ fontWeight: 800 }}>CH</span> <span className="text-xs font-bold">LNG ()</span></div>
<div className="flex gap-1"><span className="text-[8px] font-semibold text-primary-purple" style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(168,85,247,.1)' }}>G</span><span className="text-[8px] font-semibold text-status-orange" style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(249,115,22,.1)' }}>/</span></div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, marginBottom: 8 }}>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ fontFamily: 'var(--fM)' }}>74-82-8</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ fontFamily: 'var(--fM)' }}>16.04</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>-161.5°C</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>():</span> <span style={{ fontFamily: 'var(--fM)' }}>0.42</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>LFL:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>5.0%</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>UFL:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>15.0%</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">CAS:</span> <span className="font-mono">74-82-8</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono">16.04</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono text-primary-cyan">-161.5°C</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">():</span> <span className="font-mono">0.42</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">LFL:</span> <span className="font-mono text-status-orange">5.0%</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">UFL:</span> <span className="font-mono text-status-orange">15.0%</span></div>
</div>
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', lineHeight: 1.5 }}>(-162°C) RPT(), Pool Fire . Flash . · LNG .</div>
<div className="mt-1.5 text-[8px] text-text-3 leading-normal">(-162°C) RPT(), Pool Fire . Flash . · LNG .</div>
</div>
)}
{/* 페놀 */}
{filtered.find(s => s.casNumber === '108-95-2' || s.name === '페놀') && (
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--green)' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--green)', fontFamily: 'var(--fM)' }}>CHOH</span> <span style={{ fontSize: 12, fontWeight: 700 }}></span></div>
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(34,197,94,.1)', fontSize: 8, color: 'var(--green)', fontWeight: 600 }}>S/SD</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(239,68,68,.1)', fontSize: 8, color: 'var(--red)', fontWeight: 600 }}></span></div>
<div className="rounded-[10px] p-[14px] border border-border" style={{ background: 'var(--bg3)', borderLeft: '4px solid var(--green)' }}>
<div className="flex items-center justify-between mb-[10px]">
<div><span className="text-sm font-mono text-status-green" style={{ fontWeight: 800 }}>CHOH</span> <span className="text-xs font-bold"></span></div>
<div className="flex gap-1"><span className="text-[8px] font-semibold text-status-green" style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(34,197,94,.1)' }}>S/SD</span><span className="text-[8px] font-semibold text-status-red" style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(239,68,68,.1)' }}></span></div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, marginBottom: 8 }}>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ fontFamily: 'var(--fM)' }}>108-95-2</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ fontFamily: 'var(--fM)' }}>94.11</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ fontFamily: 'var(--fM)' }}>181.7°C</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ color: 'var(--green)', fontFamily: 'var(--fM)' }}>1.07 ()</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>79°C</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>84 g/L</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">CAS:</span> <span className="font-mono">108-95-2</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono">94.11</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono">181.7°C</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono text-status-green">1.07 ()</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono text-status-orange">79°C</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono text-primary-cyan">84 g/L</span></div>
</div>
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', lineHeight: 1.5 }}> 1.07 <b style={{ color: 'var(--green)' }}>Sinker </b> . ROMS 3.5. HNS (31.8kg/).</div>
<div className="mt-1.5 text-[8px] text-text-3 leading-normal"> 1.07 <b style={{ color: 'var(--green)' }}>Sinker </b> . ROMS 3.5. HNS (31.8kg/).</div>
</div>
)}
{/* 톨루엔 */}
{filtered.find(s => s.casNumber === '108-88-3') && (
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 14, borderLeft: '4px solid var(--yellow)' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
<div><span style={{ fontSize: 14, fontWeight: 800, color: 'var(--yellow)', fontFamily: 'var(--fM)' }}>CH</span> <span style={{ fontSize: 12, fontWeight: 700 }}></span></div>
<div style={{ display: 'flex', gap: 4 }}><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(234,179,8,.1)', fontSize: 8, color: 'var(--yellow)', fontWeight: 600 }}>FE</span><span style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(249,115,22,.1)', fontSize: 8, color: 'var(--orange)', fontWeight: 600 }}></span></div>
<div className="rounded-[10px] p-[14px] border border-border" style={{ background: 'var(--bg3)', borderLeft: '4px solid var(--yellow)' }}>
<div className="flex items-center justify-between mb-[10px]">
<div><span className="text-sm font-mono text-status-yellow" style={{ fontWeight: 800 }}>CH</span> <span className="text-xs font-bold"></span></div>
<div className="flex gap-1"><span className="text-[8px] font-semibold text-status-yellow" style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(234,179,8,.1)' }}>FE</span><span className="text-[8px] font-semibold text-status-orange" style={{ padding: '2px 6px', borderRadius: 3, background: 'rgba(249,115,22,.1)' }}></span></div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 8, marginBottom: 8 }}>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>CAS:</span> <span style={{ fontFamily: 'var(--fM)' }}>108-88-3</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ fontFamily: 'var(--fM)' }}>92.14</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ fontFamily: 'var(--fM)' }}>110.6°C</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ color: 'var(--yellow)', fontFamily: 'var(--fM)' }}>0.87 ()</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>4°C</span></div>
<div style={{ padding: '4px 6px', background: 'var(--bg0)', borderRadius: 3 }}><span style={{ color: 'var(--t3)' }}>:</span> <span style={{ fontFamily: 'var(--fM)' }}>0.52 g/L</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">CAS:</span> <span className="font-mono">108-88-3</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono">92.14</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono">110.6°C</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono text-status-yellow">0.87 ()</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono text-status-orange">4°C</span></div>
<div className="rounded" style={{ padding: '4px 6px', background: 'var(--bg0)' }}><span className="text-text-3">:</span> <span className="font-mono">0.52 g/L</span></div>
</div>
<div style={{ marginTop: 6, fontSize: 8, color: 'var(--t3)', lineHeight: 1.5 }}> . (4°C) . HNS. .</div>
<div className="mt-1.5 text-[8px] text-text-3 leading-normal"> . (4°C) . HNS. .</div>
</div>
)}
</div>
@ -439,127 +442,127 @@ ${styles}
<div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 16 }}>
{/* AEGL 기준 */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, borderTop: '3px solid var(--green)' }}>
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--green)', marginBottom: 10 }}>🟢 AEGL (Acute Exposure Guideline Level)</div>
<div style={{ fontSize: 9, color: 'var(--t3)', marginBottom: 10 }}> EPA (10, 30, 60, 4, 8)</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<div style={{ padding: '8px 10px', background: 'rgba(34,197,94,.04)', border: '1px solid rgba(34,197,94,.12)', borderRadius: 6, borderLeft: '4px solid var(--green)' }}>
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--green)' }}>AEGL-1 ()</div>
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.5 }}> , . .</div>
<div className="rounded-[10px] p-4 border border-border" style={{ background: 'var(--bg3)', borderTop: '3px solid var(--green)' }}>
<div className="text-[13px] font-bold text-status-green mb-[10px]">🟢 AEGL (Acute Exposure Guideline Level)</div>
<div className="text-[9px] text-text-3 mb-[10px]"> EPA (10, 30, 60, 4, 8)</div>
<div className="flex flex-col gap-1.5">
<div className="rounded-sm" style={{ padding: '8px 10px', background: 'rgba(34,197,94,.04)', border: '1px solid rgba(34,197,94,.12)', borderLeft: '4px solid var(--green)' }}>
<div className="text-[11px] font-bold text-status-green">AEGL-1 ()</div>
<div className="text-[9px] text-text-2 leading-normal"> , . .</div>
</div>
<div style={{ padding: '8px 10px', background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 6, borderLeft: '4px solid var(--orange)' }}>
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--orange)' }}>AEGL-2 ( )</div>
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.5 }}>· . <b style={{ color: 'var(--orange)' }}> </b>.</div>
<div className="rounded-sm" style={{ padding: '8px 10px', background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.12)', borderLeft: '4px solid var(--orange)' }}>
<div className="text-[11px] font-bold text-status-orange">AEGL-2 ( )</div>
<div className="text-[9px] text-text-2 leading-normal">· . <b style={{ color: 'var(--orange)' }}> </b>.</div>
</div>
<div style={{ padding: '8px 10px', background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 6, borderLeft: '4px solid var(--red)' }}>
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--red)' }}>AEGL-3 ( )</div>
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.5 }}> . <b style={{ color: 'var(--red)' }}> · </b>.</div>
<div className="rounded-sm" style={{ padding: '8px 10px', background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.12)', borderLeft: '4px solid var(--red)' }}>
<div className="text-[11px] font-bold text-status-red">AEGL-3 ( )</div>
<div className="text-[9px] text-text-2 leading-normal"> . <b style={{ color: 'var(--red)' }}> · </b>.</div>
</div>
</div>
</div>
{/* ERPG / IDLH */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, borderTop: '3px solid var(--red)' }}>
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--red)', marginBottom: 10 }}>🔴 ERPG &amp; IDLH</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<div className="rounded-[10px] p-4 border border-border" style={{ background: 'var(--bg3)', borderTop: '3px solid var(--red)' }}>
<div className="text-[13px] font-bold text-status-red mb-[10px]">🔴 ERPG &amp; IDLH</div>
<div className="flex flex-col gap-2">
<div>
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--orange)', marginBottom: 4 }}>ERPG (Emergency Response Planning Guideline)</div>
<div style={{ fontSize: 9, color: 'var(--t3)', marginBottom: 6 }}>AIHA 1 </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<div style={{ padding: '6px 8px', background: 'rgba(34,197,94,.03)', border: '1px solid rgba(34,197,94,.1)', borderRadius: 4, fontSize: 8, borderLeft: '3px solid var(--green)' }}><b style={{ color: 'var(--green)' }}>ERPG-1</b> , </div>
<div style={{ padding: '6px 8px', background: 'rgba(249,115,22,.03)', border: '1px solid rgba(249,115,22,.1)', borderRadius: 4, fontSize: 8, borderLeft: '3px solid var(--orange)' }}><b style={{ color: 'var(--orange)' }}>ERPG-2</b> , </div>
<div style={{ padding: '6px 8px', background: 'rgba(239,68,68,.03)', border: '1px solid rgba(239,68,68,.1)', borderRadius: 4, fontSize: 8, borderLeft: '3px solid var(--red)' }}><b style={{ color: 'var(--red)' }}>ERPG-3</b> </div>
<div className="text-[10px] font-bold text-status-orange mb-1">ERPG (Emergency Response Planning Guideline)</div>
<div className="text-[9px] text-text-3 mb-1.5">AIHA 1 </div>
<div className="flex flex-col gap-1">
<div className="text-[8px] rounded" style={{ padding: '6px 8px', background: 'rgba(34,197,94,.03)', border: '1px solid rgba(34,197,94,.1)', borderLeft: '3px solid var(--green)' }}><b style={{ color: 'var(--green)' }}>ERPG-1</b> , </div>
<div className="text-[8px] rounded" style={{ padding: '6px 8px', background: 'rgba(249,115,22,.03)', border: '1px solid rgba(249,115,22,.1)', borderLeft: '3px solid var(--orange)' }}><b style={{ color: 'var(--orange)' }}>ERPG-2</b> , </div>
<div className="text-[8px] rounded" style={{ padding: '6px 8px', background: 'rgba(239,68,68,.03)', border: '1px solid rgba(239,68,68,.1)', borderLeft: '3px solid var(--red)' }}><b style={{ color: 'var(--red)' }}>ERPG-3</b> </div>
</div>
</div>
<div style={{ marginTop: 4, padding: 10, background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.15)', borderRadius: 6 }}>
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--red)', marginBottom: 4 }}>IDLH (Immediately Dangerous to Life or Health)</div>
<div style={{ fontSize: 9, color: 'var(--t3)', marginBottom: 4 }}>NIOSH 30 · </div>
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.5 }}><b style={{ color: 'var(--red)' }}> </b> (SCBA) . WING .</div>
<div className="mt-1 rounded-sm" style={{ padding: 10, background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.15)' }}>
<div className="text-[10px] font-bold text-status-red mb-1">IDLH (Immediately Dangerous to Life or Health)</div>
<div className="text-[9px] text-text-3 mb-1">NIOSH 30 · </div>
<div className="text-[9px] text-text-2 leading-normal"><b style={{ color: 'var(--red)' }}> </b> (SCBA) . WING .</div>
</div>
</div>
</div>
</div>
{/* 물질별 위험도 비교표 */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16 }}>
<div style={{ fontSize: 12, fontWeight: 700, marginBottom: 12 }}>📊 HNS (ppm)</div>
<div className="rounded-[10px] p-4 border border-border" style={{ background: 'var(--bg3)' }}>
<div className="text-xs font-bold mb-3">📊 HNS (ppm)</div>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 9 }}>
<thead>
<tr style={{ background: 'rgba(168,85,247,.06)' }}>
<th style={{ padding: '7px 8px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', color: 'var(--purple)' }}></th>
<th style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)', color: 'var(--green)' }}>AEGL-1</th>
<th style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)', color: 'var(--orange)' }}>AEGL-2</th>
<th style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)', color: 'var(--red)' }}>AEGL-3</th>
<th style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)', color: 'var(--orange)' }}>ERPG-2</th>
<th style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)', color: 'var(--red)' }}>IDLH</th>
<th style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)', color: 'var(--orange)' }}>LFL(%)</th>
<th style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)', color: 'var(--t2)' }}>SEBC</th>
<th style={{ padding: '7px 8px', textAlign: 'left', borderBottom: '2px solid var(--bdL)' }} className="text-primary-purple"></th>
<th style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)' }} className="text-status-green">AEGL-1</th>
<th style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)' }} className="text-status-orange">AEGL-2</th>
<th style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)' }} className="text-status-red">AEGL-3</th>
<th style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)' }} className="text-status-orange">ERPG-2</th>
<th style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)' }} className="text-status-red">IDLH</th>
<th style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)' }} className="text-status-orange">LFL(%)</th>
<th style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)' }} className="text-text-2">SEBC</th>
</tr>
</thead>
<tbody>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td style={{ padding: '6px 8px', fontWeight: 600, color: 'var(--purple)' }}>NH </td>
<td style={{ textAlign: 'center', color: 'var(--green)', fontFamily: 'var(--fM)' }}>30</td>
<td style={{ textAlign: 'center', color: 'var(--orange)', fontFamily: 'var(--fM)' }}>160</td>
<td style={{ textAlign: 'center', color: 'var(--red)', fontFamily: 'var(--fM)' }}>1,100</td>
<td style={{ textAlign: 'center', fontFamily: 'var(--fM)' }}>150</td>
<td style={{ textAlign: 'center', color: 'var(--red)', fontFamily: 'var(--fM)' }}>300</td>
<td style={{ textAlign: 'center', fontFamily: 'var(--fM)' }}>15.0</td>
<td style={{ textAlign: 'center', fontWeight: 600, color: 'var(--purple)' }}>G/GD</td>
<td style={{ padding: '6px 8px' }} className="font-semibold text-primary-purple">NH </td>
<td className="text-center font-mono text-status-green">30</td>
<td className="text-center font-mono text-status-orange">160</td>
<td className="text-center font-mono text-status-red">1,100</td>
<td className="text-center font-mono">150</td>
<td className="text-center font-mono text-status-red">300</td>
<td className="text-center font-mono">15.0</td>
<td className="text-center font-semibold text-primary-purple">G/GD</td>
</tr>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td style={{ padding: '6px 8px', fontWeight: 600, color: 'var(--cyan)' }}>CHOH </td>
<td style={{ textAlign: 'center', color: 'var(--green)', fontFamily: 'var(--fM)' }}>530</td>
<td style={{ textAlign: 'center', color: 'var(--orange)', fontFamily: 'var(--fM)' }}>2,100</td>
<td style={{ textAlign: 'center', color: 'var(--red)', fontFamily: 'var(--fM)' }}>14,000</td>
<td style={{ textAlign: 'center', fontFamily: 'var(--fM)' }}>1,000</td>
<td style={{ textAlign: 'center', color: 'var(--red)', fontFamily: 'var(--fM)' }}>6,000</td>
<td style={{ textAlign: 'center', fontFamily: 'var(--fM)' }}>6.0</td>
<td style={{ textAlign: 'center', fontWeight: 600, color: 'var(--cyan)' }}>ED</td>
<td style={{ padding: '6px 8px' }} className="font-semibold text-primary-cyan">CHOH </td>
<td className="text-center font-mono text-status-green">530</td>
<td className="text-center font-mono text-status-orange">2,100</td>
<td className="text-center font-mono text-status-red">14,000</td>
<td className="text-center font-mono">1,000</td>
<td className="text-center font-mono text-status-red">6,000</td>
<td className="text-center font-mono">6.0</td>
<td className="text-center font-semibold text-primary-cyan">ED</td>
</tr>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td style={{ padding: '6px 8px', fontWeight: 600, color: 'var(--red)' }}>H </td>
<td style={{ textAlign: 'center', color: 'var(--t3)' }}>-</td>
<td style={{ textAlign: 'center', color: 'var(--t3)' }}>-</td>
<td style={{ textAlign: 'center', color: 'var(--t3)' }}>-</td>
<td style={{ textAlign: 'center', color: 'var(--t3)' }}>-</td>
<td style={{ textAlign: 'center', color: 'var(--t3)' }}>-</td>
<td style={{ textAlign: 'center', color: 'var(--red)', fontFamily: 'var(--fM)', fontWeight: 700 }}>4.0</td>
<td style={{ textAlign: 'center', fontWeight: 600, color: 'var(--purple)' }}>G</td>
<td style={{ padding: '6px 8px' }} className="font-semibold text-status-red">H </td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center font-mono font-bold text-status-red">4.0</td>
<td className="text-center font-semibold text-primary-purple">G</td>
</tr>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td style={{ padding: '6px 8px', fontWeight: 600, color: 'var(--orange)' }}>CH LNG</td>
<td style={{ textAlign: 'center', color: 'var(--t3)' }}>-</td>
<td style={{ textAlign: 'center', color: 'var(--t3)' }}>-</td>
<td style={{ textAlign: 'center', color: 'var(--t3)' }}>-</td>
<td style={{ textAlign: 'center', color: 'var(--t3)' }}>-</td>
<td style={{ textAlign: 'center', color: 'var(--t3)' }}>-</td>
<td style={{ textAlign: 'center', color: 'var(--orange)', fontFamily: 'var(--fM)' }}>5.0</td>
<td style={{ textAlign: 'center', fontWeight: 600, color: 'var(--purple)' }}>G</td>
<td style={{ padding: '6px 8px' }} className="font-semibold text-status-orange">CH LNG</td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center text-text-3">-</td>
<td className="text-center font-mono text-status-orange">5.0</td>
<td className="text-center font-semibold text-primary-purple">G</td>
</tr>
<tr style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td style={{ padding: '6px 8px', fontWeight: 600, color: 'var(--green)' }}>CHOH </td>
<td style={{ textAlign: 'center', color: 'var(--green)', fontFamily: 'var(--fM)' }}>19</td>
<td style={{ textAlign: 'center', color: 'var(--orange)', fontFamily: 'var(--fM)' }}>29</td>
<td style={{ textAlign: 'center', color: 'var(--red)', fontFamily: 'var(--fM)' }}>57</td>
<td style={{ textAlign: 'center', fontFamily: 'var(--fM)' }}>50</td>
<td style={{ textAlign: 'center', color: 'var(--red)', fontFamily: 'var(--fM)' }}>250</td>
<td style={{ textAlign: 'center', fontFamily: 'var(--fM)' }}>1.8</td>
<td style={{ textAlign: 'center', fontWeight: 600, color: 'var(--green)' }}>S/SD</td>
<td style={{ padding: '6px 8px' }} className="font-semibold text-status-green">CHOH </td>
<td className="text-center font-mono text-status-green">19</td>
<td className="text-center font-mono text-status-orange">29</td>
<td className="text-center font-mono text-status-red">57</td>
<td className="text-center font-mono">50</td>
<td className="text-center font-mono text-status-red">250</td>
<td className="text-center font-mono">1.8</td>
<td className="text-center font-semibold text-status-green">S/SD</td>
</tr>
<tr>
<td style={{ padding: '6px 8px', fontWeight: 600, color: 'var(--yellow)' }}>CH </td>
<td style={{ textAlign: 'center', color: 'var(--green)', fontFamily: 'var(--fM)' }}>67</td>
<td style={{ textAlign: 'center', color: 'var(--orange)', fontFamily: 'var(--fM)' }}>560</td>
<td style={{ textAlign: 'center', color: 'var(--red)', fontFamily: 'var(--fM)' }}>3,700</td>
<td style={{ textAlign: 'center', fontFamily: 'var(--fM)' }}>300</td>
<td style={{ textAlign: 'center', color: 'var(--red)', fontFamily: 'var(--fM)' }}>500</td>
<td style={{ textAlign: 'center', fontFamily: 'var(--fM)' }}>1.1</td>
<td style={{ textAlign: 'center', fontWeight: 600, color: 'var(--yellow)' }}>FE</td>
<td style={{ padding: '6px 8px' }} className="font-semibold text-status-yellow">CH </td>
<td className="text-center font-mono text-status-green">67</td>
<td className="text-center font-mono text-status-orange">560</td>
<td className="text-center font-mono text-status-red">3,700</td>
<td className="text-center font-mono">300</td>
<td className="text-center font-mono text-status-red">500</td>
<td className="text-center font-mono">1.1</td>
<td className="text-center font-semibold text-status-yellow">FE</td>
</tr>
</tbody>
</table>
<div style={{ marginTop: 8, fontSize: 7, color: 'var(--t3)' }}> AEGL: 60분 / ERPG: 1시간 / IDLH: 30분 / LFL: 폭발하한</div>
<div className="mt-2 text-[7px] text-text-3"> AEGL: 60분 / ERPG: 1시간 / IDLH: 30분 / LFL: 폭발하한</div>
</div>
</div>
)}
@ -568,18 +571,18 @@ ${styles}
{activeTab === 3 && (
<div>
{/* ── 검색창 ── */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, marginBottom: 16 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
<div style={{ fontSize: 13, fontWeight: 700 }}>🔍 HNS <span style={{ fontSize: 9, fontWeight: 400, color: 'var(--t3)' }}>(···CAS·UN번호 )</span></div>
<div style={{ display: 'flex', gap: 4 }}>
<button style={{ padding: '5px 10px', borderRadius: 4, border: '1px solid rgba(249,115,22,.25)', background: 'rgba(249,115,22,.08)', color: 'var(--orange)', fontSize: 9, fontWeight: 600, cursor: 'pointer' }}>📥 DB </button>
<button style={{ padding: '5px 10px', borderRadius: 4, border: '1px solid rgba(6,182,212,.25)', background: 'rgba(6,182,212,.08)', color: 'var(--cyan)', fontSize: 9, fontWeight: 600, cursor: 'pointer' }}>🔗 Port-MIS </button>
<div className="rounded-[10px] p-4 mb-4 border border-border" style={{ background: 'var(--bg3)' }}>
<div className="flex items-center justify-between mb-[14px]">
<div className="text-[13px] font-bold">🔍 HNS <span className="text-[9px] font-normal text-text-3">(···CAS·UN번호 )</span></div>
<div className="flex gap-1">
<button className="text-[9px] font-semibold cursor-pointer text-status-orange rounded" style={{ padding: '5px 10px', border: '1px solid rgba(249,115,22,.25)', background: 'rgba(249,115,22,.08)' }}>📥 DB </button>
<button className="text-[9px] font-semibold cursor-pointer text-primary-cyan rounded" style={{ padding: '5px 10px', border: '1px solid rgba(6,182,212,.25)', background: 'rgba(6,182,212,.08)' }}>🔗 Port-MIS </button>
</div>
</div>
<div style={{ display: 'flex', gap: 8, marginBottom: 10, alignItems: 'center' }}>
<div style={{ flexShrink: 0, display: 'flex', alignItems: 'center', gap: 4 }}>
<span style={{ fontSize: 9, fontWeight: 600, color: 'var(--t3)' }}>:</span>
<select value={hmsSearchType} onChange={e => { setHmsSearchType(e.target.value as typeof hmsSearchType); setHmsPage(1) }} style={{ padding: '6px 10px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, outline: 'none', minWidth: 130 }}>
<div className="flex gap-2 mb-[10px] items-center">
<div className="shrink-0 flex items-center gap-1">
<span className="text-[9px] font-semibold text-text-3">:</span>
<select value={hmsSearchType} onChange={e => { setHmsSearchType(e.target.value as typeof hmsSearchType); setHmsPage(1) }} className="rounded-sm border border-border text-[10px] outline-none" style={{ padding: '6px 10px', background: 'var(--bg0)', minWidth: 130 }}>
<option value="abbr"> /</option>
<option value="korName"> ()</option>
<option value="engName"> ()</option>
@ -587,39 +590,39 @@ ${styles}
<option value="un"> UN번호</option>
</select>
</div>
<input type="text" value={hmsSearchInput} onChange={e => { setHmsSearchInput(e.target.value); setHmsPage(1) }} placeholder={hmsSearchType === 'abbr' ? '검색어 입력 (부호·띄어쓰기 제외)' : hmsSearchType === 'cas' ? 'CAS번호 입력 (예: 71-43-2)' : hmsSearchType === 'un' ? 'UN번호 입력 (예: 1114)' : '검색어 입력 (* 동의어 검색)'} style={{ flex: 1, padding: '8px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 11, outline: 'none' }} />
<button onClick={() => setHmsPage(1)} style={{ padding: '8px 20px', borderRadius: 6, border: 'none', background: 'linear-gradient(135deg,var(--orange),var(--red))', color: '#fff', fontSize: 11, fontWeight: 700, cursor: 'pointer', flexShrink: 0 }}>🔎 </button>
<input type="text" value={hmsSearchInput} onChange={e => { setHmsSearchInput(e.target.value); setHmsPage(1) }} placeholder={hmsSearchType === 'abbr' ? '검색어 입력 (부호·띄어쓰기 제외)' : hmsSearchType === 'cas' ? 'CAS번호 입력 (예: 71-43-2)' : hmsSearchType === 'un' ? 'UN번호 입력 (예: 1114)' : '검색어 입력 (* 동의어 검색)'} className="flex-1 rounded-sm border border-border text-[11px] outline-none" style={{ padding: '8px 12px', background: 'var(--bg0)' }} />
<button onClick={() => setHmsPage(1)} className="text-[11px] font-bold cursor-pointer shrink-0 rounded-sm" style={{ padding: '8px 20px', border: 'none', background: 'linear-gradient(135deg,var(--orange),var(--red))', color: '#fff' }}>🔎 </button>
</div>
<div style={{ fontSize: 8, color: 'var(--t3)', lineHeight: 1.6 }}>
<div className="text-[8px] text-text-3" style={{ lineHeight: 1.6 }}>
· <b style={{ color: 'var(--orange)' }}> </b> &nbsp;|&nbsp; / <b style={{ color: 'var(--orange)' }}>, </b> &nbsp;|&nbsp; <b style={{ color: 'var(--cyan)' }}>1,316</b> ( 277 / 56 / 983)
</div>
</div>
{/* ── 검색 결과 테이블 ── */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, marginBottom: 16 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
<div style={{ fontSize: 11, fontWeight: 700 }}>📋 <span style={{ fontSize: 9, fontWeight: 400, color: 'var(--t3)' }}> {hmsTotal} </span></div>
<select value={hmsFilterSebc} onChange={e => { setHmsFilterSebc(e.target.value); setHmsPage(1) }} style={{ padding: '3px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t2)', fontSize: 9, outline: 'none' }}>
<div className="rounded-[10px] p-4 mb-4 border border-border" style={{ background: 'var(--bg3)' }}>
<div className="flex items-center justify-between mb-[10px]">
<div className="text-[11px] font-bold">📋 <span className="text-[9px] font-normal text-text-3"> {hmsTotal} </span></div>
<select value={hmsFilterSebc} onChange={e => { setHmsFilterSebc(e.target.value); setHmsPage(1) }} className="rounded border border-border text-[9px] text-text-2 outline-none" style={{ padding: '3px 8px', background: 'var(--bg0)' }}>
<option> </option><option>G (Gas)</option><option>E (Evaporator)</option><option>F (Floater)</option><option>D (Dissolver)</option><option>S (Sinker)</option>
</select>
</div>
<div style={{ overflowX: 'auto' }}>
<div className="overflow-x-auto">
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 9 }}>
<thead>
<tr style={{ background: 'rgba(249,115,22,.06)' }}>
<th style={{ padding: '7px 6px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', color: 'var(--orange)', width: 28 }}>No.</th>
<th style={{ padding: '7px 6px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', color: 'var(--orange)' }}>/</th>
<th style={{ padding: '7px 6px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', color: 'var(--t2)' }}></th>
<th style={{ padding: '7px 6px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', color: 'var(--t2)' }}> </th>
<th style={{ padding: '7px 6px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', color: 'var(--cyan)' }}></th>
<th style={{ padding: '7px 6px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', color: 'var(--t2)', fontSize: 8 }}> / </th>
<th style={{ padding: '7px 6px', textAlign: 'center', borderBottom: '2px solid var(--bdL)', color: 'var(--t2)', width: 60 }}>UN번호</th>
<th style={{ padding: '7px 6px', textAlign: 'center', borderBottom: '2px solid var(--bdL)', color: 'var(--t2)', width: 72 }}>CAS번호</th>
<th style={{ padding: '7px 6px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', width: 28 }} className="text-status-orange">No.</th>
<th style={{ padding: '7px 6px', textAlign: 'left', borderBottom: '2px solid var(--bdL)' }} className="text-status-orange">/</th>
<th style={{ padding: '7px 6px', textAlign: 'left', borderBottom: '2px solid var(--bdL)' }} className="text-text-2"></th>
<th style={{ padding: '7px 6px', textAlign: 'left', borderBottom: '2px solid var(--bdL)' }} className="text-text-2"> </th>
<th style={{ padding: '7px 6px', textAlign: 'left', borderBottom: '2px solid var(--bdL)' }} className="text-primary-cyan"></th>
<th style={{ padding: '7px 6px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', fontSize: 8 }} className="text-text-2"> / </th>
<th style={{ padding: '7px 6px', textAlign: 'center', borderBottom: '2px solid var(--bdL)', width: 60 }} className="text-text-2">UN번호</th>
<th style={{ padding: '7px 6px', textAlign: 'center', borderBottom: '2px solid var(--bdL)', width: 72 }} className="text-text-2">CAS번호</th>
</tr>
</thead>
<tbody>
{hmsLoading ? (
<tr><td colSpan={8} style={{ padding: '20px 8px', textAlign: 'center', color: 'var(--t3)' }}> ...</td></tr>
<tr><td colSpan={8} style={{ padding: '20px 8px' }} className="text-center text-text-3"> ...</td></tr>
) : hmsPageData.length > 0 ? hmsPageData.map((s: HNSSearchSubstance, idx: number) => {
const isSel = hmsSelectedId === s.id
return (
@ -628,30 +631,30 @@ ${styles}
onMouseOver={e => { if (!isSel) e.currentTarget.style.background = 'rgba(249,115,22,.03)' }}
onMouseOut={e => { if (!isSel) e.currentTarget.style.background = '' }}
>
<td style={{ padding: 6, fontFamily: 'var(--fM)', color: 'var(--t3)' }}>{(hmsPage - 1) * HMS_PER_PAGE + idx + 1}</td>
<td style={{ padding: 6, fontWeight: 600, color: 'var(--orange)', fontFamily: 'var(--fM)' }}>{s.abbreviation}</td>
<td style={{ padding: 6 }} className="font-mono text-text-3">{(hmsPage - 1) * HMS_PER_PAGE + idx + 1}</td>
<td style={{ padding: 6 }} className="font-semibold font-mono text-status-orange">{s.abbreviation}</td>
<td style={{ padding: 6 }}>{s.nameEn}</td>
<td style={{ padding: 6, color: 'var(--t3)', fontSize: 8 }}>{s.synonymsEn}</td>
<td style={{ padding: 6, fontWeight: 600 }}><span style={{ color: 'var(--cyan)', textDecoration: 'underline', cursor: 'pointer' }} onClick={e => { e.stopPropagation(); setHmsSelectedId(s.id); setHmsDetailTab(0) }}>{s.nameKr}</span></td>
<td style={{ padding: 6, color: 'var(--t3)', fontSize: 8 }}>{s.synonymsKr}</td>
<td style={{ textAlign: 'center', fontFamily: 'var(--fM)' }}>{s.unNumber}</td>
<td style={{ textAlign: 'center', fontFamily: 'var(--fM)' }}>{s.casNumber}</td>
<td style={{ padding: 6, fontSize: 8 }} className="text-text-3">{s.synonymsEn}</td>
<td style={{ padding: 6 }} className="font-semibold"><span className="text-primary-cyan underline cursor-pointer" onClick={e => { e.stopPropagation(); setHmsSelectedId(s.id); setHmsDetailTab(0) }}>{s.nameKr}</span></td>
<td style={{ padding: 6, fontSize: 8 }} className="text-text-3">{s.synonymsKr}</td>
<td className="text-center font-mono">{s.unNumber}</td>
<td className="text-center font-mono">{s.casNumber}</td>
</tr>
)
}) : (
<tr><td colSpan={8} style={{ padding: '20px 8px', textAlign: 'center', color: 'var(--t3)' }}> .</td></tr>
<tr><td colSpan={8} style={{ padding: '20px 8px' }} className="text-center text-text-3"> .</td></tr>
)}
</tbody>
</table>
</div>
<div style={{ marginTop: 10, display: 'flex', alignItems: 'center', justifyContent: 'space-between', fontSize: 9, color: 'var(--t3)' }}>
<div className="mt-[10px] flex items-center justify-between text-[9px] text-text-3">
<span> <b style={{ color: 'var(--orange)' }}>{hmsTotal.toLocaleString()}</b> · Port-MIS · · IBC CODE 692</span>
<div style={{ display: 'flex', gap: 4 }}>
<button onClick={() => setHmsPage(p => Math.max(1, p - 1))} disabled={hmsPage <= 1} style={{ padding: '4px 10px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t2)', fontSize: 9, cursor: 'pointer', opacity: hmsPage <= 1 ? 0.4 : 1 }}></button>
<div className="flex gap-1">
<button onClick={() => setHmsPage(p => Math.max(1, p - 1))} disabled={hmsPage <= 1} className="rounded cursor-pointer border border-border text-[9px] text-text-2" style={{ padding: '4px 10px', background: 'var(--bg0)', opacity: hmsPage <= 1 ? 0.4 : 1 }}></button>
{Array.from({ length: Math.min(hmsTotalPages, 5) }, (_, i) => i + 1).map(p => (
<button key={p} onClick={() => setHmsPage(p)} style={{ padding: '4px 10px', borderRadius: 4, border: '1px solid var(--bd)', background: p === hmsPage ? 'rgba(249,115,22,.08)' : 'var(--bg0)', color: p === hmsPage ? 'var(--orange)' : 'var(--t2)', fontSize: 9, cursor: 'pointer', fontWeight: p === hmsPage ? 600 : 400 }}>{p}</button>
<button key={p} onClick={() => setHmsPage(p)} className="rounded cursor-pointer border border-border text-[9px]" style={{ padding: '4px 10px', background: p === hmsPage ? 'rgba(249,115,22,.08)' : 'var(--bg0)', color: p === hmsPage ? 'var(--orange)' : 'var(--t2)', fontWeight: p === hmsPage ? 600 : 400 }}>{p}</button>
))}
<button onClick={() => setHmsPage(p => Math.min(hmsTotalPages, p + 1))} disabled={hmsPage >= hmsTotalPages} style={{ padding: '4px 10px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', color: 'var(--t2)', fontSize: 9, cursor: 'pointer', opacity: hmsPage >= hmsTotalPages ? 0.4 : 1 }}></button>
<button onClick={() => setHmsPage(p => Math.min(hmsTotalPages, p + 1))} disabled={hmsPage >= hmsTotalPages} className="rounded cursor-pointer border border-border text-[9px] text-text-2" style={{ padding: '4px 10px', background: 'var(--bg0)', opacity: hmsPage >= hmsTotalPages ? 0.4 : 1 }}></button>
</div>
</div>
</div>
@ -674,9 +677,9 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
const sebcColor = s.sebc.startsWith('G') ? 'var(--purple)' : s.sebc.startsWith('E') ? 'var(--red)' : s.sebc.startsWith('F') ? 'var(--yellow)' : s.sebc.startsWith('D') ? 'var(--cyan)' : s.sebc.startsWith('S') ? 'var(--green)' : 'var(--t2)'
return (
<div style={{ background: 'var(--bg3)', border: '1px solid rgba(6,182,212,.25)', borderRadius: 10, overflow: 'hidden', boxShadow: '0 4px 20px rgba(6,182,212,.08)' }}>
<div className="rounded-[10px] overflow-hidden" style={{ background: 'var(--bg3)', border: '1px solid rgba(6,182,212,.25)', boxShadow: '0 4px 20px rgba(6,182,212,.08)' }}>
{/* Tab Navigation */}
<div style={{ display: 'flex', borderBottom: '1px solid var(--bd)', background: 'linear-gradient(90deg,rgba(6,182,212,.06),rgba(249,115,22,.04))' }}>
<div className="flex border-b border-border" style={{ background: 'linear-gradient(90deg,rgba(6,182,212,.06),rgba(249,115,22,.04))' }}>
{tabLabels.map((label, i) => (
<button key={i} onClick={() => onTabChange(i)} style={{ flex: 1, padding: 10, fontSize: 10, fontWeight: activeTab === i ? 700 : 600, cursor: 'pointer', border: 'none', borderBottom: `2px solid ${activeTab === i ? 'var(--cyan)' : 'transparent'}`, background: activeTab === i ? 'rgba(6,182,212,.06)' : 'transparent', color: activeTab === i ? 'var(--cyan)' : 'var(--t3)' }}>{label}</button>
))}
@ -684,26 +687,26 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
{/* TAB 0: 물질특성·위험정보 */}
{activeTab === 0 && (
<div style={{ padding: 16 }}>
<div className="p-4">
{/* Header */}
<div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 16, paddingBottom: 14, borderBottom: '1px solid var(--bd)' }}>
<div style={{ width: 52, height: 52, borderRadius: 10, background: 'linear-gradient(135deg,rgba(6,182,212,.15),rgba(249,115,22,.1))', border: '1px solid rgba(6,182,212,.25)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 22, flexShrink: 0 }}>🧪</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 18, fontWeight: 800 }}>{s.nameKr} <span style={{ fontSize: 12, fontWeight: 400, color: 'var(--t3)' }}>({s.nameEn})</span></div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 6 }}>
<span style={{ padding: '2px 8px', borderRadius: 4, background: 'rgba(6,182,212,.1)', border: '1px solid rgba(6,182,212,.2)', fontSize: 9, color: 'var(--cyan)', fontWeight: 600, fontFamily: 'var(--fM)' }}>CAS: {s.casNumber}</span>
<span style={{ padding: '2px 8px', borderRadius: 4, background: 'rgba(249,115,22,.1)', border: '1px solid rgba(249,115,22,.2)', fontSize: 9, color: 'var(--orange)', fontWeight: 600, fontFamily: 'var(--fM)' }}>UN: {s.unNumber}</span>
<span style={{ padding: '2px 8px', borderRadius: 4, background: 'rgba(168,85,247,.1)', border: '1px solid rgba(168,85,247,.2)', fontSize: 9, color: 'var(--purple)', fontWeight: 600 }}>: {s.transportMethod}</span>
<span style={{ padding: '2px 8px', borderRadius: 4, background: 'rgba(34,197,94,.1)', border: '1px solid rgba(34,197,94,.2)', fontSize: 9, color: 'var(--green)', fontWeight: 600 }}>SEBC: {s.sebc}</span>
<div className="flex items-center gap-[14px] mb-4 pb-[14px] border-b border-border">
<div className="flex items-center justify-center text-[22px] shrink-0" style={{ width: 52, height: 52, borderRadius: 10, background: 'linear-gradient(135deg,rgba(6,182,212,.15),rgba(249,115,22,.1))', border: '1px solid rgba(6,182,212,.25)' }}>🧪</div>
<div className="flex-1">
<div style={{ fontSize: 18, fontWeight: 800 }}>{s.nameKr} <span className="text-xs font-normal text-text-3">({s.nameEn})</span></div>
<div className="flex flex-wrap gap-1.5 mt-1.5">
<span className="text-[9px] font-semibold font-mono text-primary-cyan rounded" style={{ padding: '2px 8px', background: 'rgba(6,182,212,.1)', border: '1px solid rgba(6,182,212,.2)' }}>CAS: {s.casNumber}</span>
<span className="text-[9px] font-semibold font-mono text-status-orange rounded" style={{ padding: '2px 8px', background: 'rgba(249,115,22,.1)', border: '1px solid rgba(249,115,22,.2)' }}>UN: {s.unNumber}</span>
<span className="text-[9px] font-semibold text-primary-purple rounded" style={{ padding: '2px 8px', background: 'rgba(168,85,247,.1)', border: '1px solid rgba(168,85,247,.2)' }}>: {s.transportMethod}</span>
<span className="text-[9px] font-semibold text-status-green rounded" style={{ padding: '2px 8px', background: 'rgba(34,197,94,.1)', border: '1px solid rgba(34,197,94,.2)' }}>SEBC: {s.sebc}</span>
</div>
<div style={{ fontSize: 9, color: 'var(--t3)', marginTop: 4 }}><b>:</b> {s.synonymsKr} &nbsp;|&nbsp; <b>:</b> {s.hazardClass}</div>
<div className="text-[9px] text-text-3 mt-1"><b>:</b> {s.synonymsKr} &nbsp;|&nbsp; <b>:</b> {s.hazardClass}</div>
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
{/* Left: 물리·화학적 특성 */}
<div>
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--orange)', marginBottom: 8 }}> · </div>
<div className="text-[11px] font-bold text-status-orange mb-2"> · </div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4, fontSize: 9 }}>
{([
['용도', s.usage, 'var(--cyan)'],
@ -720,7 +723,7 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
['폭발범위', s.explosionRange, 'var(--yellow)'],
] as [string, string, string][]).map(([label, value, borderColor]) => (
<div key={label} style={{ padding: '6px 8px', background: 'var(--bg0)', borderRadius: 4, borderLeft: `3px solid ${borderColor}` }}>
<span style={{ color: 'var(--t3)' }}>{label}</span><br />
<span className="text-text-3">{label}</span><br />
<b style={{ color: label.includes('인화') || label.includes('폭발') ? 'var(--red)' : label.includes('용해') ? 'var(--cyan)' : 'var(--t1)' }}>{value}</b>
</div>
))}
@ -729,9 +732,9 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
{/* Right: NFPA + 위험등급 */}
<div>
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--red)', marginBottom: 8 }}> ·</div>
<div style={{ display: 'flex', gap: 14, alignItems: 'flex-start', marginBottom: 12 }}>
<div style={{ width: 80, height: 80, position: 'relative', flexShrink: 0 }}>
<div className="text-[11px] font-bold text-status-red mb-2"> ·</div>
<div className="flex gap-[14px] items-start mb-3">
<div className="relative shrink-0" style={{ width: 80, height: 80 }}>
<svg viewBox="0 0 100 100" width={80} height={80}>
<polygon points="50,5 95,50 50,95 5,50" fill="none" stroke="rgba(255,255,255,.15)" strokeWidth={1.5} />
<polygon points="50,5 72,28 50,50 28,28" fill="rgba(239,68,68,.2)" stroke="rgba(239,68,68,.5)" strokeWidth={1} />
@ -743,15 +746,15 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
<polygon points="5,50 28,28 50,50 28,72" fill="rgba(59,130,246,.2)" stroke="rgba(59,130,246,.5)" strokeWidth={1} />
<text x={23} y={55} textAnchor="middle" fill="#60a5fa" fontSize={16} fontWeight={800} fontFamily="monospace">{nfpa.reactivity}</text>
</svg>
<div style={{ textAlign: 'center', fontSize: 7, fontWeight: 600, color: 'var(--t3)', marginTop: 2 }}>NFPA 704</div>
<div className="text-center text-[7px] font-semibold text-text-3 mt-0.5">NFPA 704</div>
</div>
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 4, fontSize: 8 }}>
<div className="flex-1 flex flex-col gap-1 text-[8px]">
<div style={{ padding: '4px 8px', background: 'rgba(239,68,68,.06)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 4 }}><span style={{ color: 'var(--red)', fontWeight: 700 }}>() {nfpa.health}</span> {nfpa.health >= 4 ? '치명적' : nfpa.health >= 3 ? '중상' : nfpa.health >= 2 ? '장해' : nfpa.health >= 1 ? '경미한 손상' : '무해'}</div>
<div style={{ padding: '4px 8px', background: 'rgba(234,179,8,.06)', border: '1px solid rgba(234,179,8,.12)', borderRadius: 4 }}><span style={{ color: '#fbbf24', fontWeight: 700 }}>() {nfpa.fire}</span> {nfpa.fire >= 4 ? '93°F 미만' : nfpa.fire >= 3 ? '100°F 미만' : nfpa.fire >= 2 ? '200°F 미만' : nfpa.fire >= 1 ? '200°F 이상' : '비가연'}</div>
<div style={{ padding: '4px 8px', background: 'rgba(59,130,246,.06)', border: '1px solid rgba(59,130,246,.12)', borderRadius: 4 }}><span style={{ color: '#60a5fa', fontWeight: 700 }}>() {nfpa.reactivity}</span> {nfpa.reactivity >= 3 ? '폭발 가능' : nfpa.reactivity >= 2 ? '격렬 반응' : nfpa.reactivity >= 1 ? '불안정 가능' : '안정'}</div>
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4, fontSize: 9 }}>
<div className="flex flex-col gap-1 text-[9px]">
<InfoBoxRow label="위험 분류" value={s.hazardClass} bg="rgba(239,68,68,.06)" border="rgba(239,68,68,.12)" labelColor="var(--red)" valueColor="var(--red)" />
<InfoBoxRow label="ERG 번호" value={s.ergNumber} bg="rgba(249,115,22,.06)" border="rgba(249,115,22,.12)" labelColor="var(--orange)" valueColor="var(--orange)" />
<InfoBoxRow label="IDLH" value={s.idlh} bg="rgba(168,85,247,.06)" border="rgba(168,85,247,.12)" labelColor="var(--purple)" valueColor="var(--red)" />
@ -765,55 +768,55 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
{/* TAB 1: 방제거리·PPE·MSDS */}
{activeTab === 1 && (
<div style={{ padding: 16 }}>
<div className="p-4">
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
{/* 방제거리 */}
<div style={{ border: '1px solid rgba(239,68,68,.2)', borderRadius: 8, overflow: 'hidden' }}>
<div className="rounded-md overflow-hidden" style={{ border: '1px solid rgba(239,68,68,.2)' }}>
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(239,68,68,.08),transparent)', borderBottom: '1px solid rgba(239,68,68,.12)' }}>
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)' }}>🚧 (ERG {s.ergNumber})</div>
<div className="text-xs font-bold text-status-red">🚧 (ERG {s.ergNumber})</div>
</div>
<div style={{ padding: 12, display: 'flex', flexDirection: 'column', gap: 8 }}>
<div style={{ padding: 10, background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 6 }}>
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--orange)', marginBottom: 4 }}>🔥 </div>
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.6 }}>: <b style={{ color: 'var(--red)' }}>{s.responseDistanceFire}</b> </div>
<div className="p-3 flex flex-col gap-2">
<div className="rounded-sm" style={{ padding: 10, background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.12)' }}>
<div className="text-[10px] font-bold text-status-orange mb-1">🔥 </div>
<div className="text-[9px] text-text-2" style={{ lineHeight: 1.6 }}>: <b style={{ color: 'var(--red)' }}>{s.responseDistanceFire}</b> </div>
</div>
<div style={{ padding: 10, background: 'rgba(168,85,247,.04)', border: '1px solid rgba(168,85,247,.12)', borderRadius: 6 }}>
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--purple)', marginBottom: 4 }}>💨 ()</div>
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.6 }}> : <b style={{ color: 'var(--purple)' }}>{s.responseDistanceSpillDay}</b><br /> : <b style={{ color: 'var(--red)' }}>{s.responseDistanceSpillNight}</b></div>
<div className="rounded-sm" style={{ padding: 10, background: 'rgba(168,85,247,.04)', border: '1px solid rgba(168,85,247,.12)' }}>
<div className="text-[10px] font-bold text-primary-purple mb-1">💨 ()</div>
<div className="text-[9px] text-text-2" style={{ lineHeight: 1.6 }}> : <b style={{ color: 'var(--purple)' }}>{s.responseDistanceSpillDay}</b><br /> : <b style={{ color: 'var(--red)' }}>{s.responseDistanceSpillNight}</b></div>
</div>
<div style={{ padding: 10, background: 'rgba(6,182,212,.04)', border: '1px solid rgba(6,182,212,.12)', borderRadius: 6 }}>
<div style={{ fontSize: 10, fontWeight: 700, color: 'var(--cyan)', marginBottom: 4 }}>🌊 </div>
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.6 }}>{s.marineResponse}</div>
<div className="rounded-sm" style={{ padding: 10, background: 'rgba(6,182,212,.04)', border: '1px solid rgba(6,182,212,.12)' }}>
<div className="text-[10px] font-bold text-primary-cyan mb-1">🌊 </div>
<div className="text-[9px] text-text-2" style={{ lineHeight: 1.6 }}>{s.marineResponse}</div>
</div>
</div>
</div>
<div>
{/* PPE */}
<div style={{ border: '1px solid rgba(34,197,94,.2)', borderRadius: 8, overflow: 'hidden', marginBottom: 12 }}>
<div className="rounded-md overflow-hidden mb-3" style={{ border: '1px solid rgba(34,197,94,.2)' }}>
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(34,197,94,.08),transparent)', borderBottom: '1px solid rgba(34,197,94,.12)' }}>
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--green)' }}>🛡 (PPE) </div>
<div className="text-xs font-bold text-status-green">🛡 (PPE) </div>
</div>
<div style={{ padding: 12, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, fontSize: 9 }}>
<div style={{ padding: 8, background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.1)', borderRadius: 4, textAlign: 'center' }}>
<div style={{ fontSize: 16, marginBottom: 3 }}>🧑🚒</div>
<div style={{ fontWeight: 700, color: 'var(--red)' }}></div>
<div style={{ fontSize: 8, color: 'var(--t3)', marginTop: 2 }}>{s.ppeClose}</div>
<div className="text-center rounded" style={{ padding: 8, background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.1)' }}>
<div className="text-base mb-[3px]">🧑🚒</div>
<div className="font-bold text-status-red"></div>
<div className="text-[8px] text-text-3 mt-0.5">{s.ppeClose}</div>
</div>
<div style={{ padding: 8, background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.1)', borderRadius: 4, textAlign: 'center' }}>
<div style={{ fontSize: 16, marginBottom: 3 }}>🦺</div>
<div style={{ fontWeight: 700, color: 'var(--orange)' }}></div>
<div style={{ fontSize: 8, color: 'var(--t3)', marginTop: 2 }}>{s.ppeFar}</div>
<div className="text-center rounded" style={{ padding: 8, background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.1)' }}>
<div className="text-base mb-[3px]">🦺</div>
<div className="font-bold text-status-orange"></div>
<div className="text-[8px] text-text-3 mt-0.5">{s.ppeFar}</div>
</div>
</div>
</div>
{/* MSDS */}
<div style={{ border: '1px solid rgba(59,130,246,.2)', borderRadius: 8, overflow: 'hidden' }}>
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(59,130,246,.08),transparent)', borderBottom: '1px solid rgba(59,130,246,.12)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ fontSize: 12, fontWeight: 700, color: '#60a5fa' }}>📄 MSDS </div>
<button style={{ padding: '3px 10px', background: 'rgba(59,130,246,.1)', border: '1px solid rgba(59,130,246,.2)', borderRadius: 4, color: '#60a5fa', fontSize: 8, fontWeight: 600, cursor: 'pointer' }}>📥 </button>
<div className="rounded-md overflow-hidden" style={{ border: '1px solid rgba(59,130,246,.2)' }}>
<div className="flex items-center justify-between" style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(59,130,246,.08),transparent)', borderBottom: '1px solid rgba(59,130,246,.12)' }}>
<div className="text-xs font-bold" style={{ color: '#60a5fa' }}>📄 MSDS </div>
<button className="text-[8px] font-semibold cursor-pointer rounded" style={{ padding: '3px 10px', background: 'rgba(59,130,246,.1)', border: '1px solid rgba(59,130,246,.2)', color: '#60a5fa' }}>📥 </button>
</div>
<div style={{ padding: 10, fontSize: 8, color: 'var(--t2)', lineHeight: 1.7 }}>
<div className="text-[8px] text-text-2 leading-[1.7]" style={{ padding: 10 }}>
<b>§2 ·:</b> {s.msds.hazard}<br />
<b>§4 :</b> {s.msds.firstAid}<br />
<b>§5 :</b> {s.msds.fireFighting}<br />
@ -829,14 +832,14 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
{/* TAB 2: IBC CODE·EmS 대응 */}
{activeTab === 2 && (
<div style={{ padding: 16 }}>
<div className="p-4">
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
{/* IBC CODE */}
<div style={{ border: '1px solid rgba(249,115,22,.2)', borderRadius: 8, overflow: 'hidden' }}>
<div className="rounded-md overflow-hidden" style={{ border: '1px solid rgba(249,115,22,.2)' }}>
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(249,115,22,.08),transparent)', borderBottom: '1px solid rgba(249,115,22,.12)' }}>
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--orange)' }}> IBC CODE </div>
<div className="text-xs font-bold text-status-orange"> IBC CODE </div>
</div>
<div style={{ padding: 12, display: 'flex', flexDirection: 'column', gap: 8, fontSize: 9 }}>
<div className="p-3 flex flex-col gap-2 text-[9px]">
<div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: 4 }}>
{([
['위험성', s.ibcHazard],
@ -847,13 +850,13 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
['최소적재요건', s.ibcMinRequirement],
] as [string, string][]).map(([label, value]) => (
<React.Fragment key={label}>
<div style={{ padding: '6px 8px', background: 'var(--bg0)', borderRadius: 4, color: 'var(--t3)' }}>{label}</div>
<div style={{ padding: '6px 8px', background: 'var(--bg0)', borderRadius: 4, fontWeight: 600 }}>{value}</div>
<div className="rounded text-text-3" style={{ padding: '6px 8px', background: 'var(--bg0)' }}>{label}</div>
<div className="rounded font-semibold" style={{ padding: '6px 8px', background: 'var(--bg0)' }}>{value}</div>
</React.Fragment>
))}
</div>
{/* Tank diagram SVG */}
<div style={{ padding: 10, background: 'var(--bg0)', borderRadius: 6, textAlign: 'center' }}>
<div className="rounded-sm text-center" style={{ padding: 10, background: 'var(--bg0)' }}>
<svg viewBox="0 0 200 80" width={200} height={80} style={{ display: 'inline-block' }}>
<rect x={10} y={10} width={180} height={50} rx={4} fill="none" stroke="rgba(249,115,22,.4)" strokeWidth={1.5} />
<line x1={70} y1={10} x2={70} y2={60} stroke="rgba(249,115,22,.2)" strokeWidth={1} strokeDasharray="3" />
@ -871,25 +874,25 @@ function HmsDetailPanel({ substance: s, activeTab, onTabChange }: { substance: H
</div>
{/* EmS */}
<div style={{ border: '1px solid rgba(239,68,68,.2)', borderRadius: 8, overflow: 'hidden' }}>
<div className="rounded-md overflow-hidden" style={{ border: '1px solid rgba(239,68,68,.2)' }}>
<div style={{ padding: '10px 12px', background: 'linear-gradient(135deg,rgba(239,68,68,.08),transparent)', borderBottom: '1px solid rgba(239,68,68,.12)' }}>
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--red)' }}>🆘 (EmS) ERG {s.ergNumber}</div>
<div className="text-xs font-bold text-status-red">🆘 (EmS) ERG {s.ergNumber}</div>
</div>
<div style={{ padding: 12, display: 'flex', flexDirection: 'column', gap: 8, fontSize: 9 }}>
<div style={{ padding: 8, background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.1)', borderRadius: 6 }}>
<div style={{ fontWeight: 700, color: 'var(--red)', marginBottom: 3 }}>🔥 </div>
<div style={{ color: 'var(--t2)', lineHeight: 1.6 }}>{s.emsFire}</div>
<div className="p-3 flex flex-col gap-2 text-[9px]">
<div className="rounded-sm" style={{ padding: 8, background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.1)' }}>
<div className="font-bold text-status-red mb-[3px]">🔥 </div>
<div className="text-text-2" style={{ lineHeight: 1.6 }}>{s.emsFire}</div>
</div>
<div style={{ padding: 8, background: 'rgba(168,85,247,.04)', border: '1px solid rgba(168,85,247,.1)', borderRadius: 6 }}>
<div style={{ fontWeight: 700, color: 'var(--purple)', marginBottom: 3 }}>💧 </div>
<div style={{ color: 'var(--t2)', lineHeight: 1.6 }}>{s.emsSpill}</div>
<div className="rounded-sm" style={{ padding: 8, background: 'rgba(168,85,247,.04)', border: '1px solid rgba(168,85,247,.1)' }}>
<div className="font-bold text-primary-purple mb-[3px]">💧 </div>
<div className="text-text-2" style={{ lineHeight: 1.6 }}>{s.emsSpill}</div>
</div>
<div style={{ padding: 8, background: 'rgba(6,182,212,.04)', border: '1px solid rgba(6,182,212,.1)', borderRadius: 6 }}>
<div style={{ fontWeight: 700, color: 'var(--cyan)', marginBottom: 3 }}>🏥 </div>
<div style={{ color: 'var(--t2)', lineHeight: 1.6 }}>{s.emsFirstAid}</div>
<div className="rounded-sm" style={{ padding: 8, background: 'rgba(6,182,212,.04)', border: '1px solid rgba(6,182,212,.1)' }}>
<div className="font-bold text-primary-cyan mb-[3px]">🏥 </div>
<div className="text-text-2" style={{ lineHeight: 1.6 }}>{s.emsFirstAid}</div>
</div>
<div style={{ textAlign: 'center' }}>
<button style={{ padding: '6px 16px', background: 'rgba(239,68,68,.1)', border: '1px solid rgba(239,68,68,.2)', borderRadius: 6, color: 'var(--red)', fontSize: 10, fontWeight: 600, cursor: 'pointer' }}>📋 EmS </button>
<div className="text-center">
<button className="text-[10px] font-semibold cursor-pointer rounded-sm text-status-red" style={{ padding: '6px 16px', background: 'rgba(239,68,68,.1)', border: '1px solid rgba(239,68,68,.2)' }}>📋 EmS </button>
</div>
</div>
</div>

파일 보기

@ -12,18 +12,17 @@ import { createHnsAnalysis } from '../services/hnsApi'
/* ─── HNS 매뉴얼 뷰어 컴포넌트 ─── */
function HNSManualViewer() {
const card = 'rounded-[10px] p-4 mb-3'
const cardBg: React.CSSProperties = { background: 'var(--bg3)', border: '1px solid var(--bd)' }
const card = 'rounded-md p-4 mb-3'
return (
<div className="flex-1 overflow-y-auto" style={{ background: 'var(--bg0)', scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
<div style={{ padding: '16px 20px', maxWidth: '1200px', margin: '0 auto' }}>
{/* 헤더 */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '16px' }}>
<div className="flex items-center justify-between mb-4">
<div>
<div style={{ fontSize: '16px', fontWeight: 700 }}>📖 HNS </div>
<div style={{ fontSize: '10px', color: 'var(--t3)', marginTop: '2px' }}>Marine HNS Response Manual Bonn Agreement / HELCOM / REMPEC (WestMOPoCo 2024 )</div>
<div className="text-base font-bold">📖 HNS </div>
<div className="text-[10px] text-text-3 mt-0.5">Marine HNS Response Manual Bonn Agreement / HELCOM / REMPEC (WestMOPoCo 2024 )</div>
</div>
</div>
@ -39,18 +38,18 @@ function HNSManualViewer() {
{ icon: '📋', title: '7. 사례연구', desc: '실제 HNS 해양사고 사례 분석 및 교훈', color: 'var(--cyan)' },
{ icon: '📊', title: '8. 자료표', desc: '물질별 데이터시트 · AEGL · 노출 한계값', color: 'var(--cyan)' },
].map(ch => (
<div key={ch.title} className={card} style={{ ...cardBg, cursor: 'pointer', transition: '.2s' }}>
<div key={ch.title} className={card} style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', cursor: 'pointer', transition: '.2s' }}>
<div style={{ fontSize: '20px', marginBottom: '6px' }}>{ch.icon}</div>
<div style={{ fontSize: '11px', fontWeight: 700 }}>{ch.title}</div>
<div style={{ fontSize: '9px', color: 'var(--t3)', marginTop: '4px', lineHeight: '1.4' }}>{ch.desc}</div>
<div className="text-[11px] font-bold">{ch.title}</div>
<div className="text-[9px] text-text-3 mt-1" style={{ lineHeight: '1.4' }}>{ch.desc}</div>
</div>
))}
</div>
{/* SEBC 거동 분류 */}
<div className={card} style={cardBg}>
<div style={{ fontSize: '13px', fontWeight: 700, marginBottom: '10px' }}>SEBC (Standard European Behaviour Classification)</div>
<div style={{ fontSize: '9px', color: 'var(--t3)', marginBottom: '10px', lineHeight: '1.5' }}> · (, , , ) 5 + 7 </div>
<div className={card} style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="text-[13px] font-bold mb-2.5">SEBC (Standard European Behaviour Classification)</div>
<div className="text-[9px] text-text-3 mb-2.5" style={{ lineHeight: '1.5' }}> · (, , , ) 5 + 7 </div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(5,1fr)', gap: '8px' }}>
{[
{ icon: '💨', label: 'G — 가스', desc: '대기 중 확산\n증기압 > 101.3kPa\n예: 암모니아, 염소', color: 'rgba(139,92,246' },
@ -59,18 +58,18 @@ function HNSManualViewer() {
{ icon: '💧', label: 'D — 용해', desc: '해수에 용해\n용해도 > 5%\n예: 메탄올, 염산', color: 'rgba(6,182,212' },
{ icon: '⬇️', label: 'S — 침강', desc: '해저로 침강\n밀도 > 1.025\n예: EDC, 사염화탄소', color: 'rgba(139,148,158' },
].map(s => (
<div key={s.label} style={{ textAlign: 'center', padding: '10px 6px', background: `${s.color},.08)`, border: `1px solid ${s.color},.2)`, borderRadius: '6px' }}>
<div key={s.label} className="text-center" style={{ padding: '10px 6px', background: `${s.color},.08)`, border: `1px solid ${s.color},.2)`, borderRadius: '6px' }}>
<div style={{ fontSize: '22px', marginBottom: '4px' }}>{s.icon}</div>
<div style={{ fontSize: '11px', fontWeight: 700, color: `${s.color},1)` }}>{s.label}</div>
<div style={{ fontSize: '8px', color: 'var(--t3)', marginTop: '3px', lineHeight: '1.3', whiteSpace: 'pre-line' }}>{s.desc}</div>
<div className="text-[11px] font-bold" style={{ color: `${s.color},1)` }}>{s.label}</div>
<div className="text-text-3 whitespace-pre-line" style={{ fontSize: '8px', marginTop: '3px', lineHeight: '1.3' }}>{s.desc}</div>
</div>
))}
</div>
</div>
{/* IMDG Code 위험물 등급 */}
<div className={card} style={cardBg}>
<div style={{ fontSize: '13px', fontWeight: 700, marginBottom: '10px' }}>IMDG Code (Hazard Classification)</div>
<div className={card} style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="text-[13px] font-bold mb-2.5">IMDG Code (Hazard Classification)</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: '6px' }}>
{[
{ icon: '💥', label: 'Class 1 — 폭발물', sub: 'Explosives', bg: 'rgba(249,115,22,.15)' },
@ -83,11 +82,11 @@ function HNSManualViewer() {
{ icon: '🧪', label: 'Class 8 — 부식성', sub: 'Corrosive Substances', bg: 'rgba(139,148,158,.12)' },
{ icon: '⚠️', label: 'Class 9 — 기타', sub: '환경유해물질 포함', bg: 'rgba(139,148,158,.12)' },
].map(c => (
<div key={c.label} style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '8px', background: 'var(--bg0)', borderRadius: '5px' }}>
<div style={{ width: '28px', height: '28px', background: c.bg, borderRadius: '4px', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '14px', flexShrink: 0 }}>{c.icon}</div>
<div key={c.label} className="flex items-center gap-2 p-2" style={{ background: 'var(--bg0)', borderRadius: '5px' }}>
<div className="flex items-center justify-center shrink-0 text-sm" style={{ width: '28px', height: '28px', background: c.bg, borderRadius: '4px' }}>{c.icon}</div>
<div>
<div style={{ fontSize: '10px', fontWeight: 700 }}>{c.label}</div>
<div style={{ fontSize: '8px', color: 'var(--t3)' }}>{c.sub}</div>
<div className="text-[10px] font-bold">{c.label}</div>
<div className="text-text-3" style={{ fontSize: '8px' }}>{c.sub}</div>
</div>
</div>
))}
@ -95,9 +94,9 @@ function HNSManualViewer() {
</div>
{/* HNS 사고 대응 프로세스 */}
<div className={card} style={cardBg}>
<div style={{ fontSize: '13px', fontWeight: 700, marginBottom: '10px' }}>HNS </div>
<div style={{ display: 'flex', alignItems: 'stretch', gap: '4px', fontSize: '9px' }}>
<div className={card} style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="text-[13px] font-bold mb-2.5">HNS </div>
<div className="flex items-stretch gap-1 text-[9px]">
{[
{ icon: '🚨', step: '1단계: 사고 통보', desc: 'HNS 유출 감지\nGMDSS/DSC 신호\n관계기관 통보', color: 'rgba(239,68,68', textColor: '#f87171' },
{ icon: '🔍', step: '2단계: 상황 평가', desc: '물질 식별 (UN번호)\nSEBC 거동 판단\n위험 구역 설정', color: 'rgba(249,115,22', textColor: '#fb923c' },
@ -105,13 +104,13 @@ function HNSManualViewer() {
{ icon: '⚙️', step: '4단계: 현장 대응', desc: '선박 중심 조치\n오염물질 중심 조치\n모니터링 수행', color: 'rgba(6,182,212', textColor: 'var(--cyan)' },
{ icon: '🔄', step: '5단계: 유출후 관리', desc: '환경 회복/복원\n비용 문서화\n사고 검토/교훈', color: 'rgba(34,197,94', textColor: '#22c55e' },
].map((s, i) => (
<div key={s.step} style={{ display: 'flex', alignItems: 'stretch', flex: 1 }}>
<div style={{ flex: 1, padding: '10px 8px', background: `${s.color},.08)`, border: `1px solid ${s.color},.2)`, borderRadius: '6px', textAlign: 'center' }}>
<div style={{ fontSize: '16px', marginBottom: '4px' }}>{s.icon}</div>
<div style={{ fontWeight: 700, color: s.textColor, marginBottom: '3px' }}>{s.step}</div>
<div style={{ fontSize: '8px', color: 'var(--t3)', lineHeight: '1.3', whiteSpace: 'pre-line' }}>{s.desc}</div>
<div key={s.step} className="flex items-stretch flex-1">
<div className="flex-1 text-center" style={{ padding: '10px 8px', background: `${s.color},.08)`, border: `1px solid ${s.color},.2)`, borderRadius: '6px' }}>
<div className="text-base mb-1">{s.icon}</div>
<div className="font-bold mb-0.5" style={{ color: s.textColor }}>{s.step}</div>
<div className="text-text-3 whitespace-pre-line" style={{ fontSize: '8px', lineHeight: '1.3' }}>{s.desc}</div>
</div>
{i < 4 && <div style={{ display: 'flex', alignItems: 'center', color: 'var(--bd)', fontSize: '14px', padding: '0 2px' }}></div>}
{i < 4 && <div className="flex items-center text-sm" style={{ color: 'var(--bd)', padding: '0 2px' }}></div>}
</div>
))}
</div>
@ -120,9 +119,9 @@ function HNSManualViewer() {
{/* 대응 기술 매트릭스 */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '14px', marginBottom: '14px' }}>
{/* 선박 중심 조치 */}
<div className={card} style={cardBg}>
<div style={{ fontSize: '12px', fontWeight: 700, marginBottom: '8px' }}>🚢 </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
<div className={card} style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="text-[12px] font-bold mb-2">🚢 </div>
<div className="flex flex-col gap-1">
{[
{ icon: '🚁', title: '긴급 승선 (Emergency Boarding)', desc: '전문 대응팀의 사고 선박 접근 및 상황 파악' },
{ icon: '🔗', title: '긴급 예인 (Emergency Towing)', desc: '사고 선박을 안전 해역으로 이동' },
@ -130,20 +129,20 @@ function HNSManualViewer() {
{ icon: '🔄', title: '화물 이송 (Cargo Transfer)', desc: '위험 화물을 다른 선박/탱크로 이적' },
{ icon: '🔧', title: '밀봉/마개 (Sealing & Plugging)', desc: '유출 지점 임시 차단 및 봉쇄' },
].map(item => (
<div key={item.title} style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 8px', background: 'var(--bg0)', borderRadius: '4px' }}>
<span style={{ fontSize: '12px', width: '20px', textAlign: 'center' }}>{item.icon}</span>
<div style={{ fontSize: '9px' }}>
<div key={item.title} className="flex items-center gap-2" style={{ padding: '6px 8px', background: 'var(--bg0)', borderRadius: '4px' }}>
<span className="text-[12px] text-center" style={{ width: '20px' }}>{item.icon}</span>
<div className="text-[9px]">
<b>{item.title}</b><br />
<span style={{ color: 'var(--t3)' }}>{item.desc}</span>
<span className="text-text-3">{item.desc}</span>
</div>
</div>
))}
</div>
</div>
{/* 오염물질 중심 조치 */}
<div className={card} style={cardBg}>
<div style={{ fontSize: '12px', fontWeight: 700, marginBottom: '8px' }}>🧪 </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
<div className={card} style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="text-[12px] font-bold mb-2">🧪 </div>
<div className="flex flex-col gap-1">
{[
{ icon: '💨', title: '대기 확산 모니터링', desc: '가스/증발 물질의 대기 농도 감시 (ALOHA/CAMEO)' },
{ icon: '🌊', title: '해수면 회수 (Surface Recovery)', desc: '부유 물질 흡착재/스키머로 회수' },
@ -151,11 +150,11 @@ function HNSManualViewer() {
{ icon: '⬇️', title: '해저 회수 (Subsea Recovery)', desc: '침강 물질 ROV/잠수 회수' },
{ icon: '🔥', title: '제어 연소 (Controlled Burning)', desc: '인화성 물질 현장 소각 처리' },
].map(item => (
<div key={item.title} style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '6px 8px', background: 'var(--bg0)', borderRadius: '4px' }}>
<span style={{ fontSize: '12px', width: '20px', textAlign: 'center' }}>{item.icon}</span>
<div style={{ fontSize: '9px' }}>
<div key={item.title} className="flex items-center gap-2" style={{ padding: '6px 8px', background: 'var(--bg0)', borderRadius: '4px' }}>
<span className="text-[12px] text-center" style={{ width: '20px' }}>{item.icon}</span>
<div className="text-[9px]">
<b>{item.title}</b><br />
<span style={{ color: 'var(--t3)' }}>{item.desc}</span>
<span className="text-text-3">{item.desc}</span>
</div>
</div>
))}
@ -166,9 +165,9 @@ function HNSManualViewer() {
{/* PPE / 안전구역 / 노출한계 */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '14px', marginBottom: '14px' }}>
{/* PPE */}
<div className={card} style={cardBg}>
<div style={{ fontSize: '12px', fontWeight: 700, marginBottom: '8px' }}>🦺 (PPE)</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px', fontSize: '9px' }}>
<div className={card} style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="text-[12px] font-bold mb-2">🦺 (PPE)</div>
<div className="flex flex-col gap-1 text-[9px]">
{[
{ level: 'Level A', desc: '완전 밀폐형 화학보호복 + SCBA\n증기/가스 직접 노출 구역', color: '#ef4444' },
{ level: 'Level B', desc: '비밀폐형 화학보호복 + SCBA\n액체 스플래시 위험 구역', color: '#f97316' },
@ -177,41 +176,41 @@ function HNSManualViewer() {
].map(p => (
<div key={p.level} style={{ padding: '6px 8px', background: `color-mix(in srgb,${p.color} 6%,transparent)`, borderLeft: `3px solid ${p.color}`, borderRadius: '0 4px 4px 0' }}>
<b style={{ color: p.color }}>{p.level}</b><br />
<span style={{ color: 'var(--t3)', whiteSpace: 'pre-line' }}>{p.desc}</span>
<span className="text-text-3 whitespace-pre-line">{p.desc}</span>
</div>
))}
</div>
</div>
{/* 안전구역 */}
<div className={card} style={cardBg}>
<div style={{ fontSize: '12px', fontWeight: 700, marginBottom: '8px' }}>🔴 </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px', fontSize: '9px' }}>
<div style={{ textAlign: 'center', padding: '10px', background: 'rgba(239,68,68,.08)', border: '2px solid rgba(239,68,68,.3)', borderRadius: '50%', aspectRatio: '1', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
<div className={card} style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="text-[12px] font-bold mb-2">🔴 </div>
<div className="flex flex-col gap-1.5 text-[9px]">
<div className="text-center flex flex-col items-center justify-center" style={{ padding: '10px', background: 'rgba(239,68,68,.08)', border: '2px solid rgba(239,68,68,.3)', borderRadius: '50%', aspectRatio: '1' }}>
<b style={{ color: '#f87171', fontSize: '11px' }}>HOT ZONE</b>
<span style={{ color: 'var(--t3)', fontSize: '8px' }}> <br />Level A/B PPE </span>
<span className="text-text-3" style={{ fontSize: '8px' }}> <br />Level A/B PPE </span>
</div>
<div style={{ textAlign: 'center', padding: '8px', background: 'rgba(251,191,36,.06)', border: '1.5px solid rgba(251,191,36,.25)', borderRadius: '6px' }}>
<div className="text-center" style={{ padding: '8px', background: 'rgba(251,191,36,.06)', border: '1.5px solid rgba(251,191,36,.25)', borderRadius: '6px' }}>
<b style={{ color: '#fbbf24', fontSize: '10px' }}>WARM ZONE</b>
<span style={{ color: 'var(--t3)', fontSize: '8px' }}> / </span>
<span className="text-text-3" style={{ fontSize: '8px' }}> / </span>
</div>
<div style={{ textAlign: 'center', padding: '8px', background: 'rgba(34,197,94,.06)', border: '1.5px solid rgba(34,197,94,.25)', borderRadius: '6px' }}>
<div className="text-center" style={{ padding: '8px', background: 'rgba(34,197,94,.06)', border: '1.5px solid rgba(34,197,94,.25)', borderRadius: '6px' }}>
<b style={{ color: '#22c55e', fontSize: '10px' }}>COLD ZONE</b>
<span style={{ color: 'var(--t3)', fontSize: '8px' }}> / </span>
<span className="text-text-3" style={{ fontSize: '8px' }}> / </span>
</div>
</div>
</div>
{/* 노출한계 (AEGL) */}
<div className={card} style={cardBg}>
<div style={{ fontSize: '12px', fontWeight: 700, marginBottom: '8px' }}>📊 (AEGL )</div>
<div style={{ fontSize: '9px', color: 'var(--t3)', marginBottom: '6px', lineHeight: '1.4' }}>Acute Exposure Guideline Levels (EPA)<br />(NH) ppm </div>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '8px', fontFamily: 'var(--fM)' }}>
<div className={card} style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="text-[12px] font-bold mb-2">📊 (AEGL )</div>
<div className="text-[9px] text-text-3 mb-1.5" style={{ lineHeight: '1.4' }}>Acute Exposure Guideline Levels (EPA)<br />(NH) ppm </div>
<table className="w-full font-mono" style={{ borderCollapse: 'collapse', fontSize: '8px' }}>
<thead>
<tr style={{ borderBottom: '1px solid var(--bd)' }}>
<th style={{ padding: '4px', textAlign: 'left', color: 'var(--t3)' }}></th>
<th style={{ padding: '4px', textAlign: 'center', color: 'var(--t3)' }}>10</th>
<th style={{ padding: '4px', textAlign: 'center', color: 'var(--t3)' }}>30</th>
<th style={{ padding: '4px', textAlign: 'center', color: 'var(--t3)' }}>60</th>
<th style={{ padding: '4px', textAlign: 'center', color: 'var(--t3)' }}>4</th>
<tr className="border-b border-border">
<th className="p-1 text-left text-text-3"></th>
<th className="p-1 text-center text-text-3">10</th>
<th className="p-1 text-center text-text-3">30</th>
<th className="p-1 text-center text-text-3">60</th>
<th className="p-1 text-center text-text-3">4</th>
</tr>
</thead>
<tbody>
@ -220,16 +219,16 @@ function HNSManualViewer() {
{ label: 'AEGL-2', color: '#fbbf24', vals: [220, 220, 160, 110] },
{ label: 'AEGL-3', color: '#f87171', vals: [2700, 1600, 1100, 550] },
].map((row, ri) => (
<tr key={row.label} style={{ borderBottom: ri < 2 ? '1px solid var(--bd)' : undefined }}>
<td style={{ padding: '4px', color: row.color, fontWeight: 700 }}>{row.label}</td>
<tr key={row.label} className={ri < 2 ? 'border-b border-border' : ''}>
<td className="p-1 font-bold" style={{ color: row.color }}>{row.label}</td>
{row.vals.map((v, vi) => (
<td key={vi} style={{ padding: '4px', textAlign: 'center', color: 'var(--t2)' }}>{v}</td>
<td key={vi} className="p-1 text-center text-text-2">{v}</td>
))}
</tr>
))}
</tbody>
</table>
<div style={{ fontSize: '7px', color: 'var(--t3)', marginTop: '6px', lineHeight: '1.4' }}>
<div className="text-text-3 mt-1.5" style={{ fontSize: '7px', lineHeight: '1.4' }}>
AEGL-1: 불쾌감 ()<br />
AEGL-2: 심각한 ()<br />
AEGL-3: 생명
@ -238,7 +237,7 @@ function HNSManualViewer() {
</div>
{/* 출처 */}
<div style={{ padding: '10px', background: 'var(--bg3)', borderRadius: '6px', fontSize: '8px', color: 'var(--t3)', lineHeight: '1.5' }}>
<div className="text-text-3 rounded-sm" style={{ padding: '10px', background: 'var(--bg3)', fontSize: '8px', lineHeight: '1.5' }}>
<b>:</b> Marine HNS Response Manual Bonn Agreement / HELCOM / REMPEC (WestMOPoCo Project, 2024 )<br />
번역: 원해민, , , , KRISO / NOWPAP MERRAC<br />
원본: Alcaro L., Brandt J., Giraud W., Mannozzi M., Nicolas-Kopec A. (2021) ISBN: 978-2-87893-147-1

파일 보기

@ -171,66 +171,67 @@ export function IncidentsLeftPanel({
return (
<div className="flex flex-col bg-bg-1 border-r border-border overflow-hidden" style={{ width: '360px', flexShrink: 0 }}>
{/* Search */}
<div style={{ padding: '12px 16px', borderBottom: '1px solid var(--bd)', flexShrink: 0 }}>
<div style={{ position: 'relative' }}>
<span style={{ position: 'absolute', left: '10px', top: '50%', transform: 'translateY(-50%)', fontSize: '12px' }}>🔍</span>
<div className="px-4 py-3 border-b border-border shrink-0">
<div className="relative">
<span className="absolute left-[10px] top-1/2 -translate-y-1/2 text-xs">🔍</span>
<input
type="text"
placeholder="사고명, 선박명 검색..."
value={searchTerm}
onChange={(e) => { setSearchTerm(e.target.value); resetPage() }}
style={{
width: '100%', padding: '8px 12px 8px 32px', background: 'var(--bg0)',
border: '1px solid var(--bd)', borderRadius: 'var(--rS)',
fontSize: '12px', outline: 'none',
}}
className="w-full py-2 pr-3 pl-8 bg-bg-0 border border-border text-xs outline-none"
style={{ borderRadius: 'var(--rS)' }}
/>
</div>
</div>
{/* Date Range */}
<div style={{ padding: '8px 16px', borderBottom: '1px solid var(--bd)', display: 'flex', alignItems: 'center', gap: '6px', flexShrink: 0 }}>
<div className="flex items-center gap-1.5 px-4 py-2 border-b border-border shrink-0">
<input type="date" value={dateFrom}
onChange={(e) => { setDateFrom(e.target.value); setSelectedPeriod(''); resetPage() }}
style={{ padding: '5px 8px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontFamily: 'var(--fM)', fontSize: '11px', outline: 'none', flex: 1 }}
className="bg-bg-0 border border-border font-mono text-[11px] outline-none flex-1"
style={{ padding: '5px 8px', borderRadius: 'var(--rS)' }}
/>
<span style={{ color: 'var(--t3)', fontSize: '11px' }}>~</span>
<span className="text-text-3 text-[11px]">~</span>
<input type="date" value={dateTo}
onChange={(e) => { setDateTo(e.target.value); setSelectedPeriod(''); resetPage() }}
style={{ padding: '5px 8px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', fontFamily: 'var(--fM)', fontSize: '11px', outline: 'none', flex: 1 }}
className="bg-bg-0 border border-border font-mono text-[11px] outline-none flex-1"
style={{ padding: '5px 8px', borderRadius: 'var(--rS)' }}
/>
<button onClick={resetPage} style={{ padding: '5px 12px', background: 'linear-gradient(135deg,var(--cyan),var(--blue))', color: '#fff', border: 'none', borderRadius: 'var(--rS)', fontSize: '11px', fontWeight: 600, cursor: 'pointer', whiteSpace: 'nowrap' }}></button>
<button onClick={resetPage} className="text-[11px] font-semibold cursor-pointer whitespace-nowrap text-white border-none" style={{ padding: '5px 12px', background: 'linear-gradient(135deg,var(--cyan),var(--blue))', borderRadius: 'var(--rS)' }}></button>
</div>
{/* Period Presets */}
<div style={{ padding: '6px 16px', borderBottom: '1px solid var(--bd)', display: 'flex', gap: '4px', flexShrink: 0 }}>
<div className="flex gap-1 px-4 py-1.5 border-b border-border shrink-0">
{PERIOD_PRESETS.map(p => (
<button key={p} onClick={() => handlePeriodClick(p)} style={{
padding: '3px 8px', borderRadius: '14px', border: selectedPeriod === p ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
background: selectedPeriod === p ? 'rgba(6,182,212,0.1)' : 'transparent',
color: selectedPeriod === p ? 'var(--cyan)' : 'var(--t3)', fontSize: '10px', fontWeight: 600, cursor: 'pointer',
}}>{p}</button>
<button key={p} onClick={() => handlePeriodClick(p)} className="text-[10px] font-semibold cursor-pointer"
style={{
padding: '3px 8px', borderRadius: '14px', border: selectedPeriod === p ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
background: selectedPeriod === p ? 'rgba(6,182,212,0.1)' : 'transparent',
color: selectedPeriod === p ? 'var(--cyan)' : 'var(--t3)',
}}>{p}</button>
))}
</div>
{/* Today Summary */}
<div style={{ padding: '10px 16px', borderBottom: '1px solid var(--bd)', background: 'rgba(6,182,212,0.03)', flexShrink: 0 }}>
<div style={{ fontSize: '10px', fontWeight: 700, color: 'var(--t3)', letterSpacing: '0.8px', marginBottom: '8px' }}>
<div className="px-4 py-2.5 border-b border-border shrink-0" style={{ background: 'rgba(6,182,212,0.03)' }}>
<div className="text-[10px] font-bold text-text-3 mb-2" style={{ letterSpacing: '0.8px' }}>
📅 ({todayLabel})
</div>
<div style={{ display: 'flex', gap: '6px', flexWrap: 'wrap' }}>
<div className="flex gap-1.5 flex-wrap">
{REGIONS.map(r => {
const count = regionCounts[r] ?? 0
const isActive = selectedRegion === r
return (
<button key={r} onClick={() => { setSelectedRegion(r); resetPage() }} style={{
padding: '4px 10px', borderRadius: 'var(--rS)', fontSize: '11px', cursor: 'pointer',
background: isActive ? 'rgba(6,182,212,0.1)' : 'var(--bg3)',
border: isActive ? '1px solid rgba(6,182,212,0.25)' : '1px solid var(--bd)',
color: isActive ? 'var(--cyan)' : 'var(--t2)', fontWeight: isActive ? 700 : 400,
}}>
<button key={r} onClick={() => { setSelectedRegion(r); resetPage() }} className="text-[11px] cursor-pointer"
style={{
padding: '4px 10px', borderRadius: 'var(--rS)',
background: isActive ? 'rgba(6,182,212,0.1)' : 'var(--bg3)',
border: isActive ? '1px solid rgba(6,182,212,0.25)' : '1px solid var(--bd)',
color: isActive ? 'var(--cyan)' : 'var(--t2)', fontWeight: isActive ? 700 : 400,
}}>
{r === '전체' ? '전체 ' : `${r} `}
<span style={{ fontWeight: 700, fontFamily: 'var(--fM)', color: isActive ? 'var(--cyan)' : undefined }}>
<span className="font-bold font-mono" style={{ color: isActive ? 'var(--cyan)' : undefined }}>
{r === '전체' ? count : `(${count})`}
</span>
</button>
@ -240,19 +241,19 @@ export function IncidentsLeftPanel({
</div>
{/* Status Filter */}
<div style={{ display: 'flex', gap: '5px', padding: '8px 16px', borderBottom: '1px solid var(--bd)', flexShrink: 0 }}>
<div className="flex gap-[5px] px-4 py-2 border-b border-border shrink-0">
{[
{ id: '전체', label: '전체', dot: '' },
{ id: 'active', label: `대응중 (${statusCounts.active})`, dot: 'var(--red)' },
{ id: 'investigating', label: `조사중 (${statusCounts.investigating})`, dot: 'var(--orange)' },
{ id: 'closed', label: `종료 (${statusCounts.closed})`, dot: 'var(--t3)' },
].map(s => (
<button key={s.id} onClick={() => { setSelectedStatus(s.id); resetPage() }} style={{
padding: '4px 10px', borderRadius: '12px', border: selectedStatus === s.id ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
background: 'transparent', color: selectedStatus === s.id ? 'var(--t2)' : 'var(--t3)',
fontSize: '10px', fontWeight: 600, cursor: 'pointer',
display: 'flex', alignItems: 'center', gap: '4px',
}}>
<button key={s.id} onClick={() => { setSelectedStatus(s.id); resetPage() }}
className="flex items-center gap-1 text-[10px] font-semibold cursor-pointer"
style={{
padding: '4px 10px', borderRadius: '12px', border: selectedStatus === s.id ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
background: 'transparent', color: selectedStatus === s.id ? 'var(--t2)' : 'var(--t3)',
}}>
{s.dot && <span style={{ width: '6px', height: '6px', borderRadius: '50%', background: s.dot }} />}
{s.label}
</button>
@ -260,14 +261,14 @@ export function IncidentsLeftPanel({
</div>
{/* Count */}
<div style={{ padding: '6px 16px', fontSize: '11px', color: 'var(--t3)', borderBottom: '1px solid rgba(30,42,74,0.3)', flexShrink: 0 }}>
<div className="px-4 py-1.5 text-[11px] text-text-3 shrink-0" style={{ borderBottom: '1px solid rgba(30,42,74,0.3)' }}>
{filteredIncidents.length}
</div>
{/* Incident List */}
<div style={{ flex: 1, overflowY: 'auto', scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
<div className="flex-1 overflow-y-auto" style={{ scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
{pagedIncidents.length === 0 ? (
<div style={{ padding: '40px 16px', textAlign: 'center', color: 'var(--t3)', fontSize: '11px' }}>
<div className="px-4 py-10 text-center text-text-3 text-[11px]">
.
</div>
) : pagedIncidents.map(inc => {
@ -288,61 +289,64 @@ export function IncidentsLeftPanel({
active: '대응중', investigating: '조사중', closed: '종료',
}
return (
<div key={inc.id} onClick={() => onIncidentSelect(inc.id)} style={{
padding: '12px 16px', borderBottom: '1px solid var(--bd)', cursor: 'pointer',
background: isSel ? 'rgba(6,182,212,0.04)' : undefined,
borderLeft: isSel ? '3px solid var(--cyan)' : '3px solid transparent',
transition: 'background 0.15s',
}}
<div key={inc.id} onClick={() => onIncidentSelect(inc.id)}
className="px-4 py-3 border-b border-border cursor-pointer"
style={{
background: isSel ? 'rgba(6,182,212,0.04)' : undefined,
borderLeft: isSel ? '3px solid var(--cyan)' : '3px solid transparent',
transition: 'background 0.15s',
}}
onMouseEnter={(e) => { if (!isSel) e.currentTarget.style.background = 'rgba(255,255,255,0.02)' }}
onMouseLeave={(e) => { if (!isSel) e.currentTarget.style.background = '' }}
>
{/* Row 1: name + status */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '5px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '12px', fontWeight: 700 }}>
<span style={{ width: '8px', height: '8px', borderRadius: '50%', background: dotStyle[inc.status], boxShadow: dotShadow[inc.status], flexShrink: 0 }} />
<div className="flex items-center justify-between" style={{ marginBottom: '5px' }}>
<div className="flex items-center gap-1.5 text-xs font-bold">
<span className="shrink-0" style={{ width: '8px', height: '8px', borderRadius: '50%', background: dotStyle[inc.status], boxShadow: dotShadow[inc.status] }} />
{inc.name}
</div>
<span style={{ padding: '2px 10px', borderRadius: '10px', fontSize: '10px', fontWeight: 600, background: stBg[inc.status], color: stColor[inc.status], flexShrink: 0 }}>
<span className="shrink-0 text-[10px] font-semibold" style={{ padding: '2px 10px', borderRadius: '10px', background: stBg[inc.status], color: stColor[inc.status] }}>
{stLabel[inc.status]}
</span>
</div>
{/* Row 2: meta */}
<div style={{ fontSize: '10px', color: 'var(--t3)', marginBottom: '5px', display: 'flex', alignItems: 'center', gap: '8px' }}>
<div className="flex items-center gap-2 text-[10px] text-text-3" style={{ marginBottom: '5px' }}>
<span>📅 {inc.date} {inc.time}</span>
<span>🏛 {inc.office}</span>
</div>
{/* Row 3: tags + buttons */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px' }}>
<div className="flex items-center justify-between">
<div className="flex flex-wrap gap-1">
{inc.causeType && (
<span style={{ padding: '2px 8px', borderRadius: '3px', fontSize: '10px', fontWeight: 500, background: 'rgba(100,116,139,0.08)', border: '1px solid rgba(100,116,139,0.2)', color: 'var(--t2)' }}>
<span className="text-[10px] font-medium text-text-2" style={{ padding: '2px 8px', borderRadius: '3px', background: 'rgba(100,116,139,0.08)', border: '1px solid rgba(100,116,139,0.2)' }}>
{inc.causeType}
</span>
)}
{inc.oilType && (
<span style={{ padding: '2px 8px', borderRadius: '3px', fontSize: '10px', fontWeight: 500, background: 'rgba(249,115,22,0.08)', border: '1px solid rgba(249,115,22,0.2)', color: 'var(--orange)' }}>
<span className="text-[10px] font-medium text-status-orange" style={{ padding: '2px 8px', borderRadius: '3px', background: 'rgba(249,115,22,0.08)', border: '1px solid rgba(249,115,22,0.2)' }}>
{inc.oilType}
</span>
)}
{inc.prediction && (
<span style={{ padding: '2px 8px', borderRadius: '3px', fontSize: '10px', fontWeight: 500, background: 'rgba(34,197,94,0.08)', border: '1px solid rgba(34,197,94,0.2)', color: 'var(--green)' }}>
<span className="text-[10px] font-medium text-status-green" style={{ padding: '2px 8px', borderRadius: '3px', background: 'rgba(34,197,94,0.08)', border: '1px solid rgba(34,197,94,0.2)' }}>
{inc.prediction}
</span>
)}
</div>
<div style={{ display: 'flex', gap: '4px' }}>
<button className="inc-wx-btn" onClick={(e) => handleWeatherClick(e, inc.id)} title="사고 위치 기상정보" style={{
padding: '3px 7px', borderRadius: '4px', fontSize: '11px', cursor: 'pointer', lineHeight: 1,
border: '1px solid rgba(59,130,246,0.25)', background: weatherPopupId === inc.id ? 'rgba(59,130,246,0.18)' : 'rgba(59,130,246,0.08)', color: '#60a5fa',
transition: '0.15s',
}}>🌤</button>
{(inc.mediaCount ?? 0) > 0 && (
<button onClick={(e) => { e.stopPropagation(); setMediaModalIncident(inc) }} title="현장정보 조회" style={{
padding: '3px 7px', borderRadius: '4px', fontSize: '11px', cursor: 'pointer', lineHeight: 1,
border: '1px solid rgba(59,130,246,0.25)', background: 'rgba(59,130,246,0.08)', color: '#60a5fa',
<div className="flex gap-1">
<button className="inc-wx-btn cursor-pointer text-[11px]" onClick={(e) => handleWeatherClick(e, inc.id)} title="사고 위치 기상정보"
style={{
padding: '3px 7px', borderRadius: '4px', lineHeight: 1,
border: '1px solid rgba(59,130,246,0.25)', background: weatherPopupId === inc.id ? 'rgba(59,130,246,0.18)' : 'rgba(59,130,246,0.08)', color: '#60a5fa',
transition: '0.15s',
}}>📹 <span style={{ fontSize: '8px' }}>{inc.mediaCount}</span></button>
}}>🌤</button>
{(inc.mediaCount ?? 0) > 0 && (
<button onClick={(e) => { e.stopPropagation(); setMediaModalIncident(inc) }} title="현장정보 조회" className="cursor-pointer text-[11px]"
style={{
padding: '3px 7px', borderRadius: '4px', lineHeight: 1,
border: '1px solid rgba(59,130,246,0.25)', background: 'rgba(59,130,246,0.08)', color: '#60a5fa',
transition: '0.15s',
}}>📹 <span style={{ fontSize: '8px' }}>{inc.mediaCount}</span></button>
)}
</div>
</div>
@ -367,13 +371,11 @@ export function IncidentsLeftPanel({
)}
{/* Pagination */}
<div style={{
padding: '8px 12px', borderTop: '1px solid var(--bd)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0, background: 'var(--bg1)',
}}>
<div style={{ fontSize: '9px', color: 'var(--t3)' }}>
<div className="flex items-center justify-between bg-bg-1 shrink-0 border-t border-border" style={{ padding: '8px 12px' }}>
<div className="text-[9px] text-text-3">
<b>{filteredIncidents.length}</b> {(safePage - 1) * pageSize + 1}-{Math.min(safePage * pageSize, filteredIncidents.length)}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '3px' }}>
<div className="flex items-center" style={{ gap: '3px' }}>
<PgBtn label="⏮" disabled={safePage <= 1} onClick={() => setCurrentPage(1)} />
<PgBtn label="◀" disabled={safePage <= 1} onClick={() => setCurrentPage(Math.max(1, safePage - 1))} />
{Array.from({ length: totalPages }, (_, i) => i + 1).filter(p => Math.abs(p - safePage) <= 2).map(p => (
@ -382,9 +384,9 @@ export function IncidentsLeftPanel({
<PgBtn label="▶" disabled={safePage >= totalPages} onClick={() => setCurrentPage(Math.min(totalPages, safePage + 1))} />
<PgBtn label="⏭" disabled={safePage >= totalPages} onClick={() => setCurrentPage(totalPages)} />
</div>
<select onChange={(e) => { /* page size change placeholder */ void e }} style={{
padding: '3px 6px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: '4px', color: 'var(--t2)', fontSize: '9px', outline: 'none',
}}>
<select onChange={(e) => { /* page size change placeholder */ void e }}
className="bg-bg-0 border border-border text-text-2 text-[9px] outline-none"
style={{ padding: '3px 6px', borderRadius: '4px' }}>
<option>6</option><option>10</option><option>20</option>
</select>
</div>
@ -394,14 +396,16 @@ export function IncidentsLeftPanel({
function PgBtn({ label, active, disabled, onClick }: { label: string; active?: boolean; disabled?: boolean; onClick: () => void }) {
return (
<button onClick={onClick} disabled={disabled} style={{
minWidth: '24px', height: '24px', padding: '0 5px', borderRadius: '4px', fontSize: '9px', fontWeight: active ? 700 : 600,
fontFamily: 'var(--fM)', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: disabled ? 'default' : 'pointer',
background: active ? 'rgba(6,182,212,0.15)' : 'var(--bg3)',
border: active ? '1px solid rgba(6,182,212,0.4)' : '1px solid var(--bd)',
color: active ? 'var(--cyan)' : 'var(--t3)',
opacity: disabled ? 0.4 : 1, pointerEvents: disabled ? 'none' : undefined,
}}>{label}</button>
<button onClick={onClick} disabled={disabled}
className="flex items-center justify-center font-mono text-[9px]"
style={{
minWidth: '24px', height: '24px', padding: '0 5px', borderRadius: '4px', fontWeight: active ? 700 : 600,
background: active ? 'rgba(6,182,212,0.15)' : 'var(--bg3)',
border: active ? '1px solid rgba(6,182,212,0.4)' : '1px solid var(--bd)',
color: active ? 'var(--cyan)' : 'var(--t3)',
opacity: disabled ? 0.4 : 1, pointerEvents: disabled ? 'none' : undefined,
cursor: disabled ? 'default' : 'pointer',
}}>{label}</button>
)
}
@ -414,36 +418,36 @@ const WeatherPopup = forwardRef<HTMLDivElement, {
onClose: () => void
}>(({ data, position, onClose }, ref) => {
return (
<div ref={ref} style={{
position: 'fixed', zIndex: 9990, width: 280,
<div ref={ref} className="fixed overflow-hidden" style={{
zIndex: 9990, width: 280,
top: position.top, left: position.left,
background: 'var(--bg1)', border: '1px solid rgba(59,130,246,0.3)', borderRadius: 12,
boxShadow: '0 12px 40px rgba(0,0,0,0.5)', overflow: 'hidden', backdropFilter: 'blur(12px)',
boxShadow: '0 12px 40px rgba(0,0,0,0.5)', backdropFilter: 'blur(12px)',
}}>
{/* Header */}
<div style={{
padding: '10px 14px',
background: 'linear-gradient(135deg, rgba(59,130,246,0.08), rgba(6,182,212,0.04))',
borderBottom: '1px solid var(--bd)', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<div className="flex items-center justify-between border-b border-border"
style={{
padding: '10px 14px',
background: 'linear-gradient(135deg, rgba(59,130,246,0.08), rgba(6,182,212,0.04))',
}}>
<div className="flex items-center" style={{ gap: 6 }}>
<span style={{ fontSize: 14 }}>🌤</span>
<div>
<div style={{ fontSize: 11, fontWeight: 700 }}>{data.locNm}</div>
<div style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{data.obsDtm}</div>
<div className="text-text-3 font-mono" style={{ fontSize: 8 }}>{data.obsDtm}</div>
</div>
</div>
<span onClick={onClose} style={{ fontSize: 14, cursor: 'pointer', color: 'var(--t3)', padding: 2 }}></span>
<span onClick={onClose} className="cursor-pointer text-text-3" style={{ fontSize: 14, padding: 2 }}></span>
</div>
{/* Body */}
<div style={{ padding: '12px 14px' }}>
{/* Main weather */}
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 10 }}>
<div className="flex items-center" style={{ gap: 12, marginBottom: 10 }}>
<div style={{ fontSize: 28 }}>{data.icon}</div>
<div>
<div style={{ fontSize: 20, fontWeight: 700, fontFamily: 'var(--fM)' }}>{data.temp}</div>
<div style={{ fontSize: 9, color: 'var(--t3)' }}>{data.weatherDc}</div>
<div className="font-bold font-mono" style={{ fontSize: 20 }}>{data.temp}</div>
<div className="text-text-3" style={{ fontSize: 9 }}>{data.weatherDc}</div>
</div>
</div>
@ -458,40 +462,40 @@ const WeatherPopup = forwardRef<HTMLDivElement, {
</div>
{/* Tide info */}
<div style={{ marginTop: 8, display: 'flex', gap: 6 }}>
<div style={{
flex: 1, padding: '6px 8px', background: 'rgba(59,130,246,0.06)',
border: '1px solid rgba(59,130,246,0.1)', borderRadius: 6,
display: 'flex', alignItems: 'center', gap: 6,
}}>
<div className="flex" style={{ marginTop: 8, gap: 6 }}>
<div className="flex-1 flex items-center"
style={{
padding: '6px 8px', background: 'rgba(59,130,246,0.06)',
border: '1px solid rgba(59,130,246,0.1)', borderRadius: 6, gap: 6,
}}>
<span style={{ fontSize: 12 }}></span>
<div>
<div style={{ color: 'var(--t3)', fontSize: 7 }}> ()</div>
<div style={{ color: '#60a5fa', fontWeight: 700, fontFamily: 'var(--fM)', fontSize: 10 }}>{data.highTide}</div>
<div className="text-text-3" style={{ fontSize: 7 }}> ()</div>
<div className="font-bold font-mono" style={{ color: '#60a5fa', fontSize: 10 }}>{data.highTide}</div>
</div>
</div>
<div style={{
flex: 1, padding: '6px 8px', background: 'rgba(6,182,212,0.06)',
border: '1px solid rgba(6,182,212,0.1)', borderRadius: 6,
display: 'flex', alignItems: 'center', gap: 6,
}}>
<div className="flex-1 flex items-center"
style={{
padding: '6px 8px', background: 'rgba(6,182,212,0.06)',
border: '1px solid rgba(6,182,212,0.1)', borderRadius: 6, gap: 6,
}}>
<span style={{ fontSize: 12 }}></span>
<div>
<div style={{ color: 'var(--t3)', fontSize: 7 }}> ()</div>
<div style={{ color: 'var(--cyan)', fontWeight: 700, fontFamily: 'var(--fM)', fontSize: 10 }}>{data.lowTide}</div>
<div className="text-text-3" style={{ fontSize: 7 }}> ()</div>
<div className="text-primary-cyan font-bold font-mono" style={{ fontSize: 10 }}>{data.lowTide}</div>
</div>
</div>
</div>
{/* 24h Forecast */}
<div style={{ marginTop: 10, padding: '8px 10px', background: 'var(--bg0)', borderRadius: 6 }}>
<div style={{ fontSize: 8, fontWeight: 700, color: 'var(--t3)', marginBottom: 6 }}>24h </div>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 8, fontFamily: 'var(--fM)', color: 'var(--t2)' }}>
<div className="bg-bg-0" style={{ marginTop: 10, padding: '8px 10px', borderRadius: 6 }}>
<div className="font-bold text-text-3" style={{ fontSize: 8, marginBottom: 6 }}>24h </div>
<div className="flex justify-between font-mono text-text-2" style={{ fontSize: 8 }}>
{data.forecast.map((f, i) => (
<div key={i} style={{ textAlign: 'center' }}>
<div key={i} className="text-center">
<div>{f.hour}</div>
<div style={{ fontSize: 12, margin: '2px 0' }}>{f.icon}</div>
<div style={{ fontWeight: 600 }}>{f.temp}</div>
<div className="font-semibold">{f.temp}</div>
</div>
))}
</div>
@ -502,8 +506,8 @@ const WeatherPopup = forwardRef<HTMLDivElement, {
marginTop: 8, padding: '6px 10px',
background: 'rgba(249,115,22,0.05)', border: '1px solid rgba(249,115,22,0.12)', borderRadius: 6,
}}>
<div style={{ fontSize: 8, fontWeight: 700, color: 'var(--orange)', marginBottom: 3 }}> </div>
<div style={{ fontSize: 8, color: 'var(--t2)', lineHeight: 1.5 }}>{data.impactDc}</div>
<div className="font-bold text-status-orange" style={{ fontSize: 8, marginBottom: 3 }}> </div>
<div className="text-text-2" style={{ fontSize: 8, lineHeight: 1.5 }}>{data.impactDc}</div>
</div>
</div>
</div>
@ -513,14 +517,11 @@ WeatherPopup.displayName = 'WeatherPopup'
function WxCell({ icon, label, value }: { icon: string; label: string; value: string }) {
return (
<div style={{
padding: '6px 8px', background: 'var(--bg0)', borderRadius: 6,
display: 'flex', alignItems: 'center', gap: 6,
}}>
<div className="flex items-center bg-bg-0" style={{ padding: '6px 8px', borderRadius: 6, gap: 6 }}>
<span style={{ fontSize: 12 }}>{icon}</span>
<div>
<div style={{ color: 'var(--t3)', fontSize: 7 }}>{label}</div>
<div style={{ fontWeight: 600, fontFamily: 'var(--fM)' }}>{value}</div>
<div className="text-text-3" style={{ fontSize: 7 }}>{label}</div>
<div className="font-semibold font-mono">{value}</div>
</div>
</div>
)

파일 보기

@ -124,13 +124,9 @@ export function IncidentsRightPanel({
if (!incident) {
return (
<div style={{
width: '280px', minWidth: '280px', background: 'var(--bg1)',
borderLeft: '1px solid var(--bd)', display: 'flex', flexDirection: 'column',
alignItems: 'center', justifyContent: 'center',
}}>
<div style={{ textAlign: 'center', color: 'var(--t3)', fontSize: '11px' }}>
<div style={{ fontSize: '32px', marginBottom: '8px', opacity: 0.3 }}>📊</div>
<div className="flex flex-col items-center justify-center bg-bg-1 border-l border-border" style={{ width: '280px', minWidth: '280px' }}>
<div className="text-center text-text-3 text-[11px]">
<div className="text-[32px] mb-2" style={{ opacity: 0.3 }}>📊</div>
<br />
</div>
</div>
@ -138,85 +134,72 @@ export function IncidentsRightPanel({
}
return (
<div style={{
width: '280px', minWidth: '280px', background: 'var(--bg1)',
borderLeft: '1px solid var(--bd)', display: 'flex', flexDirection: 'column',
overflow: 'hidden', height: '100%',
}}>
<div className="flex flex-col bg-bg-1 border-l border-border overflow-hidden h-full" style={{ width: '280px', minWidth: '280px' }}>
{/* Header */}
<div style={{ padding: '10px 14px', borderBottom: '1px solid var(--bd)', flexShrink: 0 }}>
<div style={{ fontSize: '12px', fontWeight: 700, marginBottom: '2px' }}>
<div className="px-[14px] py-2.5 border-b border-border shrink-0">
<div className="text-xs font-bold mb-0.5">
🔬
</div>
<div style={{ fontSize: '9px', color: 'var(--t3)' }}>
<div className="text-[9px] text-text-3">
: <b style={{ color: 'var(--cyan)' }}>{incident.name}</b>
</div>
</div>
{/* Scrollable Content */}
<div style={{
flex: 1, height: 0, overflowY: 'auto', padding: '8px',
display: 'flex', flexDirection: 'column', gap: '8px',
scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent',
}}>
<div className="flex-1 h-0 overflow-y-auto flex flex-col gap-2 p-2" style={{ scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
{/* Analysis Sections (oil / hns / rsc) */}
{sections.map(sec => {
const checkedCount = sec.items.filter(it => it.checked).length
return (
<div key={sec.key} style={{
background: 'var(--bg2)', border: '1px solid var(--bd)',
borderRadius: '8px', padding: '10px',
}}>
<div key={sec.key} className="bg-bg-2 border border-border rounded-md p-2.5">
{/* Section Header */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<span style={{ fontSize: '14px' }}>{sec.icon}</span>
<span style={{ fontSize: '12px', fontWeight: 700, color: sec.color }}>
<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>
</div>
<button style={{
padding: '3px 10px', borderRadius: '4px', fontSize: '10px', fontWeight: 600,
cursor: 'pointer',
background: `rgba(${sec.colorRgb},0.1)`,
border: `1px solid rgba(${sec.colorRgb},0.25)`,
color: sec.color,
}}>
<button className="text-[10px] font-semibold cursor-pointer"
style={{
padding: '3px 10px', borderRadius: '4px',
background: `rgba(${sec.colorRgb},0.1)`,
border: `1px solid rgba(${sec.colorRgb},0.25)`,
color: sec.color,
}}>
📋
</button>
</div>
{/* Items */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
<div className="flex flex-col gap-1">
{sec.items.map(item => (
<div key={item.id} style={{
display: 'flex', alignItems: 'center', gap: '6px',
padding: '5px 8px',
background: `rgba(${sec.colorRgb},0.06)`,
border: `1px solid rgba(${sec.colorRgb},0.15)`,
borderRadius: '4px',
}}>
<div key={item.id} 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)`,
borderRadius: '4px',
}}>
<input
type="checkbox"
checked={item.checked}
onChange={() => toggleItem(sec.key, item.id)}
style={{ accentColor: sec.color, flexShrink: 0 }}
className="shrink-0"
style={{ accentColor: sec.color }}
/>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{
fontSize: '10px', fontWeight: 600,
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
}}>
<div className="flex-1 min-w-0">
<div className="text-[10px] font-semibold whitespace-nowrap overflow-hidden" style={{ textOverflow: 'ellipsis' }}>
{item.name}
</div>
<div style={{ fontSize: '8px', color: 'var(--t3)', fontFamily: 'var(--fM)' }}>
<div className="text-text-3 font-mono" style={{ fontSize: '8px' }}>
{item.sub}
</div>
</div>
<span
onClick={() => removeItem(sec.key, item.id)}
title="제거"
style={{ fontSize: '10px', cursor: 'pointer', color: 'var(--t3)', flexShrink: 0 }}
className="text-[10px] cursor-pointer text-text-3 shrink-0"
>
</span>
@ -225,10 +208,7 @@ export function IncidentsRightPanel({
</div>
{/* Status */}
<div style={{
marginTop: '6px', fontSize: '9px', color: 'var(--t3)',
display: 'flex', alignItems: 'center', gap: '6px',
}}>
<div className="flex items-center gap-1.5 mt-1.5 text-[9px] text-text-3">
: <b style={{ color: sec.color }}>{checkedCount}</b> · {sec.totalLabel}
</div>
</div>
@ -236,24 +216,21 @@ export function IncidentsRightPanel({
})}
{/* 민감자원 */}
<div style={{
background: 'var(--bg2)', border: '1px solid var(--bd)',
borderRadius: '8px', padding: '10px',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '8px' }}>
<span style={{ fontSize: '14px' }}>🐟</span>
<span style={{ fontSize: '12px', fontWeight: 700, color: '#22c55e' }}>
<div className="bg-bg-2 border border-border 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" style={{ color: '#22c55e' }}>
</span>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '3px' }}>
<div className="flex flex-col" style={{ gap: '3px' }}>
{sensitive.map(res => (
<label key={res.id} style={{
display: 'flex', alignItems: 'center', gap: '5px',
padding: '4px 6px', background: 'rgba(34,197,94,0.06)',
borderRadius: '3px', fontSize: '9px',
cursor: 'pointer',
}}>
<label key={res.id} className="flex items-center cursor-pointer text-[9px]"
style={{
gap: '5px',
padding: '4px 6px', background: 'rgba(34,197,94,0.06)',
borderRadius: '3px',
}}>
<input
type="checkbox"
checked={res.checked}
@ -262,7 +239,7 @@ export function IncidentsRightPanel({
/>
{res.name}
{res.area && (
<span style={{ color: 'var(--t3)', fontFamily: 'var(--fM)' }}>({res.area})</span>
<span className="text-text-3 font-mono">({res.area})</span>
)}
</label>
))}
@ -270,37 +247,25 @@ export function IncidentsRightPanel({
</div>
{/* 근처 방제자원 */}
<div style={{
background: 'var(--bg2)', border: '1px solid var(--bd)',
borderRadius: '8px', padding: '10px',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: '8px' }}>
<span style={{ fontSize: '14px' }}>🛡</span>
<span style={{ fontSize: '12px', fontWeight: 700, color: '#f59e0b' }}>
<div className="bg-bg-2 border border-border 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" style={{ color: '#f59e0b' }}>
</span>
</div>
{/* Empty state */}
<div style={{
padding: '10px 0', textAlign: 'center', color: 'var(--t3)',
fontSize: '10px', lineHeight: 1.7,
}}>
<div style={{ fontSize: '20px', marginBottom: '4px', opacity: 0.4 }}>🚢</div>
<div className="py-2.5 text-center text-text-3 text-[10px]" style={{ lineHeight: 1.7 }}>
<div className="text-xl mb-1" style={{ opacity: 0.4 }}>🚢</div>
<br />
</div>
{/* Radius slider */}
<div style={{
marginTop: '8px', paddingTop: '8px',
borderTop: '1px solid rgba(245,158,11,0.1)',
}}>
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
marginBottom: '5px',
}}>
<span style={{ fontSize: '9px', color: 'var(--t3)' }}> </span>
<span style={{ fontSize: '10px', fontWeight: 700, fontFamily: 'var(--fM)', color: '#f59e0b' }}>
<div className="mt-2 pt-2" style={{ borderTop: '1px solid rgba(245,158,11,0.1)' }}>
<div className="flex items-center justify-between mb-[5px]">
<span className="text-[9px] text-text-3"> </span>
<span className="text-[10px] font-bold font-mono" style={{ color: '#f59e0b' }}>
{nearbyRadius} nm
</span>
</div>
@ -311,10 +276,10 @@ export function IncidentsRightPanel({
value={nearbyRadius}
step={10}
onChange={(e) => setNearbyRadius(Number(e.target.value))}
className="w-full outline-none cursor-pointer"
style={{
width: '100%', height: '4px',
height: '4px',
background: 'var(--bd)', borderRadius: '2px',
outline: 'none', cursor: 'pointer',
accentColor: '#f59e0b',
}}
/>
@ -323,12 +288,9 @@ export function IncidentsRightPanel({
</div>
{/* Footer */}
<div style={{
padding: '10px', borderTop: '1px solid var(--bd)', flexShrink: 0,
display: 'flex', flexDirection: 'column', gap: '6px',
}}>
<div className="flex flex-col gap-1.5 p-2.5 border-t border-border shrink-0">
{/* View Mode */}
<div style={{ display: 'flex', gap: '4px' }}>
<div className="flex gap-1">
{([
{ mode: 'overlay' as ViewMode, icon: '🗂', label: '오버레이' },
{ mode: 'split2' as ViewMode, icon: '◫', label: '2분할' },
@ -336,14 +298,14 @@ export function IncidentsRightPanel({
]).map(v => {
const isActive = viewMode === v.mode
return (
<button key={v.mode} onClick={() => onViewModeChange(v.mode)} style={{
flex: 1, padding: '6px', borderRadius: 'var(--rS)',
fontSize: '10px', fontWeight: isActive ? 700 : 600,
cursor: 'pointer',
background: isActive ? 'rgba(6,182,212,0.1)' : 'var(--bg3)',
border: isActive ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
color: isActive ? 'var(--cyan)' : 'var(--t3)',
}}>
<button key={v.mode} onClick={() => onViewModeChange(v.mode)} className="flex-1 text-[10px] cursor-pointer"
style={{
padding: '6px', borderRadius: 'var(--rS)',
fontWeight: isActive ? 700 : 600,
background: isActive ? 'rgba(6,182,212,0.1)' : 'var(--bg3)',
border: isActive ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
color: isActive ? 'var(--cyan)' : 'var(--t3)',
}}>
{v.icon} {v.label}
</button>
)
@ -356,16 +318,16 @@ export function IncidentsRightPanel({
const checkedSections = sections.filter(s => s.items.some(it => it.checked))
const sensChecked = sensitive.filter(s => s.checked).length
onRunAnalysis(checkedSections, sensChecked)
}} style={{
width: '100%', 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))',
border: analysisActive ? '1px solid rgba(239,68,68,0.3)' : '1px solid rgba(6,182,212,0.3)',
borderRadius: 'var(--rS)',
color: analysisActive ? '#f87171' : 'var(--cyan)',
fontSize: '11px', fontWeight: 700, cursor: 'pointer',
}}>
}} className="w-full text-[11px] 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))',
border: analysisActive ? '1px solid rgba(239,68,68,0.3)' : '1px solid rgba(6,182,212,0.3)',
borderRadius: 'var(--rS)',
color: analysisActive ? '#f87171' : 'var(--cyan)',
}}>
{analysisActive ? '✕ 분석 닫기' : '🔬 통합 분석 비교 실행'}
</button>
</div>

파일 보기

@ -239,38 +239,32 @@ export function IncidentsView() {
/>
{/* Center - Map + Analysis Views */}
<div className="flex-1" style={{ display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
<div className="flex-1 flex flex-col overflow-hidden">
{/* Analysis Bar */}
{analysisActive && (
<div
className="shrink-0 flex items-center justify-between border-b border-border"
style={{
flexShrink: 0,
height: 36,
padding: '0 16px',
background: 'linear-gradient(90deg,rgba(6,182,212,0.06),var(--bg1))',
borderBottom: '1px solid var(--bd)',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ fontSize: 10, fontWeight: 700 }}>
<div className="flex items-center gap-2">
<span className="text-[10px] font-bold">
🔬
</span>
<span style={{ fontSize: 9, color: 'var(--t3)' }}>
<span className="text-[9px] text-text-3">
{selectedIncident?.name}
</span>
<div style={{ display: 'flex', gap: 4 }}>
<div className="flex gap-1">
{analysisTags.map((t, i) => (
<span
key={i}
className="text-[8px] font-semibold rounded-md"
style={{
padding: '2px 8px',
borderRadius: 8,
fontSize: 8,
fontWeight: 600,
background: `${t.color}18`,
background: `${t.color}18`,
border: `1px solid ${t.color}40`,
color: t.color,
}}
@ -280,7 +274,7 @@ export function IncidentsView() {
))}
</div>
</div>
<div style={{ display: 'flex', gap: 4, marginLeft: 'auto' }}>
<div className="flex gap-1 ml-auto">
{(
[
{ mode: 'overlay' as ViewMode, icon: '🗂', label: '오버레이' },
@ -291,12 +285,9 @@ export function IncidentsView() {
<button
key={v.mode}
onClick={() => setViewMode(v.mode)}
className="text-[9px] font-semibold cursor-pointer rounded-sm"
style={{
padding: '3px 10px',
borderRadius: 3,
fontSize: 9,
fontWeight: 600,
cursor: 'pointer',
background: viewMode === v.mode ? 'rgba(6,182,212,0.12)' : 'var(--bg3)',
border: viewMode === v.mode ? '1px solid rgba(6,182,212,0.3)' : '1px solid var(--bd)',
color: viewMode === v.mode ? 'var(--cyan)' : 'var(--t3)',
@ -307,12 +298,9 @@ export function IncidentsView() {
))}
<button
onClick={handleCloseAnalysis}
className="text-[9px] font-semibold cursor-pointer rounded-sm"
style={{
padding: '3px 8px',
borderRadius: 3,
fontSize: 9,
fontWeight: 600,
cursor: 'pointer',
background: 'rgba(239,68,68,0.06)',
border: '1px solid rgba(239,68,68,0.2)',
color: '#f87171',
@ -325,10 +313,10 @@ export function IncidentsView() {
)}
{/* Map / Analysis Content Area */}
<div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
<div className="flex-1 relative overflow-hidden">
{/* Default Map (visible when not in analysis or in overlay mode) */}
{(!analysisActive || viewMode === 'overlay') && (
<div style={{ position: 'absolute', inset: 0 }}>
<div className="absolute inset-0">
<Map
initialViewState={{ longitude: 127.8, latitude: 35.0, zoom: 7 }}
mapStyle={BASE_STYLE}
@ -347,10 +335,7 @@ export function IncidentsView() {
closeButton={true}
closeOnClick={false}
>
<div
className="text-center min-w-[180px]"
style={{ fontSize: 12 }}
>
<div className="text-center min-w-[180px] text-xs">
<div style={{ fontWeight: 600, marginBottom: 6, color: '#1a1a2e' }}>
{incidentPopup.incident.name}
</div>
@ -375,18 +360,15 @@ export function IncidentsView() {
{/* 호버 툴팁 */}
{hoverInfo && (
<div
className="absolute z-[1000] pointer-events-none rounded-md"
style={{
position: 'absolute',
left: hoverInfo.x + 12,
top: hoverInfo.y - 12,
zIndex: 1000,
pointerEvents: 'none',
background: '#161b22',
border: '1px solid #30363d',
borderRadius: 8,
padding: '8px 12px',
boxShadow: '0 8px 24px rgba(0,0,0,0.5)',
minWidth: 150,
minWidth: 150,
}}
>
{hoverInfo.type === 'vessel' ? (
@ -399,14 +381,7 @@ export function IncidentsView() {
{/* 분석 오버레이 (지도 위 시각효과) */}
{analysisActive && viewMode === 'overlay' && (
<div
style={{
position: 'absolute',
inset: 0,
zIndex: 500,
pointerEvents: 'none',
}}
>
<div className="absolute inset-0 z-[500] pointer-events-none">
{analysisTags.some(t => t.label === '유출유') && (
<div
style={{
@ -455,19 +430,15 @@ export function IncidentsView() {
{/* AIS Live Badge */}
<div
className="absolute top-[10px] right-[10px] z-[500] rounded-md"
style={{
position: 'absolute',
top: 10,
right: 10,
zIndex: 500,
background: 'rgba(13,17,23,0.88)',
border: '1px solid #30363d',
borderRadius: 8,
padding: '8px 12px',
backdropFilter: 'blur(8px)',
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 5 }}>
<div className="flex items-center gap-1.5 mb-[5px]">
<div
style={{
width: 6,
@ -477,19 +448,19 @@ export function IncidentsView() {
animation: 'pd 1.5s infinite',
}}
/>
<span style={{ fontSize: 10, fontWeight: 700 }}>
<span className="text-[10px] font-bold">
AIS Live
</span>
<span style={{ fontSize: 8, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>MarineTraffic</span>
<span className="text-[8px] text-text-3 font-mono">MarineTraffic</span>
</div>
<div style={{ display: 'flex', gap: 10, fontSize: 9, fontFamily: 'var(--fM)' }}>
<div style={{ color: 'var(--t2)' }}>
<div className="flex gap-2.5 text-[9px] font-mono">
<div className="text-text-2">
<b style={{ color: 'var(--cyan)' }}>20</b>
</div>
<div style={{ color: 'var(--t2)' }}>
<div className="text-text-2">
<b style={{ color: '#f87171' }}>6</b>
</div>
<div style={{ color: 'var(--t2)' }}>
<div className="text-text-2">
<b style={{ color: '#06b6d4' }}>2</b>
</div>
</div>
@ -497,49 +468,35 @@ export function IncidentsView() {
{/* Legend */}
<div
className="absolute bottom-[10px] left-[10px] z-[500] rounded-md flex flex-col gap-1.5"
style={{
position: 'absolute',
bottom: 10,
left: 10,
zIndex: 500,
background: 'rgba(13,17,23,0.88)',
border: '1px solid #30363d',
borderRadius: 8,
padding: '8px 12px',
backdropFilter: 'blur(8px)',
display: 'flex',
flexDirection: 'column',
gap: 6,
}}
>
<div style={{ fontSize: 9, fontWeight: 700, color: 'var(--t2)' }}>
<div className="text-[9px] font-bold text-text-2">
</div>
<div style={{ display: 'flex', gap: 10 }}>
<div className="flex gap-2.5">
{[
{ c: '#ef4444', l: '대응중' },
{ c: '#f59e0b', l: '조사중' },
{ c: '#6b7280', l: '종료' },
].map(s => (
<div key={s.l} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<div key={s.l} className="flex items-center gap-1">
<div style={{ width: 8, height: 8, borderRadius: '50%', background: s.c }} />
<span style={{ fontSize: 8, color: 'var(--t3)' }}>{s.l}</span>
<span className="text-[8px] text-text-3">{s.l}</span>
</div>
))}
</div>
<div
style={{
fontSize: 9,
fontWeight: 700,
color: 'var(--t2)',
marginTop: 2,
}}
>
<div className="text-[9px] font-bold text-text-2 mt-0.5">
AIS
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px 12px' }}>
{VESSEL_LEGEND.map(vl => (
<div key={vl.type} style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<div key={vl.type} className="flex items-center gap-1">
<div
style={{
width: 0,

파일 보기

@ -36,42 +36,34 @@ export function BacktrackModal({
<div
ref={backdropRef}
style={{
position: 'fixed', inset: 0, zIndex: 9999,
inset: 0,
background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(4px)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}
className="fixed z-[9999] flex items-center justify-center"
>
<div style={{
width: '580px', maxHeight: 'calc(100vh - 120px)',
background: 'var(--bg1)', border: '1px solid var(--bd)',
borderRadius: '14px', overflow: 'hidden',
display: 'flex', flexDirection: 'column',
background: 'var(--bg1)',
borderRadius: '14px',
boxShadow: '0 20px 60px rgba(0,0,0,0.5)',
}}>
}} className="border border-border overflow-hidden flex flex-col">
{/* Header */}
<div style={{
padding: '20px 24px', borderBottom: '1px solid var(--bd)',
display: 'flex', alignItems: 'center', gap: '14px',
}}>
padding: '20px 24px',
}} className="border-b border-border flex items-center gap-[14px]">
<div style={{
width: '40px', height: '40px', 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)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: '18px',
}}>
}} className="flex items-center justify-center">
🔍
</div>
<div style={{ flex: 1 }}>
<h2 style={{
fontSize: '16px', fontWeight: 700,
margin: 0,
}}>
<div className="flex-1">
<h2 className="text-base font-bold m-0">
</h2>
<div style={{
fontSize: '11px', color: 'var(--t3)', marginTop: '2px',
}}>
<div className="text-[11px] text-text-3 mt-[2px]">
AIS
</div>
</div>
@ -79,10 +71,10 @@ export function BacktrackModal({
onClick={onClose}
style={{
width: '32px', height: '32px', borderRadius: '8px',
border: '1px solid var(--bd)', background: 'var(--bg3)',
color: 'var(--t3)', fontSize: '14px', cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center',
background: 'var(--bg3)',
fontSize: '14px',
}}
className="border border-border text-text-3 cursor-pointer flex items-center justify-center"
>
</button>
@ -90,15 +82,11 @@ export function BacktrackModal({
{/* Scrollable Content */}
<div style={{
flex: 1, overflowY: 'auto', padding: '20px 24px',
display: 'flex', flexDirection: 'column', gap: '16px',
}}>
padding: '20px 24px',
}} className="flex-1 overflow-y-auto flex flex-col gap-4">
{/* Analysis Conditions */}
<div>
<h3 style={{
fontSize: '12px', fontWeight: 700, color: 'var(--t2)',
marginBottom: '10px',
}}>
<h3 className="text-[12px] font-bold text-text-2 mb-[10px]">
</h3>
<div style={{
@ -112,12 +100,12 @@ export function BacktrackModal({
].map((item, i) => (
<div key={i} style={{
padding: '10px 12px', background: 'var(--bg3)',
border: '1px solid var(--bd)', borderRadius: '8px',
}}>
<div style={{ fontSize: '9px', color: 'var(--t3)', marginBottom: '4px' }}>
borderRadius: '8px',
}} className="border border-border">
<div className="text-[9px] text-text-3 mb-1">
{item.label}
</div>
<div style={{ fontSize: '12px', fontWeight: 600, fontFamily: 'var(--fM)' }}>
<div className="text-[12px] font-semibold font-mono">
{item.value}
</div>
</div>
@ -127,11 +115,11 @@ export function BacktrackModal({
border: '1px solid rgba(168,85,247,0.3)', borderRadius: '8px',
gridColumn: '1 / -1',
}}>
<div style={{ fontSize: '9px', color: 'var(--t3)', marginBottom: '4px' }}>
<div className="text-[9px] text-text-3 mb-1">
</div>
<div style={{ fontSize: '14px', fontWeight: 700, color: 'var(--purple)', fontFamily: 'var(--fM)' }}>
{conditions.totalVessels} <span style={{ fontSize: '10px', fontWeight: 500, color: 'var(--t3)' }}>(AIS )</span>
<div className="text-sm font-bold text-primary-purple font-mono">
{conditions.totalVessels} <span className="text-[10px] font-medium text-text-3">(AIS )</span>
</div>
</div>
</div>
@ -140,26 +128,19 @@ export function BacktrackModal({
{/* Results */}
{phase === 'results' && vessels.length > 0 && (
<div>
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
marginBottom: '12px',
}}>
<h3 style={{
fontSize: '12px', fontWeight: 700, color: 'var(--t2)',
margin: 0,
}}>
<div className="flex items-center justify-between mb-3">
<h3 className="text-[12px] font-bold text-text-2 m-0">
</h3>
<div style={{
padding: '4px 10px', borderRadius: '12px', fontSize: '10px', fontWeight: 700,
background: 'rgba(239,68,68,0.1)', border: '1px solid rgba(239,68,68,0.3)',
color: 'var(--red)',
}}>
padding: '4px 10px', borderRadius: '12px',
background: 'rgba(239,68,68,0.1)', border: '1px solid rgba(239,68,68,0.3)',
}} className="text-[10px] font-bold text-status-red">
{conditions.totalVessels} {vessels.length}
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
<div className="flex flex-col gap-2.5">
{vessels.map((v) => (
<VesselCard key={v.imo} vessel={v} />
))}
@ -170,18 +151,18 @@ export function BacktrackModal({
{/* Footer */}
<div style={{
padding: '16px 24px', borderTop: '1px solid var(--bd)',
display: 'flex', gap: '8px',
}}>
padding: '16px 24px',
}} className="border-t border-border flex gap-2">
{phase === 'conditions' && (
<button
onClick={onRunAnalysis}
style={{
flex: 1, padding: '12px', fontSize: '13px', fontWeight: 700,
borderRadius: '8px', cursor: 'pointer',
padding: '12px',
borderRadius: '8px',
background: 'linear-gradient(135deg, var(--purple), var(--cyan))',
border: 'none', color: '#fff',
}}
className="flex-1 text-[13px] font-bold cursor-pointer"
>
🔍
</button>
@ -190,11 +171,12 @@ export function BacktrackModal({
<button
disabled
style={{
flex: 1, padding: '12px', fontSize: '13px', fontWeight: 700,
padding: '12px',
borderRadius: '8px',
background: 'var(--bg3)', border: '1px solid var(--bd)',
background: 'var(--bg3)',
color: 'var(--purple)', cursor: 'wait',
}}
className="flex-1 text-[13px] font-bold border border-border"
>
AIS ...
</button>
@ -203,11 +185,12 @@ export function BacktrackModal({
<button
onClick={onStartReplay}
style={{
flex: 1, padding: '12px', fontSize: '13px', fontWeight: 700,
borderRadius: '8px', cursor: 'pointer',
padding: '12px',
borderRadius: '8px',
background: 'linear-gradient(135deg, var(--purple), var(--cyan))',
border: 'none', color: '#fff',
}}
className="flex-1 text-[13px] font-bold cursor-pointer"
>
🗺
</button>
@ -225,32 +208,31 @@ function VesselCard({ vessel }: { vessel: BacktrackVessel }) {
return (
<div style={{
padding: '14px', background: 'var(--bg0)',
border: '1px solid var(--bd)', borderLeft: `4px solid ${vessel.color}`,
borderLeft: `4px solid ${vessel.color}`,
borderRadius: '10px',
}}>
}} className="border border-border">
{/* Header row */}
<div style={{ display: 'flex', alignItems: 'center', gap: '10px', marginBottom: '10px' }}>
<div className="flex items-center gap-[10px] mb-[10px]">
<div style={{
width: '28px', height: '28px', borderRadius: '50%',
background: `${vessel.color}20`, border: `2px solid ${vessel.color}`,
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: '12px', fontWeight: 800, color: vessel.color, fontFamily: 'var(--fM)',
}}>
fontSize: '12px', fontWeight: 800, color: vessel.color,
}} className="flex items-center justify-center font-mono">
{vessel.rank}
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: '13px', fontWeight: 700, fontFamily: 'var(--fM)' }}>
<div className="flex-1">
<div className="text-[13px] font-bold font-mono">
{vessel.name}
</div>
<div style={{ fontSize: '9px', color: 'var(--t3)', fontFamily: 'var(--fM)', marginTop: '2px' }}>
<div className="text-[9px] text-text-3 font-mono mt-[2px]">
IMO: {vessel.imo} · {vessel.type} · {vessel.flag}
</div>
</div>
<div style={{ textAlign: 'right' }}>
<div style={{ fontSize: '22px', fontWeight: 800, color: probColor, fontFamily: 'var(--fM)', lineHeight: 1 }}>
<div style={{ fontSize: '22px', color: probColor, lineHeight: 1 }} className="font-bold font-mono">
{vessel.probability}%
</div>
<div style={{ fontSize: '8px', color: 'var(--t3)' }}> </div>
<div className="text-[8px] text-text-3"> </div>
</div>
</div>
@ -266,13 +248,12 @@ function VesselCard({ vessel }: { vessel: BacktrackVessel }) {
padding: '6px', background: 'var(--bg3)', borderRadius: '6px',
border: s.highlight ? '1px solid rgba(239,68,68,0.3)' : '1px solid var(--bd)',
}}>
<div style={{ fontSize: '8px', color: 'var(--t3)', marginBottom: '2px' }}>
<div className="text-[8px] text-text-3 mb-[2px]">
{s.label}
</div>
<div style={{
fontSize: '10px', fontWeight: 600, fontFamily: 'var(--fM)',
color: s.highlight ? 'var(--red)' : 'var(--t1)',
}}>
}} className="text-[10px] font-semibold font-mono">
{s.value}
</div>
</div>
@ -284,9 +265,8 @@ function VesselCard({ vessel }: { vessel: BacktrackVessel }) {
<div style={{
padding: '8px 10px', background: 'rgba(239,68,68,0.05)',
border: '1px solid rgba(239,68,68,0.15)', borderRadius: '6px',
fontSize: '9px', color: 'var(--t2)',
lineHeight: '1.5',
}}>
}} className="text-[9px] text-text-2">
{vessel.description}
</div>
)}

파일 보기

@ -17,41 +17,40 @@ export function BoomDeploymentTheoryView() {
}
return (
<div className="flex flex-col h-full overflow-hidden" style={{ background: 'var(--bg0)' }}>
<div className="flex flex-col h-full overflow-hidden bg-bg-0">
<div className="flex-1 overflow-y-auto scrollbar-thin p-5">
{/* 헤더 */}
<div className="flex items-center justify-between mb-5">
<div className="flex items-center gap-3">
<div className="w-[38px] h-[38px] rounded-[9px] flex items-center justify-content text-lg"
style={{ background: 'linear-gradient(135deg,rgba(249,115,22,.2),rgba(234,179,8,.15))', border: '1px solid rgba(249,115,22,.3)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div className="w-[38px] h-[38px] rounded-[9px] flex items-center justify-center text-lg"
style={{ background: 'linear-gradient(135deg,rgba(249,115,22,.2),rgba(234,179,8,.15))', border: '1px solid rgba(249,115,22,.3)' }}>
🛡
</div>
<div>
<div className="text-[15px] font-bold"> </div>
<div className="text-[10px] mt-0.5" style={{ color: 'var(--t3)' }}>Oil Boom Deployment Optimization · · </div>
<div className="text-[10px] mt-0.5 text-text-3">Oil Boom Deployment Optimization · · </div>
</div>
</div>
<button onClick={handleExportPDF}
className="px-3.5 py-1.5 rounded-md text-[10px] font-semibold cursor-pointer"
style={{ border: '1px solid rgba(59,130,246,.3)', background: 'rgba(59,130,246,.08)', color: 'var(--blue)' }}>
className="px-3.5 py-1.5 rounded-md text-[10px] font-semibold cursor-pointer text-primary-blue"
style={{ border: '1px solid rgba(59,130,246,.3)', background: 'rgba(59,130,246,.08)' }}>
📤 PDF
</button>
</div>
{/* 내부 네비게이션 */}
<div className="mb-5">
<div className="flex gap-[3px] rounded-lg p-1" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="flex gap-[3px] rounded-lg p-1 bg-bg-3 border border-border">
{boomTabs.map(tab => (
<button
key={tab.id}
onClick={() => setActivePanel(tab.id)}
className="flex-1 py-2 px-2 text-[12px] font-semibold rounded-md transition-all"
style={{
background: activePanel === tab.id ? 'rgba(249,115,22,.15)' : 'var(--bg3)',
background: activePanel === tab.id ? 'rgba(249,115,22,.15)' : undefined,
color: activePanel === tab.id ? 'var(--orange)' : 'var(--t3)',
border: activePanel === tab.id ? '1px solid rgba(249,115,22,.3)' : '1px solid var(--bd)',
fontWeight: 600,
}}
>
{tab.label}
@ -84,13 +83,13 @@ function OverviewPanel() {
<div className="grid grid-cols-2 gap-5">
<div>
<div className="text-[13px] font-bold mb-2">🛡 ?</div>
<div className="text-[11px] leading-[1.8]" style={{ color: 'var(--t2)' }}>
<div className="text-[11px] leading-[1.8] text-text-2">
<b style={{ color: 'var(--orange)' }}> </b> (··) , ( · ) <b style={{ color: 'var(--cyan)' }}> </b> ·· .
</div>
</div>
<div>
<div className="text-[13px] font-bold mb-2">🎯 WING </div>
<div className="flex flex-col gap-[5px] text-[11px]" style={{ color: 'var(--t2)' }}>
<div className="flex flex-col gap-[5px] text-[11px] text-text-2">
{[
{ num: '①', color: 'var(--orange)', bg: 'rgba(249,115,22,.05)', bd: 'rgba(249,115,22,.12)', text: '차단 면적 최대화 — 예측 유출유 확산 경계와 오일펜스 교차 면적 극대화' },
{ num: '②', color: 'var(--cyan)', bg: 'rgba(6,182,212,.05)', bd: 'rgba(6,182,212,.12)', text: '도달시간 최소화 — 유출유 해안·ESI 민감구역 도달 전 선제적 차단선 구축' },
@ -107,7 +106,7 @@ function OverviewPanel() {
</div>
{/* 전체 흐름도 */}
<div className="rounded-[10px] p-4 mb-4" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="rounded-md p-4 mb-4 bg-bg-3 border border-border">
<div className="text-xs font-bold mb-3.5"> WING </div>
<div className="flex items-center justify-center gap-0 flex-nowrap overflow-x-auto py-2">
{[
@ -119,16 +118,15 @@ function OverviewPanel() {
{ icon: '🗺️', label: '지도 표시', sub: 'ESI 중첩\n방제자원 연계', color: 'var(--blue)', bg: 'rgba(59,130,246,.08)', bd: 'rgba(59,130,246,.2)' },
].map((step, i) => (
<div key={i} className="flex items-center">
<div className="text-center min-w-[80px] rounded-lg px-3 py-2.5" style={{
<div className="text-center min-w-[80px] rounded-lg px-3 py-2.5 text-[9px]" style={{
background: step.bg,
border: `${step.bold ? '2px' : '1px'} solid ${step.bd}`,
fontSize: '9px'
}}>
<div className="text-[15px] mb-1">{step.icon}</div>
<div style={{ fontWeight: 700, color: step.color }}>{step.label}</div>
<div style={{ color: 'var(--t3)', whiteSpace: 'pre-line' }}>{step.sub}</div>
<div className="text-text-3" style={{ whiteSpace: 'pre-line' }}>{step.sub}</div>
</div>
{i < 5 && <div className="px-1.5" style={{ color: 'var(--t3)', fontSize: '14px' }}></div>}
{i < 5 && <div className="px-1.5 text-text-3 text-[14px]"></div>}
</div>
))}
</div>
@ -141,12 +139,12 @@ function OverviewPanel() {
{ icon: '🌊', title: '공기충전식 오일펜스', color: 'var(--blue)', desc: '공기로 부력 확보. 이동·보관 편리. 해상 광역 차단에 주로 사용.', specs: ['내조류 한계: 0.7~1.5 knot', '높이: 45~90cm · 수중 45~90cm', '전개속도: 100~300m/hr'] },
{ icon: '🔄', title: '자항식 오일펜스', color: 'var(--green)', desc: '방제정 예인 또는 자체 추진. U형·V형 동적 배치. 강조류 해역 적합.', specs: ['운용수심: 5m 이상', 'U형·V형·J형 동적 형태', '내조류: 조류각도 보정으로 극복'] },
].map((boom, i) => (
<div key={i} className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderTop: `3px solid ${boom.color}` }}>
<div key={i} className="rounded-md p-3.5 bg-bg-3 border border-border" style={{ borderTop: `3px solid ${boom.color}` }}>
<div className="text-[11px] font-bold mb-2" style={{ color: boom.color }}>{boom.icon} {boom.title}</div>
<div className="text-[10px] mb-2 leading-[1.7]" style={{ color: 'var(--t2)' }}>{boom.desc}</div>
<div className="flex flex-col gap-[3px] text-[9px]" style={{ color: 'var(--t2)' }}>
<div className="text-[10px] mb-2 leading-[1.7] text-text-2">{boom.desc}</div>
<div className="flex flex-col gap-[3px] text-[9px] text-text-2">
{boom.specs.map((spec, j) => (
<div key={j} className="px-[7px] py-[3px] rounded-[3px]" style={{ background: `${boom.color}11`, color: 'var(--t2)' }}>{spec}</div>
<div key={j} className="px-[7px] py-[3px] rounded-[3px] text-text-2" style={{ background: `${boom.color}11` }}>{spec}</div>
))}
</div>
</div>
@ -154,9 +152,9 @@ function OverviewPanel() {
</div>
{/* 핵심 제약조건 */}
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="text-[11px] font-bold mb-2.5" style={{ color: 'var(--red)' }}> </div>
<div className="grid grid-cols-3 gap-2.5 text-[10px]" style={{ color: 'var(--t2)' }}>
<div className="rounded-md p-3.5 bg-bg-3 border border-border">
<div className="text-[11px] font-bold mb-2.5 text-status-red"> </div>
<div className="grid grid-cols-3 gap-2.5 text-[10px] text-text-2">
{[
{ icon: '🌊', title: '조류 제약', color: 'var(--red)', bg: 'rgba(239,68,68,.05)', bd: 'rgba(239,68,68,.15)', lines: ['조류속도 > 임계유속 시', '오일펜스 하단 통과 발생', 'U<0.7 knot 유지 필수', '임계각도 자동 계산 적용'] },
{ icon: '📏', title: '자원 제약', color: 'var(--yellow)', bg: 'rgba(234,179,8,.05)', bd: 'rgba(234,179,8,.15)', lines: ['가용 오일펜스 총 길이', '방제정 척수·이동시간', '앵커링 가능 수심 조건', '연결부 허용 장력'] },
@ -164,7 +162,7 @@ function OverviewPanel() {
].map((c, i) => (
<div key={i} className="p-2.5 rounded-[7px]" style={{ background: c.bg, border: `1px solid ${c.bd}` }}>
<div className="font-bold mb-[5px]" style={{ color: c.color }}>{c.icon} {c.title}</div>
<div className="leading-[1.7]" style={{ color: 'var(--t2)' }}>
<div className="leading-[1.7] text-text-2">
{c.lines.map((l, j) => <span key={j}>{j > 0 && <br />}{l}</span>)}
</div>
</div>
@ -180,44 +178,44 @@ function DeploymentTheoryPanel() {
return (
<>
{/* 차단 효율 이론 */}
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="rounded-md p-4 mb-3.5 bg-bg-3 border border-border">
<div className="text-xs font-bold mb-3">📐 (Boom Containment Efficiency)</div>
<div className="grid grid-cols-2 gap-3.5">
<div>
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--orange)' }}> E(θ, U)</div>
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)' }}>
<div className="text-[11px] font-bold mb-2 text-status-orange"> E(θ, U)</div>
<div className="text-[10px] leading-[1.8] mb-2 text-text-2">
<b> (U)</b> <b> (θ)</b> . , .
</div>
<div className="rounded-md p-2.5 text-[10px] leading-[2.1]" style={{ background: 'var(--bg0)', border: '1px solid rgba(249,115,22,.2)', fontFamily: 'var(--fM)' }}>
E(θ,U) = 1 <span style={{ color: 'var(--red)' }}>F<sub>loss</sub>(U<sub>n</sub>)</span><br />
U<sub>n</sub> = U · sin(θ) <span className="text-[9px]" style={{ color: 'var(--t3)' }}>( )</span><br />
<div className="rounded-md p-2.5 text-[10px] leading-[2.1] bg-bg-0 font-mono" style={{ border: '1px solid rgba(249,115,22,.2)' }}>
E(θ,U) = 1 <span className="text-status-red">F<sub>loss</sub>(U<sub>n</sub>)</span><br />
U<sub>n</sub> = U · sin(θ) <span className="text-[9px] text-text-3">( )</span><br />
E = 1 (U<sub>n</sub> U<sub>c</sub>)<br />
E = max(0, 1 (U<sub>n</sub>/U<sub>c</sub>)²) (U<sub>n</sub> &gt; U<sub>c</sub>)<br />
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>U<sub>c</sub>: ( 0.35m/s = 0.7 knot)</span>
<span className="text-[9px] text-text-3">U<sub>c</sub>: ( 0.35m/s = 0.7 knot)</span>
</div>
</div>
<div>
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--cyan)' }}> θ* </div>
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)' }}>
<div className="text-[11px] font-bold mb-2 text-primary-cyan"> θ* </div>
<div className="text-[10px] leading-[1.8] mb-2 text-text-2">
. <b></b> , <b style={{ color: 'var(--cyan)' }}>30°~45° </b> .
</div>
<div className="rounded-md p-2.5 text-[10px] leading-[2.1]" style={{ background: 'var(--bg0)', border: '1px solid rgba(6,182,212,.2)', fontFamily: 'var(--fM)' }}>
θ* = arcsin(U<sub>c</sub> / U) <span className="text-[9px]" style={{ color: 'var(--t3)' }}>()</span><br />
<div className="rounded-md p-2.5 text-[10px] leading-[2.1] bg-bg-0 font-mono" style={{ border: '1px solid rgba(6,182,212,.2)' }}>
θ* = arcsin(U<sub>c</sub> / U) <span className="text-[9px] text-text-3">()</span><br />
θ<sub>opt</sub> = argmax [A<sub>block</sub>(θ) · E(θ,U)]<br />
실용범위: 15° θ 60°<br />
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>, θ &lt; arcsin(U<sub>c</sub>/U) </span>
<span className="text-[9px] text-text-3">, θ &lt; arcsin(U<sub>c</sub>/U) </span>
</div>
</div>
</div>
</div>
{/* V형·U형·J형 배치 패턴 */}
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="rounded-md p-4 mb-3.5 bg-bg-3 border border-border">
<div className="text-xs font-bold mb-3">🔷 </div>
<div className="grid grid-cols-3 gap-3">
{/* V형 */}
<div className="rounded-lg p-3.5" style={{ background: 'var(--bg0)', border: '1px solid rgba(6,182,212,.2)', borderTop: '3px solid var(--cyan)' }}>
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--cyan)' }}>V형 (Chevron)</div>
<div className="rounded-lg p-3.5 bg-bg-0" style={{ border: '1px solid rgba(6,182,212,.2)', borderTop: '3px solid var(--cyan)' }}>
<div className="text-[11px] font-bold mb-2 text-primary-cyan">V형 (Chevron)</div>
<div className="flex items-center justify-center p-4 rounded-md mb-2" style={{ background: 'rgba(6,182,212,.04)' }}>
<svg width="180" height="120" viewBox="0 0 100 65" style={{ overflow: 'visible' }}>
<defs><marker id="arr1" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto"><path d="M0,0 L0,6 L6,3 z" fill="rgba(6,182,212,.7)" /></marker></defs>
@ -229,17 +227,17 @@ function DeploymentTheoryPanel() {
<text x="58" y="64" fill="rgba(6,182,212,.7)" fontSize="6"></text>
</svg>
</div>
<div className="text-[10px] leading-[1.7] mb-[7px]" style={{ color: 'var(--t2)' }}> V형. . .</div>
<div className="rounded-[5px] p-[7px] text-[9px] leading-[1.9]" style={{ fontFamily: 'var(--fM)', background: 'rgba(6,182,212,.05)' }}>
<div className="text-[10px] leading-[1.7] mb-[7px] text-text-2"> V형. . .</div>
<div className="rounded-[5px] p-[7px] text-[9px] leading-[1.9] font-mono" style={{ background: 'rgba(6,182,212,.05)' }}>
A<sub>V</sub> = L²·sin(2α)/2<br />
<span style={{ color: 'var(--t3)' }}>α: 반개각, L: 편측 </span><br />
<span className="text-text-3">α: 반개각, L: 편측 </span><br />
α = 30°~45°
</div>
</div>
{/* U형 */}
<div className="rounded-lg p-3.5" style={{ background: 'var(--bg0)', border: '1px solid rgba(34,197,94,.2)', borderTop: '3px solid var(--green)' }}>
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--green)' }}>U형 (Horseshoe)</div>
<div className="rounded-lg p-3.5 bg-bg-0" style={{ border: '1px solid rgba(34,197,94,.2)', borderTop: '3px solid var(--green)' }}>
<div className="text-[11px] font-bold mb-2 text-status-green">U형 (Horseshoe)</div>
<div className="flex items-center justify-center p-4 rounded-md mb-2" style={{ background: 'rgba(34,197,94,.04)' }}>
<svg width="180" height="120" viewBox="0 0 100 65" style={{ overflow: 'visible' }}>
<defs><marker id="arr2" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto"><path d="M0,0 L0,6 L6,3 z" fill="rgba(6,182,212,.7)" /></marker></defs>
@ -250,17 +248,17 @@ function DeploymentTheoryPanel() {
<text x="58" y="5" fill="rgba(6,182,212,.7)" fontSize="6"></text>
</svg>
</div>
<div className="text-[10px] leading-[1.7] mb-[7px]" style={{ color: 'var(--t2)' }}> . . .</div>
<div className="rounded-[5px] p-[7px] text-[9px] leading-[1.9]" style={{ fontFamily: 'var(--fM)', background: 'rgba(34,197,94,.05)' }}>
<div className="text-[10px] leading-[1.7] mb-[7px] text-text-2"> . . .</div>
<div className="rounded-[5px] p-[7px] text-[9px] leading-[1.9] font-mono" style={{ background: 'rgba(34,197,94,.05)' }}>
A<sub>U</sub> = π·r²/2 + 2r·h<br />
<span style={{ color: 'var(--t3)' }}>r: 반경, h: 직선부 </span><br />
<span className="text-text-3">r: 반경, h: 직선부 </span><br />
전제: U &lt; 0.5 knot
</div>
</div>
{/* J형 */}
<div className="rounded-lg p-3.5" style={{ background: 'var(--bg0)', border: '1px solid rgba(168,85,247,.2)', borderTop: '3px solid var(--purple)' }}>
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--purple)' }}>J형 (Skimming)</div>
<div className="rounded-lg p-3.5 bg-bg-0" style={{ border: '1px solid rgba(168,85,247,.2)', borderTop: '3px solid var(--purple)' }}>
<div className="text-[11px] font-bold mb-2 text-primary-purple">J형 (Skimming)</div>
<div className="flex items-center justify-center p-4 rounded-md mb-2" style={{ background: 'rgba(168,85,247,.04)' }}>
<svg width="180" height="120" viewBox="0 0 100 65" style={{ overflow: 'visible' }}>
<defs><marker id="arr3" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto"><path d="M0,0 L0,6 L6,3 z" fill="rgba(6,182,212,.7)" /></marker></defs>
@ -272,10 +270,10 @@ function DeploymentTheoryPanel() {
<text x="58" y="5" fill="rgba(6,182,212,.7)" fontSize="6"></text>
</svg>
</div>
<div className="text-[10px] leading-[1.7] mb-[7px]" style={{ color: 'var(--t2)' }}>+ . . · .</div>
<div className="rounded-[5px] p-[7px] text-[9px] leading-[1.9]" style={{ fontFamily: 'var(--fM)', background: 'rgba(168,85,247,.05)' }}>
<div className="text-[10px] leading-[1.7] mb-[7px] text-text-2">+ . . · .</div>
<div className="rounded-[5px] p-[7px] text-[9px] leading-[1.9] font-mono" style={{ background: 'rgba(168,85,247,.05)' }}>
θ<sub>J</sub> = arcsin(U<sub>c</sub>/U) + δ<br />
<span style={{ color: 'var(--t3)' }}>δ: 안전여유각(5°~10°)</span><br />
<span className="text-text-3">δ: 안전여유각(5°~10°)</span><br />
활용: U &gt; 0.7 knot
</div>
</div>
@ -283,24 +281,24 @@ function DeploymentTheoryPanel() {
</div>
{/* 다단 배치 이론 */}
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="rounded-md p-3.5 bg-bg-3 border border-border">
<div className="text-[11px] font-bold mb-2.5">🔢 (Multi-Boom) </div>
<div className="grid grid-cols-2 gap-3">
<div className="text-[10px] leading-[1.8]" style={{ color: 'var(--t2)' }}>
<div className="text-[10px] leading-[1.8] text-text-2">
<b> </b> . n개 :
<div className="mt-2 rounded-[5px] p-[9px] leading-[2]" style={{ background: 'var(--bg0)', fontFamily: 'var(--fM)' }}>
<div className="mt-2 rounded-[5px] p-[9px] leading-[2] bg-bg-0 font-mono">
E<sub>total</sub> = 1 (1E<sub>i</sub>)<br />
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>E<sub>i</sub>: i번째 </span>
<span className="text-[9px] text-text-3">E<sub>i</sub>: i번째 </span>
</div>
</div>
<div className="flex flex-col gap-[5px] text-[9px]" style={{ color: 'var(--t2)' }}>
<div className="flex flex-col gap-[5px] text-[9px] text-text-2">
{[
{ color: 'var(--green)', bg: 'rgba(34,197,94,.05)', bd: 'rgba(34,197,94,.12)', label: '2단 직렬', text: ': E_total = E₁+E₂E₁·E₂ (예: 70%+70% → 91%)' },
{ color: 'var(--cyan)', bg: 'rgba(6,182,212,.05)', bd: 'rgba(6,182,212,.12)', label: '단간 거리', text: ': 부표 집적 방지를 위해 ≥ 200m 이격 권장' },
{ color: 'var(--purple)', bg: 'rgba(168,85,247,.05)', bd: 'rgba(168,85,247,.12)', label: '배치 우선순위', text: ': ESI 고등급 구역 보호 → 취수원 → 어항 순' },
{ color: 'var(--orange)', bg: 'rgba(249,115,22,.05)', bd: 'rgba(249,115,22,.12)', label: '조석 변화', text: ': 창조·낙조 전환 시 오일펜스 방향 재조정 필요' },
].map((item, i) => (
<div key={i} className="px-[9px] py-1.5 rounded-[5px]" style={{ background: item.bg, border: `1px solid ${item.bd}`, color: 'var(--t2)' }}>
<div key={i} className="px-[9px] py-1.5 rounded-[5px] text-text-2" style={{ background: item.bg, border: `1px solid ${item.bd}` }}>
<b style={{ color: item.color }}>{item.label}</b>{item.text}
</div>
))}
@ -320,42 +318,42 @@ function OptimizationPanel() {
style={{ background: 'linear-gradient(135deg,rgba(168,85,247,.06),rgba(59,130,246,.04))', border: '1px solid rgba(168,85,247,.2)' }}>
<div className="absolute top-0 left-0 right-0 h-[3px]" style={{ background: 'linear-gradient(90deg,var(--purple),var(--blue))' }} />
<div className="text-[13px] font-bold mb-2"> (Multi-Objective Optimization)</div>
<div className="text-[11px] leading-[1.8]" style={{ color: 'var(--t2)' }}>
<div className="text-[11px] leading-[1.8] text-text-2">
<b style={{ color: 'var(--purple)' }}> </b> . , <b style={{ color: 'var(--cyan)' }}> (Pareto Optimal) </b> .
</div>
</div>
{/* 목적함수 정의 */}
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="rounded-md p-4 mb-3.5 bg-bg-3 border border-border">
<div className="text-xs font-bold mb-3">📊 </div>
<div className="grid grid-cols-2 gap-3 mb-3">
<div className="rounded-lg p-3" style={{ background: 'var(--bg0)', border: '1px solid rgba(34,197,94,.2)' }}>
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--green)' }}>🎯 F(x)</div>
<div className="rounded-[5px] p-[9px] text-[10px] leading-[2.2]" style={{ fontFamily: 'var(--fM)', background: 'rgba(34,197,94,.04)' }}>
<b style={{ color: 'var(--green)' }}>:</b><br />
f(x) = Σ A<sub>blocked,i</sub> · w<sub>ESI,i</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>( )</span><br />
f(x) = T<sub>deadline</sub> T<sub>deploy</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>()</span><br />
<b style={{ color: 'var(--red)' }}>:</b><br />
f(x) = Σ L<sub>boom,j</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>( )</span><br />
f(x) = Σ D<sub>vessel,k</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>( )</span>
<div className="rounded-lg p-3 bg-bg-0" style={{ border: '1px solid rgba(34,197,94,.2)' }}>
<div className="text-[11px] font-bold mb-2 text-status-green">🎯 F(x)</div>
<div className="rounded-[5px] p-[9px] text-[10px] leading-[2.2] font-mono" style={{ background: 'rgba(34,197,94,.04)' }}>
<b className="text-status-green">:</b><br />
f(x) = Σ A<sub>blocked,i</sub> · w<sub>ESI,i</sub> <span className="text-[9px] text-text-3">( )</span><br />
f(x) = T<sub>deadline</sub> T<sub>deploy</sub> <span className="text-[9px] text-text-3">()</span><br />
<b className="text-status-red">:</b><br />
f(x) = Σ L<sub>boom,j</sub> <span className="text-[9px] text-text-3">( )</span><br />
f(x) = Σ D<sub>vessel,k</sub> <span className="text-[9px] text-text-3">( )</span>
</div>
</div>
<div className="rounded-lg p-3" style={{ background: 'var(--bg0)', border: '1px solid rgba(239,68,68,.2)' }}>
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--red)' }}>🚫 G(x)</div>
<div className="rounded-[5px] p-[9px] text-[10px] leading-[2.2]" style={{ fontFamily: 'var(--fM)', background: 'rgba(239,68,68,.04)' }}>
g: U·sin(θ<sub>i</sub>) U<sub>c</sub> i <span className="text-[9px]" style={{ color: 'var(--t3)' }}>()</span><br />
g: Σ L<sub>j</sub> L<sub>max</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>( )</span><br />
g: T<sub>deploy,i</sub> T<sub>arrive,i</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>( )</span><br />
g: d(p<sub>i</sub>, shore) d<sub>min</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>( )</span><br />
g: h(p<sub>i</sub>) h<sub>min</sub> <span className="text-[9px]" style={{ color: 'var(--t3)' }}>( )</span>
<div className="rounded-lg p-3 bg-bg-0" style={{ border: '1px solid rgba(239,68,68,.2)' }}>
<div className="text-[11px] font-bold mb-2 text-status-red">🚫 G(x)</div>
<div className="rounded-[5px] p-[9px] text-[10px] leading-[2.2] font-mono" style={{ background: 'rgba(239,68,68,.04)' }}>
g: U·sin(θ<sub>i</sub>) U<sub>c</sub> i <span className="text-[9px] text-text-3">()</span><br />
g: Σ L<sub>j</sub> L<sub>max</sub> <span className="text-[9px] text-text-3">( )</span><br />
g: T<sub>deploy,i</sub> T<sub>arrive,i</sub> <span className="text-[9px] text-text-3">( )</span><br />
g: d(p<sub>i</sub>, shore) d<sub>min</sub> <span className="text-[9px] text-text-3">( )</span><br />
g: h(p<sub>i</sub>) h<sub>min</sub> <span className="text-[9px] text-text-3">( )</span>
</div>
</div>
</div>
{/* ESI 가중치 */}
<div className="rounded-lg p-3" style={{ background: 'var(--bg0)', border: '1px solid rgba(234,179,8,.2)' }}>
<div className="rounded-lg p-3 bg-bg-0" style={{ border: '1px solid rgba(234,179,8,.2)' }}>
<div className="text-[10px] font-bold mb-2" style={{ color: 'var(--yellow)' }}>🏖 ESI w<sub>ESI</sub> </div>
<div className="grid grid-cols-5 gap-[5px] text-[9px]" style={{ color: 'var(--t2)' }}>
<div className="grid grid-cols-5 gap-[5px] text-[9px] text-text-2">
{[
{ grade: 'ESI 1~2', desc: '노출암반', w: 'w = 0.2', color: 'var(--green)', bg: 'rgba(34,197,94,.06)' },
{ grade: 'ESI 3~4', desc: '모래해변', w: 'w = 0.4', color: 'var(--cyan)', bg: 'rgba(6,182,212,.06)' },
@ -365,8 +363,8 @@ function OptimizationPanel() {
].map((esi, i) => (
<div key={i} className="p-1.5 rounded text-center" style={{ background: esi.bg, border: esi.bd ? `1px solid ${esi.bd}` : undefined }}>
<div style={{ fontWeight: 700, color: esi.color }}>{esi.grade}</div>
<div style={{ color: 'var(--t3)' }}>{esi.desc}</div>
<div style={{ fontWeight: 700 }}>{esi.w}</div>
<div className="text-text-3">{esi.desc}</div>
<div className="font-bold">{esi.w}</div>
</div>
))}
</div>
@ -374,14 +372,14 @@ function OptimizationPanel() {
</div>
{/* NSGA-II 알고리즘 */}
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="text-xs font-bold mb-3" style={{ color: 'var(--purple)' }}>🧬 NSGA-II (Non-dominated Sorting Genetic Algorithm II)</div>
<div className="rounded-md p-4 mb-3.5 bg-bg-3 border border-border">
<div className="text-xs font-bold mb-3 text-primary-purple">🧬 NSGA-II (Non-dominated Sorting Genetic Algorithm II)</div>
<div className="grid grid-cols-2 gap-3">
<div>
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)' }}>
<div className="text-[10px] leading-[1.8] mb-2 text-text-2">
WING의 <b style={{ color: 'var(--purple)' }}>NSGA-II</b>(Deb et al., 2002) . (Pareto Front) .
</div>
<div className="flex flex-col gap-1 text-[9px]" style={{ color: 'var(--t2)' }}>
<div className="flex flex-col gap-1 text-[9px] text-text-2">
{[
'염색체 구조 : [배치지점 좌표, 방향각θ, 길이L, 형태, 배치순서]',
'집단 크기 : 100~200개체 · 세대수 50~200',
@ -389,15 +387,15 @@ function OptimizationPanel() {
'변이 연산 : 다항식 변이(Polynomial Mutation) · ηm=20',
'선택 방식 : 비지배 정렬 + 혼잡도 거리(Crowding Distance)',
].map((item, i) => (
<div key={i} className="px-2 py-[5px] rounded" style={{ background: 'rgba(168,85,247,.05)', border: '1px solid rgba(168,85,247,.12)', color: 'var(--t2)' }}>
<div key={i} className="px-2 py-[5px] rounded text-text-2" style={{ background: 'rgba(168,85,247,.05)', border: '1px solid rgba(168,85,247,.12)' }}>
<b style={{ color: 'var(--purple)' }}>{item.split(' : ')[0]}</b> : {item.split(' : ')[1]}
</div>
))}
</div>
</div>
<div>
<div className="text-[10px] font-bold mb-[7px]" style={{ color: 'var(--t2)' }}>NSGA-II 5 </div>
<div className="flex flex-col gap-[5px] text-[9px]" style={{ color: 'var(--t2)' }}>
<div className="text-[10px] font-bold mb-[7px] text-text-2">NSGA-II 5 </div>
<div className="flex flex-col gap-[5px] text-[9px] text-text-2">
{[
{ step: '①', title: '초기 집단 생성', desc: '확산예측 결과 기반 랜덤 + 휴리스틱 배치안 혼합 초기화' },
{ step: '②', title: '적합도 평가', desc: '유출유 확산 시뮬레이터로 각 배치안의 차단면적·도달시간 계산' },
@ -407,7 +405,7 @@ function OptimizationPanel() {
].map((item, i) => (
<div key={i} className="flex gap-2 px-[9px] py-1.5 rounded-[5px]" style={{ background: i === 4 ? 'rgba(168,85,247,.05)' : 'rgba(168,85,247,.04)', border: i === 4 ? '1px solid rgba(168,85,247,.12)' : undefined }}>
<span className="min-w-[20px] font-extrabold" style={{ color: 'var(--purple)' }}>{item.step}</span>
<div className="leading-[1.6]" style={{ color: 'var(--t2)' }}><b>{item.title}</b> : {item.desc}</div>
<div className="leading-[1.6] text-text-2"><b>{item.title}</b> : {item.desc}</div>
</div>
))}
</div>
@ -416,14 +414,14 @@ function OptimizationPanel() {
</div>
{/* 보조 알고리즘 비교 */}
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="rounded-md p-3.5 bg-bg-3 border border-border">
<div className="text-[11px] font-bold mb-2.5">🔬 </div>
<div className="overflow-x-auto">
<table className="w-full" style={{ borderCollapse: 'collapse', fontSize: '10px' }}>
<table className="w-full text-[10px]" style={{ borderCollapse: 'collapse' }}>
<thead>
<tr style={{ background: 'rgba(255,255,255,.03)', borderBottom: '1px solid var(--bdL)' }}>
{['알고리즘', '유형', '장점', '단점', 'WING 활용'].map(h => (
<th key={h} className="py-[7px] px-2.5 text-left font-semibold" style={{ color: 'var(--t3)', textAlign: h === '알고리즘' ? 'left' : 'center' }}>{h}</th>
<th key={h} className="py-[7px] px-2.5 font-semibold text-text-3" style={{ textAlign: h === '알고리즘' ? 'left' : 'center' }}>{h}</th>
))}
</tr>
</thead>
@ -436,9 +434,9 @@ function OptimizationPanel() {
].map((row, i) => (
<tr key={i} style={{ borderBottom: '1px solid rgba(255,255,255,.04)', background: i % 2 === 1 ? 'rgba(255,255,255,.01)' : undefined }}>
<td className="py-[7px] px-2.5 font-bold" style={{ color: row.color }}>{row.name}</td>
<td className="py-[7px] px-2.5 text-center" style={{ color: 'var(--t2)' }}>{row.type}</td>
<td className="py-[7px] px-2.5 text-center" style={{ color: 'var(--t2)', whiteSpace: 'pre-line' }}>{row.pros}</td>
<td className="py-[7px] px-2.5 text-center" style={{ color: 'var(--t2)', whiteSpace: 'pre-line' }}>{row.cons}</td>
<td className="py-[7px] px-2.5 text-center text-text-2">{row.type}</td>
<td className="py-[7px] px-2.5 text-center text-text-2" style={{ whiteSpace: 'pre-line' }}>{row.pros}</td>
<td className="py-[7px] px-2.5 text-center text-text-2" style={{ whiteSpace: 'pre-line' }}>{row.cons}</td>
<td className="py-[7px] px-2.5 text-center" style={{ color: row.wingColor }}>{row.wing}</td>
</tr>
))}
@ -455,47 +453,47 @@ function FluidDynamicsPanel() {
return (
<>
{/* 유동 수치 모델 */}
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="rounded-md p-4 mb-3.5 bg-bg-3 border border-border">
<div className="text-xs font-bold mb-3">🌊 </div>
<div className="grid grid-cols-2 gap-3.5">
<div>
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--blue)' }}> </div>
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)' }}> . (catenary형태) .</div>
<div className="rounded-md p-2.5 text-[10px] leading-[2.1]" style={{ background: 'var(--bg0)', border: '1px solid rgba(59,130,246,.2)', fontFamily: 'var(--fM)' }}>
<div className="text-[11px] font-bold mb-2 text-primary-blue"> </div>
<div className="text-[10px] leading-[1.8] mb-2 text-text-2"> . (catenary형태) .</div>
<div className="rounded-md p-2.5 text-[10px] leading-[2.1] bg-bg-0 font-mono" style={{ border: '1px solid rgba(59,130,246,.2)' }}>
F<sub>D</sub> = ½ · ρ · C<sub>D</sub> · A · U<sub>n</sub>²<br />
T = F<sub>D</sub> · L / (2·sin(α))<br />
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>C<sub>D</sub>: (1.2), A: 수중 </span><br />
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>T: 연결부 , α: 체인각도</span>
<span className="text-[9px] text-text-3">C<sub>D</sub>: (1.2), A: 수중 </span><br />
<span className="text-[9px] text-text-3">T: 연결부 , α: 체인각도</span>
</div>
</div>
<div>
<div className="text-[11px] font-bold mb-2" style={{ color: 'var(--orange)' }}> (Splash-over) </div>
<div className="text-[10px] leading-[1.8] mb-2" style={{ color: 'var(--t2)' }}> Splash-over가 .</div>
<div className="rounded-md p-2.5 text-[10px] leading-[2.1]" style={{ background: 'var(--bg0)', border: '1px solid rgba(249,115,22,.2)', fontFamily: 'var(--fM)' }}>
<div className="text-[11px] font-bold mb-2 text-status-orange"> (Splash-over) </div>
<div className="text-[10px] leading-[1.8] mb-2 text-text-2"> Splash-over가 .</div>
<div className="rounded-md p-2.5 text-[10px] leading-[2.1] bg-bg-0 font-mono" style={{ border: '1px solid rgba(249,115,22,.2)' }}>
Fr = U<sub>n</sub> / (g·Δρ/ρ·h)<br />
Splash-over: Fr &gt; 0.5~0.6<br />
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>Fr: 수정 Froude수, h: 오일펜스 </span><br />
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>Δρ/ρ: 기름- (~0.15)</span>
<span className="text-[9px] text-text-3">Fr: 수정 Froude수, h: 오일펜스 </span><br />
<span className="text-[9px] text-text-3">Δρ/ρ: 기름- (~0.15)</span>
</div>
</div>
</div>
</div>
{/* Catenary 변형 모델 */}
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="rounded-md p-4 mb-3.5 bg-bg-3 border border-border">
<div className="text-xs font-bold mb-3">🔗 (Catenary) </div>
<div className="grid grid-cols-2 gap-3.5">
<div className="text-[10px] leading-[1.8]" style={{ color: 'var(--t2)' }}>
<div className="text-[10px] leading-[1.8] text-text-2">
(Catenary) . , <b> </b> L<sub>eff</sub> .
<div className="mt-2 rounded-[5px] p-[9px] leading-[2]" style={{ background: 'var(--bg0)', fontFamily: 'var(--fM)' }}>
<div className="mt-2 rounded-[5px] p-[9px] leading-[2] bg-bg-0 font-mono">
y(x) = a·cosh(x/a) a<br />
L<sub>arc</sub> = 2a·sinh(L<sub>span</sub>/(2a))<br />
L<sub>eff</sub> = L<sub>span</sub> · cos(φ<sub>max</sub>)<br />
<span className="text-[9px]" style={{ color: 'var(--t3)' }}>a: catenary , φ: 최대 </span>
<span className="text-[9px] text-text-3">a: catenary , φ: 최대 </span>
</div>
</div>
<div className="flex flex-col gap-1.5 text-[9px]" style={{ color: 'var(--t2)' }}>
<div className="text-[10px] font-bold mb-1" style={{ color: 'var(--t2)' }}> </div>
<div className="flex flex-col gap-1.5 text-[9px] text-text-2">
<div className="text-[10px] font-bold mb-1 text-text-2"> </div>
{[
{ cond: 'U < 0.3 knot', result: 'L_eff ≈ L (직선 유지)', bg: 'rgba(34,197,94,.05)', bd: 'rgba(34,197,94,.12)' },
{ cond: '0.3~0.7 knot', result: 'L_eff = 0.8~0.95 L (경미 변형)', bg: 'rgba(234,179,8,.05)', bd: 'rgba(234,179,8,.12)' },
@ -511,12 +509,12 @@ function FluidDynamicsPanel() {
</div>
{/* 유막 포집 모델 */}
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="rounded-md p-3.5 bg-bg-3 border border-border">
<div className="text-[11px] font-bold mb-2.5">🛢 </div>
<div className="grid grid-cols-2 gap-3 text-[10px]" style={{ color: 'var(--t2)' }}>
<div className="grid grid-cols-2 gap-3 text-[10px] text-text-2">
<div>
<div className="font-bold mb-1.5" style={{ color: 'var(--cyan)' }}> </div>
<div className="rounded-[5px] p-[9px] leading-[2]" style={{ background: 'var(--bg0)', fontFamily: 'var(--fM)' }}>
<div className="font-bold mb-1.5 text-primary-cyan"> </div>
<div className="rounded-[5px] p-[9px] leading-[2] bg-bg-0 font-mono">
dV<sub>oil</sub>/dt = Q<sub>in</sub> Q<sub>out</sub> Q<sub>loss</sub><br />
Q<sub>in</sub> = U<sub>oil</sub>·h<sub>oil</sub>·L<sub>eff</sub><br />
Q<sub>out</sub> = Q<sub>skim</sub> ( )<br />
@ -524,8 +522,8 @@ function FluidDynamicsPanel() {
</div>
</div>
<div>
<div className="font-bold mb-1.5" style={{ color: 'var(--orange)' }}> </div>
<div className="text-[10px] leading-[1.7]" style={{ color: 'var(--t2)' }}> 70~80% Skimmer . overflow . WING이 .</div>
<div className="font-bold mb-1.5 text-status-orange"> </div>
<div className="text-[10px] leading-[1.7] text-text-2"> 70~80% Skimmer . overflow . WING이 .</div>
</div>
</div>
</div>
@ -546,15 +544,15 @@ function FieldApplicationPanel() {
return (
<>
{/* 배치 5단계 절차 */}
<div className="rounded-[10px] p-4 mb-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="rounded-md p-4 mb-3.5 bg-bg-3 border border-border">
<div className="text-xs font-bold mb-3">🗺 WING 5</div>
<div className="flex flex-col gap-2">
{steps.map(step => (
<div key={step.num} className="flex gap-3 items-start p-3 rounded-lg" 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" style={{ background: step.numBg, border: `1px solid ${step.numBd}`, color: step.color }}>{step.num}</div>
<div className="text-[10px]" style={{ color: 'var(--t2)' }}>
<div className="text-[10px] text-text-2">
<div className="font-bold mb-1">{step.title}</div>
<div className="leading-[1.7]" style={{ color: 'var(--t2)' }}>{step.desc}</div>
<div className="leading-[1.7] text-text-2">{step.desc}</div>
</div>
</div>
))}
@ -562,9 +560,9 @@ function FieldApplicationPanel() {
</div>
{/* 해역별 적용 특성 */}
<div className="rounded-[10px] p-3.5" style={{ background: 'var(--bg3)', border: '1px solid var(--bd)' }}>
<div className="rounded-md p-3.5 bg-bg-3 border border-border">
<div className="text-[11px] font-bold mb-2.5">📍 </div>
<div className="grid grid-cols-3 gap-2.5 text-[9px]" style={{ color: 'var(--t2)' }}>
<div className="grid grid-cols-3 gap-2.5 text-[9px] text-text-2">
{[
{ icon: '🌊', title: '서해 (조차 대형)', color: 'var(--blue)', bg: 'rgba(59,130,246,.05)', bd: 'rgba(59,130,246,.12)', desc: '최대 조차 9m (인천), 조류 최대 3~5 knot. J형 배치 주력. 조석 전환 재배치 필수. 앵커링 수심 급변화 주의.' },
{ icon: '🌿', title: '남해 (다도해)', color: 'var(--green)', bg: 'rgba(34,197,94,.05)', bd: 'rgba(34,197,94,.12)', desc: '복잡한 해안선·섬. 조류 1~2 knot. V형·U형 복합 배치. 좁은 수로 통제 우선. ESI 고등급 갯벌 보호.' },
@ -572,7 +570,7 @@ function FieldApplicationPanel() {
].map((area, i) => (
<div key={i} className="p-2.5 rounded-[7px]" style={{ background: area.bg, border: `1px solid ${area.bd}` }}>
<div className="font-bold mb-[5px]" style={{ color: area.color }}>{area.icon} {area.title}</div>
<div className="leading-[1.7]" style={{ color: 'var(--t2)' }}>{area.desc}</div>
<div className="leading-[1.7] text-text-2">{area.desc}</div>
</div>
))}
</div>
@ -632,14 +630,14 @@ function ReferencesPanel() {
return (
<>
<div className="text-xs font-bold mb-1">📚 </div>
<div className="text-[10px] mb-3.5" style={{ color: 'var(--t3)' }}> 12 · 4 </div>
<div className="text-[10px] mb-3.5 text-text-3"> 12 · 4 </div>
{categories.map((cat, ci) => (
<div key={ci} className="mb-4">
<div className="text-[10px] font-bold mb-[7px] flex items-center gap-1.5" style={{ color: cat.color }}>
<span className="px-[7px] py-0.5 rounded" style={{ background: cat.bg, border: `1px solid ${cat.bd}` }}>{cat.title}</span>
</div>
<div className="flex flex-col gap-[5px] text-[9px]" style={{ color: 'var(--t2)' }}>
<div className="flex flex-col gap-[5px] text-[9px] text-text-2">
{cat.refs.map((ref, ri) => (
<div key={ri} className="p-[9px] px-3 rounded-[7px] grid gap-2" style={{
gridTemplateColumns: '24px 1fr',
@ -656,8 +654,8 @@ function ReferencesPanel() {
</div>
<div>
<div className="font-bold mb-0.5">{ref.title}</div>
<div className="leading-[1.6]" style={{ color: 'var(--t3)' }}>{ref.author}</div>
<div className="mt-0.5" style={{ color: 'var(--t2)' }}>{ref.desc}</div>
<div className="leading-[1.6] text-text-3">{ref.author}</div>
<div className="mt-0.5 text-text-2">{ref.desc}</div>
</div>
</div>
))}

파일 보기

@ -52,10 +52,10 @@ const OilBoomSection = ({
</div>
{expanded && (
<div className="px-4 pb-4" style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
<div className="px-4 pb-4 flex flex-col gap-3">
{/* Tab Buttons + Reset */}
<div style={{ display: 'flex', gap: '6px' }}>
<div className="flex gap-1.5">
{[
{ id: 'ai' as const, label: 'AI 자동 추천' },
{ id: 'manual' as const, label: '수동 배치' },
@ -65,17 +65,14 @@ const OilBoomSection = ({
key={tab.id}
onClick={() => setBoomPlacementTab(tab.id)}
style={{
flex: 1,
padding: '6px 8px',
fontSize: '10px',
fontWeight: 600,
borderRadius: 'var(--rS)',
borderRadius: 'var(--rS)',
border: boomPlacementTab === tab.id ? '1px solid var(--orange)' : '1px solid var(--bd)',
background: boomPlacementTab === tab.id ? 'rgba(245,158,11,0.1)' : 'var(--bg0)',
color: boomPlacementTab === tab.id ? 'var(--orange)' : 'var(--t3)',
cursor: 'pointer',
transition: '0.15s'
}}
className="flex-1 text-[10px] font-semibold cursor-pointer"
>
{tab.label}
</button>
@ -96,16 +93,14 @@ const OilBoomSection = ({
disabled={boomLines.length === 0 && !isDrawingBoom && !containmentResult}
style={{
padding: '6px 10px',
fontSize: '10px',
fontWeight: 600,
borderRadius: 'var(--rS)',
borderRadius: 'var(--rS)',
border: '1px solid var(--bd)',
background: 'var(--bg0)',
color: (boomLines.length === 0 && !isDrawingBoom && !containmentResult) ? 'var(--t3)' : 'var(--red)',
cursor: (boomLines.length === 0 && !isDrawingBoom && !containmentResult) ? 'not-allowed' : 'pointer',
transition: '0.15s',
flexShrink: 0,
}}
className="text-[10px] font-semibold shrink-0"
>
</button>
@ -121,14 +116,13 @@ const OilBoomSection = ({
<div key={idx} style={{
padding: '10px 8px',
background: 'var(--bg0)',
border: '1px solid var(--bd)',
borderRadius: 'var(--rS)',
textAlign: 'center'
}}>
<div style={{ fontSize: '18px', fontWeight: 700, color: metric.color, fontFamily: 'var(--fM)', marginBottom: '2px' }}>
}} className="border border-border">
<div style={{ color: metric.color }} className="text-lg font-bold font-mono mb-[2px]">
{metric.value}
</div>
<div style={{ fontSize: '8px', color: 'var(--t3)' }}>
<div className="text-[8px] text-text-3">
{metric.label}
</div>
</div>
@ -144,18 +138,18 @@ const OilBoomSection = ({
border: '1px solid rgba(245,158,11,0.3)',
borderRadius: 'var(--rM)'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '8px' }}>
<div className="flex items-center gap-1 mb-2">
<span style={{ width: '6px', height: '6px', borderRadius: '50%', background: oilTrajectory.length > 0 ? 'var(--green)' : 'var(--t3)' }} />
<span style={{ fontSize: '10px', fontWeight: 700, color: oilTrajectory.length > 0 ? 'var(--green)' : 'var(--t3)' }}>
<span style={{ color: oilTrajectory.length > 0 ? 'var(--green)' : 'var(--t3)' }} className="text-[10px] font-bold">
{oilTrajectory.length > 0 ? '확산 데이터 준비 완료' : '확산 예측을 먼저 실행하세요'}
</span>
</div>
<h4 style={{ fontSize: '13px', fontWeight: 700, marginBottom: '8px' }}>
<h4 className="text-[13px] font-bold mb-2">
</h4>
<p style={{ fontSize: '9px', color: 'var(--t3)', lineHeight: '1.5', marginBottom: '10px' }}>
<p style={{ lineHeight: '1.5' }} className="text-[9px] text-text-3 mb-2.5">
{oilTrajectory.length > 0
? '확산 궤적을 분석하여 해류 직교 방향 1차 방어선, U형 포위 2차 방어선, 연안 보호 3차 방어선을 자동 생성합니다.'
: '상단에서 확산 예측을 실행한 뒤 AI 배치를 적용할 수 있습니다.'
@ -173,17 +167,14 @@ const OilBoomSection = ({
}}
disabled={oilTrajectory.length === 0}
style={{
width: '100%',
padding: '10px',
fontSize: '11px',
fontWeight: 700,
background: oilTrajectory.length > 0 ? 'rgba(245,158,11,0.15)' : 'var(--bg0)',
background: oilTrajectory.length > 0 ? 'rgba(245,158,11,0.15)' : 'var(--bg0)',
border: oilTrajectory.length > 0 ? '2px solid var(--orange)' : '1px solid var(--bd)',
borderRadius: 'var(--rS)',
color: oilTrajectory.length > 0 ? 'var(--orange)' : 'var(--t3)',
cursor: oilTrajectory.length > 0 ? 'pointer' : 'not-allowed',
transition: '0.15s'
}}
className="w-full p-[10px] text-[11px] font-bold"
>
🛡
</button>
@ -191,10 +182,10 @@ const OilBoomSection = ({
{/* 알고리즘 설정 */}
<div>
<h4 style={{ fontSize: '11px', fontWeight: 700, color: 'var(--cyan)', marginBottom: '8px', letterSpacing: '0.5px' }}>
<h4 className="text-[11px] font-bold text-primary-cyan mb-2" style={{ letterSpacing: '0.5px' }}>
📊
</h4>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div className="flex flex-col gap-2">
{[
{ label: '해류 직교 보정', key: 'currentOrthogonalCorrection' as const, unit: '°', value: algorithmSettings.currentOrthogonalCorrection },
{ label: '안전 마진 (도달시간)', key: 'safetyMarginMinutes' as const, unit: '분', value: algorithmSettings.safetyMarginMinutes },
@ -202,11 +193,11 @@ const OilBoomSection = ({
{ label: '파고 보정 계수', key: 'waveHeightCorrectionFactor' as const, unit: 'x', value: algorithmSettings.waveHeightCorrectionFactor },
].map((setting) => (
<div key={setting.key} style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '6px 8px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)'
}}>
<span style={{ fontSize: '9px', color: 'var(--t3)' }}> {setting.label}</span>
<div style={{ display: 'flex', alignItems: 'center', gap: '2px' }}>
background: 'var(--bg0)',
borderRadius: 'var(--rS)'
}} className="flex items-center justify-between p-[6px_8px] border border-border">
<span className="text-[9px] text-text-3"> {setting.label}</span>
<div className="flex items-center gap-[2px]">
<input
type="number"
value={setting.value}
@ -217,7 +208,7 @@ const OilBoomSection = ({
className="boom-setting-input"
step={setting.key === 'waveHeightCorrectionFactor' ? 0.1 : 1}
/>
<span style={{ fontSize: '9px', color: 'var(--orange)' }}>{setting.unit}</span>
<span className="text-[9px] text-status-orange">{setting.unit}</span>
</div>
</div>
))}
@ -230,15 +221,15 @@ const OilBoomSection = ({
{boomPlacementTab === 'manual' && (
<>
{/* 드로잉 컨트롤 */}
<div style={{ display: 'flex', gap: '6px' }}>
<div className="flex gap-1.5">
{!isDrawingBoom ? (
<button
onClick={() => { onDrawingBoomChange(true); onDrawingPointsChange([]) }}
style={{
flex: 1, padding: '10px', fontSize: '11px', fontWeight: 700,
background: 'rgba(245,158,11,0.15)', border: '2px solid var(--orange)',
borderRadius: 'var(--rS)', color: 'var(--orange)', cursor: 'pointer', transition: '0.15s'
borderRadius: 'var(--rS)', color: 'var(--orange)', transition: '0.15s'
}}
className="flex-1 p-[10px] text-[11px] font-bold cursor-pointer"
>
🛡
</button>
@ -265,23 +256,24 @@ const OilBoomSection = ({
}}
disabled={drawingPoints.length < 2}
style={{
flex: 1, padding: '10px', fontSize: '11px', fontWeight: 700,
background: drawingPoints.length >= 2 ? 'rgba(34,197,94,0.15)' : 'var(--bg0)',
border: drawingPoints.length >= 2 ? '2px solid var(--green)' : '1px solid var(--bd)',
borderRadius: 'var(--rS)',
color: drawingPoints.length >= 2 ? 'var(--green)' : 'var(--t3)',
cursor: drawingPoints.length >= 2 ? 'pointer' : 'not-allowed', transition: '0.15s'
}}
className="flex-1 p-[10px] text-[11px] font-bold"
>
({drawingPoints.length})
</button>
<button
onClick={() => { onDrawingBoomChange(false); onDrawingPointsChange([]) }}
style={{
padding: '10px 14px', fontSize: '11px', fontWeight: 700,
padding: '10px 14px',
background: 'rgba(239,68,68,0.1)', border: '1px solid var(--red)',
borderRadius: 'var(--rS)', color: 'var(--red)', cursor: 'pointer', transition: '0.15s'
borderRadius: 'var(--rS)', color: 'var(--red)', transition: '0.15s'
}}
className="text-[11px] font-bold cursor-pointer"
>
</button>
@ -294,29 +286,28 @@ const OilBoomSection = ({
<div style={{
padding: '8px 10px', background: 'rgba(245,158,11,0.05)',
border: '1px solid rgba(245,158,11,0.3)', borderRadius: 'var(--rS)',
display: 'flex', gap: '12px', fontSize: '10px', color: 'var(--t2)'
}}>
<span>: <strong style={{ color: 'var(--orange)', fontFamily: 'var(--fM)' }}>{drawingPoints.length}</strong></span>
<span>: <strong style={{ color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>{computePolylineLength(drawingPoints).toFixed(0)}m</strong></span>
}} className="flex gap-3 text-[10px] text-text-2">
<span>: <strong className="text-status-orange font-mono">{drawingPoints.length}</strong></span>
<span>: <strong className="text-primary-cyan font-mono">{computePolylineLength(drawingPoints).toFixed(0)}m</strong></span>
{drawingPoints.length >= 2 && (
<span>: <strong style={{ fontFamily: 'var(--fM)' }}>{computeBearing(drawingPoints[0], drawingPoints[drawingPoints.length - 1]).toFixed(0)}°</strong></span>
<span>: <strong className="font-mono">{computeBearing(drawingPoints[0], drawingPoints[drawingPoints.length - 1]).toFixed(0)}°</strong></span>
)}
</div>
)}
{/* 배치된 라인 목록 */}
{boomLines.length === 0 ? (
<p style={{ fontSize: '10px', color: 'var(--t3)', textAlign: 'center', padding: '16px 0' }}>
<p className="text-[10px] text-text-3 text-center py-4">
.
</p>
) : (
boomLines.map((line, idx) => (
<div key={line.id} style={{
padding: '10px', background: 'var(--bg0)', border: '1px solid var(--bd)',
padding: '10px', background: 'var(--bg0)',
borderLeft: `3px solid ${line.priority === 'CRITICAL' ? 'var(--red)' : line.priority === 'HIGH' ? 'var(--orange)' : 'var(--yellow)'}`,
borderRadius: 'var(--rS)'
}}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '6px' }}>
}} className="border border-border">
<div className="flex items-center justify-between mb-1.5">
<input
type="text"
value={line.name}
@ -325,32 +316,26 @@ const OilBoomSection = ({
updated[idx] = { ...updated[idx], name: e.target.value }
onBoomLinesChange(updated)
}}
style={{
flex: 1, fontSize: '11px', fontWeight: 700,
background: 'transparent', border: 'none', outline: 'none'
}}
className="flex-1 text-[11px] font-bold bg-transparent border-none outline-none"
/>
<button
onClick={() => onBoomLinesChange(boomLines.filter(l => l.id !== line.id))}
style={{
fontSize: '10px', color: 'var(--red)', background: 'none', border: 'none',
cursor: 'pointer', padding: '2px 6px'
}}
className="text-[10px] text-status-red bg-transparent border-none cursor-pointer px-1.5 py-[2px]"
>
</button>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '6px', fontSize: '9px' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '6px' }} className="text-[9px]">
<div>
<span style={{ color: 'var(--t3)' }}></span>
<div style={{ fontWeight: 700, fontFamily: 'var(--fM)' }}>{line.length.toFixed(0)}m</div>
<span className="text-text-3"></span>
<div className="font-bold font-mono">{line.length.toFixed(0)}m</div>
</div>
<div>
<span style={{ color: 'var(--t3)' }}></span>
<div style={{ fontWeight: 700, fontFamily: 'var(--fM)' }}>{line.angle.toFixed(0)}°</div>
<span className="text-text-3"></span>
<div className="font-bold font-mono">{line.angle.toFixed(0)}°</div>
</div>
<div>
<span style={{ color: 'var(--t3)' }}></span>
<span className="text-text-3"></span>
<select
value={line.priority}
onChange={(e) => {
@ -359,10 +344,11 @@ const OilBoomSection = ({
onBoomLinesChange(updated)
}}
style={{
width: '100%', fontSize: '10px', fontWeight: 600,
background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: '3px',
padding: '2px', outline: 'none'
background: 'var(--bg0)',
borderRadius: '3px',
padding: '2px',
}}
className="w-full text-[10px] font-semibold border border-border outline-none"
>
<option value="CRITICAL"></option>
<option value="HIGH"></option>
@ -380,22 +366,20 @@ const OilBoomSection = ({
{boomPlacementTab === 'simulation' && (
<>
{/* 전제조건 체크 */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
<div className="flex flex-col gap-1.5">
<div style={{
display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 10px',
background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)',
fontSize: '10px'
}}>
background: 'var(--bg0)',
borderRadius: 'var(--rS)',
}} className="flex items-center gap-1.5 p-[6px_10px] border border-border text-[10px]">
<span style={{ width: '8px', height: '8px', borderRadius: '50%', background: oilTrajectory.length > 0 ? 'var(--green)' : 'var(--red)' }} />
<span style={{ color: oilTrajectory.length > 0 ? 'var(--green)' : 'var(--t3)' }}>
{oilTrajectory.length > 0 ? `(${oilTrajectory.length}개 입자)` : '없음'}
</span>
</div>
<div style={{
display: 'flex', alignItems: 'center', gap: '6px', padding: '6px 10px',
background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)',
fontSize: '10px'
}}>
background: 'var(--bg0)',
borderRadius: 'var(--rS)',
}} className="flex items-center gap-1.5 p-[6px_10px] border border-border text-[10px]">
<span style={{ width: '8px', height: '8px', borderRadius: '50%', background: boomLines.length > 0 ? 'var(--green)' : 'var(--red)' }} />
<span style={{ color: boomLines.length > 0 ? 'var(--green)' : 'var(--t3)' }}>
{boomLines.length > 0 ? `(${boomLines.length}개 배치)` : '없음'}
@ -411,7 +395,6 @@ const OilBoomSection = ({
}}
disabled={oilTrajectory.length === 0 || boomLines.length === 0}
style={{
width: '100%', padding: '10px', fontSize: '11px', fontWeight: 700,
background: (oilTrajectory.length > 0 && boomLines.length > 0) ? 'rgba(6,182,212,0.15)' : 'var(--bg0)',
border: (oilTrajectory.length > 0 && boomLines.length > 0) ? '2px solid var(--cyan)' : '1px solid var(--bd)',
borderRadius: 'var(--rS)',
@ -419,39 +402,40 @@ const OilBoomSection = ({
cursor: (oilTrajectory.length > 0 && boomLines.length > 0) ? 'pointer' : 'not-allowed',
transition: '0.15s'
}}
className="w-full p-[10px] text-[11px] font-bold"
>
🔬
</button>
{/* 시뮬레이션 결과 */}
{containmentResult && containmentResult.totalParticles > 0 && (
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
<div className="flex flex-col gap-2.5">
{/* 전체 효율 */}
<div style={{
padding: '16px', background: 'rgba(6,182,212,0.05)',
border: '1px solid rgba(6,182,212,0.3)', borderRadius: 'var(--rM)', textAlign: 'center'
}}>
<div style={{ fontSize: '28px', fontWeight: 700, color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>
<div className="text-[28px] font-bold text-primary-cyan font-mono">
{containmentResult.overallEfficiency}%
</div>
<div style={{ fontSize: '10px', color: 'var(--t3)', marginTop: '2px' }}>
<div className="text-[10px] text-text-3 mt-[2px]">
</div>
</div>
{/* 차단/통과 카운트 */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }}>
<div style={{ padding: '10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', textAlign: 'center' }}>
<div style={{ fontSize: '16px', fontWeight: 700, color: 'var(--green)', fontFamily: 'var(--fM)' }}>
<div style={{ padding: '10px', background: 'var(--bg0)', borderRadius: 'var(--rS)', textAlign: 'center' }} className="border border-border">
<div className="text-base font-bold text-status-green font-mono">
{containmentResult.blockedParticles}
</div>
<div style={{ fontSize: '8px', color: 'var(--t3)' }}> </div>
<div className="text-[8px] text-text-3"> </div>
</div>
<div style={{ padding: '10px', background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)', textAlign: 'center' }}>
<div style={{ fontSize: '16px', fontWeight: 700, color: 'var(--red)', fontFamily: 'var(--fM)' }}>
<div style={{ padding: '10px', background: 'var(--bg0)', borderRadius: 'var(--rS)', textAlign: 'center' }} className="border border-border">
<div className="text-base font-bold text-status-red font-mono">
{containmentResult.passedParticles}
</div>
<div style={{ fontSize: '8px', color: 'var(--t3)' }}> </div>
<div className="text-[8px] text-text-3"> </div>
</div>
</div>
@ -465,18 +449,16 @@ const OilBoomSection = ({
{/* 라인별 분석 */}
<div>
<h4 style={{ fontSize: '10px', fontWeight: 700, color: 'var(--t3)', marginBottom: '6px' }}>
<h4 className="text-[10px] font-bold text-text-3 mb-1.5">
</h4>
{containmentResult.perLineResults.map((r) => (
<div key={r.boomLineId} style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '6px 8px', marginBottom: '4px',
background: 'var(--bg0)', border: '1px solid var(--bd)', borderRadius: 'var(--rS)',
fontSize: '9px'
}}>
<span style={{ color: 'var(--t2)', flex: 1 }}>{r.boomLineName}</span>
<span style={{ fontWeight: 700, color: r.efficiency >= 50 ? 'var(--green)' : 'var(--orange)', fontFamily: 'var(--fM)', marginLeft: '8px' }}>
background: 'var(--bg0)',
borderRadius: 'var(--rS)',
}} className="flex items-center justify-between p-[6px_8px] mb-1 border border-border text-[9px]">
<span className="text-text-2 flex-1">{r.boomLineName}</span>
<span style={{ color: r.efficiency >= 50 ? 'var(--green)' : 'var(--orange)', marginLeft: '8px' }} className="font-bold font-mono">
{r.blocked} / {r.efficiency}%
</span>
</div>
@ -495,38 +477,38 @@ const OilBoomSection = ({
const priorityLabel = line.priority === 'CRITICAL' ? '긴급' : line.priority === 'HIGH' ? '중요' : '보통'
return (
<div key={line.id} style={{
padding: '10px', background: 'var(--bg0)', border: '1px solid var(--bd)',
padding: '10px', background: 'var(--bg0)',
borderLeft: `3px solid ${priorityColor}`, borderRadius: 'var(--rS)'
}}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '8px' }}>
<span style={{ fontSize: '11px', fontWeight: 700 }}>
}} className="border border-border">
<div className="flex items-center justify-between mb-2">
<span className="text-[11px] font-bold">
🛡 {idx + 1} ({line.type})
</span>
<span style={{
padding: '2px 6px', fontSize: '8px', fontWeight: 700,
padding: '2px 6px',
background: `${priorityColor}20`, border: `1px solid ${priorityColor}`,
borderRadius: '3px', color: priorityColor
}}>
}} className="text-[8px] font-bold">
{priorityLabel}
</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px', marginBottom: '6px' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '6px' }} className="mb-1.5">
<div>
<span style={{ fontSize: '8px', color: 'var(--t3)' }}></span>
<div style={{ fontSize: '14px', fontWeight: 700, fontFamily: 'var(--fM)' }}>
<span className="text-[8px] text-text-3"></span>
<div className="text-sm font-bold font-mono">
{line.length.toFixed(0)}m
</div>
</div>
<div>
<span style={{ fontSize: '8px', color: 'var(--t3)' }}></span>
<div style={{ fontSize: '14px', fontWeight: 700, fontFamily: 'var(--fM)' }}>
<span className="text-[8px] text-text-3"></span>
<div className="text-sm font-bold font-mono">
{line.angle.toFixed(0)}°
</div>
</div>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
<div className="flex items-center gap-1">
<span style={{ width: '6px', height: '6px', borderRadius: '50%', background: line.efficiency >= 80 ? 'var(--green)' : 'var(--orange)' }} />
<span style={{ fontSize: '9px', fontWeight: 600, color: line.efficiency >= 80 ? 'var(--green)' : 'var(--orange)' }}>
<span style={{ color: line.efficiency >= 80 ? 'var(--green)' : 'var(--orange)' }} className="text-[9px] font-semibold">
{line.efficiency}%
</span>
</div>

파일 보기

@ -127,10 +127,10 @@ ${styles}
/* ═══ 공통 스타일 유틸 ═══ */
const card = "rounded-[10px] p-[14px] mb-4"
const cardBg = { background: 'var(--bg3)', border: '1px solid var(--bd)' }
const cardBg = "bg-bg-3 border border-border"
const labelStyle = (color: string) => ({ fontSize: '14px', fontWeight: 700, color, marginBottom: '10px' } as const)
const bodyText = { fontSize: '12px', color: 'var(--t2)', lineHeight: '1.8' } as const
const codeBox = { background: 'var(--bg0)', borderRadius: '6px', padding: '10px', fontFamily: 'var(--fM)', fontSize: '12px', lineHeight: '2' } as const
const bodyText = "text-xs text-text-2 leading-[1.8]"
const codeBox = "bg-bg-0 rounded-sm p-[10px] font-mono text-xs leading-loose"
const tag = (color: string) => ({
padding: '3px 8px', borderRadius: '4px', fontSize: '11px', color,
background: `${color}14`, border: `1px solid ${color}30`
@ -177,7 +177,7 @@ function SystemOverviewPanel() {
{/* 작동 원리 3-Step */}
<div className="grid grid-cols-3 gap-3 mb-4">
{/* Step 1 */}
<div className={card} style={{ ...cardBg, borderTop: '3px solid var(--orange)', position: 'relative' }}>
<div className={`${card} ${cardBg}`} style={{ borderTop: '3px solid var(--orange)', position: 'relative' }}>
<div className="absolute top-2.5 right-3 w-7 h-7 rounded-full flex items-center justify-center text-xs font-extrabold"
style={{ background: 'rgba(249,115,22,.15)', color: 'var(--orange)', fontFamily: 'var(--fM)' }}>1</div>
<div style={labelStyle('var(--orange)')}>🛢 </div>
@ -191,7 +191,7 @@ function SystemOverviewPanel() {
</div>
</div>
{/* Step 2 */}
<div className={card} style={{ ...cardBg, borderTop: '3px solid var(--cyan)', position: 'relative' }}>
<div className={`${card} ${cardBg}`} style={{ borderTop: '3px solid var(--cyan)', position: 'relative' }}>
<div className="absolute top-2.5 right-3 w-7 h-7 rounded-full flex items-center justify-center text-xs font-extrabold"
style={{ background: 'rgba(6,182,212,.15)', color: 'var(--cyan)', fontFamily: 'var(--fM)' }}>2</div>
<div style={labelStyle('var(--cyan)')}>🌊 </div>
@ -203,7 +203,7 @@ function SystemOverviewPanel() {
</div>
</div>
{/* Step 3 */}
<div className={card} style={{ ...cardBg, borderTop: '3px solid var(--green)', position: 'relative' }}>
<div className={`${card} ${cardBg}`} style={{ borderTop: '3px solid var(--green)', position: 'relative' }}>
<div className="absolute top-2.5 right-3 w-7 h-7 rounded-full flex items-center justify-center text-xs font-extrabold"
style={{ background: 'rgba(34,197,94,.15)', color: 'var(--green)', fontFamily: 'var(--fM)' }}>3</div>
<div style={labelStyle('var(--green)')}>🧮 </div>
@ -217,7 +217,7 @@ function SystemOverviewPanel() {
</div>
{/* 모델 비교 카드 */}
<div className={card} style={cardBg}>
<div className={`${card} ${cardBg}`}>
<div className="flex items-center justify-between mb-3.5">
<div style={labelStyle('var(--t1)')}>🤖 WING </div>
<span className="text-[10px]" style={{ color: 'var(--t3)' }}>3 · </span>
@ -327,7 +327,7 @@ function KospsPanel() {
</div>
{/* 논문·특허 근거 */}
<div className={card} style={cardBg}>
<div className={`${card} ${cardBg}`}>
<div style={labelStyle('var(--t1)')}>📚 · </div>
{/* 등록특허 라벨 */}
@ -364,13 +364,13 @@ function KospsPanel() {
</div>
{/* CHARRY 모델 */}
<div className={card} style={{ ...cardBg, background: 'var(--bg0)', border: '1px solid rgba(6,182,212,.2)' }}>
<div className={card} style={{ background: 'var(--bg0)', border: '1px solid rgba(6,182,212,.2)' }}>
<div style={labelStyle('var(--cyan)')}>🌀 CHARRY ( )</div>
<div className="text-[11px] mb-2.5" style={{ color: 'var(--t2)', lineHeight: '1.8' }}>
<b>CHARRY</b> (Modulated Tide) , .
</div>
<div className="grid grid-cols-2 gap-2.5">
<div style={codeBox}>
<div className={codeBox}>
<span style={{ color: 'var(--t3)', fontSize: '10px' }}>/* 변조조석 수식 */</span><br />
ζ(t) = A(t) cos[σt θ(t)]<br />
A²(t) = Σ Yᵢ² + 2Σ YᵢYⱼ cos[(σσ)t(φφ)]
@ -390,7 +390,7 @@ function KospsPanel() {
{/* 입력자료 체계 */}
<div className="grid grid-cols-2 gap-3 mb-4">
<div className={card} style={{ ...cardBg, borderLeft: '3px solid var(--cyan)', margin: 0 }}>
<div className={`${card} ${cardBg}`} style={{ borderLeft: '3px solid var(--cyan)', margin: 0 }}>
<div style={labelStyle('var(--cyan)')}>📊 </div>
<div className="flex flex-col gap-1.5 text-[11px]" style={{ color: 'var(--t2)' }}>
{[
@ -407,7 +407,7 @@ function KospsPanel() {
))}
</div>
</div>
<div className={card} style={{ ...cardBg, borderLeft: '3px solid var(--green)', margin: 0 }}>
<div className={`${card} ${cardBg}`} style={{ borderLeft: '3px solid var(--green)', margin: 0 }}>
<div style={labelStyle('var(--green)')}>🗂 </div>
<div className="flex flex-col gap-1.5 text-[11px]" style={{ color: 'var(--t2)' }}>
<div className="px-2.5 py-1.5 rounded-md" style={{ background: 'rgba(34,197,94,.04)', border: '1px solid rgba(34,197,94,.12)' }}>
@ -423,15 +423,15 @@ function KospsPanel() {
</div>
{/* 취송류 경험식 */}
<div className={card} style={{ ...cardBg, borderLeft: '3px solid var(--orange)' }}>
<div className={`${card} ${cardBg}`} style={{ borderLeft: '3px solid var(--orange)' }}>
<div style={labelStyle('var(--orange)')}>📐 (Wind-Driven Current) </div>
<div className="grid grid-cols-2 gap-3">
<div>
<div style={codeBox} className="mb-2">
<div className={`${codeBox} mb-2`}>
<span style={{ color: 'var(--t3)', fontSize: '10px' }}>/* 취송류 유속 (이·강, 2000) */</span><br />
V_WDC = <span style={{ color: 'var(--orange)' }}>0.029</span> × V_wind
</div>
<div style={codeBox}>
<div className={codeBox}>
<span style={{ color: 'var(--t3)', fontSize: '10px' }}>/* 취송류 유향 */</span><br />
θ_WDC = θ_wind + <span style={{ color: 'var(--cyan)' }}>18.6°</span>
</div>
@ -450,7 +450,7 @@ function KospsPanel() {
</div>
</div>
{/* 상시 운용 통신 체계 */}
<div className={card} style={cardBg}>
<div className={`${card} ${cardBg}`}>
<div style={labelStyle('var(--t1)')}>🔄 </div>
<div className="flex items-center justify-center gap-0 py-3">
{[
@ -655,7 +655,7 @@ function KospsPanel() {
</div>
{/* 국내 학술논문 */}
<div className={card} style={cardBg}>
<div className={`${card} ${cardBg}`}>
<div className="flex items-center gap-1.5 mb-2 cursor-pointer select-none" onClick={() => setPapersOpen(!papersOpen)}>
<span style={{ ...tag('var(--cyan)'), fontWeight: 700, fontSize: '12px' }}>📄 </span>
<span className="text-[11px] ml-1" style={{ color: 'var(--t3)', transition: 'transform .2s', display: 'inline-block', transform: papersOpen ? 'rotate(180deg)' : 'rotate(0)' }}></span>
@ -691,7 +691,7 @@ function KospsPanel() {
</div>
{/* 국외 핵심 논문 */}
<div className={card} style={cardBg}>
<div className={`${card} ${cardBg}`}>
<div className="flex items-center gap-1.5 mb-2 cursor-pointer select-none" onClick={() => setIntlPapersOpen(!intlPapersOpen)}>
<span style={{ ...tag('var(--green)'), fontWeight: 700, fontSize: '12px' }}>🌐 </span>
<span className="text-[11px] ml-1" style={{ color: 'var(--t3)', transition: 'transform .2s', display: 'inline-block', transform: intlPapersOpen ? 'rotate(180deg)' : 'rotate(0)' }}></span>
@ -746,7 +746,7 @@ function PoseidonPanel() {
</div>
{/* 등록특허 */}
<div className={card} style={cardBg}>
<div className={`${card} ${cardBg}`}>
<div className="flex items-center gap-1.5 mb-2">
<span style={{ ...tag('var(--yellow)'), fontWeight: 700, fontSize: '12px' }}>📜 </span>
</div>
@ -771,9 +771,9 @@ function PoseidonPanel() {
</div>
<div className="grid grid-cols-2 gap-3 mb-4">
<div className={card} style={{ ...cardBg, borderLeft: '3px solid var(--cyan)', margin: 0 }}>
<div className={`${card} ${cardBg}`} style={{ borderLeft: '3px solid var(--cyan)', margin: 0 }}>
<div style={labelStyle('var(--cyan)')}>🌊 MOHID ( )</div>
<div style={bodyText} className="mb-2.5"> IST/MARETEC에서 1985 3D . 60 ····· .</div>
<div className={`${bodyText} mb-2.5`}> IST/MARETEC에서 1985 3D . 60 ····· .</div>
<div className="flex flex-col gap-1 text-[10px]" style={{ color: 'var(--t2)' }}>
{[
{ label: '격자 구성', desc: '동아시아 9km + 한반도 3km (Nesting)' },
@ -785,9 +785,9 @@ function PoseidonPanel() {
)}
</div>
</div>
<div className={card} style={{ ...cardBg, borderLeft: '3px solid var(--purple)', margin: 0 }}>
<div className={`${card} ${cardBg}`} style={{ borderLeft: '3px solid var(--purple)', margin: 0 }}>
<div style={labelStyle('var(--purple)')}> </div>
<div style={bodyText} className="mb-2.5"> 1 2 .</div>
<div className={`${bodyText} mb-2.5`}> 1 2 .</div>
<div className="flex flex-col gap-1 text-[10px]" style={{ color: 'var(--t2)' }}>
{[
{ label: '최적화 알고리즘', desc: 'GA · DE · HS · PSO 중 선택' },
@ -802,12 +802,12 @@ function PoseidonPanel() {
</div>
{/* 핵심 수식 */}
<div className={card} style={cardBg}>
<div className={`${card} ${cardBg}`}>
<div style={labelStyle('var(--blue)')}>📐 POSEIDON </div>
<div className="grid grid-cols-2 gap-3">
<div>
<div className="text-[11px] font-semibold mb-1.5" style={{ color: 'var(--t3)' }}>1 ()</div>
<div style={codeBox}>
<div className={codeBox}>
Model_x = Δt × current_u + Δt × c × wind_u<br />
Model_y = Δt × current_v + Δt × c × wind_v
</div>
@ -815,7 +815,7 @@ function PoseidonPanel() {
</div>
<div>
<div className="text-[11px] font-semibold mb-1.5" style={{ color: 'var(--t3)' }}>2 ( )</div>
<div style={codeBox}>
<div className={codeBox}>
Revised_x = a1·current_u + a2·current_v<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ a3·wind_u + a4·wind_v<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;+ a5·Model_x + a6·Model_y + a7
@ -825,7 +825,7 @@ function PoseidonPanel() {
</div>
</div>
{/* 상시 운용 통신 체계 */}
<div className={card} style={cardBg}>
<div className={`${card} ${cardBg}`}>
<div style={labelStyle('var(--t1)')}>🔄 POSEIDON_V2 </div>
{/* 외부 입력 자료 */}
@ -987,7 +987,7 @@ function OpenDriftPanel() {
{ text: 'NOAA Oil Library 탑재' }, { text: 'Geosci. Model Dev. 게재' },
] },
].map(s => (
<div key={s.title} className={card} style={{ ...cardBg, borderTop: `3px solid ${s.color}`, margin: 0 }}>
<div key={s.title} className={`${card} ${cardBg}`} style={{ borderTop: `3px solid ${s.color}`, margin: 0 }}>
<div style={labelStyle(s.color)}>{s.title}</div>
<div className="flex flex-col gap-1 text-[10px]" style={{ color: 'var(--t2)' }}>
{s.items.map((item, idx) => (
@ -1001,7 +1001,7 @@ function OpenDriftPanel() {
</div>
{/* OpenOil 풍화 프로세스 */}
<div className={card} style={cardBg}>
<div className={`${card} ${cardBg}`}>
<div style={labelStyle('var(--t1)')}>🔁 OpenOil (IKI )</div>
<div className="grid grid-cols-4 gap-2">
{[
@ -1019,7 +1019,7 @@ function OpenDriftPanel() {
</div>
</div>
{/* 상시 운용 통신 체계 */}
<div className={card} style={cardBg}>
<div className={`${card} ${cardBg}`}>
<div style={labelStyle('var(--t1)')}>🔄 </div>
<div className="flex items-center justify-center gap-0 py-3">
{[
@ -1188,9 +1188,9 @@ function LagrangianPanel() {
</div>
<div className="grid grid-cols-2 gap-3 mb-4">
<div className={card} style={{ ...cardBg, borderLeft: '3px solid var(--cyan)', margin: 0 }}>
<div className={`${card} ${cardBg}`} style={{ borderLeft: '3px solid var(--cyan)', margin: 0 }}>
<div style={labelStyle('var(--cyan)')}>📐 </div>
<div style={codeBox} className="mb-2.5">
<div className={`${codeBox} mb-2.5`}>
dx/dt = U_c + α·U_w + U_stokes + U&apos;<br />
dy/dt = V_c + α·V_w + V_stokes + V&apos;
</div>
@ -1201,9 +1201,9 @@ function LagrangianPanel() {
<div className="flex gap-2"><span style={{ color: 'var(--purple)', fontWeight: 700, width: '60px' }}>U&apos;</span><span style={{ color: 'var(--t2)' }}> ()</span></div>
</div>
</div>
<div className={card} style={{ ...cardBg, borderLeft: '3px solid var(--blue)', margin: 0 }}>
<div className={`${card} ${cardBg}`} style={{ borderLeft: '3px solid var(--blue)', margin: 0 }}>
<div style={labelStyle('var(--blue)')}>🌀 ()</div>
<div style={codeBox} className="mb-2.5">
<div className={`${codeBox} mb-2.5`}>
U&apos; = R · (2K_h / Δt)<br />
V&apos; = R · (2K_h / Δt)
</div>
@ -1216,15 +1216,15 @@ function LagrangianPanel() {
</div>
{/* Fay(1971) 중력-점성 체제 */}
<div className={card} style={{ ...cardBg, borderLeft: '3px solid var(--orange)' }}>
<div className={`${card} ${cardBg}`} style={{ borderLeft: '3px solid var(--orange)' }}>
<div style={labelStyle('var(--orange)')}>🛢 Fay(1971) - </div>
<div className="grid grid-cols-2 gap-3">
<div>
<div style={codeBox} className="mb-2">
<div className={`${codeBox} mb-2`}>
<span style={{ color: 'var(--t3)', fontSize: '10px' }}>/* 중력-관성 체제 (초기) */</span><br />
R(t) = <span style={{ color: 'var(--orange)' }}>K</span> · (<span style={{ color: 'var(--cyan)' }}>ΔρgV²</span> / <span style={{ color: 'var(--blue)' }}>ρw</span>)<sup>¼</sup> · t<sup>½</sup>
</div>
<div style={codeBox}>
<div className={codeBox}>
<span style={{ color: 'var(--t3)', fontSize: '10px' }}>/* 중력-점성 체제 (후기) */</span><br />
R(t) = <span style={{ color: 'var(--orange)' }}>K</span> · (<span style={{ color: 'var(--cyan)' }}>ΔρgV²</span> / <span style={{ color: 'var(--purple)' }}>νw</span>)<sup></sup> · t<sup>¾</sup>
</div>
@ -1268,17 +1268,17 @@ function WeatheringPanel() {
{ title: '🌊 유화(Emulsification)', color: 'var(--orange)', desc: '파랑에 의해 해수가 유류에 혼입되어 점도가 크게 증가하는 현상. 방제 작업을 어렵게 만듭니다.', formula: 'dW_c/dt = K_em·U_w²·(1-W_c/W_max)', note: 'W_c: 함수율 / K_em: 유화 속도상수' },
{ title: '💧 자연분산(Dispersion)', color: 'var(--red)', desc: '파랑 에너지에 의해 유류가 수중으로 분산되어 유막 두께가 감소하는 과정입니다.', formula: 'D_r = 0.11·(U_w+U_c)²·(1+U_w)⁻¹', note: 'Mackay 경험식 기반 분산율 계산' },
].map(w => (
<div key={w.title} className={card} style={{ ...cardBg, borderTop: `3px solid ${w.color}`, margin: 0 }}>
<div key={w.title} className={`${card} ${cardBg}`} style={{ borderTop: `3px solid ${w.color}`, margin: 0 }}>
<div style={labelStyle(w.color)}>{w.title}</div>
<div style={bodyText} className="mb-2">{w.desc}</div>
<div style={codeBox}>{w.formula}</div>
<div className={`${bodyText} mb-2`}>{w.desc}</div>
<div className={codeBox}>{w.formula}</div>
<div className="mt-2 text-[10px]" style={{ color: 'var(--t3)' }}>{w.note}</div>
</div>
))}
</div>
{/* 타임라인 */}
<div className={card} style={cardBg}>
<div className={`${card} ${cardBg}`}>
<div style={labelStyle('var(--t1)')}> ( )</div>
<div className="flex gap-0 items-stretch">
{[
@ -1315,7 +1315,7 @@ function OceanInputPanel() {
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className={card} style={{ ...cardBg, borderLeft: '3px solid var(--green)', margin: 0 }}>
<div className={`${card} ${cardBg}`} style={{ borderLeft: '3px solid var(--green)', margin: 0 }}>
<div style={labelStyle('var(--green)')}>🌬 </div>
<div className="flex flex-col gap-1.5 text-[11px]" style={{ color: 'var(--t2)' }}>
{[
@ -1330,7 +1330,7 @@ function OceanInputPanel() {
)}
</div>
</div>
<div className={card} style={{ ...cardBg, borderLeft: '3px solid var(--cyan)', margin: 0 }}>
<div className={`${card} ${cardBg}`} style={{ borderLeft: '3px solid var(--cyan)', margin: 0 }}>
<div style={labelStyle('var(--cyan)')}>🌊 </div>
<div className="flex flex-col gap-1.5 text-[11px]" style={{ color: 'var(--t2)' }}>
{[
@ -1364,7 +1364,7 @@ function VerificationPanel() {
</div>
</div>
<div className={card} style={cardBg}>
<div className={`${card} ${cardBg}`}>
<div className="flex items-center justify-between mb-2">
<div style={labelStyle('var(--t1)')}>2007 </div>
<span className="text-[10px] px-2 py-0.5 rounded" style={{ background: 'rgba(34,197,94,.12)', color: 'var(--green)', fontWeight: 600 }}></span>
@ -1384,7 +1384,7 @@ function VerificationPanel() {
</div>
</div>
{/* 2014년 우이산 충돌 사고 재현 */}
<div className={card} style={cardBg}>
<div className={`${card} ${cardBg}`}>
<div className="flex items-center justify-between mb-2">
<div style={labelStyle('var(--t1)')}>2014 </div>
<span className="text-[10px] px-2 py-0.5 rounded" style={{ background: 'rgba(6,182,212,.12)', color: 'var(--cyan)', fontWeight: 600 }}></span>
@ -1395,7 +1395,7 @@ function VerificationPanel() {
</div>
{/* 관련 논문 */}
<div className={card} style={cardBg}>
<div className={`${card} ${cardBg}`}>
<div className="flex items-center gap-1.5 mb-2 cursor-pointer select-none" onClick={() => setPapersOpen(!papersOpen)}>
<span style={{ ...tag('var(--blue)'), fontWeight: 700, fontSize: '12px' }}>📄 </span>
<span className="text-[11px] ml-1" style={{ color: 'var(--t3)', transition: 'transform .2s', display: 'inline-block', transform: papersOpen ? 'rotate(180deg)' : 'rotate(0)' }}></span>
@ -1444,16 +1444,16 @@ function EnsemblePanel() {
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div className={card} style={{ ...cardBg, borderLeft: '3px solid var(--purple)', margin: 0 }}>
<div className={`${card} ${cardBg}`} style={{ borderLeft: '3px solid var(--purple)', margin: 0 }}>
<div style={labelStyle('var(--purple)')}>📊 </div>
<div style={codeBox} className="mb-2">
<div className={`${codeBox} mb-2`}>
P_ens(x,t) = w·P_KOSPS + w·P_POSEIDON + w·P_OpenDrift
</div>
<div style={bodyText}> . 기본값: 1/3 .</div>
<div className={bodyText}> . 기본값: 1/3 .</div>
</div>
<div className={card} style={{ ...cardBg, borderLeft: '3px solid var(--orange)', margin: 0 }}>
<div className={`${card} ${cardBg}`} style={{ borderLeft: '3px solid var(--orange)', margin: 0 }}>
<div style={labelStyle('var(--orange)')}>🎯 (Worst Case)</div>
<div style={bodyText}> 3 <b style={{ color: 'var(--orange)' }}> </b> . .</div>
<div className={bodyText}> 3 <b style={{ color: 'var(--orange)' }}> </b> . .</div>
</div>
</div>
</div>
@ -1465,7 +1465,7 @@ function RoadmapPanel() {
return (
<div>
<div className="grid grid-cols-2 gap-3 mb-4">
<div className={card} style={{ ...cardBg, borderTop: '3px solid var(--red)', margin: 0 }}>
<div className={`${card} ${cardBg}`} style={{ borderTop: '3px solid var(--red)', margin: 0 }}>
<div style={labelStyle('var(--red)')}> </div>
<div className="flex flex-col gap-2 text-[11px]" style={{ color: 'var(--t2)' }}>
<div className="rounded-md" style={{ padding: '8px 10px', background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.12)', color: 'var(--t2)' }}>2D <b>3D </b></div>
@ -1473,7 +1473,7 @@ function RoadmapPanel() {
<div className="rounded-md" style={{ padding: '8px 10px', background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.12)', color: 'var(--t2)' }}> <b>· </b></div>
</div>
</div>
<div className={card} style={{ ...cardBg, borderTop: '3px solid var(--green)', margin: 0 }}>
<div className={`${card} ${cardBg}`} style={{ borderTop: '3px solid var(--green)', margin: 0 }}>
<div style={labelStyle('var(--green)')}>🚀 </div>
<div className="flex flex-col gap-2 text-[11px]" style={{ color: 'var(--t2)' }}>
{[
@ -1491,7 +1491,7 @@ function RoadmapPanel() {
</div>
{/* 로드맵 */}
<div className={card} style={{ ...cardBg, background: 'linear-gradient(135deg,rgba(6,182,212,.05),rgba(34,197,94,.04))', border: '1px solid rgba(6,182,212,.2)' }}>
<div className={card} style={{ background: 'linear-gradient(135deg,rgba(6,182,212,.05),rgba(34,197,94,.04))', border: '1px solid rgba(6,182,212,.2)' }}>
<div style={labelStyle('var(--t1)')}>📅 WING </div>
<div className="flex gap-0 items-stretch">
{[

파일 보기

@ -472,14 +472,13 @@ export function OilSpillView() {
/>
{/* 타임라인 플레이어 (리플레이 비활성 시) */}
{!isReplayActive && <div style={{
position: 'absolute', bottom: 0, left: 0, right: 0, height: '72px',
{!isReplayActive && <div className="absolute bottom-0 left-0 right-0 h-[72px] flex items-center px-5 gap-4" style={{
background: 'rgba(15,21,36,0.95)', backdropFilter: 'blur(16px)',
borderTop: '1px solid var(--bd)',
display: 'flex', alignItems: 'center', padding: '0 20px', gap: '16px', zIndex: 1100
zIndex: 1100
}}>
{/* 컨트롤 버튼 */}
<div style={{ display: 'flex', gap: '4px', flexShrink: 0 }}>
<div className="flex gap-1 shrink-0">
{[
{ icon: '⏮', action: () => setTimelinePosition(0) },
{ icon: '◀', action: () => setTimelinePosition(Math.max(0, timelinePosition - 100 / 12)) },
@ -510,7 +509,7 @@ export function OilSpillView() {
cursor: 'pointer', fontSize: '12px', transition: '0.2s'
}}>{btn.icon}</button>
))}
<div style={{ width: '8px' }} />
<div className="w-2" />
<button onClick={() => setPlaySpeed(playSpeed >= 4 ? 1 : playSpeed * 2)} style={{
width: '34px', height: '34px', borderRadius: 'var(--rS, 4px)',
border: '1px solid var(--bd)', background: 'var(--bg3)', color: 'var(--t2)',
@ -520,9 +519,9 @@ export function OilSpillView() {
</div>
{/* 타임라인 슬라이더 */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: '6px' }}>
<div className="flex-1 flex flex-col gap-1.5">
{/* 시간 라벨 */}
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '0 4px' }}>
<div className="flex justify-between px-1">
{['0h', '6h', '12h', '18h', '24h', '36h', '48h', '60h', '72h'].map((label, i) => {
const pos = [0, 8.33, 16.67, 25, 33.33, 50, 66.67, 83.33, 100][i]
const isActive = Math.abs(timelinePosition - pos) < 5
@ -537,7 +536,7 @@ export function OilSpillView() {
</div>
{/* 슬라이더 트랙 */}
<div style={{ position: 'relative', height: '24px', display: 'flex', alignItems: 'center' }}>
<div className="relative h-6 flex items-center">
{/* 트랙 레일 */}
<div
style={{ width: '100%', height: '4px', background: 'var(--bd)', borderRadius: '2px', position: 'relative', cursor: 'pointer' }}

파일 보기

@ -106,42 +106,34 @@ export function RecalcModal({
<div
ref={backdropRef}
style={{
position: 'fixed', inset: 0, zIndex: 9999,
inset: 0,
background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(4px)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}
className="fixed z-[9999] flex items-center justify-center"
>
<div style={{
width: '380px', maxHeight: 'calc(100vh - 120px)',
background: 'var(--bg1)', border: '1px solid var(--bd)',
borderRadius: '14px', overflow: 'hidden',
display: 'flex', flexDirection: 'column',
background: 'var(--bg1)',
borderRadius: '14px',
boxShadow: '0 20px 60px rgba(0,0,0,0.5)',
}}>
}} className="border border-border overflow-hidden flex flex-col">
{/* Header */}
<div style={{
padding: '16px 20px', borderBottom: '1px solid var(--bd)',
display: 'flex', alignItems: 'center', gap: '12px',
}}>
padding: '16px 20px',
}} className="border-b border-border flex items-center gap-3">
<div style={{
width: '36px', height: '36px', 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)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: '16px',
}}>
}} className="flex items-center justify-center">
🔄
</div>
<div style={{ flex: 1 }}>
<h2 style={{
fontSize: '15px', fontWeight: 700,
margin: 0,
}}>
<div className="flex-1">
<h2 className="text-[15px] font-bold m-0">
</h2>
<div style={{
fontSize: '10px', color: 'var(--t3)', marginTop: '2px',
}}>
<div className="text-[10px] text-text-3 mt-[2px]">
·
</div>
</div>
@ -149,10 +141,10 @@ export function RecalcModal({
onClick={onClose}
style={{
width: '28px', height: '28px', borderRadius: '6px',
border: '1px solid var(--bd)', background: 'var(--bg3)',
color: 'var(--t3)', fontSize: '12px', cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center',
background: 'var(--bg3)',
fontSize: '12px',
}}
className="border border-border text-text-3 cursor-pointer flex items-center justify-center"
>
</button>
@ -160,18 +152,17 @@ export function RecalcModal({
{/* Scrollable Content */}
<div style={{
flex: 1, overflowY: 'auto', padding: '16px 20px',
display: 'flex', flexDirection: 'column', gap: '14px',
}}>
padding: '16px 20px',
}} className="flex-1 overflow-y-auto flex flex-col gap-[14px]">
{/* 현재 분석 정보 */}
<div style={{
padding: '10px 12px', background: 'rgba(6,182,212,0.04)',
border: '1px solid rgba(6,182,212,0.15)', borderRadius: '8px',
}}>
<div style={{ fontSize: '9px', fontWeight: 700, color: 'var(--cyan)', marginBottom: '6px' }}>
<div className="text-[9px] font-bold text-primary-cyan mb-1.5">
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px', fontSize: '9px' }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px' }} className="text-[9px]">
<InfoItem label="사고명" value="여수 앞바다 유류오염" />
<InfoItem label="유종" value={initOilType} />
<InfoItem label="유출량" value={`${initSpillAmount} kl`} />
@ -194,13 +185,12 @@ export function RecalcModal({
{/* 유출량 */}
<FieldGroup label="유출량">
<div style={{ display: 'flex', gap: '6px' }}>
<div className="flex gap-1.5">
<input
type="number"
className="prd-i"
className="prd-i flex-1"
value={spillAmount}
onChange={(e) => setSpillAmount(Number(e.target.value))}
style={{ flex: 1 }}
/>
<select
className="prd-i"
@ -243,31 +233,29 @@ export function RecalcModal({
{/* 유출 위치 */}
<FieldGroup label="유출 위치 (좌표)">
<div style={{ display: 'flex', gap: '6px' }}>
<div style={{ flex: 1 }}>
<div style={{ fontSize: '8px', color: 'var(--t3)', marginBottom: '3px' }}>
<div className="flex gap-1.5">
<div className="flex-1">
<div className="text-[8px] text-text-3 mb-[3px]">
(N)
</div>
<input
type="number"
className="prd-i"
className="prd-i font-mono"
value={lat}
step={0.0001}
onChange={(e) => setLat(Number(e.target.value))}
style={{ fontFamily: 'var(--fM)' }}
/>
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: '8px', color: 'var(--t3)', marginBottom: '3px' }}>
<div className="flex-1">
<div className="text-[8px] text-text-3 mb-[3px]">
(E)
</div>
<input
type="number"
className="prd-i"
className="prd-i font-mono"
value={lon}
step={0.0001}
onChange={(e) => setLon(Number(e.target.value))}
style={{ fontFamily: 'var(--fM)' }}
/>
</div>
</div>
@ -275,7 +263,7 @@ export function RecalcModal({
{/* 모델 선택 */}
<FieldGroup label="예측 모델 선택">
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
<div className="flex flex-wrap gap-1.5">
{([
{ model: 'KOSPS' as PredictionModel, color: '#3b82f6' },
{ model: 'POSEIDON' as PredictionModel, color: '#22c55e' },
@ -307,19 +295,18 @@ export function RecalcModal({
{/* Footer */}
<div style={{
padding: '14px 20px', borderTop: '1px solid var(--bd)',
display: 'flex', gap: '8px',
}}>
padding: '14px 20px',
}} className="border-t border-border flex gap-2">
<button
onClick={onClose}
disabled={phase !== 'editing'}
style={{
flex: 1, padding: '10px', fontSize: '12px', fontWeight: 600,
borderRadius: '8px', cursor: 'pointer',
background: 'var(--bg3)', border: '1px solid var(--bd)',
color: 'var(--t2)',
padding: '10px',
borderRadius: '8px',
background: 'var(--bg3)',
opacity: phase !== 'editing' ? 0.5 : 1,
}}
className="flex-1 text-[12px] font-semibold border border-border text-text-2 cursor-pointer"
>
</button>
@ -327,8 +314,9 @@ export function RecalcModal({
onClick={handleRun}
disabled={phase !== 'editing' || models.size === 0}
style={{
flex: 2, padding: '10px', fontSize: '12px', fontWeight: 700,
borderRadius: '8px', cursor: phase === 'editing' ? 'pointer' : 'wait',
padding: '10px',
borderRadius: '8px',
cursor: phase === 'editing' ? 'pointer' : 'wait',
background: phase === 'done'
? 'rgba(34,197,94,0.15)'
: phase === 'running'
@ -346,6 +334,7 @@ export function RecalcModal({
: '#fff',
opacity: models.size === 0 && phase === 'editing' ? 0.5 : 1,
}}
className="flex-[2] text-[12px] font-bold"
>
{phase === 'done' ? '✅ 재계산 완료!' : phase === 'running' ? '⏳ 재계산 실행중...' : '🔄 재계산 실행'}
</button>
@ -358,10 +347,7 @@ export function RecalcModal({
function FieldGroup({ label, children }: { label: string; children: React.ReactNode }) {
return (
<div>
<div style={{
fontSize: '10px', fontWeight: 700, color: 'var(--t2)',
marginBottom: '6px',
}}>
<div className="text-[10px] font-bold text-text-2 mb-1.5">
{label}
</div>
{children}
@ -371,9 +357,9 @@ function FieldGroup({ label, children }: { label: string; children: React.ReactN
function InfoItem({ label, value }: { label: string; value: string }) {
return (
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '2px 0' }}>
<span style={{ color: 'var(--t3)' }}>{label}</span>
<span style={{ fontWeight: 600, fontFamily: 'var(--fM)' }}>{value}</span>
<div className="flex justify-between py-[2px]">
<span className="text-text-3">{label}</span>
<span className="font-semibold font-mono">{value}</span>
</div>
)
}

파일 보기

@ -23,7 +23,7 @@ export function RightPanel({ onOpenBacktrack, onOpenRecalc, onOpenReport, detail
{/* 표시 정보 제어 */}
<Section title="표시 정보 제어">
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '4px 10px' }}>
<div className="grid grid-cols-2 gap-x-2.5 gap-y-1">
<CheckboxLabel checked>/</CheckboxLabel>
<CheckboxLabel checked>/</CheckboxLabel>
<CheckboxLabel></CheckboxLabel>
@ -42,12 +42,12 @@ export function RightPanel({ onOpenBacktrack, onOpenRecalc, onOpenReport, detail
{/* 오염 종합 상황 */}
<Section title="오염 종합 상황" badge="위험" badgeColor="red">
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '2px', fontSize: '9px' }}>
<div className="grid grid-cols-2 gap-0.5 text-[9px]">
<StatBox label="유출량" value={spill?.volume != null ? spill.volume.toFixed(2) : '—'} unit={spill?.unit || 'kl'} color="var(--t1)" />
<StatBox label="풍화량" value="0.43" unit="kl" color="var(--orange)" />
<StatBox label="해상잔존" value="9.57" unit="kl" color="var(--blue)" />
<StatBox label="연안부착" value="0.00" unit="kl" color="var(--red)" />
<div style={{ gridColumn: 'span 2' }}>
<div className="col-span-2">
<StatBox label="오염해역면적" value="8.56" unit="㎢" color="var(--cyan)" />
</div>
</div>
@ -55,7 +55,7 @@ export function RightPanel({ onOpenBacktrack, onOpenRecalc, onOpenReport, detail
{/* 확산 예측 요약 */}
<Section title="확산 예측 요약 (+18h)" badge="위험" badgeColor="red">
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '2px' }}>
<div className="grid grid-cols-2 gap-0.5">
<PredictionCard value="4.7 km²" label="영향 면적" color="var(--red)" />
<PredictionCard value="6.2 km" label="최대 확산 거리" color="var(--orange)" />
<PredictionCard value="NE 42°" label="주 확산 방향" color="var(--cyan)" />
@ -65,7 +65,7 @@ export function RightPanel({ onOpenBacktrack, onOpenRecalc, onOpenReport, detail
{/* 유출유 풍화 상태 */}
<Section title="유출유 풍화 상태">
<div style={{ display: 'flex', flexDirection: 'column', gap: '3px', fontSize: '8px' }}>
<div className="flex flex-col gap-[3px] text-[8px]">
<ProgressBar label="수면잔류" value={58} color="var(--blue)" />
<ProgressBar label="증발" value={22} color="var(--cyan)" />
<ProgressBar label="분산" value={12} color="var(--green)" />
@ -82,27 +82,14 @@ export function RightPanel({ onOpenBacktrack, onOpenRecalc, onOpenReport, detail
>
<div className="space-y-2">
{/* 선박 카드 */}
<div style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '8px',
<div className="flex items-center gap-2 p-2 border border-[rgba(6,182,212,0.15)] rounded-md" style={{
background: 'linear-gradient(135deg, rgba(6,182,212,0.06), rgba(168,85,247,0.04))',
border: '1px solid rgba(6,182,212,0.15)',
borderRadius: '6px'
}}>
<div style={{
width: '30px',
height: '30px',
borderRadius: '6px',
<div className="w-[30px] h-[30px] rounded-md flex items-center justify-center text-[15px]" style={{
background: 'rgba(6,182,212,0.1)',
border: '1px solid rgba(6,182,212,0.2)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '15px'
}}>🚢</div>
<div style={{ flex: 1 }}>
<div className="flex-1">
<div className="text-[11px] font-bold text-text-1 font-korean">{vessel?.vesselNm || '—'}</div>
<div className="text-[8px] text-text-3 font-mono">IMO {vessel?.imoNo || '—'} · {vessel?.vesselTp || '—'}</div>
</div>
@ -110,7 +97,7 @@ export function RightPanel({ onOpenBacktrack, onOpenRecalc, onOpenReport, detail
</div>
{/* 제원 */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '4px' }}>
<div className="grid grid-cols-3 gap-1">
<SpecCard value={vessel?.loaM?.toFixed(1) || '—'} label="전장 LOA(m)" color="var(--purple)" />
<SpecCard value={vessel?.breadthM?.toFixed(1) || '—'} label="형폭 B(m)" color="var(--cyan)" />
<SpecCard value={vessel?.draftM?.toFixed(1) || '—'} label="흘수 d(m)" color="var(--green)" />
@ -259,21 +246,12 @@ function StatBox({
color: string
}) {
return (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
padding: '4px 8px',
background: 'var(--bg0)',
borderRadius: '3px',
border: '1px solid var(--bd)'
}}
>
<span style={{ color: 'var(--t3)' }} className="font-korean">
<div className="flex justify-between px-2 py-1 bg-bg-0 border border-border rounded-[3px]">
<span className="text-text-3 font-korean">
{label}
</span>
<span style={{ fontWeight: 700, color, fontFamily: 'var(--fM)' }}>
{value} <small style={{ fontWeight: 400, color: 'var(--t3)' }}>{unit}</small>
{value} <small className="font-normal text-text-3">{unit}</small>
</span>
</div>
)
@ -281,22 +259,14 @@ function StatBox({
function PredictionCard({ value, label, color }: { value: string; label: string; color: string }) {
return (
<div
style={{
textAlign: 'center',
padding: '5px 4px',
background: 'var(--bg0)',
borderRadius: '3px',
border: '1px solid var(--bd)'
}}
>
<div className="text-center py-[5px] px-1 bg-bg-0 border border-border rounded-[3px]">
<div
style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color }}
className="font-mono"
style={{ color }}
className="text-xs font-extrabold font-mono"
>
{value}
</div>
<div style={{ fontSize: '7px', color: 'var(--t3)' }} className="font-korean">
<div className="text-[7px] text-text-3 font-korean">
{label}
</div>
</div>
@ -305,32 +275,21 @@ function PredictionCard({ value, label, color }: { value: string; label: string;
function ProgressBar({ label, value, color }: { label: string; value: number; color: string }) {
return (
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
<span style={{ color: 'var(--t3)', minWidth: '38px' }} className="font-korean">
<div className="flex items-center gap-1">
<span className="text-text-3 font-korean" style={{ minWidth: '38px' }}>
{label}
</span>
<div
style={{
flex: 1,
height: '5px',
background: 'rgba(255,255,255,0.05)',
borderRadius: '3px',
overflow: 'hidden'
}}
className="flex-1 h-[5px] overflow-hidden rounded-[3px]"
style={{ background: 'rgba(255,255,255,0.05)' }}
>
<div
style={{ height: '100%', width: `${value}%`, background: color, borderRadius: '3px' }}
/>
</div>
<span
style={{
color,
fontWeight: 600,
minWidth: '28px',
textAlign: 'right',
fontFamily: 'var(--fM)'
}}
className="font-mono"
style={{ color, minWidth: '28px' }}
className="font-semibold text-right font-mono"
>
{value}%
</span>
@ -365,22 +324,14 @@ function CollapsibleSection({
function SpecCard({ value, label, color }: { value: string; label: string; color: string }) {
return (
<div
style={{
textAlign: 'center',
padding: '6px 2px',
background: 'var(--bg0)',
borderRadius: '4px',
border: '1px solid var(--bd)'
}}
>
<div className="text-center py-[6px] px-0.5 bg-bg-0 border border-border rounded-md">
<div
style={{ fontSize: '12px', fontWeight: 800, fontFamily: 'var(--fM)', color }}
className="font-mono"
style={{ color }}
className="text-xs font-extrabold font-mono"
>
{value}
</div>
<div style={{ fontSize: '7px', color: 'var(--t3)' }} className="font-korean">
<div className="text-[7px] text-text-3 font-korean">
{label}
</div>
</div>
@ -399,23 +350,11 @@ function InfoRow({
valueColor?: string
}) {
return (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
padding: '3px 6px',
background: 'var(--bg0)',
borderRadius: '3px'
}}
>
<span style={{ color: 'var(--t3)' }}>{label}</span>
<div className="flex justify-between py-[3px] px-[6px] bg-bg-0 rounded-[3px]">
<span className="text-text-3">{label}</span>
<span
style={{
fontWeight: 600,
color: valueColor || 'var(--t1)',
fontFamily: mono ? 'var(--fM)' : undefined
}}
className={mono ? 'font-mono' : ''}
style={{ color: valueColor || 'var(--t1)' }}
className={`font-semibold${mono ? ' font-mono' : ''}`}
>
{value}
</span>
@ -454,21 +393,16 @@ function InsuranceCard({
return (
<div
className="rounded-md"
style={{
padding: '6px 8px',
border: `1px solid ${colors.border}`,
borderRadius: '4px',
background: colors.bg
}}
>
<div
style={{
fontSize: '8px',
fontWeight: 700,
color: colors.text,
marginBottom: '4px'
}}
className="font-korean"
style={{ color: colors.text }}
className="text-[8px] font-bold font-korean mb-1"
>
{title}
</div>
@ -476,16 +410,12 @@ function InsuranceCard({
{items.map((item, i) => (
<div
key={i}
style={{ display: 'flex', justifyContent: 'space-between', padding: '2px 4px' }}
className="flex justify-between py-0.5 px-1"
>
<span style={{ color: 'var(--t3)' }}>{item.label}</span>
<span className="text-text-3">{item.label}</span>
<span
style={{
fontWeight: 600,
color: item.valueColor || 'var(--t1)',
fontFamily: item.mono ? 'var(--fM)' : undefined
}}
className={item.mono ? 'font-mono' : ''}
style={{ color: item.valueColor || 'var(--t1)' }}
className={`font-semibold${item.mono ? ' font-mono' : ''}`}
>
{item.value}
</span>

파일 보기

@ -184,10 +184,7 @@ function ECell({ value, editing, onChange, align, placeholder }: {
function AddRowBtn({ onClick, label }: { onClick: () => void; label?: string }) {
return (
<button onClick={onClick} style={{
padding: '4px 12px', fontSize: '10px', fontWeight: 600, color: '#06b6d4', background: 'rgba(6,182,212,0.08)',
border: '1px dashed #06b6d4', borderRadius: '3px', cursor: 'pointer', marginBottom: '12px',
}}>
<button onClick={onClick} className="px-3 py-1 text-[10px] font-semibold text-primary-cyan bg-[rgba(6,182,212,0.08)] border border-dashed border-primary-cyan rounded-sm cursor-pointer mb-3">
+ {label || '행 추가'}
</button>
)
@ -200,8 +197,8 @@ function Page1({ data, editing, onChange }: { data: OilSpillReportData; editing:
return (
<div style={S.page}>
<div style={{ position: 'absolute', top: 10, right: 16, fontSize: '9px', color: 'var(--t3)', fontWeight: 600 }}></div>
<div style={{ background: 'rgba(6,182,212,0.1)', color: 'var(--cyan)', padding: '12px 20px', fontSize: '18px', fontWeight: 700, marginBottom: '20px', borderRadius: '4px', letterSpacing: '1px', textAlign: 'center', border: '1px solid rgba(6,182,212,0.2)' }}>
<div className="absolute top-2.5 right-4 text-[9px] text-text-3 font-semibold"></div>
<div className="text-primary-cyan text-[18px] font-bold mb-5 rounded px-5 py-3 text-center tracking-wide border" style={{ background: 'rgba(6,182,212,0.1)', border: '1px solid rgba(6,182,212,0.2)' }}>
</div>
<div style={S.sectionTitle}>1. </div>
@ -227,7 +224,7 @@ function Page2({ data, editing, onChange }: { data: OilSpillReportData; editing:
return (
<div style={S.page}>
<div style={{ position: 'absolute', top: 10, right: 16, fontSize: '9px', color: 'var(--t3)', fontWeight: 600 }}></div>
<div className="absolute top-2.5 right-4 text-[9px] text-text-3 font-semibold"></div>
<div style={S.sectionTitle}>2. </div>
<div style={S.subHeader}> </div>
<table style={S.table}>
@ -268,7 +265,7 @@ function Page2({ data, editing, onChange }: { data: OilSpillReportData; editing:
{editing && <AddRowBtn onClick={() => onChange({ ...data, weather: [...data.weather, { time: '', sunrise: '', sunset: '', windDir: '', windSpeed: '', currentDir: '', currentSpeed: '', waveHeight: '' }] })} />}
<div style={S.sectionTitle}>3. </div>
<div style={{ display: 'flex', gap: '16px', marginBottom: '16px' }}>
<div className="flex gap-4 mb-4">
<div style={S.mapPlaceholder}> 3 </div>
<div style={S.mapPlaceholder}> 6 </div>
</div>
@ -295,7 +292,7 @@ function Page2({ data, editing, onChange }: { data: OilSpillReportData; editing:
function Page3({ data, editing, onChange }: { data: OilSpillReportData; editing: boolean; onChange: (d: OilSpillReportData) => void }) {
return (
<div style={S.page}>
<div style={{ position: 'absolute', top: 10, right: 16, fontSize: '9px', color: 'var(--t3)', fontWeight: 600 }}></div>
<div className="absolute top-2.5 right-4 text-[9px] text-text-3 font-semibold"></div>
<div style={S.sectionTitle}></div>
{editing ? (
<textarea
@ -320,7 +317,7 @@ function Page4({ data, editing, onChange }: { data: OilSpillReportData; editing:
return (
<div style={S.page}>
<div style={{ position: 'absolute', top: 10, right: 16, fontSize: '9px', color: 'var(--t3)', fontWeight: 600 }}></div>
<div className="absolute top-2.5 right-4 text-[9px] text-text-3 font-semibold"></div>
<div style={S.sectionTitle}>4. </div>
<div style={S.mapPlaceholder}> (10km ) </div>
@ -337,7 +334,7 @@ function Page4({ data, editing, onChange }: { data: OilSpillReportData; editing:
</table>
{editing && <AddRowBtn onClick={() => onChange({ ...data, aquaculture: [...data.aquaculture, { type: '', area: '', distance: '' }] })} />}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
<div className="grid grid-cols-2 gap-5">
<div>
<div style={S.subHeader}> </div>
<table style={S.table}>
@ -366,7 +363,7 @@ function Page4({ data, editing, onChange }: { data: OilSpillReportData; editing:
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px', marginTop: '8px' }}>
<div className="grid grid-cols-2 gap-5 mt-2">
<div>
<div style={S.subHeader}>(ESI) </div>
<table style={S.table}>
@ -411,7 +408,7 @@ function Page5({ data, editing, onChange }: { data: OilSpillReportData; editing:
const setSens = (i: number, v: string) => { const s = [...data.sensitivity]; s[i] = { ...s[i], area: v }; onChange({ ...data, sensitivity: s }) }
return (
<div style={S.page}>
<div style={{ position: 'absolute', top: 10, right: 16, fontSize: '9px', color: 'var(--t3)', fontWeight: 600 }}></div>
<div className="absolute top-2.5 right-4 text-[9px] text-text-3 font-semibold"></div>
<div style={S.sectionTitle}> ( )</div>
<div style={S.mapPlaceholder}> </div>
<table style={S.table}>
@ -431,10 +428,10 @@ function Page6({ data, editing, onChange }: { data: OilSpillReportData; editing:
const setVessel = (i: number, k: string, v: string) => { const vs = [...data.vessels]; vs[i] = { ...vs[i], [k]: v }; onChange({ ...data, vessels: vs }) }
return (
<div style={S.page}>
<div style={{ position: 'absolute', top: 10, right: 16, fontSize: '9px', color: 'var(--t3)', fontWeight: 600 }}></div>
<div className="absolute top-2.5 right-4 text-[9px] text-text-3 font-semibold"></div>
<div style={S.sectionTitle}>5. ·</div>
<div style={S.subHeader}> ( 30km내)</div>
<div style={{ overflowX: 'auto' }}>
<div className="overflow-x-auto">
<table style={S.table}>
<thead>
<tr><th style={S.th} rowSpan={2}>#</th><th style={S.th} rowSpan={2}></th><th style={S.th} rowSpan={2}></th><th style={S.th} rowSpan={2}>(km)</th><th style={S.th} rowSpan={2}>(knots)</th><th style={S.th} rowSpan={2}>()</th><th style={S.th} colSpan={2}></th><th style={S.th} colSpan={2}></th></tr>
@ -472,7 +469,7 @@ function Page7({ data, editing, onChange }: { data: OilSpillReportData; editing:
const setRes = (k: string, v: string) => onChange({ ...data, result: { ...data.result, [k]: v } })
return (
<div style={S.page}>
<div style={{ position: 'absolute', top: 10, right: 16, fontSize: '9px', color: 'var(--t3)', fontWeight: 600 }}></div>
<div className="absolute top-2.5 right-4 text-[9px] text-text-3 font-semibold"></div>
<div style={S.sectionTitle}>/ </div>
<div style={S.mapPlaceholder}>/ </div>
<div style={S.subHeader}></div>
@ -546,57 +543,57 @@ export function OilSpillReportTemplate({ mode, initialData, onSave, onBack }: Pr
]
return (
<div style={{ width: '100%' }}>
<div className="w-full">
{/* Toolbar */}
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '20px', flexWrap: 'wrap', gap: '12px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
{onBack && <button onClick={onBack} style={{ padding: '6px 12px', fontSize: '12px', fontWeight: 600, color: 'var(--t2)', background: 'none', border: 'none', cursor: 'pointer' }}> </button>}
<h2 style={{ fontSize: '18px', fontWeight: 700 }}>
<div className="flex items-center justify-between mb-5 flex-wrap gap-3">
<div className="flex items-center gap-2.5">
{onBack && <button onClick={onBack} className="px-3 py-1.5 text-[12px] font-semibold text-text-2 bg-transparent border-none cursor-pointer"> </button>}
<h2 className="text-[18px] font-bold">
{editing ? (
<input
value={data.title}
onChange={e => setData({ ...data, title: e.target.value })}
placeholder="보고서 제목 입력"
style={{ fontSize: '18px', fontWeight: 700, background: 'var(--bg0)', border: '1px solid var(--bdL)', borderRadius: '4px', padding: '4px 10px', outline: 'none', width: '100%', maxWidth: '600px' }}
className="text-[18px] font-bold bg-bg-0 border border-[var(--bdL)] rounded px-2.5 py-1 outline-none w-full max-w-[600px]"
/>
) : (
data.title || '유류오염사고 대응지원 상황도'
)}
</h2>
<span style={{ padding: '3px 10px', fontSize: '10px', fontWeight: 600, borderRadius: '4px', border: '1px solid', ...( editing ? { background: 'rgba(251,191,36,0.15)', color: '#f59e0b', borderColor: 'rgba(251,191,36,0.3)' } : { background: 'rgba(6,182,212,0.15)', color: 'var(--cyan)', borderColor: 'rgba(6,182,212,0.3)' }) }}>
<span className="px-2.5 py-[3px] text-[10px] font-semibold rounded border" style={editing ? { background: 'rgba(251,191,36,0.15)', color: '#f59e0b', borderColor: 'rgba(251,191,36,0.3)' } : { background: 'rgba(6,182,212,0.15)', color: 'var(--cyan)', borderColor: 'rgba(6,182,212,0.3)' }}>
{editing ? '편집 중' : mode === 'preview' ? '샘플' : '보기'}
</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<button onClick={() => setViewMode('all')} style={{ padding: '6px 14px', fontSize: '11px', fontWeight: 600, borderRadius: '4px', border: viewMode === 'all' ? '1px solid var(--cyan)' : '1px solid var(--bd)', background: viewMode === 'all' ? 'rgba(6,182,212,0.1)' : 'var(--bg2)', color: viewMode === 'all' ? 'var(--cyan)' : 'var(--t3)', cursor: 'pointer' }}> </button>
<button onClick={() => setViewMode('page')} style={{ padding: '6px 14px', fontSize: '11px', fontWeight: 600, borderRadius: '4px', border: viewMode === 'page' ? '1px solid var(--cyan)' : '1px solid var(--bd)', background: viewMode === 'page' ? 'rgba(6,182,212,0.1)' : 'var(--bg2)', color: viewMode === 'page' ? 'var(--cyan)' : 'var(--t3)', cursor: 'pointer' }}></button>
<div className="flex items-center gap-2">
<button onClick={() => setViewMode('all')} className="px-3.5 py-1.5 text-[11px] font-semibold rounded cursor-pointer" style={{ border: viewMode === 'all' ? '1px solid var(--cyan)' : '1px solid var(--bd)', background: viewMode === 'all' ? 'rgba(6,182,212,0.1)' : 'var(--bg2)', color: viewMode === 'all' ? 'var(--cyan)' : 'var(--t3)' }}> </button>
<button onClick={() => setViewMode('page')} className="px-3.5 py-1.5 text-[11px] font-semibold rounded cursor-pointer" style={{ border: viewMode === 'page' ? '1px solid var(--cyan)' : '1px solid var(--bd)', background: viewMode === 'page' ? 'rgba(6,182,212,0.1)' : 'var(--bg2)', color: viewMode === 'page' ? 'var(--cyan)' : 'var(--t3)' }}></button>
{editing && (
<button onClick={handleSave} style={{ padding: '6px 16px', fontSize: '11px', fontWeight: 700, borderRadius: '4px', border: '1px solid #22c55e', background: 'rgba(34,197,94,0.15)', color: '#22c55e', cursor: 'pointer' }}></button>
<button onClick={handleSave} className="px-4 py-1.5 text-[11px] font-bold rounded cursor-pointer border border-[#22c55e] bg-[rgba(34,197,94,0.15)] text-status-green"></button>
)}
<button onClick={() => window.print()} style={{ padding: '6px 14px', fontSize: '11px', fontWeight: 600, borderRadius: '4px', border: '1px solid var(--red)', background: 'rgba(239,68,68,0.1)', color: '#ef4444', cursor: 'pointer' }}> / PDF</button>
<button onClick={() => window.print()} className="px-3.5 py-1.5 text-[11px] font-semibold rounded cursor-pointer border border-[var(--red)] bg-[rgba(239,68,68,0.1)] text-status-red"> / PDF</button>
</div>
</div>
{/* Page tabs */}
{viewMode === 'page' && (
<div style={{ display: 'flex', gap: '4px', marginBottom: '16px', flexWrap: 'wrap' }}>
<div className="flex gap-1 mb-4 flex-wrap">
{pages.map((p, i) => (
<button key={i} onClick={() => setCurrentPage(i)} style={{ padding: '6px 12px', fontSize: '11px', fontWeight: 600, borderRadius: '4px', border: currentPage === i ? '1px solid var(--cyan)' : '1px solid var(--bd)', background: currentPage === i ? 'rgba(6,182,212,0.15)' : 'transparent', color: currentPage === i ? 'var(--cyan)' : 'var(--t3)', cursor: 'pointer' }}>{p.label}</button>
<button key={i} onClick={() => setCurrentPage(i)} className="px-3 py-1.5 text-[11px] font-semibold rounded cursor-pointer" style={{ border: currentPage === i ? '1px solid var(--cyan)' : '1px solid var(--bd)', background: currentPage === i ? 'rgba(6,182,212,0.15)' : 'transparent', color: currentPage === i ? 'var(--cyan)' : 'var(--t3)' }}>{p.label}</button>
))}
</div>
)}
{/* Pages */}
<div id="report-print-area" style={{ width: '100%' }}>
<div id="report-print-area" className="w-full">
{viewMode === 'all'
? pages.map((p, i) => <div key={i}>{p.node}</div>)
: (
<div>
{pages[currentPage].node}
<div style={{ display: 'flex', justifyContent: 'center', gap: '8px', marginTop: '16px' }}>
<button onClick={() => setCurrentPage(p => Math.max(0, p - 1))} disabled={currentPage === 0} style={{ padding: '8px 20px', fontSize: '12px', fontWeight: 600, borderRadius: '4px', border: '1px solid var(--bd)', background: 'var(--bg2)', color: currentPage === 0 ? 'var(--t3)' : 'var(--t1)', cursor: currentPage === 0 ? 'default' : 'pointer', opacity: currentPage === 0 ? 0.4 : 1 }}></button>
<span style={{ padding: '8px 16px', fontSize: '12px', color: 'var(--t2)' }}>{currentPage + 1} / {pages.length}</span>
<button onClick={() => setCurrentPage(p => Math.min(pages.length - 1, p + 1))} disabled={currentPage === pages.length - 1} style={{ padding: '8px 20px', fontSize: '12px', fontWeight: 600, borderRadius: '4px', border: '1px solid var(--cyan)', background: 'rgba(6,182,212,0.1)', color: currentPage === pages.length - 1 ? 'var(--t3)' : 'var(--cyan)', cursor: currentPage === pages.length - 1 ? 'default' : 'pointer', opacity: currentPage === pages.length - 1 ? 0.4 : 1 }}></button>
<div className="flex justify-center gap-2 mt-4">
<button onClick={() => setCurrentPage(p => Math.max(0, p - 1))} disabled={currentPage === 0} className="px-5 py-2 text-[12px] font-semibold rounded border border-border bg-bg-2 cursor-pointer" style={{ color: currentPage === 0 ? 'var(--t3)' : 'var(--t1)', opacity: currentPage === 0 ? 0.4 : 1 }}></button>
<span className="px-4 py-2 text-[12px] text-text-2">{currentPage + 1} / {pages.length}</span>
<button onClick={() => setCurrentPage(p => Math.min(pages.length - 1, p + 1))} disabled={currentPage === pages.length - 1} className="px-5 py-2 text-[12px] font-semibold rounded cursor-pointer" style={{ border: '1px solid var(--cyan)', background: 'rgba(6,182,212,0.1)', color: currentPage === pages.length - 1 ? 'var(--t3)' : 'var(--cyan)', opacity: currentPage === pages.length - 1 ? 0.4 : 1 }}></button>
</div>
</div>
)

파일 보기

@ -87,28 +87,24 @@ function ReportGenerator({ onSave }: ReportGeneratorProps) {
<p className="text-[11px] text-text-3 font-korean mt-1"> .</p>
{/* 3 카테고리 카드 */}
<div style={{ display: 'flex', gap: '14px', marginTop: '16px' }}>
<div className="flex gap-3.5 mt-4">
{CATEGORIES.map((c, i) => {
const isActive = activeCat === i
return (
<button
key={i}
onClick={() => { setActiveCat(i as ReportCategory); setSelectedTemplate(0) }}
className="flex-1 px-4 py-3.5 rounded-[10px] cursor-pointer text-center transition-[0.2s]"
style={{
flex: 1, padding: '14px 16px', borderRadius: '10px', cursor: 'pointer',
textAlign: 'center', transition: '0.2s',
border: `1px solid ${isActive ? c.borderColor : 'var(--bd)'}`,
background: isActive ? c.bgActive : 'var(--bg3)',
}}
>
<div style={{ fontSize: '22px', marginBottom: '4px' }}>{c.icon}</div>
<div style={{
fontSize: '12px', fontWeight: 700,
color: isActive ? c.color : 'var(--t3)',
}}>
<div className="text-[22px] mb-1">{c.icon}</div>
<div className="text-[12px] font-bold" style={{ color: isActive ? c.color : 'var(--t3)' }}>
{c.label}
</div>
<div style={{ fontSize: '9px', color: 'var(--t3)', marginTop: '2px' }}>
<div className="text-[9px] text-text-3 mt-0.5">
{c.desc}
</div>
</button>
@ -135,11 +131,8 @@ function ReportGenerator({ onSave }: ReportGeneratorProps) {
background: selectedTemplate === i ? cat.bgActive : 'var(--bg2)',
}}
>
<span style={{ fontSize: '14px' }}>{tmpl.icon}</span>
<span style={{
fontSize: '11px', fontWeight: 600,
color: selectedTemplate === i ? cat.color : 'var(--t2)',
}}>
<span className="text-[14px]">{tmpl.icon}</span>
<span className="text-[11px] font-semibold" style={{ color: selectedTemplate === i ? cat.color : 'var(--t2)' }}>
{tmpl.label}
</span>
</button>
@ -162,26 +155,24 @@ function ReportGenerator({ onSave }: ReportGeneratorProps) {
opacity: sec.checked ? 1 : 0.55,
}}
>
<div style={{
width: '18px', height: '18px', borderRadius: '4px', flexShrink: 0, marginTop: '1px',
display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '10px',
background: sec.checked ? cat.color : 'var(--bg3)',
color: sec.checked ? '#fff' : 'transparent',
border: sec.checked ? 'none' : '1px solid var(--bd)',
}}>
<div
className="w-[18px] h-[18px] rounded shrink-0 mt-[1px] flex items-center justify-center text-[10px]"
style={{
background: sec.checked ? cat.color : 'var(--bg3)',
color: sec.checked ? '#fff' : 'transparent',
border: sec.checked ? 'none' : '1px solid var(--bd)',
}}
>
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
<span style={{ fontSize: '12px' }}>{sec.icon}</span>
<span style={{
fontSize: '11px', fontWeight: 700,
color: sec.checked ? 'var(--t1)' : 'var(--t3)',
}}>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-1">
<span className="text-[12px]">{sec.icon}</span>
<span className="text-[11px] font-bold" style={{ color: sec.checked ? 'var(--t1)' : 'var(--t3)' }}>
{sec.title}
</span>
</div>
<p style={{ fontSize: '9px', color: 'var(--t3)', marginTop: '2px' }}>
<p className="text-[9px] text-text-3 mt-0.5">
{sec.desc}
</p>
</div>
@ -196,10 +187,7 @@ function ReportGenerator({ onSave }: ReportGeneratorProps) {
<div className="flex items-center justify-between px-6 py-3 border-b border-border bg-bg-1">
<h3 className="text-[13px] font-bold text-text-1 font-korean flex items-center gap-2">
📄
<span style={{
fontSize: '10px', fontWeight: 600, padding: '2px 8px', borderRadius: '4px',
background: cat.bgActive, color: cat.color,
}}>
<span className="text-[10px] font-semibold px-2 py-0.5 rounded" style={{ background: cat.bgActive, color: cat.color }}>
{cat.templates[selectedTemplate].label}
</span>
</h3>

파일 보기

@ -216,16 +216,16 @@ export function ReportsView() {
{/* ──── 보고서 미리보기 모달 ──── */}
{previewReport && (
<div className="fixed inset-0 z-[9999] flex items-center justify-center" style={{ background: 'rgba(0,0,0,0.65)', backdropFilter: 'blur(6px)' }}>
<div className="flex overflow-hidden" style={{ background: 'var(--bg2)', border: '1px solid var(--bd)', borderRadius: '14px', width: 'min(96vw, 1320px)', height: 'min(94vh, 860px)', boxShadow: '0 28px 72px rgba(0,0,0,0.6)' }}>
<div className="flex overflow-hidden bg-bg-2 border border-border" style={{ borderRadius: '14px', width: 'min(96vw, 1320px)', height: 'min(94vh, 860px)', boxShadow: '0 28px 72px rgba(0,0,0,0.6)' }}>
{/* ── 왼쪽: 메타 + 다운로드 ── */}
<div className="flex flex-col shrink-0" style={{ width: '240px', borderRight: '1px solid var(--bd)', background: 'var(--bg1)' }}>
<div className="flex flex-col shrink-0 w-60 border-r border-border bg-bg-1">
{/* 상단 아이콘·제목 */}
<div style={{ padding: '20px 18px 16px', borderBottom: '1px solid var(--bd)' }}>
<div className="text-center mb-2.5" style={{ fontSize: '28px' }}>
<div className="px-[18px] pt-5 pb-4 border-b border-border">
<div className="text-center mb-2.5 text-[28px]">
{({ '초기보고서': '📋', '지휘부 보고': '📊', '예측보고서': '🔬', '종합보고서': '📑', '유출유 보고': '🛢️' } as Record<string, string>)[previewReport.reportType] || '📄'}
</div>
<div className="text-center font-korean" style={{ fontSize: '13px', fontWeight: 700, lineHeight: 1.4, wordBreak: 'keep-all' }}>
<div className="text-center font-korean text-[13px] font-bold leading-snug" style={{ wordBreak: 'keep-all' }}>
{previewReport.title || '제목 없음'}
</div>
<div className="text-center mt-2">
@ -236,31 +236,30 @@ export function ReportsView() {
</div>
{/* 메타 정보 */}
<div className="flex flex-col gap-2.5 font-korean" style={{ padding: '14px 18px', fontSize: '11px', borderBottom: '1px solid var(--bd)' }}>
<div className="flex flex-col gap-2.5 font-korean text-[11px] px-[18px] py-3.5 border-b border-border">
<div className="flex flex-col gap-0.5">
<span style={{ color: 'var(--t3)', fontSize: '9px', textTransform: 'uppercase', letterSpacing: '0.5px' }}></span>
<span style={{ fontWeight: 600 }}>{previewReport.author || '—'}</span>
<span className="text-text-3 text-[9px] uppercase tracking-wide"></span>
<span className="font-semibold">{previewReport.author || '—'}</span>
</div>
<div className="flex flex-col gap-0.5">
<span style={{ color: 'var(--t3)', fontSize: '9px', textTransform: 'uppercase', letterSpacing: '0.5px' }}></span>
<span style={{ fontWeight: 600 }}>{previewReport.jurisdiction}</span>
<span className="text-text-3 text-[9px] uppercase tracking-wide"></span>
<span className="font-semibold">{previewReport.jurisdiction}</span>
</div>
<div className="flex flex-col gap-0.5">
<span style={{ color: 'var(--t3)', fontSize: '9px', textTransform: 'uppercase', letterSpacing: '0.5px' }}></span>
<span className="font-mono" style={{ fontWeight: 600 }}>{formatDate(previewReport.createdAt)}</span>
<span className="text-text-3 text-[9px] uppercase tracking-wide"></span>
<span className="font-mono font-semibold">{formatDate(previewReport.createdAt)}</span>
</div>
<div className="flex flex-col gap-0.5">
<span style={{ color: 'var(--t3)', fontSize: '9px', textTransform: 'uppercase', letterSpacing: '0.5px' }}></span>
<span className="text-text-3 text-[9px] uppercase tracking-wide"></span>
<b style={{ color: statusColors[previewReport.status]?.text || 'var(--t1)' }}>{previewReport.status}</b>
</div>
</div>
{/* 수정 버튼 */}
<div style={{ padding: '12px 18px', borderBottom: '1px solid var(--bd)' }}>
<div className="px-[18px] py-3 border-b border-border">
<button
onClick={() => { setPreviewReport(null); setView({ screen: 'edit', data: { ...previewReport } }) }}
className="w-full font-korean"
style={{ padding: '8px 0', borderRadius: '6px', border: '1px solid rgba(6,182,212,0.3)', background: 'rgba(6,182,212,0.08)', color: 'var(--cyan)', fontSize: '11px', fontWeight: 600, cursor: 'pointer' }}
className="w-full font-korean text-[11px] font-semibold cursor-pointer rounded-md border border-[rgba(6,182,212,0.3)] bg-[rgba(6,182,212,0.08)] text-primary-cyan py-2"
>
</button>
@ -270,8 +269,8 @@ export function ReportsView() {
<div className="flex-1" />
{/* 하단 다운로드 버튼 */}
<div className="flex flex-col gap-2" style={{ padding: '14px 16px', borderTop: '1px solid var(--bd)' }}>
<div className="text-center font-korean mb-0.5" style={{ fontSize: '9px', color: 'var(--t3)' }}> </div>
<div className="flex flex-col gap-2 px-4 py-3.5 border-t border-border">
<div className="text-center font-korean mb-0.5 text-[9px] text-text-3"> </div>
<button
onClick={() => {
const tpl = templateTypes.find(t => t.id === previewReport.reportType)
@ -290,8 +289,8 @@ export function ReportsView() {
exportAsPDF(html, previewReport.title || tpl.label)
}
}}
className="w-full flex items-center justify-center gap-1.5 font-korean"
style={{ padding: '11px 0', borderRadius: '6px', border: '1px solid rgba(239,68,68,0.4)', background: 'rgba(239,68,68,0.1)', color: 'var(--red)', fontSize: '12px', fontWeight: 700, cursor: 'pointer' }}
className="w-full flex items-center justify-center gap-1.5 font-korean text-[12px] font-bold cursor-pointer rounded-md py-[11px]"
style={{ border: '1px solid rgba(239,68,68,0.4)', background: 'rgba(239,68,68,0.1)', color: 'var(--red)' }}
>
<span>📄</span> PDF
</button>
@ -313,8 +312,8 @@ export function ReportsView() {
exportAsHWP(html, previewReport.title || tpl.label)
}
}}
className="w-full flex items-center justify-center gap-1.5 font-korean"
style={{ padding: '11px 0', borderRadius: '6px', border: '1px solid rgba(59,130,246,0.4)', background: 'rgba(59,130,246,0.1)', color: 'var(--blue)', fontSize: '12px', fontWeight: 700, cursor: 'pointer' }}
className="w-full flex items-center justify-center gap-1.5 font-korean text-[12px] font-bold cursor-pointer rounded-md py-[11px]"
style={{ border: '1px solid rgba(59,130,246,0.4)', background: 'rgba(59,130,246,0.1)', color: 'var(--blue)' }}
>
<span>📝</span> HWP
</button>
@ -324,29 +323,28 @@ export function ReportsView() {
{/* ── 오른쪽: 본문 뷰어 ── */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* 헤더 */}
<div className="flex items-center justify-between shrink-0" style={{ padding: '14px 20px', borderBottom: '1px solid var(--bd)' }}>
<div className="flex items-center justify-between shrink-0 px-5 py-3.5 border-b border-border">
<div className="flex items-center gap-2">
<span className="font-korean" style={{ fontSize: '9px', padding: '3px 8px', borderRadius: '4px', background: 'rgba(6,182,212,0.1)', color: 'var(--cyan)', fontWeight: 600 }}></span>
<span className="font-korean" style={{ fontSize: '12px', color: 'var(--t3)' }}> </span>
<span className="font-korean text-[9px] px-2 py-[3px] rounded bg-[rgba(6,182,212,0.1)] text-primary-cyan font-semibold"></span>
<span className="font-korean text-[12px] text-text-3"> </span>
</div>
<span
onClick={() => setPreviewReport(null)}
style={{ fontSize: '18px', cursor: 'pointer', color: 'var(--t3)', lineHeight: 1 }}
className="hover:text-text-1 transition-colors"
className="text-[18px] cursor-pointer text-text-3 leading-none hover:text-text-1 transition-colors"
>
</span>
</div>
{/* 본문 스크롤 영역 */}
<div className="flex-1 overflow-y-auto" style={{ padding: '24px', scrollbarWidth: 'thin' }}>
<div className="flex-1 overflow-y-auto p-6" style={{ scrollbarWidth: 'thin' }}>
<div className="flex flex-col gap-4">
{/* 1. 사고개요 */}
<div>
<div className="font-korean" style={{ fontSize: '12px', fontWeight: 700, color: 'var(--cyan)', borderBottom: '1px solid rgba(6,182,212,0.15)', paddingBottom: '4px' }}>
<div className="font-korean text-[12px] font-bold text-primary-cyan border-b pb-1 mb-2" style={{ borderColor: 'rgba(6,182,212,0.15)' }}>
1.
</div>
<div className="font-korean" style={{ fontSize: '12px', lineHeight: 1.7, whiteSpace: 'pre-wrap', marginTop: '8px' }}>
<div className="font-korean text-[12px] leading-[1.7] whitespace-pre-wrap mt-2">
{[
previewReport.incident.name && `사고명: ${previewReport.incident.name}`,
previewReport.incident.occurTime && `발생일시: ${previewReport.incident.occurTime}`,
@ -359,10 +357,10 @@ export function ReportsView() {
{/* 2. 유출현황 */}
<div>
<div className="font-korean" style={{ fontSize: '12px', fontWeight: 700, color: 'var(--cyan)', borderBottom: '1px solid rgba(6,182,212,0.15)', paddingBottom: '4px' }}>
<div className="font-korean text-[12px] font-bold text-primary-cyan border-b pb-1 mb-2" style={{ borderColor: 'rgba(6,182,212,0.15)' }}>
2.
</div>
<div className="font-korean" style={{ fontSize: '12px', lineHeight: 1.7, whiteSpace: 'pre-wrap', marginTop: '8px' }}>
<div className="font-korean text-[12px] leading-[1.7] whitespace-pre-wrap mt-2">
{[
previewReport.incident.pollutant && `유출유종: ${previewReport.incident.pollutant}`,
previewReport.incident.spillAmount && `유출량: ${previewReport.incident.spillAmount}`,
@ -373,20 +371,20 @@ export function ReportsView() {
{/* 3. 초동조치 / 대응현황 */}
<div>
<div className="font-korean" style={{ fontSize: '12px', fontWeight: 700, color: 'var(--cyan)', borderBottom: '1px solid rgba(6,182,212,0.15)', paddingBottom: '4px' }}>
<div className="font-korean text-[12px] font-bold text-primary-cyan border-b pb-1 mb-2" style={{ borderColor: 'rgba(6,182,212,0.15)' }}>
3. /
</div>
<div className="font-korean" style={{ fontSize: '12px', lineHeight: 1.7, whiteSpace: 'pre-wrap', marginTop: '8px' }}>
<div className="font-korean text-[12px] leading-[1.7] whitespace-pre-wrap mt-2">
{previewReport.analysis || '—'}
</div>
</div>
{/* 4. 향후 계획 */}
<div>
<div className="font-korean" style={{ fontSize: '12px', fontWeight: 700, color: 'var(--cyan)', borderBottom: '1px solid rgba(6,182,212,0.15)', paddingBottom: '4px' }}>
<div className="font-korean text-[12px] font-bold text-primary-cyan border-b pb-1 mb-2" style={{ borderColor: 'rgba(6,182,212,0.15)' }}>
4.
</div>
<div className="font-korean" style={{ fontSize: '12px', lineHeight: 1.7, whiteSpace: 'pre-wrap', marginTop: '8px' }}>
<div className="font-korean text-[12px] leading-[1.7] whitespace-pre-wrap mt-2">
{previewReport.etcEquipment || '—'}
</div>
</div>

파일 보기

@ -169,118 +169,118 @@ export function RescueScenarioView() {
}
return (
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, width: '100%', height: '100%', overflow: 'hidden', background: 'var(--bg0)' }}>
<div className="flex flex-col flex-1 w-full h-full overflow-hidden bg-bg-0">
{/* ── Header ── */}
<div style={{ padding: '14px 20px', borderBottom: '1px solid var(--bd)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{ width: 40, height: 40, borderRadius: 10, background: 'linear-gradient(135deg,rgba(6,182,212,.2),rgba(59,130,246,.15))', border: '1px solid rgba(6,182,212,.3)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 18 }}>📊</div>
<div className="px-5 py-3.5 border-b border-border flex items-center justify-between shrink-0">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-[10px] flex items-center justify-center text-lg border border-[rgba(6,182,212,.3)]" style={{ background: 'linear-gradient(135deg,rgba(6,182,212,.2),rgba(59,130,246,.15))' }}>📊</div>
<div>
<div style={{ fontSize: 15, fontWeight: 700 }}> </div>
<div style={{ fontSize: 10, color: 'var(--t3)', marginTop: 2 }}> · (SFR-009)</div>
<div className="text-[15px] font-bold"> </div>
<div className="text-[10px] text-text-3 mt-0.5"> · (SFR-009)</div>
</div>
</div>
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
<select value={selectedIncident} onChange={e => setSelectedIncident(Number(e.target.value))} style={{ padding: '6px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg3)', fontSize: 10, outline: 'none' }}>
<div className="flex gap-2 items-center">
<select value={selectedIncident} onChange={e => setSelectedIncident(Number(e.target.value))} className="px-3 py-1.5 rounded-md border border-border bg-bg-3 text-[10px] outline-none">
{ops.map((op, i) => <option key={op.rescueOpsSn} value={i}>{op.opsCd} · {op.vesselNm}</option>)}
</select>
<button onClick={() => setNewScnModalOpen(true)} style={{ padding: '6px 14px', borderRadius: 6, border: 'none', background: 'linear-gradient(135deg,var(--cyan),#3b82f6)', color: '#fff', fontSize: 10, fontWeight: 700, cursor: 'pointer' }}>+ </button>
<button onClick={() => setNewScnModalOpen(true)} className="px-3.5 py-1.5 rounded-md border-none text-white text-[10px] font-bold cursor-pointer" style={{ background: 'linear-gradient(135deg,var(--cyan),#3b82f6)' }}>+ </button>
</div>
</div>
{/* ── Content: Left List + Right Detail ── */}
<div style={{ display: 'flex', flex: 1, overflow: 'hidden' }}>
<div className="flex flex-1 overflow-hidden">
{/* ═══ LEFT: 시나리오 목록 ═══ */}
<div style={{ width: 360, minWidth: 360, background: 'var(--bg1)', borderRight: '1px solid var(--bd)', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
<div className="w-[360px] min-w-[360px] bg-bg-1 border-r border-border flex flex-col overflow-hidden">
{/* Sort bar */}
<div style={{ padding: '10px 14px', borderBottom: '1px solid var(--bd)', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ fontSize: 11, fontWeight: 700 }}>📋 <span style={{ fontWeight: 400, color: 'var(--t3)', fontSize: 9 }}>({scenarios.length})</span></div>
<div style={{ display: 'flex', gap: 4 }}>
<div className="px-3.5 py-2.5 border-b border-border flex items-center justify-between">
<div className="text-[11px] font-bold">📋 <span className="font-normal text-text-3 text-[9px]">({scenarios.length})</span></div>
<div className="flex gap-1">
{(['time', 'risk'] as const).map(s => (
<button key={s} onClick={() => setSortBy(s)} style={{ padding: '3px 8px', borderRadius: 4, border: `1px solid ${sortBy === s ? 'rgba(6,182,212,.4)' : 'var(--bd)'}`, background: sortBy === s ? 'rgba(6,182,212,.08)' : 'var(--bg3)', color: sortBy === s ? 'var(--cyan)' : 'var(--t3)', fontSize: 9, fontWeight: 600, cursor: 'pointer' }}>{s === 'time' ? '시간순' : '위험도순'}</button>
<button key={s} onClick={() => setSortBy(s)} className="px-2 py-px rounded text-[9px] font-semibold cursor-pointer" style={{ border: `1px solid ${sortBy === s ? 'rgba(6,182,212,.4)' : 'var(--bd)'}`, background: sortBy === s ? 'rgba(6,182,212,.08)' : 'var(--bg3)', color: sortBy === s ? 'var(--cyan)' : 'var(--t3)' }}>{s === 'time' ? '시간순' : '위험도순'}</button>
))}
</div>
</div>
{/* Card list */}
<div style={{ flex: 1, overflowY: 'auto', padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 8, scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
<div className="flex-1 overflow-y-auto px-3 py-2.5 flex flex-col gap-2 scrollbar-thin">
{loading && scenarios.length === 0 && (
<div style={{ textAlign: 'center', padding: '40px 0', fontSize: 11, color: 'var(--t3)' }}> ...</div>
<div className="text-center py-10 text-[11px] text-text-3"> ...</div>
)}
{sorted.map(sc => {
const isSel = selectedId === sc.id
const sev = SEV_STYLE[sc.severity]
return (
<div key={sc.id} onClick={() => setSelectedId(sc.id)} className={`hns-scn-card${isSel ? ' sel' : ''}`}
style={{ padding: '12px', borderRadius: 8, border: `1px solid ${isSel ? 'rgba(6,182,212,.35)' : 'var(--bd)'}`, background: isSel ? 'rgba(6,182,212,.04)' : 'var(--bg3)', cursor: 'pointer', transition: 'all .15s' }}>
<div key={sc.id} onClick={() => setSelectedId(sc.id)} className={`hns-scn-card${isSel ? ' sel' : ''} p-3 rounded-md cursor-pointer transition-all`}
style={{ border: `1px solid ${isSel ? 'rgba(6,182,212,.35)' : 'var(--bd)'}`, background: isSel ? 'rgba(6,182,212,.04)' : 'var(--bg3)' }}>
{/* Top: checkbox + ID + severity */}
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
<div className="flex items-center gap-2 mb-2">
<input type="checkbox" checked={checked.has(sc.id)} onChange={e => { e.stopPropagation(); toggleCheck(sc.id) }} style={{ accentColor: 'var(--cyan)' }} />
<span style={{ fontSize: 12, fontWeight: 800, fontFamily: 'var(--fM)', color: isSel ? 'var(--cyan)' : 'var(--t1)' }}>{sc.id}</span>
<span style={{ padding: '1px 6px', borderRadius: 4, background: sev.bg, color: sev.color, fontSize: 8, fontWeight: 700, fontFamily: 'var(--fM)' }}>{sev.label}</span>
<span style={{ marginLeft: 'auto', fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{sc.timeStep}</span>
<span className="text-xs font-extrabold font-mono" style={{ color: isSel ? 'var(--cyan)' : 'var(--t1)' }}>{sc.id}</span>
<span className="px-1.5 py-px rounded text-[8px] font-bold font-mono" style={{ background: sev.bg, color: sev.color }}>{sev.label}</span>
<span className="ml-auto text-[9px] text-text-3 font-mono">{sc.timeStep}</span>
</div>
{/* Name + time */}
<div style={{ fontSize: 11, fontWeight: 700, marginBottom: 4 }}>{sc.name}</div>
<div style={{ fontSize: 9, color: 'var(--t3)', fontFamily: 'var(--fM)', marginBottom: 8 }}>{sc.datetime}</div>
<div className="text-[11px] font-bold mb-1">{sc.name}</div>
<div className="text-[9px] text-text-3 font-mono mb-2">{sc.datetime}</div>
{/* KPI grid */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 4, marginBottom: 8, fontSize: 8, textAlign: 'center' }}>
<div style={{ padding: '4px 2px', background: 'var(--bg0)', borderRadius: 4 }}>
<div style={{ color: 'var(--t3)' }}>GM</div>
<div style={{ fontWeight: 700, fontFamily: 'var(--fM)', color: gmColor(parseFloat(sc.gm)) }}>{sc.gm}m</div>
<div className="grid grid-cols-4 gap-1 mb-2 text-[8px] text-center">
<div className="py-1 bg-bg-0 rounded">
<div className="text-text-3">GM</div>
<div className="font-bold font-mono" style={{ color: gmColor(parseFloat(sc.gm)) }}>{sc.gm}m</div>
</div>
<div style={{ padding: '4px 2px', background: 'var(--bg0)', borderRadius: 4 }}>
<div style={{ color: 'var(--t3)' }}></div>
<div style={{ fontWeight: 700, fontFamily: 'var(--fM)', color: listColor(parseFloat(sc.list)) }}>{sc.list}°</div>
<div className="py-1 bg-bg-0 rounded">
<div className="text-text-3"></div>
<div className="font-bold font-mono" style={{ color: listColor(parseFloat(sc.list)) }}>{sc.list}°</div>
</div>
<div style={{ padding: '4px 2px', background: 'var(--bg0)', borderRadius: 4 }}>
<div style={{ color: 'var(--t3)' }}></div>
<div style={{ fontWeight: 700, fontFamily: 'var(--fM)', color: buoyColor(sc.buoyancy) }}>{sc.buoyancy}%</div>
<div className="py-1 bg-bg-0 rounded">
<div className="text-text-3"></div>
<div className="font-bold font-mono" style={{ color: buoyColor(sc.buoyancy) }}>{sc.buoyancy}%</div>
</div>
<div style={{ padding: '4px 2px', background: 'var(--bg0)', borderRadius: 4 }}>
<div style={{ color: 'var(--t3)' }}></div>
<div style={{ fontWeight: 700, fontFamily: 'var(--fM)', color: oilColor(parseFloat(sc.oilRate)) }}>{sc.oilRate.split(' ')[0]}</div>
<div className="py-1 bg-bg-0 rounded">
<div className="text-text-3"></div>
<div className="font-bold font-mono" style={{ color: oilColor(parseFloat(sc.oilRate)) }}>{sc.oilRate.split(' ')[0]}</div>
</div>
</div>
{/* Description */}
<div style={{ fontSize: 9, color: 'var(--t2)', lineHeight: 1.5 }}>{sc.description}</div>
<div className="text-[9px] text-text-2 leading-[1.5]">{sc.description}</div>
</div>
)
})}
</div>
{/* Bottom actions */}
<div style={{ padding: '10px 14px', borderTop: '1px solid var(--bd)', display: 'flex', gap: 6 }}>
<button onClick={() => setDetailView(1)} style={{ flex: 1, padding: '8px', borderRadius: 6, border: 'none', background: 'linear-gradient(135deg,var(--cyan),#3b82f6)', color: '#fff', fontSize: 10, fontWeight: 700, cursor: 'pointer' }}>📊 </button>
<button style={{ padding: '8px 14px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg3)', color: 'var(--t2)', fontSize: 10, fontWeight: 600, cursor: 'pointer' }}>📄 </button>
<div className="px-3.5 py-2.5 border-t border-border flex gap-1.5">
<button onClick={() => setDetailView(1)} className="flex-1 py-2 rounded-md border-none text-white text-[10px] font-bold cursor-pointer" style={{ background: 'linear-gradient(135deg,var(--cyan),#3b82f6)' }}>📊 </button>
<button className="px-3.5 py-2 rounded-md border border-border bg-bg-3 text-text-2 text-[10px] font-semibold cursor-pointer">📄 </button>
</div>
</div>
{/* ═══ RIGHT: 상세/비교 ═══ */}
<div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
<div className="flex-1 min-w-0 flex flex-col overflow-hidden">
{/* Detail tabs */}
<div style={{ display: 'flex', borderBottom: '1px solid var(--bd)', flexShrink: 0 }}>
<div className="flex border-b border-border shrink-0">
{(['📋 시나리오 상세', '📊 비교 차트', '🗺 지도 오버레이'] as const).map((label, i) => (
<button key={i} onClick={() => setDetailView(i as DetailView)} className="rsc-atab" style={{ flex: 1, padding: '9px 4px', border: 'none', background: detailView === i ? 'rgba(6,182,212,.04)' : 'transparent', color: detailView === i ? 'var(--cyan)' : 'var(--t3)', fontSize: 10, fontWeight: detailView === i ? 700 : 600, cursor: 'pointer', borderBottom: `2px solid ${detailView === i ? 'var(--cyan)' : 'transparent'}`, transition: '.2s' }}>{label}</button>
<button key={i} onClick={() => setDetailView(i as DetailView)} className="rsc-atab flex-1 py-2.5 px-1 border-none cursor-pointer transition-all text-[10px]" style={{ background: detailView === i ? 'rgba(6,182,212,.04)' : 'transparent', color: detailView === i ? 'var(--cyan)' : 'var(--t3)', fontWeight: detailView === i ? 700 : 600, borderBottom: `2px solid ${detailView === i ? 'var(--cyan)' : 'transparent'}` }}>{label}</button>
))}
</div>
{/* View content */}
<div style={{ flex: 1, overflowY: 'auto', scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
<div className="flex-1 overflow-y-auto scrollbar-thin">
{/* ─── VIEW 0: 시나리오 상세 ─── */}
{detailView === 0 && selected && (
<div style={{ padding: 20 }}>
<div className="p-5">
{/* Gradient header */}
<div style={{ padding: '16px 20px', borderRadius: 10, background: 'linear-gradient(135deg,rgba(6,182,212,.06),rgba(239,68,68,.04))', border: '1px solid rgba(6,182,212,.2)', marginBottom: 16 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
<span style={{ fontSize: 16, fontWeight: 800, fontFamily: 'var(--fM)', color: 'var(--cyan)' }}>{selected.id}</span>
<span style={{ fontSize: 14, fontWeight: 700 }}>{selected.name}</span>
<span style={{ padding: '2px 8px', borderRadius: 4, background: SEV_STYLE[selected.severity].bg, color: SEV_STYLE[selected.severity].color, fontSize: 9, fontWeight: 700, fontFamily: 'var(--fM)' }}>{selected.severity}</span>
<span style={{ marginLeft: 'auto', fontSize: 10, color: 'var(--t3)', fontFamily: 'var(--fM)' }}>{selected.datetime}</span>
<div className="px-5 py-4 rounded-[10px] border border-[rgba(6,182,212,.2)] mb-4" style={{ background: 'linear-gradient(135deg,rgba(6,182,212,.06),rgba(239,68,68,.04))' }}>
<div className="flex items-center gap-2.5 mb-2.5">
<span className="text-base font-extrabold font-mono text-primary-cyan">{selected.id}</span>
<span className="text-sm font-bold">{selected.name}</span>
<span className="px-2 py-px rounded text-[9px] font-bold font-mono" style={{ background: SEV_STYLE[selected.severity].bg, color: SEV_STYLE[selected.severity].color }}>{selected.severity}</span>
<span className="ml-auto text-[10px] text-text-3 font-mono">{selected.datetime}</span>
</div>
{/* 6 KPI cards */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(6,1fr)', gap: 8, fontSize: 8, textAlign: 'center' }}>
<div className="grid gap-2 text-[8px] text-center" style={{ gridTemplateColumns: 'repeat(6,1fr)' }}>
{[
{ label: 'GM (복원심)', value: `${selected.gm}m`, color: gmColor(parseFloat(selected.gm)) },
{ label: '횡경사 (List)', value: `${selected.list}°`, color: listColor(parseFloat(selected.list)) },
@ -289,36 +289,36 @@ export function RescueScenarioView() {
{ label: '유출률', value: selected.oilRate, color: oilColor(parseFloat(selected.oilRate)) },
{ label: 'BM 비율', value: selected.bmRatio, color: parseFloat(selected.bmRatio) > 100 ? 'var(--red)' : parseFloat(selected.bmRatio) > 85 ? 'var(--orange)' : 'var(--green)' },
].map(kpi => (
<div key={kpi.label} style={{ padding: '8px 4px', background: 'var(--bg3)', borderRadius: 6, border: '1px solid var(--bd)' }}>
<div style={{ color: 'var(--t3)', marginBottom: 4 }}>{kpi.label}</div>
<div style={{ fontSize: 18, fontWeight: 800, fontFamily: 'var(--fM)', color: kpi.color }}>{kpi.value}</div>
<div key={kpi.label} className="px-1 py-2 bg-bg-3 rounded-md border border-border">
<div className="text-text-3 mb-1">{kpi.label}</div>
<div className="text-lg font-extrabold font-mono" style={{ color: kpi.color }}>{kpi.value}</div>
</div>
))}
</div>
</div>
{/* 2-column: 침수구획 + 구난판단 */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 16 }}>
<div className="grid grid-cols-2 gap-4 mb-4">
{/* 침수 구획 */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 8, padding: 14 }}>
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--cyan)', marginBottom: 10 }}>🚢 </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<div className="bg-bg-3 border border-border rounded-md p-3.5">
<div className="text-[11px] font-bold text-primary-cyan mb-2.5">🚢 </div>
<div className="flex flex-col gap-1.5">
{selected.compartments.map((c, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '6px 10px', background: 'var(--bg0)', borderRadius: 4, borderLeft: `3px solid ${c.color}` }}>
<span style={{ fontSize: 9 }}>{c.name}</span>
<span style={{ fontSize: 9, fontFamily: 'var(--fM)', color: c.color, fontWeight: 600 }}>{c.status}</span>
<div key={i} className="flex items-center justify-between px-2.5 py-1.5 bg-bg-0 rounded" style={{ borderLeft: `3px solid ${c.color}` }}>
<span className="text-[9px]">{c.name}</span>
<span className="text-[9px] font-semibold font-mono" style={{ color: c.color }}>{c.status}</span>
</div>
))}
</div>
</div>
{/* 구난 판단 */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 8, padding: 14 }}>
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--red)', marginBottom: 10 }}> </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<div className="bg-bg-3 border border-border rounded-md p-3.5">
<div className="text-[11px] font-bold text-status-red mb-2.5"> </div>
<div className="flex flex-col gap-1.5">
{selected.assessment.map((a, i) => (
<div key={i} style={{ padding: '8px 10px', background: 'var(--bg0)', borderRadius: 4, borderLeft: `3px solid ${a.color}` }}>
<div style={{ fontSize: 9, fontWeight: 700, color: a.color }}>{a.label}</div>
<div style={{ fontSize: 9, color: 'var(--t2)', marginTop: 2 }}>{a.value}</div>
<div key={i} className="px-2.5 py-2 bg-bg-0 rounded" style={{ borderLeft: `3px solid ${a.color}` }}>
<div className="text-[9px] font-bold" style={{ color: a.color }}>{a.label}</div>
<div className="text-[9px] text-text-2 mt-0.5">{a.value}</div>
</div>
))}
</div>
@ -326,14 +326,14 @@ export function RescueScenarioView() {
</div>
{/* 대응 조치 이력 */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 8, padding: 14 }}>
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--orange)', marginBottom: 10 }}>📋 </div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<div className="bg-bg-3 border border-border rounded-md p-3.5">
<div className="text-[11px] font-bold text-status-orange mb-2.5">📋 </div>
<div className="flex flex-col gap-1.5">
{selected.actions.map((a, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 10px', background: 'var(--bg0)', borderRadius: 4 }}>
<span style={{ fontSize: 10, fontWeight: 700, fontFamily: 'var(--fM)', color: a.color, minWidth: 40 }}>{a.time}</span>
<div style={{ width: 6, height: 6, borderRadius: '50%', background: a.color, flexShrink: 0 }} />
<span style={{ fontSize: 9 }}>{a.text}</span>
<div key={i} className="flex items-center gap-2.5 px-2.5 py-1.5 bg-bg-0 rounded">
<span className="text-[10px] font-bold font-mono min-w-[40px]" style={{ color: a.color }}>{a.time}</span>
<div className="w-1.5 h-1.5 rounded-full shrink-0" style={{ background: a.color }} />
<span className="text-[9px]">{a.text}</span>
</div>
))}
</div>
@ -346,21 +346,21 @@ export function RescueScenarioView() {
{/* ─── VIEW 2: 지도 오버레이 ─── */}
{detailView === 2 && (
<div style={{ padding: 20 }}>
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 20, textAlign: 'center' }}>
<div style={{ fontSize: 32, opacity: 0.3, marginBottom: 10 }}>🗺</div>
<div style={{ fontSize: 13, fontWeight: 700, marginBottom: 6 }}>GIS </div>
<div style={{ fontSize: 10, color: 'var(--t3)', lineHeight: 1.6, marginBottom: 16 }}> .</div>
<div style={{ display: 'flex', gap: 8, justifyContent: 'center', flexWrap: 'wrap' }}>
<div className="p-5">
<div className="bg-bg-3 border border-border rounded-[10px] p-5 text-center">
<div className="text-[32px] opacity-30 mb-2.5">🗺</div>
<div className="text-[13px] font-bold mb-1.5">GIS </div>
<div className="text-[10px] text-text-3 leading-relaxed mb-4"> .</div>
<div className="flex gap-2 justify-center flex-wrap">
{scenarios.map(sc => (
<div key={sc.id} style={{ padding: '6px 12px', borderRadius: 6, border: `1px solid ${SEV_STYLE[sc.severity].color}40`, background: SEV_STYLE[sc.severity].bg, fontSize: 9 }}>
<span style={{ fontWeight: 700, color: SEV_STYLE[sc.severity].color }}>{sc.id}</span>
<span style={{ color: 'var(--t2)', marginLeft: 6 }}>{sc.name}</span>
<div key={sc.id} className="px-3 py-1.5 rounded-md text-[9px]" style={{ border: `1px solid ${SEV_STYLE[sc.severity].color}40`, background: SEV_STYLE[sc.severity].bg }}>
<span className="font-bold" style={{ color: SEV_STYLE[sc.severity].color }}>{sc.id}</span>
<span className="text-text-2 ml-1.5">{sc.name}</span>
</div>
))}
</div>
<div style={{ marginTop: 16, padding: 30, background: 'var(--bg0)', borderRadius: 8, border: '1px dashed var(--bd)' }}>
<div style={{ fontSize: 11, color: 'var(--t3)' }}> </div>
<div className="mt-4 p-[30px] bg-bg-0 rounded-md border border-dashed border-border">
<div className="text-[11px] text-text-3"> </div>
</div>
</div>
</div>
@ -386,170 +386,169 @@ function NewScenarioModal({ ops, onClose }: { ops: RescueOpsItem[]; onClose: ()
setTimeout(() => { setSubmitting(false); setDone(true) }, 2000)
}
/* ── shared styles ── */
const labelSt: React.CSSProperties = { fontSize: 9, color: 'var(--t3)', display: 'block', marginBottom: 4 }
const inputSt: React.CSSProperties = { width: '100%', padding: '8px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 11, outline: 'none', boxSizing: 'border-box' as const }
const numSt: React.CSSProperties = { ...inputSt, fontFamily: 'var(--fM)' }
const selSt = inputSt
/* ── shared className helpers ── */
const labelCls = 'text-[9px] text-text-3 block mb-1'
const inputCls = 'w-full px-3 py-2 rounded-md border border-border bg-bg-0 text-[11px] outline-none'
const numCls = `${inputCls} font-mono`
const sectionIcon = (n: number) => (
<div style={{ width: 18, height: 18, borderRadius: 5, background: 'rgba(6,182,212,.12)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 9, fontWeight: 700, color: 'var(--cyan)' }}>{n}</div>
<div className="w-[18px] h-[18px] rounded-[5px] flex items-center justify-center text-[9px] font-bold text-primary-cyan" style={{ background: 'rgba(6,182,212,.12)' }}>{n}</div>
)
const sectionTitle: React.CSSProperties = { fontSize: 11, fontWeight: 700, color: 'var(--cyan)', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }
const sectionTitleCls = 'text-[11px] font-bold text-primary-cyan mb-2.5 flex items-center gap-1.5'
return (
<div ref={overlayRef} onClick={e => { if (e.target === overlayRef.current) onClose() }}
style={{ position: 'fixed', inset: 0, zIndex: 9999, background: 'rgba(0,0,0,.65)', backdropFilter: 'blur(6px)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<div style={{ background: 'var(--bg1)', border: '1px solid rgba(6,182,212,.3)', borderRadius: 14, width: 700, maxHeight: '88vh', display: 'flex', flexDirection: 'column', boxShadow: '0 24px 80px rgba(0,0,0,.6)', overflow: 'hidden' }}>
className="fixed inset-0 z-[9999] flex items-center justify-center" style={{ background: 'rgba(0,0,0,.65)', backdropFilter: 'blur(6px)' }}>
<div className="bg-bg-1 border border-[rgba(6,182,212,.3)] rounded-[14px] w-[700px] max-h-[88vh] flex flex-col overflow-hidden" style={{ boxShadow: '0 24px 80px rgba(0,0,0,.6)' }}>
{/* ── 헤더 ── */}
<div style={{ padding: '20px 24px 16px', borderBottom: '1px solid var(--bd)', flexShrink: 0, position: 'relative', overflow: 'hidden' }}>
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 2, background: 'linear-gradient(90deg,#06b6d4,#3b82f6,#8b5cf6)' }} />
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 36, height: 36, borderRadius: 10, background: 'linear-gradient(135deg,rgba(6,182,212,.15),rgba(59,130,246,.08))', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 18 }}>🚨</div>
<div className="px-6 pt-5 pb-4 border-b border-border shrink-0 relative overflow-hidden">
<div className="absolute top-0 left-0 right-0 h-0.5" style={{ background: 'linear-gradient(90deg,#06b6d4,#3b82f6,#8b5cf6)' }} />
<div className="flex items-center justify-between">
<div className="flex items-center gap-2.5">
<div className="w-9 h-9 rounded-[10px] flex items-center justify-center text-lg" style={{ background: 'linear-gradient(135deg,rgba(6,182,212,.15),rgba(59,130,246,.08))' }}>🚨</div>
<div>
<div style={{ fontSize: 15, fontWeight: 700 }}> </div>
<div style={{ fontSize: 10, color: 'var(--t3)', marginTop: 2 }}> (SFR-009)</div>
<div className="text-[15px] font-bold"> </div>
<div className="text-[10px] text-text-3 mt-0.5"> (SFR-009)</div>
</div>
</div>
<span onClick={onClose} style={{ fontSize: 18, cursor: 'pointer', color: 'var(--t3)', padding: 4 }}></span>
<span onClick={onClose} className="text-lg cursor-pointer text-text-3 p-1"></span>
</div>
</div>
{/* ── 본문 스크롤 ── */}
<div style={{ flex: 1, overflowY: 'auto', padding: '20px 24px', display: 'flex', flexDirection: 'column', gap: 18, scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }}>
<div className="flex-1 overflow-y-auto px-6 py-5 flex flex-col gap-[18px] scrollbar-thin">
{/* ① 기본 정보 */}
<div>
<div style={sectionTitle}>{sectionIcon(1)} </div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
<div className={sectionTitleCls}>{sectionIcon(1)} </div>
<div className="grid grid-cols-2 gap-2.5">
<div>
<label style={labelSt}> <span style={{ color: '#f87171' }}>*</span></label>
<input type="text" placeholder="예: T+3h 기관실 침수 확대" style={inputSt} />
<label className={labelCls}> <span className="text-[#f87171]">*</span></label>
<input type="text" placeholder="예: T+3h 기관실 침수 확대" className={inputCls} />
</div>
<div>
<label style={labelSt}> <span style={{ color: '#f87171' }}>*</span></label>
<select defaultValue="0" style={selSt}>
<label className={labelCls}> <span className="text-[#f87171]">*</span></label>
<select defaultValue="0" className={inputCls}>
{ops.map((op, i) => <option key={op.rescueOpsSn} value={i}>{op.opsCd} · {op.vesselNm}</option>)}
<option value="new">+ ...</option>
</select>
</div>
<div>
<label style={labelSt}> (Time Step) <span style={{ color: '#f87171' }}>*</span></label>
<select defaultValue="T+3h" style={selSt}>
<label className={labelCls}> (Time Step) <span className="text-[#f87171]">*</span></label>
<select defaultValue="T+3h" className={inputCls}>
{['T+0h (사고 발생 직후)', 'T+1h', 'T+2h', 'T+3h', 'T+6h', 'T+12h', 'T+24h', 'T+48h'].map(t => <option key={t} value={t}>{t}</option>)}
</select>
</div>
<div>
<label style={labelSt}> </label>
<input type="datetime-local" defaultValue="2024-10-27T13:30" style={inputSt} />
<label className={labelCls}> </label>
<input type="datetime-local" defaultValue="2024-10-27T13:30" className={inputCls} />
</div>
</div>
</div>
{/* ② 선박 정보 */}
<div>
<div style={sectionTitle}>{sectionIcon(2)} </div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 10 }}>
<div className={sectionTitleCls}>{sectionIcon(2)} </div>
<div className="grid grid-cols-3 gap-2.5">
<div>
<label style={labelSt}> <span style={{ color: '#f87171' }}>*</span></label>
<input type="text" defaultValue="M/V SEA GUARDIAN" style={inputSt} />
<label className={labelCls}> <span className="text-[#f87171]">*</span></label>
<input type="text" defaultValue="M/V SEA GUARDIAN" className={inputCls} />
</div>
<div>
<label style={labelSt}> <span style={{ color: '#f87171' }}>*</span></label>
<select defaultValue="화물선 (Cargo)" style={selSt}>
<label className={labelCls}> <span className="text-[#f87171]">*</span></label>
<select defaultValue="화물선 (Cargo)" className={inputCls}>
{['유조선 (Tanker)', '화물선 (Cargo)', '컨테이너선 (Container)', '여객선 (Passenger)', '어선 (Fishing)', 'LNG선', '케미컬선 (Chemical)', '기타'].map(v => <option key={v}>{v}</option>)}
</select>
</div>
<div>
<label style={labelSt}> (GT)</label>
<input type="number" defaultValue={45000} step={100} min={0} style={numSt} />
<label className={labelCls}> (GT)</label>
<input type="number" defaultValue={45000} step={100} min={0} className={numCls} />
</div>
<div>
<label style={labelSt}> (m)</label>
<input type="number" defaultValue={189} step={1} min={0} style={numSt} />
<label className={labelCls}> (m)</label>
<input type="number" defaultValue={189} step={1} min={0} className={numCls} />
</div>
<div>
<label style={labelSt}> (m)</label>
<input type="number" defaultValue={8.5} step={0.1} min={0} style={numSt} />
<label className={labelCls}> (m)</label>
<input type="number" defaultValue={8.5} step={0.1} min={0} className={numCls} />
</div>
<div>
<label style={labelSt}> </label>
<input type="number" defaultValue={22} step={1} min={0} style={numSt} />
<label className={labelCls}> </label>
<input type="number" defaultValue={22} step={1} min={0} className={numCls} />
</div>
</div>
<div style={{ marginTop: 8, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
<div className="mt-2 grid grid-cols-2 gap-2.5">
<div>
<label style={labelSt}> </label>
<input type="text" placeholder="예: 일반화물, 벙커C 450kL" style={inputSt} />
<label className={labelCls}> </label>
<input type="text" placeholder="예: 일반화물, 벙커C 450kL" className={inputCls} />
</div>
<div>
<label style={labelSt}> · </label>
<input type="text" defaultValue="벙커C 450kL / MGO 80kL" style={inputSt} />
<label className={labelCls}> · </label>
<input type="text" defaultValue="벙커C 450kL / MGO 80kL" className={inputCls} />
</div>
</div>
</div>
{/* ③ 사고 조건 · 선체 상태 */}
<div>
<div style={sectionTitle}>{sectionIcon(3)} · </div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 10 }}>
<div className={sectionTitleCls}>{sectionIcon(3)} · </div>
<div className="grid grid-cols-3 gap-2.5">
<div>
<label style={labelSt}> <span style={{ color: '#f87171' }}>*</span></label>
<select defaultValue="충돌 (Collision)" style={selSt}>
<label className={labelCls}> <span className="text-[#f87171]">*</span></label>
<select defaultValue="충돌 (Collision)" className={inputCls}>
{['충돌 (Collision)', '좌초 (Grounding)', '침수 (Flooding)', '기관고장 (Engine Failure)', '화재 (Fire)', '전복 (Capsizing)', '구조손상 (Structural)'].map(v => <option key={v}>{v}</option>)}
</select>
</div>
<div>
<label style={labelSt}> </label>
<select defaultValue="우현 중앙 (Starboard Mid)" style={selSt}>
<label className={labelCls}> </label>
<select defaultValue="우현 중앙 (Starboard Mid)" className={inputCls}>
{['선수부 (Bow)', '우현 중앙 (Starboard Mid)', '좌현 중앙 (Port Mid)', '선미부 (Stern)', '기관실 (Engine Room)', '선저 (Bottom)', '복수 구역'].map(v => <option key={v}>{v}</option>)}
</select>
</div>
<div>
<label style={labelSt}> ()</label>
<input type="number" defaultValue={2.8} step={0.1} min={0} style={numSt} />
<label className={labelCls}> ()</label>
<input type="number" defaultValue={2.8} step={0.1} min={0} className={numCls} />
</div>
</div>
<div style={{ marginTop: 8, display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 10 }}>
<div className="mt-2 grid grid-cols-4 gap-2.5">
<div>
<label style={labelSt}> (°)</label>
<input type="number" defaultValue={8.5} step={0.5} style={numSt} />
<label className={labelCls}> (°)</label>
<input type="number" defaultValue={8.5} step={0.5} className={numCls} />
</div>
<div>
<label style={labelSt}> (°)</label>
<input type="number" defaultValue={2.1} step={0.1} style={numSt} />
<label className={labelCls}> (°)</label>
<input type="number" defaultValue={2.1} step={0.1} className={numCls} />
</div>
<div>
<label style={labelSt}> (m)</label>
<input type="number" defaultValue={3.2} step={0.1} min={0} style={numSt} />
<label className={labelCls}> (m)</label>
<input type="number" defaultValue={3.2} step={0.1} min={0} className={numCls} />
</div>
<div>
<label style={labelSt}>GM (m)</label>
<input type="number" defaultValue={1.8} step={0.1} style={numSt} />
<label className={labelCls}>GM (m)</label>
<input type="number" defaultValue={1.8} step={0.1} className={numCls} />
</div>
</div>
{/* 침수 상태 */}
<div style={{ marginTop: 10, padding: '10px 14px', background: 'rgba(239,68,68,.04)', border: '1px solid rgba(239,68,68,.12)', borderRadius: 8 }}>
<div style={{ fontSize: 9, fontWeight: 700, color: '#f87171', marginBottom: 8 }}>💧 </div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr', gap: 8 }}>
<div className="mt-2.5 px-3.5 py-2.5 rounded-md border border-[rgba(239,68,68,.12)]" style={{ background: 'rgba(239,68,68,.04)' }}>
<div className="text-[9px] font-bold text-[#f87171] mb-2">💧 </div>
<div className="grid grid-cols-4 gap-2">
<div>
<label style={{ fontSize: 8, color: 'var(--t3)', display: 'block', marginBottom: 3 }}> </label>
<select defaultValue="2개" style={{ width: '100%', padding: '6px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, outline: 'none', boxSizing: 'border-box' as const }}>
<label className="text-[8px] text-text-3 block mb-px"> </label>
<select defaultValue="2개" className="w-full px-2 py-1.5 rounded border border-border bg-bg-0 text-[10px] outline-none">
{['1개', '2개', '3개', '4개 이상'].map(v => <option key={v}>{v}</option>)}
</select>
</div>
<div>
<label style={{ fontSize: 8, color: 'var(--t3)', display: 'block', marginBottom: 3 }}> ()</label>
<input type="number" defaultValue={850} step={50} min={0} style={{ width: '100%', padding: '6px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', boxSizing: 'border-box' as const }} />
<label className="text-[8px] text-text-3 block mb-px"> ()</label>
<input type="number" defaultValue={850} step={50} min={0} className="w-full px-2 py-1.5 rounded border border-border bg-bg-0 text-[10px] font-mono outline-none" />
</div>
<div>
<label style={{ fontSize: 8, color: 'var(--t3)', display: 'block', marginBottom: 3 }}> (t/h)</label>
<input type="number" defaultValue={120} step={10} min={0} style={{ width: '100%', padding: '6px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', boxSizing: 'border-box' as const }} />
<label className="text-[8px] text-text-3 block mb-px"> (t/h)</label>
<input type="number" defaultValue={120} step={10} min={0} className="w-full px-2 py-1.5 rounded border border-border bg-bg-0 text-[10px] font-mono outline-none" />
</div>
<div>
<label style={{ fontSize: 8, color: 'var(--t3)', display: 'block', marginBottom: 3 }}> (t/h)</label>
<input type="number" defaultValue={80} step={10} min={0} style={{ width: '100%', padding: '6px 8px', borderRadius: 4, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', boxSizing: 'border-box' as const }} />
<label className="text-[8px] text-text-3 block mb-px"> (t/h)</label>
<input type="number" defaultValue={80} step={10} min={0} className="w-full px-2 py-1.5 rounded border border-border bg-bg-0 text-[10px] font-mono outline-none" />
</div>
</div>
</div>
@ -557,85 +556,85 @@ function NewScenarioModal({ ops, onClose }: { ops: RescueOpsItem[]; onClose: ()
{/* ④ 사고 위치 · 해상 조건 */}
<div>
<div style={sectionTitle}>{sectionIcon(4)} · </div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr auto', gap: 10, alignItems: 'end' }}>
<div className={sectionTitleCls}>{sectionIcon(4)} · </div>
<div className="grid gap-2.5 items-end" style={{ gridTemplateColumns: '1fr 1fr auto' }}>
<div>
<label style={labelSt}> (Lat)</label>
<input type="text" defaultValue="34.5832" style={{ ...inputSt, fontFamily: 'var(--fM)' }} />
<label className={labelCls}> (Lat)</label>
<input type="text" defaultValue="34.5832" className={`${inputCls} font-mono`} />
</div>
<div>
<label style={labelSt}> (Lon)</label>
<input type="text" defaultValue="128.4217" style={{ ...inputSt, fontFamily: 'var(--fM)' }} />
<label className={labelCls}> (Lon)</label>
<input type="text" defaultValue="128.4217" className={`${inputCls} font-mono`} />
</div>
<button style={{ padding: '8px 14px', borderRadius: 6, border: '1px solid rgba(6,182,212,.3)', background: 'rgba(6,182,212,.08)', color: 'var(--cyan)', fontSize: 10, fontWeight: 600, cursor: 'pointer', whiteSpace: 'nowrap' }}>📍 </button>
<button className="px-3.5 py-2 rounded-md border border-[rgba(6,182,212,.3)] text-primary-cyan text-[10px] font-semibold cursor-pointer whitespace-nowrap" style={{ background: 'rgba(6,182,212,.08)' }}>📍 </button>
</div>
<div style={{ marginTop: 8, display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 10 }}>
<div className="mt-2 grid grid-cols-4 gap-2.5">
<div>
<label style={labelSt}> / <span style={{ color: '#f87171' }}>*</span></label>
<div style={{ display: 'flex', gap: 4 }}>
<select defaultValue="SW" style={{ flex: 1, padding: 8, borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, outline: 'none' }}>
<label className={labelCls}> / <span className="text-[#f87171]">*</span></label>
<div className="flex gap-1">
<select defaultValue="SW" className="flex-1 p-2 rounded-md border border-border bg-bg-0 text-[10px] outline-none">
{['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'].map(d => <option key={d}>{d}</option>)}
</select>
<input type="number" defaultValue={12.5} step={0.5} min={0} style={{ width: 60, padding: 8, borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', textAlign: 'center' }} />
<input type="number" defaultValue={12.5} step={0.5} min={0} className="w-[60px] p-2 rounded-md border border-border bg-bg-0 text-[10px] font-mono outline-none text-center" />
</div>
</div>
<div>
<label style={labelSt}> (m) <span style={{ color: '#f87171' }}>*</span></label>
<input type="number" defaultValue={2.5} step={0.1} min={0} style={numSt} />
<label className={labelCls}> (m) <span className="text-[#f87171]">*</span></label>
<input type="number" defaultValue={2.5} step={0.1} min={0} className={numCls} />
</div>
<div>
<label style={labelSt}> / </label>
<div style={{ display: 'flex', gap: 4 }}>
<select defaultValue="NE" style={{ flex: 1, padding: 8, borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, outline: 'none' }}>
<label className={labelCls}> / </label>
<div className="flex gap-1">
<select defaultValue="NE" className="flex-1 p-2 rounded-md border border-border bg-bg-0 text-[10px] outline-none">
{['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'].map(d => <option key={d}>{d}</option>)}
</select>
<input type="number" defaultValue={1.2} step={0.1} min={0} style={{ width: 60, padding: 8, borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, fontFamily: 'var(--fM)', outline: 'none', textAlign: 'center' }} />
<input type="number" defaultValue={1.2} step={0.1} min={0} className="w-[60px] p-2 rounded-md border border-border bg-bg-0 text-[10px] font-mono outline-none text-center" />
</div>
</div>
<div>
<label style={labelSt}> (m)</label>
<input type="number" defaultValue={25} step={1} min={0} style={numSt} />
<label className={labelCls}> (m)</label>
<input type="number" defaultValue={25} step={1} min={0} className={numCls} />
</div>
</div>
<div style={{ marginTop: 6, display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 10 }}>
<div className="mt-1.5 grid grid-cols-3 gap-2.5">
<div>
<label style={labelSt}> (km)</label>
<input type="number" defaultValue={8} step={1} min={0} style={numSt} />
<label className={labelCls}> (km)</label>
<input type="number" defaultValue={8} step={1} min={0} className={numCls} />
</div>
<div>
<label style={labelSt}> (Douglas)</label>
<select defaultValue="3 — 거침 (Moderate)" style={selSt}>
<label className={labelCls}> (Douglas)</label>
<select defaultValue="3 — 거침 (Moderate)" className={inputCls}>
{['0 — 평온 (Calm)', '1 — 잔잔 (Smooth)', '2 — 약간 거침 (Slight)', '3 — 거침 (Moderate)', '4 — 다소 높음 (Rough)', '5 — 높음 (Very rough)', '6 — 매우 높음 (High)'].map(v => <option key={v}>{v}</option>)}
</select>
</div>
<div>
<label style={labelSt}> (°C)</label>
<input type="number" defaultValue={17.2} step={0.5} style={numSt} />
<label className={labelCls}> (°C)</label>
<input type="number" defaultValue={17.2} step={0.5} className={numCls} />
</div>
</div>
</div>
{/* ⑤ 구난 분석 설정 */}
<div>
<div style={sectionTitle}>{sectionIcon(5)} </div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 10 }}>
<div className={sectionTitleCls}>{sectionIcon(5)} </div>
<div className="grid grid-cols-3 gap-2.5">
<div>
<label style={labelSt}> <span style={{ color: '#f87171' }}>*</span></label>
<select defaultValue="R&D 긴급구난 종합분석" style={selSt}>
<label className={labelCls}> <span className="text-[#f87171]">*</span></label>
<select defaultValue="R&D 긴급구난 종합분석" className={inputCls}>
{['R&D 긴급구난 종합분석', 'HECSALV (Salvage)', 'NAPA Emergency', '수동 입력 (직접 판단)'].map(v => <option key={v}>{v}</option>)}
</select>
</div>
<div>
<label style={labelSt}> </label>
<select defaultValue="12시간" style={selSt}>
<label className={labelCls}> </label>
<select defaultValue="12시간" className={inputCls}>
{['6시간', '12시간', '24시간', '48시간', '72시간'].map(v => <option key={v}>{v}</option>)}
</select>
</div>
<div>
<label style={labelSt}> </label>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginTop: 2 }}>
<label className={labelCls}> </label>
<div className="flex flex-wrap gap-1 mt-0.5">
{['복원성', '예인력', '인양력', '유출 위험'].map(item => (
<label key={item} style={{ display: 'flex', alignItems: 'center', gap: 3, fontSize: 8, color: 'var(--t2)', cursor: 'pointer' }}>
<label key={item} className="flex items-center gap-px text-[8px] text-text-2 cursor-pointer">
<input type="checkbox" defaultChecked style={{ accentColor: 'var(--cyan)', transform: 'scale(.85)' }} />{item}
</label>
))}
@ -643,37 +642,37 @@ function NewScenarioModal({ ops, onClose }: { ops: RescueOpsItem[]; onClose: ()
</div>
</div>
{/* R&D 연계 분석 */}
<div style={{ marginTop: 10, padding: '10px 14px', background: 'rgba(249,115,22,.04)', border: '1px solid rgba(249,115,22,.12)', borderRadius: 8, display: 'flex', flexDirection: 'column', gap: 6 }}>
<div style={{ fontSize: 9, fontWeight: 700, color: 'var(--orange)' }}>🔗 R&D </div>
<div style={{ display: 'flex', gap: 12 }}>
<label style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 9, color: 'var(--t2)', cursor: 'pointer' }}>
<div className="mt-2.5 px-3.5 py-2.5 flex flex-col gap-1.5 rounded-md border border-[rgba(249,115,22,.12)]" style={{ background: 'rgba(249,115,22,.04)' }}>
<div className="text-[9px] font-bold text-status-orange">🔗 R&D </div>
<div className="flex gap-3">
<label className="flex items-center gap-1 text-[9px] text-text-2 cursor-pointer">
<input type="checkbox" style={{ accentColor: 'var(--orange)' }} />
</label>
<label style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 9, color: 'var(--t2)', cursor: 'pointer' }}>
<label className="flex items-center gap-1 text-[9px] text-text-2 cursor-pointer">
<input type="checkbox" style={{ accentColor: 'var(--orange)' }} /> HNS
</label>
</div>
<div style={{ fontSize: 8, color: 'var(--t3)', lineHeight: 1.5 }}> , </div>
<div className="text-[8px] text-text-3 leading-[1.5]"> , </div>
</div>
</div>
{/* ⑥ 비고 */}
<div>
<div style={sectionTitle}>{sectionIcon(6)} </div>
<textarea placeholder="시나리오 설명, 가정 조건, 현장 상황 특이사항 등을 기록합니다..." style={{ width: '100%', height: 60, padding: '10px 12px', borderRadius: 6, border: '1px solid var(--bd)', background: 'var(--bg0)', fontSize: 10, outline: 'none', resize: 'vertical', lineHeight: 1.6, boxSizing: 'border-box' as const, scrollbarWidth: 'thin', scrollbarColor: 'var(--bdL) transparent' }} />
<div className={sectionTitleCls}>{sectionIcon(6)} </div>
<textarea placeholder="시나리오 설명, 가정 조건, 현장 상황 특이사항 등을 기록합니다..." className="w-full h-[60px] px-3 py-2.5 rounded-md border border-border bg-bg-0 text-[10px] outline-none resize-y leading-relaxed scrollbar-thin" />
</div>
</div>
{/* ── 하단 버튼 ── */}
<div style={{ padding: '16px 24px', borderTop: '1px solid var(--bd)', flexShrink: 0, display: 'flex', gap: 8, alignItems: 'center' }}>
<div style={{ flex: 1, fontSize: 9, color: 'var(--t3)', lineHeight: 1.5 }}>
<span style={{ color: '#f87171' }}>*</span> · / API
<div className="px-6 py-4 border-t border-border shrink-0 flex gap-2 items-center">
<div className="flex-1 text-[9px] text-text-3 leading-[1.5]">
<span className="text-[#f87171]">*</span> · / API
</div>
<button onClick={onClose} style={{ padding: '10px 20px', borderRadius: 8, border: '1px solid var(--bd)', background: 'var(--bg3)', color: 'var(--t2)', fontSize: 12, fontWeight: 600, cursor: 'pointer' }}></button>
<button onClick={onClose} className="px-5 py-2.5 rounded-md border border-border bg-bg-3 text-text-2 text-xs font-semibold cursor-pointer"></button>
{done ? (
<button onClick={onClose} style={{ padding: '10px 28px', borderRadius: 8, border: 'none', background: 'linear-gradient(135deg,#22c55e,#10b981)', color: '#fff', fontSize: 12, fontWeight: 700, cursor: 'pointer', boxShadow: '0 4px 16px rgba(34,197,94,.3)' }}> </button>
<button onClick={onClose} className="px-7 py-2.5 rounded-md border-none text-white text-xs font-bold cursor-pointer" style={{ background: 'linear-gradient(135deg,#22c55e,#10b981)', boxShadow: '0 4px 16px rgba(34,197,94,.3)' }}> </button>
) : (
<button onClick={handleSubmit} disabled={submitting} style={{ padding: '10px 28px', borderRadius: 8, border: 'none', background: submitting ? 'var(--bg3)' : 'linear-gradient(135deg,#06b6d4,#3b82f6)', color: submitting ? 'var(--t3)' : '#fff', fontSize: 12, fontWeight: 700, cursor: submitting ? 'wait' : 'pointer', boxShadow: submitting ? 'none' : '0 4px 16px rgba(6,182,212,.3)' }}>
<button onClick={handleSubmit} disabled={submitting} className="px-7 py-2.5 rounded-md border-none text-xs font-bold" style={{ background: submitting ? 'var(--bg3)' : 'linear-gradient(135deg,#06b6d4,#3b82f6)', color: submitting ? 'var(--t3)' : '#fff', cursor: submitting ? 'wait' : 'pointer', boxShadow: submitting ? 'none' : '0 4px 16px rgba(6,182,212,.3)' }}>
{submitting ? '⏳ 분석 중...' : '🚨 시나리오 생성 · 분석 실행'}
</button>
)}
@ -691,17 +690,17 @@ function ScenarioComparison({ chartData }: { chartData: ChartDataItem[] }) {
if (chartData.length === 0) {
return (
<div style={{ padding: 40, textAlign: 'center', fontSize: 11, color: 'var(--t3)' }}>
<div className="p-10 text-center text-[11px] text-text-3">
.
</div>
)
}
return (
<div style={{ padding: 20 }}>
<div className="p-5">
{/* Chart 1: GM 추이 */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16, marginBottom: 16 }}>
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--cyan)', marginBottom: 10 }}>📈 GM () (m)</div>
<div className="bg-bg-3 border border-border rounded-[10px] p-4 mb-4">
<div className="text-[11px] font-bold text-primary-cyan mb-2.5">📈 GM () (m)</div>
<svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ maxHeight: 180 }}>
{/* Grid */}
{[0, 0.5, 1.0, 1.5, 2.0].map(v => {
@ -726,10 +725,10 @@ function ScenarioComparison({ chartData }: { chartData: ChartDataItem[] }) {
</div>
{/* Charts 2 & 3: 2-column */}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, marginBottom: 16 }}>
<div className="grid grid-cols-2 gap-4 mb-4">
{/* Chart 2: 횡경사 변화 */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16 }}>
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--orange)', marginBottom: 10 }}>📉 (List) (°)</div>
<div className="bg-bg-3 border border-border rounded-[10px] p-4">
<div className="text-[11px] font-bold text-status-orange mb-2.5">📉 (List) (°)</div>
<svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ maxHeight: 160 }}>
{[0, 5, 10, 15, 20, 25].map(v => {
const y = PY + ph - (v / 25) * ph
@ -747,8 +746,8 @@ function ScenarioComparison({ chartData }: { chartData: ChartDataItem[] }) {
</div>
{/* Chart 3: 유출률 변화 (bar) */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16 }}>
<div style={{ fontSize: 11, fontWeight: 700, color: 'var(--red)', marginBottom: 10 }}>📊 (L/min)</div>
<div className="bg-bg-3 border border-border rounded-[10px] p-4">
<div className="text-[11px] font-bold text-status-red mb-2.5">📊 (L/min)</div>
<svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ maxHeight: 160 }}>
{[0, 50, 100, 150, 200].map(v => {
const y = PY + ph - (v / 200) * ph
@ -770,14 +769,14 @@ function ScenarioComparison({ chartData }: { chartData: ChartDataItem[] }) {
</div>
{/* Chart 4: 비교 테이블 */}
<div style={{ background: 'var(--bg3)', border: '1px solid var(--bd)', borderRadius: 10, padding: 16 }}>
<div style={{ fontSize: 11, fontWeight: 700, marginBottom: 10 }}>📋 </div>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 9 }}>
<div className="bg-bg-3 border border-border rounded-[10px] p-4">
<div className="text-[11px] font-bold mb-2.5">📋 </div>
<table className="w-full border-collapse text-[9px]">
<thead>
<tr style={{ background: 'rgba(6,182,212,.06)' }}>
<th style={{ padding: '7px 8px', textAlign: 'left', borderBottom: '2px solid var(--bdL)', color: 'var(--cyan)' }}></th>
<th className="py-[7px] px-2 text-left border-b-2 border-[var(--bdL)] text-primary-cyan"></th>
{chartData.map(d => (
<th key={d.id} style={{ padding: '7px 8px', textAlign: 'center', borderBottom: '2px solid var(--bdL)', color: SEV_COLOR[d.severity] }}>{d.id}<br /><span style={{ fontWeight: 400, fontSize: 8, color: 'var(--t3)' }}>{d.label}</span></th>
<th key={d.id} className="py-[7px] px-2 text-center border-b-2 border-[var(--bdL)]" style={{ color: SEV_COLOR[d.severity] }}>{d.id}<br /><span className="font-normal text-[8px] text-text-3">{d.label}</span></th>
))}
</tr>
</thead>
@ -791,9 +790,9 @@ function ScenarioComparison({ chartData }: { chartData: ChartDataItem[] }) {
{ label: '위험 등급', key: 'sev', fmt: (d: ChartDataItem) => d.severity, clr: (d: ChartDataItem) => SEV_COLOR[d.severity] },
].map(row => (
<tr key={row.label} style={{ borderBottom: '1px solid rgba(255,255,255,.04)' }}>
<td style={{ padding: '6px 8px', fontWeight: 600, color: 'var(--t2)' }}>{row.label}</td>
<td className="py-1.5 px-2 font-semibold text-text-2">{row.label}</td>
{chartData.map(d => (
<td key={d.id} style={{ padding: '6px 8px', textAlign: 'center', fontFamily: 'var(--fM)', fontWeight: 700, color: row.clr(d) }}>{row.fmt(d)}</td>
<td key={d.id} className="py-1.5 px-2 text-center font-mono font-bold" style={{ color: row.clr(d) }}>{row.fmt(d)}</td>
))}
</tr>
))}

파일 보기

@ -92,11 +92,8 @@ export function WeatherMapOverlay({
width: size,
height: size,
transform: `rotate(${station.wind.direction}deg)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
}}
className="flex items-center justify-center cursor-pointer"
>
<svg
width={size}
@ -138,70 +135,40 @@ export function WeatherMapOverlay({
style={{
background: boxBg,
border: `2px solid ${boxBorder}`,
borderRadius: 10,
padding: 8,
fontFamily: 'system-ui, -apple-system, sans-serif',
boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
display: 'flex',
flexDirection: 'column',
gap: 4,
minWidth: 70,
cursor: 'pointer',
}}
className="rounded-[10px] p-2 flex flex-col gap-1 min-w-[70px] cursor-pointer"
>
{/* 관측소명 */}
<div
style={{
textAlign: 'center',
fontSize: 12,
fontWeight: 'bold',
color: textColor,
textShadow: '1px 1px 3px rgba(0,0,0,0.7)',
paddingBottom: 4,
borderBottom: `1px solid ${isSelected ? 'rgba(0,0,0,0.2)' : 'rgba(255,255,255,0.3)'}`,
marginBottom: 2,
}}
className="text-center text-xs font-bold pb-1 mb-0.5"
>
{station.name}
</div>
{/* 수온 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<div className="flex items-center gap-1.5">
<div
style={{
background: 'linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%)',
width: 24,
height: 24,
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 11,
color: 'white',
fontWeight: 'bold',
boxShadow: '0 2px 4px rgba(0,0,0,0.15)',
}}
className="w-6 h-6 rounded-full flex items-center justify-center text-[11px] text-white font-bold"
style={{ background: 'linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%)', boxShadow: '0 2px 4px rgba(0,0,0,0.15)' }}
>
🌡
</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 2 }}>
<div className="flex items-baseline gap-0.5">
<span
style={{
fontSize: 14,
fontWeight: 'bold',
color: '#fff',
textShadow: '1px 1px 2px rgba(0,0,0,0.5)',
}}
className="text-sm font-bold text-white"
style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}
>
{station.temperature.current.toFixed(1)}
</span>
<span
style={{
fontSize: 10,
color: '#fff',
opacity: 0.9,
textShadow: '1px 1px 2px rgba(0,0,0,0.5)',
}}
className="text-[10px] text-white opacity-90"
style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}
>
°C
</span>
@ -209,42 +176,23 @@ export function WeatherMapOverlay({
</div>
{/* 파고 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<div className="flex items-center gap-1.5">
<div
style={{
background: 'linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%)',
width: 24,
height: 24,
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 11,
color: 'white',
fontWeight: 'bold',
boxShadow: '0 2px 4px rgba(0,0,0,0.15)',
}}
className="w-6 h-6 rounded-full flex items-center justify-center text-[11px] text-white font-bold"
style={{ background: 'linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%)', boxShadow: '0 2px 4px rgba(0,0,0,0.15)' }}
>
🌊
</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 2 }}>
<div className="flex items-baseline gap-0.5">
<span
style={{
fontSize: 14,
fontWeight: 'bold',
color: '#fff',
textShadow: '1px 1px 2px rgba(0,0,0,0.5)',
}}
className="text-sm font-bold text-white"
style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}
>
{station.wave.height.toFixed(1)}
</span>
<span
style={{
fontSize: 10,
color: '#fff',
opacity: 0.9,
textShadow: '1px 1px 2px rgba(0,0,0,0.5)',
}}
className="text-[10px] text-white opacity-90"
style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}
>
m
</span>
@ -252,42 +200,23 @@ export function WeatherMapOverlay({
</div>
{/* 풍속 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<div className="flex items-center gap-1.5">
<div
style={{
background: 'linear-gradient(135deg, #10b981 0%, #34d399 100%)',
width: 24,
height: 24,
borderRadius: '50%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 11,
color: 'white',
fontWeight: 'bold',
boxShadow: '0 2px 4px rgba(0,0,0,0.15)',
}}
className="w-6 h-6 rounded-full flex items-center justify-center text-[11px] text-white font-bold"
style={{ background: 'linear-gradient(135deg, #10b981 0%, #34d399 100%)', boxShadow: '0 2px 4px rgba(0,0,0,0.15)' }}
>
💨
</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 2 }}>
<div className="flex items-baseline gap-0.5">
<span
style={{
fontSize: 14,
fontWeight: 'bold',
color: '#fff',
textShadow: '1px 1px 2px rgba(0,0,0,0.5)',
}}
className="text-sm font-bold text-white"
style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}
>
{station.wind.speed.toFixed(1)}
</span>
<span
style={{
fontSize: 10,
color: '#fff',
opacity: 0.9,
textShadow: '1px 1px 2px rgba(0,0,0,0.5)',
}}
className="text-[10px] text-white opacity-90"
style={{ textShadow: '1px 1px 2px rgba(0,0,0,0.5)' }}
>
m/s
</span>

파일 보기

@ -36,10 +36,7 @@ interface WeatherRightPanelProps {
export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
if (!weatherData) {
return (
<div
className="flex flex-col bg-bg-1 border-l border-border overflow-hidden"
style={{ width: '380px', flexShrink: 0 }}
>
<div className="flex flex-col bg-bg-1 border-l border-border overflow-hidden w-[380px] shrink-0">
<div className="p-6 text-center">
<p className="text-text-3 text-sm"> </p>
</div>
@ -55,10 +52,7 @@ export function WeatherRightPanel({ weatherData }: WeatherRightPanelProps) {
const moonVisibility = '6.7 m'
return (
<div
className="flex flex-col bg-bg-1 border-l border-border overflow-hidden"
style={{ width: '380px', flexShrink: 0 }}
>
<div className="flex flex-col bg-bg-1 border-l border-border overflow-hidden w-[380px] shrink-0">
{/* Header */}
<div className="px-6 py-4 border-b border-border">
<div className="flex items-center gap-2 mb-2">

파일 보기

@ -122,7 +122,7 @@ function WeatherMapControls() {
const { current: map } = useMap()
return (
<div style={{ position: 'absolute', top: 16, right: 16, zIndex: 10 }}>
<div className="absolute top-4 right-4 z-10">
<div className="flex flex-col gap-2">
<button
onClick={() => map?.zoomIn()}
@ -310,7 +310,7 @@ 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-border bg-bg-1" style={{ flexShrink: 0 }}>
<div className="flex items-center border-b border-border bg-bg-1 shrink-0">
<div className="flex items-center gap-2 px-6">
{(['0', '3', '6', '9'] as TimeOffset[]).map((offset) => (
<button
@ -375,7 +375,7 @@ export function WeatherView() {
</Map>
{/* 레이어 컨트롤 */}
<div className="absolute top-6 left-6 bg-bg-1/90 border border-border rounded-lg p-4 backdrop-blur-sm" style={{ zIndex: 10 }}>
<div className="absolute top-6 left-6 bg-bg-1/90 border border-border rounded-lg p-4 backdrop-blur-sm z-10">
<div className="text-sm font-semibold text-text-1 mb-3"> </div>
<div className="space-y-2">
<label className="flex items-center gap-2 cursor-pointer">
@ -511,7 +511,7 @@ export function WeatherView() {
</div>
{/* 범례 */}
<div className="absolute bottom-6 left-6 bg-bg-1/90 border border-border rounded-lg p-4 backdrop-blur-sm" style={{ zIndex: 10 }}>
<div className="absolute bottom-6 left-6 bg-bg-1/90 border border-border rounded-lg p-4 backdrop-blur-sm z-10">
<div className="text-sm font-semibold text-text-1 mb-3"> </div>
<div className="space-y-3 text-xs">
{/* 바람 (Windy 스타일) */}
@ -527,10 +527,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-text-3"
style={{ fontSize: '9px' }}
>
<div className="flex justify-between text-text-3 text-[9px]">
<span>3</span>
<span>5</span>
<span>7</span>