@visns-studio/visns-components 6.20.1 → 6.21.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.
@@ -0,0 +1,278 @@
1
+ @use 'surface' as *;
2
+
3
+ /**
4
+ * BandedList — sections with a heading, a count, and an urgency on the edge.
5
+ *
6
+ * THE ONE IDEA THIS FILE CARRIES is that urgency is a property of the SECTION,
7
+ * not of the row. Each band gets a coloured left edge and a heading that says
8
+ * what its contents mean; the rows inside stay quiet and uniform. The
9
+ * alternative — colouring every overdue row red — turns a book with forty lapsed
10
+ * renewals into a wall of alarm nobody can read.
11
+ *
12
+ * The rows themselves are the host's. This draws the scaffolding and nothing
13
+ * else: a support block card, a subscription line and a client row have almost
14
+ * nothing in common, and a component that tried to render all three would be a
15
+ * config format rather than a primitive.
16
+ *
17
+ * Three variants, one per shape the CRM's screens actually draw:
18
+ *
19
+ * `card` a card per band, urgency on its left border. For bands that are
20
+ * independent of each other — this week's renewals and last month's.
21
+ * `plain` no card at all: a bare heading over whatever follows. For a page
22
+ * whose rows are already cards and would otherwise be cards inside
23
+ * cards.
24
+ * `flush` no card of its own, sitting inside ONE shared card with its
25
+ * siblings, heading sticky. For a continuous list broken up by
26
+ * dividers — the A–Z tabs in a filing drawer.
27
+ */
28
+
29
+ /* -------------------------------------------------------------------------- */
30
+ /* the container */
31
+ /* -------------------------------------------------------------------------- */
32
+
33
+ .board {
34
+ @include surface-tokens;
35
+
36
+ display: flex;
37
+ flex-direction: column;
38
+ gap: var(--spacing-md, 1rem);
39
+ }
40
+
41
+ /* ONE card holding every band, rather than a card per band — the container for
42
+ `flush`. A drawer of clients is one drawer; breaking it into twenty-six panels
43
+ would put a shadow and a radius between D and E for no reason. */
44
+ .boardCard {
45
+ display: block;
46
+ background: var(--pr-surface);
47
+ border: 1px solid var(--pr-line);
48
+ border-radius: var(--pr-radius);
49
+ box-shadow: var(--pr-shadow);
50
+ }
51
+
52
+ /* -------------------------------------------------------------------------- */
53
+ /* contained scrolling */
54
+ /* -------------------------------------------------------------------------- */
55
+
56
+ /**
57
+ * The rows scroll, the page does not.
58
+ *
59
+ * A list of four hundred clients scrolls the whole document, which takes the
60
+ * summary strip, the search box and the view tabs off the screen — so finding a
61
+ * name and then filtering it means scrolling back up to controls that used to be
62
+ * in front of you. The rows get their own scroll region instead and the chrome
63
+ * above them stays put.
64
+ *
65
+ * The height is set INLINE by `useContainedHeight`, measured from where the
66
+ * region actually starts rather than guessed from a stack of constants — the
67
+ * chrome above it changes height (a summary that has not loaded, a truncation
68
+ * notice that appears at a hundred rows) and a hard-coded offset is wrong on
69
+ * half the states of the page.
70
+ *
71
+ * `overscroll-behavior` is deliberately LEFT ALONE. Trapping the scroll at the
72
+ * end of the list is the behaviour that makes a long page feel broken on an
73
+ * iPad; when this region is done scrolling, the page should carry on.
74
+ */
75
+ .scroll {
76
+ overflow: auto;
77
+ /* Narrow screens scroll their overflow HERE rather than tipping the whole
78
+ document sideways. */
79
+ overflow-x: auto;
80
+ -webkit-overflow-scrolling: touch;
81
+ }
82
+
83
+ /* -------------------------------------------------------------------------- */
84
+ /* one band */
85
+ /* -------------------------------------------------------------------------- */
86
+
87
+ .band {
88
+ @include surface-tokens;
89
+
90
+ display: block;
91
+ }
92
+
93
+ /* NOT `overflow: hidden`, however much the rounded corners want it.
94
+ *
95
+ * A row inside a band carries a `⋯` menu, and a clipping ancestor eats it — on
96
+ * the last row of a band the popover was cut off entirely and the menu looked
97
+ * like a button that did nothing. Corners are handled by rounding the heading
98
+ * and the last row instead (see `.head` below, and the host's own last-row
99
+ * rule), which costs two rules and keeps every popover visible. */
100
+ .card {
101
+ background: var(--pr-surface);
102
+ border: 1px solid var(--pr-line);
103
+ /* Drawn at full width in the line colour and recoloured by the tone, so a
104
+ band with no urgency is not 3px narrower than one that has some. */
105
+ border-left: 3px solid var(--pr-line);
106
+ border-radius: var(--pr-radius);
107
+ }
108
+
109
+ .plain {
110
+ display: flex;
111
+ flex-direction: column;
112
+ gap: 0.6rem;
113
+ }
114
+
115
+ .toneDanger.card {
116
+ border-left-color: var(--pr-danger);
117
+ }
118
+
119
+ .toneWarn.card {
120
+ border-left-color: var(--pr-warn);
121
+ }
122
+
123
+ .toneNeutral.card {
124
+ border-left-color: var(--pr-accent);
125
+ }
126
+
127
+ .toneQuiet.card {
128
+ border-left-color: color-mix(in srgb, var(--pr-ink) 18%, transparent);
129
+ }
130
+
131
+ /* -------------------------------------------------------------------------- */
132
+ /* the heading */
133
+ /* -------------------------------------------------------------------------- */
134
+
135
+ .head {
136
+ display: flex;
137
+ flex-wrap: wrap;
138
+ gap: 0.5rem;
139
+ align-items: baseline;
140
+ justify-content: space-between;
141
+ }
142
+
143
+ .card > .head,
144
+ .flush > .head {
145
+ padding: 0.7rem var(--spacing-md, 1rem);
146
+ background: var(--pr-quiet);
147
+ border-bottom: 1px solid var(--pr-line);
148
+ }
149
+
150
+ /* The corner the card no longer clips for itself. Inset by the border width, so
151
+ the tinted heading meets the border rather than showing a hairline of page
152
+ behind it. */
153
+ .card > .head {
154
+ border-radius: calc(var(--pr-radius) - 1px) calc(var(--pr-radius) - 1px) 0 0;
155
+ }
156
+
157
+ .boardCard > .flush:first-child > .head {
158
+ border-radius: calc(var(--pr-radius) - 1px) calc(var(--pr-radius) - 1px) 0 0;
159
+ }
160
+
161
+ /* Flush bands share one card, so the divider between two of them is a rule on
162
+ the second one's heading — there is no card edge to do it for them. */
163
+ .flush + .flush > .head {
164
+ border-top: 1px solid var(--pr-line);
165
+ }
166
+
167
+ /* A `plain` heading is type on the page ground: no slab, no rule, and the
168
+ right-hand note pushed out by the count rather than by a space-between that
169
+ has nothing to push against. */
170
+ .plain > .head {
171
+ gap: 0.5rem;
172
+ padding: 0 0.25rem;
173
+ }
174
+
175
+ /* Sticky is opt-in, and it is the whole point of a flush band: the heading's
176
+ job is to tell you where you are once you have scrolled past the top of its
177
+ group. */
178
+ .headSticky {
179
+ position: sticky;
180
+ top: 0;
181
+ z-index: 1;
182
+ }
183
+
184
+ /* A sticky heading has to be OPAQUE or the rows read straight through it, and
185
+ `--pr-quiet` is usually a translucent mix — it is a tint meant to be laid over
186
+ a surface, not a colour. Layering it over `--pr-surface` as two backgrounds
187
+ composites the same shade with nothing showing through. */
188
+ .card > .headSticky,
189
+ .flush > .headSticky {
190
+ background:
191
+ linear-gradient(var(--pr-quiet), var(--pr-quiet)),
192
+ var(--pr-surface);
193
+ }
194
+
195
+ /* A plain heading has no slab of its own, so it borrows the PAGE ground — not
196
+ the card surface, which would draw a white bar across a warm page. */
197
+ .plain > .headSticky {
198
+ padding: 0.35rem 0.25rem;
199
+ background: var(--pr-page);
200
+ }
201
+
202
+ .flush > .head {
203
+ padding: 0.4rem var(--spacing-md, 1rem);
204
+ }
205
+
206
+ .title {
207
+ display: flex;
208
+ gap: 0.5rem;
209
+ align-items: center;
210
+ margin: 0;
211
+ color: var(--pr-heading);
212
+ font-size: 0.95rem;
213
+ font-weight: var(--font-weight-semibold, 600);
214
+ }
215
+
216
+ /* A plain heading is small caps on the page ground, and it takes the page's own
217
+ ink rather than the heading colour — a run of five of them in the brand green
218
+ reads as five separate pages. */
219
+ .plain > .head > .title {
220
+ gap: 0.5rem;
221
+ align-items: baseline;
222
+ color: inherit;
223
+ font-size: var(--font-size-sm, 0.8125rem);
224
+ font-weight: 700;
225
+ letter-spacing: 0.02em;
226
+ text-transform: uppercase;
227
+ }
228
+
229
+ .flush > .head > .title {
230
+ align-items: baseline;
231
+ font-size: var(--font-size-sm, 0.8125rem);
232
+ font-weight: 700;
233
+ letter-spacing: 0.04em;
234
+ }
235
+
236
+ /* A pill in a card heading, a bare number everywhere else — a pill on the page
237
+ ground reads as a badge that means something, and it does not. */
238
+ .count {
239
+ padding: 0 0.45rem;
240
+ color: var(--pr-muted);
241
+ font-size: var(--font-size-xs, 0.75rem);
242
+ font-weight: var(--font-weight-semibold, 600);
243
+ font-variant-numeric: tabular-nums;
244
+ background: var(--pr-surface);
245
+ border: 1px solid var(--pr-line);
246
+ border-radius: var(--radius-pill, 999px);
247
+ }
248
+
249
+ .plain > .head > .title > .count,
250
+ .flush > .head > .title > .count {
251
+ padding: 0;
252
+ font-size: var(--font-size-sm, 0.8125rem);
253
+ font-weight: 400;
254
+ background: none;
255
+ border: 0;
256
+ }
257
+
258
+ .flush > .head > .title > .count {
259
+ font-size: var(--font-size-xs, 0.75rem);
260
+ }
261
+
262
+ /* Whatever the band wants to say about itself on the right — a subtotal, a
263
+ sentence explaining what its dates mean. */
264
+ .meta {
265
+ display: flex;
266
+ gap: 0.6rem;
267
+ align-items: baseline;
268
+ color: var(--pr-muted);
269
+ font-size: var(--font-size-xs, 0.75rem);
270
+ }
271
+
272
+ .plain > .head > .meta {
273
+ margin-left: auto;
274
+ }
275
+
276
+ .note {
277
+ color: var(--pr-muted);
278
+ }
@@ -0,0 +1,108 @@
1
+ @use 'surface' as *;
2
+
3
+ /**
4
+ * FactsBand — the persistent strip of key facts under a record's title.
5
+ *
6
+ * The same hairline grid StatStrip draws in its `joined` variant, at a tighter
7
+ * density and carrying two kinds of cell rather than one: a FIGURE (a count,
8
+ * read at a glance) and a VALUE (a name, an address, a phone number — read as
9
+ * text, and clipped rather than wrapped, because the band's whole job is to keep
10
+ * a fixed height while the body under it changes).
11
+ *
12
+ * It is a separate module from StatStrip on purpose. A stat strip describes a
13
+ * SET and sits above a list; a facts band describes ONE RECORD and sits under
14
+ * its title, where it has to survive the tabs beneath it being switched. Merging
15
+ * them would mean one component with a density prop that changed what its slots
16
+ * meant, which is the kind of parameter that quietly grows a third value.
17
+ */
18
+
19
+ .band {
20
+ @include surface-tokens;
21
+
22
+ display: grid;
23
+ grid-template-columns: var(
24
+ --vs-facts-columns,
25
+ repeat(auto-fit, minmax(var(--vs-facts-min, 11rem), 1fr))
26
+ );
27
+ gap: 1px;
28
+ overflow: hidden;
29
+ font-size: var(--font-size-sm, 0.8125rem);
30
+ background: var(--pr-line);
31
+ border: 1px solid var(--pr-line);
32
+ border-radius: var(--pr-radius);
33
+ box-shadow: var(--pr-shadow);
34
+ }
35
+
36
+ .cell {
37
+ display: flex;
38
+ flex-direction: column;
39
+ gap: 0.15rem;
40
+ min-width: 0;
41
+ padding: 0.7rem var(--spacing-md, 1rem);
42
+ background: var(--pr-surface);
43
+ }
44
+
45
+ .label {
46
+ color: var(--pr-muted);
47
+ font-size: var(--font-size-xs, 0.75rem);
48
+ font-weight: 600;
49
+ letter-spacing: 0.02em;
50
+ text-transform: uppercase;
51
+ }
52
+
53
+ /* Text, clipped. A band that wraps is a band whose height moves, and the tab
54
+ strip under it moves with it every time a record loads. */
55
+ .value {
56
+ overflow: hidden;
57
+ color: var(--pr-heading);
58
+ font-weight: var(--font-weight-semibold, 600);
59
+ text-decoration: none;
60
+ text-overflow: ellipsis;
61
+ white-space: nowrap;
62
+ }
63
+
64
+ /* `href` turns the value into a `tel:` / `mailto:`, so it has to be obviously
65
+ tappable without looking like a stray link in a header. */
66
+ a.value:hover {
67
+ text-decoration: underline;
68
+ }
69
+
70
+ /* The counts read as figures rather than sentences, so a glance across the band
71
+ lands on the numbers. */
72
+ .figure {
73
+ font-size: 1.2rem;
74
+ font-weight: 700;
75
+ font-variant-numeric: tabular-nums;
76
+ line-height: 1.2;
77
+ }
78
+
79
+ .note {
80
+ overflow: hidden;
81
+ color: var(--pr-muted);
82
+ font-size: var(--font-size-xs, 0.75rem);
83
+ text-overflow: ellipsis;
84
+ white-space: nowrap;
85
+ }
86
+
87
+ .toneAccent {
88
+ color: var(--pr-accent);
89
+ }
90
+
91
+ .toneWarn {
92
+ color: var(--pr-warn);
93
+ }
94
+
95
+ .toneDanger {
96
+ color: var(--pr-danger);
97
+ }
98
+
99
+ /* The band is one row of a card, so a failure to load must not leave a hole in
100
+ the page where the record's facts were. Full width, same ground, one
101
+ sentence. */
102
+ .state {
103
+ grid-column: 1 / -1;
104
+ padding: 0.7rem var(--spacing-md, 1rem);
105
+ color: var(--pr-muted);
106
+ font-size: var(--font-size-xs, 0.75rem);
107
+ background: var(--pr-surface);
108
+ }
@@ -0,0 +1,98 @@
1
+ @use 'surface' as *;
2
+
3
+ /**
4
+ * PageHeader — the identity row at the top of a screen.
5
+ *
6
+ * Whatever names the page on the left, whatever acts on it hard right, and
7
+ * nothing behind either: the shape GenericDetail's `.crmtitle` draws, restated
8
+ * as a component so the six screens that had each written it out by hand stop
9
+ * drifting apart by a tenth of a rem at a time.
10
+ *
11
+ * The left half is deliberately a slot. Some of these pages lead with a
12
+ * breadcrumb, some with a back link over a title, some with a title and a line
13
+ * of explanation — and the component's job is the ROW, not a decision about
14
+ * which of those a page should use.
15
+ */
16
+
17
+ .header {
18
+ @include surface-tokens;
19
+
20
+ display: flex;
21
+ flex-wrap: wrap;
22
+ gap: var(--spacing-md, 1rem);
23
+ justify-content: space-between;
24
+
25
+ /* Breadcrumb wraps its trail in a <nav>; without this the flex item takes
26
+ its content's minimum width and pushes the actions off the row. */
27
+ nav {
28
+ min-width: 0;
29
+ }
30
+ }
31
+
32
+ .alignCenter {
33
+ align-items: center;
34
+ }
35
+
36
+ .alignStart {
37
+ align-items: flex-start;
38
+ }
39
+
40
+ .alignEnd {
41
+ align-items: flex-end;
42
+ }
43
+
44
+ /* The quarter-rem of air the breadcrumb-led screens sit in, so the trail is not
45
+ flush against the page gutter. Opt-in: the title-led screens do not want it. */
46
+ .gutter {
47
+ padding: 0 0.25rem 0.25rem;
48
+ }
49
+
50
+ /* Opt-in, and it needs a ground of its own — a transparent sticky header lets
51
+ the rows scroll through the type underneath it. */
52
+ .sticky {
53
+ position: sticky;
54
+ top: 0;
55
+ z-index: 2;
56
+ background: var(--pr-surface);
57
+ }
58
+
59
+ .main {
60
+ min-width: 0;
61
+ }
62
+
63
+ .title {
64
+ margin: 0;
65
+ padding: 0;
66
+ color: var(--pr-heading);
67
+ font-size: var(--vs-header-title-size, var(--page-header-title-size, 1.25rem));
68
+ font-weight: var(--font-weight-bold, 700);
69
+ line-height: 1.2;
70
+ }
71
+
72
+ .subtitle {
73
+ margin: 0.2rem 0 0;
74
+ /* Wide enough for a sentence, narrow enough that it stays one. */
75
+ max-width: 62ch;
76
+ color: var(--pr-muted);
77
+ font-size: var(--font-size-sm, 0.8125rem);
78
+ }
79
+
80
+ .actions {
81
+ display: flex;
82
+ flex-wrap: wrap;
83
+ gap: 0.5rem;
84
+ align-items: center;
85
+ }
86
+
87
+ /* -------------------------------------------------------------------------- */
88
+ /* narrow */
89
+ /* -------------------------------------------------------------------------- */
90
+
91
+ /* The tablet break. Actions under the title rather than squeezed beside it —
92
+ this app is driven on an iPad in portrait as much as at a desk. */
93
+ @media (max-width: 820px) {
94
+ .header {
95
+ flex-direction: column;
96
+ align-items: stretch;
97
+ }
98
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * ScrollRegion — the list scrolls, the page stays.
3
+ *
4
+ * Deliberately almost empty. The height is inline (measured, see
5
+ * `useContainedHeight`) and the region must not impose a background, a border or
6
+ * a radius: it is a scroll box around whatever the host already drew, and giving
7
+ * it a surface of its own would put a second card edge around a card.
8
+ */
9
+
10
+ .region {
11
+ overflow: auto;
12
+ /* Narrow screens scroll their overflow HERE rather than tipping the whole
13
+ document sideways. */
14
+ overflow-x: auto;
15
+ /* Momentum on iOS Safari. A no-op everywhere it is not needed. */
16
+ -webkit-overflow-scrolling: touch;
17
+ /* `overscroll-behavior` is left at its default on purpose: chaining to the
18
+ page when the list is finished is the native feel, and containing it is
19
+ what makes a long page seem stuck. */
20
+ }
@@ -0,0 +1,183 @@
1
+ @use 'surface' as *;
2
+
3
+ /**
4
+ * StatStrip — the row of labelled figures that sits above a list.
5
+ *
6
+ * Two shapes, because the screens it was lifted from genuinely draw two and
7
+ * neither is wrong:
8
+ *
9
+ * `joined` one card, cells separated by a hairline. The grid's `gap: 1px`
10
+ * is the container's own background showing through, which is how
11
+ * the rules stay exactly one device pixel at any zoom. Reads as a
12
+ * single statement about the set below it.
13
+ * `tiles` separate bordered tiles with real space between them. Reads as
14
+ * independent figures, which is what a dashboard wants — and is
15
+ * the only one of the two that can carry a per-tile emphasis skin
16
+ * without the tint bleeding across a shared border.
17
+ *
18
+ * The track width is a custom property rather than a fixed value so a page can
19
+ * reflow the strip from its own media query (`--vs-stat-min`), or replace the
20
+ * track list outright (`--vs-stat-columns`) for the cases auto-fit cannot
21
+ * express — "exactly two columns on a phone" being the one that keeps coming
22
+ * up.
23
+ */
24
+
25
+ .strip {
26
+ @include surface-tokens;
27
+
28
+ display: grid;
29
+ grid-template-columns: var(
30
+ --vs-stat-columns,
31
+ repeat(auto-fit, minmax(var(--vs-stat-min, 10rem), 1fr))
32
+ );
33
+ }
34
+
35
+ /* One card, hairline-separated. */
36
+ .joined {
37
+ gap: 1px;
38
+ overflow: hidden;
39
+ background: var(--pr-line);
40
+ border: 1px solid var(--pr-line);
41
+ border-radius: var(--pr-radius);
42
+ box-shadow: var(--pr-shadow);
43
+ }
44
+
45
+ /* Separate tiles. */
46
+ .tiles {
47
+ gap: var(--spacing-md, 1rem);
48
+ }
49
+
50
+ /* -------------------------------------------------------------------------- */
51
+ /* one figure */
52
+ /* -------------------------------------------------------------------------- */
53
+
54
+ .stat {
55
+ @include surface-tokens;
56
+
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: 0.15rem;
60
+ min-width: 0;
61
+ padding: 0.85rem var(--spacing-md, 1rem);
62
+ background: var(--pr-surface);
63
+ }
64
+
65
+ .tiles .stat {
66
+ border: 1px solid var(--pr-line);
67
+ border-radius: var(--pr-radius);
68
+ }
69
+
70
+ /* A stat that navigates. It is a real <button>, so everything a button brings
71
+ with it — the browser's font, the centred text, the 2px agent border — has
72
+ to be unwound before it can pass for a cell. */
73
+ .interactive {
74
+ color: inherit;
75
+ font: inherit;
76
+ text-align: left;
77
+ text-decoration: none;
78
+ border: 0;
79
+ cursor: pointer;
80
+ transition: background var(--speed, 160ms) var(--ease, ease);
81
+
82
+ &:hover {
83
+ background: var(--pr-quiet);
84
+ }
85
+
86
+ &:focus-visible {
87
+ outline: 2px solid var(--pr-accent);
88
+ outline-offset: -2px;
89
+ }
90
+ }
91
+
92
+ .tiles .interactive {
93
+ border: 1px solid var(--pr-line);
94
+ }
95
+
96
+ .label {
97
+ color: var(--pr-muted);
98
+ font-size: var(--font-size-xs, 0.75rem);
99
+ letter-spacing: 0.02em;
100
+ text-transform: uppercase;
101
+ }
102
+
103
+ /* The label carries a little more weight inside a hairline grid, where there is
104
+ no border to tell one cell from the next. */
105
+ .joined .label {
106
+ font-weight: 600;
107
+ }
108
+
109
+ .value {
110
+ font-size: 1.35rem;
111
+ font-weight: var(--font-weight-bold, 700);
112
+ /* Tabular figures so a column of money lines up on the decimal point. */
113
+ font-variant-numeric: tabular-nums;
114
+ line-height: 1.2;
115
+ }
116
+
117
+ /* In a tile the figure is the tile's whole content, so it takes the heading
118
+ colour. In a hairline grid it sits in a run of cells and inherits the page's
119
+ ink instead, or the run reads as six headings. */
120
+ .tiles .value {
121
+ color: var(--pr-heading);
122
+ line-height: 1.15;
123
+ }
124
+
125
+ /* `/mo`, `%`, `h` — the unit rides with the figure at small print rather than
126
+ being sized up with it. */
127
+ .unit {
128
+ margin-left: 0.15rem;
129
+ color: var(--pr-muted);
130
+ font-size: 0.8rem;
131
+ font-weight: 400;
132
+ }
133
+
134
+ .note {
135
+ color: var(--pr-muted);
136
+ font-size: var(--font-size-xs, 0.75rem);
137
+ }
138
+
139
+ /* -------------------------------------------------------------------------- */
140
+ /* tone and emphasis */
141
+ /* -------------------------------------------------------------------------- */
142
+
143
+ /* `tone` colours the FIGURE. Applied by the caller only when the figure is
144
+ non-zero — a tile that is permanently red is a tile nobody reads. */
145
+ .toneAccent {
146
+ color: var(--pr-accent);
147
+ }
148
+
149
+ .toneHeading {
150
+ color: var(--pr-heading);
151
+ }
152
+
153
+ .toneWarn {
154
+ color: var(--pr-warn);
155
+ }
156
+
157
+ .toneDanger {
158
+ color: var(--pr-danger);
159
+ }
160
+
161
+ /* A note that has to carry the weight rather than the figure above it — "3
162
+ unpriced" hanging off a money total. */
163
+ .noteWarn {
164
+ color: var(--pr-warn);
165
+ font-weight: var(--font-weight-semibold, 600);
166
+ }
167
+
168
+ /* `emphasis` skins the whole TILE, and only in the `tiles` variant: a tint
169
+ inside a hairline grid would bleed to the cell edges and read as a broken
170
+ border rather than as a warning. */
171
+ .tiles .emphasisWarn {
172
+ background: color-mix(in srgb, var(--pr-warn) 7%, var(--pr-surface));
173
+ border-color: color-mix(in srgb, var(--pr-warn) 42%, var(--pr-line));
174
+ }
175
+
176
+ .tiles .emphasisDanger {
177
+ background: color-mix(in srgb, var(--pr-danger) 5%, var(--pr-surface));
178
+ border-color: color-mix(in srgb, var(--pr-danger) 40%, var(--pr-line));
179
+
180
+ .value {
181
+ color: var(--pr-danger);
182
+ }
183
+ }