@luxfi/ui 7.4.5 → 7.4.7

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 (159) hide show
  1. package/dist/accordion.d.cts +74 -0
  2. package/dist/accordion.d.ts +74 -0
  3. package/dist/alert.cjs +3 -7
  4. package/dist/alert.d.cts +30 -0
  5. package/dist/alert.d.ts +30 -0
  6. package/dist/alert.js +3 -7
  7. package/dist/auth.cjs +481 -0
  8. package/dist/auth.d.cts +80 -0
  9. package/dist/auth.d.ts +80 -0
  10. package/dist/auth.js +456 -0
  11. package/dist/avatar.d.cts +25 -0
  12. package/dist/avatar.d.ts +25 -0
  13. package/dist/badge.cjs +4 -8
  14. package/dist/badge.d.cts +19 -0
  15. package/dist/badge.d.ts +19 -0
  16. package/dist/badge.js +4 -8
  17. package/dist/bank.d.cts +83 -0
  18. package/dist/bank.d.ts +83 -0
  19. package/dist/button.cjs +3 -7
  20. package/dist/button.d.cts +55 -0
  21. package/dist/button.d.ts +55 -0
  22. package/dist/button.js +3 -7
  23. package/dist/checkbox.d.cts +32 -0
  24. package/dist/checkbox.d.ts +32 -0
  25. package/dist/chrome.d.cts +93 -0
  26. package/dist/chrome.d.ts +93 -0
  27. package/dist/close-button.d.cts +9 -0
  28. package/dist/close-button.d.ts +9 -0
  29. package/dist/collapsible.cjs +3 -7
  30. package/dist/collapsible.d.cts +23 -0
  31. package/dist/collapsible.d.ts +23 -0
  32. package/dist/collapsible.js +3 -7
  33. package/dist/color-mode.d.cts +25 -0
  34. package/dist/color-mode.d.ts +25 -0
  35. package/dist/dialog.d.cts +118 -0
  36. package/dist/dialog.d.ts +118 -0
  37. package/dist/drawer.d.cts +36 -0
  38. package/dist/drawer.d.ts +36 -0
  39. package/dist/empty-state.d.cts +13 -0
  40. package/dist/empty-state.d.ts +13 -0
  41. package/dist/expiration-selector.d.cts +16 -0
  42. package/dist/expiration-selector.d.ts +16 -0
  43. package/dist/field.d.cts +24 -0
  44. package/dist/field.d.ts +24 -0
  45. package/dist/greeks-display.d.cts +14 -0
  46. package/dist/greeks-display.d.ts +14 -0
  47. package/dist/gui.d.cts +2 -0
  48. package/dist/gui.d.ts +2 -0
  49. package/dist/heading.d.cts +10 -0
  50. package/dist/heading.d.ts +10 -0
  51. package/dist/iam.d.cts +65 -0
  52. package/dist/iam.d.ts +65 -0
  53. package/dist/icon-button.cjs +3 -7
  54. package/dist/icon-button.d.cts +33 -0
  55. package/dist/icon-button.d.ts +33 -0
  56. package/dist/icon-button.js +3 -7
  57. package/dist/icons.cjs +0 -1
  58. package/dist/icons.d.cts +1 -0
  59. package/dist/icons.d.ts +1 -0
  60. package/dist/icons.js +0 -1
  61. package/dist/identity.d.cts +64 -0
  62. package/dist/identity.d.ts +64 -0
  63. package/dist/image.cjs +3 -7
  64. package/dist/image.d.cts +57 -0
  65. package/dist/image.d.ts +57 -0
  66. package/dist/image.js +3 -7
  67. package/dist/index.cjs +9 -8
  68. package/dist/index.d.cts +64 -0
  69. package/dist/index.d.ts +64 -0
  70. package/dist/index.js +9 -9
  71. package/dist/input-group.d.cts +17 -0
  72. package/dist/input-group.d.ts +17 -0
  73. package/dist/input.d.cts +17 -0
  74. package/dist/input.d.ts +17 -0
  75. package/dist/link.cjs +3 -7
  76. package/dist/link.d.cts +37 -0
  77. package/dist/link.d.ts +37 -0
  78. package/dist/link.js +3 -7
  79. package/dist/lux.config.d.cts +433 -0
  80. package/dist/lux.config.d.ts +433 -0
  81. package/dist/menu.d.cts +84 -0
  82. package/dist/menu.d.ts +84 -0
  83. package/dist/next.cjs +11 -1
  84. package/dist/next.d.cts +32 -0
  85. package/dist/next.d.ts +32 -0
  86. package/dist/next.js +11 -1
  87. package/dist/option-chain.d.cts +37 -0
  88. package/dist/option-chain.d.ts +37 -0
  89. package/dist/option-position.d.cts +29 -0
  90. package/dist/option-position.d.ts +29 -0
  91. package/dist/pin-input.d.cts +25 -0
  92. package/dist/pin-input.d.ts +25 -0
  93. package/dist/pnl-diagram.d.cts +11 -0
  94. package/dist/pnl-diagram.d.ts +11 -0
  95. package/dist/popover.d.cts +73 -0
  96. package/dist/popover.d.ts +73 -0
  97. package/dist/progress-circle.d.cts +47 -0
  98. package/dist/progress-circle.d.ts +47 -0
  99. package/dist/progress.d.cts +28 -0
  100. package/dist/progress.d.ts +28 -0
  101. package/dist/provider.d.cts +25 -0
  102. package/dist/provider.d.ts +25 -0
  103. package/dist/radio.d.cts +53 -0
  104. package/dist/radio.d.ts +53 -0
  105. package/dist/rating.d.cts +14 -0
  106. package/dist/rating.d.ts +14 -0
  107. package/dist/select.cjs +3 -7
  108. package/dist/select.d.cts +130 -0
  109. package/dist/select.d.ts +130 -0
  110. package/dist/select.js +3 -7
  111. package/dist/separator.d.cts +13 -0
  112. package/dist/separator.d.ts +13 -0
  113. package/dist/skeleton.cjs +8 -7
  114. package/dist/skeleton.d.cts +76 -0
  115. package/dist/skeleton.d.ts +76 -0
  116. package/dist/skeleton.js +8 -8
  117. package/dist/slider.d.cts +24 -0
  118. package/dist/slider.d.ts +24 -0
  119. package/dist/strategy-builder.d.cts +26 -0
  120. package/dist/strategy-builder.d.ts +26 -0
  121. package/dist/switch.d.cts +27 -0
  122. package/dist/switch.d.ts +27 -0
  123. package/dist/table.cjs +3 -7
  124. package/dist/table.d.cts +107 -0
  125. package/dist/table.d.ts +107 -0
  126. package/dist/table.js +3 -7
  127. package/dist/tabs.d.cts +78 -0
  128. package/dist/tabs.d.ts +78 -0
  129. package/dist/tag.cjs +4 -8
  130. package/dist/tag.d.cts +21 -0
  131. package/dist/tag.d.ts +21 -0
  132. package/dist/tag.js +4 -8
  133. package/dist/textarea.d.cts +13 -0
  134. package/dist/textarea.d.ts +13 -0
  135. package/dist/toaster.d.cts +29 -0
  136. package/dist/toaster.d.ts +29 -0
  137. package/dist/tooltip.cjs +1 -1
  138. package/dist/tooltip.d.cts +38 -0
  139. package/dist/tooltip.d.ts +38 -0
  140. package/dist/tooltip.js +1 -1
  141. package/dist/use-narrow.d.cts +12 -0
  142. package/dist/use-narrow.d.ts +12 -0
  143. package/dist/utils.d.cts +5 -0
  144. package/dist/utils.d.ts +5 -0
  145. package/dist/vite.cjs +4 -3
  146. package/dist/vite.d.cts +29 -0
  147. package/dist/vite.d.ts +29 -0
  148. package/dist/vite.js +4 -3
  149. package/dist/white-label.d.cts +59 -0
  150. package/dist/white-label.d.ts +59 -0
  151. package/package.json +12 -2
  152. package/src/auth.tsx +198 -0
  153. package/src/engine.ts +24 -3
  154. package/src/index.ts +1 -1
  155. package/src/next.ts +10 -0
  156. package/src/skeleton.tsx +36 -7
  157. package/src/tooltip.tsx +13 -5
  158. package/src/vite.ts +4 -9
  159. package/tokens.css +828 -317
package/tokens.css CHANGED
@@ -1,438 +1,949 @@
1
1
  /*
2
- * Design tokens CSS custom properties
2
+ * @luxfi/uiTHE canonical token set.
3
3
  *
4
- * These mirror the Chakra semantic tokens defined in
5
- * toolkit/theme/foundations/semanticTokens.ts and
6
- * toolkit/theme/foundations/colors.ts (DEFAULT_THEME_COLORS).
4
+ * Lux is monochrome. One achromatic ladder, plus exactly two hues reserved for
5
+ * state (error, success). Nothing else carries colour: no orange, no amber, no
6
+ * gold, no tan. Every value below either IS a ladder rung or is `var()`-derived
7
+ * from one — so a hostname can retheme the fleet by overriding a handful of
8
+ * custom properties, with no rebuild.
7
9
  *
8
- * Light mode is the default (:root). Dark mode activates via .dark class
9
- * (applied by next-themes with the "class" strategy).
10
+ * Layers, in resolution order:
11
+ * 1. ladder --lux-n-* achromatic, hue-free (Tailwind `neutral`)
12
+ * 2. brand hooks --lux-accent white-label-by-hostname overrides ONLY these
13
+ * 3. state --lux-state-* the only permitted hues (error / success)
14
+ * 4. semantic --color-* what components actually name
15
+ * 5. aliases shadcn (--background … --chart-*) so a Tailwind surface can
16
+ * drop its hand-rolled palette and import this instead
10
17
  *
11
- * Palette reference (true neutral grays):
12
- * gray.50 = #FAFAFA gray.500 = #737373
13
- * gray.100 = #F5F5F5 gray.600 = #525252
14
- * gray.200 = #E5E5E5 gray.700 = #404040
15
- * gray.300 = #D4D4D4 gray.800 = #262626
16
- * gray.400 = #A3A3A3 gray.900 = #171717
18
+ * Light mode is :root. Dark mode activates via `.dark` (next-themes class
19
+ * strategy) or [data-theme='dark'].
17
20
  *
18
- * blackAlpha.50 = rgba(16,17,18,0.04) whiteAlpha.50 = rgba(255,255,255,0.04)
19
- * blackAlpha.100 = rgba(16,17,18,0.06) whiteAlpha.100 = rgba(255,255,255,0.06)
20
- * blackAlpha.200 = rgba(16,17,18,0.08) whiteAlpha.200 = rgba(255,255,255,0.08)
21
- * blackAlpha.300 = rgba(16,17,18,0.16) whiteAlpha.300 = rgba(255,255,255,0.16)
22
- * blackAlpha.400 = rgba(16,17,18,0.24) whiteAlpha.400 = rgba(255,255,255,0.24)
23
- * blackAlpha.500 = rgba(16,17,18,0.36) whiteAlpha.500 = rgba(255,255,255,0.36)
24
- * blackAlpha.600 = rgba(16,17,18,0.48) whiteAlpha.600 = rgba(255,255,255,0.48)
25
- * blackAlpha.700 = rgba(16,17,18,0.64) whiteAlpha.700 = rgba(255,255,255,0.64)
26
- * blackAlpha.800 = rgba(16,17,18,0.80) whiteAlpha.800 = rgba(255,255,255,0.80)
27
- * blackAlpha.900 = rgba(16,17,18,0.92) whiteAlpha.900 = rgba(255,255,255,0.92)
21
+ * Rule: a component may not contain a colour literal. If a value is missing
22
+ * here, add it here.
28
23
  */
