@ponchia/ui 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +133 -0
- package/README.md +3 -3
- package/classes/classes.json +79 -7
- package/classes/index.d.ts +53 -1
- package/classes/index.js +60 -0
- package/classes/vscode.css-custom-data.json +24 -0
- package/css/app.css +14 -3
- package/css/disclosure.css +7 -7
- package/css/feedback.css +11 -8
- package/css/forms.css +3 -3
- package/css/navigation.css +1 -1
- package/css/overlay.css +19 -5
- package/css/primitives.css +101 -6
- package/css/site.css +11 -5
- package/css/skins.css +97 -3
- package/css/state.css +161 -0
- package/css/table.css +1 -1
- package/css/tokens.css +6 -0
- package/css/workbench.css +151 -0
- package/dist/bronto.css +1 -1
- package/dist/css/app.css +1 -1
- package/dist/css/disclosure.css +1 -1
- package/dist/css/feedback.css +1 -1
- package/dist/css/forms.css +1 -1
- package/dist/css/navigation.css +1 -1
- package/dist/css/overlay.css +1 -1
- package/dist/css/primitives.css +1 -1
- package/dist/css/report-kit.css +1 -1
- package/dist/css/site.css +1 -1
- package/dist/css/skins.css +1 -1
- package/dist/css/state.css +1 -1
- package/dist/css/table.css +1 -1
- package/dist/css/tokens.css +1 -1
- package/dist/css/workbench.css +1 -1
- package/docs/adr/0001-color-system.md +32 -3
- package/docs/contrast.md +102 -18
- package/docs/reference.md +52 -1
- package/docs/reporting.md +8 -8
- package/docs/stability.md +31 -2
- package/docs/state.md +51 -1
- package/docs/theming.md +18 -0
- package/docs/usage.md +62 -2
- package/docs/workbench.md +83 -4
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/tokens/figma.variables.json +84 -0
- package/tokens/index.d.ts +2 -2
- package/tokens/index.js +23 -0
- package/tokens/index.json +12 -0
- package/tokens/resolved.json +6 -0
- package/tokens/skins.js +117 -7
- package/tokens/tokens.dtcg.json +7 -1
package/css/disclosure.css
CHANGED
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
border-color var(--duration-fast) var(--ease-standard);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/* Coarse-pointer tap-target floor (
|
|
47
|
+
/* Coarse-pointer tap-target floor (`--tap-target`), matching inputs/sitenav —
|
|
48
48
|
tabs are easy to mis-tap on touch (WCAG 2.5.8 — C24). */
|
|
49
49
|
@media (pointer: coarse) {
|
|
50
50
|
.ui-tab {
|
|
51
|
-
min-block-size:
|
|
51
|
+
min-block-size: var(--tap-target);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -277,8 +277,8 @@
|
|
|
277
277
|
align-items: center;
|
|
278
278
|
display: inline-flex;
|
|
279
279
|
justify-content: center;
|
|
280
|
-
min-block-size:
|
|
281
|
-
min-inline-size:
|
|
280
|
+
min-block-size: var(--tap-target-min);
|
|
281
|
+
min-inline-size: var(--tap-target-min);
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
@@ -349,12 +349,12 @@
|
|
|
349
349
|
}
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
-
/* Coarse-pointer tap-target floor, matching inputs/sitenav
|
|
352
|
+
/* Coarse-pointer tap-target floor (`--tap-target`), matching inputs/sitenav —
|
|
353
353
|
pagination controls are easy to mis-tap on touch (WCAG 2.5.8 — C24). */
|
|
354
354
|
@media (pointer: coarse) {
|
|
355
355
|
.ui-pagination__item {
|
|
356
|
-
min-block-size:
|
|
357
|
-
min-inline-size:
|
|
356
|
+
min-block-size: var(--tap-target);
|
|
357
|
+
min-inline-size: var(--tap-target);
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
360
|
|
package/css/feedback.css
CHANGED
|
@@ -130,9 +130,12 @@
|
|
|
130
130
|
display: grid;
|
|
131
131
|
gap: 0.5rem;
|
|
132
132
|
|
|
133
|
-
/* Logical so the stack anchors bottom inline-end and mirrors in RTL.
|
|
134
|
-
|
|
135
|
-
|
|
133
|
+
/* Logical so the stack anchors bottom inline-end and mirrors in RTL. The
|
|
134
|
+
bottom offset clears the home-indicator gesture area: a toast landing there
|
|
135
|
+
is both hard to read and hard to dismiss, because the system swallows the
|
|
136
|
+
swipe. `max()` leaves the desktop offset untouched. */
|
|
137
|
+
inset-block: auto max(1.25rem, var(--safe-area-bottom));
|
|
138
|
+
inset-inline: auto max(1.25rem, var(--safe-area-right));
|
|
136
139
|
justify-items: end;
|
|
137
140
|
max-inline-size: min(22rem, calc(100vw - 2rem));
|
|
138
141
|
pointer-events: none;
|
|
@@ -144,7 +147,7 @@
|
|
|
144
147
|
overlaps the polite bottom stack and reads as a distinct, urgent
|
|
145
148
|
channel. */
|
|
146
149
|
.ui-toast-stack--assertive {
|
|
147
|
-
inset-block: 1.25rem auto;
|
|
150
|
+
inset-block: max(1.25rem, var(--safe-area-top)) auto;
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
.ui-toast {
|
|
@@ -225,15 +228,15 @@
|
|
|
225
228
|
}
|
|
226
229
|
|
|
227
230
|
/* Touch: enlarge the dismiss/close hit areas to the project's coarse-pointer
|
|
228
|
-
tap-target floor (
|
|
229
|
-
the desktop glyph size. The box is centred so the glyph stays put. */
|
|
231
|
+
tap-target floor (`--tap-target`, as in primitives.css / forms.css) without
|
|
232
|
+
changing the desktop glyph size. The box is centred so the glyph stays put. */
|
|
230
233
|
@media (pointer: coarse) {
|
|
231
234
|
.ui-alert__close,
|
|
232
235
|
.ui-toast__close {
|
|
233
236
|
display: inline-grid;
|
|
234
237
|
place-items: center;
|
|
235
|
-
min-block-size:
|
|
236
|
-
min-inline-size:
|
|
238
|
+
min-block-size: var(--tap-target);
|
|
239
|
+
min-inline-size: var(--tap-target);
|
|
237
240
|
}
|
|
238
241
|
|
|
239
242
|
.ui-alert__close {
|
package/css/forms.css
CHANGED
|
@@ -476,18 +476,18 @@
|
|
|
476
476
|
.ui-input,
|
|
477
477
|
.ui-select,
|
|
478
478
|
.ui-textarea {
|
|
479
|
-
min-block-size:
|
|
479
|
+
min-block-size: var(--tap-target);
|
|
480
480
|
}
|
|
481
481
|
|
|
482
482
|
.ui-search {
|
|
483
|
-
min-block-size:
|
|
483
|
+
min-block-size: var(--tap-target);
|
|
484
484
|
}
|
|
485
485
|
|
|
486
486
|
/* The whole label is the toggle target — float it to the ~44px floor so the
|
|
487
487
|
small track/box isn't a sub-target on touch (WCAG 2.5.8 — C24). */
|
|
488
488
|
.ui-switch,
|
|
489
489
|
.ui-check {
|
|
490
|
-
min-block-size:
|
|
490
|
+
min-block-size: var(--tap-target);
|
|
491
491
|
}
|
|
492
492
|
|
|
493
493
|
.ui-check input {
|
package/css/navigation.css
CHANGED
package/css/overlay.css
CHANGED
|
@@ -149,6 +149,15 @@ html:has(.ui-modal.is-open) {
|
|
|
149
149
|
margin-inline-start: auto;
|
|
150
150
|
max-block-size: 100vh;
|
|
151
151
|
max-inline-size: min(26rem, 100vw);
|
|
152
|
+
|
|
153
|
+
/* Full-height by design, so the panel's edges are the screen's edges: its
|
|
154
|
+
surface should still reach them, but its content must clear the cutout and
|
|
155
|
+
the home indicator. Add the INSET ONLY — `.ui-modal` is deliberately
|
|
156
|
+
`padding: 0` (its head/body parts own their spacing), so a `max(space, …)`
|
|
157
|
+
floor here would silently pad the drawer on every device, not just a
|
|
158
|
+
notched one. */
|
|
159
|
+
padding-block: var(--safe-area-top) var(--safe-area-bottom);
|
|
160
|
+
padding-inline-end: var(--safe-area-right);
|
|
152
161
|
border-radius: 0;
|
|
153
162
|
border-width: 0;
|
|
154
163
|
border-inline-start-width: 1px;
|
|
@@ -162,16 +171,16 @@ html:has(.ui-modal.is-open) {
|
|
|
162
171
|
}
|
|
163
172
|
|
|
164
173
|
/* Comfortable hit target on coarse pointers — this bespoke close button measured
|
|
165
|
-
~26px on touch, below the
|
|
166
|
-
|
|
174
|
+
~26px on touch, below the `--tap-target` floor the rest of the button family
|
|
175
|
+
meets. Scoped to coarse so the fine-pointer (mouse) rendering, which
|
|
167
176
|
already clears 24×24, is unchanged; centre the glyph in the enlarged box. */
|
|
168
177
|
@media (pointer: coarse) {
|
|
169
178
|
.ui-modal__close {
|
|
170
179
|
align-items: center;
|
|
171
180
|
display: inline-flex;
|
|
172
181
|
justify-content: center;
|
|
173
|
-
min-block-size:
|
|
174
|
-
min-inline-size:
|
|
182
|
+
min-block-size: var(--tap-target);
|
|
183
|
+
min-inline-size: var(--tap-target);
|
|
175
184
|
}
|
|
176
185
|
}
|
|
177
186
|
|
|
@@ -187,7 +196,12 @@ html:has(.ui-modal.is-open) {
|
|
|
187
196
|
color: var(--text);
|
|
188
197
|
max-block-size: 100vh;
|
|
189
198
|
max-inline-size: 100vw;
|
|
190
|
-
|
|
199
|
+
|
|
200
|
+
/* Deliberately full-bleed — the dimmed ground SHOULD cover the whole screen,
|
|
201
|
+
including the cutout. Only the insets keep the carousel's own controls out
|
|
202
|
+
from under it. */
|
|
203
|
+
padding: var(--safe-area-top) var(--safe-area-right) var(--safe-area-bottom)
|
|
204
|
+
var(--safe-area-left);
|
|
191
205
|
inline-size: 100vw;
|
|
192
206
|
}
|
|
193
207
|
|
package/css/primitives.css
CHANGED
|
@@ -423,6 +423,53 @@
|
|
|
423
423
|
padding: 0.85rem 1.4rem;
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
+
/* Dense — for bars whose HEIGHT is the constraint: a pane/node title bar, a
|
|
427
|
+
packed toolbar, a table row's actions. It lowers only the *visual* floor, to
|
|
428
|
+
the WCAG 2.5.8 minimum, and deliberately declares no coarse-pointer opinion:
|
|
429
|
+
the `@media (pointer: coarse)` block below matches at equal specificity and
|
|
430
|
+
later in the file, so it still floats a dense control to the full
|
|
431
|
+
`--tap-target`. Shrinking a control for a mouse therefore never shrinks it
|
|
432
|
+
for a finger — which is the reason this is a separate tier and not just a
|
|
433
|
+
smaller `--sm`. */
|
|
434
|
+
.ui-button--dense {
|
|
435
|
+
gap: 0.25rem;
|
|
436
|
+
min-block-size: var(--tap-target-min);
|
|
437
|
+
min-inline-size: var(--tap-target-min);
|
|
438
|
+
padding: 0.2rem 0.35rem;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/* Keep the square icon well; padding is safe to raise specificity on because
|
|
442
|
+
it is not the tap floor. */
|
|
443
|
+
.ui-button--dense.ui-button--icon {
|
|
444
|
+
padding: 0;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/* The label slot. An icon button still needs its words — for the accessible
|
|
448
|
+
name, and for the test/automation selector that reads the button by its
|
|
449
|
+
text — but not the pixels. Wrapping the text in this slot lets ONE markup
|
|
450
|
+
shape serve both: the button keeps a real text node, and `--icon` is what
|
|
451
|
+
decides whether it is painted. Without it, an icon-only button has to choose
|
|
452
|
+
between an empty accessible name and a title-bar-inflating word.
|
|
453
|
+
|
|
454
|
+
The hiding technique matches `.ui-visually-hidden` (base.css) rather than
|
|
455
|
+
`display: none`, which would take the text out of the accessible name too —
|
|
456
|
+
the exact opposite of the point. */
|
|
457
|
+
.ui-button__label {
|
|
458
|
+
min-inline-size: 0;
|
|
459
|
+
overflow: hidden;
|
|
460
|
+
text-overflow: ellipsis;
|
|
461
|
+
white-space: nowrap;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.ui-button--icon .ui-button__label {
|
|
465
|
+
clip-path: inset(50%);
|
|
466
|
+
block-size: 1px;
|
|
467
|
+
overflow: hidden;
|
|
468
|
+
position: absolute;
|
|
469
|
+
white-space: nowrap;
|
|
470
|
+
inline-size: 1px;
|
|
471
|
+
}
|
|
472
|
+
|
|
426
473
|
.ui-button:disabled,
|
|
427
474
|
.ui-button[aria-disabled='true'] {
|
|
428
475
|
cursor: not-allowed;
|
|
@@ -539,11 +586,11 @@
|
|
|
539
586
|
/* Standalone CTA links are tap targets, not inline prose links: on a coarse
|
|
540
587
|
pointer float them to the WCAG 2.5.8 AA 24px floor (the 2.5.8 inline-link
|
|
541
588
|
exception doesn't cover a block-level call-to-action, which is what these
|
|
542
|
-
are). Buttons already
|
|
589
|
+
are). Buttons already float to the full `--tap-target` on coarse pointers. */
|
|
543
590
|
@media (pointer: coarse) {
|
|
544
591
|
.ui-link--arrow,
|
|
545
592
|
.ui-link--cta {
|
|
546
|
-
min-block-size:
|
|
593
|
+
min-block-size: var(--tap-target-min);
|
|
547
594
|
}
|
|
548
595
|
}
|
|
549
596
|
|
|
@@ -743,16 +790,23 @@
|
|
|
743
790
|
}
|
|
744
791
|
}
|
|
745
792
|
|
|
746
|
-
/* --- Touch: comfortable hit targets on coarse pointers
|
|
793
|
+
/* --- Touch: comfortable hit targets on coarse pointers.
|
|
794
|
+
|
|
795
|
+
The floor is `--tap-target`, not a bare rem. Bronto's own base sets
|
|
796
|
+
`html { font-size: 0.9375rem }`, so the 2.9rem this family used to carry
|
|
797
|
+
resolved to 43.5px — half a pixel under the 44px WCAG 2.5.5 / platform-HIG
|
|
798
|
+
target, and under any host that shrinks the root further it drifted further
|
|
799
|
+
still. The token clamps in px against the rem so the standard holds however
|
|
800
|
+
the root is re-pointed. --- */
|
|
747
801
|
|
|
748
802
|
@media (pointer: coarse) {
|
|
749
803
|
.ui-button {
|
|
750
|
-
min-block-size:
|
|
751
|
-
min-inline-size:
|
|
804
|
+
min-block-size: var(--tap-target);
|
|
805
|
+
min-inline-size: var(--tap-target);
|
|
752
806
|
}
|
|
753
807
|
|
|
754
808
|
.ui-button--icon {
|
|
755
|
-
min-inline-size:
|
|
809
|
+
min-inline-size: var(--tap-target);
|
|
756
810
|
}
|
|
757
811
|
}
|
|
758
812
|
|
|
@@ -774,6 +828,47 @@
|
|
|
774
828
|
margin: 0.3rem 0 0;
|
|
775
829
|
}
|
|
776
830
|
|
|
831
|
+
/* Slots. Without them every empty surface in an app re-invents the same three
|
|
832
|
+
parts under a different name, and they drift: one has a glyph, the next has
|
|
833
|
+
two sentences in the same weight, a third is a bare <p>. The shape is always
|
|
834
|
+
mark → one sentence in full ink → a quieter follow-up. */
|
|
835
|
+
.ui-empty-state__glyph {
|
|
836
|
+
display: block;
|
|
837
|
+
font-size: 1.5rem;
|
|
838
|
+
line-height: 1;
|
|
839
|
+
opacity: 0.5;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/* The one sentence that says what is missing. Full ink — it is the message,
|
|
843
|
+
not a caption, and the container's --text-dim would bury it. */
|
|
844
|
+
.ui-empty-state__lead {
|
|
845
|
+
color: var(--text);
|
|
846
|
+
margin: 0;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
.ui-empty-state__hint {
|
|
850
|
+
font-size: var(--text-2xs);
|
|
851
|
+
margin: 0;
|
|
852
|
+
opacity: 0.8;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/* An empty state REPORTS absence; an invite OFFERS the next action. That is a
|
|
856
|
+
different job, so it gets a different frame: no dashed box (there is nothing
|
|
857
|
+
to outline — the surface itself is the thing you are being invited into) and
|
|
858
|
+
it centres in whatever block space it is given rather than sitting as a
|
|
859
|
+
card. Use it for a surface the user is meant to fill, and keep the plain
|
|
860
|
+
`.ui-empty-state` for a region that simply has no rows today. */
|
|
861
|
+
.ui-empty-state--invite {
|
|
862
|
+
align-items: center;
|
|
863
|
+
border: 0;
|
|
864
|
+
display: flex;
|
|
865
|
+
flex-direction: column;
|
|
866
|
+
gap: var(--space-2xs);
|
|
867
|
+
justify-content: center;
|
|
868
|
+
min-block-size: 100%;
|
|
869
|
+
padding: var(--space-sm);
|
|
870
|
+
}
|
|
871
|
+
|
|
777
872
|
/* --- Keyboard key — inline glyph for shortcut hints. Wrap a <kbd>. --- */
|
|
778
873
|
|
|
779
874
|
.ui-kbd {
|
package/css/site.css
CHANGED
|
@@ -41,8 +41,10 @@
|
|
|
41
41
|
z-index: var(--z-toast);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/* The skip link is the FIRST thing a keyboard or screen-reader user reaches, so
|
|
45
|
+
landing it under a display cutout is the worst place to lose a control. */
|
|
44
46
|
.ui-skiplink:focus {
|
|
45
|
-
inset-block-start: var(--space-md);
|
|
47
|
+
inset-block-start: max(var(--space-md), var(--safe-area-top));
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
/* --- Header — brand · nav · actions --- */
|
|
@@ -60,6 +62,10 @@
|
|
|
60
62
|
/* Structural sticky only — the floating-card skin (radius/shadow/panel
|
|
61
63
|
bg) is consumer identity, not a framework concern; layer it yourself. */
|
|
62
64
|
.ui-siteheader--sticky {
|
|
65
|
+
/* Only the sticky variant needs this: a header in normal flow already sits
|
|
66
|
+
below the cutout. Pad rather than offset, so the header's own border and
|
|
67
|
+
any consumer background still reach the top of the screen. */
|
|
68
|
+
padding-block-start: max(var(--space-sm), var(--safe-area-top));
|
|
63
69
|
position: sticky;
|
|
64
70
|
inset-block-start: 0;
|
|
65
71
|
z-index: var(--z-sticky);
|
|
@@ -184,7 +190,7 @@
|
|
|
184
190
|
|
|
185
191
|
@media (pointer: coarse) {
|
|
186
192
|
.ui-sitenav a {
|
|
187
|
-
min-block-size:
|
|
193
|
+
min-block-size: var(--tap-target);
|
|
188
194
|
padding-inline: 0.9rem;
|
|
189
195
|
}
|
|
190
196
|
}
|
|
@@ -256,7 +262,7 @@
|
|
|
256
262
|
|
|
257
263
|
@media (pointer: coarse) {
|
|
258
264
|
.ui-sitemenu > summary {
|
|
259
|
-
min-block-size:
|
|
265
|
+
min-block-size: var(--tap-target);
|
|
260
266
|
padding-inline: 0.9rem;
|
|
261
267
|
}
|
|
262
268
|
}
|
|
@@ -301,8 +307,8 @@
|
|
|
301
307
|
align-items: center;
|
|
302
308
|
display: inline-flex;
|
|
303
309
|
justify-content: center;
|
|
304
|
-
min-block-size:
|
|
305
|
-
min-inline-size:
|
|
310
|
+
min-block-size: var(--tap-target-min);
|
|
311
|
+
min-inline-size: var(--tap-target-min);
|
|
306
312
|
}
|
|
307
313
|
}
|
|
308
314
|
|
package/css/skins.css
CHANGED
|
@@ -4,43 +4,107 @@
|
|
|
4
4
|
* Optional display colorways (ADR-0001). OPT-IN: imported on demand via
|
|
5
5
|
* `@ponchia/ui/css/skins.css`, never part of the default bundle. Apply with
|
|
6
6
|
* `data-bronto-skin="amber-crt | e-ink | phosphor-green"` on :root / <html> (a
|
|
7
|
-
* root-level choice like data-theme), re-pointing the one accent
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* root-level choice like data-theme), re-pointing the one accent and the
|
|
8
|
+
* neutral canvas (ADR-0001 step 4, amended in 0.8.0). The accent's derived
|
|
9
|
+
* family recomputes from the live var(--accent); STATUS COLOURS ARE UNTOUCHED,
|
|
10
|
+
* because a warning must look like a warning in every skin. The canvas keeps
|
|
11
|
+
* each core neutral's OKLCH lightness and only moves its hue, so contrast is
|
|
12
|
+
* preserved by construction — and check-contrast.mjs re-measures the FULL
|
|
13
|
+
* pairing table per skin per theme rather than trusting that. */
|
|
10
14
|
|
|
11
15
|
/* Amber CRT */
|
|
12
16
|
:root[data-bronto-skin='amber-crt'] {
|
|
13
17
|
--accent: oklch(52% 0.11 67deg);
|
|
14
18
|
--dotmatrix-pulse-min: 0.35;
|
|
19
|
+
--bg: oklch(96.66% 0.02 67deg);
|
|
20
|
+
--bg-elevated: oklch(98.781% 0.022 67deg);
|
|
21
|
+
--panel: oklch(100% 0.024 67deg);
|
|
22
|
+
--panel-strong: oklch(100% 0.026 67deg);
|
|
23
|
+
--panel-soft: oklch(94.253% 0.026 67deg);
|
|
24
|
+
--line: oklch(88.11% 0.035 67deg);
|
|
25
|
+
--line-strong: oklch(72.983% 0.04 67deg);
|
|
26
|
+
--text: oklch(14.479% 0.03 67deg);
|
|
27
|
+
--text-soft: oklch(32.825% 0.03 67deg);
|
|
28
|
+
--text-dim: oklch(51.571% 0.028 67deg);
|
|
15
29
|
}
|
|
16
30
|
|
|
17
31
|
:root[data-theme='dark'][data-bronto-skin='amber-crt'] {
|
|
18
32
|
--accent: oklch(82% 0.15 82deg);
|
|
19
33
|
--dotmatrix-glow: 0.4em;
|
|
20
34
|
--dotmatrix-pulse-min: 0.3;
|
|
35
|
+
--bg: oklch(18.22% 0.02 82deg);
|
|
36
|
+
--bg-elevated: oklch(20.904% 0.022 82deg);
|
|
37
|
+
--panel: oklch(22.645% 0.024 82deg);
|
|
38
|
+
--panel-strong: oklch(25.196% 0.026 82deg);
|
|
39
|
+
--panel-soft: oklch(26.032% 0.026 82deg);
|
|
40
|
+
--line: oklch(34.07% 0.035 82deg);
|
|
41
|
+
--line-strong: oklch(44.953% 0.04 82deg);
|
|
42
|
+
--text: oklch(92.494% 0.03 82deg);
|
|
43
|
+
--text-soft: oklch(83.279% 0.03 82deg);
|
|
44
|
+
--text-dim: oklch(70.576% 0.028 82deg);
|
|
21
45
|
}
|
|
22
46
|
|
|
23
47
|
/* E-ink */
|
|
24
48
|
:root[data-bronto-skin='e-ink'] {
|
|
25
49
|
--accent: oklch(34% 0.012 250deg);
|
|
26
50
|
--dotmatrix-reveal-step: 0ms;
|
|
51
|
+
--bg: oklch(96.66% 0.005 250deg);
|
|
52
|
+
--bg-elevated: oklch(98.781% 0.006 250deg);
|
|
53
|
+
--panel: oklch(100% 0.006 250deg);
|
|
54
|
+
--panel-strong: oklch(100% 0.007 250deg);
|
|
55
|
+
--panel-soft: oklch(94.253% 0.007 250deg);
|
|
56
|
+
--line: oklch(88.11% 0.009 250deg);
|
|
57
|
+
--line-strong: oklch(72.983% 0.01 250deg);
|
|
58
|
+
--text: oklch(14.479% 0.008 250deg);
|
|
59
|
+
--text-soft: oklch(32.825% 0.008 250deg);
|
|
60
|
+
--text-dim: oklch(51.571% 0.007 250deg);
|
|
27
61
|
}
|
|
28
62
|
|
|
29
63
|
:root[data-theme='dark'][data-bronto-skin='e-ink'] {
|
|
30
64
|
--accent: oklch(84% 0.008 250deg);
|
|
31
65
|
--dotmatrix-reveal-step: 0ms;
|
|
66
|
+
--bg: oklch(18.22% 0.005 250deg);
|
|
67
|
+
--bg-elevated: oklch(20.904% 0.006 250deg);
|
|
68
|
+
--panel: oklch(22.645% 0.006 250deg);
|
|
69
|
+
--panel-strong: oklch(25.196% 0.007 250deg);
|
|
70
|
+
--panel-soft: oklch(26.032% 0.007 250deg);
|
|
71
|
+
--line: oklch(34.07% 0.009 250deg);
|
|
72
|
+
--line-strong: oklch(44.953% 0.01 250deg);
|
|
73
|
+
--text: oklch(92.494% 0.008 250deg);
|
|
74
|
+
--text-soft: oklch(83.279% 0.008 250deg);
|
|
75
|
+
--text-dim: oklch(70.576% 0.007 250deg);
|
|
32
76
|
}
|
|
33
77
|
|
|
34
78
|
/* Phosphor Green */
|
|
35
79
|
:root[data-bronto-skin='phosphor-green'] {
|
|
36
80
|
--accent: oklch(52% 0.13 150deg);
|
|
37
81
|
--dotmatrix-pulse-min: 0.35;
|
|
82
|
+
--bg: oklch(96.66% 0.02 150deg);
|
|
83
|
+
--bg-elevated: oklch(98.781% 0.022 150deg);
|
|
84
|
+
--panel: oklch(100% 0.024 150deg);
|
|
85
|
+
--panel-strong: oklch(100% 0.026 150deg);
|
|
86
|
+
--panel-soft: oklch(94.253% 0.026 150deg);
|
|
87
|
+
--line: oklch(88.11% 0.035 150deg);
|
|
88
|
+
--line-strong: oklch(72.983% 0.04 150deg);
|
|
89
|
+
--text: oklch(14.479% 0.03 150deg);
|
|
90
|
+
--text-soft: oklch(32.825% 0.03 150deg);
|
|
91
|
+
--text-dim: oklch(51.571% 0.028 150deg);
|
|
38
92
|
}
|
|
39
93
|
|
|
40
94
|
:root[data-theme='dark'][data-bronto-skin='phosphor-green'] {
|
|
41
95
|
--accent: oklch(84% 0.19 150deg);
|
|
42
96
|
--dotmatrix-glow: 0.4em;
|
|
43
97
|
--dotmatrix-pulse-min: 0.3;
|
|
98
|
+
--bg: oklch(18.22% 0.02 150deg);
|
|
99
|
+
--bg-elevated: oklch(20.904% 0.022 150deg);
|
|
100
|
+
--panel: oklch(22.645% 0.024 150deg);
|
|
101
|
+
--panel-strong: oklch(25.196% 0.026 150deg);
|
|
102
|
+
--panel-soft: oklch(26.032% 0.026 150deg);
|
|
103
|
+
--line: oklch(34.07% 0.035 150deg);
|
|
104
|
+
--line-strong: oklch(44.953% 0.04 150deg);
|
|
105
|
+
--text: oklch(92.494% 0.03 150deg);
|
|
106
|
+
--text-soft: oklch(83.279% 0.03 150deg);
|
|
107
|
+
--text-dim: oklch(70.576% 0.028 150deg);
|
|
44
108
|
}
|
|
45
109
|
|
|
46
110
|
@media (prefers-color-scheme: dark) {
|
|
@@ -48,16 +112,46 @@
|
|
|
48
112
|
--accent: oklch(82% 0.15 82deg);
|
|
49
113
|
--dotmatrix-glow: 0.4em;
|
|
50
114
|
--dotmatrix-pulse-min: 0.3;
|
|
115
|
+
--bg: oklch(18.22% 0.02 82deg);
|
|
116
|
+
--bg-elevated: oklch(20.904% 0.022 82deg);
|
|
117
|
+
--panel: oklch(22.645% 0.024 82deg);
|
|
118
|
+
--panel-strong: oklch(25.196% 0.026 82deg);
|
|
119
|
+
--panel-soft: oklch(26.032% 0.026 82deg);
|
|
120
|
+
--line: oklch(34.07% 0.035 82deg);
|
|
121
|
+
--line-strong: oklch(44.953% 0.04 82deg);
|
|
122
|
+
--text: oklch(92.494% 0.03 82deg);
|
|
123
|
+
--text-soft: oklch(83.279% 0.03 82deg);
|
|
124
|
+
--text-dim: oklch(70.576% 0.028 82deg);
|
|
51
125
|
}
|
|
52
126
|
|
|
53
127
|
:root:not([data-theme='light'])[data-bronto-skin='e-ink'] {
|
|
54
128
|
--accent: oklch(84% 0.008 250deg);
|
|
55
129
|
--dotmatrix-reveal-step: 0ms;
|
|
130
|
+
--bg: oklch(18.22% 0.005 250deg);
|
|
131
|
+
--bg-elevated: oklch(20.904% 0.006 250deg);
|
|
132
|
+
--panel: oklch(22.645% 0.006 250deg);
|
|
133
|
+
--panel-strong: oklch(25.196% 0.007 250deg);
|
|
134
|
+
--panel-soft: oklch(26.032% 0.007 250deg);
|
|
135
|
+
--line: oklch(34.07% 0.009 250deg);
|
|
136
|
+
--line-strong: oklch(44.953% 0.01 250deg);
|
|
137
|
+
--text: oklch(92.494% 0.008 250deg);
|
|
138
|
+
--text-soft: oklch(83.279% 0.008 250deg);
|
|
139
|
+
--text-dim: oklch(70.576% 0.007 250deg);
|
|
56
140
|
}
|
|
57
141
|
|
|
58
142
|
:root:not([data-theme='light'])[data-bronto-skin='phosphor-green'] {
|
|
59
143
|
--accent: oklch(84% 0.19 150deg);
|
|
60
144
|
--dotmatrix-glow: 0.4em;
|
|
61
145
|
--dotmatrix-pulse-min: 0.3;
|
|
146
|
+
--bg: oklch(18.22% 0.02 150deg);
|
|
147
|
+
--bg-elevated: oklch(20.904% 0.022 150deg);
|
|
148
|
+
--panel: oklch(22.645% 0.024 150deg);
|
|
149
|
+
--panel-strong: oklch(25.196% 0.026 150deg);
|
|
150
|
+
--panel-soft: oklch(26.032% 0.026 150deg);
|
|
151
|
+
--line: oklch(34.07% 0.035 150deg);
|
|
152
|
+
--line-strong: oklch(44.953% 0.04 150deg);
|
|
153
|
+
--text: oklch(92.494% 0.03 150deg);
|
|
154
|
+
--text-soft: oklch(83.279% 0.03 150deg);
|
|
155
|
+
--text-dim: oklch(70.576% 0.028 150deg);
|
|
62
156
|
}
|
|
63
157
|
}
|
package/css/state.css
CHANGED
|
@@ -223,6 +223,153 @@
|
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
/* ==========================================================================
|
|
227
|
+
Severity — the second axis of system state.
|
|
228
|
+
|
|
229
|
+
`.ui-state` answers "what is this thing doing". This answers "how bad is
|
|
230
|
+
it". Bronto already shipped the TONES (`--danger`, `--warning`, `--info`,
|
|
231
|
+
`--success`) as per-component modifiers, but never the SCALE: the tier names,
|
|
232
|
+
their order, and the attribute that carries them. So every consumer invents
|
|
233
|
+
the ladder, and inside a single app it drifts — one surface saying
|
|
234
|
+
`critical|error|warning|note`, the next `bad|warn`, a third
|
|
235
|
+
`critical|warning|info|ok`, under two different attribute names. Findings
|
|
236
|
+
then do not sort against alerts, and a filter written for one list silently
|
|
237
|
+
misses the other.
|
|
238
|
+
|
|
239
|
+
THE LADDER, worst to best:
|
|
240
|
+
|
|
241
|
+
critical broken now, and still losing something
|
|
242
|
+
error something failed; it is not currently getting worse
|
|
243
|
+
warning a threshold was crossed; nothing has failed yet
|
|
244
|
+
notice worth reading, no action implied
|
|
245
|
+
ok checked and healthy — an ASSERTION, not the absence of news
|
|
246
|
+
unknown not measured, stale, or the check itself failed
|
|
247
|
+
|
|
248
|
+
`unknown` sits OUTSIDE the ordering deliberately. It is not "slightly worse
|
|
249
|
+
than ok", it is "we do not know", and collapsing it into `ok` is how a dead
|
|
250
|
+
collector reads as a healthy system.
|
|
251
|
+
|
|
252
|
+
The level is carried by `data-level` — one attribute name, so the same
|
|
253
|
+
selector works on a chip, a row, a dot, or the host's own element:
|
|
254
|
+
|
|
255
|
+
<span class="ui-severity" data-level="critical">Critical</span>
|
|
256
|
+
<li class="ui-severity-row" data-level="warning">…</li>
|
|
257
|
+
|
|
258
|
+
BOUNDARY: the host owns thresholds, ranking, filtering, and the wording of
|
|
259
|
+
every label. Colour is never the only channel (WCAG 1.4.1) — `.ui-severity`
|
|
260
|
+
carries an author-written label, and `.ui-severity-dot` is only for rows that
|
|
261
|
+
ALSO name their level in text.
|
|
262
|
+
========================================================================== */
|
|
263
|
+
|
|
264
|
+
/* Declared once so a host putting the level on its own element gets the same
|
|
265
|
+
mapping from `data-level` + `var(--severity-tone)`, without copying a table. */
|
|
266
|
+
.ui-severity,
|
|
267
|
+
.ui-severity-row,
|
|
268
|
+
.ui-severity-dot {
|
|
269
|
+
--severity-tone: var(--text-dim);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
:is(.ui-severity, .ui-severity-row, .ui-severity-dot):is(
|
|
273
|
+
[data-level='critical'],
|
|
274
|
+
[data-level='error']
|
|
275
|
+
) {
|
|
276
|
+
--severity-tone: var(--danger);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
:is(.ui-severity, .ui-severity-row, .ui-severity-dot)[data-level='warning'] {
|
|
280
|
+
--severity-tone: var(--warning);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
:is(.ui-severity, .ui-severity-row, .ui-severity-dot)[data-level='notice'] {
|
|
284
|
+
--severity-tone: var(--info);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
:is(.ui-severity, .ui-severity-row, .ui-severity-dot)[data-level='ok'] {
|
|
288
|
+
--severity-tone: var(--success);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* `unknown` keeps the neutral default tone — it must not read as a verdict. */
|
|
292
|
+
|
|
293
|
+
/* The chip — a short uppercase tier label, tinted rather than filled, because
|
|
294
|
+
a list of findings is mostly chips and filled ones turn it into stripes.
|
|
295
|
+
`critical` is the exception and fills: in a scrolling list the worst tier has
|
|
296
|
+
to survive peripheral vision. */
|
|
297
|
+
.ui-severity {
|
|
298
|
+
background: color-mix(in oklch, var(--severity-tone) 16%, transparent);
|
|
299
|
+
border-radius: var(--radius-pill);
|
|
300
|
+
color: var(--severity-tone);
|
|
301
|
+
display: inline-block;
|
|
302
|
+
flex: none;
|
|
303
|
+
font-family: var(--mono);
|
|
304
|
+
font-size: var(--text-2xs);
|
|
305
|
+
font-weight: 700;
|
|
306
|
+
letter-spacing: var(--tracking-wide);
|
|
307
|
+
line-height: 1.2;
|
|
308
|
+
padding: 0.1rem 0.4rem;
|
|
309
|
+
text-transform: uppercase;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.ui-severity[data-level='critical'] {
|
|
313
|
+
background: var(--danger);
|
|
314
|
+
color: var(--button-text);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/* A tint over an unknown host surface cannot be contrast-gated, so the neutral
|
|
318
|
+
tier keeps a border instead and stays legible anywhere. */
|
|
319
|
+
.ui-severity[data-level='unknown'] {
|
|
320
|
+
background: none;
|
|
321
|
+
border: 1px solid var(--line-strong);
|
|
322
|
+
padding-block: calc(0.1rem - 1px);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* The dot — for dense rows where a chip costs more width than the level is
|
|
326
|
+
worth. An accelerator for scanning, not the label. */
|
|
327
|
+
.ui-severity-dot {
|
|
328
|
+
background: var(--severity-tone);
|
|
329
|
+
border-radius: 50%;
|
|
330
|
+
block-size: 0.5rem;
|
|
331
|
+
display: inline-block;
|
|
332
|
+
flex: none;
|
|
333
|
+
inline-size: 0.5rem;
|
|
334
|
+
print-color-adjust: exact;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.ui-severity-dot[data-level='unknown'] {
|
|
338
|
+
background: none;
|
|
339
|
+
box-shadow: inset 0 0 0 1px var(--line-strong);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/* The row — a list item that carries a level. The inline-start gutter is what
|
|
343
|
+
makes a long list readable without reading it. */
|
|
344
|
+
.ui-severity-row {
|
|
345
|
+
align-items: baseline;
|
|
346
|
+
border-block-end: 1px solid var(--line);
|
|
347
|
+
border-inline-start: 2px solid var(--severity-tone);
|
|
348
|
+
display: flex;
|
|
349
|
+
gap: var(--space-xs);
|
|
350
|
+
padding: var(--space-2xs) var(--space-xs);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.ui-severity-row:last-child {
|
|
354
|
+
border-block-end: 0;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/* Gives up space first, so the level and any trailing metadata stay put. */
|
|
358
|
+
.ui-severity-row__title {
|
|
359
|
+
flex: 1 1 auto;
|
|
360
|
+
min-inline-size: 0;
|
|
361
|
+
overflow: hidden;
|
|
362
|
+
text-overflow: ellipsis;
|
|
363
|
+
white-space: nowrap;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.ui-severity-row__meta {
|
|
367
|
+
color: var(--text-dim);
|
|
368
|
+
flex: none;
|
|
369
|
+
font-family: var(--mono);
|
|
370
|
+
font-size: var(--text-2xs);
|
|
371
|
+
}
|
|
372
|
+
|
|
226
373
|
/* Forced colours: the tone dot collapses to a system colour, so the
|
|
227
374
|
author-written label remains the state channel (it already must be). */
|
|
228
375
|
@media (forced-colors: active) {
|
|
@@ -237,4 +384,18 @@
|
|
|
237
384
|
.ui-job__bar {
|
|
238
385
|
background: CanvasText;
|
|
239
386
|
}
|
|
387
|
+
|
|
388
|
+
/* Same contract for severity: the tint and the gutter both vanish under a
|
|
389
|
+
replaced palette, so keep the structure and let the label carry meaning. */
|
|
390
|
+
.ui-severity {
|
|
391
|
+
border: 1px solid currentColor;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.ui-severity-row {
|
|
395
|
+
border-inline-start-color: CanvasText;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.ui-severity-dot {
|
|
399
|
+
background: CanvasText;
|
|
400
|
+
}
|
|
240
401
|
}
|