From eadbf26ee01718f0a7c79e3bc4b640468817da09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=97=B0=EC=9A=B0?= Date: Sat, 29 Aug 2026 02:35:30 +0900 Subject: [PATCH 01/11] =?UTF-8?q?docs:=202.0.0=20=EB=A6=B4=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=20=EB=85=B8=ED=8A=B8=EC=97=90=20=EC=A0=9C=EB=B3=B4?= =?UTF-8?q?=EC=9E=90=EC=99=80=20=EA=B8=B0=EC=97=AC=EC=9E=90=20=ED=91=9C?= =?UTF-8?q?=EA=B8=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/releases/2.0.0_en.md | 7 +++++++ docs/releases/2.0.0_ko.md | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/docs/releases/2.0.0_en.md b/docs/releases/2.0.0_en.md index 6520cd476..bbce029fe 100644 --- a/docs/releases/2.0.0_en.md +++ b/docs/releases/2.0.0_en.md @@ -9,6 +9,7 @@ - The main toolbar, properties panel, settings, modals, popups, pickers, and minimap now share one visual and behavioral baseline. - The first-run default preset and the default styles for keys, knobs, graphs, stats, counters, and notes have been rebuilt. - Replaced the app icon. + - The new key design was created with inspiration from [@DominoKorean](https://github.com/DominoKorean)'s [custom CSS skins](https://github.com/DominoKorean/DM-Note-skins). - Moved settings into the Side Panel - Sound, font, Custom CSS, and plugin settings are managed on the current screen without a separate manager window. @@ -54,6 +55,7 @@ - `Combined` triggers only while all bound keys are held. - Keyboard, mouse, and HID buttons (Windows) can be mixed within the same slot. - Add or remove keys and switch the trigger mode directly in the binding popup. + - [@superalan89](https://github.com/superalan89) suggested [multi-key binding support](https://github.com/DmNote-App/DmNote/issues/71). ๐ŸŽ‰ ### Color, Image, and Font @@ -148,6 +150,7 @@ - Added Intel Mac support [Issues 125](https://github.com/DmNote-App/DmNote/issues/125) - macOS builds are now universal binaries, so the same DMG runs on both Intel and Apple silicon Macs. + - [@ByrenYan1](https://github.com/ByrenYan1) suggested [Intel Mac support](https://github.com/DmNote-App/DmNote/issues/125). ๐ŸŽ‰ - Added support for high refresh rate displays on macOS [Issues 122](https://github.com/DmNote-App/DmNote/issues/122) - The overlay and editing screen update more smoothly on 120Hz and faster displays. @@ -173,6 +176,7 @@ - Image, sound, and font files are now quarantined in a per-session trash for 30 days instead of being deleted immediately - Fixed half-written state after a conflict or crash by making save file replacement, asset restore, and failure rollback atomic - Fixed custom images on knobs disappearing after a restart [Issues 91](https://github.com/DmNote-App/DmNote/issues/91) + - [@superalan89](https://github.com/superalan89) reported [custom images on knobs going missing](https://github.com/DmNote-App/DmNote/issues/91). ๐ŸŽ‰ - Fixed an issue where older undo/redo steps revived already-deleted sound, image, counter motion, and plugin references ### Panel, Popup, and Input @@ -193,6 +197,7 @@ - Lowered the minimum note corner radius to 0 and changed the default `Glow Size` to 10 - Fixed short notes disappearing abruptly or looking cut off at the display boundary - Reduced key event delivery latency by enabling low-latency transmission on the OBS WebSocket [Issues 72](https://github.com/DmNote-App/DmNote/issues/72) + - [@aigonanMTE](https://github.com/aigonanMTE) reported [key viewer stuttering in OBS mode](https://github.com/DmNote-App/DmNote/issues/72). ๐ŸŽ‰ ### Color, Image, Font, and CSS @@ -205,6 +210,7 @@ - Fixed plugin JavaScript failures being reported as success, and truncated error messages - Fixed the pressed state in the key viewer flickering every time a plugin updated its state [Issues 111](https://github.com/DmNote-App/DmNote/issues/111) + - [@hjg-06](https://github.com/hjg-06) contributed to [the pressed state flickering on plugin state updates](https://github.com/DmNote-App/DmNote/issues/111). ๐ŸŽ‰ - Fixed an exception in a plugin's `onMount` blanking the entire overlay - Fixed late writes from a failed or removed plugin overwriting the new run state - Restored compatibility for native `window` methods, async context, and existing global APIs used by plugins @@ -217,6 +223,7 @@ - Fixed the overlay on macOS not reaching the top of the screen and leaving empty space below it [Issues 121](https://github.com/DmNote-App/DmNote/issues/121) - `Snap to Edge` now measures from the screen edge instead of the work area, removing the gap left by the Dock and the taskbar - The track height reserved at the top is excluded from the window height when note effects are off + - [@AL-Pinecore](https://github.com/AL-Pinecore) contributed to [overlay placement on macOS](https://github.com/DmNote-App/DmNote/issues/121). ๐ŸŽ‰ - Added a quit item to the macOS app menu and stabilized Dock quit, restart, and helper process cleanup - macOS no longer raises the accessibility permission prompt on its own while the app is running - Fixed cancellations being reported as failures and the UI blocking by making file and preset dialogs async and parented to the calling window diff --git a/docs/releases/2.0.0_ko.md b/docs/releases/2.0.0_ko.md index 9cb62a4ac..0059af5a2 100644 --- a/docs/releases/2.0.0_ko.md +++ b/docs/releases/2.0.0_ko.md @@ -9,6 +9,7 @@ - ๋ฉ”์ธ ํˆด๋ฐ”, ์†์„ฑ ํŒจ๋„, ์„ค์ •, ๋ชจ๋‹ฌ, ํŒ์—…, ํ”ผ์ปค, ๋ฏธ๋‹ˆ๋งต์˜ ๋””์ž์ธ๊ณผ ๋™์ž‘์„ ํ•˜๋‚˜์˜ ๊ธฐ์ค€์œผ๋กœ ํ†ต์ผํ–ˆ์Šต๋‹ˆ๋‹ค. - ์ฒซ ์‹คํ–‰ ๊ธฐ๋ณธ ํ”„๋ฆฌ์…‹๊ณผ ํ‚ค, ๋…ธ๋ธŒ, ๊ทธ๋ž˜ํ”„, ํ†ต๊ณ„, ์นด์šดํ„ฐ, ๋…ธํŠธ์˜ ๊ธฐ๋ณธ ์Šคํƒ€์ผ์„ ์ƒˆ๋กœ ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. - ์•ฑ ์•„์ด์ฝ˜์„ ๊ต์ฒดํ–ˆ์Šต๋‹ˆ๋‹ค. + - ์‹ ๊ทœ ํ‚ค ๋””์ž์ธ์€ @DominoKorean ๋‹˜์˜ [์ปค์Šคํ…€ CSS ์Šคํ‚จ](https://github.com/DominoKorean/DM-Note-skins)์—์„œ ์˜๊ฐ์„ ๋ฐ›์•„ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. - ์„ค์ •์„ ์‚ฌ์ด๋“œ ํŒจ๋„๋กœ ์ด๋™ - ์‚ฌ์šด๋“œ, ํฐํŠธ, ์ปค์Šคํ…€ CSS, ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์ •์„ ๋ณ„๋„ ๊ด€๋ฆฌ ์ฐฝ ์—†์ด ํ˜„์žฌ ํ™”๋ฉด์—์„œ ๋ฐ”๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. @@ -54,6 +55,7 @@ - `๋™์‹œ ์ž…๋ ฅ`์€ ๋“ฑ๋กํ•œ ํ‚ค๋ฅผ ๋ชจ๋‘ ๋ˆ„๋ฅด๊ณ  ์žˆ๋Š” ๋™์•ˆ์—๋งŒ ๋ฐ˜์‘ํ•ฉ๋‹ˆ๋‹ค. - ํ‚ค๋ณด๋“œ, ๋งˆ์šฐ์Šค, HID ๋ฒ„ํŠผ(Windows)์„ ํ•œ ์Šฌ๋กฏ ์•ˆ์—์„œ ์„ž์–ด ์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. - ๋งคํ•‘ ํŒ์—…์—์„œ ํ‚ค๋ฅผ ์ถ”๊ฐ€, ์ œ๊ฑฐํ•˜๊ณ  ๋ฐ˜์‘ ๋ฐฉ์‹์„ ๋ฐ”๋กœ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + - @superalan89 ๋‹˜์ด [๋‹ค์ค‘ ํ‚ค ๋งคํ•‘ ์ง€์›](https://github.com/DmNote-App/DmNote/issues/71)์„ ์ œ์•ˆํ•ด์ฃผ์…จ์Šต๋‹ˆ๋‹ค. ๐ŸŽ‰ ### ์ƒ‰์ƒ, ์ด๋ฏธ์ง€ ๋ฐ ํฐํŠธ @@ -148,6 +150,7 @@ - ์ธํ…” ๋งฅ ์ง€์› [Issues 125](https://github.com/DmNote-App/DmNote/issues/125) - macOS ๋นŒ๋“œ๋ฅผ ์œ ๋‹ˆ๋ฒ„์„ค ๋ฐ”์ด๋„ˆ๋ฆฌ๋กœ ์ „ํ™˜ํ•ด ์ธํ…” ๋งฅ๊ณผ ์• ํ”Œ ์‹ค๋ฆฌ์ฝ˜ ๋งฅ์—์„œ ๊ฐ™์€ DMG๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. + - @ByrenYan1 ๋‹˜์ด [์ธํ…” ๋งฅ ์ง€์›](https://github.com/DmNote-App/DmNote/issues/125)์„ ์ œ์•ˆํ•ด์ฃผ์…จ์Šต๋‹ˆ๋‹ค. ๐ŸŽ‰ - macOS ๊ณ ์ฃผ์‚ฌ์œจ ๋””์Šคํ”Œ๋ ˆ์ด ์ง€์› [Issues 122](https://github.com/DmNote-App/DmNote/issues/122) - 120Hz ์ด์ƒ ๋””์Šคํ”Œ๋ ˆ์ด์—์„œ ์˜ค๋ฒ„๋ ˆ์ด์™€ ํŽธ์ง‘ ํ™”๋ฉด์ด ๋” ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๊ฐฑ์‹ ๋ฉ๋‹ˆ๋‹ค. @@ -173,6 +176,7 @@ - ์ด๋ฏธ์ง€, ์‚ฌ์šด๋“œ, ํฐํŠธ ํŒŒ์ผ์„ ์ฆ‰์‹œ ์‚ญ์ œํ•˜์ง€ ์•Š๊ณ  ์„ธ์…˜๋ณ„ ํœด์ง€ํ†ต์— 30์ผ ๊ฒฉ๋ฆฌํ•˜๋„๋ก ๋ณ€๊ฒฝ - ์ €์žฅ ํŒŒ์ผ ๊ต์ฒด, ์ž์‚ฐ ๋ณต์›, ์‹คํŒจ ๋กค๋ฐฑ์„ ์›์ž์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ด ์ถฉ๋Œ์ด๋‚˜ ํฌ๋ž˜์‹œ ๋’ค ์ผ๋ถ€ ์ƒํƒœ๋งŒ ๋‚จ๋˜ ๋ฌธ์ œ ์ˆ˜์ • - ๋…ธ๋ธŒ์— ์ ์šฉํ•œ ์ปค์Šคํ…€ ์ด๋ฏธ์ง€๊ฐ€ ์žฌ์‹คํ–‰ ํ›„ ์‚ฌ๋ผ์ง€๋˜ ๋ฌธ์ œ ์ˆ˜์ • [Issues 91](https://github.com/DmNote-App/DmNote/issues/91) + - @superalan89 ๋‹˜์ด [๋…ธ๋ธŒ ์ปค์Šคํ…€ ์ด๋ฏธ์ง€ ๋ˆ„๋ฝ ๋ฌธ์ œ](https://github.com/DmNote-App/DmNote/issues/91)๋ฅผ ์ œ๋ณดํ•ด์ฃผ์…จ์Šต๋‹ˆ๋‹ค. ๐ŸŽ‰ - ์‚ฌ์šด๋“œ, ์ด๋ฏธ์ง€, ์นด์šดํ„ฐ ๋ชจ์…˜, ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•œ ๋’ค ๊ณผ๊ฑฐ Undo/Redo๊ฐ€ ์ด๋ฏธ ์‚ญ์ œ๋œ ์ฐธ์กฐ๋ฅผ ๋ณต์›ํ•˜๋˜ ๋ฌธ์ œ ์ˆ˜์ • ### ํŒจ๋„, ํŒ์—… ๋ฐ ์ž…๋ ฅ @@ -193,6 +197,7 @@ - ๋…ธํŠธ ๋ผ์šด๋”ฉ ์ตœ์†Ÿ๊ฐ’์„ 0์œผ๋กœ ์กฐ์ •ํ•˜๊ณ  ๊ธฐ๋ณธ ๊ธ€๋กœ์šฐ ๋ฒ”์œ„๋ฅผ 10์œผ๋กœ ๋ณ€๊ฒฝ - ์งง์€ ๋…ธํŠธ๊ฐ€ ํ‘œ์‹œ ๊ฒฝ๊ณ„์—์„œ ๊ฐ‘์ž๊ธฐ ์‚ฌ๋ผ์ง€๊ฑฐ๋‚˜ ๊ธธ์ด๊ฐ€ ๋Š๊ฒจ ๋ณด์ด๋˜ ๋ฌธ์ œ ์ˆ˜์ • - OBS WebSocket์— ์ €์ง€์—ฐ ์ „์†ก ์˜ต์…˜์„ ์ ์šฉํ•ด ํ‚ค ์ด๋ฒคํŠธ ์ „๋‹ฌ ์ง€์—ฐ์„ ์ค„์ž„ [Issues 72](https://github.com/DmNote-App/DmNote/issues/72) + - @aigonanMTE ๋‹˜์ด [OBS ๋ชจ๋“œ ํ‚ค๋ทฐ์–ด ๋Š๊น€ ๋ฌธ์ œ](https://github.com/DmNote-App/DmNote/issues/72)๋ฅผ ์ œ๋ณดํ•ด์ฃผ์…จ์Šต๋‹ˆ๋‹ค. ๐ŸŽ‰ ### ์ƒ‰์ƒ, ์ด๋ฏธ์ง€, ํฐํŠธ ๋ฐ CSS @@ -205,6 +210,7 @@ - ํ”Œ๋Ÿฌ๊ทธ์ธ JavaScript ์‹คํ–‰ ์‹คํŒจ๋ฅผ ์„ฑ๊ณต์œผ๋กœ ํ‘œ์‹œํ•˜๊ฑฐ๋‚˜ ์˜ค๋ฅ˜ ์•ˆ๋‚ด๊ฐ€ ์ž˜๋ ค ๋ณด์ด๋˜ ๋ฌธ์ œ ์ˆ˜์ • - ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ์ƒํƒœ๋ฅผ ๊ฐฑ์‹ ํ•  ๋•Œ๋งˆ๋‹ค ํ‚ค๋ทฐ์–ด์˜ ๋ˆŒ๋ฆผ ํ‘œ์‹œ๊ฐ€ ๊นœ๋นก์ด๋˜ ๋ฌธ์ œ ์ˆ˜์ • [Issues 111](https://github.com/DmNote-App/DmNote/issues/111) + - @hjg-06 ๋‹˜์ด [ํ”Œ๋Ÿฌ๊ทธ์ธ ์ƒํƒœ ๊ฐฑ์‹  ์‹œ ๋ˆŒ๋ฆผ ํ‘œ์‹œ ๊นœ๋นก์ž„](https://github.com/DmNote-App/DmNote/issues/111)์— ๊ธฐ์—ฌํ•ด์ฃผ์…จ์Šต๋‹ˆ๋‹ค. ๐ŸŽ‰ - ํ”Œ๋Ÿฌ๊ทธ์ธ `onMount` ์˜ˆ์™ธ๊ฐ€ ์˜ค๋ฒ„๋ ˆ์ด ์ „์ฒด๋ฅผ ๋น„์šฐ๋˜ ๋ฌธ์ œ ์ˆ˜์ • - ์‹คํŒจํ–ˆ๊ฑฐ๋‚˜ ์ œ๊ฑฐ๋œ ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ ๋Šฆ์€ ์ €์žฅ์ด ์ƒˆ ์‹คํ–‰ ์ƒํƒœ๋ฅผ ๋ฎ์–ด์“ฐ๋˜ ๋ฌธ์ œ ์ˆ˜์ • - ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ์‚ฌ์šฉํ•˜๋Š” `window`์˜ ๋„ค์ดํ‹ฐ๋ธŒ ๋ฉ”์„œ๋“œ, ๋น„๋™๊ธฐ ์ปจํ…์ŠคํŠธ, ๊ธฐ์กด ์ „์—ญ API ํ˜ธํ™˜์„ฑ ๋ณต๊ตฌ @@ -217,6 +223,7 @@ - macOS ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ํ™”๋ฉด ์œ„์ชฝ์— ๋‹ฟ์ง€ ์•Š๊ณ  ๋นˆ ๊ณต๊ฐ„์ด ๋‚จ๋˜ ๋ฌธ์ œ ์ˆ˜์ • [Issues 121](https://github.com/DmNote-App/DmNote/issues/121) - `ํ™”๋ฉด ๋ชจ์„œ๋ฆฌ์— ๋งž์ถ”๊ธฐ` ๊ธฐ์ค€์„ ์ž‘์—… ์˜์—ญ์—์„œ ํ™”๋ฉด ๋์œผ๋กœ ์ •์ •ํ•ด ๋…๊ณผ ์ž‘์—… ํ‘œ์‹œ์ค„ ์˜์—ญ๋งŒํผ ๋–  ์žˆ๋˜ ์—ฌ๋ฐฑ ์ œ๊ฑฐ - ๋…ธํŠธ ํšจ๊ณผ๋ฅผ ๋„๋ฉด ์ƒ๋‹จ์— ์˜ˆ์•ฝ๋˜๋˜ ํŠธ๋ž™ ๋†’์ด๋ฅผ ์ฐฝ ๋†’์ด์—์„œ ํ•จ๊ป˜ ์ œ์™ธ + - @AL-Pinecore ๋‹˜์ด [macOS ์˜ค๋ฒ„๋ ˆ์ด ๋ฐฐ์น˜ ๋ฌธ์ œ](https://github.com/DmNote-App/DmNote/issues/121)์— ๊ธฐ์—ฌํ•ด์ฃผ์…จ์Šต๋‹ˆ๋‹ค. ๐ŸŽ‰ - macOS ์•ฑ ๋ฉ”๋‰ด์— ์ข…๋ฃŒ ํ•ญ๋ชฉ์„ ์ถ”๊ฐ€ํ•˜๊ณ  Dock ์ข…๋ฃŒ, ์žฌ์‹œ์ž‘, ํ—ฌํผ ํ”„๋กœ์„ธ์Šค ์ •๋ฆฌ ์•ˆ์ •ํ™” - macOS์—์„œ ์•ฑ ์‹คํ–‰ ์ค‘ ์ ‘๊ทผ์„ฑ ๊ถŒํ•œ ์š”์ฒญ์ฐฝ์ด ์ €์ ˆ๋กœ ๋œจ์ง€ ์•Š๋„๋ก ๋ณ€๊ฒฝ - ํŒŒ์ผ๊ณผ ํ”„๋ฆฌ์…‹ ์„ ํƒ์ฐฝ์„ ํ˜ธ์ถœํ•œ ์ฐฝ์— ๋ถ™๋Š” ๋น„๋™๊ธฐ ๋‹ค์ด์–ผ๋กœ๊ทธ๋กœ ํ†ต์ผํ•ด, ์ทจ์†Œ๋ฅผ ์‹คํŒจ๋กœ ์•Œ๋ฆฌ๊ฑฐ๋‚˜ UI๋ฅผ ๋ง‰๋˜ ๋ฌธ์ œ ์ˆ˜์ • From cc8aaa2cf66cec1a9b269ff744f075c6b19bbf15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=97=B0=EC=9A=B0?= Date: Sat, 29 Aug 2026 02:37:29 +0900 Subject: [PATCH 02/11] =?UTF-8?q?refactor:=20=EB=94=94=EC=9E=90=EC=9D=B8?= =?UTF-8?q?=20=ED=86=A0=ED=81=B0=EC=9D=84=20=ED=85=8C=EB=A7=88=EB=B3=84=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../digitPopAnimationContract.test.ts | 2 +- .../glassCanvasSurfaceContract.test.ts | 103 +++++- .../themeTokenParityContract.test.ts | 108 ++++++ .../__tests__/zIndexLayerContract.test.ts | 4 +- .../components/main/common/Checkbox.test.tsx | 2 +- src/renderer/styles/main.css | 42 ++- src/renderer/styles/tokens.css | 348 +----------------- src/renderer/styles/tokens/base.css | 171 +++++++++ src/renderer/styles/tokens/dark.css | 231 ++++++++++++ src/renderer/styles/tokens/light.css | 267 ++++++++++++++ src/renderer/windows/main/index.html | 37 +- src/renderer/windows/obs/index.html | 5 +- src/renderer/windows/overlay/index.html | 5 +- tailwind.config.js | 12 + 14 files changed, 955 insertions(+), 382 deletions(-) create mode 100644 src/renderer/__tests__/themeTokenParityContract.test.ts create mode 100644 src/renderer/styles/tokens/base.css create mode 100644 src/renderer/styles/tokens/dark.css create mode 100644 src/renderer/styles/tokens/light.css diff --git a/src/renderer/__tests__/digitPopAnimationContract.test.ts b/src/renderer/__tests__/digitPopAnimationContract.test.ts index 3e07aadc5..bfbda96ae 100644 --- a/src/renderer/__tests__/digitPopAnimationContract.test.ts +++ b/src/renderer/__tests__/digitPopAnimationContract.test.ts @@ -7,7 +7,7 @@ const mainCss = readFileSync( 'utf8', ); const tokensCss = readFileSync( - resolve(process.cwd(), 'src/renderer/styles/tokens.css'), + resolve(process.cwd(), 'src/renderer/styles/tokens/base.css'), 'utf8', ); diff --git a/src/renderer/__tests__/glassCanvasSurfaceContract.test.ts b/src/renderer/__tests__/glassCanvasSurfaceContract.test.ts index 856461fbe..581b675fd 100644 --- a/src/renderer/__tests__/glassCanvasSurfaceContract.test.ts +++ b/src/renderer/__tests__/glassCanvasSurfaceContract.test.ts @@ -2,23 +2,40 @@ import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { describe, expect, it } from 'vitest'; -const tokensCss = readFileSync( - resolve(process.cwd(), 'src/renderer/styles/tokens.css'), - 'utf8', -); -const mainCss = readFileSync( - resolve(process.cwd(), 'src/renderer/styles/main.css'), - 'utf8', -); -const tailwindConfig = readFileSync( - resolve(process.cwd(), 'tailwind.config.js'), - 'utf8', -); +const read = (path: string) => + readFileSync(resolve(process.cwd(), path), 'utf8'); + +const darkCss = read('src/renderer/styles/tokens/dark.css'); +const lightCss = read('src/renderer/styles/tokens/light.css'); +const mainCss = read('src/renderer/styles/main.css'); +const tailwindConfig = read('tailwind.config.js'); + +const themes: [string, string][] = [ + ['dark', darkCss], + ['light', lightCss], +]; + +const declared = (css: string, token: string): string | null => { + const match = css.match(new RegExp(`${token}:\\s*([^;]+);`)); + return match ? match[1].trim() : null; +}; describe('canvas ์œ„ glass ํ‘œ๋ฉด ๊ณ„์•ฝ', () => { - it('์ผ๋ฐ˜ glass์™€ ๋ถ„๋ฆฌ๋œ ๋ฐ๊ธฐยท์ฑ„๋„ clamp๋ฅผ ๊ฐ€์ง„๋‹ค', () => { - expect(tokensCss).toMatch(/--ui-glass-canvas-backdrop-dim:\s*0\.62/); - expect(tokensCss).toMatch(/--ui-glass-canvas-backdrop-sat:\s*0\.35/); + it('์ผ๋ฐ˜ glass์™€ ๋ถ„๋ฆฌ๋œ ๋ฐ๊ธฐยท๋Œ€๋น„ยท์ฑ„๋„ clamp๋ฅผ ๊ฐ€์ง„๋‹ค', () => { + // ์บ”๋ฒ„์Šค๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋งŒ๋“  ์ž„์˜ ๋ฐ๊ธฐ๋ผ ์ผ๋ฐ˜ ํ‘œ๋ฉด๊ณผ ๋‹ค๋ฅธ ์„ธ๊ธฐ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. + // ๊ฐ’์ด ๊ฐ™์•„์ง€๋ฉด ๋ถ„๋ฆฌํ•ด ๋‘” ์ด์œ ๊ฐ€ ์‚ฌ๋ผ์ง€๋ฏ€๋กœ ๋‹ค๋ฅด๋‹ค๋Š” ๊ฒƒ ์ž์ฒด๋ฅผ ๊ณ„์•ฝ์œผ๋กœ ๊ณ ์ • + for (const [name, css] of themes) { + for (const dial of ['dim', 'contrast', 'sat']) { + const base = declared(css, `--ui-glass-backdrop-${dial}`); + const canvas = declared(css, `--ui-glass-canvas-backdrop-${dial}`); + expect(base, `${name} ${dial}`).not.toBeNull(); + expect(canvas, `${name} canvas ${dial}`).not.toBeNull(); + } + expect( + declared(css, '--ui-glass-canvas-backdrop-sat'), + `${name} canvas sat`, + ).not.toBe(declared(css, '--ui-glass-backdrop-sat')); + } expect(mainCss).toContain( ':where(.backdrop-glass-canvas).backdrop-glass {', ); @@ -27,7 +44,36 @@ describe('canvas ์œ„ glass ํ‘œ๋ฉด ๊ณ„์•ฝ', () => { ); }); - it('ํˆฌ๋ช…๋„ ์ถ•์†Œ media ๊ทœ์น™๋ณด๋‹ค ์•ž์—์„œ ์„ ์–ธ๋œ๋‹ค', () => { + it('ํ•„ํ„ฐ ์ฒด์ธ์ด ์„ธ ๋‹ค์ด์–ผ์„ ๋ชจ๋‘ ์†Œ๋น„ํ•œ๋‹ค', () => { + // ํ† ํฐ๋งŒ ๋Š˜๋ฆฌ๊ณ  ์ฒด์ธ์— ์•ˆ ๋„ฃ์œผ๋ฉด ๋ผ์ดํŠธ์˜ ๋ฒ”์œ„ ์••์ถ•์ด ์กฐ์šฉํžˆ ์ฃฝ๋Š”๋‹ค + const filters: string[] = mainCss.match(/backdrop-filter:[^;]+;/g) ?? []; + const clamped = filters.filter((rule) => rule.includes('brightness(')); + expect(clamped.length).toBeGreaterThan(0); + for (const rule of clamped) { + expect(rule).toContain('contrast('); + expect(rule).toContain('saturate('); + } + }); + + it('ํˆฌ๋ช…๋„ ์ถ•์†Œ ํด๋ฐฑ์ด ํ…Œ๋งˆ ํ† ํฐ์„ ๊ฑฐ์นœ๋‹ค', () => { + // ๋ฆฌํ„ฐ๋Ÿด๋กœ ๋‘๋ฉด ์š”์†Œ ์Šค์ฝ”ํ”„ ์„ ์–ธ์ด ํ…Œ๋งˆ ๋ฃจํŠธ๋ฅผ ์ด๊ฒจ ๋ผ์ดํŠธ์—์„œ ๋‹คํฌ ๋ฉด์ด ๋œฌ๋‹ค + const reduced = mainCss.slice( + mainCss.indexOf('@media (prefers-reduced-transparency: reduce)'), + ); + expect(reduced).not.toMatch(/--ui-glass[\w-]*:\s*rgba?\(/); + for (const [name, css] of themes) { + for (const token of [ + '--ui-glass-reduced', + '--ui-glass-heavy-reduced', + '--ui-glass-dim-reduced', + '--ui-glass-panel-reduced', + ]) { + expect(declared(css, token), `${name} ${token}`).not.toBeNull(); + } + } + }); + + it('canvas ๊ทœ์น™์ด ํˆฌ๋ช…๋„ ์ถ•์†Œ media ๊ทœ์น™๋ณด๋‹ค ์•ž์—์„œ ์„ ์–ธ๋œ๋‹ค', () => { const canvasSurfaceRule = mainCss.indexOf( ':where(.backdrop-glass-canvas).backdrop-glass {', ); @@ -38,12 +84,29 @@ describe('canvas ์œ„ glass ํ‘œ๋ฉด ๊ณ„์•ฝ', () => { expect(reducedTransparencyRule).toBeGreaterThan(canvasSurfaceRule); }); - it('popup ์ „์šฉ ์งง์€ shadow๊ฐ€ theme์— ์—ฐ๊ฒฐ๋œ๋‹ค', () => { - expect(tokensCss).toMatch( - /--ui-shadow-popup:[\s\S]*?0 3px 10px rgba\(0, 0, 0, 0\.28\)/, - ); + it('popup ์ „์šฉ ์งง์€ shadow๊ฐ€ ๋‘ ํ…Œ๋งˆ์— ๋ชจ๋‘ ์—ฐ๊ฒฐ๋œ๋‹ค', () => { + for (const [name, css] of themes) { + const popup = declared(css, '--ui-shadow-popup'); + const modal = declared(css, '--ui-shadow-3'); + expect(popup, `${name} popup`).not.toBeNull(); + // ํŒ์—…์€ ๋ชจ๋‹ฌ์˜ ๋„“์€ ๋ฒˆ์ง์„ ์žฌ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค + expect(popup, `${name} popup`).not.toBe(modal); + } expect(tailwindConfig).toContain( "'elevation-popup': 'var(--ui-shadow-popup)'", ); }); + + it('๋ฃจ๋ฏธ๋…ธ์‹œํ‹ฐ ๋ ˆ์ด์–ด์˜ ๋ชฉํ‘œ ํœ˜๋„๊ฐ€ glass ํ‹ดํŠธ์™€ ๊ฐ™์€ ์ƒ‰์ด๋‹ค', () => { + // ํ‹ดํŠธ๋งŒ ์˜ฌ๋ฆฌ๊ณ  ์—ฌ๊ธฐ๋ฅผ ๋‘๋ฉด ์บก์ด ํœ˜๋„๋ฅผ ๋„๋กœ ๋Œ์–ด๋‹น๊ธด๋‹ค + for (const [name, css] of themes) { + const tint = declared(css, '--ui-glass-backdrop-lum-tint'); + const glass = declared(css, '--ui-glass'); + expect(tint, `${name} lum tint`).toMatch(/^#[0-9a-f]{6}$/); + const [r, g, b] = [1, 3, 5].map((i) => + parseInt((tint as string).slice(i, i + 2), 16), + ); + expect(glass, `${name} glass`).toContain(`rgba(${r}, ${g}, ${b},`); + } + }); }); diff --git a/src/renderer/__tests__/themeTokenParityContract.test.ts b/src/renderer/__tests__/themeTokenParityContract.test.ts new file mode 100644 index 000000000..b6b936ccd --- /dev/null +++ b/src/renderer/__tests__/themeTokenParityContract.test.ts @@ -0,0 +1,108 @@ +import { globSync, readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; + +const read = (name: string) => + readFileSync(resolve(process.cwd(), 'src/renderer/styles', name), 'utf8'); + +const barrelCss = read('tokens.css'); +const baseCss = read('tokens/base.css'); +const darkCss = read('tokens/dark.css'); +const lightCss = read('tokens/light.css'); +const tailwindConfig = readFileSync( + resolve(process.cwd(), 'tailwind.config.js'), + 'utf8', +); + +const stripComments = (css: string) => css.replace(/\/\*[\s\S]*?\*\//g, ''); + +// ์„ ์–ธ๋งŒ ๋ฝ‘๋Š”๋‹ค. var(--x) ์ฐธ์กฐ๋Š” ์ฝœ๋ก ์ด ์—†์–ด ๊ฑธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค +const declaredKeys = (css: string): string[] => + Array.from(stripComments(css).matchAll(/(--[\w-]+)\s*:/g)).map((m) => m[1]); + +const darkKeys = declaredKeys(darkCss); +const lightKeys = declaredKeys(lightCss); + +describe('ํ…Œ๋งˆ ํ† ํฐ ํŒŒ๋ฆฌํ‹ฐ ๊ณ„์•ฝ', () => { + it('๋‹คํฌ์™€ ๋ผ์ดํŠธ๊ฐ€ ๊ฐ™์€ ํ‚ค๋ฅผ ๊ฐ™์€ ์ˆœ์„œ๋กœ ์„ ์–ธํ•œ๋‹ค', () => { + // ์ˆœ์„œ๊นŒ์ง€ ๋ฌถ์–ด ๋‘๋ฉด ๋‘ ํŒŒ์ผ์„ ๋‚˜๋ž€ํžˆ ๋†“๊ณ  ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค + expect(lightKeys).toEqual(darkKeys); + }); + + it('ํ‚ค๊ฐ€ ์ค‘๋ณต ์„ ์–ธ๋˜์ง€ ์•Š๋Š”๋‹ค', () => { + expect(new Set(darkKeys).size).toBe(darkKeys.length); + expect(new Set(lightKeys).size).toBe(lightKeys.length); + }); + + it('ํ…Œ๋งˆ ํŒŒ์ผ์€ :root ํด๋ฐฑ์„ ๋‘์ง€ ์•Š๋Š”๋‹ค', () => { + // ๋‹คํฌ๊ฐ€ bare :root์— ๋‚จ์œผ๋ฉด ๋ผ์ดํŠธ์—์„œ ๋น ์ง„ ํ† ํฐ์ด ์กฐ์šฉํžˆ ๋‹คํฌ๋กœ ์ƒˆ์–ด๋‚˜๊ฐ„๋‹ค. + // :where()๋กœ ๊ฐ์‹ธ๋Š” ์ด์œ ๋Š” ์‚ฌ์šฉ์ž CSS์˜ :root ์žฌ์ •์˜์™€ ํŠน์ด๋„๋ฅผ ๋งž์ถ”๊ธฐ ์œ„ํ•จ + expect(stripComments(darkCss)).toContain( + ":root:where([data-theme='dark']) {", + ); + expect(stripComments(lightCss)).toContain( + ":root:where([data-theme='light']) {", + ); + expect(stripComments(darkCss)).not.toMatch(/(^|\})\s*:root\s*\{/); + expect(stripComments(lightCss)).not.toMatch(/(^|\})\s*:root\s*\{/); + }); + + it('base๋Š” ์ƒ‰์„ ์†Œ์œ ํ•˜์ง€ ์•Š๋Š”๋‹ค', () => { + const body = stripComments(baseCss); + expect(body).not.toMatch(/#[0-9a-fA-F]{3,8}\b/); + expect(body).not.toMatch(/\b(rgba?|hsla?|color-mix|oklch)\(/); + expect(body).not.toMatch(/:\s*(white|black)\b/); + }); + + it('ํ…Œ๋งˆ ํŒŒ์ผ์€ ๊ตฌ์กฐ ํ† ํฐ์„ ์†Œ์œ ํ•˜์ง€ ์•Š๋Š”๋‹ค', () => { + const structural = /(radius|duration|ease|stagger|scale|^--z-)/; + expect(darkKeys.filter((key) => structural.test(key))).toEqual([]); + expect(lightKeys.filter((key) => structural.test(key))).toEqual([]); + }); + + it('๋ฐฐ๋Ÿด์ด ์„ธ ํŒŒ์ผ์„ ์ˆœ์„œ๋Œ€๋กœ ๋กœ๋“œํ•œ๋‹ค', () => { + const order = Array.from( + barrelCss.matchAll(/@import\s+'\.\/tokens\/([\w-]+)\.css'/g), + ).map((m) => m[1]); + expect(order).toEqual(['base', 'dark', 'light']); + }); + + it('tailwind๊ฐ€ ์ฐธ์กฐํ•˜๋Š” ํ† ํฐ์ด ์–‘์ชฝ ํ…Œ๋งˆ์— ๋ชจ๋‘ ์žˆ๋‹ค', () => { + const referenced = new Set( + Array.from(tailwindConfig.matchAll(/var\((--ui-[\w-]+)\)/g)).map( + (m) => m[1], + ), + ); + const baseKeys = new Set(declaredKeys(baseCss)); + const missing = Array.from(referenced).filter( + (key) => + !baseKeys.has(key) && + !(darkKeys.includes(key) && lightKeys.includes(key)), + ); + expect(missing).toEqual([]); + }); + + it('์†Œ์Šค๊ฐ€ ์ฐธ์กฐํ•˜๋Š” ํ† ํฐ์ด ์–‘์ชฝ ํ…Œ๋งˆ์— ๋ชจ๋‘ ์žˆ๋‹ค', () => { + // ํ•œ์ชฝ ํ…Œ๋งˆ์—๋งŒ ๊ฐ’์ด ์žˆ๊ฑฐ๋‚˜ ์•„๋ฌด ๋ฐ๋„ ์—†๋Š” ํ† ํฐ์„ ์“ฐ๊ธฐ ์‹œ์ž‘ํ•œ ์ฝ”๋“œ๋ฅผ ๋ง‰๋Š”๋‹ค. + // ์‚ฌ์šฉ์ž ์ฝ˜ํ…์ธ  ๋ณ€์ˆ˜(--dmn-*, --key-*, --counter-* ๋“ฑ)๋Š” ์•ฑ ํ…Œ๋งˆ ๋Œ€์ƒ์ด ์•„๋‹ˆ๋‹ค + const known = new Set([ + ...declaredKeys(baseCss), + ...darkKeys.filter((key) => lightKeys.includes(key)), + ]); + const sources = [ + ...globSync('src/renderer/**/*.{ts,tsx}'), + ...globSync('src/renderer/styles/*.css'), + ].filter((file) => !file.includes('__tests__') && !file.includes('.test.')); + + const missing = new Map(); + for (const file of sources) { + const body = readFileSync(resolve(process.cwd(), file), 'utf8'); + for (const match of body.matchAll(/var\((--ui-[\w-]+)/g)) { + const key = match[1]; + if (known.has(key) || missing.has(key)) continue; + missing.set(key, file); + } + } + expect(Object.fromEntries(missing)).toEqual({}); + }); +}); diff --git a/src/renderer/__tests__/zIndexLayerContract.test.ts b/src/renderer/__tests__/zIndexLayerContract.test.ts index 261685024..f5a246400 100644 --- a/src/renderer/__tests__/zIndexLayerContract.test.ts +++ b/src/renderer/__tests__/zIndexLayerContract.test.ts @@ -5,7 +5,7 @@ import { describe, expect, it } from 'vitest'; const readSource = (path: string) => readFileSync(resolve(process.cwd(), path), 'utf8'); -const TOKENS = readSource('src/renderer/styles/tokens.css'); +const TOKENS = readSource('src/renderer/styles/tokens/base.css'); const tokenValue = (name: string): number => { const match = TOKENS.match(new RegExp(`--${name}:\\s*(-?\\d+);`)); @@ -91,7 +91,7 @@ describe('z-index ์ธต ๊ณ„์•ฝ', () => { }); // ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ๋งŒ ๊ฒจ๋ฃจ๋Š” ์ž‘์€ ๊ฐ’(z-10 ๋“ฑ)์€ ๊ณ„์•ฝ ๋ฐ–์ด๋‹ค. - // ์‚ฌ๋‹ค๋ฆฌ ๊ตฌ๊ฐ„์„ ๋งจ์ˆซ์ž๋กœ ์“ฐ๋ฉด tokens.css๊ฐ€ ๋‹จ์ผ ์†Œ์Šค๊ฐ€ ์•„๋‹ˆ๊ฒŒ ๋œ๋‹ค + // ์‚ฌ๋‹ค๋ฆฌ ๊ตฌ๊ฐ„์„ ๋งจ์ˆซ์ž๋กœ ์“ฐ๋ฉด tokens/base.css๊ฐ€ ๋‹จ์ผ ์†Œ์Šค๊ฐ€ ์•„๋‹ˆ๊ฒŒ ๋œ๋‹ค it('ํฌ๋กฌ ์‚ฌ๋‹ค๋ฆฌ ๊ตฌ๊ฐ„์„ ๋งจ์ˆซ์ž๋กœ ์“ฐ์ง€ ์•Š๋Š”๋‹ค', () => { const offenders = collectSources('src/renderer').filter((path) => { const source = stripComments(readSource(path)); diff --git a/src/renderer/components/main/common/Checkbox.test.tsx b/src/renderer/components/main/common/Checkbox.test.tsx index 736e80c23..f944cfb77 100644 --- a/src/renderer/components/main/common/Checkbox.test.tsx +++ b/src/renderer/components/main/common/Checkbox.test.tsx @@ -148,7 +148,7 @@ describe('Checkbox commit ์ „๋žต', () => { const trackWidth = pxOf(track.className, /w-\[(\d+)px\]/); const thumbWidth = pxOf(thumb.className, /w-\[(\d+)px\]/); const inset = pxOf(thumb.className, /left-\[(\d+)px\]/); - const tokens = readFileSync('src/renderer/styles/tokens.css', 'utf8'); + const tokens = readFileSync('src/renderer/styles/tokens/base.css', 'utf8'); const travel = Number( tokens.match(/--ui-toggle-travel:\s*(\d+)px/)?.[1] ?? NaN, ); diff --git a/src/renderer/styles/main.css b/src/renderer/styles/main.css index dde16ef06..08acc0919 100644 --- a/src/renderer/styles/main.css +++ b/src/renderer/styles/main.css @@ -100,33 +100,43 @@ body.dmn-dragging * { ๊ธ€๋ž˜์Šค๋Š” ์ผ์‹œ ํ‘œ๋ฉด(๋ชจ๋‹ฌยทํŒ์—…) ์ „์šฉ ์œ ์ง€. ํ‘œ๋ฉด transform์€ ๋“ฑํ‡ด์žฅ ํ•œ ๋ฒˆ๋งŒ ํ—ˆ์šฉํ•˜๊ณ , ์ƒ์ฃผ ์ค‘ ๋ฐ˜๋ณต ์• ๋‹ˆ๋ฉ”์ด์…˜์€ ๊ธˆ์ง€ (ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์žฌํ•„ํ„ฐ๋ฅผ ๋ถ€๋ฆ„) */ .backdrop-glass { - -webkit-backdrop-filter: blur(32px) brightness(var(--ui-glass-backdrop-dim)) + -webkit-backdrop-filter: blur(32px) + contrast(var(--ui-glass-backdrop-contrast)) + brightness(var(--ui-glass-backdrop-dim)) saturate(var(--ui-glass-backdrop-sat)); - backdrop-filter: blur(32px) brightness(var(--ui-glass-backdrop-dim)) + backdrop-filter: blur(32px) contrast(var(--ui-glass-backdrop-contrast)) + brightness(var(--ui-glass-backdrop-dim)) saturate(var(--ui-glass-backdrop-sat)); } .backdrop-glass-popup { - -webkit-backdrop-filter: blur(24px) brightness(var(--ui-glass-backdrop-dim)) + -webkit-backdrop-filter: blur(24px) + contrast(var(--ui-glass-backdrop-contrast)) + brightness(var(--ui-glass-backdrop-dim)) saturate(var(--ui-glass-backdrop-sat)); - backdrop-filter: blur(24px) brightness(var(--ui-glass-backdrop-dim)) + backdrop-filter: blur(24px) contrast(var(--ui-glass-backdrop-contrast)) + brightness(var(--ui-glass-backdrop-dim)) saturate(var(--ui-glass-backdrop-sat)); } /* canvas ์œ„ ํ‘œ๋ฉด์€ ๋ฐฐ๊ฒฝ์ƒ‰์ด ์žฌ์งˆ์„ ์ง€๋ฐฐํ•˜์ง€ ์•Š๊ฒŒ clamp ๊ฐ•ํ™” */ :where(.backdrop-glass-canvas).backdrop-glass { -webkit-backdrop-filter: blur(32px) + contrast(var(--ui-glass-canvas-backdrop-contrast)) brightness(var(--ui-glass-canvas-backdrop-dim)) saturate(var(--ui-glass-canvas-backdrop-sat)); - backdrop-filter: blur(32px) brightness(var(--ui-glass-canvas-backdrop-dim)) + backdrop-filter: blur(32px) contrast(var(--ui-glass-canvas-backdrop-contrast)) + brightness(var(--ui-glass-canvas-backdrop-dim)) saturate(var(--ui-glass-canvas-backdrop-sat)); } :where(.backdrop-glass-canvas).backdrop-glass-popup { -webkit-backdrop-filter: blur(24px) + contrast(var(--ui-glass-canvas-backdrop-contrast)) brightness(var(--ui-glass-canvas-backdrop-dim)) saturate(var(--ui-glass-canvas-backdrop-sat)); - backdrop-filter: blur(24px) brightness(var(--ui-glass-canvas-backdrop-dim)) + backdrop-filter: blur(24px) contrast(var(--ui-glass-canvas-backdrop-contrast)) + brightness(var(--ui-glass-canvas-backdrop-dim)) saturate(var(--ui-glass-canvas-backdrop-sat)); } @@ -136,7 +146,7 @@ body.dmn-dragging * { ๋ธ”๋Ÿฌ๋Š” ์ฒซ ํ”„๋ ˆ์ž„๋ถ€ํ„ฐ ์ƒ์ˆ˜, ํ‹ดํŠธ๋งŒ ํŽ˜์ด๋“œ. Windows์—์„œ ๋ชจ๋‹ฌ ์—ฐ ์ฑ„ ํ‚ค ์—ฐํƒ€ ํ”„๋ ˆ์ž„ ์‹ค์ธก์ด ์œ ์ง€ ์กฐ๊ฑด */ .backdrop-glass-scrim { - background: rgba(0, 0, 0, 0.6); + background: var(--ui-scrim); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); } @@ -164,15 +174,17 @@ body.dmn-dragging * { /* ํˆฌ๋ช…๋„ ์ถ•์†Œ ์„ค์ • ์กด์ค‘ โ€” ๋‚จ๋Š” ๋ผ์ด๋ธŒ ๊ธ€๋ž˜์Šค(๋ชจ๋‹ฌ ์นด๋“œยทํŒ์˜ค๋ฒ„)๋ฅผ ๊ณ ์•ŒํŒŒ ์†”๋ฆฌ๋“œ๋กœ ๊ฐ•๋“ฑ ํ‹ดํŠธ ํ† ํฐ๋งŒ ์š”์†Œ ์Šค์ฝ”ํ”„์—์„œ ์Šน๊ฒฉํ•ด ๋ผ์šด๋”ฉยท์ธ์…‹ ๋ง ์žฌ์งˆ์€ ๊ทธ๋Œ€๋กœ ์œ ์ง€. ๊ฐ’์€ ์บ”๋ฒ„์Šค ์œ„ ๋ผ์ด๋ธŒ ํ•ฉ์„ฑ ๊ฒฐ๊ณผ ๊ธฐ์ค€ โ€” ๊ณ ์•ŒํŒŒ ์†”๋ฆฌ๋“œ๋Š” ๋’ท๋ฉด์„ ๊ฑฐ์˜ ์•ˆ ๋ฐ›์œผ๋ฏ€๋กœ - ์Šคํฌ๋ฆผ ์œ„ ๋ชจ๋‹ฌ์—์„œ๋Š” ๋ผ์ด๋ธŒ๋ณด๋‹ค ํ•œ ํ†ค ๋ฐ๊ฒŒ ๋œฌ๋‹ค. blur ์—†์ด๋Š” ๋งฅ๋ฝ ๋ฐ˜์˜์ด ๋ถˆ๊ฐ€๋Šฅ */ + ์Šคํฌ๋ฆผ ์œ„ ๋ชจ๋‹ฌ์—์„œ๋Š” ๋ผ์ด๋ธŒ๋ณด๋‹ค ํ•œ ํ†ค ๋ฐ๊ฒŒ ๋œฌ๋‹ค. blur ์—†์ด๋Š” ๋งฅ๋ฝ ๋ฐ˜์˜์ด ๋ถˆ๊ฐ€๋Šฅ. + ์Šน๊ฒฉ ๊ฐ’์„ ๋ฆฌํ„ฐ๋Ÿด๋กœ ๋‘๋ฉด ์š”์†Œ ์Šค์ฝ”ํ”„๊ฐ€ ํ…Œ๋งˆ ๋ฃจํŠธ๋ฅผ ์ด๊ฒจ ๋ผ์ดํŠธ์—์„œ๋„ ๋‹คํฌ ๋ฉด์ด ๋œฌ๋‹ค - + ๋ฐ˜๋“œ์‹œ ํ…Œ๋งˆ๊ฐ€ ์†Œ์œ ํ•œ *-reduced ํ† ํฐ์„ ๊ฑฐ์น  ๊ฒƒ */ @media (prefers-reduced-transparency: reduce) { .backdrop-glass, .backdrop-glass-popup { -webkit-backdrop-filter: none; backdrop-filter: none; - --ui-glass: rgba(26, 26, 30, 0.96); - --ui-glass-heavy: rgba(32, 32, 36, 0.96); - --ui-glass-dim: rgba(22, 22, 26, 0.94); + --ui-glass: var(--ui-glass-reduced); + --ui-glass-heavy: var(--ui-glass-heavy-reduced); + --ui-glass-dim: var(--ui-glass-dim-reduced); } /* ์Šคํฌ๋ฆผ์€ ๋ธ”๋Ÿฌ๋งŒ ๋„๊ณ  ์•ŒํŒŒ ๋”ค์œผ๋กœ ํด๋ฐฑ */ @@ -189,7 +201,7 @@ body.dmn-dragging * { /* ๊ธ€๋ž˜์Šค ํŒจ๋„ ํ‹ดํŠธ ์‚ฌ์šฉ์ฒ˜(์‚ฌ์ด๋“œ ํŒจ๋„ ํ”„๋ ˆ์ž„ยท์บ”๋ฒ„์Šค ํŒ์—…)๋„ ๋™์ผ ์Šน๊ฒฉ */ .bg-glass-panel { - --ui-glass-panel: rgba(25, 25, 29, 0.96); + --ui-glass-panel: var(--ui-glass-panel-reduced); } } @@ -846,16 +858,16 @@ body.dmn-dragging * { } .webfont-cm-editor .cm-scroller::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.14); + background: var(--ui-scrollbar-thumb); border-radius: 2px; } .webfont-cm-editor .cm-scroller::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.2); + background: var(--ui-scrollbar-thumb-hover); } .webfont-cm-editor .cm-scroller::-webkit-scrollbar-thumb:active { - background: rgba(255, 255, 255, 0.26); + background: var(--ui-scrollbar-thumb-active); } /* ๋ชจ์…˜ ์ถ•์†Œ ์„ค์ • ์กด์ค‘ โ€” ์ด๋™์€ ์ œ๊ฑฐ, ํ‘œ์‹œ ์ž์ฒด๋Š” ์œ ์ง€ */ diff --git a/src/renderer/styles/tokens.css b/src/renderer/styles/tokens.css index 70c2f27e6..07a6984cc 100644 --- a/src/renderer/styles/tokens.css +++ b/src/renderer/styles/tokens.css @@ -1,340 +1,14 @@ /* - * ๋””์ž์ธ ํ† ํฐ โ€” ๋ชจ๋“  ์œˆ๋„์šฐ์—์„œ ๋กœ๋“œ๋จ + * ๋””์ž์ธ ํ† ํฐ ๋ฐฐ๋Ÿด โ€” ๋ชจ๋“  ์œˆ๋„์šฐ์—์„œ ๋กœ๋“œ๋จ * UI ์ƒ‰ยท๋ชจ์…˜ยท๊ทธ๋ฆผ์ž์˜ ๋‹จ์ผ ์†Œ์Šค. ์ปดํฌ๋„ŒํŠธ์—์„œ hex ์ง์ ‘ ์‚ฌ์šฉ ๊ธˆ์ง€ + * + * base ํ…Œ๋งˆ ๋ฌด๊ด€ ๊ฐ’ (๋ผ์šด๋”ฉ, ๋ชจ์…˜, ๋ ˆ์ด์–ด ์Šคํƒ) + * dark :root:where([data-theme='dark']) + * light :root:where([data-theme='light']) + * + * ๋‘ ํ…Œ๋งˆ ํŒŒ์ผ์€ ํ‚ค ์ง‘ํ•ฉ๊ณผ ์„ ์–ธ ์ˆœ์„œ๊ฐ€ ๊ฐ™์•„์•ผ ํ•œ๋‹ค. + * ๋‹คํฌ๋ฅผ bare :root ํด๋ฐฑ์œผ๋กœ ๋‘์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ฌธ์„œ html์— data-theme์ด ๋ฐ˜๋“œ์‹œ ์žˆ์–ด์•ผ ํ•œ๋‹ค */ -:root { - /* ๋ฐฐ๊ฒฝ โ€” ๊ฐ’์€ macOS ๋‹คํฌ๋ฅผ ์‹ค์ธกํ•ด์„œ ๋งž์ถ˜ ๊ฒƒ์ด๋‹ค (๊ฐ™์€ ๋””์Šคํ”Œ๋ ˆ์ด์—์„œ ์‹œ์Šคํ…œ ์„ค์ • ์ฐฝ ์บก์ฒ˜ + - NSColor ๋‹คํฌ ์นดํƒˆ๋กœ๊ทธ ์ง์ ‘ ์กฐํšŒ). ์• ํ”Œ windowBackgroundColor๊ฐ€ ์ •ํ™•ํžˆ #1e1e1e๋‹ค. - - ์ค‘์š”ํ•œ ๊ฑด ์ ˆ๋Œ€ ๋ฐ๊ธฐ๊ฐ€ ์•„๋‹ˆ๋ผ ๋‹จ๊ณ„ ํญ์ด๋‹ค. ์• ํ”Œ์€ ์‚ฌ์ด๋“œ๋ฐ” 30 โ†’ ์ฝ˜ํ…์ธ  34(+4) โ†’ - ์นด๋“œ 42(+8) โ†’ ์ธํ’‹ 49(+7)๋กœ ์•„์ฃผ ์ด˜์ด˜ํ•˜๊ณ , ์ฐฝ ํ”ฝ์…€์˜ 92%๊ฐ€ L* 10~19์— ๋“ค์–ด๊ฐ„๋‹ค. - ํญ์„ ๋ฒŒ๋ฆฌ๋ฉด ๋ฐ์€ ์ชฝ ๋์ด ํŠ€์–ด์„œ ์ „์ฒด๊ฐ€ '๋ฐ๋‹ค'๊ณ  ์ฝํžŒ๋‹ค โ€” ๋ฒ ์ด์Šค๋ฅผ ๋‚ด๋ ค๋„ ์•ˆ ๊ณ ์ณ์ง„๋‹ค. - - ์• ํ”Œ ์นดํƒˆ๋กœ๊ทธ๋Š” a*=b*=0์œผ๋กœ ์™„์ „ ๋ฌด์ฑ„์ƒ‰์ด๋‹ค. ํ™”๋ฉด์— ๋„๋Š” ์ƒ‰์กฐ๋Š” ํŒ”๋ ˆํŠธ๊ฐ€ ์‹ฌ๋Š” ๊ฒŒ - ์•„๋‹ˆ๋ผ Desktop Tinting์ด ๋งŒ๋“ ๋‹ค. ์—ฌ๊ธฐ์— ์ƒ‰ ํŽธํ–ฅ์„ ๋„ฃ์ง€ ๋ง ๊ฒƒ */ - --ui-bg-app: #131315; - --ui-bg-panel: #1b1b1d; - --ui-bg-surface: #232325; - --ui-bg-elevated: #1f1f21; - /* ์ปจํŠธ๋กค ํ•„ โ€” ๋ฐ˜ํˆฌ๋ช… ํ™”์ดํŠธ๋ผ ์–ด๋А ํ‘œ๋ฉด ์œ„์„œ๋“  ํ•œ ๋‹จ๊ณ„ ๋ฐ๊ฒŒ ๋œธ - ๊ทผํ‘ ๋ฐฐ๊ฒฝ์—์„  ์–ด๋‘์šด ํ™ˆ์ด ๋ฐฐ๊ฒฝ๊ณผ ๊ตฌ๋ถ„๋˜์ง€ ์•Š์•„ ์ธํ’‹ยทํŠธ๋ž™์€ ๋ฐ์€ ํ•„์ด ๊ธฐ์ค€ - ์กฐ์ž‘๋ฉด(--ui-fill 0.072)๋ณด๋‹ค ์•„๋ž˜ ์œ ์ง€ โ€” ์ •์  ํ™ˆ๊ณผ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ํ•„์˜ ๋ฐ๊ธฐ ๊ตฌ๋ถ„. - ๊ฐ™์€ ์ •์  ๋ฉด์ธ --ui-fill-faint์™€๋Š” ๊ฐ™์€ ๋‹จ์— ๋‘”๋‹ค - ์•ŒํŒŒ ๊ธฐ์ค€์€ ฮ”L*๊ฐ€ ์•„๋‹ˆ๋ผ ๋ฒ ๋ฒ„ ๋Œ€๋น„(ฮ”sRGB/๋ฒ ์ด์Šค)๋‹ค. ์–ด๋‘์šด ์ž๋ฐœ๊ด‘ UI์—์„œ - ์ธ์ ‘๋ฉด ๋ถ„๋ฆฌ๋Š” ์ƒ๋Œ€ ๋Œ€๋น„๊ฐ€ ์ง€๋ฐฐํ•œ๋‹ค โ€” L*๋ฅผ ๊ณ ์ •ํ•˜๋ฉด ๋ฒ ์ด์Šค๊ฐ€ ๋ฐ์•„์ง„ ๋งŒํผ - ์ƒ๋Œ€ ๋Œ€๋น„๊ฐ€ ๊นŽ์—ฌ ๋ฉด์ด ๋ญ‰๊ฐ ๋‹ค (์‹ค์ธก์œผ๋กœ ํ™•์ธ) */ - --ui-bg-inset: rgba(255, 255, 255, 0.035); - /* ๋ถˆํˆฌ๋ช… ์›ฐ โ€” ์ฝ”๋“œ ์—๋””ํ„ฐ์ฒ˜๋Ÿผ ๋ฐฐ๊ฒฝ์ด ํ”๋“ค๋ฆฌ๋ฉด ์•ˆ ๋˜๋Š” ํ‘œ๋ฉด ์ „์šฉ */ - --ui-bg-inset-solid: #161618; - /* ๋ถ„๋ฆฌ๋œ ์†์„ฑ ํŒจ๋„ ์ฐฝ์˜ ๋ฐ”๋‹ฅ โ€” ์ฐฝ ์ž์ฒด๊ฐ€ transparent๋ผ ์•ŒํŒŒ๋ฅผ ์“ฐ๋ฉด ๋ฐ์Šคํฌํ†ฑ์ด ๋น„์นœ๋‹ค. - ๊ฐ’์€ ๋„ํ‚น ์ƒํƒœ์˜ ์‚ฌ์ด๋“œ ํŒจ๋„(--ui-glass-panel ํ•ฉ์„ฑ ๊ฒฐ๊ณผ)๊ณผ ๊ฐ™์€ L*์— ๋งž์ถฐ ๋‘ . - Windows๋Š” ์ฒซ ํŽ˜์ธํŠธ ์ „ ๊ตฌ๊ฐ„์„ ๊ฐ™์€ ์ƒ‰์œผ๋กœ ๋ฉ”์šฐ๋ฏ€๋กœ windows_window_corners.rs์˜ - SEED_FILL๊ณผ ํ•จ๊ป˜ ๋ฐ”๊ฟ€ ๊ฒƒ */ - --ui-bg-panel-detached: #1c1c1e; - - /* ๊ธ€๋ž˜์Šค ํ‘œ๋ฉด โ€” ์Šค๋ชจํฌ ์žฌ์งˆ. ํŒ์—…ยทํŒจ๋„ 0.72, ์บ”๋ฒ„์Šค ํฌ๋กฌ 0.6 - ์•ŒํŒŒ โ†“ = ๋ฐฐ๊ฒฝ ํˆฌ๊ณผ โ†‘ (๊ธ€๋ž˜์Šค ์ฒด๊ฐ์˜ 1์ฐจ ๋‹ค์ด์–ผ). ๋ถ„๋ฆฌ๋Š” ๊ทธ๋ฆผ์ž ํ† ํฐ์˜ ์ธ์…‹ ๋ง+์ƒ๋‹จ ํ•˜์ด๋ผ์ดํŠธ๊ฐ€ ๋‹ด๋‹น, ๋ณด๋” ๊ธˆ์ง€ - ํ‹ดํŠธ๋Š” backdrop-filter โ†’ ๋ฃจ๋ฏธ๋…ธ์‹œํ‹ฐ ์บก โ†’ ํ‹ดํŠธ 3๋‹จ ํ•ฉ์„ฑ์„ ๊ฑฐ์นœ ์ตœ์ข… L*์—์„œ ์—ญ์‚ฐํ•œ ๊ฐ’์ด๋ผ - ํ‹ดํŠธ ์ƒ‰๋งŒ ๋ณด๊ณ  ํ‘œ๋ฉด ๋ฐ๊ธฐ๋ฅผ ์ฝ์œผ๋ฉด ์•ˆ ๋œ๋‹ค. ์บก ํ‹ดํŠธ๋„ ๊ฐ™์ด ์›€์ง์—ฌ์•ผ ์˜๋„ํ•œ ๋งŒํผ ์˜ฌ๋ผ์˜จ๋‹ค */ - --ui-glass: rgba(28, 28, 30, 0.72); - /* ๋ชจ๋‹ฌยท์‹œํŠธยทํ”ผ์ปค์šฉ โ€” ์ค‘์•™ ๋ชจ๋‹ฌ์€ ์Šคํฌ๋ฆผ(.backdrop-glass-scrim ๋”ค+๋ธ”๋Ÿฌ)์ด ๋’ค์— ๊ฒน์ณ - ์‹คํšจ ๋ฐ€๋„๋Š” ์Šคํฌ๋ฆผร—ํ‹ดํŠธ. ์Šคํฌ๋ฆผ ์•ŒํŒŒ์™€ ํ•จ๊ป˜ ์กฐ์ ˆํ•  ๊ฒƒ - ์•ŒํŒŒ๋Š” ์‚ฌ์ด๋“œ ํŒจ๋„๊ณผ ๊ฐ™์€ 0.72๋กœ ๋งž์ถฐ ๋’ท๋ฉด ๋น„์นจ์„ ํŒจ๋„ ์ˆ˜์ค€์œผ๋กœ ์–ต์ œ. - ํ‹ดํŠธ๋Š” ๋ฐ๊ธฐ ํ‹ฐ์–ด์ƒ ํŒจ๋„๋ณด๋‹ค ํ•œ ํ†ค ์œ„์— ์„œ๋„๋ก ์žก์Œ */ - --ui-glass-heavy: rgba(36, 36, 38, 0.72); - /* ์บ”๋ฒ„์Šค ์ƒ์ฃผ ํฌ๋กฌ์šฉ โ€” ๋ฐฐ๊ฒฝ๋ณด๋‹ค ํ•œ ํ†ค ์–ด๋‘์šด ์Šค๋ชจํฌ */ - --ui-glass-dim: rgba(22, 22, 24, 0.6); - /* ์‚ฌ์ด๋“œ ํŒจ๋„์šฉ โ€” ์บ”๋ฒ„์Šค๋ณด๋‹ค ์‚ด์ง ์•„๋ž˜์— ๋‘”๋‹ค. ์ƒ์ฃผ ํ‘œ๋ฉด์ด๋ผ ์—ฌ๊ธฐ๋ฅผ ์˜ฌ๋ฆฌ๋ฉด - ํŒจ๋„ ์•ˆ 3๋‹จ(ํŒจ๋„/์นด๋“œ/์นฉ)์˜ ๋ฒ ์ด์Šค๊ฐ€ ํ†ต์งธ๋กœ ๋ฐ์•„์ ธ ๋ฒ ๋ฒ„ ๋Œ€๋น„๊ฐ€ ๊นŽ์ด๊ณ  - ์„ธ ๋ฉด์ด ํ•œ ๋ฉ์–ด๋ฆฌ๋กœ ๋ญ‰๊ฐ ๋‹ค. ๋ถ„๋ฆฌ๋Š” ๋ฐ๊ธฐ๊ฐ€ ์•„๋‹ˆ๋ผ ์ธ์…‹ ๋ง+์ขŒ์ธก ๋“œ๋กญ์„€๋„๊ฐ€ ๋‹ด๋‹น. - ๋ฐ๊ธฐ ํ‹ฐ์–ด: ์บ”๋ฒ„์Šค ์นฉ < ํŒจ๋„ < ์บ”๋ฒ„์Šค < ๋“œ๋กญ๋‹ค์šด < ๋ชจ๋‹ฌ */ - --ui-glass-panel: rgba(26, 26, 28, 0.72); - /* ์ƒ์ฃผ ํฌ๋กฌ ์†”๋ฆฌ๋“œ โ€” ์‚ฌ์ด๋“œ ํŒจ๋„ยท๋ฏธ๋‹ˆ๋งต์ฒ˜๋Ÿผ ์ƒ์‹œ ๋–  ์žˆ๋Š” ํ‘œ๋ฉด์€ ๋ผ์ด๋ธŒ ๋ธ”๋Ÿฌ ๋Œ€์‹  - ๊ณ ์•ŒํŒŒ ํ‹ดํŠธ๋กœ ๊ทผ์‚ฌ (Windows์—์„œ backdrop-filter๊ฐ€ ๋งค ํ”„๋ ˆ์ž„ ๋ฉด์  ๋น„๋ก€ ์žฌํ•„ํ„ฐ) - ์•ŒํŒŒ๊ฐ€ ์œ ์ผํ•œ ๋‹ค์ด์–ผ: ๋‚ฎ์ถœ์ˆ˜๋ก ๋ฐฐ๊ฒฝ ํˆฌ๊ณผ โ†‘, ๊ธ€๋ž˜์Šค ์งˆ๊ฐ์€ ์ธ์…‹ ๋ง+ํ•˜์ด๋ผ์ดํŠธ๊ฐ€ ์œ ์ง€ - ๊ฐ’์€ ๋ผ์ด๋ธŒ ๊ธ€๋ž˜์Šค์™€ ๊ฐ™์€ L*์— ์„œ๋„๋ก ๋งž์ถฐ ๋‘  โ€” ์ „ํ™˜ ์‹œ ๋‹จ์ฐจ๊ฐ€ ์•ˆ ์ƒ๊ธฐ๊ฒŒ */ - --ui-glass-panel-solid: rgba(28, 28, 30, 0.96); - --ui-glass-dim-solid: rgba(24, 24, 26, 0.94); - /* ๊ธ€๋ž˜์Šค ๋ฐฑ๋“œ๋กญ ํด๋žจํ”„ โ€” ํ‹ดํŠธ ์ „์— ๋ฐฐ๊ฒฝ ๋ฐ๊ธฐ๋ฅผ ๋ˆ„๋ฅด๊ณ , ์ฃฝ์€ ์ฑ„๋„๋Š” ๋ณด์ƒ - dim โ†“ = ๋ฐ์€ ๋ฐฐ๊ฒฝ ํˆฌ๊ณผ ์–ต์ œ. ๊ณผํ•˜๊ฒŒ ๋ˆ„๋ฅด๋ฉด ํˆฌ๊ณผ ๋ณ€ํ™” ์ž์ฒด๊ฐ€ ์ฃฝ์–ด ๊ธ€๋ž˜์Šค๊ฐ€ ์•ˆ ์ฝํž˜ / sat = ์ƒ‰๊ธฐ์šด ์œ ์ง€๋Ÿ‰ */ - --ui-glass-backdrop-dim: 0.8; - --ui-glass-backdrop-sat: 1; - /* canvas ์œ„ ๊ธ€๋ž˜์Šค ์ „์šฉ - blur์™€ ๋ฐฐ๊ฒฝ ๋ณ€ํ™”๋Š” ๋‚จ๊ธฐ๊ณ  ์ƒ‰์กฐ ์ง€๋ฐฐ๋งŒ ์–ต์ œ */ - --ui-glass-canvas-backdrop-dim: 0.62; - --ui-glass-canvas-backdrop-sat: 0.35; - /* ๋ฃจ๋ฏธ๋…ธ์‹œํ‹ฐ ์บก - ํˆฌ๊ณผ ๊ฒฐ๊ณผ์˜ ํœ˜๋„๋ฅผ ์–ด๋‘์šด ์ชฝ์œผ๋กœ ๊ฐ•์ œ (์ƒ‰์กฐ๋Š” ์œ ์ง€) - 0 = ๋”, 1 = ์™„์ „ ์บก. ํ•„ํ„ฐ์™€ ๋…๋ฆฝ ๋™์ž‘ํ•˜๋Š” 2์ฐจ ์•ˆ์ „๋ง */ - --ui-glass-backdrop-lum: 0.1; - /* ์บก์˜ ๊ธฐ์ค€ ํœ˜๋„ โ€” --ui-glass ํ‹ดํŠธ์™€ ๊ฐ™์€ ์ƒ‰์„ ์œ ์ง€ํ•  ๊ฒƒ. - ์—ฌ๊ธฐ๋งŒ ์–ด๋‘์šด ์ฑ„๋กœ ๋‘๋ฉด ์œ ๋ฆฌ ํ‹ดํŠธ๋ฅผ ์˜ฌ๋ ค๋„ ์บก์ด ํœ˜๋„๋ฅผ ๋„๋กœ ๋Œ์–ด๋‚ด๋ฆฐ๋‹ค */ - --ui-glass-backdrop-lum-tint: #1c1c1e; - - /* ๊ฒฝ๊ณ„์„  โ€” ์žฅ์‹์šฉ ๊ธˆ์ง€, ๊ธฐ๋Šฅ์  ๊ตฌํš(๋ฐ”/ํ”Œ๋กœํŒ… ํฌ๋กฌ/ํผ ์ปจํŠธ๋กค)์—๋งŒ ์‚ฌ์šฉ. - --ui-line 0.1์€ ์• ํ”Œ separatorColor(ํ™”์ดํŠธ ฮฑ0.098) ์‹ค์ธก๊ฐ’๊ณผ ์ผ์น˜ํ•œ๋‹ค. - ํ™”๋ฉด ํ”ฝ์…€์—์„œ ์—ญ์‚ฐํ•˜๋ฉด 0.047์ฏค ๋‚˜์˜ค๋Š”๋ฐ ๊ทธ๊ฑด ์•ˆํ‹ฐ์—์ผ๋ฆฌ์–ด์‹ฑ์ด ์„ž์ธ ๊ฐ’์ด๋ผ - ์„ ์–ธ๊ฐ’์œผ๋กœ ์“ฐ๋ฉด ์„ ์ด ์ ˆ๋ฐ˜ ์ดํ•˜๋กœ ํ๋ ค์ง„๋‹ค. ๋‚ด๋ฆฌ์ง€ ๋ง ๊ฒƒ */ - --ui-line: rgba(255, 255, 255, 0.1); - --ui-line-strong: rgba(255, 255, 255, 0.14); - /* ์บ”๋ฒ„์Šค/์Šคํ…Œ์ด์ง€ ๋ณด์กฐ ๊ฒฉ์ž์„  โ€” ๋ฉ”์ด์ €(--ui-line)๋ณด๋‹ค ํ•œ ํ†ค ํ๋ฆผ */ - --ui-line-faint: rgba(255, 255, 255, 0.06); - - /* ํˆฌ๋ช… ์ƒ‰์ƒ ์ฒด์ปค */ - --ui-checker-pattern: conic-gradient( - #666 0.25turn, - #999 0.25turn 0.5turn, - #666 0.5turn 0.75turn, - #999 0.75turn - ); - --ui-checker-size: 8px; - /* ์†Œํ˜• ์Šค์™€์น˜(11~23px)์šฉ โ€” 8px ํƒ€์ผ์€ 2ร—2์นธ์ด๋ผ ๊นจ์ง„ ํ…์Šค์ฒ˜๋กœ ์ฝํž˜ */ - --ui-checker-size-sm: 4px; - - /* ํฌ๋กฌ ํ•„ โ€” ๋ฐ˜ํˆฌ๋ช… ํ™”์ดํŠธ ์˜ค๋ฒ„๋ ˆ์ด ์ƒํƒœ ๋ž˜๋” (idle โ†’ hover โ†’ active) - ์•ŒํŒŒ ๊ธฐ์ค€์€ ๋ฒ ๋ฒ„ ๋Œ€๋น„. --ui-bg-inset ์ฃผ์„ ์ฐธ์กฐ */ - --ui-fill: rgba(255, 255, 255, 0.072); - --ui-fill-hover: rgba(255, 255, 255, 0.108); - --ui-fill-active: rgba(255, 255, 255, 0.145); - /* ์ •์  ๊ทธ๋ฃน ์ปจํ…Œ์ด๋„ˆ์šฉ โ€” ์ธํ„ฐ๋ž™์…˜ ์—†๋Š” ์€์€ํ•œ ๋ฉด */ - --ui-fill-faint: rgba(255, 255, 255, 0.035); - - /* ํ…์ŠคํŠธ ์œ„๊ณ„ โ€” ์• ํ”Œ ์‹ค์ธก(1์ฐจ L*88.6 / 2์ฐจ 65.5 / 3์ฐจ 59.2)์— ๋งž์ถ˜ ๊ฐ’. - ์ฒด๊ฐ ๋ˆˆ๋ถ€์‹ฌ์—๋Š” ํ‘œ๋ฉด๋ณด๋‹ค ํ…์ŠคํŠธ๊ฐ€ ๋” ํฌ๊ฒŒ ๊ธฐ์—ฌํ•œ๋‹ค. ์ˆœ๋ฐฑ์— ๊ฐ€๊นŒ์šด ๋ณธ๋ฌธ์ƒ‰์„ ๋‘๊ณ  - ํ‘œ๋ฉด๋งŒ ์กฐ์ ˆํ•˜๋ฉด '๋ฐ๋‹ค'๋Š” ์ธ์ƒ์ด ์•ˆ ์žกํžŒ๋‹ค. - ์• ํ”Œ์€ ๊ฐ€์žฅ ๋ฐ์€ ํ‘œ๋ฉด(L*21.7)๊ณผ ๊ฐ€์žฅ ์–ด๋‘์šด ํ…์ŠคํŠธ(L*59.2) ์‚ฌ์ด 37 L*๋ฅผ ํ†ต์งธ๋กœ ๋น„์›Œ - ํ…์ŠคํŠธ์™€ ๊ฒฝ์Ÿํ•˜๋Š” ๋ฉด์„ ๋‘์ง€ ์•Š๋Š”๋‹ค. - ๋ชฉ๋ก ์›ฐ์˜ ๊บผ์ง ํ–‰ ์ด๋ฆ„(disabled)์ด 4.5:1์— ๋ชป ๋ฏธ์น˜๋Š” ๊ฒƒ์€ ์˜๋„๋œ ๊ฐ’์ด๋‹ค โ€” - ๊ธฐ์ค€์„ ๋งž์ถ”๋Š” ๊ฐ€์žฅ ์–ด๋‘์šด ํšŒ์ƒ‰์€ muted์™€ ๊ตฌ๋ถ„์ด ์•ˆ ๋ผ '๊บผ์ง'์œผ๋กœ ์ฝํžˆ์ง€ ์•Š๋Š”๋‹ค. - ๊ทผ๊ฑฐ์™€ ๋Œ€์•ˆ ๊ฒ€ํ† ๋Š” SettingsPanel/panelChrome.ts ์ฐธ์กฐ */ - --ui-fg: #dcdcde; - --ui-fg-muted: #9d9d9f; - --ui-fg-faint: #8c8c8e; - --ui-fg-disabled: #68686a; - - /* ์•ก์„ผํŠธ โ€” ๋ธŒ๋žœ๋“œ ํผํ”Œ. ์ธ๋””์ผ€์ดํ„ฐยทํฌ์ปค์Šค ๋“ฑ ์ž‘์€ ์ง€์ ์šฉ */ - --ui-accent: #8b5cf6; - --ui-accent-hover: #9d74f8; - --ui-accent-active: #7a4be0; - --ui-accent-muted: rgba(139, 92, 246, 0.16); - --ui-accent-fg: #ffffff; - /* ํฐ ๋ฉด์  CTA์šฉ โ€” ๋ฉด์ ์ด ํด์ˆ˜๋ก ํœ˜๋„ยท์ฑ„๋„๋ฅผ ๋‚ด๋ฆผ (๋‹คํฌ์—์„œ ๋ˆˆ๋ถ€์‹ฌ ๋ฐฉ์ง€) */ - --ui-accent-deep: #6b4ad0; - --ui-accent-deep-hover: #7857d8; - --ui-accent-deep-active: #5f40bd; - - /* ์บ”๋ฒ„์Šค ์ •๋ ฌ ๊ฐ€์ด๋“œ โ€” ์„ ํƒ ํฌ๋กฌ(๋ฐ”์ด์˜ฌ๋ ›)๊ณผ ์ƒ‰์œผ๋กœ ๊ฐˆ๋ผ์•ผ ํ•˜๋Š” ๋ณ„๊ฐœ ์‹ ํ˜ธ๋ผ - ์‹œ๋งจํ‹ฑ danger๋ฅผ ์žฌ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ์˜ค๋ฅ˜๊ฐ€ ์•„๋‹ˆ๋ผ ์œ„์น˜ ์Šค๋ƒ… ํ‘œ์‹œ */ - --ui-guide-align: #ff6b6b; - --ui-guide-align-glow: rgba(255, 107, 107, 0.5); - - /* ์บ”๋ฒ„์Šค ์„ ํƒ ํฌ๋กฌ โ€” ์…€๋ ‰์…˜์€ ์˜๋ฏธ ์ง€์ ์ด๋ฏ€๋กœ ๋ธŒ๋žœ๋“œ ๋ฐ”์ด์˜ฌ๋ › */ - --ui-selection: #8b5cf6; - --ui-selection-border: rgba(139, 92, 246, 0.8); - --ui-selection-border-strong: rgba(139, 92, 246, 0.9); - --ui-selection-fill: rgba(139, 92, 246, 0.15); - --ui-selection-glow: rgba(139, 92, 246, 0.5); - - /* ์•ฑ ํฌ๋กฌ ์ „์—ญ ์Šคํƒ */ - --z-chrome-panel: 30; - --z-chrome-popup: 40; - --z-chrome-modal: 50; - --z-chrome-submenu: 60; - --z-chrome-tooltip: 70; - - /* ๊ฒฉ๋ฆฌ๋œ ๊ทธ๋ฆฌ๋“œ ์•ˆ์—์„œ๋งŒ ๋น„๊ต๋˜๋Š” ์บ”๋ฒ„์Šค ๋‚ด๋ถ€ ์Šคํƒ. - ์นด์šดํ„ฐ ํ”„๋ฆฌ๋ทฐ์™€ ๋“œ๋ž˜๊ทธ ํ”„๋ฆฌ๋ทฐ๋Š” ์คŒยทํŒฌ ๋ž˜ํผ ์•ˆ์— ์žˆ๊ณ  ๊ทธ ๋ž˜ํผ๊ฐ€ transform์œผ๋กœ - ์ž๊ธฐ ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ ๋‹ค - ์•„๋ž˜ ์„ ํƒ์„ ยทํ•ธ๋“คยท๋งˆํ€ด์™€๋Š” ์• ์ดˆ์— ๊ฒจ๋ฃจ์ง€ ์•Š๋Š”๋‹ค */ - --z-canvas-counter-preview: 12; - --z-canvas-selection-outline: 20; - --z-canvas-selection-handle: 21; - --z-canvas-group-outline: 22; - --z-canvas-group-handle: 25; - --z-canvas-gradient-editor: 26; - --z-canvas-drag-preview: 1000; - --z-canvas-marquee: 9998; - --z-canvas-smart-guides: 9999; - - /* ์‹œ๋งจํ‹ฑ */ - --ui-danger: #e5484d; - --ui-danger-hover: #ec5d62; - --ui-danger-active: #d93b40; - --ui-danger-muted: rgba(229, 72, 77, 0.14); - --ui-danger-muted-hover: rgba(229, 72, 77, 0.2); - --ui-danger-muted-active: rgba(229, 72, 77, 0.26); - --ui-danger-fg: #ff9599; - --ui-success: #3dbe7b; - --ui-success-muted: rgba(61, 190, 123, 0.14); - --ui-warning: #f0b429; - --ui-warning-muted: rgba(240, 180, 41, 0.14); - - /* ๋ผ์šด๋”ฉ โ€” 4๋‹จ: ํ‘œ๋ฉด ์•ˆ ์š”์†Œ 6 < 1์ฐจ ํ‘œ๋ฉด 10 < ํŒ์—… ์…ธ 14 < ๋ชจ๋‹ฌ ์…ธ 16 */ - /* ๋™์‹ฌ ๊ทœ์น™์€ ์ฝ”๋„ˆ ๋ฐ€์ฐฉ ์ค‘์ฒฉ์—๋งŒ ์ ์šฉ (ํŒ์—… 14 = 6 + p-8), ์ปจํŠธ๋กค์€ ๊ณ ์ • ํ‹ฐ์–ด */ - /* ๋ชจ๋‹ฌ ์…ธ์€ ์ฐฝ๋ณด๋‹ค ๋‘ฅ๊ธ€์ง€ ์•Š๊ฒŒ ์œ ์ง€ */ - --ui-radius-inner: 6px; - --ui-radius-surface: 10px; - --ui-radius-popup: 14px; - --ui-radius-modal: 16px; - - /* ๋ชจ์…˜ */ - --ui-duration-fast: 120ms; - --ui-duration-base: 180ms; - --ui-duration-slow: 240ms; - --ui-ease-out: cubic-bezier(0.16, 1, 0.3, 1); - --ui-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); - - /* ํ† ๊ธ€ ๋…ธ๋ธŒ - ๋์ ์„ ์ง€๋‚˜์ณค๋‹ค ๋Œ์•„์˜จ๋‹ค. ์„ธ๊ธฐ๋Š” ์ปค๋ธŒ์˜ y๊ฐ’์ด ์†Œ์œ ํ•œ๋‹ค. - 1.35๋Š” 12px ์ด๋™์—์„œ 0.49px๋ฐ–์— ๋„˜์ง€ ์•Š์•„ ๋ˆˆ์— ์•ˆ ๋ˆ๋‹ค. 1.56์ด๋ฉด 1.17px๋กœ - ์ธ์…‹ 2px ์•ˆ์—์„œ ๋๋‚˜๋ฉด์„œ ํ™•์‹คํžˆ ์ฝํžŒ๋‹ค. - ํŠธ๋ž™ ์ƒ‰์€ ๋…ธ๋ธŒ๋ณด๋‹ค ์งง์€ clock์œผ๋กœ ๋”ฐ๋กœ ๊ฐ„๋‹ค */ - --ui-toggle-duration: var(--ui-duration-slow); - --ui-toggle-travel: 12px; - --ui-toggle-ease: cubic-bezier(0.34, 1.56, 0.64, 1); - /* ๋ˆ„๋ฆ„ ๋ณ€ํ˜• - ์›์ด ์•„๋‹ˆ๋ผ ์บก์А์ด ๋œ๋‹ค. - scale๋กœ๋Š” ๋งŒ๋“ค ์ˆ˜ ์—†๋‹ค. ์›์„ ๋น„๊ท ๋“ฑ ์Šค์ผ€์ผํ•˜๋ฉด ๋ฐ˜์ง€๋ฆ„๋„ ๊ฐ™์ด ๋ˆŒ๋ ค ํƒ€์›์ด ๋  ๋ฟ, - ์œ„์•„๋ž˜ ํ‰ํ‰ํ•œ ๋ณ€์ด ์ƒ๊ธฐ์ง€ ์•Š๋Š”๋‹ค. ์‹ค์ œ ํญยท๋†’์ด๋ฅผ ๋ฐ”๊ฟ”์•ผ ๋ฐ˜์ง€๋ฆ„์ด ๋†’์ด์˜ ์ ˆ๋ฐ˜์œผ๋กœ - ๊ณ ์ •๋œ ์บก์А์ด ๋‚˜์˜จ๋‹ค. ๋…ธ๋ธŒ๋Š” out-of-flow๋ผ ํ˜•์ œ ๋ ˆ์ด์•„์›ƒ์—๋Š” ์˜ํ–ฅ์ด ์—†๋‹ค */ - --ui-toggle-thumb-size: 12px; - /* ๊ฐ€๋กœ๋Š” ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค - ํญ๊นŒ์ง€ ์ž๋ผ๋ฉด ๋…ธ๋ธŒ๊ฐ€ ๋ถ€ํ‘ธ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค. - ํญ์ด ๋†’์ด๋ณด๋‹ค ํฌ๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฐ˜์ง€๋ฆ„์ด ๋†’์ด ์ ˆ๋ฐ˜์œผ๋กœ ์žกํ˜€ ์œ„์•„๋ž˜ ํ‰ํ‰ํ•œ ๋ณ€์ด ์ƒ๊ธด๋‹ค */ - --ui-toggle-press-height: 10px; - /* ๋ˆ„๋ฅด๋Š” ๋™์•ˆ ์ฒœ์ฒœํžˆ ๋ˆŒ๋ฆฌ๊ณ , ์†์„ ๋–ผ๋ฉด ๋น ๋ฅด๊ฒŒ ๋Œ์•„์˜จ๋‹ค */ - --ui-toggle-press-duration: 420ms; - --ui-toggle-press-ease: cubic-bezier(0.33, 1, 0.68, 1); - --ui-toggle-release-duration: var(--ui-duration-base); - - /* ์„ธ๊ทธ๋จผํŠธ ์ธ๋””์ผ€์ดํ„ฐ - ์ „์—ญ base(180ms)+ease-out-expo๋ฅผ ์“ฐ๋ฉด ์ด๋™์˜ 70%๊ฐ€ - ์ฒซ ํ”„๋ ˆ์ž„ ์•ˆ์— ๋๋‚˜ "์›€์ง์˜€๋‹ค"๊ฐ€ ์•„๋‹ˆ๋ผ "์ด๋ฏธ ๊ฐ€ ์žˆ๋‹ค"๋กœ ์ฝํžŒ๋‹ค. - ์‹ค์ธก: ํด๋ฆญ ํ›„ ์ฒซ ํ”„๋ ˆ์ž„์—์„œ ์ง„ํ–‰๋ฅ  63~75%. - ์ปค๋ธŒ๋ฅผ ์™„๋งŒํ•˜๊ฒŒ ํŽด์„œ ์ด๋™ ๊ตฌ๊ฐ„์„ ํ”„๋ ˆ์ž„์— ๋‚˜๋ˆ  ์ค€๋‹ค - 90% ์ง€์ ์ด - duration์˜ 33%(expo)์—์„œ 54%๋กœ ๋ฐ€๋ฆฐ๋‹ค. ์—…๊ณ„๋„ ์ด ๋Œ€์—ญ์ด๋‹ค - (Material Web 250ms cubic-bezier(.3,0,0,1), Fluent 300ms, MUI 300ms). - - ์˜ค๋ฒ„์ŠˆํŠธ๋Š” keyframe์ด ์•„๋‹ˆ๋ผ ์ปค๋ธŒ์˜ y๊ฐ’์ด ์†Œ์œ ํ•œ๋‹ค - ํ† ๊ธ€ ๋…ธ๋ธŒ(--ui-toggle-ease)์™€ - ๊ฐ™์€ ์›์น™. 1.2๋Š” ํ•œ ์นธ ์ด๋™(69px)์—์„œ 0.9px, ๋‘ ์นธ(139px)์—์„œ 1.7px ๋„˜์–ด๊ฐ„๋‹ค. - ํŠธ๋ž™ ์•ˆ์ชฝ ์—ฌ๋ฐฑ์ด 2px์ด๋ผ ๋„˜์–ด๊ฐ„ ๋งŒํผ์ด ํŠธ๋ž™ ๋ฐ–์œผ๋กœ ์ƒˆ์ง€ ์•Š๋Š”๋‹ค. - ์กฐ์‚ฌํ•œ ๋””์ž์ธ ์‹œ์Šคํ…œ 15๊ณณ ์ค‘ ์„ธ๊ทธ๋จผํŠธ์— ์˜ค๋ฒ„์ŠˆํŠธ๋ฅผ ์“ฐ๋Š” ๊ณณ์€ ์—†๋‹ค - ์˜๋„์  ์ผํƒˆ์ด๊ณ , - ์ด ์ €์žฅ์†Œ๊ฐ€ ํ† ๊ธ€์—์„œ ์ด๋ฏธ ๊ฐ™์€ ์„ ํƒ์„ ํ•œ ๊ฒƒ๊ณผ ๋ฌธ๋ฒ•์„ ๋งž์ถ˜ ๊ฒƒ์ด๋‹ค. - ์ด์ „์—๋Š” translate ์†Œ์ˆ˜ ํ”ฝ์…€์ด ์ •์ฐฉ ์‹œ ๋ฐ˜์˜ฌ๋ฆผ๋˜๋ฉฐ 1px ํŠ€๋Š” ์•„ํ‹ฐํŒฉํŠธ๊ฐ€ ์ด ์—ญํ• ์„ - ๋Œ€์‹ ํ–ˆ๋Š”๋ฐ, translate 0%์ธ ์ฒซ ํƒญ์—์„œ๋งŒ ์•ˆ ๋‚˜ํƒ€๋‚˜๊ณ  ํŠธ๋ž™ ํญ์— ๋”ฐ๋ผ ์‚ฌ๋ผ์ ธ์„œ - ๋ฐฉํ–ฅ ๋น„๋Œ€์นญ์œผ๋กœ ์ฝํ˜”๋‹ค */ - --ui-segment-duration: 220ms; - --ui-segment-ease: cubic-bezier(0.34, 1.2, 0.64, 1); - - /* ํŒ์—… ๋“ฑํ‡ด์žฅ - ๋“œ๋กญ๋‹ค์šด, ํŒ์˜ค๋ฒ„, ํ”ผ์ปค ๊ณตํ†ต ๋‹ค์ด์–ผ. - ๋‹ซํž˜์ด ์—ด๋ฆผ๋ณด๋‹ค ์งง์€ ๊ฑด ์˜๋„, ์‚ฌ๋ผ์ง€๋Š” ๊ฑธ ๊ธฐ๋‹ค๋ฆด ์ด์œ ๊ฐ€ ์—†์Œ */ - --ui-popup-enter-duration: var(--ui-duration-base); - --ui-popup-exit-duration: var(--ui-duration-fast); - --ui-popup-ease: cubic-bezier(0.22, 1, 0.36, 1); - /* ์ปค๋ธŒ๊ฐ€ ์•ž์ชฝ์— ๋ชฐ๋ ค ์žˆ์–ด ์ด๋™์ด 30ms ์•ˆ์— ์†Œ๋ชจ๋œ๋‹ค. - duration์„ ๋Š˜๋ฆฌ๋Š” ๋Œ€์‹  ํญ์„ ํ‚ค์›Œ ์ฒด๊ฐ์„ ํ™•๋ณด */ - --ui-popup-enter-scale: 0.94; - --ui-popup-exit-scale: 0.99; - - /* ์นด๋“œ ๋ชจ๋‹ฌ ๋“ฑํ‡ด์žฅ - ํฐ ํ‘œ๋ฉด์ด๋ผ ํŒ์—…๋ณด๋‹ค ๋А๊ธ‹ํ•˜๊ฒŒ */ - --ui-modal-enter-duration: 250ms; - --ui-modal-exit-duration: 150ms; - --ui-modal-enter-scale: 0.96; - --ui-modal-exit-scale: 0.96; - - /* ํŒจ๋„ ํŽ˜์ด์ง€ ์ „ํ™˜ โ€” ์‚ฌ์ด๋“œ ํŒจ๋„ ์ธ-ํŒจ๋„ ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋‹ค์ด์–ผ */ - --ui-duration-page: 250ms; - --ui-ease-page: cubic-bezier(0.22, 1, 0.36, 1); - --ui-page-slide-distance: 8px; - --ui-page-blur: 3px; - - /* ์•„์ด์ฝ˜ ์Šค์™‘ โ€” ํ† ๊ธ€ ๋ฒ„ํŠผ์˜ SVG ๊ต์ฒด ํฌ๋กœ์ŠคํŽ˜์ด๋“œ ๋‹ค์ด์–ผ */ - --ui-icon-swap-duration: var(--ui-duration-base); - --ui-icon-swap-ease: ease-in-out; - --ui-icon-swap-blur: 2px; - --ui-icon-swap-scale: 0.25; - - /* ๊ธ€์ž ์Šค์™‘ - ์•„์ด์ฝ˜๊ณผ ๋‹ฌ๋ฆฌ ์Šค์ผ€์ผ ๋Œ€์‹  ์„ธ๋กœ ์ด๋™. ๊ธ€์ž๊ฐ€ ์ปค์กŒ๋‹ค ์ž‘์•„์ง€๋ฉด - ์ž๊ฐ„์ด ํ”๋“ค๋ ค ์ฝ๋Š” ๋ˆˆ์ด ๋”ฐ๋ผ๊ฐ€์ง€ ๋ชปํ•œ๋‹ค */ - --ui-text-swap-duration: var(--ui-duration-fast); - --ui-text-swap-ease: ease-in-out; - --ui-text-swap-blur: 2px; - --ui-text-swap-shift: 4px; - - /* ํˆด๋ฐ” ์•„์ด์ฝ˜ ํ˜ธ๋ฒ„ ๋ชจ์…˜ - ํ˜ธ๋ฒ„ ์ง„์ž…์— ํ•œ ๋ฒˆ ์žฌ์ƒ๋˜๊ณ  ๋ฉˆ์ถ˜๋‹ค. ๋ฐ˜๋ณต ์—†์Œ. - 400ms ์•„๋ž˜๋กœ ๋‚ด๋ฆฌ๋ฉด ๋ฌด์Šจ ๋™์ž‘์ธ์ง€ ์ฝ๊ธฐ ์ „์— ๋๋‚˜๋ฏ€๋กœ ๋‹ค๋ฅธ ๋ชจ์…˜๋ณด๋‹ค ๋А๊ธ‹ํ•˜๋‹ค. - ๋™์ž‘๋งˆ๋‹ค ์ž์—ฐ์Šค๋Ÿฌ์šด clock์ด ๋‹ฌ๋ผ ์ง€์† ์‹œ๊ฐ„์€ ์šฉ๋„๋ณ„๋กœ ๋”ฐ๋กœ ๋‘”๋‹ค */ - --ui-icon-ease: cubic-bezier(0.65, 0, 0.35, 1); - /* ์˜ค๋ฒ„์ŠˆํŠธ๋Š” keyframe์ด ์•„๋‹ˆ๋ผ ์ปค๋ธŒ์˜ y๊ฐ’(1.4)์ด ์†Œ์œ ํ•œ๋‹ค. - ํ† ๊ธ€ ๋…ธ๋ธŒ(--ui-toggle-ease)์™€ ๊ฐ™์€ ์›์น™ */ - --ui-icon-ease-back: cubic-bezier(0.34, 1.4, 0.64, 1); - - --ui-icon-expand-duration: 420ms; - /* ๋ˆ„๋ฆ„ - ๋‚ด๋ ค๊ฐˆ ๋• ์†๊ฐ€๋ฝ์„ ๋”ฐ๋ผ ์ฆ‰์‹œ, ์˜ฌ๋ผ์˜ฌ ๋• ์Šคํ”„๋ง์œผ๋กœ. - ๋ณต๊ท€๊ฐ€ ํ˜ธ๋ฒ„ ๋ชจ์…˜๋งŒํผ ๊ธธ๋ฉด ํด๋ฆญ ๊ฒฐ๊ณผ๋ณด๋‹ค ๋Šฆ๊ฒŒ ๋๋‚˜ ๊ตผ๋œจ๊ฒŒ ์ฝํžŒ๋‹ค */ - --ui-icon-press-down-duration: 80ms; - --ui-icon-press-duration: 320ms; - --ui-icon-press-scale: 0.8; - --ui-icon-eye-duration: 440ms; - --ui-icon-nod-duration: 500ms; - --ui-icon-tilt-duration: 520ms; - --ui-icon-wobble-duration: 560ms; - --ui-icon-turn-duration: 600ms; - --ui-icon-sweep-duration: 620ms; - --ui-icon-fan-duration: 700ms; - --ui-icon-punch-duration: 700ms; - --ui-icon-shift-duration: 800ms; - --ui-icon-stagger: 80ms; - - /* ํŒ”๋ ˆํŠธ ๋ฌผ๊ฐ ๊ตฌ๋ฉ์ด ๋‹ค์‹œ ํ„ฐ์งˆ ๋•Œ์˜ ํฌ๊ธฐ. ์—ฌ๊ธฐ์„œ๋งŒ ๊ฐ’์ด ์˜ค๋ฒ„์ŠˆํŠธ๋ฅผ ์†Œ์œ ํ•œ๋‹ค - - ๋ฐ˜์ง€๋ฆ„ 0.875px์—์„œ๋Š” ์ปค๋ธŒ๊ฐ€ ๋งŒ๋“œ๋Š” ์˜ค๋ฒ„์ŠˆํŠธ๊ฐ€ ์†Œ์ˆ˜์  ํ”ฝ์…€์ด๋ผ ํ™”๋ฉด์— ์•ˆ ๋‚˜์˜จ๋‹ค */ - --ui-icon-punch-pop: 1.3; - - /* ์ˆซ์ž ์Šคํ… ํŒ์ธ - ๊ฐ’์ด ๋ฐ”๋€ ์ž๋ฆฟ์ˆ˜๋งŒ ํ•œ ๋ฒˆ ํŠ€์–ด์˜ค๋ฅธ๋‹ค. - ๋ˆ„๋ฅผ ๋•Œ์™€ ๋—„ ๋•Œ ํ•œ ๋ฒˆ์”ฉ๋งŒ ๋ˆ๋‹ค. ๊พน ๋ˆ„๋ฅด๋Š” ๊ตฌ๊ฐ„์€ ๊ฐ’๋งŒ ๋ฐ”๋€Œ๊ณ  ์žฌ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค - - ๋ฐ˜๋ณต ๊ฐ„๊ฒฉ์ด ์žฌ์ƒ๋ณด๋‹ค ์งง์•„ ๋งค ์Šคํ…์ด ์ด์ „ ์žฌ์ƒ์„ ๋Š์œผ๋ฉด ์ˆซ์ž๊ฐ€ ๊นœ๋นก์ด๊ณ  - ๊ฐ’์„ ๋ˆˆ์œผ๋กœ ์ข‡์„ ์ˆ˜ ์—†๋‹ค. - ์•„์ด์ฝ˜ ํ˜ธ๋ฒ„(400ms+)๋ณด๋‹ค ์งง์€ ๊ฑด ์˜๋„. ์†์ด ๊ฐ’์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ปจํŠธ๋กค์ด๋‹ค. - ์˜ค๋ฒ„์ŠˆํŠธ๋Š” keyframe์ด ์•„๋‹ˆ๋ผ ์ปค๋ธŒ์˜ y๊ฐ’์ด ์†Œ์œ ํ•œ๋‹ค (--ui-icon-ease-back๊ณผ ๊ฐ™์€ ์›์น™) */ - --ui-digit-duration: 220ms; - --ui-digit-stagger: 40ms; - --ui-digit-distance: 6px; - --ui-digit-ease: cubic-bezier(0.34, 1.4, 0.64, 1); - - /* ์˜ค๋ฅ˜ ํ”๋“ค๊ธฐ - ์ขŒ์šฐ๋กœ ํ•œ ๋ฒˆ ํ„ธ๊ณ  ์ž‘์€ ์˜ค๋ฒ„์ŠˆํŠธ๋กœ ๋ฉˆ์ถ˜๋‹ค. - ๊ตฌ๊ฐ„๋งˆ๋‹ค ์ปค๋ธŒ๋ฅผ ๋”ฐ๋กœ ๊ฑธ์–ด์•ผ ๊ฐ ๋‹ค๋ฆฌ๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐ์†ํ•œ๋‹ค. - ๊ตฌ๊ฐ„ ๋น„์œจ(80/80/60/60)์€ keyframe %๊ฐ€ ์†Œ์œ ํ•œ๋‹ค - ์„ ํƒ์ž %์—๋Š” calc๋ฅผ ๋ชป ์จ์„œ - ๊ตฌ๊ฐ„ ๊ธธ์ด๋ฅผ ํ† ํฐ์œผ๋กœ ์ชผ๊ฐœ๋„ ์ด ๊ธธ์ด๋งŒ ๋ฐ”๋€Œ๊ณ  ๋ถ„ํ• ์€ ๊ทธ๋Œ€๋กœ๋‹ค. ๋‹ค์ด์–ผ์€ ์ด ๊ธธ์ด ํ•˜๋‚˜. - ํญ์€ ํ•„๋“œ ๊ธฐ์ค€์œผ๋กœ ์žก๋Š”๋‹ค - 60px ์นธ์—์„œ 6px๋Š” 10%๋ผ ๊ฐ’์ด ํŠ€์–ด๋‚˜๊ฐ„ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค. - ์—ฌ๊ธฐ์„œ๋Š” "ํ‹€๋ ธ๋‹ค"๋Š” ์‹ ํ˜ธ๋งŒ ํ•„์š”ํ•˜๋ฏ€๋กœ ๋ˆˆ์— ๊ฑธ๋ฆด ๋งŒํผ๋งŒ ์›€์ง์ธ๋‹ค */ - --ui-shake-distance: 3px; - --ui-shake-overshoot: 1.5px; - --ui-shake-duration: 280ms; - --ui-shake-ease: cubic-bezier(0.22, 1, 0.36, 1); - - /* ๊ทธ๋ฆผ์ž โ€” ๋ณด๋” ์—†๋Š” ํ‘œ๋ฉด ๋ถ„๋ฆฌ์˜ ํ•ต์‹ฌ. 2/3์€ ์ธ์…‹ ๋ง+์ƒ๋‹จ ํ•˜์ด๋ผ์ดํŠธ๋กœ ์žฌ์งˆ ๋ฆฝ ํฌํ•จ - ์ธ์…‹ ์•ŒํŒŒ๋Š” ํ‘œ๋ฉด์ด ๋ฐ์•„์ง„ ๋งŒํผ ์˜ฌ๋ ค ๋ฆฝ์˜ ฮ”L*๋ฅผ ์œ ์ง€ */ - --ui-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.35); - --ui-shadow-2: inset 0 0 0 1px rgba(255, 255, 255, 0.08), - inset 0 1px 0 0 rgba(255, 255, 255, 0.11), 0 8px 24px rgba(0, 0, 0, 0.45), - 0 2px 6px rgba(0, 0, 0, 0.3); - /* 3์€ ๋ชจ๋‹ฌ ์นด๋“œ - ์Šคํฌ๋ฆผ์ด ์ด๋ฏธ ๋’ค๋ฅผ ์–ด๋‘กํžˆ๋ฏ€๋กœ ๋ฒˆ์ง์€ popup๋ณด๋‹ค ํ•œ ๋‹จ๊ณ„๋งŒ ๋„“๊ฒŒ */ - --ui-shadow-3: inset 0 0 0 1px rgba(255, 255, 255, 0.08), - inset 0 1px 0 0 rgba(255, 255, 255, 0.11), 0 10px 28px rgba(0, 0, 0, 0.4), - 0 3px 8px rgba(0, 0, 0, 0.25); - - /* ์ผ์‹œ popup - ์ธ์ ‘ control์„ ๋ฎ์ง€ ์•Š๊ฒŒ lip์€ ์œ ์ง€ํ•˜๊ณ  ์™ธ๋ถ€ ๋ฒˆ์ง๋งŒ ์ถ•์†Œ */ - --ui-shadow-popup: inset 0 0 0 1px rgba(255, 255, 255, 0.08), - inset 0 1px 0 0 rgba(255, 255, 255, 0.11), 0 3px 10px rgba(0, 0, 0, 0.28), - 0 1px 2px rgba(0, 0, 0, 0.2); - - /* ์‚ฌ์ด๋“œ ํŒจ๋„ โ€” ๋„ํ‚น ๊ฒฝ๊ณ„์ธ ์ขŒ์ธก ์—ฃ์ง€๋งŒ ํ—ค์–ด๋ผ์ธ, ์ขŒ์ธก ๋ฐฉํ–ฅ์„ฑ ๋“œ๋กญ์œผ๋กœ ์บ”๋ฒ„์Šค์™€ ๋ถ„๋ฆฌ */ - --ui-shadow-panel: inset 1px 0 0 0 rgba(255, 255, 255, 0.09), - -12px 0 32px rgba(0, 0, 0, 0.35); - - /* ์ƒ์ฃผ ํฌ๋กฌ โ€” ์ธ์…‹ ๋ง + ์ƒ๋‹จ ํ•˜์ด๋ผ์ดํŠธ๊ฐ€ ๋ณด๋”ยท์žฌ์งˆ ๋‹ด๋‹น, ๋“œ๋กญ์„€๋„๋Š” ํ”์ ๋งŒ */ - --ui-shadow-chrome: inset 0 0 0 1px rgba(255, 255, 255, 0.08), - inset 0 1px 0 0 rgba(255, 255, 255, 0.11), 0 1px 3px rgba(0, 0, 0, 0.2); - - /* ํฌ์ปค์Šค ๋ง โ€” ๋ณด๋” ๋Œ€์‹  ๊ทธ๋ฆผ์ž ๋ง. - ํ‘œ๋ฉด ์œ„ 3:1์ด ํ•˜ํ•œ์ด๋ผ ์•ŒํŒŒ๋ฅผ ๋‚ฎ์ถ”๊ฑฐ๋‚˜ --ui-accent๋ฅผ ๊ทธ๋Œ€๋กœ ์“ฐ๋ฉด ์•ˆ ๋œ๋‹ค. - ์•ก์„ผํŠธ ์›์ƒ‰์€ ๋ฐ์•„์ง„ ํ‘œ๋ฉด์—์„œ 3.2:1๊นŒ์ง€ ๋–จ์–ด์ ธ ๋ง์ด ๋ฉด์— ์ž ๊ธด๋‹ค */ - --ui-focus-ring: 0 0 0 1.5px rgba(160, 121, 248, 0.9); - - /* ํ™•์ •ํ•  ์ˆ˜ ์—†๋Š” ์ž…๋ ฅ - ํฌ์ปค์Šค ๋ง๊ณผ ๊ฐ™์€ ์ž๋ฆฌ, ๊ฐ™์€ ๋‘๊ป˜๋กœ ๊ทธ๋ ค ์ƒ‰๋งŒ ๋ฐ”๋€๋‹ค. - ๋ณด๋”๋ฅผ ์“ฐ๋ฉด ์ƒ์ž ์•ˆ์ชฝ์ด ์ข์•„์ง€๊ณ  ๋ฐ”๊นฅ ํฌ์ปค์Šค ๋ง๊ณผ ๋‚˜๋ž€ํžˆ ์„œ์„œ ์ƒ‰์ด ์„ž์ธ๋‹ค */ - --ui-danger-ring: 0 0 0 1.5px rgba(240, 106, 111, 0.9); -} +@import './tokens/base.css'; +@import './tokens/dark.css'; +@import './tokens/light.css'; diff --git a/src/renderer/styles/tokens/base.css b/src/renderer/styles/tokens/base.css new file mode 100644 index 000000000..9510e9e14 --- /dev/null +++ b/src/renderer/styles/tokens/base.css @@ -0,0 +1,171 @@ +/* + * ํ…Œ๋งˆ ๋ฌด๊ด€ ํ† ํฐ - ๋ผ์šด๋”ฉ, ๋ชจ์…˜, ๋ ˆ์ด์–ด ์Šคํƒ + * ์ƒ‰๊ณผ ์žฌ์งˆ์€ tokens/dark.css, tokens/light.css๊ฐ€ ์†Œ์œ ํ•œ๋‹ค. + * ์—ฌ๊ธฐ์—๋Š” ์ƒ‰์œผ๋กœ ์ฝํžˆ๋Š” ๊ฐ’(hex, rgb, hsl, color-mix)์„ ๋‘์ง€ ์•Š๋Š”๋‹ค. + * themeTokenParityContract ํ…Œ์ŠคํŠธ๊ฐ€ ์ด ๊ทœ์น™์„ ๊ฒ€์‚ฌํ•œ๋‹ค + */ +:root { + /* ํˆฌ๋ช… ์ƒ‰์ƒ ์ฒด์ปค ํƒ€์ผ ํฌ๊ธฐ. ๋ฌด๋Šฌ ์ƒ‰์€ ํ…Œ๋งˆ๊ฐ€ ์†Œ์œ  */ + --ui-checker-size: 8px; + /* ์†Œํ˜• ์Šค์™€์น˜(11~23px)์šฉ โ€” 8px ํƒ€์ผ์€ 2ร—2์นธ์ด๋ผ ๊นจ์ง„ ํ…์Šค์ฒ˜๋กœ ์ฝํž˜ */ + --ui-checker-size-sm: 4px; + + /* ์•ฑ ํฌ๋กฌ ์ „์—ญ ์Šคํƒ */ + --z-chrome-panel: 30; + --z-chrome-popup: 40; + --z-chrome-modal: 50; + --z-chrome-submenu: 60; + --z-chrome-tooltip: 70; + + /* ๊ฒฉ๋ฆฌ๋œ ๊ทธ๋ฆฌ๋“œ ์•ˆ์—์„œ๋งŒ ๋น„๊ต๋˜๋Š” ์บ”๋ฒ„์Šค ๋‚ด๋ถ€ ์Šคํƒ. + ์นด์šดํ„ฐ ํ”„๋ฆฌ๋ทฐ์™€ ๋“œ๋ž˜๊ทธ ํ”„๋ฆฌ๋ทฐ๋Š” ์คŒยทํŒฌ ๋ž˜ํผ ์•ˆ์— ์žˆ๊ณ  ๊ทธ ๋ž˜ํผ๊ฐ€ transform์œผ๋กœ + ์ž๊ธฐ ์ปจํ…์ŠคํŠธ๋ฅผ ๋งŒ๋“ ๋‹ค - ์•„๋ž˜ ์„ ํƒ์„ ยทํ•ธ๋“คยท๋งˆํ€ด์™€๋Š” ์• ์ดˆ์— ๊ฒจ๋ฃจ์ง€ ์•Š๋Š”๋‹ค */ + --z-canvas-counter-preview: 12; + --z-canvas-selection-outline: 20; + --z-canvas-selection-handle: 21; + --z-canvas-group-outline: 22; + --z-canvas-group-handle: 25; + --z-canvas-gradient-editor: 26; + --z-canvas-drag-preview: 1000; + --z-canvas-marquee: 9998; + --z-canvas-smart-guides: 9999; + + /* ๋ผ์šด๋”ฉ โ€” 4๋‹จ: ํ‘œ๋ฉด ์•ˆ ์š”์†Œ 6 < 1์ฐจ ํ‘œ๋ฉด 10 < ํŒ์—… ์…ธ 14 < ๋ชจ๋‹ฌ ์…ธ 16 */ + /* ๋™์‹ฌ ๊ทœ์น™์€ ์ฝ”๋„ˆ ๋ฐ€์ฐฉ ์ค‘์ฒฉ์—๋งŒ ์ ์šฉ (ํŒ์—… 14 = 6 + p-8), ์ปจํŠธ๋กค์€ ๊ณ ์ • ํ‹ฐ์–ด */ + /* ๋ชจ๋‹ฌ ์…ธ์€ ์ฐฝ๋ณด๋‹ค ๋‘ฅ๊ธ€์ง€ ์•Š๊ฒŒ ์œ ์ง€ */ + --ui-radius-inner: 6px; + --ui-radius-surface: 10px; + --ui-radius-popup: 14px; + --ui-radius-modal: 16px; + + /* ๋ชจ์…˜ */ + --ui-duration-fast: 120ms; + --ui-duration-base: 180ms; + --ui-duration-slow: 240ms; + --ui-ease-out: cubic-bezier(0.16, 1, 0.3, 1); + --ui-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); + + /* ํ† ๊ธ€ ๋…ธ๋ธŒ - ๋์ ์„ ์ง€๋‚˜์ณค๋‹ค ๋Œ์•„์˜จ๋‹ค. ์„ธ๊ธฐ๋Š” ์ปค๋ธŒ์˜ y๊ฐ’์ด ์†Œ์œ ํ•œ๋‹ค. + 1.35๋Š” 12px ์ด๋™์—์„œ 0.49px๋ฐ–์— ๋„˜์ง€ ์•Š์•„ ๋ˆˆ์— ์•ˆ ๋ˆ๋‹ค. 1.56์ด๋ฉด 1.17px๋กœ + ์ธ์…‹ 2px ์•ˆ์—์„œ ๋๋‚˜๋ฉด์„œ ํ™•์‹คํžˆ ์ฝํžŒ๋‹ค. + ํŠธ๋ž™ ์ƒ‰์€ ๋…ธ๋ธŒ๋ณด๋‹ค ์งง์€ clock์œผ๋กœ ๋”ฐ๋กœ ๊ฐ„๋‹ค */ + --ui-toggle-duration: var(--ui-duration-slow); + --ui-toggle-travel: 12px; + --ui-toggle-ease: cubic-bezier(0.34, 1.56, 0.64, 1); + /* ๋ˆ„๋ฆ„ ๋ณ€ํ˜• - ์›์ด ์•„๋‹ˆ๋ผ ์บก์А์ด ๋œ๋‹ค. + scale๋กœ๋Š” ๋งŒ๋“ค ์ˆ˜ ์—†๋‹ค. ์›์„ ๋น„๊ท ๋“ฑ ์Šค์ผ€์ผํ•˜๋ฉด ๋ฐ˜์ง€๋ฆ„๋„ ๊ฐ™์ด ๋ˆŒ๋ ค ํƒ€์›์ด ๋  ๋ฟ, + ์œ„์•„๋ž˜ ํ‰ํ‰ํ•œ ๋ณ€์ด ์ƒ๊ธฐ์ง€ ์•Š๋Š”๋‹ค. ์‹ค์ œ ํญยท๋†’์ด๋ฅผ ๋ฐ”๊ฟ”์•ผ ๋ฐ˜์ง€๋ฆ„์ด ๋†’์ด์˜ ์ ˆ๋ฐ˜์œผ๋กœ + ๊ณ ์ •๋œ ์บก์А์ด ๋‚˜์˜จ๋‹ค. ๋…ธ๋ธŒ๋Š” out-of-flow๋ผ ํ˜•์ œ ๋ ˆ์ด์•„์›ƒ์—๋Š” ์˜ํ–ฅ์ด ์—†๋‹ค */ + --ui-toggle-thumb-size: 12px; + /* ๊ฐ€๋กœ๋Š” ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค - ํญ๊นŒ์ง€ ์ž๋ผ๋ฉด ๋…ธ๋ธŒ๊ฐ€ ๋ถ€ํ‘ธ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค. + ํญ์ด ๋†’์ด๋ณด๋‹ค ํฌ๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฐ˜์ง€๋ฆ„์ด ๋†’์ด ์ ˆ๋ฐ˜์œผ๋กœ ์žกํ˜€ ์œ„์•„๋ž˜ ํ‰ํ‰ํ•œ ๋ณ€์ด ์ƒ๊ธด๋‹ค */ + --ui-toggle-press-height: 10px; + /* ๋ˆ„๋ฅด๋Š” ๋™์•ˆ ์ฒœ์ฒœํžˆ ๋ˆŒ๋ฆฌ๊ณ , ์†์„ ๋–ผ๋ฉด ๋น ๋ฅด๊ฒŒ ๋Œ์•„์˜จ๋‹ค */ + --ui-toggle-press-duration: 420ms; + --ui-toggle-press-ease: cubic-bezier(0.33, 1, 0.68, 1); + --ui-toggle-release-duration: var(--ui-duration-base); + + /* ์„ธ๊ทธ๋จผํŠธ ์ธ๋””์ผ€์ดํ„ฐ - ์ „์—ญ base(180ms)+ease-out-expo๋ฅผ ์“ฐ๋ฉด ์ด๋™์˜ 70%๊ฐ€ + ์ฒซ ํ”„๋ ˆ์ž„ ์•ˆ์— ๋๋‚˜ "์›€์ง์˜€๋‹ค"๊ฐ€ ์•„๋‹ˆ๋ผ "์ด๋ฏธ ๊ฐ€ ์žˆ๋‹ค"๋กœ ์ฝํžŒ๋‹ค. + ์‹ค์ธก: ํด๋ฆญ ํ›„ ์ฒซ ํ”„๋ ˆ์ž„์—์„œ ์ง„ํ–‰๋ฅ  63~75%. + ์ปค๋ธŒ๋ฅผ ์™„๋งŒํ•˜๊ฒŒ ํŽด์„œ ์ด๋™ ๊ตฌ๊ฐ„์„ ํ”„๋ ˆ์ž„์— ๋‚˜๋ˆ  ์ค€๋‹ค - 90% ์ง€์ ์ด + duration์˜ 33%(expo)์—์„œ 54%๋กœ ๋ฐ€๋ฆฐ๋‹ค. ์—…๊ณ„๋„ ์ด ๋Œ€์—ญ์ด๋‹ค + (Material Web 250ms cubic-bezier(.3,0,0,1), Fluent 300ms, MUI 300ms). + + ์˜ค๋ฒ„์ŠˆํŠธ๋Š” keyframe์ด ์•„๋‹ˆ๋ผ ์ปค๋ธŒ์˜ y๊ฐ’์ด ์†Œ์œ ํ•œ๋‹ค - ํ† ๊ธ€ ๋…ธ๋ธŒ(--ui-toggle-ease)์™€ + ๊ฐ™์€ ์›์น™. 1.2๋Š” ํ•œ ์นธ ์ด๋™(69px)์—์„œ 0.9px, ๋‘ ์นธ(139px)์—์„œ 1.7px ๋„˜์–ด๊ฐ„๋‹ค. + ํŠธ๋ž™ ์•ˆ์ชฝ ์—ฌ๋ฐฑ์ด 2px์ด๋ผ ๋„˜์–ด๊ฐ„ ๋งŒํผ์ด ํŠธ๋ž™ ๋ฐ–์œผ๋กœ ์ƒˆ์ง€ ์•Š๋Š”๋‹ค. + ์กฐ์‚ฌํ•œ ๋””์ž์ธ ์‹œ์Šคํ…œ 15๊ณณ ์ค‘ ์„ธ๊ทธ๋จผํŠธ์— ์˜ค๋ฒ„์ŠˆํŠธ๋ฅผ ์“ฐ๋Š” ๊ณณ์€ ์—†๋‹ค - ์˜๋„์  ์ผํƒˆ์ด๊ณ , + ์ด ์ €์žฅ์†Œ๊ฐ€ ํ† ๊ธ€์—์„œ ์ด๋ฏธ ๊ฐ™์€ ์„ ํƒ์„ ํ•œ ๊ฒƒ๊ณผ ๋ฌธ๋ฒ•์„ ๋งž์ถ˜ ๊ฒƒ์ด๋‹ค. + ์ด์ „์—๋Š” translate ์†Œ์ˆ˜ ํ”ฝ์…€์ด ์ •์ฐฉ ์‹œ ๋ฐ˜์˜ฌ๋ฆผ๋˜๋ฉฐ 1px ํŠ€๋Š” ์•„ํ‹ฐํŒฉํŠธ๊ฐ€ ์ด ์—ญํ• ์„ + ๋Œ€์‹ ํ–ˆ๋Š”๋ฐ, translate 0%์ธ ์ฒซ ํƒญ์—์„œ๋งŒ ์•ˆ ๋‚˜ํƒ€๋‚˜๊ณ  ํŠธ๋ž™ ํญ์— ๋”ฐ๋ผ ์‚ฌ๋ผ์ ธ์„œ + ๋ฐฉํ–ฅ ๋น„๋Œ€์นญ์œผ๋กœ ์ฝํ˜”๋‹ค */ + --ui-segment-duration: 220ms; + --ui-segment-ease: cubic-bezier(0.34, 1.2, 0.64, 1); + + /* ํŒ์—… ๋“ฑํ‡ด์žฅ - ๋“œ๋กญ๋‹ค์šด, ํŒ์˜ค๋ฒ„, ํ”ผ์ปค ๊ณตํ†ต ๋‹ค์ด์–ผ. + ๋‹ซํž˜์ด ์—ด๋ฆผ๋ณด๋‹ค ์งง์€ ๊ฑด ์˜๋„, ์‚ฌ๋ผ์ง€๋Š” ๊ฑธ ๊ธฐ๋‹ค๋ฆด ์ด์œ ๊ฐ€ ์—†์Œ */ + --ui-popup-enter-duration: var(--ui-duration-base); + --ui-popup-exit-duration: var(--ui-duration-fast); + --ui-popup-ease: cubic-bezier(0.22, 1, 0.36, 1); + /* ์ปค๋ธŒ๊ฐ€ ์•ž์ชฝ์— ๋ชฐ๋ ค ์žˆ์–ด ์ด๋™์ด 30ms ์•ˆ์— ์†Œ๋ชจ๋œ๋‹ค. + duration์„ ๋Š˜๋ฆฌ๋Š” ๋Œ€์‹  ํญ์„ ํ‚ค์›Œ ์ฒด๊ฐ์„ ํ™•๋ณด */ + --ui-popup-enter-scale: 0.94; + --ui-popup-exit-scale: 0.99; + + /* ์นด๋“œ ๋ชจ๋‹ฌ ๋“ฑํ‡ด์žฅ - ํฐ ํ‘œ๋ฉด์ด๋ผ ํŒ์—…๋ณด๋‹ค ๋А๊ธ‹ํ•˜๊ฒŒ */ + --ui-modal-enter-duration: 250ms; + --ui-modal-exit-duration: 150ms; + --ui-modal-enter-scale: 0.96; + --ui-modal-exit-scale: 0.96; + + /* ํŒจ๋„ ํŽ˜์ด์ง€ ์ „ํ™˜ โ€” ์‚ฌ์ด๋“œ ํŒจ๋„ ์ธ-ํŒจ๋„ ๋‚ด๋น„๊ฒŒ์ด์…˜ ๋‹ค์ด์–ผ */ + --ui-duration-page: 250ms; + --ui-ease-page: cubic-bezier(0.22, 1, 0.36, 1); + --ui-page-slide-distance: 8px; + --ui-page-blur: 3px; + + /* ์•„์ด์ฝ˜ ์Šค์™‘ โ€” ํ† ๊ธ€ ๋ฒ„ํŠผ์˜ SVG ๊ต์ฒด ํฌ๋กœ์ŠคํŽ˜์ด๋“œ ๋‹ค์ด์–ผ */ + --ui-icon-swap-duration: var(--ui-duration-base); + --ui-icon-swap-ease: ease-in-out; + --ui-icon-swap-blur: 2px; + --ui-icon-swap-scale: 0.25; + + /* ๊ธ€์ž ์Šค์™‘ - ์•„์ด์ฝ˜๊ณผ ๋‹ฌ๋ฆฌ ์Šค์ผ€์ผ ๋Œ€์‹  ์„ธ๋กœ ์ด๋™. ๊ธ€์ž๊ฐ€ ์ปค์กŒ๋‹ค ์ž‘์•„์ง€๋ฉด + ์ž๊ฐ„์ด ํ”๋“ค๋ ค ์ฝ๋Š” ๋ˆˆ์ด ๋”ฐ๋ผ๊ฐ€์ง€ ๋ชปํ•œ๋‹ค */ + --ui-text-swap-duration: var(--ui-duration-fast); + --ui-text-swap-ease: ease-in-out; + --ui-text-swap-blur: 2px; + --ui-text-swap-shift: 4px; + + /* ํˆด๋ฐ” ์•„์ด์ฝ˜ ํ˜ธ๋ฒ„ ๋ชจ์…˜ - ํ˜ธ๋ฒ„ ์ง„์ž…์— ํ•œ ๋ฒˆ ์žฌ์ƒ๋˜๊ณ  ๋ฉˆ์ถ˜๋‹ค. ๋ฐ˜๋ณต ์—†์Œ. + 400ms ์•„๋ž˜๋กœ ๋‚ด๋ฆฌ๋ฉด ๋ฌด์Šจ ๋™์ž‘์ธ์ง€ ์ฝ๊ธฐ ์ „์— ๋๋‚˜๋ฏ€๋กœ ๋‹ค๋ฅธ ๋ชจ์…˜๋ณด๋‹ค ๋А๊ธ‹ํ•˜๋‹ค. + ๋™์ž‘๋งˆ๋‹ค ์ž์—ฐ์Šค๋Ÿฌ์šด clock์ด ๋‹ฌ๋ผ ์ง€์† ์‹œ๊ฐ„์€ ์šฉ๋„๋ณ„๋กœ ๋”ฐ๋กœ ๋‘”๋‹ค */ + --ui-icon-ease: cubic-bezier(0.65, 0, 0.35, 1); + /* ์˜ค๋ฒ„์ŠˆํŠธ๋Š” keyframe์ด ์•„๋‹ˆ๋ผ ์ปค๋ธŒ์˜ y๊ฐ’(1.4)์ด ์†Œ์œ ํ•œ๋‹ค. + ํ† ๊ธ€ ๋…ธ๋ธŒ(--ui-toggle-ease)์™€ ๊ฐ™์€ ์›์น™ */ + --ui-icon-ease-back: cubic-bezier(0.34, 1.4, 0.64, 1); + + --ui-icon-expand-duration: 420ms; + /* ๋ˆ„๋ฆ„ - ๋‚ด๋ ค๊ฐˆ ๋• ์†๊ฐ€๋ฝ์„ ๋”ฐ๋ผ ์ฆ‰์‹œ, ์˜ฌ๋ผ์˜ฌ ๋• ์Šคํ”„๋ง์œผ๋กœ. + ๋ณต๊ท€๊ฐ€ ํ˜ธ๋ฒ„ ๋ชจ์…˜๋งŒํผ ๊ธธ๋ฉด ํด๋ฆญ ๊ฒฐ๊ณผ๋ณด๋‹ค ๋Šฆ๊ฒŒ ๋๋‚˜ ๊ตผ๋œจ๊ฒŒ ์ฝํžŒ๋‹ค */ + --ui-icon-press-down-duration: 80ms; + --ui-icon-press-duration: 320ms; + --ui-icon-press-scale: 0.8; + --ui-icon-eye-duration: 440ms; + --ui-icon-nod-duration: 500ms; + --ui-icon-tilt-duration: 520ms; + --ui-icon-wobble-duration: 560ms; + --ui-icon-turn-duration: 600ms; + --ui-icon-sweep-duration: 620ms; + --ui-icon-fan-duration: 700ms; + --ui-icon-punch-duration: 700ms; + --ui-icon-shift-duration: 800ms; + --ui-icon-stagger: 80ms; + + /* ํŒ”๋ ˆํŠธ ๋ฌผ๊ฐ ๊ตฌ๋ฉ์ด ๋‹ค์‹œ ํ„ฐ์งˆ ๋•Œ์˜ ํฌ๊ธฐ. ์—ฌ๊ธฐ์„œ๋งŒ ๊ฐ’์ด ์˜ค๋ฒ„์ŠˆํŠธ๋ฅผ ์†Œ์œ ํ•œ๋‹ค - + ๋ฐ˜์ง€๋ฆ„ 0.875px์—์„œ๋Š” ์ปค๋ธŒ๊ฐ€ ๋งŒ๋“œ๋Š” ์˜ค๋ฒ„์ŠˆํŠธ๊ฐ€ ์†Œ์ˆ˜์  ํ”ฝ์…€์ด๋ผ ํ™”๋ฉด์— ์•ˆ ๋‚˜์˜จ๋‹ค */ + --ui-icon-punch-pop: 1.3; + + /* ์ˆซ์ž ์Šคํ… ํŒ์ธ - ๊ฐ’์ด ๋ฐ”๋€ ์ž๋ฆฟ์ˆ˜๋งŒ ํ•œ ๋ฒˆ ํŠ€์–ด์˜ค๋ฅธ๋‹ค. + ๋ˆ„๋ฅผ ๋•Œ์™€ ๋—„ ๋•Œ ํ•œ ๋ฒˆ์”ฉ๋งŒ ๋ˆ๋‹ค. ๊พน ๋ˆ„๋ฅด๋Š” ๊ตฌ๊ฐ„์€ ๊ฐ’๋งŒ ๋ฐ”๋€Œ๊ณ  ์žฌ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค - + ๋ฐ˜๋ณต ๊ฐ„๊ฒฉ์ด ์žฌ์ƒ๋ณด๋‹ค ์งง์•„ ๋งค ์Šคํ…์ด ์ด์ „ ์žฌ์ƒ์„ ๋Š์œผ๋ฉด ์ˆซ์ž๊ฐ€ ๊นœ๋นก์ด๊ณ  + ๊ฐ’์„ ๋ˆˆ์œผ๋กœ ์ข‡์„ ์ˆ˜ ์—†๋‹ค. + ์•„์ด์ฝ˜ ํ˜ธ๋ฒ„(400ms+)๋ณด๋‹ค ์งง์€ ๊ฑด ์˜๋„. ์†์ด ๊ฐ’์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ปจํŠธ๋กค์ด๋‹ค. + ์˜ค๋ฒ„์ŠˆํŠธ๋Š” keyframe์ด ์•„๋‹ˆ๋ผ ์ปค๋ธŒ์˜ y๊ฐ’์ด ์†Œ์œ ํ•œ๋‹ค (--ui-icon-ease-back๊ณผ ๊ฐ™์€ ์›์น™) */ + --ui-digit-duration: 220ms; + --ui-digit-stagger: 40ms; + --ui-digit-distance: 6px; + --ui-digit-ease: cubic-bezier(0.34, 1.4, 0.64, 1); + + /* ์˜ค๋ฅ˜ ํ”๋“ค๊ธฐ - ์ขŒ์šฐ๋กœ ํ•œ ๋ฒˆ ํ„ธ๊ณ  ์ž‘์€ ์˜ค๋ฒ„์ŠˆํŠธ๋กœ ๋ฉˆ์ถ˜๋‹ค. + ๊ตฌ๊ฐ„๋งˆ๋‹ค ์ปค๋ธŒ๋ฅผ ๋”ฐ๋กœ ๊ฑธ์–ด์•ผ ๊ฐ ๋‹ค๋ฆฌ๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐ์†ํ•œ๋‹ค. + ๊ตฌ๊ฐ„ ๋น„์œจ(80/80/60/60)์€ keyframe %๊ฐ€ ์†Œ์œ ํ•œ๋‹ค - ์„ ํƒ์ž %์—๋Š” calc๋ฅผ ๋ชป ์จ์„œ + ๊ตฌ๊ฐ„ ๊ธธ์ด๋ฅผ ํ† ํฐ์œผ๋กœ ์ชผ๊ฐœ๋„ ์ด ๊ธธ์ด๋งŒ ๋ฐ”๋€Œ๊ณ  ๋ถ„ํ• ์€ ๊ทธ๋Œ€๋กœ๋‹ค. ๋‹ค์ด์–ผ์€ ์ด ๊ธธ์ด ํ•˜๋‚˜. + ํญ์€ ํ•„๋“œ ๊ธฐ์ค€์œผ๋กœ ์žก๋Š”๋‹ค - 60px ์นธ์—์„œ 6px๋Š” 10%๋ผ ๊ฐ’์ด ํŠ€์–ด๋‚˜๊ฐ„ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค. + ์—ฌ๊ธฐ์„œ๋Š” "ํ‹€๋ ธ๋‹ค"๋Š” ์‹ ํ˜ธ๋งŒ ํ•„์š”ํ•˜๋ฏ€๋กœ ๋ˆˆ์— ๊ฑธ๋ฆด ๋งŒํผ๋งŒ ์›€์ง์ธ๋‹ค */ + --ui-shake-distance: 3px; + --ui-shake-overshoot: 1.5px; + --ui-shake-duration: 280ms; + --ui-shake-ease: cubic-bezier(0.22, 1, 0.36, 1); +} diff --git a/src/renderer/styles/tokens/dark.css b/src/renderer/styles/tokens/dark.css new file mode 100644 index 000000000..58badf2d2 --- /dev/null +++ b/src/renderer/styles/tokens/dark.css @@ -0,0 +1,231 @@ +/* + * ๋‹คํฌ ํ…Œ๋งˆ ๊ฐ’ + * + * ์„ ํƒ์ž๋Š” :where()๋กœ ๊ฐ์‹ธ ํŠน์ด๋„๋ฅผ :root์™€ ๊ฐ™์€ (0,1,0)์— ๋ฌถ๋Š”๋‹ค. + * [data-theme='dark']๋ฅผ ๊ทธ๋Œ€๋กœ ์“ฐ๋ฉด (0,2,0)์ด ๋˜์–ด ๋’ค์— ์ฃผ์ž…๋˜๋Š” ์‚ฌ์šฉ์ž CSS์˜ + * :root ์žฌ์ •์˜๋ฅผ ์ด๊ธด๋‹ค. ์ปค์Šคํ…€ CSS ์ตœ์šฐ์„  ๊ณ„์•ฝ์ด ๊นจ์ง€๋Š” ์ž๋ฆฌ๋‹ค. + * + * ๋‹คํฌ ๊ฐ’์„ bare :root์— ํด๋ฐฑ์œผ๋กœ ๋‚จ๊ธฐ์ง€ ์•Š๋Š”๋‹ค. ๋ผ์ดํŠธ์—์„œ ํ† ํฐ ํ•˜๋‚˜๊ฐ€ ๋น ์ง€๋ฉด + * ์กฐ์šฉํžˆ ๋‹คํฌ ๊ฐ’์ด ์ƒˆ์–ด๋‚˜์™€ ๋ฌธ๋ฒ•์ƒ ์ •์ƒ์ธ ์ฑ„๋กœ ํ™”๋ฉด๋งŒ ํ‹€์–ด์ง„๋‹ค. + * ๋Œ€์‹  ์„ธ ์ฐฝ์˜ html์— data-theme์„ ์ •์  ์†์„ฑ์œผ๋กœ ๋ฐ•์•„ ๋ฏธ์ •์˜๋ฅผ ๋ง‰๋Š”๋‹ค. + * ๋‘ ํ…Œ๋งˆ์˜ ํ‚ค ์ง‘ํ•ฉ๊ณผ ์„ ์–ธ ์ˆœ์„œ๋Š” themeTokenParityContract ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ•์ œํ•œ๋‹ค + */ +:root:where([data-theme='dark']) { + /* ๋ฐฐ๊ฒฝ โ€” ๊ฐ’์€ macOS ๋‹คํฌ๋ฅผ ์‹ค์ธกํ•ด์„œ ๋งž์ถ˜ ๊ฒƒ์ด๋‹ค (๊ฐ™์€ ๋””์Šคํ”Œ๋ ˆ์ด์—์„œ ์‹œ์Šคํ…œ ์„ค์ • ์ฐฝ ์บก์ฒ˜ + + NSColor ๋‹คํฌ ์นดํƒˆ๋กœ๊ทธ ์ง์ ‘ ์กฐํšŒ). ์• ํ”Œ windowBackgroundColor๊ฐ€ ์ •ํ™•ํžˆ #1e1e1e๋‹ค. + + ์ค‘์š”ํ•œ ๊ฑด ์ ˆ๋Œ€ ๋ฐ๊ธฐ๊ฐ€ ์•„๋‹ˆ๋ผ ๋‹จ๊ณ„ ํญ์ด๋‹ค. ์• ํ”Œ์€ ์‚ฌ์ด๋“œ๋ฐ” 30 โ†’ ์ฝ˜ํ…์ธ  34(+4) โ†’ + ์นด๋“œ 42(+8) โ†’ ์ธํ’‹ 49(+7)๋กœ ์•„์ฃผ ์ด˜์ด˜ํ•˜๊ณ , ์ฐฝ ํ”ฝ์…€์˜ 92%๊ฐ€ L* 10~19์— ๋“ค์–ด๊ฐ„๋‹ค. + ํญ์„ ๋ฒŒ๋ฆฌ๋ฉด ๋ฐ์€ ์ชฝ ๋์ด ํŠ€์–ด์„œ ์ „์ฒด๊ฐ€ '๋ฐ๋‹ค'๊ณ  ์ฝํžŒ๋‹ค โ€” ๋ฒ ์ด์Šค๋ฅผ ๋‚ด๋ ค๋„ ์•ˆ ๊ณ ์ณ์ง„๋‹ค. + + ์• ํ”Œ ์นดํƒˆ๋กœ๊ทธ๋Š” a*=b*=0์œผ๋กœ ์™„์ „ ๋ฌด์ฑ„์ƒ‰์ด๋‹ค. ํ™”๋ฉด์— ๋„๋Š” ์ƒ‰์กฐ๋Š” ํŒ”๋ ˆํŠธ๊ฐ€ ์‹ฌ๋Š” ๊ฒŒ + ์•„๋‹ˆ๋ผ Desktop Tinting์ด ๋งŒ๋“ ๋‹ค. ์—ฌ๊ธฐ์— ์ƒ‰ ํŽธํ–ฅ์„ ๋„ฃ์ง€ ๋ง ๊ฒƒ */ + --ui-bg-app: #131315; + --ui-bg-panel: #1b1b1d; + --ui-bg-surface: #232325; + --ui-bg-elevated: #1f1f21; + /* ์ปจํŠธ๋กค ํ•„ โ€” ๋ฐ˜ํˆฌ๋ช… ํ™”์ดํŠธ๋ผ ์–ด๋А ํ‘œ๋ฉด ์œ„์„œ๋“  ํ•œ ๋‹จ๊ณ„ ๋ฐ๊ฒŒ ๋œธ + ๊ทผํ‘ ๋ฐฐ๊ฒฝ์—์„  ์–ด๋‘์šด ํ™ˆ์ด ๋ฐฐ๊ฒฝ๊ณผ ๊ตฌ๋ถ„๋˜์ง€ ์•Š์•„ ์ธํ’‹ยทํŠธ๋ž™์€ ๋ฐ์€ ํ•„์ด ๊ธฐ์ค€ + ์กฐ์ž‘๋ฉด(--ui-fill 0.072)๋ณด๋‹ค ์•„๋ž˜ ์œ ์ง€ โ€” ์ •์  ํ™ˆ๊ณผ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ํ•„์˜ ๋ฐ๊ธฐ ๊ตฌ๋ถ„. + ๊ฐ™์€ ์ •์  ๋ฉด์ธ --ui-fill-faint์™€๋Š” ๊ฐ™์€ ๋‹จ์— ๋‘”๋‹ค + ์•ŒํŒŒ ๊ธฐ์ค€์€ ฮ”L*๊ฐ€ ์•„๋‹ˆ๋ผ ๋ฒ ๋ฒ„ ๋Œ€๋น„(ฮ”sRGB/๋ฒ ์ด์Šค)๋‹ค. ์–ด๋‘์šด ์ž๋ฐœ๊ด‘ UI์—์„œ + ์ธ์ ‘๋ฉด ๋ถ„๋ฆฌ๋Š” ์ƒ๋Œ€ ๋Œ€๋น„๊ฐ€ ์ง€๋ฐฐํ•œ๋‹ค โ€” L*๋ฅผ ๊ณ ์ •ํ•˜๋ฉด ๋ฒ ์ด์Šค๊ฐ€ ๋ฐ์•„์ง„ ๋งŒํผ + ์ƒ๋Œ€ ๋Œ€๋น„๊ฐ€ ๊นŽ์—ฌ ๋ฉด์ด ๋ญ‰๊ฐ ๋‹ค (์‹ค์ธก์œผ๋กœ ํ™•์ธ) */ + --ui-bg-inset: rgba(255, 255, 255, 0.035); + /* ๋ถˆํˆฌ๋ช… ์›ฐ โ€” ์ฝ”๋“œ ์—๋””ํ„ฐ์ฒ˜๋Ÿผ ๋ฐฐ๊ฒฝ์ด ํ”๋“ค๋ฆฌ๋ฉด ์•ˆ ๋˜๋Š” ํ‘œ๋ฉด ์ „์šฉ */ + --ui-bg-inset-solid: #161618; + /* ๋ถ„๋ฆฌ๋œ ์†์„ฑ ํŒจ๋„ ์ฐฝ์˜ ๋ฐ”๋‹ฅ โ€” ์ฐฝ ์ž์ฒด๊ฐ€ transparent๋ผ ์•ŒํŒŒ๋ฅผ ์“ฐ๋ฉด ๋ฐ์Šคํฌํ†ฑ์ด ๋น„์นœ๋‹ค. + ๊ฐ’์€ ๋„ํ‚น ์ƒํƒœ์˜ ์‚ฌ์ด๋“œ ํŒจ๋„(--ui-glass-panel ํ•ฉ์„ฑ ๊ฒฐ๊ณผ)๊ณผ ๊ฐ™์€ L*์— ๋งž์ถฐ ๋‘ . + Windows๋Š” ์ฒซ ํŽ˜์ธํŠธ ์ „ ๊ตฌ๊ฐ„์„ ๊ฐ™์€ ์ƒ‰์œผ๋กœ ๋ฉ”์šฐ๋ฏ€๋กœ windows_window_corners.rs์˜ + ์”จ์•— ์ƒ‰๊ณผ ํ•จ๊ป˜ ๋ฐ”๊ฟ€ ๊ฒƒ */ + --ui-bg-panel-detached: #1c1c1e; + + /* ๊ธ€๋ž˜์Šค ํ‘œ๋ฉด โ€” ์Šค๋ชจํฌ ์žฌ์งˆ. ํŒ์—…ยทํŒจ๋„ 0.72, ์บ”๋ฒ„์Šค ํฌ๋กฌ 0.6 + ์•ŒํŒŒ โ†“ = ๋ฐฐ๊ฒฝ ํˆฌ๊ณผ โ†‘ (๊ธ€๋ž˜์Šค ์ฒด๊ฐ์˜ 1์ฐจ ๋‹ค์ด์–ผ). ๋ถ„๋ฆฌ๋Š” ๊ทธ๋ฆผ์ž ํ† ํฐ์˜ ์ธ์…‹ ๋ง+์ƒ๋‹จ ํ•˜์ด๋ผ์ดํŠธ๊ฐ€ ๋‹ด๋‹น, ๋ณด๋” ๊ธˆ์ง€ + ํ‹ดํŠธ๋Š” backdrop-filter โ†’ ๋ฃจ๋ฏธ๋…ธ์‹œํ‹ฐ ์บก โ†’ ํ‹ดํŠธ 3๋‹จ ํ•ฉ์„ฑ์„ ๊ฑฐ์นœ ์ตœ์ข… L*์—์„œ ์—ญ์‚ฐํ•œ ๊ฐ’์ด๋ผ + ํ‹ดํŠธ ์ƒ‰๋งŒ ๋ณด๊ณ  ํ‘œ๋ฉด ๋ฐ๊ธฐ๋ฅผ ์ฝ์œผ๋ฉด ์•ˆ ๋œ๋‹ค. ์บก ํ‹ดํŠธ๋„ ๊ฐ™์ด ์›€์ง์—ฌ์•ผ ์˜๋„ํ•œ ๋งŒํผ ์˜ฌ๋ผ์˜จ๋‹ค */ + --ui-glass: rgba(28, 28, 30, 0.72); + /* ๋ชจ๋‹ฌยท์‹œํŠธยทํ”ผ์ปค์šฉ โ€” ์ค‘์•™ ๋ชจ๋‹ฌ์€ ์Šคํฌ๋ฆผ(.backdrop-glass-scrim ๋”ค+๋ธ”๋Ÿฌ)์ด ๋’ค์— ๊ฒน์ณ + ์‹คํšจ ๋ฐ€๋„๋Š” ์Šคํฌ๋ฆผร—ํ‹ดํŠธ. ์Šคํฌ๋ฆผ ์•ŒํŒŒ์™€ ํ•จ๊ป˜ ์กฐ์ ˆํ•  ๊ฒƒ + ์•ŒํŒŒ๋Š” ์‚ฌ์ด๋“œ ํŒจ๋„๊ณผ ๊ฐ™์€ 0.72๋กœ ๋งž์ถฐ ๋’ท๋ฉด ๋น„์นจ์„ ํŒจ๋„ ์ˆ˜์ค€์œผ๋กœ ์–ต์ œ. + ํ‹ดํŠธ๋Š” ๋ฐ๊ธฐ ํ‹ฐ์–ด์ƒ ํŒจ๋„๋ณด๋‹ค ํ•œ ํ†ค ์œ„์— ์„œ๋„๋ก ์žก์Œ */ + --ui-glass-heavy: rgba(36, 36, 38, 0.72); + /* ์บ”๋ฒ„์Šค ์ƒ์ฃผ ํฌ๋กฌ์šฉ โ€” ๋ฐฐ๊ฒฝ๋ณด๋‹ค ํ•œ ํ†ค ์–ด๋‘์šด ์Šค๋ชจํฌ */ + --ui-glass-dim: rgba(22, 22, 24, 0.6); + /* ์‚ฌ์ด๋“œ ํŒจ๋„์šฉ โ€” ์บ”๋ฒ„์Šค๋ณด๋‹ค ์‚ด์ง ์•„๋ž˜์— ๋‘”๋‹ค. ์ƒ์ฃผ ํ‘œ๋ฉด์ด๋ผ ์—ฌ๊ธฐ๋ฅผ ์˜ฌ๋ฆฌ๋ฉด + ํŒจ๋„ ์•ˆ 3๋‹จ(ํŒจ๋„/์นด๋“œ/์นฉ)์˜ ๋ฒ ์ด์Šค๊ฐ€ ํ†ต์งธ๋กœ ๋ฐ์•„์ ธ ๋ฒ ๋ฒ„ ๋Œ€๋น„๊ฐ€ ๊นŽ์ด๊ณ  + ์„ธ ๋ฉด์ด ํ•œ ๋ฉ์–ด๋ฆฌ๋กœ ๋ญ‰๊ฐ ๋‹ค. ๋ถ„๋ฆฌ๋Š” ๋ฐ๊ธฐ๊ฐ€ ์•„๋‹ˆ๋ผ ์ธ์…‹ ๋ง+์ขŒ์ธก ๋“œ๋กญ์„€๋„๊ฐ€ ๋‹ด๋‹น. + ๋ฐ๊ธฐ ํ‹ฐ์–ด: ์บ”๋ฒ„์Šค ์นฉ < ํŒจ๋„ < ์บ”๋ฒ„์Šค < ๋“œ๋กญ๋‹ค์šด < ๋ชจ๋‹ฌ */ + --ui-glass-panel: rgba(26, 26, 28, 0.72); + /* ์ƒ์ฃผ ํฌ๋กฌ ์†”๋ฆฌ๋“œ โ€” ์‚ฌ์ด๋“œ ํŒจ๋„ยท๋ฏธ๋‹ˆ๋งต์ฒ˜๋Ÿผ ์ƒ์‹œ ๋–  ์žˆ๋Š” ํ‘œ๋ฉด์€ ๋ผ์ด๋ธŒ ๋ธ”๋Ÿฌ ๋Œ€์‹  + ๊ณ ์•ŒํŒŒ ํ‹ดํŠธ๋กœ ๊ทผ์‚ฌ (Windows์—์„œ backdrop-filter๊ฐ€ ๋งค ํ”„๋ ˆ์ž„ ๋ฉด์  ๋น„๋ก€ ์žฌํ•„ํ„ฐ) + ์•ŒํŒŒ๊ฐ€ ์œ ์ผํ•œ ๋‹ค์ด์–ผ: ๋‚ฎ์ถœ์ˆ˜๋ก ๋ฐฐ๊ฒฝ ํˆฌ๊ณผ โ†‘, ๊ธ€๋ž˜์Šค ์งˆ๊ฐ์€ ์ธ์…‹ ๋ง+ํ•˜์ด๋ผ์ดํŠธ๊ฐ€ ์œ ์ง€ + ๊ฐ’์€ ๋ผ์ด๋ธŒ ๊ธ€๋ž˜์Šค์™€ ๊ฐ™์€ L*์— ์„œ๋„๋ก ๋งž์ถฐ ๋‘  โ€” ์ „ํ™˜ ์‹œ ๋‹จ์ฐจ๊ฐ€ ์•ˆ ์ƒ๊ธฐ๊ฒŒ */ + --ui-glass-panel-solid: rgba(28, 28, 30, 0.96); + --ui-glass-dim-solid: rgba(24, 24, 26, 0.94); + + /* ํˆฌ๋ช…๋„ ์ถ•์†Œ ํด๋ฐฑ โ€” ๋ผ์ด๋ธŒ ๋ธ”๋Ÿฌ๋ฅผ ๋„๊ณ  ๊ณ ์•ŒํŒŒ ์†”๋ฆฌ๋“œ๋กœ ๊ฐ•๋“ฑํ•  ๋•Œ์˜ ํ‹ดํŠธ. + media ๋ธ”๋ก์ด ์š”์†Œ ์Šค์ฝ”ํ”„์—์„œ ์ด ๊ฐ’์„ ์Šน๊ฒฉํ•˜๋ฏ€๋กœ ๋ฆฌํ„ฐ๋Ÿด๋กœ ๋‘๋ฉด ํ…Œ๋งˆ ๋ฃจํŠธ๋ฅผ ์ด๊ธด๋‹ค. + ๊ฐ’์€ ์บ”๋ฒ„์Šค ์œ„ ๋ผ์ด๋ธŒ ํ•ฉ์„ฑ ๊ฒฐ๊ณผ ๊ธฐ์ค€. ๋ธ”๋Ÿฌ ์—†์ด๋Š” ๋งฅ๋ฝ ๋ฐ˜์˜์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ + ์ด ๊ฐ’ ์ž์ฒด๊ฐ€ ๋ชฉํ‘œ ํœ˜๋„์™€ ๋Œ€๋น„๋ฅผ ํ˜ผ์ž ๋งŒ์กฑํ•ด์•ผ ํ•œ๋‹ค */ + --ui-glass-reduced: rgba(26, 26, 30, 0.96); + --ui-glass-heavy-reduced: rgba(32, 32, 36, 0.96); + --ui-glass-dim-reduced: rgba(22, 22, 26, 0.94); + --ui-glass-panel-reduced: rgba(25, 25, 29, 0.96); + + /* ๊ธ€๋ž˜์Šค ๋ฐฑ๋“œ๋กญ ํด๋žจํ”„ โ€” ํ‹ดํŠธ ์ „์— ๋ฐฐ๊ฒฝ ๋ฐ๊ธฐ๋ฅผ ๋ˆ„๋ฅด๊ณ , ์ฃฝ์€ ์ฑ„๋„๋Š” ๋ณด์ƒ + dim โ†“ = ๋ฐ์€ ๋ฐฐ๊ฒฝ ํˆฌ๊ณผ ์–ต์ œ. ๊ณผํ•˜๊ฒŒ ๋ˆ„๋ฅด๋ฉด ํˆฌ๊ณผ ๋ณ€ํ™” ์ž์ฒด๊ฐ€ ์ฃฝ์–ด ๊ธ€๋ž˜์Šค๊ฐ€ ์•ˆ ์ฝํž˜ / sat = ์ƒ‰๊ธฐ์šด ์œ ์ง€๋Ÿ‰ + contrast๋Š” ๋‹คํฌ์—์„œ ํ•ญ๋“ฑ(1). ๋ผ์ดํŠธ๊ฐ€ ์“ฐ๋Š” ๋ฒ”์œ„ ์••์ถ• ํ•ญ์ด๋ผ ์—ฌ๊ธฐ์„œ๋Š” ์ผœ์ง€ ์•Š๋Š”๋‹ค */ + --ui-glass-backdrop-dim: 0.8; + --ui-glass-backdrop-contrast: 1; + --ui-glass-backdrop-sat: 1; + /* canvas ์œ„ ๊ธ€๋ž˜์Šค ์ „์šฉ - blur์™€ ๋ฐฐ๊ฒฝ ๋ณ€ํ™”๋Š” ๋‚จ๊ธฐ๊ณ  ์ƒ‰์กฐ ์ง€๋ฐฐ๋งŒ ์–ต์ œ */ + --ui-glass-canvas-backdrop-dim: 0.62; + --ui-glass-canvas-backdrop-contrast: 1; + --ui-glass-canvas-backdrop-sat: 0.35; + /* ๋ฃจ๋ฏธ๋…ธ์‹œํ‹ฐ ๋ ˆ์ด์–ด - ํˆฌ๊ณผ ๊ฒฐ๊ณผ์˜ ํœ˜๋„๋ฅผ ํ‹ดํŠธ์˜ ํœ˜๋„ ์ชฝ์œผ๋กœ ๋‹น๊ธด๋‹ค (์ƒ‰์กฐ๋Š” ์œ ์ง€). + 0 = ๋”, 1 = ์™„์ „ ์น˜ํ™˜. ์ƒํ•œ์ด ์•„๋‹ˆ๋ผ ๋ชฉํ‘œ๊ฐ’์œผ๋กœ ๋Œ์–ด๋‹น๊ธฐ๋Š” ๋ณด์ •์ด๋ผ + ๋ฐ์€ ํ”ฝ์…€์€ ๋‚ด๋ ค์˜ค๊ณ  ์–ด๋‘์šด ํ”ฝ์…€์€ ์˜ฌ๋ผ๊ฐ„๋‹ค. ํ•„ํ„ฐ์™€ ๋…๋ฆฝ ๋™์ž‘ํ•˜๋Š” 2์ฐจ ์•ˆ์ „๋ง */ + --ui-glass-backdrop-lum: 0.1; + /* ๋‹น๊ฒจ๊ฐˆ ๋ชฉํ‘œ ํœ˜๋„ โ€” --ui-glass ํ‹ดํŠธ์™€ ๊ฐ™์€ ์ƒ‰์„ ์œ ์ง€ํ•  ๊ฒƒ. + ์—ฌ๊ธฐ๋งŒ ์–ด๋‘์šด ์ฑ„๋กœ ๋‘๋ฉด ์œ ๋ฆฌ ํ‹ดํŠธ๋ฅผ ์˜ฌ๋ ค๋„ ์บก์ด ํœ˜๋„๋ฅผ ๋„๋กœ ๋Œ์–ด๋‚ด๋ฆฐ๋‹ค */ + --ui-glass-backdrop-lum-tint: #1c1c1e; + + /* ๋ชจ๋‹ฌ ์Šคํฌ๋ฆผ โ€” ๋”ค ๋ ˆ์‹œํ”ผ ๋‹จ์ผ ์†Œ์Šค. ๋ธ”๋Ÿฌ๋Š” main.css๊ฐ€ ์†Œ์œ  */ + --ui-scrim: rgba(0, 0, 0, 0.6); + + /* ๊ฒฝ๊ณ„์„  โ€” ์žฅ์‹์šฉ ๊ธˆ์ง€, ๊ธฐ๋Šฅ์  ๊ตฌํš(๋ฐ”/ํ”Œ๋กœํŒ… ํฌ๋กฌ/ํผ ์ปจํŠธ๋กค)์—๋งŒ ์‚ฌ์šฉ. + --ui-line 0.1์€ ์• ํ”Œ separatorColor(ํ™”์ดํŠธ ฮฑ0.098) ์‹ค์ธก๊ฐ’๊ณผ ์ผ์น˜ํ•œ๋‹ค. + ํ™”๋ฉด ํ”ฝ์…€์—์„œ ์—ญ์‚ฐํ•˜๋ฉด 0.047์ฏค ๋‚˜์˜ค๋Š”๋ฐ ๊ทธ๊ฑด ์•ˆํ‹ฐ์—์ผ๋ฆฌ์–ด์‹ฑ์ด ์„ž์ธ ๊ฐ’์ด๋ผ + ์„ ์–ธ๊ฐ’์œผ๋กœ ์“ฐ๋ฉด ์„ ์ด ์ ˆ๋ฐ˜ ์ดํ•˜๋กœ ํ๋ ค์ง„๋‹ค. ๋‚ด๋ฆฌ์ง€ ๋ง ๊ฒƒ */ + --ui-line: rgba(255, 255, 255, 0.1); + --ui-line-strong: rgba(255, 255, 255, 0.14); + /* ์บ”๋ฒ„์Šค/์Šคํ…Œ์ด์ง€ ๋ณด์กฐ ๊ฒฉ์ž์„  โ€” ๋ฉ”์ด์ €(--ui-line)๋ณด๋‹ค ํ•œ ํ†ค ํ๋ฆผ */ + --ui-line-faint: rgba(255, 255, 255, 0.06); + + /* ํˆฌ๋ช… ์ƒ‰์ƒ ์ฒด์ปค ๋ฌด๋Šฌ. ํƒ€์ผ ํฌ๊ธฐ๋Š” base.css๊ฐ€ ์†Œ์œ  */ + --ui-checker-pattern: conic-gradient( + #666 0.25turn, + #999 0.25turn 0.5turn, + #666 0.5turn 0.75turn, + #999 0.75turn + ); + + /* ํฌ๋กฌ ํ•„ โ€” ๋ฐ˜ํˆฌ๋ช… ํ™”์ดํŠธ ์˜ค๋ฒ„๋ ˆ์ด ์ƒํƒœ ๋ž˜๋” (idle โ†’ hover โ†’ active) + ์•ŒํŒŒ ๊ธฐ์ค€์€ ๋ฒ ๋ฒ„ ๋Œ€๋น„. --ui-bg-inset ์ฃผ์„ ์ฐธ์กฐ */ + --ui-fill: rgba(255, 255, 255, 0.072); + --ui-fill-hover: rgba(255, 255, 255, 0.108); + --ui-fill-active: rgba(255, 255, 255, 0.145); + /* ์ •์  ๊ทธ๋ฃน ์ปจํ…Œ์ด๋„ˆ์šฉ โ€” ์ธํ„ฐ๋ž™์…˜ ์—†๋Š” ์€์€ํ•œ ๋ฉด */ + --ui-fill-faint: rgba(255, 255, 255, 0.035); + + /* ์ฝ”๋“œ ์—๋””ํ„ฐ ์Šคํฌ๋กค๋ฐ” ์ธ โ€” ํฌ๋กฌ ํ•„๋ณด๋‹ค ์ง„ํ•˜๋‹ค. ์ข์€ 4px ๋ง‰๋Œ€๋ผ + ๊ฐ™์€ ์•ŒํŒŒ๋กœ๋Š” ์›ฐ ๋ฐ”๋‹ฅ๊ณผ ์•ˆ ๊ฐˆ๋ฆฐ๋‹ค */ + --ui-scrollbar-thumb: rgba(255, 255, 255, 0.14); + --ui-scrollbar-thumb-hover: rgba(255, 255, 255, 0.2); + --ui-scrollbar-thumb-active: rgba(255, 255, 255, 0.26); + + /* ํ…์ŠคํŠธ ์œ„๊ณ„ โ€” ์• ํ”Œ ์‹ค์ธก(1์ฐจ L*88.6 / 2์ฐจ 65.5 / 3์ฐจ 59.2)์— ๋งž์ถ˜ ๊ฐ’. + ์ฒด๊ฐ ๋ˆˆ๋ถ€์‹ฌ์—๋Š” ํ‘œ๋ฉด๋ณด๋‹ค ํ…์ŠคํŠธ๊ฐ€ ๋” ํฌ๊ฒŒ ๊ธฐ์—ฌํ•œ๋‹ค. ์ˆœ๋ฐฑ์— ๊ฐ€๊นŒ์šด ๋ณธ๋ฌธ์ƒ‰์„ ๋‘๊ณ  + ํ‘œ๋ฉด๋งŒ ์กฐ์ ˆํ•˜๋ฉด '๋ฐ๋‹ค'๋Š” ์ธ์ƒ์ด ์•ˆ ์žกํžŒ๋‹ค. + ์• ํ”Œ์€ ๊ฐ€์žฅ ๋ฐ์€ ํ‘œ๋ฉด(L*21.7)๊ณผ ๊ฐ€์žฅ ์–ด๋‘์šด ํ…์ŠคํŠธ(L*59.2) ์‚ฌ์ด 37 L*๋ฅผ ํ†ต์งธ๋กœ ๋น„์›Œ + ํ…์ŠคํŠธ์™€ ๊ฒฝ์Ÿํ•˜๋Š” ๋ฉด์„ ๋‘์ง€ ์•Š๋Š”๋‹ค. + ๋ชฉ๋ก ์›ฐ์˜ ๊บผ์ง ํ–‰ ์ด๋ฆ„(disabled)์ด 4.5:1์— ๋ชป ๋ฏธ์น˜๋Š” ๊ฒƒ์€ ์˜๋„๋œ ๊ฐ’์ด๋‹ค โ€” + ๊ธฐ์ค€์„ ๋งž์ถ”๋Š” ๊ฐ€์žฅ ์–ด๋‘์šด ํšŒ์ƒ‰์€ muted์™€ ๊ตฌ๋ถ„์ด ์•ˆ ๋ผ '๊บผ์ง'์œผ๋กœ ์ฝํžˆ์ง€ ์•Š๋Š”๋‹ค. + ๊ทผ๊ฑฐ์™€ ๋Œ€์•ˆ ๊ฒ€ํ† ๋Š” SettingsPanel/panelChrome.ts ์ฐธ์กฐ */ + /* ํ˜ธ๋ฒ„์—์„œ ํ•œ ์นธ ๋” ์˜ฌ๋ฆด ์ž๋ฆฌ. ๋ณธ๋ฌธ(--ui-fg)์ด ์ด๋ฏธ ์ˆœ๋ฐฑ์ด ์•„๋‹ˆ๋ผ ์œ„๊ฐ€ ๋น„์–ด ์žˆ๋‹ค */ + --ui-fg-strong: #ffffff; + --ui-fg: #dcdcde; + --ui-fg-muted: #9d9d9f; + --ui-fg-faint: #8c8c8e; + --ui-fg-disabled: #68686a; + + /* ์•ก์„ผํŠธ โ€” ๋ธŒ๋žœ๋“œ ํผํ”Œ. ์ธ๋””์ผ€์ดํ„ฐยทํฌ์ปค์Šค ๋“ฑ ์ž‘์€ ์ง€์ ์šฉ */ + --ui-accent: #8b5cf6; + --ui-accent-hover: #9d74f8; + --ui-accent-active: #7a4be0; + --ui-accent-muted: rgba(139, 92, 246, 0.16); + --ui-accent-fg: #ffffff; + /* ํฐ ๋ฉด์  CTA์šฉ โ€” ๋ฉด์ ์ด ํด์ˆ˜๋ก ํœ˜๋„ยท์ฑ„๋„๋ฅผ ๋‚ด๋ฆผ (๋‹คํฌ์—์„œ ๋ˆˆ๋ถ€์‹ฌ ๋ฐฉ์ง€) */ + --ui-accent-deep: #6b4ad0; + --ui-accent-deep-hover: #7857d8; + --ui-accent-deep-active: #5f40bd; + + /* ํ† ๊ธ€ ๋…ธ๋ธŒ์™€ ๊บผ์ง ํŠธ๋ž™. ๋…ธ๋ธŒ๋Š” ๋‘ ํ…Œ๋งˆ ๋ชจ๋‘ ํฐ์ƒ‰์ด๋‹ค - ์ผœ์ง์€ ์•ก์„ผํŠธ, + ๊บผ์ง์€ ํšŒ์ƒ‰ ํŠธ๋ž™์ด๋ผ ์–ด๋А ์ชฝ์—์„œ๋„ ํฐ ๋…ธ๋ธŒ๊ฐ€ ํŠธ๋ž™ ์œ„๋กœ ๋– ์˜ค๋ฅธ๋‹ค */ + --ui-control-knob: #ffffff; + --ui-control-track-off: rgba(255, 255, 255, 0.14); + --ui-control-track-off-hover: rgba(255, 255, 255, 0.19); + + /* ์บ”๋ฒ„์Šค ํ•ธ๋“ค โ€” ์‚ฌ์šฉ์ž๊ฐ€ ๋งŒ๋“  ์ž„์˜ ๋ฐฐ๊ฒฝ ์œ„์— ๋œฌ๋‹ค. ์•ฑ ํ…Œ๋งˆ๊ฐ€ ์•„๋‹ˆ๋ผ + ์•„๋ž˜ ์ฝ˜ํ…์ธ ๊ฐ€ ๋ฐ์„์ง€ ์–ด๋‘์šธ์ง€๋ฅผ ๋ชจ๋ฅธ๋‹ค๋Š” ๊ฒŒ ์ œ์•ฝ์ด๋ผ ๋‘ ํ…Œ๋งˆ๊ฐ€ ๊ฐ™์€ ๊ฐ’์„ ์“ด๋‹ค. + ๋ถ„๋ฆฌ๋Š” ์ฑ„์›€์ƒ‰์ด ์•„๋‹ˆ๋ผ ๋ง๊ณผ ๋“œ๋กญ์„€๋„๊ฐ€ ๋‹ด๋‹น */ + --ui-handle-fill: #ffffff; + --ui-handle-ring: rgba(0, 0, 0, 0.35); + --ui-handle-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + + /* ์บ”๋ฒ„์Šค ์ •๋ ฌ ๊ฐ€์ด๋“œ โ€” ์„ ํƒ ํฌ๋กฌ(๋ฐ”์ด์˜ฌ๋ ›)๊ณผ ์ƒ‰์œผ๋กœ ๊ฐˆ๋ผ์•ผ ํ•˜๋Š” ๋ณ„๊ฐœ ์‹ ํ˜ธ๋ผ + ์‹œ๋งจํ‹ฑ danger๋ฅผ ์žฌ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ์˜ค๋ฅ˜๊ฐ€ ์•„๋‹ˆ๋ผ ์œ„์น˜ ์Šค๋ƒ… ํ‘œ์‹œ */ + --ui-guide-align: #ff6b6b; + --ui-guide-align-glow: rgba(255, 107, 107, 0.5); + + /* ์บ”๋ฒ„์Šค ์„ ํƒ ํฌ๋กฌ โ€” ์…€๋ ‰์…˜์€ ์˜๋ฏธ ์ง€์ ์ด๋ฏ€๋กœ ๋ธŒ๋žœ๋“œ ๋ฐ”์ด์˜ฌ๋ › */ + --ui-selection: #8b5cf6; + --ui-selection-border: rgba(139, 92, 246, 0.8); + --ui-selection-border-strong: rgba(139, 92, 246, 0.9); + --ui-selection-fill: rgba(139, 92, 246, 0.15); + --ui-selection-glow: rgba(139, 92, 246, 0.5); + + /* ์‹œ๋งจํ‹ฑ */ + --ui-danger: #e5484d; + --ui-danger-hover: #ec5d62; + --ui-danger-active: #d93b40; + --ui-danger-muted: rgba(229, 72, 77, 0.14); + --ui-danger-muted-hover: rgba(229, 72, 77, 0.2); + --ui-danger-muted-active: rgba(229, 72, 77, 0.26); + --ui-danger-fg: #ff9599; + --ui-success: #3dbe7b; + --ui-success-muted: rgba(61, 190, 123, 0.14); + --ui-warning: #f0b429; + --ui-warning-muted: rgba(240, 180, 41, 0.14); + + /* ์ฝ”๋“œ ์—๋””ํ„ฐ ๊ตฌ๋ฌธ ์ƒ‰ โ€” ๋ถˆํˆฌ๋ช… ์›ฐ(--ui-bg-inset-solid) ์œ„ ๊ธฐ์ค€ */ + --ui-syntax-comment: #6a9955; + --ui-syntax-string: #ce9178; + --ui-syntax-keyword: #c586c0; + --ui-syntax-property: #9cdcfe; + --ui-syntax-punctuation: #d4d4d4; + + /* ๊ทธ๋ฆผ์ž โ€” ๋ณด๋” ์—†๋Š” ํ‘œ๋ฉด ๋ถ„๋ฆฌ์˜ ํ•ต์‹ฌ. 2/3์€ ์ธ์…‹ ๋ง+์ƒ๋‹จ ํ•˜์ด๋ผ์ดํŠธ๋กœ ์žฌ์งˆ ๋ฆฝ ํฌํ•จ + ์ธ์…‹ ์•ŒํŒŒ๋Š” ํ‘œ๋ฉด์ด ๋ฐ์•„์ง„ ๋งŒํผ ์˜ฌ๋ ค ๋ฆฝ์˜ ฮ”L*๋ฅผ ์œ ์ง€ */ + --ui-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.35); + --ui-shadow-2: inset 0 0 0 1px rgba(255, 255, 255, 0.08), + inset 0 1px 0 0 rgba(255, 255, 255, 0.11), 0 8px 24px rgba(0, 0, 0, 0.45), + 0 2px 6px rgba(0, 0, 0, 0.3); + /* 3์€ ๋ชจ๋‹ฌ ์นด๋“œ - ์Šคํฌ๋ฆผ์ด ์ด๋ฏธ ๋’ค๋ฅผ ์–ด๋‘กํžˆ๋ฏ€๋กœ ๋ฒˆ์ง์€ popup๋ณด๋‹ค ํ•œ ๋‹จ๊ณ„๋งŒ ๋„“๊ฒŒ */ + --ui-shadow-3: inset 0 0 0 1px rgba(255, 255, 255, 0.08), + inset 0 1px 0 0 rgba(255, 255, 255, 0.11), 0 10px 28px rgba(0, 0, 0, 0.4), + 0 3px 8px rgba(0, 0, 0, 0.25); + + /* ์ผ์‹œ popup - ์ธ์ ‘ control์„ ๋ฎ์ง€ ์•Š๊ฒŒ lip์€ ์œ ์ง€ํ•˜๊ณ  ์™ธ๋ถ€ ๋ฒˆ์ง๋งŒ ์ถ•์†Œ */ + --ui-shadow-popup: inset 0 0 0 1px rgba(255, 255, 255, 0.08), + inset 0 1px 0 0 rgba(255, 255, 255, 0.11), 0 3px 10px rgba(0, 0, 0, 0.28), + 0 1px 2px rgba(0, 0, 0, 0.2); + + /* ์‚ฌ์ด๋“œ ํŒจ๋„ โ€” ๋„ํ‚น ๊ฒฝ๊ณ„์ธ ์ขŒ์ธก ์—ฃ์ง€๋งŒ ํ—ค์–ด๋ผ์ธ, ์ขŒ์ธก ๋ฐฉํ–ฅ์„ฑ ๋“œ๋กญ์œผ๋กœ ์บ”๋ฒ„์Šค์™€ ๋ถ„๋ฆฌ */ + --ui-shadow-panel: inset 1px 0 0 0 rgba(255, 255, 255, 0.09), + -12px 0 32px rgba(0, 0, 0, 0.35); + + /* ์ƒ์ฃผ ํฌ๋กฌ โ€” ์ธ์…‹ ๋ง + ์ƒ๋‹จ ํ•˜์ด๋ผ์ดํŠธ๊ฐ€ ๋ณด๋”ยท์žฌ์งˆ ๋‹ด๋‹น, ๋“œ๋กญ์„€๋„๋Š” ํ”์ ๋งŒ */ + --ui-shadow-chrome: inset 0 0 0 1px rgba(255, 255, 255, 0.08), + inset 0 1px 0 0 rgba(255, 255, 255, 0.11), 0 1px 3px rgba(0, 0, 0, 0.2); + + /* ํฌ์ปค์Šค ๋ง โ€” ๋ณด๋” ๋Œ€์‹  ๊ทธ๋ฆผ์ž ๋ง. + ํ‘œ๋ฉด ์œ„ 3:1์ด ํ•˜ํ•œ์ด๋ผ ์•ŒํŒŒ๋ฅผ ๋‚ฎ์ถ”๊ฑฐ๋‚˜ --ui-accent๋ฅผ ๊ทธ๋Œ€๋กœ ์“ฐ๋ฉด ์•ˆ ๋œ๋‹ค. + ์•ก์„ผํŠธ ์›์ƒ‰์€ ๋ฐ์•„์ง„ ํ‘œ๋ฉด์—์„œ 3.2:1๊นŒ์ง€ ๋–จ์–ด์ ธ ๋ง์ด ๋ฉด์— ์ž ๊ธด๋‹ค */ + --ui-focus-ring: 0 0 0 1.5px rgba(160, 121, 248, 0.9); + + /* ํ™•์ •ํ•  ์ˆ˜ ์—†๋Š” ์ž…๋ ฅ - ํฌ์ปค์Šค ๋ง๊ณผ ๊ฐ™์€ ์ž๋ฆฌ, ๊ฐ™์€ ๋‘๊ป˜๋กœ ๊ทธ๋ ค ์ƒ‰๋งŒ ๋ฐ”๋€๋‹ค. + ๋ณด๋”๋ฅผ ์“ฐ๋ฉด ์ƒ์ž ์•ˆ์ชฝ์ด ์ข์•„์ง€๊ณ  ๋ฐ”๊นฅ ํฌ์ปค์Šค ๋ง๊ณผ ๋‚˜๋ž€ํžˆ ์„œ์„œ ์ƒ‰์ด ์„ž์ธ๋‹ค */ + --ui-danger-ring: 0 0 0 1.5px rgba(240, 106, 111, 0.9); +} diff --git a/src/renderer/styles/tokens/light.css b/src/renderer/styles/tokens/light.css new file mode 100644 index 000000000..cab7b3147 --- /dev/null +++ b/src/renderer/styles/tokens/light.css @@ -0,0 +1,267 @@ +/* + * ๋ผ์ดํŠธ ํ…Œ๋งˆ ๊ฐ’ + * + * ํ‚ค ์ง‘ํ•ฉ๊ณผ ์„ ์–ธ ์ˆœ์„œ๋ฅผ tokens/dark.css์™€ ์™„์ „ํžˆ ์ผ์น˜์‹œํ‚จ๋‹ค. ๋‚˜๋ž€ํžˆ ๋†“๊ณ  ์ฝ๋Š” + * ๊ฒƒ์ด ์ด ํŒŒ์ผ์˜ ์‚ฌ์šฉ๋ฒ•์ด๊ณ , themeTokenParityContract ํ…Œ์ŠคํŠธ๊ฐ€ ์–ด๊ธ‹๋‚จ์„ ๋ง‰๋Š”๋‹ค. + * + * ๊ฐ’์„ ์ •ํ•œ ๋ฐฉ๋ฒ•: ๋‹คํฌ ํŒ”๋ ˆํŠธ์˜ ์‹ค์ œ ํ•ฉ์„ฑ ๊ฒฐ๊ณผ๋ฅผ ์žฌ์„œ ๊ฐ™์€ ๋‹จ์ฐจ๋ฅผ ๋ผ์ดํŠธ์—์„œ + * ์žฌํ˜„ํ–ˆ๋‹ค. ์ƒ‰์„ ๋ฐ˜์ „ํ•œ ๊ฒŒ ์•„๋‹ˆ๋ผ ๋‹จ์ฐจ๋ฅผ ์˜ฎ๊ธด ๊ฒƒ์ด๋‹ค. ์ฃผ์š” ๋Œ€์กฐ๋Š” ์ด๋ ‡๋‹ค. + * + * ๋ฉด ๊ด€๊ณ„ ๋‹คํฌ ฮ”L* ๋ผ์ดํŠธ ฮ”L* + * ํŒจ๋„ -> ์นด๋“œ +4.0 +4.4 + * ์นด๋“œ -> ์ž…๋ ฅ ์›ฐ +3.4 -3.8 + * ์บ”๋ฒ„์Šค -> ๊ฒฉ์ž์„  +6.9 -6.5 + * ์œ ๋ฆฌ -> ๋‘˜๋ ˆ ๋ง +8.2 -7.7 + * ์œ ๋ฆฌ -> ์ƒ๋‹จ ๋ฆฝ +3.3 +2.9 + * + * ๊ตฌ์กฐ์ ์œผ๋กœ ๋‹ค๋ฅธ ์ ์ด ์…‹ ์žˆ๋‹ค. + * + * 1. ์•ŒํŒŒ์˜ ๊ธฐ์ค€์„ . ๋‹คํฌ๋Š” ๊ทผํ‘ ๋ฒ ์ด์Šค๋ผ ์ธ์ ‘๋ฉด ๋ถ„๋ฆฌ๋ฅผ ๋ฒ ๋ฒ„ ๋Œ€๋น„๊ฐ€ ์ง€๋ฐฐํ•œ๋‹ค. + * ๋ฐ์€ ํ•„๋“œ์—์„œ๋Š” ๋ˆˆ์ด ๋ฐ์€ ์ชฝ์— ์ ์‘ํ•ด ๊ฐ™์€ ์ƒ๋Œ€ ๋Œ€๋น„๊ฐ€ ํ›จ์”ฌ ์„ธ๊ฒŒ ์ฝํžˆ๋ฏ€๋กœ + * ๊ธฐ์ค€์„ ฮ”L*๋กœ ๋ฐ”๊พผ๋‹ค. ์œ„ ํ‘œ๊ฐ€ ๊ทธ ๊ธฐ์ค€์œผ๋กœ ๋งž์ถ˜ ๊ฒฐ๊ณผ๋‹ค. + * + * 2. ์œ ๋ฆฌ์˜ ๋ฐ๊ธฐ ๋ฐ”๋‹ฅ. ๋‹คํฌ๋Š” backdrop-filter์˜ brightness๋กœ ๋ฐ์€ ๋ฐฐ๊ฒฝ์„ ๋ˆŒ๋Ÿฌ + * ์–ด๋‘ ์„ ์ง€์ผฐ๋‹ค. ๋ผ์ดํŠธ์—์„œ brightness๋ฅผ 1 ์œ„๋กœ ์˜ฌ๋ฆฌ๋Š” ๊ฒƒ์€ ๋Œ€์‘๋ฌผ์ด ์•„๋‹ˆ๋‹ค. + * sRGB ๊ณฑ์—ฐ์‚ฐ์ด๋ผ ๊ฒ€์ •์€ ์•„๋ฌด๋ฆฌ ๊ณฑํ•ด๋„ ๊ฒ€์ •์ด๊ณ  ๋ฐ์€ ์ชฝ๋งŒ ๋จผ์ € ํฌํ™”ํ•œ๋‹ค. + * ๋ผ์ดํŠธ์˜ ๋ฐ”๋‹ฅ์€ ๋ฐ์€ ํ‹ดํŠธ์˜ ์•ŒํŒŒ ํ•ฉ์„ฑ์ด ์†Œ์œ ํ•œ๋‹ค. ๊ฒ€์ • ๋ฐฐ๊ฒฝ์—์„œ๋„ ๊ฒฐ๊ณผ๊ฐ€ + * ์ตœ์†Œ ์•ŒํŒŒร—ํ‹ดํŠธ๋กœ ๋– ์˜ค๋ฅด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ํ•„ํ„ฐ๋Š” ๋ฒ”์œ„๋ฅผ ์ขํžˆ๋Š” ๋ณด์กฐ ์—ญํ• ๋งŒ ํ•œ๋‹ค. + * + * 3. ์นด๋“œ๊ฐ€ ๋œจ๋Š” ๋ฐฉํ–ฅ. --ui-fill-faint๋งŒ ๋‹คํฌ์™€ ๋ฐ˜๋Œ€๋กœ ํฐ ์•ŒํŒŒ๋‹ค. ์ด ํ† ํฐ์€ + * ์ƒํ˜ธ์ž‘์šฉ ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ผ ์ •์  ์นด๋“œ ๋ฉด์ด๊ณ , ์นด๋“œ๋Š” ๋‘ ํ…Œ๋งˆ ๋ชจ๋‘ ์•„๋ž˜ ๋ฉด๋ณด๋‹ค + * ๋– ์˜ฌ๋ผ์•ผ ํ•œ๋‹ค. ๋ฐ์€ ๋ฉด ์œ„์—์„œ ๋– ์˜ค๋ฅธ๋‹ค๋Š” ๊ฒƒ์€ ํฐ ์ž‰ํฌ๊ฐ€ ์–นํžŒ๋‹ค๋Š” ๋œป์ด๋‹ค. + * ๋ฐ˜๋Œ€๋กœ --ui-fill ๊ณ„์—ด์€ ์กฐ์ž‘๋ฉด์ด๋ผ ๋ผ์ดํŠธ์—์„œ๋„ ์ž‰ํฌ๊ฐ€ ๋Š˜์–ด๋‚˜๋Š” ๋ฐฉํ–ฅ์„ + * ์œ ์ง€ํ•œ๋‹ค. ๋ˆ„๋ฅด๋ฉด ์ง„ํ•ด์ง€๋Š” ๊ฒƒ์ด ์†์˜ ๊ธฐ๋Œ€์— ๋งž๋Š”๋‹ค. + * + * ์ž‰ํฌ๋Š” ์ˆœํ‘์ด ์•„๋‹ˆ๋ผ ์•„์ฃผ ์‚ด์ง ๋‚จ์ƒ‰์œผ๋กœ ๊ธฐ์šด rgb(16, 24, 40)์„ ์“ด๋‹ค. ๋ฌด์ฑ„์ƒ‰ + * ์œ„์— ์–น์€ ์ˆœํ‘ ์ €์•ŒํŒŒ๋Š” ๋•Œ๊ฐ€ ํƒ„ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด๊ณ , ์ฟจ ๊ทธ๋ ˆ์ด ๋ฐ”ํƒ•๊ณผ๋„ ๊ฒ‰๋ˆ๋‹ค + */ +:root:where([data-theme='light']) { + /* ๋ฐฐ๊ฒฝ โ€” ๋ฐ”๋‹ฅ์ด ํšŒ์ƒ‰์ด๊ณ  ์นด๋“œ๊ฐ€ ํฐ ์ชฝ์œผ๋กœ ๋– ์˜ค๋ฅธ๋‹ค. ๋ฐฉํ–ฅ์€ ๋‹คํฌ์™€ ๊ฐ™๋‹ค. + ํญ์€ ๋‹คํฌ(๋์—์„œ ๋๊นŒ์ง€ ๋Œ€๋น„ 1.18:1)๋ณด๋‹ค ๋„“๊ฒŒ ์žก์•˜๋‹ค. ๋ฐ์€ ์ชฝ ๋์—์„œ๋Š” + ๊ฐ™์€ L* ์ฐจ์ด๊ฐ€ ํ›จ์”ฌ ์•ฝํ•˜๊ฒŒ ์ฝํ˜€, ๋‹คํฌ์™€ ๊ฐ™์€ ํญ์„ ์“ฐ๋ฉด ๋„ค ๋‹จ์ด ์ „๋ถ€ + ํฐ์ƒ‰ ํ•œ ๋ฉ์–ด๋ฆฌ๋กœ ๋ญ‰๊ฐ ๋‹ค. + + ๋ฌด์ฑ„์ƒ‰์ด ์•„๋‹ˆ๋ผ ์ฟจ ๊ทธ๋ ˆ์ด๋‹ค. ์ฑ„๋„์„ R < G < B๋กœ ๋ฒŒ๋ ค(์•ฝ 9๋‹จ๊ณ„) ํšŒ์ƒ‰์ด + ํƒํ•ด ๋ณด์ด์ง€ ์•Š๊ฒŒ ํ–ˆ๋‹ค. ๋‹คํฌ ํŒ”๋ ˆํŠธ๋„ ๊ฐ™์€ ๋ฐฉํ–ฅ์˜ ํŽธํ–ฅ์„ ๊ฐ–๊ณ  ์žˆ์–ด + ๋‘ ํ…Œ๋งˆ๊ฐ€ ๊ฐ™์€ ๊ณ„์—ด๋กœ ์ฝํžŒ๋‹ค */ + --ui-bg-app: #ccd3dc; + --ui-bg-panel: #dee3ea; + --ui-bg-surface: #ffffff; + --ui-bg-elevated: #f1f5fa; + /* ์ปจํŠธ๋กค ํ•„ โ€” ๋ฐฉํ–ฅ๋งŒ ๋’ค์ง‘ํžŒ๋‹ค. ๋‹คํฌ์—์„œ ์–ด๋‘์šด ํ™ˆ์ด ๊ทผํ‘ ๋ฐฐ๊ฒฝ๊ณผ ์•ˆ ๊ฐˆ๋ ธ๋“ฏ + ๋ผ์ดํŠธ์—์„œ๋Š” ๋ฐ์€ ํ™ˆ์ด ์นด๋“œ์™€ ์•ˆ ๊ฐˆ๋ฆฐ๋‹ค. ๊ทธ๋ž˜์„œ ์ธํ’‹ยทํŠธ๋ž™์€ ์–ด๋‘์šด ํ•„์ด ๊ธฐ์ค€. + ์นด๋“œ(--ui-fill-faint)๋ณด๋‹ค ์•„๋ž˜๋ผ๋Š” ๊ด€๊ณ„๋Š” ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•œ๋‹ค */ + --ui-bg-inset: rgba(16, 24, 40, 0.05); + /* ๋ถˆํˆฌ๋ช… ์›ฐ โ€” ์ฝ”๋“œ ์—๋””ํ„ฐ์ฒ˜๋Ÿผ ๋ฐฐ๊ฒฝ์ด ํ”๋“ค๋ฆฌ๋ฉด ์•ˆ ๋˜๋Š” ํ‘œ๋ฉด ์ „์šฉ */ + --ui-bg-inset-solid: #e6ebf1; + /* ๋ถ„๋ฆฌ๋œ ์†์„ฑ ํŒจ๋„ ์ฐฝ์˜ ๋ฐ”๋‹ฅ โ€” ์ฐฝ ์ž์ฒด๊ฐ€ transparent๋ผ ์•ŒํŒŒ๋ฅผ ์“ฐ๋ฉด ๋ฐ์Šคํฌํ†ฑ์ด ๋น„์นœ๋‹ค. + ๊ฐ’์€ ๋„ํ‚น ์ƒํƒœ ์‚ฌ์ด๋“œ ํŒจ๋„(--ui-glass-panel ํ•ฉ์„ฑ ๊ฒฐ๊ณผ)๊ณผ ๊ฐ™์€ L*. + windows_window_corners.rs์˜ ๋ผ์ดํŠธ ์”จ์•— ์ƒ‰๊ณผ ํ•จ๊ป˜ ๋ฐ”๊ฟ€ ๊ฒƒ */ + --ui-bg-panel-detached: #eef3f7; + + /* ๊ธ€๋ž˜์Šค ํ‘œ๋ฉด โ€” ๋ฐ์€ ์Šค๋ชจํฌ. ์•ŒํŒŒ๊ฐ€ ๊ณง ๋ฐ๊ธฐ ๋ฐ”๋‹ฅ์ด๋ผ ๋‹คํฌ๋ณด๋‹ค ๋ฌด๊ฑฐ์šด ๋‹ค์ด์–ผ์ด๋‹ค. + ์•ŒํŒŒ โ†“ = ๋ฐฐ๊ฒฝ ํˆฌ๊ณผ โ†‘ ์ธ ๊ฒƒ์€ ๊ฐ™์ง€๋งŒ, ์—ฌ๊ธฐ์„œ๋Š” ์•ŒํŒŒ๋ฅผ ๋‚ด๋ฆฌ๋ฉด ์–ด๋‘์šด ๋ฐฐ๊ฒฝ ์œ„์—์„œ + ์œ ๋ฆฌ๊ฐ€ ํšŒ์ƒ‰์œผ๋กœ ๊ฐ€๋ผ์•‰๋Š”๋‹ค. ํˆฌ๊ณผ๋ฅผ ๋Š˜๋ฆฌ๋ ค๋ฉด ์•ŒํŒŒ๋ณด๋‹ค backdrop contrast๋ฅผ ๋จผ์ € ๋งŒ์งˆ ๊ฒƒ. + ๋ฐ๊ธฐ ํ‹ฐ์–ด๋Š” ๋‹คํฌ์™€ ๊ฐ™์€ ์ˆœ์„œ๋‹ค: ์บ”๋ฒ„์Šค ์นฉ < ํŒจ๋„ < ์บ”๋ฒ„์Šค < ๋“œ๋กญ๋‹ค์šด < ๋ชจ๋‹ฌ */ + --ui-glass: rgba(242, 246, 251, 0.74); + /* ๋ชจ๋‹ฌยท์‹œํŠธยทํ”ผ์ปค์šฉ โ€” ์Šคํฌ๋ฆผ์ด ๋’ค๋ฅผ ๋ฎ์€ ๋’ค ์˜ฌ๋ผ์˜ค๋ฏ€๋กœ ํ‹ฐ์–ด ์ตœ์ƒ๋‹จ */ + --ui-glass-heavy: rgba(249, 251, 254, 0.76); + /* ์บ”๋ฒ„์Šค ์ƒ์ฃผ ํฌ๋กฌ์šฉ โ€” ๋ฐฐ๊ฒฝ๋ณด๋‹ค ํ•œ ํ†ค ๊ฐ€๋ผ์•‰์€ ์Šค๋ชจํฌ */ + --ui-glass-dim: rgba(214, 221, 231, 0.62); + /* ์‚ฌ์ด๋“œ ํŒจ๋„์šฉ โ€” ์บ”๋ฒ„์Šค๋ณด๋‹ค ์‚ด์ง ์•„๋ž˜์— ๋‘”๋‹ค. ์ƒ์ฃผ ํ‘œ๋ฉด์ด๋ผ ์—ฌ๊ธฐ๋ฅผ ํฐ ์ชฝ์œผ๋กœ + ์˜ฌ๋ฆฌ๋ฉด ํŒจ๋„ ์•ˆ 3๋‹จ(ํŒจ๋„/์นด๋“œ/์นฉ)์˜ ๋ฒ ์ด์Šค๊ฐ€ ํ†ต์งธ๋กœ ์ฒœ์žฅ์— ๋ถ™์–ด ์„ธ ๋ฉด์ด + ํ•œ ๋ฉ์–ด๋ฆฌ๋กœ ๋ญ‰๊ฐ ๋‹ค. ๋ฐ์€ ์ชฝ ๋์€ ๋‹คํฌ๋ณด๋‹ค ์—ฌ์œ ๊ฐ€ ํ›จ์”ฌ ์ ์–ด ์ด ๊ทœ์น™์ด ๋” ๋นก๋นกํ•˜๋‹ค. + ๋ถ„๋ฆฌ๋Š” ๋ฐ๊ธฐ๊ฐ€ ์•„๋‹ˆ๋ผ ์ธ์…‹ ๋ง+์ขŒ์ธก ๋“œ๋กญ์„€๋„๊ฐ€ ๋‹ด๋‹น */ + --ui-glass-panel: rgba(244, 248, 252, 0.74); + /* ์ƒ์ฃผ ํฌ๋กฌ ์†”๋ฆฌ๋“œ โ€” ๋ผ์ด๋ธŒ ๋ธ”๋Ÿฌ ๋Œ€์‹  ๊ณ ์•ŒํŒŒ ํ‹ดํŠธ๋กœ ๊ทผ์‚ฌ. + ๊ฐ’์€ ๋ผ์ด๋ธŒ ๊ธ€๋ž˜์Šค์™€ ๊ฐ™์€ L*์— ์„œ๋„๋ก ๋งž์ถฐ ๋‘  */ + --ui-glass-panel-solid: rgba(240, 244, 249, 0.96); + --ui-glass-dim-solid: rgba(217, 224, 233, 0.94); + + /* ํˆฌ๋ช…๋„ ์ถ•์†Œ ํด๋ฐฑ โ€” ๋ธ”๋Ÿฌ๊ฐ€ ์—†์œผ๋ฉด ๋ฐฐ๊ฒฝ ๋งฅ๋ฝ์ด ์ „ํ˜€ ์•ˆ ๋“ค์–ด์˜ค๋ฏ€๋กœ + ์ด ๊ฐ’ ์ž์ฒด๊ฐ€ ๋ชฉํ‘œ ํœ˜๋„์™€ ๋Œ€๋น„๋ฅผ ํ˜ผ์ž ๋งŒ์กฑํ•ด์•ผ ํ•œ๋‹ค. + ๋ผ์ดํŠธ์—์„œ๋Š” ๋ฃจ๋ฏธ๋…ธ์‹œํ‹ฐ ๋ ˆ์ด์–ด(::after)๋„ ํ•จ๊ป˜ ๊บผ์ง€๋Š”๋ฐ, ๋ฐ๊ธฐ ๋ฐ”๋‹ฅ์„ + ์›๋ž˜ ํ‹ดํŠธ๊ฐ€ ๊ฐ–๊ณ  ์žˆ์–ด์„œ ์†์‹ค์ด ๋‹คํฌ๋ณด๋‹ค ์ž‘๋‹ค */ + --ui-glass-reduced: rgba(243, 247, 252, 0.96); + --ui-glass-heavy-reduced: rgba(250, 252, 254, 0.97); + --ui-glass-dim-reduced: rgba(216, 223, 232, 0.94); + --ui-glass-panel-reduced: rgba(242, 246, 251, 0.96); + + /* ๊ธ€๋ž˜์Šค ๋ฐฑ๋“œ๋กญ ํด๋žจํ”„ โ€” ๋ผ์ดํŠธ์—์„œ๋Š” dim์ด ์ฃผ์—ญ์ด ์•„๋‹ˆ๋‹ค. + contrast๋ฅผ 1 ์•„๋ž˜๋กœ ๋‚ด๋ ค ๋ฐฐ๊ฒฝ์˜ ๋ฐ๊ณ  ์–ด๋‘์šด ๋์„ ์ค‘์•™์œผ๋กœ ๋ชจ์œผ๊ณ , + dim์€ ํ‰๊ท  ํœ˜๋„๋ฅผ ๋ฏธ์„ธ ์กฐ์ •ํ•˜๋Š” ์ •๋„๋กœ๋งŒ ์˜ฌ๋ฆฐ๋‹ค. + sat์€ 1์„ ๋„˜๊ธฐ์ง€ ์•Š๋Š”๋‹ค. ๋ฐ์€ ์œ ๋ฆฌ๊ฐ€ ์œ ์ƒ‰ ๋ฐฐ๊ฒฝ์„ ๊ทธ๋Œ€๋กœ ๋ฐ›์œผ๋ฉด ์–ผ๋ฃฉ์ง„ + ํ”Œ๋ผ์Šคํ‹ฑ์ฒ˜๋Ÿผ ์ฝํžŒ๋‹ค */ + --ui-glass-backdrop-dim: 1.06; + --ui-glass-backdrop-contrast: 0.9; + --ui-glass-backdrop-sat: 1; + /* canvas ์œ„ ๊ธ€๋ž˜์Šค ์ „์šฉ โ€” ์‚ฌ์šฉ์ž๊ฐ€ ๋งŒ๋“  ์ž„์˜ ๋ฐ๊ธฐยท์ฑ„๋„๊ฐ€ ์•„๋ž˜์— ๊น”๋ฆฐ๋‹ค. + ์–ด๋‘์šด ์˜ค๋ฒ„๋ ˆ์ด ์œ„์—์„œ ์œ ๋ฆฌ๊ฐ€ ํšŒ์ƒ‰์œผ๋กœ ๊ฐ€๋ผ์•‰์ง€ ์•Š๊ฒŒ ๋ฒ”์œ„ ์••์ถ•์„ ๊ฐ•ํ•˜๊ฒŒ, + ์ƒ‰์กฐ ์ง€๋ฐฐ ์–ต์ œ๋Š” ๋‹คํฌ์™€ ๊ฐ™์€ ์„ธ๊ธฐ๋กœ */ + --ui-glass-canvas-backdrop-dim: 1.1; + --ui-glass-canvas-backdrop-contrast: 0.74; + --ui-glass-canvas-backdrop-sat: 0.35; + /* ๋ฃจ๋ฏธ๋…ธ์‹œํ‹ฐ ๋ ˆ์ด์–ด โ€” ๋‹คํฌ์™€ ๊ฐ™์€ ์žฅ์น˜๊ฐ€ ๋ฐ˜๋Œ€ ๋ฐฉํ–ฅ์œผ๋กœ ์“ฐ์ธ๋‹ค. + ๋ฐ์€ ํ‹ดํŠธ๋ฅผ ์ฃผ๋ฉด ์–ด๋‘์šด ํ”ฝ์…€์ด ์˜ฌ๋ผ์˜จ๋‹ค. ๋‹ค๋งŒ ์ด๊ฑด ํ•˜ํ•œ์ด ์•„๋‹ˆ๋ผ + ๋ชฉํ‘œ ํœ˜๋„๋กœ ๋‹น๊ธฐ๋Š” ๋ณด์ •์ด๋ผ ๋ฐ์€ ํ”ฝ์…€์€ ์˜คํžˆ๋ ค ๋‚ด๋ ค์˜จ๋‹ค. + ์„ธ๊ฒŒ ๊ฑธ๋ฉด ๋ฐฐ๊ฒฝ ์ƒ‰๊ธฐ์šด์ด ํฐ ์ชฝ์œผ๋กœ ์„ธํƒ๋˜๋ฏ€๋กœ ๋‚ฎ๊ฒŒ ์œ ์ง€ํ•˜๊ณ , + ์‹ค์ œ ๋ฐ๊ธฐ ๋ฐ”๋‹ฅ์€ ์œ„์˜ ํ‹ดํŠธ ์•ŒํŒŒ์— ๋งก๊ธด๋‹ค */ + --ui-glass-backdrop-lum: 0.12; + /* ๋‹น๊ฒจ๊ฐˆ ๋ชฉํ‘œ ํœ˜๋„ โ€” --ui-glass ํ‹ดํŠธ์™€ ๊ฐ™์€ ์ƒ‰์„ ์œ ์ง€ํ•  ๊ฒƒ */ + --ui-glass-backdrop-lum-tint: #f2f6fb; + + /* ๋ชจ๋‹ฌ ์Šคํฌ๋ฆผ โ€” ๋ฐฉํ–ฅ์€ ๋’ค์ง‘์ง€ ์•Š๋Š”๋‹ค. ๋ฐ์€ ๋ฐฐ๊ฒฝ์„ ๋” ๋ฐ๊ฒŒ ๋ฎ์œผ๋ฉด ๋’ค๊ฐ€ ์ง€์›Œ์งˆ ๋ฟ + '๊ฐ€๋ ค์กŒ๋‹ค'๋กœ ์•ˆ ์ฝํžŒ๋‹ค. ๋Œ€์‹  ์•ŒํŒŒ๋ฅผ ํฌ๊ฒŒ ๋‚ด๋ฆฐ๋‹ค. + ๋ฐ์€ ๋ฐ”ํƒ•์—์„œ๋Š” ๊ฐ™์€ ์•ŒํŒŒ๊ฐ€ ํ›จ์”ฌ ๋ฌด๊ฒ๊ฒŒ ์•‰๋Š”๋‹ค */ + --ui-scrim: rgba(16, 24, 40, 0.34); + + /* ๊ฒฝ๊ณ„์„  โ€” ์žฅ์‹์šฉ ๊ธˆ์ง€, ๊ธฐ๋Šฅ์  ๊ตฌํš(๋ฐ”/ํ”Œ๋กœํŒ… ํฌ๋กฌ/ํผ ์ปจํŠธ๋กค)์—๋งŒ ์‚ฌ์šฉ. + ๋‹คํฌ์˜ ํฐ ์„ ์ด ํŒจ๋„ ์œ„์—์„œ ๋งŒ๋“œ๋Š” ๋‹จ์ฐจ(์•ฝ 11 L*)๋ฅผ ๋งž์ถ˜ ๊ฐ’์ด๋‹ค. + 0.1๋กœ ๋‚ด๋ฆฌ๋ฉด ๋‹คํฌ์—์„œ ๋ณด์ด๋˜ ๊ตฌํš์„ ์ด ๋ผ์ดํŠธ์—์„œ๋งŒ ์‚ฌ๋ผ์ง„๋‹ค */ + --ui-line: rgba(16, 24, 40, 0.12); + --ui-line-strong: rgba(16, 24, 40, 0.17); + /* ์บ”๋ฒ„์Šค/์Šคํ…Œ์ด์ง€ ๋ณด์กฐ ๊ฒฉ์ž์„  โ€” ๋ฉ”์ด์ €(--ui-line)๋ณด๋‹ค ํ•œ ํ†ค ํ๋ฆผ. + ์บ”๋ฒ„์Šค๊ฐ€ ํšŒ์ƒ‰์ด๋ผ ์ด ์•ŒํŒŒ์—์„œ ๊ฒฉ์ž๊ฐ€ ๋‹คํฌ์™€ ๊ฐ™์€ ์„ธ๊ธฐ๋กœ ์ฝํžŒ๋‹ค */ + --ui-line-faint: rgba(16, 24, 40, 0.085); + + /* ํˆฌ๋ช… ์ƒ‰์ƒ ์ฒด์ปค ๋ฌด๋Šฌ โ€” ๋ฐ์€ ํ‘œ๋ฉด ์œ„๋ผ ๋‘ ์นธ ๋ชจ๋‘ ํฐ ์ชฝ์œผ๋กœ ์˜ฌ๋ฆฐ๋‹ค. + ๋‹คํฌ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์“ฐ๋ฉด ์Šค์™€์น˜๊ฐ€ ๊ฒ€์€ ๊ฒฉ์ž๋กœ ํŠ„๋‹ค */ + --ui-checker-pattern: conic-gradient( + #cfd5dd 0.25turn, + #eaeef4 0.25turn 0.5turn, + #cfd5dd 0.5turn 0.75turn, + #eaeef4 0.75turn + ); + + /* ํฌ๋กฌ ํ•„ โ€” ์กฐ์ž‘๋ฉด์ด๋ผ ๋ผ์ดํŠธ์—์„œ๋„ ์ž‰ํฌ๊ฐ€ ๋Š˜์–ด๋‚˜๋Š” ๋ฐฉํ–ฅ์ด๋‹ค. + ์นด๋“œ ์œ„์—์„œ ํ•œ ์นธ์ด ์•ฝ 2.6~3.4 L*๋กœ, ๋‹คํฌ์˜ ์นธ ํญ๊ณผ ๋งž๋Š”๋‹ค */ + --ui-fill: rgba(16, 24, 40, 0.085); + --ui-fill-hover: rgba(16, 24, 40, 0.125); + --ui-fill-active: rgba(16, 24, 40, 0.165); + /* ์ •์  ๊ทธ๋ฃน ์ปจํ…Œ์ด๋„ˆ์šฉ โ€” ์—ฌ๊ธฐ๋งŒ ๋ฐฉํ–ฅ์ด ๋ฐ˜๋Œ€๋‹ค. ์ƒํ˜ธ์ž‘์šฉ ์ƒํƒœ๊ฐ€ ์•„๋‹ˆ๋ผ + ์นด๋“œ ๋ฉด์ด๊ณ , ์นด๋“œ๋Š” ๋‘ ํ…Œ๋งˆ ๋ชจ๋‘ ์•„๋ž˜ ๋ฉด๋ณด๋‹ค ๋– ์˜ฌ๋ผ์•ผ ํ•œ๋‹ค. + ํŒจ๋„ ์œ„์—์„œ +3.9 L*๋กœ ๋‹คํฌ์˜ +4.0๊ณผ ๊ฐ™์€ ๋‹จ์ฐจ */ + --ui-fill-faint: rgba(255, 255, 255, 0.58); + + /* ์ฝ”๋“œ ์—๋””ํ„ฐ ์Šคํฌ๋กค๋ฐ” ์ธ โ€” ์ข์€ 4px ๋ง‰๋Œ€๋ผ ํฌ๋กฌ ํ•„๋ณด๋‹ค ์ง„ํ•˜๊ฒŒ. + ๋ฐ์€ ์›ฐ ์œ„์—์„œ๋Š” ๋‹คํฌ๋ณด๋‹ค ์กฐ๊ธˆ ๋” ๋ฐ€์–ด์•ผ ๋ง‰๋Œ€๊ฐ€ ํ˜•ํƒœ๋กœ ์žกํžŒ๋‹ค */ + --ui-scrollbar-thumb: rgba(16, 24, 40, 0.2); + --ui-scrollbar-thumb-hover: rgba(16, 24, 40, 0.26); + --ui-scrollbar-thumb-active: rgba(16, 24, 40, 0.32); + + /* ํ…์ŠคํŠธ ์œ„๊ณ„ โ€” ๋‹คํฌ๊ฐ€ ์ˆœ๋ฐฑ์„ ํ”ผํ•˜๋“ฏ ์—ฌ๊ธฐ์„œ๋„ ์ˆœํ‘์„ ํ”ผํ•œ๋‹ค. ๋ฐฐ๊ฒฝ๊ณผ ๊ฐ™์€ + ๋ฐฉํ–ฅ์˜ ์ฟจ ํŽธํ–ฅ์„ ์ค˜ ๊ธ€์ž๋งŒ ๋ฌด์ฑ„์ƒ‰์œผ๋กœ ๋–  ๋ณด์ด์ง€ ์•Š๊ฒŒ ํ–ˆ๋‹ค. + ์นด๋“œ ์œ„ ๋Œ€๋น„๋Š” 13.5 / 6.3 / 4.5 / 2.6์œผ๋กœ ๋‹คํฌ(12.6 / 6.4 / 5.1 / 3.1)์™€ + ๊ฐ™์€ ๋ชจ์–‘์ด๋‹ค. ๋ฐ์€ ๋ฐ”ํƒ•์—์„œ๋Š” ๋Œ€๋น„๋น„๊ฐ€ ๋น ๋ฅด๊ฒŒ ์••์ถ•๋ผ ์•„๋ž˜ ๋‘ ๋‹จ์ด + ๋‹คํฌ๋งŒํผ ๋ฒŒ์–ด์ง€์ง€ ์•Š๋Š”๋‹ค - ๋Œ€๋น„๋น„๊ฐ€ ์•„๋‹ˆ๋ผ L* ๊ฐ„๊ฒฉ์„ ๋งž์ท„๋‹ค. + ๊บผ์ง ํ–‰ ์ด๋ฆ„(disabled)์ด ๋ณธ๋ฌธ ๊ธฐ์ค€์— ๋ชป ๋ฏธ์น˜๋Š” ๊ฒƒ์€ ๋‹คํฌ์™€ ๊ฐ™์€ ์˜๋„๋‹ค. + ๊ธฐ์ค€์„ ๋งž์ถ”๋ฉด muted์™€ ๊ตฌ๋ถ„์ด ์•ˆ ๋ผ '๊บผ์ง'์œผ๋กœ ์ฝํžˆ์ง€ ์•Š๋Š”๋‹ค */ + /* ํ˜ธ๋ฒ„์—์„œ ํ•œ ์นธ ๋” ์˜ฌ๋ฆด ์ž๋ฆฌ. ์—ฌ๊ธฐ์„œ '์˜ฌ๋ฆฐ๋‹ค'๋Š” ์ž‰ํฌ๋ฅผ ๋” ๋„ฃ๋Š”๋‹ค๋Š” ๋œป */ + --ui-fg-strong: #0d1219; + --ui-fg: #242a33; + --ui-fg-muted: #545c68; + --ui-fg-faint: #69727f; + --ui-fg-disabled: #949ca9; + + /* ์•ก์„ผํŠธ โ€” ์ƒ‰์ƒํ™˜ ์œ„์น˜๋Š” ๋ธŒ๋žœ๋“œ ๊ทธ๋Œ€๋กœ ๋‘๊ณ  ๋ช…๋„๋งŒ ๋‚ด๋ฆฐ๋‹ค. + ๋‹คํฌ์˜ ์›์ƒ‰์€ ํฐ ๋ฐ”ํƒ•์—์„œ 4.0:1๊นŒ์ง€ ๋–จ์–ด์ ธ ํฐ ๊ธ€์ž๋ฅผ ์–น๋Š” ๋ฒ„ํŠผ์œผ๋กœ ๋ชป ์“ด๋‹ค. + ํ˜ธ๋ฒ„ ๋ฐฉํ–ฅ๋„ ๋‹คํฌ์™€ ๋ฐ˜๋Œ€๋‹ค. ๋ฐ์€ ๋ฐ”ํƒ•์—์„œ ์ƒํƒœ๊ฐ€ ์„ธ์ง€๋Š” ๊ฒƒ์€ ์ž‰ํฌ๊ฐ€ ๋Š˜์–ด๋‚˜๋Š” ๊ฒƒ */ + --ui-accent: #7c47e8; + --ui-accent-hover: #6f3ade; + --ui-accent-active: #6230cc; + --ui-accent-muted: rgba(124, 71, 232, 0.13); + --ui-accent-fg: #ffffff; + /* ํฐ ๋ฉด์  CTA์šฉ โ€” ๋„“์€ ์ฑ„๋„๋ฉด์€ ๋ฐ์€ ๋ฐ”ํƒ•์—์„œ๋„ ๋ˆˆ์„ ์ฐŒ๋ฅธ๋‹ค. ํ•œ ๋‹จ๊ณ„ ๋” ๊นŠ๊ฒŒ */ + --ui-accent-deep: #6b39d8; + --ui-accent-deep-hover: #5d2bca; + --ui-accent-deep-active: #5225b4; + + /* ํ† ๊ธ€ ๋…ธ๋ธŒ์™€ ๊บผ์ง ํŠธ๋ž™. ๋…ธ๋ธŒ๋Š” ๋‘ ํ…Œ๋งˆ ๋ชจ๋‘ ํฐ์ƒ‰์ด๋‹ค - ์ผœ์ง์€ ์•ก์„ผํŠธ, + ๊บผ์ง์€ ํšŒ์ƒ‰ ํŠธ๋ž™์ด๋ผ ์–ด๋А ์ชฝ์—์„œ๋„ ํฐ ๋…ธ๋ธŒ๊ฐ€ ํŠธ๋ž™ ์œ„๋กœ ๋– ์˜ค๋ฅธ๋‹ค */ + --ui-control-knob: #ffffff; + --ui-control-track-off: rgba(16, 24, 40, 0.15); + --ui-control-track-off-hover: rgba(16, 24, 40, 0.21); + + /* ์บ”๋ฒ„์Šค ํ•ธ๋“ค โ€” ๋‹คํฌ์™€ ๊ฐ™์€ ๊ฐ’์ด๋‹ค. ์•„๋ž˜ ๊น”๋ฆฌ๋Š” ๊ฒƒ์ด ์•ฑ ํ‘œ๋ฉด์ด ์•„๋‹ˆ๋ผ + ์‚ฌ์šฉ์ž๊ฐ€ ๋งŒ๋“  ์ž„์˜ ์ฝ˜ํ…์ธ ๋ผ ์•ฑ ํ…Œ๋งˆ๋กœ ๋ฐ๊ธฐ๋ฅผ ์ •ํ•  ๊ทผ๊ฑฐ๊ฐ€ ์—†๋‹ค. + ๋ถ„๋ฆฌ๋Š” ์ฑ„์›€์ƒ‰์ด ์•„๋‹ˆ๋ผ ๋ง๊ณผ ๋“œ๋กญ์„€๋„๊ฐ€ ๋‹ด๋‹น */ + --ui-handle-fill: #ffffff; + --ui-handle-ring: rgba(0, 0, 0, 0.35); + --ui-handle-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + + /* ์บ”๋ฒ„์Šค ์ •๋ ฌ ๊ฐ€์ด๋“œ โ€” ์„ ํƒ ํฌ๋กฌ(๋ฐ”์ด์˜ฌ๋ ›)๊ณผ ์ƒ‰์œผ๋กœ ๊ฐˆ๋ผ์•ผ ํ•˜๋Š” ๋ณ„๊ฐœ ์‹ ํ˜ธ. + ๋ฐ์€ ์บ”๋ฒ„์Šค์—์„œ ์—ฐํ•œ ์‚ฐํ˜ธ์ƒ‰์€ ๋ฐฐ๊ฒฝ์— ์ž ๊ธฐ๋ฏ€๋กœ ์ฑ„๋„๋ฅผ ์œ ์ง€ํ•œ ์ฑ„ ๋ช…๋„๋ฅผ ๋‚ด๋ฆฐ๋‹ค */ + --ui-guide-align: #e0393f; + --ui-guide-align-glow: rgba(224, 57, 63, 0.45); + + /* ์บ”๋ฒ„์Šค ์„ ํƒ ํฌ๋กฌ โ€” ์…€๋ ‰์…˜์€ ์˜๋ฏธ ์ง€์ ์ด๋ฏ€๋กœ ๋ธŒ๋žœ๋“œ ๋ฐ”์ด์˜ฌ๋ › */ + --ui-selection: #7c47e8; + --ui-selection-border: rgba(124, 71, 232, 0.8); + --ui-selection-border-strong: rgba(124, 71, 232, 0.9); + --ui-selection-fill: rgba(124, 71, 232, 0.13); + --ui-selection-glow: rgba(124, 71, 232, 0.45); + + /* ์‹œ๋งจํ‹ฑ โ€” ์ „๋ถ€ ๋ช…๋„๋ฅผ ๋‚ด๋ ค ํฐ ๋ฐ”ํƒ•์—์„œ 4.5:1์„ ๋„˜๊ธด๋‹ค. + muted ๊ณ„์—ด์€ ๋ฐฐ๊ฒฝ ํ•„์ด๋ผ ์•ŒํŒŒ๋งŒ ์‚ด์ง ๋‚ฎ์ถ˜๋‹ค. ๊ฒ€์ • ์ž‰ํฌ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ + ๋ฐ์€ ๋ฉด ์œ„์˜ ์œ ์ƒ‰ ํ•„๋„ ๊ฐ™์€ ์•ŒํŒŒ์—์„œ ๋” ๋ฌด๊ฒ๊ฒŒ ์•‰๋Š”๋‹ค */ + --ui-danger: #d0353a; + --ui-danger-hover: #bd2c31; + --ui-danger-active: #a82428; + --ui-danger-muted: rgba(208, 53, 58, 0.11); + --ui-danger-muted-hover: rgba(208, 53, 58, 0.16); + --ui-danger-muted-active: rgba(208, 53, 58, 0.21); + --ui-danger-fg: #b62a2f; + --ui-success: #1a8a52; + --ui-success-muted: rgba(26, 138, 82, 0.12); + --ui-warning: #a56a00; + --ui-warning-muted: rgba(165, 106, 0, 0.12); + + /* ์ฝ”๋“œ ์—๋””ํ„ฐ ๊ตฌ๋ฌธ ์ƒ‰ โ€” ๋ถˆํˆฌ๋ช… ์›ฐ(--ui-bg-inset-solid) ์œ„ ๊ธฐ์ค€. + ์ƒ‰์ƒํ™˜ ์œ„์น˜๋Š” ๋‹คํฌ์™€ ๋งž์ถ”๊ณ  ๋ช…๋„๋งŒ ๋‚ด๋ ค ์—ญํ•  ์ธ์ง€๊ฐ€ ์œ ์ง€๋˜๊ฒŒ ํ•œ๋‹ค */ + --ui-syntax-comment: #4c7a4a; + --ui-syntax-string: #a63c1e; + --ui-syntax-keyword: #8b2ea3; + --ui-syntax-property: #1a5fa8; + --ui-syntax-punctuation: #3a3a3e; + + /* ๊ทธ๋ฆผ์ž โ€” ๋ฆฝ์˜ ์ฃผ์ธ์ด ๋ฐ”๋€๋‹ค. + ๋‘˜๋ ˆ ๋ง์€ ํ‘œ๋ฉด ๊ฒฝ๊ณ„๋ผ ๋ฐ์€ ๋ฉด ์œ„์—์„œ๋Š” ์–ด๋‘์šด ์ž‰ํฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ์•ŒํŒŒ 0.10์€ + ๋‹คํฌ์˜ ํฐ ๋ง์ด ๋งŒ๋“œ๋Š” ๋‹จ์ฐจ(+8.2 L*)๋ฅผ ๋ผ์ดํŠธ์—์„œ ์žฌํ˜„ํ•œ ๊ฐ’(-7.7 L*)์ด๋‹ค. + ์ƒ๋‹จ ํ•˜์ด๋ผ์ดํŠธ๋Š” ๊ฒฝ๊ณ„์„ ์ด ์•„๋‹ˆ๋ผ ์œ„์—์„œ ๋“ค์–ด์˜ค๋Š” ๋ฐ˜์‚ฌ๊ด‘์ด๋ผ ํฐ์ƒ‰์„ ์œ ์ง€ํ•œ๋‹ค. + ํ…Œ๋งˆ๊ฐ€ ๋ฐ”๋€๋‹ค๊ณ  ๊ด‘์› ๋ฐฉํ–ฅ์ด ๋’ค์ง‘ํžˆ์ง€๋Š” ์•Š๋Š”๋‹ค. ๋‘˜ ๋‹ค ์–ด๋‘ก๊ฒŒ ๋งŒ๋“ค๋ฉด ์ƒ๋‹จ์— + ๋‘ ์„ ์ด ๊ฒน์ณ ๋–  ์žˆ๋Š” ์œ ๋ฆฌ๊ฐ€ ์•„๋‹ˆ๋ผ ํŒŒ์ธ ํ™ˆ์œผ๋กœ ์ฝํžŒ๋‹ค. + ์œ ๋ฆฌ ํ‹ดํŠธ๋ฅผ ํฐ์ƒ‰๊นŒ์ง€ ์˜ฌ๋ฆฌ์ง€ ์•Š์€ ๋•์— ์ด ํ•˜์ด๋ผ์ดํŠธ๊ฐ€ ์‹ค์ œ๋กœ ๋ณด์ธ๋‹ค - + ํ‹ดํŠธ๊ฐ€ ์ฒœ์žฅ์— ๋ถ™์œผ๋ฉด ๋ฆฝ์˜ ์œ„์ชฝ ์ ˆ๋ฐ˜์ด ํ†ต์งธ๋กœ ์‚ฌ๋ผ์ง„๋‹ค. + + ๋“œ๋กญ์„€๋„๋Š” ์•ŒํŒŒ๋ฅผ ํฌ๊ฒŒ ๋‚ด๋ฆฐ๋‹ค. ๊ฐ™์€ ฮฑ0.45๋ผ๋„ ๊ทผํ‘ ์œ„์—์„œ๋Š” sRGB ๋ณ€ํ™”๋Ÿ‰์ด + 12 ๋‚จ์ง“์ธ๋ฐ ๋ฐ์€ ๋ฉด ์œ„์—์„œ๋Š” 100์„ ๋„˜์–ด ๊ฒ€์€ ์–ผ๋ฃฉ์ด ๋œ๋‹ค. + ๋ฐ˜๊ฒฝ์€ ์œ ์ง€ํ•˜๊ฑฐ๋‚˜ ๋„“ํ˜€ ๋ฐ€๋„๋ฅผ ๋ถ„์‚ฐ์‹œํ‚ค๊ณ , ์ ‘์ด‰ ๊ทธ๋ฆผ์ž ์ชฝ์— ํž˜์„ ์กฐ๊ธˆ ๋” ์ค€๋‹ค */ + --ui-shadow-1: 0 1px 2px rgba(16, 24, 40, 0.1); + --ui-shadow-2: inset 0 0 0 1px rgba(16, 24, 40, 0.12), + inset 0 1px 0 0 rgba(255, 255, 255, 0.9), 0 8px 24px rgba(16, 24, 40, 0.11), + 0 2px 6px rgba(16, 24, 40, 0.07); + /* 3์€ ๋ชจ๋‹ฌ ์นด๋“œ - ์Šคํฌ๋ฆผ์ด ์ด๋ฏธ ๋’ค๋ฅผ ์–ด๋‘กํžˆ๋ฏ€๋กœ ๋ฒˆ์ง์€ popup๋ณด๋‹ค ํ•œ ๋‹จ๊ณ„๋งŒ ๋„“๊ฒŒ */ + --ui-shadow-3: inset 0 0 0 1px rgba(16, 24, 40, 0.12), + inset 0 1px 0 0 rgba(255, 255, 255, 0.9), 0 12px 32px rgba(16, 24, 40, 0.13), + 0 3px 8px rgba(16, 24, 40, 0.08); + + /* ์ผ์‹œ popup - ์ธ์ ‘ control์„ ๋ฎ์ง€ ์•Š๊ฒŒ lip์€ ์œ ์ง€ํ•˜๊ณ  ์™ธ๋ถ€ ๋ฒˆ์ง๋งŒ ์ถ•์†Œ */ + --ui-shadow-popup: inset 0 0 0 1px rgba(16, 24, 40, 0.12), + inset 0 1px 0 0 rgba(255, 255, 255, 0.9), 0 4px 14px rgba(16, 24, 40, 0.11), + 0 1px 3px rgba(16, 24, 40, 0.07); + + /* ์‚ฌ์ด๋“œ ํŒจ๋„ โ€” ๋„ํ‚น ๊ฒฝ๊ณ„์ธ ์ขŒ์ธก ์—ฃ์ง€๋งŒ ํ—ค์–ด๋ผ์ธ, ์ขŒ์ธก ๋ฐฉํ–ฅ์„ฑ ๋“œ๋กญ์œผ๋กœ ์บ”๋ฒ„์Šค์™€ ๋ถ„๋ฆฌ */ + --ui-shadow-panel: inset 1px 0 0 0 rgba(16, 24, 40, 0.18), + -14px 0 34px rgba(16, 24, 40, 0.14); + + /* ์ƒ์ฃผ ํฌ๋กฌ โ€” ์ธ์…‹ ๋ง + ์ƒ๋‹จ ํ•˜์ด๋ผ์ดํŠธ๊ฐ€ ๋ณด๋”ยท์žฌ์งˆ ๋‹ด๋‹น, ๋“œ๋กญ์„€๋„๋Š” ํ”์ ๋งŒ */ + --ui-shadow-chrome: inset 0 0 0 1px rgba(16, 24, 40, 0.12), + inset 0 1px 0 0 rgba(255, 255, 255, 0.9), 0 1px 3px rgba(16, 24, 40, 0.08); + + /* ํฌ์ปค์Šค ๋ง โ€” ํ‘œ๋ฉด ์œ„ 3:1 ํ•˜ํ•œ์€ ๊ทธ๋Œ€๋กœ๋‹ค. ๋ฐ์€ ํ‘œ๋ฉด์—์„œ๋Š” ์•ก์„ผํŠธ ์›์ƒ‰์ด + ์•„๋‹ˆ๋ผ ํ•œ ๋‹จ๊ณ„ ๊นŠ์€ ๋ณด๋ผ๋ผ์•ผ ๋ง์ด ๋ฉด์— ์•ˆ ์ž ๊ธด๋‹ค */ + --ui-focus-ring: 0 0 0 1.5px rgba(109, 56, 218, 0.9); + + /* ํ™•์ •ํ•  ์ˆ˜ ์—†๋Š” ์ž…๋ ฅ - ํฌ์ปค์Šค ๋ง๊ณผ ๊ฐ™์€ ์ž๋ฆฌ, ๊ฐ™์€ ๋‘๊ป˜๋กœ ๊ทธ๋ ค ์ƒ‰๋งŒ ๋ฐ”๋€๋‹ค */ + --ui-danger-ring: 0 0 0 1.5px rgba(196, 45, 50, 0.9); +} diff --git a/src/renderer/windows/main/index.html b/src/renderer/windows/main/index.html index c5e02528a..5d326363d 100644 --- a/src/renderer/windows/main/index.html +++ b/src/renderer/windows/main/index.html @@ -1,9 +1,9 @@ - + - + + - +
diff --git a/src/renderer/windows/obs/index.html b/src/renderer/windows/obs/index.html index 87e41bf0f..35d6d3a45 100644 --- a/src/renderer/windows/obs/index.html +++ b/src/renderer/windows/obs/index.html @@ -1,6 +1,9 @@ - + + DM NOTE OBS diff --git a/src/renderer/windows/overlay/index.html b/src/renderer/windows/overlay/index.html index 9096d7b0c..9f0d6e527 100644 --- a/src/renderer/windows/overlay/index.html +++ b/src/renderer/windows/overlay/index.html @@ -1,6 +1,9 @@ - + + diff --git a/tailwind.config.js b/tailwind.config.js index fadd99ad5..c7938707d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -37,10 +37,21 @@ module.exports = { }, fg: { DEFAULT: 'var(--ui-fg)', + strong: 'var(--ui-fg-strong)', muted: 'var(--ui-fg-muted)', faint: 'var(--ui-fg-faint)', disabled: 'var(--ui-fg-disabled)', }, + scrim: 'var(--ui-scrim)', + control: { + knob: 'var(--ui-control-knob)', + 'track-off': 'var(--ui-control-track-off)', + 'track-off-hover': 'var(--ui-control-track-off-hover)', + }, + handle: { + DEFAULT: 'var(--ui-handle-fill)', + ring: 'var(--ui-handle-ring)', + }, accent: { DEFAULT: 'var(--ui-accent)', hover: 'var(--ui-accent-hover)', @@ -96,6 +107,7 @@ module.exports = { 'elevation-popup': 'var(--ui-shadow-popup)', 'elevation-chrome': 'var(--ui-shadow-chrome)', 'elevation-panel': 'var(--ui-shadow-panel)', + handle: 'var(--ui-handle-shadow)', 'focus-ring': 'var(--ui-focus-ring)', 'danger-ring': 'var(--ui-danger-ring)', }, From c378ab2042bcc5b9eb11f00cc34734aabc443f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=97=B0=EC=9A=B0?= Date: Sat, 29 Aug 2026 02:37:38 +0900 Subject: [PATCH 03/11] =?UTF-8?q?refactor:=20=EC=95=B1=20=ED=81=AC?= =?UTF-8?q?=EB=A1=AC=20=EC=83=89=EC=83=81=EA=B0=92=EC=9D=84=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EA=B4=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/Grid/handles/GradientAxisHandle.tsx | 18 +++++++++--------- .../main/Grid/handles/GroupResizeHandles.tsx | 6 ++++-- .../main/Grid/handles/ResizeHandles.test.tsx | 4 ++-- .../main/Grid/handles/ResizeHandles.tsx | 4 +++- .../editors/CounterAnimationEditorModal.tsx | 2 +- .../content/pickers/CommonListPickerPage.tsx | 2 +- .../main/Modal/content/pickers/GooeyThumb.tsx | 4 +++- .../content/pickers/GradientFormatControls.tsx | 6 ++---- .../content/pickers/WebFontInputModal.tsx | 16 +++++++++++----- .../components/main/common/Checkbox.tsx | 6 ++++-- src/renderer/utils/plugin/pluginComponents.ts | 4 ++-- 11 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/renderer/components/main/Grid/handles/GradientAxisHandle.tsx b/src/renderer/components/main/Grid/handles/GradientAxisHandle.tsx index 7a5853fd5..f1276f42f 100644 --- a/src/renderer/components/main/Grid/handles/GradientAxisHandle.tsx +++ b/src/renderer/components/main/Grid/handles/GradientAxisHandle.tsx @@ -830,8 +830,8 @@ const GradientAxisOverlay = ({ width: ANCHOR_DOT_SIZE, height: ANCHOR_DOT_SIZE, borderRadius: '50%', - background: 'white', - boxShadow: '0 1px 3px rgba(0,0,0,0.5)', + background: 'var(--ui-handle-fill)', + boxShadow: 'var(--ui-handle-shadow)', }} /> @@ -853,8 +853,8 @@ const GradientAxisOverlay = ({ width: STOP_ANCHOR_DOT_SIZE, height: STOP_ANCHOR_DOT_SIZE, borderRadius: '50%', - background: 'white', - boxShadow: '0 1px 3px rgba(0,0,0,0.5)', + background: 'var(--ui-handle-fill)', + boxShadow: 'var(--ui-handle-shadow)', transform: 'translate(-50%, -50%)', pointerEvents: 'none', }} @@ -875,11 +875,11 @@ const GradientAxisOverlay = ({ borderRadius: 4, // ๋ฐ˜ํˆฌ๋ช… ์ƒ‰์€ ๊ฒฉ์ž ์œ„ ํ•ฉ์„ฑ์œผ๋กœ ํ‘œ์‹œ - ๋’ค ์š”์†Œ ๋น„์นจ ๋ฐฉ์ง€ background: `linear-gradient(${stop.color}, ${stop.color}), var(--ui-checker-pattern) center / var(--ui-checker-size-sm) var(--ui-checker-size-sm) repeat`, - border: '1.5px solid white', + border: '1.5px solid var(--ui-handle-fill)', boxShadow: i === session.selectedIndex - ? '0 0 0 2px var(--ui-selection-border-strong), 0 1px 4px rgba(0,0,0,0.5)' - : '0 1px 4px rgba(0,0,0,0.5)', + ? '0 0 0 2px var(--ui-selection-border-strong), var(--ui-handle-shadow)' + : 'var(--ui-handle-shadow)', transform: 'translate(-50%, -50%)', cursor: dragStop?.index === i ? 'grabbing' : 'default', pointerEvents: 'auto', @@ -900,7 +900,7 @@ const GradientAxisOverlay = ({ top: endY - 24, padding: '2px 6px', borderRadius: 6, - background: 'var(--ui-bg-inset-solid, rgba(24,24,29,0.9))', + background: 'var(--ui-bg-inset-solid)', boxShadow: 'inset 0 0 0 1px var(--ui-line)', whiteSpace: 'nowrap', }} @@ -918,7 +918,7 @@ const GradientAxisOverlay = ({ top: dragStopScreen.y - 24, padding: '2px 6px', borderRadius: 6, - background: 'var(--ui-bg-inset-solid, rgba(24,24,29,0.9))', + background: 'var(--ui-bg-inset-solid)', boxShadow: 'inset 0 0 0 1px var(--ui-line)', whiteSpace: 'nowrap', }} diff --git a/src/renderer/components/main/Grid/handles/GroupResizeHandles.tsx b/src/renderer/components/main/Grid/handles/GroupResizeHandles.tsx index 616cb368d..e3c36dfb0 100644 --- a/src/renderer/components/main/Grid/handles/GroupResizeHandles.tsx +++ b/src/renderer/components/main/Grid/handles/GroupResizeHandles.tsx @@ -150,7 +150,9 @@ const getHandleStyle = ( isHovered: boolean, ): React.CSSProperties => { const baseStyle: React.CSSProperties = { - backgroundColor: isHovered ? 'var(--ui-selection)' : 'white', + backgroundColor: isHovered + ? 'var(--ui-selection)' + : 'var(--ui-handle-fill)', border: '2px solid var(--ui-selection-border-strong)', pointerEvents: 'none', transition: 'background-color 0.15s ease', @@ -875,7 +877,7 @@ const GroupResizeHandles = ({ top: bounds.y * zoom + panY - 2, width: bounds.width * zoom + 4, height: bounds.height * zoom + 4, - border: '2px dashed rgba(251, 146, 60, 0.9)', + border: '2px dashed var(--ui-warning)', borderRadius: '4px', pointerEvents: 'none' as const, zIndex: 'var(--z-canvas-selection-handle)', diff --git a/src/renderer/components/main/Grid/handles/ResizeHandles.test.tsx b/src/renderer/components/main/Grid/handles/ResizeHandles.test.tsx index 92facb413..ca1252583 100644 --- a/src/renderer/components/main/Grid/handles/ResizeHandles.test.tsx +++ b/src/renderer/components/main/Grid/handles/ResizeHandles.test.tsx @@ -265,7 +265,7 @@ describe('ResizeHandles ํ•ธ๋“ค ํ˜ธ๋ฒ„ ์ƒ๋ช…์ฃผ๊ธฐ', () => { expect(hasCursorBodyClass()).toBe(false); // ์‹œ๊ฐ์  ํ˜ธ๋ฒ„ ์ƒํƒœ๋„ ์ผœ์ง€์ง€ ์•Š์Œ const visual = handle.firstElementChild as HTMLElement; - expect(visual.style.backgroundColor).toBe('white'); + expect(visual.style.backgroundColor).toBe('var(--ui-handle-fill)'); }); it('์–ต์ œ ์ค‘ enter๋Š” resume ์‹œ hover๋กœ ์ ์šฉ๋œ๋‹ค', async () => { @@ -306,7 +306,7 @@ describe('ResizeHandles ํ•ธ๋“ค ํ˜ธ๋ฒ„ ์ƒ๋ช…์ฃผ๊ธฐ', () => { expect(hasCursorBodyClass()).toBe(false); const visual = handle.firstElementChild as HTMLElement; - expect(visual.style.backgroundColor).toBe('white'); + expect(visual.style.backgroundColor).toBe('var(--ui-handle-fill)'); }); it('๋ณด๋ฅ˜ ์ค‘ ํ•ธ๋“ค์ด unmount๋˜๋ฉด pending๋„ ์ •๋ฆฌํ•œ๋‹ค', async () => { diff --git a/src/renderer/components/main/Grid/handles/ResizeHandles.tsx b/src/renderer/components/main/Grid/handles/ResizeHandles.tsx index 7bb0ae95c..f0e0087df 100644 --- a/src/renderer/components/main/Grid/handles/ResizeHandles.tsx +++ b/src/renderer/components/main/Grid/handles/ResizeHandles.tsx @@ -143,7 +143,9 @@ const getHandleStyle = ( isHovered: boolean, ): React.CSSProperties => { const baseStyle: React.CSSProperties = { - backgroundColor: isHovered ? 'var(--ui-selection)' : 'white', + backgroundColor: isHovered + ? 'var(--ui-selection)' + : 'var(--ui-handle-fill)', border: '1.5px solid var(--ui-selection-border-strong)', pointerEvents: 'none', transition: 'background-color 0.15s ease', diff --git a/src/renderer/components/main/Modal/content/editors/CounterAnimationEditorModal.tsx b/src/renderer/components/main/Modal/content/editors/CounterAnimationEditorModal.tsx index c67530c0f..a5e0e9a1d 100644 --- a/src/renderer/components/main/Modal/content/editors/CounterAnimationEditorModal.tsx +++ b/src/renderer/components/main/Modal/content/editors/CounterAnimationEditorModal.tsx @@ -1045,7 +1045,7 @@ const CounterAnimationEditorModal = ({ cy={p2w.y} r={HANDLE_RADIUS * uns} fill="var(--ui-bg-inset-solid)" - stroke="rgba(255, 255, 255, 0.85)" + stroke="var(--ui-handle-fill)" strokeWidth={2 * uns} style={{ pointerEvents: 'none' }} /> diff --git a/src/renderer/components/main/Modal/content/pickers/CommonListPickerPage.tsx b/src/renderer/components/main/Modal/content/pickers/CommonListPickerPage.tsx index 74142ac6f..f7c9cc544 100644 --- a/src/renderer/components/main/Modal/content/pickers/CommonListPickerPage.tsx +++ b/src/renderer/components/main/Modal/content/pickers/CommonListPickerPage.tsx @@ -147,7 +147,7 @@ export default function CommonListPickerPage({ strokeLinejoin="round" /> - + {pageTitle} diff --git a/src/renderer/components/main/Modal/content/pickers/GooeyThumb.tsx b/src/renderer/components/main/Modal/content/pickers/GooeyThumb.tsx index b9d1cb06f..bb041f34d 100644 --- a/src/renderer/components/main/Modal/content/pickers/GooeyThumb.tsx +++ b/src/renderer/components/main/Modal/content/pickers/GooeyThumb.tsx @@ -11,6 +11,8 @@ const GOO_OFFSET = -6; // erode์˜ ์‚ฌ๊ฐ ์ปค๋„๊ณผ ๋‹ฌ๋ฆฌ ๋ฐฉํ–ฅ์— ์ƒ๊ด€์—†์ด ๋ง ๋‘๊ป˜๋งŒํผ ๋“ค์–ด์˜จ๋‹ค const INSET_BLUR = 1.5; const INSET_CONTRAST = 20; +// ๋…ธ๋ธŒ ์•ˆ์ชฝ ์•ŒํŒŒ ํƒ€์ผ. ๋ง์ด ๋‘ ํ…Œ๋งˆ ๋ชจ๋‘ ํฐ์ƒ‰์ด๋ผ ํƒ€์ผ๋„ ๊ณ ์ •์ด๋‹ค - +// feImage ๋ฐ์ดํ„ฐ URI๋ผ CSS ๋ณ€์ˆ˜๋ฅผ ๋ชป ๋ฐ›๋Š” ๊ฒƒ๋„ ๊ฐ™์€ ๊ฒฐ๋ก ์„ ๊ฐ€๋ฆฌํ‚จ๋‹ค const CHECKER_CELL = 4; const CHECKER_TILE = `data:image/svg+xml,${encodeURIComponent( ` + diff --git a/src/renderer/components/main/Modal/content/pickers/GradientFormatControls.tsx b/src/renderer/components/main/Modal/content/pickers/GradientFormatControls.tsx index 740d095b4..a3ca2948b 100644 --- a/src/renderer/components/main/Modal/content/pickers/GradientFormatControls.tsx +++ b/src/renderer/components/main/Modal/content/pickers/GradientFormatControls.tsx @@ -264,10 +264,8 @@ export const GradientStopEditor = ({ style={{ left: `${stop.pos * 100}%` }} > { }; const WEBFONT_EDITOR_HIGHLIGHT_STYLE = HighlightStyle.define([ - { tag: tags.comment, color: '#6A9955' }, - { tag: [tags.string, tags.special(tags.string)], color: '#CE9178' }, - { tag: tags.keyword, color: '#C586C0' }, - { tag: [tags.propertyName], color: '#9CDCFE' }, - { tag: [tags.bracket, tags.punctuation], color: '#D4D4D4' }, + { tag: tags.comment, color: 'var(--ui-syntax-comment)' }, + { + tag: [tags.string, tags.special(tags.string)], + color: 'var(--ui-syntax-string)', + }, + { tag: tags.keyword, color: 'var(--ui-syntax-keyword)' }, + { tag: [tags.propertyName], color: 'var(--ui-syntax-property)' }, + { + tag: [tags.bracket, tags.punctuation], + color: 'var(--ui-syntax-punctuation)', + }, ]); // ํ™œ์„ฑ ์ค„ ํ•˜์ด๋ผ์ดํŠธ๋Š” ์ œ์™ธ โ€” ์—ฌ๋Ÿฌ ์ค„ placeholder๊ฐ€ ํ•œ ์ค„์— ๋‹ด๊ฒจ diff --git a/src/renderer/components/main/common/Checkbox.tsx b/src/renderer/components/main/common/Checkbox.tsx index 64bcfd1df..529e71ad5 100644 --- a/src/renderer/components/main/common/Checkbox.tsx +++ b/src/renderer/components/main/common/Checkbox.tsx @@ -63,14 +63,16 @@ const Checkbox = ({ role="switch" aria-checked={visualChecked} className={`dmn-toggle-track relative w-[28px] h-[16px] rounded-full cursor-pointer transition-colors duration-base ease-out-expo ${ - displayChecked ? 'bg-accent' : 'bg-line-strong hover:bg-white/[0.19]' + displayChecked + ? 'bg-accent' + : 'bg-control-track-off hover:bg-control-track-off-hover' }`} onClick={handleClick} {...drag.handlers} > {/* ์ด๋™๋Ÿ‰์€ --ui-toggle-travel์ด ์†Œ์œ , ํŠธ๋ž™ 28 - ๋…ธ๋ธŒ 12 - ์ธ์…‹ 2ร—2. ๋“œ๋ž˜๊ทธ ํ›…์ด ์ด ํด๋ž˜์Šค๋กœ ๋…ธ๋ธŒ๋ฅผ ์ฐพ์œผ๋ฏ€๋กœ ํด๋ž˜์Šค๋ช…์€ ๊ณ„์•ฝ */} -
+
); }; diff --git a/src/renderer/utils/plugin/pluginComponents.ts b/src/renderer/utils/plugin/pluginComponents.ts index 45098271c..4dd397b34 100644 --- a/src/renderer/utils/plugin/pluginComponents.ts +++ b/src/renderer/utils/plugin/pluginComponents.ts @@ -132,7 +132,7 @@ export function createCheckbox(options: CheckboxOptions = {}): string { const { checked = false, onChange, id = '' } = options; // ๋ฉ”์ธ UI Checkbox์™€ ๋™์ผํ•œ ํฌ๊ธฐยทํ† ํฐ (30x18, ์•ก์„ผํŠธ/line-strong ํŠธ๋ž™, 14px ํฐ ๋…ธ๋ธŒ) - const bgClass = checked ? 'bg-accent' : 'bg-line-strong'; + const bgClass = checked ? 'bg-accent' : 'bg-control-track-off'; const knobClass = checked ? 'left-[14px]' : 'left-[2px]'; // ํ•ธ๋“ค๋Ÿฌ ์ฒ˜๋ฆฌ: ํ•จ์ˆ˜๋ฉด ๋“ฑ๋ก, ๋ฌธ์ž์—ด์ด๋ฉด ๋ ˆ๊ฑฐ์‹œ ๋ฐฉ์‹ @@ -162,7 +162,7 @@ export function createCheckbox(options: CheckboxOptions = {}): string { -
+
`; } From 8d977c5033fa3117e141f568484289f70d98b78f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=97=B0=EC=9A=B0?= Date: Sat, 29 Aug 2026 02:37:38 +0900 Subject: [PATCH 04/11] =?UTF-8?q?fix:=20=EC=A0=91=ED=9E=98=20=ED=86=A0?= =?UTF-8?q?=EA=B8=80=20=EC=B9=A9=20=EC=9E=AC=EC=A7=88=EC=9D=84=20=EB=AF=B8?= =?UTF-8?q?=EB=8B=88=EB=A7=B5=EA=B3=BC=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/Grid/PropertiesPanel/panelChrome.test.ts | 10 +++++++++- .../main/Grid/PropertiesPanel/panelChrome.ts | 8 ++++++-- src/renderer/components/main/Grid/core/GridMinimap.tsx | 6 +++--- .../components/main/Grid/core/minimapChrome.ts | 7 +++++++ 4 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 src/renderer/components/main/Grid/core/minimapChrome.ts diff --git a/src/renderer/components/main/Grid/PropertiesPanel/panelChrome.test.ts b/src/renderer/components/main/Grid/PropertiesPanel/panelChrome.test.ts index 301472a9d..69459d177 100644 --- a/src/renderer/components/main/Grid/PropertiesPanel/panelChrome.test.ts +++ b/src/renderer/components/main/Grid/PropertiesPanel/panelChrome.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; +import { MINIMAP_SURFACE_CLASS } from '@components/main/Grid/core/minimapChrome'; import { PANEL_HEADER_CLASS, PANEL_HEADER_HEIGHT, @@ -23,7 +24,14 @@ describe('panelChrome', () => { // ์นฉ์€ opacity๋กœ ๋“ฑํ‡ด์žฅํ•œ๋‹ค - ๋ผ์ด๋ธŒ ๋ธ”๋Ÿฌ๊ฐ€ ๋ถ™์œผ๋ฉด WKWebView์—์„œ ์ ๋ฉธ it('์ ‘ํž˜ ํ† ๊ธ€ ์นฉ์€ ๋ผ์ด๋ธŒ ๋ธ”๋Ÿฌ ์—†๋Š” ์†”๋ฆฌ๋“œ ์žฌ์งˆ์ด๋‹ค', () => { - expect(SIDE_PANEL_CHIP_MATERIAL_CLASS).toContain('bg-glass-panel-solid'); + expect(SIDE_PANEL_CHIP_MATERIAL_CLASS).toContain('-solid'); expect(SIDE_PANEL_CHIP_MATERIAL_CLASS).not.toContain('backdrop-glass'); }); + + // ์นฉ์ด ๋ณด์ด๋Š” ๊ฑด ํŒจ๋„์ด ๋‹ซํ˜€ ์žˆ์„ ๋•Œ๋ฟ์ด๊ณ , ๊ทธ๋•Œ๋Š” ๋ฏธ๋‹ˆ๋งต๊ณผ ๊ฐ™์€ ํ‹ฐ์–ด์˜ + // ์บ”๋ฒ„์Šค ๋ถ€์œ  ์นฉ์ด๋‹ค. ํŒจ๋„ ๊ณ„์—ด ์žฌ์งˆ๋กœ ๋˜๋Œ๋ฆฌ๋ฉด ๋ผ์ดํŠธ์—์„œ ๋‘ ์นฉ์ด 8 L* ๋ฒŒ์–ด์ง„๋‹ค + it('์ ‘ํž˜ ํ† ๊ธ€ ์นฉ์€ ๋ฏธ๋‹ˆ๋งต๊ณผ ๊ฐ™์€ ์บ”๋ฒ„์Šค ํฌ๋กฌ ๊ณ„์—ด์„ ์“ด๋‹ค', () => { + expect(SIDE_PANEL_CHIP_MATERIAL_CLASS).toContain('bg-glass-dim'); + expect(MINIMAP_SURFACE_CLASS).toContain('bg-glass-dim'); + }); }); diff --git a/src/renderer/components/main/Grid/PropertiesPanel/panelChrome.ts b/src/renderer/components/main/Grid/PropertiesPanel/panelChrome.ts index f679aea55..471349803 100644 --- a/src/renderer/components/main/Grid/PropertiesPanel/panelChrome.ts +++ b/src/renderer/components/main/Grid/PropertiesPanel/panelChrome.ts @@ -9,8 +9,12 @@ export const SIDE_PANEL_MATERIAL_CLASS = // ์ ‘ํž˜ ํ† ๊ธ€ ์นฉ - opacity๋กœ ๋“ฑํ‡ด์žฅํ•˜๋ฏ€๋กœ ๋ผ์ด๋ธŒ ๋ธ”๋Ÿฌ ๊ธˆ์ง€ // (๋ธ”๋Ÿฌ+opacity ์กฐํ•ฉ์€ WKWebView์—์„œ ๋ธ”๋Ÿฌ ๋ ˆ์ด์–ด๊ฐ€ ์ ๋ฉธํ•˜๊ณ , ํŽ˜์ด๋“œ๋ฅผ ๋ž˜ํผ๋กœ // ์˜ฎ๊ฒจ๋„ opacity < 1์ธ ์กฐ์ƒ์ด backdrop root๊ฐ€ ๋˜์–ด ๊ฐ™์€ ์ฆ์ƒ - Modal.tsx ์ฐธ์กฐ). -// ํ”„๋ ˆ์ž„๊ณผ ๊ฐ™์€ L*์— ์„œ๋„๋ก ์ƒ์ฃผ ํฌ๋กฌ ์†”๋ฆฌ๋“œ ํ† ํฐ์„ ์“ด๋‹ค -export const SIDE_PANEL_CHIP_MATERIAL_CLASS = 'bg-glass-panel-solid'; +// ์žฌ์งˆ์€ ํŒจ๋„ ๊ณ„์—ด์ด ์•„๋‹ˆ๋ผ ์บ”๋ฒ„์Šค ์ƒ์ฃผ ํฌ๋กฌ์ด๋‹ค - ์นฉ์ด ๋ณด์ด๋Š” ๊ฑด ํŒจ๋„์ด ๋‹ซํ˜€ ์žˆ์„ +// ๋•Œ๋ฟ์ด๊ณ , ๊ทธ๋•Œ๋Š” ๋ฏธ๋‹ˆ๋งต๊ณผ ๊ฐ™์€ ํ‹ฐ์–ด์˜ ๋ถ€์œ  ์นฉ์ด๋ผ ๊ฐ™์€ ๋ฉด์œผ๋กœ ์ฝํ˜€์•ผ ํ•œ๋‹ค. +// ์—ด๋ฆด ๋•Œ ํ”„๋ ˆ์ž„๊ณผ L*๊ฐ€ ์–ด๊ธ‹๋‚˜๋Š” ๊ตฌ๊ฐ„์ด ์ƒ๊ธฐ์ง€๋งŒ ๊ทธ๊ฑด ํŽ˜์ด๋“œ 200ms ์•ˆ์ด๊ณ , +// ์ƒ์‹œ ๋ณด์ด๋Š” ์ƒํƒœ๊ฐ€ ์šฐ์„ ํ•œ๋‹ค. +// -solid๋Š” ๋ผ์ด๋ธŒ dim๊ณผ ๊ฐ™์€ L*์— ์„œ๋„๋ก ๋งž์ถฐ ๋‘” ์†”๋ฆฌ๋“œ ์Œ๋‘ฅ์ด๋‹ค +export const SIDE_PANEL_CHIP_MATERIAL_CLASS = 'bg-glass-dim-solid'; export const SIDE_PANEL_FRAME_CLASS = `absolute right-0 top-0 bottom-0 w-[240px] ${SIDE_PANEL_MATERIAL_CLASS} shadow-elevation-panel z-[var(--z-chrome-panel)]`; diff --git a/src/renderer/components/main/Grid/core/GridMinimap.tsx b/src/renderer/components/main/Grid/core/GridMinimap.tsx index 7a6d100f0..4f37d3b66 100644 --- a/src/renderer/components/main/Grid/core/GridMinimap.tsx +++ b/src/renderer/components/main/Grid/core/GridMinimap.tsx @@ -1,4 +1,5 @@ import { beginDragCursor, endDragCursor } from '@utils/core/dragCursor'; +import { MINIMAP_SURFACE_CLASS } from './minimapChrome'; import React, { useState, useRef, useEffect } from 'react'; import { useGridViewStore } from '@stores/grid/useGridViewStore'; import { usePluginDisplayElementStore } from '@stores/plugin/usePluginDisplayElementStore'; @@ -442,10 +443,9 @@ const GridMinimap = ({ return (
Date: Sat, 29 Aug 2026 02:37:46 +0900 Subject: [PATCH 05/11] =?UTF-8?q?feat:=20=EC=95=B1=20=ED=85=8C=EB=A7=88=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=A0=80=EC=9E=A5=EA=B3=BC=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=ED=8B=B0=EB=B8=8C=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/commands/keys/keys.rs | 3 +- src-tauri/src/commands/layout/settings.rs | 5 +- src-tauri/src/commands/preset/load.rs | 13 ++- src-tauri/src/main.rs | 28 +++++- src-tauri/src/models/mod.rs | 42 +++++++++ src-tauri/src/services/settings.rs | 79 ++++++++++++++--- src-tauri/src/state/app_state.rs | 31 ++++++- src-tauri/src/state/store.rs | 88 +++++++++++++++++-- src-tauri/src/state/windows_window_corners.rs | 61 +++++++++---- 9 files changed, 303 insertions(+), 47 deletions(-) diff --git a/src-tauri/src/commands/keys/keys.rs b/src-tauri/src/commands/keys/keys.rs index 01ea3fdf0..7c3b4836a 100644 --- a/src-tauri/src/commands/keys/keys.rs +++ b/src-tauri/src/commands/keys/keys.rs @@ -571,7 +571,8 @@ fn keys_reset_all_inner( &stat_positions, &app_data_dir, ); - let settings_diff = apply_patch_to_store(store, &settings_patch); + let settings_diff = apply_patch_to_store(store, &settings_patch) + .map_err(crate::services::settings::settings_patch_validation_error)?; Ok((settings_diff, cleared_tab_css_ids)) }, )?; diff --git a/src-tauri/src/commands/layout/settings.rs b/src-tauri/src/commands/layout/settings.rs index f88382859..914a09eda 100644 --- a/src-tauri/src/commands/layout/settings.rs +++ b/src-tauri/src/commands/layout/settings.rs @@ -36,9 +36,8 @@ pub async fn settings_update( state .store .commit_history_overlap_mutation_with_admission(admission, |store| { - Ok(crate::services::settings::apply_patch_to_store( - store, &patch, - )) + crate::services::settings::apply_patch_to_store(store, &patch) + .map_err(crate::services::settings::settings_patch_validation_error) })?; if let Some(previous) = previous.as_ref() { state.resync_global_css_watcher(previous, &state.store.snapshot()); diff --git a/src-tauri/src/commands/preset/load.rs b/src-tauri/src/commands/preset/load.rs index 3d442d414..615ddbaa8 100644 --- a/src-tauri/src/commands/preset/load.rs +++ b/src-tauri/src/commands/preset/load.rs @@ -520,7 +520,8 @@ fn preset_load_from_path( } rekey_full_preset_elements(store); crate::state::migration::clear_dangling_group_ids(store); - let diff = apply_patch_to_store(store, &settings_patch); + let diff = apply_patch_to_store(store, &settings_patch) + .map_err(crate::services::settings::settings_patch_validation_error)?; Ok(( diff, previous_tab_css_overrides, @@ -824,7 +825,9 @@ fn preset_load_tab_from_path( ..SettingsPatchInput::default() }, ) - }); + }) + .transpose() + .map_err(crate::services::settings::settings_patch_validation_error)?; crate::state::migration::clear_dangling_group_ids(store); Ok(( settings_diff, @@ -1791,6 +1794,7 @@ mod tests { }]; let mut store = AppStoreData { custom_css_history: history.clone(), + ui_theme: crate::models::UiTheme::Light, ..AppStoreData::default() }; let mut preset = PresetFile { @@ -1802,6 +1806,8 @@ mod tests { ..PresetFile::default() }; let resolved = resolve_full_preset_settings(&mut preset, &store); + let preset_wire = serde_json::to_value(&preset).expect("preset ์ง๋ ฌํ™”"); + assert!(preset_wire.get("uiTheme").is_none()); let patch = SettingsPatchInput { use_custom_css: Some(resolved.use_custom_css), custom_css: Some(CustomCssPatch { @@ -1811,9 +1817,10 @@ mod tests { ..SettingsPatchInput::default() }; - apply_patch_to_store(&mut store, &patch); + apply_patch_to_store(&mut store, &patch).expect("preset ์„ค์ • ์ ์šฉ"); assert_eq!(store.custom_css_history, history); + assert_eq!(store.ui_theme, crate::models::UiTheme::Light); } #[test] diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 15a15aa3d..269f72554 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -25,7 +25,8 @@ use std::{fs, path::PathBuf}; use tauri::{ ipc::CapabilityBuilder, webview::{NewWindowResponse, PageLoadEvent}, - LogicalSize, Manager, PhysicalPosition, Position, + window::Color, + LogicalSize, Manager, PhysicalPosition, Position, Theme, }; use dm_note::{compute_compensating_zoom, should_apply_compensating_zoom}; @@ -37,6 +38,18 @@ const INTERACTION_BENCHMARK_ENV: &str = "DMN_INTERACTION_WEBVIEW_BENCHMARK"; // ๋ฉ”์ธ ์ฐฝ ๋…ผ๋ฆฌ ํฌ๊ธฐ - ๊ณ ์ • ํฌ๊ธฐ์ด๊ณ , ๋ถ„๋ฆฌ ํŒจ๋„์ด ์ด ์‚ฌ๊ฐํ˜•์„ ๊ธฐ์ค€์œผ๋กœ ์˜†์— ๋ถ™๋Š”๋‹ค const MAIN_WINDOW_WIDTH: f64 = 902.0; const MAIN_WINDOW_HEIGHT: f64 = 488.0; +// tokens/dark.css, tokens/light.css์˜ --ui-bg-app๊ณผ ๊ฐ™์€ ๊ฐ’์„ ์œ ์ง€ํ•œ๋‹ค. +// ๋ฉ”์ธ ์ฐฝ ๋ฐ”๋‹ฅ์ด๋ผ ๋ถ„๋ฆฌ ํŒจ๋„ ์”จ์•— ์ƒ‰(--ui-bg-panel-detached)๊ณผ๋Š” ๋‹ค๋ฅธ ํ† ํฐ์ด๋‹ค +const MAIN_DARK_BACKGROUND: Color = Color(0x13, 0x13, 0x15, 0xFF); +const MAIN_LIGHT_BACKGROUND: Color = Color(0xCC, 0xD3, 0xDC, 0xFF); + +fn main_background_color(theme: Theme) -> Color { + match theme { + Theme::Light => MAIN_LIGHT_BACKGROUND, + Theme::Dark => MAIN_DARK_BACKGROUND, + _ => MAIN_DARK_BACKGROUND, + } +} fn is_interaction_benchmark_mode() -> bool { std::env::var_os(INTERACTION_BENCHMARK_ENV).is_some() @@ -595,6 +608,19 @@ fn apply_main_window_configuration( log::debug!("failed to hide main window before configuration: {err}"); } + let preference = app.state::().store.snapshot().ui_theme; + if let Err(err) = window.set_theme(preference.as_tauri_theme()) { + log::warn!("failed to apply main window theme: {err}"); + } + let resolved_theme = match preference { + models::UiTheme::Light => Theme::Light, + models::UiTheme::Dark => Theme::Dark, + models::UiTheme::System => window.theme().unwrap_or(Theme::Dark), + }; + if let Err(err) = window.set_background_color(Some(main_background_color(resolved_theme))) { + log::warn!("failed to apply main window background color: {err}"); + } + if cfg!(target_os = "windows") { if let Err(err) = window.set_decorations(false) { log::warn!("failed to disable decorations: {err}"); diff --git a/src-tauri/src/models/mod.rs b/src-tauri/src/models/mod.rs index dc59c88f6..a6cc4ebe5 100644 --- a/src-tauri/src/models/mod.rs +++ b/src-tauri/src/models/mod.rs @@ -2394,11 +2394,45 @@ pub struct LayerGroupDef { pub type LayerGroups = HashMap>; +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)] +#[serde(rename_all = "lowercase")] +pub enum UiTheme { + #[default] + System, + Light, + Dark, +} + +impl TryFrom<&str> for UiTheme { + type Error = (); + + fn try_from(value: &str) -> Result { + match value { + "system" => Ok(Self::System), + "light" => Ok(Self::Light), + "dark" => Ok(Self::Dark), + _ => Err(()), + } + } +} + +impl UiTheme { + pub(crate) fn as_tauri_theme(self) -> Option { + match self { + Self::System => None, + Self::Light => Some(tauri::Theme::Light), + Self::Dark => Some(tauri::Theme::Dark), + } + } +} + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct AppStoreData { pub hardware_acceleration: bool, pub always_on_top: bool, + #[serde(default)] + pub ui_theme: UiTheme, pub overlay_locked: bool, #[serde(default)] pub overlay_visible: bool, @@ -2500,6 +2534,7 @@ impl Default for AppStoreData { Self { hardware_acceleration: true, always_on_top: true, + ui_theme: UiTheme::default(), overlay_locked: false, overlay_visible: false, note_effect: false, @@ -2560,6 +2595,7 @@ impl AppStoreData { SettingsState { hardware_acceleration: self.hardware_acceleration, always_on_top: self.always_on_top, + ui_theme: self.ui_theme, overlay_locked: self.overlay_locked, note_effect: self.note_effect, note_settings: self.note_settings.clone(), @@ -2820,6 +2856,8 @@ pub struct DefaultsPayload { pub struct SettingsState { pub hardware_acceleration: bool, pub always_on_top: bool, + #[serde(default)] + pub ui_theme: UiTheme, pub overlay_locked: bool, pub note_effect: bool, #[serde(default)] @@ -2888,6 +2926,7 @@ pub struct NoteSettingsPatch { pub struct SettingsPatchInput { pub hardware_acceleration: Option, pub always_on_top: Option, + pub ui_theme: Option, pub overlay_locked: Option, pub note_effect: Option, pub note_settings: Option, @@ -2949,6 +2988,7 @@ impl SettingsDiff { [ p.hardware_acceleration.is_some(), p.always_on_top.is_some(), + p.ui_theme.is_some(), p.overlay_locked.is_some(), p.note_effect.is_some(), p.note_settings.is_some(), @@ -2984,6 +3024,8 @@ pub struct SettingsPatch { #[serde(skip_serializing_if = "Option::is_none")] pub always_on_top: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub ui_theme: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub overlay_locked: Option, #[serde(skip_serializing_if = "Option::is_none")] pub note_effect: Option, diff --git a/src-tauri/src/services/settings.rs b/src-tauri/src/services/settings.rs index abe79bd91..640dfc5fe 100644 --- a/src-tauri/src/services/settings.rs +++ b/src-tauri/src/services/settings.rs @@ -5,9 +5,9 @@ use anyhow::{Context, Result}; use crate::models::{ AppStoreData, CustomCss, CustomCssPatch, CustomJs, CustomJsPatch, FontType, NoteSettings, NoteSettingsPatch, SettingsDiff, SettingsPatch, SettingsPatchInput, SettingsState, - ShortcutsState, + ShortcutsState, UiTheme, }; -use crate::state::AppStore; +use crate::{errors::EditorCommitError, state::AppStore}; #[derive(Clone)] pub struct SettingsService { @@ -28,7 +28,7 @@ impl SettingsService { self.store.update(|state| { diff = Some(apply_patch_to_store(state, &patch)); })?; - diff.context("settings patch did not produce a diff") + diff.context("settings patch did not produce a diff")? } } @@ -36,16 +36,21 @@ pub(crate) fn settings_from_store(store: &AppStoreData) -> SettingsState { store.settings_state() } +pub(crate) fn settings_patch_validation_error(error: anyhow::Error) -> EditorCommitError { + EditorCommitError::validation("INVALID_SETTINGS_PATCH", error.to_string()) +} + pub(crate) fn apply_patch_to_store( store: &mut AppStoreData, patch: &SettingsPatchInput, -) -> SettingsDiff { +) -> Result { let current = settings_from_store(store); - let normalized = normalize_patch(patch, ¤t); + let normalized = normalize_patch(patch, ¤t)?; let next = apply_changes(current, &normalized); store.hardware_acceleration = next.hardware_acceleration; store.always_on_top = next.always_on_top; + store.ui_theme = next.ui_theme; store.overlay_locked = next.overlay_locked; store.note_effect = next.note_effect; store.note_settings = next.note_settings.clone(); @@ -67,13 +72,13 @@ pub(crate) fn apply_patch_to_store( store.shortcuts = next.shortcuts.clone(); store.obs_mode_enabled = next.obs_mode_enabled; - SettingsDiff { + Ok(SettingsDiff { changed: normalized, full: Some(next), - } + }) } -fn normalize_patch(patch: &SettingsPatchInput, current: &SettingsState) -> SettingsPatch { +fn normalize_patch(patch: &SettingsPatchInput, current: &SettingsState) -> Result { let mut normalized = SettingsPatch::default(); if let Some(value) = patch.hardware_acceleration { normalized.hardware_acceleration = Some(value); @@ -81,6 +86,12 @@ fn normalize_patch(patch: &SettingsPatchInput, current: &SettingsState) -> Setti if let Some(value) = patch.always_on_top { normalized.always_on_top = Some(value); } + if let Some(value) = patch.ui_theme.as_deref() { + normalized.ui_theme = Some( + UiTheme::try_from(value) + .map_err(|()| anyhow::anyhow!("invalid uiTheme value: {value}"))?, + ); + } if let Some(value) = patch.overlay_locked { normalized.overlay_locked = Some(value); } @@ -195,7 +206,7 @@ fn normalize_patch(patch: &SettingsPatchInput, current: &SettingsState) -> Setti if let Some(value) = patch.obs_mode_enabled { normalized.obs_mode_enabled = Some(value); } - normalized + Ok(normalized) } fn apply_changes(mut current: SettingsState, patch: &SettingsPatch) -> SettingsState { @@ -205,6 +216,9 @@ fn apply_changes(mut current: SettingsState, patch: &SettingsPatch) -> SettingsS if let Some(value) = patch.always_on_top { current.always_on_top = value; } + if let Some(value) = patch.ui_theme { + current.ui_theme = value; + } if let Some(value) = patch.overlay_locked { current.overlay_locked = value; } @@ -350,6 +364,7 @@ mod tests { assert!(d.hardware_acceleration); assert!(d.always_on_top); + assert_eq!(d.ui_theme, UiTheme::System); assert!(!d.overlay_locked); assert!(!d.note_effect); if cfg!(target_os = "macos") { @@ -499,6 +514,7 @@ mod tests { let expected_a = SettingsState { hardware_acceleration: false, always_on_top: false, + ui_theme: UiTheme::Light, overlay_locked: true, note_effect: true, note_settings: expected_note.clone(), @@ -531,6 +547,7 @@ mod tests { let patch_a = SettingsPatchInput { hardware_acceleration: Some(false), always_on_top: Some(false), + ui_theme: Some("light".to_string()), overlay_locked: Some(true), note_effect: Some(true), note_settings: Some(NoteSettingsPatch { @@ -579,13 +596,14 @@ mod tests { }; let mut store = AppStoreData::default(); - let diff_a = apply_patch_to_store(&mut store, &patch_a); + let diff_a = apply_patch_to_store(&mut store, &patch_a).expect("์ฒซ patch ์ ์šฉ"); assert_eq!(diff_a.full.expect("full snapshot ๋ฐ˜ํ™˜"), expected_a); assert_eq!(settings_from_store(&store), expected_a); // ๊ฐ™์€ ๊ธฐ๋ณธ๊ฐ’์„ ๊ณต์œ ํ•˜๋Š” bool ์Œ์ด ์„œ๋กœ ๋ฐ˜๋Œ€๊ฐ€ ๋˜๋„๋ก ์ ˆ๋ฐ˜๋งŒ ๋ฐ˜์ „ let patch_b = SettingsPatchInput { always_on_top: Some(true), + ui_theme: Some("dark".to_string()), note_effect: Some(false), developer_mode_enabled: Some(false), auto_update_enabled: Some(true), @@ -595,6 +613,7 @@ mod tests { }; let expected_b = SettingsState { always_on_top: true, + ui_theme: UiTheme::Dark, note_effect: false, developer_mode_enabled: false, auto_update_enabled: true, @@ -603,8 +622,8 @@ mod tests { ..expected_a }; - let diff_b = apply_patch_to_store(&mut store, &patch_b); - assert_eq!(diff_b.changed_count(), 6); + let diff_b = apply_patch_to_store(&mut store, &patch_b).expect("๋‘ ๋ฒˆ์งธ patch ์ ์šฉ"); + assert_eq!(diff_b.changed_count(), 7); assert_eq!(diff_b.full.expect("full snapshot ๋ฐ˜ํ™˜"), expected_b); assert_eq!(settings_from_store(&store), expected_b); // ๊ต์ฐจ ๋งคํ•‘ ๊ฒ€์ถœ ํ•ต์‹ฌ ์Œ, use_custom_css์™€ use_custom_js๊ฐ€ ์„œ๋กœ ๋ฐ˜๋Œ€ @@ -619,7 +638,8 @@ mod tests { let from_empty_store = settings_from_store(&AppStoreData::default()); let mut store = AppStoreData::default(); - let diff = apply_patch_to_store(&mut store, &SettingsPatchInput::default()); + let diff = apply_patch_to_store(&mut store, &SettingsPatchInput::default()) + .expect("๋นˆ patch ์ ์šฉ"); assert_eq!(diff.changed_count(), 0); let from_empty_patch = diff.full.expect("๋นˆ patch๋„ full snapshot ๋ฐ˜ํ™˜"); @@ -628,4 +648,37 @@ mod tests { // ๋นˆ patch๊ฐ€ store ๊ธฐ๋ณธ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์Œ assert_eq!(settings_from_store(&store), from_default); } + + #[test] + fn unknown_ui_theme_is_rejected_without_mutating_store() { + let mut store = AppStoreData::default(); + let before = store.clone(); + let patch = SettingsPatchInput { + ui_theme: Some("neon".to_string()), + ..SettingsPatchInput::default() + }; + + let error = apply_patch_to_store(&mut store, &patch).expect_err("๋ฏธ์ง€ ํ…Œ๋งˆ ๊ฑฐ์ ˆ"); + + assert!(error.to_string().contains("invalid uiTheme value: neon")); + assert_eq!(store, before); + } + + #[test] + fn ui_theme_uses_camel_case_in_settings_payloads() { + let mut store = AppStoreData::default(); + let diff = apply_patch_to_store( + &mut store, + &SettingsPatchInput { + ui_theme: Some("light".to_string()), + ..SettingsPatchInput::default() + }, + ) + .expect("ํ…Œ๋งˆ patch ์ ์šฉ"); + + let wire = serde_json::to_value(diff).expect("์„ค์ • diff ์ง๋ ฌํ™”"); + assert_eq!(wire["changed"]["uiTheme"], "light"); + assert_eq!(wire["full"]["uiTheme"], "light"); + assert!(wire["changed"].get("ui_theme").is_none()); + } } diff --git a/src-tauri/src/state/app_state.rs b/src-tauri/src/state/app_state.rs index 67fd8353c..f9027b1cb 100644 --- a/src-tauri/src/state/app_state.rs +++ b/src-tauri/src/state/app_state.rs @@ -48,7 +48,7 @@ use crate::{ CommittedEditorChange, DefaultsPayload, EditorCommitRequest, GestureCommitRequest, HistoryStatus, KeyCounterSettings, KeyCounters, KeyMappings, KeyPositions, KeySlot, KeySoundOutputBackendPersist, OverlayBounds, OverlayResizeAnchor, PanelBounds, - SettingsDiff, SettingsState, TabCssOverrides, + SettingsDiff, SettingsState, TabCssOverrides, UiTheme, }, services::{ css_watcher::CssWatcher, @@ -74,6 +74,17 @@ const MIN_OVERLAY_DIMENSION: f64 = 100.0; // ๋„“์€ ๋ฐฐ์น˜์— ํŠธ๋ž™ ๋†’์ด๋ฅผ ํฌ๊ฒŒ ์žก์œผ๋ฉด ์ด์ „ ์ƒํ•œ 2000์—์„œ ์กฐ์šฉํžˆ ์ž˜๋ ธ์Œ const MAX_OVERLAY_DIMENSION: f64 = 4096.0; +fn resolve_ui_theme(app: &AppHandle, preference: UiTheme) -> tauri::Theme { + match preference { + UiTheme::Light => tauri::Theme::Light, + UiTheme::Dark => tauri::Theme::Dark, + UiTheme::System => app + .get_webview_window("main") + .and_then(|window| window.theme().ok()) + .unwrap_or(tauri::Theme::Dark), + } +} + #[cfg(target_os = "macos")] const OVERLAY_FRAME_APPLY_TIMEOUT_MS: u64 = 250; @@ -3607,6 +3618,7 @@ impl AppState { let snapshot = self.store.snapshot(); let stored_bounds = snapshot.panel_bounds; let layout = resolve_panel_window_layout(stored_bounds, main_rect, monitors, None); + let resolved_theme = resolve_ui_theme(app, snapshot.ui_theme); // about:blank๋Š” runtime-wry๊ฐ€ ์ดˆ๊ธฐ ๋„ค๋น„๊ฒŒ์ด์…˜์„ ๊ฑด๋„ˆ๋›ด๋‹ค - opener๊ฐ€ ๋ฌธ์„œ๋ฅผ ์ฑ„์šฐ๊ณ , // WebView2์˜ "NewWindow๋Š” ๋„ค๋น„๊ฒŒ์ด์…˜ ์ „์ด์–ด์•ผ ํ•จ" ์š”๊ฑด๋„ ์ด๊ฑธ๋กœ ๋งž๋Š”๋‹ค. @@ -3636,6 +3648,7 @@ impl AppState { // (์œ ํ‹ธ๋ฆฌํ‹ฐ ํŒจ๋„ ๊ด€๋ก€ - ๋ฒ„ํŠผ์ด ์ฒซ ํด๋ฆญ์— ๋ฐ”๋กœ ๋™์ž‘) .accept_first_mouse(true) .visible(false) + .theme(snapshot.ui_theme.as_tauri_theme()) .inner_size(PANEL_WIDTH, layout.height) .min_inner_size(PANEL_WIDTH, layout.min_height) .max_inner_size(PANEL_WIDTH, layout.max_height) @@ -3645,7 +3658,8 @@ impl AppState { // ์ด๋ฏธ ๋‚ด๋น„๊ฒŒ์ด์…˜์ด ์‹œ์ž‘๋œ ๋’ค๋ผ ๋Šฆ๋‹ค. ์‹ค์ œ ์ƒ‰์€ ๋ Œ๋”๋Ÿฌ๊ฐ€ ํ† ํฐ์„ ์ฝ์–ด ๋ฎ๋Š”๋‹ค #[cfg(target_os = "windows")] { - builder = builder.background_color(super::windows_window_corners::SEED_FILL); + builder = + builder.background_color(super::windows_window_corners::seed_fill(resolved_theme)); } if let Some(position) = layout.position { @@ -3671,7 +3685,7 @@ impl AppState { } } - self.configure_panel_window(&window, app, layout.max_height); + self.configure_panel_window(&window, app, layout.max_height, resolved_theme); Ok(window) } @@ -3680,6 +3694,7 @@ impl AppState { window: &WebviewWindow, app: &AppHandle, initial_max_height: f64, + _resolved_theme: tauri::Theme, ) { // ์›น ์ฝ˜ํ…์ธ ๊ฐ€ ๊ทธ๋ฆฌ๋Š” ๋ผ์šด๋”ฉ์€ ๋ฆฌ์‚ฌ์ด์ฆˆ ํ”„๋ ˆ์ž„์„ ๋ชป ๋”ฐ๋ผ์˜ด - ์‹ค๋ฃจ์—ฃ์€ ์ปดํฌ์ง€ํ„ฐ๊ฐ€ ์†Œ์œ  #[cfg(target_os = "macos")] @@ -3687,7 +3702,7 @@ impl AppState { // Windows๋Š” DWM์ด ์ด๋ฏธ ์ž๊ธฐ ๋ฐ˜๊ฒฝ์œผ๋กœ ์ž๋ฅด๊ณ  ์žˆ์–ด ์›น ๋ผ์šด๋”ฉ๊ณผ ์–ด๊ธ‹๋‚œ๋‹ค - ๋ฐ˜๊ฒฝ ์ง€์ •์ด // ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ ์‹ค๋ฃจ์—ฃ์„ DWM์— ๋„˜๊ธฐ๊ณ  ์›น์€ ์‚ฌ๊ฐ์œผ๋กœ ์ฑ„์šด๋‹ค (๋ฉ”์ธ ์ฐฝ๊ณผ ๊ฐ™์€ ์ฒ˜๋ฆฌ) #[cfg(target_os = "windows")] - super::windows_window_corners::apply_initial_chrome(window); + super::windows_window_corners::apply_initial_chrome(window, _resolved_theme); let bounds_session = self .panel_bounds_persistence @@ -4073,6 +4088,14 @@ impl AppState { // ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ๋ณด์ด๋Š” ์ƒํƒœ์ผ ๋•Œ๋งŒ ์„ค์ • ์ ์šฉ let is_visible = *self.overlay_visible.read(); + if let Some(value) = diff.changed.ui_theme { + for label in ["main", PANEL_LABEL] { + if let Some(window) = app.get_webview_window(label) { + window.set_theme(value.as_tauri_theme())?; + } + } + } + if let Some(value) = diff.changed.always_on_top { if is_visible { if let Some(window) = app.get_webview_window(OVERLAY_LABEL) { diff --git a/src-tauri/src/state/store.rs b/src-tauri/src/state/store.rs index c9e5a2486..f2f91bdda 100644 --- a/src-tauri/src/state/store.rs +++ b/src-tauri/src/state/store.rs @@ -1783,7 +1783,8 @@ impl AppStore { let settings_diff = crate::services::settings::apply_patch_to_store( &mut scratch, &preset_history_settings_patch(&target.settings), - ); + ) + .map_err(crate::services::settings::settings_patch_validation_error)?; crate::state::migration::canonicalize_gradient_pairs(&mut scratch); crate::state::migration::canonicalize_image_modes(&mut scratch); let candidate = EditorDocumentV1::from_store(&scratch); @@ -4848,8 +4849,8 @@ mod tests { NoteColor, OverlayBounds, PanelBounds, PendingProcessedWavReplacement, PluginInstancesCommitRequest, PluginInstancesReconcileRequest, PluginPoint, SavedPluginInstance, SettingsPatchInput, SlotMatch, SoundLibraryEntry, SoundSource, - StatPosition, StatType, TabCss, TabNoteSettings, EDITOR_COMMIT_SCHEMA_VERSION_V2, - EDITOR_OPS_VERSION, EDITOR_SCHEMA_VERSION, + StatPosition, StatType, TabCss, TabNoteSettings, UiTheme, + EDITOR_COMMIT_SCHEMA_VERSION_V2, EDITOR_OPS_VERSION, EDITOR_SCHEMA_VERSION, }, services::{css_watcher::commit_css_reload, settings::apply_patch_to_store}, state::{ @@ -17552,7 +17553,8 @@ mod tests { background_color: Some(format!("#{:06x}", index + 1)), ..SettingsPatchInput::default() }, - ); + ) + .expect("์„ค์ • patch ์ ์šฉ"); } Ok(()) }) @@ -17570,6 +17572,62 @@ mod tests { let _ = std::fs::remove_dir_all(dir); } + #[test] + fn ui_theme_change_does_not_invalidate_editor_history() { + let dir = test_directory("ui-theme-history-exclusion-test"); + std::fs::create_dir_all(&dir).unwrap(); + let store = AppStore::initialize_in_dir(&dir).unwrap(); + let initial_dx = store.editor_get().document.key_positions["4key"][0].dx; + + store + .commit_editor_document(editor_request( + store.editor_get().revision, + uuid::Uuid::new_v4().to_string(), + position_patch(&store, initial_dx + 1.0), + )) + .unwrap(); + let operation_id = uuid::Uuid::new_v4().to_string(); + let gate = store.history_gate(); + let barrier = gate.close(&operation_id).unwrap(); + let current_counters = store.snapshot().key_counters; + store + .apply_history_operation( + HistoryDirection::Undo, + &operation_id, + ¤t_counters, + || {}, + ) + .unwrap(); + drop(barrier); + let before = store.history_status(); + assert!(before.can_redo); + + let transaction = store + .commit_history_overlap_mutation(|data| { + apply_patch_to_store( + data, + &SettingsPatchInput { + ui_theme: Some("light".to_string()), + ..SettingsPatchInput::default() + }, + ) + .map_err(crate::services::settings::settings_patch_validation_error) + }) + .unwrap(); + + assert!(transaction.history_status.is_none()); + assert_eq!(store.snapshot().ui_theme, UiTheme::Light); + let after = store.history_status(); + assert_eq!(after.history_revision, before.history_revision); + assert_eq!(after.history_epoch, before.history_epoch); + assert_eq!(after.can_undo, before.can_undo); + assert_eq!(after.can_redo, before.can_redo); + assert_eq!(after.truncated, before.truncated); + + store.flush_and_shutdown().unwrap(); + let _ = std::fs::remove_dir_all(dir); + } + #[test] fn unrecorded_legacy_editor_mutations_invalidate_stale_undo_and_redo() { let dir = test_directory("legacy-editor-history-invalidation-test"); @@ -20330,7 +20388,8 @@ mod tests { background_color: Some("#123456".to_string()), ..SettingsPatchInput::default() }, - ); + ) + .expect("์„ค์ • patch ์ ์šฉ"); Ok(()) }, ) @@ -20356,7 +20415,8 @@ mod tests { background_color: Some("#FFFFFF".to_string()), ..SettingsPatchInput::default() }, - ); + ) + .expect("์„ค์ • patch ์ ์šฉ"); Ok(()) }, ); @@ -20810,6 +20870,22 @@ mod tests { let _ = std::fs::remove_dir_all(dir); } + #[test] + fn ui_theme_missing_field_defaults_to_system() { + let dir = test_directory("ui-theme-default-test"); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("store.json"); + let mut fixture = serde_json::to_value(AppStoreData::default()).unwrap(); + assert!(fixture.as_object_mut().unwrap().remove("uiTheme").is_some()); + std::fs::write(&path, serde_json::to_vec_pretty(&fixture).unwrap()).unwrap(); + + let store = AppStore::initialize_in_dir(&dir).unwrap(); + assert_eq!(store.snapshot().ui_theme, UiTheme::System); + store.flush_and_shutdown().unwrap(); + drop(store); + let _ = std::fs::remove_dir_all(dir); + } + #[test] fn regular_update_commits_pending_deferred_state() { let dir = test_directory("store-deferred-regular-update-test"); diff --git a/src-tauri/src/state/windows_window_corners.rs b/src-tauri/src/state/windows_window_corners.rs index 581550110..800efe632 100644 --- a/src-tauri/src/state/windows_window_corners.rs +++ b/src-tauri/src/state/windows_window_corners.rs @@ -1,7 +1,7 @@ // lib์™€ bin์˜ ๋…๋ฆฝ state ๋ชจ๋“ˆ ์ปดํŒŒ์ผ #![allow(dead_code)] -use tauri::{window::Color, WebviewWindow}; +use tauri::{window::Color, Theme, WebviewWindow}; use windows::Win32::Foundation::COLORREF; use windows::Win32::Graphics::Dwm::{ DwmSetWindowAttribute, DWMWA_BORDER_COLOR, DWMWA_WINDOW_CORNER_PREFERENCE, DWMWCP_ROUND, @@ -27,24 +27,45 @@ use windows::Win32::Graphics::Dwm::{ // ์ฒซ ํŽ˜์ธํŠธ ์ „ ๊ตฌ๊ฐ„์„ ๋ฉ”์šฐ๋Š” ์”จ์•—๊ฐ’ - tokens.css์˜ --ui-bg-panel-detached์™€ ๊ฐ™์€ ๊ฐ’์„ ์œ ์ง€ํ•œ๋‹ค. // ์ฐฝ ๋นŒ๋”์— ์‹ค๋ ค WebView2 ์ปจํŠธ๋กค๋Ÿฌ ์ƒ์„ฑ ์˜ต์…˜์œผ๋กœ ๋“ค์–ด๊ฐ€๋ฏ€๋กœ ์ฒซ ํ”„๋ ˆ์ž„๋ถ€ํ„ฐ ์œ ํšจํ•˜๊ณ , // ์‹ค์ œ ์ƒ‰์€ ๋ Œ๋”๋Ÿฌ๊ฐ€ ํ† ํฐ์„ ์ฝ์–ด apply_surface_chrome์œผ๋กœ ๋ฎ๋Š”๋‹ค -pub(crate) const SEED_FILL: Color = Color(0x1C, 0x1C, 0x1E, 0xFF); +const DARK_SEED_FILL: Color = Color(0x1C, 0x1C, 0x1E, 0xFF); +const LIGHT_SEED_FILL: Color = Color(0xEE, 0xF3, 0xF7, 0xFF); // tokens.css์˜ --ui-line๊ณผ ๊ฐ™์€ ๊ฐ’. DWM ๊ธฐ๋ณธ ๋ณด๋”๋Š” ์‹œ์Šคํ…œ ์ƒ‰์ด๋ผ ํ›จ์”ฌ ์ง„ํ•ด์„œ, ๋ Œ๋”๋Ÿฌ๊ฐ€ // ํ† ํฐ์„ ์ฝ์–ด ๋ฎ๋Š” ์ˆœ๊ฐ„ ์„  ๊ตต๊ธฐ๊ฐ€ ๋ฐ”๋€ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค - ์ฐฝ ์ƒ์„ฑ ์‹œ์ ์— ๋ฏธ๋ฆฌ ๋งž์ถฐ ์ „ํ™˜์„ ์—†์•ค๋‹ค -const SEED_LINE: [f64; 4] = [1.0, 1.0, 1.0, 0.1]; +const DARK_SEED_LINE: [f64; 4] = [1.0, 1.0, 1.0, 0.1]; +// ๋ผ์ดํŠธ --ui-line์€ ์ˆœํ‘์ด ์•„๋‹ˆ๋ผ ์‚ด์ง ๋‚จ์ƒ‰์œผ๋กœ ๊ธฐ์šด ์ž‰ํฌ๋‹ค. +// ๋ฌด์ฑ„์ƒ‰ ์œ„ ์ˆœํ‘ ์ €์•ŒํŒŒ๋Š” ๋•Œ๊ฐ€ ํƒ„ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ธ๋‹ค +const LIGHT_SEED_LINE: [f64; 4] = [0.062745, 0.094118, 0.156863, 0.12]; + +pub(crate) fn seed_fill(theme: Theme) -> Color { + match theme { + Theme::Light => LIGHT_SEED_FILL, + Theme::Dark => DARK_SEED_FILL, + _ => DARK_SEED_FILL, + } +} + +fn seed_line(theme: Theme) -> [f64; 4] { + match theme { + Theme::Light => LIGHT_SEED_LINE, + Theme::Dark => DARK_SEED_LINE, + _ => DARK_SEED_LINE, + } +} -fn seed_fill_components() -> [f64; 4] { +fn seed_fill_components(theme: Theme) -> [f64; 4] { + let fill = seed_fill(theme); [ - f64::from(SEED_FILL.0) / 255.0, - f64::from(SEED_FILL.1) / 255.0, - f64::from(SEED_FILL.2) / 255.0, + f64::from(fill.0) / 255.0, + f64::from(fill.1) / 255.0, + f64::from(fill.2) / 255.0, 1.0, ] } // ์ฐฝ ์ƒ์„ฑ ์‹œ์  - ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋‹ค๋ฆด ์†Œ๋น„์ž๊ฐ€ ์—†์–ด fire-and-forget. // Win11์˜ ๊ธฐ๋ณธ๊ฐ’์ด ์ด๋ฏธ ๋ผ์šด๋“œ์ง€๋งŒ, ์ฐฝ ์ข…๋ฅ˜์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง€๋Š” ํœด๋ฆฌ์Šคํ‹ฑ์— ๊ธฐ๋Œ€์ง€ ์•Š๋Š”๋‹ค -pub(crate) fn apply_initial_chrome(window: &WebviewWindow) { +pub(crate) fn apply_initial_chrome(window: &WebviewWindow, theme: Theme) { match set_corner_preference(window) { Ok(()) => log::info!( "[window-corners] applied native corner preference to '{}'", @@ -56,8 +77,10 @@ pub(crate) fn apply_initial_chrome(window: &WebviewWindow) { ), } - if let Err(reason) = set_border_color(window, composite_over(SEED_LINE, seed_fill_components())) - { + if let Err(reason) = set_border_color( + window, + composite_over(seed_line(theme), seed_fill_components(theme)), + ) { log::debug!( "[window-corners] skipped seed border color for '{}': {reason}", window.label() @@ -170,7 +193,8 @@ fn to_colorref(rgb: [f64; 3]) -> COLORREF { #[cfg(test)] mod tests { - use super::{composite_over, to_colorref}; + use super::{composite_over, seed_fill_components, seed_line, to_colorref}; + use tauri::Theme; #[test] fn opaque_line_replaces_the_fill() { @@ -190,15 +214,20 @@ mod tests { // --ui-line rgba(255,255,255,0.1)์„ --ui-bg-panel-detached #1c1c1e ์œ„์— ์˜ฌ๋ฆฐ ๊ฐ’ #[test] - fn translucent_line_blends_toward_the_fill() { - let blended = composite_over( - [1.0, 1.0, 1.0, 0.1], - [28.0 / 255.0, 28.0 / 255.0, 30.0 / 255.0, 1.0], - ); + fn dark_translucent_line_blends_toward_the_fill() { + let blended = composite_over(seed_line(Theme::Dark), seed_fill_components(Theme::Dark)); assert_eq!(super::channel(blended[0]), 51); assert_eq!(super::channel(blended[2]), 53); } + // --ui-line rgba(16,24,40,0.12)์„ --ui-bg-panel-detached #eef3f7 ์œ„์— ์˜ฌ๋ฆฐ ๊ฐ’ + #[test] + fn light_translucent_line_blends_toward_the_fill() { + let blended = composite_over(seed_line(Theme::Light), seed_fill_components(Theme::Light)); + assert_eq!(super::channel(blended[0]), 211); + assert_eq!(super::channel(blended[2]), 222); + } + #[test] fn colorref_packs_blue_into_the_high_byte() { assert_eq!(to_colorref([1.0, 0.0, 0.0]).0, 0x0000_00FF); From 48b816c1cb387b8f5438cddefcfc8791a7a9a86d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=97=B0=EC=9A=B0?= Date: Sat, 29 Aug 2026 02:37:46 +0900 Subject: [PATCH 06/11] =?UTF-8?q?feat:=20=EB=9D=BC=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EB=AA=A8=EB=93=9C=EC=99=80=20=ED=85=8C=EB=A7=88=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/Grid/PropertiesPanelHost.tsx | 10 +- .../Modal/content/managers/SoundTrimModal.tsx | 11 ++ src/renderer/components/main/Settings.tsx | 34 +++++- src/renderer/defaults.test.ts | 1 + src/renderer/defaults.ts | 1 + src/renderer/hooks/app/useAppBootstrap.ts | 1 + src/renderer/hooks/app/useAppTheme.ts | 31 +++++ src/renderer/hooks/app/useResolvedTheme.ts | 19 ++++ src/renderer/locales/en.json | 4 + src/renderer/locales/ko.json | 4 + src/renderer/locales/ru.json | 4 + src/renderer/locales/zh-Hant.json | 4 + src/renderer/locales/zh-cn.json | 4 + src/renderer/stores/useSettingsStore.test.ts | 1 + src/renderer/stores/useSettingsStore.ts | 10 +- .../utils/panelWindow/panelChildWindow.ts | 17 ++- src/renderer/utils/theme/applyTheme.ts | 107 ++++++++++++++++++ src/renderer/windows/main/App.tsx | 2 + src/types/settings/settings.ts | 18 +++ 19 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 src/renderer/hooks/app/useAppTheme.ts create mode 100644 src/renderer/hooks/app/useResolvedTheme.ts create mode 100644 src/renderer/utils/theme/applyTheme.ts diff --git a/src/renderer/components/main/Grid/PropertiesPanelHost.tsx b/src/renderer/components/main/Grid/PropertiesPanelHost.tsx index 8d6a41ee6..0aca286df 100644 --- a/src/renderer/components/main/Grid/PropertiesPanelHost.tsx +++ b/src/renderer/components/main/Grid/PropertiesPanelHost.tsx @@ -7,6 +7,7 @@ import { useBlockBrowserShortcuts } from '@hooks/app/useBlockBrowserShortcuts'; import { usePanelHeaderDrag } from '@hooks/panel/usePanelHeaderDrag'; import { restoreLenisScroll } from '@hooks/useLenis'; import { panelWindowApi } from '@api/modules/panelWindowApi'; +import { subscribeResolvedTheme } from '@utils/theme/applyTheme'; import { flushFocusedEditor } from '@src/renderer/editor/runtime/lifecycleEditorFlush'; import { isHistoryEditorFlushLocked } from '@src/renderer/editor/runtime/historyEditorFlushLock'; import { @@ -392,8 +393,15 @@ const PropertiesPanelHost = ({ subtree: true, characterData: true, }); + // ํ…Œ๋งˆ ์ „ํ™˜์€ html ์†์„ฑ๋งŒ ๋ฐ”๊พผ๋‹ค - head ์˜ต์ €๋ฒ„์— ์•ˆ ๊ฑธ๋ ค ๋„ค์ดํ‹ฐ๋ธŒ ๊ฐ€์žฅ์ž๋ฆฌ๊ฐ€ + // ์˜› ์ƒ‰์œผ๋กœ ๊ณ ์ฐฉ๋œ๋‹ค. ์†์„ฑ์ด ๋ฐ”๋€ ๋’ค ํ† ํฐ์ด ์ƒˆ ๊ฐ’์ด๋ฏ€๋กœ ๋‹ค์Œ ํ”„๋ ˆ์ž„์— ์ฝ๋Š”๋‹ค + const unsubscribeTheme = subscribeResolvedTheme(() => { + cancelAnimationFrame(scheduled); + scheduled = requestAnimationFrame(sync); + }); return () => { observer.disconnect(); + unsubscribeTheme(); cancelAnimationFrame(scheduled); }; }, [childWindow]); @@ -405,7 +413,7 @@ const PropertiesPanelHost = ({ {dockHint && (
diff --git a/src/renderer/components/main/Grid/PropertiesPanel/layer/LayerTabContent.tsx b/src/renderer/components/main/Grid/PropertiesPanel/layer/LayerTabContent.tsx index b1fcfe7c0..8cc8701bf 100644 --- a/src/renderer/components/main/Grid/PropertiesPanel/layer/LayerTabContent.tsx +++ b/src/renderer/components/main/Grid/PropertiesPanel/layer/LayerTabContent.tsx @@ -638,7 +638,7 @@ const LayerTabContent: React.FC = ({ > {layerItems.length === 0 ? (
-

+

{t('propertiesPanel.noLayers') || 'No layers'}

diff --git a/src/renderer/components/main/Grid/PropertiesPanel/single/SingleSelectionPanel.tsx b/src/renderer/components/main/Grid/PropertiesPanel/single/SingleSelectionPanel.tsx index a889aa2ae..bf438071a 100644 --- a/src/renderer/components/main/Grid/PropertiesPanel/single/SingleSelectionPanel.tsx +++ b/src/renderer/components/main/Grid/PropertiesPanel/single/SingleSelectionPanel.tsx @@ -304,7 +304,7 @@ export const PluginSelectionPanel: React.FC = ({
-

{noticeText}

+

{noticeText}

); @@ -341,13 +341,13 @@ export const PluginSelectionPanel: React.FC = ({ /> )} {!hasSinglePluginSelection && ( -

+

{t('propertiesPanel.pluginMultiSelection') || 'ํ”Œ๋Ÿฌ๊ทธ์ธ ์š”์†Œ๋Š” ํ•œ ๋ฒˆ์— ํ•˜๋‚˜๋งŒ ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.'}

)} {hasSinglePluginSelection && showModalHint && ( -

+

{t('propertiesPanel.pluginModalHint') || '์ด ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ์„ค์ • ๋ชจ๋‹ฌ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์š”์†Œ๋ฅผ ํด๋ฆญํ•ด ์„ค์ •ํ•˜์„ธ์š”.'}

diff --git a/src/renderer/components/main/Grid/PropertiesPanel/single/StyleTabContent.tsx b/src/renderer/components/main/Grid/PropertiesPanel/single/StyleTabContent.tsx index f9fed9ef2..ee6d0647e 100644 --- a/src/renderer/components/main/Grid/PropertiesPanel/single/StyleTabContent.tsx +++ b/src/renderer/components/main/Grid/PropertiesPanel/single/StyleTabContent.tsx @@ -726,7 +726,7 @@ const StyleTabContent: React.FC = ({ {!(slotListening && !slotPickerOpen) && slotParts.extra && ( // case ํ”ผ์ฒ˜: +๋ฅผ ์ˆซ์ž ์ค‘์‹ฌ์— ๋งž์ถ˜ ๊ธ€๋ฆฌํ”„๋กœ ์น˜ํ™˜. // tracking์€ +์™€ ์ˆซ์ž ์‚ฌ์ด 0.25px ํ™•๋ณด์šฉ, ๋ ๊ธ€์ž ๋’ค ์—ฌ๋ถ„์€ -mr๋กœ ์ƒ์‡„ (๋ฐฐ์ง€๋Š” ํ•œ ์ž๋ฆฌ ์ „์ œ) - + {slotParts.extra} )} diff --git a/src/renderer/components/main/Modal/content/dialogs/PluginDataDeleteModal.tsx b/src/renderer/components/main/Modal/content/dialogs/PluginDataDeleteModal.tsx index a5c63cdc5..be62ae7dd 100644 --- a/src/renderer/components/main/Modal/content/dialogs/PluginDataDeleteModal.tsx +++ b/src/renderer/components/main/Modal/content/dialogs/PluginDataDeleteModal.tsx @@ -72,7 +72,7 @@ export function PluginDataDeleteModal({ ๋กœ์ผ€์ผ ๋ฌธ์žฅ์€ "๋ช…์‚ฌ๊ตฌ + ์„œ์ˆ ๋ถ€"๋กœ ๊ฐˆ๋ฆฌ๋Š” ๊ฒƒ์ด ์ „์ œ๋‹ค */} {t('settings.pluginDataScope')} diff --git a/src/renderer/components/main/Modal/content/editors/CounterAnimationEditorModal.tsx b/src/renderer/components/main/Modal/content/editors/CounterAnimationEditorModal.tsx index a5e0e9a1d..6188e2031 100644 --- a/src/renderer/components/main/Modal/content/editors/CounterAnimationEditorModal.tsx +++ b/src/renderer/components/main/Modal/content/editors/CounterAnimationEditorModal.tsx @@ -897,7 +897,7 @@ const CounterAnimationEditorModal = ({ onClose={onClose} title={headerTitle} headerInfo={ -
+
{/* ํ•˜๋‹จ ์•ˆ๋‚ด โ€” ์Šคํฌ๋ฆผ ์—†์ด ํ๋ฆฐ ์บก์…˜๋งŒ (ํ’€ํ•˜์ดํŠธ ์Šคํ…Œ์ด์ง€๋ผ ํ‚ค์™€ ์ถฉ๋Œ ์—†์Œ) */}
- + {t('counterSetting.pressToPreview') || '๋ˆŒ๋Ÿฌ์„œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ'}
diff --git a/src/renderer/components/main/Modal/content/managers/SoundTrimModal.tsx b/src/renderer/components/main/Modal/content/managers/SoundTrimModal.tsx index f3095ee16..b02b7f973 100644 --- a/src/renderer/components/main/Modal/content/managers/SoundTrimModal.tsx +++ b/src/renderer/components/main/Modal/content/managers/SoundTrimModal.tsx @@ -1132,7 +1132,7 @@ const SoundTrimModal = ({ title={sheetTitle} headerInfo={ headerFileName ? ( - + {headerFileName} ) : undefined @@ -1166,7 +1166,7 @@ const SoundTrimModal = ({ {/* ํ•˜๋‹จ ์•ˆ๋‚ด โ€” ์Šคํฌ๋ฆผ ์—†์ด ํ๋ฆฐ ์บก์…˜๋งŒ */}
- + {t('soundTrimModal.dragHint')}
diff --git a/src/renderer/components/main/Modal/content/pickers/CommonListPickerPage.tsx b/src/renderer/components/main/Modal/content/pickers/CommonListPickerPage.tsx index f7c9cc544..8f092f657 100644 --- a/src/renderer/components/main/Modal/content/pickers/CommonListPickerPage.tsx +++ b/src/renderer/components/main/Modal/content/pickers/CommonListPickerPage.tsx @@ -187,7 +187,7 @@ export default function CommonListPickerPage({
{emptyText}
diff --git a/src/renderer/components/main/Modal/content/pickers/WebFontInputModal.tsx b/src/renderer/components/main/Modal/content/pickers/WebFontInputModal.tsx index f2cb2881a..2937d8135 100644 --- a/src/renderer/components/main/Modal/content/pickers/WebFontInputModal.tsx +++ b/src/renderer/components/main/Modal/content/pickers/WebFontInputModal.tsx @@ -373,7 +373,7 @@ const WebFontInputModal = ({ {/* CSS ์ž…๋ ฅ ์„น์…˜ */}
-

+

{t('webFontInput.cssLabel') || '@font-face CSS'}

@@ -388,7 +388,7 @@ const WebFontInputModal = ({ {/* ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์„น์…˜ โ€” ๊ฒ€์ฆ ์ƒํƒœ๋Š” ๊ฒฐ๊ณผ ์˜†์ด ์ œ์ž๋ฆฌ, ์ƒ‰์€ ๋ฌด์ฑ„์ƒ‰ ์œ ์ง€ */}
-

+

{t('webFontInput.previewLabel') || '๋ฏธ๋ฆฌ๋ณด๊ธฐ'}

@@ -426,7 +426,7 @@ const WebFontInputModal = ({ key={weight} className="min-w-0 flex items-baseline gap-[6px]" > - + {weight} {failedWeights.has(weight) ? ( diff --git a/src/renderer/components/main/Modal/content/settings/NoteSetting.tsx b/src/renderer/components/main/Modal/content/settings/NoteSetting.tsx index 403e38c2b..bc63f60f8 100644 --- a/src/renderer/components/main/Modal/content/settings/NoteSetting.tsx +++ b/src/renderer/components/main/Modal/content/settings/NoteSetting.tsx @@ -441,7 +441,7 @@ const NoteSetting = ({ />
-

+

{t('laboratory.keyDelayAuto', { value: calculatedDelay })}

`; htmlContent += '
'; diff --git a/src/renderer/plugins/runtime/api/defineSettings.ts b/src/renderer/plugins/runtime/api/defineSettings.ts index 83d10abad..a15fe6694 100644 --- a/src/renderer/plugins/runtime/api/defineSettings.ts +++ b/src/renderer/plugins/runtime/api/defineSettings.ts @@ -433,7 +433,7 @@ export const createDefineSettings = (deps: DefineSettingsDependencies) => { normalizedSections.some((section) => section.renderVisible) ? 'display:none' : '' - }" class="text-fg-faint text-body text-center">${noSettingsText}
`; + }" class="text-fg-caption text-body text-center">${noSettingsText}
`; htmlContent += '
'; diff --git a/src/renderer/styles/main.css b/src/renderer/styles/main.css index 08acc0919..9bebed1de 100644 --- a/src/renderer/styles/main.css +++ b/src/renderer/styles/main.css @@ -808,7 +808,7 @@ body.dmn-dragging * { ๋žจํ”„๋ฅผ ์˜ฌ๋ฆด ๋•Œ๋งˆ๋‹ค ๊ฑฐํ„ฐ๋งŒ ์œ ๋… ๊นŠ๊ฒŒ ํŒŒ์ธ๋‹ค */ .webfont-cm-editor .cm-gutters { background: var(--ui-bg-app); - color: var(--ui-fg-faint); + color: var(--ui-fg-caption); border: none; } diff --git a/src/renderer/styles/tokens/dark.css b/src/renderer/styles/tokens/dark.css index 58badf2d2..f1638e389 100644 --- a/src/renderer/styles/tokens/dark.css +++ b/src/renderer/styles/tokens/dark.css @@ -137,6 +137,9 @@ --ui-fg-strong: #ffffff; --ui-fg: #dcdcde; --ui-fg-muted: #9d9d9f; + /* ์ž‘์€ ์•ˆ๋‚ดยท์ˆ˜์น˜์ฒ˜๋Ÿผ ๋ฐ˜๋“œ์‹œ ์ฝํ˜€์•ผ ํ•˜๋Š” ์ €๊ฐ•๋„ ํ…์ŠคํŠธ */ + --ui-fg-caption: #8c8c8e; + /* placeholderยท์žฅ์‹ ์•„์ด์ฝ˜์ฒ˜๋Ÿผ ์ •๋ณด ์œ„๊ณ„ ๋ฐ–์˜ ๋ณด์กฐ ํ‘œํ˜„ */ --ui-fg-faint: #8c8c8e; --ui-fg-disabled: #68686a; diff --git a/src/renderer/styles/tokens/light.css b/src/renderer/styles/tokens/light.css index cab7b3147..ead25686d 100644 --- a/src/renderer/styles/tokens/light.css +++ b/src/renderer/styles/tokens/light.css @@ -151,15 +151,24 @@ /* ํ…์ŠคํŠธ ์œ„๊ณ„ โ€” ๋‹คํฌ๊ฐ€ ์ˆœ๋ฐฑ์„ ํ”ผํ•˜๋“ฏ ์—ฌ๊ธฐ์„œ๋„ ์ˆœํ‘์„ ํ”ผํ•œ๋‹ค. ๋ฐฐ๊ฒฝ๊ณผ ๊ฐ™์€ ๋ฐฉํ–ฅ์˜ ์ฟจ ํŽธํ–ฅ์„ ์ค˜ ๊ธ€์ž๋งŒ ๋ฌด์ฑ„์ƒ‰์œผ๋กœ ๋–  ๋ณด์ด์ง€ ์•Š๊ฒŒ ํ–ˆ๋‹ค. - ์นด๋“œ ์œ„ ๋Œ€๋น„๋Š” 13.5 / 6.3 / 4.5 / 2.6์œผ๋กœ ๋‹คํฌ(12.6 / 6.4 / 5.1 / 3.1)์™€ - ๊ฐ™์€ ๋ชจ์–‘์ด๋‹ค. ๋ฐ์€ ๋ฐ”ํƒ•์—์„œ๋Š” ๋Œ€๋น„๋น„๊ฐ€ ๋น ๋ฅด๊ฒŒ ์••์ถ•๋ผ ์•„๋ž˜ ๋‘ ๋‹จ์ด - ๋‹คํฌ๋งŒํผ ๋ฒŒ์–ด์ง€์ง€ ์•Š๋Š”๋‹ค - ๋Œ€๋น„๋น„๊ฐ€ ์•„๋‹ˆ๋ผ L* ๊ฐ„๊ฒฉ์„ ๋งž์ท„๋‹ค. + ๋ฐ์€ ๋ฐ”ํƒ•์—์„œ๋Š” ๋Œ€๋น„๋น„๊ฐ€ ๋น ๋ฅด๊ฒŒ ์••์ถ•๋˜๋ฏ€๋กœ ์ฝ์–ด์•ผ ํ•˜๋Š” ์ž‘์€ ์•ˆ๋‚ดยท์ˆ˜์น˜๋Š” + caption์ด ๋งก๊ณ , placeholderยท์žฅ์‹์€ faint์— ๋‚จ๊ธด๋‹ค. ๋‘ ์—ญํ• ์„ ํ•œ ๊ฐ’์œผ๋กœ ๋ฌถ์œผ๋ฉด + ์•ฑ ๋ฐ”๋‹ฅ์˜ ์ž‘์€ ๊ธ€์ž๋ฅผ ์‚ด๋ฆด ๋•Œ muted์™€ faint๊ฐ€ ๊ฐ™์€ ๋‹จ๊ณ„๋กœ ๋ญ‰์นœ๋‹ค. + + ๊ทธ ๊ฒฐ๊ณผ ๋ผ์ดํŠธ์—์„œ๋Š” muted์™€ caption์ด 1.6 L*๊นŒ์ง€ ๋ถ™๋Š”๋‹ค. ์˜๋„๋œ ๊ฐ’์ด๋‹ค - + muted๊ฐ€ ์•ฑ ๋ฐ”๋‹ฅ์—์„œ ์ด๋ฏธ 4.9:1์ด๋ผ ๊ทธ ์•„๋ž˜์— ๋‹คํฌ๋งŒํผ(6.5 L*)์˜ ๋‹จ์„ ๋” ๋‘˜ + ์ž๋ฆฌ๊ฐ€ ์—†๋‹ค. ๊ฐ„๊ฒฉ์ด ์ข์•„ ๋ณด์ธ๋‹ค๊ณ  caption์„ ๋ฐํžˆ๋ฉด ์•ฑ ๋ฐ”๋‹ฅ๊ณผ ์ฝ”๋“œ ๊ฑฐํ„ฐ์˜ + ์ž‘์€ ๊ธ€์ž๊ฐ€ ๋‹ค์‹œ 4.5:1 ์•„๋ž˜๋กœ ๋–จ์–ด์ง„๋‹ค. ๋‘ ๋‹จ์˜ ๊ตฌ๋ถ„์€ ํ†ค์ด ์•„๋‹ˆ๋ผ + ํฌ๊ธฐ์™€ ์œ„์น˜๊ฐ€ ๋งก๋Š”๋‹ค. ๊บผ์ง ํ–‰ ์ด๋ฆ„(disabled)์ด ๋ณธ๋ฌธ ๊ธฐ์ค€์— ๋ชป ๋ฏธ์น˜๋Š” ๊ฒƒ์€ ๋‹คํฌ์™€ ๊ฐ™์€ ์˜๋„๋‹ค. ๊ธฐ์ค€์„ ๋งž์ถ”๋ฉด muted์™€ ๊ตฌ๋ถ„์ด ์•ˆ ๋ผ '๊บผ์ง'์œผ๋กœ ์ฝํžˆ์ง€ ์•Š๋Š”๋‹ค */ /* ํ˜ธ๋ฒ„์—์„œ ํ•œ ์นธ ๋” ์˜ฌ๋ฆด ์ž๋ฆฌ. ์—ฌ๊ธฐ์„œ '์˜ฌ๋ฆฐ๋‹ค'๋Š” ์ž‰ํฌ๋ฅผ ๋” ๋„ฃ๋Š”๋‹ค๋Š” ๋œป */ --ui-fg-strong: #0d1219; --ui-fg: #242a33; - --ui-fg-muted: #545c68; + --ui-fg-muted: #4e5665; + /* bg-app ์œ„ 12px ์•ˆ๋‚ดยท์ฝ”๋“œ ๊ฑฐํ„ฐ์—์„œ 4.5:1 ์ด์ƒ */ + --ui-fg-caption: #525a66; + /* placeholderยท์žฅ์‹ ์•„์ด์ฝ˜ ์ „์šฉ, muted์™€ ์•ฝ 11.3 L* ๋ถ„๋ฆฌ */ --ui-fg-faint: #69727f; --ui-fg-disabled: #949ca9; diff --git a/src/renderer/utils/cardRecipes.ts b/src/renderer/utils/cardRecipes.ts index 3dc68bfce..d27ef67c2 100644 --- a/src/renderer/utils/cardRecipes.ts +++ b/src/renderer/utils/cardRecipes.ts @@ -5,8 +5,9 @@ // ์„น์…˜ ๋ž˜ํผ โ€” ๋ผ๋ฒจ + ์นด๋“œ๋ฅผ 6px ๊ฐ„๊ฒฉ์œผ๋กœ ๋ฌถ์Œ export const SECTION_WRAPPER_CLASS = 'flex flex-col gap-[6px]'; -// ์„น์…˜ ๋ผ๋ฒจ โ€” ์นด๋“œ ์œ„ faint ์„œ๋ธŒํ—ค๋” -export const SECTION_LABEL_CLASS = 'text-fg-faint text-body text-left px-[2px]'; +// ์„น์…˜ ๋ผ๋ฒจ โ€” ์นด๋“œ ์œ„์—์„œ ์ฝํ˜€์•ผ ํ•˜๋Š” ์ €๊ฐ•๋„ ์„œ๋ธŒํ—ค๋” +export const SECTION_LABEL_CLASS = + 'text-fg-caption text-body text-left px-[2px]'; // ๊ทธ๋ฃน ์นด๋“œ โ€” ๊ด€๋ จ ํ–‰์„ ํ•˜๋‚˜์˜ ๋ฉด์œผ๋กœ ๋ฌถ๋Š” ์ปจํ…Œ์ด๋„ˆ export const SECTION_CARD_CLASS = diff --git a/tailwind.config.js b/tailwind.config.js index c7938707d..2b5d21a87 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -39,6 +39,7 @@ module.exports = { DEFAULT: 'var(--ui-fg)', strong: 'var(--ui-fg-strong)', muted: 'var(--ui-fg-muted)', + caption: 'var(--ui-fg-caption)', faint: 'var(--ui-fg-faint)', disabled: 'var(--ui-fg-disabled)', }, From 3e147ac6519c4dc301d2f5ec7909ff97f8f67b28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=97=B0=EC=9A=B0?= Date: Sat, 29 Aug 2026 03:53:07 +0900 Subject: [PATCH 11/11] =?UTF-8?q?fix:=20=EC=95=84=EC=9D=B4=EC=BD=98=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EC=A0=91=EA=B7=BC=EC=84=B1=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UpdateModal.accessibility.test.tsx | 66 +++++++++++++++++++ .../Modal/content/dialogs/UpdateModal.tsx | 6 +- .../main/Tool/SettingTool.modalLock.test.tsx | 35 ++++++++++ .../components/main/Tool/SettingTool.tsx | 36 +++++----- .../main/Tool/TabTool.accessibility.test.tsx | 49 ++++++++++++++ src/renderer/components/main/Tool/TabTool.tsx | 3 + .../main/Tool/ToolBar.modalLock.test.tsx | 26 +++++++- src/renderer/components/main/Tool/ToolBar.tsx | 3 + 8 files changed, 206 insertions(+), 18 deletions(-) create mode 100644 src/renderer/components/main/Modal/content/dialogs/UpdateModal.accessibility.test.tsx create mode 100644 src/renderer/components/main/Tool/TabTool.accessibility.test.tsx diff --git a/src/renderer/components/main/Modal/content/dialogs/UpdateModal.accessibility.test.tsx b/src/renderer/components/main/Modal/content/dialogs/UpdateModal.accessibility.test.tsx new file mode 100644 index 000000000..6a43febfa --- /dev/null +++ b/src/renderer/components/main/Modal/content/dialogs/UpdateModal.accessibility.test.tsx @@ -0,0 +1,66 @@ +import React, { act } from 'react'; +import { createRoot, type Root } from 'react-dom/client'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import UpdateModal from './UpdateModal'; + +vi.mock('@contexts/useTranslation', () => ({ + useTranslation: () => ({ t: (key: string) => key }), +})); + +const updateInfo = { + currentVersion: '2.0.0', + latestVersion: '2.0.1', + releaseUrl: 'https://example.com/release', + releaseName: '2.0.1', + releaseNotes: '', + publishedAt: '', +}; + +describe('UpdateModal ์ ‘๊ทผ์„ฑ ์ด๋ฆ„', () => { + let host: HTMLDivElement; + let root: Root; + + beforeEach(() => { + globalThis.IS_REACT_ACT_ENVIRONMENT = true; + host = document.createElement('div'); + document.body.appendChild(host); + root = createRoot(host); + }); + + afterEach(async () => { + await act(async () => root.unmount()); + host.remove(); + document.body.innerHTML = ''; + }); + + const renderModal = async (isLatestVersion: boolean) => { + await act(async () => { + root.render( + , + ); + }); + }; + + it('์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ์œผ๋ฉด ์ƒˆ ๋ฒ„์ „ ์ œ๋ชฉ์„ dialog ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค', async () => { + await renderModal(false); + + expect( + document.querySelector('[role="dialog"]')?.getAttribute('aria-label'), + ).toBe('update.title'); + }); + + it('์ตœ์‹  ๋ฒ„์ „์ด๋ฉด ํ™”๋ฉด ์ œ๋ชฉ๊ณผ ๊ฐ™์€ dialog ์ด๋ฆ„์„ ์‚ฌ์šฉํ•œ๋‹ค', async () => { + await renderModal(true); + + expect( + document.querySelector('[role="dialog"]')?.getAttribute('aria-label'), + ).toBe('update.latestAlready'); + }); +}); diff --git a/src/renderer/components/main/Modal/content/dialogs/UpdateModal.tsx b/src/renderer/components/main/Modal/content/dialogs/UpdateModal.tsx index c546acbde..0d7bf93f2 100644 --- a/src/renderer/components/main/Modal/content/dialogs/UpdateModal.tsx +++ b/src/renderer/components/main/Modal/content/dialogs/UpdateModal.tsx @@ -93,7 +93,9 @@ const UpdateModal = ({

{t('update.title')}

{publishedLabel && ( -

+

{publishedLabel}

)} diff --git a/src/renderer/components/main/Tool/SettingTool.modalLock.test.tsx b/src/renderer/components/main/Tool/SettingTool.modalLock.test.tsx index 7bb7d36c6..2a749fc09 100644 --- a/src/renderer/components/main/Tool/SettingTool.modalLock.test.tsx +++ b/src/renderer/components/main/Tool/SettingTool.modalLock.test.tsx @@ -97,4 +97,39 @@ describe('SettingTool modal lock', () => { ).toBe('false'); expect(useUIStore.getState().isExportImportPopupOpen).toBe(false); }); + + it('FloatingTooltip ๋ฒ„ํŠผ์€ ์ ‘๊ทผ์„ฑ ์ด๋ฆ„๋งŒ ์ œ๊ณตํ•˜๊ณ  native title์„ ์ค‘๋ณตํ•˜์ง€ ์•Š๋Š”๋‹ค', async () => { + await render(); + + for (const label of [ + 'tooltip.exportPreset', + 'tooltip.importExport', + 'tooltip.overlayClose', + 'tooltip.settings', + ]) { + const button = host.querySelector( + `[aria-label="${label}"]`, + ); + expect(button, label).not.toBeNull(); + expect(button?.hasAttribute('title')).toBe(false); + } + + await act(async () => { + host + .querySelector('[aria-label="tooltip.overlayClose"]') + ?.click(); + }); + const openOverlayButton = host.querySelector( + '[aria-label="tooltip.overlayOpen"]', + ); + expect(openOverlayButton).not.toBeNull(); + expect(openOverlayButton?.hasAttribute('title')).toBe(false); + + await act(async () => root.render()); + const backButton = host.querySelector( + '[aria-label="tooltip.back"]', + ); + expect(backButton).not.toBeNull(); + expect(backButton?.hasAttribute('title')).toBe(false); + }); }); diff --git a/src/renderer/components/main/Tool/SettingTool.tsx b/src/renderer/components/main/Tool/SettingTool.tsx index 7cbd4b9ea..43282833d 100644 --- a/src/renderer/components/main/Tool/SettingTool.tsx +++ b/src/renderer/components/main/Tool/SettingTool.tsx @@ -221,6 +221,15 @@ SettingToolProps) => { } }; + const overlayToggleLabel = isObsModeActive + ? t('tooltip.overlayObsDisabled') + : isOverlayVisible + ? t('tooltip.overlayClose') + : t('tooltip.overlayOpen'); + const settingsToggleLabel = isSettingsOpen + ? t('tooltip.back') + : t('tooltip.settings'); + return (
{!isSettingsOpen && ( @@ -228,6 +237,7 @@ SettingToolProps) => {