@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.
package/dist/tf.css CHANGED
@@ -278,6 +278,705 @@
278
278
  *
279
279
  * `scripts/copy-css.mjs` flattens these into one file at build time.
280
280
  */
281
+ /* ---- ./_tailwind.built.css ---- */
282
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
283
+ @layer properties;
284
+ @layer theme, base, components, utilities;
285
+ @layer theme {
286
+ :root, :host {
287
+ --font-sans: var(--font-sans);
288
+ --font-mono: var(--font-mono);
289
+ --text-xs: var(--text-xs);
290
+ --text-xs--line-height: inherit;
291
+ --text-sm: var(--text-sm);
292
+ --text-sm--line-height: inherit;
293
+ --text-base: var(--text-base);
294
+ --text-lg: var(--text-lg);
295
+ --text-lg--line-height: inherit;
296
+ --text-xl: var(--text-xl);
297
+ --font-weight-normal: var(--weight);
298
+ --tracking-normal: var(--tracking);
299
+ --radius-sm: var(--radius-sm);
300
+ --radius-md: var(--radius-md);
301
+ --radius-lg: var(--radius-lg);
302
+ --ease-out: cubic-bezier(0, 0, 0.2, 1);
303
+ --spacing-1: var(--space-1);
304
+ --spacing-2: var(--space-2);
305
+ --spacing-3: var(--space-3);
306
+ --spacing-4: var(--space-4);
307
+ --spacing-5: var(--space-5);
308
+ --spacing-6: var(--space-6);
309
+ --spacing-10: var(--space-10);
310
+ --text-2xs--line-height: inherit;
311
+ --text-2xs: var(--text-2xs);
312
+ --text-md: var(--text-md);
313
+ --font-weight-strong: var(--weight-strong);
314
+ --radius-pill: var(--radius-pill);
315
+ --color-text: var(--text);
316
+ --color-muted: var(--muted);
317
+ --color-accent: var(--accent);
318
+ --color-border: var(--border);
319
+ --color-border-strong: var(--border-strong);
320
+ }
321
+ }
322
+ @layer utilities {
323
+ .collapse {
324
+ visibility: collapse;
325
+ }
326
+ .invisible {
327
+ visibility: hidden;
328
+ }
329
+ .visible {
330
+ visibility: visible;
331
+ }
332
+ .sr-only {
333
+ position: absolute;
334
+ width: 1px;
335
+ height: 1px;
336
+ padding: 0;
337
+ margin: -1px;
338
+ overflow: hidden;
339
+ clip-path: inset(50%);
340
+ white-space: nowrap;
341
+ border-width: 0;
342
+ }
343
+ .absolute {
344
+ position: absolute;
345
+ }
346
+ .fixed {
347
+ position: fixed;
348
+ }
349
+ .relative {
350
+ position: relative;
351
+ }
352
+ .sticky {
353
+ position: sticky;
354
+ }
355
+ .z-\[60\] {
356
+ z-index: 60;
357
+ }
358
+ .container {
359
+ width: 100%;
360
+ @media (width >= 40rem) {
361
+ max-width: 40rem;
362
+ }
363
+ @media (width >= 48rem) {
364
+ max-width: 48rem;
365
+ }
366
+ @media (width >= 64rem) {
367
+ max-width: 64rem;
368
+ }
369
+ @media (width >= 80rem) {
370
+ max-width: 80rem;
371
+ }
372
+ @media (width >= 96rem) {
373
+ max-width: 96rem;
374
+ }
375
+ }
376
+ .m-0 {
377
+ margin: 0px;
378
+ }
379
+ .m-auto {
380
+ margin: auto;
381
+ }
382
+ .mx-auto {
383
+ margin-inline: auto;
384
+ }
385
+ .mt-1 {
386
+ margin-top: var(--spacing-1);
387
+ }
388
+ .mb-3 {
389
+ margin-bottom: var(--spacing-3);
390
+ }
391
+ .ml-2 {
392
+ margin-left: var(--spacing-2);
393
+ }
394
+ .block {
395
+ display: block;
396
+ }
397
+ .contents {
398
+ display: contents;
399
+ }
400
+ .flex {
401
+ display: flex;
402
+ }
403
+ .grid {
404
+ display: grid;
405
+ }
406
+ .hidden {
407
+ display: none;
408
+ }
409
+ .inline {
410
+ display: inline;
411
+ }
412
+ .inline-flex {
413
+ display: inline-flex;
414
+ }
415
+ .table {
416
+ display: table;
417
+ }
418
+ .h-\[14px\] {
419
+ height: 14px;
420
+ }
421
+ .h-\[18px\] {
422
+ height: 18px;
423
+ }
424
+ .max-h-\[24rem\] {
425
+ max-height: 24rem;
426
+ }
427
+ .max-h-\[280px\] {
428
+ max-height: 280px;
429
+ }
430
+ .min-h-0 {
431
+ min-height: 0px;
432
+ }
433
+ .min-h-screen {
434
+ min-height: 100vh;
435
+ }
436
+ .w-\[18px\] {
437
+ width: 18px;
438
+ }
439
+ .w-full {
440
+ width: 100%;
441
+ }
442
+ .max-w-\[46ch\] {
443
+ max-width: 46ch;
444
+ }
445
+ .max-w-\[min\(20rem\,calc\(100vw-var\(--space-6\)\)\)\] {
446
+ max-width: min(20rem, calc(100vw - var(--space-6)));
447
+ }
448
+ .max-w-\[var\(--shell-measure\)\] {
449
+ max-width: var(--shell-measure);
450
+ }
451
+ .max-w-\[var\(--shell-measure-wide\)\] {
452
+ max-width: var(--shell-measure-wide);
453
+ }
454
+ .min-w-0 {
455
+ min-width: 0px;
456
+ }
457
+ .min-w-\[12rem\] {
458
+ min-width: 12rem;
459
+ }
460
+ .flex-1 {
461
+ flex: 1;
462
+ }
463
+ .flex-none {
464
+ flex: none;
465
+ }
466
+ .shrink {
467
+ flex-shrink: 1;
468
+ }
469
+ .grow {
470
+ flex-grow: 1;
471
+ }
472
+ .transform {
473
+ transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
474
+ }
475
+ .cursor-help {
476
+ cursor: help;
477
+ }
478
+ .cursor-pointer {
479
+ cursor: pointer;
480
+ }
481
+ .resize {
482
+ resize: both;
483
+ }
484
+ .list-none {
485
+ list-style-type: none;
486
+ }
487
+ .flex-col {
488
+ flex-direction: column;
489
+ }
490
+ .flex-wrap {
491
+ flex-wrap: wrap;
492
+ }
493
+ .items-baseline {
494
+ align-items: baseline;
495
+ }
496
+ .items-center {
497
+ align-items: center;
498
+ }
499
+ .items-start {
500
+ align-items: flex-start;
501
+ }
502
+ .justify-between {
503
+ justify-content: space-between;
504
+ }
505
+ .justify-items-center {
506
+ justify-items: center;
507
+ }
508
+ .gap-0 {
509
+ gap: 0px;
510
+ }
511
+ .gap-1 {
512
+ gap: var(--spacing-1);
513
+ }
514
+ .gap-2 {
515
+ gap: var(--spacing-2);
516
+ }
517
+ .gap-3 {
518
+ gap: var(--spacing-3);
519
+ }
520
+ .gap-4 {
521
+ gap: var(--spacing-4);
522
+ }
523
+ .gap-\[var\(--border-width\)\] {
524
+ gap: var(--border-width);
525
+ }
526
+ .self-center {
527
+ align-self: center;
528
+ }
529
+ .truncate {
530
+ overflow: hidden;
531
+ text-overflow: ellipsis;
532
+ white-space: nowrap;
533
+ }
534
+ .overflow-auto {
535
+ overflow: auto;
536
+ }
537
+ .overflow-hidden {
538
+ overflow: hidden;
539
+ }
540
+ .overflow-x-auto {
541
+ overflow-x: auto;
542
+ }
543
+ .overflow-y-auto {
544
+ overflow-y: auto;
545
+ }
546
+ .overscroll-contain {
547
+ overscroll-behavior: contain;
548
+ }
549
+ .rounded {
550
+ border-radius: 0.25rem;
551
+ }
552
+ .rounded-full {
553
+ border-radius: calc(infinity * 1px);
554
+ }
555
+ .rounded-md {
556
+ border-radius: var(--radius-md);
557
+ }
558
+ .rounded-sm {
559
+ border-radius: var(--radius-sm);
560
+ }
561
+ .border {
562
+ border-style: var(--tw-border-style);
563
+ border-width: var(--border-width);
564
+ }
565
+ .border-b {
566
+ border-bottom-style: var(--tw-border-style);
567
+ border-bottom-width: var(--border-width);
568
+ }
569
+ .border-dashed {
570
+ --tw-border-style: dashed;
571
+ border-style: dashed;
572
+ }
573
+ .border-accent {
574
+ border-color: var(--color-accent);
575
+ }
576
+ .border-border {
577
+ border-color: var(--color-border);
578
+ }
579
+ .border-border-strong {
580
+ border-color: var(--color-border-strong);
581
+ }
582
+ .p-0 {
583
+ padding: 0px;
584
+ }
585
+ .p-1 {
586
+ padding: var(--spacing-1);
587
+ }
588
+ .p-2 {
589
+ padding: var(--spacing-2);
590
+ }
591
+ .p-4 {
592
+ padding: var(--spacing-4);
593
+ }
594
+ .px-4 {
595
+ padding-inline: var(--spacing-4);
596
+ }
597
+ .py-2 {
598
+ padding-block: var(--spacing-2);
599
+ }
600
+ .py-3 {
601
+ padding-block: var(--spacing-3);
602
+ }
603
+ .py-5 {
604
+ padding-block: var(--spacing-5);
605
+ }
606
+ .py-6 {
607
+ padding-block: var(--spacing-6);
608
+ }
609
+ .py-10 {
610
+ padding-block: var(--spacing-10);
611
+ }
612
+ .pb-2 {
613
+ padding-bottom: var(--spacing-2);
614
+ }
615
+ .pb-3 {
616
+ padding-bottom: var(--spacing-3);
617
+ }
618
+ .text-center {
619
+ text-align: center;
620
+ }
621
+ .font-mono {
622
+ font-family: var(--font-mono);
623
+ }
624
+ .text-2xs {
625
+ font-size: var(--text-2xs);
626
+ line-height: var(--tw-leading, var(--text-2xs--line-height));
627
+ }
628
+ .text-lg {
629
+ font-size: var(--text-lg);
630
+ line-height: var(--tw-leading, var(--text-lg--line-height));
631
+ }
632
+ .text-sm {
633
+ font-size: var(--text-sm);
634
+ line-height: var(--tw-leading, var(--text-sm--line-height));
635
+ }
636
+ .text-xs {
637
+ font-size: var(--text-xs);
638
+ line-height: var(--tw-leading, var(--text-xs--line-height));
639
+ }
640
+ .leading-\[0\] {
641
+ --tw-leading: 0;
642
+ line-height: 0;
643
+ }
644
+ .leading-\[1\.5\] {
645
+ --tw-leading: 1.5;
646
+ line-height: 1.5;
647
+ }
648
+ .leading-\[1\.25\] {
649
+ --tw-leading: 1.25;
650
+ line-height: 1.25;
651
+ }
652
+ .leading-\[16px\] {
653
+ --tw-leading: 16px;
654
+ line-height: 16px;
655
+ }
656
+ .font-normal {
657
+ --tw-font-weight: var(--font-weight-normal);
658
+ font-weight: var(--font-weight-normal);
659
+ }
660
+ .font-strong {
661
+ --tw-font-weight: var(--font-weight-strong);
662
+ font-weight: var(--font-weight-strong);
663
+ }
664
+ .tracking-normal {
665
+ --tw-tracking: var(--tracking-normal);
666
+ letter-spacing: var(--tracking-normal);
667
+ }
668
+ .text-ellipsis {
669
+ text-overflow: ellipsis;
670
+ }
671
+ .whitespace-nowrap {
672
+ white-space: nowrap;
673
+ }
674
+ .text-muted {
675
+ color: var(--color-muted);
676
+ }
677
+ .text-text {
678
+ color: var(--color-text);
679
+ }
680
+ .lowercase {
681
+ text-transform: lowercase;
682
+ }
683
+ .normal-case {
684
+ text-transform: none;
685
+ }
686
+ .uppercase {
687
+ text-transform: uppercase;
688
+ }
689
+ .tabular-nums {
690
+ --tw-numeric-spacing: tabular-nums;
691
+ font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
692
+ }
693
+ .opacity-0 {
694
+ opacity: 0%;
695
+ }
696
+ .opacity-60 {
697
+ opacity: 60%;
698
+ }
699
+ .opacity-\[0\.55\] {
700
+ opacity: 0.55;
701
+ }
702
+ .shadow {
703
+ --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));
704
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
705
+ }
706
+ .ring {
707
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
708
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
709
+ }
710
+ .outline {
711
+ outline-style: var(--tw-outline-style);
712
+ outline-width: 1px;
713
+ }
714
+ .blur {
715
+ --tw-blur: blur(8px);
716
+ 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,);
717
+ }
718
+ .sepia {
719
+ --tw-sepia: sepia(100%);
720
+ 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,);
721
+ }
722
+ .filter {
723
+ 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,);
724
+ }
725
+ .outline-none {
726
+ --tw-outline-style: none;
727
+ outline-style: none;
728
+ }
729
+ .\[font\:inherit\] {
730
+ font: inherit;
731
+ }
732
+ .surface-bg {
733
+ background: var(--bg);
734
+ }
735
+ .surface-panel {
736
+ background: var(--panel);
737
+ }
738
+ .surface-panel-2 {
739
+ background: var(--panel-2);
740
+ }
741
+ .disabled\:cursor-default:disabled {
742
+ cursor: default;
743
+ }
744
+ .disabled\:opacity-50:disabled {
745
+ opacity: 50%;
746
+ }
747
+ .\[\&\>p\]\:m-0 > p {
748
+ margin: 0px;
749
+ }
750
+ }
751
+ @property --tw-rotate-x {
752
+ syntax: "*";
753
+ inherits: false;
754
+ }
755
+ @property --tw-rotate-y {
756
+ syntax: "*";
757
+ inherits: false;
758
+ }
759
+ @property --tw-rotate-z {
760
+ syntax: "*";
761
+ inherits: false;
762
+ }
763
+ @property --tw-skew-x {
764
+ syntax: "*";
765
+ inherits: false;
766
+ }
767
+ @property --tw-skew-y {
768
+ syntax: "*";
769
+ inherits: false;
770
+ }
771
+ @property --tw-border-style {
772
+ syntax: "*";
773
+ inherits: false;
774
+ initial-value: solid;
775
+ }
776
+ @property --tw-leading {
777
+ syntax: "*";
778
+ inherits: false;
779
+ }
780
+ @property --tw-font-weight {
781
+ syntax: "*";
782
+ inherits: false;
783
+ }
784
+ @property --tw-tracking {
785
+ syntax: "*";
786
+ inherits: false;
787
+ }
788
+ @property --tw-ordinal {
789
+ syntax: "*";
790
+ inherits: false;
791
+ }
792
+ @property --tw-slashed-zero {
793
+ syntax: "*";
794
+ inherits: false;
795
+ }
796
+ @property --tw-numeric-figure {
797
+ syntax: "*";
798
+ inherits: false;
799
+ }
800
+ @property --tw-numeric-spacing {
801
+ syntax: "*";
802
+ inherits: false;
803
+ }
804
+ @property --tw-numeric-fraction {
805
+ syntax: "*";
806
+ inherits: false;
807
+ }
808
+ @property --tw-shadow {
809
+ syntax: "*";
810
+ inherits: false;
811
+ initial-value: 0 0 #0000;
812
+ }
813
+ @property --tw-shadow-color {
814
+ syntax: "*";
815
+ inherits: false;
816
+ }
817
+ @property --tw-shadow-alpha {
818
+ syntax: "<percentage>";
819
+ inherits: false;
820
+ initial-value: 100%;
821
+ }
822
+ @property --tw-inset-shadow {
823
+ syntax: "*";
824
+ inherits: false;
825
+ initial-value: 0 0 #0000;
826
+ }
827
+ @property --tw-inset-shadow-color {
828
+ syntax: "*";
829
+ inherits: false;
830
+ }
831
+ @property --tw-inset-shadow-alpha {
832
+ syntax: "<percentage>";
833
+ inherits: false;
834
+ initial-value: 100%;
835
+ }
836
+ @property --tw-ring-color {
837
+ syntax: "*";
838
+ inherits: false;
839
+ }
840
+ @property --tw-ring-shadow {
841
+ syntax: "*";
842
+ inherits: false;
843
+ initial-value: 0 0 #0000;
844
+ }
845
+ @property --tw-inset-ring-color {
846
+ syntax: "*";
847
+ inherits: false;
848
+ }
849
+ @property --tw-inset-ring-shadow {
850
+ syntax: "*";
851
+ inherits: false;
852
+ initial-value: 0 0 #0000;
853
+ }
854
+ @property --tw-ring-inset {
855
+ syntax: "*";
856
+ inherits: false;
857
+ }
858
+ @property --tw-ring-offset-width {
859
+ syntax: "<length>";
860
+ inherits: false;
861
+ initial-value: 0px;
862
+ }
863
+ @property --tw-ring-offset-color {
864
+ syntax: "*";
865
+ inherits: false;
866
+ initial-value: #fff;
867
+ }
868
+ @property --tw-ring-offset-shadow {
869
+ syntax: "*";
870
+ inherits: false;
871
+ initial-value: 0 0 #0000;
872
+ }
873
+ @property --tw-outline-style {
874
+ syntax: "*";
875
+ inherits: false;
876
+ initial-value: solid;
877
+ }
878
+ @property --tw-blur {
879
+ syntax: "*";
880
+ inherits: false;
881
+ }
882
+ @property --tw-brightness {
883
+ syntax: "*";
884
+ inherits: false;
885
+ }
886
+ @property --tw-contrast {
887
+ syntax: "*";
888
+ inherits: false;
889
+ }
890
+ @property --tw-grayscale {
891
+ syntax: "*";
892
+ inherits: false;
893
+ }
894
+ @property --tw-hue-rotate {
895
+ syntax: "*";
896
+ inherits: false;
897
+ }
898
+ @property --tw-invert {
899
+ syntax: "*";
900
+ inherits: false;
901
+ }
902
+ @property --tw-opacity {
903
+ syntax: "*";
904
+ inherits: false;
905
+ }
906
+ @property --tw-saturate {
907
+ syntax: "*";
908
+ inherits: false;
909
+ }
910
+ @property --tw-sepia {
911
+ syntax: "*";
912
+ inherits: false;
913
+ }
914
+ @property --tw-drop-shadow {
915
+ syntax: "*";
916
+ inherits: false;
917
+ }
918
+ @property --tw-drop-shadow-color {
919
+ syntax: "*";
920
+ inherits: false;
921
+ }
922
+ @property --tw-drop-shadow-alpha {
923
+ syntax: "<percentage>";
924
+ inherits: false;
925
+ initial-value: 100%;
926
+ }
927
+ @property --tw-drop-shadow-size {
928
+ syntax: "*";
929
+ inherits: false;
930
+ }
931
+ @layer properties {
932
+ @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
933
+ *, ::before, ::after, ::backdrop {
934
+ --tw-rotate-x: initial;
935
+ --tw-rotate-y: initial;
936
+ --tw-rotate-z: initial;
937
+ --tw-skew-x: initial;
938
+ --tw-skew-y: initial;
939
+ --tw-border-style: solid;
940
+ --tw-leading: initial;
941
+ --tw-font-weight: initial;
942
+ --tw-tracking: initial;
943
+ --tw-ordinal: initial;
944
+ --tw-slashed-zero: initial;
945
+ --tw-numeric-figure: initial;
946
+ --tw-numeric-spacing: initial;
947
+ --tw-numeric-fraction: initial;
948
+ --tw-shadow: 0 0 #0000;
949
+ --tw-shadow-color: initial;
950
+ --tw-shadow-alpha: 100%;
951
+ --tw-inset-shadow: 0 0 #0000;
952
+ --tw-inset-shadow-color: initial;
953
+ --tw-inset-shadow-alpha: 100%;
954
+ --tw-ring-color: initial;
955
+ --tw-ring-shadow: 0 0 #0000;
956
+ --tw-inset-ring-color: initial;
957
+ --tw-inset-ring-shadow: 0 0 #0000;
958
+ --tw-ring-inset: initial;
959
+ --tw-ring-offset-width: 0px;
960
+ --tw-ring-offset-color: #fff;
961
+ --tw-ring-offset-shadow: 0 0 #0000;
962
+ --tw-outline-style: solid;
963
+ --tw-blur: initial;
964
+ --tw-brightness: initial;
965
+ --tw-contrast: initial;
966
+ --tw-grayscale: initial;
967
+ --tw-hue-rotate: initial;
968
+ --tw-invert: initial;
969
+ --tw-opacity: initial;
970
+ --tw-saturate: initial;
971
+ --tw-sepia: initial;
972
+ --tw-drop-shadow: initial;
973
+ --tw-drop-shadow-color: initial;
974
+ --tw-drop-shadow-alpha: 100%;
975
+ --tw-drop-shadow-size: initial;
976
+ }
977
+ }
978
+ }
979
+
281
980
  /* ---- ./base.css ---- */
