@ceebee/ui 0.4.0 → 0.5.2

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,117 @@ 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
+ /* Same shape and the same reasoning as the Select's popup — see select.css. */
551
+ .cb-autocomplete__popup {
552
+ box-sizing: border-box;
553
+ width: max-content;
554
+ min-width: var(--anchor-width, 12rem);
555
+ max-width: var(--available-width, 100vw);
556
+ max-height: min(18rem, var(--available-height, 18rem));
557
+ overflow-y: auto;
558
+ padding: var(--cb-space-1);
559
+ background: var(--cb-surface-raised);
560
+ border: var(--cb-border-width) solid var(--cb-border);
561
+ border-radius: var(--cb-radius-md);
562
+ box-shadow: var(--cb-shadow-md);
563
+ font-family: var(--cb-font-sans);
564
+ transition: opacity var(--cb-duration-fast) var(--cb-ease-entrance);
565
+ }
566
+ .cb-autocomplete__popup[data-starting-style],
567
+ .cb-autocomplete__popup[data-ending-style] { opacity: 0; }
568
+
569
+ .cb-autocomplete__item {
570
+ display: grid;
571
+ grid-template-columns: 1fr 1.25rem;
572
+ align-items: start;
573
+ gap: var(--cb-space-2);
574
+ padding: var(--cb-space-2) var(--cb-space-2);
575
+ min-height: 2.25rem;
576
+ border-radius: var(--cb-radius-sm);
577
+ font-size: var(--cb-text-sm);
578
+ color: var(--cb-fg);
579
+ cursor: pointer;
580
+ outline: none;
581
+ }
582
+ .cb-autocomplete__item[data-highlighted] { background: color-mix(in oklch, var(--cb-tone-brand) 12%, transparent); }
583
+ .cb-autocomplete__check { order: 2; display: inline-flex; justify-content: center; align-items: center; min-height: 1.25rem; color: var(--cb-tone-brand); }
584
+ .cb-autocomplete__text { display: flex; flex-direction: column; gap: 0.0625rem; min-width: 0; line-height: 1.25rem; }
585
+ .cb-autocomplete__description { font-size: var(--cb-text-xs); color: var(--cb-fg-subtle); }
586
+ .cb-autocomplete__empty,
587
+ .cb-autocomplete__status { padding: var(--cb-space-3); font-size: var(--cb-text-sm); color: var(--cb-fg-subtle); text-align: center; }
588
+ /* Base UI keeps the empty message mounted while the list has matches, so it sat
589
+ as a silent 24px band above every result. A message with nothing to say takes
590
+ no room. */
591
+ .cb-autocomplete__empty:empty { display: none; }
592
+ .cb-autocomplete__status { margin: 0; }
593
+
438
594
  /* form/button.css */
439
595
  .cb-button {
440
596
  --cb-button-accent: var(--cb-tone-brand);
@@ -510,6 +666,10 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
510
666
  .cb-button__spinner { animation: none; opacity: 0.6; }
511
667
  }
512
668
 
669
+ /* An anchor does not inherit a button's reset, so the two only match once the
670
+ browser's own link styling is taken back off. */
671
+ .cb-link-button { text-decoration: none; }
672
+
513
673
  /* form/choice.css */
514
674
  .cb-choice { display: flex; align-items: flex-start; gap: var(--cb-space-3); }
515
675
  /* A row whose text is a single line centres on the control instead of hanging from its top. */
@@ -646,98 +806,72 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
646
806
  .cb-switch__thumb { transition: none; }
647
807
  }
648
808
 
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);
809
+ /* Segmented: the same radiogroup, drawn as one track.
810
+
811
+ The dot is not hidden with `display: none` — a removed control cannot be
812
+ focused or hit. It is stretched over its whole cell at zero opacity instead,
813
+ so the cell IS the radio: pointer, focus ring and keyboard all keep working
814
+ and only the drawing changes. */
815
+ .cb-radio-group--segmented {
816
+ display: inline-flex;
817
+ align-items: stretch;
818
+ gap: var(--cb-space-hair, 2px);
819
+ padding: 2px;
820
+ background: var(--cb-bg-subtle);
821
+ border: var(--cb-border-width) solid var(--cb-border);
658
822
  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
- }
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
823
  }
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;
824
+ .cb-radio-group--segmented .cb-choice {
825
+ position: relative;
826
+ align-items: center;
827
+ gap: 0;
675
828
  min-width: 0;
676
- border: none;
829
+ }
830
+ .cb-radio-group--segmented .cb-radio {
831
+ position: absolute;
832
+ inset: 0;
833
+ z-index: 1;
834
+ width: 100%;
835
+ height: 100%;
836
+ margin: 0;
837
+ border: 0;
677
838
  background: transparent;
678
- color: var(--cb-fg);
679
- font-family: var(--cb-font-sans);
680
- font-size: var(--cb-text-sm);
681
- outline: none;
839
+ border-radius: var(--cb-radius-sm);
840
+ opacity: 0;
682
841
  }
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;
842
+ .cb-radio-group--segmented .cb-radio::before { content: none; }
843
+ .cb-radio-group--segmented .cb-radio__indicator { display: none; }
844
+ .cb-radio-group--segmented .cb-choice__text { width: 100%; }
845
+ .cb-radio-group--segmented .cb-choice__label {
846
+ display: block;
847
+ padding: var(--cb-space-2) var(--cb-space-3);
694
848
  border-radius: var(--cb-radius-sm);
695
- background: transparent;
696
- color: var(--cb-fg-subtle);
697
- cursor: pointer;
849
+ font-weight: var(--cb-weight-medium);
850
+ color: var(--cb-fg-muted);
851
+ text-align: center;
852
+ white-space: nowrap;
853
+ transition: background-color var(--cb-duration-fast) var(--cb-ease-standard),
854
+ color var(--cb-duration-fast) var(--cb-ease-standard);
698
855
  }
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);
856
+ .cb-radio-group--segmented .cb-radio[data-checked] ~ .cb-choice__text .cb-choice__label {
710
857
  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);
