@ceebee/ui 0.3.3 → 0.5.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/styles.css CHANGED
@@ -46,10 +46,18 @@
46
46
 
47
47
  --cb-z-base: 0;
48
48
  --cb-z-sticky: 100;
49
- --cb-z-overlay: 200;
50
- --cb-z-popover: 300;
51
- --cb-z-toast: 400;
52
- --cb-z-coachmark: 500;
49
+ --cb-z-modal-backdrop: 200;
50
+ --cb-z-modal: 300;
51
+ --cb-z-dropdown: 400;
52
+ --cb-z-popover: 500;
53
+ --cb-z-tooltip: 600;
54
+ --cb-z-command-backdrop: 700;
55
+ --cb-z-command: 800;
56
+ --cb-z-toast: 900;
57
+ --cb-z-coachmark-spotlight: 1000;
58
+ --cb-z-coachmark: 1100;
59
+ /* Compatibility for consumers that used the earlier coarse overlay layer. */
60
+ --cb-z-overlay: var(--cb-z-modal-backdrop);
53
61
  }
54
62
 
55
63
  /* tokens/skin.css */
@@ -263,20 +271,57 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
263
271
  :root { --cb-motion-scale: 0; }
264
272
  }
265
273
 
274
+ /* foundation/divider.css */
275
+ .cb-divider {
276
+ border: 0;
277
+ border-top: var(--cb-border-width) solid var(--cb-border);
278
+ margin: var(--cb-space-4) 0;
279
+ }
280
+
281
+ .cb-divider--vertical {
282
+ display: inline-block;
283
+ align-self: stretch;
284
+ width: 0;
285
+ min-height: 1em;
286
+ margin: 0 var(--cb-space-3);
287
+ border-top: 0;
288
+ border-inline-start: var(--cb-border-width) solid var(--cb-border);
289
+ }
290
+
291
+ .cb-divider--labelled {
292
+ display: flex;
293
+ align-items: center;
294
+ gap: var(--cb-space-3);
295
+ border-top: 0;
296
+ }
297
+ .cb-divider--labelled::before,
298
+ .cb-divider--labelled::after {
299
+ content: '';
300
+ flex: 1;
301
+ border-top: var(--cb-border-width) solid var(--cb-border);
302
+ }
303
+ .cb-divider__label {
304
+ flex: none;
305
+ font-family: var(--cb-font-sans);
306
+ font-size: var(--cb-text-xs);
307
+ font-weight: var(--cb-weight-semibold);
308
+ color: var(--cb-fg-muted);
309
+ }
310
+
266
311
  /* foundation/layout.css */
267
- .cb-stack { display: flex; gap: var(--cb-stack-gap); }
268
- .cb-stack--column { flex-direction: column; }
269
- .cb-stack--row { flex-direction: row; }
270
- .cb-stack--wrap { flex-wrap: wrap; }
271
- .cb-stack[data-align="start"] { align-items: flex-start; }
272
- .cb-stack[data-align="center"] { align-items: center; }
273
- .cb-stack[data-align="end"] { align-items: flex-end; }
274
- .cb-stack[data-align="stretch"] { align-items: stretch; }
275
- .cb-stack[data-align="baseline"] { align-items: baseline; }
276
- .cb-stack[data-justify="start"] { justify-content: flex-start; }
277
- .cb-stack[data-justify="center"] { justify-content: center; }
278
- .cb-stack[data-justify="end"] { justify-content: flex-end; }
279
- .cb-stack[data-justify="between"] { justify-content: space-between; }
312
+ .cb-flex { display: flex; gap: var(--cb-flex-gap); }
313
+ .cb-flex--column { flex-direction: column; }
314
+ .cb-flex--row { flex-direction: row; }
315
+ .cb-flex--wrap { flex-wrap: wrap; }
316
+ .cb-flex[data-align="start"] { align-items: flex-start; }
317
+ .cb-flex[data-align="center"] { align-items: center; }
318
+ .cb-flex[data-align="end"] { align-items: flex-end; }
319
+ .cb-flex[data-align="stretch"] { align-items: stretch; }
320
+ .cb-flex[data-align="baseline"] { align-items: baseline; }
321
+ .cb-flex[data-justify="start"] { justify-content: flex-start; }
322
+ .cb-flex[data-justify="center"] { justify-content: center; }
323
+ .cb-flex[data-justify="end"] { justify-content: flex-end; }
324
+ .cb-flex[data-justify="between"] { justify-content: space-between; }
280
325
 
