@wtfalch/design 0.8.0 → 0.9.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.
@@ -8,6 +8,705 @@
8
8
  *
9
9
  * `scripts/copy-css.mjs` flattens these into one file at build time.
10
10
  */
11
+ /* ---- ./_tailwind.built.css ---- */
12
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
13
+ @layer properties;
14
+ @layer theme, base, components, utilities;
15
+ @layer theme {
16
+ :root, :host {
17
+ --font-sans: var(--font-sans);
18
+ --font-mono: var(--font-mono);
19
+ --text-xs: var(--text-xs);
20
+ --text-xs--line-height: inherit;
21
+ --text-sm: var(--text-sm);
22
+ --text-sm--line-height: inherit;
23
+ --text-base: var(--text-base);
24
+ --text-lg: var(--text-lg);
25
+ --text-lg--line-height: inherit;
26
+ --text-xl: var(--text-xl);
27
+ --font-weight-normal: var(--weight);
28
+ --tracking-normal: var(--tracking);
29
+ --radius-sm: var(--radius-sm);
30
+ --radius-md: var(--radius-md);
31
+ --radius-lg: var(--radius-lg);
32
+ --ease-out: cubic-bezier(0, 0, 0.2, 1);
33
+ --spacing-1: var(--space-1);
34
+ --spacing-2: var(--space-2);
35
+ --spacing-3: var(--space-3);
36
+ --spacing-4: var(--space-4);
37
+ --spacing-5: var(--space-5);
38
+ --spacing-6: var(--space-6);
39
+ --spacing-10: var(--space-10);
40
+ --text-2xs--line-height: inherit;
41
+ --text-2xs: var(--text-2xs);
42
+ --text-md: var(--text-md);
43
+ --font-weight-strong: var(--weight-strong);
44
+ --radius-pill: var(--radius-pill);
45
+ --color-text: var(--text);
46
+ --color-muted: var(--muted);
47
+ --color-accent: var(--accent);
48
+ --color-border: var(--border);
49
+ --color-border-strong: var(--border-strong);
50
+ }
51
+ }
52
+ @layer utilities {
53
+ .collapse {
54
+ visibility: collapse;
55
+ }
56
+ .invisible {
57
+ visibility: hidden;
58
+ }
59
+ .visible {
60
+ visibility: visible;
61
+ }
62
+ .sr-only {
63
+ position: absolute;
64
+ width: 1px;
65
+ height: 1px;
66
+ padding: 0;
67
+ margin: -1px;
68
+ overflow: hidden;
69
+ clip-path: inset(50%);
70
+ white-space: nowrap;
71
+ border-width: 0;
72
+ }
73
+ .absolute {
74
+ position: absolute;
75
+ }
76
+ .fixed {
77
+ position: fixed;
78
+ }
79
+ .relative {
80
+ position: relative;
81
+ }
82
+ .sticky {
83
+ position: sticky;
84
+ }
85
+ .z-\[60\] {
86
+ z-index: 60;
87
+ }
88
+ .container {
89
+ width: 100%;
90
+ @media (width >= 40rem) {
91
+ max-width: 40rem;
92
+ }
93
+ @media (width >= 48rem) {
94
+ max-width: 48rem;
95
+ }
96
+ @media (width >= 64rem) {
97
+ max-width: 64rem;
98
+ }
99
+ @media (width >= 80rem) {
100
+ max-width: 80rem;
101
+ }
102
+ @media (width >= 96rem) {
103
+ max-width: 96rem;
104
+ }
105
+ }
106
+ .m-0 {
107
+ margin: 0px;
108
+ }
109
+ .m-auto {
110
+ margin: auto;
111
+ }
112
+ .mx-auto {
113
+ margin-inline: auto;
114
+ }
115
+ .mt-1 {
116
+ margin-top: var(--spacing-1);
117
+ }
118
+ .mb-3 {
119
+ margin-bottom: var(--spacing-3);
120
+ }
121
+ .ml-2 {
122
+ margin-left: var(--spacing-2);
123
+ }
124
+ .block {
125
+ display: block;
126
+ }
127
+ .contents {
128
+ display: contents;
129
+ }
130
+ .flex {
131
+ display: flex;
132
+ }
133
+ .grid {
134
+ display: grid;
135
+ }
136
+ .hidden {
137
+ display: none;
138
+ }
139
+ .inline {
140
+ display: inline;
141
+ }
142
+ .inline-flex {
143
+ display: inline-flex;
144
+ }
145
+ .table {
146
+ display: table;
147
+ }
148
+ .h-\[14px\] {
149
+ height: 14px;
150
+ }
151
+ .h-\[18px\] {
152
+ height: 18px;
153
+ }
154
+ .max-h-\[24rem\] {
155
+ max-height: 24rem;
156
+ }
157
+ .max-h-\[280px\] {
158
+ max-height: 280px;
159
+ }
160
+ .min-h-0 {
161
+ min-height: 0px;
162
+ }
163
+ .min-h-screen {
164
+ min-height: 100vh;
165
+ }
166
+ .w-\[18px\] {
167
+ width: 18px;
168
+ }
169
+ .w-full {
170
+ width: 100%;
171
+ }
172
+ .max-w-\[46ch\] {
173
+ max-width: 46ch;
174
+ }
175
+ .max-w-\[min\(20rem\,calc\(100vw-var\(--space-6\)\)\)\] {
176
+ max-width: min(20rem, calc(100vw - var(--space-6)));
177
+ }
178
+ .max-w-\[var\(--shell-measure\)\] {
179
+ max-width: var(--shell-measure);
180
+ }
181
+ .max-w-\[var\(--shell-measure-wide\)\] {
182
+ max-width: var(--shell-measure-wide);
183
+ }
184
+ .min-w-0 {
185
+ min-width: 0px;
186
+ }
187
+ .min-w-\[12rem\] {
188
+ min-width: 12rem;
189
+ }
190
+ .flex-1 {
191
+ flex: 1;
192
+ }
193
+ .flex-none {
194
+ flex: none;
195
+ }
196
+ .shrink {
197
+ flex-shrink: 1;
198
+ }
199
+ .grow {
200
+ flex-grow: 1;
201
+ }
202
+ .transform {
203
+ transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
204
+ }
205
+ .cursor-help {
206
+ cursor: help;
207
+ }
208
+ .cursor-pointer {
209
+ cursor: pointer;
210
+ }
211
+ .resize {
212
+ resize: both;
213
+ }
214
+ .list-none {
215
+ list-style-type: none;
216
+ }
217
+ .flex-col {
218
+ flex-direction: column;
219
+ }
220
+ .flex-wrap {
221
+ flex-wrap: wrap;
222
+ }
223
+ .items-baseline {
224
+ align-items: baseline;
225
+ }
226
+ .items-center {
227
+ align-items: center;
228
+ }
229
+ .items-start {
230
+ align-items: flex-start;
231
+ }
232
+ .justify-between {
233
+ justify-content: space-between;
234
+ }
235
+ .justify-items-center {
236
+ justify-items: center;
237
+ }
238
+ .gap-0 {
239
+ gap: 0px;
240
+ }
241
+ .gap-1 {
242
+ gap: var(--spacing-1);
243
+ }
244
+ .gap-2 {
245
+ gap: var(--spacing-2);
246
+ }
247
+ .gap-3 {
248
+ gap: var(--spacing-3);
249
+ }
250
+ .gap-4 {
251
+ gap: var(--spacing-4);
252
+ }
253
+ .gap-\[var\(--border-width\)\] {
254
+ gap: var(--border-width);
255
+ }
256
+ .self-center {
257
+ align-self: center;
258
+ }
259
+ .truncate {
260
+ overflow: hidden;
261
+ text-overflow: ellipsis;
262
+ white-space: nowrap;
263
+ }
264
+ .overflow-auto {
265
+ overflow: auto;
266
+ }
267
+ .overflow-hidden {
268
+ overflow: hidden;
269
+ }
270
+ .overflow-x-auto {
271
+ overflow-x: auto;
272
+ }
273
+ .overflow-y-auto {
274
+ overflow-y: auto;
275
+ }
276
+ .overscroll-contain {
277
+ overscroll-behavior: contain;
278
+ }
279
+ .rounded {
280
+ border-radius: 0.25rem;
281
+ }
282
+ .rounded-full {
283
+ border-radius: calc(infinity * 1px);
284
+ }
285
+ .rounded-md {
286
+ border-radius: var(--radius-md);
287
+ }
288
+ .rounded-sm {
289
+ border-radius: var(--radius-sm);
290
+ }
291
+ .border {
292
+ border-style: var(--tw-border-style);
293
+ border-width: var(--border-width);
294
+ }
295
+ .border-b {
296
+ border-bottom-style: var(--tw-border-style);
297
+ border-bottom-width: var(--border-width);
298
+ }
299
+ .border-dashed {
300
+ --tw-border-style: dashed;
301
+ border-style: dashed;
302
+ }
303
+ .border-accent {
304
+ border-color: var(--color-accent);
305
+ }
306
+ .border-border {
307
+ border-color: var(--color-border);
308
+ }
309
+ .border-border-strong {
310
+ border-color: var(--color-border-strong);
311
+ }
312
+ .p-0 {
313
+ padding: 0px;
314
+ }
315
+ .p-1 {
316
+ padding: var(--spacing-1);
317
+ }
318
+ .p-2 {
319
+ padding: var(--spacing-2);
320
+ }
321
+ .p-4 {
322
+ padding: var(--spacing-4);
323
+ }
324
+ .px-4 {
325
+ padding-inline: var(--spacing-4);
326
+ }
327
+ .py-2 {
328
+ padding-block: var(--spacing-2);
329
+ }
330
+ .py-3 {
331
+ padding-block: var(--spacing-3);
332
+ }
333
+ .py-5 {
334
+ padding-block: var(--spacing-5);
335
+ }
336
+ .py-6 {
337
+ padding-block: var(--spacing-6);
338
+ }
339
+ .py-10 {
340
+ padding-block: var(--spacing-10);
341
+ }
342
+ .pb-2 {
343
+ padding-bottom: var(--spacing-2);
344
+ }
345
+ .pb-3 {
346
+ padding-bottom: var(--spacing-3);
347
+ }
348
+ .text-center {
349
+ text-align: center;
350
+ }
351
+ .font-mono {
352
+ font-family: var(--font-mono);
353
+ }
354
+ .text-2xs {
355
+ font-size: var(--text-2xs);
356
+ line-height: var(--tw-leading, var(--text-2xs--line-height));
357
+ }
358
+ .text-lg {
359
+ font-size: var(--text-lg);
360
+ line-height: var(--tw-leading, var(--text-lg--line-height));
361
+ }
362
+ .text-sm {
363
+ font-size: var(--text-sm);
364
+ line-height: var(--tw-leading, var(--text-sm--line-height));
365
+ }
366
+ .text-xs {
367
+ font-size: var(--text-xs);
368
+ line-height: var(--tw-leading, var(--text-xs--line-height));
369
+ }
370
+ .leading-\[0\] {
371
+ --tw-leading: 0;
372
+ line-height: 0;
373
+ }
374
+ .leading-\[1\.5\] {
375
+ --tw-leading: 1.5;
376
+ line-height: 1.5;
377
+ }
378
+ .leading-\[1\.25\] {
379
+ --tw-leading: 1.25;
380
+ line-height: 1.25;
381
+ }
382
+ .leading-\[16px\] {
383
+ --tw-leading: 16px;
384
+ line-height: 16px;
385
+ }
386
+ .font-normal {
387
+ --tw-font-weight: var(--font-weight-normal);
388
+ font-weight: var(--font-weight-normal);
389
+ }
390
+ .font-strong {
391
+ --tw-font-weight: var(--font-weight-strong);
392
+ font-weight: var(--font-weight-strong);
393
+ }
394
+ .tracking-normal {
395
+ --tw-tracking: var(--tracking-normal);
396
+ letter-spacing: var(--tracking-normal);
397
+ }
398
+ .text-ellipsis {
399
+ text-overflow: ellipsis;
400
+ }
401
+ .whitespace-nowrap {
402
+ white-space: nowrap;
403
+ }
404
+ .text-muted {
405
+ color: var(--color-muted);
406
+ }
407
+ .text-text {
408
+ color: var(--color-text);
409
+ }
410
+ .lowercase {
411
+ text-transform: lowercase;
412
+ }
413
+ .normal-case {
414
+ text-transform: none;
415
+ }
416
+ .uppercase {
417
+ text-transform: uppercase;
418
+ }
419
+ .tabular-nums {
420
+ --tw-numeric-spacing: tabular-nums;
421
+ font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
422
+ }
423
+ .opacity-0 {
424
+ opacity: 0%;
425
+ }
426
+ .opacity-60 {
427
+ opacity: 60%;
428
+ }
429
+ .opacity-\[0\.55\] {
430
+ opacity: 0.55;
431
+ }
432
+ .shadow {
433
+ --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
434
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
435
+ }
436
+ .ring {
437
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
438
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
439
+ }
440
+ .outline {
441
+ outline-style: var(--tw-outline-style);
442
+ outline-width: 1px;
443
+ }
444
+ .blur {
445
+ --tw-blur: blur(8px);
446
+ filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
447
+ }
448
+ .sepia {
449
+ --tw-sepia: sepia(100%);
450
+ filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
451
+ }
452
+ .filter {
453
+ filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
454
+ }
455
+ .outline-none {
456
+ --tw-outline-style: none;
457
+ outline-style: none;
458
+ }
459
+ .\[font\:inherit\] {
460
+ font: inherit;
461
+ }
462
+ .surface-bg {
463
+ background: var(--bg);
464
+ }
465
+ .surface-panel {
466
+ background: var(--panel);
467
+ }
468
+ .surface-panel-2 {
469
+ background: var(--panel-2);
470
+ }
471
+ .disabled\:cursor-default:disabled {
472
+ cursor: default;
473
+ }
474
+ .disabled\:opacity-50:disabled {
475
+ opacity: 50%;
476
+ }
477
+ .\[\&\>p\]\:m-0 > p {
478
+ margin: 0px;
479
+ }
480
+ }
481
+ @property --tw-rotate-x {
482
+ syntax: "*";
483
+ inherits: false;
484
+ }
485
+ @property --tw-rotate-y {
486
+ syntax: "*";
487
+ inherits: false;
488
+ }
489
+ @property --tw-rotate-z {
490
+ syntax: "*";
491
+ inherits: false;
492
+ }
493
+ @property --tw-skew-x {
494
+ syntax: "*";
495
+ inherits: false;
496
+ }
497
+ @property --tw-skew-y {
498
+ syntax: "*";
499
+ inherits: false;
500
+ }
501
+ @property --tw-border-style {
502
+ syntax: "*";
503
+ inherits: false;
504
+ initial-value: solid;
505
+ }
506
+ @property --tw-leading {
507
+ syntax: "*";
508
+ inherits: false;
509
+ }
510
+ @property --tw-font-weight {
511
+ syntax: "*";
512
+ inherits: false;
513
+ }
514
+ @property --tw-tracking {
515
+ syntax: "*";
516
+ inherits: false;
517
+ }
518
+ @property --tw-ordinal {
519
+ syntax: "*";
520
+ inherits: false;
521
+ }
522
+ @property --tw-slashed-zero {
523
+ syntax: "*";
524
+ inherits: false;
525
+ }
526
+ @property --tw-numeric-figure {
527
+ syntax: "*";
528
+ inherits: false;
529
+ }
530
+ @property --tw-numeric-spacing {
531
+ syntax: "*";
532
+ inherits: false;
533
+ }
534
+ @property --tw-numeric-fraction {
535
+ syntax: "*";
536
+ inherits: false;
537
+ }
538
+ @property --tw-shadow {
539
+ syntax: "*";
540
+ inherits: false;
541
+ initial-value: 0 0 #0000;
542
+ }
543
+ @property --tw-shadow-color {
544
+ syntax: "*";
545
+ inherits: false;
546
+ }
547
+ @property --tw-shadow-alpha {
548
+ syntax: "<percentage>";
549
+ inherits: false;
550
+ initial-value: 100%;
551
+ }
552
+ @property --tw-inset-shadow {
553
+ syntax: "*";
554
+ inherits: false;
555
+ initial-value: 0 0 #0000;
556
+ }
557
+ @property --tw-inset-shadow-color {
558
+ syntax: "*";
559
+ inherits: false;
560
+ }
561
+ @property --tw-inset-shadow-alpha {
562
+ syntax: "<percentage>";
563
+ inherits: false;
564
+ initial-value: 100%;
565
+ }
566
+ @property --tw-ring-color {
567
+ syntax: "*";
568
+ inherits: false;
569
+ }
570
+ @property --tw-ring-shadow {
571
+ syntax: "*";
572
+ inherits: false;
573
+ initial-value: 0 0 #0000;
574
+ }
575
+ @property --tw-inset-ring-color {
576
+ syntax: "*";
577
+ inherits: false;
578
+ }
579
+ @property --tw-inset-ring-shadow {
580
+ syntax: "*";
581
+ inherits: false;
582
+ initial-value: 0 0 #0000;
583
+ }
584
+ @property --tw-ring-inset {
585
+ syntax: "*";
586
+ inherits: false;
587
+ }
588
+ @property --tw-ring-offset-width {
589
+ syntax: "<length>";
590
+ inherits: false;
591
+ initial-value: 0px;
592
+ }
593
+ @property --tw-ring-offset-color {
594
+ syntax: "*";
595
+ inherits: false;
596
+ initial-value: #fff;
597
+ }
598
+ @property --tw-ring-offset-shadow {
599
+ syntax: "*";
600
+ inherits: false;
601
+ initial-value: 0 0 #0000;
602
+ }
603
+ @property --tw-outline-style {
604
+ syntax: "*";
605
+ inherits: false;
606
+ initial-value: solid;
607
+ }
608
+ @property --tw-blur {
609
+ syntax: "*";
610
+ inherits: false;
611
+ }
612
+ @property --tw-brightness {
613
+ syntax: "*";
614
+ inherits: false;
615
+ }
616
+ @property --tw-contrast {
617
+ syntax: "*";
618
+ inherits: false;
619
+ }
620
+ @property --tw-grayscale {
621
+ syntax: "*";
622
+ inherits: false;
623
+ }
624
+ @property --tw-hue-rotate {
625
+ syntax: "*";
626
+ inherits: false;
627
+ }
628
+ @property --tw-invert {
629
+ syntax: "*";
630
+ inherits: false;
631
+ }
632
+ @property --tw-opacity {
633
+ syntax: "*";
634
+ inherits: false;
635
+ }
636
+ @property --tw-saturate {
637
+ syntax: "*";
638
+ inherits: false;
639
+ }
640
+ @property --tw-sepia {
641
+ syntax: "*";
642
+ inherits: false;
643
+ }
644
+ @property --tw-drop-shadow {
645
+ syntax: "*";
646
+ inherits: false;
647
+ }
648
+ @property --tw-drop-shadow-color {
649
+ syntax: "*";
650
+ inherits: false;
651
+ }
652
+ @property --tw-drop-shadow-alpha {
653
+ syntax: "<percentage>";
654
+ inherits: false;
655
+ initial-value: 100%;
656
+ }
657
+ @property --tw-drop-shadow-size {
658
+ syntax: "*";
659
+ inherits: false;
660
+ }
661
+ @layer properties {
662
+ @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
663
+ *, ::before, ::after, ::backdrop {
664
+ --tw-rotate-x: initial;
665
+ --tw-rotate-y: initial;
666
+ --tw-rotate-z: initial;
667
+ --tw-skew-x: initial;
668
+ --tw-skew-y: initial;
669
+ --tw-border-style: solid;
670
+ --tw-leading: initial;
671
+ --tw-font-weight: initial;
672
+ --tw-tracking: initial;
673
+ --tw-ordinal: initial;
674
+ --tw-slashed-zero: initial;
675
+ --tw-numeric-figure: initial;
676
+ --tw-numeric-spacing: initial;
677
+ --tw-numeric-fraction: initial;
678
+ --tw-shadow: 0 0 #0000;
679
+ --tw-shadow-color: initial;
680
+ --tw-shadow-alpha: 100%;
681
+ --tw-inset-shadow: 0 0 #0000;
682
+ --tw-inset-shadow-color: initial;
683
+ --tw-inset-shadow-alpha: 100%;
684
+ --tw-ring-color: initial;
685
+ --tw-ring-shadow: 0 0 #0000;
686
+ --tw-inset-ring-color: initial;
687
+ --tw-inset-ring-shadow: 0 0 #0000;
688
+ --tw-ring-inset: initial;
689
+ --tw-ring-offset-width: 0px;
690
+ --tw-ring-offset-color: #fff;
691
+ --tw-ring-offset-shadow: 0 0 #0000;
692
+ --tw-outline-style: solid;
693
+ --tw-blur: initial;
694
+ --tw-brightness: initial;
695
+ --tw-contrast: initial;
696
+ --tw-grayscale: initial;
697
+ --tw-hue-rotate: initial;
698
+ --tw-invert: initial;
699
+ --tw-opacity: initial;
700
+ --tw-saturate: initial;
701
+ --tw-sepia: initial;
702
+ --tw-drop-shadow: initial;
703
+ --tw-drop-shadow-color: initial;
704
+ --tw-drop-shadow-alpha: 100%;
705
+ --tw-drop-shadow-size: initial;
706
+ }
707
+ }
708
+ }
709
+
11
710
  /* ---- ./base.css ---- */