858
+ color: var(--cb-fg);
859
+ box-shadow: var(--cb-shadow-sm);
860
+ }
861
+ .cb-radio-group--segmented .cb-radio:focus-visible ~ .cb-choice__text .cb-choice__label {
862
+ outline: var(--cb-focus-width) solid var(--cb-tone-brand);
863
+ outline-offset: var(--cb-focus-offset);
864
+ }
865
+ .cb-radio-group--segmented .cb-radio[data-disabled] ~ .cb-choice__text .cb-choice__label {
866
+ opacity: 0.55;
867
+ cursor: not-allowed;
717
868
  }
718
- .cb-combobox__popup[data-starting-style],
719
- .cb-combobox__popup[data-ending-style] { opacity: 0; }
720
869
 
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;
870
+ @media (prefers-reduced-motion: reduce) {
871
+ .cb-radio-group--segmented .cb-choice__label { transition: none; }
733
872
  }
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
873
 
740
- /* form/date-input.css */
874
+ /* form/date-picker.css */
741
875
  .cb-date {
742
876
  display: flex;
743
877
  align-items: center;
@@ -795,7 +929,6 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
795
929
  border-radius: var(--cb-radius-md);
796
930
  box-shadow: var(--cb-shadow-md);
797
931
  font-family: var(--cb-font-sans);
798
- z-index: var(--cb-z-popover);
799
932
  transition: opacity var(--cb-duration-fast) var(--cb-ease-entrance);
800
933
  }
801
934
  .cb-calendar[data-starting-style],
@@ -852,7 +985,6 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
852
985
  border: var(--cb-border-width) solid var(--cb-border);
853
986
  border-radius: var(--cb-radius-md);
854
987
  box-shadow: var(--cb-shadow-md);
855
- z-index: var(--cb-z-popover);
856
988
  font-family: var(--cb-font-sans);
857
989
  }
858
990
  .cb-times__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.125rem; min-width: 7rem; }
@@ -872,6 +1004,8 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
872
1004
  .cb-times__option:hover { background: var(--cb-bg-subtle); }
873
1005
  .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
1006
  .cb-times__option:focus-visible { outline: var(--cb-focus-width) solid var(--cb-tone-brand); outline-offset: var(--cb-focus-offset-inset); }
1007
+ .cb-calendar__positioner,
1008
+ .cb-times__positioner { z-index: var(--cb-z-dropdown); }
875
1009
 
876
1010
  /* form/field.css */
877
1011
  .cb-field { display: flex; flex-direction: column; gap: var(--cb-space-2); }
@@ -924,71 +1058,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
924
1058
  box-shadow: 0 0 0 var(--cb-focus-width) color-mix(in oklch, var(--cb-tone-danger) 30%, transparent);
925
1059
  }
