@redseed/redseed-ui-tailwindcss 7.64.0 → 7.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/components/activity_feed.css +1 -1
  2. package/components/badge.css +4 -4
  3. package/components/badge_group.css +8 -8
  4. package/components/breadcrumbs.css +2 -2
  5. package/components/button_group_item.css +2 -2
  6. package/components/button_slot.css +7 -7
  7. package/components/card_group.css +3 -3
  8. package/components/column_picker.css +3 -3
  9. package/components/comment.css +16 -16
  10. package/components/comments.css +1 -1
  11. package/components/drawer.css +5 -5
  12. package/components/dropdown_menu.css +1 -1
  13. package/components/dropdown_option.css +1 -1
  14. package/components/empty.css +3 -3
  15. package/components/feed_item.css +4 -4
  16. package/components/form_field_checkbox.css +1 -1
  17. package/components/form_field_combobox.css +7 -7
  18. package/components/form_field_date_range.css +9 -9
  19. package/components/form_field_radio_group.css +2 -2
  20. package/components/form_field_select.css +3 -3
  21. package/components/form_field_slot.css +5 -5
  22. package/components/form_field_text.css +2 -2
  23. package/components/form_field_textarea.css +1 -1
  24. package/components/form_field_tree_select.css +9 -9
  25. package/components/form_field_uploader.css +3 -3
  26. package/components/form_fieldset.css +5 -5
  27. package/components/form_slot.css +6 -6
  28. package/components/link_slot.css +1 -1
  29. package/components/linked_list_item.css +1 -1
  30. package/components/list.css +3 -3
  31. package/components/list_control.css +2 -2
  32. package/components/list_item.css +6 -6
  33. package/components/message_box.css +6 -6
  34. package/components/meta_info.css +1 -1
  35. package/components/metric_card.css +213 -2
  36. package/components/modal.css +8 -8
  37. package/components/pagination.css +4 -4
  38. package/components/pagination_item.css +1 -1
  39. package/components/progress_bar.css +1 -1
  40. package/components/progress_tracker.css +3 -3
  41. package/components/progress_tracker_step.css +3 -3
  42. package/components/radio_card.css +3 -3
  43. package/components/read_more.css +1 -1
  44. package/components/record_count.css +1 -1
  45. package/components/section_header.css +1 -1
  46. package/components/section_slider.css +3 -3
  47. package/components/switcher.css +3 -3
  48. package/components/switcher_item.css +2 -2
  49. package/components/tab_slider_item.css +1 -1
  50. package/components/table.css +1 -1
  51. package/components/td.css +1 -1
  52. package/components/td_user.css +1 -1
  53. package/components/th.css +4 -4
  54. package/components/toggle.css +2 -2
  55. package/components/tooltip.css +1 -1
  56. package/components/two_column_layout.css +3 -3
  57. package/package.json +1 -1
  58. package/tokens.css +18 -3
@@ -8,7 +8,7 @@
8
8
 
9
9
  .rsui-form-field-tree-select__trigger {
10
10
  @apply block w-full border ring-0 bg-none cursor-pointer appearance-none transition;
11
- @apply py-3 px-5 truncate text-left;
11
+ @apply py-space-sm px-5 truncate text-left;
12
12
  @apply text-base rounded-lg outline-hidden focus:outline-hidden;
13
13
  @apply bg-background-primary text-text-primary placeholder-text-secondary border-border-primary;
14
14
  @apply focus:border-border-primary;
@@ -21,31 +21,31 @@
21
21
  }
22
22
 
23
23
  .rsui-form-field-tree-select__modal-header {
24
- @apply flex flex-col gap-y-2 xs:gap-y-0 xs:flex-row xs:items-center xs:justify-between;
24
+ @apply flex flex-col gap-y-space-xs xs:gap-y-0 xs:flex-row xs:items-center xs:justify-between;
25
25
  }
26
26
 
27
27
  .rsui-form-field-tree-select__search {
28
- @apply mb-4;
28
+ @apply mb-space-md;
29
29
  }
30
30
 
31
31
  .rsui-form-field-tree-select__tree {
32
- @apply max-h-96 flex flex-col gap-3 overflow-y-auto pt-1 pl-1;
32
+ @apply max-h-96 flex flex-col gap-space-sm overflow-y-auto pt-space-xxs pl-space-xxs;
33
33
  }
34
34
 
35
35
  .rsui-form-field-tree-select-children {
36
- @apply flex flex-col gap-3 ml-4 sm:ml-6 md:ml-7;
36
+ @apply flex flex-col gap-space-sm ml-space-md sm:ml-space-lg md:ml-7;
37
37
  }
