@wtfalch/design 0.7.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.
Files changed (41) hide show
  1. package/README.md +18 -0
  2. package/dist/components/Button.js +8 -7
  3. package/dist/components/Callout.js +1 -1
  4. package/dist/components/Card.d.ts +1 -1
  5. package/dist/components/Card.js +21 -4
  6. package/dist/components/Command.js +2 -2
  7. package/dist/components/Empty.js +22 -1
  8. package/dist/components/Field.d.ts +22 -19
  9. package/dist/components/Field.js +11 -9
  10. package/dist/components/Input.d.ts +10 -0
  11. package/dist/components/Input.js +26 -5
  12. package/dist/components/Kbd.js +11 -1
  13. package/dist/components/Menu.js +3 -3
  14. package/dist/components/Modal.js +2 -2
  15. package/dist/components/Pagination.js +6 -1
  16. package/dist/components/Progress.js +8 -1
  17. package/dist/components/Rows.d.ts +1 -1
  18. package/dist/components/Rows.js +1 -1
  19. package/dist/components/Select.d.ts +3 -1
  20. package/dist/components/Select.js +28 -6
  21. package/dist/components/Shell.d.ts +35 -0
  22. package/dist/components/Shell.js +36 -0
  23. package/dist/components/SizeGrid.js +14 -2
  24. package/dist/components/Stat.js +11 -2
  25. package/dist/components/Table.js +7 -1
  26. package/dist/components/Textarea.js +14 -4
  27. package/dist/components/ThemeSwitch.d.ts +19 -0
  28. package/dist/components/ThemeSwitch.js +65 -0
  29. package/dist/components/Toast.js +1 -1
  30. package/dist/components/Tooltip.js +16 -2
  31. package/dist/components/Tour.js +1 -1
  32. package/dist/components/fieldWiring.d.ts +44 -0
  33. package/dist/components/fieldWiring.js +30 -0
  34. package/dist/index.d.ts +10 -1
  35. package/dist/index.js +8 -0
  36. package/dist/styles/index.css +1110 -355
  37. package/dist/tf.css +1110 -355
  38. package/dist/themes/choice.d.ts +71 -0
  39. package/dist/themes/choice.js +81 -0
  40. package/dist/valet.css +1110 -355
  41. package/package.json +3 -1
package/dist/valet.css CHANGED
@@ -282,6 +282,705 @@
282
282
  *
283
283
  * `scripts/copy-css.mjs` flattens these into one file at build time.
284
284
  */