926
1060
 
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 */
1061
+ /* form/input-number.css */
992
1062
  .cb-number {
993
1063
  display: inline-flex;
994
1064
  align-items: center;
@@ -1045,7 +1115,41 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1045
1115
  .cb-number__step:disabled { opacity: 0.35; cursor: not-allowed; }
1046
1116
  .cb-number__step:focus-visible { outline: var(--cb-focus-width) solid var(--cb-tone-brand); outline-offset: var(--cb-focus-offset-inset); }
1047
1117
 
1118
+ /* form/rate.css */
1119
+ .cb-rate {
1120
+ display: inline-flex;
1121
+ align-items: center;
1122
+ gap: var(--cb-space-1);
1123
+ color: var(--cb-tone-warning);
1124
+ line-height: 0;
1125
+ }
1126
+
1127
+ .cb-rate__star {
1128
+ padding: 0;
1129
+ border: 0;
1130
+ background: none;
1131
+ color: inherit;
1132
+ cursor: pointer;
1133
+ line-height: 0;
1134
+ border-radius: var(--cb-radius-sm);
1135
+ transition: transform var(--cb-duration-fast) var(--cb-ease-standard);
1136
+ }
1137
+ .cb-rate__star:hover { transform: scale(1.12); }
1138
+ .cb-rate__star:focus-visible {
1139
+ outline: var(--cb-focus-width) solid var(--cb-tone-brand);
1140
+ outline-offset: var(--cb-focus-offset);
1141
+ }
1142
+
1143
+ /* Reduced motion drops the transform and keeps the state visible: the star is
1144
+ already filled or not, which is the whole feedback. */
1145
+ @media (prefers-reduced-motion: reduce) {
1146
+ .cb-rate__star { transition: none; }
1147
+ .cb-rate__star:hover { transform: none; }
1148
+ }
1149
+
1048
1150
  /* form/select.css */
1151
+ .cb-select__positioner { z-index: var(--cb-z-dropdown); }
1152
+
1049
1153
  .cb-select {
1050
1154
  display: inline-flex;
1051
1155
  align-items: center;
@@ -1078,17 +1182,32 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1078
1182
  .cb-select[data-disabled] { opacity: 0.55; cursor: not-allowed; }
1079
1183
  .cb-select__icon { display: inline-flex; color: var(--cb-fg-subtle); }
1080
1184
 
1185
+ /* As wide as its longest option, and never wider than the screen.
1186
+ *
1187
+ * Pinned to the anchor's width, a popup dropping from a field that is half a
1188
+ * filter row is 175px on a phone — and the options in it are place names.
1189
+ * "Kabupaten Tangerang Selatan (5)" wrapped to three lines there, the one below
1190
+ * it to two, and the list read as a cramped column of broken phrases rather
1191
+ * than as a set of choices.
1192
+ *
1193
+ * So the anchor's width becomes the floor rather than the value: still never
1194
+ * narrower than the field it belongs to, because a popup narrower than its
1195
+ * field reads as belonging to something else. `--available-width` is the room
1196
+ * the positioner measured between the anchor and the edge of the screen, so
1197
+ * the ceiling moves with the viewport instead of being a number written here.
1198
+ */
1081
1199
  .cb-select__popup {
1082
1200
  box-sizing: border-box;
1201
+ width: max-content;
1083
1202
  min-width: var(--anchor-width, 10rem);
1084
- max-height: 18rem;
1203
+ max-width: var(--available-width, 100vw);
1204
+ max-height: min(18rem, var(--available-height, 18rem));
1085
1205
  overflow-y: auto;
1086
1206
  background: var(--cb-surface-raised);
1087
1207
  border: var(--cb-border-width) solid var(--cb-border);
1088
1208
  border-radius: var(--cb-radius-md);
1089
1209
  box-shadow: var(--cb-shadow-md);
1090
1210
  padding: var(--cb-space-1);
1091
- z-index: var(--cb-z-popover);
1092
1211
  font-family: var(--cb-font-sans);
1093
1212
  font-size: var(--cb-text-sm);
1094
1213
  transition: opacity var(--cb-duration-fast) var(--cb-ease-entrance),
@@ -1104,7 +1223,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1104
1223
 
1105
1224
  .cb-select__item {
1106
1225
  display: grid;
1107
- grid-template-columns: 1.25rem 1fr;
1226
+ grid-template-columns: 1fr 1.25rem;
1108
1227
  align-items: center;
1109
1228
  gap: var(--cb-space-2);
1110
1229
  padding: var(--cb-space-2) var(--cb-space-2);
@@ -1116,7 +1235,71 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1116
1235
  .cb-select__item[data-highlighted] { background: color-mix(in oklch, var(--cb-tone-brand) 12%, transparent); }
1117
1236
  .cb-select__item[data-selected] { font-weight: var(--cb-weight-medium); }
1118
1237
  .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); }
1238
+ .cb-select__check { order: 2; display: inline-flex; justify-content: center; color: var(--cb-tone-brand); }
1239
+
1240
+ /* form/upload.css */
1241
+ .cb-filedrop { display: flex; flex-direction: column; gap: var(--cb-space-3); font-family: var(--cb-font-sans); }
1242
+ .cb-filedrop__zone {
1243
+ cursor: pointer;
1244
+ display: flex;
1245
+ flex-direction: column;
1246
+ align-items: center;
1247
+ gap: var(--cb-space-2);
1248
+ padding: var(--cb-space-6) var(--cb-space-4);
1249
+ border: 1px dashed var(--cb-border-strong);
1250
+ border-radius: var(--cb-radius-lg);
1251
+ background: var(--cb-surface);
1252
+ text-align: center;
1253
+ transition: border-color var(--cb-duration-fast) var(--cb-ease-standard),
1254
+ background-color var(--cb-duration-fast) var(--cb-ease-standard);
1255
+ }
1256
+ .cb-filedrop__zone[data-over] {
1257
+ border-color: var(--cb-tone-brand);
1258
+ background: color-mix(in oklch, var(--cb-tone-brand) 8%, var(--cb-surface));
1259
+ }
1260
+ .cb-filedrop__zone[data-disabled] { opacity: 0.55; }
1261
+ .cb-filedrop__icon { color: var(--cb-fg-subtle); }
1262
+ .cb-filedrop__button {
1263
+ color: var(--cb-tone-brand);
1264
+ font-size: var(--cb-text-sm);
1265
+ font-weight: var(--cb-weight-medium);
1266
+ border-radius: var(--cb-radius-sm);
1267
+ padding: var(--cb-space-1) var(--cb-space-2);
1268
+ }
1269
+ .cb-filedrop__zone:hover .cb-filedrop__button { background: color-mix(in oklch, var(--cb-tone-brand) 12%, transparent); }
1270
+ /* The input is what receives focus, so the ring is drawn on the zone around it. */
1271
+ .cb-filedrop__zone:has(input:focus-visible) {
1272
+ outline: var(--cb-focus-width) solid var(--cb-tone-brand);
1273
+ outline-offset: var(--cb-focus-offset);
1274
+ }
1275
+ .cb-filedrop__zone[data-disabled] { cursor: not-allowed; }
1276
+ .cb-filedrop__hint { margin: 0; font-size: var(--cb-text-xs); color: var(--cb-fg-subtle); }
1277
+
1278
+ .cb-filedrop__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--cb-space-2); }
1279
+ .cb-filedrop__file {
1280
+ margin: 0;
1281
+ display: flex;
1282
+ align-items: center;
1283
+ gap: var(--cb-space-3);
1284
+ padding: var(--cb-space-2) var(--cb-space-3);
1285
+ border: var(--cb-border-width) solid var(--cb-border);
1286
+ border-radius: var(--cb-radius-md);
1287
+ background: var(--cb-surface);
1288
+ font-size: var(--cb-text-sm);
1289
+ }
1290
+ .cb-filedrop__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--cb-fg); }
1291
+ .cb-filedrop__size { font-size: var(--cb-text-xs); color: var(--cb-fg-subtle); font-variant-numeric: tabular-nums; }
1292
+ .cb-filedrop__remove {
1293
+ border: none;
1294
+ background: transparent;
1295
+ color: var(--cb-fg-subtle);
1296
+ cursor: pointer;
1297
+ display: inline-flex;
1298
+ padding: var(--cb-space-1);
1299
+ border-radius: var(--cb-radius-sm);
1300
+ }
1301
+ .cb-filedrop__remove:hover { color: var(--cb-tone-danger); background: color-mix(in oklch, var(--cb-tone-danger) 10%, transparent); }
1302
+ .cb-filedrop__remove:focus-visible { outline: var(--cb-focus-width) solid var(--cb-tone-danger); outline-offset: var(--cb-focus-offset); }
1120
1303
 
