임동욱 임동욱 07-28
style: data-theme 기반 라이트/다크 수동 전환 지원
Co-Authored-By: Claude Opus 5 
@a8abb3c79603cbaa5c8657b726e14590fba76fa7
app/globals.css
--- app/globals.css
+++ app/globals.css
@@ -2,36 +2,57 @@
 
 /* 색상 토큰 출처: edupay-app lib/ui/core/tokens/app_colors.dart
    (Figma: FOX PAY 1차 제작시안, node 91:10205). 다크 값은 edupay-app에 대응
-   다크 시안이 없어 "임의 도출 — 정식 다크 시안 확정 시 교체". */
+   다크 시안이 없어 "임의 도출 — 정식 다크 시안 확정 시 교체".
+   메커니즘: `color-scheme` + `light-dark()`(Chrome 123+/Safari 17.5+/Firefox 120+
+   지원, 미지원 브라우저는 값이 무효화되므로 사내 어드민 한정 트레이드오프로 채택
+   — design 계획 §4.1/§5.3, 2026-07-28 게이트 승인). 각 시맨틱 토큰이
+   `light-dark(라이트값, 다크값)`으로 라이트/다크 값을 한 선언에 함께 보유해
+   드리프트를 구조적으로 차단한다. 수동 전환은 아래 `:root[data-theme]`가
+   `color-scheme`만 덮어써 처리하므로 값 자체는 이 블록 한 곳에만 존재한다
+   (Next.js 16 번들 문서 preventing-flash-before-hydration.md §Themes 패턴). */
 :root {
+  color-scheme: light dark;
+
   /* Brand */
-  --primary: #502ef5;
-  --primary-container: #6d5ce7;
-  --on-primary: #ffffff;
-  --on-primary-container: #e2e0fb;
-  --secondary: #877aec;
+  --primary: light-dark(#502ef5, #877aec);
+  --primary-container: light-dark(#6d5ce7, #502ef5);
+  --on-primary: light-dark(#ffffff, #ffffff);
+  --on-primary-container: light-dark(#e2e0fb, #e2e0fb);
+  --secondary: light-dark(#877aec, #9b92ff);
 
   /* Surface / background (원 토큰: surface / gray0) */
-  --background: #ffffff;
-  --surface: #ffffff;
-  --surface-muted: #f5f7fa;
+  --background: light-dark(#ffffff, #14161b);
+  --surface: light-dark(#ffffff, #1e2128);
+  --surface-muted: light-dark(#f5f7fa, #2c3038);
 
   /* Foreground / text (원 토큰: onSurface 계열) */
-  --foreground: #2c3038;
-  --foreground-muted: #565d6b;
-  --foreground-subtle: #6b7486;
+  --foreground: light-dark(#2c3038, #f5f7fa);
+  --foreground-muted: light-dark(#565d6b, #bbc4d8);
+  --foreground-subtle: light-dark(#6b7486, #8790a3);
 
   /* Border (원 토큰: outline 계열) */
-  --border: #dae0ed;
-  --border-subtle: #e1e6f1;
+  --border: light-dark(#dae0ed, #424752);
+  --border-subtle: light-dark(#e1e6f1, #343943);
 
   /* Status */
-  --danger: #ec2e2f;
-  --success: #21b770;
-  --warning: #ffb020;
+  --danger: light-dark(#ec2e2f, #ff5c5d);
+  --success: light-dark(#21b770, #3acb93);
+  --warning: light-dark(#ffb020, #ffc24d);
 
   /* Overlay */
-  --scrim: rgb(0 0 0 / 0.6);
+  --scrim: light-dark(rgb(0 0 0 / 0.6), rgb(0 0 0 / 0.7));
+}
+
+/* 수동 테마 선택 — <html data-theme="light"|"dark">가 `color-scheme`만 덮어써
+   위 light-dark() 값이 해당 분기로 고정된다(값 자체는 여기서 재정의하지 않음).
+   data-theme 속성이 없거나 "system"이면 :root의 `color-scheme: light dark`가
+   유지되어 OS `prefers-color-scheme`를 그대로 따른다(feature 전달 사양). */
+:root[data-theme="light"] {
+  color-scheme: light;
+}
+
+:root[data-theme="dark"] {
+  color-scheme: dark;
 }
 
 @theme inline {
@@ -231,34 +252,6 @@
    4/8 grid(4·8·12·16·20·24·32·40)는 Tailwind 기본 --spacing: 0.25rem 스케일과
    정확히 일치한다: xs→p-1, sm→p-2, md→p-3, lg→p-4, xl→p-5, xxl→p-6,
    xxxl→p-8, xxxxl→p-10. */
-
-@media (prefers-color-scheme: dark) {
-  :root {
-    /* 임의 도출 — 정식 다크 시안 확정 시 교체 */
-    --primary: #877aec;
-    --primary-container: #502ef5;
-    --on-primary: #ffffff;
-    --on-primary-container: #e2e0fb;
-    --secondary: #9b92ff;
-
-    --background: #14161b;
-    --surface: #1e2128;
-    --surface-muted: #2c3038;
-
-    --foreground: #f5f7fa;
-    --foreground-muted: #bbc4d8;
-    --foreground-subtle: #8790a3;
-
-    --border: #424752;
-    --border-subtle: #343943;
-
-    --danger: #ff5c5d;
-    --success: #3acb93;
-    --warning: #ffc24d;
-
-    --scrim: rgb(0 0 0 / 0.7);
-  }
-}
 
 body {
   background: var(--background);
Add a comment
List