38
38
 
39
39
  .rsui-form-field-tree-select-item {
40
- @apply flex items-start gap-x-1 sm:gap-x-1 md:gap-x-2;
40
+ @apply flex items-start gap-x-space-xxs sm:gap-x-space-xxs md:gap-x-space-xs;
41
41
  }
42
42
 
43
43
  .rsui-form-field-tree-select-item__controls {
44
- @apply flex items-start gap-x-0 sm:gap-x-1 md:gap-x-2;
44
+ @apply flex items-start gap-x-0 sm:gap-x-space-xxs md:gap-x-space-xs;
45
45
  }
46
46
 
47
47
  .rsui-form-field-tree-select-item__opener {
48
- @apply pt-1 md:pt-0.5 w-5 text-text-secondary;
48
+ @apply pt-space-xxs md:pt-0.5 w-5 text-text-secondary;
49
49
  }
50
50
 
51
51
  .rsui-form-field-tree-select-item__opener--clickable {
@@ -61,7 +61,7 @@
61
61
  }
62
62
 
63
63
  .rsui-form-field-tree-select-item__name {
64
- @apply font-medium select-none cursor-pointer text-xs sm:text-sm md:text-base pt-1 sm:pt-0.5 md:pt-0;
64
+ @apply font-medium select-none cursor-pointer text-xs sm:text-sm md:text-base pt-space-xxs sm:pt-0.5 md:pt-0;
65
65
  }
66
66
 
67
67
  .rsui-form-field-tree-select-item__name--searched {
@@ -2,8 +2,8 @@
2
2
  @apply w-fit max-w-80 bg-background-secondary;
3
3
  @apply relative select-none;
4
4
  @apply rounded-lg border border-border-primary border-dashed;
5
- @apply flex flex-nowrap justify-center items-center gap-4;
6
- @apply px-4 py-2.5;
5
+ @apply flex flex-nowrap justify-center items-center gap-space-md;
6
+ @apply px-space-md py-2.5;
7
7
  }
8
8
 
9
9
  .rsui-form-field-uploader__container--success {
@@ -11,7 +11,7 @@
11
11
  }
12
12
 
13
13
  .rsui-form-field-uploader__content {
14
- @apply relative flex flex-nowrap justify-center items-center gap-4;
14
+ @apply relative flex flex-nowrap justify-center items-center gap-space-md;
15
15
  @apply text-text-primary;
16
16
  }
17
17
 
@@ -3,11 +3,11 @@
3
3
  }
4
4
 
5
5
  .rsui-form-fieldset__fields {
6
- @apply flex flex-col gap-3;
6
+ @apply flex flex-col gap-space-sm;
7
7
  }
8
8
 
9
9
  .rsui-form-fieldset__fields--padded {
10
- @apply mt-3 px-2;
10
+ @apply mt-space-sm px-space-xs;
11
11
  }
12
12
 
13
13
  .rsui-form-fieldset__fields .rsui-form-field-slot label {
@@ -34,14 +34,14 @@
34
34
  .rsui-form-fieldset__fields .rsui-form-field-slot--compact input[type='time'],
35
35
  .rsui-form-fieldset__fields .rsui-form-field-slot--compact input[type='url'],
36
36
  .rsui-form-fieldset__fields .rsui-form-field-slot--compact input[type='week'] {
37
- @apply py-1.5 px-2;
37
+ @apply py-1.5 px-space-xs;
38
38
  }
39
39
 
40
40
  .rsui-form-fieldset__fields .rsui-form-field-slot--compact select {
41
- @apply py-1.5 pl-3 pr-12 text-sm;
41
+ @apply py-1.5 pl-space-sm pr-space-2xl text-sm;
42
42
  }
43
43
 
44
44
  .rsui-form-fieldset__fields .rsui-form-field-select__icon { @apply right-2; }
45
45
  .rsui-form-fieldset__fields .rsui-form-field-select__icon svg { @apply size-5; }
46
- .rsui-form-fieldset__fields .rsui-form-field-select__option { @apply py-3 px-2; }
46
+ .rsui-form-fieldset__fields .rsui-form-field-select__option { @apply py-space-sm px-space-xs; }
47
47
  .rsui-form-fieldset__fields .rsui-form-field-select__option-label { @apply text-sm; }
@@ -5,11 +5,11 @@
5
5
  }
6
6
 
7
7
  .rsui-form-slot__image {
8
- @apply mb-8;
8
+ @apply mb-space-xl;
9
9
  }
10
10
 
11
11
  .rsui-form-slot__social-divider {
12
- @apply w-full relative flex justify-center mb-2;
12
+ @apply w-full relative flex justify-center mb-space-xs;
13
13
  }
14
14
 
15
15
  .rsui-form-slot__social-divider-line {
@@ -17,18 +17,18 @@
17
17
  }
18
18
 
19
19
  .rsui-form-slot__social-divider-or {
20
- @apply relative z-1 bg-background-primary px-6 text-text-primary;
20
+ @apply relative z-1 bg-background-primary px-space-lg text-text-primary;
21
21
  }
22
22
 
23
23
  .rsui-form-slot__content {
24
- @apply w-full sm:max-w-md px-10 xs:px-12 md:px-16 py-10 bg-background-primary shadow-md overflow-hidden rounded-lg;
24
+ @apply w-full sm:max-w-md px-10 xs:px-space-2xl md:px-space-3xl py-10 bg-background-primary shadow-md overflow-hidden rounded-lg;
25
25
  }
26
26
 
27
27
  .rsui-form-slot__content > * {
28
- @apply first:mt-0 mt-4;
28
+ @apply first:mt-0 mt-space-md;
29
29
  }
30
30
 
31
31
  .rsui-form-slot__content form > * {
32
- @apply first:mt-0 mt-4;
32
+ @apply first:mt-0 mt-space-md;
33
33
  }
34
34
 
@@ -7,7 +7,7 @@
7
7
  @apply focus-visible:ring-offset-2 focus-visible:ring-2 focus-visible:ring-border-primary;
8
8
 
9
9
  /* default text styles */
10
- @apply font-medium text-text-primary gap-2;
10
+ @apply font-medium text-text-primary gap-space-xs;
11
11
  }
12
12
 
13
13
  .rsui-link-slot--disabled {
@@ -1,5 +1,5 @@
1
1
  .rsui-linked-list-item {
2
- @apply pl-9 pr-3 pt-0 pb-2 relative text-text-disabled;
2
+ @apply pl-9 pr-space-sm pt-0 pb-space-xs relative text-text-disabled;
3
3
  }
4
4
 
5
5
  .rsui-linked-list-item::before {
@@ -1,12 +1,12 @@
1
1
  .rsui-list__items {
2
- @apply mt-6;
2
+ @apply mt-space-lg;
3
3
  }
4
4
 
5
5
  .rsui-list__empty {
6
- @apply mt-6;
6
+ @apply mt-space-lg;
7
7
  }
8
8
 
9
9
  .rsui-list__control-bottom {
10
- @apply mt-3;
10
+ @apply mt-space-sm;
11
11
  }
12
12
 
@@ -1,5 +1,5 @@
1
1
  .rsui-list-control {
2
- @apply flex flex-wrap justify-between gap-x-2 gap-y-3;
2
+ @apply flex flex-wrap justify-between gap-x-space-xs gap-y-space-sm;
3
3
  }
4
4
 
5
5
  .rsui-list-control--sm .rsui-list-control__filters {
@@ -49,7 +49,7 @@
49
49
  }
50
50
 
51
51
  .rsui-list-control__filters {
52
- @apply grid gap-2;
52
+ @apply grid gap-space-xs;
53
53
  }
54
54
 
55
55
  .rsui-list-control__sort {
@@ -3,7 +3,7 @@
3
3
  cramped against its neighbours, which is the note that came back from laying these
4
4
  out on a real page. Horizontal padding is unchanged. Half-steps are already used
5
5
  across the button sizes, so 3.5 is in keeping. */
6
- @apply relative flex flex-col rounded-xl px-3 py-3.5 overflow-hidden;
6
+ @apply relative flex flex-col rounded-xl px-space-sm py-3.5 overflow-hidden;
7
7
  }
8
8
 
9
9
  .rsui-list-item--clickable {
@@ -14,7 +14,7 @@
14
14
  }
15
15
 
16
16
  .rsui-list-item--avatar {
17
- @apply pl-12 lg:pl-16 *:pl-2 *:lg:pl-1;
17
+ @apply pl-space-2xl lg:pl-space-3xl *:pl-space-xs *:lg:pl-space-xxs;
18
18
  }
19
19
 
20
20
  .rsui-list-item--wide .rsui-list-item__status {
@@ -22,7 +22,7 @@
22
22
  }
23
23
 
24
24
  .rsui-list-item__header {
25
- @apply relative z-1 flex flex-wrap items-center gap-2 pointer-events-none;
25
+ @apply relative z-1 flex flex-wrap items-center gap-space-xs pointer-events-none;
26
26
  }
27
27
 
28
28
  .rsui-list-item__bg {
@@ -39,7 +39,7 @@
39
39
  }
40
40
 
41
41
  .rsui-list-item__avatar {
42
- @apply absolute top-0 ml-1 -left-10 lg:-left-14;
42
+ @apply absolute top-0 ml-space-xxs -left-10 lg:-left-14;
43
43
  @apply aspect-square overflow-hidden size-9 lg:size-12;
44
44
  }
45
45
 
@@ -70,7 +70,7 @@
70
70
  }
71
71
 
72
72
  .rsui-list-item__meta {
73
- @apply mt-2 flex flex-wrap gap-x-4 gap-y-2;
73
+ @apply mt-space-xs flex flex-wrap gap-x-space-md gap-y-space-xs;
74
74
  }
75
75
 
76
76
  /* Leading icon. Inline, unlike .rsui-list-item__avatar above, which hangs outside the row
@@ -133,5 +133,5 @@
133
133
  }
134
134
 
135
135
  .rsui-list-item__description {
136
- @apply line-clamp-2 mt-2 text-base;
136
+ @apply line-clamp-2 mt-space-xs text-base;
137
137
  }
@@ -4,7 +4,7 @@
4
4
  * against a tall body.
5
5
  */
6
6
  .rsui-message-box {
7
- @apply border rounded-lg overflow-hidden p-4 flex items-start gap-x-3;
7
+ @apply border rounded-lg overflow-hidden p-space-md flex items-start gap-x-space-sm;
8
8
  }
9
9
 
10
10
  /*
@@ -12,7 +12,7 @@
12
12
  * box with no icon and no actions lays out exactly as it did before.
13
13
  */
14
14
  .rsui-message-box__main {
15
- @apply flex-1 flex flex-col gap-y-3;
15
+ @apply flex-1 flex flex-col gap-y-space-sm;
16
16
  }
17
17
 
18
18
  /*
@@ -31,7 +31,7 @@
31
31
  * rhythm. Inert at `sm:flex-nowrap`, where there is only ever one line.
32
32
  */
33
33
  .rsui-message-box--with-icon {
34
- @apply flex-wrap gap-y-3 sm:flex-nowrap;
34
+ @apply flex-wrap gap-y-space-sm sm:flex-nowrap;
35
35
  }
36
36
 
37
37
  .rsui-message-box--with-icon .rsui-message-box__close {
@@ -78,11 +78,11 @@
78
78
  }
79
79
 
80
80
  .rsui-message-box__head {
81
- @apply flex items-center gap-x-2;
81
+ @apply flex items-center gap-x-space-xs;
82
82
  }
83
83
 
84
84
  .rsui-message-box__content {
85
- @apply flex items-start gap-x-2;
85
+ @apply flex items-start gap-x-space-xs;
86
86
  }
87
87
 
88
88
  /*
@@ -104,7 +104,7 @@
104
104
  * puts beside it, which is the only hierarchy this row needs.
105
105
  */
106
106
  .rsui-message-box__actions {
107
- @apply flex flex-wrap items-center gap-x-4 gap-y-2 pt-1;
107
+ @apply flex flex-wrap items-center gap-x-space-md gap-y-space-xs pt-space-xxs;
108
108
  }
109
109
 
110
110
  .rsui-message-box__dismiss,
@@ -13,7 +13,7 @@
13
13
  caption floor. Fix the size and 8px becomes correct; that belongs with the MetaInfo
14
14
  size ramp in #316, not here. Until then this is the size the component ships at, and
15
15
  the gap has to suit it. */
16
- @apply flex flex-col gap-y-1;
16
+ @apply flex flex-col gap-y-space-xxs;
17
17
  }
18
18
 
19
19
  /* gap-x rather than space-x: space-* works by adding margins to children, which is the
@@ -21,8 +21,17 @@
21
21
  @apply shrink-0 grow-0 size-10;
22
22
  }
23
23
 
24
- .rsui-metric-card__heading .rsui-metric-card__label {
25
- @apply flex-1 min-w-0;
24
+ /* The flexing text column, not the label itself — the label now shares it with
25
+ the optional microcopy, and the two stack. Kept on the wrapper so a tile with
26
+ no description renders exactly as it did before.
27
+
28
+ 8px, not the 4px TIGHT PAIR value, even though label -> description is the same
29
+ shape as the three pairs that do take it. CardSpacing.test.js caps the family at
30
+ exactly three justified exceptions and argues that a fourth means the brand scale
31
+ is missing a step rather than the family needing another escape hatch (#317).
32
+ That argument belongs on #317, not smuggled in here — so this sits on the rhythm. */
33
+ .rsui-metric-card__heading-text {
34
+ @apply flex-1 min-w-0 flex flex-col gap-space-xs;
26
35
  }