1121
1304
  /* feedback/badge.css */
1122
1305
  .cb-badge {
@@ -1127,11 +1310,27 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1127
1310
  border-radius: var(--cb-radius-full);
1128
1311
  font-family: var(--cb-font-sans);
1129
1312
  font-weight: var(--cb-weight-medium);
1313
+ /* The line box must not out-measure the height above. Left at its default a
1314
+ word sits in an 18px box and a 12px glyph in a 22px one, so two badges in a
1315
+ row ended up two pixels apart for a reason neither of them chose. */
1316
+ line-height: 1;
1130
1317
  white-space: nowrap;
1131
1318
  border: var(--cb-border-width) solid transparent;
1132
1319
  }
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); }
1320
+ /* A height, not just padding. Padding alone makes the height a function of the
1321
+ content, so a badge carrying a glyph stands taller than the plain one beside
1322
+ it — and a row of them then disagrees about its own size for a reason nobody
1323
+ chose. One number per size, and the padding sits inside it. */
1324
+ .cb-badge--sm {
1325
+ font-size: 0.6875rem;
1326
+ min-height: 1.375rem;
1327
+ padding: 0.0625rem var(--cb-space-2);
1328
+ }
1329
+ .cb-badge--md {
1330
+ font-size: var(--cb-text-xs);
1331
+ min-height: 1.75rem;
1332
+ padding: var(--cb-space-1) var(--cb-space-3);
1333
+ }
1135
1334
 
1136
1335
  .cb-badge[data-tone="brand"] { --cb-badge-accent: var(--cb-tone-brand); }
1137
1336
  .cb-badge[data-tone="info"] { --cb-badge-accent: var(--cb-tone-info); }
@@ -1201,25 +1400,25 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1201
1400
  .cb-empty__actions { display: flex; gap: var(--cb-space-2); margin-top: var(--cb-space-3); }
1202
1401
 
1203
1402
  /* feedback/progress.css */
1204
- .cb-spinner {
1205
- --cb-spinner-accent: var(--cb-tone-brand);
1403
+ .cb-spin {
1404
+ --cb-spin-accent: var(--cb-tone-brand);
1206
1405
  display: inline-block;
1207
1406
  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);
1407
+ border: 2px solid color-mix(in oklch, var(--cb-spin-accent) 25%, transparent);
1408
+ border-top-color: var(--cb-spin-accent);
1210
1409
  animation: cb-spin calc(var(--cb-duration-deliberate) * 1.4) linear infinite;
1211
1410
  }
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; }
1411
+ .cb-spin[data-tone="neutral"] { --cb-spin-accent: var(--cb-fg-muted); }
1412
+ .cb-spin[data-tone="info"] { --cb-spin-accent: var(--cb-tone-info); }
1413
+ .cb-spin[data-tone="success"] { --cb-spin-accent: var(--cb-tone-success); }
1414
+ .cb-spin[data-tone="warning"] { --cb-spin-accent: var(--cb-tone-warning); }
1415
+ .cb-spin[data-tone="danger"] { --cb-spin-accent: var(--cb-tone-danger); }
1416
+ .cb-spin--sm { width: 0.875rem; height: 0.875rem; }
1417
+ .cb-spin--md { width: 1.25rem; height: 1.25rem; border-width: 2px; }
1418
+ .cb-spin--lg { width: 2rem; height: 2rem; border-width: 3px; }
1220
1419
  /* Reduced motion: it stops turning and reads as a ring — the surrounding text says "loading". */
1221
1420
  @media (prefers-reduced-motion: reduce) {
1222
- .cb-spinner { animation: none; opacity: 0.6; }
1421
+ .cb-spin { animation: none; opacity: 0.6; }
1223
1422
  }
1224
1423
 