285
+ /* ---- ./_tailwind.built.css ---- */
286
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
287
+ @layer properties;
288
+ @layer theme, base, components, utilities;
289
+ @layer theme {
290
+ :root, :host {
291
+ --font-sans: var(--font-sans);
292
+ --font-mono: var(--font-mono);
293
+ --text-xs: var(--text-xs);
294
+ --text-xs--line-height: inherit;
295
+ --text-sm: var(--text-sm);
296
+ --text-sm--line-height: inherit;
297
+ --text-base: var(--text-base);
298
+ --text-lg: var(--text-lg);
299
+ --text-lg--line-height: inherit;
300
+ --text-xl: var(--text-xl);
301
+ --font-weight-normal: var(--weight);
302
+ --tracking-normal: var(--tracking);
303
+ --radius-sm: var(--radius-sm);
304
+ --radius-md: var(--radius-md);
305
+ --radius-lg: var(--radius-lg);
306
+ --ease-out: cubic-bezier(0, 0, 0.2, 1);
307
+ --spacing-1: var(--space-1);
308
+ --spacing-2: var(--space-2);
309
+ --spacing-3: var(--space-3);
310
+ --spacing-4: var(--space-4);
311
+ --spacing-5: var(--space-5);
312
+ --spacing-6: var(--space-6);
313
+ --spacing-10: var(--space-10);
314
+ --text-2xs--line-height: inherit;
315
+ --text-2xs: var(--text-2xs);
316
+ --text-md: var(--text-md);
317
+ --font-weight-strong: var(--weight-strong);
318
+ --radius-pill: var(--radius-pill);
319
+ --color-text: var(--text);
320
+ --color-muted: var(--muted);
321
+ --color-accent: var(--accent);
322
+ --color-border: var(--border);
323
+ --color-border-strong: var(--border-strong);
324
+ }
325
+ }
326
+ @layer utilities {
327
+ .collapse {
328
+ visibility: collapse;
329
+ }
330
+ .invisible {
331
+ visibility: hidden;
332
+ }
333
+ .visible {
334
+ visibility: visible;
335
+ }
336
+ .sr-only {
337
+ position: absolute;
338
+ width: 1px;
339
+ height: 1px;
340
+ padding: 0;
341
+ margin: -1px;
342
+ overflow: hidden;
343
+ clip-path: inset(50%);
344
+ white-space: nowrap;
345
+ border-width: 0;
346
+ }
347
+ .absolute {
348
+ position: absolute;
349
+ }
350
+ .fixed {
351
+ position: fixed;
352
+ }
353
+ .relative {
354
+ position: relative;
355
+ }
356
+ .sticky {
357
+ position: sticky;
358
+ }
359
+ .z-\[60\] {
360
+ z-index: 60;
361
+ }
362
+ .container {
363
+ width: 100%;
364
+ @media (width >= 40rem) {
365
+ max-width: 40rem;
366
+ }
367
+ @media (width >= 48rem) {
368
+ max-width: 48rem;
369
+ }
370
+ @media (width >= 64rem) {
371
+ max-width: 64rem;
372
+ }
373
+ @media (width >= 80rem) {
374
+ max-width: 80rem;
375
+ }
376
+ @media (width >= 96rem) {
377
+ max-width: 96rem;
378
+ }
379
+ }
380
+ .m-0 {
381
+ margin: 0px;
382
+ }
383
+ .m-auto {
384
+ margin: auto;
385
+ }
386
+ .mx-auto {
387
+ margin-inline: auto;
388
+ }
389
+ .mt-1 {
390
+ margin-top: var(--spacing-1);
391
+ }
392
+ .mb-3 {
393
+ margin-bottom: var(--spacing-3);
394
+ }
395
+ .ml-2 {
396
+ margin-left: var(--spacing-2);
397
+ }
398
+ .block {
399
+ display: block;
400
+ }
401
+ .contents {
402
+ display: contents;
403
+ }
404
+ .flex {
405
+ display: flex;
406
+ }
407
+ .grid {
408
+ display: grid;
409
+ }
410
+ .hidden {
411
+ display: none;
412
+ }
413
+ .inline {
414
+ display: inline;
415
+ }
416
+ .inline-flex {
417
+ display: inline-flex;
418
+ }
419
+ .table {
420
+ display: table;
421
+ }
422
+ .h-\[14px\] {
423
+ height: 14px;
424
+ }
425
+ .h-\[18px\] {
426
+ height: 18px;
427
+ }
428
+ .max-h-\[24rem\] {
429
+ max-height: 24rem;
430
+ }
431
+ .max-h-\[280px\] {
432
+ max-height: 280px;
433
+ }
434
+ .min-h-0 {
435
+ min-height: 0px;
436
+ }
437
+ .min-h-screen {
438
+ min-height: 100vh;
439
+ }
440
+ .w-\[18px\] {
441
+ width: 18px;
442
+ }
443
+ .w-full {
444
+ width: 100%;
445
+ }
446
+ .max-w-\[46ch\] {
447
+ max-width: 46ch;
448
+ }
449
+ .max-w-\[min\(20rem\,calc\(100vw-var\(--space-6\)\)\)\] {
450
+ max-width: min(20rem, calc(100vw - var(--space-6)));
451
+ }
452
+ .max-w-\[var\(--shell-measure\)\] {
453
+ max-width: var(--shell-measure);
454
+ }
455
+ .max-w-\[var\(--shell-measure-wide\)\] {
456
+ max-width: var(--shell-measure-wide);
457
+ }
458
+ .min-w-0 {
459
+ min-width: 0px;
460
+ }
461
+ .min-w-\[12rem\] {
462
+ min-width: 12rem;
463
+ }
464
+ .flex-1 {
465
+ flex: 1;
466
+ }
467
+ .flex-none {
468
+ flex: none;
469
+ }
470
+ .shrink {
471
+ flex-shrink: 1;
472
+ }
473
+ .grow {
474
+ flex-grow: 1;
475
+ }
476
+ .transform {
477
+ transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
478
+ }
479
+ .cursor-help {
480
+ cursor: help;
481
+ }
482
+ .cursor-pointer {
483
+ cursor: pointer;
484
+ }
485
+ .resize {
486
+ resize: both;
487
+ }
488
+ .list-none {
489
+ list-style-type: none;
490
+ }
491
+ .flex-col {
492
+ flex-direction: column;
493
+ }
494
+ .flex-wrap {
495
+ flex-wrap: wrap;
496
+ }
497
+ .items-baseline {
498
+ align-items: baseline;
499
+ }
500
+ .items-center {
501
+ align-items: center;
502
+ }
503
+ .items-start {
504
+ align-items: flex-start;
505
+ }
506
+ .justify-between {
507
+ justify-content: space-between;
508
+ }
509
+ .justify-items-center {
510
+ justify-items: center;
511
+ }
512
+ .gap-0 {
513
+ gap: 0px;
514
+ }
515
+ .gap-1 {
516
+ gap: var(--spacing-1);
517
+ }
518
+ .gap-2 {
519
+ gap: var(--spacing-2);
520
+ }
521
+ .gap-3 {
522
+ gap: var(--spacing-3);
523
+ }
524
+ .gap-4 {
525
+ gap: var(--spacing-4);
526
+ }
527
+ .gap-\[var\(--border-width\)\] {
528
+ gap: var(--border-width);
529
+ }
530
+ .self-center {
531
+ align-self: center;
532
+ }
533
+ .truncate {
534
+ overflow: hidden;
535
+ text-overflow: ellipsis;
536
+ white-space: nowrap;
537
+ }
538
+ .overflow-auto {
539
+ overflow: auto;
540
+ }
541
+ .overflow-hidden {
542
+ overflow: hidden;
543
+ }
544
+ .overflow-x-auto {
545
+ overflow-x: auto;
546
+ }
547
+ .overflow-y-auto {
548
+ overflow-y: auto;
549
+ }
550
+ .overscroll-contain {
551
+ overscroll-behavior: contain;
552
+ }
553
+ .rounded {
554
+ border-radius: 0.25rem;
555
+ }
556
+ .rounded-full {
557
+ border-radius: calc(infinity * 1px);
558
+ }
559
+ .rounded-md {
560
+ border-radius: var(--radius-md);
561
+ }
562
+ .rounded-sm {
563
+ border-radius: var(--radius-sm);
564
+ }
565
+ .border {
566
+ border-style: var(--tw-border-style);
567
+ border-width: var(--border-width);
568
+ }
569
+ .border-b {
570
+ border-bottom-style: var(--tw-border-style);
571
+ border-bottom-width: var(--border-width);
572
+ }
573
+ .border-dashed {
574
+ --tw-border-style: dashed;
575
+ border-style: dashed;
576
+ }
577
+ .border-accent {
578
+ border-color: var(--color-accent);
579
+ }
580
+ .border-border {
581
+ border-color: var(--color-border);
582
+ }
583
+ .border-border-strong {
584
+ border-color: var(--color-border-strong);
585
+ }
586
+ .p-0 {
587
+ padding: 0px;
588
+ }
589
+ .p-1 {
590
+ padding: var(--spacing-1);
591
+ }
592
+ .p-2 {
593
+ padding: var(--spacing-2);
594
+ }
595
+ .p-4 {
596
+ padding: var(--spacing-4);
597
+ }
598
+ .px-4 {
599
+ padding-inline: var(--spacing-4);
600
+ }
601
+ .py-2 {
602
+ padding-block: var(--spacing-2);
603
+ }
604
+ .py-3 {
605
+ padding-block: var(--spacing-3);
606
+ }
607
+ .py-5 {
608
+ padding-block: var(--spacing-5);
609
+ }
610
+ .py-6 {
611
+ padding-block: var(--spacing-6);
612
+ }
613
+ .py-10 {
614
+ padding-block: var(--spacing-10);
615
+ }
616
+ .pb-2 {
617
+ padding-bottom: var(--spacing-2);
618
+ }
619
+ .pb-3 {
620
+ padding-bottom: var(--spacing-3);
621
+ }
622
+ .text-center {
623
+ text-align: center;
624
+ }
625
+ .font-mono {
626
+ font-family: var(--font-mono);
627
+ }
628
+ .text-2xs {
629
+ font-size: var(--text-2xs);
630
+ line-height: var(--tw-leading, var(--text-2xs--line-height));
631
+ }
632
+ .text-lg {
633
+ font-size: var(--text-lg);
634
+ line-height: var(--tw-leading, var(--text-lg--line-height));
635
+ }
636
+ .text-sm {
637
+ font-size: var(--text-sm);
638
+ line-height: var(--tw-leading, var(--text-sm--line-height));
639
+ }
640
+ .text-xs {
641
+ font-size: var(--text-xs);
642
+ line-height: var(--tw-leading, var(--text-xs--line-height));
643
+ }
644
+ .leading-\[0\] {
645
+ --tw-leading: 0;
646
+ line-height: 0;
647
+ }
648
+ .leading-\[1\.5\] {
649
+ --tw-leading: 1.5;
650
+ line-height: 1.5;
651
+ }
652
+ .leading-\[1\.25\] {
653
+ --tw-leading: 1.25;
654
+ line-height: 1.25;
655
+ }
656
+ .leading-\[16px\] {
657
+ --tw-leading: 16px;
658
+ line-height: 16px;
659
+ }
660
+ .font-normal {
661
+ --tw-font-weight: var(--font-weight-normal);
662
+ font-weight: var(--font-weight-normal);
663
+ }
664
+ .font-strong {
665
+ --tw-font-weight: var(--font-weight-strong);
666
+ font-weight: var(--font-weight-strong);
667
+ }
668
+ .tracking-normal {
669
+ --tw-tracking: var(--tracking-normal);
670
+ letter-spacing: var(--tracking-normal);
671
+ }
672
+ .text-ellipsis {
673
+ text-overflow: ellipsis;
674
+ }
675
+ .whitespace-nowrap {
676
+ white-space: nowrap;
677
+ }
678
+ .text-muted {
679
+ color: var(--color-muted);
680
+ }
681
+ .text-text {
682
+ color: var(--color-text);
683
+ }
684
+ .lowercase {
685
+ text-transform: lowercase;
686
+ }
687
+ .normal-case {
688
+ text-transform: none;
689
+ }
690
+ .uppercase {
691
+ text-transform: uppercase;
692
+ }
693
+ .tabular-nums {
694
+ --tw-numeric-spacing: tabular-nums;
695
+ font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
696
+ }
697
+ .opacity-0 {
698
+ opacity: 0%;
699
+ }
700
+ .opacity-60 {
701
+ opacity: 60%;
702
+ }
703
+ .opacity-\[0\.55\] {
704
+ opacity: 0.55;
705
+ }
706
+ .shadow {
707
+ --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));
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
+ .ring {
711
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
712
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
713
+ }
714
+ .outline {
715
+ outline-style: var(--tw-outline-style);
716
+ outline-width: 1px;
717
+ }
718
+ .blur {
719
+ --tw-blur: blur(8px);
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
+ .sepia {
723
+ --tw-sepia: sepia(100%);
724
+ 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,);
725
+ }
726
+ .filter {
727
+ 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,);
728
+ }
729
+ .outline-none {
730
+ --tw-outline-style: none;
731
+ outline-style: none;
732
+ }
733
+ .\[font\:inherit\] {
734
+ font: inherit;
735
+ }
736
+ .surface-bg {
737
+ background: var(--bg);
738
+ }
739
+ .surface-panel {
740
+ background: var(--panel);
741
+ }
742
+ .surface-panel-2 {
743
+ background: var(--panel-2);
744
+ }
745
+ .disabled\:cursor-default:disabled {
746
+ cursor: default;
747
+ }
748
+ .disabled\:opacity-50:disabled {
749
+ opacity: 50%;
750
+ }
751
+ .\[\&\>p\]\:m-0 > p {
752
+ margin: 0px;
753
+ }
754
+ }
755
+ @property --tw-rotate-x {
756
+ syntax: "*";
757
+ inherits: false;
758
+ }
759
+ @property --tw-rotate-y {
760
+ syntax: "*";
761
+ inherits: false;
762
+ }
763
+ @property --tw-rotate-z {
764
+ syntax: "*";
765
+ inherits: false;
766
+ }
767
+ @property --tw-skew-x {
768
+ syntax: "*";
769
+ inherits: false;
770
+ }
771
+ @property --tw-skew-y {
772
+ syntax: "*";
773
+ inherits: false;
774
+ }
775
+ @property --tw-border-style {
776
+ syntax: "*";
777
+ inherits: false;
778
+ initial-value: solid;
779
+ }
780
+ @property --tw-leading {
781
+ syntax: "*";
782
+ inherits: false;
783
+ }
784
+ @property --tw-font-weight {
785
+ syntax: "*";
786
+ inherits: false;
787
+ }
788
+ @property --tw-tracking {
789
+ syntax: "*";
790
+ inherits: false;
791
+ }
792
+ @property --tw-ordinal {
793
+ syntax: "*";
794
+ inherits: false;
795
+ }
796
+ @property --tw-slashed-zero {
797
+ syntax: "*";
798
+ inherits: false;
799
+ }
800
+ @property --tw-numeric-figure {
801
+ syntax: "*";
802
+ inherits: false;
803
+ }
804
+ @property --tw-numeric-spacing {
805
+ syntax: "*";
806
+ inherits: false;
807
+ }
808
+ @property --tw-numeric-fraction {
809
+ syntax: "*";
810
+ inherits: false;
811
+ }
812
+ @property --tw-shadow {
813
+ syntax: "*";
814
+ inherits: false;
815
+ initial-value: 0 0 #0000;
816
+ }
817
+ @property --tw-shadow-color {
818
+ syntax: "*";
819
+ inherits: false;
820
+ }
821
+ @property --tw-shadow-alpha {
822
+ syntax: "<percentage>";
823
+ inherits: false;
824
+ initial-value: 100%;
825
+ }
826
+ @property --tw-inset-shadow {
827
+ syntax: "*";
828
+ inherits: false;
829
+ initial-value: 0 0 #0000;
830
+ }
831
+ @property --tw-inset-shadow-color {
832
+ syntax: "*";
833
+ inherits: false;
834
+ }
835
+ @property --tw-inset-shadow-alpha {
836
+ syntax: "<percentage>";
837
+ inherits: false;
838
+ initial-value: 100%;
839
+ }
840
+ @property --tw-ring-color {
841
+ syntax: "*";
842
+ inherits: false;
843
+ }
844
+ @property --tw-ring-shadow {
845
+ syntax: "*";
846
+ inherits: false;
847
+ initial-value: 0 0 #0000;
848
+ }
849
+ @property --tw-inset-ring-color {
850
+ syntax: "*";
851
+ inherits: false;
852
+ }
853
+ @property --tw-inset-ring-shadow {
854
+ syntax: "*";
855
+ inherits: false;
856
+ initial-value: 0 0 #0000;
857
+ }
858
+ @property --tw-ring-inset {
859
+ syntax: "*";
860
+ inherits: false;
861
+ }
862
+ @property --tw-ring-offset-width {
863
+ syntax: "<length>";
864
+ inherits: false;
865
+ initial-value: 0px;
866
+ }
867
+ @property --tw-ring-offset-color {
868
+ syntax: "*";
869
+ inherits: false;
870
+ initial-value: #fff;
871
+ }
872
+ @property --tw-ring-offset-shadow {
873
+ syntax: "*";
874
+ inherits: false;
875
+ initial-value: 0 0 #0000;
876
+ }
877
+ @property --tw-outline-style {
878
+ syntax: "*";
879
+ inherits: false;
880
+ initial-value: solid;
881
+ }
882
+ @property --tw-blur {
883
+ syntax: "*";
884
+ inherits: false;
885
+ }
886
+ @property --tw-brightness {
887
+ syntax: "*";
888
+ inherits: false;
889
+ }
890
+ @property --tw-contrast {
891
+ syntax: "*";
892
+ inherits: false;
893
+ }
894
+ @property --tw-grayscale {
895
+ syntax: "*";
896
+ inherits: false;
897
+ }
898
+ @property --tw-hue-rotate {
899
+ syntax: "*";
900
+ inherits: false;
901
+ }
902
+ @property --tw-invert {
903
+ syntax: "*";
904
+ inherits: false;
905
+ }
906
+ @property --tw-opacity {
907
+ syntax: "*";
908
+ inherits: false;
909
+ }
910
+ @property --tw-saturate {
911
+ syntax: "*";
912
+ inherits: false;
913
+ }
914
+ @property --tw-sepia {
915
+ syntax: "*";
916
+ inherits: false;
917
+ }
918
+ @property --tw-drop-shadow {
919
+ syntax: "*";
920
+ inherits: false;
921
+ }
922
+ @property --tw-drop-shadow-color {
923
+ syntax: "*";
924
+ inherits: false;
925
+ }
926
+ @property --tw-drop-shadow-alpha {
927
+ syntax: "<percentage>";
928
+ inherits: false;
929
+ initial-value: 100%;
930
+ }
931
+ @property --tw-drop-shadow-size {
932
+ syntax: "*";
933
+ inherits: false;
934
+ }
935
+ @layer properties {
936
+ @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
937
+ *, ::before, ::after, ::backdrop {
938
+ --tw-rotate-x: initial;
939
+ --tw-rotate-y: initial;
940
+ --tw-rotate-z: initial;
941
+ --tw-skew-x: initial;
942
+ --tw-skew-y: initial;
943
+ --tw-border-style: solid;
944
+ --tw-leading: initial;
945
+ --tw-font-weight: initial;
946
+ --tw-tracking: initial;
947
+ --tw-ordinal: initial;
948
+ --tw-slashed-zero: initial;
949
+ --tw-numeric-figure: initial;
950
+ --tw-numeric-spacing: initial;
951
+ --tw-numeric-fraction: initial;
952
+ --tw-shadow: 0 0 #0000;
953
+ --tw-shadow-color: initial;
954
+ --tw-shadow-alpha: 100%;
955
+ --tw-inset-shadow: 0 0 #0000;
956
+ --tw-inset-shadow-color: initial;
957
+ --tw-inset-shadow-alpha: 100%;
958
+ --tw-ring-color: initial;
959
+ --tw-ring-shadow: 0 0 #0000;
960
+ --tw-inset-ring-color: initial;
961
+ --tw-inset-ring-shadow: 0 0 #0000;
962
+ --tw-ring-inset: initial;
963
+ --tw-ring-offset-width: 0px;
964
+ --tw-ring-offset-color: #fff;
965
+ --tw-ring-offset-shadow: 0 0 #0000;
966
+ --tw-outline-style: solid;
967
+ --tw-blur: initial;
968
+ --tw-brightness: initial;
969
+ --tw-contrast: initial;
970
+ --tw-grayscale: initial;
971
+ --tw-hue-rotate: initial;
972
+ --tw-invert: initial;
973
+ --tw-opacity: initial;
974
+ --tw-saturate: initial;
975
+ --tw-sepia: initial;
976
+ --tw-drop-shadow: initial;
977
+ --tw-drop-shadow-color: initial;
978
+ --tw-drop-shadow-alpha: 100%;
979
+ --tw-drop-shadow-size: initial;
980
+ }
981
+ }
982
+ }
983
+
285
984
  /* ---- ./base.css ---- */