282
981
  /* Light, only under the `system` theme. This used to override `:root`
283
982
  unconditionally, so choosing a dark theme on a light-mode laptop got
@@ -371,23 +1070,31 @@ body::before {
371
1070
  `[aria-disabled='true']` twin rather than a rewrite, which leaves each
372
1071
  `button` selector at the specificity it had and every committed baseline
373
1072
  untouched. */
374
- a.btn {
1073
+ .btn {
1074
+ /* The three an anchor brings that a `<button>` does not. Harmless on the
1075
+ button form -- it has no underline and centres already -- and the reason
1076
+ `Button` puts one class on both elements rather than keeping a twin
1077
+ selector for each. */
375
1078
  text-decoration: none;
376
1079
  color: var(--text);
377
1080
  text-align: center;
378
1081
  }
379
1082
 
380
- a.btn[aria-disabled='true'] {
1083
+ .btn[aria-disabled='true'] {
381
1084
  /* A disabled anchor still takes the pointer, so say so. The click itself is
382
1085
  blocked in the capture phase by the component. */
383
1086
  cursor: not-allowed;
384
1087
  }
385
1088
 
386
- button,
387
- a.btn,
388
- input,
389
- textarea,
390
- select {
1089
+ /* `:where`, so this base carries no specificity of its own.
1090
+ *
1091
+ * It was `button, input, textarea, select` -- an element selector, which
1092
+ * `.size-sm` and every other class outranked without anyone thinking about
1093
+ * it. Moving it to `.btn` to stop a tab inheriting a control's look raised it
1094
+ * to a class, so it tied with `.size-sm` and won on source order: every
1095
+ * button came out `8px 12px` whatever size it asked for. `:where` puts it
1096
+ * back to zero, which is what the element selector was silently providing. */
1097
+ :where(.btn, input, textarea, select) {
391
1098
  font: inherit;
392
1099
  color: var(--text);
393
1100
  background: var(--control);
@@ -408,8 +1115,7 @@ select {
408
1115
  the icon; the button simply had no opinion about how to arrange two things.
409
1116
  `inline-flex` and a gap gives it one, and the gap replaces the space
410
1117
  character people were putting between an icon and its label. */
411
- button,
412
- a.btn {
1118
+ .btn {
413
1119
  display: inline-flex;
414
1120
  align-items: center;
415
1121
  justify-content: center;
@@ -425,18 +1131,41 @@ a.btn {
425
1131
 
426
1132
  Several places already drew this by hand as `.chat-bar-btn.on`. This is the
427
1133
  same look, said once, on a plain button. */
428
- button[aria-pressed='true'] {
1134
+ .btn[aria-pressed='true'] {
429
1135
  border-color: var(--accent);
430
1136
  color: var(--accent);
431
1137
  background: color-mix(in srgb, var(--accent) 10%, var(--panel));
432
1138
  }
433
1139
 
434
- button[aria-pressed='true']:hover:not(:disabled) { border-color: var(--accent); }
1140
+ .btn[aria-pressed='true']:hover:not(:disabled) { border-color: var(--accent); }
435
1141
 
436
1142
  /* The glyph never shrinks: in a narrow row flexbox would take it from the icon
437
1143
  before the text, and half an icon is not a smaller icon. */
438
- button > svg,
439
- a.btn > svg { flex: none; }
1144
+ .btn > svg { flex: none; }
1145
+
1146
+ /* `Select`'s control wears the control styling, and says so.
1147
+ *
1148
+ * It renders React Aria's `Button` -- a bare `<button>` -- and used to take
1149
+ * `font`, colour, the surface and the border from the
1150
+ * `button, input, textarea, select` reset without asking. When that reset
1151
+ * moved to `.btn`, so a tab and a row would stop inheriting a control's look
1152
+ * only to undo it, this lost all of it at once.
1153
+ *
1154
+ * Here rather than in `select.css`, and that placement is the rule, not
1155
+ * tidiness: `font` is a shorthand and it resets `font-size`, so declared
1156
+ * after `.size-sm` below it silently makes every small control full size.
1157
+ * `index.css` says the import order is behaviour; this is what that means.
1158
+ *
1159
+ * `:where` for the same reason the reset above uses it: this is a base a
1160
+ * `size` class has to be able to beat, and a bare class ties with one. */
1161
+ :where(.sel-control) {
1162
+ font: inherit;
1163
+ color: var(--text);
1164
+ background: var(--control);
1165
+ border: var(--border-width) solid var(--border-strong);
1166
+ border-radius: var(--radius);
1167
+ cursor: pointer;
1168
+ }
440
1169
 
441
1170
  .size-sm { font-size: var(--text-xs); padding: var(--space-1) var(--space-2); }
442
1171
 
@@ -461,8 +1190,7 @@ input, textarea, select { width: 100%; }
461
1190
  same disagreement written twice. */
462
1191
  textarea { resize: none; }
463
1192
 
464
- button:hover:not(:disabled),
465
- a.btn:hover:not([aria-disabled='true']),
1193
+ .btn:hover:not(:disabled),
466
1194
  input:hover:not(:disabled),
467
1195
  textarea:hover:not(:disabled),
468
1196
  select:hover:not(:disabled) { border-color: var(--accent); }
@@ -470,8 +1198,7 @@ select:hover:not(:disabled) { border-color: var(--accent); }
470
1198
  /* `:focus-visible` on the button so a click does not leave a ring behind it,
471
1199
  `:focus` on the fields because a focused field is a place you are typing and
472
1200
  should say so however you got there. */
473
- button:focus-visible,
474
- a.btn:focus-visible,
1201
+ .btn:focus-visible,
475
1202
  input:focus,
476
1203
  textarea:focus,
477
1204
  select:focus { outline: none; border-color: var(--accent); }
@@ -564,14 +1291,13 @@ input[type='radio']:focus-visible { outline: none; box-shadow: var(--focus-ring)
564
1291
  input[type='checkbox']:disabled,
565
1292
  input[type='radio']:disabled { cursor: not-allowed; background: var(--panel-2); }
566
1293
 
567
- button:disabled,
568
- a.btn[aria-disabled='true'],
1294
+ .btn:disabled,
1295
+ .btn[aria-disabled='true'],
569
1296
  input:disabled,
570
1297
  textarea:disabled,
571
1298
  select:disabled { opacity: 0.45; cursor: not-allowed; }
572
1299
 
573
- button.primary,
574
- a.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
1300
+ .btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
575
1301
 
576
1302
  /* A disabled primary is not a faded primary.
577
1303
  `opacity: .45` was the whole of it, and a coloured fill at 45% is still a
@@ -579,8 +1305,7 @@ a.btn.primary { background: var(--accent); border-color: var(--accent); color: v
579
1305
  thing on the panel. That got worse when onboarding started holding people at
580
1306
  a disabled Next. So it gives up the fill entirely and becomes the shape of a
581
1307
  disabled control -- the opacity then has nothing left to do. */
582
- button.primary:disabled,
583
- a.btn.primary[aria-disabled='true'] {
1308
+ .btn.primary:disabled {
584
1309
  background: var(--panel-2);
585
1310
  border-color: var(--border);
586
1311
  color: var(--muted);
@@ -594,7 +1319,7 @@ a.btn.primary[aria-disabled='true'] {
594
1319
  line under it that travels.
595
1320
  `aria-busy` rather than a class: the attribute is what a screen reader reads,
596
1321
  and styling the thing that carries the meaning is how they stay in step. */
597
- button[aria-busy='true'] {
1322
+ .btn[aria-busy='true'] {
598
1323
  position: relative;
599
1324
  overflow: hidden;
600
1325
  cursor: progress;
@@ -607,18 +1332,18 @@ button[aria-busy='true'] {
607
1332
  something that is working, that reads as a control that failed and went
608
1333
  grey. So a busy button keeps every colour it had and takes only the loss of
609
1334
  the cursor and the press; the travelling line is what says why. */
610
- button[aria-busy='true']:disabled {
1335
+ .btn[aria-busy='true']:disabled {
611
1336
  opacity: 1;
612
1337
  cursor: progress;
613
1338
  }
614
1339
 
615
- button.primary[aria-busy='true']:disabled {
1340
+ .btn.primary[aria-busy='true']:disabled {
616
1341
  background: var(--accent);
617
1342
  border-color: var(--accent);
618
1343
  color: var(--on-accent);
619
1344
  }
620
1345
 
621
- button[aria-busy='true']::after {
1346
+ .btn[aria-busy='true']::after {
622
1347
  content: '';
623
1348
  position: absolute;
624
1349
  left: 0;
@@ -650,15 +1375,13 @@ button[aria-busy='true']::after {
650
1375
 
651
1376
  /* Quiet: no border, no fill. For the action beside the action -- Cancel next to
652
1377
  Save -- where a bordered box competes with the thing it is next to. */
653
- button.ghost,
654
- a.btn.ghost {
1378
+ .btn.ghost {
655
1379
  background: transparent;
656
1380
  border-color: transparent;
657
1381
  color: var(--muted);
658
1382
  }
659
1383
 
660
- button.ghost:hover:not(:disabled),
661
- a.btn.ghost:hover:not([aria-disabled='true']) {
1384
+ .btn.ghost:hover:not(:disabled) {
662
1385
  background: var(--panel-2);
663
1386
  border-color: transparent;
664
1387
  color: var(--text);
@@ -740,7 +1463,7 @@ a.btn.ghost:hover:not([aria-disabled='true']) {
740
1463
  header.) */
741
1464
  .row { display: flex; align-items: center; gap: var(--space-3); }
742
1465
 
743
- .grow { flex: 1; min-width: 0; }
1466
+ .ctl-grow { flex: 1; min-width: 0; }
744
1467
 
745
1468
  .mono { font-family: var(--font-mono); font-size: var(--text-sm); }
746
1469
 
@@ -804,10 +1527,17 @@ a.btn.ghost:hover:not([aria-disabled='true']) {
804
1527
  /* A pill following a name needs the gap outside its own border. Leading
805
1528
  whitespace inside the span puts it under the pill's background instead,
806
1529
  which reads as the badge touching the word it qualifies. */
807
- .row-pill { margin-left: var(--space-2); vertical-align: baseline; }
1530
+ /* A pill beside a name, and the one thing that keeps the pair working:
1531
+ `flex: none`, so the pill never shrinks and the name ellipsises instead.
1532
+ That was `rows.css`'s `.named > .pill`, which meant anybody laying out a
1533
+ name and its pills had to know a package class by name to get it. The
1534
+ variant already says "I am in a row"; it may as well say what that costs. */
1535
+ .row-pill { margin-left: var(--space-2); vertical-align: baseline; flex: none; }
808
1536
 
809
1537
  /* The tile's only control. Dim until hovered: it is chrome on someone's app. */
810
1538
  .icon-btn {
1539
+ /* 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`. */
1540
+ font: inherit;
811
1541
  display: inline-flex; align-items: center; padding: var(--space-1);
812
1542
  background: none; border: 0; color: var(--muted); opacity: 0.6; cursor: pointer;
813
1543
  }
@@ -873,19 +1603,17 @@ a.btn.ghost:hover:not([aria-disabled='true']) {
873
1603
  transition: none;
874
1604
  }
875
1605
 
876
- button.primary:active:not(:disabled),
877
- a.btn.primary:active:not([aria-disabled='true']) {
1606
+ .btn.primary:active:not(:disabled) {
878
1607
  background: color-mix(in srgb, var(--text) var(--press-ink), var(--accent));
879
1608
  border-color: color-mix(in srgb, var(--text) var(--press-ink), var(--accent));
880
1609
  }
881
1610
 
882
- button.ghost:active:not(:disabled),
883
- a.btn.ghost:active:not([aria-disabled='true']) {
1611
+ .btn.ghost:active:not(:disabled) {
884
1612
  background: color-mix(in srgb, var(--text) var(--press-ink), var(--panel-2));
885
1613
  border-color: transparent;
886
1614
  }
887
1615
 
888
- button[aria-pressed='true']:active:not(:disabled) {
1616
+ .btn[aria-pressed='true']:active:not(:disabled) {
889
1617
  background: color-mix(in srgb, var(--accent) 18%, var(--panel));
890
1618
  }
891
1619
 
@@ -1056,7 +1784,7 @@ input:active, textarea:active, select:active { transform: none; }
1056
1784
 
1057
1785
  /* The top bar carries the title, the views and the way into settings. Nothing
1058
1786
  that belongs to a conversation lives here any more, so it can breathe. */
1059
- .topbar .grow { flex: 1; min-width: 0; }
1787
+ .topbar .ctl-grow { flex: 1; min-width: 0; }
1060
1788
 
1061
1789
  /* Back on the left, Next on the right, with the gap between them doing the
1062
1790
  separating -- so the button that goes forward is in the same place on both
@@ -1088,6 +1816,57 @@ input:active, textarea:active, select:active { transform: none; }
1088
1816
  to { opacity: 1; }
1089
1817
  }
1090
1818
 
1819
+ /* The states the `button` element rule used to hand every bare `<button>` in
1820
+ the package, now asked for by name.
1821
+ *
1822
+ * This is the half of the decoupling that a screenshot cannot show: `:focus-visible`
1823
+ * is not photographed, so nine components would have lost their focus ring in
1824
+ * silence. `keyboard.spec.ts` is what actually holds this.
1825
+ *
1826
+ * `.btn` has its own, in the rules above. These are the ones that are a button
1827
+ * for their behaviour and not for their appearance -- a tab, a row, a card, a
1828
+ * pager step, a grid cell, a remove cross. */
1829
+ .tab:focus-visible,
1830
+ .row-hit:focus-visible,
1831
+ .card:focus-visible,
1832
+ .ident-remove:focus-visible,
1833
+ .size-cell:focus-visible,
1834
+ .pager-step:focus-visible,
1835
+ .pager-page:focus-visible,
1836
+ .sel-control:focus-visible {
1837
+ outline: 2px solid var(--accent);
1838
+ outline-offset: 2px;
1839
+ }
1840
+
1841
+ /* `:disabled` came from the same rule. A control that cannot be pressed has to
1842
+ say so whatever element it is. */
1843
+ .tab:disabled,
1844
+ .row-hit:disabled,
1845
+ .card:disabled,
1846
+ .ident-remove:disabled,
1847
+ .size-cell:disabled,
1848
+ .pager-step:disabled,
1849
+ .pager-page:disabled,
1850
+ .sel-control:disabled {
1851
+ opacity: 0.45;
1852
+ cursor: not-allowed;
1853
+ }
1854
+
1855
+ /* The anchor form's disabled states, alongside the button's.
1856
+ *
1857
+ * `.btn` is one class on two elements, and an `<a>` has no `:disabled` -- it
1858
+ * carries `aria-disabled` and a capture-phase click block instead. Every
1859
+ * rule above that reads `:disabled` needs the attribute twin, or a disabled
1860
+ * link is a link that looks pressable and refuses silently. Folding the old
1861
+ * `a.btn` twins into `.btn` dropped these, and the baselines showed a
1862
+ * disabled link at full strength. */
1863
+ .btn.primary[aria-disabled='true'] {
1864
+ background: var(--panel-2);
1865
+ border-color: var(--border);
1866
+ color: var(--muted);
1867
+ opacity: 1;
1868
+ }
1869
+
1091
1870
  /* ---- ./select.css ---- */
1092
1871
  /* How big a control is, asked for rather than inherited.
1093
1872
  Four sizes existed and every one of them came from a descendant selector:
@@ -1103,8 +1882,7 @@ input:active, textarea:active, select:active { transform: none; }
1103
1882
  /* Full width, for the one action a card or a step is about. Not a size: a
1104
1883
  `block` button is the same height as any other, it just stops being a thing
1105
1884
  you have to aim at. */
1106
- button.block,
1107
- a.btn.block { display: flex; width: 100%; }
1885
+ .btn.ctl-block { display: flex; width: 100%; }
1108
1886
 
1109
1887
  /* The default is `size-md`, spelled once for every control.
1110
1888
  The paragraph above promised one scale for buttons, inputs and selects, and
@@ -1112,13 +1890,24 @@ a.btn.block { display: flex; width: 100%; }
1112
1890
  input was eight pixels taller than the button beside it and no row of the two
1113
1891
  could line up. The download modal's search box against its Search button was
1114
1892
  the version you could see from across the room. */
1115
- button,
1116
- a.btn,
1117
- input,
1118
- textarea,
1119
- select,
1893
+ /* `:where`, so this base carries no specificity of its own.
1894
+ *
1895
+ * It was `button, input, textarea, select` -- an element selector, which
1896
+ * `.size-sm` and every other class outranked without anyone thinking about
1897
+ * it. Moving it to `.btn` to stop a tab inheriting a control's look raised it
1898
+ * to a class, so it tied with `.size-sm` and won on source order: every
1899
+ * button came out `8px 12px` whatever size it asked for. `:where` puts it
1900
+ * back to zero, which is what the element selector was silently providing. */
1901
+ :where(.btn, input, textarea, select) { padding: var(--space-2) var(--space-3); }
1902
+
1903
+ /* The select's control keeps its padding at full class weight, and after
1904
+ `.size-sm` in the cascade, because that is where it has always been: this
1905
+ file is imported after `base.css`, so a `size`d select took the size's type
1906
+ and this padding. Folding it into the `:where` above handed the padding
1907
+ back to `.size-sm` and shrank every small select by seven pixels. */
1120
1908
  .sel-control { padding: var(--space-2) var(--space-3); }
1121
1909
 
1910
+
1122
1911
  /* Centred on the whole block -- the caption and the sentence together -- rather
1123
1912
  than aligned to either end of it. `.field-row` pins to the bottom, which is
1124
1913
  right for a field beside a button and wrong beside two lines of prose. */
@@ -1142,16 +1931,16 @@ select,
1142
1931
  .sel { position: relative; display: inline-block; }
1143
1932
 
1144
1933
  /* The subject of its pane rather than a field in it: full width and a size up.
1145
- That was `.select-field.block` on the native element, and it carried the
1934
+ That was `.select-field.ctl-block` on the native element, and it carried the
1146
1935
  reason with it -- model ids are long (`qwen2.5-coder:32b-instruct-q4_K_M`) and
1147
1936
  at the narrow size they truncated mid-name. */
1148
- .sel.block { display: block; width: 100%; }
1937
+ .sel.ctl-block { display: block; width: 100%; }
1149
1938
 
1150
- .sel.block .sel-control { padding: var(--space-2) var(--space-3); font-weight: 500; }
1939
+ .sel.ctl-block .sel-control { padding: var(--space-2) var(--space-3); font-weight: 500; }
1151
1940
 
1152
1941
  /* Not full width otherwise: a select as wide as its row, holding one short
1153
1942
  word, reads as an input somebody forgot to fill in. */
1154
- .sel:not(.block) .sel-control { width: auto; max-width: 320px; }
1943
+ .sel:not(.ctl-block) .sel-control { width: auto; max-width: 320px; }
1155
1944
 
1156
1945
  .sel-control {
1157
1946
  display: flex;
@@ -1164,9 +1953,7 @@ select,
1164
1953
  background: var(--control);
1165
1954
  }
1166
1955
 
1167
- .sel-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1168
-
1169
- .sel-caret { flex: none; opacity: 0.6; transition: transform var(--dur-fast) var(--ease); }
1956
+ .sel-caret { transition: transform var(--dur-fast) var(--ease); }
1170
1957
 
1171
1958
  .sel[data-open] .sel-caret { transform: rotate(180deg); }
1172
1959
 
@@ -1175,21 +1962,7 @@ select,
1175
1962
  /* State is read off React Aria's attributes: `data-open` on the root,
1176
1963
  `data-focused` / `data-selected` / `data-disabled` on an item, where
1177
1964
  `.open`, `[data-active]`, `.on` and `.off` used to be. */
1178
- .sel-list {
1179
- /* `fixed`, and in `document.body`: an ancestor that scrolls or hides its
1180
- overflow would otherwise clip it, which in this app means every settings
1181
- pane. Position comes from the button's measured rectangle. */
1182
- /* Positioned by React Aria -- inline `top`/`left` on a portalled popover
1183
- that flips when the edge is near. `position: fixed` and the measured
1184
- rectangle it was placed by are gone with the hand-rolled version. */
1185
- z-index: 60;
1186
- max-height: 280px;
1187
- overflow-y: auto;
1188
- margin: 0;
1189
- padding: var(--space-1);
1190
- /* The same room while the list is scrolled as it has at rest. Padding at
1191
- the top of a scroll container scrolls away with the content, so the row
1192
- `scrollIntoView({block: 'nearest'})` brings up ended flush against the
1965
+ .sel-list { scroll-padding-block: var(--space-1); box-shadow: var(--shadow-2); })` brings up ended flush against the
1193
1966
  border and read as clipped rather than as one of a set -- and this list
1194
1967
  opens scrolled to whatever is chosen, so that was the ordinary state.
1195
1968
  `scroll-padding` insets what counts as "in view", so it stops short by
@@ -1202,18 +1975,6 @@ select,
1202
1975
  box-shadow: var(--shadow-2);
1203
1976
  }
1204
1977
 
1205
- .sel-item {
1206
- display: flex;
1207
- align-items: center;
1208
- gap: var(--space-2);
1209
- padding: var(--space-2) var(--space-2);
1210
- border-radius: var(--radius-sm);
1211
- cursor: pointer;
1212
- white-space: nowrap;
1213
- }
1214
-
1215
- .sel-item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
1216
-
1217
1978
  /* Hover and keyboard land on the same row, because they are the same idea --
1218
1979
  two highlights for one cursor is how a list starts lying about where Enter
1219
1980
  will go. */
@@ -1225,8 +1986,6 @@ select,
1225
1986
 
1226
1987
  /* The tick marks the chosen row. Colour alone would not: a list where the only
1227
1988
  difference is a hue is a list you cannot read past. */
1228
- .sel-tick { flex: none; opacity: 0; }
1229
-
1230
1989
  .sel-item[data-selected] .sel-tick { opacity: 1; }
1231
1990
 
1232
1991
  /* The icon on a speaker's row in the open list -- `Select`'s `aside`. Last in
@@ -1264,21 +2023,18 @@ select,
1264
2023
 
1265
2024
  /* ---- ./dangerzone.css ---- */
1266
2025
  /* Destructive, and legible as such standing still. */
1267
- button.danger,
1268
- a.btn.danger {
2026
+ .btn.danger {
1269
2027
  color: var(--bad);
1270
2028
  border-color: color-mix(in srgb, var(--bad) 45%, var(--border-strong));
1271
2029
  }
1272
2030
 
1273
- button.danger:hover:not(:disabled),
1274
- a.btn.danger:hover:not([aria-disabled='true']) {
2031
+ .btn.danger:hover:not(:disabled) {
1275
2032
  background: color-mix(in srgb, var(--bad) 12%, var(--panel));
1276
2033
  border-color: var(--bad);
1277
2034
  color: var(--bad);
1278
2035
  }
1279
2036
 
1280
- button.danger:active:not(:disabled),
1281
- a.btn.danger:active:not([aria-disabled='true']) {
2037
+ .btn.danger:active:not(:disabled) {
1282
2038
  background: color-mix(in srgb, var(--bad) 20%, var(--panel));
1283
2039
  border-color: var(--bad);
1284
2040
  }
@@ -1519,33 +2275,46 @@ a.btn.danger:active:not([aria-disabled='true']) {
1519
2275
  .callout button { flex: none; }
1520
2276
 
1521
2277
  /* ---- ./card.css ---- */
2278
+ /* `.card` is public API, not this component's private styling.
2279
+ *
2280
+ * Consumers apply it to their own markup -- the gallery's skeleton specimen
2281
+ * renders `<div className="card">` around three rows, and app-template uses
2282
+ * `card-pick`. Moving the box into `Card`'s own class list took that away
2283
+ * with no type error and no failing test in this package: it broke only
2284
+ * where somebody else had used the class, which is the worst way for an API
2285
+ * to go.
2286
+ *
2287
+ * So the rule stays and `Card` wears it. The lesson generalises to every
2288
+ * class in this package that a consumer can reach: a component may draw
2289
+ * itself with utilities only where its class is genuinely private. */
1522
2290
  .card {
2291
+ display: block;
2292
+ width: 100%;
2293
+ box-sizing: border-box;
2294
+ font: inherit;
2295
+ color: var(--text);
2296
+ text-align: left;
1523
2297
  background: var(--panel);
1524
2298
  border: var(--border-width) solid var(--border);
1525
2299
  border-radius: var(--radius-md);
1526
- padding: var(--space-3) var(--space-3);
2300
+ padding: var(--space-3);
1527
2301
  margin-bottom: var(--space-3);
1528
2302
  }
1529
2303
 
2304
+ /* What is left once `Card` draws its own box: the tones, the pick and
2305
+ selected states, and the rules that reach from the card into what it
2306
+ holds. `:has(.card-icon) .card-inline` is a relationship, and a tone is a
2307
+ `color-mix`; neither is a property on one element. */
2308
+
1530
2309
  /* Header, when there is one. The title and the line under it were retyped at
1531
2310
  every call site and no two agreed on the gap between them. */
1532
- .card-head { display: flex; align-items: flex-start; gap: var(--space-3); }
1533
-
1534
- .card-head:not(:last-child) { margin-bottom: var(--space-3); }
1535
-
1536
- .card-headings { min-width: 0; flex: 1; }
1537
-
1538
2311
  /* The card owns the space between the things it holds. Without this a callout
1539
2312
  and the button under it sit edge to edge, and every caller has to remember a
1540
2313
  margin on whichever child happens to be second. */
1541
- .card-body { display: grid; gap: var(--space-3); }
1542
-
1543
2314
  /* The card's own control, on the card's padding rather than indented into its
1544
2315
  body. Centred against the whole heading block, not against the title -- a
1545
2316
  switch aligned to the first line drifts upward the moment the description
1546
2317
  runs to two. */
1547
- .card-action { flex: none; align-self: center; display: flex; align-items: center; }
1548
-
1549
2318
  /* A row inside a card that lines up with the header rather than indenting past
1550
2319
  it: same left edge as the title, same right edge as the action. Its label is
1551
2320
  a quiet caption, not a second heading -- the card already has one, and two
@@ -1592,17 +2361,6 @@ a.btn.danger:active:not([aria-disabled='true']) {
1592
2361
  color: var(--muted);
1593
2362
  }
1594
2363
 
1595
- /* A card that is itself the choice. Reset first, because it is a `<button>`
1596
- now and inherits a control's padding and centring. */
1597
- button.card {
1598
- display: block;
1599
- width: 100%;
1600
- text-align: left;
1601
- font: inherit;
1602
- color: inherit;
1603
- padding: var(--space-3);
1604
- }
1605
-
1606
2364
  /* Variants, because a card is doing four jobs and looked like one.
1607
2365
  A search result you can act on, a thing already installed, something that
1608
2366
  went wrong, and a heading with rows under it are not the same object, and
@@ -1645,6 +2403,20 @@ button.card {
1645
2403
 
1646
2404
  .modal-body .card { background: var(--panel-2); }
1647
2405
 
2406
+ /* Selected, which used to arrive from `button[aria-pressed='true']`.
2407
+ *
2408
+ * A card marks its choice with `aria-pressed`, and the tint came from the
2409
+ * element rule every bare `<button>` in the package was picking up. That rule
2410
+ * belongs to `Button` now, so the card says what being chosen looks like --
2411
+ * which is the right place for it anyway: a chosen card is a card's idea, not
2412
+ * a button's. */
2413
+ .card[aria-pressed='true'] {
2414
+ border-color: var(--accent);
2415
+ background: color-mix(in srgb, var(--accent) 10%, var(--panel));
2416
+ }
2417
+
2418
+ .card[aria-pressed='true']:hover { border-color: var(--accent); }
2419
+
1648
2420
  /* ---- ./toggle.css ---- */
1649
2421
  .card-action .switch-row { padding: 0; gap: var(--space-2); }
1650
2422
 
@@ -2195,71 +2967,21 @@ input[type='radio']:active:not(:disabled) {
2195
2967
 
2196
2968
  .row-hit:focus-visible { outline: none; box-shadow: var(--focus-ring); }
2197
2969
 
2198
- /* ---- ./empty.css ---- */
2199
- /* `Empty`: a list with nothing in it.
2200
-
2201
- Quieter than `.empty`, which is a whole-screen state at `--space-10`. These
2202
- sit inside a card that already has a heading and a description, so the same
2203
- padding would leave a card that is mostly air about nothing.
2204
-
2205
- Dashed rather than solid: a solid border reads as a thing, and the point of
2206
- this box is that there is no thing. It marks out where the list will be. */
2207
- .nothing {
2208
- display: grid;
2209
- justify-items: center;
2210
- gap: var(--space-2);
2211
- padding: var(--space-5) var(--space-4);
2212
- border: var(--border-width) dashed var(--border-strong);
2213
- border-radius: var(--radius-md);
2214
- text-align: center;
2215
- }
2216
-
2217
- .nothing-mark { color: var(--muted); line-height: 0; }
2218
-
2219
- /* The same box with a figure in it, for a whole surface rather than a slot in
2220
- a card. Two things change and both follow from the size: the dashed outline
2221
- goes, because an outline marks out where a list will be and a window-sized
2222
- dashed rectangle reads as a layout that failed rather than a view with
2223
- nothing on it; and the box centres itself in whatever it was dropped into,
2224
- which is what makes it land in the middle of an empty dashboard rather than
2225
- under the header. */
2226
- .nothing-surface {
2227
- border: none;
2228
- padding: var(--space-10) var(--space-4);
2229
- gap: var(--space-3);
2230
- margin: auto;
2231
- }
2232
-
2233
- /* `currentColor` reaches the figure through the two rules the illustrations
2234
- ship with, so the colour is set here and the drawing follows it -- the same
2235
- grey as the sentence under it, in every theme.
2236
- Two classes, not one: `.illo` sets `color: inherit` further down this file,
2237
- which beats a single-class rule written earlier and drew the figure in full
2238
- text black beside a muted sentence. */
2239
- .nothing-surface .nothing-figure { color: var(--muted); }
2240
-
2241
- /* `--text-sm`, not the `--text-xs` these lines used to be: it is a sentence
2242
- somebody is meant to read, not a caption qualifying something else. */
2243
- .nothing-said { margin: 0; color: var(--muted); font-size: var(--text-sm); line-height: 1.5; max-width: 46ch; }
2244
-
2245
- .nothing-act { margin-top: var(--space-1); }
2246
-
2247
2970
  /* ---- ./progress.css ---- */
2248
2971
  /* progress ----------------------------------------------------------- */
2249
2972
 
2250
- /* The bar sat 4px under its own label and flush against whatever was above
2973
+ /* The bar itself: a track with a child it fills, an animation and a
2974
+ reduced-motion answer. A utility is a property on one element, so none of
2975
+ that is one -- `Progress` draws its own wrapper and head with utilities and
2976
+ this is what is left.
2977
+
2978
+ The bar sat 4px under its own label and flush against whatever was above
2251
2979
  the whole block, so a download row read as four lines of text with a rule
2252
2980
  drawn through the last one. The label is a caption for the bar and stays
2253
2981
  close to it; the block as a whole is a separate statement from the row it
2254
2982
  follows. */
2255
- .progress { display: grid; gap: var(--space-1); }
2256
-
2257
2983
  .card > .progress { margin-top: var(--space-3); }
2258
2984
 
2259
- .progress-head { display: flex; align-items: baseline; gap: var(--space-2); font-size: var(--text-sm); }
2260
-
2261
- .progress-detail { color: var(--muted); font-size: var(--text-xs); }
2262
-
2263
2985
  .bar { height: 6px; background: var(--panel-2); border-radius: var(--radius-pill); overflow: hidden; }
2264
2986
 
2265
2987
  .bar > i {
@@ -2293,6 +3015,14 @@ input[type='radio']:active:not(:disabled) {
2293
3015
  .tabs { position: relative; display: flex; gap: var(--space-1); }
2294
3016
 
2295
3017
  .tab {
3018
+ /* 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. */
3019
+ display: inline-flex;
3020
+ align-items: center;
3021
+ justify-content: center;
3022
+ gap: var(--space-2);
3023
+ /* 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`. */
3024
+ font: inherit;
3025
+ cursor: pointer;
2296
3026
  position: relative;
2297
3027
  z-index: 1;
2298
3028
  background: transparent;
@@ -2449,6 +3179,38 @@ input[type='radio']:active:not(:disabled) {
2449
3179
  .toast[data-exiting] { animation: none; }
2450
3180
  }
2451
3181
 
3182
+ /* The dismiss control, in its own words.
3183
+ *
3184
+ * It was `className="ghost size-sm"` -- `Button`'s vocabulary, borrowed from
3185
+ * a component that does not render a `Button`. That only ever worked because
3186
+ * `.ghost` and `.size-sm` were element-and-class rules any `<button>` could
3187
+ * pick up, which is the coupling this pass is removing. Nothing photographed
3188
+ * it, either: the toast is transient and the close button is not in any
3189
+ * baseline, so this would have gone quiet. */
3190
+ .toast-close {
3191
+ display: inline-flex;
3192
+ align-items: center;
3193
+ justify-content: center;
3194
+ font: inherit;
3195
+ font-size: var(--text-xs);
3196
+ padding: var(--space-1) var(--space-2);
3197
+ background: transparent;
3198
+ border: var(--border-width) solid transparent;
3199
+ border-radius: var(--radius);
3200
+ color: var(--muted);
3201
+ cursor: pointer;
3202
+ }
3203
+
3204
+ .toast-close:hover:not(:disabled) {
3205
+ background: var(--panel-2);
3206
+ color: var(--text);
3207
+ }
3208
+
3209
+ .toast-close:focus-visible {
3210
+ outline: 2px solid var(--accent);
3211
+ outline-offset: 2px;
3212
+ }
3213
+
2452
3214
  /* ---- ./modal.css ---- */
2453
3215
  /* modal --------------------------------------------------------------- */
2454
3216
 
@@ -2489,13 +3251,6 @@ input[type='radio']:active:not(:disabled) {
2489
3251
  Not `display: contents`, which was the first attempt. An element with no box
2490
3252
  cannot take focus, so React Aria's initial focus fell through to the first
2491
3253
  input and Escape -- handled on the section -- stopped closing the window. */
2492
- .modal-dialog {
2493
- display: flex;
2494
- flex-direction: column;
2495
- min-height: 0;
2496
- flex: 1;
2497
- }
2498
-
2499
3254
  .modal {
2500
3255
  background: var(--panel);
2501
3256
  border: 1px solid var(--border);
@@ -2546,18 +3301,9 @@ input[type='radio']:active:not(:disabled) {
2546
3301
  laid out for the bare `<strong>` it used to hold -- 86px tall with them,
2547
3302
  41px without, on a head whose padding is 12 over 8. `font: inherit` so the
2548
3303
  `<strong>` rule above is the one that sets the size. */
2549
- .modal-title { margin: 0; font: inherit; }
2550
-
2551
3304
  /* Under the title, on the header's padding rather than the body's, so it reads
2552
3305
  as part of the heading rather than as the first line of content. */
2553
- .modal-desc {
2554
- margin: 0;
2555
- padding: 0 var(--space-4) var(--space-3);
2556
- color: var(--muted);
2557
- font-size: var(--text-sm);
2558
- line-height: 1.5;
2559
- max-width: 62ch;
2560
- }
3306
+ .modal-desc { line-height: 1.5; }
2561
3307
 
2562
3308
  .modal-body { padding: var(--space-2) var(--space-4) var(--space-3); overflow-y: auto; }
2563
3309
 
@@ -2917,7 +3663,8 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
2917
3663
  buttons only, so a button in a row stood 25px beside a 35px select and no
2918
3664
  size class could ask for the select's height. Type from the row, box from
2919
3665
  the scale, and the two line up; an explicit `size-*` still wins. */
2920
- :where(.set-row) button, :where(.set-row) .slider-box { font-size: var(--text-xs); }
3666
+ :where(.set-row) button,
3667
+ :where(.set-row) .slider-box { font-size: var(--text-xs); }
2921
3668
 
2922
3669
  /* ---- ./checkbox.css ---- */
2923
3670
  /* A row you choose. There is no checkbox in it.
@@ -3095,30 +3842,28 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3095
3842
  one. The cell is bold because it is a heading; what hangs under it is not,
3096
3843
  and inherited the weight -- so a pinned version read as loud as the thing it
3097
3844
  versions. */
3098
- .table th .set-hint { font-weight: 400; }
3099
-
3100
- .table-scroll { overflow-x: auto; }
3845
+ .data-table th .set-hint { font-weight: 400; }
3101
3846
 
3102
- .table {
3847
+ .data-table {
3103
3848
  width: 100%;
3104
3849
  border-collapse: collapse;
3105
3850
  font-size: var(--text-sm);
3106
3851
  }
3107
3852
 
3108
- .table th,
3109
- .table td {
3853
+ .data-table th,
3854
+ .data-table td {
3110
3855
  text-align: left;
3111
3856
  padding: var(--space-2) var(--space-3);
3112
3857
  border-bottom: var(--border-width) solid var(--border);
3113
3858
  }
3114
3859
 
3115
- .table th:first-child,
3116
- .table td:first-child { padding-left: 0; }
3860
+ .data-table th:first-child,
3861
+ .data-table td:first-child { padding-left: 0; }
3117
3862
 
3118
- .table th:last-child,
3119
- .table td:last-child { padding-right: 0; }
3863
+ .data-table th:last-child,
3864
+ .data-table td:last-child { padding-right: 0; }
3120
3865
 
3121
- .table thead th {
3866
+ .data-table thead th {
3122
3867
  font-size: var(--text-xs);
3123
3868
  font-weight: var(--weight-strong);
3124
3869
  text-transform: uppercase;
@@ -3129,40 +3874,31 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3129
3874
 
3130
3875
  /* The row's own name: a `<th scope="row">`, so it carries weight rather than
3131
3876
  reading as one more cell. */
3132
- .table tbody th { font-weight: var(--weight-strong); }
3877
+ .data-table tbody th { font-weight: var(--weight-strong); }
3133
3878
 
3134
- .table tbody tr:last-child th,
3135
- .table tbody tr:last-child td { border-bottom: none; }
3879
+ .data-table tbody tr:last-child th,
3880
+ .data-table tbody tr:last-child td { border-bottom: none; }
3136
3881
 
3137
3882
  /* Numbers compare by column, so they align right and hold their tracks --
3138
3883
  proportional digits shift the alignment by a pixel per glyph, which is
3139
3884
  exactly the comparison the column was for. */
3140
- .table .num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
3885
+ .data-table .num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
3141
3886
 
3142
- .table thead th[style*='right'] { text-align: right; }
3887
+ .data-table thead th[style*='right'] { text-align: right; }
3143
3888
 
3144
3889
  /* ---- ./sizegrid.css ---- */
3145
- /* Tile size, picked as a rectangle rather than read as a notation. */
3146
- .size-grid-wrap { display: flex; align-items: center; gap: var(--space-2); }
3147
-
3148
- .size-grid { display: grid; gap: var(--space-1); }
3149
-
3150
- .size-cell {
3151
- width: 18px; height: 14px; padding: 0;
3152
- border: 1px solid var(--border); border-radius: var(--radius-sm);
3153
- background: var(--panel-2); cursor: pointer;
3890
+ /* What is left of the size grid once it draws itself.
3891
+ *
3892
+ * `on` follows the pointer; `chosen` is what is actually saved, shown only
3893
+ * when nothing is hovered so the two can never contradict each other.
3894
+ * `chosen` is a border colour and says so at the call site; `on` is a fill
3895
+ * mixed from the accent, and `color-mix` in an arbitrary value is less
3896
+ * readable than the rule it replaces. */
3897
+ .size-cell.on {
3898
+ border-color: var(--accent);
3899
+ background: color-mix(in srgb, var(--accent) 45%, transparent);
3154
3900
  }
3155
3901
 
3156
- /* `on` follows the pointer; `chosen` is what is actually saved, shown only
3157
- when nothing is hovered so the two can never contradict each other. */
3158
- .size-cell.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 45%, transparent); }
3159
-
3160
- .size-cell.chosen { border-color: var(--accent); }
3161
-
3162
- .size-cell:disabled { cursor: default; opacity: 0.5; }
3163
-
3164
- .size-ceiling { opacity: 0.55; }
3165
-
3166
3902
  /* ---- ./tooltip.css ---- */
3167
3903
  /* A callout: something worth knowing, with nothing to answer. Distinct from a
3168
3904
  `?`, which is an offer to read more, and from a banner, which is a problem.
@@ -3171,36 +3907,15 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3171
3907
  /* An info-toned `.callout`, spelled the old way. Identical to it now rather than
3172
3908
  nearly identical to it, which is the whole point of the consolidation: the
3173
3909
 
3174
- /* The `?` beside a setting, and the box it shows on hover. Built like the
3910
+ /* What is left once `Tooltip` draws its own mark: the hover and focus that
3911
+ reach the mark from its parent, and the tip -- whose closed and open
3912
+ states are three rules the cascade resolves in order, with
3913
+ `data-entering` and `data-exiting` around the transition. None of that is
3914
+ a property on one element.
3915
+
3916
+ The `?` beside a setting, and the box it shows on hover. Built like the
3175
3917
  status dot's tooltip and for the same reasons: `title` waits a second and a
3176
3918
  half, renders newlines however it likes, and cannot be styled. */
3177
- .explain {
3178
- position: relative;
3179
- flex: none;
3180
- display: inline-flex;
3181
- align-items: center;
3182
- /* The heading it follows is tracked out, which leaves the last letter's
3183
- spacing sitting between them and reads as none at all. */
3184
- margin-left: var(--space-2);
3185
- cursor: help;
3186
- /* Headings here are uppercase and tracked out, and one of these sits inside
3187
- a heading. */
3188
- text-transform: none;
3189
- letter-spacing: normal;
3190
- font-weight: 400;
3191
- }
3192
-
3193
- .explain-mark {
3194
- width: 18px;
3195
- height: 18px;
3196
- border-radius: 50%;
3197
- border: 1px solid var(--border);
3198
- color: var(--muted);
3199
- font-size: var(--text-xs);
3200
- line-height: 16px;
3201
- text-align: center;
3202
- }
3203
-
3204
3919
  .explain:hover .explain-mark,
3205
3920
  .explain:focus-visible .explain-mark { color: var(--text); border-color: var(--accent); }
3206
3921
 
@@ -3301,6 +4016,18 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3301
4016
  a shirt that lets the arm behind it show through reads as a hole. */
3302
4017
  .illo .paper { fill: var(--illo-paper); }
3303
4018
 
4019
+ /* The figure in a whole-surface `Empty`, drawn in the muted grey of the
4020
+ sentence under it.
4021
+ *
4022
+ * A utility cannot do this, and the reason is worth keeping: `.illo` sets
4023
+ * `color: inherit` and is unlayered, while every Tailwind utility lives in
4024
+ * `@layer utilities` -- and an unlayered declaration beats a layered one
4025
+ * whatever the specificity. So `text-muted` on the figure lost, silently,
4026
+ * and the shirt came out full text black beside a muted sentence. The
4027
+ * original rule already carried a note about needing two classes to beat
4028
+ * `.illo`; layers make that impossible rather than merely fiddly. */
4029
+ .nothing-figure { color: var(--muted); }
4030
+
3304
4031
  /* ---- ./field.css ---- */
3305
4032
  /* fields --------------------------------------------------------------- */
3306
4033
 
@@ -3399,7 +4126,7 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3399
4126
  .adorned > .adorned-mark,
3400
4127
  .adorned > .adorned-end { grid-area: 1 / 1; }
3401
4128
 
3402
- .adorned.block { width: 100%; }
4129
+ .adorned.ctl-block { width: 100%; }
3403
4130
 
3404
4131
  .adorned > .adorned-mark {
3405
4132
  justify-self: start;
@@ -3452,6 +4179,31 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3452
4179
 
3453
4180
  .adorned-lg .adorned-clear { padding: var(--space-2); }
3454
4181
 
4182
+ /* The eye and the clear button stop borrowing `ghost`.
4183
+ *
4184
+ * Both were `className="icon-btn ghost …"` -- reaching into `Button`'s class
4185
+ * vocabulary from another component, which worked only because `.ghost` was
4186
+ * written as `button.ghost` and they happen to be `<button>`s. It is `.btn.ghost`
4187
+ * now, so the borrowing stopped matching and `.icon-btn`'s panel background
4188
+ * came through: the baselines showed a filled tile where there had been a bare
4189
+ * glyph.
4190
+ *
4191
+ * Stated here instead. What they actually wanted from `ghost` is two lines,
4192
+ * and saying them is cheaper than either component knowing what a button's
4193
+ * classes are called. */
4194
+ .secret-eye,
4195
+ .adorned-clear {
4196
+ background: transparent;
4197
+ border-color: transparent;
4198
+ color: var(--muted);
4199
+ }
4200
+
4201
+ .secret-eye:hover:not(:disabled),
4202
+ .adorned-clear:hover:not(:disabled) {
4203
+ background: var(--panel-2);
4204
+ color: var(--text);
4205
+ }
4206
+
3455
4207
  /* ---- ./tour.css ---- */
3456
4208
  /* tour ----------------------------------------------------------------- */
3457
4209
 
@@ -3519,136 +4271,32 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3519
4271
  anchored surfaces (`.pop`, `.menu-sheet`, `.cmd`) sit over everything and a
3520
4272
  rule of equal specificity that came earlier would lose to whatever the
3521
4273
  sheets they cover happen to set. */
3522
- /* ---- ./kbd.css ---- */
3523
- /* One rule where there were two identical ones, in `menu.css` and
3524
- `command.css`. Both keep their class as well, so `Menu` and `Command` are
3525
- `.kbd .menu-key` and `.kbd .cmd-key` now: the declarations are the same, so
3526
- nothing moves, and the context classes stay available for a rule that ever
3527
- needs to tell the two apart. */
3528
- .kbd {
3529
- flex: none;
3530
- font-family: var(--font-mono);
3531
- font-size: var(--text-2xs);
3532
- color: var(--muted);
3533
- letter-spacing: var(--tracking);
3534
- }
3535
-
3536
4274
  /* ---- ./stat.css ---- */
3537
- /* A figure with furniture. `--space-1` between the three lines because they
3538
- are one object read top to bottom, not three paragraphs. */
3539
- .stat {
3540
- display: grid;
3541
- gap: var(--space-1);
3542
- padding: var(--space-4);
3543
- border: var(--border-width) solid var(--border);
3544
- border-radius: var(--radius-md);
3545
- background: var(--panel);
3546
- }
3547
-
3548
- /* Inside something already bordered. A bordered tile in a bordered row is two
3549
- boxes saying one thing. */
3550
- .stat-bare {
3551
- padding: 0;
3552
- border: 0;
3553
- border-radius: 0;
3554
- background: none;
3555
- gap: 0;
3556
- line-height: 1.25;
3557
- }
3558
-
3559
- .stat > p { margin: 0; }
3560
-
3561
- .stat-label {
3562
- color: var(--muted);
3563
- font-size: var(--text-xs);
3564
- }
3565
-
3566
- /* Tabular figures are the point: a column of numbers is compared by its
3567
- digits' positions, and proportional ones move them per row. */
3568
- .stat-value {
3569
- font-size: var(--text-lg);
3570
- font-weight: var(--weight-strong);
3571
- font-variant-numeric: tabular-nums;
3572
- }
3573
-
3574
- .stat-bare .stat-value { font-size: var(--text-sm); }
3575
-
3576
- .stat-note {
3577
- color: var(--muted);
3578
- font-size: var(--text-xs);
3579
- }
4275
+ /* What the utilities on `Stat` cannot say: a tone on the tile colouring the
4276
+ figure inside it. An attribute on the parent selecting a child is a
4277
+ relationship; a utility is a property on one element.
3580
4278
 
3581
- /* Only the figure takes the colour. Colouring the label too makes the tile a
3582
- status box, and a tone here is a remark about the number. */
4279
+ Only the figure takes the colour. Colouring the label too makes the tile a
4280
+ status box, where a tone here is a remark about the number. */
3583
4281
  .stat[data-tone='info'] .stat-value { color: var(--info); }
3584
4282
  .stat[data-tone='good'] .stat-value { color: var(--good); }
3585
4283
  .stat[data-tone='warn'] .stat-value { color: var(--warn); }
3586
4284
  .stat[data-tone='bad'] .stat-value { color: var(--bad); }
3587
4285
 
3588
4286
  /* ---- ./shell.css ---- */
3589
- /* The frame. Layout only: every colour and every step is a token, and the
3590
- page inside it owns its own spacing. */
3591
- .shell {
3592
- display: flex;
3593
- flex-direction: column;
3594
- min-height: 100vh;
3595
- background: var(--bg);
3596
- color: var(--text);
3597
- }
3598
-
3599
- .shell-head {
3600
- border-bottom: var(--border-width) solid var(--border);
3601
- background: var(--panel);
3602
- /* `flex: none` so a long page does not squeeze the header. */
3603
- flex: none;
3604
- }
3605
-
3606
- /* The measure, said once. `--shell-measure` rather than a literal so an app
3607
- with a different one sets a variable instead of overriding three rules and
3608
- missing the fourth -- which is how manage ended up with `head-inner-wide`
3609
- and `main-wide` as separate classes. */
4287
+ /* The measure, and only the measure.
4288
+ *
4289
+ * `Shell` draws itself with utilities -- it is layout and nothing else. What
4290
+ * stays here is the one value the header band, the nav band and the main band
4291
+ * all have to agree on: a custom property, so an app changes it in one place
4292
+ * rather than overriding three rules and missing the fourth. That is how
4293
+ * manage ended up carrying `head-inner-wide` and `main-wide` as separate
4294
+ * classes it had to remember together. */
3610
4295
  .shell {
3611
4296
  --shell-measure: 64rem;
3612
4297
  --shell-measure-wide: 90rem;
3613
4298
  }
3614
4299
 
3615
- .shell-head-inner,
3616
- .shell-nav,
3617
- .shell-main {
3618
- width: 100%;
3619
- max-width: var(--shell-measure);
3620
- margin-inline: auto;
3621
- padding-inline: var(--space-4);
3622
- }
3623
-
3624
- .shell-wide { max-width: var(--shell-measure-wide); }
3625
-
3626
- .shell-head-inner {
3627
- display: flex;
3628
- align-items: center;
3629
- justify-content: space-between;
3630
- gap: var(--space-4);
3631
- padding-block: var(--space-3);
3632
- min-width: 0;
3633
- }
3634
-
3635
- .shell-who {
3636
- display: flex;
3637
- align-items: center;
3638
- gap: var(--space-3);
3639
- min-width: 0;
3640
- }
3641
-
3642
- .shell-nav { padding-bottom: var(--space-2); }
3643
-
3644
- .shell-main {
3645
- flex: 1;
3646
- padding-block: var(--space-6);
3647
- /* `min-width: 0` because a flex child will not shrink below its content,
3648
- and one wide table inside then pushes the whole page sideways. */
3649
- min-width: 0;
3650
- }
3651
-
3652
4300
  /* The label beside the theme control, when it is drawn. */
3653
4301
  .theme-switch {
3654
4302
  display: flex;
@@ -3914,27 +4562,13 @@ body.splitting * { cursor: inherit !important; }
3914
4562
  }
3915
4563
 
3916
4564
  /* ---- ./menu.css ---- */
3917
- /* A list of verbs, on the same sheet as every other anchored surface. */
3918
- .menu-sheet {
3919
- background: var(--panel);
3920
- border: var(--border-width) solid var(--border);
3921
- border-radius: var(--radius-md);
3922
- box-shadow: var(--shadow-2);
3923
- min-width: 12rem;
3924
- max-width: min(20rem, calc(100vw - var(--space-6)));
3925
- max-height: 24rem;
3926
- overflow: auto;
3927
- overscroll-behavior: contain;
3928
- z-index: 60;
3929
- }
3930
-
3931
- .menu-list {
3932
- padding: var(--space-1);
3933
- outline: none;
3934
- display: grid;
3935
- gap: var(--border-width);
3936
- }
4565
+ /* What is left once `Menu` draws its sheet and its list: the rows, whose
4566
+ `data-focused`, `data-pressed` and danger states React Aria stamps on
4567
+ them, and the rules that reach from a row into what it holds. The
4568
+ `box-shadow` stays on the sheet too -- `--shadow-2` is a token, and a
4569
+ utility would have to inline it. */
3937
4570
 
4571
+ /* A list of verbs, on the same sheet as every other anchored surface. */
3938
4572
  .menu-item {
3939
4573
  display: flex;
3940
4574
  align-items: center;
@@ -3973,14 +4607,6 @@ body.splitting * { cursor: inherit !important; }
3973
4607
  /* The binding, set in the mono face at the size of the small print. Right of
3974
4608
  the row and never the reason the row is wide: `flex: none` with the label
3975
4609
  taking the slack. */
3976
- .menu-key {
3977
- flex: none;
3978
- font-family: var(--font-mono);
3979
- font-size: var(--text-2xs);
3980
- color: var(--muted);
3981
- letter-spacing: var(--tracking);
3982
- }
3983
-
3984
4610
  .menu-more { flex: none; color: var(--muted); }
3985
4611
 
3986
4612
  .menu-rule {
@@ -4016,6 +4642,8 @@ body.splitting * { cursor: inherit !important; }
4016
4642
  .menu-sheet[data-exiting] { animation: none; }
4017
4643
  }
4018
4644
 
4645
+ .menu-sheet { box-shadow: var(--shadow-2); }
4646
+
4019
4647
  /* ---- ./command.css ---- */
4020
4648
  /* The command palette: a field over the app and a list under it. */
4021
4649
  .cmd-scrim {
@@ -4133,14 +4761,6 @@ body.splitting * { cursor: inherit !important; }
4133
4761
  white-space: nowrap;
4134
4762
  }
4135
4763
 
4136
- .cmd-key {
4137
- flex: none;
4138
- font-family: var(--font-mono);
4139
- font-size: var(--text-2xs);
4140
- color: var(--muted);
4141
- letter-spacing: var(--tracking);
4142
- }
4143
-
4144
4764
  .cmd-empty {
4145
4765
  margin: 0;
4146
4766
  padding: var(--space-6) var(--space-4);
@@ -4168,23 +4788,14 @@ body.splitting * { cursor: inherit !important; }
4168
4788
  }
4169
4789
 
4170
4790
  /* ---- ./pagination.css ---- */
4171
- /* Moving through a list that does not fit, and saying how far in you are. */
4172
- .pager {
4173
- display: flex;
4174
- align-items: center;
4175
- justify-content: space-between;
4176
- gap: var(--space-4);
4177
- flex-wrap: wrap;
4178
- padding: var(--space-2) 0;
4179
- font-size: var(--text-xs);
4180
- color: var(--muted);
4181
- }
4791
+ /* What is left once `Pagination` draws its own frame: the step and page
4792
+ buttons, whose hover, focus-visible and current states are pseudo-classes
4793
+ on two selectors at once, and `.pager-count strong`, which reaches into
4794
+ text the caller passed. */
4182
4795
 
4183
- .pager-count { margin: 0; }
4796
+ /* Moving through a list that does not fit, and saying how far in you are. */
4184
4797
  .pager-count strong { color: var(--text); font-weight: var(--weight-strong); }
4185
4798
 
4186
- .pager-controls { display: flex; align-items: center; gap: var(--space-1); }
4187
-
4188
4799
  .pager-step,
4189
4800
  .pager-page {
4190
4801
  display: inline-flex;
@@ -4376,6 +4987,8 @@ body.splitting * { cursor: inherit !important; }
4376
4987
  }
4377
4988
 
4378
4989
  .ident-remove {
4990
+ /* 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`. */
4991
+ font: inherit;
4379
4992
  flex: none;
4380
4993
  display: inline-flex;
4381
4994
  align-items: center;