29
24
 
30
25
  /* ================================================================
31
- LIGHT MODE (default)
26
+ 1 THE LADDER. Achromatic: R = G = B at every rung.
27
+ ================================================================ */
28
+ :root {
29
+ --lux-n-0: #FFFFFF;
30
+ --lux-n-50: #FAFAFA;
31
+ --lux-n-100: #F5F5F5;
32
+ --lux-n-200: #E5E5E5;
33
+ --lux-n-300: #D4D4D4;
34
+ --lux-n-400: #A3A3A3;
35
+ --lux-n-500: #737373;
36
+ --lux-n-600: #525252;
37
+ --lux-n-700: #404040;
38
+ --lux-n-800: #262626;
39
+ --lux-n-900: #171717;
40
+ --lux-n-950: #0A0A0A;
41
+ --lux-n-1000: #000000;
42
+
43
+ /* The opacity ladder — the real palette for overlays and hairlines. */
44
+ --lux-ink-04: rgba(16, 17, 18, 0.04);
45
+ --lux-ink-06: rgba(16, 17, 18, 0.06);
46
+ --lux-ink-08: rgba(16, 17, 18, 0.08);
47
+ --lux-ink-10: rgba(16, 17, 18, 0.10);
48
+ --lux-ink-16: rgba(16, 17, 18, 0.16);
49
+ --lux-ink-24: rgba(16, 17, 18, 0.24);
50
+ --lux-ink-36: rgba(16, 17, 18, 0.36);
51
+ --lux-ink-48: rgba(16, 17, 18, 0.48);
52
+ --lux-ink-64: rgba(16, 17, 18, 0.64);
53
+ --lux-ink-80: rgba(16, 17, 18, 0.80);
54
+ --lux-ink-92: rgba(16, 17, 18, 0.92);
55
+ --lux-paper-04: rgba(255, 255, 255, 0.04);
56
+ --lux-paper-06: rgba(255, 255, 255, 0.06);
57
+ --lux-paper-08: rgba(255, 255, 255, 0.08);
58
+ --lux-paper-10: rgba(255, 255, 255, 0.10);
59
+ --lux-paper-12: rgba(255, 255, 255, 0.12);
60
+ --lux-paper-16: rgba(255, 255, 255, 0.16);
61
+ --lux-paper-24: rgba(255, 255, 255, 0.24);
62
+ --lux-paper-36: rgba(255, 255, 255, 0.36);
63
+ --lux-paper-48: rgba(255, 255, 255, 0.48);
64
+ --lux-paper-64: rgba(255, 255, 255, 0.64);
65
+ --lux-paper-80: rgba(255, 255, 255, 0.80);
66
+ --lux-paper-92: rgba(255, 255, 255, 0.92);
67
+
68
+ /* ------------------------------------------------------------
69
+ 2 — BRAND HOOKS. resolveWhiteLabel(host) writes these two and
70
+ nothing else. Default is monochrome: the accent is the ink.
71
+ ------------------------------------------------------------ */
72
+ --lux-accent: var(--lux-n-900);
73
+ --lux-accent-fg: var(--lux-n-0);
74
+
75
+ /* ------------------------------------------------------------
76
+ 3 — STATE. The ONLY permitted hues. Both meet WCAG AA (>=4.5:1)
77
+ as text on the --lux-n-50 card fill.
78
+ ------------------------------------------------------------ */
79
+ --lux-state-error: #B91C1C;
80
+ --lux-state-success: #1A7A47;
81
+ /* Attention/pending is NOT a hue in Lux. It is the loudest neutral. */
82
+ --lux-state-notice: var(--lux-n-600);
83
+ }
84
+
85
+ /* ================================================================
86
+ 4 — SEMANTIC (light). Every value is a var() off the ladder.
32
87
  ================================================================ */