286
985
  /* Light, only under the `system` theme. This used to override `:root`
287
986
  unconditionally, so choosing a dark theme on a light-mode laptop got
@@ -375,23 +1074,31 @@ body::before {
375
1074
  `[aria-disabled='true']` twin rather than a rewrite, which leaves each
376
1075
  `button` selector at the specificity it had and every committed baseline
377
1076
  untouched. */
378
- a.btn {
1077
+ .btn {
1078
+ /* The three an anchor brings that a `<button>` does not. Harmless on the
1079
+ button form -- it has no underline and centres already -- and the reason
1080
+ `Button` puts one class on both elements rather than keeping a twin
1081
+ selector for each. */
379
1082
  text-decoration: none;
380
1083
  color: var(--text);
381
1084
  text-align: center;
382
1085
  }
383
1086
 
384
- a.btn[aria-disabled='true'] {
1087
+ .btn[aria-disabled='true'] {
385
1088
  /* A disabled anchor still takes the pointer, so say so. The click itself is
386
1089
  blocked in the capture phase by the component. */
387
1090
  cursor: not-allowed;
388
1091
  }
389
1092
 
390
- button,
391
- a.btn,
392
- input,
393
- textarea,
394
- select {
1093
+ /* `:where`, so this base carries no specificity of its own.
1094
+ *
1095
+ * It was `button, input, textarea, select` -- an element selector, which
1096
+ * `.size-sm` and every other class outranked without anyone thinking about
1097
+ * it. Moving it to `.btn` to stop a tab inheriting a control's look raised it
1098
+ * to a class, so it tied with `.size-sm` and won on source order: every
1099
+ * button came out `8px 12px` whatever size it asked for. `:where` puts it
1100
+ * back to zero, which is what the element selector was silently providing. */
1101
+ :where(.btn, input, textarea, select) {
395
1102
  font: inherit;
396
1103
  color: var(--text);
397
1104
  background: var(--control);
@@ -412,8 +1119,7 @@ select {
412
1119
  the icon; the button simply had no opinion about how to arrange two things.
413
1120
  `inline-flex` and a gap gives it one, and the gap replaces the space
414
1121
  character people were putting between an icon and its label. */
415
- button,
416
- a.btn {
1122
+ .btn {
417
1123
  display: inline-flex;
418
1124
  align-items: center;
419
1125
  justify-content: center;
@@ -429,18 +1135,41 @@ a.btn {
429
1135
 
430
1136
  Several places already drew this by hand as `.chat-bar-btn.on`. This is the
431
1137
  same look, said once, on a plain button. */
432
- button[aria-pressed='true'] {
1138
+ .btn[aria-pressed='true'] {
433
1139
  border-color: var(--accent);
434
1140
  color: var(--accent);
435
1141
  background: color-mix(in srgb, var(--accent) 10%, var(--panel));
436
1142
  }
437
1143
 
438
- button[aria-pressed='true']:hover:not(:disabled) { border-color: var(--accent); }
1144
+ .btn[aria-pressed='true']:hover:not(:disabled) { border-color: var(--accent); }
439
1145
 
440
1146
  /* The glyph never shrinks: in a narrow row flexbox would take it from the icon
441
1147
  before the text, and half an icon is not a smaller icon. */
442
- button > svg,
443
- a.btn > svg { flex: none; }
1148
+ .btn > svg { flex: none; }
1149
+
1150
+ /* `Select`'s control wears the control styling, and says so.
1151
+ *
1152
+ * It renders React Aria's `Button` -- a bare `<button>` -- and used to take
1153
+ * `font`, colour, the surface and the border from the
1154
+ * `button, input, textarea, select` reset without asking. When that reset
1155
+ * moved to `.btn`, so a tab and a row would stop inheriting a control's look
1156
+ * only to undo it, this lost all of it at once.
1157
+ *
1158
+ * Here rather than in `select.css`, and that placement is the rule, not
1159
+ * tidiness: `font` is a shorthand and it resets `font-size`, so declared
1160
+ * after `.size-sm` below it silently makes every small control full size.
1161
+ * `index.css` says the import order is behaviour; this is what that means.
1162
+ *
1163
+ * `:where` for the same reason the reset above uses it: this is a base a
1164
+ * `size` class has to be able to beat, and a bare class ties with one. */
1165
+ :where(.sel-control) {
1166
+ font: inherit;
1167
+ color: var(--text);
1168
+ background: var(--control);
1169
+ border: var(--border-width) solid var(--border-strong);
1170
+ border-radius: var(--radius);
1171
+ cursor: pointer;
1172
+ }
444
1173
 
445
1174
  .size-sm { font-size: var(--text-xs); padding: var(--space-1) var(--space-2); }
446
1175
 
@@ -465,8 +1194,7 @@ input, textarea, select { width: 100%; }
465
1194
  same disagreement written twice. */
466
1195
  textarea { resize: none; }
467
1196
 
468
- button:hover:not(:disabled),
469
- a.btn:hover:not([aria-disabled='true']),
1197
+ .btn:hover:not(:disabled),
470
1198
  input:hover:not(:disabled),
471
1199
  textarea:hover:not(:disabled),
472
1200
  select:hover:not(:disabled) { border-color: var(--accent); }
@@ -474,8 +1202,7 @@ select:hover:not(:disabled) { border-color: var(--accent); }
474
1202
  /* `:focus-visible` on the button so a click does not leave a ring behind it,
475
1203
  `:focus` on the fields because a focused field is a place you are typing and
476
1204
  should say so however you got there. */
477
- button:focus-visible,
478
- a.btn:focus-visible,
1205
+ .btn:focus-visible,
479
1206
  input:focus,
480
1207
  textarea:focus,
481
1208
  select:focus { outline: none; border-color: var(--accent); }
@@ -568,14 +1295,13 @@ input[type='radio']:focus-visible { outline: none; box-shadow: var(--focus-ring)
568
1295
  input[type='checkbox']:disabled,
569
1296
  input[type='radio']:disabled { cursor: not-allowed; background: var(--panel-2); }
570
1297
 
571
- button:disabled,
572
- a.btn[aria-disabled='true'],
1298
+ .btn:disabled,
1299
+ .btn[aria-disabled='true'],
573
1300
  input:disabled,
574
1301
  textarea:disabled,
575
1302
  select:disabled { opacity: 0.45; cursor: not-allowed; }
576
1303
 
577
- button.primary,
578
- a.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
1304
+ .btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
579
1305
 
580
1306
  /* A disabled primary is not a faded primary.
581
1307
  `opacity: .45` was the whole of it, and a coloured fill at 45% is still a
@@ -583,8 +1309,7 @@ a.btn.primary { background: var(--accent); border-color: var(--accent); color: v
583
1309
  thing on the panel. That got worse when onboarding started holding people at
584
1310
  a disabled Next. So it gives up the fill entirely and becomes the shape of a
585
1311
  disabled control -- the opacity then has nothing left to do. */
586
- button.primary:disabled,
587
- a.btn.primary[aria-disabled='true'] {
1312
+ .btn.primary:disabled {
588
1313
  background: var(--panel-2);
589
1314
  border-color: var(--border);
590
1315
  color: var(--muted);
@@ -598,7 +1323,7 @@ a.btn.primary[aria-disabled='true'] {
598
1323
  line under it that travels.
599
1324
  `aria-busy` rather than a class: the attribute is what a screen reader reads,
600
1325
  and styling the thing that carries the meaning is how they stay in step. */
601
- button[aria-busy='true'] {
1326
+ .btn[aria-busy='true'] {
602
1327
  position: relative;
603
1328
  overflow: hidden;
604
1329
  cursor: progress;
@@ -611,18 +1336,18 @@ button[aria-busy='true'] {
611
1336
  something that is working, that reads as a control that failed and went
612
1337
  grey. So a busy button keeps every colour it had and takes only the loss of
613
1338
  the cursor and the press; the travelling line is what says why. */
614
- button[aria-busy='true']:disabled {
1339
+ .btn[aria-busy='true']:disabled {
615
1340
  opacity: 1;
616
1341
  cursor: progress;
617
1342
  }
618
1343
 
619
- button.primary[aria-busy='true']:disabled {
1344
+ .btn.primary[aria-busy='true']:disabled {
620
1345
  background: var(--accent);
621
1346
  border-color: var(--accent);
622
1347
  color: var(--on-accent);
623
1348
  }
624
1349
 
625
- button[aria-busy='true']::after {
1350
+ .btn[aria-busy='true']::after {
626
1351
  content: '';
627
1352
  position: absolute;
628
1353
  left: 0;
@@ -654,15 +1379,13 @@ button[aria-busy='true']::after {
654
1379
 
655
1380
  /* Quiet: no border, no fill. For the action beside the action -- Cancel next to
656
1381
  Save -- where a bordered box competes with the thing it is next to. */
657
- button.ghost,
658
- a.btn.ghost {
1382
+ .btn.ghost {
659
1383
  background: transparent;
660
1384
  border-color: transparent;
661
1385
  color: var(--muted);
662
1386
  }
663
1387
 
664
- button.ghost:hover:not(:disabled),
665
- a.btn.ghost:hover:not([aria-disabled='true']) {
1388
+ .btn.ghost:hover:not(:disabled) {
666
1389
  background: var(--panel-2);
667
1390
  border-color: transparent;
668
1391
  color: var(--text);
@@ -744,7 +1467,7 @@ a.btn.ghost:hover:not([aria-disabled='true']) {
744
1467
  header.) */
745
1468
  .row { display: flex; align-items: center; gap: var(--space-3); }
746
1469
 
747
- .grow { flex: 1; min-width: 0; }
1470
+ .ctl-grow { flex: 1; min-width: 0; }
748
1471
 
749
1472
  .mono { font-family: var(--font-mono); font-size: var(--text-sm); }
750
1473
 
@@ -808,10 +1531,17 @@ a.btn.ghost:hover:not([aria-disabled='true']) {
808
1531
  /* A pill following a name needs the gap outside its own border. Leading
809
1532
  whitespace inside the span puts it under the pill's background instead,
810
1533
  which reads as the badge touching the word it qualifies. */
811
- .row-pill { margin-left: var(--space-2); vertical-align: baseline; }
1534
+ /* A pill beside a name, and the one thing that keeps the pair working:
1535
+ `flex: none`, so the pill never shrinks and the name ellipsises instead.
1536
+ That was `rows.css`'s `.named > .pill`, which meant anybody laying out a
1537
+ name and its pills had to know a package class by name to get it. The
1538
+ variant already says "I am in a row"; it may as well say what that costs. */
1539
+ .row-pill { margin-left: var(--space-2); vertical-align: baseline; flex: none; }
812
1540
 
813
1541
  /* The tile's only control. Dim until hovered: it is chrome on someone's app. */
814
1542
  .icon-btn {
1543
+ /* 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`. */
1544
+ font: inherit;
815
1545
  display: inline-flex; align-items: center; padding: var(--space-1);
816
1546
  background: none; border: 0; color: var(--muted); opacity: 0.6; cursor: pointer;
817
1547
  }
@@ -877,19 +1607,17 @@ a.btn.ghost:hover:not([aria-disabled='true']) {
877
1607
  transition: none;
878
1608
  }
879
1609
 
880
- button.primary:active:not(:disabled),
881
- a.btn.primary:active:not([aria-disabled='true']) {
1610
+ .btn.primary:active:not(:disabled) {
882
1611
  background: color-mix(in srgb, var(--text) var(--press-ink), var(--accent));
883
1612
  border-color: color-mix(in srgb, var(--text) var(--press-ink), var(--accent));
884
1613
  }
885
1614
 
886
- button.ghost:active:not(:disabled),
887
- a.btn.ghost:active:not([aria-disabled='true']) {
1615
+ .btn.ghost:active:not(:disabled) {
888
1616
  background: color-mix(in srgb, var(--text) var(--press-ink), var(--panel-2));
889
1617
  border-color: transparent;
890
1618
  }
891
1619
 
892
- button[aria-pressed='true']:active:not(:disabled) {
1620
+ .btn[aria-pressed='true']:active:not(:disabled) {
893
1621
  background: color-mix(in srgb, var(--accent) 18%, var(--panel));
894
1622
  }
895
1623
 
@@ -1060,7 +1788,7 @@ input:active, textarea:active, select:active { transform: none; }
1060
1788
 
1061
1789
  /* The top bar carries the title, the views and the way into settings. Nothing
1062
1790
  that belongs to a conversation lives here any more, so it can breathe. */
1063
- .topbar .grow { flex: 1; min-width: 0; }
1791
+ .topbar .ctl-grow { flex: 1; min-width: 0; }
1064
1792
 
1065
1793
  /* Back on the left, Next on the right, with the gap between them doing the
1066
1794
  separating -- so the button that goes forward is in the same place on both
@@ -1092,6 +1820,57 @@ input:active, textarea:active, select:active { transform: none; }
1092
1820
  to { opacity: 1; }
1093
1821
  }
1094
1822
 
1823
+ /* The states the `button` element rule used to hand every bare `<button>` in
1824
+ the package, now asked for by name.
1825
+ *
1826
+ * This is the half of the decoupling that a screenshot cannot show: `:focus-visible`
1827
+ * is not photographed, so nine components would have lost their focus ring in
1828
+ * silence. `keyboard.spec.ts` is what actually holds this.
1829
+ *
1830
+ * `.btn` has its own, in the rules above. These are the ones that are a button
1831
+ * for their behaviour and not for their appearance -- a tab, a row, a card, a
1832
+ * pager step, a grid cell, a remove cross. */
1833
+ .tab:focus-visible,
1834
+ .row-hit:focus-visible,
1835
+ .card:focus-visible,
1836
+ .ident-remove:focus-visible,
1837
+ .size-cell:focus-visible,
1838
+ .pager-step:focus-visible,
1839
+ .pager-page:focus-visible,
1840
+ .sel-control:focus-visible {
1841
+ outline: 2px solid var(--accent);
1842
+ outline-offset: 2px;
1843
+ }
1844
+
1845
+ /* `:disabled` came from the same rule. A control that cannot be pressed has to
1846
+ say so whatever element it is. */
1847
+ .tab:disabled,
1848
+ .row-hit:disabled,
1849
+ .card:disabled,
1850
+ .ident-remove:disabled,
1851
+ .size-cell:disabled,
1852
+ .pager-step:disabled,
1853
+ .pager-page:disabled,
1854
+ .sel-control:disabled {
1855
+ opacity: 0.45;
1856
+ cursor: not-allowed;
1857
+ }
1858
+
1859
+ /* The anchor form's disabled states, alongside the button's.
1860
+ *
1861
+ * `.btn` is one class on two elements, and an `<a>` has no `:disabled` -- it
1862
+ * carries `aria-disabled` and a capture-phase click block instead. Every
1863
+ * rule above that reads `:disabled` needs the attribute twin, or a disabled
1864
+ * link is a link that looks pressable and refuses silently. Folding the old
1865
+ * `a.btn` twins into `.btn` dropped these, and the baselines showed a
1866
+ * disabled link at full strength. */
1867
+ .btn.primary[aria-disabled='true'] {
1868
+ background: var(--panel-2);
1869
+ border-color: var(--border);
1870
+ color: var(--muted);
1871
+ opacity: 1;
1872
+ }
1873
+
1095
1874
  /* ---- ./select.css ---- */
1096
1875
  /* How big a control is, asked for rather than inherited.
1097
1876
  Four sizes existed and every one of them came from a descendant selector:
@@ -1107,8 +1886,7 @@ input:active, textarea:active, select:active { transform: none; }
1107
1886
  /* Full width, for the one action a card or a step is about. Not a size: a
1108
1887
  `block` button is the same height as any other, it just stops being a thing
1109
1888
  you have to aim at. */
1110
- button.block,
1111
- a.btn.block { display: flex; width: 100%; }
1889
+ .btn.ctl-block { display: flex; width: 100%; }
1112
1890
 
1113
1891
  /* The default is `size-md`, spelled once for every control.
1114
1892
  The paragraph above promised one scale for buttons, inputs and selects, and
@@ -1116,13 +1894,24 @@ a.btn.block { display: flex; width: 100%; }
1116
1894
  input was eight pixels taller than the button beside it and no row of the two
1117
1895
  could line up. The download modal's search box against its Search button was
1118
1896
  the version you could see from across the room. */
1119
- button,
1120
- a.btn,
1121
- input,
1122
- textarea,
1123
- select,
1897
+ /* `:where`, so this base carries no specificity of its own.
1898
+ *
1899
+ * It was `button, input, textarea, select` -- an element selector, which
1900
+ * `.size-sm` and every other class outranked without anyone thinking about
1901
+ * it. Moving it to `.btn` to stop a tab inheriting a control's look raised it
1902
+ * to a class, so it tied with `.size-sm` and won on source order: every
1903
+ * button came out `8px 12px` whatever size it asked for. `:where` puts it
1904
+ * back to zero, which is what the element selector was silently providing. */
1905
+ :where(.btn, input, textarea, select) { padding: var(--space-2) var(--space-3); }
1906
+
1907
+ /* The select's control keeps its padding at full class weight, and after
1908
+ `.size-sm` in the cascade, because that is where it has always been: this
1909
+ file is imported after `base.css`, so a `size`d select took the size's type
1910
+ and this padding. Folding it into the `:where` above handed the padding
1911
+ back to `.size-sm` and shrank every small select by seven pixels. */
1124
1912
  .sel-control { padding: var(--space-2) var(--space-3); }
1125
1913
 
1914
+
1126
1915
  /* Centred on the whole block -- the caption and the sentence together -- rather
1127
1916
  than aligned to either end of it. `.field-row` pins to the bottom, which is
1128
1917
  right for a field beside a button and wrong beside two lines of prose. */
@@ -1146,16 +1935,16 @@ select,
1146
1935
  .sel { position: relative; display: inline-block; }
1147
1936
 
1148
1937
  /* The subject of its pane rather than a field in it: full width and a size up.
1149
- That was `.select-field.block` on the native element, and it carried the
1938
+ That was `.select-field.ctl-block` on the native element, and it carried the
1150
1939
  reason with it -- model ids are long (`qwen2.5-coder:32b-instruct-q4_K_M`) and
1151
1940
  at the narrow size they truncated mid-name. */
1152
- .sel.block { display: block; width: 100%; }
1941
+ .sel.ctl-block { display: block; width: 100%; }
1153
1942
 
1154
- .sel.block .sel-control { padding: var(--space-2) var(--space-3); font-weight: 500; }
1943
+ .sel.ctl-block .sel-control { padding: var(--space-2) var(--space-3); font-weight: 500; }
1155
1944
 
1156
1945
  /* Not full width otherwise: a select as wide as its row, holding one short
1157
1946
  word, reads as an input somebody forgot to fill in. */
1158
- .sel:not(.block) .sel-control { width: auto; max-width: 320px; }
1947
+ .sel:not(.ctl-block) .sel-control { width: auto; max-width: 320px; }
1159
1948
 
1160
1949
  .sel-control {
1161
1950
  display: flex;
@@ -1168,9 +1957,7 @@ select,
1168
1957
  background: var(--control);
1169
1958
  }
1170
1959
 
1171
- .sel-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1172
-
1173
- .sel-caret { flex: none; opacity: 0.6; transition: transform var(--dur-fast) var(--ease); }
1960
+ .sel-caret { transition: transform var(--dur-fast) var(--ease); }
1174
1961
 
1175
1962
  .sel[data-open] .sel-caret { transform: rotate(180deg); }
1176
1963
 
@@ -1179,21 +1966,7 @@ select,
1179
1966
  /* State is read off React Aria's attributes: `data-open` on the root,
1180
1967
  `data-focused` / `data-selected` / `data-disabled` on an item, where
1181
1968
  `.open`, `[data-active]`, `.on` and `.off` used to be. */
1182
- .sel-list {
1183
- /* `fixed`, and in `document.body`: an ancestor that scrolls or hides its
1184
- overflow would otherwise clip it, which in this app means every settings
1185
- pane. Position comes from the button's measured rectangle. */
1186
- /* Positioned by React Aria -- inline `top`/`left` on a portalled popover
1187
- that flips when the edge is near. `position: fixed` and the measured
1188
- rectangle it was placed by are gone with the hand-rolled version. */
1189
- z-index: 60;
1190
- max-height: 280px;
1191
- overflow-y: auto;
1192
- margin: 0;
1193
- padding: var(--space-1);
1194
- /* The same room while the list is scrolled as it has at rest. Padding at
1195
- the top of a scroll container scrolls away with the content, so the row
1196
- `scrollIntoView({block: 'nearest'})` brings up ended flush against the
1969
+ .sel-list { scroll-padding-block: var(--space-1); box-shadow: var(--shadow-2); })` brings up ended flush against the
1197
1970
  border and read as clipped rather than as one of a set -- and this list
1198
1971
  opens scrolled to whatever is chosen, so that was the ordinary state.
1199
1972
  `scroll-padding` insets what counts as "in view", so it stops short by
@@ -1206,18 +1979,6 @@ select,
1206
1979
  box-shadow: var(--shadow-2);
1207
1980
  }
1208
1981
 
1209
- .sel-item {
1210
- display: flex;
1211
- align-items: center;
1212
- gap: var(--space-2);
1213
- padding: var(--space-2) var(--space-2);
1214
- border-radius: var(--radius-sm);
1215
- cursor: pointer;
1216
- white-space: nowrap;
1217
- }
1218
-
1219
- .sel-item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
1220
-
1221
1982
  /* Hover and keyboard land on the same row, because they are the same idea --
1222
1983
  two highlights for one cursor is how a list starts lying about where Enter
1223
1984
  will go. */
@@ -1229,8 +1990,6 @@ select,
1229
1990
 
1230
1991
  /* The tick marks the chosen row. Colour alone would not: a list where the only
1231
1992
  difference is a hue is a list you cannot read past. */
1232
- .sel-tick { flex: none; opacity: 0; }
1233
-
1234
1993
  .sel-item[data-selected] .sel-tick { opacity: 1; }
1235
1994
 
1236
1995
  /* The icon on a speaker's row in the open list -- `Select`'s `aside`. Last in
@@ -1268,21 +2027,18 @@ select,
1268
2027
 
1269
2028
  /* ---- ./dangerzone.css ---- */
1270
2029
  /* Destructive, and legible as such standing still. */
1271
- button.danger,
1272
- a.btn.danger {
2030
+ .btn.danger {
1273
2031
  color: var(--bad);
1274
2032
  border-color: color-mix(in srgb, var(--bad) 45%, var(--border-strong));
1275
2033
  }
1276
2034
 
1277
- button.danger:hover:not(:disabled),
1278
- a.btn.danger:hover:not([aria-disabled='true']) {
2035
+ .btn.danger:hover:not(:disabled) {
1279
2036
  background: color-mix(in srgb, var(--bad) 12%, var(--panel));
1280
2037
  border-color: var(--bad);
1281
2038
  color: var(--bad);
1282
2039
  }
1283
2040
 
1284
- button.danger:active:not(:disabled),
1285
- a.btn.danger:active:not([aria-disabled='true']) {
2041
+ .btn.danger:active:not(:disabled) {
1286
2042
  background: color-mix(in srgb, var(--bad) 20%, var(--panel));
1287
2043
  border-color: var(--bad);
1288
2044
  }
@@ -1523,33 +2279,46 @@ a.btn.danger:active:not([aria-disabled='true']) {
1523
2279
  .callout button { flex: none; }
1524
2280
 
1525
2281
  /* ---- ./card.css ---- */
2282
+ /* `.card` is public API, not this component's private styling.
2283
+ *
2284
+ * Consumers apply it to their own markup -- the gallery's skeleton specimen
2285
+ * renders `<div className="card">` around three rows, and app-template uses
2286
+ * `card-pick`. Moving the box into `Card`'s own class list took that away
2287
+ * with no type error and no failing test in this package: it broke only
2288
+ * where somebody else had used the class, which is the worst way for an API
2289
+ * to go.
2290
+ *
2291
+ * So the rule stays and `Card` wears it. The lesson generalises to every
2292
+ * class in this package that a consumer can reach: a component may draw
2293
+ * itself with utilities only where its class is genuinely private. */
1526
2294
  .card {
2295
+ display: block;
2296
+ width: 100%;
2297
+ box-sizing: border-box;
2298
+ font: inherit;
2299
+ color: var(--text);
2300
+ text-align: left;
1527
2301
  background: var(--panel);
1528
2302
  border: var(--border-width) solid var(--border);
1529
2303
  border-radius: var(--radius-md);
1530
- padding: var(--space-3) var(--space-3);
2304
+ padding: var(--space-3);
1531
2305
  margin-bottom: var(--space-3);
1532
2306
  }
1533
2307
 
2308
+ /* What is left once `Card` draws its own box: the tones, the pick and
2309
+ selected states, and the rules that reach from the card into what it
2310
+ holds. `:has(.card-icon) .card-inline` is a relationship, and a tone is a
2311
+ `color-mix`; neither is a property on one element. */
2312
+
1534
2313
  /* Header, when there is one. The title and the line under it were retyped at
1535
2314
  every call site and no two agreed on the gap between them. */
1536
- .card-head { display: flex; align-items: flex-start; gap: var(--space-3); }
1537
-
1538
- .card-head:not(:last-child) { margin-bottom: var(--space-3); }
1539
-
1540
- .card-headings { min-width: 0; flex: 1; }
1541
-
1542
2315
  /* The card owns the space between the things it holds. Without this a callout
1543
2316
  and the button under it sit edge to edge, and every caller has to remember a
1544
2317
  margin on whichever child happens to be second. */
1545
- .card-body { display: grid; gap: var(--space-3); }
1546
-
1547
2318
  /* The card's own control, on the card's padding rather than indented into its
1548
2319
  body. Centred against the whole heading block, not against the title -- a
1549
2320
  switch aligned to the first line drifts upward the moment the description
1550
2321
  runs to two. */
1551
- .card-action { flex: none; align-self: center; display: flex; align-items: center; }
1552
-
1553
2322
  /* A row inside a card that lines up with the header rather than indenting past
1554
2323
  it: same left edge as the title, same right edge as the action. Its label is
1555
2324
  a quiet caption, not a second heading -- the card already has one, and two
@@ -1596,17 +2365,6 @@ a.btn.danger:active:not([aria-disabled='true']) {
1596
2365
  color: var(--muted);
1597
2366
  }
1598
2367
 
1599
- /* A card that is itself the choice. Reset first, because it is a `<button>`
1600
- now and inherits a control's padding and centring. */
1601
- button.card {
1602
- display: block;
1603
- width: 100%;
1604
- text-align: left;
1605
- font: inherit;
1606
- color: inherit;
1607
- padding: var(--space-3);
1608
- }
1609
-
1610
2368
  /* Variants, because a card is doing four jobs and looked like one.
1611
2369
  A search result you can act on, a thing already installed, something that
1612
2370
  went wrong, and a heading with rows under it are not the same object, and
@@ -1649,6 +2407,20 @@ button.card {
1649
2407
 
1650
2408
  .modal-body .card { background: var(--panel-2); }
1651
2409
 
2410
+ /* Selected, which used to arrive from `button[aria-pressed='true']`.
2411
+ *
2412
+ * A card marks its choice with `aria-pressed`, and the tint came from the
2413
+ * element rule every bare `<button>` in the package was picking up. That rule
2414
+ * belongs to `Button` now, so the card says what being chosen looks like --
2415
+ * which is the right place for it anyway: a chosen card is a card's idea, not
2416
+ * a button's. */
2417
+ .card[aria-pressed='true'] {
2418
+ border-color: var(--accent);
2419
+ background: color-mix(in srgb, var(--accent) 10%, var(--panel));
2420
+ }
2421
+
2422
+ .card[aria-pressed='true']:hover { border-color: var(--accent); }
2423
+
1652
2424
  /* ---- ./toggle.css ---- */
1653
2425
  .card-action .switch-row { padding: 0; gap: var(--space-2); }
1654
2426
 
@@ -2199,71 +2971,21 @@ input[type='radio']:active:not(:disabled) {
2199
2971
 
2200
2972
  .row-hit:focus-visible { outline: none; box-shadow: var(--focus-ring); }
2201
2973
 
2202
- /* ---- ./empty.css ---- */
2203
- /* `Empty`: a list with nothing in it.
2204
-
2205
- Quieter than `.empty`, which is a whole-screen state at `--space-10`. These
2206
- sit inside a card that already has a heading and a description, so the same
2207
- padding would leave a card that is mostly air about nothing.
2208
-
2209
- Dashed rather than solid: a solid border reads as a thing, and the point of
2210
- this box is that there is no thing. It marks out where the list will be. */
2211
- .nothing {
2212
- display: grid;
2213
- justify-items: center;
2214
- gap: var(--space-2);
2215
- padding: var(--space-5) var(--space-4);
2216
- border: var(--border-width) dashed var(--border-strong);
2217
- border-radius: var(--radius-md);
2218
- text-align: center;
2219
- }
2220
-
2221
- .nothing-mark { color: var(--muted); line-height: 0; }
2222
-
2223
- /* The same box with a figure in it, for a whole surface rather than a slot in
2224
- a card. Two things change and both follow from the size: the dashed outline
2225
- goes, because an outline marks out where a list will be and a window-sized
2226
- dashed rectangle reads as a layout that failed rather than a view with
2227
- nothing on it; and the box centres itself in whatever it was dropped into,
2228
- which is what makes it land in the middle of an empty dashboard rather than
2229
- under the header. */
2230
- .nothing-surface {
2231
- border: none;
2232
- padding: var(--space-10) var(--space-4);
2233
- gap: var(--space-3);
2234
- margin: auto;
2235
- }
2236
-
2237
- /* `currentColor` reaches the figure through the two rules the illustrations
2238
- ship with, so the colour is set here and the drawing follows it -- the same
2239
- grey as the sentence under it, in every theme.
2240
- Two classes, not one: `.illo` sets `color: inherit` further down this file,
2241
- which beats a single-class rule written earlier and drew the figure in full
2242
- text black beside a muted sentence. */
2243
- .nothing-surface .nothing-figure { color: var(--muted); }
2244
-
2245
- /* `--text-sm`, not the `--text-xs` these lines used to be: it is a sentence
2246
- somebody is meant to read, not a caption qualifying something else. */
2247
- .nothing-said { margin: 0; color: var(--muted); font-size: var(--text-sm); line-height: 1.5; max-width: 46ch; }
2248
-
2249
- .nothing-act { margin-top: var(--space-1); }
2250
-
2251
2974
  /* ---- ./progress.css ---- */
2252
2975
  /* progress ----------------------------------------------------------- */
2253
2976
 
2254
- /* The bar sat 4px under its own label and flush against whatever was above
2977
+ /* The bar itself: a track with a child it fills, an animation and a
2978
+ reduced-motion answer. A utility is a property on one element, so none of
2979
+ that is one -- `Progress` draws its own wrapper and head with utilities and
2980
+ this is what is left.
2981
+
2982
+ The bar sat 4px under its own label and flush against whatever was above
2255
2983
  the whole block, so a download row read as four lines of text with a rule
2256
2984
  drawn through the last one. The label is a caption for the bar and stays
2257
2985
  close to it; the block as a whole is a separate statement from the row it
2258
2986
  follows. */
2259
- .progress { display: grid; gap: var(--space-1); }
2260
-
2261
2987
  .card > .progress { margin-top: var(--space-3); }
2262
2988
 
2263
- .progress-head { display: flex; align-items: baseline; gap: var(--space-2); font-size: var(--text-sm); }
2264
-
2265
- .progress-detail { color: var(--muted); font-size: var(--text-xs); }
2266
-
2267
2989
  .bar { height: 6px; background: var(--panel-2); border-radius: var(--radius-pill); overflow: hidden; }
2268
2990
 
2269
2991
  .bar > i {
@@ -2297,6 +3019,14 @@ input[type='radio']:active:not(:disabled) {
2297
3019
  .tabs { position: relative; display: flex; gap: var(--space-1); }
2298
3020
 
2299
3021
  .tab {
3022
+ /* 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. */
3023
+ display: inline-flex;
3024
+ align-items: center;
3025
+ justify-content: center;
3026
+ gap: var(--space-2);
3027
+ /* 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`. */
3028
+ font: inherit;
3029
+ cursor: pointer;
2300
3030
  position: relative;
2301
3031
  z-index: 1;
2302
3032
  background: transparent;
@@ -2453,6 +3183,38 @@ input[type='radio']:active:not(:disabled) {
2453
3183
  .toast[data-exiting] { animation: none; }
2454
3184
  }
2455
3185
 
3186
+ /* The dismiss control, in its own words.
3187
+ *
3188
+ * It was `className="ghost size-sm"` -- `Button`'s vocabulary, borrowed from
3189
+ * a component that does not render a `Button`. That only ever worked because
3190
+ * `.ghost` and `.size-sm` were element-and-class rules any `<button>` could
3191
+ * pick up, which is the coupling this pass is removing. Nothing photographed
3192
+ * it, either: the toast is transient and the close button is not in any
3193
+ * baseline, so this would have gone quiet. */
3194
+ .toast-close {
3195
+ display: inline-flex;
3196
+ align-items: center;
3197
+ justify-content: center;
3198
+ font: inherit;
3199
+ font-size: var(--text-xs);
3200
+ padding: var(--space-1) var(--space-2);
3201
+ background: transparent;
3202
+ border: var(--border-width) solid transparent;
3203
+ border-radius: var(--radius);
3204
+ color: var(--muted);
3205
+ cursor: pointer;
3206
+ }
3207
+
3208
+ .toast-close:hover:not(:disabled) {
3209
+ background: var(--panel-2);
3210
+ color: var(--text);
3211
+ }
3212
+
3213
+ .toast-close:focus-visible {
3214
+ outline: 2px solid var(--accent);
3215
+ outline-offset: 2px;
3216
+ }
3217
+
2456
3218
  /* ---- ./modal.css ---- */
2457
3219
  /* modal --------------------------------------------------------------- */
2458
3220
 
@@ -2493,13 +3255,6 @@ input[type='radio']:active:not(:disabled) {
2493
3255
  Not `display: contents`, which was the first attempt. An element with no box
2494
3256
  cannot take focus, so React Aria's initial focus fell through to the first
2495
3257
  input and Escape -- handled on the section -- stopped closing the window. */
2496
- .modal-dialog {
2497
- display: flex;
2498
- flex-direction: column;
2499
- min-height: 0;
2500
- flex: 1;
2501
- }
2502
-
2503
3258
  .modal {
2504
3259
  background: var(--panel);
2505
3260
  border: 1px solid var(--border);
@@ -2550,18 +3305,9 @@ input[type='radio']:active:not(:disabled) {
2550
3305
  laid out for the bare `<strong>` it used to hold -- 86px tall with them,
2551
3306
  41px without, on a head whose padding is 12 over 8. `font: inherit` so the
2552
3307
  `<strong>` rule above is the one that sets the size. */
2553
- .modal-title { margin: 0; font: inherit; }
2554
-
2555
3308
  /* Under the title, on the header's padding rather than the body's, so it reads
2556
3309
  as part of the heading rather than as the first line of content. */
2557
- .modal-desc {
2558
- margin: 0;
2559
- padding: 0 var(--space-4) var(--space-3);
2560
- color: var(--muted);
2561
- font-size: var(--text-sm);
2562
- line-height: 1.5;
2563
- max-width: 62ch;
2564
- }
3310
+ .modal-desc { line-height: 1.5; }
2565
3311
 
2566
3312
  .modal-body { padding: var(--space-2) var(--space-4) var(--space-3); overflow-y: auto; }
2567
3313
 
@@ -2921,7 +3667,8 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
2921
3667
  buttons only, so a button in a row stood 25px beside a 35px select and no
2922
3668
  size class could ask for the select's height. Type from the row, box from
2923
3669
  the scale, and the two line up; an explicit `size-*` still wins. */
2924
- :where(.set-row) button, :where(.set-row) .slider-box { font-size: var(--text-xs); }
3670
+ :where(.set-row) button,
3671
+ :where(.set-row) .slider-box { font-size: var(--text-xs); }
2925
3672
 
2926
3673
  /* ---- ./checkbox.css ---- */
2927
3674
  /* A row you choose. There is no checkbox in it.
@@ -3099,30 +3846,28 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3099
3846
  one. The cell is bold because it is a heading; what hangs under it is not,
3100
3847
  and inherited the weight -- so a pinned version read as loud as the thing it
3101
3848
  versions. */
3102
- .table th .set-hint { font-weight: 400; }
3849
+ .data-table th .set-hint { font-weight: 400; }
3103
3850
 
3104
- .table-scroll { overflow-x: auto; }
3105
-
3106
- .table {
3851
+ .data-table {
3107
3852
  width: 100%;
3108
3853
  border-collapse: collapse;
3109
3854
  font-size: var(--text-sm);
3110
3855
  }
3111
3856
 
3112
- .table th,
3113
- .table td {
3857
+ .data-table th,
3858
+ .data-table td {
3114
3859
  text-align: left;
3115
3860
  padding: var(--space-2) var(--space-3);
3116
3861
  border-bottom: var(--border-width) solid var(--border);
3117
3862
  }
3118
3863
 
3119
- .table th:first-child,
3120
- .table td:first-child { padding-left: 0; }
3864
+ .data-table th:first-child,
3865
+ .data-table td:first-child { padding-left: 0; }
3121
3866
 
3122
- .table th:last-child,
3123
- .table td:last-child { padding-right: 0; }
3867
+ .data-table th:last-child,
3868
+ .data-table td:last-child { padding-right: 0; }
3124
3869
 
3125
- .table thead th {
3870
+ .data-table thead th {
3126
3871
  font-size: var(--text-xs);
3127
3872
  font-weight: var(--weight-strong);
3128
3873
  text-transform: uppercase;
@@ -3133,40 +3878,31 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3133
3878
 
3134
3879
  /* The row's own name: a `<th scope="row">`, so it carries weight rather than
3135
3880
  reading as one more cell. */
3136
- .table tbody th { font-weight: var(--weight-strong); }
3881
+ .data-table tbody th { font-weight: var(--weight-strong); }
3137
3882
 
3138
- .table tbody tr:last-child th,
3139
- .table tbody tr:last-child td { border-bottom: none; }
3883
+ .data-table tbody tr:last-child th,
3884
+ .data-table tbody tr:last-child td { border-bottom: none; }
3140
3885
 
3141
3886
  /* Numbers compare by column, so they align right and hold their tracks --
3142
3887
  proportional digits shift the alignment by a pixel per glyph, which is
3143
3888
  exactly the comparison the column was for. */
3144
- .table .num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
3889
+ .data-table .num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
3145
3890
 
3146
- .table thead th[style*='right'] { text-align: right; }
3891
+ .data-table thead th[style*='right'] { text-align: right; }
3147
3892
 
3148
3893
  /* ---- ./sizegrid.css ---- */
3149
- /* Tile size, picked as a rectangle rather than read as a notation. */
3150
- .size-grid-wrap { display: flex; align-items: center; gap: var(--space-2); }
3151
-
3152
- .size-grid { display: grid; gap: var(--space-1); }
3153
-
3154
- .size-cell {
3155
- width: 18px; height: 14px; padding: 0;
3156
- border: 1px solid var(--border); border-radius: var(--radius-sm);
3157
- background: var(--panel-2); cursor: pointer;
3894
+ /* What is left of the size grid once it draws itself.
3895
+ *
3896
+ * `on` follows the pointer; `chosen` is what is actually saved, shown only
3897
+ * when nothing is hovered so the two can never contradict each other.
3898
+ * `chosen` is a border colour and says so at the call site; `on` is a fill
3899
+ * mixed from the accent, and `color-mix` in an arbitrary value is less
3900
+ * readable than the rule it replaces. */
3901
+ .size-cell.on {
3902
+ border-color: var(--accent);
3903
+ background: color-mix(in srgb, var(--accent) 45%, transparent);
3158
3904
  }
3159
3905
 
3160
- /* `on` follows the pointer; `chosen` is what is actually saved, shown only
3161
- when nothing is hovered so the two can never contradict each other. */
3162
- .size-cell.on { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 45%, transparent); }
3163
-
3164
- .size-cell.chosen { border-color: var(--accent); }
3165
-
3166
- .size-cell:disabled { cursor: default; opacity: 0.5; }
3167
-
3168
- .size-ceiling { opacity: 0.55; }
3169
-
3170
3906
  /* ---- ./tooltip.css ---- */
3171
3907
  /* A callout: something worth knowing, with nothing to answer. Distinct from a
3172
3908
  `?`, which is an offer to read more, and from a banner, which is a problem.
@@ -3175,36 +3911,15 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3175
3911
  /* An info-toned `.callout`, spelled the old way. Identical to it now rather than
3176
3912
  nearly identical to it, which is the whole point of the consolidation: the
3177
3913
 
3178
- /* The `?` beside a setting, and the box it shows on hover. Built like the
3914
+ /* What is left once `Tooltip` draws its own mark: the hover and focus that
3915
+ reach the mark from its parent, and the tip -- whose closed and open
3916
+ states are three rules the cascade resolves in order, with
3917
+ `data-entering` and `data-exiting` around the transition. None of that is
3918
+ a property on one element.
3919
+
3920
+ The `?` beside a setting, and the box it shows on hover. Built like the
3179
3921
  status dot's tooltip and for the same reasons: `title` waits a second and a
3180
3922
  half, renders newlines however it likes, and cannot be styled. */
3181
- .explain {
3182
- position: relative;
3183
- flex: none;
3184
- display: inline-flex;
3185
- align-items: center;
3186
- /* The heading it follows is tracked out, which leaves the last letter's
3187
- spacing sitting between them and reads as none at all. */
3188
- margin-left: var(--space-2);
3189
- cursor: help;
3190
- /* Headings here are uppercase and tracked out, and one of these sits inside
3191
- a heading. */
3192
- text-transform: none;
3193
- letter-spacing: normal;
3194
- font-weight: 400;
3195
- }
3196
-
3197
- .explain-mark {
3198
- width: 18px;
3199
- height: 18px;
3200
- border-radius: 50%;
3201
- border: 1px solid var(--border);
3202
- color: var(--muted);
3203
- font-size: var(--text-xs);
3204
- line-height: 16px;
3205
- text-align: center;
3206
- }
3207
-
3208
3923
  .explain:hover .explain-mark,
3209
3924
  .explain:focus-visible .explain-mark { color: var(--text); border-color: var(--accent); }
3210
3925
 
@@ -3305,6 +4020,18 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3305
4020
  a shirt that lets the arm behind it show through reads as a hole. */
3306
4021
  .illo .paper { fill: var(--illo-paper); }
3307
4022
 
4023
+ /* The figure in a whole-surface `Empty`, drawn in the muted grey of the
4024
+ sentence under it.
4025
+ *
4026
+ * A utility cannot do this, and the reason is worth keeping: `.illo` sets
4027
+ * `color: inherit` and is unlayered, while every Tailwind utility lives in
4028
+ * `@layer utilities` -- and an unlayered declaration beats a layered one
4029
+ * whatever the specificity. So `text-muted` on the figure lost, silently,
4030
+ * and the shirt came out full text black beside a muted sentence. The
4031
+ * original rule already carried a note about needing two classes to beat
4032
+ * `.illo`; layers make that impossible rather than merely fiddly. */
4033
+ .nothing-figure { color: var(--muted); }
4034
+
3308
4035
  /* ---- ./field.css ---- */
3309
4036
  /* fields --------------------------------------------------------------- */
3310
4037
 
@@ -3392,6 +4119,95 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3392
4119
 
3393
4120
  .secret-lg > .secret-eye { padding: var(--space-2); }
3394
4121
 
4122
+ /* An input with something inside its edges: a glyph on the left, a hint or a
4123
+ clear button on the right. Same arrangement as `.secret` above, and for the
4124
+ same reason -- one grid cell with everything stacked in it, and the room
4125
+ reserved in the input's own padding, so nothing is positioned against a
4126
+ height that changes with `size`. */
4127
+ .adorned { display: grid; align-items: center; }
4128
+
4129
+ .adorned > input,
4130
+ .adorned > .adorned-mark,
4131
+ .adorned > .adorned-end { grid-area: 1 / 1; }
4132
+
4133
+ .adorned.ctl-block { width: 100%; }
4134
+
4135
+ .adorned > .adorned-mark {
4136
+ justify-self: start;
4137
+ margin-left: var(--space-2);
4138
+ color: var(--muted);
4139
+ /* The glyph is decoration over the box; clicks belong to the input under
4140
+ it, which is otherwise unreachable in its own left edge. */
4141
+ pointer-events: none;
4142
+ /* Over the input, not under it. Both share one grid cell, the input has an
4143
+ opaque `--control` background, and it comes later in the DOM -- so
4144
+ without this the glyph is painted and then covered, which looks exactly
4145
+ like an icon that failed to render. `.secret` never hit this because its
4146
+ button is written after the input. */
4147
+ z-index: 1;
4148
+ }
4149
+
4150
+ /* Room for the glyph, on the scale, so a density change moves the text with
4151
+ the mark rather than leaving one behind. `--space-8` is what `.secret`
4152
+ reserves on the right for the same reason; the scale has no 7, and an
4153
+ undefined custom property does not fall back -- the declaration computes
4154
+ to 0 and the text sits under the glyph. */
4155
+ .adorned-icon > input { padding-left: var(--space-8); }
4156
+
4157
+ /* No `--space-12` on the scale; two steps that are, added. Same arithmetic
4158
+ as `.secret-lg`. */
4159
+ .adorned-icon.adorned-lg > input { padding-left: calc(var(--space-10) + var(--space-1)); }
4160
+
4161
+ .adorned > .adorned-end {
4162
+ justify-self: end;
4163
+ display: flex;
4164
+ align-items: center;
4165
+ gap: var(--space-1);
4166
+ margin-right: var(--space-1);
4167
+ }
4168
+
4169
+ /* `:has`, not a sibling combinator: the input comes *before* the end cap in
4170
+ the DOM, so `~` reads the wrong way and matches nothing. */
4171
+ .adorned:has(> .adorned-end) > input { padding-right: var(--space-8); }
4172
+ .adorned-lg:has(> .adorned-end) > input {
4173
+ padding-right: calc(var(--space-10) + var(--space-1));
4174
+ }
4175
+
4176
+ /* Sized from its glyph, not `.icon-btn`'s 34px tile, which would be taller
4177
+ than a small input -- the note above `.secret-eye` in full. */
4178
+ .adorned-clear {
4179
+ width: auto; height: auto;
4180
+ padding: var(--space-1);
4181
+ opacity: 1;
4182
+ }
4183
+
4184
+ .adorned-lg .adorned-clear { padding: var(--space-2); }
4185
+
4186
+ /* The eye and the clear button stop borrowing `ghost`.
4187
+ *
4188
+ * Both were `className="icon-btn ghost …"` -- reaching into `Button`'s class
4189
+ * vocabulary from another component, which worked only because `.ghost` was
4190
+ * written as `button.ghost` and they happen to be `<button>`s. It is `.btn.ghost`
4191
+ * now, so the borrowing stopped matching and `.icon-btn`'s panel background
4192
+ * came through: the baselines showed a filled tile where there had been a bare
4193
+ * glyph.
4194
+ *
4195
+ * Stated here instead. What they actually wanted from `ghost` is two lines,
4196
+ * and saying them is cheaper than either component knowing what a button's
4197
+ * classes are called. */
4198
+ .secret-eye,
4199
+ .adorned-clear {
4200
+ background: transparent;
4201
+ border-color: transparent;
4202
+ color: var(--muted);
4203
+ }
4204
+
4205
+ .secret-eye:hover:not(:disabled),
4206
+ .adorned-clear:hover:not(:disabled) {
4207
+ background: var(--panel-2);
4208
+ color: var(--text);
4209
+ }
4210
+
3395
4211
  /* ---- ./tour.css ---- */
3396
4212
  /* tour ----------------------------------------------------------------- */
3397
4213
 
@@ -3459,72 +4275,46 @@ input[type='range'].slider:disabled::-moz-range-thumb { opacity: 0; }
3459
4275
  anchored surfaces (`.pop`, `.menu-sheet`, `.cmd`) sit over everything and a
3460
4276
  rule of equal specificity that came earlier would lose to whatever the
3461
4277
  sheets they cover happen to set. */
3462
- /* ---- ./kbd.css ---- */
3463
- /* One rule where there were two identical ones, in `menu.css` and
3464
- `command.css`. Both keep their class as well, so `Menu` and `Command` are
3465
- `.kbd .menu-key` and `.kbd .cmd-key` now: the declarations are the same, so
3466
- nothing moves, and the context classes stay available for a rule that ever
3467
- needs to tell the two apart. */
3468
- .kbd {
3469
- flex: none;
3470
- font-family: var(--font-mono);
3471
- font-size: var(--text-2xs);
3472
- color: var(--muted);
3473
- letter-spacing: var(--tracking);
3474
- }
3475
-
3476
4278
  /* ---- ./stat.css ---- */
3477
- /* A figure with furniture. `--space-1` between the three lines because they
3478
- are one object read top to bottom, not three paragraphs. */
3479
- .stat {
3480
- display: grid;
3481
- gap: var(--space-1);
3482
- padding: var(--space-4);
3483
- border: var(--border-width) solid var(--border);
3484
- border-radius: var(--radius-md);
3485
- background: var(--panel);
3486
- }
3487
-
3488
- /* Inside something already bordered. A bordered tile in a bordered row is two
3489
- boxes saying one thing. */
3490
- .stat-bare {
3491
- padding: 0;
3492
- border: 0;
3493
- border-radius: 0;
3494
- background: none;
3495
- gap: 0;
3496
- line-height: 1.25;
3497
- }
3498
-
3499
- .stat > p { margin: 0; }
4279
+ /* What the utilities on `Stat` cannot say: a tone on the tile colouring the
4280
+ figure inside it. An attribute on the parent selecting a child is a
4281
+ relationship; a utility is a property on one element.
3500
4282
 
3501
- .stat-label {
3502
- color: var(--muted);
3503
- font-size: var(--text-xs);
3504
- }
4283
+ Only the figure takes the colour. Colouring the label too makes the tile a
4284
+ status box, where a tone here is a remark about the number. */
4285
+ .stat[data-tone='info'] .stat-value { color: var(--info); }
4286
+ .stat[data-tone='good'] .stat-value { color: var(--good); }
4287
+ .stat[data-tone='warn'] .stat-value { color: var(--warn); }
4288
+ .stat[data-tone='bad'] .stat-value { color: var(--bad); }
3505
4289
 
3506
- /* Tabular figures are the point: a column of numbers is compared by its
3507
- digits' positions, and proportional ones move them per row. */
3508
- .stat-value {
3509
- font-size: var(--text-lg);
3510
- font-weight: var(--weight-strong);
3511
- font-variant-numeric: tabular-nums;
4290
+ /* ---- ./shell.css ---- */
4291
+ /* The measure, and only the measure.
4292
+ *
4293
+ * `Shell` draws itself with utilities -- it is layout and nothing else. What
4294
+ * stays here is the one value the header band, the nav band and the main band
4295
+ * all have to agree on: a custom property, so an app changes it in one place
4296
+ * rather than overriding three rules and missing the fourth. That is how
4297
+ * manage ended up carrying `head-inner-wide` and `main-wide` as separate
4298
+ * classes it had to remember together. */
4299
+ .shell {
4300
+ --shell-measure: 64rem;
4301
+ --shell-measure-wide: 90rem;
4302
+ }
4303
+
4304
+ /* The label beside the theme control, when it is drawn. */
4305
+ .theme-switch {
4306
+ display: flex;
4307
+ align-items: center;
4308
+ gap: var(--space-2);
4309
+ min-width: 0;
3512
4310
  }
3513
4311
 
3514
- .stat-bare .stat-value { font-size: var(--text-sm); }
3515
-
3516
- .stat-note {
4312
+ .theme-switch-label {
3517
4313
  color: var(--muted);
3518
- font-size: var(--text-xs);
4314
+ font-size: var(--text-sm);
4315
+ white-space: nowrap;
3519
4316
  }
3520
4317
 
3521
- /* Only the figure takes the colour. Colouring the label too makes the tile a
3522
- status box, and a tone here is a remark about the number. */
3523
- .stat[data-tone='info'] .stat-value { color: var(--info); }
3524
- .stat[data-tone='good'] .stat-value { color: var(--good); }
3525
- .stat[data-tone='warn'] .stat-value { color: var(--warn); }
3526
- .stat[data-tone='bad'] .stat-value { color: var(--bad); }
3527
-
3528
4318
  /* ---- ./scrollarea.css ---- */
3529
4319
  /* A box that scrolls, with its edges drawn.
3530
4320
 
@@ -3776,27 +4566,13 @@ body.splitting * { cursor: inherit !important; }
3776
4566
  }
3777
4567
 
3778
4568
  /* ---- ./menu.css ---- */
3779
- /* A list of verbs, on the same sheet as every other anchored surface. */
3780
- .menu-sheet {
3781
- background: var(--panel);
3782
- border: var(--border-width) solid var(--border);
3783
- border-radius: var(--radius-md);
3784
- box-shadow: var(--shadow-2);
3785
- min-width: 12rem;
3786
- max-width: min(20rem, calc(100vw - var(--space-6)));
3787
- max-height: 24rem;
3788
- overflow: auto;
3789
- overscroll-behavior: contain;
3790
- z-index: 60;
3791
- }
3792
-
3793
- .menu-list {
3794
- padding: var(--space-1);
3795
- outline: none;
3796
- display: grid;
3797
- gap: var(--border-width);
3798
- }
4569
+ /* What is left once `Menu` draws its sheet and its list: the rows, whose
4570
+ `data-focused`, `data-pressed` and danger states React Aria stamps on
4571
+ them, and the rules that reach from a row into what it holds. The
4572
+ `box-shadow` stays on the sheet too -- `--shadow-2` is a token, and a
4573
+ utility would have to inline it. */
3799
4574
 
4575
+ /* A list of verbs, on the same sheet as every other anchored surface. */
3800
4576
  .menu-item {
3801
4577
  display: flex;
3802
4578
  align-items: center;
@@ -3835,14 +4611,6 @@ body.splitting * { cursor: inherit !important; }
3835
4611
  /* The binding, set in the mono face at the size of the small print. Right of
3836
4612
  the row and never the reason the row is wide: `flex: none` with the label
3837
4613
  taking the slack. */
3838
- .menu-key {
3839
- flex: none;
3840
- font-family: var(--font-mono);
3841
- font-size: var(--text-2xs);
3842
- color: var(--muted);
3843
- letter-spacing: var(--tracking);
3844
- }
3845
-
3846
4614
  .menu-more { flex: none; color: var(--muted); }
3847
4615
 
3848
4616
  .menu-rule {
@@ -3878,6 +4646,8 @@ body.splitting * { cursor: inherit !important; }
3878
4646
  .menu-sheet[data-exiting] { animation: none; }
3879
4647
  }
3880
4648
 
4649
+ .menu-sheet { box-shadow: var(--shadow-2); }
4650
+
3881
4651
  /* ---- ./command.css ---- */
3882
4652
  /* The command palette: a field over the app and a list under it. */
3883
4653
  .cmd-scrim {
@@ -3995,14 +4765,6 @@ body.splitting * { cursor: inherit !important; }
3995
4765
  white-space: nowrap;
3996
4766
  }
3997
4767
 
3998
- .cmd-key {
3999
- flex: none;
4000
- font-family: var(--font-mono);
4001
- font-size: var(--text-2xs);
4002
- color: var(--muted);
4003
- letter-spacing: var(--tracking);
4004
- }
4005
-
4006
4768
  .cmd-empty {
4007
4769
  margin: 0;
4008
4770
  padding: var(--space-6) var(--space-4);
@@ -4030,23 +4792,14 @@ body.splitting * { cursor: inherit !important; }
4030
4792
  }
4031
4793
 
4032
4794
  /* ---- ./pagination.css ---- */
4033
- /* Moving through a list that does not fit, and saying how far in you are. */
4034
- .pager {
4035
- display: flex;
4036
- align-items: center;
4037
- justify-content: space-between;
4038
- gap: var(--space-4);
4039
- flex-wrap: wrap;
4040
- padding: var(--space-2) 0;
4041
- font-size: var(--text-xs);
4042
- color: var(--muted);
4043
- }
4795
+ /* What is left once `Pagination` draws its own frame: the step and page
4796
+ buttons, whose hover, focus-visible and current states are pseudo-classes
4797
+ on two selectors at once, and `.pager-count strong`, which reaches into
4798
+ text the caller passed. */
4044
4799
 
4045
- .pager-count { margin: 0; }
4800
+ /* Moving through a list that does not fit, and saying how far in you are. */
4046
4801
  .pager-count strong { color: var(--text); font-weight: var(--weight-strong); }
4047
4802
 
4048
- .pager-controls { display: flex; align-items: center; gap: var(--space-1); }
4049
-
4050
4803
  .pager-step,
4051
4804
  .pager-page {
4052
4805
  display: inline-flex;
@@ -4238,6 +4991,8 @@ body.splitting * { cursor: inherit !important; }
4238
4991
  }
4239
4992
 
4240
4993
  .ident-remove {
4994
+ /* 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`. */
4995
+ font: inherit;
4241
4996
  flex: none;
4242
4997
  display: inline-flex;
4243
4998
  align-items: center;