1225
1424
  .cb-progress {
@@ -1306,6 +1505,57 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1306
1505
 
1307
1506
  .cb-skeleton-text { display: flex; flex-direction: column; gap: var(--cb-space-2); }
1308
1507
 
1508
+ /* feedback/tag.css */
1509
+ /* The look is Badge's, literally — the same `cb-badge` classes rather than a
1510
+ second set that would agree today and drift apart later. Only what a Tag can
1511
+ do that a Badge cannot lives here. */
1512
+ .cb-tag { max-width: 100%; }
1513
+
1514
+ .cb-tag--pressable {
1515
+ border-width: var(--cb-border-width);
1516
+ cursor: pointer;
1517
+ text-decoration: none;
1518
+ /* NOT `font: inherit`. The shorthand resets size and line-height too, so a
1519
+ pressable tag took the page's 16px and stood two pixels taller than the
1520
+ badge beside it — the size classes were being overruled by a reset meant
1521
+ only to undo the browser's button face. */
1522
+ font-family: inherit;
1523
+ font-weight: inherit;
1524
+ transition: border-color var(--cb-duration-fast) var(--cb-ease-standard),
1525
+ background-color var(--cb-duration-fast) var(--cb-ease-standard);
1526
+ }
1527
+ .cb-tag--pressable:hover { border-color: var(--cb-tone-brand); }
1528
+ .cb-tag--pressable:focus-visible {
1529
+ outline: var(--cb-focus-width) solid var(--cb-tone-brand);
1530
+ outline-offset: var(--cb-focus-offset);
1531
+ }
1532
+ .cb-tag--pressable[aria-pressed="true"] { border-color: var(--cb-tone-brand); }
1533
+
1534
+ .cb-tag__close {
1535
+ display: inline-flex;
1536
+ align-items: center;
1537
+ justify-content: center;
1538
+ margin-inline-start: var(--cb-space-1);
1539
+ margin-inline-end: calc(var(--cb-space-1) * -1);
1540
+ padding: 0;
1541
+ border: 0;
1542
+ border-radius: var(--cb-radius-full);
1543
+ background: none;
1544
+ color: inherit;
1545
+ opacity: 0.7;
1546
+ cursor: pointer;
1547
+ }
1548
+ .cb-tag__close:hover { opacity: 1; }
1549
+ .cb-tag__close:focus-visible {
1550
+ outline: var(--cb-focus-width) solid currentColor;
1551
+ outline-offset: 1px;
1552
+ opacity: 1;
1553
+ }
1554
+
1555
+ @media (prefers-reduced-motion: reduce) {
1556
+ .cb-tag--pressable { transition: none; }
1557
+ }
1558
+
1309
1559
  /* feedback/toast.css */
1310
1560
  .cb-toast__viewport {
1311
1561
  position: fixed;
@@ -1399,12 +1649,23 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1399
1649
  .cb-toast__action:focus-visible { outline: var(--cb-focus-width) solid var(--cb-toast-accent); outline-offset: var(--cb-focus-offset); }
1400
1650
 
1401
1651
  /* overlay/command-palette.css */
1652
+ .cb-palette__backdrop {
1653
+ position: fixed;
1654
+ inset: 0;
1655
+ z-index: var(--cb-z-command-backdrop);
1656
+ background: var(--cb-scrim);
1657
+ backdrop-filter: blur(2px);
1658
+ transition: opacity var(--cb-duration-base) var(--cb-ease-entrance);
1659
+ }
1660
+ .cb-palette__backdrop[data-starting-style],
1661
+ .cb-palette__backdrop[data-ending-style] { opacity: 0; }
1662
+
1402
1663
  .cb-palette {
1403
1664
  position: fixed;
1404
1665
  top: 12vh;
1405
1666
  left: 50%;
1406
1667
  translate: -50% 0;
1407
- z-index: calc(var(--cb-z-overlay) + 1);
1668
+ z-index: var(--cb-z-command);
1408
1669
  display: flex;
1409
1670
  flex-direction: column;
1410
1671
  width: min(34rem, calc(100vw - var(--cb-space-6)));
@@ -1422,6 +1683,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1422
1683
  .cb-palette[data-starting-style],
1423
1684
  .cb-palette[data-ending-style] { opacity: 0; translate: -50% -0.5rem; }
1424
1685
  @media (prefers-reduced-motion: reduce) {
1686
+ .cb-palette__backdrop { transition: opacity var(--cb-duration-fast) linear; }
1425
1687
  .cb-palette { transition: opacity var(--cb-duration-fast) linear; }
1426
1688
  .cb-palette[data-starting-style],
1427
1689
  .cb-palette[data-ending-style] { translate: -50% 0; }
@@ -1472,21 +1734,79 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1472
1734
  .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
1735
  .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
1736
 
1475
- /* overlay/dialog.css */
1476
- .cb-dialog__backdrop {
1737
+ /* overlay/drawer.css */
1738
+ .cb-drawer__backdrop {
1739
+ position: fixed;
1740
+ inset: 0;
1741
+ z-index: var(--cb-z-modal-backdrop);
1742
+ background: var(--cb-scrim);
1743
+ backdrop-filter: blur(2px);
1744
+ transition: opacity var(--cb-duration-base) var(--cb-ease-standard);
1745
+ }
1746
+ .cb-drawer__backdrop[data-starting-style],
1747
+ .cb-drawer__backdrop[data-ending-style] { opacity: 0; }
1748
+
1749
+ .cb-drawer {
1750
+ position: fixed;
1751
+ z-index: var(--cb-z-modal);
1752
+ inset-block: 0;
1753
+ inset-inline-end: 0;
1754
+ display: flex;
1755
+ flex-direction: column;
1756
+ gap: var(--cb-space-3);
1757
+ box-sizing: border-box;
1758
+ width: min(22rem, calc(100vw - var(--cb-space-6)));
1759
+ height: 100dvh;
1760
+ padding: var(--cb-space-5);
1761
+ overflow: hidden;
1762
+ background: var(--cb-surface-raised);
1763
+ color: var(--cb-fg);
1764
+ border-inline-start: var(--cb-border-width) solid var(--cb-border);
1765
+ border-radius: var(--cb-radius-lg) 0 0 var(--cb-radius-lg);
1766
+ box-shadow: var(--cb-shadow-lg);
1767
+ transform: translateX(0);
1768
+ transition: opacity var(--cb-duration-base) var(--cb-ease-entrance),
1769
+ transform var(--cb-duration-base) var(--cb-ease-entrance);
1770
+ }
1771
+ .cb-drawer[data-starting-style],
1772
+ .cb-drawer[data-ending-style] { opacity: 0; transform: translateX(100%); }
1773
+
1774
+ @media (prefers-reduced-motion: reduce) {
1775
+ .cb-drawer { transition: opacity var(--cb-duration-fast) linear; }
1776
+ .cb-drawer[data-starting-style],
1777
+ .cb-drawer[data-ending-style] { transform: translateX(0); }
1778
+ }
1779
+
1780
+ .cb-drawer__title { margin: 0; font-family: var(--cb-font-sans); font-size: var(--cb-text-lg); font-weight: var(--cb-weight-semibold); }
1781
+ .cb-drawer__description { margin: 0; font-family: var(--cb-font-sans); font-size: var(--cb-text-sm); color: var(--cb-fg-muted); }
1782
+ /* Same scrollbar as the Modal's body, same answer — see modal.css. */
1783
+ .cb-drawer__body {
1784
+ min-height: 0;
1785
+ flex: 1;
1786
+ overflow-y: auto;
1787
+ scrollbar-gutter: stable;
1788
+ margin-inline: calc(-1 * var(--cb-space-5));
1789
+ padding-inline: var(--cb-space-5);
1790
+ font-family: var(--cb-font-sans);
1791
+ font-size: var(--cb-text-sm);
1792
+ }
1793
+ .cb-drawer__footer { display: flex; justify-content: flex-end; gap: var(--cb-space-2); }
1794
+
1795
+ /* overlay/modal.css */
1796
+ .cb-modal__backdrop {
1477
1797
  position: fixed;
1478
1798
  inset: 0;
1479
- z-index: var(--cb-z-overlay);
1799
+ z-index: var(--cb-z-modal-backdrop);
1480
1800
  background: var(--cb-scrim);
1481
1801
  backdrop-filter: blur(2px);
1482
1802
  transition: opacity var(--cb-duration-base) var(--cb-ease-standard);
1483
1803
  }
1484
- .cb-dialog__backdrop[data-starting-style],
1485
- .cb-dialog__backdrop[data-ending-style] { opacity: 0; }
1804
+ .cb-modal__backdrop[data-starting-style],
1805
+ .cb-modal__backdrop[data-ending-style] { opacity: 0; }
1486
1806
 
1487
- .cb-dialog {
1807
+ .cb-modal {
1488
1808
  position: fixed;
1489
- z-index: calc(var(--cb-z-overlay) + 1);
1809
+ z-index: var(--cb-z-modal);
1490
1810
  display: flex;
1491
1811
  flex-direction: column;
1492
1812
  gap: var(--cb-space-3);
@@ -1498,36 +1818,60 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1498
1818
  box-shadow: var(--cb-shadow-lg);
1499
1819
  padding: var(--cb-space-5);
1500
1820
  width: calc(100vw - var(--cb-space-6));
1821
+ max-height: calc(100dvh - var(--cb-space-6));
1501
1822
  transition: opacity var(--cb-duration-base) var(--cb-ease-entrance),
1502
1823
  transform var(--cb-duration-base) var(--cb-ease-entrance);
1503
1824
  }
1504
- .cb-dialog--sm { max-width: 24rem; }
1505
- .cb-dialog--md { max-width: 32rem; }
1506
- .cb-dialog--lg { max-width: 46rem; }
1825
+ .cb-modal--sm { max-width: 24rem; }
1826
+ .cb-modal--md { max-width: 32rem; }
1827
+ .cb-modal--lg { max-width: 46rem; }
1507
1828
 
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); }
1829
+ .cb-modal--center { top: 50%; left: 50%; transform: translate(-50%, -50%) scale(1); }
1830
+ .cb-modal--center[data-starting-style],
1831
+ .cb-modal--center[data-ending-style] { opacity: 0; transform: translate(-50%, -48%) scale(0.96); }
1511
1832
 
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); }
1833
+ .cb-modal--end { inset-inline-end: var(--cb-space-4); inset-block-end: var(--cb-space-4); transform: translateY(0); }
1834
+ .cb-modal--end[data-starting-style],
1835
+ .cb-modal--end[data-ending-style] { opacity: 0; transform: translateY(12px); }
1515
1836
 
1516
1837
  /* Reduced motion: the dialog still fades so its arrival is not invisible, but it stops moving. */
1517
1838
  @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); }
