style: data-theme 기반 라이트/다크 수동 전환 지원
Co-Authored-By: Claude Opus 5
@a8abb3c79603cbaa5c8657b726e14590fba76fa7
--- app/globals.css
+++ app/globals.css
... | ... | @@ -2,36 +2,57 @@ |
| 2 | 2 |
|
| 3 | 3 |
/* 색상 토큰 출처: edupay-app lib/ui/core/tokens/app_colors.dart |
| 4 | 4 |
(Figma: FOX PAY 1차 제작시안, node 91:10205). 다크 값은 edupay-app에 대응 |
| 5 |
- 다크 시안이 없어 "임의 도출 — 정식 다크 시안 확정 시 교체". */ |
|
| 5 |
+ 다크 시안이 없어 "임의 도출 — 정식 다크 시안 확정 시 교체". |
|
| 6 |
+ 메커니즘: `color-scheme` + `light-dark()`(Chrome 123+/Safari 17.5+/Firefox 120+ |
|
| 7 |
+ 지원, 미지원 브라우저는 값이 무효화되므로 사내 어드민 한정 트레이드오프로 채택 |
|
| 8 |
+ — design 계획 §4.1/§5.3, 2026-07-28 게이트 승인). 각 시맨틱 토큰이 |
|
| 9 |
+ `light-dark(라이트값, 다크값)`으로 라이트/다크 값을 한 선언에 함께 보유해 |
|
| 10 |
+ 드리프트를 구조적으로 차단한다. 수동 전환은 아래 `:root[data-theme]`가 |
|
| 11 |
+ `color-scheme`만 덮어써 처리하므로 값 자체는 이 블록 한 곳에만 존재한다 |
|
| 12 |
+ (Next.js 16 번들 문서 preventing-flash-before-hydration.md §Themes 패턴). */ |
|
| 6 | 13 |
:root {
|
| 14 |
+ color-scheme: light dark; |
|
| 15 |
+ |
|
| 7 | 16 |
/* Brand */ |
| 8 |
- --primary: #502ef5; |
|
| 9 |
- --primary-container: #6d5ce7; |
|
| 10 |
- --on-primary: #ffffff; |
|
| 11 |
- --on-primary-container: #e2e0fb; |
|
| 12 |
- --secondary: #877aec; |
|
| 17 |
+ --primary: light-dark(#502ef5, #877aec); |
|
| 18 |
+ --primary-container: light-dark(#6d5ce7, #502ef5); |
|
| 19 |
+ --on-primary: light-dark(#ffffff, #ffffff); |
|
| 20 |
+ --on-primary-container: light-dark(#e2e0fb, #e2e0fb); |
|
| 21 |
+ --secondary: light-dark(#877aec, #9b92ff); |
|
| 13 | 22 |
|
| 14 | 23 |
/* Surface / background (원 토큰: surface / gray0) */ |
| 15 |
- --background: #ffffff; |
|
| 16 |
- --surface: #ffffff; |
|
| 17 |
- --surface-muted: #f5f7fa; |
|
| 24 |
+ --background: light-dark(#ffffff, #14161b); |
|
| 25 |
+ --surface: light-dark(#ffffff, #1e2128); |
|
| 26 |
+ --surface-muted: light-dark(#f5f7fa, #2c3038); |
|
| 18 | 27 |
|
| 19 | 28 |
/* Foreground / text (원 토큰: onSurface 계열) */ |
| 20 |
- --foreground: #2c3038; |
|
| 21 |
- --foreground-muted: #565d6b; |
|
| 22 |
- --foreground-subtle: #6b7486; |
|
| 29 |
+ --foreground: light-dark(#2c3038, #f5f7fa); |
|
| 30 |
+ --foreground-muted: light-dark(#565d6b, #bbc4d8); |
|
| 31 |
+ --foreground-subtle: light-dark(#6b7486, #8790a3); |
|
| 23 | 32 |
|
| 24 | 33 |
/* Border (원 토큰: outline 계열) */ |
| 25 |
- --border: #dae0ed; |
|
| 26 |
- --border-subtle: #e1e6f1; |
|
| 34 |
+ --border: light-dark(#dae0ed, #424752); |
|
| 35 |
+ --border-subtle: light-dark(#e1e6f1, #343943); |
|
| 27 | 36 |
|
| 28 | 37 |
/* Status */ |
| 29 |
- --danger: #ec2e2f; |
|
| 30 |
- --success: #21b770; |
|
| 31 |
- --warning: #ffb020; |
|
| 38 |
+ --danger: light-dark(#ec2e2f, #ff5c5d); |
|
| 39 |
+ --success: light-dark(#21b770, #3acb93); |
|
| 40 |
+ --warning: light-dark(#ffb020, #ffc24d); |
|
| 32 | 41 |
|
| 33 | 42 |
/* Overlay */ |
| 34 |
- --scrim: rgb(0 0 0 / 0.6); |
|
| 43 |
+ --scrim: light-dark(rgb(0 0 0 / 0.6), rgb(0 0 0 / 0.7)); |
|
| 44 |
+} |
|
| 45 |
+ |
|
| 46 |
+/* 수동 테마 선택 — <html data-theme="light"|"dark">가 `color-scheme`만 덮어써 |
|
| 47 |
+ 위 light-dark() 값이 해당 분기로 고정된다(값 자체는 여기서 재정의하지 않음). |
|
| 48 |
+ data-theme 속성이 없거나 "system"이면 :root의 `color-scheme: light dark`가 |
|
| 49 |
+ 유지되어 OS `prefers-color-scheme`를 그대로 따른다(feature 전달 사양). */ |
|
| 50 |
+:root[data-theme="light"] {
|
|
| 51 |
+ color-scheme: light; |
|
| 52 |
+} |
|
| 53 |
+ |
|
| 54 |
+:root[data-theme="dark"] {
|
|
| 55 |
+ color-scheme: dark; |
|
| 35 | 56 |
} |
| 36 | 57 |
|
| 37 | 58 |
@theme inline {
|
... | ... | @@ -231,34 +252,6 @@ |
| 231 | 252 |
4/8 grid(4·8·12·16·20·24·32·40)는 Tailwind 기본 --spacing: 0.25rem 스케일과 |
| 232 | 253 |
정확히 일치한다: xs→p-1, sm→p-2, md→p-3, lg→p-4, xl→p-5, xxl→p-6, |
| 233 | 254 |
xxxl→p-8, xxxxl→p-10. */ |
| 234 |
- |
|
| 235 |
-@media (prefers-color-scheme: dark) {
|
|
| 236 |
- :root {
|
|
| 237 |
- /* 임의 도출 — 정식 다크 시안 확정 시 교체 */ |
|
| 238 |
- --primary: #877aec; |
|
| 239 |
- --primary-container: #502ef5; |
|
| 240 |
- --on-primary: #ffffff; |
|
| 241 |
- --on-primary-container: #e2e0fb; |
|
| 242 |
- --secondary: #9b92ff; |
|
| 243 |
- |
|
| 244 |
- --background: #14161b; |
|
| 245 |
- --surface: #1e2128; |
|
| 246 |
- --surface-muted: #2c3038; |
|
| 247 |
- |
|
| 248 |
- --foreground: #f5f7fa; |
|
| 249 |
- --foreground-muted: #bbc4d8; |
|
| 250 |
- --foreground-subtle: #8790a3; |
|
| 251 |
- |
|
| 252 |
- --border: #424752; |
|
| 253 |
- --border-subtle: #343943; |
|
| 254 |
- |
|
| 255 |
- --danger: #ff5c5d; |
|
| 256 |
- --success: #3acb93; |
|
| 257 |
- --warning: #ffc24d; |
|
| 258 |
- |
|
| 259 |
- --scrim: rgb(0 0 0 / 0.7); |
|
| 260 |
- } |
|
| 261 |
-} |
|
| 262 | 255 |
|
| 263 | 256 |
body {
|
| 264 | 257 |
background: var(--background); |
Add a comment
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?