@nextlyhq/ui 0.0.2-alpha.6 → 0.0.2-alpha.62
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.
- package/README.md +128 -23
- package/dist/color.cjs +193 -0
- package/dist/color.cjs.map +1 -0
- package/dist/color.d.cts +251 -0
- package/dist/color.d.ts +251 -0
- package/dist/color.mjs +179 -0
- package/dist/color.mjs.map +1 -0
- package/dist/index.cjs +3402 -1089
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1475 -315
- package/dist/index.d.ts +1475 -315
- package/dist/index.mjs +3137 -833
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +2 -0
- package/dist/styles.scoped.css +2 -0
- package/dist/tailwind-preset.cjs +153 -0
- package/dist/tailwind-preset.cjs.map +1 -0
- package/dist/tailwind-preset.d.cts +120 -0
- package/dist/tailwind-preset.d.ts +120 -0
- package/dist/tailwind-preset.mjs +148 -0
- package/dist/tailwind-preset.mjs.map +1 -0
- package/dist/theme.css +1249 -0
- package/dist/utils.cjs +13 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +19 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.mjs +11 -0
- package/dist/utils.mjs.map +1 -0
- package/docs/plugin-ui-authoring.md +230 -0
- package/package.json +78 -17
package/dist/theme.css
ADDED
|
@@ -0,0 +1,1249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nextly design-system theme.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for tokens, the Tailwind `@theme inline` mappings, the
|
|
5
|
+
* dark variant, and the animation keyframes. It does NOT include a base reset —
|
|
6
|
+
* that lives in `index.css` and ships in the pre-compiled `styles.css` bundle.
|
|
7
|
+
* Consumed two ways:
|
|
8
|
+
* - Source: `@import "@nextlyhq/ui/theme.css"` after `@import "tailwindcss"`
|
|
9
|
+
* to compile against the token contract (admin, power-user plugins).
|
|
10
|
+
* - Pre-compiled: `@nextlyhq/ui/styles.css` bundles this behind a Tailwind
|
|
11
|
+
* build so plugins get styled components with zero setup.
|
|
12
|
+
*
|
|
13
|
+
* Selectors are unscoped (`:root` / `.dark`); a consumer that embeds the UI in a
|
|
14
|
+
* host page scopes them at build time (admin rewrites to `.nextly-admin`).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Activate `dark:` utilities from the `.dark` class rather than the OS
|
|
19
|
+
* `prefers-color-scheme`, so an explicit light choice wins over system dark.
|
|
20
|
+
*/
|
|
21
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
22
|
+
|
|
23
|
+
@theme inline {
|
|
24
|
+
/* ============================================
|
|
25
|
+
* ANIMATIONS (Tailwind v4.1)
|
|
26
|
+
* ============================================ */
|
|
27
|
+
--animate-accordion-down: accordion-down 0.3s cubic-bezier(0.87, 0, 0.13, 1);
|
|
28
|
+
--animate-accordion-up: accordion-up 0.3s cubic-bezier(0.87, 0, 0.13, 1);
|
|
29
|
+
--animate-fade-in: fade-in 0.2s ease-out;
|
|
30
|
+
--animate-fade-out: fade-out 0.2s ease-in;
|
|
31
|
+
--animate-zoom-in-95: zoom-in-95 0.1s ease-out;
|
|
32
|
+
--animate-zoom-out-95: zoom-out-95 0.1s ease-in;
|
|
33
|
+
--animate-slide-in-from-top-2: slide-in-from-top-2 0.2s ease-out;
|
|
34
|
+
--animate-slide-in-from-bottom-2: slide-in-from-bottom-2 0.2s ease-out;
|
|
35
|
+
--animate-slide-in-from-left-2: slide-in-from-left-2 0.2s ease-out;
|
|
36
|
+
--animate-slide-in-from-right-2: slide-in-from-right-2 0.2s ease-out;
|
|
37
|
+
--animate-brand-orbit: brand-orbit 2.4s linear infinite;
|
|
38
|
+
--animate-brand-pulse: brand-pulse 2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite;
|
|
39
|
+
|
|
40
|
+
/* ============================================
|
|
41
|
+
* COLOR PALETTE (Tailwind v4.1)
|
|
42
|
+
* Monochrome design system, WCAG 2.2 AA compliant.
|
|
43
|
+
*
|
|
44
|
+
* Color Format Strategy:
|
|
45
|
+
* - Reference scales (blue/slate/etc.): HEX, kept for utility classes.
|
|
46
|
+
* - Semantic tokens: OKLCH full-color values defined in `:root` / `.dark`,
|
|
47
|
+
* mapped here via `@theme inline` with bare `var()` so scoped/dark
|
|
48
|
+
* overrides resolve at the consuming element.
|
|
49
|
+
* ============================================ */
|
|
50
|
+
|
|
51
|
+
/* Blue Scale - Primary Brand Color */
|
|
52
|
+
--color-blue-50: #eff6ff;
|
|
53
|
+
--color-blue-100: #dbeafe;
|
|
54
|
+
--color-blue-200: #bfdbfe;
|
|
55
|
+
--color-blue-300: #93c5fd;
|
|
56
|
+
--color-blue-400: #60a5fa;
|
|
57
|
+
--color-blue-500: #3b82f6;
|
|
58
|
+
--color-blue-600: #2563eb;
|
|
59
|
+
--color-blue-700: #1d4ed8;
|
|
60
|
+
--color-blue-800: #1e40af;
|
|
61
|
+
--color-blue-900: #1e3a8a;
|
|
62
|
+
--color-blue-950: #172554;
|
|
63
|
+
|
|
64
|
+
/* Cyan Scale - Accent Color */
|
|
65
|
+
--color-cyan-50: #ecfeff;
|
|
66
|
+
--color-cyan-100: #cffafe;
|
|
67
|
+
--color-cyan-200: #a5f3fc;
|
|
68
|
+
--color-cyan-300: #67e8f9;
|
|
69
|
+
--color-cyan-400: #22d3ee;
|
|
70
|
+
--color-cyan-500: #06b6d4;
|
|
71
|
+
--color-cyan-600: #0891b2;
|
|
72
|
+
--color-cyan-700: #0e7490;
|
|
73
|
+
--color-cyan-800: #155e75;
|
|
74
|
+
--color-cyan-900: #164e63;
|
|
75
|
+
--color-cyan-950: #083344;
|
|
76
|
+
|
|
77
|
+
/* Green Scale - Success Color */
|
|
78
|
+
--color-green-50: #f0fdf4;
|
|
79
|
+
--color-green-100: #dcfce7;
|
|
80
|
+
--color-green-200: #bbf7d0;
|
|
81
|
+
--color-green-300: #86efac;
|
|
82
|
+
--color-green-400: #4ade80;
|
|
83
|
+
--color-green-500: #22c55e;
|
|
84
|
+
--color-green-600: #16a34a;
|
|
85
|
+
--color-green-700: #15803d;
|
|
86
|
+
--color-green-800: #166534;
|
|
87
|
+
--color-green-900: #14532d;
|
|
88
|
+
--color-green-950: #052e16;
|
|
89
|
+
|
|
90
|
+
/* Amber Scale - Warning Color */
|
|
91
|
+
--color-amber-50: #fffbeb;
|
|
92
|
+
--color-amber-100: #fef3c7;
|
|
93
|
+
--color-amber-200: #fde68a;
|
|
94
|
+
--color-amber-300: #fcd34d;
|
|
95
|
+
--color-amber-400: #fbbf24;
|
|
96
|
+
--color-amber-500: #f59e0b;
|
|
97
|
+
--color-amber-600: #d97706;
|
|
98
|
+
--color-amber-700: #b45309;
|
|
99
|
+
--color-amber-800: #92400e;
|
|
100
|
+
--color-amber-900: #78350f;
|
|
101
|
+
--color-amber-950: #451a03;
|
|
102
|
+
|
|
103
|
+
/* Red Scale - Error/Destructive Color */
|
|
104
|
+
--color-red-50: #fef2f2;
|
|
105
|
+
--color-red-100: #fee2e2;
|
|
106
|
+
--color-red-200: #fecaca;
|
|
107
|
+
--color-red-300: #fca5a5;
|
|
108
|
+
--color-red-400: #f87171;
|
|
109
|
+
--color-red-500: #ef4444;
|
|
110
|
+
--color-red-600: #dc2626;
|
|
111
|
+
--color-red-700: #b91c1c;
|
|
112
|
+
--color-red-800: #991b1b;
|
|
113
|
+
--color-red-900: #7f1d1d;
|
|
114
|
+
--color-red-950: #450a0a;
|
|
115
|
+
|
|
116
|
+
/* Slate Scale - Neutral Colors */
|
|
117
|
+
--color-slate-50: #f8fafc;
|
|
118
|
+
--color-slate-100: #f1f5f9;
|
|
119
|
+
--color-slate-200: #e2e8f0;
|
|
120
|
+
--color-slate-300: #cbd5e1;
|
|
121
|
+
--color-slate-400: #94a3b8;
|
|
122
|
+
--color-slate-500: #64748b;
|
|
123
|
+
--color-slate-600: #475569;
|
|
124
|
+
--color-slate-700: #334155;
|
|
125
|
+
--color-slate-800: #1e293b;
|
|
126
|
+
--color-slate-900: #0f172a;
|
|
127
|
+
--color-slate-950: #020617;
|
|
128
|
+
|
|
129
|
+
/* ============================================
|
|
130
|
+
* SEMANTIC COLOR TOKENS (Tailwind v4.1)
|
|
131
|
+
* Reference color scales defined above
|
|
132
|
+
* ============================================ */
|
|
133
|
+
|
|
134
|
+
/* shadcn/ui semantic colors - reference CSS variables defined below */
|
|
135
|
+
--color-overlay: var(--nx-overlay);
|
|
136
|
+
--color-overlay-soft: var(--nx-overlay-soft);
|
|
137
|
+
--color-overlay-strong: var(--nx-overlay-strong);
|
|
138
|
+
--color-border: var(--nx-border);
|
|
139
|
+
--color-border-subtle: var(--nx-border-subtle);
|
|
140
|
+
--color-border-strong: var(--nx-border-strong);
|
|
141
|
+
--color-input: var(--nx-input);
|
|
142
|
+
--color-control-border: var(--nx-control-border);
|
|
143
|
+
--color-ring: var(--nx-ring);
|
|
144
|
+
--color-background: var(--nx-background);
|
|
145
|
+
--color-foreground: var(--nx-foreground);
|
|
146
|
+
|
|
147
|
+
--color-primary: var(--nx-primary);
|
|
148
|
+
--color-primary-50: color-mix(in srgb, var(--nx-primary), white 95%);
|
|
149
|
+
--color-primary-100: color-mix(in srgb, var(--nx-primary), white 90%);
|
|
150
|
+
--color-primary-200: color-mix(in srgb, var(--nx-primary), white 70%);
|
|
151
|
+
--color-primary-300: color-mix(in srgb, var(--nx-primary), white 50%);
|
|
152
|
+
--color-primary-400: color-mix(in srgb, var(--nx-primary), white 30%);
|
|
153
|
+
--color-primary-500: var(--nx-primary);
|
|
154
|
+
--color-primary-600: color-mix(in srgb, var(--nx-primary), black 10%);
|
|
155
|
+
--color-primary-700: color-mix(in srgb, var(--nx-primary), black 30%);
|
|
156
|
+
--color-primary-800: color-mix(in srgb, var(--nx-primary), black 50%);
|
|
157
|
+
--color-primary-900: color-mix(in srgb, var(--nx-primary), black 70%);
|
|
158
|
+
--color-primary-950: color-mix(in srgb, var(--nx-primary), black 85%);
|
|
159
|
+
--color-primary-foreground: var(--nx-primary-foreground);
|
|
160
|
+
|
|
161
|
+
--color-secondary: var(--nx-secondary);
|
|
162
|
+
--color-secondary-foreground: var(--nx-secondary-foreground);
|
|
163
|
+
|
|
164
|
+
/* Status scales, derived the same way as the primary scale above.
|
|
165
|
+
*
|
|
166
|
+
* A status role is one token; the scale is mixed from it, so retheming
|
|
167
|
+
* `--nx-success` moves every shade with it and they cannot drift apart. The
|
|
168
|
+
* shades exist because a status needs more than its solid colour: a tinted
|
|
169
|
+
* surface, a border on that surface, and text dark enough to read on it.
|
|
170
|
+
* Mixing toward white and black keeps 50 pale and 900 deep in both modes,
|
|
171
|
+
* which is what a `dark:` pair at the call site already assumes. */
|
|
172
|
+
--color-destructive: var(--nx-destructive);
|
|
173
|
+
--color-destructive-50: color-mix(in srgb, var(--nx-destructive), white 95%);
|
|
174
|
+
--color-destructive-100: color-mix(in srgb, var(--nx-destructive), white 90%);
|
|
175
|
+
--color-destructive-200: color-mix(in srgb, var(--nx-destructive), white 70%);
|
|
176
|
+
--color-destructive-300: color-mix(in srgb, var(--nx-destructive), white 50%);
|
|
177
|
+
--color-destructive-400: color-mix(in srgb, var(--nx-destructive), white 30%);
|
|
178
|
+
--color-destructive-500: var(--nx-destructive);
|
|
179
|
+
--color-destructive-600: color-mix(in srgb, var(--nx-destructive), black 10%);
|
|
180
|
+
--color-destructive-700: color-mix(in srgb, var(--nx-destructive), black 30%);
|
|
181
|
+
--color-destructive-800: color-mix(in srgb, var(--nx-destructive), black 50%);
|
|
182
|
+
--color-destructive-900: color-mix(in srgb, var(--nx-destructive), black 70%);
|
|
183
|
+
--color-destructive-950: color-mix(in srgb, var(--nx-destructive), black 85%);
|
|
184
|
+
/* Saturated fill for solid buttons, kept separate from the text-tuned base
|
|
185
|
+
* so each can be solved for its own job. The ink that lands on it is
|
|
186
|
+
* `-foreground`, which is white in light mode and black in dark. */
|
|
187
|
+
--color-destructive-solid: var(--nx-destructive-solid);
|
|
188
|
+
--color-destructive-foreground: var(--nx-destructive-foreground);
|
|
189
|
+
|
|
190
|
+
--color-success: var(--nx-success);
|
|
191
|
+
--color-success-50: color-mix(in srgb, var(--nx-success), white 95%);
|
|
192
|
+
--color-success-100: color-mix(in srgb, var(--nx-success), white 90%);
|
|
193
|
+
--color-success-200: color-mix(in srgb, var(--nx-success), white 70%);
|
|
194
|
+
--color-success-300: color-mix(in srgb, var(--nx-success), white 50%);
|
|
195
|
+
--color-success-400: color-mix(in srgb, var(--nx-success), white 30%);
|
|
196
|
+
--color-success-500: var(--nx-success);
|
|
197
|
+
--color-success-600: color-mix(in srgb, var(--nx-success), black 10%);
|
|
198
|
+
--color-success-700: color-mix(in srgb, var(--nx-success), black 30%);
|
|
199
|
+
--color-success-800: color-mix(in srgb, var(--nx-success), black 50%);
|
|
200
|
+
--color-success-900: color-mix(in srgb, var(--nx-success), black 70%);
|
|
201
|
+
--color-success-950: color-mix(in srgb, var(--nx-success), black 85%);
|
|
202
|
+
/* Saturated fill for solid usage; distinct from the text-tuned base. */
|
|
203
|
+
--color-success-solid: var(--nx-success-solid);
|
|
204
|
+
--color-success-foreground: var(--nx-success-foreground);
|
|
205
|
+
|
|
206
|
+
--color-warning: var(--nx-warning);
|
|
207
|
+
--color-warning-50: color-mix(in srgb, var(--nx-warning), white 95%);
|
|
208
|
+
--color-warning-100: color-mix(in srgb, var(--nx-warning), white 90%);
|
|
209
|
+
--color-warning-200: color-mix(in srgb, var(--nx-warning), white 70%);
|
|
210
|
+
--color-warning-300: color-mix(in srgb, var(--nx-warning), white 50%);
|
|
211
|
+
--color-warning-400: color-mix(in srgb, var(--nx-warning), white 30%);
|
|
212
|
+
--color-warning-500: var(--nx-warning);
|
|
213
|
+
--color-warning-600: color-mix(in srgb, var(--nx-warning), black 10%);
|
|
214
|
+
--color-warning-700: color-mix(in srgb, var(--nx-warning), black 30%);
|
|
215
|
+
--color-warning-800: color-mix(in srgb, var(--nx-warning), black 50%);
|
|
216
|
+
--color-warning-900: color-mix(in srgb, var(--nx-warning), black 70%);
|
|
217
|
+
--color-warning-950: color-mix(in srgb, var(--nx-warning), black 85%);
|
|
218
|
+
--color-warning-foreground: var(--nx-warning-foreground);
|
|
219
|
+
|
|
220
|
+
/* The editor's highlight mark. A token rather than a literal because it
|
|
221
|
+
* styles authored content, which an integrator restyles to their own site
|
|
222
|
+
* and which has to stay readable on a dark page. */
|
|
223
|
+
--color-highlight: var(--nx-highlight);
|
|
224
|
+
--color-highlight-foreground: var(--nx-highlight-foreground);
|
|
225
|
+
|
|
226
|
+
/* Syntax colors, so code tokens are styled the same way as everything else
|
|
227
|
+
* (text-code-keyword, bg-code-bg) instead of through bespoke CSS. */
|
|
228
|
+
--color-code-bg: var(--nx-code-bg);
|
|
229
|
+
--color-code-fg: var(--nx-code-fg);
|
|
230
|
+
--color-code-comment: var(--nx-code-comment);
|
|
231
|
+
--color-code-keyword: var(--nx-code-keyword);
|
|
232
|
+
--color-code-string: var(--nx-code-string);
|
|
233
|
+
--color-code-number: var(--nx-code-number);
|
|
234
|
+
--color-code-function: var(--nx-code-function);
|
|
235
|
+
--color-code-operator: var(--nx-code-operator);
|
|
236
|
+
--color-code-punctuation: var(--nx-code-punctuation);
|
|
237
|
+
--color-code-variable: var(--nx-code-variable);
|
|
238
|
+
--color-code-tag: var(--nx-code-tag);
|
|
239
|
+
--color-code-deleted: var(--nx-code-deleted);
|
|
240
|
+
--color-code-inserted: var(--nx-code-inserted);
|
|
241
|
+
|
|
242
|
+
--color-muted: var(--nx-muted);
|
|
243
|
+
--color-muted-foreground: var(--nx-muted-foreground);
|
|
244
|
+
|
|
245
|
+
--color-accent: var(--nx-accent);
|
|
246
|
+
--color-accent-50: color-mix(in srgb, var(--nx-accent), white 95%);
|
|
247
|
+
--color-accent-100: color-mix(in srgb, var(--nx-accent), white 90%);
|
|
248
|
+
--color-accent-200: color-mix(in srgb, var(--nx-accent), white 70%);
|
|
249
|
+
--color-accent-300: color-mix(in srgb, var(--nx-accent), white 50%);
|
|
250
|
+
--color-accent-400: color-mix(in srgb, var(--nx-accent), white 30%);
|
|
251
|
+
--color-accent-500: var(--nx-accent);
|
|
252
|
+
--color-accent-600: color-mix(in srgb, var(--nx-accent), black 10%);
|
|
253
|
+
--color-accent-700: color-mix(in srgb, var(--nx-accent), black 30%);
|
|
254
|
+
--color-accent-800: color-mix(in srgb, var(--nx-accent), black 50%);
|
|
255
|
+
--color-accent-900: color-mix(in srgb, var(--nx-accent), black 70%);
|
|
256
|
+
--color-accent-950: color-mix(in srgb, var(--nx-accent), black 85%);
|
|
257
|
+
--color-accent-foreground: var(--nx-accent-foreground);
|
|
258
|
+
|
|
259
|
+
--color-card: var(--nx-card);
|
|
260
|
+
--color-card-foreground: var(--nx-card-foreground);
|
|
261
|
+
|
|
262
|
+
--color-popover: var(--nx-popover);
|
|
263
|
+
--color-popover-foreground: var(--nx-popover-foreground);
|
|
264
|
+
|
|
265
|
+
/* Sidebar semantic tokens */
|
|
266
|
+
--color-sidebar: var(--nx-sidebar-background);
|
|
267
|
+
--color-sidebar-background: var(--nx-sidebar-background);
|
|
268
|
+
--color-sidebar-foreground: var(--nx-sidebar-foreground);
|
|
269
|
+
--color-sidebar-primary: var(--nx-sidebar-primary);
|
|
270
|
+
--color-sidebar-primary-foreground: var(--nx-sidebar-primary-foreground);
|
|
271
|
+
--color-sidebar-accent: var(--nx-sidebar-accent);
|
|
272
|
+
--color-sidebar-accent-background: var(--nx-sidebar-accent);
|
|
273
|
+
--color-sidebar-accent-foreground: var(--nx-sidebar-accent-foreground);
|
|
274
|
+
--color-sidebar-border: var(--nx-sidebar-border);
|
|
275
|
+
--color-sidebar-ring: var(--nx-sidebar-ring);
|
|
276
|
+
|
|
277
|
+
/* ============================================
|
|
278
|
+
* TYPOGRAPHY (Tailwind v4.1)
|
|
279
|
+
* Perfect Fourth Scale (1.333 ratio) - WCAG 2.2 AA Compliant
|
|
280
|
+
* ============================================ */
|
|
281
|
+
|
|
282
|
+
/* Font Families live in the non-inline @theme block below. */
|
|
283
|
+
|
|
284
|
+
/* Type Scale - Base Size: 16px (1rem) */
|
|
285
|
+
--text-xs: 0.75rem;
|
|
286
|
+
/* 12px */
|
|
287
|
+
--text-sm: 0.875rem;
|
|
288
|
+
/* 14px */
|
|
289
|
+
--text-base: 1rem;
|
|
290
|
+
/* 16px - body text (WCAG minimum) */
|
|
291
|
+
--text-lg: 1.125rem;
|
|
292
|
+
/* 18px */
|
|
293
|
+
--text-xl: 1.25rem;
|
|
294
|
+
/* 20px */
|
|
295
|
+
--text-2xl: 1.5rem;
|
|
296
|
+
/* 24px */
|
|
297
|
+
--text-3xl: 1.875rem;
|
|
298
|
+
/* 30px */
|
|
299
|
+
--text-4xl: 2.25rem;
|
|
300
|
+
/* 36px */
|
|
301
|
+
--text-5xl: 3rem;
|
|
302
|
+
/* 48px */
|
|
303
|
+
--text-6xl: 3.75rem;
|
|
304
|
+
/* 60px */
|
|
305
|
+
|
|
306
|
+
/* Line Heights (Leading) */
|
|
307
|
+
--leading-none: 1;
|
|
308
|
+
/* Large display text */
|
|
309
|
+
--leading-tight: 1.25;
|
|
310
|
+
/* Headings */
|
|
311
|
+
--leading-snug: 1.375;
|
|
312
|
+
/* Tight UI elements */
|
|
313
|
+
--leading-normal: 1.5;
|
|
314
|
+
/* Default for UI text */
|
|
315
|
+
--leading-relaxed: 1.625;
|
|
316
|
+
/* Long-form content */
|
|
317
|
+
--leading-loose: 2;
|
|
318
|
+
/* Very spacious text */
|
|
319
|
+
|
|
320
|
+
/* Letter Spacing (Tracking) */
|
|
321
|
+
--tracking-tighter: -0.05em;
|
|
322
|
+
/* -0.8px at 16px */
|
|
323
|
+
--tracking-tight: -0.025em;
|
|
324
|
+
/* -0.4px at 16px - for large headings */
|
|
325
|
+
--tracking-normal: 0em;
|
|
326
|
+
/* Default */
|
|
327
|
+
--tracking-wide: 0.025em;
|
|
328
|
+
/* +0.4px at 16px - for small text */
|
|
329
|
+
--tracking-wider: 0.05em;
|
|
330
|
+
/* +0.8px at 16px - for uppercase */
|
|
331
|
+
--tracking-widest: 0.1em;
|
|
332
|
+
/* +1.6px at 16px */
|
|
333
|
+
|
|
334
|
+
/* ============================================
|
|
335
|
+
* SPACING & LAYOUT (Tailwind v4.1)
|
|
336
|
+
* 8px Grid System - Industry Standard
|
|
337
|
+
* ============================================ */
|
|
338
|
+
|
|
339
|
+
/* Spacing Scale (8px base unit) */
|
|
340
|
+
--space-0: 0rem;
|
|
341
|
+
/* 0px */
|
|
342
|
+
--space-px: 0.0625rem;
|
|
343
|
+
/* 1px - for fine borders */
|
|
344
|
+
--space-0-5: 0.125rem;
|
|
345
|
+
/* 2px */
|
|
346
|
+
--space-1: 0.25rem;
|
|
347
|
+
/* 4px - icon padding, tiny gaps */
|
|
348
|
+
--space-1-5: 0.375rem;
|
|
349
|
+
/* 6px */
|
|
350
|
+
--space-2: 0.5rem;
|
|
351
|
+
/* 8px - tight spacing, inline elements */
|
|
352
|
+
--space-2-5: 0.625rem;
|
|
353
|
+
/* 10px */
|
|
354
|
+
--space-3: 0.75rem;
|
|
355
|
+
/* 12px - between related elements */
|
|
356
|
+
--space-3-5: 0.875rem;
|
|
357
|
+
/* 14px */
|
|
358
|
+
--space-4: 1rem;
|
|
359
|
+
/* 16px - standard element spacing */
|
|
360
|
+
--space-5: 1.25rem;
|
|
361
|
+
/* 20px */
|
|
362
|
+
--space-6: 1.5rem;
|
|
363
|
+
/* 24px - between form fields */
|
|
364
|
+
--space-7: 1.75rem;
|
|
365
|
+
/* 28px */
|
|
366
|
+
--space-8: 2rem;
|
|
367
|
+
/* 32px - between sections */
|
|
368
|
+
--space-9: 2.25rem;
|
|
369
|
+
/* 36px */
|
|
370
|
+
--space-10: 2.5rem;
|
|
371
|
+
/* 40px */
|
|
372
|
+
--space-11: 2.75rem;
|
|
373
|
+
/* 44px - minimum touch target */
|
|
374
|
+
--space-12: 3rem;
|
|
375
|
+
/* 48px - between major sections */
|
|
376
|
+
--space-14: 3.5rem;
|
|
377
|
+
/* 56px */
|
|
378
|
+
--space-16: 4rem;
|
|
379
|
+
/* 64px - page margins */
|
|
380
|
+
--space-20: 5rem;
|
|
381
|
+
/* 80px */
|
|
382
|
+
--space-24: 6rem;
|
|
383
|
+
/* 96px - large section breaks */
|
|
384
|
+
|
|
385
|
+
/* ============================================
|
|
386
|
+
* BORDER RADIUS (Tailwind v4.1)
|
|
387
|
+
*
|
|
388
|
+
* The contract is three steps — sm, md, lg — all derived from the single
|
|
389
|
+
* --radius knob (shadcn pattern), so the whole admin re-rounds from one
|
|
390
|
+
* declaration. Those three are also exactly what `tailwind-preset.ts` exports,
|
|
391
|
+
* so a plugin built against the published preset gets the same scale.
|
|
392
|
+
*
|
|
393
|
+
* That only holds if components pick a TIER BY ELEMENT CATEGORY instead of
|
|
394
|
+
* naming a length. A component that hardcodes a corner value — `rounded-none`
|
|
395
|
+
* as much as `rounded-[6px]` — pins itself at that value and silently opts
|
|
396
|
+
* out of the knob. The tiers, and what the shipped components put in each:
|
|
397
|
+
*
|
|
398
|
+
* rounded-lg (--radius-lg, the knob itself)
|
|
399
|
+
* Floating and grouping containers: cards, dialogs, alert dialogs,
|
|
400
|
+
* popovers, tooltips, dropdown / select / command panels, empty-state
|
|
401
|
+
* panels.
|
|
402
|
+
*
|
|
403
|
+
* rounded-md (--radius-md, one step tighter)
|
|
404
|
+
* Controls and the inline surfaces that sit among them: buttons, inputs,
|
|
405
|
+
* textareas, select triggers, icon buttons, alerts, table wrappers and
|
|
406
|
+
* image frames. A control nested in a container reads as inset when its
|
|
407
|
+
* corner is tighter than the surface holding it.
|
|
408
|
+
*
|
|
409
|
+
* rounded-sm (--radius-sm, tighter again)
|
|
410
|
+
* Small controls and adornments: menu and list items, badges, chips,
|
|
411
|
+
* tags, checkboxes, toolbar affordances — anything already inside a
|
|
412
|
+
* control or a dense row.
|
|
413
|
+
*
|
|
414
|
+
* Circles sit deliberately outside the scale: avatars, status dots, spinners,
|
|
415
|
+
* progress tracks and switch tracks and thumbs use `rounded-full`, which is
|
|
416
|
+
* 9999px and not a --radius step. They are round because of their shape, not
|
|
417
|
+
* because of the theme, so a retheme must not flatten them.
|
|
418
|
+
*
|
|
419
|
+
* rounded-xl / rounded-2xl are NOT tiers of this knob. `tailwind-preset.ts`
|
|
420
|
+
* exports only lg/md/sm, so a plugin using the preset gets Tailwind's fixed
|
|
421
|
+
* 0.75rem / 1rem for them, detached from --radius; and the definitions below
|
|
422
|
+
* ADD to the knob rather than subtracting, so they always sit rounder than
|
|
423
|
+
* the container tier and drift further as the knob grows. Nothing in the repo
|
|
424
|
+
* uses them. Treat them as off-contract and pick from sm/md/lg.
|
|
425
|
+
*
|
|
426
|
+
* At the shipped 0.5rem the three tiers are 4px, 6px and 8px. All positive,
|
|
427
|
+
* which matters: `sm` and `md` SUBTRACT from the knob, so a knob under 4px
|
|
428
|
+
* makes them negative. They still render square, because border-radius clamps
|
|
429
|
+
* a negative length to 0 at used-value time — but the token keeps its
|
|
430
|
+
* negative value, so reading `--radius-sm` for a padding, an inset, or
|
|
431
|
+
* through getComputedStyle yields -4px rather than 0. Anything consuming a
|
|
432
|
+
* step as a LENGTH rather than as a corner must clamp it itself, and that
|
|
433
|
+
* obligation returns the moment someone dials the knob back down.
|
|
434
|
+
*
|
|
435
|
+
* A site that genuinely must stay square keeps `rounded-none` WITH an inline
|
|
436
|
+
* comment saying why — overlapped-border strips, full-bleed fills inside an
|
|
437
|
+
* already-rounded parent, underline tabs (which is why Tabs is square, not a
|
|
438
|
+
* `rounded-sm` adornment). Square with a stated reason is a decision; square
|
|
439
|
+
* without one is an element that was never wired to the knob. Sheet is the
|
|
440
|
+
* standing example of the latter: it is anchored to the viewport edge and
|
|
441
|
+
* carries no radius class at all.
|
|
442
|
+
*
|
|
443
|
+
* Corollary for containers: a rounded box whose child paints a background to
|
|
444
|
+
* its edge must either clip that child (`overflow-hidden`) or give the child
|
|
445
|
+
* a matching corner (`rounded-b-[inherit]` and friends). Otherwise the child's
|
|
446
|
+
* fill paints square across the parent's curve at any nonzero --radius.
|
|
447
|
+
* ============================================ */
|
|
448
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
449
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
450
|
+
--radius-lg: var(--radius);
|
|
451
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
452
|
+
--radius-2xl: calc(var(--radius) + 8px);
|
|
453
|
+
|
|
454
|
+
/* ============================================
|
|
455
|
+
* BOX SHADOWS (Tailwind v4.1)
|
|
456
|
+
* The ramp's colour comes from --nx-shadow-color, mixed down to each step's
|
|
457
|
+
* alpha. `@theme inline` copies a declaration's value into the generated
|
|
458
|
+
* utility, so a literal rgba() would be frozen at build time while this
|
|
459
|
+
* var() stays live and re-resolves per mode / per scope at the element.
|
|
460
|
+
* ============================================ */
|
|
461
|
+
--shadow-sm: 0 1px 2px 0
|
|
462
|
+
color-mix(in srgb, var(--nx-shadow-color) 5%, transparent);
|
|
463
|
+
--shadow-md:
|
|
464
|
+
0 4px 6px -1px color-mix(in srgb, var(--nx-shadow-color) 10%, transparent),
|
|
465
|
+
0 2px 4px -1px color-mix(in srgb, var(--nx-shadow-color) 6%, transparent);
|
|
466
|
+
--shadow-lg:
|
|
467
|
+
0 10px 15px -3px color-mix(in srgb, var(--nx-shadow-color) 10%, transparent),
|
|
468
|
+
0 4px 6px -2px color-mix(in srgb, var(--nx-shadow-color) 5%, transparent);
|
|
469
|
+
--shadow-xl:
|
|
470
|
+
0 20px 25px -5px color-mix(in srgb, var(--nx-shadow-color) 10%, transparent),
|
|
471
|
+
0 10px 10px -5px color-mix(in srgb, var(--nx-shadow-color) 4%, transparent);
|
|
472
|
+
|
|
473
|
+
/* Custom Functional Shadows (Fixes build warnings from arbitrary values) */
|
|
474
|
+
--shadow-inner-subtle: 0 -4px 6px -1px
|
|
475
|
+
color-mix(in srgb, var(--nx-shadow-color) 5%, transparent);
|
|
476
|
+
--shadow-glow-success: 0 0 10px
|
|
477
|
+
color-mix(in srgb, var(--nx-success) 10%, transparent);
|
|
478
|
+
--shadow-glow-primary: 0 0 15px
|
|
479
|
+
color-mix(in srgb, var(--nx-primary) 50%, transparent);
|
|
480
|
+
--shadow-elevation-primary: 0 8px 20px -8px
|
|
481
|
+
color-mix(in srgb, var(--nx-primary) 40%, transparent);
|
|
482
|
+
--shadow-soft-primary: 0 8px 30px
|
|
483
|
+
color-mix(in srgb, var(--nx-primary) 6%, transparent);
|
|
484
|
+
--shadow-neo: 4px 4px 0px 0px
|
|
485
|
+
color-mix(in srgb, var(--nx-shadow-color) 10%, transparent);
|
|
486
|
+
|
|
487
|
+
/* ============================================
|
|
488
|
+
* FLUID TYPOGRAPHY (Tailwind v4.1)
|
|
489
|
+
* Fixes build warnings from arbitrary values
|
|
490
|
+
* ============================================ */
|
|
491
|
+
--text-fluid-2xs: clamp(0.6rem, 0.55rem + 0.1vw, 0.65rem);
|
|
492
|
+
--text-fluid-xs: clamp(0.65rem, 0.6rem + 0.1vw, 0.7rem);
|
|
493
|
+
--text-fluid-sm: clamp(0.7rem, 0.65rem + 0.1vw, 0.75rem);
|
|
494
|
+
--text-fluid-base: clamp(0.875rem, 0.8rem + 0.2vw, 1rem);
|
|
495
|
+
--text-fluid-lg: clamp(1rem, 2vw, 1.125rem);
|
|
496
|
+
--text-fluid-xl: clamp(1.25rem, 3vw, 1.5rem);
|
|
497
|
+
--text-fluid-2xl: clamp(1.5rem, 5vw, 2.25rem);
|
|
498
|
+
--text-fluid-3xl: clamp(1.5rem, 5vw, 2.5rem);
|
|
499
|
+
--text-fluid-4xl: clamp(1.75rem, 5vw, 2.5rem);
|
|
500
|
+
|
|
501
|
+
/* Custom Transitions */
|
|
502
|
+
--ease-premium: cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/* ============================================
|
|
506
|
+
* FONT FAMILIES & WEIGHTS (Tailwind v4.1)
|
|
507
|
+
*
|
|
508
|
+
* Deliberately a non-inline `@theme`. `@theme inline` copies a declaration's
|
|
509
|
+
* value into whatever consumes it, which froze these stacks into
|
|
510
|
+
* `--default-font-family` / `--default-mono-font-family` and into the
|
|
511
|
+
* `font-mono` utility at build time, so the variables existed but nothing read
|
|
512
|
+
* them and re-declaring one on a scope changed nothing. A plain `@theme`
|
|
513
|
+
* emits each token as a real custom property and makes its consumers
|
|
514
|
+
* reference it with `var()`, so the base rule and the `font-*` utilities
|
|
515
|
+
* re-resolve wherever the token is redeclared. The weights sit here for the
|
|
516
|
+
* same reason, and to match the weights Tailwind ships (`--font-weight-black`
|
|
517
|
+
* and friends), which the `font-*` utilities also reach through `var()`.
|
|
518
|
+
*
|
|
519
|
+
* `--font-*` is the font-FAMILY namespace and `--font-weight-*` is the weight
|
|
520
|
+
* one, so a weight declared as `--font-bold` registers a family literally named
|
|
521
|
+
* "bold": `font-bold` then compiles to `font-family: 700`, which browsers drop,
|
|
522
|
+
* and it shadows the real weight utility rather than adding to it.
|
|
523
|
+
* ============================================ */
|
|
524
|
+
@theme {
|
|
525
|
+
/* The host app supplies the face through the variable; the family name is the
|
|
526
|
+
* var()'s own FALLBACK ARGUMENT, covering a host that installed the font
|
|
527
|
+
* without `next/font`, and the generic stack after it covers a host that did
|
|
528
|
+
* neither. Naming only the family would resolve to nothing under `next/font`,
|
|
529
|
+
* which self-hosts the face and exposes it ONLY as a variable -- a bare
|
|
530
|
+
* "Geist" there silently falls through to the system sans.
|
|
531
|
+
*
|
|
532
|
+
* The fallback has to sit INSIDE the parentheses. A family listed after the
|
|
533
|
+
* closing `)` is not reached when the variable is undefined: an unresolved
|
|
534
|
+
* `var()` with no fallback makes the whole declaration invalid at
|
|
535
|
+
* computed-value time, which for an inherited property like font-family means
|
|
536
|
+
* the element inherits its parent's face and the entire stack is discarded.
|
|
537
|
+
* The comma list looks like a fallback chain and is not one, so a host that
|
|
538
|
+
* skipped the variable rendered in whatever the page already used rather than
|
|
539
|
+
* in the generic sans this stack names.
|
|
540
|
+
*
|
|
541
|
+
* These are declared in a non-inline `@theme`, so they are emitted into
|
|
542
|
+
* `:root` and substitution happens THERE, not on the consuming element. A
|
|
543
|
+
* host must expose the variable at or above that scope -- `next/font`'s
|
|
544
|
+
* generated class belongs on `<html>`. On `<body>` the variable is invisible
|
|
545
|
+
* to this declaration no matter how it is spelled. */
|
|
546
|
+
--font-sans:
|
|
547
|
+
var(--font-geist, Geist), ui-sans-serif, system-ui, -apple-system,
|
|
548
|
+
"Segoe UI", sans-serif;
|
|
549
|
+
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
|
550
|
+
--font-mono:
|
|
551
|
+
var(--font-geist-mono, "Geist Mono"), ui-monospace, SFMono-Regular, Menlo,
|
|
552
|
+
Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
553
|
+
|
|
554
|
+
/* Body text, captions */
|
|
555
|
+
--font-weight-normal: 400;
|
|
556
|
+
/* Subheadings, emphasis */
|
|
557
|
+
--font-weight-medium: 500;
|
|
558
|
+
/* Headings, buttons */
|
|
559
|
+
--font-weight-semibold: 600;
|
|
560
|
+
/* Strong emphasis (rarely used) */
|
|
561
|
+
--font-weight-bold: 700;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/* ============================================
|
|
565
|
+
* RESPONSIVE TYPOGRAPHY (Desktop)
|
|
566
|
+
* Scale up larger text sizes on desktop screens
|
|
567
|
+
* Must be outside @theme block due to Tailwind v4.1 restrictions
|
|
568
|
+
* ============================================ */
|
|
569
|
+
@media (min-width: 768px) {
|
|
570
|
+
@theme {
|
|
571
|
+
--text-2xl: 1.75rem;
|
|
572
|
+
/* 28px on desktop (from 24px) */
|
|
573
|
+
--text-3xl: 2.25rem;
|
|
574
|
+
/* 36px on desktop (from 30px) */
|
|
575
|
+
--text-4xl: 3rem;
|
|
576
|
+
/* 48px on desktop (from 36px) */
|
|
577
|
+
--text-5xl: 4rem;
|
|
578
|
+
/* 64px on desktop (from 48px) */
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/* Animation keyframes referenced by the --animate-* tokens above. */
|
|
583
|
+
/* Keyframes for custom animations */
|
|
584
|
+
@keyframes brand-orbit {
|
|
585
|
+
from {
|
|
586
|
+
transform: rotate(0deg);
|
|
587
|
+
}
|
|
588
|
+
to {
|
|
589
|
+
transform: rotate(360deg);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
@keyframes brand-pulse {
|
|
594
|
+
0%,
|
|
595
|
+
100% {
|
|
596
|
+
transform: scale(0.97);
|
|
597
|
+
filter: drop-shadow(
|
|
598
|
+
0 0 10px color-mix(in srgb, var(--nx-primary) 8%, transparent)
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
50% {
|
|
602
|
+
transform: scale(1.03);
|
|
603
|
+
filter: drop-shadow(
|
|
604
|
+
0 0 20px color-mix(in srgb, var(--nx-primary) 22%, transparent)
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
@keyframes accordion-down {
|
|
610
|
+
from {
|
|
611
|
+
height: 0;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
to {
|
|
615
|
+
height: var(--radix-accordion-content-height);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
@keyframes accordion-up {
|
|
620
|
+
from {
|
|
621
|
+
height: var(--radix-accordion-content-height);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
to {
|
|
625
|
+
height: 0;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
@keyframes fade-in {
|
|
630
|
+
from {
|
|
631
|
+
opacity: 0;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
to {
|
|
635
|
+
opacity: 1;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
@keyframes fade-out {
|
|
640
|
+
from {
|
|
641
|
+
opacity: 1;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
to {
|
|
645
|
+
opacity: 0;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
@keyframes zoom-in-95 {
|
|
650
|
+
from {
|
|
651
|
+
opacity: 0;
|
|
652
|
+
transform: scale(0.95);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
to {
|
|
656
|
+
opacity: 1;
|
|
657
|
+
transform: scale(1);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
@keyframes zoom-out-95 {
|
|
662
|
+
from {
|
|
663
|
+
opacity: 1;
|
|
664
|
+
transform: scale(1);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
to {
|
|
668
|
+
opacity: 0;
|
|
669
|
+
transform: scale(0.95);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
@keyframes slide-in-from-top-2 {
|
|
674
|
+
from {
|
|
675
|
+
transform: translateY(-0.5rem);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
to {
|
|
679
|
+
transform: translateY(0);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
@keyframes slide-in-from-bottom-2 {
|
|
684
|
+
from {
|
|
685
|
+
transform: translateY(0.5rem);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
to {
|
|
689
|
+
transform: translateY(0);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
@keyframes slide-in-from-left-2 {
|
|
694
|
+
from {
|
|
695
|
+
transform: translateX(-0.5rem);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
to {
|
|
699
|
+
transform: translateX(0);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
@keyframes slide-in-from-right-2 {
|
|
704
|
+
from {
|
|
705
|
+
transform: translateX(0.5rem);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
to {
|
|
709
|
+
transform: translateX(0);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
:root {
|
|
714
|
+
/* Base colors - using Slate neutral scale */
|
|
715
|
+
--nx-background: oklch(0.99 0 0);
|
|
716
|
+
/* Pure white for weightless aesthetic */
|
|
717
|
+
--nx-page-background: oklch(0.97 0 0);
|
|
718
|
+
/* #f9f9f9 for page content area */
|
|
719
|
+
--nx-foreground: oklch(0 0 0);
|
|
720
|
+
|
|
721
|
+
/* Component colors */
|
|
722
|
+
--nx-card: oklch(1 0 0);
|
|
723
|
+
/* pure white for cards to pop on off-white bg */
|
|
724
|
+
--nx-card-foreground: oklch(0 0 0);
|
|
725
|
+
--nx-popover: oklch(0.99 0 0);
|
|
726
|
+
/* white */
|
|
727
|
+
--nx-popover-foreground: oklch(0 0 0);
|
|
728
|
+
/* Table headers, table footers and the pagination bar. A surface, so it lives
|
|
729
|
+
with the other surfaces: declared next to the component that paints it, a
|
|
730
|
+
palette change would move everything around it and leave it behind. */
|
|
731
|
+
--nx-table-header-bg: oklch(1 0 0);
|
|
732
|
+
|
|
733
|
+
/* Brand colors - Blue-Cyan Professional Palette */
|
|
734
|
+
--nx-primary: oklch(0 0 0);
|
|
735
|
+
/* Black for premium minimalist aesthetic */
|
|
736
|
+
--nx-primary-foreground: oklch(1 0 0);
|
|
737
|
+
/* white */
|
|
738
|
+
|
|
739
|
+
/* Secondary colors - using slate-100 for subtle backgrounds */
|
|
740
|
+
--nx-secondary: oklch(0.94 0 0);
|
|
741
|
+
/* slate-100 */
|
|
742
|
+
--nx-secondary-foreground: oklch(0 0 0);
|
|
743
|
+
|
|
744
|
+
/* Utility colors */
|
|
745
|
+
--nx-muted: oklch(0.97 0 0);
|
|
746
|
+
/* Darkened so muted text clears WCAG 4.5:1 on the muted surface (was 4.35:1). */
|
|
747
|
+
--nx-muted-foreground: oklch(0.44 0 0);
|
|
748
|
+
/* A hover and selection SURFACE, not ink: one step off the page, with
|
|
749
|
+
`--nx-accent-foreground` as the only thing meant to be read on it. */
|
|
750
|
+
--nx-accent: oklch(0.94 0 0);
|
|
751
|
+
--nx-accent-foreground: oklch(0 0 0);
|
|
752
|
+
|
|
753
|
+
/* State colors - WCAG 2.2 AA Compliant.
|
|
754
|
+
* Each status has two roles kept as separate tokens, the industry-standard
|
|
755
|
+
* split (Radix/Primer): the base token is the readable TEXT color used by
|
|
756
|
+
* `text-destructive` and derived shades, and `-solid` is the saturated FILL
|
|
757
|
+
* used by buttons under the on-color ink. In light mode both roles can be
|
|
758
|
+
* the same value and the ink is white. In dark mode they cannot: a fill
|
|
759
|
+
* light enough to read as text on the page is too light for white text, so
|
|
760
|
+
* success splits the fill and destructive flips its ink to black instead.
|
|
761
|
+
* The on-color token is what varies, not just the fill. */
|
|
762
|
+
/* red-500 itself, the reference palette's red. It is lighter than the 4.5:1
|
|
763
|
+
* text minimum allows; darkening it to pass reads as a different colour, and
|
|
764
|
+
* the palette is built around this one. `contrast/accepted.ts` records the
|
|
765
|
+
* affected pairings with their measured ratios. */
|
|
766
|
+
--nx-destructive: oklch(0.63 0.19 23.03);
|
|
767
|
+
--nx-destructive-solid: oklch(0.63 0.19 23.03);
|
|
768
|
+
--nx-destructive-foreground: oklch(1 0 0);
|
|
769
|
+
/* white */
|
|
770
|
+
/* Darkened from green-600; renders as #00852c, ~4.8:1 as text on white. */
|
|
771
|
+
--nx-success: oklch(0.4981 0.17 149.2);
|
|
772
|
+
--nx-success-solid: oklch(0.53 0.17 149.2);
|
|
773
|
+
--nx-success-foreground: oklch(1 0 0);
|
|
774
|
+
/* white */
|
|
775
|
+
/* Darkened from amber-500 so it reads as text on white at WCAG 4.5:1 (was
|
|
776
|
+
* 2.15:1); its light shades still tint badge/alert surfaces. */
|
|
777
|
+
--nx-warning: oklch(0.529 0.1646 70.11);
|
|
778
|
+
--nx-warning-foreground: oklch(0.2079 0.0399 265.73);
|
|
779
|
+
/* slate-900 - REQUIRED for AA contrast (warning-500 on white = 3.4:1, fails AA) */
|
|
780
|
+
|
|
781
|
+
/* The highlighter mark in authored content. Kept a light yellow in both
|
|
782
|
+
* modes on purpose: it stands for a marker pen drawn over the text, and the
|
|
783
|
+
* pairing below is what keeps that readable rather than the page's own
|
|
784
|
+
* foreground, which would be white-on-yellow in dark mode. */
|
|
785
|
+
--nx-highlight: oklch(0.9297 0.1638 101.06);
|
|
786
|
+
/* yellow-200 */
|
|
787
|
+
--nx-highlight-foreground: oklch(0.2079 0.0399 265.73);
|
|
788
|
+
/* slate-900 — the mark keeps its own dark text, whatever the page does */
|
|
789
|
+
|
|
790
|
+
/* Syntax colors. The tokenizer reports what a token *is*; these decide how
|
|
791
|
+
* it looks, so a code block carries no colour of its own and renders from
|
|
792
|
+
* whichever mode is active. Kept muted against the monochrome chrome, and
|
|
793
|
+
* held above 4.5:1 on --nx-code-bg since code is small text. */
|
|
794
|
+
--nx-code-bg: oklch(0.9761 0.0035 247.86);
|
|
795
|
+
--nx-code-fg: oklch(0.2079 0.0399 265.73);
|
|
796
|
+
/* Darkened slightly to clear WCAG 4.5:1 on the code surface (was 4.44:1). */
|
|
797
|
+
--nx-code-comment: oklch(0.5264 0.0407 257.44);
|
|
798
|
+
--nx-code-keyword: oklch(0.4882 0.2172 303.9);
|
|
799
|
+
--nx-code-string: oklch(0.4478 0.1189 152.1);
|
|
800
|
+
--nx-code-number: oklch(0.5106 0.1518 44.2);
|
|
801
|
+
--nx-code-function: oklch(0.4757 0.1444 254.6);
|
|
802
|
+
--nx-code-operator: oklch(0.4936 0.1418 8.4);
|
|
803
|
+
/* Darkened slightly to clear WCAG 4.5:1 on the code surface (was 4.44:1). */
|
|
804
|
+
--nx-code-punctuation: oklch(0.5264 0.0407 257.44);
|
|
805
|
+
--nx-code-variable: oklch(0.4694 0.1173 62.3);
|
|
806
|
+
--nx-code-tag: oklch(0.5054 0.1905 27.5);
|
|
807
|
+
--nx-code-deleted: oklch(0.5054 0.1905 27.5);
|
|
808
|
+
--nx-code-inserted: oklch(0.4478 0.1189 152.1);
|
|
809
|
+
|
|
810
|
+
/* Element colors — 3-tier translucent border scale (monochrome).
|
|
811
|
+
* The first two tiers are decorative and are tuned by eye: subtle = faint
|
|
812
|
+
* dividers, default = the container edge on cards, groups, and tables
|
|
813
|
+
* (composites to a light grey on white, a hairline rather than a rule). The third
|
|
814
|
+
* is not decorative — strong marks the edge of a control that has no other
|
|
815
|
+
* boundary, so it is held to a contrast minimum instead. */
|
|
816
|
+
/* The scrim behind a modal. A token rather than a literal because it is the
|
|
817
|
+
* one surface colour that must differ between the two modes for the same
|
|
818
|
+
* reason a border does: it composites over what is beneath it, and what is
|
|
819
|
+
* beneath it is white here and near-black in dark. It was written inline as
|
|
820
|
+
* `bg-black/80`, identical in both modes, at four different strengths across
|
|
821
|
+
* six components — drift rather than intent. */
|
|
822
|
+
--nx-overlay: oklch(0 0 0 / 0.55);
|
|
823
|
+
/* A scrim you READ ON rather than look through. `--nx-overlay` sits under a
|
|
824
|
+
* modal, which supplies its own surface for text; a full-screen state screen
|
|
825
|
+
* puts its message straight onto the scrim, where the MUTED line decides the
|
|
826
|
+
* strength, not the heading: `text-white/60` over 55% on a white page is
|
|
827
|
+
* 2.81:1, and over 80% it is 5.66:1. Pure white clears AA on either (4.76:1
|
|
828
|
+
* and 12.63:1), so the heading alone would not have justified this token. */
|
|
829
|
+
--nx-overlay-strong: oklch(0 0 0 / 0.8);
|
|
830
|
+
/* For a scrim over CONTENT rather than the page: an image's hover actions,
|
|
831
|
+
* where the content beneath still has to read through. */
|
|
832
|
+
--nx-overlay-soft: oklch(0 0 0 / 0.4);
|
|
833
|
+
--nx-border-subtle: color-mix(in srgb, oklch(0.92 0 0), transparent 60%);
|
|
834
|
+
--nx-border: oklch(0.92 0 0);
|
|
835
|
+
/* These two sit BELOW the WCAG 1.4.11 3:1 minimum for a control boundary,
|
|
836
|
+
* deliberately, to keep the light border weight the palette is designed
|
|
837
|
+
* around. `contrast/accepted.ts` records each affected pairing with its
|
|
838
|
+
* measured ratio and is what keeps that visible rather than silent; the
|
|
839
|
+
* contrast suite reads that list, so neither token can drift further without
|
|
840
|
+
* failing. */
|
|
841
|
+
--nx-border-strong: color-mix(in srgb, oklch(0.92 0 0), black 25%);
|
|
842
|
+
--nx-input: oklch(0.94 0 0);
|
|
843
|
+
/* The boundary of a control that has NOTHING ELSE to identify it.
|
|
844
|
+
*
|
|
845
|
+
* A text field at --nx-input is identifiable without its edge: it carries a
|
|
846
|
+
* label, a placeholder, a value, and a focus ring the moment it is used. An
|
|
847
|
+
* unchecked checkbox or radio is only the box. Its border is not decoration
|
|
848
|
+
* around the control, it IS the control, so 1.4.11's 3:1 applies with nothing
|
|
849
|
+
* to fall back on -- and at the reference palette's border weight the box is
|
|
850
|
+
* effectively invisible.
|
|
851
|
+
*
|
|
852
|
+
* These were one token, which is why the reference weight could not be taken
|
|
853
|
+
* without losing the checkbox. They are two questions -- "how heavy is a field
|
|
854
|
+
* edge" and "can this control be seen at all" -- and they only shared an
|
|
855
|
+
* answer by accident. Splitting them is what lets the field borders match the
|
|
856
|
+
* reference while the controls stay identifiable.
|
|
857
|
+
*
|
|
858
|
+
* Solved to the LIGHTEST value that still clears 3:1 with the margin the
|
|
859
|
+
* contrast suite requires, against the darkest surface a control lands on, so
|
|
860
|
+
* it sits as close to the reference weight as the criterion permits. */
|
|
861
|
+
--nx-control-border: oklch(0.62 0 0);
|
|
862
|
+
--nx-ring: var(--nx-primary);
|
|
863
|
+
|
|
864
|
+
/* The base colour of the elevation ramp (--shadow-sm..xl, -inner-subtle,
|
|
865
|
+
* -neo). Each step mixes this down to its own alpha, so retinting every
|
|
866
|
+
* shadow at once is a single token change. */
|
|
867
|
+
--nx-shadow-color: oklch(0 0 0);
|
|
868
|
+
|
|
869
|
+
/* Chart colors for data visualization */
|
|
870
|
+
--nx-chart-1: var(--nx-primary);
|
|
871
|
+
--nx-chart-2: oklch(0.7399 0.1277 209.34);
|
|
872
|
+
--nx-chart-3: oklch(0.6273 0.17 149.2);
|
|
873
|
+
/* green-600 */
|
|
874
|
+
--nx-chart-4: oklch(0.7686 0.1646 70.11);
|
|
875
|
+
/* amber-500 */
|
|
876
|
+
--nx-chart-5: oklch(0.6368 0.2078 25.33);
|
|
877
|
+
/* red-500 */
|
|
878
|
+
|
|
879
|
+
/* Design tokens */
|
|
880
|
+
/* The one corner knob. Every tier below derives from it, so this single
|
|
881
|
+
* declaration re-rounds the whole admin and a plugin built against the
|
|
882
|
+
* published preset re-rounds with it. */
|
|
883
|
+
--radius: 0.5rem;
|
|
884
|
+
|
|
885
|
+
/* Control sizing — derived from a single height knob so control density
|
|
886
|
+
* scales from one place. Steps map to the control size variants:
|
|
887
|
+
* sm 32px, md 36px, default 40px, lg 44px. */
|
|
888
|
+
--nx-control-height: 2.5rem;
|
|
889
|
+
--nx-control-height-sm: calc(var(--nx-control-height) - 0.5rem);
|
|
890
|
+
--nx-control-height-md: calc(var(--nx-control-height) - 0.25rem);
|
|
891
|
+
--nx-control-height-lg: calc(var(--nx-control-height) + 0.25rem);
|
|
892
|
+
|
|
893
|
+
/* Field width caps — bound how wide a form control grows so a short value
|
|
894
|
+
* (a slug, a toggle, a single date) does not stretch to the full content
|
|
895
|
+
* width. `FieldShell` reads these with a literal fallback, so a consumer
|
|
896
|
+
* theme that predates these tokens still renders correctly; the token is
|
|
897
|
+
* what lets a theme retune the caps centrally instead. A length, not a
|
|
898
|
+
* colour, so it is declared once here rather than per light/dark block —
|
|
899
|
+
* a field's width does not change with the theme. */
|
|
900
|
+
--nx-field-half: 380px;
|
|
901
|
+
--nx-field-full: 760px;
|
|
902
|
+
|
|
903
|
+
/* Page shell — the inset from the content panel's edge to the page's content,
|
|
904
|
+
* and the measure that content is bounded to. `PageShell` spends these as
|
|
905
|
+
* GRID COLUMNS rather than as padding, because padding cannot be cancelled by
|
|
906
|
+
* a descendant: that is why full-bleed content previously had to be rendered
|
|
907
|
+
* OUTSIDE the wrapper to escape the measure, and why two wrappers each
|
|
908
|
+
* applying their own inset silently doubled it.
|
|
909
|
+
*
|
|
910
|
+
* A length rather than a colour, so it is declared once here rather than per
|
|
911
|
+
* light/dark block — a page's inset does not change with the theme. */
|
|
912
|
+
--nx-gutter: 2rem;
|
|
913
|
+
--nx-measure-form: 56rem;
|
|
914
|
+
--nx-measure-wide: 72rem;
|
|
915
|
+
|
|
916
|
+
/* The measure the content column is bounded to, declared here so a theme can
|
|
917
|
+
* retune it centrally and so the token resolves to a real value rather than
|
|
918
|
+
* to a `var()` fallback. `PageShell` overrides it INLINE per its `width`
|
|
919
|
+
* prop, which is what makes the three widths one grid template instead of
|
|
920
|
+
* three near-identical ones; this declaration is the value that applies when
|
|
921
|
+
* the grid is used without that component. */
|
|
922
|
+
--nx-shell-measure: var(--nx-measure-form);
|
|
923
|
+
|
|
924
|
+
/* The single vertical-rhythm token for a form section. `FormSection` applies
|
|
925
|
+
* it to every DIRECT child, so the card's top and bottom breathing room and
|
|
926
|
+
* the gap between two fields are one decision and cannot drift apart — the
|
|
927
|
+
* first child's top padding IS the card's top inset.
|
|
928
|
+
*
|
|
929
|
+
* The rhythm belongs to the section rather than to the rows it is handed. A
|
|
930
|
+
* section renders whatever children it is given, and cannot tell one row
|
|
931
|
+
* idiom from another; leaving each row to supply its own vertical padding
|
|
932
|
+
* makes the card's inset depend on which idiom the caller happened to reach
|
|
933
|
+
* for, and a row that supplies none renders flush against the borders. */
|
|
934
|
+
--nx-field-gap: 1.25rem;
|
|
935
|
+
|
|
936
|
+
/* Focus ring colors */
|
|
937
|
+
--nx-focus-ring: var(--nx-primary);
|
|
938
|
+
--nx-focus-ring-offset: oklch(1 0 0);
|
|
939
|
+
/* white */
|
|
940
|
+
|
|
941
|
+
/* Sidebar specific colors - using Slate scale */
|
|
942
|
+
--nx-sidebar-background: oklch(0.99 0 0);
|
|
943
|
+
/* flat with the content surface; nav is separated by borders, not fill */
|
|
944
|
+
/* Resting and active nav ink are the same value in light mode: nav labels sit
|
|
945
|
+
* at body-text weight rather than a step back from it, which is the reference
|
|
946
|
+
* palette's arrangement.
|
|
947
|
+
*
|
|
948
|
+
* So the active row is marked by its FILL (--nx-sidebar-accent) TOGETHER WITH
|
|
949
|
+
* a font-weight change, and the pair is load-bearing rather than decorative.
|
|
950
|
+
* The fill alone is about 1.11:1, far under the 3:1 WCAG 1.4.11 asks of
|
|
951
|
+
* information identifying a component's state, and there is no exemption for
|
|
952
|
+
* a large filled region -- that is 1.4.3, and it is about text. The weight
|
|
953
|
+
* change is what satisfies the criterion, being a difference that is not a
|
|
954
|
+
* colour and so carries no ratio at all.
|
|
955
|
+
*
|
|
956
|
+
* Removing `data-[active=true]:font-medium` from the sidebar menu buttons
|
|
957
|
+
* therefore leaves the nav with no conforming active state, and
|
|
958
|
+
* `contrast/__tests__/sidebar-ink-hierarchy.test.ts` fails per variant if
|
|
959
|
+
* either loses it.
|
|
960
|
+
*
|
|
961
|
+
* Dark mode keeps the two-step ink relationship instead, where a near-white
|
|
962
|
+
* active ink on a dark row is what separates the states. */
|
|
963
|
+
--nx-sidebar-foreground: oklch(0 0 0);
|
|
964
|
+
--nx-sidebar-primary: oklch(0 0 0);
|
|
965
|
+
--nx-sidebar-primary-foreground: oklch(1 0 0);
|
|
966
|
+
/* slate-50 */
|
|
967
|
+
--nx-sidebar-accent: oklch(0.94 0 0);
|
|
968
|
+
/* slate-100 */
|
|
969
|
+
--nx-sidebar-accent-foreground: oklch(0 0 0);
|
|
970
|
+
/* Below the 3:1 boundary minimum, deliberately, to match the border weight of
|
|
971
|
+
* the rest of the palette. Recorded in `contrast/accepted.ts`. */
|
|
972
|
+
--nx-sidebar-border: oklch(0.94 0 0);
|
|
973
|
+
--nx-sidebar-ring: var(--nx-primary);
|
|
974
|
+
|
|
975
|
+
/* Data table specific colors */
|
|
976
|
+
--nx-table-row-hover: oklch(0.97 0 0);
|
|
977
|
+
--nx-table-row-selected: var(--nx-primary);
|
|
978
|
+
--nx-table-border: var(--nx-border);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/* Dark theme overrides when using class-based theming */
|
|
982
|
+
.dark {
|
|
983
|
+
/* Base colors - using Slate dark scale */
|
|
984
|
+
--nx-background: oklch(0 0 0);
|
|
985
|
+
/* ultra deep dark background (darker than slate-950) */
|
|
986
|
+
--nx-page-background: oklch(0.23 0 0);
|
|
987
|
+
/* Matches background in dark mode */
|
|
988
|
+
--nx-foreground: oklch(1 0 0);
|
|
989
|
+
/* slate-50 */
|
|
990
|
+
|
|
991
|
+
/* Component colors */
|
|
992
|
+
--nx-card: oklch(0.14 0 0);
|
|
993
|
+
/* raised one tonal step above --nx-background (222 47% 4%) so panels/cards
|
|
994
|
+
* read as distinct surfaces in dark mode instead of merging into the page */
|
|
995
|
+
--nx-card-foreground: oklch(1 0 0);
|
|
996
|
+
/* slate-50 */
|
|
997
|
+
/* Darkened toward the card elevation so borders, inputs, and muted text
|
|
998
|
+
* placed on a popover clear their WCAG minimums (border was 2.85:1, input
|
|
999
|
+
* 1.73:1); the surface was too light for its own contents. */
|
|
1000
|
+
--nx-popover: oklch(0.18 0 0);
|
|
1001
|
+
--nx-popover-foreground: oklch(1 0 0);
|
|
1002
|
+
/* slate-50 */
|
|
1003
|
+
/* A step above the card beneath it, carrying no chroma of its own: a hue here
|
|
1004
|
+
would tint the header band independently of every surface around it. */
|
|
1005
|
+
--nx-table-header-bg: oklch(0.1573 0 0);
|
|
1006
|
+
|
|
1007
|
+
/* Brand colors - Blue-Cyan Professional Palette (Dark Mode) */
|
|
1008
|
+
--nx-primary: oklch(1 0 0); /* High contrast White for dark mode */
|
|
1009
|
+
--nx-primary-foreground: oklch(0 0 0);
|
|
1010
|
+
|
|
1011
|
+
/* Secondary colors - using slate-800 for dark backgrounds */
|
|
1012
|
+
--nx-secondary: oklch(0.25 0 0);
|
|
1013
|
+
/* slate-800 */
|
|
1014
|
+
--nx-secondary-foreground: oklch(1 0 0);
|
|
1015
|
+
/* slate-50 */
|
|
1016
|
+
|
|
1017
|
+
/* Utility colors */
|
|
1018
|
+
--nx-muted: oklch(0.23 0 0);
|
|
1019
|
+
--nx-muted-foreground: oklch(0.72 0 0);
|
|
1020
|
+
/* The hover and selected surface: one step lighter than --nx-muted so a row
|
|
1021
|
+
under the pointer separates from a merely quiet one. Neutral, not the blue
|
|
1022
|
+
it was once labelled -- a tinted accent reads as a status on a surface that
|
|
1023
|
+
carries none. */
|
|
1024
|
+
--nx-accent: oklch(0.32 0 0);
|
|
1025
|
+
--nx-accent-foreground: oklch(1 0 0);
|
|
1026
|
+
/* white */
|
|
1027
|
+
|
|
1028
|
+
/* State colors - WCAG 2.2 AA Compliant (Dark Mode).
|
|
1029
|
+
* The base token is lightened from the fill color so it reads as text on the
|
|
1030
|
+
* dark page at 4.5:1. The two statuses then resolve the on-color problem
|
|
1031
|
+
* differently, because a fill light enough to read as text on a dark page is
|
|
1032
|
+
* too light for white text: destructive keeps ONE value for base and fill
|
|
1033
|
+
* and flips its ink to black (6.90:1), while success keeps a darker separate
|
|
1034
|
+
* fill and keeps its ink white (4.80:1). Warning is already light enough to
|
|
1035
|
+
* read on dark, so its base is unchanged here. */
|
|
1036
|
+
/* Lightened red so it reads as text at WCAG 4.5:1 on the dark page and on the
|
|
1037
|
+
* lighter popover surface (was 4.17:1 on the page, 4.25:1 on a popover). */
|
|
1038
|
+
--nx-destructive: oklch(0.69 0.2 23.91);
|
|
1039
|
+
/* Same value as the base: the button fill needs no darkening here because
|
|
1040
|
+
its ink is black rather than white. */
|
|
1041
|
+
--nx-destructive-solid: oklch(0.69 0.2 23.91);
|
|
1042
|
+
/* Black, not white. The dark-mode fill is light enough that black on-color
|
|
1043
|
+
text reads at 6.90:1 where white would fall below the minimum. */
|
|
1044
|
+
--nx-destructive-foreground: oklch(0 0 0);
|
|
1045
|
+
/* Lightened green so it reads as text at WCAG 4.5:1 on the dark page and on
|
|
1046
|
+
* the lighter popover surface (was 4.17:1 on the page, 4.29:1 on a popover). */
|
|
1047
|
+
/* Lightened a step so success ink clears 4.5:1 on --nx-muted with the margin
|
|
1048
|
+
* the suite requires. Muted is the binding surface here, being the lightest
|
|
1049
|
+
* of the dark surfaces this ink lands on; against card or page it was never
|
|
1050
|
+
* close. */
|
|
1051
|
+
--nx-success: oklch(0.61 0.1921 149.58);
|
|
1052
|
+
/* Darker green fill so white on-color button text clears 4.5:1. */
|
|
1053
|
+
--nx-success-solid: oklch(0.5225 0.1921 149.58);
|
|
1054
|
+
--nx-success-foreground: oklch(1 0 0);
|
|
1055
|
+
/* white */
|
|
1056
|
+
--nx-warning: oklch(0.7686 0.1646 70.11);
|
|
1057
|
+
/* amber-500 */
|
|
1058
|
+
--nx-warning-foreground: oklch(0.2079 0.0399 265.73);
|
|
1059
|
+
/* slate-900 */
|
|
1060
|
+
|
|
1061
|
+
/* Dimmed so the mark reads as highlighted rather than as a lamp on a dark
|
|
1062
|
+
* page, while its own foreground stays dark enough to read against it. */
|
|
1063
|
+
--nx-highlight: oklch(0.8 0.1425 101.06);
|
|
1064
|
+
/* yellow-300-ish, pulled back for the dark surface */
|
|
1065
|
+
--nx-highlight-foreground: oklch(0.2079 0.0399 265.73);
|
|
1066
|
+
/* slate-900 */
|
|
1067
|
+
|
|
1068
|
+
/* Syntax colors (dark). Lightened rather than reused from light mode: the
|
|
1069
|
+
* light hues sit near 0.45-0.5 lightness and disappear on the dark surface.
|
|
1070
|
+
* Chroma is pulled back slightly so they don't vibrate against it. */
|
|
1071
|
+
--nx-code-bg: oklch(0.1916 0.0228 266.36);
|
|
1072
|
+
--nx-code-fg: oklch(0.9838 0.0035 247.86);
|
|
1073
|
+
--nx-code-comment: oklch(0.6626 0.0364 256.79);
|
|
1074
|
+
--nx-code-keyword: oklch(0.7482 0.1235 303.9);
|
|
1075
|
+
--nx-code-string: oklch(0.7654 0.1476 152.1);
|
|
1076
|
+
--nx-code-number: oklch(0.7807 0.1189 44.2);
|
|
1077
|
+
--nx-code-function: oklch(0.7365 0.1163 254.6);
|
|
1078
|
+
--nx-code-operator: oklch(0.7549 0.1234 8.4);
|
|
1079
|
+
--nx-code-punctuation: oklch(0.6626 0.0364 256.79);
|
|
1080
|
+
--nx-code-variable: oklch(0.7938 0.1052 62.3);
|
|
1081
|
+
--nx-code-tag: oklch(0.7118 0.1476 27.5);
|
|
1082
|
+
--nx-code-deleted: oklch(0.7118 0.1476 27.5);
|
|
1083
|
+
--nx-code-inserted: oklch(0.7654 0.1476 152.1);
|
|
1084
|
+
|
|
1085
|
+
/* Element colors — 3-tier translucent border scale (white-alpha for dark).
|
|
1086
|
+
* Recomputed as white-over-dark alpha (not a flipped hex) so dividers read
|
|
1087
|
+
* on the deep background without glowing. The decorative tiers carry the same
|
|
1088
|
+
* alphas as light mode, which lands the container edge at a matched 1.41:1 on
|
|
1089
|
+
* the page (1.38:1 light), so a hairline weighs the same in both modes. */
|
|
1090
|
+
/* Stronger than light: the surfaces beneath are mid-tone rather than white,
|
|
1091
|
+
* so the same alpha suppresses less of them. Still black rather than a tinted
|
|
1092
|
+
* scrim, because the modal above it supplies the contrast. */
|
|
1093
|
+
--nx-overlay: oklch(0 0 0 / 0.7);
|
|
1094
|
+
--nx-overlay-strong: oklch(0 0 0 / 0.85);
|
|
1095
|
+
--nx-overlay-soft: oklch(0 0 0 / 0.5);
|
|
1096
|
+
--nx-border-subtle: color-mix(in srgb, oklch(0.26 0 0), transparent 60%);
|
|
1097
|
+
--nx-border: oklch(0.26 0 0);
|
|
1098
|
+
/* Alpha raised so the composited strong border clears WCAG 3:1 / ~4:1 (was 3.00:1). */
|
|
1099
|
+
--nx-border-strong: oklch(0.5444 0 0);
|
|
1100
|
+
/* Brightened so the field border clears WCAG 3:1 on the lighter popover
|
|
1101
|
+
* surface as well as the page and card (was 1.73:1 on a popover). */
|
|
1102
|
+
--nx-input: oklch(0.5445 0 0);
|
|
1103
|
+
/* Dark mode never gave up the boundary minimum, so the control border simply
|
|
1104
|
+
tracks the field border here and the split costs nothing. */
|
|
1105
|
+
--nx-control-border: oklch(0.5445 0 0);
|
|
1106
|
+
--nx-ring: var(--nx-primary);
|
|
1107
|
+
|
|
1108
|
+
/* Black in dark mode too: the ramp reads as depth against the deep surfaces
|
|
1109
|
+
* rather than as a glow, which a lightened shadow colour would produce.
|
|
1110
|
+
* One --shadow-* ramp serves both modes and inlines a single set of
|
|
1111
|
+
* color-mix opacities, so this token is the only place elevation is
|
|
1112
|
+
* retuned -- overriding it resolves the whole ramp to that hue. */
|
|
1113
|
+
--nx-shadow-color: oklch(0 0 0);
|
|
1114
|
+
|
|
1115
|
+
/* Chart colors for data visualization (same as light mode) */
|
|
1116
|
+
--nx-chart-1: var(--nx-primary);
|
|
1117
|
+
--nx-chart-2: oklch(0.7399 0.1277 209.34);
|
|
1118
|
+
--nx-chart-3: oklch(0.7229 0.1921 149.58);
|
|
1119
|
+
/* green-500 */
|
|
1120
|
+
--nx-chart-4: oklch(0.7686 0.1646 70.11);
|
|
1121
|
+
/* amber-500 */
|
|
1122
|
+
--nx-chart-5: oklch(0.6368 0.2078 25.33);
|
|
1123
|
+
/* red-500 */
|
|
1124
|
+
|
|
1125
|
+
/* Sidebar specific colors - dark mode */
|
|
1126
|
+
--nx-sidebar-background: oklch(0.18 0 0);
|
|
1127
|
+
/* flat with the content surface; nav is separated by borders, not fill */
|
|
1128
|
+
/* Resting nav ink, dimmed off slate-50 so it sits a step below
|
|
1129
|
+
* --nx-sidebar-accent-foreground the way the light pair does: the lightness
|
|
1130
|
+
* gap here (0.164) is the same one light mode puts between slate-600 and
|
|
1131
|
+
* slate-900, so an active row reads as emphasised by its ink and not only by
|
|
1132
|
+
* its fill. Chroma and hue track the theme's other secondary ink
|
|
1133
|
+
* (--nx-muted-foreground) so lightness alone carries the hierarchy. */
|
|
1134
|
+
--nx-sidebar-foreground: oklch(0.72 0 0);
|
|
1135
|
+
--nx-sidebar-primary: oklch(1 0 0);
|
|
1136
|
+
/* slate-50 */
|
|
1137
|
+
--nx-sidebar-primary-foreground: oklch(0 0 0);
|
|
1138
|
+
--nx-sidebar-accent: oklch(0.32 0 0);
|
|
1139
|
+
--nx-sidebar-accent-foreground: oklch(1 0 0);
|
|
1140
|
+
/* slate-50 */
|
|
1141
|
+
/* Lightened so the sidebar border clears WCAG 3:1 on its surface (was 1.19:1). */
|
|
1142
|
+
--nx-sidebar-border: oklch(0.519 0 0);
|
|
1143
|
+
--nx-sidebar-ring: var(--nx-primary);
|
|
1144
|
+
|
|
1145
|
+
/* Data table specific colors - dark mode */
|
|
1146
|
+
--nx-table-row-hover: oklch(0.23 0 0);
|
|
1147
|
+
--nx-table-row-selected: var(--nx-primary);
|
|
1148
|
+
--nx-table-border: var(--nx-border);
|
|
1149
|
+
/* inherit the retuned white-alpha border (was a fixed faint slate) */
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/* ============================================================
|
|
1153
|
+
* Page shell
|
|
1154
|
+
* ============================================================
|
|
1155
|
+
*
|
|
1156
|
+
* The gutter and the measure are ONE grid definition, spent as columns rather
|
|
1157
|
+
* than as padding. That is load-bearing rather than stylistic:
|
|
1158
|
+
*
|
|
1159
|
+
* - Padding cannot be cancelled by a descendant, so a block that must run
|
|
1160
|
+
* edge-to-edge previously had to be rendered OUTSIDE the wrapper imposing
|
|
1161
|
+
* the measure. `.nx-bleed` turns that from an accident into a declaration.
|
|
1162
|
+
* - Two wrappers that each apply an inset silently add. As columns there is
|
|
1163
|
+
* one declaration, so double-padding is unrepresentable rather than merely
|
|
1164
|
+
* discouraged.
|
|
1165
|
+
* - Every direct child defaults to the `content` column, so a page header and
|
|
1166
|
+
* the card beneath it cannot disagree about their left edge.
|
|
1167
|
+
*
|
|
1168
|
+
* Centring comes from the two outer tracks being equal rather than from
|
|
1169
|
+
* `margin-inline: auto` on a max-width box, which does nothing at panel widths
|
|
1170
|
+
* where the cap does not bind.
|
|
1171
|
+
*/
|
|
1172
|
+
@layer components {
|
|
1173
|
+
/* A form section's vertical rhythm, applied to every DIRECT child.
|
|
1174
|
+
*
|
|
1175
|
+
* Plain CSS rather than a Tailwind utility on the element, for two reasons.
|
|
1176
|
+
* The parenthesised custom-property shorthand is v4-only, so the same class
|
|
1177
|
+
* compiles to a rule under one Tailwind major and to nothing under the other,
|
|
1178
|
+
* and the section renders flush wherever it compiles to nothing — silently,
|
|
1179
|
+
* because the markup is identical either way. And an arbitrary-value token is
|
|
1180
|
+
* EXTRACTED from any file the scanner reads, a comment or a test string
|
|
1181
|
+
* included, so merely naming one of these utilities in prose emits its rule;
|
|
1182
|
+
* a malformed spelling anywhere in the repository fails the whole stylesheet.
|
|
1183
|
+
*
|
|
1184
|
+
* A rule shipped in the theme has neither property: it is the same CSS under
|
|
1185
|
+
* both majors, and there is no token for a scanner to find.
|
|
1186
|
+
*
|
|
1187
|
+
* Children must not pad themselves. The two paddings are additive, so a row
|
|
1188
|
+
* carrying its own `py-*` doubles the rhythm on the sections that are already
|
|
1189
|
+
* correct. */
|
|
1190
|
+
.nx-form-section-rows > * {
|
|
1191
|
+
padding-block: var(--nx-field-gap);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
.nx-page-shell {
|
|
1195
|
+
display: grid;
|
|
1196
|
+
/* The outer tracks are `minmax(gutter, 1fr)` rather than a fixed gutter so
|
|
1197
|
+
* the grid always spans its container: a fixed pair would total only
|
|
1198
|
+
* `measure + 2 * gutter`, leaving `full-start`/`full-end` short of the
|
|
1199
|
+
* panel's edges in anything wider, and a `Bleed` child short with them. The
|
|
1200
|
+
* `1fr` maximum absorbs the surplus while `gutter` holds as the minimum.
|
|
1201
|
+
*
|
|
1202
|
+
* Their being equal is also what centres the content column, so no
|
|
1203
|
+
* `justify-content` is declared: with flexible tracks the grid fills its
|
|
1204
|
+
* container and there is no free space for it to distribute. */
|
|
1205
|
+
grid-template-columns:
|
|
1206
|
+
[full-start] minmax(var(--nx-gutter), 1fr)
|
|
1207
|
+
[content-start] minmax(0, var(--nx-shell-measure))
|
|
1208
|
+
[content-end] minmax(var(--nx-gutter), 1fr)
|
|
1209
|
+
[full-end];
|
|
1210
|
+
align-content: start;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/* `min-width: 0` because a grid item's default `min-width: auto` refuses to
|
|
1214
|
+
* shrink below its content, which lets one wide descendant — a long code
|
|
1215
|
+
* string, a table — push the column past the measure it was given. */
|
|
1216
|
+
.nx-page-shell > * {
|
|
1217
|
+
grid-column: content;
|
|
1218
|
+
min-width: 0;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
/* Scoped to a DIRECT child: the named lines exist on this grid alone, so a
|
|
1222
|
+
* `.nx-bleed` nested deeper resolves against a grid that never declared them.
|
|
1223
|
+
* Writing the rule this way means such a case is inert rather than
|
|
1224
|
+
* mysteriously half-working. */
|
|
1225
|
+
.nx-page-shell > .nx-bleed {
|
|
1226
|
+
grid-column: full;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
/* The gutter steps with the content PANEL rather than the viewport: the panel's
|
|
1231
|
+
* width changes when a sidebar opens, and the panel is what the page sits in.
|
|
1232
|
+
* `content` is the container the admin's <main> already names. The step values
|
|
1233
|
+
* match Payload's --gutter-h ladder (32 / 24 / 16).
|
|
1234
|
+
*
|
|
1235
|
+
* The override lands on `.nx-page-shell`, a DESCENDANT of that container. A
|
|
1236
|
+
* container query styles only elements inside its container, so a rule naming
|
|
1237
|
+
* `:root` or `.nextly-admin` — both ancestors of the `<main>` that declares it —
|
|
1238
|
+
* never matches, and the gutter keeps its widest value at every panel size. */
|
|
1239
|
+
@container content (max-width: 1024px) {
|
|
1240
|
+
.nx-page-shell {
|
|
1241
|
+
--nx-gutter: 1.5rem;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
@container content (max-width: 768px) {
|
|
1246
|
+
.nx-page-shell {
|
|
1247
|
+
--nx-gutter: 1rem;
|
|
1248
|
+
}
|
|
1249
|
+
}
|