281
326
  .cb-grid {
282
327
  display: grid;
@@ -435,6 +480,115 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
435
480
  .cb-text[data-tone="warning"] { color: var(--cb-tone-warning); }
436
481
  .cb-text[data-tone="danger"] { color: var(--cb-tone-danger); }
437
482
 
483
+ /* form/autocomplete.css */
484
+ .cb-autocomplete__positioner { z-index: var(--cb-z-dropdown); }
485
+
486
+ .cb-autocomplete {
487
+ display: flex;
488
+ align-items: center;
489
+ gap: var(--cb-space-1);
490
+ width: 100%;
491
+ box-sizing: border-box;
492
+ background: var(--cb-surface);
493
+ border: var(--cb-border-width) solid var(--cb-border-strong);
494
+ border-radius: var(--cb-radius-md);
495
+ padding-inline: var(--cb-space-3) var(--cb-space-1);
496
+ transition: border-color var(--cb-duration-fast) var(--cb-ease-standard),
497
+ box-shadow var(--cb-duration-fast) var(--cb-ease-standard);
498
+ }
499
+ .cb-autocomplete--sm { height: var(--cb-control-height-sm); }
500
+ .cb-autocomplete--md { height: var(--cb-control-height-md); }
501
+ .cb-autocomplete--lg { height: var(--cb-control-height-lg); }
502
+ .cb-autocomplete:focus-within {
503
+ border-color: var(--cb-tone-brand);
504
+ box-shadow: 0 0 0 var(--cb-focus-width) color-mix(in oklch, var(--cb-tone-brand) 30%, transparent);
505
+ }
506
+ .cb-autocomplete[data-disabled] { opacity: 0.55; }
507
+ .cb-autocomplete:has(.cb-autocomplete__input[aria-invalid="true"]) { border-color: var(--cb-tone-danger); }
508
+
509
+ .cb-autocomplete__input {
510
+ flex: 1;
511
+ min-width: 0;
512
+ border: none;
513
+ background: transparent;
514
+ color: var(--cb-fg);
515
+ font-family: var(--cb-font-sans);
516
+ font-size: var(--cb-text-sm);
517
+ outline: none;
518
+ }
519
+ .cb-autocomplete__input::placeholder { color: var(--cb-fg-subtle); }
520
+
521
+ .cb-autocomplete__clear,
522
+ .cb-autocomplete__trigger {
523
+ flex: none;
524
+ display: inline-flex;
525
+ align-items: center;
526
+ justify-content: center;
527
+ width: 1.75rem;
528
+ height: 1.75rem;
529
+ border: none;
530
+ border-radius: var(--cb-radius-sm);
531
+ background: transparent;
532
+ color: var(--cb-fg-subtle);
533
+ cursor: pointer;
534
+ }
535
+ .cb-autocomplete__clear:hover,
536
+ .cb-autocomplete__trigger:hover { color: var(--cb-fg); background: var(--cb-bg-subtle); }
537
+ .cb-autocomplete__clear:focus-visible,
538
+ .cb-autocomplete__trigger:focus-visible { outline: var(--cb-focus-width) solid var(--cb-tone-brand); outline-offset: var(--cb-focus-offset-inset); }
539
+
540
+
541
+ /* The list is as wide as the control it drops from, and its labels start where
542
+ that control's own text starts.
543
+
544
+ Both were off by a little and the little showed: a popup narrower than its
545
+ field reads as belonging to something else, and a leading indicator column
546
+ pushed every label 29px right of the text it was replacing, so the list did
547
+ not line up with the thing it was a list for. The tick moves to the trailing
548
+ edge — `order`, so the reading order is untouched — and the row's inline
549
+ padding matches the field's. */
550
+ .cb-autocomplete__popup {
551
+ box-sizing: border-box;
552
+ width: var(--anchor-width, auto);
553
+ min-width: var(--anchor-width, 12rem);
554
+ max-height: 18rem;
555
+ overflow-y: auto;
556
+ padding: var(--cb-space-1);
557
+ background: var(--cb-surface-raised);
558
+ border: var(--cb-border-width) solid var(--cb-border);
559
+ border-radius: var(--cb-radius-md);
560
+ box-shadow: var(--cb-shadow-md);
561
+ font-family: var(--cb-font-sans);
562
+ transition: opacity var(--cb-duration-fast) var(--cb-ease-entrance);
563
+ }
564
+ .cb-autocomplete__popup[data-starting-style],
565
+ .cb-autocomplete__popup[data-ending-style] { opacity: 0; }
566
+
567
+ .cb-autocomplete__item {
568
+ display: grid;
569
+ grid-template-columns: 1fr 1.25rem;
570
+ align-items: start;
571
+ gap: var(--cb-space-2);
572
+ padding: var(--cb-space-2) var(--cb-space-2);
573
+ min-height: 2.25rem;
574
+ border-radius: var(--cb-radius-sm);
575
+ font-size: var(--cb-text-sm);
576
+ color: var(--cb-fg);
577
+ cursor: pointer;
578
+ outline: none;
579
+ }
580
+ .cb-autocomplete__item[data-highlighted] { background: color-mix(in oklch, var(--cb-tone-brand) 12%, transparent); }
581
+ .cb-autocomplete__check { order: 2; display: inline-flex; justify-content: center; align-items: center; min-height: 1.25rem; color: var(--cb-tone-brand); }
582
+ .cb-autocomplete__text { display: flex; flex-direction: column; gap: 0.0625rem; min-width: 0; line-height: 1.25rem; }
583
+ .cb-autocomplete__description { font-size: var(--cb-text-xs); color: var(--cb-fg-subtle); }
584
+ .cb-autocomplete__empty,
585
+ .cb-autocomplete__status { padding: var(--cb-space-3); font-size: var(--cb-text-sm); color: var(--cb-fg-subtle); text-align: center; }
586
+ /* Base UI keeps the empty message mounted while the list has matches, so it sat
587
+ as a silent 24px band above every result. A message with nothing to say takes
588
+ no room. */
589
+ .cb-autocomplete__empty:empty { display: none; }
590
+ .cb-autocomplete__status { margin: 0; }
591
+
438
592
  /* form/button.css */
439
593
  .cb-button {
440
594
  --cb-button-accent: var(--cb-tone-brand);
@@ -510,6 +664,10 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
510
664
  .cb-button__spinner { animation: none; opacity: 0.6; }
511
665
  }
512
666
 
667
+ /* An anchor does not inherit a button's reset, so the two only match once the
668
+ browser's own link styling is taken back off. */
669
+ .cb-link-button { text-decoration: none; }
670
+
513
671
  /* form/choice.css */
514
672
  .cb-choice { display: flex; align-items: flex-start; gap: var(--cb-space-3); }
515
673
  /* A row whose text is a single line centres on the control instead of hanging from its top. */
@@ -646,98 +804,72 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
646
804
  .cb-switch__thumb { transition: none; }
647
805
  }
648
806
 