1839
+ .cb-modal { transition: opacity var(--cb-duration-fast) linear; }
1840
+ .cb-modal--center[data-starting-style],
1841
+ .cb-modal--center[data-ending-style] { transform: translate(-50%, -50%) scale(1); }
1842
+ .cb-modal--end[data-starting-style],
1843
+ .cb-modal--end[data-ending-style] { transform: translateY(0); }
1844
+ }
1845
+
1846
+ .cb-modal__title { margin: 0; font-family: var(--cb-font-sans); font-size: var(--cb-text-lg); font-weight: var(--cb-weight-semibold); }
1847
+ .cb-modal__description { margin: 0; font-family: var(--cb-font-sans); font-size: var(--cb-text-sm); color: var(--cb-fg-muted); }
1848
+ /* The body is the only scrolling part of the dialog, and a scroller draws its
1849
+ bar at its own inline edge — which, with the body running to the content edge,
1850
+ is the right edge of every field inside it. Platforms differ in how that looks
1851
+ and neither way is acceptable: macOS and Android float an overlay bar over the
1852
+ field, Windows reserves real width and squeezes it.
1853
+
1854
+ So both halves are needed. `scrollbar-gutter: stable` keeps the reserved bar
1855
+ out of the content box — always, not only while the content overflows, so a
1856
+ field appearing does not shift the rest sideways. The bleed-and-pad puts a
1857
+ space-5 margin between the content and the edge where an overlay bar is drawn,
1858
+ and hands the width back to the dialog so the padding costs nothing. It also
1859
+ stops a focused field's ring being clipped at the scroller's edge. */
1860
+ .cb-modal__body {
1861
+ min-height: 0;
1862
+ overflow-y: auto;
1863
+ scrollbar-gutter: stable;
1864
+ margin-inline: calc(-1 * var(--cb-space-5));
1865
+ padding-inline: var(--cb-space-5);
1866
+ font-family: var(--cb-font-sans);
1867
+ font-size: var(--cb-text-sm);
1523
1868
  }
