@iyulab/components 1.40.1 → 1.40.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/_virtual/_glob-assets_raw/dark.css.e8ac080d.js +1 -1
  3. package/dist/_virtual/_glob-assets_raw/light.css.637cb2f8.js +1 -1
  4. package/dist/components/UElement.js +25 -0
  5. package/dist/components/button/UButton.d.ts +1 -1
  6. package/dist/components/button/UButton.js +1 -1
  7. package/dist/components/checkbox/UCheckbox.d.ts +1 -1
  8. package/dist/components/checkbox/UCheckbox.js +1 -1
  9. package/dist/components/checkbox/UCheckbox.styles.js +1 -1
  10. package/dist/components/chip/UChip.styles.js +1 -1
  11. package/dist/components/date-picker/UDatePicker.d.ts +1 -1
  12. package/dist/components/field/UField.styles.js +1 -1
  13. package/dist/components/file-input/UFileInput.d.ts +1 -1
  14. package/dist/components/file-input/UFileInput.js +1 -1
  15. package/dist/components/icon/UIcon.js +2 -2
  16. package/dist/components/icon-button/UIconButton.d.ts +1 -1
  17. package/dist/components/icon-button/UIconButton.js +1 -1
  18. package/dist/components/input/UInput.styles.js +1 -1
  19. package/dist/components/split-panel/USplitPanel.d.ts +2 -2
  20. package/dist/components/split-panel/USplitPanel.js +11 -3
  21. package/dist/components/switch/USwitch.d.ts +1 -1
  22. package/dist/components/switch/USwitch.js +1 -1
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +2 -1
  25. package/dist/styles/dark.css +1 -1
  26. package/dist/styles/light.css +2 -2
  27. package/dist/utilities/Toast.d.ts +1 -1
  28. package/dist/utilities/Toast.js +1 -1
  29. package/dist/utilities/devWarning.d.ts +22 -6
  30. package/dist/utilities/devWarning.js +45 -3
  31. package/dist/utilities/elements.d.ts +1 -1
  32. package/dist/utilities/elements.js +1 -1
  33. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.40.3] - 2026-09-13
4
+
5
+ ### Fixed
6
+
7
+ - **`createDevWarner`/`devWarnOnce`, and the `u-icon`/`u-split-panel` warnings built on them, were
8
+ a complete no-op in every published release since 1.40.1** — the guard used
9
+ `import.meta.env.DEV`, which Vite resolves statically at this library's own build time (not the
10
+ consumer's), so the published dist always shipped with `false` baked in and Rollup's dead-code
11
+ elimination removed the warning body entirely. No published dev-mode warning from this package
12
+ has ever actually fired for a consumer. Replaced with `process.env.NODE_ENV !== 'production'`,
13
+ which this library's own build leaves untouched and each consumer's own bundler resolves against
14
+ its own dev/production build (the same convention used by other libraries in this ecosystem for
15
+ the same reason). A new test asserts the built dist directly, not just the source.
16
+
17
+ ## [1.40.2] - 2026-09-13
18
+
19
+ ### Added
20
+
21
+ - **`createDevWarner(scope)` / `devWarnOnce` / `resetDevWarnings` are public** (barrel and
22
+ `@iyulab/components/dist/utilities/devWarning.js`). The one-shot, development-only warning
23
+ helper that `u-icon` and `u-split-panel` use since 1.40.1 is now the shared primitive for the
24
+ sibling packages' warnings of the same kind — each package gets its own `[scope]` prefix and
25
+ its own key space, and `resetDevWarnings()` clears every scope for tests. No behavior change
26
+ for existing warnings.
27
+
3
28
  ## [1.40.1] - 2026-09-13
4
29
 
5
30
  ### Added
@@ -1,4 +1,4 @@
1
1
  //#region \0glob-assets_raw/dark.css.e8ac080d