649
- /* form/combobox.css */
650
- .cb-combobox {
651
- display: flex;
652
- align-items: center;
653
- gap: var(--cb-space-1);
654
- width: 100%;
655
- box-sizing: border-box;
656
- background: var(--cb-surface);
657
- border: var(--cb-border-width) solid var(--cb-border-strong);
807
+ /* Segmented: the same radiogroup, drawn as one track.
808
+
809
+ The dot is not hidden with `display: none` — a removed control cannot be
810
+ focused or hit. It is stretched over its whole cell at zero opacity instead,
811
+ so the cell IS the radio: pointer, focus ring and keyboard all keep working
812
+ and only the drawing changes. */
813
+ .cb-radio-group--segmented {
814
+ display: inline-flex;
815
+ align-items: stretch;
816
+ gap: var(--cb-space-hair, 2px);
817
+ padding: 2px;
818
+ background: var(--cb-bg-subtle);
819
+ border: var(--cb-border-width) solid var(--cb-border);
658
820
  border-radius: var(--cb-radius-md);
659
- padding-inline: var(--cb-space-3) var(--cb-space-1);
660
- transition: border-color var(--cb-duration-fast) var(--cb-ease-standard),
661
- box-shadow var(--cb-duration-fast) var(--cb-ease-standard);
662
821
  }
663
- .cb-combobox--sm { height: var(--cb-control-height-sm); }
664
- .cb-combobox--md { height: var(--cb-control-height-md); }
665
- .cb-combobox--lg { height: var(--cb-control-height-lg); }
666
- .cb-combobox:focus-within {
667
- border-color: var(--cb-tone-brand);
668
- box-shadow: 0 0 0 var(--cb-focus-width) color-mix(in oklch, var(--cb-tone-brand) 30%, transparent);
669
- }
670
- .cb-combobox[data-disabled] { opacity: 0.55; }
671
- .cb-combobox:has(.cb-combobox__input[aria-invalid="true"]) { border-color: var(--cb-tone-danger); }
672
-
673
- .cb-combobox__input {
674
- flex: 1;
822
+ .cb-radio-group--segmented .cb-choice {
823
+ position: relative;
824
+ align-items: center;
825
+ gap: 0;
675
826
  min-width: 0;
676
- border: none;
827
+ }
828
+ .cb-radio-group--segmented .cb-radio {
829
+ position: absolute;
830
+ inset: 0;
831
+ z-index: 1;
832
+ width: 100%;
833
+ height: 100%;
834
+ margin: 0;
835
+ border: 0;
677
836
  background: transparent;
678
- color: var(--cb-fg);
679
- font-family: var(--cb-font-sans);
680
- font-size: var(--cb-text-sm);
681
- outline: none;
837
+ border-radius: var(--cb-radius-sm);
838
+ opacity: 0;
682
839
  }
683
- .cb-combobox__input::placeholder { color: var(--cb-fg-subtle); }
684
-
685
- .cb-combobox__clear,
686
- .cb-combobox__trigger {
687
- flex: none;
688
- display: inline-flex;
689
- align-items: center;
690
- justify-content: center;
691
- width: 1.75rem;
692
- height: 1.75rem;
693
- border: none;
840
+ .cb-radio-group--segmented .cb-radio::before { content: none; }
841
+ .cb-radio-group--segmented .cb-radio__indicator { display: none; }
842
+ .cb-radio-group--segmented .cb-choice__text { width: 100%; }
843
+ .cb-radio-group--segmented .cb-choice__label {
844
+ display: block;
845
+ padding: var(--cb-space-2) var(--cb-space-3);
694
846
  border-radius: var(--cb-radius-sm);
695
- background: transparent;
696
- color: var(--cb-fg-subtle);
697
- cursor: pointer;
847
+ font-weight: var(--cb-weight-medium);
848
+ color: var(--cb-fg-muted);
849
+ text-align: center;
850
+ white-space: nowrap;
851
+ transition: background-color var(--cb-duration-fast) var(--cb-ease-standard),
852
+ color var(--cb-duration-fast) var(--cb-ease-standard);
698
853
  }
699
- .cb-combobox__clear:hover,
700
- .cb-combobox__trigger:hover { color: var(--cb-fg); background: var(--cb-bg-subtle); }
701
- .cb-combobox__clear:focus-visible,
702
- .cb-combobox__trigger:focus-visible { outline: var(--cb-focus-width) solid var(--cb-tone-brand); outline-offset: var(--cb-focus-offset-inset); }
703
-
704
- .cb-combobox__popup {
705
- box-sizing: border-box;
706
- min-width: var(--anchor-width, 12rem);
707
- max-height: 18rem;
708
- overflow-y: auto;
709
- padding: var(--cb-space-1);
854
+ .cb-radio-group--segmented .cb-radio[data-checked] ~ .cb-choice__text .cb-choice__label {
710
855
  background: var(--cb-surface-raised);
711
- border: var(--cb-border-width) solid var(--cb-border);
712
- border-radius: var(--cb-radius-md);
713
- box-shadow: var(--cb-shadow-md);
714
- z-index: var(--cb-z-popover);
715
- font-family: var(--cb-font-sans);
716
- transition: opacity var(--cb-duration-fast) var(--cb-ease-entrance);
856
+ color: var(--cb-fg);
857
+ box-shadow: var(--cb-shadow-sm);
858
+ }
859
+ .cb-radio-group--segmented .cb-radio:focus-visible ~ .cb-choice__text .cb-choice__label {
860
+ outline: var(--cb-focus-width) solid var(--cb-tone-brand);
861
+ outline-offset: var(--cb-focus-offset);
862
+ }
863
+ .cb-radio-group--segmented .cb-radio[data-disabled] ~ .cb-choice__text .cb-choice__label {
864
+ opacity: 0.55;
865
+ cursor: not-allowed;
717
866
  }
718
- .cb-combobox__popup[data-starting-style],
719
- .cb-combobox__popup[data-ending-style] { opacity: 0; }
720
867
 
721
- .cb-combobox__item {
722
- display: grid;
723
- grid-template-columns: 1.25rem 1fr;
724
- align-items: start;
725
- gap: var(--cb-space-2);
726
- padding: var(--cb-space-2) var(--cb-space-3);
727
- min-height: 2.25rem;
728
- border-radius: var(--cb-radius-sm);
729
- font-size: var(--cb-text-sm);
730
- color: var(--cb-fg);
731
- cursor: pointer;
732
- outline: none;
868
+ @media (prefers-reduced-motion: reduce) {
869
+ .cb-radio-group--segmented .cb-choice__label { transition: none; }
733
870
  }
734
- .cb-combobox__item[data-highlighted] { background: color-mix(in oklch, var(--cb-tone-brand) 12%, transparent); }
735
- .cb-combobox__check { display: inline-flex; justify-content: center; align-items: center; min-height: 1.25rem; color: var(--cb-tone-brand); }
736
- .cb-combobox__text { display: flex; flex-direction: column; gap: 0.0625rem; min-width: 0; line-height: 1.25rem; }
737
- .cb-combobox__description { font-size: var(--cb-text-xs); color: var(--cb-fg-subtle); }
738
- .cb-combobox__empty { padding: var(--cb-space-3); font-size: var(--cb-text-sm); color: var(--cb-fg-subtle); text-align: center; }
739
871
 
740
- /* form/date-input.css */
872
+ /* form/date-picker.css */
741
873
  .cb-date {
742
874
  display: flex;
743
875
  align-items: center;
@@ -795,7 +927,6 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
795
927
  border-radius: var(--cb-radius-md);
796
928
  box-shadow: var(--cb-shadow-md);
797
929
  font-family: var(--cb-font-sans);
798
- z-index: var(--cb-z-popover);
799
930
  transition: opacity var(--cb-duration-fast) var(--cb-ease-entrance);
800
931
  }
801
932
  .cb-calendar[data-starting-style],
@@ -852,7 +983,6 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
852
983
  border: var(--cb-border-width) solid var(--cb-border);
853
984
  border-radius: var(--cb-radius-md);
854
985
  box-shadow: var(--cb-shadow-md);
855
- z-index: var(--cb-z-popover);
856
986
  font-family: var(--cb-font-sans);
857
987
  }
858
988
  .cb-times__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.125rem; min-width: 7rem; }
@@ -872,6 +1002,8 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
872
1002
  .cb-times__option:hover { background: var(--cb-bg-subtle); }
873
1003
  .cb-times__option[data-selected] { background: color-mix(in oklch, var(--cb-tone-brand) 16%, transparent); color: var(--cb-tone-brand); font-weight: var(--cb-weight-medium); }
874
1004
  .cb-times__option:focus-visible { outline: var(--cb-focus-width) solid var(--cb-tone-brand); outline-offset: var(--cb-focus-offset-inset); }
1005
+ .cb-calendar__positioner,
1006
+ .cb-times__positioner { z-index: var(--cb-z-dropdown); }
875
1007
 
876
1008
  /* form/field.css */
877
1009
  .cb-field { display: flex; flex-direction: column; gap: var(--cb-space-2); }
@@ -924,71 +1056,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
924
1056
  box-shadow: 0 0 0 var(--cb-focus-width) color-mix(in oklch, var(--cb-tone-danger) 30%, transparent);
925
1057
  }
926
1058
 
927
- /* form/file-drop.css */
928
- .cb-filedrop { display: flex; flex-direction: column; gap: var(--cb-space-3); font-family: var(--cb-font-sans); }
929
- .cb-filedrop__zone {
930
- cursor: pointer;
931
- display: flex;
932
- flex-direction: column;
933
- align-items: center;
934
- gap: var(--cb-space-2);
935
- padding: var(--cb-space-6) var(--cb-space-4);
936
- border: 1px dashed var(--cb-border-strong);
937
- border-radius: var(--cb-radius-lg);
938
- background: var(--cb-surface);
939
- text-align: center;
940
- transition: border-color var(--cb-duration-fast) var(--cb-ease-standard),
941
- background-color var(--cb-duration-fast) var(--cb-ease-standard);
942
- }
943
- .cb-filedrop__zone[data-over] {
944
- border-color: var(--cb-tone-brand);
945
- background: color-mix(in oklch, var(--cb-tone-brand) 8%, var(--cb-surface));
946
- }
947
- .cb-filedrop__zone[data-disabled] { opacity: 0.55; }
948
- .cb-filedrop__icon { color: var(--cb-fg-subtle); }
949
- .cb-filedrop__button {
950
- color: var(--cb-tone-brand);
951
- font-size: var(--cb-text-sm);
952
- font-weight: var(--cb-weight-medium);
953
- border-radius: var(--cb-radius-sm);
954
- padding: var(--cb-space-1) var(--cb-space-2);
955
- }
956
- .cb-filedrop__zone:hover .cb-filedrop__button { background: color-mix(in oklch, var(--cb-tone-brand) 12%, transparent); }
957
- /* The input is what receives focus, so the ring is drawn on the zone around it. */
958
- .cb-filedrop__zone:has(input:focus-visible) {
959
- outline: var(--cb-focus-width) solid var(--cb-tone-brand);
960
- outline-offset: var(--cb-focus-offset);
961
- }
962
- .cb-filedrop__zone[data-disabled] { cursor: not-allowed; }
963
- .cb-filedrop__hint { margin: 0; font-size: var(--cb-text-xs); color: var(--cb-fg-subtle); }
964
-
965
- .cb-filedrop__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--cb-space-2); }
966
- .cb-filedrop__file {
967
- margin: 0;
968
- display: flex;
969
- align-items: center;
970
- gap: var(--cb-space-3);
971
- padding: var(--cb-space-2) var(--cb-space-3);
972
- border: var(--cb-border-width) solid var(--cb-border);
973
- border-radius: var(--cb-radius-md);
974
- background: var(--cb-surface);
975
- font-size: var(--cb-text-sm);
976
- }
977
- .cb-filedrop__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--cb-fg); }
978
- .cb-filedrop__size { font-size: var(--cb-text-xs); color: var(--cb-fg-subtle); font-variant-numeric: tabular-nums; }
979
- .cb-filedrop__remove {
980
- border: none;
981
- background: transparent;
982
- color: var(--cb-fg-subtle);
983
- cursor: pointer;
984
- display: inline-flex;
985
- padding: var(--cb-space-1);
986
- border-radius: var(--cb-radius-sm);
987
- }
988
- .cb-filedrop__remove:hover { color: var(--cb-tone-danger); background: color-mix(in oklch, var(--cb-tone-danger) 10%, transparent); }
989
- .cb-filedrop__remove:focus-visible { outline: var(--cb-focus-width) solid var(--cb-tone-danger); outline-offset: var(--cb-focus-offset); }
990
-
991
- /* form/number-input.css */
1059
+ /* form/input-number.css */
992
1060
  .cb-number {
993
1061
  display: inline-flex;
994
1062
  align-items: center;
@@ -1045,7 +1113,41 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1045
1113
  .cb-number__step:disabled { opacity: 0.35; cursor: not-allowed; }
1046
1114
  .cb-number__step:focus-visible { outline: var(--cb-focus-width) solid var(--cb-tone-brand); outline-offset: var(--cb-focus-offset-inset); }
1047
1115
 
1116
+ /* form/rate.css */
1117
+ .cb-rate {
1118
+ display: inline-flex;
1119
+ align-items: center;
1120
+ gap: var(--cb-space-1);
1121
+ color: var(--cb-tone-warning);
1122
+ line-height: 0;
1123
+ }
1124
+
1125
+ .cb-rate__star {
1126
+ padding: 0;
1127
+ border: 0;
1128
+ background: none;
1129
+ color: inherit;
1130
+ cursor: pointer;
1131
+ line-height: 0;
1132
+ border-radius: var(--cb-radius-sm);
1133
+ transition: transform var(--cb-duration-fast) var(--cb-ease-standard);
1134
+ }
1135
+ .cb-rate__star:hover { transform: scale(1.12); }
1136
+ .cb-rate__star:focus-visible {
1137
+ outline: var(--cb-focus-width) solid var(--cb-tone-brand);
1138
+ outline-offset: var(--cb-focus-offset);
1139
+ }
1140
+
1141
+ /* Reduced motion drops the transform and keeps the state visible: the star is
1142
+ already filled or not, which is the whole feedback. */
1143
+ @media (prefers-reduced-motion: reduce) {
1144
+ .cb-rate__star { transition: none; }
1145
+ .cb-rate__star:hover { transform: none; }
1146
+ }
1147
+
1048
1148
  /* form/select.css */
1149
+ .cb-select__positioner { z-index: var(--cb-z-dropdown); }
1150
+
1049
1151
  .cb-select {
1050
1152
  display: inline-flex;
1051
1153
  align-items: center;
@@ -1080,6 +1182,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1080
1182
 
1081
1183
  .cb-select__popup {
1082
1184
  box-sizing: border-box;
1185
+ width: var(--anchor-width, auto);
1083
1186
  min-width: var(--anchor-width, 10rem);
1084
1187
  max-height: 18rem;
1085
1188
  overflow-y: auto;
@@ -1088,7 +1191,6 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1088
1191
  border-radius: var(--cb-radius-md);
1089
1192
  box-shadow: var(--cb-shadow-md);
1090
1193
  padding: var(--cb-space-1);
1091
- z-index: var(--cb-z-popover);
1092
1194
  font-family: var(--cb-font-sans);
1093
1195
  font-size: var(--cb-text-sm);
1094
1196
  transition: opacity var(--cb-duration-fast) var(--cb-ease-entrance),
@@ -1104,7 +1206,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1104
1206
 
1105
1207
  .cb-select__item {
1106
1208
  display: grid;
1107
- grid-template-columns: 1.25rem 1fr;
1209
+ grid-template-columns: 1fr 1.25rem;
1108
1210
  align-items: center;
1109
1211
  gap: var(--cb-space-2);
1110
1212
  padding: var(--cb-space-2) var(--cb-space-2);
@@ -1116,7 +1218,71 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1116
1218
  .cb-select__item[data-highlighted] { background: color-mix(in oklch, var(--cb-tone-brand) 12%, transparent); }
1117
1219
  .cb-select__item[data-selected] { font-weight: var(--cb-weight-medium); }
1118
1220
  .cb-select__item[data-disabled] { color: var(--cb-fg-subtle); text-decoration: line-through; cursor: not-allowed; }
1119
- .cb-select__check { display: inline-flex; justify-content: center; color: var(--cb-tone-brand); }
1221
+ .cb-select__check { order: 2; display: inline-flex; justify-content: center; color: var(--cb-tone-brand); }
1222
+
1223
+ /* form/upload.css */
1224
+ .cb-filedrop { display: flex; flex-direction: column; gap: var(--cb-space-3); font-family: var(--cb-font-sans); }
1225
+ .cb-filedrop__zone {
1226
+ cursor: pointer;
1227
+ display: flex;
1228
+ flex-direction: column;
1229
+ align-items: center;
1230
+ gap: var(--cb-space-2);
1231
+ padding: var(--cb-space-6) var(--cb-space-4);
1232
+ border: 1px dashed var(--cb-border-strong);
1233
+ border-radius: var(--cb-radius-lg);
1234
+ background: var(--cb-surface);
1235
+ text-align: center;
1236
+ transition: border-color var(--cb-duration-fast) var(--cb-ease-standard),
1237
+ background-color var(--cb-duration-fast) var(--cb-ease-standard);
1238
+ }
1239
+ .cb-filedrop__zone[data-over] {
1240
+ border-color: var(--cb-tone-brand);
1241
+ background: color-mix(in oklch, var(--cb-tone-brand) 8%, var(--cb-surface));
1242
+ }
1243
+ .cb-filedrop__zone[data-disabled] { opacity: 0.55; }
1244
+ .cb-filedrop__icon { color: var(--cb-fg-subtle); }
1245
+ .cb-filedrop__button {
1246
+ color: var(--cb-tone-brand);
1247
+ font-size: var(--cb-text-sm);
1248
+ font-weight: var(--cb-weight-medium);
1249
+ border-radius: var(--cb-radius-sm);
1250
+ padding: var(--cb-space-1) var(--cb-space-2);
1251
+ }
1252
+ .cb-filedrop__zone:hover .cb-filedrop__button { background: color-mix(in oklch, var(--cb-tone-brand) 12%, transparent); }
1253
+ /* The input is what receives focus, so the ring is drawn on the zone around it. */
1254
+ .cb-filedrop__zone:has(input:focus-visible) {
1255
+ outline: var(--cb-focus-width) solid var(--cb-tone-brand);
1256
+ outline-offset: var(--cb-focus-offset);
1257
+ }
1258
+ .cb-filedrop__zone[data-disabled] { cursor: not-allowed; }
1259
+ .cb-filedrop__hint { margin: 0; font-size: var(--cb-text-xs); color: var(--cb-fg-subtle); }
1260
+
1261
+ .cb-filedrop__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--cb-space-2); }
1262
+ .cb-filedrop__file {
1263
+ margin: 0;
1264
+ display: flex;
1265
+ align-items: center;
1266
+ gap: var(--cb-space-3);
1267
+ padding: var(--cb-space-2) var(--cb-space-3);
1268
+ border: var(--cb-border-width) solid var(--cb-border);
1269
+ border-radius: var(--cb-radius-md);
1270
+ background: var(--cb-surface);
1271
+ font-size: var(--cb-text-sm);
1272
+ }
1273
+ .cb-filedrop__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--cb-fg); }
1274
+ .cb-filedrop__size { font-size: var(--cb-text-xs); color: var(--cb-fg-subtle); font-variant-numeric: tabular-nums; }
1275
+ .cb-filedrop__remove {
1276
+ border: none;
1277
+ background: transparent;
1278
+ color: var(--cb-fg-subtle);
1279
+ cursor: pointer;
1280
+ display: inline-flex;
1281
+ padding: var(--cb-space-1);
1282
+ border-radius: var(--cb-radius-sm);
1283
+ }
1284
+ .cb-filedrop__remove:hover { color: var(--cb-tone-danger); background: color-mix(in oklch, var(--cb-tone-danger) 10%, transparent); }
1285
+ .cb-filedrop__remove:focus-visible { outline: var(--cb-focus-width) solid var(--cb-tone-danger); outline-offset: var(--cb-focus-offset); }
1120
1286
 