1524
-
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); }
1869
+ .cb-modal__footer { flex: none; display: flex; justify-content: flex-end; gap: var(--cb-space-2); margin-top: var(--cb-space-2); }
1529
1870
 
1530
1871
  /* overlay/popover.css */
1872
+ .cb-popover-positioner { z-index: var(--cb-z-popover); }
1873
+ .cb-tooltip-positioner { z-index: var(--cb-z-tooltip); }
1874
+
1531
1875
  .cb-popover {
1532
1876
  box-sizing: border-box;
1533
1877
  max-width: 22rem;
@@ -1539,7 +1883,6 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1539
1883
  padding: var(--cb-space-4);
1540
1884
  font-family: var(--cb-font-sans);
1541
1885
  font-size: var(--cb-text-sm);
1542
- z-index: var(--cb-z-popover);
1543
1886
  transition: opacity var(--cb-duration-fast) var(--cb-ease-entrance),
1544
1887
  transform var(--cb-duration-fast) var(--cb-ease-entrance);
1545
1888
  }
@@ -1551,33 +1894,42 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1551
1894
  .cb-popover[data-ending-style] { transform: none; }
1552
1895
  }
1553
1896
 
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 {
1897
+ /* Base UI positions this wrapper along the anchor edge. The SVG fill overlaps the popup edge by
1898
+ one pixel, hiding its border below the tail; only the two outward edges receive a stroke. */
1899
+ .cb-popover__arrow,
1900
+ .cb-tooltip__arrow {
1901
+ --cb-arrow-bg: var(--cb-surface-raised);
1902
+ --cb-arrow-border: var(--cb-border);
1559
1903
  position: absolute;
1560
- width: 18px;
1561
- height: 9px;
1904
+ display: block;
1905
+ width: 16px;
1906
+ height: 8px;
1907
+ overflow: visible;
1562
1908
  pointer-events: none;
1563
1909
  }
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; }
1910
+ .cb-popover__arrow[data-side="bottom"],
1911
+ .cb-tooltip__arrow[data-side="bottom"] { top: -7px; }
1912
+ .cb-popover__arrow[data-side="top"],
1913
+ .cb-tooltip__arrow[data-side="top"] { bottom: -7px; rotate: 180deg; }
1914
+ .cb-popover__arrow[data-side="left"],
1915
+ .cb-tooltip__arrow[data-side="left"] { right: -11px; rotate: 90deg; }
1916
+ .cb-popover__arrow[data-side="right"],
1917
+ .cb-tooltip__arrow[data-side="right"] { left: -11px; rotate: -90deg; }
1568
1918
 
1569
1919
  .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;
1920
+ display: block;
1921
+ width: 100%;
1922
+ height: 100%;
1923
+ overflow: visible;
1924
+ }
1925
+ .cb-arrow__fill { fill: var(--cb-arrow-bg); }
1926
+ .cb-arrow__edge {
1927
+ fill: none;
1928
+ stroke: var(--cb-arrow-border);
1929
+ stroke-width: var(--cb-border-width);
1930
+ stroke-linecap: round;
1931
+ stroke-linejoin: round;
1932
+ vector-effect: non-scaling-stroke;
1581
1933
  }
1582
1934
 
1583
1935
  .cb-tooltip {
@@ -1587,9 +1939,12 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1587
1939
  font-size: var(--cb-text-xs);
1588
1940
  padding: var(--cb-space-1) var(--cb-space-2);
1589
1941
  border-radius: var(--cb-radius-sm);
1590
- z-index: var(--cb-z-popover);
1591
1942
  transition: opacity var(--cb-duration-fast) var(--cb-ease-standard);
1592
1943
  }
1944
+ .cb-tooltip__arrow {
1945
+ --cb-arrow-bg: var(--cb-fg);
1946
+ --cb-arrow-border: var(--cb-fg);
1947
+ }
1593
1948
  .cb-tooltip[data-starting-style],
1594
1949
  .cb-tooltip[data-ending-style] { opacity: 0; }
1595
1950
 
@@ -1730,7 +2085,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
1730
2085
  color: var(--cb-fg);
1731
2086
  }
1732
2087
 
1733
- /* data/stat-card.css */
2088
+ /* data/statistic.css */
1734
2089
  .cb-stat { display: flex; flex-direction: column; gap: var(--cb-space-3); }
1735
2090
  .cb-stat__head { display: flex; align-items: center; justify-content: space-between; gap: var(--cb-space-2); }
1736
2091
  .cb-stat__icon { display: inline-flex; color: var(--cb-surface-accent); }
@@ -2104,9 +2459,14 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
2104
2459
  }
2105
2460
 
2106
2461
  /* nav/nav.css */
