From efb9fb46de45c4b366c4e9faca80b396710a28cd Mon Sep 17 00:00:00 2001 From: lee-hyeon-cheol Date: Mon, 21 Sep 2026 10:26:52 +0900 Subject: [PATCH] =?UTF-8?q?style(frontend):=20=EC=82=BC=EC=84=B1=ED=96=A5?= =?UTF-8?q?=20=EB=9D=BC=EC=9D=B4=ED=8A=B8=20=ED=8C=94=EB=A0=88=ED=8A=B8(sa?= =?UTF-8?q?msung)=20=EC=B6=94=EA=B0=80=ED=95=98=EA=B3=A0=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=EC=9C=BC=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 삼성닷컴(sec) 공개 웹 실측값 — 검정 주색/CTA, 흰 canvas, #f7f7f7 surface, #707070 muted, #dddddd border, 링크 #007aff, 그림자 없음, 버튼·카드 radius 20px(알약). One UI 파랑은 폰 앱용이라 안 씀. 폰트는 재배포 근거 없어 안 넣음. 다크 모드는 기존 그대로. 기존 사용자 저장값 clean-blue 는 한 번만 samsung 으로 교체(index.html 부트스트랩). Co-Authored-By: Claude Fable 5.1 --- 2_frontend/index.html | 12 +++--- 2_frontend/src/shared/theme/derive.ts | 49 +++++++++++++++++++++++-- 2_frontend/src/shared/theme/palettes.ts | 7 +++- 2_frontend/tsconfig.tsbuildinfo | 2 +- 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/2_frontend/index.html b/2_frontend/index.html index c4e213a..604365c 100644 --- a/2_frontend/index.html +++ b/2_frontend/index.html @@ -10,14 +10,14 @@ // 팔레트는 localStorage 'theme' 키, 다크모드는 'theme-store' (zustand persist). ;(function () { try { - var pal = localStorage.getItem("theme") || "clean-blue" - // 이전 기본 베이지는 한 번만 교체. 이후 직접 고른 팔레트는 유지. - if (!localStorage.getItem("clean-blue-default-v1")) { - if (pal === "warm-tan") { - pal = "clean-blue" + var pal = localStorage.getItem("theme") || "samsung" + // 기본 팔레트 교체는 한 번만(삼성향, 2026-09). 이후 직접 고른 팔레트는 유지. + if (!localStorage.getItem("samsung-default-v1")) { + if (pal === "warm-tan" || pal === "clean-blue") { + pal = "samsung" localStorage.setItem("theme", pal) } - localStorage.setItem("clean-blue-default-v1", "1") + localStorage.setItem("samsung-default-v1", "1") } document.documentElement.setAttribute("data-theme", pal) var raw = localStorage.getItem("theme-store") diff --git a/2_frontend/src/shared/theme/derive.ts b/2_frontend/src/shared/theme/derive.ts index 07388c6..5a49f54 100644 --- a/2_frontend/src/shared/theme/derive.ts +++ b/2_frontend/src/shared/theme/derive.ts @@ -83,6 +83,37 @@ export type DerivedTokens = { // 의미색 — 모든 테마 공통 const DESTRUCTIVE = "oklch(0.577 0.245 27.325)" +// 삼성향 — 삼성닷컴(sec) 공개 웹 2026-07 실측값. 검정 주색·흰 바탕·회색 면, 그림자 없음, 알약 버튼(20px). +// One UI 파랑(#0381fe)은 폰 앱용이라 안 씀. 링크만 #007aff. 폰트는 재배포 근거 없어 안 넣음. +const SAMSUNG_TOKENS: DerivedTokens = { + background: "#FFFFFF", // canvas + foreground: "#000000", + card: "#FFFFFF", + cardForeground: "#000000", + popover: "#FFFFFF", + popoverForeground: "#000000", + primary: "#000000", // contained CTA + primaryForeground: "#FFFFFF", + secondary: "#F7F7F7", // surface + secondaryForeground: "#000000", + muted: "#F7F7F7", + mutedForeground: "#707070", + accent: "#EDEDED", // hover — surface 보다 한 단계 + accentForeground: "#000000", + destructive: DESTRUCTIVE, + border: "#DDDDDD", + input: "#DDDDDD", + ring: "#000000", + sidebar: "#F7F7F7", + sidebarForeground: "#000000", + sidebarPrimary: "#000000", + sidebarPrimaryForeground: "#FFFFFF", + sidebarAccent: "#EDEDED", + sidebarAccentForeground: "#000000", + sidebarBorder: "#DDDDDD", + sidebarRing: "#000000", +} + // 기본 팔레트(id 는 하위호환으로 clean-blue 유지) — ABAP_OPENCODE DESIGN_SYSTEM.md §7 라이트 값. // globals.css :root 와 같은 값. 명도 정렬(derive)을 안 타서 블루가 차콜로 안 바뀜. const CLEAN_BLUE_TOKENS: DerivedTokens = { @@ -114,6 +145,16 @@ const CLEAN_BLUE_TOKENS: DerivedTokens = { sidebarRing: "#3563E9", } +// 고정 토큰 팔레트(명도 정렬 안 탐). 나머지는 swatch 에서 derive. +const FIXED_TOKENS: Record = { + "clean-blue": CLEAN_BLUE_TOKENS, + samsung: SAMSUNG_TOKENS, +} +// 팔레트별 추가 CSS — samsung 은 알약 radius(실측 20px) + 링크색 +const EXTRA_CSS: Record = { + samsung: `[data-theme="samsung"]:not(.dark){--radius:20px;--link:#007aff}`, +} + export function derivePaletteTokens( swatch: readonly [string, string, string, string] ): DerivedTokens { @@ -181,13 +222,15 @@ function paletteToCss(id: string, t: DerivedTokens): string { } // 전체 테마 CSS를 한 번에 생성. 모듈 로드 시 1회 실행. -export const ALL_THEMES_CSS: string = PALETTES.map((p) => - paletteToCss(p.id, p.id === "clean-blue" ? CLEAN_BLUE_TOKENS : derivePaletteTokens(p.swatch)) +export const ALL_THEMES_CSS: string = PALETTES.map( + (p) => + paletteToCss(p.id, FIXED_TOKENS[p.id] ?? derivePaletteTokens(p.swatch)) + + (EXTRA_CSS[p.id] ?? "") ).join("") // 미리보기 카드용 — id로 derived tokens 조회 export function getDerivedTokens(id: string): DerivedTokens | null { - if (id === "clean-blue") return { ...CLEAN_BLUE_TOKENS } + if (FIXED_TOKENS[id]) return { ...FIXED_TOKENS[id] } const p = PALETTES.find((x) => x.id === id) return p ? derivePaletteTokens(p.swatch) : null } diff --git a/2_frontend/src/shared/theme/palettes.ts b/2_frontend/src/shared/theme/palettes.ts index baf0ecf..0000b0b 100644 --- a/2_frontend/src/shared/theme/palettes.ts +++ b/2_frontend/src/shared/theme/palettes.ts @@ -9,6 +9,11 @@ export type RawPalette = { } export const PALETTES: RawPalette[] = [ + { + id: "samsung", // 고정 토큰(derive.ts SAMSUNG_TOKENS) — 삼성닷컴 공개 웹 실측값. 라이트 전용, 다크는 공통 + name: "Samsung", + swatch: ["#FFFFFF", "#F7F7F7", "#000000", "#707070"], + }, { id: "clean-blue", // id 는 저장된 값 호환 위해 유지 — 실제 토큰은 derive.ts 워크스페이스(ABAP_OPENCODE) 값 name: "워크스페이스", @@ -80,7 +85,7 @@ export const PALETTES: RawPalette[] = [ }, ] -export const DEFAULT_THEME = "clean-blue" +export const DEFAULT_THEME = "samsung" export const PALETTE_BY_ID: Record = Object.fromEntries( PALETTES.map((p) => [p.id, p]) diff --git a/2_frontend/tsconfig.tsbuildinfo b/2_frontend/tsconfig.tsbuildinfo index 47547b1..0392afc 100644 --- a/2_frontend/tsconfig.tsbuildinfo +++ b/2_frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/routes.tsx","./src/vite-env.d.ts","./src/config/env.ts","./src/config/routes.ts","./src/features/auth/index.ts","./src/features/auth/api/auth.api.ts","./src/features/auth/components/entraloginbutton.tsx","./src/features/auth/components/entraloginsection.tsx","./src/features/auth/components/loginform.tsx","./src/features/auth/components/sessionexpirydialog.tsx","./src/features/auth/hooks/useentraenabled.ts","./src/features/auth/hooks/useentralogin.ts","./src/features/auth/hooks/uselogin.ts","./src/features/auth/hooks/uselogout.ts","./src/features/auth/hooks/useme.ts","./src/features/auth/hooks/useslidingrefresh.ts","./src/features/auth/pages/loginpage.tsx","./src/features/auth/schemas/index.ts","./src/features/auth/store/authstore.ts","./src/features/auth/store/sessionexpirystore.ts","./src/features/auth/utils/saferedirectpath.ts","./src/features/snap/api/snap.api.ts","./src/features/snap/api/snap.stream.ts","./src/features/snap/components/chatheader.tsx","./src/features/snap/components/clipboardhistory.tsx","./src/features/snap/components/codeblock.tsx","./src/features/snap/components/composer.tsx","./src/features/snap/components/hero.tsx","./src/features/snap/components/message.tsx","./src/features/snap/components/navrail.tsx","./src/features/snap/components/searchhitcard.tsx","./src/features/snap/components/sessioncard.tsx","./src/features/snap/components/sessionsearch.tsx","./src/features/snap/components/snaplayout.tsx","./src/features/snap/components/snapusercontrols.tsx","./src/features/snap/components/abaphljs.ts","./src/features/snap/contract/types.ts","./src/features/snap/hooks/useescapekey.ts","./src/features/snap/hooks/usesnapchat.ts","./src/features/snap/lib/format.ts","./src/features/snap/pages/newchatpage.tsx","./src/features/snap/pages/sessionchatpage.tsx","./src/features/snap/pages/sessionlistpage.tsx","./src/features/snap/store/snapchatstore.ts","./src/features/snippets/types.ts","./src/features/snippets/api/snippets.api.ts","./src/features/snippets/components/categorychips.tsx","./src/features/snippets/components/editdialog.tsx","./src/features/snippets/components/previewpane.tsx","./src/features/snippets/components/snippetrow.tsx","./src/features/snippets/core/ranking.ts","./src/features/snippets/core/search.ts","./src/features/snippets/hooks/usesnippets.ts","./src/features/snippets/pages/snippetpalettepage.tsx","./src/lib/api/client.ts","./src/lib/api/errors.ts","./src/lib/auth/msal.ts","./src/lib/auth/tokenprovider.ts","./src/lib/bridge/bridgenavigate.ts","./src/lib/bridge/snippetbridge.ts","./src/lib/bridge/transport.ts","./src/lib/bridge/webviewbridge.ts","./src/lib/hooks/usedebounce.ts","./src/lib/query/queryclient.ts","./src/lib/streaming/stoppednotice.tsx","./src/lib/streaming/streamingtext.tsx","./src/lib/streaming/abort.ts","./src/lib/streaming/index.ts","./src/lib/streaming/sse.ts","./src/lib/streaming/streamllm.ts","./src/lib/streaming/usesmoothedtext.ts","./src/lib/streaming/usestreamsession.ts","./src/lib/utils/cn.ts","./src/lib/utils/randomid.ts","./src/lib/utils/relativetime.ts","./src/lib/utils/sleep.ts","./src/shared/components/desktopwindowframe.tsx","./src/shared/components/errorboundary.tsx","./src/shared/components/kbd.tsx","./src/shared/components/layout.tsx","./src/shared/components/paletteshell.tsx","./src/shared/components/pastetargetbadge.tsx","./src/shared/components/protectedroute.tsx","./src/shared/components/themetoggle.tsx","./src/shared/hooks/usepastetarget.ts","./src/shared/store/themestore.ts","./src/shared/theme/derive.ts","./src/shared/theme/palettes.ts","./src/shared/theme/usetheme.ts","./src/shared/ui/alert-dialog.tsx","./src/shared/ui/alert.tsx","./src/shared/ui/button.tsx","./src/shared/ui/dialog.tsx","./src/shared/ui/dropdown-menu.tsx","./src/shared/ui/input.tsx","./src/shared/ui/label.tsx","./src/shared/ui/separator.tsx","./src/shared/ui/skeleton.tsx","./src/shared/ui/textarea.tsx","./src/shared/ui/tooltip.tsx","./src/types/api.ts"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/routes.tsx","./src/vite-env.d.ts","./src/config/env.ts","./src/config/routes.ts","./src/features/auth/index.ts","./src/features/auth/api/auth.api.ts","./src/features/auth/components/entraloginbutton.tsx","./src/features/auth/components/entraloginsection.tsx","./src/features/auth/components/loginform.tsx","./src/features/auth/components/sessionexpirydialog.tsx","./src/features/auth/hooks/useentraenabled.ts","./src/features/auth/hooks/useentralogin.ts","./src/features/auth/hooks/uselogin.ts","./src/features/auth/hooks/uselogout.ts","./src/features/auth/hooks/useme.ts","./src/features/auth/hooks/useslidingrefresh.ts","./src/features/auth/pages/loginpage.tsx","./src/features/auth/schemas/index.ts","./src/features/auth/store/authstore.ts","./src/features/auth/store/sessionexpirystore.ts","./src/features/auth/utils/saferedirectpath.ts","./src/features/snap/api/snap.api.ts","./src/features/snap/api/snap.stream.ts","./src/features/snap/components/chatheader.tsx","./src/features/snap/components/clipboardhistory.tsx","./src/features/snap/components/codeblock.tsx","./src/features/snap/components/composer.tsx","./src/features/snap/components/hero.tsx","./src/features/snap/components/message.tsx","./src/features/snap/components/navrail.tsx","./src/features/snap/components/searchhitcard.tsx","./src/features/snap/components/sessioncard.tsx","./src/features/snap/components/sessionsearch.tsx","./src/features/snap/components/snaplayout.tsx","./src/features/snap/components/snapusercontrols.tsx","./src/features/snap/components/abaphljs.ts","./src/features/snap/contract/types.ts","./src/features/snap/hooks/useescapekey.ts","./src/features/snap/hooks/usesnapchat.ts","./src/features/snap/lib/format.ts","./src/features/snap/pages/newchatpage.tsx","./src/features/snap/pages/sessionchatpage.tsx","./src/features/snap/pages/sessionlistpage.tsx","./src/features/snap/store/snapchatstore.ts","./src/features/snippets/types.ts","./src/features/snippets/api/snippets.api.ts","./src/features/snippets/components/categorychips.tsx","./src/features/snippets/components/editdialog.tsx","./src/features/snippets/components/previewpane.tsx","./src/features/snippets/components/snippetrow.tsx","./src/features/snippets/core/ranking.ts","./src/features/snippets/core/search.ts","./src/features/snippets/hooks/usesnippets.ts","./src/features/snippets/pages/snippetpalettepage.tsx","./src/lib/api/client.ts","./src/lib/api/errors.ts","./src/lib/auth/msal.ts","./src/lib/auth/tokenprovider.ts","./src/lib/bridge/bridgenavigate.ts","./src/lib/bridge/snippetbridge.ts","./src/lib/bridge/transport.ts","./src/lib/bridge/webviewbridge.ts","./src/lib/hooks/usedebounce.ts","./src/lib/query/queryclient.ts","./src/lib/streaming/stoppednotice.tsx","./src/lib/streaming/streamingtext.tsx","./src/lib/streaming/abort.ts","./src/lib/streaming/index.ts","./src/lib/streaming/sse.ts","./src/lib/streaming/streamllm.ts","./src/lib/streaming/usesmoothedtext.ts","./src/lib/streaming/usestreamsession.ts","./src/lib/utils/cn.ts","./src/lib/utils/randomid.ts","./src/lib/utils/relativetime.ts","./src/lib/utils/sleep.ts","./src/shared/components/centerspinner.tsx","./src/shared/components/desktopwindowframe.tsx","./src/shared/components/errorboundary.tsx","./src/shared/components/kbd.tsx","./src/shared/components/layout.tsx","./src/shared/components/logomark.tsx","./src/shared/components/paletteshell.tsx","./src/shared/components/pastetargetbadge.tsx","./src/shared/components/protectedroute.tsx","./src/shared/components/themetoggle.tsx","./src/shared/hooks/usepastetarget.ts","./src/shared/store/themestore.ts","./src/shared/theme/derive.ts","./src/shared/theme/palettes.ts","./src/shared/theme/usetheme.ts","./src/shared/ui/alert-dialog.tsx","./src/shared/ui/alert.tsx","./src/shared/ui/button.tsx","./src/shared/ui/dialog.tsx","./src/shared/ui/dropdown-menu.tsx","./src/shared/ui/input.tsx","./src/shared/ui/label.tsx","./src/shared/ui/separator.tsx","./src/shared/ui/skeleton.tsx","./src/shared/ui/textarea.tsx","./src/shared/ui/tooltip.tsx","./src/types/api.ts"],"version":"5.9.3"} \ No newline at end of file