1121
1287
  /* feedback/badge.css */
1122
1288
  .cb-badge {
@@ -1127,11 +1293,27 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1127
1293
  border-radius: var(--cb-radius-full);
1128
1294
  font-family: var(--cb-font-sans);
1129
1295
  font-weight: var(--cb-weight-medium);
1296
+ /* The line box must not out-measure the height above. Left at its default a
1297
+ word sits in an 18px box and a 12px glyph in a 22px one, so two badges in a
1298
+ row ended up two pixels apart for a reason neither of them chose. */
1299
+ line-height: 1;
1130
1300
  white-space: nowrap;
1131
1301
  border: var(--cb-border-width) solid transparent;
1132
1302
  }
1133
- .cb-badge--sm { font-size: 0.6875rem; padding: 0.0625rem var(--cb-space-2); }
1134
- .cb-badge--md { font-size: var(--cb-text-xs); padding: var(--cb-space-1) var(--cb-space-3); }
1303
+ /* A height, not just padding. Padding alone makes the height a function of the
1304
+ content, so a badge carrying a glyph stands taller than the plain one beside
1305
+ it — and a row of them then disagrees about its own size for a reason nobody
1306
+ chose. One number per size, and the padding sits inside it. */
1307
+ .cb-badge--sm {
1308
+ font-size: 0.6875rem;
1309
+ min-height: 1.375rem;
1310
+ padding: 0.0625rem var(--cb-space-2);
1311
+ }
1312
+ .cb-badge--md {
1313
+ font-size: var(--cb-text-xs);
1314
+ min-height: 1.75rem;
1315
+ padding: var(--cb-space-1) var(--cb-space-3);
1316
+ }
1135
1317
 