33
88
  :root {
34
89
  /* --- Backgrounds --- */
35
- --color-bg-primary: #FFFFFF;
36
- --color-bg-info: #FAFAFA;
37
- --color-bg-body: #FFFFFF;
90
+ --color-bg-primary: var(--lux-n-0);
91
+ --color-bg-info: var(--lux-n-50);
92
+ --color-bg-body: var(--lux-n-0);
38
93
 
39
94
  /* --- Foregrounds --- */
40
- --color-fg-info: #525252;
95
+ --color-fg-info: var(--lux-n-600);
41
96
 
42
97
  /* --- Text --- */
43
- --color-text-primary: rgba(16, 17, 18, 0.80);
44
- --color-text-secondary: #737373;
45
- --color-text-error: #E53E3E;
46
- --color-text-success: #38A169;
98
+ --color-text-primary: var(--lux-ink-80);
99
+ --color-text-secondary: var(--lux-n-500);
100
+ --color-text-error: var(--lux-state-error);
101
+ --color-text-success: var(--lux-state-success);
102
+
103
+ /* --- Status foreground (AA >=4.5:1 on the --lux-n-50 card bg) --- */
104
+ --color-status-good: var(--lux-state-success);
105
+ --color-status-warn: var(--lux-state-notice);
106
+ --color-status-bad: var(--lux-state-error);
47
107
 
48
108
  /* --- Headings --- */
49
- --color-heading: rgba(16, 17, 18, 0.80);
109
+ --color-heading: var(--lux-ink-80);
50
110
 
51
111
  /* --- Icons --- */
52
- --color-icon-primary: #737373;
53
- --color-icon-secondary: #A3A3A3;
112
+ --color-icon-primary: var(--lux-n-500);
113
+ --color-icon-secondary: var(--lux-n-400);
54
114
 
55
115
  /* --- Borders --- */
56
- --color-border-divider: rgba(16, 17, 18, 0.06);
57
- --color-border-error: #E53E3E;
58
- --color-border-info: #A3A3A3;
116
+ --color-border-divider: var(--lux-ink-06);
117
+ --color-border-error: var(--lux-state-error);
118
+ --color-border-info: var(--lux-n-400);
59
119
 
60
120
  /* --- Links --- */
61
- --color-link-primary: #404040;
62
- --color-link-primary-hover: #525252;
63
- --color-link-secondary: #737373;
64
- --color-link-subtle: rgba(16, 17, 18, 0.80);
65
- --color-link-subtle-hover: rgba(16, 17, 18, 0.80);
66
- --color-link-menu: rgba(16, 17, 18, 0.80);
67
- --color-link-underlaid-bg: #F5F5F5;
68
- --color-link-nav-fg: rgba(16, 17, 18, 0.80);
69
- --color-link-nav-fg-hover: #525252;
70
- --color-link-nav-fg-selected: #171717;
71
- --color-link-nav-fg-active: #525252;
72
- --color-link-nav-bg-selected: #F5F5F5;
121
+ --color-link-primary: var(--lux-n-700);
122
+ --color-link-primary-hover: var(--lux-n-600);
123
+ --color-link-secondary: var(--lux-n-500);
124
+ --color-link-subtle: var(--lux-ink-80);
125
+ --color-link-subtle-hover: var(--lux-ink-80);
126
+ --color-link-menu: var(--lux-ink-80);
127
+ --color-link-underlaid-bg: var(--lux-n-100);
128
+ --color-link-nav-fg: var(--lux-ink-80);
129
+ --color-link-nav-fg-hover: var(--lux-n-600);
130
+ --color-link-nav-fg-selected: var(--lux-n-900);
131
+ --color-link-nav-fg-active: var(--lux-n-600);
132
+ --color-link-nav-bg-selected: var(--lux-n-100);
73
133
 
74
134
  /* --- Buttons --- */
75
- --color-button-solid-bg: #171717;
76
- --color-button-solid-text: #FFFFFF;
77
- --color-button-outline-fg: #171717;
78
- --color-button-subtle-fg: rgba(16, 17, 18, 0.80);
79
- --color-button-subtle-bg: rgba(16, 17, 18, 0.08);
80
- --color-button-dropdown-fg: rgba(16, 17, 18, 0.80);
81
- --color-button-dropdown-border: #F5F5F5;
82
- --color-button-header-fg: rgba(16, 17, 18, 0.80);
83
- --color-button-header-fg-selected: rgba(16, 17, 18, 0.80);
84
- --color-button-header-fg-highlighted: rgba(16, 17, 18, 0.80);
85
- --color-button-header-border: #F5F5F5;
86
- --color-button-header-bg-selected: rgba(16, 17, 18, 0.04);
87
- --color-button-header-bg-highlighted: #FEEBCB;
88
- --color-button-pagination-fg: rgba(16, 17, 18, 0.80);
89
- --color-button-pagination-border: #F5F5F5;
90
- --color-button-icon-background-bg: #FAFAFA;
91
- --color-button-hero-bg: #171717;
92
- --color-button-hero-fg: #FFFFFF;
93
- --color-button-hero-bg-hover: #525252;
94
- --color-button-hero-fg-hover: #FFFFFF;
95
- --color-button-hero-bg-selected: rgba(255, 255, 255, 0.06);
96
- --color-button-hero-fg-selected: rgba(16, 17, 18, 0.80);
97
- --color-button-segmented-fg: rgba(16, 17, 18, 0.80);
98
- --color-button-icon-bg: rgba(16, 17, 18, 0.04);
135
+ --color-button-solid-bg: var(--lux-accent);
136
+ --color-button-solid-text: var(--lux-accent-fg);
137
+ --color-button-outline-fg: var(--lux-n-900);
138
+ --color-button-subtle-fg: var(--lux-ink-80);
139
+ --color-button-subtle-bg: var(--lux-ink-08);
140
+ --color-button-dropdown-fg: var(--lux-ink-80);
141
+ --color-button-dropdown-border: var(--lux-n-100);
142
+ --color-button-header-fg: var(--lux-ink-80);
143
+ --color-button-header-fg-selected: var(--lux-ink-80);
144
+ --color-button-header-fg-highlighted: var(--lux-ink-80);
145
+ --color-button-header-border: var(--lux-n-100);
146
+ --color-button-header-bg-selected: var(--lux-ink-04);
147
+ --color-button-header-bg-highlighted: var(--lux-n-200);
148
+ --color-button-pagination-fg: var(--lux-ink-80);
149
+ --color-button-pagination-border: var(--lux-n-100);
150
+ --color-button-icon-background-bg: var(--lux-n-50);
151
+ --color-button-hero-bg: var(--lux-accent);
152
+ --color-button-hero-fg: var(--lux-accent-fg);
153
+ --color-button-hero-bg-hover: var(--lux-n-600);
154
+ --color-button-hero-fg-hover: var(--lux-n-0);
155
+ --color-button-hero-bg-selected: var(--lux-paper-06);
156
+ --color-button-hero-fg-selected: var(--lux-ink-80);
157
+ --color-button-segmented-fg: var(--lux-ink-80);
158
+ --color-button-icon-bg: var(--lux-ink-04);
99
159
 
100
160
  /* --- Hover / Selected --- */
101
- --color-hover: #525252;
102
- --color-selected-control-bg: #F5F5F5;
103
- --color-selected-control-text: #171717;
104
- --color-selected-option-bg: #404040;
161
+ --color-hover: var(--lux-n-600);
162
+ --color-selected-control-bg: var(--lux-n-100);
163
+ --color-selected-control-text: var(--lux-n-900);
164
+ --color-selected-option-bg: var(--lux-n-700);
105
165
 
106
166
  /* --- Navigation --- */
107
- --color-nav-bg-selected: #F5F5F5;
108
- --color-nav-text-selected: #171717;
167
+ --color-nav-bg-selected: var(--lux-n-100);
168
+ --color-nav-text-selected: var(--lux-n-900);
109
169
 
110
170
  /* --- Inputs --- */
111
- --color-input-fg: #262626;
112
- --color-input-bg: #FFFFFF;
113
- --color-input-bg-readonly: #E5E5E5;
114
- --color-input-border: #F5F5F5;
115
- --color-input-border-hover: #E5E5E5;
116
- --color-input-border-focus: #525252;
117
- --color-input-placeholder: #A3A3A3;
171
+ --color-input-fg: var(--lux-n-800);
172
+ --color-input-bg: var(--lux-n-0);
173
+ --color-input-bg-readonly: var(--lux-n-200);
174
+ --color-input-border: var(--lux-n-100);
175
+ --color-input-border-hover: var(--lux-n-200);
176
+ --color-input-border-focus: var(--lux-n-600);
177
+ /* n-400 on white is 2.5:1 — under the 3:1 non-text minimum. n-500 clears it. */
178
+ --color-input-placeholder: var(--lux-n-500);
118
179
 
119
180
  /* --- Tags --- */
120
- --color-tag-subtle-bg: rgba(16, 17, 18, 0.04);
121
- --color-tag-subtle-fg: rgba(16, 17, 18, 0.80);
122
- --color-tag-clickable-bg: #F5F5F5;
123
- --color-tag-clickable-fg: rgba(16, 17, 18, 0.80);
124
- --color-tag-filter-bg: #F5F5F5;
125
- --color-tag-select-bg: #F5F5F5;
126
- --color-tag-select-fg: #737373;
127
- --color-tag-select-selected-bg: #404040;
128
- --color-tag-select-selected-fg: rgba(255, 255, 255, 0.80);
129
-
130
- /* --- Badges --- */
131
- --color-badge-gray-bg: rgba(16, 17, 18, 0.04);
132
- --color-badge-gray-fg: rgba(16, 17, 18, 0.80);
181
+ --color-tag-subtle-bg: var(--lux-ink-04);
182
+ --color-tag-subtle-fg: var(--lux-ink-80);
183
+ --color-tag-clickable-bg: var(--lux-n-100);
184
+ --color-tag-clickable-fg: var(--lux-ink-80);
185
+ --color-tag-filter-bg: var(--lux-n-100);
186
+ --color-tag-select-bg: var(--lux-n-100);
187
+ --color-tag-select-fg: var(--lux-n-500);
188
+ --color-tag-select-selected-bg: var(--lux-n-700);
189
+ --color-tag-select-selected-fg: var(--lux-paper-80);
190
+
191
+ /* --- Badges ---
192
+ The colour NAMES are a legacy API (call sites say colorScheme="orange").
193
+ They are one monochrome ramp now; only green/red keep a hue, because they
194
+ carry state rather than decoration. */
195
+ --color-badge-gray-bg: var(--lux-ink-04);
196
+ --color-badge-gray-fg: var(--lux-ink-80);
133
197
  --color-badge-green-bg: #F0FFF4;
134
- --color-badge-green-fg: #38A169;
198
+ --color-badge-green-fg: var(--lux-state-success);
135
199
  --color-badge-red-bg: #FFF5F5;
136
- --color-badge-red-fg: #E53E3E;
137
- --color-badge-purple-bg: #F5F5F5;
138
- --color-badge-purple-fg: #525252;
139
- --color-badge-orange-bg: #FFFAF0;
140
- --color-badge-orange-fg: #DD6B20;
141
- --color-badge-blue-bg: #F5F5F5;
142
- --color-badge-blue-fg: #525252;
143
- --color-badge-yellow-bg: #FFFFF0;
144
- --color-badge-yellow-fg: #D69E2E;
145
- --color-badge-teal-bg: #F5F5F5;
146
- --color-badge-teal-fg: #525252;
147
- --color-badge-cyan-bg: #F5F5F5;
148
- --color-badge-cyan-fg: #525252;
149
- --color-badge-pink-bg: #FFF5F7;
150
- --color-badge-pink-fg: #D53F8C;
151
- --color-badge-purple-alt-bg: #F5F5F5;
152
- --color-badge-purple-alt-fg: rgba(16, 17, 18, 0.80);
153
- --color-badge-blue-alt-bg: #F5F5F5;
154
- --color-badge-blue-alt-fg: rgba(16, 17, 18, 0.80);
155
- --color-badge-bright-gray-bg: #F5F5F5;
156
- --color-badge-bright-gray-fg: #525252;
200
+ --color-badge-red-fg: var(--lux-state-error);
201
+ --color-badge-purple-bg: var(--lux-n-100);
202
+ --color-badge-purple-fg: var(--lux-n-600);
203
+ --color-badge-orange-bg: var(--lux-n-100);
204
+ --color-badge-orange-fg: var(--lux-n-600);
205
+ --color-badge-blue-bg: var(--lux-n-100);
206
+ --color-badge-blue-fg: var(--lux-n-600);
207
+ --color-badge-yellow-bg: var(--lux-n-100);
208
+ --color-badge-yellow-fg: var(--lux-n-600);
209
+ --color-badge-teal-bg: var(--lux-n-100);
210
+ --color-badge-teal-fg: var(--lux-n-600);
211
+ --color-badge-cyan-bg: var(--lux-n-100);
212
+ --color-badge-cyan-fg: var(--lux-n-600);
213
+ --color-badge-pink-bg: var(--lux-n-100);
214
+ --color-badge-pink-fg: var(--lux-n-600);
215
+ --color-badge-purple-alt-bg: var(--lux-n-100);
216
+ --color-badge-purple-alt-fg: var(--lux-ink-80);
217
+ --color-badge-blue-alt-bg: var(--lux-n-100);
218
+ --color-badge-blue-alt-fg: var(--lux-ink-80);
219
+ --color-badge-bright-gray-bg: var(--lux-n-100);
220
+ --color-badge-bright-gray-fg: var(--lux-n-600);
157
221
  --color-badge-bright-green-bg: #C6F6D5;
158
222
  --color-badge-bright-green-fg: #25855A;
159
223
  --color-badge-bright-red-bg: #FED7D7;
160
224
  --color-badge-bright-red-fg: #C53030;
161
- --color-badge-bright-blue-bg: #E5E5E5;
162
- --color-badge-bright-blue-fg: #404040;
163
- --color-badge-bright-yellow-bg: #FEFCBF;
164
- --color-badge-bright-yellow-fg: #B7791F;
165
- --color-badge-bright-teal-bg: #E5E5E5;
166
- --color-badge-bright-teal-fg: #404040;
167
- --color-badge-bright-cyan-bg: #E5E5E5;
168
- --color-badge-bright-cyan-fg: #404040;
169
- --color-badge-bright-orange-bg: #FEEBCB;
170
- --color-badge-bright-orange-fg: #C05621;
171
- --color-badge-bright-purple-bg: #E5E5E5;
172
- --color-badge-bright-purple-fg: #404040;
173
- --color-badge-bright-pink-bg: #FFF5F7;
174
- --color-badge-bright-pink-fg: #B83280;
225
+ --color-badge-bright-blue-bg: var(--lux-n-200);
226
+ --color-badge-bright-blue-fg: var(--lux-n-700);
227
+ --color-badge-bright-yellow-bg: var(--lux-n-200);
228
+ --color-badge-bright-yellow-fg: var(--lux-n-700);
229
+ --color-badge-bright-teal-bg: var(--lux-n-200);
230
+ --color-badge-bright-teal-fg: var(--lux-n-700);
231
+ --color-badge-bright-cyan-bg: var(--lux-n-200);
232
+ --color-badge-bright-cyan-fg: var(--lux-n-700);
233
+ --color-badge-bright-orange-bg: var(--lux-n-200);
234
+ --color-badge-bright-orange-fg: var(--lux-n-700);
235
+ --color-badge-bright-purple-bg: var(--lux-n-200);
236
+ --color-badge-bright-purple-fg: var(--lux-n-700);
237
+ --color-badge-bright-pink-bg: var(--lux-n-200);
238
+ --color-badge-bright-pink-fg: var(--lux-n-700);
175
239
 
176
240
  /* --- Alerts --- */
177
- --color-alert-fg: rgba(16, 17, 18, 0.80);
178
- --color-alert-bg-info: rgba(16, 17, 18, 0.04);
179
- --color-alert-bg-warning: #FEEBCB;
180
- --color-alert-bg-warning-table: #FFFAF0;
241
+ --color-alert-fg: var(--lux-ink-80);
242
+ --color-alert-bg-info: var(--lux-ink-04);
243
+ --color-alert-bg-warning: var(--lux-n-200);
244
+ --color-alert-bg-warning-table: var(--lux-n-50);
181
245
  --color-alert-bg-success: #C6F6D5;
182
246
  --color-alert-bg-error: #FED7D7;
183
247
 
184
248
  /* --- Dialogs / Popovers / Drawers --- */
185
- --color-dialog-bg: #FFFFFF;
186
- --color-dialog-fg: rgba(16, 17, 18, 0.80);
187
- --color-popover-bg: #FFFFFF;
188
- --color-popover-shadow: rgba(16, 17, 18, 0.08);
189
- --color-drawer-bg: #FFFFFF;
249
+ --color-dialog-bg: var(--lux-n-0);
250
+ --color-dialog-fg: var(--lux-ink-80);
251
+ --color-popover-bg: var(--lux-n-0);
252
+ --color-popover-border: var(--lux-ink-10);
253
+ --color-popover-shadow: var(--lux-ink-08);
254
+ --color-drawer-bg: var(--lux-n-0);
190
255
 
191
256
  /* --- Tooltip --- */
192
- --color-tooltip-bg: #171717;
193
- --color-tooltip-fg: #FFFFFF;
257
+ --color-tooltip-bg: var(--lux-n-900);
258
+ --color-tooltip-fg: var(--lux-n-0);
194
259
 
195
260
  /* --- Table --- */
196
- --color-table-header-bg: rgba(16, 17, 18, 0.06);
197
- --color-table-header-fg: rgba(16, 17, 18, 0.64);
261
+ --color-table-header-bg: var(--lux-ink-06);
262
+ --color-table-header-fg: var(--lux-ink-64);
198
263
 
199
264
  /* --- Tabs --- */
200
- --color-tabs-solid-fg: #171717;
201
- --color-tabs-secondary-fg: rgba(16, 17, 18, 0.80);
202
- --color-tabs-secondary-border: #D4D4D4;
203
-
204
- /* --- Switch --- */
205
- --color-switch-bg: #D4D4D4;
206
-
207
- /* --- Skeleton --- */
208
- --color-skeleton-start: rgba(16, 17, 18, 0.04);
209
- --color-skeleton-end: rgba(16, 17, 18, 0.06);
210
-
211
- /* --- Progress --- */
212
- --color-progress-track: #F5F5F5;
213
-
214
- /* --- Stats / Topbar --- */
215
- --color-stats-bg: #FAFAFA;
216
- --color-topbar-bg: #FAFAFA;
217
-
218
- /* --- Graph --- */
219
- --color-graph-line: #737373;
265
+ --color-tabs-solid-fg: var(--lux-n-900);
266
+ --color-tabs-solid-bg: var(--lux-n-0);
267
+ --color-tabs-solid-fg-selected: var(--lux-n-0);
268
+ --color-tabs-solid-bg-selected: var(--lux-n-900);
269
+ --color-tabs-secondary-fg: var(--lux-n-500);
270
+ --color-tabs-secondary-fg-selected: var(--lux-n-900);
271
+ --color-tabs-radio-fg: var(--lux-n-500);
272
+ --color-tabs-radio-bg-selected: var(--lux-n-0);
273
+ --color-tabs-radio-fg-selected: var(--lux-n-900);
274
+
275
+ --color-tabs-secondary-border: var(--lux-n-300);
276
+
277
+ /* --- Controls --- */
278
+ --color-switch-bg: var(--lux-n-300);
279
+ --color-progress-track: var(--lux-n-100);
280
+
281
+ /* --- Chrome / charts --- */
282
+ --color-stats-bg: var(--lux-n-50);
283
+ --color-topbar-bg: var(--lux-n-50);
284
+ --color-graph-line: var(--lux-n-500);
220
285
  --color-graph-gradient-start: rgba(163, 163, 163, 0.3);
221
286
  --color-graph-gradient-stop: rgba(163, 163, 163, 0);
222
287
 
288
+ /* --- Skeleton --- */
289
+ --color-skeleton-start: var(--lux-n-100);
290
+ --color-skeleton-end: var(--lux-n-200);
291
+
223
292
  /* --- Scrollbar --- */
224
- --color-scrollbar-thumb: rgba(16, 17, 18, 0.16);
293
+ --color-scrollbar-thumb: var(--lux-ink-16);
225
294
 
226
295
  /* --- Selection --- */
227
- --color-selection-bg: #E3CFE7;
296
+ --color-selection-bg: var(--lux-n-200);
297
+
298
+ /* --- Raw palette (an alias surface; the ladder is the source) --- */
299
+ --color-gray-50: var(--lux-n-50);
300
+ --color-gray-100: var(--lux-n-100);
301
+ --color-gray-200: var(--lux-n-200);
302
+ --color-gray-300: var(--lux-n-300);
303
+ --color-gray-400: var(--lux-n-400);
304
+ --color-gray-500: var(--lux-n-500);
305
+ --color-gray-600: var(--lux-n-600);
306
+ --color-gray-700: var(--lux-n-700);
307
+ --color-gray-800: var(--lux-n-800);
308
+ --color-gray-900: var(--lux-n-900);
309
+ --color-white: var(--lux-n-0);
310
+ --color-black: var(--lux-n-1000);
311
+ /* The two state ramps — the only rungs in this file that carry a hue.
312
+ Monotone, like the warm ramps, and likewise not flipped in `.dark`. */
313
+ --color-green-50: #F0FFF4;
314
+ --color-green-100: #C6F6D5;
315
+ --color-green-200: #9AE6B4;
316
+ --color-green-500: var(--lux-state-success);
317
+ --color-green-800: #22543D;
318
+ --color-red-50: #FFF5F5;
319
+ --color-red-100: #FED7D7;
320
+ --color-red-200: #FEB2B2;
321
+ --color-red-500: var(--lux-state-error);
322
+ --color-red-600: #C53030;
323
+ --color-red-800: #822727;
324
+ /* ------------------------------------------------------------
325
+ THE WARM RAMPS, DEFANGED.
326
+ Tailwind v4 compiles `bg-orange-100` to `background-color:
327
+ var(--color-orange-100)`, so redefining the rung here retints every
328
+ warm utility on every surface that imports this file — no component
329
+ edit, no rebuild. The ramps stay MONOTONE (low rung = light, high rung
330
+ = dark) in both modes, exactly like Tailwind's, so an author's
331
+ `bg-orange-50 dark:bg-yellow-900` still reads pale-on-light and
332
+ dark-on-dark. They are therefore NOT overridden in `.dark` below.
333
+ A rung is a rung; a warning is `--color-status-warn`.
334
+ ------------------------------------------------------------ */
335
+ --color-orange-50: var(--lux-n-50);
336
+ --color-orange-100: var(--lux-n-100);
337
+ --color-orange-200: var(--lux-n-200);
338
+ --color-orange-300: var(--lux-n-300);
339
+ --color-orange-400: var(--lux-n-400);
340
+ --color-orange-500: var(--lux-n-500);
341
+ --color-orange-600: var(--lux-n-600);
342
+ --color-orange-700: var(--lux-n-700);
343
+ --color-orange-800: var(--lux-n-800);
344
+ --color-orange-900: var(--lux-n-900);
345
+ --color-orange-950: var(--lux-n-950);
346
+ --color-amber-50: var(--color-orange-50);
347
+ --color-amber-100: var(--color-orange-100);
348
+ --color-amber-200: var(--color-orange-200);
349
+ --color-amber-300: var(--color-orange-300);
350
+ --color-amber-400: var(--color-orange-400);
351
+ --color-amber-500: var(--color-orange-500);
352
+ --color-amber-600: var(--color-orange-600);
353
+ --color-amber-700: var(--color-orange-700);
354
+ --color-amber-800: var(--color-orange-800);
355
+ --color-amber-900: var(--color-orange-900);
356
+ --color-amber-950: var(--color-orange-950);
357
+ --color-yellow-50: var(--color-orange-50);
358
+ --color-yellow-100: var(--color-orange-100);
359
+ --color-yellow-200: var(--color-orange-200);
360
+ --color-yellow-300: var(--color-orange-300);
361
+ --color-yellow-400: var(--color-orange-400);
362
+ --color-yellow-500: var(--color-orange-500);
363
+ --color-yellow-600: var(--color-orange-600);
364
+ --color-yellow-700: var(--color-orange-700);
365
+ --color-yellow-800: var(--color-orange-800);
366
+ --color-yellow-900: var(--color-orange-900);
367
+ --color-yellow-950: var(--color-orange-950);
368
+ /* Tailwind's warm neutral. Lux has exactly one neutral, and it is not warm. */
369
+ --color-stone-50: var(--lux-n-50);
370
+ --color-stone-100: var(--lux-n-100);
371
+ --color-stone-200: var(--lux-n-200);
372
+ --color-stone-300: var(--lux-n-300);
373
+ --color-stone-400: var(--lux-n-400);
374
+ --color-stone-500: var(--lux-n-500);
375
+ --color-stone-600: var(--lux-n-600);
376
+ --color-stone-700: var(--lux-n-700);
377
+ --color-stone-800: var(--lux-n-800);
378
+ --color-stone-900: var(--lux-n-900);
379
+ --color-stone-950: var(--lux-n-950);
380
+ --color-blackAlpha-50: var(--lux-ink-04);
381
+ --color-blackAlpha-100: var(--lux-ink-06);
382
+ --color-blackAlpha-200: var(--lux-ink-08);
383
+ --color-blackAlpha-300: var(--lux-ink-16);
384
+ --color-blackAlpha-400: var(--lux-ink-24);
385
+ --color-blackAlpha-500: var(--lux-ink-36);
386
+ --color-blackAlpha-600: var(--lux-ink-48);
387
+ --color-blackAlpha-700: var(--lux-ink-64);
388
+ --color-blackAlpha-800: var(--lux-ink-80);
389
+ --color-blackAlpha-900: var(--lux-ink-92);
390
+ --color-whiteAlpha-50: var(--lux-paper-04);
391
+ --color-whiteAlpha-100: var(--lux-paper-06);
392
+ --color-whiteAlpha-200: var(--lux-paper-08);
393
+ --color-whiteAlpha-300: var(--lux-paper-16);
394
+ --color-whiteAlpha-400: var(--lux-paper-24);
395
+ --color-whiteAlpha-500: var(--lux-paper-36);
396
+ --color-whiteAlpha-600: var(--lux-paper-48);
397
+ --color-whiteAlpha-700: var(--lux-paper-64);
398
+ --color-whiteAlpha-800: var(--lux-paper-80);
399
+ --color-whiteAlpha-900: var(--lux-paper-92);
400
+
401
+ /* --- Chakra legacy aliases (used by migrated components) --- */
402
+ --chakra-colors-bg-primary: var(--color-bg-primary);
403
+ --chakra-colors-text-primary: var(--color-text-primary);
404
+ --chakra-colors-text-secondary: var(--color-text-secondary);
405
+ --chakra-colors-icon-secondary: var(--color-icon-secondary);
406
+ --chakra-colors-border-divider: var(--color-border-divider);
407
+ --chakra-colors-link-primary: var(--color-link-primary);
408
+ --chakra-colors-link-primary-hover: var(--color-link-primary-hover);
409
+ --chakra-colors-hover: var(--color-hover);
410
+ --chakra-colors-gray-100: var(--color-gray-100);
411
+ --chakra-colors-whiteAlpha-50: var(--color-whiteAlpha-50);
412
+ --chakra-colors-whiteAlpha-100: var(--color-whiteAlpha-100);
228
413
 
229
414
  /* --- Shadows --- */
230
415
  --shadow-action-bar: 0 4px 4px -4px rgb(0 0 0 / 10%), 0 2px 4px -4px rgb(0 0 0 / 6%);
231
416
  --shadow-popover: 0px 15px 50px -12px rgba(0, 0, 0, 0.25);
232
417
  --shadow-drawer: 0px 4px 6px -2px rgba(0, 0, 0, 0.05), 0px 10px 15px -3px rgba(0, 0, 0, 0.1);
418
+
419
+ /* ------------------------------------------------------------
420
+ 5 — SHADCN / TAILWIND ALIASES (light).
421
+ A surface imports this file and deletes its own palette block.
422
+ ------------------------------------------------------------ */
423
+ --background: var(--lux-n-0);
424
+ --foreground: var(--lux-n-900);
425
+ --card: var(--lux-n-0);
426
+ --card-foreground: var(--lux-n-900);
427
+ --popover: var(--lux-n-0);
428
+ --popover-foreground: var(--lux-n-900);
429
+ --primary: var(--lux-accent);
430
+ --primary-foreground: var(--lux-accent-fg);
431
+ --secondary: var(--lux-n-100);
432
+ --secondary-foreground: var(--lux-n-900);
433
+ --muted: var(--lux-n-100);
434
+ --muted-foreground: var(--lux-n-500);
435
+ --accent: var(--lux-n-100);
436
+ --accent-foreground: var(--lux-n-900);
437
+ --destructive: var(--lux-state-error);
438
+ --destructive-foreground: var(--lux-n-0);
439
+ /* success / warning / info as shadcn-style pairs. `warning` is achromatic —
440
+ it used to be hsl(38 96% 57%), which is where lux.market got its tan. */
441
+ --success: var(--lux-state-success);
442
+ --success-foreground: var(--lux-n-0);
443
+ --warning: var(--lux-state-notice);
444
+ --warning-foreground: var(--lux-n-0);
445
+ --info: var(--lux-n-700);
446
+ --info-foreground: var(--lux-n-0);
447
+ --border: var(--lux-n-200);
448
+ --input: var(--lux-n-200);
449
+ --ring: var(--lux-n-400);
450
+ /* Charts: five achromatic steps, ordered by contrast, not by hue. */
451
+ --chart-1: var(--lux-n-900);
452
+ --chart-2: var(--lux-n-600);
453
+ --chart-3: var(--lux-n-400);
454
+ --chart-4: var(--lux-n-700);
455
+ --chart-5: var(--lux-n-300);
456
+ --sidebar: var(--lux-n-50);
457
+ --sidebar-foreground: var(--lux-n-900);
458
+ --sidebar-primary: var(--lux-accent);
459
+ --sidebar-primary-foreground: var(--lux-accent-fg);
460
+ --sidebar-accent: var(--lux-n-100);
461
+ --sidebar-accent-foreground: var(--lux-n-900);
462
+ --sidebar-border: var(--lux-n-200);
463
+ --sidebar-ring: var(--lux-n-400);
233
464
  }
