WIP: 인스트루먼트 UI 시도 (DS 컴포넌트 + CRT 셰이더)

- DS 컴포넌트: CrtDisplay, InstrumentPanel, Led, PhysicalButton, MetalCard, PhosphorText
- Dashboard: CRT WebGL 셰이더 + 물리 버튼 + LED 클러스터
- 사이드바: 72px 미니 네비게이션
- 문제: SSOT 위반(매직넘버 41곳), 시안 A 정체성 부족, 모달/팝업 방치
- 다음: 디자인 근본 재설계 필요
This commit is contained in:
Yun Chan 2026-04-05 09:20:40 +09:00
parent 3f4d0c5828
commit 85d626b922
13 changed files with 930 additions and 746 deletions

View file

@ -1,50 +1,29 @@
// src/renderer/components/StatusBar.tsx
// 하단 상태 표시: LED 인디케이터 + 태그 시스템. 08-design-system.md SSOT.
// 인스트루먼트 섀시 하단 — 각인 스타일 상태 표시
import { useState, useEffect } from 'react'
import { Box, Typography, Chip } from '@mui/material'
import { d3roPalette, d3roFontMono } from '../theme'
import { Box, Typography } from '@mui/material'
import { Led } from './ds'
import type { LLMStatus } from '@shared/types'
function Led({ active, color }: { active: boolean; color?: string }): React.ReactElement {
const c = color ?? d3roPalette.tag.green
return (
<Box
sx={{
width: 6,
height: 6,
borderRadius: '50%',
bgcolor: active ? c : d3roPalette.text.disabled,
boxShadow: active ? `0 0 4px ${c}, 0 0 8px ${c}40` : 'none',
flexShrink: 0,
transition: 'background 0.3s ease, box-shadow 0.3s ease',
}}
/>
)
}
const MONO = 'MONO_PLACEHOLDER'
export function StatusBar(): React.ReactElement {
const [llmStatus, setLlmStatus] = useState<LLMStatus | null>(null)
useEffect(() => {
window.electronAPI.llm.getStatus().then((result) => {
if (result.success) setLlmStatus(result.data)
})
const unsub = window.electronAPI.llm.onStatusChanged((event) => {
setLlmStatus(event.status)
window.electronAPI.llm.getStatus().then((r) => {
if (r.success) setLlmStatus(r.data)
})
const unsub = window.electronAPI.llm.onStatusChanged((e) => setLlmStatus(e.status))
const interval = setInterval(() => {
window.electronAPI.llm.getStatus().then((result) => {
if (result.success) setLlmStatus(result.data)
window.electronAPI.llm.getStatus().then((r) => {
if (r.success) setLlmStatus(r.data)
})
}, 5000)
return () => {
unsub()
clearInterval(interval)
}
return () => { unsub(); clearInterval(interval) }
}, [])
const connected = llmStatus?.connectionState === 'connected'
@ -56,50 +35,29 @@ export function StatusBar(): React.ReactElement {
alignItems: 'center',
gap: 2,
px: 2,
py: 0.75,
borderTop: `1px solid ${d3roPalette.border.subtle}`,
bgcolor: 'background.paper',
minHeight: 32,
py: 0.5,
borderTop: '1px solid rgba(255,255,255,0.04)',
bgcolor: '#1e1f21',
minHeight: 28,
}}
>
{/* Ollama 상태 */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.75 }}>
<Led active={connected} color={connected ? d3roPalette.tag.green : d3roPalette.tag.red} />
<Typography
sx={{
fontFamily: d3roFontMono,
fontSize: '11px',
color: d3roPalette.text.secondary,
letterSpacing: '0.02em',
}}
>
<Led color={connected ? 'green' : 'red'} size={6} />
<Typography sx={{ fontFamily: MONO, fontSize: '9px', color: '#77797c', letterSpacing: '1px', fontWeight: 700 }}>
{connected ? 'OLLAMA' : 'OFFLINE'}
</Typography>
</Box>
{/* 활성 모델 태그 */}
{llmStatus?.activeModel && (
<Chip
label={llmStatus.activeModel}
size="small"
color="primary"
sx={{ height: 20, '& .MuiChip-label': { px: 1, fontSize: '10px' } }}
/>
<Typography sx={{ fontFamily: MONO, fontSize: '9px', color: '#5c2615', letterSpacing: '0.5px' }}>
{llmStatus.activeModel.toUpperCase()}
</Typography>
)}
{/* 스페이서 */}
<Box sx={{ flex: 1 }} />
{/* 핫키 힌트 */}
<Typography
sx={{
fontFamily: d3roFontMono,
fontSize: '10px',
color: d3roPalette.text.label,
letterSpacing: '0.05em',
}}
>
RIGHT ALT DICTATE
<Typography sx={{ fontFamily: MONO, fontSize: '9px', color: '#3a3b3f', letterSpacing: '1px', fontWeight: 700 }}>
PRECISION DATA LINK
</Typography>
</Box>
)