2
- var dark_css_default = ":root[theme=\"dark\"] {\n color-scheme: dark;\n\n /* Neutral Colors */\n --u-neutral-0: #000000;\n --u-neutral-50: #0A0A0A;\n --u-neutral-100: #121212;\n --u-neutral-200: #1E1E1E;\n --u-neutral-300: #2A2A2A;\n --u-neutral-400: #3D3D3D;\n --u-neutral-500: #525252;\n --u-neutral-600: #6E6E6E;\n --u-neutral-700: #8A8A8A;\n --u-neutral-800: #B0B0B0;\n --u-neutral-900: #D4D4D4;\n --u-neutral-1000: #FFFFFF;\n\n /* Blue Colors */\n --u-blue-0: #030D1A;\n --u-blue-100: #061629;\n --u-blue-200: #0A223B;\n --u-blue-300: #103150;\n --u-blue-400: #174167;\n --u-blue-500: #205281;\n --u-blue-600: #2A659D;\n --u-blue-700: #3678B8;\n --u-blue-800: #478DCE;\n --u-blue-900: #5DA2DF;\n --u-blue-1000: #79BBEA;\n\n /* Green Colors */\n --u-green-0: #06140C;\n --u-green-100: #0A2114;\n --u-green-200: #10301D;\n --u-green-300: #164228;\n --u-green-400: #1F5535;\n --u-green-500: #2A6942;\n --u-green-600: #367E50;\n --u-green-700: #4A9563;\n --u-green-800: #63AB7A;\n --u-green-900: #80BE97;\n --u-green-1000: #9BCFB1;\n\n /* Yellow Colors */\n --u-yellow-0: #1C1300;\n --u-yellow-100: #2D1E00;\n --u-yellow-200: #402B00;\n --u-yellow-300: #553900;\n --u-yellow-400: #6B4900;\n --u-yellow-500: #825A00;\n --u-yellow-600: #9A6C00;\n --u-yellow-700: #B08012;\n --u-yellow-800: #C69635;\n --u-yellow-900: #D9AD5A;\n --u-yellow-1000: #E8C57F;\n\n /* Red Colors */\n --u-red-0: #1C0707;\n --u-red-100: #2C0B0B;\n --u-red-200: #3E1111;\n --u-red-300: #541818;\n --u-red-400: #6C2020;\n --u-red-500: #852A2A;\n --u-red-600: #A13535;\n --u-red-700: #B94949;\n --u-red-800: #CB6262;\n --u-red-900: #D98383;\n --u-red-1000: #E7A2A2;\n\n /* Orange Colors */\n --u-orange-0: #1D0E00;\n --u-orange-100: #2F1700;\n --u-orange-200: #421F00;\n --u-orange-300: #582A00;\n --u-orange-400: #703600;\n --u-orange-500: #8A4500;\n --u-orange-600: #A55600;\n --u-orange-700: #BD6A18;\n --u-orange-800: #D1823C;\n --u-orange-900: #E19D67;\n --u-orange-1000: #EDB992;\n\n /* Teal Colors */\n --u-teal-0: #041411;\n --u-teal-100: #07201B;\n --u-teal-200: #0B2E28;\n --u-teal-300: #10403A;\n --u-teal-400: #16544D;\n --u-teal-500: #1E6860;\n --u-teal-600: #297D74;\n --u-teal-700: #3A9389;\n --u-teal-800: #54A89F;\n --u-teal-900: #73BBB3;\n --u-teal-1000: #93CBC5;\n\n /* Cyan Colors */\n --u-cyan-0: #041318;\n --u-cyan-100: #071F29;\n --u-cyan-200: #0B2D3A;\n --u-cyan-300: #104050;\n --u-cyan-400: #165469;\n --u-cyan-500: #1E6A82;\n --u-cyan-600: #287F9C;\n --u-cyan-700: #3995B5;\n --u-cyan-800: #53AACC;\n --u-cyan-900: #73BEDD;\n --u-cyan-1000: #95CFE8;\n\n /* Purple Colors */\n --u-purple-0: #150A22;\n --u-purple-100: #220F36;\n --u-purple-200: #31164B;\n --u-purple-300: #432064;\n --u-purple-400: #572B80;\n --u-purple-500: #6C379C;\n --u-purple-600: #8245B7;\n --u-purple-700: #975BCB;\n --u-purple-800: #AB79D9;\n --u-purple-900: #BD99E2;\n --u-purple-1000: #CEB8EA;\n\n /* Pink Colors */\n --u-pink-0: #1F0A14;\n --u-pink-100: #310F20;\n --u-pink-200: #451532;\n --u-pink-300: #5C1D45;\n --u-pink-400: #74285A;\n --u-pink-500: #8F336F;\n --u-pink-600: #AB4187;\n --u-pink-700: #C0569D;\n --u-pink-800: #D174B3;\n --u-pink-900: #E095C7;\n --u-pink-1000: #EBB5D9;\n\n /* ==========================================================================\n Role Colors — 의미 역할 층\n 컴포넌트는 팔레트 프리미티브가 아니라 이 층을 참조한다.\n 브랜드 색 교체는 아래 25개만 재정의하면 시맨틱 층까지 따라온다.\n\n 단은 '강도' 축이다 (weakest -> strong). 용도(배경/테두리/텍스트)는\n 소비처가 정한다 -- 단을 속성에 묶으면 primary 버튼의 배경이 어느\n 단인지 같은 모순이 생긴다.\n\n ★**다만 두 단은 용도가 확정돼 있다** — 대비 요구가 서로 다르기 때문이다.\n `-color` = **면**. `--u-*-txt-color` 가 그 위에 얹히므로 on-color 기준 4.5.\n `-strong` = **글자·아이콘**. `--u-bg-color` 위에 놓이므로 바탕 기준 4.5.\n 라이트에서는 바탕도 on-color 도 흰색이라 두 요구가 한 조건으로 합쳐지지만,\n **다크에서는 정반대 방향**이다(바탕 #121212 · on-color #FFFFFF). 바탕에서 읽히는\n 밝은 단은 흰 글자를 못 받고, 흰 글자를 받는 어두운 단은 바탕에서 안 읽힌다.\n ⇒ 다크에서 `-color` 와 `-strong` 이 두 단 벌어지는 것이 그 결과다.\n `tests/build/token-contrast.test.ts` 가 값을 지킨다.\n ========================================================================== */\n\n /* primary — 브랜드 · 강조 · 포커스 · 링크 */\n --u-primary-color-weakest: var(--u-blue-200);\n --u-primary-color-weaker: var(--u-blue-300);\n --u-primary-color-weak: var(--u-blue-500);\n --u-primary-color: var(--u-blue-600); /* 면: 흰 글자 6.09 ✓ */\n --u-primary-color-strong: var(--u-blue-800); /* 글자: 바탕 5.32 ✓ (700 은 4.05 ✗) */\n\n /* info — 정보 안내 (primary 와 기본값은 같으나 역할이 다르다 — 브랜딩은 primary 만 바꾼다) */\n --u-info-color-weakest: var(--u-blue-200);\n --u-info-color-weaker: var(--u-blue-300);\n --u-info-color-weak: var(--u-blue-500);\n --u-info-color: var(--u-blue-600);\n --u-info-color-strong: var(--u-blue-800);\n\n /* success — 성공 · 완료 */\n --u-success-color-weakest: var(--u-green-200);\n --u-success-color-weaker: var(--u-green-300);\n --u-success-color-weak: var(--u-green-500);\n --u-success-color: var(--u-green-600);\n --u-success-color-strong: var(--u-green-700);\n\n /* warning — 주의 */\n --u-warning-color-weakest: var(--u-yellow-200);\n --u-warning-color-weaker: var(--u-yellow-300);\n --u-warning-color-weak: var(--u-yellow-500);\n --u-warning-color: var(--u-yellow-600);\n --u-warning-color-strong: var(--u-yellow-700);\n\n /* danger — 오류 · 위험 · 유효성 실패 */\n --u-danger-color-weakest: var(--u-red-200);\n --u-danger-color-weaker: var(--u-red-300);\n --u-danger-color-weak: var(--u-red-500);\n --u-danger-color: var(--u-red-600); /* 면: 흰 글자 6.83 ✓ */\n --u-danger-color-strong: var(--u-red-800); /* 글자: 바탕 4.87 ✓ (700 은 3.67 ✗) */\n /* ==========================================================================\n Radius Scale — 모서리 반경\n 리터럴을 흩뿌리면 값이 갈린다(실제로 pill 이 999px/9999px 두 표기로 나뉘어 있었다).\n ========================================================================== */\n --u-radius-none: 0;\n --u-radius-sm: 3px;\n --u-radius-md: 4px;\n --u-radius-lg: 6px;\n --u-radius-xl: 8px;\n /* ★상단 2단은 **면(surface)용**이다 — 카드·패널·대화상자처럼 큰 사각형은 컨트롤과\n 같은 반경을 쓰면 각져 보인다. 기존 5단은 **컨트롤용**이고 값을 바꾸지 않는다\n (바꾸면 모든 소비자의 버튼·입력이 동시에 움직인다). */\n --u-radius-2xl: 12px;\n --u-radius-3xl: 16px;\n --u-radius-pill: 9999px;\n --u-radius-circle: 50%;\n\n /* ==========================================================================\n Space Scale — 컨테이너·오버레이의 절대 여백\n ⚠**여백은 하나의 축이 아니다.** 이 스케일은 컨테이너/오버레이의 **레이아웃 여백**만\n 담당한다. 폼·인라인 요소의 `em` 여백은 상속된 `font-size` 에 비례하는 것이 의도이므로\n **여기로 흡수하지 않는다.** 반경과 마찬가지로 테마와 무관하므로 두 시트가 같다.\n 축 구분과 근거는 docs/design-tokens.md 참조.\n ========================================================================== */\n --u-space-3xs: 2px;\n --u-space-2xs: 4px;\n --u-space-xs: 6px;\n --u-space-sm: 8px;\n --u-space-md: 12px;\n --u-space-lg: 16px;\n /* ★상단 4단은 **화면 리듬**이다 — 카드 안쪽 여백(20~24)·섹션 간격(24~32)·페이지 여백\n (32~40). 종전에는 이 구간이 통째로 비어 있어 소비자가 전부 리터럴로 적었고,\n 그래서 앱마다 화면 밀도가 달라졌다. `lg`(16px) 이하는 **요소 사이**, `xl` 이상은\n **영역 사이**로 나눠 쓴다.\n ⚠`xs: 6px` 는 4px 격자에서 벗어나 있으나 **바꾸지 않는다** — 이미 배선된 컨트롤\n 여백이 전부 움직인다. 신규 단은 전부 4의 배수로 둔다. */\n --u-space-xl: 20px;\n --u-space-2xl: 24px;\n --u-space-3xl: 32px;\n --u-space-4xl: 40px;\n\n /* Text Colors */\n --u-txt-color: var(--u-neutral-900);\n --u-txt-color-inverse: var(--u-neutral-100);\n\n /* 유채색 배경 위의 글자 — warning 만 다르다(light.css 참조). */\n --u-primary-txt-color: #FFFFFF;\n --u-info-txt-color: #FFFFFF;\n --u-success-txt-color: #FFFFFF;\n --u-danger-txt-color: #FFFFFF;\n /* ★라이트 시트와 같은 이유로 중립 최상단을 쓴다 — 다크에서는 그 값이 #FFFFFF 라\n 결과가 종전과 같다(4.65 ✓). 리터럴 대신 토큰을 쓰는 이유는 라이트에서 뒤집히기\n 때문이다(#000000). 두 시트가 같은 선언이 되어 어긋날 자리가 없어진다. */\n --u-warning-txt-color: var(--u-neutral-1000);\n --u-txt-color-hover: var(--u-primary-color-strong);\n --u-txt-color-active: var(--u-primary-color-strong);\n --u-txt-color-disabled: var(--u-neutral-500);\n /* 라이트와 같은 기준 — 놓이는 모든 면에서 AA. neutral-700(#8A8A8A)은 바탕 5.43 ·\n 올림면(neutral-300) 4.16 · active 면(neutral-400) 3.15 로 미달이었다.\n neutral-800(#B0B0B0) = 8.64 · 6.62 · 5.01. 본문(neutral-900)과의 대비비 1.46. */\n --u-txt-color-weak: var(--u-neutral-800);\n --u-txt-color-strong: var(--u-neutral-1000);\n\n --u-link-txt-color: var(--u-primary-color-strong);\n --u-tooltip-txt-color: var(--u-neutral-100);\n\n /* Icon Colors */\n --u-icon-color: var(--u-neutral-800);\n --u-icon-color-inverse: var(--u-neutral-100);\n --u-icon-color-hover: var(--u-primary-color-strong);\n --u-icon-color-active: var(--u-primary-color-strong);\n --u-icon-color-disabled: var(--u-neutral-500);\n\n /* Border Colors */\n --u-border-color: var(--u-neutral-400);\n --u-border-color-weak: var(--u-neutral-300);\n --u-border-color-strong: var(--u-neutral-500);\n --u-border-color-hover: var(--u-neutral-500);\n\n /* ★상태 테두리는 `-strong` 단이다 — light.css 의 같은 자리 주석 참조.\n 이 테마가 실제로 미달이었다: focus 2.74 · invalid 2.44 → 4.74 / 4.34. */\n --u-input-border-color: var(--u-neutral-400);\n --u-input-border-color-hover: var(--u-neutral-500);\n --u-input-border-color-focus: var(--u-primary-color-strong);\n --u-input-border-color-invalid: var(--u-danger-color-strong);\n\n /* Background Colors */\n --u-bg-color: var(--u-neutral-100);\n --u-bg-color-inverse: var(--u-neutral-0);\n --u-bg-color-hover: var(--u-neutral-300);\n --u-bg-color-active: var(--u-neutral-400);\n --u-bg-color-disabled: var(--u-neutral-100);\n\n /* 면 높이 — 상세는 light.css 참조. 다크는 **바탕보다 밝은 쪽**이 위다.\n neutral-300(#2A2A2A)은 바탕(#121212) 대비 1.42 로, 소비 측이 오버라이드로 쓰던\n neutral-200(1.15)보다 실제로 더 잘 구분된다 — 그 오버라이드가 해결하려던 증상이\n *\"툴바·헤더가 배경에 묻힌다\"* 였다. */\n --u-bg-color-raised: var(--u-neutral-300);\n\n /* ★올림면 위의 hover — `--u-bg-color-hover`(전역, 바탕면 전용)와 값이 같아서\n («둘 다 neutral-300») 카드·표 머리 위에 마우스를 올려도 아무 일도 안 일어났다\n (소비앱 초안 2026-08-06 · `L2-11-1`). 전역 토큰 하나로는 \"지금 위에 있는 면\"을\n 알 방법이 없어(참조 지점마다 계산 기준이 다를 수 없다) **전용 토큰**을 신설한다.\n\n ⚠**A안**(`raised`를 neutral-200으로) — 과거에 거부된 값(위 주석). **B안**(전역\n `hover`를 neutral-400으로) — `--u-bg-color-active` 와 충돌해 결함이 다른 쌍으로\n 옮겨간다. ⇒ hover 는 본래 \"현재 면보다 한 단 위\"이지 절대 단이 아니므로\n `color-mix()` 로 raised 자신을 기준 삼아 상대 계산한다.\n\n N=6% 는 raised(L≈0.023)와 active(L≈0.047, neutral-400) 사이 대비 계산으로 골랐다 —\n 결과 L≈0.038, raised 대비 ≈1.21(라이트의 기존 \"약한\" 1.04보다 뚜렷하고, `bg→raised`\n 의 기존 1.31 과 비슷한 체감), active 와는 값이 달라 겹치지 않는다(≈#373737 vs\n active #3D3D3D). N 을 더 올리면(≈10%+) active 를 넘어서 순서가 뒤집힌다 — 확인함. */\n --u-bg-color-raised-hover: color-mix(in srgb, var(--u-bg-color-raised), white 6%);\n\n /* 유채색 표면 — 라이트와 **같은 단이 아니라 같은 세기**로 짝지었다(light.css 참조).\n 팔레트 유채색 틴트가 다크에서 짓눌리므로 한 단 위를 쓴다. green 은 대칭이라 그대로. */\n --u-primary-bg-color: var(--u-blue-100);\n --u-info-bg-color: var(--u-blue-100);\n --u-success-bg-color: var(--u-green-0);\n --u-danger-bg-color: var(--u-red-100);\n /* warning 합류 — 라이트는 yellow-200 이다(단 이름이 다른 이유는 light.css 참조). */\n --u-warning-bg-color: var(--u-yellow-100);\n\n --u-input-bg-color: var(--u-neutral-200);\n --u-panel-bg-color: var(--u-neutral-200);\n --u-overlay-bg-color: rgba(0, 0, 0, 0.7);\n --u-tooltip-bg-color: rgba(255, 255, 255, 0.85);\n\n /* Shadow Colors */\n --u-shadow-color-weaker: rgba(0, 0, 0, 0.15);\n --u-shadow-color-weak: rgba(0, 0, 0, 0.25);\n --u-shadow-color-normal: rgba(0, 0, 0, 0.35);\n --u-shadow-color-strong: rgba(0, 0, 0, 0.45);\n --u-shadow-color-stronger: rgba(0, 0, 0, 0.60);\n\n /* Elevation Scale — 근거는 light.css 의 같은 자리 주석 참조.\n ★다크는 알파가 3배 가까이 진하다 — 리터럴로 흩어져 있던 종전에는 이 차이가\n 적용되지 않아 다크에서 그림자가 사실상 보이지 않았다. */\n --u-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.25), 0 1px 1px rgba(0, 0, 0, 0.14); /* = shadow-color-weak */\n --u-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.35), 0 1px 2px rgba(0, 0, 0, 0.18); /* = shadow-color-normal */\n --u-shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.45), 0 2px 4px rgba(0, 0, 0, 0.22); /* = shadow-color-strong */\n --u-shadow-xl: 0 6px 18px rgba(0, 0, 0, 0.60), 0 2px 6px rgba(0, 0, 0, 0.30); /* = shadow-color-stronger */\n\n\n /* ==========================================================================\n Type Scale — 글자 크기·굵기·행간·자간의 **한 벌**\n ★**크기만 주면 스케일이 되지 않는다.** 소비자가 크기만 받고 굵기·행간을 각자 정하면\n 결국 화면마다 다른 위계가 생긴다 — 그래서 네 값을 **단 단위로 묶어** 낸다.\n\n 단 이름은 크기가 아니라 **역할**이다(`lg`/`xl` 이 아니라 `title`/`label`). 역할로 두면\n 소비자가 *\"어느 크기를 쓸까\"* 가 아니라 *\"이 글자는 무엇인가\"* 를 묻게 된다.\n\n ★**한글 기준으로 값을 골랐다**:\n ⑴ 행간은 **1.4 이상**. 라틴 관례(1.2)는 한글에서 답답하다 — 받침 때문에 글자 상자가\n 세로로 꽉 차 있어 라틴과 같은 행간이면 줄이 붙어 보인다.\n ⑵ 자간은 **0 이 기본**이고 큰 제목만 **음수**. 한글에 양수 자간(letter-spacing)을 주면\n 가독성이 떨어진다 — 라틴 소문자 조판 관례를 그대로 옮기면 안 되는 자리다.\n ⑶ `overline` 에만 양수 자간을 둔다. 그 단은 **영문·숫자 라벨**을 전제한 자리이고,\n 한글을 넣을 소비자는 자간을 0 으로 덮어야 한다(문서에 명시).\n\n ⚠**`text-transform` 은 토큰으로 내지 않는다.** 한글에는 대문자가 없어 `uppercase` 가\n 아무 효과가 없고, 영문이 섞이면 그것만 커져 오히려 어수선해진다.\n ========================================================================== */\n --u-text-display-size: 26px;\n --u-text-display-weight: 700;\n --u-text-display-leading: 1.4;\n --u-text-display-tracking: -0.02em;\n\n --u-text-title-size: 20px;\n --u-text-title-weight: 700;\n --u-text-title-leading: 1.45;\n --u-text-title-tracking: -0.01em;\n\n --u-text-subtitle-size: 16px;\n --u-text-subtitle-weight: 600;\n --u-text-subtitle-leading: 1.5;\n --u-text-subtitle-tracking: 0;\n\n --u-text-body-size: 14px;\n --u-text-body-weight: 400;\n --u-text-body-leading: 1.6;\n --u-text-body-tracking: 0;\n\n --u-text-label-size: 13px;\n --u-text-label-weight: 600;\n --u-text-label-leading: 1.5;\n --u-text-label-tracking: 0;\n\n --u-text-caption-size: 12px;\n --u-text-caption-weight: 400;\n --u-text-caption-leading: 1.5;\n --u-text-caption-tracking: 0;\n\n --u-text-overline-size: 11px;\n --u-text-overline-weight: 700;\n --u-text-overline-leading: 1.45;\n --u-text-overline-tracking: 0.06em;\n\n /* ==========================================================================\n Motion — 지속시간·이징\n ★값이 없으면 앱마다 다른 속도가 생기고, `prefers-reduced-motion` 은 **반드시**\n 빠뜨리는 앱이 나온다. 축을 여기 두고 존중 규칙은 아래 미디어 쿼리가 진다.\n ========================================================================== */\n --u-duration-instant: 80ms;\n --u-duration-fast: 140ms;\n --u-duration-normal: 220ms;\n --u-duration-slow: 320ms;\n --u-ease-standard: cubic-bezier(0.2, 0, 0, 1);\n --u-ease-decelerate: cubic-bezier(0, 0, 0, 1);\n --u-ease-accelerate: cubic-bezier(0.3, 0, 1, 1);\n\n /* Scrollbar Colors */\n --u-scrollbar-color: var(--u-neutral-500);\n --u-scrollbar-color-hover: var(--u-neutral-600);\n --u-scrollbar-track-color: transparent;\n\n /* ==========================================================================\n Font Styles\n ★**CJK 폴백을 스택에 포함한다 — 국제화이지 취향이 아니다.**\n 종전 스택(`-apple-system, Segoe UI, Noto Sans, Helvetica, Arial`)에는 **한중일 글립을\n 가진 폰트가 하나도 없었다.** Windows 에서 한글은 `Segoe UI` 에 없으므로 UA 가 임의로\n 맑은 고딕을 고르고, macOS 는 `-apple-system` 이 Apple SD Gothic Neo 를 준다 ⇒ **같은 앱이\n OS 마다 다른 글꼴로 렌더**됐다. 폴백을 명시하면 그 선택이 결정적이 된다.\n ⚠**웹폰트를 로드하지 않는다.** 라이브러리가 네트워크 요청을 만들면 소비자의 CSP·\n 오프라인 환경을 깬다. 스택 맨 앞의 `Pretendard` 는 **소비자가 로드했을 때만** 잡히는\n 선택적 이름이다(없으면 조용히 다음으로 넘어간다).\n ========================================================================== */\n --u-font-base: 'Pretendard Variable', Pretendard, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR', 'Hiragino Sans', 'Yu Gothic UI', 'Microsoft YaHei', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';\n --u-font-mono: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Monaco, 'Courier New', monospace;\n --u-font-serif: 'Georgia', 'Times New Roman', Times, serif;\n --u-font-display: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;\n --u-font-modern: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n --u-font-rounded: 'Nunito', 'Quicksand', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n}";
2
+ var dark_css_default = ":root[theme=\"dark\"] {\n color-scheme: dark;\n\n /* Neutral Colors */\n --u-neutral-0: #000000;\n --u-neutral-50: #0A0A0A;\n --u-neutral-100: #121212;\n --u-neutral-200: #1E1E1E;\n --u-neutral-300: #2A2A2A;\n --u-neutral-400: #3D3D3D;\n --u-neutral-500: #525252;\n --u-neutral-600: #6E6E6E;\n --u-neutral-700: #8A8A8A;\n --u-neutral-800: #B0B0B0;\n --u-neutral-900: #D4D4D4;\n --u-neutral-1000: #FFFFFF;\n\n /* Blue Colors */\n --u-blue-0: #030D1A;\n --u-blue-100: #061629;\n --u-blue-200: #0A223B;\n --u-blue-300: #103150;\n --u-blue-400: #174167;\n --u-blue-500: #205281;\n --u-blue-600: #2A659D;\n --u-blue-700: #3678B8;\n --u-blue-800: #478DCE;\n --u-blue-900: #5DA2DF;\n --u-blue-1000: #79BBEA;\n\n /* Green Colors */\n --u-green-0: #06140C;\n --u-green-100: #0A2114;\n --u-green-200: #10301D;\n --u-green-300: #164228;\n --u-green-400: #1F5535;\n --u-green-500: #2A6942;\n --u-green-600: #367E50;\n --u-green-700: #4A9563;\n --u-green-800: #63AB7A;\n --u-green-900: #80BE97;\n --u-green-1000: #9BCFB1;\n\n /* Yellow Colors */\n --u-yellow-0: #1C1300;\n --u-yellow-100: #2D1E00;\n --u-yellow-200: #402B00;\n --u-yellow-300: #553900;\n --u-yellow-400: #6B4900;\n --u-yellow-500: #825A00;\n --u-yellow-600: #9A6C00;\n --u-yellow-700: #B08012;\n --u-yellow-800: #C69635;\n --u-yellow-900: #D9AD5A;\n --u-yellow-1000: #E8C57F;\n\n /* Red Colors */\n --u-red-0: #1C0707;\n --u-red-100: #2C0B0B;\n --u-red-200: #3E1111;\n --u-red-300: #541818;\n --u-red-400: #6C2020;\n --u-red-500: #852A2A;\n --u-red-600: #A13535;\n --u-red-700: #B94949;\n --u-red-800: #CB6262;\n --u-red-900: #D98383;\n --u-red-1000: #E7A2A2;\n\n /* Orange Colors */\n --u-orange-0: #1D0E00;\n --u-orange-100: #2F1700;\n --u-orange-200: #421F00;\n --u-orange-300: #582A00;\n --u-orange-400: #703600;\n --u-orange-500: #8A4500;\n --u-orange-600: #A55600;\n --u-orange-700: #BD6A18;\n --u-orange-800: #D1823C;\n --u-orange-900: #E19D67;\n --u-orange-1000: #EDB992;\n\n /* Teal Colors */\n --u-teal-0: #041411;\n --u-teal-100: #07201B;\n --u-teal-200: #0B2E28;\n --u-teal-300: #10403A;\n --u-teal-400: #16544D;\n --u-teal-500: #1E6860;\n --u-teal-600: #297D74;\n --u-teal-700: #3A9389;\n --u-teal-800: #54A89F;\n --u-teal-900: #73BBB3;\n --u-teal-1000: #93CBC5;\n\n /* Cyan Colors */\n --u-cyan-0: #041318;\n --u-cyan-100: #071F29;\n --u-cyan-200: #0B2D3A;\n --u-cyan-300: #104050;\n --u-cyan-400: #165469;\n --u-cyan-500: #1E6A82;\n --u-cyan-600: #287F9C;\n --u-cyan-700: #3995B5;\n --u-cyan-800: #53AACC;\n --u-cyan-900: #73BEDD;\n --u-cyan-1000: #95CFE8;\n\n /* Purple Colors */\n --u-purple-0: #150A22;\n --u-purple-100: #220F36;\n --u-purple-200: #31164B;\n --u-purple-300: #432064;\n --u-purple-400: #572B80;\n --u-purple-500: #6C379C;\n --u-purple-600: #8245B7;\n --u-purple-700: #975BCB;\n --u-purple-800: #AB79D9;\n --u-purple-900: #BD99E2;\n --u-purple-1000: #CEB8EA;\n\n /* Pink Colors */\n --u-pink-0: #1F0A14;\n --u-pink-100: #310F20;\n --u-pink-200: #451532;\n --u-pink-300: #5C1D45;\n --u-pink-400: #74285A;\n --u-pink-500: #8F336F;\n --u-pink-600: #AB4187;\n --u-pink-700: #C0569D;\n --u-pink-800: #D174B3;\n --u-pink-900: #E095C7;\n --u-pink-1000: #EBB5D9;\n\n /* ==========================================================================\n Role Colors — 의미 역할 층\n 컴포넌트는 팔레트 프리미티브가 아니라 이 층을 참조한다.\n 브랜드 색 교체는 아래 25개만 재정의하면 시맨틱 층까지 따라온다.\n\n 단은 '강도' 축이다 (weakest -> strong). 용도(배경/테두리/텍스트)는\n 소비처가 정한다 -- 단을 속성에 묶으면 primary 버튼의 배경이 어느\n 단인지 같은 모순이 생긴다.\n\n ★**다만 두 단은 용도가 확정돼 있다** — 대비 요구가 서로 다르기 때문이다.\n `-color` = **면**. `--u-*-txt-color` 가 그 위에 얹히므로 on-color 기준 4.5.\n `-strong` = **글자·아이콘**. `--u-bg-color` 위에 놓이므로 바탕 기준 4.5.\n 라이트에서는 바탕도 on-color 도 흰색이라 두 요구가 한 조건으로 합쳐지지만,\n **다크에서는 정반대 방향**이다(바탕 #121212 · on-color #FFFFFF). 바탕에서 읽히는\n 밝은 단은 흰 글자를 못 받고, 흰 글자를 받는 어두운 단은 바탕에서 안 읽힌다.\n ⇒ 다크에서 `-color` 와 `-strong` 이 두 단 벌어지는 것이 그 결과다.\n `tests/build/token-contrast.test.ts` 가 값을 지킨다.\n ========================================================================== */\n\n /* primary — 브랜드 · 강조 · 포커스 · 링크 */\n --u-primary-color-weakest: var(--u-blue-200);\n --u-primary-color-weaker: var(--u-blue-300);\n --u-primary-color-weak: var(--u-blue-500);\n --u-primary-color: var(--u-blue-600); /* 면: 흰 글자 6.09 ✓ */\n --u-primary-color-strong: var(--u-blue-800); /* 글자: 바탕 5.32 ✓ (700 은 4.05 ✗) */\n\n /* info — 정보 안내 (primary 와 기본값은 같으나 역할이 다르다 — 브랜딩은 primary 만 바꾼다) */\n --u-info-color-weakest: var(--u-blue-200);\n --u-info-color-weaker: var(--u-blue-300);\n --u-info-color-weak: var(--u-blue-500);\n --u-info-color: var(--u-blue-600);\n --u-info-color-strong: var(--u-blue-800);\n\n /* success — 성공 · 완료 */\n --u-success-color-weakest: var(--u-green-200);\n --u-success-color-weaker: var(--u-green-300);\n --u-success-color-weak: var(--u-green-500);\n --u-success-color: var(--u-green-600);\n --u-success-color-strong: var(--u-green-700);\n\n /* warning — 주의 */\n --u-warning-color-weakest: var(--u-yellow-200);\n --u-warning-color-weaker: var(--u-yellow-300);\n --u-warning-color-weak: var(--u-yellow-500);\n --u-warning-color: var(--u-yellow-600);\n --u-warning-color-strong: var(--u-yellow-700);\n\n /* danger — 오류 · 위험 · 유효성 실패 */\n --u-danger-color-weakest: var(--u-red-200);\n --u-danger-color-weaker: var(--u-red-300);\n --u-danger-color-weak: var(--u-red-500);\n --u-danger-color: var(--u-red-600); /* 면: 흰 글자 6.83 ✓ */\n --u-danger-color-strong: var(--u-red-800); /* 글자: 바탕 4.87 ✓ (700 은 3.67 ✗) */\n /* ==========================================================================\n Radius Scale — 모서리 반경\n 리터럴을 흩뿌리면 값이 갈린다(실제로 pill 이 999px/9999px 두 표기로 나뉘어 있었다).\n ========================================================================== */\n --u-radius-none: 0;\n --u-radius-sm: 3px;\n --u-radius-md: 4px;\n --u-radius-lg: 6px;\n --u-radius-xl: 8px;\n /* ★상단 2단은 **면(surface)용**이다 — 카드·패널·대화상자처럼 큰 사각형은 컨트롤과\n 같은 반경을 쓰면 각져 보인다. 기존 5단은 **컨트롤용**이고 값을 바꾸지 않는다\n (바꾸면 모든 소비자의 버튼·입력이 동시에 움직인다). */\n --u-radius-2xl: 12px;\n --u-radius-3xl: 16px;\n --u-radius-pill: 9999px;\n --u-radius-circle: 50%;\n\n /* ==========================================================================\n Space Scale — 컨테이너·오버레이의 절대 여백\n ⚠**여백은 하나의 축이 아니다.** 이 스케일은 컨테이너/오버레이의 **레이아웃 여백**만\n 담당한다. 폼·인라인 요소의 `em` 여백은 상속된 `font-size` 에 비례하는 것이 의도이므로\n **여기로 흡수하지 않는다.** 반경과 마찬가지로 테마와 무관하므로 두 시트가 같다.\n 축 구분과 근거는 docs/design-tokens.md 참조.\n ========================================================================== */\n --u-space-3xs: 2px;\n --u-space-2xs: 4px;\n --u-space-xs: 6px;\n --u-space-sm: 8px;\n --u-space-md: 12px;\n --u-space-lg: 16px;\n /* ★상단 4단은 **화면 리듬**이다 — 카드 안쪽 여백(20~24)·섹션 간격(24~32)·페이지 여백\n (32~40). 종전에는 이 구간이 통째로 비어 있어 소비자가 전부 리터럴로 적었고,\n 그래서 앱마다 화면 밀도가 달라졌다. `lg`(16px) 이하는 **요소 사이**, `xl` 이상은\n **영역 사이**로 나눠 쓴다.\n ⚠`xs: 6px` 는 4px 격자에서 벗어나 있으나 **바꾸지 않는다** — 이미 배선된 컨트롤\n 여백이 전부 움직인다. 신규 단은 전부 4의 배수로 둔다. */\n --u-space-xl: 20px;\n --u-space-2xl: 24px;\n --u-space-3xl: 32px;\n --u-space-4xl: 40px;\n\n /* Text Colors */\n --u-txt-color: var(--u-neutral-900);\n --u-txt-color-inverse: var(--u-neutral-100);\n\n /* 유채색 배경 위의 글자 — warning 만 다르다(light.css 참조). */\n --u-primary-txt-color: #FFFFFF;\n --u-info-txt-color: #FFFFFF;\n --u-success-txt-color: #FFFFFF;\n --u-danger-txt-color: #FFFFFF;\n /* ★라이트 시트와 같은 이유로 중립 최상단을 쓴다 — 다크에서는 그 값이 #FFFFFF 라\n 결과가 종전과 같다(4.65 ✓). 리터럴 대신 토큰을 쓰는 이유는 라이트에서 뒤집히기\n 때문이다(#000000). 두 시트가 같은 선언이 되어 어긋날 자리가 없어진다. */\n --u-warning-txt-color: var(--u-neutral-1000);\n --u-txt-color-hover: var(--u-primary-color-strong);\n --u-txt-color-active: var(--u-primary-color-strong);\n --u-txt-color-disabled: var(--u-neutral-500);\n /* 라이트와 같은 기준 — 놓이는 모든 면에서 AA. neutral-700(#8A8A8A)은 바탕 5.43 ·\n 올림면(neutral-300) 4.16 · active 면(neutral-400) 3.15 로 미달이었다.\n neutral-800(#B0B0B0) = 8.64 · 6.62 · 5.01. 본문(neutral-900)과의 대비비 1.46. */\n --u-txt-color-weak: var(--u-neutral-800);\n --u-txt-color-strong: var(--u-neutral-1000);\n\n --u-link-txt-color: var(--u-primary-color-strong);\n --u-tooltip-txt-color: var(--u-neutral-100);\n\n /* Icon Colors */\n --u-icon-color: var(--u-neutral-800);\n --u-icon-color-inverse: var(--u-neutral-100);\n --u-icon-color-hover: var(--u-primary-color-strong);\n --u-icon-color-active: var(--u-primary-color-strong);\n --u-icon-color-disabled: var(--u-neutral-500);\n\n /* Border Colors */\n --u-border-color: var(--u-neutral-400);\n --u-border-color-weak: var(--u-neutral-300);\n --u-border-color-strong: var(--u-neutral-500);\n --u-border-color-hover: var(--u-neutral-500);\n\n /* ★상태 테두리는 `-strong` 단이다 — light.css 의 같은 자리 주석 참조.\n 이 테마가 실제로 미달이었다: focus 2.74 · invalid 2.44 → 4.74 / 4.34. */\n --u-input-border-color: var(--u-neutral-400);\n --u-input-border-color-hover: var(--u-neutral-500);\n --u-input-border-color-focus: var(--u-primary-color-strong);\n --u-input-border-color-invalid: var(--u-danger-color-strong);\n\n /* Background Colors */\n --u-bg-color: var(--u-neutral-100);\n --u-bg-color-inverse: var(--u-neutral-0);\n --u-bg-color-hover: var(--u-neutral-300);\n --u-bg-color-active: var(--u-neutral-400);\n --u-bg-color-disabled: var(--u-neutral-100);\n\n /* 면 높이 — 상세는 light.css 참조. 다크는 **바탕보다 밝은 쪽**이 위다.\n neutral-300(#2A2A2A)은 바탕(#121212) 대비 1.42 로, 소비 측이 오버라이드로 쓰던\n neutral-200(1.15)보다 실제로 더 잘 구분된다 — 그 오버라이드가 해결하려던 증상이\n *\"툴바·헤더가 배경에 묻힌다\"* 였다. */\n --u-bg-color-raised: var(--u-neutral-300);\n\n /* ★올림면 위의 hover — `--u-bg-color-hover`(전역, 바탕면 전용)와 값이 같아서\n («둘 다 neutral-300») 카드·표 머리 위에 마우스를 올려도 아무 일도 안 일어났다\n (소비자 실측). 전역 토큰 하나로는 \"지금 위에 있는 면\"을\n 알 방법이 없어(참조 지점마다 계산 기준이 다를 수 없다) **전용 토큰**을 신설한다.\n\n ⚠**A안**(`raised`를 neutral-200으로) — 과거에 거부된 값(위 주석). **B안**(전역\n `hover`를 neutral-400으로) — `--u-bg-color-active` 와 충돌해 결함이 다른 쌍으로\n 옮겨간다. ⇒ hover 는 본래 \"현재 면보다 한 단 위\"이지 절대 단이 아니므로\n `color-mix()` 로 raised 자신을 기준 삼아 상대 계산한다.\n\n N=6% 는 raised(L≈0.023)와 active(L≈0.047, neutral-400) 사이 대비 계산으로 골랐다 —\n 결과 L≈0.038, raised 대비 ≈1.21(라이트의 기존 \"약한\" 1.04보다 뚜렷하고, `bg→raised`\n 의 기존 1.31 과 비슷한 체감), active 와는 값이 달라 겹치지 않는다(≈#373737 vs\n active #3D3D3D). N 을 더 올리면(≈10%+) active 를 넘어서 순서가 뒤집힌다 — 확인함. */\n --u-bg-color-raised-hover: color-mix(in srgb, var(--u-bg-color-raised), white 6%);\n\n /* 유채색 표면 — 라이트와 **같은 단이 아니라 같은 세기**로 짝지었다(light.css 참조).\n 팔레트 유채색 틴트가 다크에서 짓눌리므로 한 단 위를 쓴다. green 은 대칭이라 그대로. */\n --u-primary-bg-color: var(--u-blue-100);\n --u-info-bg-color: var(--u-blue-100);\n --u-success-bg-color: var(--u-green-0);\n --u-danger-bg-color: var(--u-red-100);\n /* warning 합류 — 라이트는 yellow-200 이다(단 이름이 다른 이유는 light.css 참조). */\n --u-warning-bg-color: var(--u-yellow-100);\n\n --u-input-bg-color: var(--u-neutral-200);\n --u-panel-bg-color: var(--u-neutral-200);\n --u-overlay-bg-color: rgba(0, 0, 0, 0.7);\n --u-tooltip-bg-color: rgba(255, 255, 255, 0.85);\n\n /* Shadow Colors */\n --u-shadow-color-weaker: rgba(0, 0, 0, 0.15);\n --u-shadow-color-weak: rgba(0, 0, 0, 0.25);\n --u-shadow-color-normal: rgba(0, 0, 0, 0.35);\n --u-shadow-color-strong: rgba(0, 0, 0, 0.45);\n --u-shadow-color-stronger: rgba(0, 0, 0, 0.60);\n\n /* Elevation Scale — 근거는 light.css 의 같은 자리 주석 참조.\n ★다크는 알파가 3배 가까이 진하다 — 리터럴로 흩어져 있던 종전에는 이 차이가\n 적용되지 않아 다크에서 그림자가 사실상 보이지 않았다. */\n --u-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.25), 0 1px 1px rgba(0, 0, 0, 0.14); /* = shadow-color-weak */\n --u-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.35), 0 1px 2px rgba(0, 0, 0, 0.18); /* = shadow-color-normal */\n --u-shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.45), 0 2px 4px rgba(0, 0, 0, 0.22); /* = shadow-color-strong */\n --u-shadow-xl: 0 6px 18px rgba(0, 0, 0, 0.60), 0 2px 6px rgba(0, 0, 0, 0.30); /* = shadow-color-stronger */\n\n\n /* ==========================================================================\n Type Scale — 글자 크기·굵기·행간·자간의 **한 벌**\n ★**크기만 주면 스케일이 되지 않는다.** 소비자가 크기만 받고 굵기·행간을 각자 정하면\n 결국 화면마다 다른 위계가 생긴다 — 그래서 네 값을 **단 단위로 묶어** 낸다.\n\n 단 이름은 크기가 아니라 **역할**이다(`lg`/`xl` 이 아니라 `title`/`label`). 역할로 두면\n 소비자가 *\"어느 크기를 쓸까\"* 가 아니라 *\"이 글자는 무엇인가\"* 를 묻게 된다.\n\n ★**한글 기준으로 값을 골랐다**:\n ⑴ 행간은 **1.4 이상**. 라틴 관례(1.2)는 한글에서 답답하다 — 받침 때문에 글자 상자가\n 세로로 꽉 차 있어 라틴과 같은 행간이면 줄이 붙어 보인다.\n ⑵ 자간은 **0 이 기본**이고 큰 제목만 **음수**. 한글에 양수 자간(letter-spacing)을 주면\n 가독성이 떨어진다 — 라틴 소문자 조판 관례를 그대로 옮기면 안 되는 자리다.\n ⑶ `overline` 에만 양수 자간을 둔다. 그 단은 **영문·숫자 라벨**을 전제한 자리이고,\n 한글을 넣을 소비자는 자간을 0 으로 덮어야 한다(문서에 명시).\n\n ⚠**`text-transform` 은 토큰으로 내지 않는다.** 한글에는 대문자가 없어 `uppercase` 가\n 아무 효과가 없고, 영문이 섞이면 그것만 커져 오히려 어수선해진다.\n ========================================================================== */\n --u-text-display-size: 26px;\n --u-text-display-weight: 700;\n --u-text-display-leading: 1.4;\n --u-text-display-tracking: -0.02em;\n\n --u-text-title-size: 20px;\n --u-text-title-weight: 700;\n --u-text-title-leading: 1.45;\n --u-text-title-tracking: -0.01em;\n\n --u-text-subtitle-size: 16px;\n --u-text-subtitle-weight: 600;\n --u-text-subtitle-leading: 1.5;\n --u-text-subtitle-tracking: 0;\n\n --u-text-body-size: 14px;\n --u-text-body-weight: 400;\n --u-text-body-leading: 1.6;\n --u-text-body-tracking: 0;\n\n --u-text-label-size: 13px;\n --u-text-label-weight: 600;\n --u-text-label-leading: 1.5;\n --u-text-label-tracking: 0;\n\n --u-text-caption-size: 12px;\n --u-text-caption-weight: 400;\n --u-text-caption-leading: 1.5;\n --u-text-caption-tracking: 0;\n\n --u-text-overline-size: 11px;\n --u-text-overline-weight: 700;\n --u-text-overline-leading: 1.45;\n --u-text-overline-tracking: 0.06em;\n\n /* ==========================================================================\n Motion — 지속시간·이징\n ★값이 없으면 앱마다 다른 속도가 생기고, `prefers-reduced-motion` 은 **반드시**\n 빠뜨리는 앱이 나온다. 축을 여기 두고 존중 규칙은 아래 미디어 쿼리가 진다.\n ========================================================================== */\n --u-duration-instant: 80ms;\n --u-duration-fast: 140ms;\n --u-duration-normal: 220ms;\n --u-duration-slow: 320ms;\n --u-ease-standard: cubic-bezier(0.2, 0, 0, 1);\n --u-ease-decelerate: cubic-bezier(0, 0, 0, 1);\n --u-ease-accelerate: cubic-bezier(0.3, 0, 1, 1);\n\n /* Scrollbar Colors */\n --u-scrollbar-color: var(--u-neutral-500);\n --u-scrollbar-color-hover: var(--u-neutral-600);\n --u-scrollbar-track-color: transparent;\n\n /* ==========================================================================\n Font Styles\n ★**CJK 폴백을 스택에 포함한다 — 국제화이지 취향이 아니다.**\n 종전 스택(`-apple-system, Segoe UI, Noto Sans, Helvetica, Arial`)에는 **한중일 글립을\n 가진 폰트가 하나도 없었다.** Windows 에서 한글은 `Segoe UI` 에 없으므로 UA 가 임의로\n 맑은 고딕을 고르고, macOS 는 `-apple-system` 이 Apple SD Gothic Neo 를 준다 ⇒ **같은 앱이\n OS 마다 다른 글꼴로 렌더**됐다. 폴백을 명시하면 그 선택이 결정적이 된다.\n ⚠**웹폰트를 로드하지 않는다.** 라이브러리가 네트워크 요청을 만들면 소비자의 CSP·\n 오프라인 환경을 깬다. 스택 맨 앞의 `Pretendard` 는 **소비자가 로드했을 때만** 잡히는\n 선택적 이름이다(없으면 조용히 다음으로 넘어간다).\n ========================================================================== */\n --u-font-base: 'Pretendard Variable', Pretendard, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR', 'Hiragino Sans', 'Yu Gothic UI', 'Microsoft YaHei', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';\n --u-font-mono: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Monaco, 'Courier New', monospace;\n --u-font-serif: 'Georgia', 'Times New Roman', Times, serif;\n --u-font-display: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;\n --u-font-modern: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n --u-font-rounded: 'Nunito', 'Quicksand', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n}";
3
3
  //#endregion