234
465
 
235
466
  /* ================================================================
236
467
  DARK MODE
237
468
  ================================================================ */
238
- .dark {
469
+ .dark,
470
+ [data-theme='dark'] {
471
+ /* Brand hooks invert: on ink, the accent is the paper. */
472
+ --lux-accent: var(--lux-n-0);
473
+ --lux-accent-fg: var(--lux-n-900);
474
+
475
+ /* State, lightened for a dark ground (AA >=4.5:1 on --lux-n-900). */
476
+ --lux-state-error: #FCA5A5;
477
+ --lux-state-success: #6EE7A8;
478
+ --lux-state-notice: var(--lux-n-300);
479
+
239
480
  /* --- Backgrounds --- */
240
- --color-bg-primary: #000000;
241
- --color-bg-info: #171717;
242
- --color-bg-body: #000000;
481
+ --color-bg-primary: var(--lux-n-900);
482
+ --color-bg-info: var(--lux-n-800);
483
+ --color-bg-body: var(--lux-n-1000);
243
484
 
244
485
  /* --- Foregrounds --- */
245
- --color-fg-info: #D4D4D4;
486
+ --color-fg-info: var(--lux-n-300);
246
487
 
247
488
  /* --- Text --- */
248
- --color-text-primary: rgba(255, 255, 255, 0.80);
249
- --color-text-secondary: #A3A3A3;
250
- --color-text-error: #E53E3E;
251
- --color-text-success: #9AE6B4;
489
+ --color-text-primary: var(--lux-paper-80);
490
+ --color-text-secondary: var(--lux-n-400);
491
+ --color-text-error: var(--lux-state-error);
492
+ --color-text-success: var(--lux-state-success);
493
+
494
+ /* --- Status foreground --- */
495
+ --color-status-good: var(--lux-state-success);
496
+ --color-status-warn: var(--lux-state-notice);
497
+ --color-status-bad: var(--lux-state-error);
252
498
 
253
499
  /* --- Headings --- */
254
- --color-heading: rgba(255, 255, 255, 0.80);
500
+ --color-heading: var(--lux-paper-80);
255
501
 
256
502
  /* --- Icons --- */
257
- --color-icon-primary: #A3A3A3;
258
- --color-icon-secondary: #737373;
503
+ --color-icon-primary: var(--lux-n-400);
504
+ --color-icon-secondary: var(--lux-n-500);
259
505
 
260
506
  /* --- Borders --- */
261
- --color-border-divider: rgba(255, 255, 255, 0.06);
262
- --color-border-error: #E53E3E;
263
- --color-border-info: #737373;
507
+ --color-border-divider: var(--lux-paper-06);
508
+ --color-border-error: var(--lux-state-error);
509
+ --color-border-info: var(--lux-n-500);
264
510
 
265
511
  /* --- Links --- */
266
- --color-link-primary: rgba(255, 255, 255, 0.64);
267
- --color-link-primary-hover: rgba(255, 255, 255, 0.64);
268
- --color-link-secondary: #A3A3A3;
269
- --color-link-subtle: #A3A3A3;
270
- --color-link-subtle-hover: #A3A3A3;
271
- --color-link-menu: rgba(255, 255, 255, 0.80);
272
- --color-link-underlaid-bg: #262626;
273
- --color-link-nav-fg: rgba(255, 255, 255, 0.80);
274
- --color-link-nav-fg-hover: rgba(255, 255, 255, 0.64);
275
- --color-link-nav-fg-selected: #FAFAFA;
276
- --color-link-nav-fg-active: rgba(255, 255, 255, 0.64);
277
- --color-link-nav-bg-selected: rgba(255, 255, 255, 0.06);
512
+ --color-link-primary: var(--lux-n-300);
513
+ --color-link-primary-hover: var(--lux-n-200);
514
+ --color-link-secondary: var(--lux-n-400);
515
+ --color-link-subtle: var(--lux-paper-80);
516
+ --color-link-subtle-hover: var(--lux-paper-80);
517
+ --color-link-menu: var(--lux-paper-80);
518
+ --color-link-underlaid-bg: var(--lux-n-800);
519
+ --color-link-nav-fg: var(--lux-paper-80);
520
+ --color-link-nav-fg-hover: var(--lux-n-200);
521
+ --color-link-nav-fg-selected: var(--lux-n-0);
522
+ --color-link-nav-fg-active: var(--lux-n-200);
523
+ --color-link-nav-bg-selected: var(--lux-n-800);
278
524
 
279
525
  /* --- Buttons --- */
280
- --color-button-solid-bg: rgba(255, 255, 255, 0.92);
281
- --color-button-solid-text: #000000;
282
- --color-button-outline-fg: rgba(255, 255, 255, 0.92);
283
- --color-button-subtle-fg: rgba(255, 255, 255, 0.80);
284
- --color-button-subtle-bg: rgba(255, 255, 255, 0.08);
285
- --color-button-dropdown-fg: rgba(255, 255, 255, 0.80);
286
- --color-button-dropdown-border: #525252;
287
- --color-button-header-fg: #A3A3A3;
288
- --color-button-header-fg-selected: rgba(255, 255, 255, 0.80);
289
- --color-button-header-fg-highlighted: rgba(255, 255, 255, 0.80);
290
- --color-button-header-border: #525252;
291
- --color-button-header-bg-selected: rgba(255, 255, 255, 0.06);
292
- --color-button-header-bg-highlighted: #7B341E;
293
- --color-button-pagination-fg: #FAFAFA;
294
- --color-button-pagination-border: rgba(255, 255, 255, 0.06);
295
- --color-button-icon-background-bg: rgba(255, 255, 255, 0.04);
296
- --color-button-hero-bg: rgba(255, 255, 255, 0.92);
297
- --color-button-hero-fg: #FFFFFF;
298
- --color-button-hero-bg-hover: rgba(255, 255, 255, 0.64);
299
- --color-button-hero-fg-hover: #FFFFFF;
300
- --color-button-hero-bg-selected: rgba(255, 255, 255, 0.06);
301
- --color-button-hero-fg-selected: rgba(16, 17, 18, 0.80);
302
- --color-button-segmented-fg: rgba(255, 255, 255, 0.80);
303
- --color-button-icon-bg: rgba(255, 255, 255, 0.04);
526
+ --color-button-solid-bg: var(--lux-accent);
527
+ --color-button-solid-text: var(--lux-accent-fg);
528
+ --color-button-outline-fg: var(--lux-n-0);
529
+ --color-button-subtle-fg: var(--lux-paper-80);
530
+ --color-button-subtle-bg: var(--lux-paper-08);
531
+ --color-button-dropdown-fg: var(--lux-paper-80);
532
+ --color-button-dropdown-border: var(--lux-n-800);
533
+ --color-button-header-fg: var(--lux-paper-80);
534
+ --color-button-header-fg-selected: var(--lux-paper-80);
535
+ --color-button-header-fg-highlighted: var(--lux-paper-80);
536
+ --color-button-header-border: var(--lux-n-800);
537
+ --color-button-header-bg-selected: var(--lux-paper-04);
538
+ --color-button-header-bg-highlighted: var(--lux-n-700);
539
+ --color-button-pagination-fg: var(--lux-paper-80);
540
+ --color-button-pagination-border: var(--lux-n-800);
541
+ --color-button-icon-background-bg: var(--lux-n-800);
542
+ --color-button-hero-bg: var(--lux-accent);
543
+ --color-button-hero-fg: var(--lux-accent-fg);
544
+ --color-button-hero-bg-hover: var(--lux-n-200);
545
+ --color-button-hero-fg-hover: var(--lux-n-900);
546
+ --color-button-hero-bg-selected: var(--lux-paper-06);
547
+ --color-button-hero-fg-selected: var(--lux-paper-80);
548
+ --color-button-segmented-fg: var(--lux-paper-80);
549
+ --color-button-icon-bg: var(--lux-paper-04);
304
550
 
305
551
  /* --- Hover / Selected --- */
306
- --color-hover: rgba(255, 255, 255, 0.64);
307
- --color-selected-control-bg: rgba(255, 255, 255, 0.06);
308
- --color-selected-control-text: #FAFAFA;
309
- --color-selected-option-bg: rgba(255, 255, 255, 0.16);
552
+ --color-hover: var(--lux-n-200);
553
+ --color-selected-control-bg: var(--lux-n-800);
554
+ --color-selected-control-text: var(--lux-n-0);
555
+ --color-selected-option-bg: var(--lux-n-700);
310
556
 
311
557
  /* --- Navigation --- */
312
- --color-nav-bg-selected: rgba(255, 255, 255, 0.06);
313
- --color-nav-text-selected: #FAFAFA;
558
+ --color-nav-bg-selected: var(--lux-n-800);
559
+ --color-nav-text-selected: var(--lux-n-0);
314
560
 
315
561
  /* --- Inputs --- */
316
- --color-input-fg: #FAFAFA;
317
- --color-input-bg: #000000;
318
- --color-input-bg-readonly: #262626;
319
- --color-input-border: #525252;
320
- --color-input-border-hover: #737373;
321
- --color-input-border-focus: rgba(255, 255, 255, 0.64);
322
- --color-input-placeholder: #737373;
562
+ --color-input-fg: var(--lux-n-200);
563
+ --color-input-bg: var(--lux-n-900);
564
+ --color-input-bg-readonly: var(--lux-n-800);
565
+ --color-input-border: var(--lux-n-800);
566
+ --color-input-border-hover: var(--lux-n-700);
567
+ --color-input-border-focus: var(--lux-n-400);
568
+ --color-input-placeholder: var(--lux-n-500);
323
569
 
324
570
  /* --- Tags --- */
325
- --color-tag-subtle-bg: rgba(255, 255, 255, 0.06);
326
- --color-tag-subtle-fg: rgba(255, 255, 255, 0.80);
327
- --color-tag-clickable-bg: #262626;
328
- --color-tag-clickable-fg: rgba(255, 255, 255, 0.80);
329
- --color-tag-filter-bg: rgba(255, 255, 255, 0.08);
330
- --color-tag-select-bg: #262626;
331
- --color-tag-select-fg: rgba(255, 255, 255, 0.80);
332
- --color-tag-select-selected-bg: rgba(255, 255, 255, 0.16);
333
- --color-tag-select-selected-fg: rgba(255, 255, 255, 0.80);
571
+ --color-tag-subtle-bg: var(--lux-paper-06);
572
+ --color-tag-subtle-fg: var(--lux-paper-80);
573
+ --color-tag-clickable-bg: var(--lux-n-800);
574
+ --color-tag-clickable-fg: var(--lux-paper-80);
575
+ --color-tag-filter-bg: var(--lux-n-800);
576
+ --color-tag-select-bg: var(--lux-n-800);
577
+ --color-tag-select-fg: var(--lux-n-400);
578
+ --color-tag-select-selected-bg: var(--lux-n-700);
579
+ --color-tag-select-selected-fg: var(--lux-paper-80);
334
580
 
335
581
  /* --- Badges --- */
336
- --color-badge-gray-bg: rgba(255, 255, 255, 0.06);
337
- --color-badge-gray-fg: rgba(255, 255, 255, 0.80);
582
+ --color-badge-gray-bg: var(--lux-paper-06);
583
+ --color-badge-gray-fg: var(--lux-paper-80);
338
584
  --color-badge-green-bg: #22543D;
339
585
  --color-badge-green-fg: #9AE6B4;
340
586
  --color-badge-red-bg: #822727;
341
587
  --color-badge-red-fg: #FEB2B2;
342
- --color-badge-purple-bg: #262626;
343
- --color-badge-purple-fg: #D4D4D4;
344
- --color-badge-orange-bg: #7B341E;
345
- --color-badge-orange-fg: #FEEBCB;
346
- --color-badge-blue-bg: #262626;
347
- --color-badge-blue-fg: #D4D4D4;
348
- --color-badge-yellow-bg: #744210;
349
- --color-badge-yellow-fg: #FEFCBF;
350
- --color-badge-teal-bg: #262626;
351
- --color-badge-teal-fg: #D4D4D4;
352
- --color-badge-cyan-bg: #262626;
353
- --color-badge-cyan-fg: #D4D4D4;
354
- --color-badge-pink-bg: #702459;
355
- --color-badge-pink-fg: #FED7E2;
356
- --color-badge-purple-alt-bg: #262626;
357
- --color-badge-purple-alt-fg: rgba(255, 255, 255, 0.80);
358
- --color-badge-blue-alt-bg: #262626;
359
- --color-badge-blue-alt-fg: rgba(255, 255, 255, 0.80);
360
- --color-badge-bright-gray-bg: #262626;
361
- --color-badge-bright-gray-fg: #E5E5E5;
588
+ --color-badge-purple-bg: var(--lux-n-800);
589
+ --color-badge-purple-fg: var(--lux-n-300);
590
+ --color-badge-orange-bg: var(--lux-n-800);
591
+ --color-badge-orange-fg: var(--lux-n-300);
592
+ --color-badge-blue-bg: var(--lux-n-800);
593
+ --color-badge-blue-fg: var(--lux-n-300);
594
+ --color-badge-yellow-bg: var(--lux-n-800);
595
+ --color-badge-yellow-fg: var(--lux-n-300);
596
+ --color-badge-teal-bg: var(--lux-n-800);
597
+ --color-badge-teal-fg: var(--lux-n-300);
598
+ --color-badge-cyan-bg: var(--lux-n-800);
599
+ --color-badge-cyan-fg: var(--lux-n-300);
600
+ --color-badge-pink-bg: var(--lux-n-800);
601
+ --color-badge-pink-fg: var(--lux-n-300);
602
+ --color-badge-purple-alt-bg: var(--lux-n-800);
603
+ --color-badge-purple-alt-fg: var(--lux-paper-80);
604
+ --color-badge-blue-alt-bg: var(--lux-n-800);
605
+ --color-badge-blue-alt-fg: var(--lux-paper-80);
606
+ --color-badge-bright-gray-bg: var(--lux-n-800);
607
+ --color-badge-bright-gray-fg: var(--lux-n-200);
362
608
  --color-badge-bright-green-bg: #22543D;
363
609
  --color-badge-bright-green-fg: #9AE6B4;
364
610
  --color-badge-bright-red-bg: #822727;
365
611
  --color-badge-bright-red-fg: #FEB2B2;
366
- --color-badge-bright-blue-bg: #404040;
367
- --color-badge-bright-blue-fg: #E5E5E5;
368
- --color-badge-bright-yellow-bg: #744210;
369
- --color-badge-bright-yellow-fg: #FAF089;
370
- --color-badge-bright-teal-bg: #404040;
371
- --color-badge-bright-teal-fg: #E5E5E5;
372
- --color-badge-bright-cyan-bg: #404040;
373
- --color-badge-bright-cyan-fg: #E5E5E5;
374
- --color-badge-bright-orange-bg: #C05621;
375
- --color-badge-bright-orange-fg: #FEEBCB;
376
- --color-badge-bright-purple-bg: #404040;
377
- --color-badge-bright-purple-fg: #E5E5E5;
378
- --color-badge-bright-pink-bg: #B83280;
379
- --color-badge-bright-pink-fg: #FFF5F7;
612
+ --color-badge-bright-blue-bg: var(--lux-n-700);
613
+ --color-badge-bright-blue-fg: var(--lux-n-200);
614
+ --color-badge-bright-yellow-bg: var(--lux-n-700);
615
+ --color-badge-bright-yellow-fg: var(--lux-n-200);
616
+ --color-badge-bright-teal-bg: var(--lux-n-700);
617
+ --color-badge-bright-teal-fg: var(--lux-n-200);
618
+ --color-badge-bright-cyan-bg: var(--lux-n-700);
619
+ --color-badge-bright-cyan-fg: var(--lux-n-200);
620
+ --color-badge-bright-orange-bg: var(--lux-n-700);
621
+ --color-badge-bright-orange-fg: var(--lux-n-200);
622
+ --color-badge-bright-purple-bg: var(--lux-n-700);
623
+ --color-badge-bright-purple-fg: var(--lux-n-200);
624
+ --color-badge-bright-pink-bg: var(--lux-n-700);
625
+ --color-badge-bright-pink-fg: var(--lux-n-200);
380
626
 
381
627
  /* --- Alerts --- */
382
- --color-alert-fg: rgba(255, 255, 255, 0.80);
383
- --color-alert-bg-info: rgba(255, 255, 255, 0.06);
384
- --color-alert-bg-warning: rgba(124, 67, 16, 0.44);
385
- --color-alert-bg-warning-table: rgba(123, 52, 30, 0.44);
628
+ --color-alert-fg: var(--lux-paper-80);
629
+ --color-alert-bg-info: var(--lux-paper-06);
630
+ --color-alert-bg-warning: var(--lux-paper-08);
631
+ --color-alert-bg-warning-table: var(--lux-paper-06);
386
632
  --color-alert-bg-success: #1C4532;
387
633
  --color-alert-bg-error: #63171B;
388
634
 
389
635
  /* --- Dialogs / Popovers / Drawers --- */
390
- --color-dialog-bg: #171717;
391
- --color-dialog-fg: rgba(255, 255, 255, 0.80);
392
- --color-popover-bg: #171717;
393
- --color-popover-shadow: rgba(255, 255, 255, 0.16);
394
- --color-drawer-bg: #171717;
636
+ --color-dialog-bg: var(--lux-n-900);
637
+ --color-dialog-fg: var(--lux-paper-80);
638
+ --color-popover-bg: var(--lux-n-900);
639
+ --color-popover-border: var(--lux-paper-12);
640
+ --color-popover-shadow: var(--lux-paper-16);
641
+ --color-drawer-bg: var(--lux-n-900);
395
642
 
396
643
  /* --- Tooltip --- */
397
- --color-tooltip-bg: #171717;
398
- --color-tooltip-fg: #FFFFFF;
644
+ --color-tooltip-bg: var(--lux-n-900);
645
+ --color-tooltip-fg: var(--lux-n-0);
399
646
 
400
647
  /* --- Table --- */
401
- --color-table-header-bg: rgba(255, 255, 255, 0.08);
402
- --color-table-header-fg: rgba(255, 255, 255, 0.64);
648
+ --color-table-header-bg: var(--lux-paper-06);
649
+ --color-table-header-fg: var(--lux-paper-64);
403
650
 
404
651
  /* --- Tabs --- */
405
- --color-tabs-solid-fg: rgba(255, 255, 255, 0.80);
406
- --color-tabs-secondary-fg: rgba(255, 255, 255, 0.80);
407
- --color-tabs-secondary-border: #525252;
408
-
409
- /* --- Switch --- */
410
- --color-switch-bg: rgba(255, 255, 255, 0.24);
652
+ --color-tabs-solid-fg: var(--lux-n-0);
653
+ --color-tabs-solid-bg: var(--lux-n-900);
654
+ --color-tabs-solid-fg-selected: var(--lux-n-900);
655
+ --color-tabs-solid-bg-selected: var(--lux-n-0);
656
+ --color-tabs-secondary-fg: var(--lux-n-400);
657
+ --color-tabs-secondary-fg-selected: var(--lux-n-0);
658
+ --color-tabs-radio-fg: var(--lux-n-400);
659
+ --color-tabs-radio-bg-selected: var(--lux-n-800);
660
+ --color-tabs-radio-fg-selected: var(--lux-n-0);
661
+
662
+ --color-tabs-secondary-border: var(--lux-n-600);
663
+
664
+ /* --- Controls --- */
665
+ --color-switch-bg: var(--lux-paper-24);
666
+ --color-progress-track: var(--lux-paper-06);
667
+
668
+ /* --- Chrome / charts --- */
669
+ --color-stats-bg: var(--lux-paper-04);
670
+ --color-topbar-bg: var(--lux-paper-04);
671
+ --color-graph-line: var(--lux-paper-48);
672
+ --color-graph-gradient-start: rgba(255, 255, 255, 0.15);
673
+ --color-graph-gradient-stop: rgba(255, 255, 255, 0);
674
+ --shadow-drawer: 0px 15px 40px 0px rgba(0, 0, 0, 0.4), 0px 5px 10px 0px rgba(0, 0, 0, 0.2), 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
411
675
 
412
676
  /* --- Skeleton --- */
413
- --color-skeleton-start: rgba(255, 255, 255, 0.04);
414
- --color-skeleton-end: rgba(255, 255, 255, 0.06);
677
+ --color-skeleton-start: var(--lux-n-800);
678
+ --color-skeleton-end: var(--lux-n-700);
415
679
 
416
- /* --- Progress --- */
417
- --color-progress-track: rgba(255, 255, 255, 0.06);
680
+ /* --- Scrollbar --- */
681
+ --color-scrollbar-thumb: var(--lux-paper-16);
418
682
 
419
- /* --- Stats / Topbar --- */
420
- --color-stats-bg: rgba(255, 255, 255, 0.04);
421
- --color-topbar-bg: rgba(255, 255, 255, 0.04);
683
+ /* --- Selection --- */
684
+ --color-selection-bg: var(--lux-n-700);
685
+
686
+ /* The warm ramps are NOT redefined here on purpose — see the light block.
687
+ They are monotone in both modes so `dark:bg-yellow-900` still means
688
+ "dark". A call site that needs ink that FLIPS with the mode wants
689
+ --color-status-warn, which is a semantic token, not a palette rung. */
690
+
691
+ /* ------------------------------------------------------------
692
+ SHADCN / TAILWIND ALIASES (dark)
693
+ ------------------------------------------------------------ */
694
+ --background: var(--lux-n-1000);
695
+ --foreground: var(--lux-n-50);
696
+ --card: var(--lux-n-950);
697
+ --card-foreground: var(--lux-n-50);
698
+ --popover: var(--lux-n-950);
699
+ --popover-foreground: var(--lux-n-50);
700
+ --primary: var(--lux-accent);
701
+ --primary-foreground: var(--lux-accent-fg);
702
+ --secondary: var(--lux-n-900);
703
+ --secondary-foreground: var(--lux-n-50);
704
+ --muted: var(--lux-n-900);
705
+ --muted-foreground: var(--lux-n-400);
706
+ --accent: var(--lux-n-900);
707
+ --accent-foreground: var(--lux-n-50);
708
+ --destructive: var(--lux-state-error);
709
+ --destructive-foreground: var(--lux-n-950);
710
+ --success: var(--lux-state-success);
711
+ --success-foreground: var(--lux-n-950);
712
+ --warning: var(--lux-state-notice);
713
+ --warning-foreground: var(--lux-n-950);
714
+ --info: var(--lux-n-300);
715
+ --info-foreground: var(--lux-n-950);
716
+ --border: var(--lux-paper-10);
717
+ --input: var(--lux-paper-12);
718
+ --ring: var(--lux-n-500);
719
+ --chart-1: var(--lux-n-50);
720
+ --chart-2: var(--lux-n-400);
721
+ --chart-3: var(--lux-n-500);
722
+ --chart-4: var(--lux-n-300);
723
+ --chart-5: var(--lux-n-600);
724
+ --sidebar: var(--lux-n-950);
725
+ --sidebar-foreground: var(--lux-n-50);
726
+ --sidebar-primary: var(--lux-accent);
727
+ --sidebar-primary-foreground: var(--lux-accent-fg);
728
+ --sidebar-accent: var(--lux-n-900);
729
+ --sidebar-accent-foreground: var(--lux-n-50);
730
+ --sidebar-border: var(--lux-paper-10);
731
+ --sidebar-ring: var(--lux-n-500);
732
+ }
422
733
 
423
- /* --- Graph --- */
424
- --color-graph-line: rgba(255, 255, 255, 0.48);
425
- --color-graph-gradient-start: rgba(255, 255, 255, 0.15);
426
- --color-graph-gradient-stop: rgba(255, 255, 255, 0);
734
+ /* ================================================================
735
+ 6 — MOTION. One system: five durations, four curves, six moves.
736
+
737
+ Two rules make "smooth" measurable. Both were established by
738
+ benchmark, not by taste, and the second one is the one that bites.
739
+
740
+ 1. WHAT you animate. A decorative animation may touch `transform`
741
+ and `opacity` and nothing else. Those two are the only properties
742
+ the compositor can run off the main thread, so they survive a
743
+ busy CPU. Animating width / height / top / left / margin /
744
+ background-position forces layout or paint every frame. Worse,
745
+ the layout ones also register as layout shift: the explorer's
746
+ mobile drawer animated `left` and scored CLS 0.726 every time it
747
+ opened — Google calls anything over 0.25 "poor". The same drawer
748
+ on `transform` scores 0.
749
+
750
+ 2. HOW MANY things animate. Keep it O(1) in the size of the page.
751
+ Every independently animated element is a compositor layer to
752
+ allocate, rasterise and composite. Past a few hundred, that count
753
+ — not the property — is the bottleneck, and rule 1 alone will
754
+ happily walk you off a cliff. The measurements are in 6c.
755
+
756
+ The one deliberate exception is a disclosure widget (accordion,
757
+ collapsible). There the box change IS the content the user asked
758
+ for, not decoration, so animating its height is correct. Decoration
759
+ animates transform; disclosure animates the box it discloses.
760
+
761
+ Durations are named for their ROLE so a caller cannot pick "300ms
762
+ because it felt right". Exits are shorter than entrances — by the
763
+ time a thing leaves the user has already decided, and waiting on it
764
+ reads as lag.
765
+ ================================================================ */
766
+ :root {
767
+ /* --- Durations -------------------------------------------------- */
768
+ --lux-dur-press: 80ms; /* tap acknowledgement */
769
+ --lux-dur-hover: 120ms; /* pointer feedback */
770
+ --lux-dur-exit: 140ms; /* something leaves */
771
+ --lux-dur-enter: 200ms; /* something appears */
772
+ --lux-dur-move: 280ms; /* a panel travels across the viewport */
773
+
774
+ /* --- Curves ----------------------------------------------------- */
775
+ --lux-ease: cubic-bezier(0.4, 0, 0.2, 1); /* A -> B */
776
+ --lux-ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* entering */
777
+ --lux-ease-in: cubic-bezier(0.7, 0, 0.84, 0); /* leaving */
778
+ --lux-ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1); /* press */
779
+
780
+ /* --- Distances. Enter offsets are one value, not a per-component
781
+ guess. Both collapse to 0 under reduced motion. -------------- */
782
+ --lux-lift: 2px; /* hover raise */
783
+ --lux-rise: 8px; /* enter offset */
784
+ }
427
785
 
