feat(ui): 형식 색상 + 손실/상태 시맨틱 토큰 (Catppuccin/Material/Radix 영감)
디자인 리서치(VS Code Material Icon Theme·Seti UI·Linguist·Radix dark·Catppuccin·Linear·Geist) 기반으로 형식 색상과 시맨틱 토큰을 확장. - 신규 형식색 FsFmtCsv/Xlsx/Json/Svg/Video/Audio (회색→카테고리별 hue) + AllFormats/FormatPalette 매핑 - 손실 등급 토큰 FsLoss*(Lossless=초록/Container=회색/Recode=앰버/Rasterize=빨강, Radix dark) - 상태 토큰 FsStatus*(Success/Warn/Danger/Info × dot/text/bg) - Geist식 8% 투명 보더 FsBorderHairline
This commit is contained in:
parent
df07eb9bdd
commit
faffb8f205
5 changed files with 1171 additions and 16 deletions
450
docs/ssot/_data/design_designs.json
Normal file
450
docs/ssot/_data/design_designs.json
Normal file
File diff suppressed because one or more lines are too long
504
docs/ssot/_data/design_research.json
Normal file
504
docs/ssot/_data/design_research.json
Normal file
|
|
@ -0,0 +1,504 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "palettes",
|
||||||
|
"topic": "다크 UI 색상 시스템 리서치 — FormatShift Utility(다크 Fluent 파일 변환 앱)의 형식별 카테고리 컬러 팔레트 선정",
|
||||||
|
"findings": [
|
||||||
|
{
|
||||||
|
"system": "Radix Colors (dark scales)",
|
||||||
|
"url": "https://www.radix-ui.com/colors/docs/palette-composition/understanding-the-scale",
|
||||||
|
"takeaway": "12단계 스케일을 '의미 역할'로 표준화한 게 핵심. step1=앱 배경 / step2=서브틀 배경 / step3-5=컴포넌트 배경(normal/hover/pressed) / step6-8=보더 / step9=가장 채도 높은 솔리드(브랜드/뱃지 칠) / step10=hover / step11=저대비 텍스트 / step12=고대비 텍스트. 형식별 색을 '텍스트/라벨'로 쓸 거면 step11, '꽉 찬 칩 배경'으로 쓸 거면 step9를 골라야 대비가 보장됨. 현재 프로젝트의 FsBg(090A0C~1C1E22)/FsText 3계층 구조가 Radix step1·2·3·11·12와 1:1 매핑되므로 이 철학을 그대로 차용하면 토큰 정리가 깔끔해짐."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Catppuccin (Mocha)",
|
||||||
|
"url": "https://catppuccin.com/palette/",
|
||||||
|
"takeaway": "다크 배경(base #1e1e2e)에서 명도가 거의 균일하게 튜닝된 14개 파스텔 액센트가 그대로 '12색 카테고리 팔레트'로 최적. 서로 충돌 안 하고 채도/명도가 고름 — 이 앱의 신규 형식 색 분배에 1순위 추천. 배경 3계층(crust #11111b / mantle #181825 / base #1e1e2e)·텍스트 3계층(text #cdd6f4 / subtext0 #a6adc8 / overlay1 #7f849c)도 현 구조와 정확히 대응."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Tokyo Night",
|
||||||
|
"url": "https://github.com/folke/tokyonight.nvim",
|
||||||
|
"takeaway": "배경 #1a1b26 / 사이드바·탭 #1f2335 / 보더 #3b4261 라는 '약간 푸른빛 도는 다크' 계층이 현재 앱의 중립 회색 계층보다 따뜻함은 덜하지만 액센트 8색(빨강 f7768e·초록 9ece6a·노랑 e0af68·파랑 7aa2f7·마젠타 bb9af7·시안 7dcfff)이 채도가 적당히 죽어 있어 다수 칩을 나란히 둬도 눈이 안 아픔. Catppuccin보다 살짝 더 채도가 있는 톤을 원하면 대안."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Tailwind CSS",
|
||||||
|
"url": "https://tailwindcss.com/docs/colors",
|
||||||
|
"takeaway": "slate/zinc 중립 + 채도 컬러를 '셰이드 번호'로 균일 관리하는 방식이 표준. 다크 배경에선 500이 살짝 어두워 400~500을 텍스트/라벨용으로 쓰는 게 정석(red-400 #F87171, green-400 #4ADE80, blue-400 #60A5FA, yellow-400 #FACC15). 현재 앱의 FsAccentGreen #10B981·Amber #F59E0B·Red #EF4444가 이미 Tailwind 값이므로, 신규 형식색도 Tailwind 400/500에서 뽑으면 기존 액센트와 일관됨. 단 한 팔레트에서 14색을 뽑으면 인접 색(emerald/teal/cyan 등)이 헷갈릴 수 있어 색상환을 넓게 띄워 골라야 함."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Nord",
|
||||||
|
"url": "https://www.nordtheme.com/docs/colors-and-palettes",
|
||||||
|
"takeaway": "Aurora 액센트가 단 5색(빨강 bf616a·주황 d08770·노랑 ebcb8b·초록 a3be8c·보라 b48ead)이라 12 카테고리엔 부족. 저채도·차분함이 강점이지만 색 가짓수가 적어 '많은 형식 구분'엔 부적합 — 참고용. Polar Night(2e3440~4c566a)는 이 앱보다 밝은 회색이라 #090A0C 기조와는 안 맞음."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "GitHub Primer (dark)",
|
||||||
|
"url": "https://primer.style/foundations/color/overview/",
|
||||||
|
"takeaway": "기능색을 역할명(fg/bg/accent/danger/success/attention/done/sponsors)으로 토큰화. 다크 기본값: 배경 canvas #0d1117·텍스트 fg.default #e6edf3·muted #7d8590·accent #2f81f7. 형식 구분용으로 쓸 만한 '밝은 다크용' 액센트는 syntax 팔레트가 검증됨: 빨강 #ff7b72·주황 #ffa657·노랑/금 (number)#79c0ff·초록 #7ee787·파랑 #79c0ff·연파랑 #a5d6ff·보라 #d2a8ff·danger #f85149. 배경 #0d1117이 이 앱 #090A0C와 가장 가까워 대비 수치를 거의 그대로 신뢰 가능."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"recommendation": "결론: 신규 형식(csv/json/xlsx/svg/영상/오디오)과 회색으로 방치된 형식들을 위해 '단일 출처에서 명도·채도가 균일하게 튜닝된 12~14색 세트'를 통째로 채택하라. 현재 FsFmt* 14색은 Chakra UI 계열을 손으로 모은 것이라 명도가 들쭉날쭉하고(예: Docx #2B6CB0·Hwp #9B2C2C·Bmp #718096은 #090A0C 배경에서 너무 어두워 라벨이 안 읽힘) 일관성이 없다. 이를 Catppuccin Mocha 액센트(다크 검증 완료, 1순위) 또는 Radix dark step-11(라벨/텍스트로 쓸 때 WCAG 대비 보장, 2순위)로 일괄 교체할 것을 권한다.\\n\\n구체 적용: (1) 형식색을 '라벨/아이콘 텍스트'로 쓰므로 채도 솔리드(Radix step9)가 아니라 명도가 높은 파스텔(Catppuccin) 또는 step11을 써야 #090A0C에서 4.5:1 이상 대비가 난다. (2) 색상환을 빨강→주황→노랑→초록→틸→시안→파랑→인디고→보라→마젠타→핑크 순으로 넓게 띄워 배정해 인접 형식이 헷갈리지 않게 한다(예: PDF=빨강, XLSX=초록, CSV=틸, JSON=노랑, SVG=주황, 영상=보라, 오디오=핑크, DOCX=파랑). (3) 기존 액센트 FsAccentGreen #10B981·Amber #F59E0B·Red #EF4444가 이미 Tailwind 값이므로, 만약 Catppuccin으로 안 가고 기존과의 통일을 우선한다면 Tailwind 400 세트로 형식색을 뽑아 액센트와 한 가족으로 묶는 것도 정합적이다. (4) 배경/텍스트 3계층은 현 구조가 이미 Radix·Catppuccin 철학과 일치하므로 유지하되, FsBorderSubtle/Strong을 Radix step6/8 개념으로만 재확인하면 충분하다.\\n\\npalette 항목에는 '형식 카테고리 12색'을 Catppuccin Mocha 기준으로 제시한다(다크 배경 대비·조화가 가장 안전). Tailwind/Radix 대체 값은 findings·sources 참고.",
|
||||||
|
"palette": [
|
||||||
|
{
|
||||||
|
"name": "Red (PDF/HWP 류)",
|
||||||
|
"hex": "#F38BA8",
|
||||||
|
"category": "형식 카테고리 (Catppuccin Mocha)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Peach/Orange (HTML/SVG 류)",
|
||||||
|
"hex": "#FAB387",
|
||||||
|
"category": "형식 카테고리 (Catppuccin Mocha)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Yellow (JSON/HEIC 류)",
|
||||||
|
"hex": "#F9E2AF",
|
||||||
|
"category": "형식 카테고리 (Catppuccin Mocha)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Green (XLSX/WEBP 류)",
|
||||||
|
"hex": "#A6E3A1",
|
||||||
|
"category": "형식 카테고리 (Catppuccin Mocha)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Teal (CSV/RAW 류)",
|
||||||
|
"hex": "#94E2D5",
|
||||||
|
"category": "형식 카테고리 (Catppuccin Mocha)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sky/Cyan (TIFF 류)",
|
||||||
|
"hex": "#89DCEB",
|
||||||
|
"category": "형식 카테고리 (Catppuccin Mocha)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sapphire (오디오 류)",
|
||||||
|
"hex": "#74C7EC",
|
||||||
|
"category": "형식 카테고리 (Catppuccin Mocha)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Blue (JPG/DOCX 류)",
|
||||||
|
"hex": "#89B4FA",
|
||||||
|
"category": "형식 카테고리 (Catppuccin Mocha)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lavender (BMP/Other 류)",
|
||||||
|
"hex": "#B4BEFE",
|
||||||
|
"category": "형식 카테고리 (Catppuccin Mocha)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mauve (PNG/GIF/영상 류)",
|
||||||
|
"hex": "#CBA6F7",
|
||||||
|
"category": "형식 카테고리 (Catppuccin Mocha)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pink (AVIF 류)",
|
||||||
|
"hex": "#F5C2E7",
|
||||||
|
"category": "형식 카테고리 (Catppuccin Mocha)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Maroon (예비/위험 형식)",
|
||||||
|
"hex": "#EBA0AC",
|
||||||
|
"category": "형식 카테고리 (Catppuccin Mocha)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "배경 Base (앱 최하층, 현 #090A0C 대응)",
|
||||||
|
"hex": "#11111B",
|
||||||
|
"category": "배경 계층 (Catppuccin Crust)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "배경 Panel (사이드바/패널)",
|
||||||
|
"hex": "#181825",
|
||||||
|
"category": "배경 계층 (Catppuccin Mantle)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "배경 Surface (카드/큐 row)",
|
||||||
|
"hex": "#1E1E2E",
|
||||||
|
"category": "배경 계층 (Catppuccin Base)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "텍스트 Primary",
|
||||||
|
"hex": "#CDD6F4",
|
||||||
|
"category": "텍스트 계층 (Catppuccin Text)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "텍스트 Secondary",
|
||||||
|
"hex": "#A6ADC8",
|
||||||
|
"category": "텍스트 계층 (Catppuccin Subtext0)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "텍스트 Tertiary",
|
||||||
|
"hex": "#7F849C",
|
||||||
|
"category": "텍스트 계층 (Catppuccin Overlay1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "액센트 Blue (대안: Radix blue step9)",
|
||||||
|
"hex": "#0090FF",
|
||||||
|
"category": "액센트 (Radix dark)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "형식색 대안 — Tailwind red-400",
|
||||||
|
"hex": "#F87171",
|
||||||
|
"category": "형식 대안 (Tailwind 400, 기존 액센트와 동일 계열)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "형식색 대안 — Tailwind green-400",
|
||||||
|
"hex": "#4ADE80",
|
||||||
|
"category": "형식 대안 (Tailwind 400)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "형식색 대안 — Tailwind blue-400",
|
||||||
|
"hex": "#60A5FA",
|
||||||
|
"category": "형식 대안 (Tailwind 400)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "형식색 대안 — Radix amber step11",
|
||||||
|
"hex": "#FFCA16",
|
||||||
|
"category": "형식 대안 (Radix dark step11, 텍스트 대비 보장)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "형식색 대안 — Radix teal step11",
|
||||||
|
"hex": "#0BD8B6",
|
||||||
|
"category": "형식 대안 (Radix dark step11)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
"https://www.radix-ui.com/colors/docs/palette-composition/understanding-the-scale",
|
||||||
|
"https://www.radix-ui.com/colors/docs/palette-composition/scales",
|
||||||
|
"https://github.com/radix-ui/colors",
|
||||||
|
"https://catppuccin.com/palette/",
|
||||||
|
"https://github.com/catppuccin/palette",
|
||||||
|
"https://github.com/folke/tokyonight.nvim",
|
||||||
|
"https://github.com/tokyo-night/tokyo-night-vscode-theme",
|
||||||
|
"https://tailwindcss.com/docs/colors",
|
||||||
|
"https://www.nordtheme.com/docs/colors-and-palettes",
|
||||||
|
"https://primer.style/foundations/color/overview/",
|
||||||
|
"https://github.com/primer/github-vscode-theme",
|
||||||
|
"https://stylishthemes.github.io/GitHub-Dark/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "ui-patterns",
|
||||||
|
"topic": "생산성/유틸리티 데스크톱 앱(Linear, Raycast, Geist/Vercel, Arc, Things, Notion Calendar) 다크 모드 UI 패턴 — 3컬럼 파일 변환 앱(FormatShift) 완성도 향상을 위한 리서치",
|
||||||
|
"findings": [
|
||||||
|
{
|
||||||
|
"system": "Linear (앱 + 디자인 시스템)",
|
||||||
|
"takeaway": "중성 색상 거의 100%로 구성하고 브랜드 색은 액센트에서만 사용. 배경 ramp가 핵심: Pitch Black #08090A(앱 배경) → Graphite #0F1011(패널/사이드바) → Charcoal #23252A(카드/입력) → Gunmetal #383B3F(보더/구분선). 텍스트는 #F7F8F8(Porcelain, 주) / #D0D6E0(Light Steel, 보조) / #8A8F98(Storm Cloud, 약). 액센트 Aether Blue #5E6AD2. 2024 리디자인의 핵심 교훈: '사이드바를 몇 단계 더 어둡게(dimmer)' 만들어 중앙 작업 영역이 우선순위를 갖게 함 → 현재 FormatShift 좌측 옵션패널/우측 미리보기를 배경보다 약간 어둡게, 중앙 변환큐를 가장 밝게 처리하면 시선 위계가 잡힌다. 구분선(separator)을 줄여 시각 노이즈 감소. radius 6px 통일.",
|
||||||
|
"url": "https://linear.app/now/how-we-redesigned-the-linear-ui"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Linear 타이포 + spacing",
|
||||||
|
"takeaway": "Inter Variable 사용 + letter-spacing 음수 보정(display -0.22px, body -0.11px)이 '깔끔한 느낌'의 핵심. 기본 element gap 8px, primary 버튼/카드/입력 radius 6px 통일. FormatShift도 Inter 이미 사용 중이므로 letter-spacing 음수값(-0.1~-0.2px)을 본문/제목에 적용하면 즉각 고급감 상승. LCH/OKLCH 색공간으로 라이트니스 균일화 — 14개 형식색상을 OKLCH 기준 동일 lightness로 재정렬하면 신규 형식(csv/json/xlsx 등)도 자연스럽게 채도만 다른 동급 칩으로 보인다.",
|
||||||
|
"url": "https://rogerwong.me/2024/11/how-we-redesigned-the-linear-ui-part-ii"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Vercel Geist 디자인 시스템",
|
||||||
|
"takeaway": "다크 배경 2단계 분리 원칙: Background 1 = #0A0A0A(주 배경, 그 위에 색을 올릴 때), Background 2 = #000(미묘한 차별화가 필요할 때만 절제 사용). 보더는 rgba 8% 불투명도(rgba(255,255,255,0.08))로 매우 낮게 — 다크에서 보더는 '거의 안 보일 만큼' 얇고 투명해야 자연스럽다. radius 0~4px로 매우 보수적, 그림자 거의 안 씀(보더 기반). letter-spacing -0.04em. FormatShift의 3컬럼 구분을 그림자 대신 1px / 8% 보더로 처리하면 다크에서 깔끔. 현재 보더가 진하면 8% 투명도로 낮춰라.",
|
||||||
|
"url": "https://vercel.com/geist/colors"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Raycast",
|
||||||
|
"takeaway": "List / Grid / Detail 3종 컴포넌트 = 사실상 좌(목록)·우(상세) 패턴의 정석. Grid의 insets로 아이템 콘텐츠와 보더 사이 여백을 itemSize에 비례시킴 → FormatShift 변환큐 row의 내부 패딩을 row 높이에 비례시키면 밀도 조절 시 일관성 유지. 시맨틱 토큰(background/primaryText/support)을 역할 기반으로 정의해 라이트/다크 자동 적응 — 색을 hex 직접 박지 말고 'surface/text-primary/accent' 같은 역할 이름의 리소스로 추상화하라(WPF DynamicResource와 궁합 좋음).",
|
||||||
|
"url": "https://developers.raycast.com/api-reference/user-interface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Notion Calendar (구 Cron)",
|
||||||
|
"takeaway": "구체 수치가 가장 풍부: radius 4px(sm)/6px(md), 그리드 보더 1px @ 9% 불투명도(구조용, 장식 아님), 팝오버 그림자 0 4px 12px rgba(0,0,0,0.12)로 최소한만. 타이포 극단 대비(display 64px vs label 12px = 5.3:1 비율, 일반 2~3:1보다 큼)로 단일 폰트만으로 포스터 같은 임팩트. 시간 거터는 11px monospaced numerals — FormatShift의 파일크기/진행률 숫자에 JetBrains Mono tabular-nums 적용하면 정렬감 상승. 키보드-퍼스트가 시각적 미니멀리즘을 정당화 → 모든 액션에 단축키 부여하면 버튼/툴바를 줄여도 됨. 밀도는 Zoom In/Out으로 사용자가 조절.",
|
||||||
|
"url": "https://blakecrosley.com/guides/design/notion-calendar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Arc Browser",
|
||||||
|
"takeaway": "Space마다 고유 그라데이션으로 즉각적 시각 인식 부여 + 채도/그레인 조절. 자동 숨김 사이드바로 콘텐츠 우선. soft rounded corners + subtle animation + smooth transition으로 '산만함 제한'이 모토. FormatShift에 적용: 변환 형식(이미지/문서/표/벡터/영상/오디오) 카테고리별로 은은한 그라데이션/색조를 옵션패널 헤더나 큐 row 좌측 accent bar에 부여하면 형식 분류가 직관적. 단, 채도는 낮게(다크 배경에서 grain/저채도가 자연스러움).",
|
||||||
|
"url": "https://blakecrosley.com/guides/design/arc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Things 3",
|
||||||
|
"takeaway": "Apple Design Award 수상의 핵심은 마이크로인터랙션: hover 시에만 액션 버튼이 페이드인(평소엔 숨김)으로 노이즈 제거. hover state는 '무엇이 인터랙션 가능한지'를 알려주는 신호 — row/카드/입력/필터드롭다운 모든 요소에 일관된 hover 피드백 필요. FormatShift 큐 row도 hover 시에만 삭제/재시도/폴더열기 아이콘을 페이드인하면 평소엔 깔끔하고 필요할 때만 노출.",
|
||||||
|
"url": "https://culturedcode.com/things/features/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Empty State 베스트 프랙티스 (Linear/Notion 사례)",
|
||||||
|
"takeaway": "Linear/Notion은 simple monochrome 일러스트로 인터페이스에 녹아들면서 따뜻함 제공(현란한 일러스트 금지). 구성 4요소: 간결한 헤드라인 + 보조 설명(왜 비었는지를 plain language로) + 모노크롬 아이콘/일러스트 + 구체적 CTA. CTA는 'Add Your First Task'처럼 행동 지향 + 구체적. 최신 트렌드는 '같은 화면에서 바로 추가'(passive 메시지 → interactive). FormatShift 빈 변환큐: 'Drop files here to convert' + 점선 드롭존 + 'Browse files' 버튼 + 지원 형식 칩 미리보기를 같은 자리에 인터랙티브하게.",
|
||||||
|
"url": "https://www.eleken.co/blog-posts/empty-state-ux"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"recommendation": "FormatShift에 적용할 구체 변경안 (다크 Fluent 유지 전제):\n\n1) 배경 3단계 위계 재정렬 (Linear 패턴). 현재 #090A0C~#1C1E22를 명확한 역할로 분리: 앱 최외곽/창 배경 #090A0C → 좌측 옵션패널·우측 미리보기 패널 #0F1011~#121316(약간 더 어둡게, dimmer) → 중앙 변환큐 카드/큐 row #1A1C20~#1C1E22(가장 밝게). 핵심: 작업 대상인 중앙 큐가 가장 밝아야 시선이 모인다.\n\n2) 보더는 투명도 기반으로 (Geist + Notion Calendar). 3컬럼 구분과 row 구분을 진한 회색 선 대신 1px rgba(255,255,255,0.06~0.09)로. 그림자는 팝오버/컨텍스트메뉴에만 0 4px 12px rgba(0,0,0,0.4~0.5) 수준으로 최소 사용. 패널 분리는 보더로, 떠 있는 요소만 그림자로.\n\n3) radius 통일 6px (Linear). 버튼/입력/카드/큐 row 모두 6px. 미세 요소(칩/배지)는 4px. 현재 값이 제각각이면 6px/4px 2단계로 수렴.\n\n4) 타이포 letter-spacing 음수 보정. Inter 본문/라벨에 -0.1~-0.2px, 큰 제목엔 -0.3px 수준. 파일크기·진행률·해상도 숫자는 JetBrains Mono + tabular-nums(고정폭 숫자)로 큐에서 세로 정렬.\n\n5) 신규 형식 색상 정상화 (Linear OKLCH 균일화). 회색으로 방치된 csv/json/xlsx/svg/영상/오디오에 기존 14색과 동일 lightness·채도의 색 부여. 권장: 데이터/표 계열 green #0F7B6C(엑셀 연상), 코드/구조 계열 amber #CB912F(json/yaml), 벡터 purple #9065B0, 영상 red/orange, 오디오 pink #C14C8A — 모두 OKLCH lightness 동일하게 맞춰 동급 칩으로 보이게. 칩 스타일은 저채도 배경(색 @ 12~15% 알파) + 진한 텍스트.\n\n6) hover 마이크로인터랙션 (Things 3). 큐 row의 삭제/재시도/출력폴더열기 아이콘은 평소 숨기고 hover 시 페이드인(150~200ms). focus 링은 액센트 #3B72FF @ 2px outline. 모든 인터랙티브 요소(버튼/row/드롭다운)에 일관된 hover 배경(흰색 4~6% 오버레이).\n\n7) 빈 변환큐 empty state. 점선 드롭존 + 모노크롬 아이콘 + 'Drop files to convert, or Browse' + 지원형식 칩 프리뷰를 같은 영역에 인터랙티브하게. 현란한 일러스트 금지(Linear식 모노크롬).\n\n8) 8px spacing scale. 패널 패딩 16px, 그룹 간 24px, row 내부 8/12px, 칩 간격 8px로 8의 배수 그리드 통일.\n\n9) 형식 카테고리 accent bar (Arc). 큐 row 좌측에 형식 카테고리별 2px 저채도 accent bar를 두면 이미지/문서/표/벡터/영상/오디오가 한눈에 구분된다.",
|
||||||
|
"palette": [
|
||||||
|
{
|
||||||
|
"name": "App Background (창 배경)",
|
||||||
|
"hex": "#08090A",
|
||||||
|
"category": "background"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Panel / Sidebar (좌·우 패널, dimmer)",
|
||||||
|
"hex": "#0F1011",
|
||||||
|
"category": "background"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Card / Queue Row (중앙 큐, 밝게)",
|
||||||
|
"hex": "#1C1E22",
|
||||||
|
"category": "background"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Elevated Surface (입력/호버 표면)",
|
||||||
|
"hex": "#23252A",
|
||||||
|
"category": "surface"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Border / Separator (8% 알파)",
|
||||||
|
"hex": "rgba(255,255,255,0.08)",
|
||||||
|
"category": "border"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Border subtle (그리드 라인 6%)",
|
||||||
|
"hex": "rgba(255,255,255,0.06)",
|
||||||
|
"category": "border"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Text Primary",
|
||||||
|
"hex": "#F7F8F8",
|
||||||
|
"category": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Text Secondary",
|
||||||
|
"hex": "#D0D6E0",
|
||||||
|
"category": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Text Tertiary / Muted",
|
||||||
|
"hex": "#8A8F98",
|
||||||
|
"category": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Accent Blue (현 유지 가능)",
|
||||||
|
"hex": "#3B72FF",
|
||||||
|
"category": "accent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Accent Blue (Linear 대안)",
|
||||||
|
"hex": "#5E6AD2",
|
||||||
|
"category": "accent"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Format - Data/Table (xlsx/csv)",
|
||||||
|
"hex": "#0F7B6C",
|
||||||
|
"category": "format"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Format - Code/Structure (json/yaml)",
|
||||||
|
"hex": "#CB912F",
|
||||||
|
"category": "format"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Format - Vector (svg)",
|
||||||
|
"hex": "#9065B0",
|
||||||
|
"category": "format"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Format - Video",
|
||||||
|
"hex": "#D9730D",
|
||||||
|
"category": "format"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Format - Audio",
|
||||||
|
"hex": "#C14C8A",
|
||||||
|
"category": "format"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Popover Shadow",
|
||||||
|
"hex": "0 4px 12px rgba(0,0,0,0.45)",
|
||||||
|
"category": "shadow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
"https://linear.app/now/how-we-redesigned-the-linear-ui",
|
||||||
|
"https://rogerwong.me/2024/11/how-we-redesigned-the-linear-ui-part-ii",
|
||||||
|
"https://styles.refero.design/style/90ce5883-bb24-4466-93f7-801cd617b0d1",
|
||||||
|
"https://vercel.com/geist/colors",
|
||||||
|
"https://github.com/ephraimduncan/geist-colors",
|
||||||
|
"https://developers.raycast.com/api-reference/user-interface",
|
||||||
|
"https://manual.raycast.com/themes",
|
||||||
|
"https://blakecrosley.com/guides/design/notion-calendar",
|
||||||
|
"https://blakecrosley.com/guides/design/arc",
|
||||||
|
"https://culturedcode.com/things/features/",
|
||||||
|
"https://www.eleken.co/blog-posts/empty-state-ux",
|
||||||
|
"https://muz.li/blog/dark-mode-design-systems-a-complete-guide-to-patterns-tokens-and-hierarchy/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "file-colors",
|
||||||
|
"topic": "파일 형식/확장자별 색상 컨벤션 — 신규 형식(csv/json/xlsx/svg/영상/오디오) 색상 매핑 (다크 Fluent 기준)",
|
||||||
|
"findings": [
|
||||||
|
{
|
||||||
|
"system": "VS Code Material Icon Theme (실제 SVG fill hex)",
|
||||||
|
"takeaway": "가장 실용적인 1차 레퍼런스. 실제 렌더 색상: json=#F9A825(앰버/골드), svg=#FFB300(앰버), table(csv/xlsx/tsv 공용)=#8BC34A(라임그린), image(png/jpg/gif/webp)=#26A69A(틸), video(mp4 등)=#FF9800(오렌지), audio(mp3 등)=#EF5350(레드), markdown=#42A5F5(블루), html=#E65100(딥오렌지), xml=#8BC34A(그린), pdf=#EF5350, yaml=#FF5252. 핵심: json/svg는 '노랑~앰버', 데이터테이블(csv/xlsx)은 '그린'이 표준. 이 앱은 png를 보라로 쓰지만 Material은 이미지 전체를 틸로 묶음 → 신규형식만 추가하는 입장에선 Material의 카테고리 색만 차용 권장.",
|
||||||
|
"url": "https://github.com/material-extensions/vscode-material-icon-theme"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Seti UI 아이콘 테마 (Solarized 9색 팔레트)",
|
||||||
|
"takeaway": "확장자→색 매핑이 명시적. json=yellow(#B58900), csv=green(#859900), yaml=purple(#6C71C4), svg=purple(#6C71C4), 이미지(png/jpg)=purple, mp4(video)=pink(#D33682), mp3(audio)=purple, xlsx=green, pdf=red(#DC322F), markdown=blue(#268BD2), html/xml=orange(#CB4B16). 핵심 시사점: 영상은 '핑크', 오디오/벡터는 '보라' 계열을 쓴다는 또 다른 강한 컨벤션. Material과 종합하면 영상=레드~핑크~오렌지(난색), 오디오=레드~보라(난색~퍼플)로 수렴.",
|
||||||
|
"url": "https://github.com/jesseweed/seti-ui/blob/master/styles/components/icons/mapping.less"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "GitHub Linguist languages.yml (언어 식별 color)",
|
||||||
|
"takeaway": "브랜드 정체성 색이라 UI 가독성엔 부적합한 경우 있음(json=#292929 거의 검정, yaml=#CB171E 레드). 단 일부는 유용: svg=#FF9900(오렌지), css=#663399(퍼플), html=#E34C26(주황빨강), scss=#C6538C(핑크), markdown=#083FA1(딥블루). 결론: csv/json/xlsx 같은 데이터 형식엔 Linguist 대신 아이콘테마 색을 쓰는 게 맞다. svg가 오렌지/앰버라는 점만 교차검증 자료로 채택.",
|
||||||
|
"url": "https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "기존 FormatShiftTheme.xaml 14색 형식 팔레트",
|
||||||
|
"takeaway": "이미 Chakra/Tailwind 톤(채도 중간, 다크 배경 대비 양호)으로 통일돼 있음: pdf=#E53E3E, docx/jpg=블루(#2B6CB0/#3182CE), html=#DD6B20(오렌지), png/gif=퍼플(#805AD5/#9F7AEA), webp=#48BB78(그린), raw/tiff=틸. 신규색은 반드시 이 채도/명도대(Tailwind 500 전후, S~70% L~55%)에 맞춰야 14개와 한 세트로 보인다. 생짜 Material/Solarized hex를 그대로 넣으면 채도가 튄다 → 컨벤션의 '색상(hue)'만 가져오고 채도는 기존 톤에 맞춰 재조정 권장.",
|
||||||
|
"url": "https://github.com/material-extensions/vscode-material-icon-theme/blob/main/src/core/icons/fileIcons.ts"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"recommendation": "신규 6개 형식의 회색을 카테고리 컨벤션 색으로 교체하되, 기존 14색과 동일한 톤(Tailwind 500대, 다크 #090A0C~#1C1E22 배경 위 대비 4.5:1+)으로 채도를 맞춘다. 3개 레퍼런스(Material 실제 hex / Seti Solarized / Linguist) 종합 결론은 다음과 같다.\\n\\n1) JSON = 앰버/골드 — Material json=#F9A825, Seti=yellow가 압도적 컨벤션. 기존 heic(#D69E2E 앰버)와 충돌하니 살짝 더 노란 #E3B341(또는 #ECC94B) 권장. '데이터=노랑' 직관.\\n2) CSV / XLSX(스프레드시트) = 그린 — Material table=#8BC34A, Seti=green 일치. 둘은 같은 데이터테이블 카테고리이므로 동일 hue로 묶되 명도만 살짝 다르게: csv=#68A063, xlsx=#38A169(엑셀 브랜드 그린과도 정합). 기존 webp(#48BB78)와 구분되게 약간 더 딥하게.\\n3) SVG(벡터) = 앰버~오렌지 — Material svg=#FFB300, Linguist svg=#FF9900 교차검증. 단 json이 노랑을 쓰므로 SVG는 핑크/마젠타로 차별화하는 것도 강한 옵션(Seti는 svg=purple, css계열은 핑크). 권장: 벡터 = 핑크-마젠타 #E05A9C 또는 앰버 #F6AD55 중 택1. json과 한 화면에 같이 뜨면 핑크 추천(노랑과 즉시 구분).\\n4) 영상(mp4 등) = 레드~오렌지(난색) — Material video=#FF9800, Seti video=pink, 통념 '영상=빨강'. 기존 pdf가 이미 레드(#E53E3E)라 충돌 → 영상은 코랄/오렌지레드 #FF7043(또는 #ED8936) 권장. 난색 유지하되 pdf와 구분.\\n5) 오디오(mp3 등) = 퍼플~핑크 — Seti audio=purple, 통념 '오디오=보라/핑크'. 기존 png/gif가 퍼플(#805AD5/#9F7AEA)이라 충돌 → 오디오는 더 핑크 쪽 바이올렛 #B794F4 또는 마젠타핑크 #ED64A6(기존 avif와 동일하니 회피) → #B794F4(라이트 퍼플) 권장.\\n6) 마크업/기타 데이터(yaml/xml 등 추가 시) = Material yaml=#FF5252, xml=#8BC34A이나 일관성 위해: yaml=오렌지 #DD6B20(기존 html과 같은 마크업군), xml=그린계 회피하고 #ECC94B(데이터)로.\\n\\n충돌 회피 최종 매핑(기존 14색과 안 겹치게): json=#E3B341(골드), csv=#68A063(라임그린), xlsx=#38A169(엑셀그린), svg=#E05A9C(핑크-마젠타, 노랑json과 즉시 구분), video=#FF7043(코랄, pdf레드와 구분), audio=#B794F4(라이트퍼플). 6색 모두 Tailwind 400~500 명도라 기존 팔레트와 한 세트로 보이고, 다크 배경 대비도 충분. 적용은 FormatShiftTheme.xaml 28~40행의 FsFmt* 패턴에 FsFmtCsv/FsFmtJson/FsFmtXlsx/FsFmtSvg/FsFmtVideo/FsFmtAudio 키를 추가하면 됨.\",\"palette\":[{\"name\":\"JSON (데이터)\",\"hex\":\"#E3B341\",\"category\":\"data\"},{\"name\":\"CSV (데이터/표)\",\"hex\":\"#68A063\",\"category\":\"data\"},{\"name\":\"XLSX (스프레드시트)\",\"hex\":\"#38A169\",\"category\":\"data\"},{\"name\":\"SVG (벡터)\",\"hex\":\"#E05A9C\",\"category\":\"vector\"},{\"name\":\"영상 mp4/mkv/webm 등 (video)\",\"hex\":\"#FF7043\",\"category\":\"video\"},{\"name\":\"오디오 mp3/wav/flac 등 (audio)\",\"hex\":\"#B794F4\",\"category\":\"audio\"},{\"name\":\"[참고] SVG 앰버 대안\",\"hex\":\"#F6AD55\",\"category\":\"vector-alt\"},{\"name\":\"[참고] 영상 오렌지 대안\",\"hex\":\"#ED8936\",\"category\":\"video-alt\"},{\"name\":\"[레퍼런스] Material json 실제 fill\",\"hex\":\"#F9A825\",\"category\":\"reference\"},{\"name\":\"[레퍼런스] Material svg 실제 fill\",\"hex\":\"#FFB300\",\"category\":\"reference\"},{\"name\":\"[레퍼런스] Material table(csv/xlsx) fill\",\"hex\":\"#8BC34A\",\"category\":\"reference\"},{\"name\":\"[레퍼런스] Material image(png/jpg) fill\",\"hex\":\"#26A69A\",\"category\":\"reference\"},{\"name\":\"[레퍼런스] Material video fill\",\"hex\":\"#FF9800\",\"category\":\"reference\"},{\"name\":\"[레퍼런스] Material audio fill\",\"hex\":\"#EF5350\",\"category\":\"reference\"},{\"name\":\"[레퍼런스] Seti video=pink\",\"hex\":\"#D33682\",\"category\":\"reference\"},{\"name\":\"[레퍼런스] Seti audio/svg=purple\",\"hex\":\"#6C71C4\",\"category\":\"reference\"}]",
|
||||||
|
"sources": [
|
||||||
|
"https://github.com/material-extensions/vscode-material-icon-theme",
|
||||||
|
"https://github.com/material-extensions/vscode-material-icon-theme/blob/main/src/core/icons/fileIcons.ts",
|
||||||
|
"https://raw.githubusercontent.com/material-extensions/vscode-material-icon-theme/main/icons/json.svg",
|
||||||
|
"https://raw.githubusercontent.com/material-extensions/vscode-material-icon-theme/main/icons/svg.svg",
|
||||||
|
"https://raw.githubusercontent.com/material-extensions/vscode-material-icon-theme/main/icons/table.svg",
|
||||||
|
"https://raw.githubusercontent.com/material-extensions/vscode-material-icon-theme/main/icons/image.svg",
|
||||||
|
"https://raw.githubusercontent.com/material-extensions/vscode-material-icon-theme/main/icons/video.svg",
|
||||||
|
"https://raw.githubusercontent.com/material-extensions/vscode-material-icon-theme/main/icons/audio.svg",
|
||||||
|
"https://github.com/jesseweed/seti-ui/blob/master/styles/components/icons/mapping.less",
|
||||||
|
"https://github.com/microsoft/vscode/blob/main/extensions/theme-seti/icons/vs-seti-icon-theme.json",
|
||||||
|
"https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml",
|
||||||
|
"https://github.com/nvim-tree/nvim-web-devicons/blob/master/lua/nvim-web-devicons/icons-default.lua"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "components",
|
||||||
|
"topic": "FormatShift Utility 다크 Fluent 데스크톱 앱 UI 컴포넌트 리서치 (API키 입력 / 상태배지 / 미설치 안내 / 세그먼트·토글·셀렉트)",
|
||||||
|
"findings": [
|
||||||
|
{
|
||||||
|
"system": "Cursor (Settings → Models → API Keys)",
|
||||||
|
"takeaway": "API 키 입력은 단일 행에 [마스킹된 입력 필드] + [Verify 버튼]을 나란히 둔다. 저장 즉시가 아니라 명시적 'Verify'로 키 유효성을 검증하고, 성공 시 녹색 체크/‘valid’, 실패 시 인라인 에러를 표시. Cursor의 핵심 교훈은 '검증 피드백이 없으면(no verify/no error) 사용자가 키가 적용됐는지 알 수 없어 불신한다'는 점 — 반드시 가시적 검증 결과(성공/실패 배지)를 줘야 한다. OpenAI/Anthropic 키를 프로바이더별 섹션으로 분리.",
|
||||||
|
"url": "https://cursor.com/help/models-and-usage/api-keys"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Zed (Agent: open settings)",
|
||||||
|
"takeaway": "키는 settings 파일(평문)에 저장하지 않고 OS 키체인(Windows에선 Credential Manager/DPAPI)에 저장한다고 명시. 또한 OPENAI_API_KEY / ANTHROPIC_API_KEY 환경변수가 있으면 자동 사용하고 입력 필드에 '환경변수에서 감지됨' 상태를 노출. FormatShift도 DPAPI로 키를 암호화 저장하고, 입력란 옆에 '환경변수 감지됨' 또는 '키체인에 저장됨' 보조 텍스트를 #8B909A 색으로 표기하면 신뢰도가 올라간다.",
|
||||||
|
"url": "https://zed.dev/docs/ai/llm-providers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Raycast (Extension Preferences / Form.PasswordField)",
|
||||||
|
"takeaway": "민감 입력 전용 'password' preference 타입과 Form.PasswordField를 별도 제공 — 일반 textfield와 구분된 보안 필드. 빈 상태에는 placeholder로 형식 힌트(예: 'sk-...')를 주는 것을 권장. FormatShift도 PasswordBox 기반 마스킹 + 우측에 eye(👁) 토글로 평문 확인 + 'sk-ant-...'/'sk-...' 형식 placeholder를 적용.",
|
||||||
|
"url": "https://developers.raycast.com/api-reference/preferences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Radix Colors (Dark scales — 다크 UI 시맨틱 색 사실상 표준)",
|
||||||
|
"takeaway": "shadcn/Radix Badge가 쓰는 12-step 다크 스케일. 배지/dot의 solid는 step9, 배지 텍스트는 step11, 배지 배경(subtle fill)은 step3을 쓰는 것이 규칙(같은 step 번호 = 같은 UI 역할). 신규 회색 형식 색을 이 값으로 채우면 일관된 시맨틱이 됨 — Success/Green9 #30a46c·11 #3dd68c·3 #132d21, Warning/Amber9 #ffc53d·11 #ffca16·3 #302008, Danger/Red9 #e5484d·11 #ff9592·3 #3b1219, Info/Blue9 #0090ff·11 #70b8ff·3 #0d2847. '손실(lossy)' 배지는 Amber(주의), '비손실'은 Green을 권장.",
|
||||||
|
"url": "https://www.radix-ui.com/themes/docs/components/badge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Linear (status dot/pill)",
|
||||||
|
"takeaway": "상태는 채워진 점(dot)/얇은 pill로 표현하고, 다크모드에서 LCH 색공간으로 명도를 인지적으로 균일하게 맞춰 어느 색이든 같은 step이면 같은 밝기로 보이게 함(빨강·노랑이 동일 명도로 보임). 교훈: 14개 형식색을 그냥 채도만 다르게 두지 말고 '명도(밝기)를 통일'해야 다크 배경(#090A0C~#1C1E22)에서 한 형식만 튀지 않는다. 상태 dot은 8px 원 + 6px gap + 라벨 패턴이 표준.",
|
||||||
|
"url": "https://linear.app/now/how-we-redesigned-the-linear-ui"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Microsoft Fluent / WinUI InfoBar (미설치·경고 안내)",
|
||||||
|
"takeaway": "외부 의존성(FFmpeg/LibreOffice 등) 미설치 안내는 Severity(Informational/Success/Warning/Error)로 색·아이콘·접근성을 일괄 지정하는 InfoBar 패턴이 정석. 다크에서는 채도 높은 원색 대신 surface 위 subtle tint(Radix step3 배경 + step11 텍스트)를 쓰고, 우측에 단일 다운로드 CTA 버튼 하나만 배치('하나의 명확한 경로'). FormatShift는 Wpf.Ui FluentWindow이므로 ui:InfoBar Severity='Warning'을 그대로 활용 가능.",
|
||||||
|
"url": "https://learn.microsoft.com/en-us/windows/apps/design/controls/infobar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "빈 상태 / 다운로드 CTA (Atlassian·Material·PatternFly)",
|
||||||
|
"takeaway": "의존성 미설치 = 빈/경고 상태. 효과적 빈 상태는 항상 단 하나의 명확한 CTA를 제공('vague한 3개보다 명확한 1개'). 구성: 아이콘/일러스트 + 무엇이 없고 왜 필요한지 1줄 + 'XXX 다운로드' primary 버튼 + 보조로 '이미 설치했다면 경로 지정' 링크. 자동 감지 후에는 상태를 Success 배지로 전환해 진행 가능함을 즉시 알림.",
|
||||||
|
"url": "https://atlassian.design/components/empty-state/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"system": "Apple HIG / Mobbin (Segmented Control vs Toggle vs Select)",
|
||||||
|
"takeaway": "선택 기준이 명확함: 2개 이진 옵션 → Switch(토글), 상호배타 2~5개 + 즉시 전환 → Segmented Control(모두 보임/계층 없음), 5개 초과 또는 동적 → Select(드롭다운). 6개 이상을 세그먼트에 넣으면 너무 넓고 빽빽해 가독성 붕괴. 다크에서는 active 세그먼트를 '채워진(filled)' 처리로 확실히 구분해야 함(accent #3B72FF fill 또는 elevated surface).",
|
||||||
|
"url": "https://developer.apple.com/design/human-interface-guidelines/segmented-controls"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"recommendation": "FormatShift(다크 Fluent, accent #3B72FF) 4개 컴포넌트 적용안.\n\n(1) API 키 설정 화면: 좌측 옵션패널이 아닌 별도 Settings 화면에 OpenAI/Anthropic 섹션을 분리. 각 섹션은 한 행에 [PasswordBox(마스킹, placeholder 'sk-ant-...')] + [👁 평문토글] + [Verify 버튼] 배치(Cursor 패턴). 저장은 Windows DPAPI/Credential Manager로 암호화(Zed 패턴), 평문 settings 금지. 환경변수(ANTHROPIC_API_KEY) 감지 시 입력란 위에 '환경변수에서 감지됨' 보조텍스트 #8B909A. Verify 결과를 우측에 상태 배지로: 성공 = Green dot #30a46c + 'Valid', 실패 = Red #e5484d + 인라인 에러, 검증중 = spinner. 검증 전 빈 필드는 submit 전까지 에러 표시 안 함(premature validation 회피).\n\n(2) 상태/손실 배지: 14개 형식색 + 신규 6종(csv/json/xlsx/svg/영상/오디오)을 Radix 다크 step9 기준으로 명도 통일(Linear 교훈). 손실 여부 배지는 시맨틱 고정 — 비손실 = Green(#30a46c solid / #132d21 bg / #3dd68c text), 손실 = Amber(#ffc53d / #302008 / #ffca16), 변환불가 = Red(#e5484d / #3b1219 / #ff9592). pill 형태: 높이 20~22px, radius 6px(또는 full), 수평 패딩 8px, 상태 dot은 8px 원 + 6px gap.\n\n(3) 의존성 미설치 안내: Wpf.Ui InfoBar Severity='Warning'(Amber tint #302008 배경 + #ffca16 텍스트/아이콘)로 'FFmpeg가 필요합니다 — 영상/오디오 변환에 필요' 1줄 + 단일 'FFmpeg 다운로드' CTA(accent #3B72FF) + '이미 설치됨? 경로 지정' 보조 링크. 자동 감지 성공 시 Green Success InfoBar로 전환. 큐의 해당 형식 칩은 미설치 동안 회색 유지 → 설치 후 정상색 점등.\n\n(4) 세그먼트/토글/셀렉트: 출력 형식처럼 2~5개 즉시 전환은 Segmented Control(active 세그먼트 accent #3B72FF fill 또는 elevated #1C1E22 surface로 명확히 구분). on/off(예: '메타데이터 보존')는 ToggleSwitch. 5개 초과(형식 전체 목록 등)는 ComboBox(Select). 6개 이상을 세그먼트에 넣지 말 것.\n\n간격/반경 통일: 컴포넌트 radius 6px, 컨테이너 8px, 컨트롤 높이 32px, 배지 높이 20~22px, dot 8px, gap 6~8px.",
|
||||||
|
"palette": [
|
||||||
|
{
|
||||||
|
"name": "Success solid (비손실 dot/배지)",
|
||||||
|
"hex": "#30A46C",
|
||||||
|
"category": "status-success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Success text (배지 텍스트)",
|
||||||
|
"hex": "#3DD68C",
|
||||||
|
"category": "status-success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Success bg (subtle fill)",
|
||||||
|
"hex": "#132D21",
|
||||||
|
"category": "status-success"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Warning solid (손실/주의 dot)",
|
||||||
|
"hex": "#FFC53D",
|
||||||
|
"category": "status-warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Warning text (배지 텍스트)",
|
||||||
|
"hex": "#FFCA16",
|
||||||
|
"category": "status-warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Warning bg (InfoBar tint)",
|
||||||
|
"hex": "#302008",
|
||||||
|
"category": "status-warning"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Danger solid (변환불가/에러 dot)",
|
||||||
|
"hex": "#E5484D",
|
||||||
|
"category": "status-danger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Danger text (배지 텍스트)",
|
||||||
|
"hex": "#FF9592",
|
||||||
|
"category": "status-danger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Danger bg (subtle fill)",
|
||||||
|
"hex": "#3B1219",
|
||||||
|
"category": "status-danger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Info solid (정보 dot)",
|
||||||
|
"hex": "#0090FF",
|
||||||
|
"category": "status-info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Info text (배지 텍스트)",
|
||||||
|
"hex": "#70B8FF",
|
||||||
|
"category": "status-info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Info bg (subtle fill)",
|
||||||
|
"hex": "#0D2847",
|
||||||
|
"category": "status-info"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Neutral text (보조/감지됨 텍스트)",
|
||||||
|
"hex": "#8B909A",
|
||||||
|
"category": "neutral-existing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Accent (앱 기존 — CTA/active 세그먼트)",
|
||||||
|
"hex": "#3B72FF",
|
||||||
|
"category": "accent-existing"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
"https://cursor.com/help/models-and-usage/api-keys",
|
||||||
|
"https://forum.cursor.com/t/no-verify-button-when-setting-custom-openai-api-key-in-settings-models/140843",
|
||||||
|
"https://zed.dev/docs/ai/llm-providers",
|
||||||
|
"https://developers.raycast.com/api-reference/preferences",
|
||||||
|
"https://developers.raycast.com/api-reference/user-interface",
|
||||||
|
"https://uxpatterns.dev/patterns/forms/password",
|
||||||
|
"https://www.radix-ui.com/themes/docs/components/badge",
|
||||||
|
"https://www.radix-ui.com/colors",
|
||||||
|
"https://github.com/radix-ui/colors/blob/main/src/dark.ts",
|
||||||
|
"https://linear.app/now/how-we-redesigned-the-linear-ui",
|
||||||
|
"https://linear.app/changelog/2020-12-04-themes",
|
||||||
|
"https://learn.microsoft.com/en-us/windows/apps/design/controls/infobar",
|
||||||
|
"https://github.com/microsoft/microsoft-ui-xaml/issues/3744",
|
||||||
|
"https://atlassian.design/components/empty-state/",
|
||||||
|
"https://www.patternfly.org/components/empty-state/",
|
||||||
|
"https://www.setproduct.com/blog/empty-state-ui-design",
|
||||||
|
"https://developer.apple.com/design/human-interface-guidelines/segmented-controls",
|
||||||
|
"https://mobbin.com/glossary/segmented-control",
|
||||||
|
"https://wpfui.lepo.co/documentation/themes.html"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
145
docs/ssot/_data/design_synth.json
Normal file
145
docs/ssot/_data/design_synth.json
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
{
|
||||||
|
"vision": "FormatShift를 \"변환 전에 결과를 신뢰할 수 있는\" 다크 유틸리티로 끌어올린다. 세 축이 직교한다: (1) 형식색(FsFmt*) = '무엇을/무엇으로', (2) 손실 등급색(FsLoss*) = '얼마나 보존되나', (3) 상태색(FsStatus*, Radix dark) = 'AI키·외부도구가 준비됐나'. 사용자가 명시 요구한 '형식 색상'과 'AI 설정창'을 최우선으로 하되, 형식색은 이미 직전 커밋(25bebb4)에서 구현 완료된 상태이므로 잔여 마감(빈 상태 칩 텍스트)만 처리하고, 남은 에너지를 AI 설정창과 손실 등급 배지에 집중한다. 모든 신규 토큰은 기존 FsBg/FsText/FsFmt 명명 규칙으로만 확장하고, Core(LossClass/IChatClient/ExternalToolDetector)는 SSOT 그대로 재사용해 UI 계층만 손댄다.",
|
||||||
|
"priorities": [
|
||||||
|
{
|
||||||
|
"title": "AI API 키 설정 창(SettingsWindow) 신설 — 사용자 명시 요구, 현재 키 입력 UI가 전무함",
|
||||||
|
"impact": "high",
|
||||||
|
"effort": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "App에 Settings(ISettingsStore) 공유 노출 + ExternalToolDetector public 승격 — 설정창의 전제 인프라",
|
||||||
|
"impact": "high",
|
||||||
|
"effort": "low"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "손실 등급(Lossless/Container/Recode/Rasterize) 배지·dot 시스템 — Core LossClass가 이미 있는데 UI 미노출, 체감 가치 큼",
|
||||||
|
"impact": "high",
|
||||||
|
"effort": "medium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Radix dark 상태 시맨틱 토큰 + 손실 토큰 + FsPasswordInputStyle 추가 (테마 1회 확장)",
|
||||||
|
"impact": "high",
|
||||||
|
"effort": "low"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "capability 경고를 평문 TextBlock → InfoBar(Amber tint) 패턴으로 격상",
|
||||||
|
"impact": "medium",
|
||||||
|
"effort": "low"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "형식색 잔여 마감: 빈 큐 상태 지원형식 칩 텍스트 갱신(신규 16형식 반영) — 색 토큰/매핑은 이미 완료됨",
|
||||||
|
"impact": "medium",
|
||||||
|
"effort": "low"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "전반 UX 폴리시(인터랙티브 빈 상태·hover 페이드인·accent bar·tabular-nums)",
|
||||||
|
"impact": "medium",
|
||||||
|
"effort": "medium"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"implementationPlan": [
|
||||||
|
{
|
||||||
|
"step": "테마 토큰 1회 확장 — Radix dark 상태색 + 손실 등급색 + PasswordBox 스타일",
|
||||||
|
"files": [
|
||||||
|
"src/Everything2Everything.App/Views/FormatShiftTheme.xaml"
|
||||||
|
],
|
||||||
|
"detail": "FsAccent 블록(25행) 아래에 추가: (A) Radix dark 상태 12토큰 — FsStatusSuccess Solid #30A46C/Text #3DD68C/Bg #132D21, Warn #FFC53D/#FFCA16/#302008, Danger #E5484D/#FF9592/#3B1219, Info #0090FF/#70B8FF/#0D2847. (B) 손실 등급 토큰 — FsLossLossless=Success계열(녹색), FsLossContainer=중립회색 dot(FsTextSecondary #8B909A 재사용, userDecision 참조), FsLossRecode=Amber계열, FsLossRasterize=Danger계열의 dot/text/bg 3쌍. (C) Geist 투명보더 FsBorderHairline=#14FFFFFF(8% 알파). 그리고 FsPathInputStyle(287행) 복제해 FsPasswordInputStyle(TargetType=PasswordBox, PART_ContentHost 대신 PasswordBox 템플릿, FsBgInput 배경/FsFontMono/radius 6, 포커스 시 BorderBrush=FsAccentBlue) 추가. 형식색 6토큰(43-57행)은 이미 존재하므로 건드리지 않음."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "App에 ISettingsStore 공유 노출 + Bootstrap 시그니처 확장",
|
||||||
|
"files": [
|
||||||
|
"src/Everything2Everything.Core/Everything2EverythingBootstrap.cs",
|
||||||
|
"src/Everything2Everything.App/App.xaml.cs"
|
||||||
|
],
|
||||||
|
"detail": "Bootstrap.CreateDefault()가 내부에서 new DpapiSettingsStore()를 만들어 LlmProvider에만 넘기는데(9·26행), 동일 인스턴스를 App이 공유해야 함. CreateDefault(out ISettingsStore settings) 오버로드(또는 (ConversionEngine, ISettingsStore) 튜플 반환)를 추가. App.xaml.cs(10행)에서 Engine과 함께 'public ISettingsStore Settings { get; }'를 같은 store로 초기화해 LlmProvider·SettingsWindow가 키 'openai.apikey'/'anthropic.apikey'/'ai.backend'/'ai.model'을 공유하게 함."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "ExternalToolDetector를 internal→public 승격(또는 public ToolStatusProbe 래퍼)",
|
||||||
|
"files": [
|
||||||
|
"src/Everything2Everything.Core/Converters/ExternalToolDetector.cs"
|
||||||
|
],
|
||||||
|
"detail": "현재 internal static(5행). 설정창 외부도구 섹션이 App 계층에서 TryFindFfmpeg/TryFindLibreOfficeSoffice/IsWordComAvailable를 호출해야 하므로 class를 public으로 바꾸거나, 이미 public인 표면을 원하면 Core에 얇은 'public static class ToolStatusProbe' 래퍼를 신설해 3개 메서드를 위임. 시그니처(out string)는 그대로 유지."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "SettingsWindow.xaml 신설 — 3카드(AI백엔드/모델/외부도구) 모달",
|
||||||
|
"files": [
|
||||||
|
"src/Everything2Everything.App/Views/SettingsWindow.xaml"
|
||||||
|
],
|
||||||
|
"detail": "FluentWindow(또는 기존 Window) 640x720, FormatShiftTheme.xaml 머지 + FsBgBase 배경. 32px TitleBar + ScrollViewer 3카드(FsBgSurface #1C1E22, 보더 FsBorderHairline 8%, radius 6). 카드1: '활성 백엔드' FsSegmentStyle 3분할(Auto/OpenAI/Anthropic) + OpenAI·Anthropic 각 행에 [PasswordBox(FsPasswordInputStyle, placeholder sk-.../sk-ant-...)] + [👁 평문토글] + [Verify 버튼] + 우측 status dot(8px)+배지 자리. 빈 필드엔 에러 미표시(premature validation 회피). 카드2: 백엔드별 모델 ComboBox + '비워두면 기본 모델' 캡션. 카드3: FFmpeg/LibreOffice/MS Word COM StatusRow 3개(dot + 도구명 + 상태라벨 + 미설치 시 Warn tint 배경 + 다운로드 CTA). 푸터: 저장·닫기. DiagnoseWindow.xaml을 구조 레퍼런스로 사용."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "SettingsWindow.xaml.cs 신설 — 로드/Verify/저장/외부도구 감지 핸들러",
|
||||||
|
"files": [
|
||||||
|
"src/Everything2Everything.App/Views/SettingsWindow.xaml.cs"
|
||||||
|
],
|
||||||
|
"detail": "생성자 new SettingsWindow(ISettingsStore settings, ConversionEngine engine). 로드: Settings.Get로 키 존재 시 '저장된 키 있음' placeholder, 환경변수(OPENAI_API_KEY/ANTHROPIC_API_KEY) 감지 시 EnvHint 노출. OnBackendSegmentClick(3-way 토글), OnToggleReveal(PasswordBox↔평문 TextBox 스왑), OnVerifyClick(new OpenAiChatClient/AnthropicChatClient(key).CompleteAsync('ping','ping',model,maxTokens=1,ct) → 200=Success배지/401·403=Danger/네트워크오류=Warn, 검증중 indeterminate ProgressBar). OnSaveClick(Settings.Set). OnDownloadClick(Process.Start로 gyan.dev/ffmpeg·libreoffice.org 브라우저 오픈). 외부도구 섹션은 ToolStatusProbe.TryFind*로 채워 dot/배지 매핑."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "MainWindow에 ⚙ 설정 진입점 추가",
|
||||||
|
"files": [
|
||||||
|
"src/Everything2Everything.App/Views/MainWindow.xaml",
|
||||||
|
"src/Everything2Everything.App/Views/MainWindow.xaml.cs"
|
||||||
|
],
|
||||||
|
"detail": "MainWindow.xaml Actions StackPanel(336행) 'Register Menu' 앞에 ⚙ Button(FsSecondaryButtonStyle, 32x32, gear Path, Click=OnSettingsClick). MainWindow.xaml.cs에 OnSettingsClick: var app=(App)Application.Current; new SettingsWindow(app.Settings, app.Engine){Owner=this}.ShowDialog(); 닫힌 뒤 _ = RefreshCapabilityStatusAsync()로 키 반영(모달이라 동기화 단순)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "손실 등급 팔레트 헬퍼 + dot/pill 스타일",
|
||||||
|
"files": [
|
||||||
|
"src/Everything2Everything.App/Views/MainWindow.xaml.cs",
|
||||||
|
"src/Everything2Everything.App/Views/FormatShiftTheme.xaml"
|
||||||
|
],
|
||||||
|
"detail": "MainWindow.xaml.cs FormatPalette(1146행) 옆에 'internal static class LossPalette'를 동형으로 추가: LossClass→(DotBrushKey, TextBrushKey, BgBrushKey, ShortLabel, KoLabel). 가시 라벨은 한국어(무손실/재포장/재인코딩/래스터화), 영어 enum명은 툴팁(ConversionPair.cs XML doc 문구 재사용). FormatShiftTheme.xaml에 FsLossDotStyle(Ellipse 8x8)+FsLossPillStyle(Border radius6, Padding 8,2, Height22) 추가."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "손실 등급 계산 + 출력 드롭다운/출력 배지 노출",
|
||||||
|
"files": [
|
||||||
|
"src/Everything2Everything.App/Views/MainWindow.xaml.cs",
|
||||||
|
"src/Everything2Everything.App/Views/MainWindow.xaml"
|
||||||
|
],
|
||||||
|
"detail": "MainWindow.xaml.cs에 LossGradeFor(IEnumerable<string> sources, string outputExt): 각 입력에 engine.Providers.Graph.FindBestPath(in,out)→path.Max(e=>e.Loss)를 구하고 큐 전체의 worst-case(Max severity)를 등급으로 채택. RefreshAvailableOutputFormats(852행)의 ComboBoxItem.Content를 dot+라벨 템플릿으로 교체하거나 ItemTemplate 도입해 각 형식 옆 등급 dot 예고. OutputFormatBadge(xaml 89행) 아래에 현재 선택 등급 pill 1줄(LossGradeBadge) 추가. 선택이 Rasterize면 OutputFormatHint(xaml 102행)에 '벡터/텍스트 편집성이 사라집니다(단방향)'를 FsLossRasterizeText 색으로 표시."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "큐 row 손실 dot + capability 경고 InfoBar화 + 빈 상태 칩 텍스트 마감",
|
||||||
|
"files": [
|
||||||
|
"src/Everything2Everything.App/Views/MainWindow.xaml.cs",
|
||||||
|
"src/Everything2Everything.App/Views/MainWindow.xaml"
|
||||||
|
],
|
||||||
|
"detail": "QueueItem에 LossClass + 파생속성(LossDotBrush/LossPillVisibility/LossLabel/LossBrush) 추가. 큐 row DataTemplate(386행)의 형식배지 우상단 또는 Grid.Column 3 근처에 dot 배치, 절제: Lossless/Container는 dot만, Recode/Rasterize만 pill(LossPillVisibility 조건부). CapabilityStatusText(xaml 233행 평문 TextBlock)를 Background=FsStatusWarnBg(#302008) + 경고아이콘 + FsStatusWarnText(#FFCA16) + 'Diagnose' CTA의 Border(InfoBar 패턴)로 교체. 형식색은 이미 완료됐으므로 DropZoneEmpty(xaml 377행)의 지원형식 칩 텍스트만 신규 16형식 포함하도록 갱신(현재 'PNG·JPG·GIF·HEIC·RAW·PDF·DOCX·HTML·HWP/HWPX'에 CSV/JSON/XLSX/SVG/MP4/MP3 등 추가, 또는 WrapPanel 형식색 칩으로 교체)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": "빌드/검증 — publish + 카스케이드 재등록",
|
||||||
|
"files": [
|
||||||
|
"src/Everything2Everything.App/Everything2Everything.App.csproj"
|
||||||
|
],
|
||||||
|
"detail": "dotnet build로 경고0/오류0 확인 후, MEMORY project_build_pipeline 시퀀스대로 publish + 카스케이드 재등록. 사용자가 직접 push & GUI 검증(feedback_user_workflow: 단일 commit 선호). Verify 1토큰 핑은 소액 과금 가능성을 커밋 메시지/PR에 명시."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"userDecisions": [
|
||||||
|
{
|
||||||
|
"question": "Verify(키 검증) 방식을 실제 API 1토큰 핑으로 할지, 형식 정규식 검사로 할지?",
|
||||||
|
"options": [
|
||||||
|
"실제 API 1토큰 핑(CompleteAsync maxTokens=1) — 무효 키까지 확실히 잡지만 소액 과금 가능",
|
||||||
|
"sk-/sk-ant- prefix 정규식 검사 — 무과금이지만 무효 키는 못 잡음",
|
||||||
|
"2단계: prefix 검사로 Verify 버튼 활성화 → 클릭 시에만 실제 핑"
|
||||||
|
],
|
||||||
|
"recommendation": "3번(2단계). 빈/형식오류 키는 무과금으로 즉시 걸러 Verify 비활성, 사용자가 명시적으로 Verify를 누를 때만 실제 핑을 보내 과금 동의를 분명히 함. Cursor가 권장하는 실제 검증의 신뢰성과 과금 투명성을 모두 충족."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "손실 등급 'Container'를 Blue(Radix Blue9)로 둘지, 무채색 회색 dot으로 둘지?",
|
||||||
|
"options": [
|
||||||
|
"Blue #0090FF — Radix 시맨틱 일관",
|
||||||
|
"중립 회색 dot(FsTextSecondary #8B909A) — 구조변경은 손실이 아니므로 조용히 처리",
|
||||||
|
"Teal 등 제3의 색"
|
||||||
|
],
|
||||||
|
"recommendation": "2번(중립 회색). 앱 액센트 FsAccentBlue(#3B72FF)와 hue가 가까워 Blue는 '클릭 가능'으로 오인될 수 있음. Container는 손실이 아니므로 색을 아껴 조용히 두고, Blue는 순수 info용으로 보존하는 게 4등급 그라데이션(녹→회→앰버→레드) 가독성에 유리."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": "외부도구 '경로 지정'을 ISettingsStore 영구 키(ffmpeg.dir)로 저장할지, 기존 %LOCALAPPDATA% 폴더 복사 안내만 할지?",
|
||||||
|
"options": [
|
||||||
|
"ISettingsStore 'ffmpeg.dir' 신설 — 사용자가 임의 경로 지정 가능, ExternalToolDetector도 이 키 우선 조회하도록 확장 필요",
|
||||||
|
"기존 %LOCALAPPDATA%\\Everything2Everything\\ffmpeg 폴더 복사 안내만 — 코드 변경 최소(이미 ExternalToolDetector가 이 폴더를 1순위 조회)"
|
||||||
|
],
|
||||||
|
"recommendation": "2번(폴더 안내). ExternalToolDetector.TryFindFfmpeg이 이미 %LOCALAPPDATA%\\Everything2Everything\\ffmpeg를 1순위로 보고 있어(54-63행) 추가 키·조회 분기 없이 다운로드 CTA + '이 폴더에 넣으세요' 안내만으로 충분. 설정창 1차 릴리스 범위를 줄여 AI 키 검증에 집중하고, 임의 경로 지정은 후속으로."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -40,6 +40,53 @@
|
||||||
<SolidColorBrush x:Key="FsFmtHwp" Color="#9B2C2C"/>
|
<SolidColorBrush x:Key="FsFmtHwp" Color="#9B2C2C"/>
|
||||||
<SolidColorBrush x:Key="FsFmtOther" Color="#4A5568"/>
|
<SolidColorBrush x:Key="FsFmtOther" Color="#4A5568"/>
|
||||||
|
|
||||||
|
<!-- Format pills (신규: 데이터/벡터/영상/오디오 카테고리) -->
|
||||||
|
<!-- 출처: VS Code Material Icon Theme + Seti UI(Solarized) + GitHub Linguist 의 hue 컨벤션을
|
||||||
|
차용하되, 채도/명도는 기존 Chakra/Tailwind 500 솔리드 톤(흰 텍스트 위 대비)에 맞춰 재조정.
|
||||||
|
Linear OKLCH 균일 명도 원칙(다크 #090A0C~#1C1E22 배경에서 한 칩만 튀지 않게)을 적용. -->
|
||||||
|
<!-- 데이터/표 = 그린 (Material table=#8BC34A, Seti green, Excel 브랜드 그린) -->
|
||||||
|
<SolidColorBrush x:Key="FsFmtCsv" Color="#68A063"/>
|
||||||
|
<SolidColorBrush x:Key="FsFmtXlsx" Color="#38A169"/>
|
||||||
|
<!-- 데이터/구조 = 골드 (Material json=#F9A825, Seti yellow); 기존 HEIC(#D69E2E)보다 노랑쪽 -->
|
||||||
|
<SolidColorBrush x:Key="FsFmtJson" Color="#E3B341"/>
|
||||||
|
<!-- 벡터 = 핑크-마젠타 (Seti svg=purple/css=pink); 노랑 JSON과 즉시 구분 -->
|
||||||
|
<SolidColorBrush x:Key="FsFmtSvg" Color="#E05A9C"/>
|
||||||
|
<!-- 영상 = 코랄/오렌지레드 (Material video=#FF9800, 통념 영상=난색); 기존 PDF(#E53E3E)와 구분 -->
|
||||||
|
<SolidColorBrush x:Key="FsFmtVideo" Color="#FF7043"/>
|
||||||
|
<!-- 오디오 = 라이트 퍼플 (Seti audio=purple, 통념 오디오=보라); 기존 PNG/GIF보다 라이트 -->
|
||||||
|
<SolidColorBrush x:Key="FsFmtAudio" Color="#B794F4"/>
|
||||||
|
|
||||||
|
<!-- 손실 등급 토큰 (Radix dark; Container는 중립 회색 — 사용자 결정) -->
|
||||||
|
<SolidColorBrush x:Key="FsLossLosslessDot" Color="#30A46C"/>
|
||||||
|
<SolidColorBrush x:Key="FsLossLosslessText" Color="#3DD68C"/>
|
||||||
|
<SolidColorBrush x:Key="FsLossLosslessBg" Color="#132D21"/>
|
||||||
|
<SolidColorBrush x:Key="FsLossContainerDot" Color="#717784"/>
|
||||||
|
<SolidColorBrush x:Key="FsLossContainerText" Color="#A6ADC8"/>
|
||||||
|
<SolidColorBrush x:Key="FsLossContainerBg" Color="#23252A"/>
|
||||||
|
<SolidColorBrush x:Key="FsLossRecodeDot" Color="#FFC53D"/>
|
||||||
|
<SolidColorBrush x:Key="FsLossRecodeText" Color="#FFCA16"/>
|
||||||
|
<SolidColorBrush x:Key="FsLossRecodeBg" Color="#302008"/>
|
||||||
|
<SolidColorBrush x:Key="FsLossRasterizeDot" Color="#E5484D"/>
|
||||||
|
<SolidColorBrush x:Key="FsLossRasterizeText" Color="#FF9592"/>
|
||||||
|
<SolidColorBrush x:Key="FsLossRasterizeBg" Color="#3B1219"/>
|
||||||
|
|
||||||
|
<!-- 상태 시맨틱 토큰 (Radix dark scales) -->
|
||||||
|
<SolidColorBrush x:Key="FsStatusSuccess" Color="#30A46C"/>
|
||||||
|
<SolidColorBrush x:Key="FsStatusSuccessText" Color="#3DD68C"/>
|
||||||
|
<SolidColorBrush x:Key="FsStatusSuccessBg" Color="#132D21"/>
|
||||||
|
<SolidColorBrush x:Key="FsStatusWarn" Color="#FFC53D"/>
|
||||||
|
<SolidColorBrush x:Key="FsStatusWarnText" Color="#FFCA16"/>
|
||||||
|
<SolidColorBrush x:Key="FsStatusWarnBg" Color="#302008"/>
|
||||||
|
<SolidColorBrush x:Key="FsStatusDanger" Color="#E5484D"/>
|
||||||
|
<SolidColorBrush x:Key="FsStatusDangerText" Color="#FF9592"/>
|
||||||
|
<SolidColorBrush x:Key="FsStatusDangerBg" Color="#3B1219"/>
|
||||||
|
<SolidColorBrush x:Key="FsStatusInfo" Color="#0090FF"/>
|
||||||
|
<SolidColorBrush x:Key="FsStatusInfoText" Color="#70B8FF"/>
|
||||||
|
<SolidColorBrush x:Key="FsStatusInfoBg" Color="#0D2847"/>
|
||||||
|
|
||||||
|
<!-- Geist식 8% 투명 보더 -->
|
||||||
|
<SolidColorBrush x:Key="FsBorderHairline" Color="#14FFFFFF"/>
|
||||||
|
|
||||||
<!-- Font families -->
|
<!-- Font families -->
|
||||||
<FontFamily x:Key="FsFontSans">Inter, Segoe UI Variable Text, Segoe UI</FontFamily>
|
<FontFamily x:Key="FsFontSans">Inter, Segoe UI Variable Text, Segoe UI</FontFamily>
|
||||||
<FontFamily x:Key="FsFontMono">JetBrains Mono, Cascadia Mono, Consolas</FontFamily>
|
<FontFamily x:Key="FsFontMono">JetBrains Mono, Cascadia Mono, Consolas</FontFamily>
|
||||||
|
|
|
||||||
|
|
@ -824,22 +824,22 @@ public partial class MainWindow : Wpf.Ui.Controls.FluentWindow
|
||||||
new(".html", "HTML", "HTML", "FsFmtHtml"),
|
new(".html", "HTML", "HTML", "FsFmtHtml"),
|
||||||
new(".md", "Markdown", "MD", "FsFmtOther"),
|
new(".md", "Markdown", "MD", "FsFmtOther"),
|
||||||
new(".txt", "텍스트", "TXT", "FsFmtOther"),
|
new(".txt", "텍스트", "TXT", "FsFmtOther"),
|
||||||
new(".csv", "CSV", "CSV", "FsFmtOther"),
|
new(".csv", "CSV", "CSV", "FsFmtCsv"),
|
||||||
new(".json", "JSON", "JSON", "FsFmtOther"),
|
new(".json", "JSON", "JSON", "FsFmtJson"),
|
||||||
new(".xlsx", "Excel", "XLSX", "FsFmtOther"),
|
new(".xlsx", "Excel", "XLSX", "FsFmtXlsx"),
|
||||||
new(".svg", "SVG", "SVG", "FsFmtOther"),
|
new(".svg", "SVG", "SVG", "FsFmtSvg"),
|
||||||
new(".mp4", "MP4", "MP4", "FsFmtOther"),
|
new(".mp4", "MP4", "MP4", "FsFmtVideo"),
|
||||||
new(".webm", "WebM", "WEBM", "FsFmtOther"),
|
new(".webm", "WebM", "WEBM", "FsFmtVideo"),
|
||||||
new(".mkv", "MKV", "MKV", "FsFmtOther"),
|
new(".mkv", "MKV", "MKV", "FsFmtVideo"),
|
||||||
new(".mov", "MOV", "MOV", "FsFmtOther"),
|
new(".mov", "MOV", "MOV", "FsFmtVideo"),
|
||||||
new(".avi", "AVI", "AVI", "FsFmtOther"),
|
new(".avi", "AVI", "AVI", "FsFmtVideo"),
|
||||||
new(".mp3", "MP3", "MP3", "FsFmtOther"),
|
new(".mp3", "MP3", "MP3", "FsFmtAudio"),
|
||||||
new(".aac", "AAC", "AAC", "FsFmtOther"),
|
new(".aac", "AAC", "AAC", "FsFmtAudio"),
|
||||||
new(".m4a", "M4A", "M4A", "FsFmtOther"),
|
new(".m4a", "M4A", "M4A", "FsFmtAudio"),
|
||||||
new(".opus", "Opus", "OPUS", "FsFmtOther"),
|
new(".opus", "Opus", "OPUS", "FsFmtAudio"),
|
||||||
new(".ogg", "OGG", "OGG", "FsFmtOther"),
|
new(".ogg", "OGG", "OGG", "FsFmtAudio"),
|
||||||
new(".flac", "FLAC", "FLAC", "FsFmtOther"),
|
new(".flac", "FLAC", "FLAC", "FsFmtAudio"),
|
||||||
new(".wav", "WAV", "WAV", "FsFmtOther"),
|
new(".wav", "WAV", "WAV", "FsFmtAudio"),
|
||||||
};
|
};
|
||||||
|
|
||||||
private bool _suppressFormatChanged;
|
private bool _suppressFormatChanged;
|
||||||
|
|
@ -1160,6 +1160,15 @@ internal static class FormatPalette
|
||||||
"bmp" => ("BMP", "FsFmtBmp"),
|
"bmp" => ("BMP", "FsFmtBmp"),
|
||||||
"raw" or "dng" or "nef" or "cr2" or "cr3" or "arw" or "raf" or "orf" or "rw2" or "srw" or "pef"
|
"raw" or "dng" or "nef" or "cr2" or "cr3" or "arw" or "raf" or "orf" or "rw2" or "srw" or "pef"
|
||||||
=> ("RAW", "FsFmtRaw"),
|
=> ("RAW", "FsFmtRaw"),
|
||||||
|
// 신규 카테고리 (output AllFormats 매핑과 동일 hue 유지)
|
||||||
|
"csv" => ("CSV", "FsFmtCsv"),
|
||||||
|
"json" => ("JSON", "FsFmtJson"),
|
||||||
|
"xlsx" or "xls" => ("XLSX", "FsFmtXlsx"),
|
||||||
|
"svg" => ("SVG", "FsFmtSvg"),
|
||||||
|
"mp4" or "webm" or "mkv" or "mov" or "avi" or "m4v"
|
||||||
|
=> (ext.ToUpperInvariant(), "FsFmtVideo"),
|
||||||
|
"mp3" or "aac" or "m4a" or "opus" or "ogg" or "oga" or "flac" or "wav"
|
||||||
|
=> (ext.ToUpperInvariant(), "FsFmtAudio"),
|
||||||
_ => (ext.ToUpperInvariant(), "FsFmtOther"),
|
_ => (ext.ToUpperInvariant(), "FsFmtOther"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue