diff --git a/apps/desktop/src/renderer/components/CloudSyncSection.tsx b/apps/desktop/src/renderer/components/CloudSyncSection.tsx
index 81f5548..6344b61 100644
--- a/apps/desktop/src/renderer/components/CloudSyncSection.tsx
+++ b/apps/desktop/src/renderer/components/CloudSyncSection.tsx
@@ -4,10 +4,7 @@
import { useEffect, useState } from 'react'
import { Box, Button, Stack, Alert, CircularProgress } from '@mui/material'
-import CloudIcon from '@mui/icons-material/Cloud'
-import CloudDoneIcon from '@mui/icons-material/CloudDone'
-import GoogleIcon from '@mui/icons-material/Google'
-import GitHubIcon from '@mui/icons-material/GitHub'
+import { Cloud, CloudCheck, Globe, GitBranch } from 'lucide-react'
import { d3roPalette, typoSx } from '@d3ro/ui/theme'
import { useI18n } from '@d3ro/i18n'
@@ -128,9 +125,9 @@ export function CloudSyncSection(): React.ReactElement {
{state.authenticated ? (
-
+
) : (
-
+
)}
Cloud Sync
@@ -141,7 +138,7 @@ export function CloudSyncSection(): React.ReactElement {
}
+ startIcon={}
onClick={() => void handleSignIn('google')}
disabled={busy}
>
@@ -149,7 +146,7 @@ export function CloudSyncSection(): React.ReactElement {
}
+ startIcon={}
onClick={() => void handleSignIn('github')}
disabled={busy}
>
diff --git a/apps/desktop/src/renderer/components/FileDropZone.tsx b/apps/desktop/src/renderer/components/FileDropZone.tsx
index 4a615dc..e0d58e6 100644
--- a/apps/desktop/src/renderer/components/FileDropZone.tsx
+++ b/apps/desktop/src/renderer/components/FileDropZone.tsx
@@ -3,9 +3,7 @@
import { useState, useEffect, useCallback, useRef } from 'react'
import { Box, LinearProgress, IconButton, Tooltip } from '@mui/material'
-import ContentCopyIcon from '@mui/icons-material/ContentCopy'
-import CloseIcon from '@mui/icons-material/Close'
-import UploadFileIcon from '@mui/icons-material/UploadFile'
+import { Copy, X, FileUp } from 'lucide-react'
import { MetalCard, PhosphorText, Led } from '@d3ro/ui/components/ds'
import { d3roPalette, d3roTypo } from '@d3ro/ui/theme'
import { useI18n } from '@d3ro/i18n'
@@ -136,7 +134,7 @@ export function FileDropZone(): React.ReactElement {
'&:hover': { borderColor: d3roPalette.accent.amber },
}}
>
-
+
{t('fileTranscription.dropZone')}
@@ -216,11 +214,11 @@ export function FileDropZone(): React.ReactElement {
-
+
-
+
diff --git a/apps/desktop/src/renderer/components/LicenseModal.tsx b/apps/desktop/src/renderer/components/LicenseModal.tsx
index 87e779e..b56bcee 100644
--- a/apps/desktop/src/renderer/components/LicenseModal.tsx
+++ b/apps/desktop/src/renderer/components/LicenseModal.tsx
@@ -10,9 +10,7 @@ import {
Divider,
LinearProgress,
} from '@mui/material'
-import CloseIcon from '@mui/icons-material/Close'
-import CheckCircleIcon from '@mui/icons-material/CheckCircle'
-import CancelIcon from '@mui/icons-material/Cancel'
+import { X, CircleCheck, XCircle } from 'lucide-react'
import { MetalCard, PhosphorText, Led, PhysicalButton } from '@d3ro/ui/components/ds'
import { d3roPalette, d3roTypo, d3roRadius, d3roShadow, d3roFontMono } from '@d3ro/ui/theme'
import { useI18n } from '@d3ro/i18n'
@@ -61,7 +59,7 @@ export function LicenseModal({ open, onClose }: LicenseModalProps): React.ReactE
>
{t('license.title')}
-
+
@@ -325,10 +323,10 @@ function UpgradeButton({ tier, t, onUpgrade }: { tier: 'pro' | 'pro_plus'; t: (k
function TierCell({ value }: { value: boolean | string }): React.ReactElement {
if (value === true) {
- return
+ return
}
if (value === false) {
- return
+ return
}
return (
diff --git a/apps/desktop/src/renderer/components/LicenseTab.tsx b/apps/desktop/src/renderer/components/LicenseTab.tsx
index 6b20bbd..ebb8b11 100644
--- a/apps/desktop/src/renderer/components/LicenseTab.tsx
+++ b/apps/desktop/src/renderer/components/LicenseTab.tsx
@@ -6,8 +6,7 @@ import {
Divider,
LinearProgress,
} from '@mui/material'
-import CheckCircleIcon from '@mui/icons-material/CheckCircle'
-import CancelIcon from '@mui/icons-material/Cancel'
+import { CircleCheck, XCircle } from 'lucide-react'
import { Led, PhosphorText, PhysicalButton, MetalCard } from '@d3ro/ui/components/ds'
import { d3roPalette, d3roFontMono, d3roTypo, d3roRadius } from '@d3ro/ui/theme'
import { useI18n } from '@d3ro/i18n'
@@ -316,10 +315,10 @@ function ComparisonTable({ t, comparison }: { t: (k: string) => string; comparis
function TierCell({ value }: { value: boolean | string }): React.ReactElement {
if (value === true) {
- return
+ return
}
if (value === false) {
- return
+ return
}
return (
diff --git a/apps/desktop/src/renderer/components/OllamaGuideModal.tsx b/apps/desktop/src/renderer/components/OllamaGuideModal.tsx
index 8f2795a..9e704ee 100644
--- a/apps/desktop/src/renderer/components/OllamaGuideModal.tsx
+++ b/apps/desktop/src/renderer/components/OllamaGuideModal.tsx
@@ -12,9 +12,7 @@ import {
Divider,
IconButton,
} from '@mui/material'
-import CloseIcon from '@mui/icons-material/Close'
-import OpenInNewIcon from '@mui/icons-material/OpenInNew'
-import ContentCopyIcon from '@mui/icons-material/ContentCopy'
+import { X, ExternalLink, Copy } from 'lucide-react'
import { d3roPalette, d3roFontMono, d3roShadow } from '@d3ro/ui/theme'
import { Led } from '@d3ro/ui/components/ds'
import { useI18n } from '@d3ro/i18n'
@@ -46,7 +44,7 @@ function CodeBlock({ children }: { children: string }): React.ReactElement {
onClick={() => navigator.clipboard.writeText(children)}
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.accent.amber } }}
>
-
+
)
@@ -88,7 +86,7 @@ export function OllamaGuideModal({ open, onClose }: OllamaGuideModalProps): Reac
>
{t('ollama.title')}
-
+
@@ -108,7 +106,7 @@ export function OllamaGuideModal({ open, onClose }: OllamaGuideModalProps): Reac
}
+ endIcon={}
onClick={() => handleOpenLink('https://ollama.com/download')}
sx={{ fontFamily: d3roFontMono, fontSize: '11px' }}
>
diff --git a/apps/desktop/src/renderer/components/OnboardingModal.tsx b/apps/desktop/src/renderer/components/OnboardingModal.tsx
index 28cc1ee..76044eb 100644
--- a/apps/desktop/src/renderer/components/OnboardingModal.tsx
+++ b/apps/desktop/src/renderer/components/OnboardingModal.tsx
@@ -21,9 +21,7 @@ import {
Box,
LinearProgress,
} from '@mui/material'
-import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'
-import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'
-import CloudDownloadIcon from '@mui/icons-material/CloudDownload'
+import { CheckCircle2, AlertCircle, CloudDownload } from 'lucide-react'
import { d3roPalette, d3roRadius, typoSx } from '@d3ro/ui/theme'
import { useI18n } from '@d3ro/i18n'
@@ -281,11 +279,11 @@ export function OnboardingModal({ open, onClose }: OnboardingModalProps): React.
}}
>
{isSuccess ? (
-
+
) : isFailed ? (
-
+
) : (
-
+
)}
{t('onboarding.title')}
diff --git a/apps/desktop/src/renderer/components/ProBadge.tsx b/apps/desktop/src/renderer/components/ProBadge.tsx
index 335f0d6..accbf2c 100644
--- a/apps/desktop/src/renderer/components/ProBadge.tsx
+++ b/apps/desktop/src/renderer/components/ProBadge.tsx
@@ -3,7 +3,7 @@
// shows lock overlay with PRO badge if locked.
import { Box, Typography } from '@mui/material'
-import LockIcon from '@mui/icons-material/Lock'
+import { Lock } from 'lucide-react'
import { d3roPalette, d3roFontMono, d3roTypo, d3roRadius } from '@d3ro/ui/theme'
import { useProFeature } from '../hooks/useProFeature'
import { useI18n } from '@d3ro/i18n'
@@ -59,7 +59,7 @@ export function ProBadge({ feature, children }: ProBadgeProps): React.ReactEleme
border: `1px solid ${d3roPalette.border.default}`,
}}
>
-
+
)}
-
+
)
@@ -342,7 +335,7 @@ export function SettingsModal({ open, onClose }: SettingsModalProps): React.Reac
>
{t('settings.title')}
-
+
@@ -370,12 +363,12 @@ export function SettingsModal({ open, onClose }: SettingsModalProps): React.Reac
'& .MuiTabs-indicator': { bgcolor: d3roPalette.accent.amber, height: 2 },
}}
>
- } iconPosition="start" />
- } iconPosition="start" />
+ } iconPosition="start" />
+ } iconPosition="start" />
- } iconPosition="start" />
- } iconPosition="start" />
+ } iconPosition="start" />
+ } iconPosition="start" />
@@ -531,7 +524,7 @@ export function SettingsModal({ open, onClose }: SettingsModalProps): React.Reac
setSelectedDeviceId(deviceId)
window.electronAPI.audio.setSelectedDevice({ deviceId })
}}
- startAdornment={}
+ startAdornment={}
>
{audioDevices.map((device, idx) => (
@@ -260,12 +257,12 @@ export function TemplateSection(): React.ReactElement {
disabled={formFields.length <= 1}
sx={{ color: d3roPalette.text.inactive }}
>
-
+
))}
- } size="small" sx={{ alignSelf: 'flex-start' }}>
+ } size="small" sx={{ alignSelf: 'flex-start' }}>
{t('template.addField')}
diff --git a/apps/desktop/src/renderer/components/UpgradePromptModal.tsx b/apps/desktop/src/renderer/components/UpgradePromptModal.tsx
index c638cf3..0fd811f 100644
--- a/apps/desktop/src/renderer/components/UpgradePromptModal.tsx
+++ b/apps/desktop/src/renderer/components/UpgradePromptModal.tsx
@@ -12,8 +12,7 @@ import {
Box,
LinearProgress,
} from '@mui/material'
-import LockIcon from '@mui/icons-material/Lock'
-import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'
+import { Lock, CheckCircle2 } from 'lucide-react'
import { d3roPalette, d3roFontMono, d3roTypo, d3roRadius, d3roShadow } from '@d3ro/ui/theme'
import { useI18n } from '@d3ro/i18n'
import type { UpgradePromptEvent, UsageQuota } from '@d3ro/core/types'
@@ -80,7 +79,7 @@ export function UpgradePromptModal(): React.ReactElement {
color: d3roPalette.accent.amber,
}}
>
-
+
{isQuota
? t('license.quotaExceeded.title', { feature: featureLabel })
: isLoginRequired
@@ -134,9 +133,7 @@ export function UpgradePromptModal(): React.ReactElement {
key={benefit}
sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 0.5 }}
>
-
+
-
+
{t('common.delete')}
diff --git a/apps/desktop/src/renderer/components/meeting/ExportMenu.tsx b/apps/desktop/src/renderer/components/meeting/ExportMenu.tsx
index ca8836d..664e9b9 100644
--- a/apps/desktop/src/renderer/components/meeting/ExportMenu.tsx
+++ b/apps/desktop/src/renderer/components/meeting/ExportMenu.tsx
@@ -3,7 +3,7 @@
import { useState, useCallback } from 'react'
import { Menu, MenuItem } from '@mui/material'
-import FileDownloadIcon from '@mui/icons-material/FileDownload'
+import { Download } from 'lucide-react'
import { PhysicalButton } from '@d3ro/ui/components/ds'
import { d3roPalette, d3roTypo, d3roFontMono, d3roRadius, d3roShadow } from '@d3ro/ui/theme'
import { useI18n } from '@d3ro/i18n'
@@ -54,7 +54,7 @@ export function ExportMenu({ onExport, onCopyToClipboard }: ExportMenuProps): Re
onClick={handleOpen}
sx={{ height: 32, fontSize: d3roTypo.engrave.size }}
>
-
+
{t('meeting.exportFormat')}
diff --git a/apps/desktop/src/renderer/components/meeting/MeetingChatPanel.tsx b/apps/desktop/src/renderer/components/meeting/MeetingChatPanel.tsx
index fb6ccb6..afce403 100644
--- a/apps/desktop/src/renderer/components/meeting/MeetingChatPanel.tsx
+++ b/apps/desktop/src/renderer/components/meeting/MeetingChatPanel.tsx
@@ -3,10 +3,7 @@
import { useState, useCallback, useEffect, useRef, useMemo } from 'react'
import { Box, TextField, IconButton, Tooltip } from '@mui/material'
-import SendIcon from '@mui/icons-material/Send'
-import DeleteSweepIcon from '@mui/icons-material/DeleteSweep'
-import ExpandLessIcon from '@mui/icons-material/ExpandLess'
-import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
+import { Send, Trash2, ChevronUp, ChevronDown } from 'lucide-react'
import { PhosphorText } from '@d3ro/ui/components/ds'
import { PhysicalButton } from '@d3ro/ui/components/ds'
import { isImeComposingEvent } from '../../utils/keyboard'
@@ -187,16 +184,16 @@ export function MeetingChatPanel({ sessionId }: MeetingChatPanelProps): React.Re
onClick={handleClear}
disabled={messages.length === 0 && !streaming}
>
-
+
{collapsed ? (
-
+
) : (
-
+
)}
@@ -408,7 +405,7 @@ export function MeetingChatPanel({ sessionId }: MeetingChatPanelProps): React.Re
disabled={!inputValue.trim() || streaming}
sx={{ flexShrink: 0, height: 36, minWidth: 36, px: 1 }}
>
-
+
>
diff --git a/apps/desktop/src/renderer/components/meeting/MeetingDetailTabs.tsx b/apps/desktop/src/renderer/components/meeting/MeetingDetailTabs.tsx
index dc9cf35..0cd3987 100644
--- a/apps/desktop/src/renderer/components/meeting/MeetingDetailTabs.tsx
+++ b/apps/desktop/src/renderer/components/meeting/MeetingDetailTabs.tsx
@@ -10,10 +10,7 @@ import {
TextField,
Tooltip,
} from '@mui/material'
-import ArrowBackIcon from '@mui/icons-material/ArrowBack'
-import AddIcon from '@mui/icons-material/Add'
-import EditIcon from '@mui/icons-material/Edit'
-import CheckIcon from '@mui/icons-material/Check'
+import { ArrowLeft, Plus, Pencil, Check } from 'lucide-react'
import { TranscriptTab } from './TranscriptTab'
import { DocumentTab } from './DocumentTab'
import { AddDocumentDialog } from './AddDocumentDialog'
@@ -231,7 +228,7 @@ export function MeetingDetailTabs({
}}
>
-
+
{editingTitle ? (
@@ -251,7 +248,7 @@ export function MeetingDetailTabs({
autoFocus
/>
-
+
) : (
@@ -264,7 +261,7 @@ export function MeetingDetailTabs({
size="small"
onClick={() => { setEditingTitle(true); setTitleDraft(detail.title ?? '') }}
>
-
+
>
@@ -303,7 +300,7 @@ export function MeetingDetailTabs({
))}
}
+ icon={}
label={t('meeting.addDocument')}
iconPosition="start"
sx={{ minWidth: 120 }}
diff --git a/apps/desktop/src/renderer/components/meeting/TranscriptTab.tsx b/apps/desktop/src/renderer/components/meeting/TranscriptTab.tsx
index 591b27e..1537437 100644
--- a/apps/desktop/src/renderer/components/meeting/TranscriptTab.tsx
+++ b/apps/desktop/src/renderer/components/meeting/TranscriptTab.tsx
@@ -8,9 +8,7 @@ import { PhysicalButton } from '@d3ro/ui/components/ds'
import { PhosphorText } from '@d3ro/ui/components/ds'
import { d3roPalette, d3roFontMono, d3roTypo } from '@d3ro/ui/theme'
import { useI18n } from '@d3ro/i18n'
-import FileDownloadIcon from '@mui/icons-material/FileDownload'
-import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'
-import RecordVoiceOverIcon from '@mui/icons-material/RecordVoiceOver'
+import { Download, WandSparkles, Speech } from 'lucide-react'
interface TranscriptTabProps {
sessionId: string
@@ -129,7 +127,7 @@ export function TranscriptTab({
disabled={polishing || diarizing}
sx={{ height: 30, fontSize: d3roTypo.engrave.size }}
>
-
+
{polishing ? t('meeting.polishing') : t('meeting.polish')}
{onDiarize && (
@@ -139,7 +137,7 @@ export function TranscriptTab({
disabled={polishing || diarizing}
sx={{ height: 30, fontSize: d3roTypo.engrave.size }}
>
-
+
{diarizing ? t('meeting.diarizing') : t('meeting.diarize')}
)}
@@ -148,7 +146,7 @@ export function TranscriptTab({
onClick={handleDownloadTxt}
sx={{ height: 30, fontSize: d3roTypo.engrave.size }}
>
-
+
{t('meeting.downloadTranscript')}
diff --git a/apps/desktop/src/renderer/components/shared/HistoryEntryCard.tsx b/apps/desktop/src/renderer/components/shared/HistoryEntryCard.tsx
index ddf276b..00e96c0 100644
--- a/apps/desktop/src/renderer/components/shared/HistoryEntryCard.tsx
+++ b/apps/desktop/src/renderer/components/shared/HistoryEntryCard.tsx
@@ -4,12 +4,7 @@
import { useState, useEffect, useCallback } from 'react'
import { Box, IconButton, Tooltip, Chip } from '@mui/material'
-import ContentCopyIcon from '@mui/icons-material/ContentCopy'
-import DeleteIcon from '@mui/icons-material/Delete'
-import LocalOfferIcon from '@mui/icons-material/LocalOffer'
-import CloseIcon from '@mui/icons-material/Close'
-import SummarizeIcon from '@mui/icons-material/Summarize'
-import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
+import { Copy, Trash2, Tag, X, ScrollText, ChevronDown } from 'lucide-react'
import { MetalCard, Led } from '@d3ro/ui/components/ds'
import { d3roPalette, d3roFontMono, d3roTypo, d3roRadius } from '@d3ro/ui/theme'
import { useI18n } from '@d3ro/i18n'
@@ -149,7 +144,7 @@ export function HistoryEntryCard({ entry, onCopy, onDelete, showTags = false, on
{entry.mode.toUpperCase()}
{hasSummary && (
}
+ icon={}
label={t('meetingSummary.title')}
size="small"
onClick={handleToggleSummary}
@@ -176,7 +171,7 @@ export function HistoryEntryCard({ entry, onCopy, onDelete, showTags = false, on
size="small"
onClick={() => onTagClick?.(tag.tag)}
onDelete={() => handleRemoveTag(tag.tag)}
- deleteIcon={}
+ deleteIcon={}
sx={{
height: 20,
fontFamily: d3roFontMono,
@@ -219,7 +214,7 @@ export function HistoryEntryCard({ entry, onCopy, onDelete, showTags = false, on
onClick={() => setShowTagInput(true)}
sx={{ p: 0.25, color: d3roPalette.text.muted, '&:hover': { color: d3roPalette.accent.amber } }}
>
-
+
)}
@@ -234,7 +229,7 @@ export function HistoryEntryCard({ entry, onCopy, onDelete, showTags = false, on
onClick={() => onCopy(displayText)}
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.accent.amber } }}
>
-
+
)}
@@ -245,7 +240,7 @@ export function HistoryEntryCard({ entry, onCopy, onDelete, showTags = false, on
onClick={() => onDelete(entry.id)}
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.tag.red } }}
>
-
+
)}
@@ -290,7 +285,7 @@ export function HistoryEntryCard({ entry, onCopy, onDelete, showTags = false, on
-
+
@@ -324,7 +319,7 @@ export function HistoryEntryCard({ entry, onCopy, onDelete, showTags = false, on
sx={{ mt: 1, textAlign: 'center', cursor: 'pointer', color: d3roPalette.text.muted, '&:hover': { color: d3roPalette.accent.amber } }}
onClick={handleToggleSummary}
>
-
+
)}
diff --git a/apps/desktop/src/renderer/components/shared/SearchInput.tsx b/apps/desktop/src/renderer/components/shared/SearchInput.tsx
index 462860a..77dd892 100644
--- a/apps/desktop/src/renderer/components/shared/SearchInput.tsx
+++ b/apps/desktop/src/renderer/components/shared/SearchInput.tsx
@@ -2,7 +2,7 @@
// 공유: 모노 폰트 검색 입력 필드
import { TextField, InputAdornment } from '@mui/material'
-import SearchIcon from '@mui/icons-material/Search'
+import { Search } from 'lucide-react'
import { d3roPalette, d3roFontMono, d3roTypo } from '@d3ro/ui/theme'
interface SearchInputProps {
@@ -30,7 +30,7 @@ export function SearchInput({ value, onChange, placeholder }: SearchInputProps):
input: {
startAdornment: (
-
+
),
},
diff --git a/apps/desktop/src/renderer/components/voice-conversation/VoiceRecordingPanel.tsx b/apps/desktop/src/renderer/components/voice-conversation/VoiceRecordingPanel.tsx
index afbcc59..111ce00 100644
--- a/apps/desktop/src/renderer/components/voice-conversation/VoiceRecordingPanel.tsx
+++ b/apps/desktop/src/renderer/components/voice-conversation/VoiceRecordingPanel.tsx
@@ -1,34 +1,20 @@
// src/renderer/components/voice-conversation/VoiceRecordingPanel.tsx
-// Phase 5 Part 6: Voice Conversation 몰입 녹음 패널
-// recording-tip 팝업의 9바 waveform (cos 분포, 100ms, smoothing)을 React로 포팅.
-// 기존 구현: src/renderer/popups/recording-tip/script.js (Vanilla JS, 팝업 전용)
-// 스펙: docs/design/03-db-and-ui.md:428+
+// Voice Conversation 몰입 녹음 패널 (리디자인 v2)
+// 기존 9바 DOM waveform을 DS의 GradientWave(canvas 스펙트럼 웨이브)로 교체.
//
// AudioLevelSource:
// - VoiceConversationService가 listening 상태에서만 AUDIO_LEVEL 채널로 forwarding.
// - level 값은 0.0~1.0. 구독자가 없는 thinking/speaking에는 Panel 자체가 언마운트되므로
// useEffect cleanup으로 subscription이 자동 해제됨.
+// - 레벨 스무딩/유휴 진동은 GradientWave 내부에서 처리하므로 여기서는 raw level만 전달.
import { useEffect, useRef, useState } from 'react'
import { Box } from '@mui/material'
-import { Led, PhosphorText } from '@d3ro/ui/components/ds'
+import { GradientWave, Led, PhosphorText } from '@d3ro/ui/components/ds'
import { d3roPalette, d3roFontMono } from '@d3ro/ui/theme'
import { useI18n } from '@d3ro/i18n'
-// ── Waveform 상수 (recording-tip/script.js 수치 그대로) ──
-const BAR_COUNT = 9
-const UPDATE_INTERVAL = 100
-const MIN_HEIGHT = 2
-const MAX_HEIGHT = 28
-const SMOOTHING = 0.5
-const RANDOM_FACTOR = 0.35
-
-/** 코사인 분포 가중치 (중앙이 가장 높음). 설계서 03. */
-const weights: readonly number[] = Array.from({ length: BAR_COUNT }, (_, i) => {
- const center = (BAR_COUNT - 1) / 2
- const normalized = (i - center) / center
- return Math.cos((normalized * Math.PI) / 2)
-})
+const WAVE_HEIGHT = 48
function formatDuration(ms: number): string {
const elapsed = Math.floor(ms / 1000)
@@ -39,59 +25,34 @@ function formatDuration(ms: number): string {
/**
* Voice Conversation이 listening 상태일 때만 렌더되는 몰입 녹음 패널.
- * - 9바 phosphor waveform (audio-level 기반)
+ * - GradientWave 스펙트럼 웨이브 (audio-level 기반, 유휴 애니메이션 내장)
* - REC LED + elapsed 타이머
* - "SPEAK NOW · PRESS ⏹ TO SEND" 힌트 (i18n)
*/
export function VoiceRecordingPanel(): React.ReactElement {
const { t } = useI18n()
- const [heights, setHeights] = useState(() =>
- new Array(BAR_COUNT).fill(MIN_HEIGHT),
- )
+ const [audioLevel, setAudioLevel] = useState(0)
const [elapsedMs, setElapsedMs] = useState(0)
-
- const heightsRef = useRef(new Array(BAR_COUNT).fill(MIN_HEIGHT))
- const audioLevelRef = useRef(0)
const startedAtRef = useRef(Date.now())
// audio-level 구독 (VoiceConversationService → AUDIO_LEVEL)
useEffect(() => {
const unsubscribe = window.electronAPI.voiceConversation.onAudioLevel((e) => {
- audioLevelRef.current = e.level
+ setAudioLevel(e.level)
})
return unsubscribe
}, [])
- // 9바 애니메이션 루프 (100ms interval)
+ // 경과 시간 타이머
useEffect(() => {
startedAtRef.current = Date.now()
- heightsRef.current = new Array(BAR_COUNT).fill(MIN_HEIGHT)
- audioLevelRef.current = 0
-
- const animInterval = window.setInterval(() => {
- const level = audioLevelRef.current
- // 최소 진동: audioLevel이 0이어도 바가 미세하게 움직여 "살아있음" 표현
- const effectiveLevel = Math.max(0.08, level)
- const next = new Array(BAR_COUNT).fill(MIN_HEIGHT) as number[]
- for (let i = 0; i < BAR_COUNT; i++) {
- const baseTarget = effectiveLevel * MAX_HEIGHT * weights[i]
- const randomized = baseTarget * (1 + (Math.random() - 0.5) * 2 * RANDOM_FACTOR)
- const target = Math.max(MIN_HEIGHT, Math.min(MAX_HEIGHT, randomized))
- // 스무딩 보간
- const prev = heightsRef.current[i]
- const smoothed = prev + (target - prev) * SMOOTHING
- heightsRef.current[i] = smoothed
- next[i] = Math.round(smoothed)
- }
- setHeights(next)
- }, UPDATE_INTERVAL)
+ setElapsedMs(0)
const durationInterval = window.setInterval(() => {
setElapsedMs(Date.now() - startedAtRef.current)
}, 500)
return () => {
- window.clearInterval(animInterval)
window.clearInterval(durationInterval)
}
}, [])
@@ -111,7 +72,7 @@ export function VoiceRecordingPanel(): React.ReactElement {
{/* REC LED + 타이머 */}
-
+
{t('conversation.state.recording')}
- {/* 9바 waveform */}
+ {/* 그라디언트 스펙트럼 웨이브 */}
- {heights.map((h, i) => (
-
- ))}
+
{/* 힌트 */}
diff --git a/apps/desktop/src/renderer/pages/CommandsPage.tsx b/apps/desktop/src/renderer/pages/CommandsPage.tsx
index 82b15f9..5699752 100644
--- a/apps/desktop/src/renderer/pages/CommandsPage.tsx
+++ b/apps/desktop/src/renderer/pages/CommandsPage.tsx
@@ -4,12 +4,7 @@
import { useState, useEffect, useCallback } from 'react'
import { Box, Button, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, TextField, Chip, Select, MenuItem, FormControl, InputLabel } from '@mui/material'
-import AddIcon from '@mui/icons-material/Add'
-import DeleteIcon from '@mui/icons-material/Delete'
-import EditIcon from '@mui/icons-material/Edit'
-import CheckCircleIcon from '@mui/icons-material/CheckCircle'
-import PlayArrowIcon from '@mui/icons-material/PlayArrow'
-import LinkIcon from '@mui/icons-material/Link'
+import { Plus, Trash2, Pencil, CircleCheck, Play, Link } from 'lucide-react'
import { MetalCard, PhosphorText, Led, ScreenPanel } from '@d3ro/ui/components/ds'
import { PageHeader, EmptyStateCard } from '../components/shared'
import { isImeComposingEvent } from '../utils/keyboard'
@@ -131,7 +126,7 @@ export function CommandsPage(): React.ReactElement {
title={t('commands.title').toUpperCase()}
count={t('commands.count', { count: instructions.length }).toUpperCase()}
action={
- } onClick={openAdd} size="small">
+ } onClick={openAdd} size="small">
{t('commands.add').toUpperCase()}
}
@@ -183,7 +178,7 @@ export function CommandsPage(): React.ReactElement {
{isActive ? (
-
+
) : (
)}
@@ -210,7 +205,7 @@ export function CommandsPage(): React.ReactElement {
onClick={() => openEdit(inst)}
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.accent.amber } }}
>
-
+
{!inst.isBuiltin && (
handleDelete(inst.id)}
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.tag.red } }}
>
-
+
)}
@@ -360,7 +355,7 @@ function VoiceKeywordsSection({ instructions }: { instructions: CustomInstructio
onClick={() => { setEditingRule(rule.instructionId); setKeywordInput('') }}
sx={{ p: 0.25, color: d3roPalette.text.muted, '&:hover': { color: d3roPalette.accent.amber } }}
>
-
+
)}
@@ -456,7 +451,7 @@ function ChainSection({ instructions }: { instructions: CustomInstruction[] }):
{t('chain.title').toUpperCase()}
- } onClick={openAdd} size="small" sx={{ fontSize: d3roTypo.micro.size }}>
+ } onClick={openAdd} size="small" sx={{ fontSize: d3roTypo.micro.size }}>
{t('chain.add').toUpperCase()}
@@ -497,15 +492,15 @@ function ChainSection({ instructions }: { instructions: CustomInstruction[] }):
handleExecute(chain.id)}
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.tag.green } }}>
-
+
openEdit(chain)}
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.accent.amber } }}>
-
+
handleDelete(chain.id)}
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.tag.red } }}>
-
+
@@ -563,12 +558,12 @@ function ChainSection({ instructions }: { instructions: CustomInstruction[] }):
)}
removeStep(idx)} disabled={formSteps.length <= 1}
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.tag.red } }}>
-
+
))}
- } size="small" sx={{ mt: 1 }}>
+ } size="small" sx={{ mt: 1 }}>
{t('chain.addStep')}
diff --git a/apps/desktop/src/renderer/pages/DictionaryPage.tsx b/apps/desktop/src/renderer/pages/DictionaryPage.tsx
index d0b8b4c..936fee7 100644
--- a/apps/desktop/src/renderer/pages/DictionaryPage.tsx
+++ b/apps/desktop/src/renderer/pages/DictionaryPage.tsx
@@ -3,9 +3,7 @@
import { useState, useEffect, useCallback } from 'react'
import { Box, TextField, Button, IconButton, Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material'
-import AddIcon from '@mui/icons-material/Add'
-import DeleteIcon from '@mui/icons-material/Delete'
-import EditIcon from '@mui/icons-material/Edit'
+import { Plus, Trash2, Pencil } from 'lucide-react'
import { MetalCard, PhosphorText } from '@d3ro/ui/components/ds'
import { PageHeader, SearchInput, EmptyStateCard } from '../components/shared'
import { d3roPalette, d3roFontMono, d3roTypo } from '@d3ro/ui/theme'
@@ -73,7 +71,7 @@ export function DictionaryPage(): React.ReactElement {
title={t('dictionary.title').toUpperCase()}
count={t('dictionary.words', { count: data?.total ?? 0 }).toUpperCase()}
action={
- } onClick={openAdd} size="small">
+ } onClick={openAdd} size="small">
{t('dictionary.add').toUpperCase()}
}
@@ -114,11 +112,11 @@ export function DictionaryPage(): React.ReactElement {
openEdit(entry)}
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.accent.amber } }}>
-
+
{ window.electronAPI.dictionary.delete({ id: entry.id }); loadData() }}
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.tag.red } }}>
-
+
diff --git a/apps/desktop/src/renderer/pages/HistoryPage.tsx b/apps/desktop/src/renderer/pages/HistoryPage.tsx
index 9bb2563..97eb46b 100644
--- a/apps/desktop/src/renderer/pages/HistoryPage.tsx
+++ b/apps/desktop/src/renderer/pages/HistoryPage.tsx
@@ -4,7 +4,7 @@
import { useState, useEffect, useCallback, useMemo } from 'react'
import { Box, Chip, IconButton, Tooltip } from '@mui/material'
-import FileDownloadIcon from '@mui/icons-material/FileDownload'
+import { Download } from 'lucide-react'
import { PhosphorText } from '@d3ro/ui/components/ds'
import { d3roPalette, d3roTypo, d3roFontMono, d3roRadius } from '@d3ro/ui/theme'
import { useI18n } from '@d3ro/i18n'
@@ -96,7 +96,7 @@ export function HistoryPage(): React.ReactElement {
onClick={handleExport}
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.accent.amber } }}
>
-
+
diff --git a/apps/desktop/src/renderer/pages/KnowledgeBasePage.tsx b/apps/desktop/src/renderer/pages/KnowledgeBasePage.tsx
index 3bf904b..56c0333 100644
--- a/apps/desktop/src/renderer/pages/KnowledgeBasePage.tsx
+++ b/apps/desktop/src/renderer/pages/KnowledgeBasePage.tsx
@@ -4,12 +4,7 @@
import { useState, useEffect, useCallback } from 'react'
import { Box, TextField, IconButton, Tooltip, LinearProgress } from '@mui/material'
-import AddIcon from '@mui/icons-material/Add'
-import DeleteIcon from '@mui/icons-material/Delete'
-import RefreshIcon from '@mui/icons-material/Refresh'
-import SearchIcon from '@mui/icons-material/Search'
-import SendIcon from '@mui/icons-material/Send'
-import DescriptionIcon from '@mui/icons-material/Description'
+import { Plus, Trash2, RefreshCw, Search, Send, FileText } from 'lucide-react'
import { MetalCard, PhosphorText, Led, PhysicalButton, ScreenPanel } from '@d3ro/ui/components/ds'
import { PageHeader, EmptyStateCard } from '../components/shared'
import { isImeComposingEvent } from '../utils/keyboard'
@@ -96,7 +91,7 @@ export function KnowledgeBasePage(): React.ReactElement {
title={t('rag.title').toUpperCase()}
action={
- {adding ? t('rag.adding') : t('rag.addDocument')}
+ {adding ? t('rag.adding') : t('rag.addDocument')}
}
/>
@@ -160,7 +155,7 @@ export function KnowledgeBasePage(): React.ReactElement {
{documents.map((doc) => (
-
+
{doc.fileName}
@@ -176,13 +171,13 @@ export function KnowledgeBasePage(): React.ReactElement {
handleReindex(doc.id)}
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.accent.amber } }}>
-
+
handleRemoveDocument(doc.id)}
sx={{ color: d3roPalette.text.inactive, '&:hover': { color: d3roPalette.tag.red } }}>
-
+
@@ -198,7 +193,7 @@ export function KnowledgeBasePage(): React.ReactElement {
-
+
setQuery(e.target.value)}
@@ -219,7 +214,7 @@ export function KnowledgeBasePage(): React.ReactElement {
/>
-
+
diff --git a/apps/desktop/src/renderer/pages/MeetingModePage.tsx b/apps/desktop/src/renderer/pages/MeetingModePage.tsx
index 7a846f0..ca88567 100644
--- a/apps/desktop/src/renderer/pages/MeetingModePage.tsx
+++ b/apps/desktop/src/renderer/pages/MeetingModePage.tsx
@@ -9,9 +9,7 @@ import {
Snackbar,
Alert,
} from '@mui/material'
-import AddIcon from '@mui/icons-material/Add'
-import StopIcon from '@mui/icons-material/Stop'
-import FiberManualRecordIcon from '@mui/icons-material/FiberManualRecord'
+import { Plus, Square, Circle } from 'lucide-react'
import { MetalCard, PhosphorText, Led, PhysicalButton, ScreenPanel } from '@d3ro/ui/components/ds'
import { MeetingDetailTabs } from '../components/meeting/MeetingDetailTabs'
import { EditableSegment } from '../components/meeting/EditableSegment'
@@ -229,7 +227,7 @@ export function MeetingModePage(): React.ReactElement {
count={totalSessions > 0 ? t('meeting.sessions', { count: totalSessions }) : undefined}
action={
-
+
{t('meeting.newMeeting')}
}
@@ -344,7 +342,7 @@ export function MeetingModePage(): React.ReactElement {
{/* 상단 바 */}
-
+
{formatTime(elapsedMs)}
@@ -354,7 +352,7 @@ export function MeetingModePage(): React.ReactElement {
{!isProcessing && (
-
+
{t('meeting.stopRecording')}
)}
diff --git a/apps/desktop/src/renderer/pages/VoiceConversationPage.tsx b/apps/desktop/src/renderer/pages/VoiceConversationPage.tsx
index 7eb4ded..1dd28da 100644
--- a/apps/desktop/src/renderer/pages/VoiceConversationPage.tsx
+++ b/apps/desktop/src/renderer/pages/VoiceConversationPage.tsx
@@ -4,11 +4,7 @@
import { useState, useEffect, useCallback, useRef } from 'react'
import { Alert, Box, IconButton, Snackbar, TextField, Tooltip } from '@mui/material'
-import MicIcon from '@mui/icons-material/Mic'
-import StopIcon from '@mui/icons-material/Stop'
-import SendIcon from '@mui/icons-material/Send'
-import DeleteSweepIcon from '@mui/icons-material/DeleteSweep'
-import CancelIcon from '@mui/icons-material/Cancel'
+import { Mic, Square, Send, Trash2, XCircle } from 'lucide-react'
import { MetalCard, PhosphorText, Led, PhysicalButton, ScreenPanel, InstrumentPanel } from '@d3ro/ui/components/ds'
import { PageHeader } from '../components/shared'
import { VoiceRecordingPanel } from '../components/voice-conversation/VoiceRecordingPanel'
@@ -248,7 +244,7 @@ export function VoiceConversationPage(): React.ReactElement {
{messages.length > 0 && (
-
+
)}
@@ -378,7 +374,7 @@ export function VoiceConversationPage(): React.ReactElement {
{/* 녹음 버튼 */}
{!isActive ? (
-
+
) : state === 'listening' ? (
-
+
) : state === 'thinking' || state === 'speaking' ? (
-
+
) : (
-
+
)}
@@ -439,7 +435,7 @@ export function VoiceConversationPage(): React.ReactElement {
: d3roPalette.text.inactive,
}}
>
-
+
)}
diff --git a/apps/desktop/src/renderer/popups/caption-overlay/style.css b/apps/desktop/src/renderer/popups/caption-overlay/style.css
index 81ad4b7..5184bb6 100644
--- a/apps/desktop/src/renderer/popups/caption-overlay/style.css
+++ b/apps/desktop/src/renderer/popups/caption-overlay/style.css
@@ -3,14 +3,14 @@
* WindowManager.insertCSS()가 :root에 테마 변수를 주입하면 자동 전환.
*/
-/* ── 다크 테마 fallback 기본값 (:root) ─────────────────────────── */
+/* ── Midnight(다크) fallback 기본값 (:root) ────────────────────── */
:root {
- --d3-accent-main: #f25b29;
- --d3-accent-glow: rgba(242, 91, 41, 0.6);
- --d3-accent-glow-dim: rgba(242, 91, 41, 0.3);
- --d3-accent-light: #ff8a65;
- --d3-accent-light-glow: rgba(255, 138, 101, 0.7);
- --d3-accent-light-glow-dim: rgba(255, 138, 101, 0.4);
+ --d3-accent-main: #3b82f6;
+ --d3-accent-glow: rgba(59, 130, 246, 0.6);
+ --d3-accent-glow-dim: rgba(59, 130, 246, 0.3);
+ --d3-accent-light: #60a5fa;
+ --d3-accent-light-glow: rgba(96, 165, 250, 0.7);
+ --d3-accent-light-glow-dim: rgba(96, 165, 250, 0.4);
}
* {
@@ -59,8 +59,10 @@ html, body {
0 0 8px var(--d3-accent-glow),
0 0 16px var(--d3-accent-glow-dim),
0 1px 3px rgba(0, 0, 0, 0.8);
- background: rgba(0, 0, 0, 0.92);
- border-radius: 6px;
+ background: rgba(13, 20, 38, 0.92);
+ /* 헤어라인은 box-shadow inset — border는 라인 높이에 영향을 주므로 금지 */
+ box-shadow: inset 0 0 0 1px rgba(148, 180, 255, 0.1);
+ border-radius: 8px;
padding: 4px 12px;
opacity: 1;
transition: opacity 0.5s ease-out;
diff --git a/apps/desktop/src/renderer/popups/command-popup/style.css b/apps/desktop/src/renderer/popups/command-popup/style.css
index 6dfa83d..1e05413 100644
--- a/apps/desktop/src/renderer/popups/command-popup/style.css
+++ b/apps/desktop/src/renderer/popups/command-popup/style.css
@@ -3,19 +3,19 @@
* WindowManager.insertCSS()가 :root에 테마 변수를 주입하면 자동 전환.
*/
-/* ── 다크 테마 fallback 기본값 (:root) ─────────────────────────── */
+/* ── Midnight(다크) fallback 기본값 (:root) ────────────────────── */
:root {
- --d3-bg-card: #242427;
- --d3-border-default: rgba(255, 255, 255, 0.08);
- --d3-border-subtle: rgba(255, 255, 255, 0.06);
- --d3-text-primary: rgba(255, 255, 255, 0.87);
- --d3-text-inactive: rgba(255, 255, 255, 0.3);
- --d3-text-muted: rgba(255, 255, 255, 0.25);
- --d3-text-dimLabel: #5c2615;
- --d3-text-secondary: rgba(255, 255, 255, 0.35);
- --d3-accent-main: #f25b29;
- --d3-accent-dim: rgba(242, 91, 41, 0.08);
- --d3-shadow-popup: 0 8px 32px rgba(0, 0, 0, 0.5);
+ --d3-bg-card: rgba(17, 26, 48, 0.85);
+ --d3-border-default: rgba(148, 180, 255, 0.12);
+ --d3-border-subtle: rgba(148, 180, 255, 0.07);
+ --d3-text-primary: #eef2fb;
+ --d3-text-inactive: #7385ab;
+ --d3-text-muted: rgba(147, 164, 200, 0.45);
+ --d3-text-dimLabel: #4e5f85;
+ --d3-text-secondary: #93a4c8;
+ --d3-accent-main: #3b82f6;
+ --d3-accent-dim: rgba(59, 130, 246, 0.14);
+ --d3-shadow-popup: 0 12px 40px rgba(3, 7, 18, 0.5);
}
* {
@@ -39,8 +39,9 @@ body {
.command-popup {
background: var(--d3-bg-card);
border: 1px solid var(--d3-border-default);
- border-radius: 12px;
+ border-radius: 14px;
padding: 6px;
+ backdrop-filter: blur(24px);
box-shadow: var(--d3-shadow-popup);
opacity: 0;
transform: translateY(4px) scale(0.98);
diff --git a/apps/desktop/src/renderer/popups/history-popup/style.css b/apps/desktop/src/renderer/popups/history-popup/style.css
index cbc8e11..8a3d9bc 100644
--- a/apps/desktop/src/renderer/popups/history-popup/style.css
+++ b/apps/desktop/src/renderer/popups/history-popup/style.css
@@ -3,16 +3,16 @@
* WindowManager.insertCSS()가 :root에 테마 변수를 주입하면 자동 전환.
*/
-/* ── 다크 테마 fallback 기본값 (:root) ─────────────────────────── */
+/* ── Midnight(다크) fallback 기본값 (:root) ────────────────────── */
:root {
- --d3-bg-card: #242427;
- --d3-border-default: rgba(255, 255, 255, 0.08);
- --d3-border-subtle: rgba(255, 255, 255, 0.06);
- --d3-text-primary: rgba(255, 255, 255, 0.87);
- --d3-text-inactive: rgba(255, 255, 255, 0.3);
- --d3-text-muted: rgba(255, 255, 255, 0.25);
- --d3-accent-main: #f25b29;
- --d3-shadow-popup: 0 8px 32px rgba(0, 0, 0, 0.5);
+ --d3-bg-card: rgba(17, 26, 48, 0.85);
+ --d3-border-default: rgba(148, 180, 255, 0.12);
+ --d3-border-subtle: rgba(148, 180, 255, 0.07);
+ --d3-text-primary: #eef2fb;
+ --d3-text-inactive: #7385ab;
+ --d3-text-muted: rgba(147, 164, 200, 0.45);
+ --d3-accent-main: #3b82f6;
+ --d3-shadow-popup: 0 12px 40px rgba(3, 7, 18, 0.5);
}
* {
@@ -36,8 +36,9 @@ body {
.history-popup {
background: var(--d3-bg-card);
border: 1px solid var(--d3-border-default);
- border-radius: 12px;
+ border-radius: 14px;
padding: 6px;
+ backdrop-filter: blur(24px);
box-shadow: var(--d3-shadow-popup);
opacity: 0;
transform: translateY(4px) scale(0.98);
diff --git a/apps/desktop/src/renderer/popups/recording-tip/style.css b/apps/desktop/src/renderer/popups/recording-tip/style.css
index daf788f..6bfd71a 100644
--- a/apps/desktop/src/renderer/popups/recording-tip/style.css
+++ b/apps/desktop/src/renderer/popups/recording-tip/style.css
@@ -3,13 +3,15 @@
* WindowManager.insertCSS()가 :root에 테마 변수를 주입하면 자동 전환.
*/
-/* ── 다크 테마 fallback 기본값 (:root) ─────────────────────────── */
+/* ── Midnight(다크) fallback 기본값 (:root) ────────────────────── */
:root {
- --d3-bg-tip: rgba(0, 0, 0, 0.85);
- --d3-text-primary: rgba(255, 255, 255, 0.87);
- --d3-text-secondary: rgba(255, 255, 255, 0.6);
- --d3-accent-main: #f25b29;
- --d3-border-strong: rgba(255, 255, 255, 0.15);
+ --d3-bg-tip: rgba(17, 26, 48, 0.85);
+ --d3-text-primary: #eef2fb;
+ --d3-text-secondary: #93a4c8;
+ --d3-accent-main: #3b82f6;
+ --d3-border-default: rgba(148, 180, 255, 0.12);
+ --d3-border-strong: rgba(148, 180, 255, 0.2);
+ --d3-shadow-popup: 0 12px 40px rgba(3, 7, 18, 0.5);
}
* {
@@ -35,12 +37,14 @@ body {
.recording-tip {
background: var(--d3-bg-tip);
- border-radius: 8px;
+ border: 1px solid var(--d3-border-default);
+ border-radius: 14px;
padding: 8px 12px;
display: flex;
align-items: center;
gap: 8px;
- backdrop-filter: blur(10px);
+ backdrop-filter: blur(24px);
+ box-shadow: var(--d3-shadow-popup);
transition: opacity 150ms ease-in-out;
cursor: pointer;
}
@@ -60,6 +64,17 @@ body {
min-height: 2px;
}
+/* 9바 시안→블루→퍼플 스펙트럼 (v2 Midnight — RAW.dark gradient.wave1~3 보간) */
+.wave-bar:nth-child(1) { background: #22d3ee; box-shadow: 0 0 6px rgba(34, 211, 238, 0.35); }
+.wave-bar:nth-child(2) { background: #28bef0; box-shadow: 0 0 6px rgba(40, 190, 240, 0.35); }
+.wave-bar:nth-child(3) { background: #2fa9f2; box-shadow: 0 0 6px rgba(47, 169, 242, 0.35); }
+.wave-bar:nth-child(4) { background: #3596f4; box-shadow: 0 0 6px rgba(53, 150, 244, 0.35); }
+.wave-bar:nth-child(5) { background: #3b82f6; box-shadow: 0 0 6px rgba(59, 130, 246, 0.35); }
+.wave-bar:nth-child(6) { background: #4f78f6; box-shadow: 0 0 6px rgba(79, 120, 246, 0.35); }
+.wave-bar:nth-child(7) { background: #636ff6; box-shadow: 0 0 6px rgba(99, 111, 246, 0.35); }
+.wave-bar:nth-child(8) { background: #7765f6; box-shadow: 0 0 6px rgba(119, 101, 246, 0.35); }
+.wave-bar:nth-child(9) { background: #8b5cf6; box-shadow: 0 0 6px rgba(139, 92, 246, 0.35); }
+
.duration {
color: var(--d3-text-primary);
font-size: 13px;
diff --git a/apps/desktop/src/renderer/popups/result-popup/style.css b/apps/desktop/src/renderer/popups/result-popup/style.css
index 423b805..2cc38fb 100644
--- a/apps/desktop/src/renderer/popups/result-popup/style.css
+++ b/apps/desktop/src/renderer/popups/result-popup/style.css
@@ -4,15 +4,15 @@
* WindowManager.insertCSS()가 :root에 테마 변수를 주입하면 자동 전환.
*/
-/* ── 다크 테마 fallback 기본값 (:root) ─────────────────────────── */
+/* ── Midnight(다크) fallback 기본값 (:root) ────────────────────── */
:root {
- --d3-bg-result: #1e1e1e;
- --d3-border-result: rgba(255, 255, 255, 0.08);
- --d3-text-result: rgba(255, 255, 255, 0.87);
- --d3-action-btn: rgba(255, 255, 255, 0.4);
- --d3-action-btn-hover-bg: rgba(255, 255, 255, 0.08);
- --d3-action-btn-hover: rgba(255, 255, 255, 0.7);
- --d3-shadow-popup: 0 8px 32px rgba(0, 0, 0, 0.5);
+ --d3-bg-result: rgba(17, 26, 48, 0.85);
+ --d3-border-result: rgba(148, 180, 255, 0.12);
+ --d3-text-result: #eef2fb;
+ --d3-action-btn: rgba(147, 164, 200, 0.7);
+ --d3-action-btn-hover-bg: rgba(148, 180, 255, 0.08);
+ --d3-action-btn-hover: #c9d5f2;
+ --d3-shadow-popup: 0 12px 40px rgba(3, 7, 18, 0.5);
}
* {
@@ -37,8 +37,9 @@ body {
.result-popup {
background: var(--d3-bg-result);
border: 1px solid var(--d3-border-result);
- border-radius: 12px;
+ border-radius: 14px;
padding: 12px 16px;
+ backdrop-filter: blur(24px);
box-shadow: var(--d3-shadow-popup);
opacity: 0;
transform: translateY(4px);
@@ -89,7 +90,7 @@ body {
}
.action-btn.copied {
- color: #4caf50;
+ color: #34d399;
}
.hidden {
diff --git a/memory/project_status.md b/memory/project_status.md
index ac17d47..644418a 100644
--- a/memory/project_status.md
+++ b/memory/project_status.md
@@ -18,11 +18,12 @@
- **StatusBar**: 브랜드+버전+모델 / 로컬 시간 시계
- 검증: tsc 0, vitest 41/41, 실행 스크린샷으로 대시보드/히스토리/대화/회의/지식/설정 전 페이지 확인. 스크롤 시 폭 불변 확인. 상단 1px 잡티는 detached DevTools 겹침(앱 버그 아님)
-### 남은 마감(진행 중)
-1. 내부 페이지들의 @mui/icons-material → lucide-react 통일
-2. Vanilla 팝업 5종(recording-tip 등) 스타일 v2 정합
-3. VoiceRecordingPanel(대화 몰입 패널) → GradientWave 교체
-4. 라이트 테마 시각 검수, v0.2.0-alpha 릴리스
+### 마감 완료 (워크플로 8 에이전트 병렬)
+1. ✅ @mui/icons-material → lucide-react 전환 (23파일, 잔여 0 — 의존성 제거는 추후)
+2. ✅ Vanilla 팝업 5종 CSS v2 정합 (글래스 네이비 + 블루 액센트, var(--) 주입 구조 유지)
+3. ✅ VoiceRecordingPanel → GradientWave 교체
+4. 검증: tsc 0 / vitest 41/41 / 실화면 캡처 (Midnight·Nord 두 테마)
+5. 잔여(후속): 라이트 테마 정밀 검수, @mui/icons-material 의존성 package.json 제거, MetalDial(미사용) 정리, v0.2.0-alpha 릴리스
## 모델 현대화 트랙 (2026-07-21) — B 완료, A 완료(코드), C 완료(코드)