27
36
 
28
37
  .rsui-metric-card__number {
@@ -39,6 +48,167 @@
39
48
  @apply text-text-brand;
40
49
  }
41
50
 
51
+ /* Microcopy. Explains what the metric measures, so a number does not need a
52
+ support doc beside it. In the flow rather than behind a Tooltip on an info
53
+ icon: hover hides the explanation from touch and from keyboard, and this is
54
+ the sentence that makes the tile legible at all.
55
+
56
+ The cost is vertical rhythm — in a CardGroup row of tiles, uneven copy lengths
57
+ make tiles ragged unless the group equalises height. Muted and a step smaller
58
+ than the label, wrapping freely with no truncation, because a clipped
59
+ explanation is worse than a tall tile. */
60
+ .rsui-metric-card__description {
61
+ @apply text-text-tertiary text-sm font-normal;
62
+ }
63
+
64
+ /* ── The figure block ─────────────────────────────────────────────────────────
65
+ The number and the comparison that qualifies it, as one box. It exists so the
66
+ two placements are a single property on one wrapper rather than two template
67
+ branches: everything else about the comparison — the a11y wiring, the
68
+ sentiment modifiers, the tonal overrides — stays placement-agnostic.
69
+
70
+ The 8px gap is not arbitrary: before this wrapper existed the two sat directly
71
+ in MetricCard's FlexContainer, whose default gap is space-xs. Matching it is
72
+ what makes the default `below` placement render byte-for-byte as it did. */
73
+ .rsui-metric-card__figure {
74
+ @apply flex flex-col gap-space-xs;
75
+ }
76
+
77
+ /* ── Inline placement ─────────────────────────────────────────────────────────
78
+ The reference shape: `2,000 ↗100% vs last month` on one row. Worth the option
79
+ because it buys back a line of vertical height, which is the difference between
80
+ a dashboard row of these fitting above the fold and not.
81
+
82
+ Centred, not baseline-aligned. The comparison is a 20px line box beside a 36px
83
+ one, and sitting them on a shared baseline hangs the small text off the bottom
84
+ of the figure — it reads as a footnote that slipped up a line rather than as
85
+ part of the same object. Centring is what the reference shows.
86
+
87
+ 16px, and deliberately NOT the reference's 12px. The brand runs an 8px rhythm --
88
+ 8, 16, 24, 32 -- and CardSpacing.test.js bans 12 outright across the card family
89
+ on the grounds that mixing it in is what made the family read untidy. So the
90
+ reference value rounds up onto the scale rather than earning a fourth exception.
91
+ Up rather than down because the gap is wider than the 8px stack gap on purpose:
92
+ stacked, the two are on separate lines and the leading already separates them;
93
+ side by side they compete for one reading line, and 8px there reads as a wide
94
+ space inside a single phrase rather than as a break between two things.
95
+
96
+ It wraps. A metric tile has no width contract — it is whatever the dashboard
97
+ grid hands it — and `175.95% Increase vs last month` beside a five-digit figure
98
+ will not fit a narrow column. Wrapping degrades to the `below` shape, which is
99
+ the right failure: the alternative is an overflowing row or a truncated
100
+ comparison, and a clipped percentage is a wrong number, not a terse one. */
101
+ .rsui-metric-card__figure--inline {
102
+ @apply flex-row flex-wrap items-center gap-space-md;
103
+ }
104
+
105
+ /* ── Previous-period comparison ───────────────────────────────────────────────
106
+ Its own element, not a Badge or a Pill. Both of those are pill-shaped chips
107
+ with a border and a filled ground, which under a text-3xl number reads as a
108
+ second object competing with the figure rather than as a caption qualifying
109
+ it. This is a line of text with an arrow, which is what the reference shows.
110
+
111
+ The arrow is DECORATIVE (aria-hidden in the template, per the pattern __icon
112
+ already uses) — the meaning is carried by the text the consumer passes in.
113
+
114
+ WCAG 1.4.1 is satisfied on two separate axes, which is worth stating plainly
115
+ because conflating them is how the first pass got it wrong:
116
+
117
+ DIRECTION the arrow plus the consumer's word ("Increase" / "Decrease").
118
+ A bare coloured percentage would not do it, which is why the
119
+ content is a slot the consumer fills rather than a number this
120
+ component formats.
121
+ SENTIMENT the badge below. Direction and the word say the figure ROSE;
122
+ they say nothing about whether rising is good, and hue alone
123
+ was carrying that. */
124
+ .rsui-metric-card__comparison {
125
+ @apply inline-flex items-center gap-space-xs;
126
+ @apply text-sm font-medium;
127
+ }
128
+
129
+ /* EVERY sentiment is circled, not just bad news. The asymmetric version — a badge
130
+ on the negative tile and a bare arrow everywhere else — read as an inconsistency
131
+ rather than as a signal: a treatment that appears on one state out of three looks
132
+ like the other two are missing something.
133
+
134
+ 20px, which matches text-sm's line box exactly, so circling every arrow shifts no
135
+ row height against the uncircled version this replaces. */
136
+ .rsui-metric-card__comparison-icon {
137
+ @apply inline-flex items-center justify-center shrink-0 size-5 rounded-full;
138
+ /* 1.5px, the weight Pill's own ring already uses, so the system is not learning
139
+ a second hairline. border-box keeps the outer box at 20px either way. */
140
+ @apply border-[1.5px] border-current;
141
+ }
142
+
143
+ .rsui-metric-card__comparison-icon svg {
144
+ @apply size-3;
145
+ }
146
+
147
+ /* ── The quieter half of the comparison ───────────────────────────────────────
148
+ The reference splits the line in two: the change itself takes the sentiment
149
+ colour, and the phrase qualifying it ("vs last month") drops to tertiary ink.
150
+ The point is emphasis, not meaning — the eye should land on the figure and the
151
+ change first, and a whole line in one saturated colour flattens that.
152
+
153
+ Colour only. The reference sets both halves at text-sm Medium, so the weight is
154
+ inherited rather than stepped down; lightening the weight as well would push the
155
+ phrase past quiet into illegible at 14px.
156
+
157
+ A direct declaration, which beats the sentiment colour inherited from the parent
158
+ without needing to out-specify it. */
159
+ .rsui-metric-card__comparison-context {
160
+ @apply text-text-tertiary;
161
+ }
162
+
163
+ /* On the two dark tones the comparison has already given up its semantic colour
164
+ for the tile's muted ink, so the change and its qualifier land on the same
165
+ value and the split collapses. That is cosmetic rather than a lost signal — the
166
+ two-tone is an emphasis device, and nothing about sentiment or direction rides
167
+ on it — and the alternative is inventing a second readable ink per tone to
168
+ preserve a nicety. tone-light keeps its semantic colour, so it keeps the split. */
169
+ .rsui-metric-card--tone-green .rsui-metric-card__comparison-context,
170
+ .rsui-metric-card--tone-teal .rsui-metric-card__comparison-context {
171
+ color: var(--rsui-metric-card-ink-muted);
172
+ }
173
+
174
+ /* ── The non-colour sentiment cue ─────────────────────────────────────────────
175
+ Raised in review on #333, and still the constraint this has to satisfy: `trend`
176
+ and the consumer's word carry the DIRECTION, but nothing carries the SENTIMENT
177
+ except hue. In the up-is-bad case both tiles render an up arrow and the word
178
+ "Increase", so favourable and unfavourable are separated by colour alone
179
+ (WCAG 1.4.1) — and on the two dark tones, where the colour is dropped for
180
+ contrast, they would be identical outright.
181
+
182
+ So the circle is now on every sentiment, and FILL is what carries the meaning
183
+ instead of the circle's presence:
184
+
185
+ positive / neutral ringed, open centre
186
+ negative solid, arrow knocked out of the fill
187
+
188
+ That keeps the treatment symmetric — the thing the presence-or-absence version
189
+ got wrong — while leaving a difference that survives greyscale and survives the
190
+ colour being stripped on a dark tone, which presence alone was carrying before.
191
+ A solid mark also still reads heavier than an open one, so bad news keeps the
192
+ extra weight without being the only state that gets a mark at all.
193
+
194
+ Direction is untouched — an up arrow that is bad news still points up. */
195
+ .rsui-metric-card__comparison--negative .rsui-metric-card__comparison-icon {
196
+ @apply border-transparent text-text-on-color;
197
+ background-color: var(--color-text-error);
198
+ }
199
+
200
+ .rsui-metric-card__comparison--positive {
201
+ @apply text-text-success;
202
+ }
203
+
204
+ .rsui-metric-card__comparison--negative {
205
+ @apply text-text-error;
206
+ }
207
+
208
+ .rsui-metric-card__comparison--neutral {
209
+ @apply text-text-tertiary;
210
+ }
211
+
42
212
  /* ---------------------------------------------------------------------------
43
213
  Tonal variants
44
214
 
@@ -99,6 +269,47 @@
99
269
  color: var(--rsui-metric-card-ink-muted);
100
270
  }
101
271
 
272
+ .rsui-metric-card--toned .rsui-metric-card__description {
273
+ color: var(--rsui-metric-card-ink-muted);
274
+ }
275
+
276
+ /* The semantic status inks are unreadable on the two DARK tiles, measured:
277
+
278
+ ink on tone-green #0e5942 on tone-teal #0f373c
279
+ --color-text-success #067647 1.46:1 2.26:1
280
+ --color-text-error #b42318 1.26:1 1.96:1
281
+
282
+ Both invisible — the same failure this file already documents for text-brand
283
+ on a toned tile. So the comparison gives up its colour there and takes the
284
+ tile's muted ink, exactly as the active state gives up its brand number, and
285
+ the arrow direction plus the consumer's word carry the meaning on their own.
286
+ Journey Green 200 measures 5.92:1 and Deep Teal 300 6.36:1, both clearing AA.
287
+
288
+ tone-light is deliberately NOT in this list. Its surface is Modern Bone 200
289
+ (#f9f8f6), where success measures 5.36:1 and error 6.19:1 — both pass AA, so
290
+ there is nothing to fix and stripping the colour would only cost a signal.
291
+
292
+ (0,2,0), so it outranks the (0,1,0) sentiment modifiers wherever they sit. */
293
+ .rsui-metric-card--tone-green .rsui-metric-card__comparison,
294
+ .rsui-metric-card--tone-teal .rsui-metric-card__comparison {
295
+ color: var(--rsui-metric-card-ink-muted);
296
+ }
297
+
298
+ /* The solid badge goes with it. Filling it with the error ink on a dark tile would
299
+ reproduce exactly the 1.26:1 the rule above exists to avoid, so it takes the
300
+ tile's muted ink and puts the arrow in the tile's own surface colour — the
301
+ same 5.92:1 and 6.36:1 pairing, read the other way round.
302
+
303
+ The ringed badge needs no rule of its own: its border is `currentColor`, and the
304
+ rule above has already moved the comparison's ink to the tile's muted value, so
305
+ the ring follows it. This is the tone where solid-vs-ringed does its real work,
306
+ because once the colour is stripped it is the only sentiment signal left. */
307
+ .rsui-metric-card--tone-green .rsui-metric-card__comparison--negative .rsui-metric-card__comparison-icon,
308
+ .rsui-metric-card--tone-teal .rsui-metric-card__comparison--negative .rsui-metric-card__comparison-icon {
309
+ background-color: var(--rsui-metric-card-ink-muted);
310
+ color: var(--rsui-metric-card-surface);
311
+ }
312
+
102
313
  /* The active state's brand-coloured number is colour reinforcement only — the
103
314
  Card action layer carries the actual selection ring (WCAG 1.4.1). On a tonal
104
315
  surface that reinforcement has to go: text-brand is Brand-500 (#136c52),
@@ -1,7 +1,7 @@
1
1
  .rsui-modal {
2
2
  @apply fixed inset-0 z-50 overflow-y-auto transition-all;
3
3
  @apply flex flex-col;
4
- @apply px-4 py-6 md:px-0;
4
+ @apply px-space-md py-space-lg md:px-0;
5
5
  height: 100dvh;
6
6
  }
7
7
 
@@ -15,7 +15,7 @@
15
15
 
16
16
  .rsui-modal__content {
17
17
  @apply relative max-h-full overflow-scroll overscroll-contain transform transition-all;
18
- @apply rounded-xl shadow-full md:w-full md:mx-auto mx-4;
18
+ @apply rounded-xl shadow-full md:w-full md:mx-auto mx-space-md;
19
19
  @apply bg-background-primary;
20
20
  -ms-overflow-style: none;
21
21
  scrollbar-width: none;
@@ -26,7 +26,7 @@
26
26
  }
27
27
 
28
28
  .rsui-modal__content--sm {
29
- @apply sm:max-w-sm xs:mx-4 sm:mx-auto;
29
+ @apply sm:max-w-sm xs:mx-space-md sm:mx-auto;
30
30
  }
31
31
 
32
32
  .rsui-modal__content--md {
@@ -62,7 +62,7 @@
62
62
  }
63
63
 
64
64
  .rsui-modal__header--padded {
65
- @apply p-4;
65
+ @apply p-space-md;
66
66
  }
67
67
 
68
68
  .rsui-modal__body {
@@ -72,7 +72,7 @@
72
72
  }
73
73
 
74
74
  .rsui-modal__body--padded {
75
- @apply p-4;
75
+ @apply p-space-md;
76
76
  }
77
77
 
78
78
  .rsui-modal__body::-webkit-scrollbar {
@@ -80,13 +80,13 @@
80
80
  }
81
81
 
82
82
  .rsui-modal__footer {
83
- @apply sticky inset-x-0 bottom-0 z-1 p-0 bg-background-secondary shrink-0 flex flex-col items-stretch gap-y-2;
84
- @apply sm:flex-row sm:items-center sm:justify-end sm:gap-x-2 sm:gap-y-0;
83
+ @apply sticky inset-x-0 bottom-0 z-1 p-0 bg-background-secondary shrink-0 flex flex-col items-stretch gap-y-space-xs;
84
+ @apply sm:flex-row sm:items-center sm:justify-end sm:gap-x-space-xs sm:gap-y-0;
85
85
  padding-bottom: max(0.5rem, env(safe-area-inset-bottom));
86
86
  }
87
87
 
88
88
  .rsui-modal__footer--padded {
89
- @apply p-4;
89
+ @apply p-space-md;
90
90
  padding-bottom: max(1rem, env(safe-area-inset-bottom));
91
91
  }
92
92
 
@@ -1,13 +1,13 @@
1
1
  .rsui-pagination {
2
- @apply flex flex-nowrap items-center w-fit border border-border-secondary rounded-full p-1 bg-background-primary divide-x divide-border-secondary;
2
+ @apply flex flex-nowrap items-center w-fit border border-border-secondary rounded-full p-space-xxs bg-background-primary divide-x divide-border-secondary;
3
3
  }
4
4
 
5
5
  .rsui-pagination__previous {
6
- @apply size-full flex items-center pr-1;
6
+ @apply size-full flex items-center pr-space-xxs;
7
7
  }
8
8
 
9
9
  .rsui-pagination__items {
10
- @apply size-full flex items-center gap-px px-1;
10
+ @apply size-full flex items-center gap-px px-space-xxs;
11
11
  }
12
12
 
13
13
  .rsui-pagination__items-group {
@@ -15,7 +15,7 @@
15
15
  }
16
16
 
17
17
  .rsui-pagination__next {
18
- @apply size-full flex items-center pl-1;
18
+ @apply size-full flex items-center pl-space-xxs;
19
19
  }
20
20
 
21
21
  .rsui-pagination-parent {
@@ -1,6 +1,6 @@
1
1
  .rsui-pagination-item {
2
2
  @apply inline-flex items-center justify-center overflow-hidden select-none;
3
- @apply min-w-9 w-fit h-7 p-1 rounded-lg cursor-pointer transition;
3
+ @apply min-w-9 w-fit h-7 p-space-xxs rounded-lg cursor-pointer transition;
4
4
  @apply bg-background-primary hover:bg-background-primary-invert text-text-secondary text-sm;
5
5
 
6
6
  /* focus state */
@@ -1,5 +1,5 @@
1
1
  .rsui-progress-bar {
2
- @apply flex items-center gap-3 w-full;
2
+ @apply flex items-center gap-space-sm w-full;
3
3
  }
4
4
 
5
5
  .rsui-progress-bar__track {
@@ -3,13 +3,13 @@
3
3
  }
4
4
 
5
5
  .rsui-progress-tracker--sm {
6
- @apply gap-8 text-xs;
6
+ @apply gap-space-xl text-xs;
7
7
  }
8
8
 
9
9
  .rsui-progress-tracker--md {
10
- @apply gap-8 sm:gap-12 text-xs sm:text-base;
10
+ @apply gap-space-xl sm:gap-space-2xl text-xs sm:text-base;
11
11
  }
12
12
 
13
13
  .rsui-progress-tracker--lg {
14
- @apply gap-8 sm:gap-16 text-xs sm:text-xl;
14
+ @apply gap-space-xl sm:gap-space-3xl text-xs sm:text-xl;
15
15
  }