[ { "key": "formats", "area": "FormatShift 형식 색상·카테고리 시스템 (FsFmt* 토큰 + AllFormats / FormatPalette 매핑)", "inspiredBy": "파일 형식 hue 컨벤션은 VS Code Material Icon Theme(실제 SVG fill: table=#8BC34A, json=#F9A825, video=#FF9800, svg=#FFB300), Seti UI(Solarized: csv/xlsx=green, audio/svg=purple, video=pink), GitHub Linguist(svg=#FF9900 교차검증)에서 차용. 단, 이 앱의 형식색은 32x28px 솔리드 배지 위 흰 텍스트(Foreground=White)로 쓰이므로 — Catppuccin/Radix step11 파스텔(텍스트 라벨용, 흰 텍스트엔 너무 밝음)이 아니라 — 기존 FsFmt 14색과 동일한 Chakra/Tailwind 500 솔리드 톤에 채도·명도를 맞춰 재조정. 다크 배경(#090A0C~#1C1E22)에서 한 칩만 튀지 않도록 Linear의 'OKLCH 균일 명도' 원칙을 적용해 6색을 동급 luminance로 정렬. 충돌 회피(file-format-colors 토픽의 '최종 매핑')에 따라 기존 14색과 겹치지 않게 hue를 배정: PDF가 이미 red라 영상=코랄, PNG/GIF가 purple이라 오디오=라이트퍼플, HEIC가 amber라 JSON=더 노란 골드.", "proposal": "회색(FsFmtOther)으로 방치돼 있던 신규 16개 형식(csv/json/xlsx/svg/mp4·webm·mkv·mov·avi/mp3·aac·m4a·opus·ogg·flac·wav)을 6개 카테고리 색으로 점등. 카테고리 단위로 묶어 '데이터/표=그린(csv #68A063, xlsx #38A169), 데이터/구조=골드(json #E3B341), 벡터=핑크마젠타(svg #E05A9C), 영상=코랄(#FF7043, mp4·webm·mkv·mov·avi 공유), 오디오=라이트퍼플(#B794F4, mp3·aac·m4a·opus·ogg·flac·wav 공유)'로 배정. 색상환을 노랑→그린→핑크→코랄→퍼플로 넓게 띄워 인접 형식이 헷갈리지 않게 함. 6색 모두 Tailwind 400~500 luminance라 기존 14색과 한 세트로 보이고, 출력 배지(OutputFormatBadge)와 입력 큐 row 배지 양쪽에서 동일 hue로 일관되게 표시.", "implementationSteps": [ "FormatShiftTheme.xaml의 FsFmtOther(41행) 다음에 신규 6개 SolidColorBrush 토큰 블록 추가: FsFmtCsv #68A063, FsFmtXlsx #38A169, FsFmtJson #E3B341, FsFmtSvg #E05A9C, FsFmtVideo #FF7043, FsFmtAudio #B794F4. 각 토큰에 출처(Material/Seti/Linguist) 주석 명시.", "MainWindow.xaml.cs의 AllFormats 배열(813~843행)에서 신규 16개 항목의 4번째 인자 colorResource를 'FsFmtOther' → 카테고리 키로 교체(csv→FsFmtCsv, json→FsFmtJson, xlsx→FsFmtXlsx, svg→FsFmtSvg, 영상 5종→FsFmtVideo, 오디오 7종→FsFmtAudio). 이로써 TARGET FORMAT 사이드바 배지(UpdateOutputFormatBadge)가 카테고리 색으로 점등.", "MainWindow.xaml.cs의 FormatPalette.For() switch(1148~1164행)에 동일 매핑 추가 — 입력 파일이 큐 row / Past Results / 미리보기 메타에서도 같은 색 배지로 보이도록(QueueItem.FromPath·HistoryRow.From가 이 함수를 사용). xls·m4v·oga 등 형제 확장자도 같은 카테고리로 포함.", "dotnet build로 검증 — 경고 0개 / 오류 0개 확인 완료." ], "colorTokens": [ { "name": "FsFmtCsv", "hex": "#68A063", "usage": "CSV (데이터/표) — Material table=#8BC34A·Seti green 차용, xlsx보다 살짝 라이트한 라임그린으로 명도 분리" }, { "name": "FsFmtXlsx", "hex": "#38A169", "usage": "Excel/XLSX (스프레드시트) — Excel 브랜드 그린과 정합, 기존 webp(#48BB78)보다 딥하게 구분" }, { "name": "FsFmtJson", "hex": "#E3B341", "usage": "JSON (데이터/구조) — Material json=#F9A825·Seti yellow 컨벤션, 기존 HEIC(#D69E2E)보다 노랑쪽 골드로 충돌 회피" }, { "name": "FsFmtSvg", "hex": "#E05A9C", "usage": "SVG (벡터) — Seti svg=purple/css=pink 계열, 노랑 JSON과 한 화면에서 즉시 구분되는 핑크마젠타" }, { "name": "FsFmtVideo", "hex": "#FF7043", "usage": "영상 mp4/webm/mkv/mov/avi (공유) — 통념 '영상=난색', Material video=#FF9800 기반 코랄. 기존 PDF red(#E53E3E)와 구분" }, { "name": "FsFmtAudio", "hex": "#B794F4", "usage": "오디오 mp3/aac/m4a/opus/ogg/flac/wav (공유) — Seti audio=purple 통념, 기존 PNG/GIF purple보다 라이트한 바이올렛" } ], "xamlSnippets": [ { "purpose": "FormatShiftTheme.xaml — FsFmtOther 다음에 추가한 신규 카테고리 형식색 토큰 (출처 주석 포함)", "xaml": "\n\n\n\n\n\n\n\n\n\n\n\n" }, { "purpose": "MainWindow.xaml.cs — AllFormats 매핑 갱신 (FsFmtOther → 카테고리 색)", "xaml": "new(\".csv\", \"CSV\", \"CSV\", \"FsFmtCsv\"),\nnew(\".json\", \"JSON\", \"JSON\", \"FsFmtJson\"),\nnew(\".xlsx\", \"Excel\", \"XLSX\", \"FsFmtXlsx\"),\nnew(\".svg\", \"SVG\", \"SVG\", \"FsFmtSvg\"),\nnew(\".mp4\", \"MP4\", \"MP4\", \"FsFmtVideo\"),\nnew(\".webm\", \"WebM\", \"WEBM\", \"FsFmtVideo\"),\nnew(\".mkv\", \"MKV\", \"MKV\", \"FsFmtVideo\"),\nnew(\".mov\", \"MOV\", \"MOV\", \"FsFmtVideo\"),\nnew(\".avi\", \"AVI\", \"AVI\", \"FsFmtVideo\"),\nnew(\".mp3\", \"MP3\", \"MP3\", \"FsFmtAudio\"),\nnew(\".aac\", \"AAC\", \"AAC\", \"FsFmtAudio\"),\nnew(\".m4a\", \"M4A\", \"M4A\", \"FsFmtAudio\"),\nnew(\".opus\", \"Opus\", \"OPUS\", \"FsFmtAudio\"),\nnew(\".ogg\", \"OGG\", \"OGG\", \"FsFmtAudio\"),\nnew(\".flac\", \"FLAC\", \"FLAC\", \"FsFmtAudio\"),\nnew(\".wav\", \"WAV\", \"WAV\", \"FsFmtAudio\")," }, { "purpose": "MainWindow.xaml.cs — FormatPalette.For() 입력 확장자 매핑 추가 (큐 row/이력 배지용)", "xaml": "// 신규 카테고리 (output AllFormats 매핑과 동일 hue 유지)\n\"csv\" => (\"CSV\", \"FsFmtCsv\"),\n\"json\" => (\"JSON\", \"FsFmtJson\"),\n\"xlsx\" or \"xls\" => (\"XLSX\", \"FsFmtXlsx\"),\n\"svg\" => (\"SVG\", \"FsFmtSvg\"),\n\"mp4\" or \"webm\" or \"mkv\" or \"mov\" or \"avi\" or \"m4v\"\n => (ext.ToUpperInvariant(), \"FsFmtVideo\"),\n\"mp3\" or \"aac\" or \"m4a\" or \"opus\" or \"ogg\" or \"oga\" or \"flac\" or \"wav\"\n => (ext.ToUpperInvariant(), \"FsFmtAudio\")," } ], "userDecisions": [ "형식색을 Catppuccin 파스텔(2순위 권고)이 아니라 기존 14색과 동일한 Chakra/Tailwind 500 솔리드 톤으로 맞췄습니다 — 이 앱의 형식색은 흰 텍스트(Foreground=White) 솔리드 배지 배경으로 쓰이기 때문입니다. 만약 향후 형식색을 '라벨/텍스트'로도 쓰려면(예: row 우측 형식명) 그때는 Catppuccin/Radix step11 파스텔로 전체 14+6색 일괄 교체가 더 적합합니다 — 진행할까요?", "JSON 골드(#E3B341)는 흰 텍스트 대비가 다소 낮은 경계 케이스입니다. 다만 기존 FsFmtHeic(#D69E2E)가 이미 동급 골드에 흰 텍스트를 쓰고 있어 일관성은 유지됩니다. 가독성을 더 높이려면 JSON 배지에 한해 어두운 텍스트(#1C1E22)를 쓰는 옵션도 있습니다 — 적용할까요?", "리서치 권고에 있던 '배경/텍스트 3계층 Catppuccin/Radix 재정렬', '보더 8% 투명도화', 'OKLCH 14색 전체 재정렬', 'hover 마이크로인터랙션', '큐 row 좌측 카테고리 accent bar', 'empty state 개선'은 이번 과제 범위(신규 형식 색 부여) 밖이라 적용하지 않았습니다. 별도 작업으로 진행을 원하시면 알려주세요." ] }, { "key": "settings", "area": "AI API 키 설정 창(SettingsWindow.xaml/.cs) + 외부 도구(FFmpeg/LibreOffice) 미설치 안내 + MainWindow 우상단 ⚙ 진입점", "inspiredBy": "Cursor(Settings → API Keys: 마스킹 입력 + 명시적 Verify 버튼 + 성공/실패 인라인 배지 — \"검증 피드백 없으면 사용자가 키 적용을 불신한다\"), Zed(OS 키체인/DPAPI 암호화 저장 + 환경변수 자동 감지 후 \"환경변수에서 감지됨\" 보조 텍스트 노출), Raycast(Form.PasswordField 전용 보안 필드 + 'sk-...' placeholder 힌트, List/Detail 좌-우 섹션 구조), Microsoft Fluent/WinUI InfoBar(외부 의존성 미설치 안내를 Severity Warning/Success로 일괄 처리 — 다크에선 원색 대신 surface 위 subtle tint), Radix Colors dark scales(상태 시맨틱 12-step: solid=step9 / 텍스트=step11 / subtle 배경=step3 — Success #30A46C·#3DD68C·#132D21, Warning #FFC53D·#FFCA16·#302008, Danger #E5484D·#FF9592·#3B1219, Info #0090FF·#70B8FF·#0D2847), Linear(중립색 위주 + 사이드바를 더 어둡게 / 본문 작업영역을 밝게 하는 위계, radius 6px 통일, status dot 8px + 6px gap), Vercel Geist(보더는 rgba(255,255,255,0.08) 8% 알파로 거의 안 보이게, 그림자 대신 보더로 패널 분리), Apple HIG(2~5개 상호배타 즉시전환=Segmented Control, on/off=ToggleSwitch, 5개 초과=Select 드롭다운 기준). 기존 FsBg/FsText/FsAccent/FsFmt 토큰 체계와 FsSegmentStyle·FsPrimaryButtonStyle·FsPathInputStyle을 그대로 계승.", "proposal": "기존 DiagnoseWindow처럼 ui:FluentWindow 모달로 신규 SettingsWindow(640x720)를 만들되, Mica 대신 MainWindow와 동일한 FsBgBase 다크 톤 + FormatShiftTheme.xaml 머지로 일관성을 유지한다. 화면은 3개 섹션 카드(FsBgSurface #1C1E22, radius 6px, 보더 rgba 8%)로 구성한다.\n\n(1) AI 백엔드 섹션 — Cursor/Zed 패턴. '활성 백엔드'는 Segmented Control(Auto / OpenAI / Anthropic, 기존 FsSegmentStyle 재사용)로 즉시 전환. OpenAI·Anthropic 각각 한 행에 [PasswordBox(마스킹, placeholder 'sk-...' / 'sk-ant-...')] + [👁 평문토글] + [Verify 버튼]. PasswordBox는 FsPathInputStyle을 PasswordBox용으로 포팅한 FsPasswordInputStyle(FsBgInput #0F1012, FsFontMono, radius 6px)을 쓴다. Verify 결과는 우측에 status dot(8px) + 라벨 배지로: 성공=Green dot #30A46C + 'Valid'(#3DD68C / 배경 #132D21), 실패=Red #E5484D + 인라인 에러(#FF9592 / 배경 #3B1219), 검증중=indeterminate ProgressBar(180x3, FsAccentBlue). 환경변수(OPENAI_API_KEY/ANTHROPIC_API_KEY)가 잡히면 입력란 위에 '환경변수에서 감지됨' 보조텍스트(#8B909A=FsTextSecondary) + Info dot #0090FF. 검증 전 빈 필드엔 에러를 표시하지 않는다(premature validation 회피).\n\n(2) 모델 섹션 — 백엔드별 모델 ComboBox(OpenAI: gpt-4o-mini/gpt-4o, Anthropic: claude-3-5-sonnet-latest/claude-3-5-haiku-latest). 선택값은 ConvertOptions.AiOptions.Model로 흐르고, '비워두면 백엔드 기본 모델 사용' 캡션을 단다.\n\n(3) 외부 도구 섹션 — Fluent InfoBar 패턴을 FsFmt 톤으로 직접 그린 StatusRow 3개(FFmpeg / LibreOffice / MS Word COM). 각 행: 좌측 status dot + 도구명 + 상태 라벨 + (미설치 시) Warning subtle tint 배경(#302008) + 단일 '다운로드' CTA(FsPrimaryButtonStyle, 형식별 색은 영상=#FF7043 FFmpeg, 문서=#38A169 LibreOffice) + '이미 설치됨? 경로 지정' 보조 링크. 감지되면 Green Success 행으로 전환('준비됨' #3DD68C). ExternalToolDetector를 internal→public 노출하거나 App.Engine.Providers 점검 결과를 재사용해 채운다.\n\n저장은 ISettingsStore(DpapiSettingsStore) 단일 인스턴스를 App에서 노출해 LlmProvider와 공유 — 키 'openai.apikey'/'anthropic.apikey', 백엔드 'ai.backend', 모델 'ai.model'. 평문 settings.json 금지(이미 DPAPI 암호화로 보장됨). MainWindow 네비바 우상단 Actions StackPanel 맨 앞에 ⚙ 아이콘 버튼(FsSecondaryButtonStyle, 32x32, Path gear)을 추가해 SettingsWindow를 Owner=this 모달로 띄운다.\n\n상태 토큰은 FsFmt 신규색(#FF7043 video, #38A169 xlsx, #E3B341 json 등)과 함께 Radix dark 시맨틱 6종(Success/Warning/Danger/Info의 solid·text·bg)을 FormatShiftTheme.xaml에 추가해 배지/dot/InfoBar 전반에서 재사용한다.", "colorTokens": [ { "name": "FsStatusSuccess (Radix Green9 solid · dot/CTA)", "hex": "#30A46C", "usage": "Verify 성공 dot, 외부도구 '준비됨' dot, Save 버튼 호버" }, { "name": "FsStatusSuccessText (Green11 · 배지 텍스트)", "hex": "#3DD68C", "usage": "'Valid' / '준비됨' 라벨 텍스트 (다크 #1C1E22 위 4.5:1+)" }, { "name": "FsStatusSuccessBg (Green3 · subtle fill)", "hex": "#132D21", "usage": "성공 배지/Success InfoBar 행 배경" }, { "name": "FsStatusWarn (Radix Amber9 solid)", "hex": "#FFC53D", "usage": "미설치 경고 dot, InfoBar 아이콘" }, { "name": "FsStatusWarnText (Amber11)", "hex": "#FFCA16", "usage": "'미설치'/'점검 필요' 라벨 텍스트" }, { "name": "FsStatusWarnBg (Amber3 · InfoBar tint)", "hex": "#302008", "usage": "FFmpeg/LibreOffice 미설치 안내 행 배경 (subtle tint)" }, { "name": "FsStatusDanger (Radix Red9 solid)", "hex": "#E5484D", "usage": "Verify 실패 dot, 변환불가 표시" }, { "name": "FsStatusDangerText (Red11)", "hex": "#FF9592", "usage": "키 검증 실패 인라인 에러 텍스트" }, { "name": "FsStatusDangerBg (Red3 · subtle fill)", "hex": "#3B1219", "usage": "실패 배지/에러 박스 배경" }, { "name": "FsStatusInfo (Radix Blue9 solid)", "hex": "#0090FF", "usage": "'환경변수에서 감지됨' dot, 정보성 표시" }, { "name": "FsStatusInfoText (Blue11)", "hex": "#70B8FF", "usage": "환경변수 감지 보조텍스트 강조 시" }, { "name": "FsStatusInfoBg (Blue3)", "hex": "#0D2847", "usage": "정보성 배지 배경" }, { "name": "FsFmtVideo (FFmpeg 카테고리 · Material/Seti video)", "hex": "#FF7043", "usage": "FFmpeg 다운로드 CTA / 영상 형식칩 (pdf #E53E3E와 구분되는 코랄)" }, { "name": "FsFmtXlsx (LibreOffice/문서 · 엑셀 그린)", "hex": "#38A169", "usage": "LibreOffice 다운로드 CTA / 문서 형식 강조" }, { "name": "FsFmtJson (데이터 · Material json 골드)", "hex": "#E3B341", "usage": "신규 데이터 형식칩 (회색 방치 형식 정상화, 동시 적용 권장)" }, { "name": "FsBorderAlpha8 (Geist 8% 보더)", "hex": "#14FFFFFF", "usage": "설정 카드/섹션 구분 1px 보더 (rgba(255,255,255,0.08) ARGB 표기)" }, { "name": "FsTextSecondary (기존 · 보조텍스트, 변경 없음)", "hex": "#8B909A", "usage": "'환경변수에서 감지됨'/'비워두면 기본 모델' 캡션 (Zed 패턴)" } ], "xamlSnippets": [ { "purpose": "FormatShiftTheme.xaml에 추가할 Radix dark 상태 시맨틱 토큰 + FFmpeg/LibreOffice 형식색 (기존 FsFmt* 패턴 계승, 28~41행 뒤에 삽입)", "xaml": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" }, { "purpose": "PasswordBox 전용 입력 스타일 (FsPathInputStyle을 PasswordBox용으로 포팅 — PART_ContentHost 동일 사용 가능)", "xaml": "" }, { "purpose": "SettingsWindow.xaml — 창 셸 + API 키 한 행(PasswordBox + 👁 토글 + Verify + status dot 배지). Cursor/Zed/Raycast 패턴 집약", "xaml": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n " } ], "implementationSteps": [ "FormatShiftTheme.xaml: Radix dark 상태 시맨틱 12토큰(FsStatusSuccess/Warn/Danger/Info × solid/text/bg) + 신규 형식색(FsFmtVideo #FF7043, FsFmtAudio #B794F4, FsFmtXlsx #38A169, FsFmtJson #E3B341, FsFmtCsv #68A063, FsFmtSvg #E05A9C) 추가. FsPasswordInputStyle(PasswordBox용) 스타일 추가 — 기존 FsPathInputStyle 구조 복제 + PasswordChar ● + 포커스 시 FsAccentBlue 보더", "App.xaml.cs: 현재 Bootstrap이 내부에서 DpapiSettingsStore를 생성하므로, Everything2EverythingBootstrap.CreateDefault()가 ISettingsStore를 외부에서 받거나 반환하도록 시그니처를 확장하고 App에 'public ISettingsStore Settings { get; }'를 노출해 LlmProvider와 SettingsWindow가 동일 인스턴스를 공유 (키 'openai.apikey'/'anthropic.apikey'/'ai.backend'/'ai.model')", "ExternalToolDetector를 internal→public으로 승격(또는 얇은 public ToolStatusProbe 래퍼 신설)해 TryFindFfmpeg/TryFindLibreOfficeSoffice/IsWordComAvailable를 App 계층에서 호출 가능하게 함. 결과를 Settings 외부도구 섹션의 status dot/배지에 매핑(준비됨=Success, 미설치=Warning)", "SettingsWindow.xaml 신규: 32px TitleBar + ScrollViewer 3카드(AI 백엔드/모델/외부도구) + 푸터(저장·닫기). 카드는 FsBgSurface #1C1E22 + 보더 #14FFFFFF(8% 알파) + radius 6px. 백엔드는 FsSegmentStyle 3-segment, 키는 PasswordBox+👁토글+Verify 행 + status 배지, 외부도구는 InfoBar식 StatusRow", "SettingsWindow.xaml.cs 신규: 생성자에서 ((App)Application.Current).Settings로 기존 키 로드 → PasswordBox에 마스킹 표시(또는 '저장된 키 있음' placeholder), 환경변수 감지 시 EnvHint 노출. OnBackendSegmentClick(3-way 토글, ConflictSegment 패턴 차용), OnToggleReveal*(PasswordChar 토글 또는 평문 TextBox 스왑), OnVerify*Click(IChatClient.CompleteAsync로 1토큰 핑 → 성공 Green/실패 Red 배지, 검증중 indeterminate), OnSaveClick(ISettingsStore.Set), OnDownload*/OnSetPath* 핸들러", "Verify 구현: OpenAiChatClient/AnthropicChatClient를 임시 인스턴스화해 CompleteAsync(system='ping', user='ping', maxTokens=1) 호출 → 401/403=키 무효(Danger), 200=Valid(Success), 네트워크 오류=Warning. premature validation 회피: 빈 필드는 Verify 버튼 비활성", "외부도구 다운로드 CTA: FFmpeg→https://www.gyan.dev/ffmpeg/builds/ 또는 https://ffmpeg.org/download.html, LibreOffice→https://www.libreoffice.org/download/ 를 Process.Start로 브라우저 오픈. '경로 지정' 링크는 OpenFolderDialog로 ffmpeg 폴더를 받아 %LOCALAPPDATA%\\Everything2Everything\\ffmpeg에 매핑하거나 설정 키로 저장", "MainWindow.xaml: 네비바 Actions StackPanel(336행) 맨 앞에 ⚙ Button(FsSecondaryButtonStyle, 32x32, gear Path) 추가. MainWindow.xaml.cs: OnSettingsClick에서 new SettingsWindow(((App)Application.Current).Settings, ((App)Application.Current).Engine){ Owner=this }.ShowDialog(); 닫힌 뒤 _ = RefreshCapabilityStatusAsync()로 키 반영" ], "userDecisions": [ "Verify 방식: 실제 API 1토큰 핑(소액 과금 발생 가능, 가장 확실)으로 갈지 vs 키 형식 정규식 검사(sk-/sk-ant- prefix, 무과금이지만 무효 키 못 잡음)만 할지 — Cursor는 실제 검증 권장", "모델 선택 범위: 고정 드롭다운(gpt-4o-mini/gpt-4o, claude-3-5-sonnet/haiku)로 충분한지, 아니면 자유 입력 TextBox로 임의 모델명 허용할지", "외부도구 '경로 지정'을 SettingsWindow에서 영구 저장(ISettingsStore 'ffmpeg.dir' 키 신설)할지, 아니면 기존 %LOCALAPPDATA%\\Everything2Everything\\ffmpeg 폴더에 복사 안내만 할지 — 후자는 코드 변경 최소", "신규 형식색(FsFmtVideo/Audio/Xlsx/Json/Csv/Svg)을 이번에 함께 적용해 MainWindow.xaml.cs의 AllFormats 'FsFmtOther' 회색 매핑까지 교체할지, 아니면 Settings 창에 필요한 상태색만 먼저 넣을지 (리서치 권고는 일괄 교체)", "SettingsWindow를 모달(ShowDialog, DiagnoseWindow와 동일)로 둘지 vs 비모달로 둘지 — 모달이면 닫힐 때 RefreshCapabilityStatus 동기화가 단순" ] }, { "key": "badges", "area": "손실 등급(Lossless / Container / Recode / Rasterize) 배지 + 상태 dot 시스템 — 출력 드롭다운 / 큐 row / capability 경고", "inspiredBy": "Radix Colors (dark scales) — Badge의 \"같은 step 번호 = 같은 UI 역할\" 규칙(solid=step9 / text=step11 / subtle bg=step3)을 4개 손실 등급의 dot·텍스트·배경 토큰화에 그대로 적용. 실제 hex: Green9 #30A46C·Green11 #3DD68C·Green3 #132D21, Amber9 #FFC53D·Amber11 #FFCA16·Amber3 #302008, Red9 #E5484D·Red11 #FF9592·Red3 #3B1219, Blue9 #0090FF·Blue11 #70B8FF·Blue3 #0D2847 (https://www.radix-ui.com/themes/docs/components/badge). shadcn/Radix Badge — pill 형태(height 20~22px, radius 6px, h-padding 8px). Linear (status dot/pill, https://linear.app/now/how-we-redesigned-the-linear-ui) — 8px 채워진 원 + 6px gap + 라벨 패턴, 다크에서 LCH로 명도 통일해 어떤 등급이든 같은 밝기로 보이게. Microsoft Fluent / WinUI InfoBar (https://learn.microsoft.com/windows/apps/design/controls/infobar) — capability 경고를 Severity(Warning) subtle tint(step3 배경 + step11 텍스트) + 단일 CTA로. Things 3 (https://culturedcode.com/things/features/) — 큐 row hover 시에만 보조 정보 페이드인. 기존 FsFmt/FsAccent 토큰 체계와 8px spacing·6px radius는 계승.", "proposal": "손실 등급은 이미 Core의 LossClass enum(Lossless/Container/Recode/Rasterize, ProviderRegistry.cs)에 SSOT로 존재하고 FindBestPath가 경로의 엣지별 Loss를 반환한다. 하지만 UI에는 전혀 노출되지 않아, 사용자가 \"JPG로 바꾸면 화질이 깎이는지\", \"PDF→PNG는 텍스트 편집성이 사라지는지\"를 알 수 없다. 이 4등급을 Radix Badge의 step9/11/3 3토큰 규칙으로 시각화하되, 색상은 \"심각도 그라데이션\"으로 의미 고정한다: Lossless=Green(안전), Container=Blue(중립·구조변경), Recode=Amber(주의·화질저하), Rasterize=Red(경고·편집성 상실, 단방향 절벽). 형식색(FsFmt*)은 \"무엇으로/무엇에서\"를 나타내고, 손실 등급색은 \"얼마나 보존되는가\"를 나타내는 직교(orthogonal)한 축이므로 절대 섞지 않는다. 표현 강도는 3단계로 차등: (1) 출력 드롭다운 = 선택지마다 8px dot + 짧은 라벨(Lossless/Recode 등)로 선택 전 예고, (2) 큐 row = 등급에 따라 dot만 또는 Recode/Rasterize일 때만 작은 pill 배지(과하지 않게 — Lossless/Container는 dot만), (3) capability 경고 = InfoBar Warning tint. 핵심 절제 원칙: Lossless·Container는 조용한 dot, Recode·Rasterize만 텍스트 라벨까지 노출해 \"주의가 필요할 때만 눈에 띄게\".", "colorTokens": [ { "name": "FsLossLosslessDot", "hex": "#30A46C", "usage": "Lossless 등급 dot/solid (Radix Green9). 무손실 — 픽셀·내용 완전 보존" }, { "name": "FsLossLosslessText", "hex": "#3DD68C", "usage": "Lossless 배지 텍스트 (Radix Green11). 다크 배경 4.5:1+ 대비" }, { "name": "FsLossLosslessBg", "hex": "#132D21", "usage": "Lossless pill subtle 배경 (Radix Green3)" }, { "name": "FsLossContainerDot", "hex": "#0090FF", "usage": "Container 등급 dot (Radix Blue9). 컨테이너/구조 변경, 내용 보존" }, { "name": "FsLossContainerText", "hex": "#70B8FF", "usage": "Container 배지 텍스트 (Radix Blue11)" }, { "name": "FsLossContainerBg", "hex": "#0D2847", "usage": "Container pill subtle 배경 (Radix Blue3)" }, { "name": "FsLossRecodeDot", "hex": "#FFC53D", "usage": "Recode 등급 dot (Radix Amber9). 재인코딩/손실 압축 — 주의" }, { "name": "FsLossRecodeText", "hex": "#FFCA16", "usage": "Recode 배지 텍스트 (Radix Amber11)" }, { "name": "FsLossRecodeBg", "hex": "#302008", "usage": "Recode pill subtle 배경 (Radix Amber3) — InfoBar Warning tint와 공유" }, { "name": "FsLossRasterizeDot", "hex": "#E5484D", "usage": "Rasterize 등급 dot (Radix Red9). 래스터화 — 편집성 상실, 단방향 절벽" }, { "name": "FsLossRasterizeText", "hex": "#FF9592", "usage": "Rasterize 배지 텍스트 (Radix Red11)" }, { "name": "FsLossRasterizeBg", "hex": "#3B1219", "usage": "Rasterize pill subtle 배경 (Radix Red3)" } ], "implementationSteps": [ "FormatShiftTheme.xaml: 기존 FsAccent* 블록(line 25) 아래에 위 12개 FsLoss* SolidColorBrush 토큰을 'Loss grade tokens (Radix dark Badge)' 주석과 함께 추가. 기존 FsFmt* 형식색은 손대지 않는다(직교 축).", "FormatShiftTheme.xaml: 손실 dot 재사용 스타일 FsLossDotStyle(Ellipse, Width/Height 8) + pill 컨테이너 스타일 FsLossPillStyle(Border, CornerRadius 6, Padding 8,2, Height 22)를 추가. dot은 8px 원 + 6px gap 라벨(Linear 패턴).", "MainWindow.xaml.cs: LossClass→(DotBrush, TextBrush, BgBrush, ShortLabel, FullLabel) 매핑 헬퍼 정적 클래스 LossPalette 추가(FormatPalette와 동형). ShortLabel='Lossless'/'Container'/'Recode'/'Rasterize', FullLabel은 한국어 툴팁('무손실 — 픽셀 완전 보존' 등 enum XML doc 문구 재사용).", "MainWindow.xaml.cs: 출력 형식 선택 시 engine.Providers.Graph.FindBestPath(input, output)의 worst-case Loss를 계산하는 메서드 LossGradeFor(IEnumerable sources, string outputExt) 추가 — 큐 전체 입력에 대해 각 경로의 최악 등급 중 가장 나쁜 것을 등급으로 채택(Max severity).", "출력 드롭다운: OutputFormatCombo ItemTemplate을 dot+라벨+등급dot 3요소로 교체(현재는 'JPEG (.jpg)' 텍스트만). 각 ComboBoxItem.Tag 옆에 그 형식의 손실 등급 dot을 우측 정렬로 배치해 선택 전 예고. OutputFormatBadge 아래에 현재 선택 등급 pill 1줄 추가(LossGradeBadge x:Name).", "큐 row: QueueItem에 LossClass 속성 + LossDotBrush/LossPillVisibility/LossLabel/LossBrush 파생 속성 추가. ItemTemplate Grid.Column 3(StateText) 영역 또는 형식 배지 우상단에 dot 배치. 절제: Lossless/Container는 dot만, Recode/Rasterize일 때만 pill 라벨을 ProgressVisibility처럼 조건부 노출(LossPillVisibility).", "capability 경고: 현재 CapabilityStatusText(평문 TextBlock, line 233)를 Radix Amber tint InfoBar 패턴 Border로 교체 — Background FsLossRecodeBg(#302008), 좌측 경고 아이콘 + FsLossRecodeText(#FFCA16) 텍스트 + 단일 'Diagnose' CTA. RefreshCapabilityStatusAsync의 '⚠ N개 형식이...' 문자열을 이 Border 내부 TextBlock에 바인딩.", "Rasterize 경고 강화(선택): 선택한 출력이 Rasterize 등급이면 출력 드롭다운 hint(OutputFormatHint)에 '벡터/텍스트 편집성이 사라집니다 (단방향)' 한 줄을 FsLossRasterizeText 색으로 표시해 비가역성을 사전 고지." ], "userDecisions": [ "Container 등급 색을 Blue(#0090FF, Radix Blue9)로 둘지 vs Teal/중립 회색으로 둘지 — Blue는 앱 액센트 FsAccentBlue(#3B72FF)와 hue가 가까워 '클릭 가능'으로 오인될 여지가 있음. 대안: Container를 무채색 dot(FsTextSecondary #8B909A)으로 '조용히' 처리(구조 변경은 손실이 아니므로 색을 아예 안 주는 것도 정합적). 추천: Container=중립 회색 dot, Blue는 순수 정보(info)용으로 아껴두기.", "큐 row에서 Recode/Rasterize pill을 '항상 노출' vs 'hover 시에만 페이드인'(Things 3 패턴) — 항상 노출은 정보 우선, hover는 미니멀 우선. 큐는 변환 전 검토 화면이므로 손실은 항상 보이는 게 안전하다고 보지만, 사용자가 미니멀을 선호하면 hover로 전환.", "출력 드롭다운 각 항목에 등급 dot을 '모든 형식'에 표시할지 vs Recode/Rasterize에만 표시할지 — 모든 형식에 dot을 주면 일관되지만 Lossless 초록 dot이 다수라 시각 노이즈가 될 수 있음. 추천: 전부 표시하되 Lossless는 가장 낮은 채도라 자연히 묻힘(Radix가 의도한 명도 통일 효과).", "손실 등급 라벨을 영어(Lossless/Container/Recode/Rasterize, enum 이름 그대로) vs 한국어(무손실/재포장/재인코딩/래스터화)로 표기할지 — 코드/툴팁은 영어 enum 유지, UI 가시 라벨만 한국어로 갈지 결정 필요. 앱이 한국어 hint를 쓰므로 가시 라벨도 한국어 권장, 영어는 툴팁 보조." ], "xamlSnippets": [ { "purpose": "FormatShiftTheme.xaml — 손실 등급 토큰 (Radix dark Badge step9/11/3). 기존 FsAccent* 블록(line 25) 바로 아래에 삽입", "xaml": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" }, { "purpose": "FormatShiftTheme.xaml — 재사용 dot + pill 스타일 (Linear 8px dot / shadcn pill 22px h, 6px radius)", "xaml": "\n\n\n\n" }, { "purpose": "MainWindow.xaml — 출력 드롭다운 아래 '현재 선택 손실 등급' pill (OutputFormatBadge 영역 line 89~101 아래, OutputFormatHint 위에 삽입). dot 8px + 6px gap + 한국어 라벨", "xaml": "\n\n \n \n \n \n" }, { "purpose": "MainWindow.xaml — 큐 row ItemTemplate: 형식 배지(line 401~409) 우상단에 손실 dot 오버레이. Lossless/Container는 dot만, Recode/Rasterize는 dot이 눈에 띄는 색", "xaml": "\n\n \n \n \n \n" }, { "purpose": "MainWindow.xaml — 큐 row의 StateText 컬럼(Grid.Column 3, line 428~432) 위/옆에 조건부 Recode·Rasterize pill (절제: Recode/Rasterize일 때만 Visible)", "xaml": "\n \n \n \n \n \n \n \n" }, { "purpose": "MainWindow.xaml — capability 경고를 평문 TextBlock(line 233)에서 Radix Amber InfoBar(Fluent Severity=Warning)로 교체. subtle tint 배경 + 단일 Diagnose CTA", "xaml": "\n \n \n \n \n \n \n \n \n \n \n \n" }, { "purpose": "MainWindow.xaml.cs — LossClass → 색/라벨/툴팁 매핑 헬퍼 (FormatPalette와 동형, SSOT 한 곳)", "xaml": "internal static class LossPalette\n{\n // (DotKey, TextKey, BgKey, 한국어 라벨, 툴팁)\n public static (string Dot, string Text, string Bg, string Label, string Tip) For(LossClass loss) => loss switch\n {\n LossClass.Lossless => (\"FsLossLosslessDot\", \"FsLossLosslessText\", \"FsLossLosslessBg\",\n \"무손실\", \"픽셀·내용을 완전 보존합니다\"),\n LossClass.Container => (\"FsLossContainerDot\", \"FsLossContainerText\", \"FsLossContainerBg\",\n \"재포장\", \"컨테이너·구조만 변경, 내용은 보존됩니다\"),\n LossClass.Recode => (\"FsLossRecodeDot\", \"FsLossRecodeText\", \"FsLossRecodeBg\",\n \"재인코딩\", \"재인코딩으로 화질이 일부 손실됩니다\"),\n LossClass.Rasterize => (\"FsLossRasterizeDot\", \"FsLossRasterizeText\", \"FsLossRasterizeBg\",\n \"래스터화\", \"벡터·텍스트 편집성이 사라집니다 (단방향)\"),\n _ => (\"FsLossRecodeDot\", \"FsLossRecodeText\", \"FsLossRecodeBg\", \"재인코딩\", \"\"),\n };\n\n // 배지/pill을 Recode·Rasterize일 때만 노출하는 절제 규칙\n public static Visibility PillVisibility(LossClass loss) =>\n loss is LossClass.Recode or LossClass.Rasterize\n ? Visibility.Visible : Visibility.Collapsed;\n}" }, { "purpose": "MainWindow.xaml.cs — 선택한 출력의 worst-case 손실 등급 계산 (큐 전체 입력에 대해 FindBestPath 경로의 최악 등급 중 최댓값). 기존 RefreshAvailableOutputFormats에서 호출", "xaml": "// 큐 전체에서 이 출력으로 갈 때의 '최악 손실 등급'을 산출 (Max severity)\nprivate LossClass WorstLossFor(string outputExt)\n{\n var graph = ((App)Application.Current).Engine.Providers.Graph;\n var worst = LossClass.Lossless;\n var inputs = _activeQueue.Count == 0\n ? Array.Empty()\n : _activeQueue.Select(q => Path.GetExtension(q.SourcePath)).Distinct().ToArray();\n\n foreach (var input in inputs)\n {\n var path = graph.FindBestPath(input, outputExt, maxHops: 3, allowLossy: true);\n if (path is null) continue;\n foreach (var edge in path) // 경로의 worst edge가 그 변환의 등급\n if ((int)edge.Loss > (int)worst) worst = edge.Loss;\n }\n return worst; // enum 순서가 곧 심각도: Lossless\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" }, { "purpose": "FormatShiftTheme.xaml — 타이포 letter-spacing 음수 보정(Linear) + tabular-nums(Notion Calendar). 기존 FsBodyStyle/FsLabelStyle/FsMonoStyle Setter에 추가. WPF는 TextBlock에 직접 letter-spacing이 없으므로 TextOptions/Typography 사용.", "xaml": "\n\n\n\n\n" }, { "purpose": "MainWindow.xaml — 빈 큐 상태(DropZoneEmpty, 363~380행 교체). Linear/Notion식 인터랙티브 empty state: 점선 드롭존 + 모노크롬 아이콘 + 헤드라인 + plain-language + 동작하는 Browse 버튼 + 형식 칩 프리뷰.", "xaml": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n" }, { "purpose": "MainWindow.xaml — row 액션 버튼 hover 페이드인을 트리거로 연결(위 스니펫의 정석 버전). 부모 Border의 IsMouseOver에 바인딩해 Opacity 0→1 애니메이션(150ms). DataTemplate 내부 Button.Style 에 삽입.", "xaml": "\n \n" }, { "purpose": "MainWindow.xaml — Preview empty state(PreviewEmpty, 606~618행) 강화. plain-language + '같은 자리 인터랙티브' 원칙으로 'Browse files' 보조 링크 추가, 그리고 Past Results 인라인 #10B98122 → 토큰 교체.", "xaml": "\n\n \n \n \n \n \n\n\n\n\n \n" }, { "purpose": "MainWindow.xaml.cs — 신규 형식색 매핑 연결. FormatPalette.For 와 AllFormats 의 FsFmtOther 폴백을 신규 토큰으로 교체.", "xaml": "// FormatPalette.For() switch 에 추가\n\"csv\" => (\"CSV\", \"FsFmtCsv\"),\n\"json\" => (\"JSON\", \"FsFmtJson\"),\n\"xlsx\" or \"xls\" => (\"XLSX\", \"FsFmtXlsx\"),\n\"svg\" => (\"SVG\", \"FsFmtSvg\"),\n\"mp4\" or \"webm\" or \"mkv\" or \"mov\" or \"avi\" => (ext.ToUpperInvariant(), \"FsFmtVideo\"),\n\"mp3\" or \"aac\" or \"m4a\" or \"opus\" or \"ogg\" or \"flac\" or \"wav\" => (ext.ToUpperInvariant(), \"FsFmtAudio\"),\n\"md\" or \"markdown\" => (\"MD\", \"FsFmtMd\"),\n\n// AllFormats 배열의 ColorResource 도 동일하게 (csv→FsFmtCsv, json→FsFmtJson, xlsx→FsFmtXlsx,\n// svg→FsFmtSvg, mp4/webm/mkv/mov/avi→FsFmtVideo, mp3/aac/m4a/opus/ogg/flac/wav→FsFmtAudio, md→FsFmtMd)" } ], "implementationSteps": [ "1) FormatShiftTheme.xaml 28~41행 FsFmt 블록 아래에 신규 형식색 7종(FsFmtJson/Csv/Xlsx/Svg/Video/Audio/Md) + Geist 투명보더 3종(FsBorderHairline/HairlineWeak/AccentBlueGlow) + Radix status 4종(FsStatusSuccessBg/Text/WarnBg/WarnText)을 추가 (1번 스니펫).", "2) 같은 파일에 FsNumStyle(tabular-nums) 추가하고 FsBodyStyle에 Typography.Kerning 보정 (2번 스니펫).", "3) FormatShiftTheme.xaml에 공통 row 스타일 FsRowStyle + 액션버튼 FsRowActionStyle 추가 (5번 스니펫).", "4) MainWindow.xaml.cs의 FormatPalette.For() switch와 AllFormats 배열에서 csv/json/xlsx/svg/video/audio/md의 ColorResource를 FsFmtOther → 신규 토큰으로 교체 (9번 스니펫). 이걸로 큐/이력/배지 색이 일괄 정상화됨.", "5) MainWindow.xaml DropZoneEmpty(363~380행)를 인터랙티브 empty state로 교체 — 점선 드롭존 + Browse 버튼(AddFilesCommand 바인딩) + 형식 칩 프리뷰 (3번 스니펫).", "6) DropHintOverlay(717~728행)를 액센트 점선 프레임 + 진해진 배경(#E6090A0C)으로 교체 (4번 스니펫).", "7) 큐 row DataTemplate(386~446행)에 좌측 2px accent bar 추가 + 삭제버튼을 FsRowActionStyle로 + 부모 hover DataTrigger로 페이드인(150ms) + SizeText에 FsNumStyle 적용 (6·7번 스니펫). Past Results row(484~560행)도 폴더열기 아이콘에 동일 hover 패턴 적용.", "8) Past Results 날짜 헤더 savings 배지의 하드코딩 #10B98122 → FsStatusSuccessBg 토큰으로, 텍스트는 FsStatusSuccessText로 교체 (8번 스니펫). PreviewEmpty도 아이콘 원형 칩 + plain-language로 다듬기.", "9) 여백 통일 패스: 패널 padding 16~24, 그룹 간 24~32(현행 유지), row 내부 12/10, 칩 간격 6~8px로 8 배수 그리드 점검. radius는 컨테이너 6 / 칩·배지 4로 수렴 (대부분 이미 일치).", "10) dotnet publish 후 카스케이드 재등록 → 사용자가 직접 push & GUI 검증 (MEMORY: project_build_pipeline / feedback_user_workflow)." ], "userDecisions": [ "신규 형식색을 Catppuccin/Material 컨벤션 hue(제안값: json=#E3B341 골드, csv=#68A063, xlsx=#38A169, svg=#E05A9C 핑크, video=#FF7043 코랄, audio=#B794F4 퍼플)로 갈지, 아니면 기존 14색이 Chakra/Tailwind 톤이므로 Tailwind 400 세트로 통일할지 — 제안은 컨벤션 hue + 기존 톤에 채도 맞춤(전자). 승인 필요.", "markdown(.md) 색을 신규 추가(FsFmtMd #60A5FA 블루)할지, 기존대로 FsFmtOther 회색 유지할지. (md=blue는 Material 컨벤션이나 docx/jpg 블루와 인접 — 선택 사항)", "row 좌측 Arc식 2px accent bar를 형식별 '개별 색'으로 둘지, 아니면 이미지/문서/표/벡터/영상/오디오 '카테고리 6색'으로 묶을지. 제안은 형식색 그대로(개별). 카테고리로 묶으면 더 차분하지만 코드에 카테고리 매핑 추가 필요.", "드롭 오버레이 점선(dashed) 표현 — WPF Border는 native dash가 없어 StrokeDashArray를 쓰려면 Rectangle 오버레이로 대체해야 함. 제안 스니펫은 solid 2px 프레임으로 단순화했는데, 진짜 점선이 필요하면 Rectangle+StrokeDashArray=\"6,4\"로 교체할지 확정 필요.", "Things3 hover 페이드인 애니메이션(150ms)을 전 row에 적용 시 대량 큐(수백 개)에서 Storyboard 비용 — 큐가 클 때 VirtualizingStackPanel 적용 여부도 같이 볼지(현재 ItemsControl은 가상화 비활성)." ] } ]