1136
1318
  .cb-badge[data-tone="brand"] { --cb-badge-accent: var(--cb-tone-brand); }
1137
1319
  .cb-badge[data-tone="info"] { --cb-badge-accent: var(--cb-tone-info); }
@@ -1201,25 +1383,25 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1201
1383
  .cb-empty__actions { display: flex; gap: var(--cb-space-2); margin-top: var(--cb-space-3); }
1202
1384
 
1203
1385
  /* feedback/progress.css */
1204
- .cb-spinner {
1205
- --cb-spinner-accent: var(--cb-tone-brand);
1386
+ .cb-spin {
1387
+ --cb-spin-accent: var(--cb-tone-brand);
1206
1388
  display: inline-block;
1207
1389
  border-radius: var(--cb-radius-full);
1208
- border: 2px solid color-mix(in oklch, var(--cb-spinner-accent) 25%, transparent);
1209
- border-top-color: var(--cb-spinner-accent);
1390
+ border: 2px solid color-mix(in oklch, var(--cb-spin-accent) 25%, transparent);
1391
+ border-top-color: var(--cb-spin-accent);
1210
1392
  animation: cb-spin calc(var(--cb-duration-deliberate) * 1.4) linear infinite;
1211
1393
  }
1212
- .cb-spinner[data-tone="neutral"] { --cb-spinner-accent: var(--cb-fg-muted); }
1213
- .cb-spinner[data-tone="info"] { --cb-spinner-accent: var(--cb-tone-info); }
1214
- .cb-spinner[data-tone="success"] { --cb-spinner-accent: var(--cb-tone-success); }
1215
- .cb-spinner[data-tone="warning"] { --cb-spinner-accent: var(--cb-tone-warning); }
1216
- .cb-spinner[data-tone="danger"] { --cb-spinner-accent: var(--cb-tone-danger); }
1217
- .cb-spinner--sm { width: 0.875rem; height: 0.875rem; }
1218
- .cb-spinner--md { width: 1.25rem; height: 1.25rem; border-width: 2px; }
1219
- .cb-spinner--lg { width: 2rem; height: 2rem; border-width: 3px; }
1394
+ .cb-spin[data-tone="neutral"] { --cb-spin-accent: var(--cb-fg-muted); }
1395
+ .cb-spin[data-tone="info"] { --cb-spin-accent: var(--cb-tone-info); }
1396
+ .cb-spin[data-tone="success"] { --cb-spin-accent: var(--cb-tone-success); }
1397
+ .cb-spin[data-tone="warning"] { --cb-spin-accent: var(--cb-tone-warning); }
1398
+ .cb-spin[data-tone="danger"] { --cb-spin-accent: var(--cb-tone-danger); }
1399
+ .cb-spin--sm { width: 0.875rem; height: 0.875rem; }
1400
+ .cb-spin--md { width: 1.25rem; height: 1.25rem; border-width: 2px; }
1401
+ .cb-spin--lg { width: 2rem; height: 2rem; border-width: 3px; }
1220
1402
  /* Reduced motion: it stops turning and reads as a ring — the surrounding text says "loading". */
1221
1403
  @media (prefers-reduced-motion: reduce) {
1222
- .cb-spinner { animation: none; opacity: 0.6; }
1404
+ .cb-spin { animation: none; opacity: 0.6; }
1223
1405
  }
1224
1406
 
1225
1407
  .cb-progress {
@@ -1306,6 +1488,57 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1306
1488
 
1307
1489
  .cb-skeleton-text { display: flex; flex-direction: column; gap: var(--cb-space-2); }
1308
1490
 
1491
+ /* feedback/tag.css */
1492
+ /* The look is Badge's, literally — the same `cb-badge` classes rather than a
1493
+ second set that would agree today and drift apart later. Only what a Tag can
1494
+ do that a Badge cannot lives here. */
1495
+ .cb-tag { max-width: 100%; }
1496
+
1497
+ .cb-tag--pressable {
1498
+ border-width: var(--cb-border-width);
1499
+ cursor: pointer;
1500
+ text-decoration: none;
1501
+ /* NOT `font: inherit`. The shorthand resets size and line-height too, so a
1502
+ pressable tag took the page's 16px and stood two pixels taller than the
1503
+ badge beside it — the size classes were being overruled by a reset meant
1504
+ only to undo the browser's button face. */
1505
+ font-family: inherit;
1506
+ font-weight: inherit;
1507
+ transition: border-color var(--cb-duration-fast) var(--cb-ease-standard),
1508
+ background-color var(--cb-duration-fast) var(--cb-ease-standard);
1509
+ }
1510
+ .cb-tag--pressable:hover { border-color: var(--cb-tone-brand); }
1511
+ .cb-tag--pressable:focus-visible {
1512
+ outline: var(--cb-focus-width) solid var(--cb-tone-brand);
1513
+ outline-offset: var(--cb-focus-offset);
1514
+ }
1515
+ .cb-tag--pressable[aria-pressed="true"] { border-color: var(--cb-tone-brand); }
1516
+
1517
+ .cb-tag__close {
1518
+ display: inline-flex;
1519
+ align-items: center;
1520
+ justify-content: center;
1521
+ margin-inline-start: var(--cb-space-1);
1522
+ margin-inline-end: calc(var(--cb-space-1) * -1);
1523
+ padding: 0;
1524
+ border: 0;
1525
+ border-radius: var(--cb-radius-full);
1526
+ background: none;
1527
+ color: inherit;
1528
+ opacity: 0.7;
1529
+ cursor: pointer;
1530
+ }
1531
+ .cb-tag__close:hover { opacity: 1; }
1532
+ .cb-tag__close:focus-visible {
1533
+ outline: var(--cb-focus-width) solid currentColor;
1534
+ outline-offset: 1px;
1535
+ opacity: 1;
1536
+ }
1537
+
1538
+ @media (prefers-reduced-motion: reduce) {
1539
+ .cb-tag--pressable { transition: none; }
1540
+ }
1541
+
1309
1542
  /* feedback/toast.css */
1310
1543
  .cb-toast__viewport {
1311
1544
  position: fixed;
@@ -1399,12 +1632,23 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1399
1632
  .cb-toast__action:focus-visible { outline: var(--cb-focus-width) solid var(--cb-toast-accent); outline-offset: var(--cb-focus-offset); }
1400
1633
 
1401
1634
  /* overlay/command-palette.css */
1635
+ .cb-palette__backdrop {
1636
+ position: fixed;
1637
+ inset: 0;
1638
+ z-index: var(--cb-z-command-backdrop);
1639
+ background: var(--cb-scrim);
1640
+ backdrop-filter: blur(2px);
1641
+ transition: opacity var(--cb-duration-base) var(--cb-ease-entrance);
1642
+ }
1643
+ .cb-palette__backdrop[data-starting-style],
1644
+ .cb-palette__backdrop[data-ending-style] { opacity: 0; }
1645
+
1402
1646
  .cb-palette {
1403
1647
  position: fixed;
1404
1648
  top: 12vh;
1405
1649
  left: 50%;
1406
1650
  translate: -50% 0;
1407
- z-index: calc(var(--cb-z-overlay) + 1);
1651
+ z-index: var(--cb-z-command);
1408
1652
  display: flex;
1409
1653
  flex-direction: column;
1410
1654
  width: min(34rem, calc(100vw - var(--cb-space-6)));
@@ -1422,6 +1666,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1422
1666
  .cb-palette[data-starting-style],
1423
1667
  .cb-palette[data-ending-style] { opacity: 0; translate: -50% -0.5rem; }
1424
1668
  @media (prefers-reduced-motion: reduce) {
1669
+ .cb-palette__backdrop { transition: opacity var(--cb-duration-fast) linear; }
1425
1670
  .cb-palette { transition: opacity var(--cb-duration-fast) linear; }
1426
1671
  .cb-palette[data-starting-style],
1427
1672
  .cb-palette[data-ending-style] { translate: -50% 0; }
@@ -1472,21 +1717,69 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1472
1717
  .cb-palette__shortcut { margin: 0; padding: 0; border: none; background: none; font-family: var(--cb-font-mono); font-size: var(--cb-text-xs); color: var(--cb-fg-subtle); }
1473
1718
  .cb-palette__empty { margin: 0; padding: var(--cb-space-5); text-align: center; font-size: var(--cb-text-sm); color: var(--cb-fg-subtle); }
1474
1719
 
1475
- /* overlay/dialog.css */
1476
- .cb-dialog__backdrop {
1720
+ /* overlay/drawer.css */
1721
+ .cb-drawer__backdrop {
1722
+ position: fixed;
1723
+ inset: 0;
1724
+ z-index: var(--cb-z-modal-backdrop);
1725
+ background: var(--cb-scrim);
1726
+ backdrop-filter: blur(2px);
1727
+ transition: opacity var(--cb-duration-base) var(--cb-ease-standard);
1728
+ }
1729
+ .cb-drawer__backdrop[data-starting-style],
1730
+ .cb-drawer__backdrop[data-ending-style] { opacity: 0; }
1731
+
1732
+ .cb-drawer {
1733
+ position: fixed;
1734
+ z-index: var(--cb-z-modal);
1735
+ inset-block: 0;
1736
+ inset-inline-end: 0;
1737
+ display: flex;
1738
+ flex-direction: column;
1739
+ gap: var(--cb-space-3);
1740
+ box-sizing: border-box;
1741
+ width: min(22rem, calc(100vw - var(--cb-space-6)));
1742
+ height: 100dvh;
1743
+ padding: var(--cb-space-5);
1744
+ overflow: hidden;
1745
+ background: var(--cb-surface-raised);
1746
+ color: var(--cb-fg);
1747
+ border-inline-start: var(--cb-border-width) solid var(--cb-border);
1748
+ border-radius: var(--cb-radius-lg) 0 0 var(--cb-radius-lg);
1749
+ box-shadow: var(--cb-shadow-lg);
1750
+ transform: translateX(0);
1751
+ transition: opacity var(--cb-duration-base) var(--cb-ease-entrance),
1752
+ transform var(--cb-duration-base) var(--cb-ease-entrance);
1753
+ }
1754
+ .cb-drawer[data-starting-style],
1755
+ .cb-drawer[data-ending-style] { opacity: 0; transform: translateX(100%); }
1756
+
1757
+ @media (prefers-reduced-motion: reduce) {
1758
+ .cb-drawer { transition: opacity var(--cb-duration-fast) linear; }
1759
+ .cb-drawer[data-starting-style],
1760
+ .cb-drawer[data-ending-style] { transform: translateX(0); }
1761
+ }
1762
+
1763
+ .cb-drawer__title { margin: 0; font-family: var(--cb-font-sans); font-size: var(--cb-text-lg); font-weight: var(--cb-weight-semibold); }
1764
+ .cb-drawer__description { margin: 0; font-family: var(--cb-font-sans); font-size: var(--cb-text-sm); color: var(--cb-fg-muted); }
1765
+ .cb-drawer__body { min-height: 0; flex: 1; overflow-y: auto; font-family: var(--cb-font-sans); font-size: var(--cb-text-sm); }
1766
+ .cb-drawer__footer { display: flex; justify-content: flex-end; gap: var(--cb-space-2); }
1767
+
1768
+ /* overlay/modal.css */
1769
+ .cb-modal__backdrop {
1477
1770
  position: fixed;
1478
1771
  inset: 0;
1479
- z-index: var(--cb-z-overlay);
1772
+ z-index: var(--cb-z-modal-backdrop);
1480
1773
  background: var(--cb-scrim);
1481
1774
  backdrop-filter: blur(2px);
1482
1775
  transition: opacity var(--cb-duration-base) var(--cb-ease-standard);
1483
1776
  }
1484
- .cb-dialog__backdrop[data-starting-style],
1485
- .cb-dialog__backdrop[data-ending-style] { opacity: 0; }
1777
+ .cb-modal__backdrop[data-starting-style],
1778
+ .cb-modal__backdrop[data-ending-style] { opacity: 0; }
1486
1779
 
1487
- .cb-dialog {
1780
+ .cb-modal {
1488
1781
  position: fixed;
1489
- z-index: calc(var(--cb-z-overlay) + 1);
1782
+ z-index: var(--cb-z-modal);
1490
1783
  display: flex;
1491
1784
  flex-direction: column;
1492
1785
  gap: var(--cb-space-3);
@@ -1498,36 +1791,40 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1498
1791
  box-shadow: var(--cb-shadow-lg);
1499
1792
  padding: var(--cb-space-5);
1500
1793
  width: calc(100vw - var(--cb-space-6));
1794
+ max-height: calc(100dvh - var(--cb-space-6));
1501
1795
  transition: opacity var(--cb-duration-base) var(--cb-ease-entrance),
1502
1796
  transform var(--cb-duration-base) var(--cb-ease-entrance);
1503
1797
  }
1504
- .cb-dialog--sm { max-width: 24rem; }
1505
- .cb-dialog--md { max-width: 32rem; }
1506
- .cb-dialog--lg { max-width: 46rem; }
1798
+ .cb-modal--sm { max-width: 24rem; }
1799
+ .cb-modal--md { max-width: 32rem; }
1800
+ .cb-modal--lg { max-width: 46rem; }
1507
1801
 
1508
- .cb-dialog--center { top: 50%; left: 50%; transform: translate(-50%, -50%) scale(1); }
1509
- .cb-dialog--center[data-starting-style],
1510
- .cb-dialog--center[data-ending-style] { opacity: 0; transform: translate(-50%, -48%) scale(0.96); }
1802
+ .cb-modal--center { top: 50%; left: 50%; transform: translate(-50%, -50%) scale(1); }
1803
+ .cb-modal--center[data-starting-style],
1804
+ .cb-modal--center[data-ending-style] { opacity: 0; transform: translate(-50%, -48%) scale(0.96); }
1511
1805
 
1512
- .cb-dialog--end { inset-inline-end: var(--cb-space-4); inset-block-end: var(--cb-space-4); transform: translateY(0); }
1513
- .cb-dialog--end[data-starting-style],
1514
- .cb-dialog--end[data-ending-style] { opacity: 0; transform: translateY(12px); }
1806
+ .cb-modal--end { inset-inline-end: var(--cb-space-4); inset-block-end: var(--cb-space-4); transform: translateY(0); }
1807
+ .cb-modal--end[data-starting-style],
1808
+ .cb-modal--end[data-ending-style] { opacity: 0; transform: translateY(12px); }
1515
1809
 
1516
1810
  /* Reduced motion: the dialog still fades so its arrival is not invisible, but it stops moving. */
1517
1811
  @media (prefers-reduced-motion: reduce) {
1518
- .cb-dialog { transition: opacity var(--cb-duration-fast) linear; }
1519
- .cb-dialog--center[data-starting-style],
1520
- .cb-dialog--center[data-ending-style] { transform: translate(-50%, -50%) scale(1); }
1521
- .cb-dialog--end[data-starting-style],
1522
- .cb-dialog--end[data-ending-style] { transform: translateY(0); }
1812
+ .cb-modal { transition: opacity var(--cb-duration-fast) linear; }
1813
+ .cb-modal--center[data-starting-style],
1814
+ .cb-modal--center[data-ending-style] { transform: translate(-50%, -50%) scale(1); }
1815
+ .cb-modal--end[data-starting-style],
1816
+ .cb-modal--end[data-ending-style] { transform: translateY(0); }
1523
1817
  }
1524
1818
 
1525
- .cb-dialog__title { margin: 0; font-family: var(--cb-font-sans); font-size: var(--cb-text-lg); font-weight: var(--cb-weight-semibold); }
1526
- .cb-dialog__description { margin: 0; font-family: var(--cb-font-sans); font-size: var(--cb-text-sm); color: var(--cb-fg-muted); }
1527
- .cb-dialog__body { font-family: var(--cb-font-sans); font-size: var(--cb-text-sm); }
1528
- .cb-dialog__footer { display: flex; justify-content: flex-end; gap: var(--cb-space-2); margin-top: var(--cb-space-2); }
1819
+ .cb-modal__title { margin: 0; font-family: var(--cb-font-sans); font-size: var(--cb-text-lg); font-weight: var(--cb-weight-semibold); }
1820
+ .cb-modal__description { margin: 0; font-family: var(--cb-font-sans); font-size: var(--cb-text-sm); color: var(--cb-fg-muted); }
1821
+ .cb-modal__body { min-height: 0; overflow-y: auto; font-family: var(--cb-font-sans); font-size: var(--cb-text-sm); }
1822
+ .cb-modal__footer { flex: none; display: flex; justify-content: flex-end; gap: var(--cb-space-2); margin-top: var(--cb-space-2); }
1529
1823
 
1530
1824
  /* overlay/popover.css */
1825
+ .cb-popover-positioner { z-index: var(--cb-z-popover); }
1826
+ .cb-tooltip-positioner { z-index: var(--cb-z-tooltip); }
1827
+
1531
1828
  .cb-popover {
1532
1829
  box-sizing: border-box;
1533
1830
  max-width: 22rem;
@@ -1539,7 +1836,6 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1539
1836
  padding: var(--cb-space-4);
1540
1837
  font-family: var(--cb-font-sans);
1541
1838
  font-size: var(--cb-text-sm);
1542
- z-index: var(--cb-z-popover);
1543
1839
  transition: opacity var(--cb-duration-fast) var(--cb-ease-entrance),
1544
1840
  transform var(--cb-duration-fast) var(--cb-ease-entrance);
1545
1841
  }
@@ -1551,33 +1847,42 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1551
1847
  .cb-popover[data-ending-style] { transform: none; }
1552
1848
  }
1553
1849
 
1554
- /* The tail is a square straddling the popup's edge, rotated 45deg, with a border on only the
1555
- two edges that end up facing outward. Its own fill covers the popup's border underneath, so
1556
- the outline runs from bubble into tail without a line across the join — which is what the
1557
- clip-a-diamond-in-half version could never do: the popup border still crossed its base. */
1558
- .cb-popover__arrow {
1850
+ /* Base UI positions this wrapper along the anchor edge. The SVG fill overlaps the popup edge by
1851
+ one pixel, hiding its border below the tail; only the two outward edges receive a stroke. */
1852
+ .cb-popover__arrow,
1853
+ .cb-tooltip__arrow {
1854
+ --cb-arrow-bg: var(--cb-surface-raised);
1855
+ --cb-arrow-border: var(--cb-border);
1559
1856
  position: absolute;
1560
- width: 18px;
1561
- height: 9px;
1857
+ display: block;
1858
+ width: 16px;
1859
+ height: 8px;
1860
+ overflow: visible;
1562
1861
  pointer-events: none;
1563
1862
  }
1564
- .cb-popover__arrow[data-side="bottom"] { top: 0; }
1565
- .cb-popover__arrow[data-side="top"] { bottom: 0; rotate: 180deg; }
1566
- .cb-popover__arrow[data-side="left"] { right: 0; rotate: 90deg; }
1567
- .cb-popover__arrow[data-side="right"] { left: 0; rotate: -90deg; }
1863
+ .cb-popover__arrow[data-side="bottom"],
1864
+ .cb-tooltip__arrow[data-side="bottom"] { top: -7px; }
1865
+ .cb-popover__arrow[data-side="top"],
1866
+ .cb-tooltip__arrow[data-side="top"] { bottom: -7px; rotate: 180deg; }
1867
+ .cb-popover__arrow[data-side="left"],
1868
+ .cb-tooltip__arrow[data-side="left"] { right: -11px; rotate: 90deg; }
1869
+ .cb-popover__arrow[data-side="right"],
1870
+ .cb-tooltip__arrow[data-side="right"] { left: -11px; rotate: -90deg; }
1568
1871
 
1569
1872
  .cb-arrow {
1570
- position: absolute;
1571
- left: 50%;
1572
- top: 0;
1573
- width: 12px;
1574
- height: 12px;
1575
- translate: -50% -50%;
1576
- rotate: 45deg;
1577
- background: var(--cb-surface-raised);
1578
- border-top: var(--cb-border-width) solid var(--cb-border);
1579
- border-left: var(--cb-border-width) solid var(--cb-border);
1580
- border-start-start-radius: 3px;
1873
+ display: block;
1874
+ width: 100%;
1875
+ height: 100%;
1876
+ overflow: visible;
1877
+ }
1878
+ .cb-arrow__fill { fill: var(--cb-arrow-bg); }
1879
+ .cb-arrow__edge {
1880
+ fill: none;
1881
+ stroke: var(--cb-arrow-border);
1882
+ stroke-width: var(--cb-border-width);
1883
+ stroke-linecap: round;
1884
+ stroke-linejoin: round;
1885
+ vector-effect: non-scaling-stroke;
1581
1886
  }
1582
1887
 
1583
1888
  .cb-tooltip {
@@ -1587,9 +1892,12 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1587
1892
  font-size: var(--cb-text-xs);
1588
1893
  padding: var(--cb-space-1) var(--cb-space-2);
1589
1894
  border-radius: var(--cb-radius-sm);
1590
- z-index: var(--cb-z-popover);
1591
1895
  transition: opacity var(--cb-duration-fast) var(--cb-ease-standard);
1592
1896
  }
1897
+ .cb-tooltip__arrow {
1898
+ --cb-arrow-bg: var(--cb-fg);
1899
+ --cb-arrow-border: var(--cb-fg);
1900
+ }
1593
1901
  .cb-tooltip[data-starting-style],
1594
1902
  .cb-tooltip[data-ending-style] { opacity: 0; }
1595
1903
 
@@ -1730,7 +2038,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1730
2038
  color: var(--cb-fg);
1731
2039
  }
1732
2040
 
1733
- /* data/stat-card.css */
2041
+ /* data/statistic.css */
1734
2042
  .cb-stat { display: flex; flex-direction: column; gap: var(--cb-space-3); }
1735
2043
  .cb-stat__head { display: flex; align-items: center; justify-content: space-between; gap: var(--cb-space-2); }
1736
2044
  .cb-stat__icon { display: inline-flex; color: var(--cb-surface-accent); }
@@ -2104,9 +2412,14 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
2104
2412
  }
2105
2413
 
2106
2414
  /* nav/nav.css */
2415
+ .cb-menu-positioner { z-index: var(--cb-z-dropdown); }
2416
+
2107
2417
  .cb-menu {
2108
2418
  box-sizing: border-box;
2109
- min-width: 12rem;
2419
+ min-width: 14rem;
2420
+ max-height: min(calc(100dvh - var(--cb-space-6)), var(--available-height));
2421
+ overflow-y: auto;
2422
+ overscroll-behavior: contain;
2110
2423
  padding: var(--cb-space-1);
2111
2424
  background: var(--cb-surface-raised);
2112
2425
  border: var(--cb-border-width) solid var(--cb-border);
@@ -2114,10 +2427,17 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
2114
2427
  box-shadow: var(--cb-shadow-md);
2115
2428
  font-family: var(--cb-font-sans);
2116
2429
  font-size: var(--cb-text-sm);
2117
- z-index: var(--cb-z-popover);
2118
2430
  transition: opacity var(--cb-duration-fast) var(--cb-ease-entrance),
2119
2431
  transform var(--cb-duration-fast) var(--cb-ease-entrance);
2120
2432
  }
2433
+ .cb-menu__group-label {
2434
+ padding: var(--cb-space-2) var(--cb-space-2) var(--cb-space-1);
2435
+ color: var(--cb-fg-subtle);
2436
+ font-size: var(--cb-text-xs);
2437
+ font-weight: var(--cb-weight-semibold);
2438
+ letter-spacing: 0.06em;
2439
+ text-transform: uppercase;
2440
+ }
2121
2441
  .cb-menu[data-starting-style],
2122
2442
  .cb-menu[data-ending-style] { opacity: 0; transform: scale(0.97); }
2123
2443
  @media (prefers-reduced-motion: reduce) {
@@ -2173,11 +2493,11 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
2173
2493
  .cb-crumbs__current { color: var(--cb-fg); font-weight: var(--cb-weight-medium); }
2174
2494
  .cb-crumbs__separator { color: var(--cb-fg-subtle); flex: none; }
2175
2495
 
2176
- .cb-stepper { list-style: none; margin: 0; padding: 0; font-family: var(--cb-font-sans); display: flex; }
2177
- .cb-stepper--horizontal { flex-direction: row; gap: var(--cb-space-5); flex-wrap: wrap; }
2178
- .cb-stepper--vertical { flex-direction: column; gap: var(--cb-space-4); }
2179
- .cb-stepper__step { margin: 0; display: flex; align-items: flex-start; gap: var(--cb-space-3); }
2180
- .cb-stepper__marker {
2496
+ .cb-steps { list-style: none; margin: 0; padding: 0; font-family: var(--cb-font-sans); display: flex; }
2497
+ .cb-steps--horizontal { flex-direction: row; gap: var(--cb-space-5); flex-wrap: wrap; }
2498
+ .cb-steps--vertical { flex-direction: column; gap: var(--cb-space-4); }
2499
+ .cb-steps__step { margin: 0; display: flex; align-items: flex-start; gap: var(--cb-space-3); }
2500
+ .cb-steps__marker {
2181
2501
  flex: none;
2182
2502
  display: inline-flex;
2183
2503
  align-items: center;
@@ -2191,20 +2511,20 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
2191
2511
  color: var(--cb-fg-subtle);
2192
2512
  background: var(--cb-surface);
2193
2513
  }
2194
- .cb-stepper__step[data-state="done"] .cb-stepper__marker {
2514
+ .cb-steps__step[data-state="done"] .cb-steps__marker {
2195
2515
  background: var(--cb-tone-success);
2196
2516
  border-color: var(--cb-tone-success);
2197
2517
  color: var(--cb-fg-on-brand);
2198
2518
  }
2199
- .cb-stepper__step[data-state="current"] .cb-stepper__marker {
2519
+ .cb-steps__step[data-state="current"] .cb-steps__marker {
2200
2520
  border-color: var(--cb-tone-brand);
2201
2521
  color: var(--cb-tone-brand);
2202
2522
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--cb-tone-brand) 16%, transparent);
2203
2523
  }
2204
- .cb-stepper__text { display: flex; flex-direction: column; gap: 0.125rem; }
2205
- .cb-stepper__label { font-size: var(--cb-text-sm); font-weight: var(--cb-weight-medium); color: var(--cb-fg-muted); }
2206
- .cb-stepper__step[data-state="current"] .cb-stepper__label { color: var(--cb-fg); }
2207
- .cb-stepper__description { font-size: var(--cb-text-xs); color: var(--cb-fg-subtle); }
2524
+ .cb-steps__text { display: flex; flex-direction: column; gap: 0.125rem; }
2525
+ .cb-steps__label { font-size: var(--cb-text-sm); font-weight: var(--cb-weight-medium); color: var(--cb-fg-muted); }
2526
+ .cb-steps__step[data-state="current"] .cb-steps__label { color: var(--cb-fg); }
2527
+ .cb-steps__description { font-size: var(--cb-text-xs); color: var(--cb-fg-subtle); }
2208
2528
 
2209
2529
  .cb-sidebar {
2210
2530
  display: flex;
@@ -2315,7 +2635,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
2315
2635
  .cb-sidebar__item--nested { font-size: var(--cb-text-sm); }
2316
2636
 
2317
2637
  .cb-sidebar__flyout-trigger { display: block; width: 100%; }
2318
- .cb-sidebar__flyout-positioner { z-index: var(--cb-z-popover); }
2638
+ .cb-sidebar__flyout-positioner { z-index: var(--cb-z-dropdown); }
2319
2639
 
2320
2640
  /* Collapsed, this is where the labels live. */
2321
2641
  .cb-sidebar__flyout {
@@ -2465,7 +2785,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
2465
2785
  costs no extra element, and the shadow colour is the same token the dialog backdrop uses. */
2466
2786
  .cb-spotlight {
2467
2787
  position: fixed;
2468
- z-index: var(--cb-z-coachmark);
2788
+ z-index: var(--cb-z-coachmark-spotlight);
2469
2789
  left: var(--cb-spotlight-x);
2470
2790
  top: var(--cb-spotlight-y);
2471
2791
  width: var(--cb-spotlight-w);
@@ -2485,7 +2805,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
2485
2805
 
2486
2806
  /* The positioner is what the portal places, so the stacking order has to live here; a z-index
2487
2807
  on the popup alone loses to the spotlight's own layer. */
2488
- .cb-coachmark__positioner { z-index: calc(var(--cb-z-coachmark) + 1); }
2808
+ .cb-coachmark__positioner { z-index: var(--cb-z-coachmark); }
2489
2809
 
2490
2810
  .cb-coachmark {
2491
2811
  box-sizing: border-box;