2462
+ .cb-menu-positioner { z-index: var(--cb-z-dropdown); }
2463
+
2107
2464
  .cb-menu {
2108
2465
  box-sizing: border-box;
2109
2466
  min-width: 14rem;
2467
+ max-height: min(calc(100dvh - var(--cb-space-6)), var(--available-height));
2468
+ overflow-y: auto;
2469
+ overscroll-behavior: contain;
2110
2470
  padding: var(--cb-space-1);
2111
2471
  background: var(--cb-surface-raised);
2112
2472
  border: var(--cb-border-width) solid var(--cb-border);
@@ -2114,7 +2474,6 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
2114
2474
  box-shadow: var(--cb-shadow-md);
2115
2475
  font-family: var(--cb-font-sans);
2116
2476
  font-size: var(--cb-text-sm);
2117
- z-index: var(--cb-z-popover);
2118
2477
  transition: opacity var(--cb-duration-fast) var(--cb-ease-entrance),
2119
2478
  transform var(--cb-duration-fast) var(--cb-ease-entrance);
2120
2479
  }
@@ -2181,11 +2540,11 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
2181
2540
  .cb-crumbs__current { color: var(--cb-fg); font-weight: var(--cb-weight-medium); }
2182
2541
  .cb-crumbs__separator { color: var(--cb-fg-subtle); flex: none; }
2183
2542
 
2184
- .cb-stepper { list-style: none; margin: 0; padding: 0; font-family: var(--cb-font-sans); display: flex; }
2185
- .cb-stepper--horizontal { flex-direction: row; gap: var(--cb-space-5); flex-wrap: wrap; }
2186
- .cb-stepper--vertical { flex-direction: column; gap: var(--cb-space-4); }
2187
- .cb-stepper__step { margin: 0; display: flex; align-items: flex-start; gap: var(--cb-space-3); }
2188
- .cb-stepper__marker {
2543
+ .cb-steps { list-style: none; margin: 0; padding: 0; font-family: var(--cb-font-sans); display: flex; }
2544
+ .cb-steps--horizontal { flex-direction: row; gap: var(--cb-space-5); flex-wrap: wrap; }
2545
+ .cb-steps--vertical { flex-direction: column; gap: var(--cb-space-4); }
2546
+ .cb-steps__step { margin: 0; display: flex; align-items: flex-start; gap: var(--cb-space-3); }
2547
+ .cb-steps__marker {
2189
2548
  flex: none;
2190
2549
  display: inline-flex;
2191
2550
  align-items: center;
@@ -2199,20 +2558,20 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
2199
2558
  color: var(--cb-fg-subtle);
2200
2559
  background: var(--cb-surface);
2201
2560
  }
2202
- .cb-stepper__step[data-state="done"] .cb-stepper__marker {
2561
+ .cb-steps__step[data-state="done"] .cb-steps__marker {
2203
2562
  background: var(--cb-tone-success);
2204
2563
  border-color: var(--cb-tone-success);
2205
2564
  color: var(--cb-fg-on-brand);
2206
2565
  }
2207
- .cb-stepper__step[data-state="current"] .cb-stepper__marker {
2566
+ .cb-steps__step[data-state="current"] .cb-steps__marker {
2208
2567
  border-color: var(--cb-tone-brand);
2209
2568
  color: var(--cb-tone-brand);
2210
2569
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--cb-tone-brand) 16%, transparent);
2211
2570
  }
2212
- .cb-stepper__text { display: flex; flex-direction: column; gap: 0.125rem; }
2213
- .cb-stepper__label { font-size: var(--cb-text-sm); font-weight: var(--cb-weight-medium); color: var(--cb-fg-muted); }
2214
- .cb-stepper__step[data-state="current"] .cb-stepper__label { color: var(--cb-fg); }
2215
- .cb-stepper__description { font-size: var(--cb-text-xs); color: var(--cb-fg-subtle); }
2571
+ .cb-steps__text { display: flex; flex-direction: column; gap: 0.125rem; }
2572
+ .cb-steps__label { font-size: var(--cb-text-sm); font-weight: var(--cb-weight-medium); color: var(--cb-fg-muted); }
2573
+ .cb-steps__step[data-state="current"] .cb-steps__label { color: var(--cb-fg); }
2574
+ .cb-steps__description { font-size: var(--cb-text-xs); color: var(--cb-fg-subtle); }
2216
2575
 
2217
2576
  .cb-sidebar {
2218
2577
  display: flex;
@@ -2323,7 +2682,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
2323
2682
  .cb-sidebar__item--nested { font-size: var(--cb-text-sm); }
2324
2683
 
2325
2684
  .cb-sidebar__flyout-trigger { display: block; width: 100%; }
2326
- .cb-sidebar__flyout-positioner { z-index: var(--cb-z-popover); }
2685
+ .cb-sidebar__flyout-positioner { z-index: var(--cb-z-dropdown); }
2327
2686
 
2328
2687
  /* Collapsed, this is where the labels live. */
2329
2688
  .cb-sidebar__flyout {
@@ -2473,7 +2832,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
2473
2832
  costs no extra element, and the shadow colour is the same token the dialog backdrop uses. */
2474
2833
  .cb-spotlight {
2475
2834
  position: fixed;
2476
- z-index: var(--cb-z-coachmark);
2835
+ z-index: var(--cb-z-coachmark-spotlight);
2477
2836
  left: var(--cb-spotlight-x);
2478
2837
  top: var(--cb-spotlight-y);
2479
2838
  width: var(--cb-spotlight-w);
@@ -2493,7 +2852,7 @@ html, body { background: var(--cb-bg); color: var(--cb-fg); }
2493
2852
 
2494
2853
  /* The positioner is what the portal places, so the stacking order has to live here; a z-index
2495
2854
  on the popup alone loses to the spotlight's own layer. */
2496
- .cb-coachmark__positioner { z-index: calc(var(--cb-z-coachmark) + 1); }
2855
+ .cb-coachmark__positioner { z-index: var(--cb-z-coachmark); }
2497
2856
 
2498
2857
  .cb-coachmark {
2499
2858
  box-sizing: border-box;