4
4
  export { dark_css_default as default };
@@ -1,4 +1,4 @@
1
1
  //#region \0glob-assets_raw/light.css.637cb2f8
2
- var light_css_default = ":root {\n color-scheme: light;\n\n /* Neutral Colors */\n --u-neutral-0: #FFFFFF;\n --u-neutral-50: #FAFAFA;\n --u-neutral-100: #F5F5F5;\n --u-neutral-200: #EEEEEE;\n --u-neutral-300: #E0E0E0;\n --u-neutral-400: #BDBDBD;\n --u-neutral-500: #9E9E9E;\n --u-neutral-600: #757575;\n --u-neutral-700: #616161;\n --u-neutral-800: #424242;\n --u-neutral-900: #212121;\n --u-neutral-1000: #000000;\n\n /* Blue Colors */\n --u-blue-0: #E3F2FD;\n --u-blue-100: #BBDEFB;\n --u-blue-200: #90CAF9;\n --u-blue-300: #64B5F6;\n --u-blue-400: #42A5F5;\n --u-blue-500: #2196F3;\n --u-blue-600: #1E88E5;\n --u-blue-700: #1976D2;\n --u-blue-800: #1565C0;\n --u-blue-900: #0D47A1;\n --u-blue-1000: #062A5E;\n\n /* Green Colors */\n --u-green-0: #E8F5E9;\n --u-green-100: #C8E6C9;\n --u-green-200: #A5D6A7;\n --u-green-300: #81C784;\n --u-green-400: #66BB6A;\n --u-green-500: #4CAF50;\n --u-green-600: #43A047;\n --u-green-700: #388E3C;\n --u-green-800: #2E7D32;\n --u-green-900: #1B5E20;\n --u-green-1000: #0F3A12;\n\n /* Yellow Colors */\n --u-yellow-0: #FFFDE7;\n --u-yellow-100: #FFF9C4;\n --u-yellow-200: #FFF59D;\n --u-yellow-300: #FFF176;\n --u-yellow-400: #FFEE58;\n --u-yellow-500: #FFEB3B;\n --u-yellow-600: #FDD835;\n --u-yellow-700: #FBC02D;\n --u-yellow-800: #F9A825;\n --u-yellow-900: #F57F17;\n --u-yellow-1000: #8A4A00;\n\n /* Red Colors */\n --u-red-0: #FFEBEE;\n --u-red-100: #FFCDD2;\n --u-red-200: #EF9A9A;\n --u-red-300: #E57373;\n --u-red-400: #EF5350;\n --u-red-500: #F44336;\n --u-red-600: #E53935;\n --u-red-700: #D32F2F;\n --u-red-800: #C62828;\n --u-red-900: #B71C1C;\n --u-red-1000: #6B0F0F;\n\n /* Orange Colors */\n --u-orange-0: #FFF3E0;\n --u-orange-100: #FFE0B2;\n --u-orange-200: #FFCC80;\n --u-orange-300: #FFB74D;\n --u-orange-400: #FFA726;\n --u-orange-500: #FF9800;\n --u-orange-600: #FB8C00;\n --u-orange-700: #F57C00;\n --u-orange-800: #EF6C00;\n --u-orange-900: #E65100;\n --u-orange-1000: #8C3100;\n\n /* Teal Colors */\n --u-teal-0: #E0F2F1;\n --u-teal-100: #B2DFDB;\n --u-teal-200: #80CBC4;\n --u-teal-300: #4DB6AC;\n --u-teal-400: #26A69A;\n --u-teal-500: #009688;\n --u-teal-600: #00897B;\n --u-teal-700: #00796B;\n --u-teal-800: #00695C;\n --u-teal-900: #004D40;\n --u-teal-1000: #002E26;\n\n /* Cyan Colors */\n --u-cyan-0: #E0F7FA;\n --u-cyan-100: #B2EBF2;\n --u-cyan-200: #80DEEA;\n --u-cyan-300: #4DD0E1;\n --u-cyan-400: #26C6DA;\n --u-cyan-500: #00BCD4;\n --u-cyan-600: #00ACC1;\n --u-cyan-700: #0097A7;\n --u-cyan-800: #00838F;\n --u-cyan-900: #006064;\n --u-cyan-1000: #003A3D;\n\n /* Purple Colors */\n --u-purple-0: #F3E5F5;\n --u-purple-100: #E1BEE7;\n --u-purple-200: #CE93D8;\n --u-purple-300: #BA68C8;\n --u-purple-400: #AB47BC;\n --u-purple-500: #9C27B0;\n --u-purple-600: #8E24AA;\n --u-purple-700: #7B1FA2;\n --u-purple-800: #6A1B9A;\n --u-purple-900: #4A148C;\n --u-purple-1000: #2C0B54;\n\n /* Pink Colors */\n --u-pink-0: #FCE4EC;\n --u-pink-100: #F8BBD0;\n --u-pink-200: #F48FB1;\n --u-pink-300: #F06292;\n --u-pink-400: #EC407A;\n --u-pink-500: #E91E63;\n --u-pink-600: #D81B60;\n --u-pink-700: #C2185B;\n --u-pink-800: #AD1457;\n --u-pink-900: #880E4F;\n --u-pink-1000: #520830;\n\n /* ==========================================================================\n Role Colors — 의미 역할 층\n 컴포넌트는 팔레트 프리미티브가 아니라 이 층을 참조한다.\n 브랜드 색 교체는 아래 25개만 재정의하면 시맨틱 층까지 따라온다.\n\n 단은 '강도' 축이다 (weakest -> strong). 용도(배경/테두리/텍스트)는\n 소비처가 정한다 -- 단을 속성에 묶으면 primary 버튼의 배경이 어느\n 단인지 같은 모순이 생긴다.\n ========================================================================== */\n\n /* ★단이 팔레트 shade 와 1:1 이 아니다 — **대비로 고른다.**\n `-color` 는 `--u-*-txt-color` 를 받는 면이고 `-strong` 은 바탕 위의 글자다.\n 라이트에서는 바탕도 on-color 도 흰색이라 두 요구가 `contrast(shade, #FFF) ≥ 4.5`\n 하나로 합쳐지고, 그 단이 계열마다 다르다(blue 700 · red 700 · green 800).\n Material 램프는 휘도가 아니라 색상 기준으로 만들어져 있어서 단 번호가 세기를\n 보장하지 않는다 — 값은 `tests/build/token-contrast.test.ts` 가 지킨다. */\n\n /* primary — 브랜드 · 강조 · 포커스 · 링크 */\n --u-primary-color-weakest: var(--u-blue-200);\n --u-primary-color-weaker: var(--u-blue-300);\n --u-primary-color-weak: var(--u-blue-500);\n --u-primary-color: var(--u-blue-700); /* 4.60 ✓ (600 은 3.68 ✗) */\n --u-primary-color-strong: var(--u-blue-800); /* 5.75 ✓ */\n\n /* info — 정보 안내 (primary 와 기본값은 같으나 역할이 다르다 — 브랜딩은 primary 만 바꾼다) */\n --u-info-color-weakest: var(--u-blue-200);\n --u-info-color-weaker: var(--u-blue-300);\n --u-info-color-weak: var(--u-blue-500);\n --u-info-color: var(--u-blue-700);\n --u-info-color-strong: var(--u-blue-800);\n\n /* success — 성공 · 완료\n ⚠green 은 700 도 4.12 로 미달이라 두 단을 건너뛴다. 계열마다 통과 단이 다른 것이\n 이 층의 정상 상태다(단 번호를 맞추면 대비가 어긋난다). */\n --u-success-color-weakest: var(--u-green-200);\n --u-success-color-weaker: var(--u-green-300);\n --u-success-color-weak: var(--u-green-500);\n --u-success-color: var(--u-green-800); /* 5.13 ✓ (600=3.30 · 700=4.12 ✗) */\n --u-success-color-strong: var(--u-green-900); /* 7.87 ✓ */\n\n /* warning — 주의 */\n --u-warning-color-weakest: var(--u-yellow-200);\n --u-warning-color-weaker: var(--u-yellow-300);\n --u-warning-color-weak: var(--u-yellow-500);\n --u-warning-color: var(--u-yellow-600);\n /* ★다른 계열은 -strong 이 shade-800~900 인데 warning 만 1000 이다 — 노랑 램프는\n 900 까지도 흰 바탕 2.65 로 미달이고, 1000(#8A4A00)에서 처음 6.86 이 된다.\n 그 값은 갈색에 가깝지만, 이 단의 용도는 **면이 아니라 글자·아이콘**이므로 색상보다\n 가독성이 먼저다(경고 문구를 어두운 호박색으로 쓰는 것은 보편 관행이다). */\n --u-warning-color-strong: var(--u-yellow-1000);\n\n /* danger — 오류 · 위험 · 유효성 실패 */\n --u-danger-color-weakest: var(--u-red-200);\n --u-danger-color-weaker: var(--u-red-300);\n --u-danger-color-weak: var(--u-red-500);\n --u-danger-color: var(--u-red-700); /* 4.98 ✓ (600 은 4.23 ✗ — 오류 문구가 이 단이었다) */\n --u-danger-color-strong: var(--u-red-800); /* 5.62 ✓ */\n /* ==========================================================================\n Radius Scale — 모서리 반경\n 리터럴을 흩뿌리면 값이 갈린다(실제로 pill 이 999px/9999px 두 표기로 나뉘어 있었다).\n ========================================================================== */\n --u-radius-none: 0;\n --u-radius-sm: 3px;\n --u-radius-md: 4px;\n --u-radius-lg: 6px;\n --u-radius-xl: 8px;\n /* ★상단 2단은 **면(surface)용**이다 — 카드·패널·대화상자처럼 큰 사각형은 컨트롤과\n 같은 반경을 쓰면 각져 보인다. 기존 5단은 **컨트롤용**이고 값을 바꾸지 않는다\n (바꾸면 모든 소비자의 버튼·입력이 동시에 움직인다). */\n --u-radius-2xl: 12px;\n --u-radius-3xl: 16px;\n --u-radius-pill: 9999px;\n --u-radius-circle: 50%;\n\n /* ==========================================================================\n Space Scale — 컨테이너·오버레이의 절대 여백\n ⚠**여백은 하나의 축이 아니다.** 이 스케일은 컨테이너/오버레이(카드·대화상자·드로어·\n 알림·메뉴·툴팁 등)의 **레이아웃 여백**만 담당한다. 폼·인라인 요소의 `em` 여백은\n 상속된 `font-size` 에 비례하는 것이 의도이므로 **여기로 흡수하지 않는다** —\n 흡수하면 타이포를 키운 소비자의 여백이 따라 커지지 않는다.\n 축 구분과 근거는 docs/design-tokens.md 참조.\n ========================================================================== */\n --u-space-3xs: 2px;\n --u-space-2xs: 4px;\n --u-space-xs: 6px;\n --u-space-sm: 8px;\n --u-space-md: 12px;\n --u-space-lg: 16px;\n /* ★상단 4단은 **화면 리듬**이다 — 카드 안쪽 여백(20~24)·섹션 간격(24~32)·페이지 여백\n (32~40). 종전에는 이 구간이 통째로 비어 있어 소비자가 전부 리터럴로 적었고,\n 그래서 앱마다 화면 밀도가 달라졌다. `lg`(16px) 이하는 **요소 사이**, `xl` 이상은\n **영역 사이**로 나눠 쓴다.\n ⚠`xs: 6px` 는 4px 격자에서 벗어나 있으나 **바꾸지 않는다** — 이미 배선된 컨트롤\n 여백이 전부 움직인다. 신규 단은 전부 4의 배수로 둔다. */\n --u-space-xl: 20px;\n --u-space-2xl: 24px;\n --u-space-3xl: 32px;\n --u-space-4xl: 40px;\n\n /* Text Colors */\n --u-txt-color: var(--u-neutral-900);\n --u-txt-color-inverse: var(--u-neutral-0);\n /* 유채색 배경 위의 글자. --u-txt-color-inverse 는 다크에서 neutral-100(#121212)이라\n 유채색 버튼 위에서 읽히지 않는다. 브랜드 색을 바꾸는 소비자가 전경색도 함께\n 조절할 수 있도록 별도 이름을 준다.\n ⚠이 토큰이 흰색이라는 것과 그 위의 배경이 AA 를 받친다는 것은 별개다 —\n 라이트 팔레트의 shade-600 은 8색 중 6색이 흰 글자로 AA 에 미달한다(미해결). */\n --u-primary-txt-color: #FFFFFF;\n --u-info-txt-color: #FFFFFF;\n --u-success-txt-color: #FFFFFF;\n --u-danger-txt-color: #FFFFFF;\n /* ★warning 만 어둡다 — 취향이 아니라 측정이다. 노랑은 **어떤 단도** 흰 글자로 AA 를\n 받치지 못한다(라이트 최대 2.65 @900, 그마저 갈색). 다른 넷처럼 단을 옮기는 수단이\n 없으므로 전경을 뒤집는다. `--u-neutral-1000` 은 라이트 #000000 / 다크 #FFFFFF 이라\n 한 줄로 양 테마를 만족한다(라이트 15.05 · 다크 4.65). */\n --u-warning-txt-color: var(--u-neutral-1000);\n\n --u-txt-color-hover: var(--u-primary-color-strong);\n --u-txt-color-active: var(--u-primary-color-strong);\n --u-txt-color-disabled: var(--u-neutral-400);\n /* 보조 텍스트는 «놓이는 모든 면»에서 AA(4.5)를 받쳐야 한다 — 바탕만이 아니다.\n neutral-600(#757575)은 바탕 4.61 에서만 통과하고 올림면(neutral-50) 4.41 ·\n active 면(neutral-200) 3.97 로 미달이었다(표 머리·카드 위 보조 텍스트).\n neutral-700(#616161) = 바탕 6.19 · 올림면 5.93 · active 5.34.\n 본문(neutral-900)과의 대비비는 2.60 으로, 보조 텍스트를 모든 표면 위에서\n 읽히게 보장하는 흔한 디자인 시스템 기준(본문 대비 약 1.3~1.8)보다 위계가 넓다. */\n --u-txt-color-weak: var(--u-neutral-700);\n --u-txt-color-strong: var(--u-neutral-1000);\n\n --u-link-txt-color: var(--u-primary-color-strong);\n --u-tooltip-txt-color: var(--u-neutral-0);\n\n /* Icon Colors */\n --u-icon-color: var(--u-neutral-700);\n --u-icon-color-inverse: var(--u-neutral-0);\n --u-icon-color-hover: var(--u-primary-color-strong);\n --u-icon-color-active: var(--u-primary-color-strong);\n --u-icon-color-disabled: var(--u-neutral-400);\n\n /* Border Colors */\n --u-border-color: var(--u-neutral-300);\n --u-border-color-weak: var(--u-neutral-200);\n --u-border-color-strong: var(--u-neutral-400);\n --u-border-color-hover: var(--u-neutral-400);\n\n /* ★상태 테두리는 `-strong` 단이다 — 면이 아니라 **면 위에 그리는 선**이기 때문이다.\n 1.16.0 이 가른 용도(`-color` = 면 / `-strong` = 면·바탕 위)에 이 자리가 맞춰지지\n 않아 다크에서 미달이었다: focus 2.74 · invalid 2.44 (WCAG 1.4.11 기준 3.0).\n `-strong` 으로 옮기면 다크 4.74 / 4.34, 라이트 5.75 / 5.62 — 두 테마 모두 단조 상승. */\n --u-input-border-color: var(--u-neutral-300);\n --u-input-border-color-hover: var(--u-neutral-400);\n --u-input-border-color-focus: var(--u-primary-color-strong);\n --u-input-border-color-invalid: var(--u-danger-color-strong);\n\n /* Background Colors */\n --u-bg-color: var(--u-neutral-0);\n --u-bg-color-inverse: var(--u-neutral-900);\n --u-bg-color-hover: var(--u-neutral-100);\n --u-bg-color-active: var(--u-neutral-200);\n --u-bg-color-disabled: var(--u-neutral-50);\n\n /* ★면 높이 — 바탕에 인접해 있으면서 한 단 구분되는 크롬 면(툴바·표 헤더·푸터).\n 상호작용 상태(-hover/-active/-disabled)와 **다른 축**이다. 상태 이름에 높이를\n 얹으면 의미가 어긋난다(`DL-131-1`).\n `--u-panel-bg-color` 와도 다르다 — 그쪽은 **떠 있는 패널**(카드·대화상자·메뉴)이라\n 라이트에서 페이지와 같은 흰색이고 그림자가 높이를 나른다. 크롬 면은 그림자가 없어\n 라이트에서도 틴트가 필요하다.\n ⚠한 단만 정의한다. 두 단째의 실측 수요는 라이트 한쪽뿐이었다(다크에서는 소비 측\n 오버라이드가 두 값을 같은 단으로 합치고 있었다). */\n --u-bg-color-raised: var(--u-neutral-50);\n\n /* ⚠**라이트는 전역 hover 에 그대로 별칭한다 — 결손이 아니라 실측 결론이다**\n (L2-11-1, dark.css 의 같은 자리 참조). 두 시트는 각각 완결적이어야 한다는\n 불변식(`token-fallbacks.test.ts` 「키 패리티」)이 있어 다크에만 선언할 수 없다.\n 라이트 팔레트는 raised(250)·hover(245)·active(238) 가 12단 안에 몰려 있어\n (L≈0.956/0.913/0.855) 셋과 겹치지 않는 네 번째 단을 넣을 여유가 없다 — 대비를\n 조금만 올려도(≈1.10) active 와 거의 붙고(≈1단 차), 더 올리면 순서가 뒤집힌다.\n 다크와 달리 raised(250)≠hover(245) 라 «완전히 안 움직이는» 결함이 아니었으므로\n (약한 대비 1.04 로 이미 알려진 채로 남는다 — 같은 초안이 지적한 곁가지),\n 새 단을 억지로 만들지 않고 기존 hover 를 그대로 쓴다. */\n --u-bg-color-raised-hover: var(--u-bg-color-hover);\n \n /* 유채색 표면 — 정보/성공/위험 상태의 '면'.\n 역할 층의 유채색은 전경 5단뿐이라, 상태 배경이 필요한 컴포넌트는 팔레트를 직접\n 읽고 다크를 손으로 보정해야 했다. 팔레트의 유채색 틴트는 다크에서 짓눌리므로\n (바탕 대비 델타의 다크/라이트 비 — 청색 0.38~0.54) 두 테마가 **같은 단이 아니라\n 같은 세기**가 되도록 짝지었다. 실측 비: blue 1.09 · red 1.12 · green 0.91.\n\n ★**warning 이 합류했다(1.16.0 → 1.17.0).** 종전에는 *\"세기를 맞출 단 짝이 없다\"* 며\n 빠져 있었는데, 그 판정은 **shade-0 짝만** 재고 있었다. 노랑만 한 단 내려 잡으면\n 라이트 쪽 세기가 맞는다 — 바탕 대비 휘도 델타 실측:\n blue-0 0.131 · red-0 0.132 · green-0 0.117 · **yellow-200 0.110** (yellow-0 은 0.027)\n ⚠단 이름이 계열마다 다른 것(yellow 만 200)은 이 층의 정상 상태다. 라이트 팔레트는\n 휘도 순이 아니므로 같은 단 번호가 같은 세기를 뜻하지 않는다. */\n --u-primary-bg-color: var(--u-blue-0);\n --u-info-bg-color: var(--u-blue-0);\n --u-success-bg-color: var(--u-green-0);\n --u-danger-bg-color: var(--u-red-0);\n --u-warning-bg-color: var(--u-yellow-200);\n\n --u-input-bg-color: var(--u-neutral-0);\n --u-panel-bg-color: var(--u-neutral-0);\n --u-overlay-bg-color: rgba(0, 0, 0, 0.5);\n --u-tooltip-bg-color: rgba(0, 0, 0, 0.75);\n\n /* Shadow Colors */\n --u-shadow-color-weaker: rgba(0, 0, 0, 0.04);\n --u-shadow-color-weak: rgba(0, 0, 0, 0.08);\n --u-shadow-color-normal: rgba(0, 0, 0, 0.12);\n --u-shadow-color-strong: rgba(0, 0, 0, 0.16);\n --u-shadow-color-stronger: rgba(0, 0, 0, 0.24);\n\n /* ==========================================================================\n Elevation Scale — 높이(그림자)\n\n ★색 축은 있었지만 **높이 축이 없었다.** 그 결과 컴포넌트가 그림자를 리터럴로\n 썼고, 같은 리듬을 의도한 자리에 서로 다른 값 11가지가 생겼다(카드 .1 vs 캐러셀\n .15 — 둘 다 \"표면에 얹힌 카드\"인데 알파가 다르다).\n\n ⚠**그리고 그 리터럴들은 테마를 몰랐다** — `rgba(0,0,0,.1)` 은 다크 바탕에서\n 거의 보이지 않는다. 이 축을 경유하면 다크의 진한 알파(.25~.60)를 자동으로 받는다.\n\n ⚠**알파를 `--u-shadow-color-*` 로 중첩하지 않고 인라인한다.** use-site 폴백\n 생성기는 합성 값을 리터럴로 다루므로, 중첩하면 `var(--u-shadow-md, 0 2px 8px\n var(--u-shadow-color-normal))` 처럼 **폴백 안에 또 토큰이 들어간다** — 시트가\n 없을 때 그 폴백도 함께 무효가 되어 그림자가 통째로 사라진다.\n 두 축의 알파가 어긋나지 않는지는 `tests/build/elevation-scale.test.ts` 가 지킨다.\n ========================================================================== */\n --u-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 1px rgba(0, 0, 0, 0.04); /* = shadow-color-weak — 표면에 얹힌 작은 컨트롤(탭·손잡이) */\n --u-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.05); /* = shadow-color-normal — 카드·표면 */\n --u-shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.16), 0 2px 4px rgba(0, 0, 0, 0.06); /* = shadow-color-strong — 떠 있는 레이어(드롭다운·알림) */\n --u-shadow-xl: 0 6px 18px rgba(0, 0, 0, 0.24), 0 2px 6px rgba(0, 0, 0, 0.08); /* = shadow-color-stronger — 모달·최상위(대화상자·툴팁) */\n\n\n /* ==========================================================================\n Type Scale — 글자 크기·굵기·행간·자간의 **한 벌**\n ★**크기만 주면 스케일이 되지 않는다.** 소비자가 크기만 받고 굵기·행간을 각자 정하면\n 결국 화면마다 다른 위계가 생긴다 — 그래서 네 값을 **단 단위로 묶어** 낸다.\n\n 단 이름은 크기가 아니라 **역할**이다(`lg`/`xl` 이 아니라 `title`/`label`). 역할로 두면\n 소비자가 *\"어느 크기를 쓸까\"* 가 아니라 *\"이 글자는 무엇인가\"* 를 묻게 된다.\n\n ★**한글 기준으로 값을 골랐다**:\n ⑴ 행간은 **1.4 이상**. 라틴 관례(1.2)는 한글에서 답답하다 — 받침 때문에 글자 상자가\n 세로로 꽉 차 있어 라틴과 같은 행간이면 줄이 붙어 보인다.\n ⑵ 자간은 **0 이 기본**이고 큰 제목만 **음수**. 한글에 양수 자간(letter-spacing)을 주면\n 가독성이 떨어진다 — 라틴 소문자 조판 관례를 그대로 옮기면 안 되는 자리다.\n ⑶ `overline` 에만 양수 자간을 둔다. 그 단은 **영문·숫자 라벨**을 전제한 자리이고,\n 한글을 넣을 소비자는 자간을 0 으로 덮어야 한다(문서에 명시).\n\n ⚠**`text-transform` 은 토큰으로 내지 않는다.** 한글에는 대문자가 없어 `uppercase` 가\n 아무 효과가 없고, 영문이 섞이면 그것만 커져 오히려 어수선해진다.\n ========================================================================== */\n --u-text-display-size: 26px;\n --u-text-display-weight: 700;\n --u-text-display-leading: 1.4;\n --u-text-display-tracking: -0.02em;\n\n --u-text-title-size: 20px;\n --u-text-title-weight: 700;\n --u-text-title-leading: 1.45;\n --u-text-title-tracking: -0.01em;\n\n --u-text-subtitle-size: 16px;\n --u-text-subtitle-weight: 600;\n --u-text-subtitle-leading: 1.5;\n --u-text-subtitle-tracking: 0;\n\n --u-text-body-size: 14px;\n --u-text-body-weight: 400;\n --u-text-body-leading: 1.6;\n --u-text-body-tracking: 0;\n\n --u-text-label-size: 13px;\n --u-text-label-weight: 600;\n --u-text-label-leading: 1.5;\n --u-text-label-tracking: 0;\n\n --u-text-caption-size: 12px;\n --u-text-caption-weight: 400;\n --u-text-caption-leading: 1.5;\n --u-text-caption-tracking: 0;\n\n --u-text-overline-size: 11px;\n --u-text-overline-weight: 700;\n --u-text-overline-leading: 1.45;\n --u-text-overline-tracking: 0.06em;\n\n /* ==========================================================================\n Motion — 지속시간·이징\n ★값이 없으면 앱마다 다른 속도가 생기고, `prefers-reduced-motion` 은 **반드시**\n 빠뜨리는 앱이 나온다. 축을 여기 두고 존중 규칙은 아래 미디어 쿼리가 진다.\n ========================================================================== */\n --u-duration-instant: 80ms;\n --u-duration-fast: 140ms;\n --u-duration-normal: 220ms;\n --u-duration-slow: 320ms;\n --u-ease-standard: cubic-bezier(0.2, 0, 0, 1);\n --u-ease-decelerate: cubic-bezier(0, 0, 0, 1);\n --u-ease-accelerate: cubic-bezier(0.3, 0, 1, 1);\n\n /* Scrollbar Colors */\n --u-scrollbar-color: var(--u-neutral-400);\n --u-scrollbar-color-hover: var(--u-neutral-500);\n --u-scrollbar-track-color: transparent;\n\n /* ==========================================================================\n Font Styles\n ★**CJK 폴백을 스택에 포함한다 — 국제화이지 취향이 아니다.**\n 종전 스택(`-apple-system, Segoe UI, Noto Sans, Helvetica, Arial`)에는 **한중일 글립을\n 가진 폰트가 하나도 없었다.** Windows 에서 한글은 `Segoe UI` 에 없으므로 UA 가 임의로\n 맑은 고딕을 고르고, macOS 는 `-apple-system` 이 Apple SD Gothic Neo 를 준다 ⇒ **같은 앱이\n OS 마다 다른 글꼴로 렌더**됐다. 폴백을 명시하면 그 선택이 결정적이 된다.\n ⚠**웹폰트를 로드하지 않는다.** 라이브러리가 네트워크 요청을 만들면 소비자의 CSP·\n 오프라인 환경을 깬다. 스택 맨 앞의 `Pretendard` 는 **소비자가 로드했을 때만** 잡히는\n 선택적 이름이다(없으면 조용히 다음으로 넘어간다).\n ========================================================================== */\n --u-font-base: 'Pretendard Variable', Pretendard, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR', 'Hiragino Sans', 'Yu Gothic UI', 'Microsoft YaHei', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';\n --u-font-mono: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Monaco, 'Courier New', monospace;\n --u-font-serif: 'Georgia', 'Times New Roman', Times, serif;\n --u-font-display: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;\n --u-font-modern: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n --u-font-rounded: 'Nunito', 'Quicksand', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n}\n\n/* ==========================================================================\n ★`prefers-reduced-motion` 존중 — **라이브러리가 진다.**\n 소비자마다 각자 처리하게 두면 반드시 빠뜨리는 앱이 나온다(그리고 그 앱은\n 접근성 감사 전까지 아무도 모른다). 지속시간 축을 0 으로 눌러 두면 이 축을\n 경유하는 모든 애니메이션이 함께 멈춘다.\n ⚠`animation: none` 으로 강제하지 않는다 — 로딩 스피너처럼 **의미를 나르는**\n 움직임까지 죽이면 사용자가 진행 중인지 알 수 없게 된다.\n ========================================================================== */\n@media (prefers-reduced-motion: reduce) {\n :root {\n --u-duration-instant: 0ms;\n --u-duration-fast: 0ms;\n --u-duration-normal: 0ms;\n --u-duration-slow: 0ms;\n }\n}\n";
2
+ var light_css_default = ":root {\n color-scheme: light;\n\n /* Neutral Colors */\n --u-neutral-0: #FFFFFF;\n --u-neutral-50: #FAFAFA;\n --u-neutral-100: #F5F5F5;\n --u-neutral-200: #EEEEEE;\n --u-neutral-300: #E0E0E0;\n --u-neutral-400: #BDBDBD;\n --u-neutral-500: #9E9E9E;\n --u-neutral-600: #757575;\n --u-neutral-700: #616161;\n --u-neutral-800: #424242;\n --u-neutral-900: #212121;\n --u-neutral-1000: #000000;\n\n /* Blue Colors */\n --u-blue-0: #E3F2FD;\n --u-blue-100: #BBDEFB;\n --u-blue-200: #90CAF9;\n --u-blue-300: #64B5F6;\n --u-blue-400: #42A5F5;\n --u-blue-500: #2196F3;\n --u-blue-600: #1E88E5;\n --u-blue-700: #1976D2;\n --u-blue-800: #1565C0;\n --u-blue-900: #0D47A1;\n --u-blue-1000: #062A5E;\n\n /* Green Colors */\n --u-green-0: #E8F5E9;\n --u-green-100: #C8E6C9;\n --u-green-200: #A5D6A7;\n --u-green-300: #81C784;\n --u-green-400: #66BB6A;\n --u-green-500: #4CAF50;\n --u-green-600: #43A047;\n --u-green-700: #388E3C;\n --u-green-800: #2E7D32;\n --u-green-900: #1B5E20;\n --u-green-1000: #0F3A12;\n\n /* Yellow Colors */\n --u-yellow-0: #FFFDE7;\n --u-yellow-100: #FFF9C4;\n --u-yellow-200: #FFF59D;\n --u-yellow-300: #FFF176;\n --u-yellow-400: #FFEE58;\n --u-yellow-500: #FFEB3B;\n --u-yellow-600: #FDD835;\n --u-yellow-700: #FBC02D;\n --u-yellow-800: #F9A825;\n --u-yellow-900: #F57F17;\n --u-yellow-1000: #8A4A00;\n\n /* Red Colors */\n --u-red-0: #FFEBEE;\n --u-red-100: #FFCDD2;\n --u-red-200: #EF9A9A;\n --u-red-300: #E57373;\n --u-red-400: #EF5350;\n --u-red-500: #F44336;\n --u-red-600: #E53935;\n --u-red-700: #D32F2F;\n --u-red-800: #C62828;\n --u-red-900: #B71C1C;\n --u-red-1000: #6B0F0F;\n\n /* Orange Colors */\n --u-orange-0: #FFF3E0;\n --u-orange-100: #FFE0B2;\n --u-orange-200: #FFCC80;\n --u-orange-300: #FFB74D;\n --u-orange-400: #FFA726;\n --u-orange-500: #FF9800;\n --u-orange-600: #FB8C00;\n --u-orange-700: #F57C00;\n --u-orange-800: #EF6C00;\n --u-orange-900: #E65100;\n --u-orange-1000: #8C3100;\n\n /* Teal Colors */\n --u-teal-0: #E0F2F1;\n --u-teal-100: #B2DFDB;\n --u-teal-200: #80CBC4;\n --u-teal-300: #4DB6AC;\n --u-teal-400: #26A69A;\n --u-teal-500: #009688;\n --u-teal-600: #00897B;\n --u-teal-700: #00796B;\n --u-teal-800: #00695C;\n --u-teal-900: #004D40;\n --u-teal-1000: #002E26;\n\n /* Cyan Colors */\n --u-cyan-0: #E0F7FA;\n --u-cyan-100: #B2EBF2;\n --u-cyan-200: #80DEEA;\n --u-cyan-300: #4DD0E1;\n --u-cyan-400: #26C6DA;\n --u-cyan-500: #00BCD4;\n --u-cyan-600: #00ACC1;\n --u-cyan-700: #0097A7;\n --u-cyan-800: #00838F;\n --u-cyan-900: #006064;\n --u-cyan-1000: #003A3D;\n\n /* Purple Colors */\n --u-purple-0: #F3E5F5;\n --u-purple-100: #E1BEE7;\n --u-purple-200: #CE93D8;\n --u-purple-300: #BA68C8;\n --u-purple-400: #AB47BC;\n --u-purple-500: #9C27B0;\n --u-purple-600: #8E24AA;\n --u-purple-700: #7B1FA2;\n --u-purple-800: #6A1B9A;\n --u-purple-900: #4A148C;\n --u-purple-1000: #2C0B54;\n\n /* Pink Colors */\n --u-pink-0: #FCE4EC;\n --u-pink-100: #F8BBD0;\n --u-pink-200: #F48FB1;\n --u-pink-300: #F06292;\n --u-pink-400: #EC407A;\n --u-pink-500: #E91E63;\n --u-pink-600: #D81B60;\n --u-pink-700: #C2185B;\n --u-pink-800: #AD1457;\n --u-pink-900: #880E4F;\n --u-pink-1000: #520830;\n\n /* ==========================================================================\n Role Colors — 의미 역할 층\n 컴포넌트는 팔레트 프리미티브가 아니라 이 층을 참조한다.\n 브랜드 색 교체는 아래 25개만 재정의하면 시맨틱 층까지 따라온다.\n\n 단은 '강도' 축이다 (weakest -> strong). 용도(배경/테두리/텍스트)는\n 소비처가 정한다 -- 단을 속성에 묶으면 primary 버튼의 배경이 어느\n 단인지 같은 모순이 생긴다.\n ========================================================================== */\n\n /* ★단이 팔레트 shade 와 1:1 이 아니다 — **대비로 고른다.**\n `-color` 는 `--u-*-txt-color` 를 받는 면이고 `-strong` 은 바탕 위의 글자다.\n 라이트에서는 바탕도 on-color 도 흰색이라 두 요구가 `contrast(shade, #FFF) ≥ 4.5`\n 하나로 합쳐지고, 그 단이 계열마다 다르다(blue 700 · red 700 · green 800).\n Material 램프는 휘도가 아니라 색상 기준으로 만들어져 있어서 단 번호가 세기를\n 보장하지 않는다 — 값은 `tests/build/token-contrast.test.ts` 가 지킨다. */\n\n /* primary — 브랜드 · 강조 · 포커스 · 링크 */\n --u-primary-color-weakest: var(--u-blue-200);\n --u-primary-color-weaker: var(--u-blue-300);\n --u-primary-color-weak: var(--u-blue-500);\n --u-primary-color: var(--u-blue-700); /* 4.60 ✓ (600 은 3.68 ✗) */\n --u-primary-color-strong: var(--u-blue-800); /* 5.75 ✓ */\n\n /* info — 정보 안내 (primary 와 기본값은 같으나 역할이 다르다 — 브랜딩은 primary 만 바꾼다) */\n --u-info-color-weakest: var(--u-blue-200);\n --u-info-color-weaker: var(--u-blue-300);\n --u-info-color-weak: var(--u-blue-500);\n --u-info-color: var(--u-blue-700);\n --u-info-color-strong: var(--u-blue-800);\n\n /* success — 성공 · 완료\n ⚠green 은 700 도 4.12 로 미달이라 두 단을 건너뛴다. 계열마다 통과 단이 다른 것이\n 이 층의 정상 상태다(단 번호를 맞추면 대비가 어긋난다). */\n --u-success-color-weakest: var(--u-green-200);\n --u-success-color-weaker: var(--u-green-300);\n --u-success-color-weak: var(--u-green-500);\n --u-success-color: var(--u-green-800); /* 5.13 ✓ (600=3.30 · 700=4.12 ✗) */\n --u-success-color-strong: var(--u-green-900); /* 7.87 ✓ */\n\n /* warning — 주의 */\n --u-warning-color-weakest: var(--u-yellow-200);\n --u-warning-color-weaker: var(--u-yellow-300);\n --u-warning-color-weak: var(--u-yellow-500);\n --u-warning-color: var(--u-yellow-600);\n /* ★다른 계열은 -strong 이 shade-800~900 인데 warning 만 1000 이다 — 노랑 램프는\n 900 까지도 흰 바탕 2.65 로 미달이고, 1000(#8A4A00)에서 처음 6.86 이 된다.\n 그 값은 갈색에 가깝지만, 이 단의 용도는 **면이 아니라 글자·아이콘**이므로 색상보다\n 가독성이 먼저다(경고 문구를 어두운 호박색으로 쓰는 것은 보편 관행이다). */\n --u-warning-color-strong: var(--u-yellow-1000);\n\n /* danger — 오류 · 위험 · 유효성 실패 */\n --u-danger-color-weakest: var(--u-red-200);\n --u-danger-color-weaker: var(--u-red-300);\n --u-danger-color-weak: var(--u-red-500);\n --u-danger-color: var(--u-red-700); /* 4.98 ✓ (600 은 4.23 ✗ — 오류 문구가 이 단이었다) */\n --u-danger-color-strong: var(--u-red-800); /* 5.62 ✓ */\n /* ==========================================================================\n Radius Scale — 모서리 반경\n 리터럴을 흩뿌리면 값이 갈린다(실제로 pill 이 999px/9999px 두 표기로 나뉘어 있었다).\n ========================================================================== */\n --u-radius-none: 0;\n --u-radius-sm: 3px;\n --u-radius-md: 4px;\n --u-radius-lg: 6px;\n --u-radius-xl: 8px;\n /* ★상단 2단은 **면(surface)용**이다 — 카드·패널·대화상자처럼 큰 사각형은 컨트롤과\n 같은 반경을 쓰면 각져 보인다. 기존 5단은 **컨트롤용**이고 값을 바꾸지 않는다\n (바꾸면 모든 소비자의 버튼·입력이 동시에 움직인다). */\n --u-radius-2xl: 12px;\n --u-radius-3xl: 16px;\n --u-radius-pill: 9999px;\n --u-radius-circle: 50%;\n\n /* ==========================================================================\n Space Scale — 컨테이너·오버레이의 절대 여백\n ⚠**여백은 하나의 축이 아니다.** 이 스케일은 컨테이너/오버레이(카드·대화상자·드로어·\n 알림·메뉴·툴팁 등)의 **레이아웃 여백**만 담당한다. 폼·인라인 요소의 `em` 여백은\n 상속된 `font-size` 에 비례하는 것이 의도이므로 **여기로 흡수하지 않는다** —\n 흡수하면 타이포를 키운 소비자의 여백이 따라 커지지 않는다.\n 축 구분과 근거는 docs/design-tokens.md 참조.\n ========================================================================== */\n --u-space-3xs: 2px;\n --u-space-2xs: 4px;\n --u-space-xs: 6px;\n --u-space-sm: 8px;\n --u-space-md: 12px;\n --u-space-lg: 16px;\n /* ★상단 4단은 **화면 리듬**이다 — 카드 안쪽 여백(20~24)·섹션 간격(24~32)·페이지 여백\n (32~40). 종전에는 이 구간이 통째로 비어 있어 소비자가 전부 리터럴로 적었고,\n 그래서 앱마다 화면 밀도가 달라졌다. `lg`(16px) 이하는 **요소 사이**, `xl` 이상은\n **영역 사이**로 나눠 쓴다.\n ⚠`xs: 6px` 는 4px 격자에서 벗어나 있으나 **바꾸지 않는다** — 이미 배선된 컨트롤\n 여백이 전부 움직인다. 신규 단은 전부 4의 배수로 둔다. */\n --u-space-xl: 20px;\n --u-space-2xl: 24px;\n --u-space-3xl: 32px;\n --u-space-4xl: 40px;\n\n /* Text Colors */\n --u-txt-color: var(--u-neutral-900);\n --u-txt-color-inverse: var(--u-neutral-0);\n /* 유채색 배경 위의 글자. --u-txt-color-inverse 는 다크에서 neutral-100(#121212)이라\n 유채색 버튼 위에서 읽히지 않는다. 브랜드 색을 바꾸는 소비자가 전경색도 함께\n 조절할 수 있도록 별도 이름을 준다.\n ⚠이 토큰이 흰색이라는 것과 그 위의 배경이 AA 를 받친다는 것은 별개다 —\n 라이트 팔레트의 shade-600 은 8색 중 6색이 흰 글자로 AA 에 미달한다(미해결). */\n --u-primary-txt-color: #FFFFFF;\n --u-info-txt-color: #FFFFFF;\n --u-success-txt-color: #FFFFFF;\n --u-danger-txt-color: #FFFFFF;\n /* ★warning 만 어둡다 — 취향이 아니라 측정이다. 노랑은 **어떤 단도** 흰 글자로 AA 를\n 받치지 못한다(라이트 최대 2.65 @900, 그마저 갈색). 다른 넷처럼 단을 옮기는 수단이\n 없으므로 전경을 뒤집는다. `--u-neutral-1000` 은 라이트 #000000 / 다크 #FFFFFF 이라\n 한 줄로 양 테마를 만족한다(라이트 15.05 · 다크 4.65). */\n --u-warning-txt-color: var(--u-neutral-1000);\n\n --u-txt-color-hover: var(--u-primary-color-strong);\n --u-txt-color-active: var(--u-primary-color-strong);\n --u-txt-color-disabled: var(--u-neutral-400);\n /* 보조 텍스트는 «놓이는 모든 면»에서 AA(4.5)를 받쳐야 한다 — 바탕만이 아니다.\n neutral-600(#757575)은 바탕 4.61 에서만 통과하고 올림면(neutral-50) 4.41 ·\n active 면(neutral-200) 3.97 로 미달이었다(표 머리·카드 위 보조 텍스트).\n neutral-700(#616161) = 바탕 6.19 · 올림면 5.93 · active 5.34.\n 본문(neutral-900)과의 대비비는 2.60 으로, 보조 텍스트를 모든 표면 위에서\n 읽히게 보장하는 흔한 디자인 시스템 기준(본문 대비 약 1.3~1.8)보다 위계가 넓다. */\n --u-txt-color-weak: var(--u-neutral-700);\n --u-txt-color-strong: var(--u-neutral-1000);\n\n --u-link-txt-color: var(--u-primary-color-strong);\n --u-tooltip-txt-color: var(--u-neutral-0);\n\n /* Icon Colors */\n --u-icon-color: var(--u-neutral-700);\n --u-icon-color-inverse: var(--u-neutral-0);\n --u-icon-color-hover: var(--u-primary-color-strong);\n --u-icon-color-active: var(--u-primary-color-strong);\n --u-icon-color-disabled: var(--u-neutral-400);\n\n /* Border Colors */\n --u-border-color: var(--u-neutral-300);\n --u-border-color-weak: var(--u-neutral-200);\n --u-border-color-strong: var(--u-neutral-400);\n --u-border-color-hover: var(--u-neutral-400);\n\n /* ★상태 테두리는 `-strong` 단이다 — 면이 아니라 **면 위에 그리는 선**이기 때문이다.\n 1.16.0 이 가른 용도(`-color` = 면 / `-strong` = 면·바탕 위)에 이 자리가 맞춰지지\n 않아 다크에서 미달이었다: focus 2.74 · invalid 2.44 (WCAG 1.4.11 기준 3.0).\n `-strong` 으로 옮기면 다크 4.74 / 4.34, 라이트 5.75 / 5.62 — 두 테마 모두 단조 상승. */\n --u-input-border-color: var(--u-neutral-300);\n --u-input-border-color-hover: var(--u-neutral-400);\n --u-input-border-color-focus: var(--u-primary-color-strong);\n --u-input-border-color-invalid: var(--u-danger-color-strong);\n\n /* Background Colors */\n --u-bg-color: var(--u-neutral-0);\n --u-bg-color-inverse: var(--u-neutral-900);\n --u-bg-color-hover: var(--u-neutral-100);\n --u-bg-color-active: var(--u-neutral-200);\n --u-bg-color-disabled: var(--u-neutral-50);\n\n /* ★면 높이 — 바탕에 인접해 있으면서 한 단 구분되는 크롬 면(툴바·표 헤더·푸터).\n 상호작용 상태(-hover/-active/-disabled)와 **다른 축**이다. 상태 이름에 높이를\n 얹으면 의미가 어긋난다.\n `--u-panel-bg-color` 와도 다르다 — 그쪽은 **떠 있는 패널**(카드·대화상자·메뉴)이라\n 라이트에서 페이지와 같은 흰색이고 그림자가 높이를 나른다. 크롬 면은 그림자가 없어\n 라이트에서도 틴트가 필요하다.\n ⚠한 단만 정의한다. 두 단째의 실측 수요는 라이트 한쪽뿐이었다(다크에서는 소비 측\n 오버라이드가 두 값을 같은 단으로 합치고 있었다). */\n --u-bg-color-raised: var(--u-neutral-50);\n\n /* ⚠**라이트는 전역 hover 에 그대로 별칭한다 — 결손이 아니라 실측 결론이다**\n (dark.css 의 같은 자리 참조). 두 시트는 각각 완결적이어야 한다는\n 불변식(`token-fallbacks.test.ts` 「키 패리티」)이 있어 다크에만 선언할 수 없다.\n 라이트 팔레트는 raised(250)·hover(245)·active(238) 가 12단 안에 몰려 있어\n (L≈0.956/0.913/0.855) 셋과 겹치지 않는 네 번째 단을 넣을 여유가 없다 — 대비를\n 조금만 올려도(≈1.10) active 와 거의 붙고(≈1단 차), 더 올리면 순서가 뒤집힌다.\n 다크와 달리 raised(250)≠hover(245) 라 «완전히 안 움직이는» 결함이 아니었으므로\n (약한 대비 1.04 로 이미 알려진 채로 남는다 — 같은 초안이 지적한 곁가지),\n 새 단을 억지로 만들지 않고 기존 hover 를 그대로 쓴다. */\n --u-bg-color-raised-hover: var(--u-bg-color-hover);\n \n /* 유채색 표면 — 정보/성공/위험 상태의 '면'.\n 역할 층의 유채색은 전경 5단뿐이라, 상태 배경이 필요한 컴포넌트는 팔레트를 직접\n 읽고 다크를 손으로 보정해야 했다. 팔레트의 유채색 틴트는 다크에서 짓눌리므로\n (바탕 대비 델타의 다크/라이트 비 — 청색 0.38~0.54) 두 테마가 **같은 단이 아니라\n 같은 세기**가 되도록 짝지었다. 실측 비: blue 1.09 · red 1.12 · green 0.91.\n\n ★**warning 이 합류했다(1.16.0 → 1.17.0).** 종전에는 *\"세기를 맞출 단 짝이 없다\"* 며\n 빠져 있었는데, 그 판정은 **shade-0 짝만** 재고 있었다. 노랑만 한 단 내려 잡으면\n 라이트 쪽 세기가 맞는다 — 바탕 대비 휘도 델타 실측:\n blue-0 0.131 · red-0 0.132 · green-0 0.117 · **yellow-200 0.110** (yellow-0 은 0.027)\n ⚠단 이름이 계열마다 다른 것(yellow 만 200)은 이 층의 정상 상태다. 라이트 팔레트는\n 휘도 순이 아니므로 같은 단 번호가 같은 세기를 뜻하지 않는다. */\n --u-primary-bg-color: var(--u-blue-0);\n --u-info-bg-color: var(--u-blue-0);\n --u-success-bg-color: var(--u-green-0);\n --u-danger-bg-color: var(--u-red-0);\n --u-warning-bg-color: var(--u-yellow-200);\n\n --u-input-bg-color: var(--u-neutral-0);\n --u-panel-bg-color: var(--u-neutral-0);\n --u-overlay-bg-color: rgba(0, 0, 0, 0.5);\n --u-tooltip-bg-color: rgba(0, 0, 0, 0.75);\n\n /* Shadow Colors */\n --u-shadow-color-weaker: rgba(0, 0, 0, 0.04);\n --u-shadow-color-weak: rgba(0, 0, 0, 0.08);\n --u-shadow-color-normal: rgba(0, 0, 0, 0.12);\n --u-shadow-color-strong: rgba(0, 0, 0, 0.16);\n --u-shadow-color-stronger: rgba(0, 0, 0, 0.24);\n\n /* ==========================================================================\n Elevation Scale — 높이(그림자)\n\n ★색 축은 있었지만 **높이 축이 없었다.** 그 결과 컴포넌트가 그림자를 리터럴로\n 썼고, 같은 리듬을 의도한 자리에 서로 다른 값 11가지가 생겼다(카드 .1 vs 캐러셀\n .15 — 둘 다 \"표면에 얹힌 카드\"인데 알파가 다르다).\n\n ⚠**그리고 그 리터럴들은 테마를 몰랐다** — `rgba(0,0,0,.1)` 은 다크 바탕에서\n 거의 보이지 않는다. 이 축을 경유하면 다크의 진한 알파(.25~.60)를 자동으로 받는다.\n\n ⚠**알파를 `--u-shadow-color-*` 로 중첩하지 않고 인라인한다.** use-site 폴백\n 생성기는 합성 값을 리터럴로 다루므로, 중첩하면 `var(--u-shadow-md, 0 2px 8px\n var(--u-shadow-color-normal))` 처럼 **폴백 안에 또 토큰이 들어간다** — 시트가\n 없을 때 그 폴백도 함께 무효가 되어 그림자가 통째로 사라진다.\n 두 축의 알파가 어긋나지 않는지는 `tests/build/elevation-scale.test.ts` 가 지킨다.\n ========================================================================== */\n --u-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 1px rgba(0, 0, 0, 0.04); /* = shadow-color-weak — 표면에 얹힌 작은 컨트롤(탭·손잡이) */\n --u-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.05); /* = shadow-color-normal — 카드·표면 */\n --u-shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.16), 0 2px 4px rgba(0, 0, 0, 0.06); /* = shadow-color-strong — 떠 있는 레이어(드롭다운·알림) */\n --u-shadow-xl: 0 6px 18px rgba(0, 0, 0, 0.24), 0 2px 6px rgba(0, 0, 0, 0.08); /* = shadow-color-stronger — 모달·최상위(대화상자·툴팁) */\n\n\n /* ==========================================================================\n Type Scale — 글자 크기·굵기·행간·자간의 **한 벌**\n ★**크기만 주면 스케일이 되지 않는다.** 소비자가 크기만 받고 굵기·행간을 각자 정하면\n 결국 화면마다 다른 위계가 생긴다 — 그래서 네 값을 **단 단위로 묶어** 낸다.\n\n 단 이름은 크기가 아니라 **역할**이다(`lg`/`xl` 이 아니라 `title`/`label`). 역할로 두면\n 소비자가 *\"어느 크기를 쓸까\"* 가 아니라 *\"이 글자는 무엇인가\"* 를 묻게 된다.\n\n ★**한글 기준으로 값을 골랐다**:\n ⑴ 행간은 **1.4 이상**. 라틴 관례(1.2)는 한글에서 답답하다 — 받침 때문에 글자 상자가\n 세로로 꽉 차 있어 라틴과 같은 행간이면 줄이 붙어 보인다.\n ⑵ 자간은 **0 이 기본**이고 큰 제목만 **음수**. 한글에 양수 자간(letter-spacing)을 주면\n 가독성이 떨어진다 — 라틴 소문자 조판 관례를 그대로 옮기면 안 되는 자리다.\n ⑶ `overline` 에만 양수 자간을 둔다. 그 단은 **영문·숫자 라벨**을 전제한 자리이고,\n 한글을 넣을 소비자는 자간을 0 으로 덮어야 한다(문서에 명시).\n\n ⚠**`text-transform` 은 토큰으로 내지 않는다.** 한글에는 대문자가 없어 `uppercase` 가\n 아무 효과가 없고, 영문이 섞이면 그것만 커져 오히려 어수선해진다.\n ========================================================================== */\n --u-text-display-size: 26px;\n --u-text-display-weight: 700;\n --u-text-display-leading: 1.4;\n --u-text-display-tracking: -0.02em;\n\n --u-text-title-size: 20px;\n --u-text-title-weight: 700;\n --u-text-title-leading: 1.45;\n --u-text-title-tracking: -0.01em;\n\n --u-text-subtitle-size: 16px;\n --u-text-subtitle-weight: 600;\n --u-text-subtitle-leading: 1.5;\n --u-text-subtitle-tracking: 0;\n\n --u-text-body-size: 14px;\n --u-text-body-weight: 400;\n --u-text-body-leading: 1.6;\n --u-text-body-tracking: 0;\n\n --u-text-label-size: 13px;\n --u-text-label-weight: 600;\n --u-text-label-leading: 1.5;\n --u-text-label-tracking: 0;\n\n --u-text-caption-size: 12px;\n --u-text-caption-weight: 400;\n --u-text-caption-leading: 1.5;\n --u-text-caption-tracking: 0;\n\n --u-text-overline-size: 11px;\n --u-text-overline-weight: 700;\n --u-text-overline-leading: 1.45;\n --u-text-overline-tracking: 0.06em;\n\n /* ==========================================================================\n Motion — 지속시간·이징\n ★값이 없으면 앱마다 다른 속도가 생기고, `prefers-reduced-motion` 은 **반드시**\n 빠뜨리는 앱이 나온다. 축을 여기 두고 존중 규칙은 아래 미디어 쿼리가 진다.\n ========================================================================== */\n --u-duration-instant: 80ms;\n --u-duration-fast: 140ms;\n --u-duration-normal: 220ms;\n --u-duration-slow: 320ms;\n --u-ease-standard: cubic-bezier(0.2, 0, 0, 1);\n --u-ease-decelerate: cubic-bezier(0, 0, 0, 1);\n --u-ease-accelerate: cubic-bezier(0.3, 0, 1, 1);\n\n /* Scrollbar Colors */\n --u-scrollbar-color: var(--u-neutral-400);\n --u-scrollbar-color-hover: var(--u-neutral-500);\n --u-scrollbar-track-color: transparent;\n\n /* ==========================================================================\n Font Styles\n ★**CJK 폴백을 스택에 포함한다 — 국제화이지 취향이 아니다.**\n 종전 스택(`-apple-system, Segoe UI, Noto Sans, Helvetica, Arial`)에는 **한중일 글립을\n 가진 폰트가 하나도 없었다.** Windows 에서 한글은 `Segoe UI` 에 없으므로 UA 가 임의로\n 맑은 고딕을 고르고, macOS 는 `-apple-system` 이 Apple SD Gothic Neo 를 준다 ⇒ **같은 앱이\n OS 마다 다른 글꼴로 렌더**됐다. 폴백을 명시하면 그 선택이 결정적이 된다.\n ⚠**웹폰트를 로드하지 않는다.** 라이브러리가 네트워크 요청을 만들면 소비자의 CSP·\n 오프라인 환경을 깬다. 스택 맨 앞의 `Pretendard` 는 **소비자가 로드했을 때만** 잡히는\n 선택적 이름이다(없으면 조용히 다음으로 넘어간다).\n ========================================================================== */\n --u-font-base: 'Pretendard Variable', Pretendard, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, 'Apple SD Gothic Neo', 'Malgun Gothic', 'Noto Sans KR', 'Hiragino Sans', 'Yu Gothic UI', 'Microsoft YaHei', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';\n --u-font-mono: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Monaco, 'Courier New', monospace;\n --u-font-serif: 'Georgia', 'Times New Roman', Times, serif;\n --u-font-display: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;\n --u-font-modern: 'Inter', 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n --u-font-rounded: 'Nunito', 'Quicksand', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n}\n\n/* ==========================================================================\n ★`prefers-reduced-motion` 존중 — **라이브러리가 진다.**\n 소비자마다 각자 처리하게 두면 반드시 빠뜨리는 앱이 나온다(그리고 그 앱은\n 접근성 감사 전까지 아무도 모른다). 지속시간 축을 0 으로 눌러 두면 이 축을\n 경유하는 모든 애니메이션이 함께 멈춘다.\n ⚠`animation: none` 으로 강제하지 않는다 — 로딩 스피너처럼 **의미를 나르는**\n 움직임까지 죽이면 사용자가 진행 중인지 알 수 없게 된다.\n ========================================================================== */\n@media (prefers-reduced-motion: reduce) {\n :root {\n --u-duration-instant: 0ms;\n --u-duration-fast: 0ms;\n --u-duration-normal: 0ms;\n --u-duration-slow: 0ms;\n }\n}\n";
3
3
  //#endregion