428
- /* --- Scrollbar --- */
429
- --color-scrollbar-thumb: rgba(255, 255, 255, 0.16);
786
+ /* --- The six moves. transform + opacity only. --------------------- */
787
+ @keyframes lux-fade-in { from { opacity: 0 } to { opacity: 1 } }
788
+ @keyframes lux-fade-out { from { opacity: 1 } to { opacity: 0 } }
430
789
 
431
- /* --- Selection --- */
432
- --color-selection-bg: #754B7D;
790
+ @keyframes lux-rise-in {
791
+ from { opacity: 0; transform: translate3d(0, var(--lux-rise), 0) }
792
+ to { opacity: 1; transform: translate3d(0, 0, 0) }
793
+ }
433
794
 
434
- /* --- Shadows --- */
435
- --shadow-action-bar: 0 4px 4px -4px rgb(0 0 0 / 10%), 0 2px 4px -4px rgb(0 0 0 / 6%);
436
- --shadow-popover: 0px 15px 40px 0px rgba(0, 0, 0, 0.4), 0px 5px 10px 0px rgba(0, 0, 0, 0.2), 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
437
- --shadow-drawer: 0px 15px 40px 0px rgba(0, 0, 0, 0.4), 0px 5px 10px 0px rgba(0, 0, 0, 0.2), 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
795
+ @keyframes lux-scale-in {
796
+ from { opacity: 0; transform: scale(0.96) }
797
+ to { opacity: 1; transform: scale(1) }
798
+ }
799
+
800
+ @keyframes lux-scale-out {
801
+ from { opacity: 1; transform: scale(1) }
802
+ to { opacity: 0; transform: scale(0.96) }
803
+ }
804
+
805
+ /* The loading sweep — ONE per page, never one per skeleton. See 6c. */
806
+ @keyframes lux-sweep {
807
+ from { transform: translate3d(-100%, 0, 0) }
808
+ to { transform: translate3d(350%, 0, 0) }
809
+ }
810
+
811
+ @keyframes lux-spin { to { transform: rotate(360deg) } }
812
+
813
+ /* ================================================================
814
+ 6b — The primitives. Every surface reaches for these, never for a
815
+ hand-rolled keyframe.
816
+ ================================================================ */
817
+
818
+ .lux-enter { animation: lux-rise-in var(--lux-dur-enter) var(--lux-ease-out) both }
819
+ .lux-exit { animation: lux-fade-out var(--lux-dur-exit) var(--lux-ease-in) both }
820
+
821
+ /* Floating surfaces — menu, popover, tooltip, dialog. They grow from
822
+ where they were summoned rather than sliding in from nowhere. */
823
+ .lux-pop { animation: lux-scale-in var(--lux-dur-enter) var(--lux-ease-out) both }
824
+ .lux-pop-exit { animation: lux-scale-out var(--lux-dur-exit) var(--lux-ease-in) both }
825
+
826
+ /* A list cascades by setting --lux-i on each row; one property, no JS. */
827
+ .lux-stagger { animation-delay: calc(var(--lux-i, 0) * 40ms) }
828
+
829
+ /* Hover / press feedback. `transform` and `opacity` are the only
830
+ transitioned properties, so this is safe on any element. */
831
+ .lux-interactive {
832
+ transition:
833
+ transform var(--lux-dur-hover) var(--lux-ease-out),
834
+ opacity var(--lux-dur-hover) var(--lux-ease-out);
835
+ }
836
+ .lux-interactive:hover { transform: translate3d(0, calc(-1 * var(--lux-lift)), 0) }
837
+ .lux-interactive:active { transform: scale(0.98); transition-duration: var(--lux-dur-press) }
838
+ .lux-interactive:disabled,
839
+ .lux-interactive[aria-disabled='true'] { transform: none }
840
+
841
+ /* ================================================================
842
+ 6c — The loading state. Two primitives, one job each.
843
+
844
+ `.lux-skeleton` RESERVES SPACE. `.lux-loading` SAYS WORK IS IN
845
+ FLIGHT. Keeping those apart is what makes the whole thing cheap,
846
+ because only the second one moves — once for the page, not once per
847
+ skeleton.
848
+
849
+ This is the part that is counter-intuitive, so here are the numbers
850
+ that settled it. Chromium, 390x844 @3x, 4x CPU throttle, 5s samples,
851
+ n = concurrently-mounted skeletons:
852
+
853
+ n=600 bgpos 79.6fps sheen 57.2fps pulse 115.4fps bar 120fps
854
+ n=1460 bgpos 33.6fps sheen 23.0fps pulse 39.0fps bar 120fps
855
+ n=3000 bgpos 13.8fps sheen 5.4fps pulse 18.2fps bar 120fps
856
+
857
+ bgpos = per-element background-position gradient (what we shipped)
858
+ sheen = per-element transform on a ::after (the "obvious" fix)
859
+ pulse = per-element opacity keyframe
860
+ bar = flat skeletons + ONE page-level sweep (what we ship now)
861
+
862
+ Read the middle column twice. Rewriting the shimmer as a per-element
863
+ `transform` — obeying "animate transform and opacity only" to the
864
+ letter — made it 32% SLOWER than the background-position version it
865
+ replaced, and at n=3000 it produced a single 2.5-SECOND frame.
866
+
867
+ The property rule is necessary and not sufficient. Past a few
868
+ hundred elements the dominant cost is the NUMBER of independently
869
+ animated elements: each one is a compositor layer to allocate,
870
+ rasterise and composite, and `overflow:hidden` on each adds a
871
+ render surface on top. Only the O(1) design is flat in n — 120fps
872
+ at 600, at 1460 and at 3000, identical to animating nothing.
873
+
874
+ So: a skeleton is a promise about layout and nothing else. It
875
+ occupies exactly the space the real content will (zero CLS) and it
876
+ holds still.
877
+ ================================================================ */
878
+ .lux-skeleton {
879
+ background: var(--color-skeleton-start);
880
+ /* Nothing inside a skeleton is readable yet; say so once, here. */
881
+ color: transparent;
882
+ user-select: none;
883
+ }
884
+
885
+ /* The one moving part. Fixed, 2px, above everything, out of flow — so it
886
+ cannot shift layout and cannot tint content. Mount it while any request
887
+ is in flight; unmount it when they land. One per page. */
888
+ .lux-loading {
889
+ position: fixed;
890
+ inset: 0 0 auto;
891
+ height: 2px;
892
+ z-index: 60;
893
+ overflow: hidden;
894
+ background: var(--color-skeleton-start);
895
+ pointer-events: none;
896
+ }
897
+
898
+ .lux-loading::after {
899
+ content: '';
900
+ position: absolute;
901
+ inset: 0;
902
+ width: 40%;
903
+ background: linear-gradient(
904
+ 90deg,
905
+ transparent 0%,
906
+ var(--color-skeleton-end) 50%,
907
+ transparent 100%
908
+ );
909
+ animation: lux-sweep 1.1s var(--lux-ease) infinite;
910
+ }
911
+
912
+ /* ================================================================
913
+ 6d — Reduced motion.
914
+
915
+ Belt AND braces, because either alone leaks:
916
+ - zeroing the TOKENS catches anything that reads them, including
917
+ inline styles and the Web Animations API, which !important
918
+ cannot reach;
919
+ - the global override catches every hand-rolled transition that
920
+ never adopted a token.
921
+ Unscoped on purpose. A reduced-motion rule that only applies inside
922
+ one component is a rule the rest of the app ignores.
923
+
924
+ Motion goes; FEEDBACK stays. State changes still land, instantly.
925
+ ================================================================ */
926
+ @media (prefers-reduced-motion: reduce) {
927
+ :root {
928
+ --lux-dur-press: 1ms;
929
+ --lux-dur-hover: 1ms;
930
+ --lux-dur-exit: 1ms;
931
+ --lux-dur-enter: 1ms;
932
+ --lux-dur-move: 1ms;
933
+ --lux-lift: 0px;
934
+ --lux-rise: 0px;
935
+ }
936
+
937
+ *,
938
+ *::before,
939
+ *::after {
940
+ animation-duration: 1ms !important;
941
+ animation-iteration-count: 1 !important;
942
+ transition-duration: 1ms !important;
943
+ scroll-behavior: auto !important;
944
+ }
945
+
946
+ /* A sweep that runs once and freezes mid-track is a smear. Drop the
947
+ motion; the bar itself stays, so "work in flight" is still visible. */
948
+ .lux-loading::after { animation: none; width: 100%; opacity: 0.5 }
438
949
  }