12
711
  /* Light, only under the `system` theme. This used to override `:root`
13
712
  unconditionally, so choosing a dark theme on a light-mode laptop got
@@ -101,23 +800,31 @@ body::before {
101
800
  `[aria-disabled='true']` twin rather than a rewrite, which leaves each
102
801
  `button` selector at the specificity it had and every committed baseline
103
802
  untouched. */
104
- a.btn {
803
+ .btn {
804
+ /* The three an anchor brings that a `<button>` does not. Harmless on the
805
+ button form -- it has no underline and centres already -- and the reason
806
+ `Button` puts one class on both elements rather than keeping a twin
807
+ selector for each. */
105
808
  text-decoration: none;
106
809
  color: var(--text);
107
810
  text-align: center;
108
811
  }
109
812
 
110
- a.btn[aria-disabled='true'] {
813
+ .btn[aria-disabled='true'] {
111
814
  /* A disabled anchor still takes the pointer, so say so. The click itself is
112
815
  blocked in the capture phase by the component. */
113
816
  cursor: not-allowed;
114
817
  }
115
818
 
116
- button,
117
- a.btn,
118
- input,
119
- textarea,
120
- select {
819
+ /* `:where`, so this base carries no specificity of its own.
820
+ *
821
+ * It was `button, input, textarea, select` -- an element selector, which
822
+ * `.size-sm` and every other class outranked without anyone thinking about
823
+ * it. Moving it to `.btn` to stop a tab inheriting a control's look raised it
824
+ * to a class, so it tied with `.size-sm` and won on source order: every
825
+ * button came out `8px 12px` whatever size it asked for. `:where` puts it
826
+ * back to zero, which is what the element selector was silently providing. */
827
+ :where(.btn, input, textarea, select) {
121
828
  font: inherit;
122
829
  color: var(--text);
123
830
  background: var(--control);
@@ -138,8 +845,7 @@ select {
138
845
  the icon; the button simply had no opinion about how to arrange two things.
139
846
  `inline-flex` and a gap gives it one, and the gap replaces the space
140
847
  character people were putting between an icon and its label. */
141
- button,
142
- a.btn {
848
+ .btn {
143
849
  display: inline-flex;
144
850
  align-items: center;
145
851
  justify-content: center;
@@ -155,18 +861,41 @@ a.btn {
155
861
 
156
862
  Several places already drew this by hand as `.chat-bar-btn.on`. This is the
157
863
  same look, said once, on a plain button. */
158
- button[aria-pressed='true'] {
864
+ .btn[aria-pressed='true'] {
159
865
  border-color: var(--accent);
160
866
  color: var(--accent);
161
867
  background: color-mix(in srgb, var(--accent) 10%, var(--panel));
162
868
  }
163
869
 
164
- button[aria-pressed='true']:hover:not(:disabled) { border-color: var(--accent); }
870
+ .btn[aria-pressed='true']:hover:not(:disabled) { border-color: var(--accent); }
165
871
 
166
872
  /* The glyph never shrinks: in a narrow row flexbox would take it from the icon
167
873
  before the text, and half an icon is not a smaller icon. */
168
- button > svg,
169
- a.btn > svg { flex: none; }
874
+ .btn > svg { flex: none; }
875
+
876
+ /* `Select`'s control wears the control styling, and says so.
877
+ *
878
+ * It renders React Aria's `Button` -- a bare `<button>` -- and used to take
879
+ * `font`, colour, the surface and the border from the
880
+ * `button, input, textarea, select` reset without asking. When that reset
881
+ * moved to `.btn`, so a tab and a row would stop inheriting a control's look
882
+ * only to undo it, this lost all of it at once.
883
+ *
884
+ * Here rather than in `select.css`, and that placement is the rule, not
885
+ * tidiness: `font` is a shorthand and it resets `font-size`, so declared
886
+ * after `.size-sm` below it silently makes every small control full size.
887
+ * `index.css` says the import order is behaviour; this is what that means.
888
+ *
889
+ * `:where` for the same reason the reset above uses it: this is a base a
890
+ * `size` class has to be able to beat, and a bare class ties with one. */
891
+ :where(.sel-control) {
892
+ font: inherit;
893
+ color: var(--text);
894
+ background: var(--control);
895
+ border: var(--border-width) solid var(--border-strong);
896
+ border-radius: var(--radius);
897
+ cursor: pointer;
898
+ }
170
899
 
171
900
  .size-sm { font-size: var(--text-xs); padding: var(--space-1) var(--space-2); }
172
901
 
@@ -191,8 +920,7 @@ input, textarea, select { width: 100%; }
191
920
  same disagreement written twice. */
192
921
  textarea { resize: none; }
193
922
 
194
- button:hover:not(:disabled),
195
- a.btn:hover:not([aria-disabled='true']),
923
+ .btn:hover:not(:disabled),
196
924
  input:hover:not(:disabled),
197
925
  textarea:hover:not(:disabled),
198
926
  select:hover:not(:disabled) { border-color: var(--accent); }
@@ -200,8 +928,7 @@ select:hover:not(:disabled) { border-color: var(--accent); }
200
928
  /* `:focus-visible` on the button so a click does not leave a ring behind it,
201
929
  `:focus` on the fields because a focused field is a place you are typing and
202
930
  should say so however you got there. */
203
- button:focus-visible,
204
- a.btn:focus-visible,
931
+ .btn:focus-visible,
205
932
  input:focus,
206
933
  textarea:focus,
207
934
  select:focus { outline: none; border-color: var(--accent); }
@@ -294,14 +1021,13 @@ input[type='radio']:focus-visible { outline: none; box-shadow: var(--focus-ring)
294
1021
  input[type='checkbox']:disabled,
295
1022
  input[type='radio']:disabled { cursor: not-allowed; background: var(--panel-2); }
296
1023
 
297
- button:disabled,
298
- a.btn[aria-disabled='true'],
1024
+ .btn:disabled,
1025
+ .btn[aria-disabled='true'],
299
1026
  input:disabled,
300
1027
  textarea:disabled,
301
1028
  select:disabled { opacity: 0.45; cursor: not-allowed; }
302
1029
 
303
- button.primary,
304
- a.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
1030
+ .btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
305
1031
 
306
1032
  /* A disabled primary is not a faded primary.
307
1033
  `opacity: .45` was the whole of it, and a coloured fill at 45% is still a
@@ -309,8 +1035,7 @@ a.btn.primary { background: var(--accent); border-color: var(--accent); color: v
309
1035
  thing on the panel. That got worse when onboarding started holding people at
310
1036
  a disabled Next. So it gives up the fill entirely and becomes the shape of a
311
1037
  disabled control -- the opacity then has nothing left to do. */
312
- button.primary:disabled,
313
- a.btn.primary[aria-disabled='true'] {
1038
+ .btn.primary:disabled {
314
1039
  background: var(--panel-2);
315
1040
  border-color: var(--border);
316
1041
  color: var(--muted);
@@ -324,7 +1049,7 @@ a.btn.primary[aria-disabled='true'] {
324
1049
  line under it that travels.
325
1050
  `aria-busy` rather than a class: the attribute is what a screen reader reads,
326
1051
  and styling the thing that carries the meaning is how they stay in step. */
327
- button[aria-busy='true'] {
1052
+ .btn[aria-busy='true'] {
328
1053
  position: relative;
329
1054
  overflow: hidden;
330
1055
  cursor: progress;
@@ -337,18 +1062,18 @@ button[aria-busy='true'] {
337
1062
  something that is working, that reads as a control that failed and went
338
1063
  grey. So a busy button keeps every colour it had and takes only the loss of
339
1064
  the cursor and the press; the travelling line is what says why. */
340
- button[aria-busy='true']:disabled {
1065
+ .btn[aria-busy='true']:disabled {
341
1066
  opacity: 1;
342
1067
  cursor: progress;
343
1068
  }
344
1069
 
345
- button.primary[aria-busy='true']:disabled {
1070
+ .btn.primary[aria-busy='true']:disabled {
346
1071
  background: var(--accent);
347
1072
  border-color: var(--accent);
348
1073
  color: var(--on-accent);
349
1074
  }
350
1075
 
351
- button[aria-busy='true']::after {
1076
+ .btn[aria-busy='true']::after {
352
1077
  content: '';
353
1078
  position: absolute;
354
1079
  left: 0;
@@ -380,15 +1105,13 @@ button[aria-busy='true']::after {
380
1105
 
381
1106
  /* Quiet: no border, no fill. For the action beside the action -- Cancel next to
382
1107
  Save -- where a bordered box competes with the thing it is next to. */
383
- button.ghost,
384
- a.btn.ghost {
1108
+ .btn.ghost {
385
1109
  background: transparent;
386
1110
  border-color: transparent;
387
1111
  color: var(--muted);
388
1112
  }
389
1113
 
390
- button.ghost:hover:not(:disabled),
391
- a.btn.ghost:hover:not([aria-disabled='true']) {
1114
+ .btn.ghost:hover:not(:disabled) {
392
1115
  background: var(--panel-2);
393
1116
  border-color: transparent;
394
1117
  color: var(--text);
@@ -470,7 +1193,7 @@ a.btn.ghost:hover:not([aria-disabled='true']) {
470
1193
  header.) */
471
1194
  .row { display: flex; align-items: center; gap: var(--space-3); }
472
1195
 
473
- .grow { flex: 1; min-width: 0; }
1196
+ .ctl-grow { flex: 1; min-width: 0; }
474
1197
 
475
1198
  .mono { font-family: var(--font-mono); font-size: var(--text-sm); }
476
1199
 
@@ -534,10 +1257,17 @@ a.btn.ghost:hover:not([aria-disabled='true']) {
534
1257
  /* A pill following a name needs the gap outside its own border. Leading
535
1258
  whitespace inside the span puts it under the pill's background instead,
536
1259
  which reads as the badge touching the word it qualifies. */
537
- .row-pill { margin-left: var(--space-2); vertical-align: baseline; }
1260
+ /* A pill beside a name, and the one thing that keeps the pair working:
1261
+ `flex: none`, so the pill never shrinks and the name ellipsises instead.
1262
+ That was `rows.css`'s `.named > .pill`, which meant anybody laying out a
1263
+ name and its pills had to know a package class by name to get it. The
1264
+ variant already says "I am in a row"; it may as well say what that costs. */
1265
+ .row-pill { margin-left: var(--space-2); vertical-align: baseline; flex: none; }
538
1266
 
539
1267
  /* The tile's only control. Dim until hovered: it is chrome on someone's app. */
540
1268
  .icon-btn {
1269
+ /* Stated rather than inherited. These came from the `button, input, textarea, select` reset, which applied control styling to everything that happened to be a `<button>` -- so this rule spent its opening lines undoing it, and would break silently if that rule ever moved. It did move, to `.btn`. */
1270
+ font: inherit;
541
1271
  display: inline-flex; align-items: center; padding: var(--space-1);
542
1272
  background: none; border: 0; color: var(--muted); opacity: 0.6; cursor: pointer;
543
1273
  }
@@ -603,19 +1333,17 @@ a.btn.ghost:hover:not([aria-disabled='true']) {
603
1333
  transition: none;
604
1334
  }
605
1335
 
606
- button.primary:active:not(:disabled),
607
- a.btn.primary:active:not([aria-disabled='true']) {
1336
+ .btn.primary:active:not(:disabled) {
608
1337
  background: color-mix(in srgb, var(--text) var(--press-ink), var(--accent));
609
1338
  border-color: color-mix(in srgb, var(--text) var(--press-ink), var(--accent));
610
1339
  }
611
1340
 
612
- button.ghost:active:not(:disabled),
613
- a.btn.ghost:active:not([aria-disabled='true']) {
1341
+ .btn.ghost:active:not(:disabled) {
614
1342
  background: color-mix(in srgb, var(--text) var(--press-ink), var(--panel-2));
615
1343
  border-color: transparent;
616
1344
  }
617
1345
 
618
- button[aria-pressed='true']:active:not(:disabled) {
1346
+ .btn[aria-pressed='true']:active:not(:disabled) {
619
1347
  background: color-mix(in srgb, var(--accent) 18%, var(--panel));
620
1348
  }
621
1349
 
@@ -786,7 +1514,7 @@ input:active, textarea:active, select:active { transform: none; }
786
1514
 
787
1515
  /* The top bar carries the title, the views and the way into settings. Nothing
788
1516
  that belongs to a conversation lives here any more, so it can breathe. */
789
- .topbar .grow { flex: 1; min-width: 0; }
1517
+ .topbar .ctl-grow { flex: 1; min-width: 0; }
790
1518
 
791
1519
  /* Back on the left, Next on the right, with the gap between them doing the
792
1520
  separating -- so the button that goes forward is in the same place on both
@@ -818,6 +1546,57 @@ input:active, textarea:active, select:active { transform: none; }
818
1546
  to { opacity: 1; }
819
1547
  }
820
1548
 
1549
+ /* The states the `button` element rule used to hand every bare `<button>` in
1550
+ the package, now asked for by name.
1551
+ *
1552
+ * This is the half of the decoupling that a screenshot cannot show: `:focus-visible`
1553
+ * is not photographed, so nine components would have lost their focus ring in
1554
+ * silence. `keyboard.spec.ts` is what actually holds this.
1555
+ *
1556
+ * `.btn` has its own, in the rules above. These are the ones that are a button
1557
+ * for their behaviour and not for their appearance -- a tab, a row, a card, a
1558
+ * pager step, a grid cell, a remove cross. */
1559
+ .tab:focus-visible,
1560
+ .row-hit:focus-visible,
1561
+ .card:focus-visible,
1562
+ .ident-remove:focus-visible,
1563
+ .size-cell:focus-visible,
1564
+ .pager-step:focus-visible,
1565
+ .pager-page:focus-visible,
1566
+ .sel-control:focus-visible {
1567
+ outline: 2px solid var(--accent);
1568
+ outline-offset: 2px;
1569
+ }
1570
+
1571
+ /* `:disabled` came from the same rule. A control that cannot be pressed has to
1572
+ say so whatever element it is. */
1573
+ .tab:disabled,
1574
+ .row-hit:disabled,
1575
+ .card:disabled,
1576
+ .ident-remove:disabled,
1577
+ .size-cell:disabled,
1578
+ .pager-step:disabled,
1579
+ .pager-page:disabled,
1580
+ .sel-control:disabled {
1581
+ opacity: 0.45;
1582
+ cursor: not-allowed;
1583
+ }
1584
+
1585
+ /* The anchor form's disabled states, alongside the button's.
1586
+ *
1587
+ * `.btn` is one class on two elements, and an `<a>` has no `:disabled` -- it
1588
+ * carries `aria-disabled` and a capture-phase click block instead. Every
1589
+ * rule above that reads `:disabled` needs the attribute twin, or a disabled
1590
+ * link is a link that looks pressable and refuses silently. Folding the old
1591
+ * `a.btn` twins into `.btn` dropped these, and the baselines showed a
1592
+ * disabled link at full strength. */
1593
+ .btn.primary[aria-disabled='true'] {
1594
+ background: var(--panel-2);
1595
+ border-color: var(--border);
1596
+ color: var(--muted);
1597
+ opacity: 1;
1598
+ }
1599
+
821
1600
  /* ---- ./select.css ---- */
822
1601
  /* How big a control is, asked for rather than inherited.
823
1602
  Four sizes existed and every one of them came from a descendant selector:
@@ -833,8 +1612,7 @@ input:active, textarea:active, select:active { transform: none; }
833
1612
  /* Full width, for the one action a card or a step is about. Not a size: a
834
1613
  `block` button is the same height as any other, it just stops being a thing
835
1614
  you have to aim at. */
836
- button.block,
837
- a.btn.block { display: flex; width: 100%; }
1615
+ .btn.ctl-block { display: flex; width: 100%; }
838
1616
 
839
1617
  /* The default is `size-md`, spelled once for every control.
840
1618
  The paragraph above promised one scale for buttons, inputs and selects, and
@@ -842,13 +1620,24 @@ a.btn.block { display: flex; width: 100%; }
842
1620
  input was eight pixels taller than the button beside it and no row of the two
843
1621
  could line up. The download modal's search box against its Search button was
844
1622
  the version you could see from across the room. */
845
- button,
846
- a.btn,
847
- input,
848
- textarea,
849
- select,
1623
+ /* `:where`, so this base carries no specificity of its own.
1624
+ *
1625
+ * It was `button, input, textarea, select` -- an element selector, which
1626
+ * `.size-sm` and every other class outranked without anyone thinking about
1627
+ * it. Moving it to `.btn` to stop a tab inheriting a control's look raised it
1628
+ * to a class, so it tied with `.size-sm` and won on source order: every
1629
+ * button came out `8px 12px` whatever size it asked for. `:where` puts it
1630
+ * back to zero, which is what the element selector was silently providing. */
1631
+ :where(.btn, input, textarea, select) { padding: var(--space-2) var(--space-3); }
1632
+
1633
+ /* The select's control keeps its padding at full class weight, and after
1634
+ `.size-sm` in the cascade, because that is where it has always been: this
1635
+ file is imported after `base.css`, so a `size`d select took the size's type
1636
+ and this padding. Folding it into the `:where` above handed the padding
1637
+ back to `.size-sm` and shrank every small select by seven pixels. */
850
1638
  .sel-control { padding: var(--space-2) var(--space-3); }
851
1639
 
1640
+
852
1641
  /* Centred on the whole block -- the caption and the sentence together -- rather
853
1642
  than aligned to either end of it. `.field-row` pins to the bottom, which is
854
1643
  right for a field beside a button and wrong beside two lines of prose. */
@@ -872,16 +1661,16 @@ select,
872
1661
  .sel { position: relative; display: inline-block; }
873
1662
 
874
1663
  /* The subject of its pane rather than a field in it: full width and a size up.
875
- That was `.select-field.block` on the native element, and it carried the
1664
+ That was `.select-field.ctl-block` on the native element, and it carried the
876
1665
  reason with it -- model ids are long (`qwen2.5-coder:32b-instruct-q4_K_M`) and
877
1666
  at the narrow size they truncated mid-name. */
878
- .sel.block { display: block; width: 100%; }
1667
+ .sel.ctl-block { display: block; width: 100%; }
879
1668
 
880
- .sel.block .sel-control { padding: var(--space-2) var(--space-3); font-weight: 500; }
1669
+ .sel.ctl-block .sel-control { padding: var(--space-2) var(--space-3); font-weight: 500; }
881
1670
 
882
1671
  /* Not full width otherwise: a select as wide as its row, holding one short
883
1672
  word, reads as an input somebody forgot to fill in. */
884
- .sel:not(.block) .sel-control { width: auto; max-width: 320px; }
1673
+ .sel:not(.ctl-block) .sel-control { width: auto; max-width: 320px; }
885
1674
 
886
1675
  .sel-control {
887
1676
  display: flex;
@@ -894,9 +1683,7 @@ select,
894
1683
  background: var(--control);
895
1684
  }
896
1685
 
897
- .sel-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
898
-
899
- .sel-caret { flex: none; opacity: 0.6; transition: transform var(--dur-fast) var(--ease); }
1686
+ .sel-caret { transition: transform var(--dur-fast) var(--ease); }
900
1687
 
901
1688
  .sel[data-open] .sel-caret { transform: rotate(180deg); }
902
1689
 
@@ -905,21 +1692,7 @@ select,
905
1692
  /* State is read off React Aria's attributes: `data-open` on the root,
906
1693
  `data-focused` / `data-selected` / `data-disabled` on an item, where
907
1694
  `.open`, `[data-active]`, `.on` and `.off` used to be. */
908
- .sel-list {
909
- /* `fixed`, and in `document.body`: an ancestor that scrolls or hides its
910
- overflow would otherwise clip it, which in this app means every settings
911
- pane. Position comes from the button's measured rectangle. */
912
- /* Positioned by React Aria -- inline `top`/`left` on a portalled popover
913
- that flips when the edge is near. `position: fixed` and the measured
914
- rectangle it was placed by are gone with the hand-rolled version. */
915
- z-index: 60;
916
- max-height: 280px;
917
- overflow-y: auto;
918
- margin: 0;
919
- padding: var(--space-1);
920
- /* The same room while the list is scrolled as it has at rest. Padding at
921
- the top of a scroll container scrolls away with the content, so the row
922
- `scrollIntoView({block: 'nearest'})` brings up ended flush against the
1695
+ .sel-list { scroll-padding-block: var(--space-1); box-shadow: var(--shadow-2); })` brings up ended flush against the
923
1696
  border and read as clipped rather than as one of a set -- and this list
924
1697
  opens scrolled to whatever is chosen, so that was the ordinary state.
925
1698
  `scroll-padding` insets what counts as "in view", so it stops short by
@@ -932,18 +1705,6 @@ select,
932
1705
  box-shadow: var(--shadow-2);
933
1706
  }
934
1707
 
935
- .sel-item {
936
- display: flex;
937
- align-items: center;
938
- gap: var(--space-2);
939
- padding: var(--space-2) var(--space-2);
940
- border-radius: var(--radius-sm);
941
- cursor: pointer;
942
- white-space: nowrap;
943
- }
944
-
945
- .sel-item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
946
-
947
1708
  /* Hover and keyboard land on the same row, because they are the same idea --
948
1709
  two highlights for one cursor is how a list starts lying about where Enter
949
1710
  will go. */
@@ -955,8 +1716,6 @@ select,
955
1716
 
956
1717
  /* The tick marks the chosen row. Colour alone would not: a list where the only
957
1718
  difference is a hue is a list you cannot read past. */
958
- .sel-tick { flex: none; opacity: 0; }
959
-
960
1719
  .sel-item[data-selected] .sel-tick { opacity: 1; }
961
1720
 
962
1721
  /* The icon on a speaker's row in the open list -- `Select`'s `aside`. Last in
@@ -994,21 +1753,18 @@ select,
994
1753
 
995
1754
  /* ---- ./dangerzone.css ---- */
996
1755
  /* Destructive, and legible as such standing still. */
997
- button.danger,
998
- a.btn.danger {
1756
+ .btn.danger {
999
1757
  color: var(--bad);
1000
1758
  border-color: color-mix(in srgb, var(--bad) 45%, var(--border-strong));
1001
1759
  }
1002
1760
 
1003
- button.danger:hover:not(:disabled),
1004
- a.btn.danger:hover:not([aria-disabled='true']) {
1761
+ .btn.danger:hover:not(:disabled) {
1005
1762
  background: color-mix(in srgb, var(--bad) 12%, var(--panel));
1006
1763
  border-color: var(--bad);
1007
1764
  color: var(--bad);
1008
1765
  }
1009
1766
 
1010
- button.danger:active:not(:disabled),
1011
- a.btn.danger:active:not([aria-disabled='true']) {
1767
+ .btn.danger:active:not(:disabled) {
1012
1768
  background: color-mix(in srgb, var(--bad) 20%, var(--panel));
1013
1769
  border-color: var(--bad);
1014
1770
  }
@@ -1249,33 +2005,46 @@ a.btn.danger:active:not([aria-disabled='true']) {
1249
2005
  .callout button { flex: none; }
1250
2006
 
1251
2007
  /* ---- ./card.css ---- */
2008
+ /* `.card` is public API, not this component's private styling.
2009
+ *
2010
+ * Consumers apply it to their own markup -- the gallery's skeleton specimen
2011
+ * renders `<div className="card">` around three rows, and app-template uses
2012
+ * `card-pick`. Moving the box into `Card`'s own class list took that away
2013
+ * with no type error and no failing test in this package: it broke only
2014
+ * where somebody else had used the class, which is the worst way for an API
2015
+ * to go.
2016
+ *
2017
+ * So the rule stays and `Card` wears it. The lesson generalises to every
2018
+ * class in this package that a consumer can reach: a component may draw
2019
+ * itself with utilities only where its class is genuinely private. */
1252
2020
  .card {
2021
+ display: block;
2022
+ width: 100%;
2023
+ box-sizing: border-box;
2024
+ font: inherit;
2025
+ color: var(--text);
2026
+ text-align: left;
1253
2027
  background: var(--panel);
1254
2028
  border: var(--border-width) solid var(--border);
1255
2029
  border-radius: var(--radius-md);
1256
- padding: var(--space-3) var(--space-3);
2030
+ padding: var(--space-3);
1257
2031
  margin-bottom: var(--space-3);
1258
2032
  }
1259
2033
 
2034
+ /* What is left once `Card` draws its own box: the tones, the pick and
2035
+ selected states, and the rules that reach from the card into what it
2036
+ holds. `:has(.card-icon) .card-inline` is a relationship, and a tone is a
2037
+ `color-mix`; neither is a property on one element. */
2038
+
1260
2039
  /* Header, when there is one. The title and the line under it were retyped at
1261
2040
  every call site and no two agreed on the gap between them. */
1262
- .card-head { display: flex; align-items: flex-start; gap: var(--space-3); }
1263
-
1264
- .card-head:not(:last-child) { margin-bottom: var(--space-3); }
1265
-
1266
- .card-headings { min-width: 0; flex: 1; }
1267
-
1268
2041
  /* The card owns the space between the things it holds. Without this a callout
1269
2042
  and the button under it sit edge to edge, and every caller has to remember a
1270
2043
  margin on whichever child happens to be second. */
1271
- .card-body { display: grid; gap: var(--space-3); }
1272
-
1273
2044
  /* The card's own control, on the card's padding rather than indented into its
1274
2045
  body. Centred against the whole heading block, not against the title -- a
1275
2046
  switch aligned to the first line drifts upward the moment the description
1276
2047
  runs to two. */
1277
- .card-action { flex: none; align-self: center; display: flex; align-items: center; }
1278
-
1279
2048
  /* A row inside a card that lines up with the header rather than indenting past
1280
2049
  it: same left edge as the title, same right edge as the action. Its label is
1281
2050
  a quiet caption, not a second heading -- the card already has one, and two
@@ -1322,17 +2091,6 @@ a.btn.danger:active:not([aria-disabled='true']) {
1322
2091
  color: var(--muted);
1323
2092
  }
1324
2093
 
1325
- /* A card that is itself the choice. Reset first, because it is a `<button>`
1326
- now and inherits a control's padding and centring. */
1327
- button.card {
1328
- display: block;
1329
- width: 100%;
1330
- text-align: left;
1331
- font: inherit;
1332
- color: inherit;
1333
- padding: var(--space-3);
1334
- }
1335
-
1336
2094
  /* Variants, because a card is doing four jobs and looked like one.
1337
2095
  A search result you can act on, a thing already installed, something that
1338
2096
  went wrong, and a heading with rows under it are not the same object, and
@@ -1375,6 +2133,20 @@ button.card {
1375
2133
 
1376
2134
  .modal-body .card { background: var(--panel-2); }
1377
2135
 
2136
+ /* Selected, which used to arrive from `button[aria-pressed='true']`.
2137
+ *
2138
+ * A card marks its choice with `aria-pressed`, and the tint came from the
2139
+ * element rule every bare `<button>` in the package was picking up. That rule
2140
+ * belongs to `Button` now, so the card says what being chosen looks like --
2141
+ * which is the right place for it anyway: a chosen card is a card's idea, not
2142
+ * a button's. */
2143
+ .card[aria-pressed='true'] {
2144
+ border-color: var(--accent);
2145
+ background: color-mix(in srgb, var(--accent) 10%, var(--panel));
2146
+ }
2147
+
2148
+ .card[aria-pressed='true']:hover { border-color: var(--accent); }
2149
+
1378
2150
  /* ---- ./toggle.css ---- */
1379
2151
  .card-action .switch-row { padding: 0; gap: var(--space-2); }
1380
2152
 
@@ -1925,71 +2697,21 @@ input[type='radio']:active:not(:disabled) {
1925
2697
 
1926
2698
  .row-hit:focus-visible { outline: none; box-shadow: var(--focus-ring); }
1927
2699
 
1928
- /* ---- ./empty.css ---- */
1929
- /* `Empty`: a list with nothing in it.
1930
-
1931
- Quieter than `.empty`, which is a whole-screen state at `--space-10`. These
1932
- sit inside a card that already has a heading and a description, so the same
1933
- padding would leave a card that is mostly air about nothing.
1934
-
1935
- Dashed rather than solid: a solid border reads as a thing, and the point of
1936
- this box is that there is no thing. It marks out where the list will be. */
1937
- .nothing {
1938
- display: grid;
1939
- justify-items: center;
1940
- gap: var(--space-2);
1941
- padding: var(--space-5) var(--space-4);
1942
- border: var(--border-width) dashed var(--border-strong);
1943
- border-radius: var(--radius-md);
1944
- text-align: center;
1945
- }
1946
-
1947
- .nothing-mark { color: var(--muted); line-height: 0; }
1948
-
1949
- /* The same box with a figure in it, for a whole surface rather than a slot in
1950
- a card. Two things change and both follow from the size: the dashed outline
1951
- goes, because an outline marks out where a list will be and a window-sized
1952
- dashed rectangle reads as a layout that failed rather than a view with
1953
- nothing on it; and the box centres itself in whatever it was dropped into,
1954
- which is what makes it land in the middle of an empty dashboard rather than
1955
- under the header. */
1956
- .nothing-surface {
1957
- border: none;
1958
- padding: var(--space-10) var(--space-4);
1959
- gap: var(--space-3);
1960
- margin: auto;
1961
- }
1962
-
1963
- /* `currentColor` reaches the figure through the two rules the illustrations
1964
- ship with, so the colour is set here and the drawing follows it -- the same
1965
- grey as the sentence under it, in every theme.
1966
- Two classes, not one: `.illo` sets `color: inherit` further down this file,
1967
- which beats a single-class rule written earlier and drew the figure in full
1968
- text black beside a muted sentence. */
1969
- .nothing-surface .nothing-figure { color: var(--muted); }
1970
-
1971
- /* `--text-sm`, not the `--text-xs` these lines used to be: it is a sentence
1972
- somebody is meant to read, not a caption qualifying something else. */
1973
- .nothing-said { margin: 0; color: var(--muted); font-size: var(--text-sm); line-height: 1.5; max-width: 46ch; }
1974
-
1975
- .nothing-act { margin-top: var(--space-1); }
1976
-
1977
2700
  /* ---- ./progress.css ---- */
1978
2701
  /* progress ----------------------------------------------------------- */
1979
2702
 
1980
- /* The bar sat 4px under its own label and flush against whatever was above
2703
+ /* The bar itself: a track with a child it fills, an animation and a
2704
+ reduced-motion answer. A utility is a property on one element, so none of
2705
+ that is one -- `Progress` draws its own wrapper and head with utilities and
2706
+ this is what is left.
2707
+
2708
+ The bar sat 4px under its own label and flush against whatever was above
1981
2709
  the whole block, so a download row read as four lines of text with a rule
1982
2710
  drawn through the last one. The label is a caption for the bar and stays
1983
2711
  close to it; the block as a whole is a separate statement from the row it
1984
2712
  follows. */
1985
- .progress { display: grid; gap: var(--space-1); }
1986
-
1987
2713
  .card > .progress { margin-top: var(--space-3); }
1988
2714
 
1989
- .progress-head { display: flex; align-items: baseline; gap: var(--space-2); font-size: var(--text-sm); }
1990
-
1991
- .progress-detail { color: var(--muted); font-size: var(--text-xs); }
1992
-
1993
2715
  .bar { height: 6px; background: var(--panel-2); border-radius: var(--radius-pill); overflow: hidden; }
1994
2716
 
1995
2717
  .bar > i {
@@ -2023,6 +2745,14 @@ input[type='radio']:active:not(:disabled) {
2023
2745
  .tabs { position: relative; display: flex; gap: var(--space-1); }
2024
2746
 
2025
2747
  .tab {
2748
+ /* Layout the `button` element rule used to supply. A tab is a button for its behaviour, not its look, so it now says what it wants instead of taking a control's arrangement and undoing the rest. */
2749
+ display: inline-flex;
2750
+ align-items: center;
2751
+ justify-content: center;
2752
+ gap: var(--space-2);
2753
+ /* Stated rather than inherited. These came from the `button, input, textarea, select` reset, which applied control styling to everything that happened to be a `<button>` -- so this rule spent its opening lines undoing it, and would break silently if that rule ever moved. It did move, to `.btn`. */
2754
+ font: inherit;
2755
+ cursor: pointer;
2026
2756
  position: relative;
2027
2757
  z-index: 1;
2028
2758
  background: transparent;
@@ -2179,6 +2909,38 @@ input[type='radio']:active:not(:disabled) {
2179
2909
  .toast[data-exiting] { animation: none; }
2180
2910
  }
2181
2911
 
2912
+ /* The dismiss control, in its own words.
2913
+ *
2914
+ * It was `className="ghost size-sm"` -- `Button`'s vocabulary, borrowed from
2915
+ * a component that does not render a `Button`. That only ever worked because
2916
+ * `.ghost` and `.size-sm` were element-and-class rules any `<button>` could
2917
+ * pick up, which is the coupling this pass is removing. Nothing photographed
2918
+ * it, either: the toast is transient and the close button is not in any
2919
+ * baseline, so this would have gone quiet. */
2920
+ .toast-close {
2921
+ display: inline-flex;
2922
+ align-items: center;
2923
+ justify-content: center;
2924
+ font: inherit;
2925
+ font-size: var(--text-xs);
2926
+ padding: var(--space-1) var(--space-2);
2927
+ background: transparent;
2928
+ border: var(--border-width) solid transparent;
2929
+ border-radius: var(--radius);
2930
+ color: var(--muted);
2931
+ cursor: pointer;
2932
+ }
2933
+
2934
+ .toast-close:hover:not(:disabled) {
2935
+ background: var(--panel-2);
2936
+ color: var(--text);
2937
+ }
2938
+
2939
+ .toast-close:focus-visible {
2940
+ outline: 2px solid var(--accent);
2941
+ outline-offset: 2px;
2942
+ }
2943
+
2182
2944
  /* ---- ./modal.css ---- */
2183
2945
  /* modal --------------------------------------------------------------- */
2184
2946
 
@@ -2219,13 +2981,6 @@ input[type='radio']:active:not(:disabled) {
2219
2981
  Not `display: contents`, which was the first attempt. An element with no box
2220
2982
  cannot take focus, so React Aria's initial focus fell through to the first
2221
2983
  input and Escape -- handled on the section -- stopped closing the window. */
2222
- .modal-dialog {
2223
- display: flex;
2224
- flex-direction: column;
2225
- min-height: 0;
2226
- flex: 1;
2227
- }
2228
-
2229
2984
  .modal {
2230
2985
  background: var(--panel);
2231
2986
  border: 1px solid var(--border);
@@ -2276,18 +3031,9 @@ input[type='radio']:active:not(:disabled) {
2276
3031
  laid out for the bare `<strong>` it used to hold -- 86px tall with them,
2277
3032
  41px without, on a head whose padding is 12 over 8. `font: inherit` so the
2278
3033
  `<strong>` rule above is the one that sets the size. */
2279
- .modal-title { margin: 0; font: inherit; }
2280
-
2281
3034
  /* Under the title, on the header's padding rather than the body's, so it reads
2282
3035
  as part of the heading rather than as the first line of content. */
2283
- .modal-desc {
2284
- margin: 0;
2285
- padding: 0 var(--space-4) var(--space-3);
2286
- color: var(--muted);
2287
- font-size: var(--text-sm);
2288
- line-height: 1.5;
2289
- max-width: 62ch;
2290
- }
3036
+ .modal-desc { line-height: 1.5; }
2291
3037
 
2292
3038
  .modal-body { padding: var(--space-2) var(--space-4) var(--space-3); overflow-y: auto; }
2293
3039
 
@@ -2647,7 +3393,8 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
2647
3393
  buttons only, so a button in a row stood 25px beside a 35px select and no
2648
3394
  size class could ask for the select's height. Type from the row, box from
2649
3395
  the scale, and the two line up; an explicit `size-*` still wins. */
2650
- :where(.set-row) button, :where(.set-row) .slider-box { font-size: var(--text-xs); }
3396
+ :where(.set-row) button,
3397
+ :where(.set-row) .slider-box { font-size: var(--text-xs); }
2651
3398
 
2652
3399
  /* ---- ./checkbox.css ---- */
2653
3400
  /* A row you choose. There is no checkbox in it.
@@ -2825,30 +3572,28 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
2825
3572
  one. The cell is bold because it is a heading; what hangs under it is not,
2826
3573
  and inherited the weight -- so a pinned version read as loud as the thing it
2827
3574
  versions. */
2828
- .table th .set-hint { font-weight: 400; }
2829
-
2830
- .table-scroll { overflow-x: auto; }
3575
+ .data-table th .set-hint { font-weight: 400; }
2831
3576
 
2832
- .table {
3577
+ .data-table {
2833
3578
  width: 100%;
2834
3579
  border-collapse: collapse;
2835
3580
  font-size: var(--text-sm);
2836
3581
  }
2837
3582
 
2838
- .table th,
2839
- .table td {
3583
+ .data-table th,
3584
+ .data-table td {
2840
3585
  text-align: left;
2841
3586
  padding: var(--space-2) var(--space-3);
2842
3587
  border-bottom: var(--border-width) solid var(--border);
2843
3588
  }
2844
3589
 
2845
- .table th:first-child,
2846
- .table td:first-child { padding-left: 0; }
3590
+ .data-table th:first-child,
3591
+ .data-table td:first-child { padding-left: 0; }
2847
3592
 
2848
- .table th:last-child,
2849
- .table td:last-child { padding-right: 0; }
3593
+ .data-table th:last-child,
3594
+ .data-table td:last-child { padding-right: 0; }
2850
3595
 
2851
- .table thead th {
3596
+ .data-table thead th {
2852
3597
  font-size: var(--text-xs);
2853
3598
  font-weight: var(--weight-strong);
2854
3599
  text-transform: uppercase;
@@ -2859,40 +3604,31 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
2859
3604
 
2860
3605
  /* The row's own name: a `<th scope="row">`, so it carries weight rather than
2861
3606
  reading as one more cell. */
2862
- .table tbody th { font-weight: var(--weight-strong); }
3607
+ .data-table tbody th { font-weight: var(--weight-strong); }
2863
3608
 
2864
- .table tbody tr:last-child th,
2865
- .table tbody tr:last-child td { border-bottom: none; }
3609
+ .data-table tbody tr:last-child th,
3610
+ .data-table tbody tr:last-child td { border-bottom: none; }
2866
3611
 
2867
3612
  /* Numbers compare by column, so they align right and hold their tracks --
2868
3613
  proportional digits shift the alignment by a pixel per glyph, which is
2869
3614
  exactly the comparison the column was for. */
2870
- .table .num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
3615
+ .data-table .num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
2871
3616
 
2872
- .table thead th[style*='right'] { text-align: right; }
3617
+ .data-table thead th[style*='right'] { text-align: right; }
2873
3618
 
2874
3619
  /* ---- ./sizegrid.css ---- */
2875
- /* Tile size, picked as a rectangle rather than read as a notation. */
2876
- .size-grid-wrap { display: flex; align-items: center; gap: var(--space-2); }
2877
-
2878
- .size-grid { display: grid; gap: var(--space-1); }
2879
-
2880
- .size-cell {
2881
- width: 18px; height: 14px; padding: 0;
2882
- border: 1px solid var(--border); border-radius: var(--radius-sm);
2883
- background: var(--panel-2); cursor: pointer;
3620
+ /* What is left of the size grid once it draws itself.
3621
+ *
3622
+ * `on` follows the pointer; `chosen` is what is actually saved, shown only
3623
+ * when nothing is hovered so the two can never contradict each other.
3624
+ * `chosen` is a border colour and says so at the call site; `on` is a fill
3625
+ * mixed from the accent, and `color-mix` in an arbitrary value is less
3626
+ * readable than the rule it replaces. */
3627
+ .size-cell.on {
3628
+ border-color: var(--accent);
3629
+ background: color-mix(in srgb, var(--accent) 45%, transparent);
2884
3630
  }
2885
3631
 
2886
- /* `on` follows the pointer; `chosen` is what is actually saved, shown only
2887
- when nothing is hovered so the two can never contradict each other. */
2888
- .size-cell.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 45%, transparent); }
2889
-
2890
- .size-cell.chosen { border-color: var(--accent); }
2891
-
2892
- .size-cell:disabled { cursor: default; opacity: 0.5; }
2893
-
2894
- .size-ceiling { opacity: 0.55; }
2895
-
2896
3632
  /* ---- ./tooltip.css ---- */
2897
3633
  /* A callout: something worth knowing, with nothing to answer. Distinct from a
2898
3634
  `?`, which is an offer to read more, and from a banner, which is a problem.
@@ -2901,36 +3637,15 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
2901
3637
  /* An info-toned `.callout`, spelled the old way. Identical to it now rather than
2902
3638
  nearly identical to it, which is the whole point of the consolidation: the
2903
3639
 
2904
- /* The `?` beside a setting, and the box it shows on hover. Built like the
3640
+ /* What is left once `Tooltip` draws its own mark: the hover and focus that
3641
+ reach the mark from its parent, and the tip -- whose closed and open
3642
+ states are three rules the cascade resolves in order, with
3643
+ `data-entering` and `data-exiting` around the transition. None of that is
3644
+ a property on one element.
3645
+
3646
+ The `?` beside a setting, and the box it shows on hover. Built like the
2905
3647
  status dot's tooltip and for the same reasons: `title` waits a second and a
2906
3648
  half, renders newlines however it likes, and cannot be styled. */
2907
- .explain {
2908
- position: relative;
2909
- flex: none;
2910
- display: inline-flex;
2911
- align-items: center;
2912
- /* The heading it follows is tracked out, which leaves the last letter's
2913
- spacing sitting between them and reads as none at all. */
2914
- margin-left: var(--space-2);
2915
- cursor: help;
2916
- /* Headings here are uppercase and tracked out, and one of these sits inside
2917
- a heading. */
2918
- text-transform: none;
2919
- letter-spacing: normal;
2920
- font-weight: 400;
2921
- }
2922
-
2923
- .explain-mark {
2924
- width: 18px;
2925
- height: 18px;
2926
- border-radius: 50%;
2927
- border: 1px solid var(--border);
2928
- color: var(--muted);
2929
- font-size: var(--text-xs);
2930
- line-height: 16px;
2931
- text-align: center;
2932
- }
2933
-
2934
3649
  .explain:hover .explain-mark,
2935
3650
  .explain:focus-visible .explain-mark { color: var(--text); border-color: var(--accent); }
2936
3651
 
@@ -3031,6 +3746,18 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3031
3746
  a shirt that lets the arm behind it show through reads as a hole. */
3032
3747
  .illo .paper { fill: var(--illo-paper); }
3033
3748
 
3749
+ /* The figure in a whole-surface `Empty`, drawn in the muted grey of the
3750
+ sentence under it.
3751
+ *
3752
+ * A utility cannot do this, and the reason is worth keeping: `.illo` sets
3753
+ * `color: inherit` and is unlayered, while every Tailwind utility lives in
3754
+ * `@layer utilities` -- and an unlayered declaration beats a layered one
3755
+ * whatever the specificity. So `text-muted` on the figure lost, silently,
3756
+ * and the shirt came out full text black beside a muted sentence. The
3757
+ * original rule already carried a note about needing two classes to beat
3758
+ * `.illo`; layers make that impossible rather than merely fiddly. */
3759
+ .nothing-figure { color: var(--muted); }
3760
+
3034
3761
  /* ---- ./field.css ---- */
3035
3762
  /* fields --------------------------------------------------------------- */
3036
3763
 
@@ -3129,7 +3856,7 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3129
3856
  .adorned > .adorned-mark,
3130
3857
  .adorned > .adorned-end { grid-area: 1 / 1; }
3131
3858
 
3132
- .adorned.block { width: 100%; }
3859
+ .adorned.ctl-block { width: 100%; }
3133
3860
 
3134
3861
  .adorned > .adorned-mark {
3135
3862
  justify-self: start;
@@ -3182,6 +3909,31 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3182
3909
 
3183
3910
  .adorned-lg .adorned-clear { padding: var(--space-2); }
3184
3911
 
3912
+ /* The eye and the clear button stop borrowing `ghost`.
3913
+ *
3914
+ * Both were `className="icon-btn ghost …"` -- reaching into `Button`'s class
3915
+ * vocabulary from another component, which worked only because `.ghost` was
3916
+ * written as `button.ghost` and they happen to be `<button>`s. It is `.btn.ghost`
3917
+ * now, so the borrowing stopped matching and `.icon-btn`'s panel background
3918
+ * came through: the baselines showed a filled tile where there had been a bare
3919
+ * glyph.
3920
+ *
3921
+ * Stated here instead. What they actually wanted from `ghost` is two lines,
3922
+ * and saying them is cheaper than either component knowing what a button's
3923
+ * classes are called. */
3924
+ .secret-eye,
3925
+ .adorned-clear {
3926
+ background: transparent;
3927
+ border-color: transparent;
3928
+ color: var(--muted);
3929
+ }
3930
+
3931
+ .secret-eye:hover:not(:disabled),
3932
+ .adorned-clear:hover:not(:disabled) {
3933
+ background: var(--panel-2);
3934
+ color: var(--text);
3935
+ }
3936
+
3185
3937
  /* ---- ./tour.css ---- */
3186
3938
  /* tour ----------------------------------------------------------------- */
3187
3939
 
@@ -3249,136 +4001,32 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3249
4001
  anchored surfaces (`.pop`, `.menu-sheet`, `.cmd`) sit over everything and a
3250
4002
  rule of equal specificity that came earlier would lose to whatever the
3251
4003
  sheets they cover happen to set. */
3252
- /* ---- ./kbd.css ---- */
3253
- /* One rule where there were two identical ones, in `menu.css` and
3254
- `command.css`. Both keep their class as well, so `Menu` and `Command` are
3255
- `.kbd .menu-key` and `.kbd .cmd-key` now: the declarations are the same, so
3256
- nothing moves, and the context classes stay available for a rule that ever
3257
- needs to tell the two apart. */
3258
- .kbd {
3259
- flex: none;
3260
- font-family: var(--font-mono);
3261
- font-size: var(--text-2xs);
3262
- color: var(--muted);
3263
- letter-spacing: var(--tracking);
3264
- }
3265
-
3266
4004
  /* ---- ./stat.css ---- */
3267
- /* A figure with furniture. `--space-1` between the three lines because they
3268
- are one object read top to bottom, not three paragraphs. */
3269
- .stat {
3270
- display: grid;
3271
- gap: var(--space-1);
3272
- padding: var(--space-4);
3273
- border: var(--border-width) solid var(--border);
3274
- border-radius: var(--radius-md);
3275
- background: var(--panel);
3276
- }
3277
-
3278
- /* Inside something already bordered. A bordered tile in a bordered row is two
3279
- boxes saying one thing. */
3280
- .stat-bare {
3281
- padding: 0;
3282
- border: 0;
3283
- border-radius: 0;
3284
- background: none;
3285
- gap: 0;
3286
- line-height: 1.25;
3287
- }
3288
-
3289
- .stat > p { margin: 0; }
4005
+ /* What the utilities on `Stat` cannot say: a tone on the tile colouring the
4006
+ figure inside it. An attribute on the parent selecting a child is a
4007
+ relationship; a utility is a property on one element.
3290
4008
 
3291
- .stat-label {
3292
- color: var(--muted);
3293
- font-size: var(--text-xs);
3294
- }
3295
-
3296
- /* Tabular figures are the point: a column of numbers is compared by its
3297
- digits' positions, and proportional ones move them per row. */
3298
- .stat-value {
3299
- font-size: var(--text-lg);
3300
- font-weight: var(--weight-strong);
3301
- font-variant-numeric: tabular-nums;
3302
- }
3303
-
3304
- .stat-bare .stat-value { font-size: var(--text-sm); }
3305
-
3306
- .stat-note {
3307
- color: var(--muted);
3308
- font-size: var(--text-xs);
3309
- }
3310
-
3311
- /* Only the figure takes the colour. Colouring the label too makes the tile a
3312
- status box, and a tone here is a remark about the number. */
4009
+ Only the figure takes the colour. Colouring the label too makes the tile a
4010
+ status box, where a tone here is a remark about the number. */
3313
4011
  .stat[data-tone='info'] .stat-value { color: var(--info); }
3314
4012
  .stat[data-tone='good'] .stat-value { color: var(--good); }
3315
4013
  .stat[data-tone='warn'] .stat-value { color: var(--warn); }
3316
4014
  .stat[data-tone='bad'] .stat-value { color: var(--bad); }
3317
4015
 
3318
4016
  /* ---- ./shell.css ---- */
3319
- /* The frame. Layout only: every colour and every step is a token, and the
3320
- page inside it owns its own spacing. */
3321
- .shell {
3322
- display: flex;
3323
- flex-direction: column;
3324
- min-height: 100vh;
3325
- background: var(--bg);
3326
- color: var(--text);
3327
- }
3328
-
3329
- .shell-head {
3330
- border-bottom: var(--border-width) solid var(--border);
3331
- background: var(--panel);
3332
- /* `flex: none` so a long page does not squeeze the header. */
3333
- flex: none;
3334
- }
3335
-
3336
- /* The measure, said once. `--shell-measure` rather than a literal so an app
3337
- with a different one sets a variable instead of overriding three rules and
3338
- missing the fourth -- which is how manage ended up with `head-inner-wide`
3339
- and `main-wide` as separate classes. */
4017
+ /* The measure, and only the measure.
4018
+ *
4019
+ * `Shell` draws itself with utilities -- it is layout and nothing else. What
4020
+ * stays here is the one value the header band, the nav band and the main band
4021
+ * all have to agree on: a custom property, so an app changes it in one place
4022
+ * rather than overriding three rules and missing the fourth. That is how
4023
+ * manage ended up carrying `head-inner-wide` and `main-wide` as separate
4024
+ * classes it had to remember together. */
3340
4025
  .shell {
3341
4026
  --shell-measure: 64rem;
3342
4027
  --shell-measure-wide: 90rem;
3343
4028
  }
3344
4029
 
3345
- .shell-head-inner,
3346
- .shell-nav,
3347
- .shell-main {
3348
- width: 100%;
3349
- max-width: var(--shell-measure);
3350
- margin-inline: auto;
3351
- padding-inline: var(--space-4);
3352
- }
3353
-
3354
- .shell-wide { max-width: var(--shell-measure-wide); }
3355
-
3356
- .shell-head-inner {
3357
- display: flex;
3358
- align-items: center;
3359
- justify-content: space-between;
3360
- gap: var(--space-4);
3361
- padding-block: var(--space-3);
3362
- min-width: 0;
3363
- }
3364
-
3365
- .shell-who {
3366
- display: flex;
3367
- align-items: center;
3368
- gap: var(--space-3);
3369
- min-width: 0;
3370
- }
3371
-
3372
- .shell-nav { padding-bottom: var(--space-2); }
3373
-
3374
- .shell-main {
3375
- flex: 1;
3376
- padding-block: var(--space-6);
3377
- /* `min-width: 0` because a flex child will not shrink below its content,
3378
- and one wide table inside then pushes the whole page sideways. */
3379
- min-width: 0;
3380
- }
3381
-
3382
4030
  /* The label beside the theme control, when it is drawn. */
3383
4031
  .theme-switch {
3384
4032
  display: flex;
@@ -3644,27 +4292,13 @@ body.splitting * { cursor: inherit !important; }
3644
4292
  }
3645
4293
 
3646
4294
  /* ---- ./menu.css ---- */
3647
- /* A list of verbs, on the same sheet as every other anchored surface. */
3648
- .menu-sheet {
3649
- background: var(--panel);
3650
- border: var(--border-width) solid var(--border);
3651
- border-radius: var(--radius-md);
3652
- box-shadow: var(--shadow-2);
3653
- min-width: 12rem;
3654
- max-width: min(20rem, calc(100vw - var(--space-6)));
3655
- max-height: 24rem;
3656
- overflow: auto;
3657
- overscroll-behavior: contain;
3658
- z-index: 60;
3659
- }
3660
-
3661
- .menu-list {
3662
- padding: var(--space-1);
3663
- outline: none;
3664
- display: grid;
3665
- gap: var(--border-width);
3666
- }
4295
+ /* What is left once `Menu` draws its sheet and its list: the rows, whose
4296
+ `data-focused`, `data-pressed` and danger states React Aria stamps on
4297
+ them, and the rules that reach from a row into what it holds. The
4298
+ `box-shadow` stays on the sheet too -- `--shadow-2` is a token, and a
4299
+ utility would have to inline it. */
3667
4300
 
4301
+ /* A list of verbs, on the same sheet as every other anchored surface. */
3668
4302
  .menu-item {
3669
4303
  display: flex;
3670
4304
  align-items: center;
@@ -3703,14 +4337,6 @@ body.splitting * { cursor: inherit !important; }
3703
4337
  /* The binding, set in the mono face at the size of the small print. Right of
3704
4338
  the row and never the reason the row is wide: `flex: none` with the label
3705
4339
  taking the slack. */
3706
- .menu-key {
3707
- flex: none;
3708
- font-family: var(--font-mono);
3709
- font-size: var(--text-2xs);
3710
- color: var(--muted);
3711
- letter-spacing: var(--tracking);
3712
- }
3713
-
3714
4340
  .menu-more { flex: none; color: var(--muted); }
3715
4341
 
3716
4342
  .menu-rule {
@@ -3746,6 +4372,8 @@ body.splitting * { cursor: inherit !important; }
3746
4372
  .menu-sheet[data-exiting] { animation: none; }
3747
4373
  }
3748
4374
 
4375
+ .menu-sheet { box-shadow: var(--shadow-2); }
4376
+
3749
4377
  /* ---- ./command.css ---- */
3750
4378
  /* The command palette: a field over the app and a list under it. */
3751
4379
  .cmd-scrim {
@@ -3863,14 +4491,6 @@ body.splitting * { cursor: inherit !important; }
3863
4491
  white-space: nowrap;
3864
4492
  }
3865
4493
 
3866
- .cmd-key {
3867
- flex: none;
3868
- font-family: var(--font-mono);
3869
- font-size: var(--text-2xs);
3870
- color: var(--muted);
3871
- letter-spacing: var(--tracking);
3872
- }
3873
-
3874
4494
  .cmd-empty {
3875
4495
  margin: 0;
3876
4496
  padding: var(--space-6) var(--space-4);
@@ -3898,23 +4518,14 @@ body.splitting * { cursor: inherit !important; }
3898
4518
  }
3899
4519
 
3900
4520
  /* ---- ./pagination.css ---- */
3901
- /* Moving through a list that does not fit, and saying how far in you are. */
3902
- .pager {
3903
- display: flex;
3904
- align-items: center;
3905
- justify-content: space-between;
3906
- gap: var(--space-4);
3907
- flex-wrap: wrap;
3908
- padding: var(--space-2) 0;
3909
- font-size: var(--text-xs);
3910
- color: var(--muted);
3911
- }
4521
+ /* What is left once `Pagination` draws its own frame: the step and page
4522
+ buttons, whose hover, focus-visible and current states are pseudo-classes
4523
+ on two selectors at once, and `.pager-count strong`, which reaches into
4524
+ text the caller passed. */
3912
4525
 
3913
- .pager-count { margin: 0; }
4526
+ /* Moving through a list that does not fit, and saying how far in you are. */
3914
4527
  .pager-count strong { color: var(--text); font-weight: var(--weight-strong); }
3915
4528
 
3916
- .pager-controls { display: flex; align-items: center; gap: var(--space-1); }
3917
-
3918
4529
  .pager-step,
3919
4530
  .pager-page {
3920
4531
  display: inline-flex;
@@ -4106,6 +4717,8 @@ body.splitting * { cursor: inherit !important; }
4106
4717
  }
4107
4718
 
4108
4719
  .ident-remove {
4720
+ /* Stated rather than inherited. These came from the `button, input, textarea, select` reset, which applied control styling to everything that happened to be a `<button>` -- so this rule spent its opening lines undoing it, and would break silently if that rule ever moved. It did move, to `.btn`. */
4721
+ font: inherit;
4109
4722
  flex: none;
4110
4723
  display: inline-flex;
4111
4724
  align-items: center;