4
4
  export { light_css_default as default };
@@ -1,12 +1,37 @@
1
1
  import { styles } from "./UElement.styles.js";
2
2
  import { LitElement, render } from "lit";
3
3
  //#region src/components/UElement.ts
4
+ /**
5
+ * 모든 UI 컴포넌트의 기반 클래스.
6
+ * LitElement를 확장하여 이벤트 발행 및 렌더 교체 헬퍼를 제공합니다.
7
+ */
8
+ /**
9
+ * 디자인 토큰 시트 부재를 개발 빌드에서 1회 경고한다.
10
+ *
11
+ * 토큰이 없으면 컴포넌트 시트의 `var(--u-…)` 가 전부 무효가 되어 테두리·배경이 **에러 없이**
12
+ * 사라진다. CSS 는 이때 아무 신호도 내지 않으므로, 소비자는 자기 CSS 를 의심하며 며칠을 쓴다.
13
+ * 실제로 운영 화면이 무스타일로 렌더된 사례가 있었다 — 토큰 주입이 `Theme.init()` 호출
14
+ * (셸이 대신 부른다)에만 딸려 있어서, 셸 밖에서 렌더되는 로그인 화면만 조용히 깨졌다.
15
+ *
16
+ * ★리터럴 폴백(1.14.0)이 그 **소멸**은 막는다. 그래도 경고는 유지한다 — 폴백은 라이트
17
+ * 시트 값을 구워 넣은 것이라 **다크 테마가 라이트 색으로 렌더되고**, 테마 변수 오버라이드도
18
+ * 먹지 않는다. 폴백은 *"조용히 사라지지 않게"* 하는 안전망이지 *"시트 없이 써도 된다"* 가
19
+ * 아니다. 둘을 섞으면 이 경고가 고발하는 무증상 열화가 형태만 바꿔 돌아온다.
20
+ */
21
+ var tokenCheckDone = false;
22
+ function warnIfTokensMissing() {
23
+ if (tokenCheckDone || typeof document === "undefined") return;
24
+ tokenCheckDone = true;
25
+ if (getComputedStyle(document.documentElement).getPropertyValue("--u-blue-600").trim()) return;
26
+ console.warn("[@iyulab/components] No design-token sheet found in the document — components render with their built-in fallbacks (baked from the light theme). Dark theme and theme variable overrides will not apply.\n Static CSS: import '@iyulab/components/styles/tokens.css'\n At runtime: Theme.init()\n (The @iyulab/modern-app shell calls Theme.init() for you. Screens rendered outside the shell — login, onboarding, embeds — need one of the two.)");
27
+ }
4
28
  var UElement = class extends LitElement {
5
29
  static {
6
30
  this.styles = styles;
7
31
  }
8
32
  connectedCallback() {
9
33
  super.connectedCallback();
34
+ if (process.env.NODE_ENV !== "production") warnIfTokensMissing();
10
35
  }
11
36
  /**
12
37
  * 커스텀 이벤트를 생성하여 발행합니다.
@@ -96,7 +96,7 @@ export declare class UButton extends UElement {
96
96
  /**
97
97
  * 호스트에 세팅된 `aria-label`은 실제 접근 가능한(포커스 대상) 엘리먼트가 아니라 —
98
98
  * 그 안쪽 shadow DOM 의 네이티브 `<a>`/`<button>`이다. 섀도우 경계를 넘지 않으므로
99
- * 접근성 트리에 자동 반영되지 않는다(docket #75 실측 — 속성은 붙어 있는데
99
+ * 접근성 트리에 자동 반영되지 않는다(실측 — 속성은 붙어 있는데
100
100
  * 접근성 이름이 비어 있음). `render()`가 이 값을 읽어 내부 엘리먼트에 직접 옮긴다.
101
101
  *
102
102
  * `aria-label`은 Lit 리액티브 프로퍼티로 선언돼 있지 않아 `observedAttributes`에
@@ -56,7 +56,7 @@ var UButton = class UButton extends UElement {
56
56
  /**
57
57
  * 호스트에 세팅된 `aria-label`은 실제 접근 가능한(포커스 대상) 엘리먼트가 아니라 —
58
58
  * 그 안쪽 shadow DOM 의 네이티브 `<a>`/`<button>`이다. 섀도우 경계를 넘지 않으므로
59
- * 접근성 트리에 자동 반영되지 않는다(docket #75 실측 — 속성은 붙어 있는데
59
+ * 접근성 트리에 자동 반영되지 않는다(실측 — 속성은 붙어 있는데
60
60
  * 접근성 이름이 비어 있음). `render()`가 이 값을 읽어 내부 엘리먼트에 직접 옮긴다.
61
61
  *
62
62
  * `aria-label`은 Lit 리액티브 프로퍼티로 선언돼 있지 않아 `observedAttributes`에
@@ -37,7 +37,7 @@ export declare class UCheckbox extends UFormControlElement<string> {
37
37
  protected shouldValidate(changed: PropertyValues): boolean;
38
38
  /** `checked`(또는 그 값을 함께 결정하는 `value`)가 바뀌는 모든 경로(초기 속성 설정 ·
39
39
  * 프로그램적 대입 · 사용자 클릭)에서 폼 제출값을 동기화한다 — `UInput`/`UTextarea`와
40
- * 같은 경계(`DL-289-1`). */
40
+ * 같은 경계. */
41
41
  protected updated(changedProperties: PropertyValues): void;
42
42
  render(): import('lit-html').TemplateResult<1>;
43
43
  protected setValidity(): void;
@@ -33,7 +33,7 @@ var UCheckbox = class UCheckbox extends UFormControlElement {
33
33
  }
34
34
  /** `checked`(또는 그 값을 함께 결정하는 `value`)가 바뀌는 모든 경로(초기 속성 설정 ·
35
35
  * 프로그램적 대입 · 사용자 클릭)에서 폼 제출값을 동기화한다 — `UInput`/`UTextarea`와
36
- * 같은 경계(`DL-289-1`). */
36
+ * 같은 경계. */
37
37
  updated(changedProperties) {
38
38
  super.updated(changedProperties);
39
39
  if (changedProperties.has("checked") || changedProperties.has("value")) this.internals?.setFormValue(this.checked ? this.value || String(this.checked) : String(this.checked));
@@ -148,7 +148,7 @@ var styles = css`
148
148
  }
149
149
 
150
150
  /* 체크박스 외형 — 클릭은 항상 .wrapper(<label>)가 받아야 한다(위임으로 이미
151
- 충분하다, docket #147 후속 실측) — 자신도 pointer-events:none이라야 좌표 기반
151
+ 충분하다 — 실측) — 자신도 pointer-events:none이라야 좌표 기반
152
152
  히트테스트(예: Playwright actionability check)가 이 박스를 투과해 label까지
153
153
  도달한다. */
154
154
  .checkbox {
@@ -26,7 +26,7 @@ var styles = css`
26
26
  .remove-btn {
27
27
  font-size: 0.7em;
28
28
  /* WCAG 2.2 SC 2.5.8 — 제거 버튼은 포인터 타깃이고 실측 27x23 으로 **1px 미달**이었다.
29
- cycle-484 가 checkbox·switch 에 쓴 것과 같은 처방(글리프가 아니라 타깃을 넓힌다) —
29
+ checkbox·switch 와 같은 처방(글리프가 아니라 타깃을 넓힌다) —
30
30
  아이콘 크기(0.7em)는 그대로다.
31
31
  ⚠실측: 버튼 27x23 → 27x24, 그리고 **칩 자신이 31 → 32px 로 1px 늘어난다.**
32
32
  («늘지 않는다»가 아니다 — 24px 타깃은 그만한 공간을 요구한다.) */
@@ -94,7 +94,7 @@ export declare class UDatePicker extends UFormControlElement<string> {
94
94
  /** "오늘" 퀵액션 — `today` 셀이 이미 렌더에서 계산해 표시 중인 값(`renderDay`의
95
95
  * `isSameDay(date, new Date())`)을 실제로 선택하는 것뿐이라 `selectDay`를 그대로 탄다
96
96
  * (범위 밖이면 `selectDay`가 조용히 no-op — 클릭 불가 상태인 day 셀과 동일 규약).
97
- * datetime 모드에서는 "지금"을 통째로 채우는 것이 요청의 본질(§D-28 항목 2 docket 코멘트)
97
+ * datetime 모드에서는 "지금"을 통째로 채우는 것이 소비자 요청의 본질
98
98
  * 이라 시간까지 `now`로 덮어쓴다 — 평범한 day 셀 클릭과 달리 기존 시각을 보존하지 않는다. */
99
99
  private handleTodayClick;
100
100
  /** 캘린더 팝오버 안 "초기화" 퀵액션 — 트리거의 clear 아이콘(`handleClearClick`)과 값을
@@ -24,7 +24,7 @@ var styles = css`
24
24
 
25
25
  ⚠소비자가 인라인 style 로 폭을 주면 그쪽이 이긴다(인라인 > ::slotted).
26
26
  ⚠이 주석에 백틱을 쓰지 말 것 — css 태그드 템플릿을 그 자리에서 끝낸다(이 파일이 실제로
27
- 그렇게 깨졌고, cycle-217 이 만든 타입 게이트가 잡았다). */
27
+ 그렇게 깨졌고, 타입 게이트가 잡았다). */
28
28
  ::slotted(*) {
29
29
  width: 100%;
30
30
  box-sizing: border-box;
@@ -32,7 +32,7 @@ export declare class UFileInput extends UFormControlElement<File[] | null> {
32
32
  triggerEl?: HTMLButtonElement;
33
33
  inputEl?: HTMLInputElement;
34
34
  /** `value`가 바뀌는 모든 경로(선택·지우기)에서 폼 제출값을 동기화한다 —
35
- * `UInput`/`UCheckbox`와 같은 경계(`DL-289-1`). 파일은 문자열로 표현할 수
35
+ * `UInput`/`UCheckbox`와 같은 경계. 파일은 문자열로 표현할 수
36
36
  * 없으므로 1개면 `File`을, 여러 개면 같은 키로 반복 append한 `FormData`를
37
37
  * 넘긴다 — 네이티브 `<input type="file" multiple>`의 제출 형태와 동일하다. */
38
38
  protected updated(changedProperties: PropertyValues): void;
@@ -48,7 +48,7 @@ var UFileInput = class UFileInput extends UFormControlElement {
48
48
  this.styles = [super.styles, styles];
49
49
  }
50
50
  /** `value`가 바뀌는 모든 경로(선택·지우기)에서 폼 제출값을 동기화한다 —
51
- * `UInput`/`UCheckbox`와 같은 경계(`DL-289-1`). 파일은 문자열로 표현할 수
51
+ * `UInput`/`UCheckbox`와 같은 경계. 파일은 문자열로 표현할 수
52
52
  * 없으므로 1개면 `File`을, 여러 개면 같은 키로 반복 append한 `FormData`를
53
53
  * 넘긴다 — 네이티브 `<input type="file" multiple>`의 제출 형태와 동일하다. */
54
54
  updated(changedProperties) {
@@ -2,7 +2,7 @@ import { UElement } from "../UElement.js";
2
2
  import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
3
3
  import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
4
4
  import { IconRegistry, getDefaultBaseUrl } from "../../utilities/icons.js";
5
- import "../../utilities/devWarning.js";
5
+ import { devWarnOnce } from "../../utilities/devWarning.js";
6
6
  import { styles } from "./UIcon.styles.js";
7
7
  import { nothing } from "lit";
8
8
  import { customElement, property } from "lit/decorators.js";
@@ -37,7 +37,7 @@ var UIcon = class UIcon extends UElement {
37
37
  where = `${url} (no lib given — the default base URL)`;
38
38
  }
39
39
  const svg = this.sanitize(html);
40
- if (svg === void 0) `${this.lib ?? ""}${name}`, `${name}${where}`, this.fallback;
40
+ if (svg === void 0) devWarnOnce(`icon:${this.lib ?? ""}:${name}`, `u-icon "${name}" did not resolve from ${where} — ${this.fallback ? "drawing the fallback instead" : "drawing nothing"}. Check the name, set lib="…" to the library that has it, or register one with IconRegistry.register(). Pick one icon set and name it on every icon; sets do not mix.`);
41
41
  return svg;
42
42
  }
43
43
  sanitize(html) {
@@ -40,7 +40,7 @@ export declare class UIconButton extends UElement {
40
40
  tooltipOffset: OffsetOptions;
41
41
  /**
42
42
  * 호스트에 세팅된 `aria-label`을 내부 `<u-button>`으로 옮긴다 — `UButton`과 같은 이유
43
- * (docket #75류, 섀도우 경계 안쪽 엘리먼트가 실제 접근 가능한 이름 대상). 아이콘 전용
43
+ * (섀도우 경계 안쪽 엘리먼트가 실제 접근 가능한 이름 대상). 아이콘 전용
44
44
  * 버튼이라 텍스트 콘텐츠로 이름이 생기지 않으므로 이 전달이 없으면 접근 가능한 이름이
45
45
  * 아예 비게 된다 — 내장 툴팁(`<u-tooltip>`)은 ARIA를 배선하지 않아 대체 경로가 안 된다.
46
46
  */
@@ -24,7 +24,7 @@ var UIconButton = class UIconButton extends UElement {
24
24
  }
25
25
  /**
26
26
  * 호스트에 세팅된 `aria-label`을 내부 `<u-button>`으로 옮긴다 — `UButton`과 같은 이유
27
- * (docket #75류, 섀도우 경계 안쪽 엘리먼트가 실제 접근 가능한 이름 대상). 아이콘 전용
27
+ * (섀도우 경계 안쪽 엘리먼트가 실제 접근 가능한 이름 대상). 아이콘 전용
28
28
  * 버튼이라 텍스트 콘텐츠로 이름이 생기지 않으므로 이 전달이 없으면 접근 가능한 이름이
29
29
  * 아예 비게 된다 — 내장 툴팁(`<u-tooltip>`)은 ARIA를 배선하지 않아 대체 경로가 안 된다.
30
30
  */
@@ -176,7 +176,7 @@ var styles = css`
176
176
  }
177
177
 
178
178
  /* 포인터를 받는 접미 아이콘(지우기 · 비밀번호 토글 · 스테퍼) — 글리프는 1em 그대로, 받는 영역은 **모두** 24px(1.5em)다
179
- (WCAG 2.2 SC 2.5.8 · 사람 결정 HD-57 ⒜). 기전: 좌우 0.25em 패딩으로 1em 글리프를 1.5em(24px) 상자로 감싸고,
179
+ (WCAG 2.2 SC 2.5.8). 기전: 좌우 0.25em 패딩으로 1em 글리프를 1.5em(24px) 상자로 감싸고,
180
180
  왼쪽 여백 0.25em · 오른쪽 여백 -0.25em 으로 이웃과 **맞닿되 겹치지 않게** 놓는다 — 그 결과 글리프 사이가 0.5em(8px)이 된다.
181
181
  세로는 음수 여백으로 컨테이너 높이를 그대로 둔다.
182
182
  ⚠종전에는 간격이 0.25em 이라 «맨 뒤 아이콘만» 24 였고 나란히 놓인 쌍·스테퍼는 24 에 못 미쳤다 — 그것을 고치는 대가가
@@ -54,8 +54,8 @@ export declare class USplitPanel extends UElement {
54
54
  private handleSlotChange;
55
55
  /**
56
56
  * 호스트에 높이(또는 세로 분할이면 폭) 제약이 없으면 이 컴포넌트는 손잡이 두께로 붕괴한다 —
57
- * 실측 18px(cycle-565). 호스트가 `overflow: hidden` 이라 패널 내용에 닿을 방법이 없고,
58
- * 오류도 없다. 첫 배치 뒤 한 번 재서 개발 모드에서 알린다(HD-61 ⒝).
57
+ * 실측 18px. 호스트가 `overflow: hidden` 이라 패널 내용에 닿을 방법이 없고,
58
+ * 오류도 없다. 첫 배치 뒤 한 번 재서 개발 모드에서 알린다.
59
59
  */
60
60
  private warnIfCollapsed;
61
61
  private handleSplitterSlotChange;
@@ -2,6 +2,7 @@ import { UElement } from "../UElement.js";
2
2
  import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorateMetadata.js";
3
3
  import __decorate from "../../_virtual/_@oxc-project_runtime@0.149.0/helpers/esm/decorate.js";
4
4
  import { arrayAttrConverter } from "../../utilities/converters.js";
5
+ import { devWarnOnce } from "../../utilities/devWarning.js";
5
6
  import { Locale } from "../../utilities/Locale.js";
6
7
  import { styles } from "./USplitPanel.styles.js";
7
8
  import { html } from "lit";
@@ -244,10 +245,17 @@ var USplitPanel = class USplitPanel extends UElement {
244
245
  }
245
246
  /**
246
247
  * 호스트에 높이(또는 세로 분할이면 폭) 제약이 없으면 이 컴포넌트는 손잡이 두께로 붕괴한다 —
247
- * 실측 18px(cycle-565). 호스트가 `overflow: hidden` 이라 패널 내용에 닿을 방법이 없고,
248
- * 오류도 없다. 첫 배치 뒤 한 번 재서 개발 모드에서 알린다(HD-61 ⒝).
248
+ * 실측 18px. 호스트가 `overflow: hidden` 이라 패널 내용에 닿을 방법이 없고,
249
+ * 오류도 없다. 첫 배치 뒤 한 번 재서 개발 모드에서 알린다.
249
250
  */
250
- warnIfCollapsed() {}
251
+ warnIfCollapsed() {
252
+ if (process.env.NODE_ENV === "production" || this.panels.length === 0 || !this.isConnected) return;
253
+ {
254
+ const height = this.getBoundingClientRect().height;
255
+ if (height >= 40) return;
256
+ devWarnOnce(`split-panel:${this.id || "height"}`, `u-split-panel is only ${Math.round(height)}px tall — it has no height of its own (its height: 100% found no sized parent), so its panels take one line of content and anything sized 100% inside them collapses to nothing. Give the host or its parent a height (e.g. height: 400px, or flex: 1 1 auto inside a sized parent).`);
257
+ }
258
+ }
251
259
  appendSplitters() {
252
260
  const root = this.renderRoot;
253
261
  if (!root) return;
@@ -36,7 +36,7 @@ export declare class USwitch extends UFormControlElement<string> {
36
36
  protected shouldValidate(changed: PropertyValues): boolean;
37
37
  /** `checked`(또는 그 값을 함께 결정하는 `value`)가 바뀌는 모든 경로(초기 속성 설정 ·
38
38
  * 프로그램적 대입 · 사용자 클릭)에서 폼 제출값을 동기화한다 — `UInput`/`UTextarea`/
39
- * `UCheckbox`와 같은 경계(`DL-289-1`). */
39
+ * `UCheckbox`와 같은 경계. */
40
40
  protected updated(changedProperties: PropertyValues): void;
41
41
  render(): import('lit-html').TemplateResult<1>;
42
42
  protected setValidity(): void;
@@ -28,7 +28,7 @@ var USwitch = class USwitch extends UFormControlElement {
28
28
  }
29
29
  /** `checked`(또는 그 값을 함께 결정하는 `value`)가 바뀌는 모든 경로(초기 속성 설정 ·
30
30
  * 프로그램적 대입 · 사용자 클릭)에서 폼 제출값을 동기화한다 — `UInput`/`UTextarea`/
31
- * `UCheckbox`와 같은 경계(`DL-289-1`). */
31
+ * `UCheckbox`와 같은 경계. */
32
32
  updated(changedProperties) {
33
33
  super.updated(changedProperties);
34
34
  if (changedProperties.has("checked") || changedProperties.has("value")) this.internals?.setFormValue(this.checked ? this.value || String(this.checked) : String(this.checked));
package/dist/index.d.ts CHANGED
@@ -59,6 +59,7 @@ export * from './events/ShiftEvent';
59
59
  export * from './events/ShowEvent';
60
60
  export * from './utilities/BrowserStorage.js';
61
61
  export * from './utilities/converters.js';
62
+ export * from './utilities/devWarning.js';
62
63
  export * from './utilities/Dialog.js';
63
64
  export * from './utilities/elements.js';
64
65
  export * from './utilities/format.js';
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { UOverlayElement } from "./components/UOverlayElement.js";
8
8
  import { USpinner } from "./components/spinner/USpinner.js";
9
9
  import { UButton } from "./components/button/UButton.js";
10
10
  import { IconCache, IconRegistry, getDefaultBaseUrl, setDefaultBaseUrl } from "./utilities/icons.js";
11
+ import { createDevWarner, devWarnOnce, resetDevWarnings } from "./utilities/devWarning.js";
11
12
  import { UIcon } from "./components/icon/UIcon.js";
12
13
  import { Locale } from "./utilities/Locale.js";
13
14
  import { UAlert } from "./components/alert/UAlert.js";
@@ -58,4 +59,4 @@ import { BrowserStorage } from "./utilities/BrowserStorage.js";
58
59
  import { Dialog } from "./utilities/Dialog.js";
59
60
  import { Theme } from "./utilities/Theme.js";
60
61
  import { Toast } from "./utilities/Toast.js";
61
- export { BrowserStorage, Dialog, IconCache, IconRegistry, Locale, OverlayManager, Theme, Toast, UAlert, UAvatar, UBadge, UBreadcrumb, UBreadcrumbItem, UButton, UButtonGroup, UCard, UCarousel, UCheckbox, UChip, UCopyButton, UDatePicker, UDialog, UDivider, UDrawer, UElement, UExpander, UField, UFileInput, UFloatingElement, UForm, UFormControlElement, UIcon, UIconButton, UInput, UMenu, UMenuItem, UOption, UOverlayElement, UPanel, UPopover, UProgressBar, UProgressRing, URadio, URating, USelect, USkeleton, USlider, USpinner, USplitPanel, USwitch, UTab, UTabPanel, UTag, UText, UTextarea, UTooltip, UTree, UTreeItem, arrayAttrConverter, booleanAttrConverter, dateAttrConverter, formatCurrency, formatDate, formatNumber, getDefaultBaseUrl, getParentElement, isCoarsePointer, jsonAttrConverter, querySelectorAllWithin, querySelectorWithin, setDefaultBaseUrl, urlAttrConverter };
62
+ export { BrowserStorage, Dialog, IconCache, IconRegistry, Locale, OverlayManager, Theme, Toast, UAlert, UAvatar, UBadge, UBreadcrumb, UBreadcrumbItem, UButton, UButtonGroup, UCard, UCarousel, UCheckbox, UChip, UCopyButton, UDatePicker, UDialog, UDivider, UDrawer, UElement, UExpander, UField, UFileInput, UFloatingElement, UForm, UFormControlElement, UIcon, UIconButton, UInput, UMenu, UMenuItem, UOption, UOverlayElement, UPanel, UPopover, UProgressBar, UProgressRing, URadio, URating, USelect, USkeleton, USlider, USpinner, USplitPanel, USwitch, UTab, UTabPanel, UTag, UText, UTextarea, UTooltip, UTree, UTreeItem, arrayAttrConverter, booleanAttrConverter, createDevWarner, dateAttrConverter, devWarnOnce, formatCurrency, formatDate, formatNumber, getDefaultBaseUrl, getParentElement, isCoarsePointer, jsonAttrConverter, querySelectorAllWithin, querySelectorWithin, resetDevWarnings, setDefaultBaseUrl, urlAttrConverter };
@@ -286,7 +286,7 @@
286
286
 
287
287
  /* ★올림면 위의 hover — `--u-bg-color-hover`(전역, 바탕면 전용)와 값이 같아서
288
288
  («둘 다 neutral-300») 카드·표 머리 위에 마우스를 올려도 아무 일도 안 일어났다
289
- (소비앱 초안 2026-08-06 · `L2-11-1`). 전역 토큰 하나로는 "지금 위에 있는 면"을
289
+ (소비자 실측). 전역 토큰 하나로는 "지금 위에 있는 면"을
290
290
  알 방법이 없어(참조 지점마다 계산 기준이 다를 수 없다) **전용 토큰**을 신설한다.
291
291
 
292
292
  ⚠**A안**(`raised`를 neutral-200으로) — 과거에 거부된 값(위 주석). **B안**(전역
@@ -295,7 +295,7 @@
295
295
 
296
296
  /* ★면 높이 — 바탕에 인접해 있으면서 한 단 구분되는 크롬 면(툴바·표 헤더·푸터).
297
297
  상호작용 상태(-hover/-active/-disabled)와 **다른 축**이다. 상태 이름에 높이를
298
- 얹으면 의미가 어긋난다(`DL-131-1`).
298
+ 얹으면 의미가 어긋난다.
299
299
  `--u-panel-bg-color` 와도 다르다 — 그쪽은 **떠 있는 패널**(카드·대화상자·메뉴)이라
300
300
  라이트에서 페이지와 같은 흰색이고 그림자가 높이를 나른다. 크롬 면은 그림자가 없어
301
301
  라이트에서도 틴트가 필요하다.
@@ -304,7 +304,7 @@
304
304
  --u-bg-color-raised: var(--u-neutral-50);
305
305
 
306
306
  /* ⚠**라이트는 전역 hover 에 그대로 별칭한다 — 결손이 아니라 실측 결론이다**
307
- (L2-11-1, dark.css 의 같은 자리 참조). 두 시트는 각각 완결적이어야 한다는
307
+ (dark.css 의 같은 자리 참조). 두 시트는 각각 완결적이어야 한다는
308
308
  불변식(`token-fallbacks.test.ts` 「키 패리티」)이 있어 다크에만 선언할 수 없다.
309
309
  라이트 팔레트는 raised(250)·hover(245)·active(238) 가 12단 안에 몰려 있어
310
310
  (L≈0.956/0.913/0.855) 셋과 겹치지 않는 네 번째 단을 넣을 여유가 없다 — 대비를
@@ -76,7 +76,7 @@ export declare class Toast {
76
76
  * `OverlayManager` 의 «이름 있는 띠» 계약은 **우리 오버레이보다 위**를 보장한다(실측:
77
77
  * 컨테이너 `z-index: 11000`). 그러나 ***top layer 는 z-index 축 밖에 있다*** — 소비앱이
78
78
  * 네이티브 `<dialog>.showModal()` 을 열면 그 다이얼로그는 **어떤 z-index 보다도 위**에
79
- * 서고, 우리 토스트는 가려진다. 실측(cycle-477, 크로미움 `elementFromPoint`):
79
+ * 서고, 우리 토스트는 가려진다. 실측(크로미움 `elementFromPoint`):
80
80
  *
81
81
  * ```
82
82
  * containerZIndex 11000 · notificationZIndex 11000 · hitTag "DIALOG" · toastReachable false
@@ -135,7 +135,7 @@ var Toast = class {
135
135
  * `OverlayManager` 의 «이름 있는 띠» 계약은 **우리 오버레이보다 위**를 보장한다(실측:
136
136
  * 컨테이너 `z-index: 11000`). 그러나 ***top layer 는 z-index 축 밖에 있다*** — 소비앱이
137
137
  * 네이티브 `<dialog>.showModal()` 을 열면 그 다이얼로그는 **어떤 z-index 보다도 위**에
138
- * 서고, 우리 토스트는 가려진다. 실측(cycle-477, 크로미움 `elementFromPoint`):
138
+ * 서고, 우리 토스트는 가려진다. 실측(크로미움 `elementFromPoint`):
139
139
  *
140
140
  * ```
141
141
  * containerZIndex 11000 · notificationZIndex 11000 · hitTag "DIALOG" · toastReachable false
@@ -2,16 +2,32 @@
2
2
  * 개발 모드 «사용 안내» 경고 — 오류 경로가 아닌, ***조용히 틀리는*** 상태를 한 번 알린다.
3
3
  *
4
4
  * 이 패키지에는 같은 모양의 경고가 이미 있다(`UElement.warnIfTokensMissing` — 토큰 시트 부재).
5
- * 공통 규약: `[@iyulab/components]` 네임스페이스 · 개발 모드에서만(`import.meta.env.DEV`) ·
5
+ * 공통 규약: `[<패키지>]` 네임스페이스 · 개발 모드에서만(`process.env.NODE_ENV !== 'production'`) ·
6
6
  * 같은 키로는 **한 번만** · 무엇이 틀렸는지가 아니라 **무엇을 하면 되는지**를 적는다.
7
7
  *
8
+ * ⚠**`import.meta.env.DEV` 를 쓰지 않는다** — Vite 는 이 라이브러리 **자신의** `vite build`
9
+ * 시점에 그 값을 정적으로 `false` 로 치환하고, Rollup 의 dead-code elimination 이 이후
10
+ * 분기를 통째로 지운다. 소비자가 dev 서버로 띄워도 이미 게시본에서 죽은 코드라 살아나지
11
+ * 않는다(2026-09-13 실측 — 1.40.1·1.40.2 게시본에서 이 경고가 전부 no-op 이었다).
12
+ * `process.env.NODE_ENV` 는 이 라이브러리의 빌드가 손대지 않고 그대로 dist 에 남아,
13
+ * **소비자 자신의** 번들러가 소비자의 빌드 시점에 치환한다(React 등 생태계 라이브러리와
14
+ * 동일한 관례) — `tests/build/dev-warning-dist.test.ts` 가 게시본에 대해 이 계약을 지킨다.
15
+ *
8
16
  * 이 파일이 생긴 계기: 소비앱의 메뉴 30개가 해석되지 않는 아이콘 이름으로 **한꺼번에 같은
9
- * 큐브 폴백**을 그렸는데 신호가 0 이었다(docket #265 R3). 폴백은 의도된 것이라(접힌 사이드바에서
17
+ * 큐브 폴백**을 그렸는데 신호가 0 이었다(소비자 실측). 폴백은 의도된 것이라(접힌 사이드바에서
10
18
  * 아이콘이 없으면 메뉴가 «높이만 있는 빈 줄» 이 된다) 제거가 답이 아니다 — **없는 것은 경고다.**
11
19
  * 같은 부류가 셋 더 있었다: 높이 제약이 없어 18px 로 붕괴하는 `u-split-panel`, 가상화가 꺼지는
12
- * `flex-table`, 133px 로 앉는 `modern-app` 셸 — 모두 «에러 없이 틀린 화면» 이다(HD-61 ⒝).
20
+ * `flex-table`, 133px 로 앉는 `modern-app` 셸 — 모두 «에러 없이 틀린 화면» 이다.
21
+ *
22
+ * 형제 패키지도 같은 규약을 쓴다 — `createDevWarner('@iyulab/flex-table')` 로 자기 네임스페이스의
23
+ * 경고 함수를 만든다. 키는 네임스페이스 안에서만 겹치므로 두 패키지가 같은 키를 써도 서로를 막지 않는다.
24
+ */
25
+ /**
26
+ * `scope` 네임스페이스(`[@iyulab/flex-table]` 처럼 찍힌다)의 개발 모드 1회성 경고 함수를 만든다.
27
+ * 프로덕션 빌드에서는 호출 자체가 no-op 이다.
13
28
  */
14
- /** `key` 당 한 번만, 개발 모드에서만 `console.warn` 한다. 프로덕션 빌드에서는 호출 자체가 no-op 이다. */
15
- export declare function devWarnOnce(key: string, message: string): void;
16
- /** 테스트용 — 가드를 비운다. */
29
+ export declare function createDevWarner(scope: string): (key: string, message: string) => void;
30
+ /** `key` 당 한 번만, 개발 모드에서만 `[@iyulab/components]` 로 `console.warn` 한다. */
31
+ export declare const devWarnOnce: (key: string, message: string) => void;
32
+ /** 테스트용 — 모든 네임스페이스의 가드를 비운다. */
17
33
  export declare function resetDevWarnings(): void;
@@ -1,5 +1,47 @@
1
1
  //#region src/utilities/devWarning.ts
2
- /** `key` 당 한 번만, 개발 모드에서만 `console.warn` 한다. 프로덕션 빌드에서는 호출 자체가 no-op 이다. */
3
- function devWarnOnce(key, message) {}
2
+ /**
3
+ * 개발 모드 «사용 안내» 경고 — 오류 경로가 아닌, ***조용히 틀리는*** 상태를 한 번 알린다.
4
+ *
5
+ * 이 패키지에는 같은 모양의 경고가 이미 있다(`UElement.warnIfTokensMissing` — 토큰 시트 부재).
6
+ * 공통 규약: `[<패키지>]` 네임스페이스 · 개발 모드에서만(`process.env.NODE_ENV !== 'production'`) ·
7
+ * 같은 키로는 **한 번만** · 무엇이 틀렸는지가 아니라 **무엇을 하면 되는지**를 적는다.
8
+ *
9
+ * ⚠**`import.meta.env.DEV` 를 쓰지 않는다** — Vite 는 이 라이브러리 **자신의** `vite build`
10
+ * 시점에 그 값을 정적으로 `false` 로 치환하고, Rollup 의 dead-code elimination 이 이후
11
+ * 분기를 통째로 지운다. 소비자가 dev 서버로 띄워도 이미 게시본에서 죽은 코드라 살아나지
12
+ * 않는다(2026-09-13 실측 — 1.40.1·1.40.2 게시본에서 이 경고가 전부 no-op 이었다).
13
+ * `process.env.NODE_ENV` 는 이 라이브러리의 빌드가 손대지 않고 그대로 dist 에 남아,
14
+ * **소비자 자신의** 번들러가 소비자의 빌드 시점에 치환한다(React 등 생태계 라이브러리와
15
+ * 동일한 관례) — `tests/build/dev-warning-dist.test.ts` 가 게시본에 대해 이 계약을 지킨다.
16
+ *
17
+ * 이 파일이 생긴 계기: 소비앱의 메뉴 30개가 해석되지 않는 아이콘 이름으로 **한꺼번에 같은
18
+ * 큐브 폴백**을 그렸는데 신호가 0 이었다(소비자 실측). 폴백은 의도된 것이라(접힌 사이드바에서
19
+ * 아이콘이 없으면 메뉴가 «높이만 있는 빈 줄» 이 된다) 제거가 답이 아니다 — **없는 것은 경고다.**
20
+ * 같은 부류가 셋 더 있었다: 높이 제약이 없어 18px 로 붕괴하는 `u-split-panel`, 가상화가 꺼지는
21
+ * `flex-table`, 133px 로 앉는 `modern-app` 셸 — 모두 «에러 없이 틀린 화면» 이다.
22
+ *
23
+ * 형제 패키지도 같은 규약을 쓴다 — `createDevWarner('@iyulab/flex-table')` 로 자기 네임스페이스의
24
+ * 경고 함수를 만든다. 키는 네임스페이스 안에서만 겹치므로 두 패키지가 같은 키를 써도 서로를 막지 않는다.
25
+ */
26
+ var warned = /* @__PURE__ */ new Set();
27
+ /**
28
+ * `scope` 네임스페이스(`[@iyulab/flex-table]` 처럼 찍힌다)의 개발 모드 1회성 경고 함수를 만든다.
29
+ * 프로덕션 빌드에서는 호출 자체가 no-op 이다.
30
+ */
31
+ function createDevWarner(scope) {
32
+ return (key, message) => {
33
+ if (process.env.NODE_ENV === "production") return;
34
+ const scoped = `${scope} ${key}`;
35
+ if (warned.has(scoped)) return;
36
+ warned.add(scoped);
37
+ console.warn(`[${scope}] ${message}`);
38
+ };
39
+ }
40
+ /** `key` 당 한 번만, 개발 모드에서만 `[@iyulab/components]` 로 `console.warn` 한다. */
41
+ var devWarnOnce = createDevWarner("@iyulab/components");
42
+ /** 테스트용 — 모든 네임스페이스의 가드를 비운다. */
43
+ function resetDevWarnings() {
44
+ warned.clear();
45
+ }
4
46
  //#endregion
5
- export { devWarnOnce };
47
+ export { createDevWarner, devWarnOnce, resetDevWarnings };
@@ -18,6 +18,6 @@ export declare function querySelectorAllWithin(element: Element, selectors: stri
18
18
  /**
19
19
  * 터치/펜처럼 지속되는 hover 상태가 없는 포인터인지 여부입니다.
20
20
  * - 이런 포인터는 탭 시 `pointerenter` 직후 `pointerleave`가 뒤따르므로, hover 트리거
21
- * 컴포넌트가 이를 구분하지 않으면 열리자마자 닫히는 결함으로 이어집니다(docket #104).
21
+ * 컴포넌트가 이를 구분하지 않으면 열리자마자 닫히는 결함으로 이어집니다(모바일 실측).
22
22
  */
23
23
  export declare function isCoarsePointer(event: PointerEvent): boolean;
@@ -37,7 +37,7 @@ function querySelectorAllWithin(element, selectors) {
37
37
  /**
38
38
  * 터치/펜처럼 지속되는 hover 상태가 없는 포인터인지 여부입니다.
39
39
  * - 이런 포인터는 탭 시 `pointerenter` 직후 `pointerleave`가 뒤따르므로, hover 트리거
40
- * 컴포넌트가 이를 구분하지 않으면 열리자마자 닫히는 결함으로 이어집니다(docket #104).
40
+ * 컴포넌트가 이를 구분하지 않으면 열리자마자 닫히는 결함으로 이어집니다(모바일 실측).
41
41
  */
42
42
  function isCoarsePointer(event) {
43
43
  return event.pointerType === "touch" || event.pointerType === "pen";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@iyulab/components",
3
3
  "description": "web-components library based on lit-element made by iyulab",
4
- "version": "1.40.1",
4
+ "version": "1.40.3",
5
5
  "keywords": [
6
6
  "iyulab",
7
7
  "components",