@navecss/core 0.2.0 → 0.3.0

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.
@@ -0,0 +1,1137 @@
1
+ /*
2
+ * Nave Design System — the @layer order statement, alone.
3
+ *
4
+ * Import this first, before any other stylesheet. CSS fixes a layer's
5
+ * position the first time the page declares it, so if a stylesheet of yours
6
+ * that declares a layer loads before this statement, that layer's position is
7
+ * fixed first and Nave's order inverts with no error. Imported first, this one
8
+ * line keeps the order wherever the rest of Nave and your own stylesheets land
9
+ * in the import graph. Importing '@navecss/core' afterwards is correct: its
10
+ * own copy of this statement is identical and changes nothing.
11
+ *
12
+ * Full contract:
13
+ * https://github.com/navecss/navecss/blob/main/docs/04-adr/0003-layer-cascade-contract.md
14
+ */
15
+ @layer tokens.defaults, tokens.presets, reset, atomic, components.nave, components.consumer, overrides;
16
+
17
+ /*
18
+ * Nave Design System — core entry point
19
+ * Import this once in your app, before any stylesheet of yours that
20
+ * declares a layer.
21
+ *
22
+ * The @layer statement below is the cascade contract: seven names across five
23
+ * top-level layers (tokens, reset, atomic, components, overrides), each name
24
+ * beating every name before it. Do not reorder. The contract holds only if
25
+ * this statement is the first @layer declaration the document sees: a layer
26
+ * your CSS declares earlier keeps that earlier position, and the order
27
+ * inverts. Author into a sublayer, never into a bare parent (a parent
28
+ * outranks its own sublayers). Unlayered CSS beats every layer. All of this
29
+ * is for normal declarations: !important reverses layer order, which the
30
+ * reset uses on purpose. Full contract:
31
+ * https://github.com/navecss/navecss/blob/main/docs/04-adr/0003-layer-cascade-contract.md
32
+ *
33
+ * tokens.defaults → CSS custom properties (read-only, set by @navecss/tokens)
34
+ * tokens.presets → RESERVED: 0.1.0 ships zero presets, but the
35
+ * slot is declared now so a preset always beats the defaults
36
+ * regardless of import order, and a consumer's `overrides`
37
+ * always beats a preset. Retrofitting this slot later would be
38
+ * a breaking change to the published cascade contract.
39
+ * A project's own `navecss-tokens build` output lands here too.
40
+ * reset → Cross-browser normalisation
41
+ * atomic → Global atom classes that cx() names: defaults, below
42
+ * component CSS (@nave inlines into your own rule instead)
43
+ * components.nave → Components Nave publishes
44
+ * components.consumer → Your component CSS: @layer components.consumer { … }
45
+ * overrides → Your deliberate exceptions to everything above
46
+ */
47
+ @layer tokens.defaults, tokens.presets, reset, atomic, components.nave, components.consumer, overrides;
48
+
49
+ /* Token layer — all CSS custom properties.
50
+ * Imported plain: tokens.css declares its own `@layer tokens.defaults` so that
51
+ * consumers importing @navecss/tokens/css directly are layered correctly too.
52
+ * Adding layer(tokens.defaults) here would nest it a second time. */
53
+ /* Nave Design System — generated, do not edit */
54
+ @layer tokens.defaults, tokens.presets, reset, atomic, components.nave, components.consumer, overrides;
55
+
56
+ /**
57
+ * @property declarations provide:
58
+ * - Type safety (browsers reject invalid values)
59
+ * - CSS transitions on custom properties (e.g. color tokens)
60
+ * - IDE autocomplete in supported editors
61
+ *
62
+ * Only tokens whose initial-value is computationally independent are
63
+ * registered. Font-relative values (rem/em) cannot be, so those tokens
64
+ * ship as plain custom properties below.
65
+ */
66
+ @property --nave-font-weight-regular {
67
+ syntax: '<number>';
68
+ inherits: true;
69
+ initial-value: 400;
70
+ }
71
+
72
+ @property --nave-font-weight-medium {
73
+ syntax: '<number>';
74
+ inherits: true;
75
+ initial-value: 500;
76
+ }
77
+
78
+ @property --nave-font-weight-semibold {
79
+ syntax: '<number>';
80
+ inherits: true;
81
+ initial-value: 600;
82
+ }
83
+
84
+ @property --nave-font-weight-bold {
85
+ syntax: '<number>';
86
+ inherits: true;
87
+ initial-value: 700;
88
+ }
89
+
90
+ @property --nave-line-height-none {
91
+ syntax: '<number>';
92
+ inherits: true;
93
+ initial-value: 1;
94
+ }
95
+
96
+ @property --nave-line-height-tight {
97
+ syntax: '<number>';
98
+ inherits: true;
99
+ initial-value: 1.2;
100
+ }
101
+
102
+ @property --nave-line-height-snug {
103
+ syntax: '<number>';
104
+ inherits: true;
105
+ initial-value: 1.375;
106
+ }
107
+
108
+ @property --nave-line-height-base {
109
+ syntax: '<number>';
110
+ inherits: true;
111
+ initial-value: 1.5;
112
+ }
113
+
114
+ @property --nave-line-height-relaxed {
115
+ syntax: '<number>';
116
+ inherits: true;
117
+ initial-value: 1.625;
118
+ }
119
+
120
+ @property --nave-line-height-loose {
121
+ syntax: '<number>';
122
+ inherits: true;
123
+ initial-value: 2;
124
+ }
125
+
126
+ @property --nave-spacing-content-xs {
127
+ syntax: '<length>';
128
+ inherits: true;
129
+ initial-value: 4px;
130
+ }
131
+
132
+ @property --nave-spacing-content-sm {
133
+ syntax: '<length>';
134
+ inherits: true;
135
+ initial-value: 8px;
136
+ }
137
+
138
+ @property --nave-spacing-content-md {
139
+ syntax: '<length>';
140
+ inherits: true;
141
+ initial-value: 16px;
142
+ }
143
+
144
+ @property --nave-spacing-content-lg {
145
+ syntax: '<length>';
146
+ inherits: true;
147
+ initial-value: 24px;
148
+ }
149
+
150
+ @property --nave-spacing-content-xl {
151
+ syntax: '<length>';
152
+ inherits: true;
153
+ initial-value: 32px;
154
+ }
155
+
156
+ @property --nave-spacing-layout-xs {
157
+ syntax: '<length>';
158
+ inherits: true;
159
+ initial-value: 48px;
160
+ }
161
+
162
+ @property --nave-spacing-layout-sm {
163
+ syntax: '<length>';
164
+ inherits: true;
165
+ initial-value: 64px;
166
+ }
167
+
168
+ @property --nave-spacing-layout-md {
169
+ syntax: '<length>';
170
+ inherits: true;
171
+ initial-value: 96px;
172
+ }
173
+
174
+ @property --nave-spacing-layout-lg {
175
+ syntax: '<length>';
176
+ inherits: true;
177
+ initial-value: 128px;
178
+ }
179
+
180
+ @property --nave-spacing-control-xs {
181
+ syntax: '<length>';
182
+ inherits: true;
183
+ initial-value: 4px;
184
+ }
185
+
186
+ @property --nave-spacing-control-sm {
187
+ syntax: '<length>';
188
+ inherits: true;
189
+ initial-value: 6px;
190
+ }
191
+
192
+ @property --nave-spacing-control-md {
193
+ syntax: '<length>';
194
+ inherits: true;
195
+ initial-value: 8px;
196
+ }
197
+
198
+ @property --nave-spacing-control-lg {
199
+ syntax: '<length>';
200
+ inherits: true;
201
+ initial-value: 12px;
202
+ }
203
+
204
+ @property --nave-border-width-none {
205
+ syntax: '<length>';
206
+ inherits: true;
207
+ initial-value: 0px;
208
+ }
209
+
210
+ @property --nave-border-width-sm {
211
+ syntax: '<length>';
212
+ inherits: true;
213
+ initial-value: 1px;
214
+ }
215
+
216
+ @property --nave-border-width-focus {
217
+ syntax: '<length>';
218
+ inherits: true;
219
+ initial-value: 2px;
220
+ }
221
+
222
+ @property --nave-border-width-mark {
223
+ syntax: '<length>';
224
+ inherits: true;
225
+ initial-value: 2px;
226
+ }
227
+
228
+ @property --nave-border-width-lg {
229
+ syntax: '<length>';
230
+ inherits: true;
231
+ initial-value: 4px;
232
+ }
233
+
234
+ @property --nave-radius-none {
235
+ syntax: '<length>';
236
+ inherits: true;
237
+ initial-value: 0px;
238
+ }
239
+
240
+ @property --nave-radius-sm {
241
+ syntax: '<length>';
242
+ inherits: true;
243
+ initial-value: 4px;
244
+ }
245
+
246
+ @property --nave-radius-control {
247
+ syntax: '<length>';
248
+ inherits: true;
249
+ initial-value: 6px;
250
+ }
251
+
252
+ @property --nave-radius-card {
253
+ syntax: '<length>';
254
+ inherits: true;
255
+ initial-value: 12px;
256
+ }
257
+
258
+ @property --nave-radius-overlay {
259
+ syntax: '<length>';
260
+ inherits: true;
261
+ initial-value: 12px;
262
+ }
263
+
264
+ @property --nave-radius-full {
265
+ syntax: '<length>';
266
+ inherits: true;
267
+ initial-value: 9999px;
268
+ }
269
+
270
+ @property --nave-opacity-disabled {
271
+ syntax: '<number>';
272
+ inherits: true;
273
+ initial-value: 0.4;
274
+ }
275
+
276
+ @property --nave-layer-base {
277
+ syntax: '<integer>';
278
+ inherits: true;
279
+ initial-value: 0;
280
+ }
281
+
282
+ @property --nave-layer-dropdown {
283
+ syntax: '<integer>';
284
+ inherits: true;
285
+ initial-value: 100;
286
+ }
287
+
288
+ @property --nave-layer-sticky {
289
+ syntax: '<integer>';
290
+ inherits: true;
291
+ initial-value: 200;
292
+ }
293
+
294
+ @property --nave-layer-overlay {
295
+ syntax: '<integer>';
296
+ inherits: true;
297
+ initial-value: 300;
298
+ }
299
+
300
+ @property --nave-layer-modal {
301
+ syntax: '<integer>';
302
+ inherits: true;
303
+ initial-value: 400;
304
+ }
305
+
306
+ @property --nave-layer-toast {
307
+ syntax: '<integer>';
308
+ inherits: true;
309
+ initial-value: 500;
310
+ }
311
+
312
+ @property --nave-layer-tooltip {
313
+ syntax: '<integer>';
314
+ inherits: true;
315
+ initial-value: 600;
316
+ }
317
+
318
+ @property --nave-motion-duration-instant {
319
+ syntax: '<time>';
320
+ inherits: true;
321
+ initial-value: 0ms;
322
+ }
323
+
324
+ @property --nave-motion-duration-fast {
325
+ syntax: '<time>';
326
+ inherits: true;
327
+ initial-value: 100ms;
328
+ }
329
+
330
+ @property --nave-motion-duration-base {
331
+ syntax: '<time>';
332
+ inherits: true;
333
+ initial-value: 200ms;
334
+ }
335
+
336
+ @property --nave-motion-duration-slow {
337
+ syntax: '<time>';
338
+ inherits: true;
339
+ initial-value: 400ms;
340
+ }
341
+
342
+ /**
343
+ * Token values scoped under @layer tokens.defaults.
344
+ * Override in @layer overrides — no specificity battles, ever.
345
+ */
346
+ @layer tokens.defaults {
347
+ :root {
348
+ --nave-font-family-base: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
349
+ --nave-font-family-display: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
350
+ --nave-font-family-mono: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, monospace;
351
+ --nave-font-size-xs: 0.75rem;
352
+ --nave-font-size-sm: 0.875rem;
353
+ --nave-font-size-md: 1rem;
354
+ --nave-font-size-lg: 1.125rem;
355
+ --nave-font-size-xl: 1.25rem;
356
+ --nave-font-size-2xl: 1.5rem;
357
+ --nave-font-size-3xl: 1.875rem;
358
+ --nave-font-size-4xl: 2.25rem;
359
+ --nave-font-weight-regular: 400;
360
+ --nave-font-weight-medium: 500;
361
+ --nave-font-weight-semibold: 600;
362
+ --nave-font-weight-bold: 700;
363
+ --nave-line-height-none: 1;
364
+ --nave-line-height-tight: 1.2;
365
+ --nave-line-height-snug: 1.375;
366
+ --nave-line-height-base: 1.5;
367
+ --nave-line-height-relaxed: 1.625;
368
+ --nave-line-height-loose: 2;
369
+ --nave-letter-spacing-tight: -0.025em;
370
+ --nave-letter-spacing-normal: 0em;
371
+ --nave-letter-spacing-wide: 0.075em;
372
+ --nave-spacing-content-xs: 4px;
373
+ --nave-spacing-content-sm: 8px;
374
+ --nave-spacing-content-md: 16px;
375
+ --nave-spacing-content-lg: 24px;
376
+ --nave-spacing-content-xl: 32px;
377
+ --nave-spacing-layout-xs: 48px;
378
+ --nave-spacing-layout-sm: 64px;
379
+ --nave-spacing-layout-md: 96px;
380
+ --nave-spacing-layout-lg: 128px;
381
+ --nave-spacing-control-xs: 4px;
382
+ --nave-spacing-control-sm: 6px;
383
+ --nave-spacing-control-md: 8px;
384
+ --nave-spacing-control-lg: 12px;
385
+ --nave-size-control-xs: 1.5rem;
386
+ --nave-size-control-sm: 2rem;
387
+ --nave-size-control-md: 2.5rem;
388
+ --nave-size-control-lg: 3rem;
389
+ --nave-size-icon-xs: 0.75rem;
390
+ --nave-size-icon-sm: 1rem;
391
+ --nave-size-icon-md: 1.25rem;
392
+ --nave-size-icon-lg: 1.5rem;
393
+ --nave-size-icon-xl: 2rem;
394
+ --nave-size-avatar-sm: 1.5rem;
395
+ --nave-size-avatar-md: 2rem;
396
+ --nave-size-avatar-lg: 3rem;
397
+ --nave-border-width-none: 0px;
398
+ --nave-border-width-sm: 1px;
399
+ --nave-border-width-focus: 2px;
400
+ --nave-border-width-mark: 2px;
401
+ --nave-border-width-lg: 4px;
402
+ --nave-radius-none: 0px;
403
+ --nave-radius-sm: 4px;
404
+ --nave-radius-control: 6px;
405
+ --nave-radius-card: 12px;
406
+ --nave-radius-overlay: 12px;
407
+ --nave-radius-full: 9999px;
408
+ --nave-shadow-none: none;
409
+ --nave-shadow-raised: 0px 0.0625rem 0.1875rem 0px oklch(0 0 0 / 0.08), 0px 0.0625rem 0.125rem 0px oklch(0 0 0 / 0.06);
410
+ --nave-shadow-overlay: 0px 0.25rem 0.75rem 0px oklch(0 0 0 / 0.1), 0px 0.125rem 0.375rem 0px oklch(0 0 0 / 0.08);
411
+ --nave-shadow-modal: 0px 1rem 2rem 0px oklch(0 0 0 / 0.15), 0px 0.5rem 1rem 0px oklch(0 0 0 / 0.1);
412
+ --nave-opacity-disabled: 0.4;
413
+ --nave-layer-base: 0;
414
+ --nave-layer-dropdown: 100;
415
+ --nave-layer-sticky: 200;
416
+ --nave-layer-overlay: 300;
417
+ --nave-layer-modal: 400;
418
+ --nave-layer-toast: 500;
419
+ --nave-layer-tooltip: 600;
420
+ --nave-motion-duration-instant: 0ms;
421
+ --nave-motion-duration-fast: 100ms;
422
+ --nave-motion-duration-base: 200ms;
423
+ --nave-motion-duration-slow: 400ms;
424
+ --nave-motion-easing-standard: cubic-bezier(0.4, 0, 0.2, 1);
425
+ --nave-motion-easing-decelerate: cubic-bezier(0, 0, 0.2, 1);
426
+ --nave-motion-easing-accelerate: cubic-bezier(0.4, 0, 1, 1);
427
+ }
428
+
429
+ /**
430
+ * Collapse all motion durations for users who prefer reduced motion.
431
+ * Any component using --nave-motion-duration-* gets this for free.
432
+ */
433
+ @media (prefers-reduced-motion: reduce) {
434
+ :root {
435
+ --nave-motion-duration-fast: 0.01ms;
436
+ --nave-motion-duration-base: 0.01ms;
437
+ --nave-motion-duration-slow: 0.01ms;
438
+ }
439
+ }
440
+ }
441
+
442
+ /* Nave theming — generated, do not edit. See the @navecss/tokens README ("Theming"). */
443
+
444
+ @property --nave-color-tint {
445
+ syntax: '<color>';
446
+ inherits: true;
447
+ initial-value: oklch(0.7859 0.1316 186.17);
448
+ }
449
+
450
+ @layer tokens.defaults {
451
+ :root {
452
+ /* color-scheme lives here, not in @navecss/core's reset, so
453
+ * light-dark() resolves even for a tokens-only consumer. */
454
+ color-scheme: light dark;
455
+ /* Tint seed: Changing this changes everything Nave derives from it. The contrast of the resulting palette follows from what you set, and checking it is yours. */
456
+ --nave-color-tint: oklch(0.7859 0.1316 186.17);
457
+ --nave-color-surface-base: light-dark(oklch(from var(--nave-color-tint) 1 0 h / 1), oklch(from var(--nave-color-tint) 0.18 0.0067 h / 1));
458
+ --nave-color-surface-raised: light-dark(oklch(from var(--nave-color-tint) 1 0 h / 1), oklch(from var(--nave-color-tint) 0.225 0.0067 h / 1));
459
+ --nave-color-surface-overlay: light-dark(oklch(from var(--nave-color-tint) 1 0 h / 1), oklch(from var(--nave-color-tint) 0.27 0.0067 h / 1));
460
+ --nave-color-surface-sunken: light-dark(oklch(from var(--nave-color-tint) 0.985 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.12 0.0067 h / 1));
461
+ --nave-color-surface-inverse: light-dark(oklch(from var(--nave-color-tint) 0.18 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.965 0.0067 h / 1));
462
+ --nave-color-content-primary: light-dark(oklch(from var(--nave-color-tint) 0.18 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.965 0.0067 h / 1));
463
+ --nave-color-content-secondary: light-dark(oklch(from var(--nave-color-tint) 0.37 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.835 0.0067 h / 1));
464
+ --nave-color-content-tertiary: light-dark(oklch(from var(--nave-color-tint) 0.47 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.72 0.0067 h / 1));
465
+ --nave-color-content-disabled: light-dark(oklch(from var(--nave-color-tint) 0.835 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.37 0.0067 h / 1));
466
+ --nave-color-content-inverse: light-dark(oklch(from var(--nave-color-tint) 0.965 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.18 0.0067 h / 1));
467
+ --nave-color-content-link: light-dark(oklch(0.47 0.0748 186.17), oklch(0.835 0.123 186.17));
468
+ --nave-color-border-default: light-dark(oklch(from var(--nave-color-tint) 0.905 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.37 0.0067 h / 1));
469
+ --nave-color-border-strong: light-dark(oklch(from var(--nave-color-tint) 0.72 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.47 0.0067 h / 1));
470
+ --nave-color-border-control: light-dark(oklch(from var(--nave-color-tint) 0.59 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.59 0.0067 h / 1));
471
+ --nave-color-border-disabled: light-dark(oklch(from var(--nave-color-tint) 0.94 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.27 0.0067 h / 1));
472
+ --nave-color-border-focus: light-dark(oklch(0.59 0.0939 186.17), oklch(0.59 0.0939 186.17));
473
+ --nave-color-action-primary: light-dark(oklch(0.47 0.0748 186.17), oklch(0.835 0.123 186.17));
474
+ --nave-color-action-primary-hover: light-dark(oklch(0.37 0.0589 186.17), oklch(0.905 0.1106 186.17));
475
+ --nave-color-action-primary-active: light-dark(oklch(0.27 0.0398 186.17), oklch(0.94 0.0574 186.17));
476
+ --nave-color-on-action-primary: light-dark(oklch(from var(--nave-color-tint) 1 0 h / 1), oklch(from var(--nave-color-tint) 0.18 0.0067 h / 1));
477
+ --nave-color-action-secondary: light-dark(oklch(from var(--nave-color-tint) 0.965 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.27 0.0067 h / 1));
478
+ --nave-color-feedback-danger: light-dark(oklch(0.47 0.1455 15.02), oklch(0.835 0.0661 15.02));
479
+ --nave-color-feedback-danger-foreground: light-dark(oklch(0.47 0.1455 15.02), oklch(0.835 0.0661 15.02));
480
+ --nave-color-on-feedback-danger: light-dark(oklch(from var(--nave-color-tint) 1 0 h / 1), oklch(from var(--nave-color-tint) 0.18 0.0067 h / 1));
481
+ /* Feedback tokens: warning, success and info, and their -foreground variants, resolve to one shared value here; only danger differs. Colour alone therefore cannot tell those three states apart, so wherever a feedback token carries meaning, give it a text label naming the state and a distinct icon or shape that differs per state. */
482
+ --nave-color-feedback-warning: light-dark(oklch(from var(--nave-color-tint) 0.47 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.835 0.0067 h / 1));
483
+ --nave-color-feedback-warning-foreground: light-dark(oklch(from var(--nave-color-tint) 0.47 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.835 0.0067 h / 1));
484
+ --nave-color-on-feedback-warning: light-dark(oklch(from var(--nave-color-tint) 1 0 h / 1), oklch(from var(--nave-color-tint) 0.18 0.0067 h / 1));
485
+ --nave-color-feedback-success: light-dark(oklch(from var(--nave-color-tint) 0.47 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.835 0.0067 h / 1));
486
+ --nave-color-feedback-success-foreground: light-dark(oklch(from var(--nave-color-tint) 0.47 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.835 0.0067 h / 1));
487
+ --nave-color-on-feedback-success: light-dark(oklch(from var(--nave-color-tint) 1 0 h / 1), oklch(from var(--nave-color-tint) 0.18 0.0067 h / 1));
488
+ --nave-color-feedback-info: light-dark(oklch(from var(--nave-color-tint) 0.47 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.835 0.0067 h / 1));
489
+ --nave-color-feedback-info-foreground: light-dark(oklch(from var(--nave-color-tint) 0.47 0.0067 h / 1), oklch(from var(--nave-color-tint) 0.835 0.0067 h / 1));
490
+ --nave-color-on-feedback-info: light-dark(oklch(from var(--nave-color-tint) 1 0 h / 1), oklch(from var(--nave-color-tint) 0.18 0.0067 h / 1));
491
+ }
492
+ }
493
+
494
+ /* Reset layer. Imported plain: dist/reset.css now declares its own
495
+ * `@layer reset` so that consumers importing ./reset directly are layered
496
+ * correctly too. Adding layer(reset) here would nest it a second time. */
497
+ /*
498
+ * Self-layered under @layer reset: this file ships as a published
499
+ * subpath export (./reset) and is imported directly by some consumers, so
500
+ * the layer guarantee has to be a property of the artifact rather than of
501
+ * index.css's import site.
502
+ */
503
+ @layer tokens.defaults, tokens.presets, reset, atomic, components.nave, components.consumer, overrides;
504
+
505
+ @layer reset {
506
+ /*
507
+ * Nave Reset — opinionated modern cross-browser reset.
508
+ * Every rule fixes a real, documented cross-browser inconsistency.
509
+ * Tokens are available here because @layer tokens is declared first.
510
+ */
511
+
512
+ /* ── Box model ───────────────────────────────────────────────────────────────
513
+ * Border-box everywhere. The alternative is layout math errors.
514
+ */
515
+ *,
516
+ *::before,
517
+ *::after {
518
+ box-sizing: border-box;
519
+ }
520
+
521
+ /* ── Root ────────────────────────────────────────────────────────────────────
522
+ * 1. Stop Safari on iPhone enlarging body text by itself (for example on an orientation
523
+ * change), so a Nave layout renders at the size it declares. Safari reads only the
524
+ * -webkit- prefixed property; the unprefixed one is the standard spelling. This does not
525
+ * take away a reader's own text-size setting in Safari, in Chrome from version 143, in
526
+ * Android WebView from version 145, or in Firefox: each applies that setting whatever the
527
+ * value here. Chrome no longer autosizes text, and Firefox for Android accepts only auto
528
+ * or none here, so it ignores this declaration. -moz-text-size-adjust is left out on
529
+ * purpose: none, its only opt-out value, would switch off Firefox's own font inflation.
530
+ * The value is 100%, the convention normalize.css set; on iPad and in Firefox, none
531
+ * would not behave the same.
532
+ * 2. Smooth scroll — reduced motion override is at the bottom of this file.
533
+ * 3. hanging-punctuation: Safari only, progressive enhancement.
534
+ * Punctuation hangs outside the text box correctly.
535
+ * 4. Font smoothing: prefer grayscale AA on macOS/iOS.
536
+ * subpixel-antialiased is less consistent across displays.
537
+ * 5. text-rendering: optimizelegibility — enables kerning and ligatures in Firefox and
538
+ * Safari; a no-op elsewhere. Unrelated to font-size inflation (1); kept as its own number.
539
+ */
540
+ html {
541
+ -webkit-font-smoothing: antialiased; /* 4 */
542
+ -moz-osx-font-smoothing: grayscale; /* 4 */
543
+ hanging-punctuation: first last; /* 3 */
544
+ scroll-behavior: smooth; /* 2 */
545
+ text-rendering: optimizelegibility; /* 5 */
546
+ -webkit-text-size-adjust: 100%; /* 1 */
547
+ text-size-adjust: 100%; /* 1 */
548
+ }
549
+
550
+ /* ── Body ────────────────────────────────────────────────────────────────────
551
+ * min-height: 100dvh — dynamic viewport height.
552
+ * Fixes iOS Safari bottom bar eating into 100vh layouts.
553
+ * dvh is Baseline 2023, safe for all modern browsers.
554
+ */
555
+ body {
556
+ background-color: var(--nave-color-surface-base);
557
+ color: var(--nave-color-content-primary);
558
+ font-family: var(--nave-font-family-base);
559
+ font-size: var(--nave-font-size-md);
560
+ line-height: var(--nave-line-height-base);
561
+ margin: 0;
562
+ min-height: 100dvh;
563
+ }
564
+
565
+ /* ── Remove default margins ──────────────────────────────────────────────────
566
+ * Browsers apply inconsistent margins to these elements.
567
+ * Spacing is a layout concern, not an element default.
568
+ */
569
+ h1,
570
+ h2,
571
+ h3,
572
+ h4,
573
+ h5,
574
+ h6,
575
+ p,
576
+ figure,
577
+ blockquote,
578
+ dl,
579
+ dd,
580
+ pre {
581
+ margin: 0;
582
+ }
583
+
584
+ /* ── Lists ───────────────────────────────────────────────────────────────────
585
+ * Only remove list styles when role="list" is present.
586
+ * VoiceOver (Safari) removes list semantics when list-style is removed.
587
+ * Adding role="list" signals intentional non-list usage to the browser.
588
+ * Pattern: Scott O'Hara / Andy Bell.
589
+ */
590
+ ul[role='list'],
591
+ ol[role='list'] {
592
+ list-style: none;
593
+ margin: 0;
594
+ padding: 0;
595
+ }
596
+
597
+ /* ── Headings ─────────────────────────────────────────────────────────────────
598
+ * 1. Tighter line-height — body line-height is too loose for headings.
599
+ * 2. text-wrap: balance — prevents single orphan words on the last line.
600
+ * Baseline since May 2024 (Chrome 114, Firefox 121, Safari 17.5), so it is
601
+ * inside the browser floor (ADR 0005) and applies everywhere Nave supports.
602
+ */
603
+ h1,
604
+ h2,
605
+ h3,
606
+ h4,
607
+ h5,
608
+ h6 {
609
+ font-family: var(--nave-font-family-display);
610
+ font-weight: var(--nave-font-weight-bold);
611
+ line-height: var(--nave-line-height-tight); /* 1 */
612
+ overflow-wrap: break-word;
613
+ text-wrap: balance; /* 2 */
614
+ }
615
+
616
+ /* ── Paragraphs ───────────────────────────────────────────────────────────────
617
+ * text-wrap: pretty — optimises last 4 lines, prevents orphans.
618
+ * Chrome and Edge 117+, Safari 26+, not yet Firefox. Past the browser floor, so
619
+ * progressive enhancement: without it, paragraphs wrap normally (ADR 0005).
620
+ */
621
+ p {
622
+ overflow-wrap: break-word;
623
+ text-wrap: pretty;
624
+ }
625
+
626
+ /* ── Overflow wrap ────────────────────────────────────────────────────────────
627
+ * Prevents long words / URLs from breaking layouts.
628
+ */
629
+ li,
630
+ figcaption,
631
+ dt,
632
+ dd {
633
+ overflow-wrap: break-word;
634
+ }
635
+
636
+ /* ── Replaced elements ────────────────────────────────────────────────────────
637
+ * display: block removes phantom baseline space under images.
638
+ * Caused by inline baseline alignment — one of CSS's oldest layout bugs.
639
+ * height: auto maintains aspect ratio when width is constrained.
640
+ */
641
+ img,
642
+ picture,
643
+ video,
644
+ canvas {
645
+ display: block;
646
+ height: auto;
647
+ max-width: 100%;
648
+ }
649
+
650
+ /* ── SVG ──────────────────────────────────────────────────────────────────────
651
+ * SVGs without an explicit fill inherit the parent text color.
652
+ * Controls icon color via CSS color — no fill attribute required.
653
+ */
654
+ svg {
655
+ display: block;
656
+ }
657
+
658
+ svg:not([fill]) {
659
+ fill: currentcolor;
660
+ }
661
+
662
+ /* ── Figcaption ───────────────────────────────────────────────────────────────
663
+ * Safari applies font-style: italic to figcaption by default.
664
+ */
665
+ figcaption {
666
+ font-style: normal;
667
+ }
668
+
669
+ /* ── Forms ───────────────────────────────────────────────────────────────────
670
+ * font: inherit — all browsers apply their own font to form controls,
671
+ * ignoring the cascade. This is the most important form reset rule.
672
+ * letter-spacing / word-spacing: inherit — Safari ignores these on inputs.
673
+ */
674
+ input,
675
+ button,
676
+ textarea,
677
+ select,
678
+ optgroup {
679
+ color: inherit;
680
+ font: inherit;
681
+ letter-spacing: inherit;
682
+ word-spacing: inherit;
683
+ }
684
+
685
+ /* ── Buttons ──────────────────────────────────────────────────────────────────
686
+ * Strip all browser button chrome, from <button> and every button-like <input>.
687
+ * The text-input rule below also unsets appearance/background for
688
+ * input[type=submit|reset|button] (it excludes only checkbox/radio/range), but leaves the
689
+ * UA's own outset border since it never targeted the button rule's selector; listed here too
690
+ * so all four get the same border: none.
691
+ * The three input types are wrapped in :where() so they carry zero specificity, same as a
692
+ * bare `button` selector: without it, `input[type='submit']` etc. sit at (0,1,1), which beats
693
+ * the Disabled rule's `[disabled], [aria-disabled='true']` at (0,1,0) and makes this rule's
694
+ * `cursor: pointer` win on a disabled button-like input even though the Disabled rule runs
695
+ * later in the file. :where() keeps this rule at (0,0,1), so the Disabled rule's cursor still
696
+ * wins, and a consumer overriding button chrome never needs a specificity escalation either.
697
+ * This rule is placed before the text-input rule below (rather than after, its previous
698
+ * position) because a (0,0,1) selector following a (0,1,1) one that can match the same element
699
+ * (a submit/reset/button input matches both) is a descending-specificity smell the linter
700
+ * flags; placing the lower-specificity rule first avoids that without changing which
701
+ * declaration wins for any element — specificity, not source order, still decides.
702
+ * Nave component styles define all button appearance explicitly.
703
+ */
704
+ button,
705
+ input:where([type='button'], [type='reset'], [type='submit']) {
706
+ appearance: none;
707
+ background: none;
708
+ border: none;
709
+ color: inherit;
710
+ cursor: pointer;
711
+ font: inherit;
712
+ padding: 0;
713
+ }
714
+
715
+ /* ── Text inputs & textareas ──────────────────────────────────────────────────
716
+ * 1. Removes iOS Safari grey background on inputs.
717
+ * 2. appearance: none removes Safari's rounded pill input style. Unprefixed
718
+ * (Baseline) rather than -webkit-appearance.
719
+ * 3. border-radius: 0 — iOS Safari rounds inputs regardless of appearance.
720
+ * Scoped to text-like inputs and textarea only (excludes checkbox, radio,
721
+ * range, and select) — a bare `input` or `select` selector here would strip
722
+ * native rendering from checkboxes/radios (invisible empty boxes) and the
723
+ * select dropdown indicator.
724
+ */
725
+ input:not([type='checkbox'], [type='radio'], [type='range']),
726
+ textarea {
727
+ /* 2 */
728
+ appearance: none; /* 2 */
729
+ background-color: transparent; /* 1 */
730
+ border-radius: 0; /* 3 */
731
+ }
732
+
733
+ /* ── Links ────────────────────────────────────────────────────────────────────
734
+ * color: inherit — link color is a design decision, not a browser default.
735
+ * text-decoration-skip-ink: prevents underline from clipping descenders (g, y, p).
736
+ */
737
+ a {
738
+ color: inherit;
739
+ text-decoration-skip-ink: auto;
740
+ }
741
+
742
+ /* ── Tables ───────────────────────────────────────────────────────────────────
743
+ * border-collapse: collapse removes double borders between cells.
744
+ * border-color: inherit — Firefox bug where table borders don't inherit color.
745
+ */
746
+ table {
747
+ border-collapse: collapse;
748
+ border-color: inherit;
749
+ }
750
+
751
+ /* ── Code ─────────────────────────────────────────────────────────────────────
752
+ * font-size: 1em — browsers render code at ~13px by default. This overrides it.
753
+ */
754
+ code,
755
+ kbd,
756
+ samp,
757
+ pre {
758
+ font-family: var(--nave-font-family-mono);
759
+ font-size: 1em;
760
+ }
761
+
762
+ /* ── Horizontal rule ──────────────────────────────────────────────────────────
763
+ * Chrome and Firefox disagree on hr height and border rendering.
764
+ * border: none clears the UA default first — Chrome leaves border-bottom-width: 1px,
765
+ * border-left-width: 1px and border-bottom-style: inset when only border-top is set,
766
+ * rendering a three-sided box under the token-coloured top border. border-top then
767
+ * normalises to a single 1px line.
768
+ */
769
+ hr {
770
+ border: none;
771
+ border-top: var(--nave-border-width-sm) solid var(--nave-color-border-default);
772
+ color: inherit;
773
+ height: 0;
774
+ }
775
+
776
+ /* ── Hidden attribute ─────────────────────────────────────────────────────────
777
+ * display: flex / grid overrides [hidden] in some browsers.
778
+ * !important is correct here — [hidden] must always mean hidden — except
779
+ * hidden="until-found": excluded via :where() (contributes zero specificity, so the
780
+ * selector stays exactly as specific as plain [hidden]), so find-in-page can still reveal it.
781
+ * The `i` flag makes the attribute-value match case-insensitive: `hidden` is an HTML
782
+ * enumerated attribute, whose keywords (including "until-found") are matched ASCII
783
+ * case-insensitively by the spec, and a browser resolving hidden="UNTIL-FOUND" into the
784
+ * until-found state must not fall through to this !important display: none.
785
+ */
786
+ [hidden]:where(:not([hidden='until-found' i])) {
787
+ display: none !important;
788
+ }
789
+
790
+ /* ── Disabled ─────────────────────────────────────────────────────────────────
791
+ * Consistent disabled cursor across all interactive elements.
792
+ */
793
+ [disabled],
794
+ [aria-disabled='true'] {
795
+ cursor: not-allowed;
796
+ }
797
+
798
+ /* ── Focus ───────────────────────────────────────────────────────────────────
799
+ * The reset does not remove the user agent's focus indicator, and it does
800
+ * not replace it either. That is a statement about what Nave declines to
801
+ * do: no rule here strips outline from a selector that is not itself gated
802
+ * on :focus-visible, and no rule here ships an author-styled indicator at
803
+ * document scope. The user-agent default is the baseline. Consumers who
804
+ * want a different treatment override it via the @layer contract (reset
805
+ * sits early; every consumer layer wins over it).
806
+ */
807
+
808
+ /* ── Color scheme ─────────────────────────────────────────────────────────────
809
+ * `color-scheme` is now emitted by the TOKENS layer (`@navecss/tokens`, which
810
+ * ships full light+dark values via `light-dark()`), not by this reset. A
811
+ * declaration here would win over anything the tokens package emits
812
+ * (`@layer tokens` loads before `@layer reset`), which would make
813
+ * `light-dark()` inert and leave a tokens-only consumer (a documented tier) with
814
+ * no dark mode at all. Do not reintroduce a `color-scheme` declaration here.
815
+ */
816
+
817
+ /* ── Reduced motion ───────────────────────────────────────────────────────────
818
+ * Belt-and-suspenders alongside the token-level reduced motion in tokens.css.
819
+ * Catches animations not using Nave tokens (third-party, consumer code).
820
+ * 0.01ms, not 0ms — avoids triggering animationend event bugs in some browsers.
821
+ * The four !important flags are DELIBERATE. Important declarations reverse
822
+ * cascade-layer order, so these beat a consumer's own !important in any later
823
+ * layer; without them a universal-selector rule in an early layer loses to
824
+ * almost anything, and the collapse would be inert over exactly the
825
+ * third-party and consumer motion it exists to reach. The cost is accepted
826
+ * knowingly: a consumer who wants a GENTLER reduced-motion treatment rather
827
+ * than none cannot have one here, because their shorter duration is
828
+ * clobbered too. This is a deliberate exception to Nave's otherwise-
829
+ * overridable defaults, not an oversight. Do not remove the flags; if you
830
+ * think the trade is wrong, open an issue rather than editing them out.
831
+ */
832
+ @media (prefers-reduced-motion: reduce) {
833
+ *,
834
+ *::before,
835
+ *::after {
836
+ animation-duration: 0.01ms !important;
837
+ animation-iteration-count: 1 !important;
838
+ scroll-behavior: auto !important;
839
+ transition-duration: 0.01ms !important;
840
+ }
841
+ }
842
+ }
843
+
844
+ /* Atomic layer. Imported plain: dist/atomic.css now declares its own
845
+ * `@layer atomic` so that consumers importing ./atomic directly are
846
+ * layered correctly too. Adding layer(atomic) here would nest it a second
847
+ * time. */
848
+ /*
849
+ * Nave atomic utilities — generated from src/atoms.ts
850
+ * Do not edit directly. Run `pnpm build` in packages/core to regenerate.
851
+ *
852
+ * Primary path: .root { @nave interactive focusRing; } (postcss plugin)
853
+ * Escape hatch: `${cx('interactive', 'focusRing')} ${styles.root}`
854
+ *
855
+ * Classes are global, namespaced with nave- prefix.
856
+ * Consumer atoms (navePlugin extend) are NOT included —
857
+ * they are resolved inline by the PostCSS plugin only.
858
+ *
859
+ * Self-layered under @layer atomic: this file ships as a published subpath
860
+ * export (./atomic) and is imported directly by some consumers, so the
861
+ * layer guarantee has to be a property of the artifact rather than of
862
+ * index.css's import site.
863
+ */
864
+
865
+ @layer tokens.defaults, tokens.presets, reset, atomic, components.nave, components.consumer, overrides;
866
+
867
+ @layer atomic {
868
+ .nave-flex {
869
+ display: flex;
870
+ }
871
+
872
+ .nave-inline-flex {
873
+ display: inline-flex;
874
+ }
875
+
876
+ .nave-grid {
877
+ display: grid;
878
+ }
879
+
880
+ .nave-block {
881
+ display: block;
882
+ }
883
+
884
+ .nave-inline-block {
885
+ display: inline-block;
886
+ }
887
+
888
+ .nave-hidden {
889
+ display: none;
890
+ }
891
+
892
+ .nave-flex-col {
893
+ flex-direction: column;
894
+ }
895
+
896
+ .nave-flex-wrap {
897
+ flex-wrap: wrap;
898
+ }
899
+
900
+ .nave-items-center {
901
+ align-items: center;
902
+ }
903
+
904
+ .nave-items-start {
905
+ align-items: flex-start;
906
+ }
907
+
908
+ .nave-items-end {
909
+ align-items: flex-end;
910
+ }
911
+
912
+ .nave-justify-center {
913
+ justify-content: center;
914
+ }
915
+
916
+ .nave-justify-between {
917
+ justify-content: space-between;
918
+ }
919
+
920
+ .nave-justify-end {
921
+ justify-content: flex-end;
922
+ }
923
+
924
+ .nave-flex-grow {
925
+ flex-grow: 1;
926
+ }
927
+
928
+ .nave-flex-shrink0 {
929
+ flex-shrink: 0;
930
+ }
931
+
932
+ .nave-gap {
933
+ gap: var(--nave-spacing-content-md);
934
+ }
935
+
936
+ .nave-relative {
937
+ position: relative;
938
+ }
939
+
940
+ .nave-absolute {
941
+ position: absolute;
942
+ }
943
+
944
+ .nave-inset-full {
945
+ inset: 0;
946
+ }
947
+
948
+ .nave-w-full {
949
+ width: 100%;
950
+ }
951
+
952
+ .nave-h-full {
953
+ height: 100%;
954
+ }
955
+
956
+ .nave-min-w0 {
957
+ min-width: 0;
958
+ }
959
+
960
+ .nave-truncate {
961
+ overflow: hidden;
962
+ text-overflow: ellipsis;
963
+ white-space: nowrap;
964
+ }
965
+
966
+ .nave-sr-only {
967
+ position: absolute;
968
+ width: 1px;
969
+ height: 1px;
970
+ padding: 0;
971
+ margin: -1px;
972
+ overflow: hidden;
973
+ clip-path: inset(50%);
974
+ white-space: nowrap;
975
+ border-width: 0;
976
+ }
977
+
978
+ .nave-sr-only-focusable {
979
+ position: absolute;
980
+ width: 1px;
981
+ height: 1px;
982
+ padding: 0;
983
+ margin: -1px;
984
+ overflow: hidden;
985
+ clip-path: inset(50%);
986
+ white-space: nowrap;
987
+ border-width: 0;
988
+
989
+ &:focus-visible {
990
+ position: static;
991
+ width: auto;
992
+ height: auto;
993
+ margin: 0;
994
+ overflow: visible;
995
+ clip-path: none;
996
+ white-space: normal;
997
+ }
998
+
999
+ &:focus-within {
1000
+ position: static;
1001
+ width: auto;
1002
+ height: auto;
1003
+ margin: 0;
1004
+ overflow: visible;
1005
+ clip-path: none;
1006
+ white-space: normal;
1007
+ }
1008
+ }
1009
+
1010
+ .nave-no-wrap {
1011
+ white-space: nowrap;
1012
+ }
1013
+
1014
+ .nave-break-word {
1015
+ overflow-wrap: break-word;
1016
+ }
1017
+
1018
+ .nave-text-left {
1019
+ text-align: left;
1020
+ }
1021
+
1022
+ .nave-text-center {
1023
+ text-align: center;
1024
+ }
1025
+
1026
+ .nave-text-right {
1027
+ text-align: right;
1028
+ }
1029
+
1030
+ .nave-interactive {
1031
+ cursor: pointer;
1032
+ -webkit-user-select: none;
1033
+ user-select: none;
1034
+ -webkit-tap-highlight-color: transparent;
1035
+ }
1036
+
1037
+ .nave-focus-ring {
1038
+ outline: none;
1039
+
1040
+ &:focus-visible {
1041
+ outline-style: solid;
1042
+ outline-width: var(--nave-border-width-focus, 2px);
1043
+ outline-color: var(--nave-color-border-focus, currentColor);
1044
+ outline-offset: 2px;
1045
+ }
1046
+ }
1047
+
1048
+ .nave-disabled-state {
1049
+ &:disabled, &[aria-disabled="true"] {
1050
+ color: var(--nave-color-content-disabled);
1051
+ border-color: var(--nave-color-border-disabled);
1052
+ pointer-events: none;
1053
+ }
1054
+ }
1055
+
1056
+ .nave-rounded {
1057
+ border-radius: var(--nave-radius-control);
1058
+ }
1059
+
1060
+ .nave-rounded-card {
1061
+ border-radius: var(--nave-radius-card);
1062
+ }
1063
+
1064
+ .nave-rounded-full {
1065
+ border-radius: var(--nave-radius-full);
1066
+ }
1067
+
1068
+ .nave-border {
1069
+ border: var(--nave-border-width-sm) solid var(--nave-color-border-default);
1070
+ }
1071
+
1072
+ .nave-overflow-hidden {
1073
+ overflow: hidden;
1074
+ }
1075
+
1076
+ .nave-overflow-auto {
1077
+ overflow: auto;
1078
+ }
1079
+
1080
+ .nave-transition {
1081
+ transition-property: color, background-color, border-color, opacity, box-shadow;
1082
+ transition-duration: var(--nave-motion-duration-base);
1083
+ transition-timing-function: var(--nave-motion-easing-standard);
1084
+ }
1085
+
1086
+ .nave-container {
1087
+ container-type: inline-size;
1088
+ }
1089
+
1090
+ .nave-hide-phone-only {
1091
+ @media (width < 37.5em) {
1092
+ & {
1093
+ display: none;
1094
+ }
1095
+ }
1096
+ }
1097
+
1098
+ .nave-hide-tablet-portrait-up {
1099
+ @media (width >= 37.5em) {
1100
+ & {
1101
+ display: none;
1102
+ }
1103
+ }
1104
+ }
1105
+
1106
+ .nave-hide-tablet-landscape-up {
1107
+ @media (width >= 56.25em) {
1108
+ & {
1109
+ display: none;
1110
+ }
1111
+ }
1112
+ }
1113
+
1114
+ .nave-hide-desktop-up {
1115
+ @media (width >= 75em) {
1116
+ & {
1117
+ display: none;
1118
+ }
1119
+ }
1120
+ }
1121
+
1122
+ .nave-stack-phone-only {
1123
+ @media (width < 37.5em) {
1124
+ & {
1125
+ flex-direction: column;
1126
+ }
1127
+ }
1128
+ }
1129
+
1130
+ .nave-w-full-phone-only {
1131
+ @media (width < 37.5em) {
1132
+ & {
1133
+ width: 100%;
1134
+ }
1135
+ }
1136
+ }
1137
+ }