@tensor_1/tokens 2.47.0 → 2.49.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 (77) hide show
  1. package/build/css/components.css +1 -1
  2. package/build/css/styles.css +1 -1
  3. package/build/css/tokens.css +4 -0
  4. package/build/ts/tokens.d.ts +1 -1
  5. package/build/ts/tokens.js +8 -0
  6. package/package.json +1 -1
  7. package/styles/components/alert.css +6 -4
  8. package/styles/components/app-shell.css +40 -0
  9. package/styles/components/arrival.css +7 -1
  10. package/styles/components/attachment.css +4 -1
  11. package/styles/components/audit-log.css +29 -0
  12. package/styles/components/breadcrumbs.css +2 -0
  13. package/styles/components/busy-region.css +10 -2
  14. package/styles/components/button.css +7 -0
  15. package/styles/components/card.css +22 -0
  16. package/styles/components/change-highlight.css +58 -0
  17. package/styles/components/checkbox.css +18 -0
  18. package/styles/components/code-display.css +43 -0
  19. package/styles/components/combobox.css +4 -1
  20. package/styles/components/command-palette.css +16 -2
  21. package/styles/components/contact-card.css +17 -6
  22. package/styles/components/count.css +23 -0
  23. package/styles/components/data-workspace.css +40 -0
  24. package/styles/components/date-picker.css +14 -1
  25. package/styles/components/detail-layout.css +9 -0
  26. package/styles/components/diff-viewer.css +5 -0
  27. package/styles/components/display-preferences.css +38 -0
  28. package/styles/components/drag.css +63 -0
  29. package/styles/components/drawer.css +6 -5
  30. package/styles/components/editorial-home.css +24 -2
  31. package/styles/components/flow.css +5 -0
  32. package/styles/components/heartbeat.css +8 -0
  33. package/styles/components/icon.css +14 -0
  34. package/styles/components/inline-edit.css +19 -3
  35. package/styles/components/input.css +62 -0
  36. package/styles/components/kanban.css +10 -1
  37. package/styles/components/kbd.css +25 -0
  38. package/styles/components/lightbox.css +13 -0
  39. package/styles/components/list-motion.css +27 -0
  40. package/styles/components/list.css +2 -0
  41. package/styles/components/match-review.css +40 -0
  42. package/styles/components/media.css +4 -2
  43. package/styles/components/metric-input.css +3 -1
  44. package/styles/components/modal.css +10 -5
  45. package/styles/components/page-header.css +12 -0
  46. package/styles/components/pagination.css +2 -0
  47. package/styles/components/peek.css +6 -5
  48. package/styles/components/popover.css +14 -10
  49. package/styles/components/print-layout.css +177 -0
  50. package/styles/components/progress.css +7 -0
  51. package/styles/components/qr-code.css +30 -0
  52. package/styles/components/qr-scanner.css +61 -0
  53. package/styles/components/relationship-graph.css +71 -15
  54. package/styles/components/rule-builder.css +2 -1
  55. package/styles/components/save-status.css +5 -0
  56. package/styles/components/scheduling-calendar.css +82 -1
  57. package/styles/components/search-suggest.css +8 -6
  58. package/styles/components/segmented.css +31 -1
  59. package/styles/components/select.css +20 -2
  60. package/styles/components/sidebar.css +51 -1
  61. package/styles/components/sortable-list.css +42 -0
  62. package/styles/components/spinner.css +5 -0
  63. package/styles/components/stat-tile.css +36 -12
  64. package/styles/components/states.css +50 -0
  65. package/styles/components/status-pill.css +28 -6
  66. package/styles/components/steps.css +2 -0
  67. package/styles/components/table-toolbar.css +1 -1
  68. package/styles/components/table.css +48 -28
  69. package/styles/components/tabs.css +32 -3
  70. package/styles/components/tag-input.css +21 -0
  71. package/styles/components/textarea.css +3 -0
  72. package/styles/components/toast.css +23 -8
  73. package/styles/components/tooltip.css +7 -3
  74. package/styles/components/tree.css +3 -1
  75. package/styles/components/view-menu.css +3 -0
  76. package/styles/motion.css +23 -4
  77. package/tokens/base/effect.json +6 -0
@@ -6,21 +6,22 @@
6
6
  border: 0; border-left: 1px solid var(--uix-border); border-radius: 0;
7
7
  background: var(--uix-surface); box-shadow: var(--uix-shadow-overlay);
8
8
  transform: translateX(100%);
9
- transition: transform var(--uix-dur-slow) var(--uix-ease-out),
10
- overlay var(--uix-dur-slow) allow-discrete, display var(--uix-dur-slow) allow-discrete;
9
+ /* the closed state's durations are the exit's (--uix-dur-exit-slow); [open] carries the enter */
10
+ transition: transform var(--uix-dur-exit-slow) var(--uix-ease-out),
11
+ overlay var(--uix-dur-exit-slow) allow-discrete, display var(--uix-dur-exit-slow) allow-discrete;
11
12
  /* Author display values for BOTH states are required for the slide-in to animate: the
12
13
  allow-discrete `display` transition only engages with author values, and that gating also
13
14
  lets the @starting-style entrance fire. With only the UA dialog display, the panel stays
14
15
  parked at translateX(100%). Closed = none; open = flex. */
15
16
  display: none;
16
17
  }
17
- .uix-peek[open] { transform: none; display: flex; flex-direction: column; }
18
+ .uix-peek[open] { transform: none; display: flex; flex-direction: column; transition-duration: var(--uix-dur-slow); }
18
19
  @starting-style { .uix-peek[open] { transform: translateX(100%); } }
19
20
  .uix-peek::backdrop {
20
21
  background: var(--uix-scrim); opacity: 0;
21
- transition: opacity var(--uix-dur-slow) var(--uix-ease-out), overlay var(--uix-dur-slow) allow-discrete, display var(--uix-dur-slow) allow-discrete;
22
+ transition: opacity var(--uix-dur-exit-slow) var(--uix-ease-out), overlay var(--uix-dur-exit-slow) allow-discrete, display var(--uix-dur-exit-slow) allow-discrete;
22
23
  }
23
- .uix-peek[open]::backdrop { opacity: 1; }
24
+ .uix-peek[open]::backdrop { opacity: 1; transition-duration: var(--uix-dur-slow); }
24
25
  @starting-style { .uix-peek[open]::backdrop { opacity: 0; } }
25
26
 
26
27
  .uix-peek__header { display: flex; align-items: center; gap: var(--uix-space-3); padding: var(--uix-space-4) var(--uix-space-4); border-bottom: 1px solid var(--uix-border); }
@@ -1,18 +1,18 @@
1
1
  /* uix popover — generic anchored surface via the native Popover API; animated with @starting-style.
2
- Enter/exit: fade + --uix-lift slide, always away from the anchor (the React Popover sets
3
- data-placement, and data-uix-placing holds the first frame until it is placed). */
2
+ Enter: fade + scale(.95 → 1) growing out of the trigger — useAnchoredPosition writes the anchor
3
+ point as --uix-anchor-origin — at --uix-dur-fast. Exit: the same, back in, at --uix-dur-exit-fast
4
+ (faster than the enter, still ease-out). data-uix-placing holds the first frame until it is placed;
5
+ a popover opened from the keyboard carries [data-uix-key-opened] and does not animate (motion.css). */
4
6
  @layer uix.components {
5
7
  .uix-popover { margin: 0; padding: var(--uix-space-2); background: var(--uix-surface-2); border: 1px solid var(--uix-border); border-radius: var(--uix-radius-md); box-shadow: var(--uix-shadow-popover); color: var(--uix-text); }
6
8
  [popover].uix-popover {
7
- --popover-from: translateY(calc(-1 * var(--uix-lift)));
8
- opacity: 0; transform: var(--popover-from);
9
- transition: opacity var(--uix-dur-fast) var(--uix-ease-out), transform var(--uix-dur-fast) var(--uix-ease-out),
10
- overlay var(--uix-dur-fast) allow-discrete, display var(--uix-dur-fast) allow-discrete;
9
+ --popover-from: scale(.95);
10
+ opacity: 0; transform: var(--popover-from); transform-origin: var(--uix-anchor-origin, top left);
11
+ /* the closed state's durations are the exit's */
12
+ transition: opacity var(--uix-dur-exit-fast) var(--uix-ease-out), transform var(--uix-dur-exit-fast) var(--uix-ease-out),
13
+ overlay var(--uix-dur-exit-fast) allow-discrete, display var(--uix-dur-exit-fast) allow-discrete;
11
14
  }
12
- [popover].uix-popover[data-placement^="top"] { --popover-from: translateY(var(--uix-lift)); }
13
- [popover].uix-popover[data-placement^="left"] { --popover-from: translateX(var(--uix-lift)); }
14
- [popover].uix-popover[data-placement^="right"] { --popover-from: translateX(calc(-1 * var(--uix-lift))); }
15
- [popover].uix-popover:popover-open { opacity: 1; transform: none; }
15
+ [popover].uix-popover:popover-open { opacity: 1; transform: none; transition-duration: var(--uix-dur-fast); }
16
16
  /* A closed popover is display:none by the UA stylesheet only, so a component that sets its
17
17
  own display (.uix-popconfirm, .uix-filter-popover: grid) stayed laid out while closed: an
18
18
  invisible fixed box at the viewport's top-left that took clicks (HAR-2029). The display
@@ -27,5 +27,9 @@
27
27
  .uix-popconfirm { display: grid; gap: var(--uix-space-2); width: max-content; max-width: min(20rem, calc(100vw - 2 * var(--uix-space-4))); padding: var(--uix-space-3); }
28
28
  .uix-popconfirm__title { font-weight: 600; color: var(--uix-text); }
29
29
  .uix-popconfirm__body { display: grid; gap: var(--uix-space-2); color: var(--uix-text-hushed); font-size: var(--uix-text-meta); }
30
+ /* content of its own, e.g. a required reason field (HAR-1831): the step takes its full width
31
+ so the field is not squeezed to the text's max-content */
32
+ .uix-popconfirm--content { width: min(20rem, calc(100vw - 2 * var(--uix-space-4))); }
33
+ .uix-popconfirm__content { display: grid; gap: var(--uix-space-2); min-width: 0; }
30
34
  .uix-popconfirm__actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--uix-space-2); margin-top: var(--uix-space-1); }
31
35
  }
@@ -0,0 +1,177 @@
1
+ /* uix print layout (HAR-986, HAR-1252) — React PrintLayout, PrintSignature, PrintPageBreak.
2
+ A document frame for the browser's own print: A4 or A5 named pages with the paper's margins,
3
+ a header and a footer that repeat on every page (a CSS table's header and footer groups, which
4
+ the browser repeats per page and which take their own room, so they never sit on the content),
5
+ "page / pages" in the bottom-right page margin, and black-on-white tokens inside the frame.
6
+
7
+ On screen the frame is a paper preview: the page at its true width and its print type size,
8
+ scaled as a whole (zoom, measured by PrintLayout) to the room it has, ink on paper in both
9
+ themes — what you see, and what fits on one page, is what prints.
10
+ Units: mm and pt are the paper's own units; they are not --uix-* scale values. */
11
+ @layer uix.components {
12
+ /* ── the paper ─────────────────────────────────────────────────────────────── */
13
+ @page uix-print-a4 { size: A4 portrait; margin: 16mm 16mm 18mm; @bottom-right { content: counter(page) " / " counter(pages); font-size: 8pt; color: var(--uix-print-ink-quiet); } }
14
+ @page uix-print-a4-landscape { size: A4 landscape; margin: 14mm 16mm 16mm; @bottom-right { content: counter(page) " / " counter(pages); font-size: 8pt; color: var(--uix-print-ink-quiet); } }
15
+ @page uix-print-a5 { size: A5 portrait; margin: 10mm 10mm 12mm; @bottom-right { content: counter(page) " / " counter(pages); font-size: 7pt; color: var(--uix-print-ink-quiet); } }
16
+ @page uix-print-a5-landscape { size: A5 landscape; margin: 10mm 12mm 12mm; @bottom-right { content: counter(page) " / " counter(pages); font-size: 7pt; color: var(--uix-print-ink-quiet); } }
17
+ @page uix-print-a4-plain { size: A4 portrait; margin: 16mm 16mm 18mm; }
18
+ @page uix-print-a4-landscape-plain { size: A4 landscape; margin: 14mm 16mm 16mm; }
19
+ @page uix-print-a5-plain { size: A5 portrait; margin: 10mm 10mm 12mm; }
20
+ @page uix-print-a5-landscape-plain { size: A5 landscape; margin: 10mm 12mm 12mm; }
21
+
22
+ .uix-print-layout {
23
+ /* the page box and its margins, for the screen preview (the same as the @page rules) */
24
+ --print-width: 210mm; --print-height: 297mm; --print-margin: 16mm 16mm 18mm;
25
+ /* everything inside reads ink on paper, in both themes, on screen and on paper */
26
+ --uix-text: var(--uix-print-ink);
27
+ --uix-text-muted: var(--uix-print-ink-quiet);
28
+ --uix-text-hushed: var(--uix-print-ink-quiet);
29
+ --uix-surface: var(--uix-print-paper);
30
+ --uix-surface-2: var(--uix-print-paper);
31
+ --uix-surface-3: var(--uix-print-paper-quiet);
32
+ --uix-bg-app: var(--uix-print-paper);
33
+ --uix-bg-subtle: var(--uix-print-paper-quiet);
34
+ --uix-bg-hover: var(--uix-print-paper-quiet);
35
+ --uix-border: var(--uix-print-line);
36
+ --uix-border-strong: var(--uix-print-ink-quiet);
37
+ --uix-link: var(--uix-print-ink);
38
+ --uix-accent-text: var(--uix-print-ink);
39
+ --uix-row: var(--uix-print-paper);
40
+ --uix-row-alt: var(--uix-print-paper-quiet);
41
+ --uix-row-selected-bg: var(--uix-print-paper-quiet);
42
+ --uix-row-pinned-bg: var(--uix-print-paper-quiet);
43
+ color-scheme: light;
44
+ box-sizing: border-box;
45
+ width: var(--print-width);
46
+ margin-inline: auto;
47
+ /* the type and the rows of the paper, on screen too */
48
+ font-size: 10pt;
49
+ line-height: 1.45;
50
+ --uix-row-h: var(--uix-row-h-compact);
51
+ background: var(--uix-print-paper);
52
+ color: var(--uix-print-ink);
53
+ box-shadow: var(--uix-shadow-md);
54
+ border: 1px solid var(--uix-print-line);
55
+ }
56
+ /* The dark theme's tints are dark: on the paper they read as quiet paper with ink text, so a
57
+ pill or a callout inside the document prints in black and white instead of a dark slab. */
58
+ :root:where(.dark,[data-theme="dark"]) .uix-print-layout {
59
+ --uix-brand-muted: var(--uix-print-paper-quiet);
60
+ --uix-neutral-bg: var(--uix-print-paper-quiet); --uix-neutral-text: var(--uix-print-ink); --uix-neutral-border: var(--uix-print-line);
61
+ --uix-info-bg: var(--uix-print-paper-quiet); --uix-info-text: var(--uix-print-ink);
62
+ --uix-success-bg: var(--uix-print-paper-quiet); --uix-success-text: var(--uix-print-ink);
63
+ --uix-warning-bg: var(--uix-print-paper-quiet); --uix-warning-text: var(--uix-print-ink); --uix-warning-border: var(--uix-print-line);
64
+ --uix-danger-bg: var(--uix-print-paper-quiet); --uix-danger-text: var(--uix-print-danger); --uix-danger-border: var(--uix-print-danger);
65
+ --uix-attention-bg: var(--uix-print-paper-quiet); --uix-attention-text: var(--uix-print-ink);
66
+ --uix-amber-bg: var(--uix-print-paper-quiet); --uix-amber-text: var(--uix-print-ink);
67
+ --uix-overdue-bg: var(--uix-print-paper-quiet); --uix-overdue-text: var(--uix-print-ink);
68
+ --uix-sev-4-bg: var(--uix-print-paper-quiet); --uix-sev-4-text: var(--uix-print-ink);
69
+ --uix-sev-5-bg: var(--uix-print-paper-quiet); --uix-sev-5-text: var(--uix-print-ink);
70
+ }
71
+ .uix-print-layout[data-size="A4"][data-orientation="landscape"] { --print-width: 297mm; --print-height: 210mm; --print-margin: 14mm 16mm 16mm; }
72
+ .uix-print-layout[data-size="A5"] { --print-width: 148mm; --print-height: 210mm; --print-margin: 10mm 10mm 12mm; font-size: 9pt; }
73
+ @media screen { .uix-print-layout { zoom: var(--uix-print-preview-scale, 1); } }
74
+ .uix-print-layout[data-size="A5"][data-orientation="landscape"] { --print-width: 210mm; --print-height: 148mm; --print-margin: 10mm 12mm 12mm; }
75
+ /* The preview box: the paper's width (set inline by PrintLayout), or its container's when that
76
+ is narrower; the page inside is scaled to it. */
77
+ .uix-print-preview { box-sizing: border-box; max-inline-size: 100%; margin-inline: auto; }
78
+ .uix-print-preview + .uix-print-preview { margin-top: var(--uix-space-6); }
79
+
80
+ /* The page is a <table> for print's sake (a repeated thead and tfoot); on screen it is a column. */
81
+ .uix-print-layout__page {
82
+ box-sizing: border-box;
83
+ display: flex;
84
+ flex-direction: column;
85
+ gap: var(--uix-space-4);
86
+ width: 100%;
87
+ min-height: var(--print-height);
88
+ padding: var(--print-margin);
89
+ border-spacing: 0;
90
+ font-size: inherit;
91
+ text-align: start;
92
+ overflow-wrap: anywhere;
93
+ }
94
+ /* zero specificity, so the part classes below (and the print rules) win over this reset */
95
+ :where(.uix-print-layout__page > thead, .uix-print-layout__page > tbody, .uix-print-layout__page > tfoot, .uix-print-layout__page > * > tr, .uix-print-layout__page > * > tr > td) { display: block; padding: 0; }
96
+ .uix-print-layout__main { flex: 1 0 auto; }
97
+ .uix-print-layout__bar { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: var(--uix-space-2) var(--uix-space-4); }
98
+ .uix-print-layout__header .uix-print-layout__bar { padding-bottom: var(--uix-space-3); border-bottom: 1px solid var(--uix-print-ink); }
99
+ .uix-print-layout__body { display: flex; flex-direction: column; gap: var(--uix-space-4); min-width: 0; }
100
+ .uix-print-layout__footer { color: var(--uix-print-ink-quiet); font-size: var(--uix-text-meta); }
101
+ .uix-print-layout__footer .uix-print-layout__bar { padding-top: var(--uix-space-2); border-top: 1px solid var(--uix-print-line); }
102
+
103
+ /* one page and no more: the preview is one page tall; content that does not fit is outlined */
104
+ .uix-print-layout[data-single-page] .uix-print-layout__page { height: var(--print-height); overflow: hidden; }
105
+ .uix-print-layout[data-overflowing] { outline: 2px dashed var(--uix-danger); outline-offset: 2px; }
106
+
107
+ /* ── signature ─────────────────────────────────────────────────────────────── */
108
+ .uix-print-signature { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: flex-end; gap: var(--uix-space-4) var(--uix-space-6); margin-top: var(--uix-space-6); break-inside: avoid; }
109
+ .uix-print-signature__place { margin-right: auto; }
110
+ .uix-print-signature__field { display: grid; gap: var(--uix-space-1); width: min(70mm, 100%); text-align: center; }
111
+ .uix-print-signature__line { height: 14mm; border-bottom: 1px solid var(--uix-print-ink); }
112
+ .uix-print-signature__caption { color: var(--uix-print-ink-quiet); font-size: var(--uix-text-meta); }
113
+ .uix-print-signature__name { font-weight: 600; }
114
+ .uix-print-signature__stamp { display: grid; place-items: center; width: 34mm; height: 34mm; border: 1px dashed var(--uix-print-line); border-radius: 50%; color: var(--uix-print-ink-quiet); font-size: var(--uix-text-meta); }
115
+
116
+ /* ── page breaks and helpers ───────────────────────────────────────────────── */
117
+ .uix-print-break { height: 0; margin-block: var(--uix-space-3); border-top: 1px dashed var(--uix-print-line); }
118
+ .uix-print-keep { break-inside: avoid; }
119
+ .uix-print-only { display: none; }
120
+
121
+ @media print {
122
+ .uix-print-hide { display: none !important; }
123
+ .uix-print-only { display: revert; }
124
+
125
+ /* Only the documents print: every element that neither is nor holds a print layout goes,
126
+ and the ones that hold it (the app shell, its scrolling main) become plain flow, so a
127
+ scroll box cannot cut the document to one page. */
128
+ :root:has(.uix-print-layout[data-isolate]) body :not(:has(.uix-print-layout[data-isolate]), .uix-print-layout[data-isolate], .uix-print-layout[data-isolate] *) { display: none !important; }
129
+ :root:has(.uix-print-layout[data-isolate]) body :has(.uix-print-layout[data-isolate]) {
130
+ display: block !important; position: static !important; overflow: visible !important;
131
+ height: auto !important; min-height: 0 !important; max-height: none !important;
132
+ margin: 0 !important; padding: 0 !important; border: 0 !important; background: none !important;
133
+ box-shadow: none !important; transform: none !important;
134
+ }
135
+ /* the sheet is paper whatever the screen theme was, also with background graphics on */
136
+ :root:has(.uix-print-layout[data-isolate]) { color-scheme: light; background: var(--uix-print-paper) !important; }
137
+ :root:has(.uix-print-layout[data-isolate]) body { margin: 0; padding: 0; background: none !important; }
138
+
139
+ .uix-print-layout {
140
+ display: block; width: 100%; max-width: none; margin: 0;
141
+ border: 0; box-shadow: none; outline: none;
142
+ -webkit-print-color-adjust: exact; print-color-adjust: exact;
143
+ }
144
+ .uix-print-preview { inline-size: auto !important; max-inline-size: none; }
145
+ .uix-print-preview + .uix-print-preview { margin: 0; break-before: page; }
146
+ .uix-print-layout[data-page="uix-print-a4"] { page: uix-print-a4; }
147
+ .uix-print-layout[data-page="uix-print-a4-landscape"] { page: uix-print-a4-landscape; }
148
+ .uix-print-layout[data-page="uix-print-a5"] { page: uix-print-a5; }
149
+ .uix-print-layout[data-page="uix-print-a5-landscape"] { page: uix-print-a5-landscape; }
150
+ .uix-print-layout[data-page="uix-print-a4-plain"] { page: uix-print-a4-plain; }
151
+ .uix-print-layout[data-page="uix-print-a4-landscape-plain"] { page: uix-print-a4-landscape-plain; }
152
+ .uix-print-layout[data-page="uix-print-a5-plain"] { page: uix-print-a5-plain; }
153
+ .uix-print-layout[data-page="uix-print-a5-landscape-plain"] { page: uix-print-a5-landscape-plain; }
154
+
155
+ /* the page is a table again, and the browser repeats its thead and tfoot on every page */
156
+ .uix-print-layout__page { display: table; table-layout: fixed; min-height: 0; padding: 0; }
157
+ :where(.uix-print-layout__page > thead, .uix-print-layout__page > tbody, .uix-print-layout__page > tfoot, .uix-print-layout__page > * > tr, .uix-print-layout__page > * > tr > td) { display: revert; }
158
+ .uix-print-layout__body { display: table-cell; }
159
+ .uix-print-layout__body > * + * { margin-top: var(--uix-space-4); }
160
+ /* space between the bars and the body, on every page */
161
+ .uix-print-layout__header .uix-print-layout__bar { margin-bottom: 5mm; }
162
+ .uix-print-layout__footer .uix-print-layout__bar { margin-top: 5mm; }
163
+
164
+ /* one page: nothing repeats, nothing is cut, the frame stays in one piece */
165
+ .uix-print-layout[data-single-page] { display: block; break-inside: avoid; }
166
+ .uix-print-layout[data-single-page] .uix-print-layout__page { display: flex; height: auto; overflow: visible; }
167
+ .uix-print-layout[data-single-page] :where(.uix-print-layout__page > thead, .uix-print-layout__page > tbody, .uix-print-layout__page > tfoot, .uix-print-layout__page > * > tr, .uix-print-layout__page > * > tr > td) { display: block; }
168
+ .uix-print-layout[data-single-page] .uix-print-layout__body { display: flex; }
169
+ .uix-print-layout[data-single-page] .uix-print-layout__body > * + * { margin-top: 0; }
170
+
171
+ /* tables, lists and figures inside keep their rows whole; focus rings belong to the screen */
172
+ .uix-print-layout__body :is(tr, li, figure, .uix-card) { break-inside: avoid; }
173
+ .uix-print-layout__body thead { display: table-header-group; }
174
+ .uix-print-layout :focus, .uix-print-layout :focus-visible { outline: none; }
175
+ .uix-print-break { height: 0; margin: 0; border: 0; break-after: page; }
176
+ }
177
+ }
@@ -16,3 +16,10 @@
16
16
  background: conic-gradient(var(--uix-accent) calc(var(--val, .66) * 360deg), var(--uix-bg-active) 0); }
17
17
  .uix-ring__inner { width: 32px; height: 32px; border-radius: 50%; background: var(--uix-surface); display: grid; place-items: center; font-size: var(--uix-text-meta); font-weight: 600; }
18
18
  }
19
+
20
+ /* the in-app reduced-motion preference (HAR-1372, <html data-motion="reduce">): the same rule */
21
+ @layer uix.components {
22
+ @media (prefers-reduced-motion: no-preference) {
23
+ :root[data-motion="reduce"] .uix-progress--indeterminate .uix-progress__bar { width: 100%; animation: uix-pulse var(--uix-loop-pulse) var(--uix-ease-in-out) infinite !important; }
24
+ }
25
+ }
@@ -0,0 +1,30 @@
1
+ /* uix QR code (HAR-1251) — React QrCode renders an <svg class="uix-qr-code"> with a paper rect
2
+ and one path of dark modules; the four-module quiet zone is inside the viewBox.
3
+ Scanners read dark on light, so the symbol never follows the theme: it is print ink on print
4
+ paper in light, dark, print and forced colours alike, and it is printed as drawn. */
5
+ @layer uix.components {
6
+ .uix-qr-code {
7
+ display: block;
8
+ max-width: 100%;
9
+ height: auto;
10
+ aspect-ratio: 1;
11
+ border-radius: var(--uix-radius-sm);
12
+ forced-color-adjust: none;
13
+ -webkit-print-color-adjust: exact;
14
+ print-color-adjust: exact;
15
+ }
16
+ .uix-qr-code__paper { fill: var(--uix-print-paper); }
17
+ .uix-qr-code__ink { fill: var(--uix-print-ink); }
18
+
19
+ /* value too long for a QR code: a square that says so, never a broken symbol */
20
+ .uix-qr-code--error {
21
+ display: grid;
22
+ place-items: center;
23
+ padding: var(--uix-space-3);
24
+ border: 1px dashed var(--uix-border-strong);
25
+ background: var(--uix-bg-subtle);
26
+ color: var(--uix-text-muted);
27
+ font-size: var(--uix-text-meta);
28
+ text-align: center;
29
+ }
30
+ }
@@ -0,0 +1,61 @@
1
+ /* uix QR scanner (HAR-1254) — React QrScanner: a camera viewfinder with an explainer before the
2
+ browser's prompt, plain states for blocked / unsupported / no camera / no picture, and the
3
+ manual entry always below it. The frame is 4:3 while the camera runs; in every other state it
4
+ is a padded panel that grows with its text, so nothing is cut at 320 px.
5
+ No decorative scanning line: the target is still, and a read is answered once (the target
6
+ turns to the success colour for a moment; a status message says what was read). */
7
+ @layer uix.components {
8
+ .uix-qr-scanner { display: grid; gap: var(--uix-space-3); width: 100%; max-width: 32rem; min-width: 0; }
9
+
10
+ .uix-qr-scanner__frame {
11
+ position: relative;
12
+ display: grid;
13
+ place-items: center;
14
+ min-height: 12rem;
15
+ overflow: hidden;
16
+ border: 1px solid var(--uix-border);
17
+ border-radius: var(--uix-radius-lg);
18
+ background: var(--uix-bg-subtle);
19
+ }
20
+ .uix-qr-scanner[data-status="scanning"] .uix-qr-scanner__frame { aspect-ratio: 4 / 3; min-height: 0; background: var(--uix-print-ink); border-color: transparent; }
21
+ .uix-qr-scanner__video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
22
+ .uix-qr-scanner__video[hidden] { display: none; }
23
+
24
+ /* the aiming square: four corners, still */
25
+ .uix-qr-scanner__target {
26
+ --target-ink: var(--uix-print-paper);
27
+ position: relative;
28
+ width: min(62%, 16rem);
29
+ aspect-ratio: 1;
30
+ border-radius: var(--uix-radius-md);
31
+ background:
32
+ linear-gradient(var(--target-ink), var(--target-ink)) top left / 24px 3px no-repeat,
33
+ linear-gradient(var(--target-ink), var(--target-ink)) top left / 3px 24px no-repeat,
34
+ linear-gradient(var(--target-ink), var(--target-ink)) top right / 24px 3px no-repeat,
35
+ linear-gradient(var(--target-ink), var(--target-ink)) top right / 3px 24px no-repeat,
36
+ linear-gradient(var(--target-ink), var(--target-ink)) bottom left / 24px 3px no-repeat,
37
+ linear-gradient(var(--target-ink), var(--target-ink)) bottom left / 3px 24px no-repeat,
38
+ linear-gradient(var(--target-ink), var(--target-ink)) bottom right / 24px 3px no-repeat,
39
+ linear-gradient(var(--target-ink), var(--target-ink)) bottom right / 3px 24px no-repeat;
40
+ box-shadow: 0 0 0 100vmax color-mix(in srgb, var(--uix-print-ink) 35%, transparent);
41
+ transition: opacity var(--uix-dur-fast) var(--uix-ease-out), transform var(--uix-dur-fast) var(--uix-ease-out);
42
+ }
43
+ /* did it work: the corners turn green and settle in, once */
44
+ .uix-qr-scanner__frame[data-read] .uix-qr-scanner__target { --target-ink: var(--uix-success-solid); transform: scale(.96); }
45
+
46
+ .uix-qr-scanner__panel { display: grid; justify-items: start; gap: var(--uix-space-3); width: 100%; padding: var(--uix-space-5); }
47
+ .uix-qr-scanner__title { margin: 0; font-weight: 600; font-size: var(--uix-text-h3); }
48
+ .uix-qr-scanner__body { margin: 0; color: var(--uix-text-muted); }
49
+ .uix-qr-scanner[data-status="denied"] .uix-qr-scanner__title { color: var(--uix-danger-text); }
50
+ .uix-qr-scanner__explainer { color: var(--uix-text); max-width: 60ch; }
51
+
52
+ .uix-qr-scanner__bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--uix-space-2); }
53
+ .uix-qr-scanner__message { flex: 1 1 12rem; margin: 0; color: var(--uix-text-muted); font-size: var(--uix-text-meta); }
54
+ .uix-qr-scanner__message[data-hint] { color: var(--uix-warning-text); }
55
+ .uix-qr-scanner__manual { padding-top: var(--uix-space-3); border-top: 1px solid var(--uix-border); }
56
+
57
+ @media (prefers-reduced-motion: reduce) {
58
+ .uix-qr-scanner__target { transition: none; }
59
+ .uix-qr-scanner__frame[data-read] .uix-qr-scanner__target { transform: none; }
60
+ }
61
+ }
@@ -9,34 +9,64 @@
9
9
  /* Node fills must be opaque: edges run to node centres, so a translucent tint lets them strike through the label. */
10
10
  .uix-relationship-graph__node[data-selected] rect,.uix-relationship-graph__node[data-highlighted] rect { fill:color-mix(in srgb, var(--uix-accent) 16%, var(--uix-surface)); stroke:var(--uix-accent); stroke-width:3; } .uix-relationship-graph__node[data-conflicted] rect { fill:color-mix(in srgb, var(--uix-danger) 20%, var(--uix-surface)); stroke:var(--uix-danger); stroke-dasharray:4 2; }
11
11
  .uix-relationship-graph__node:focus-visible rect { stroke:var(--uix-ring); stroke-width:4; }
12
- .uix-relationship-graph__legend { display:flex; gap:var(--uix-space-4); flex-wrap:wrap; margin:0; padding:0; list-style:none; color:var(--uix-text-muted); font-size:var(--uix-text-meta); }
13
- .uix-relationship-graph__legend li::before { content:""; display:inline-block; width:.8rem; height:.8rem; margin-right:var(--uix-space-1); border:2px solid var(--uix-graph-type-color, var(--uix-chart-1)); border-radius:var(--uix-radius-pill); vertical-align:middle; }
12
+ /* One row per item, symbol and words centred on each other (operator review 2026-10-23: the dashed
13
+ samples and swatches sat off the text's middle). Items wrap whole on a narrow screen. */
14
+ .uix-relationship-graph__legend { display:flex; flex-wrap:wrap; align-items:center; gap:var(--uix-space-2) var(--uix-space-4); margin:0; padding:0; list-style:none; color:var(--uix-text-muted); font-size:var(--uix-text-meta); line-height:1.4; }
15
+ .uix-relationship-graph__legend li { display:inline-flex; align-items:center; gap:var(--uix-space-2); min-width:0; }
16
+ .uix-relationship-graph__legend-mark { flex:none; box-sizing:border-box; width:.8rem; height:.8rem; border:2px solid var(--uix-graph-type-color, var(--uix-chart-1)); border-radius:var(--uix-radius-pill); }
14
17
  /* TENSOR RX-125 (UIX-14): an edge whose type is in the legend draws that type's colour. */
15
18
  .uix-relationship-graph__edge[data-typed] { stroke:var(--uix-graph-type-color, var(--uix-border-strong)); }
16
19
  .uix-relationship-graph__list { padding:var(--uix-space-3); border:1px solid var(--uix-border); border-radius:var(--uix-radius-lg); background:var(--uix-surface); }
17
20
  .uix-relationship-graph__list>ul { display:grid; gap:var(--uix-space-2); margin:var(--uix-space-2) 0 0; padding:0; list-style:none; }
18
- .uix-relationship-graph__list li { display:flex; justify-content:space-between; gap:var(--uix-space-2); padding:var(--uix-space-2); border-radius:var(--uix-radius-md); background:var(--uix-bg-subtle); }
19
- .uix-relationship-graph__list li>button:first-child { display:grid; gap:var(--uix-space-1); flex:1; border:0; background:transparent; color:var(--uix-text); text-align:left; } .uix-relationship-graph__list li>button span { color:var(--uix-text-muted); font-size:var(--uix-text-meta); }
21
+ .uix-relationship-graph__list>ul>li { display:flex; justify-content:space-between; gap:var(--uix-space-2); padding:var(--uix-space-2); border-radius:var(--uix-radius-md); background:var(--uix-bg-subtle); }
22
+ /* A row: the node's button (its name only), then its neighbours as a list of their own (HAR-1387). */
23
+ .uix-relationship-graph__list-main { display:grid; flex:1; gap:var(--uix-space-1); min-width:0; }
24
+ .uix-relationship-graph__list-main>button { display:grid; justify-items:start; gap:var(--uix-space-1); padding:0; border:0; background:transparent; color:var(--uix-text); font:inherit; text-align:left; cursor:pointer; }
25
+ .uix-relationship-graph__list-main>button span,.uix-relationship-graph__list-relations,.uix-relationship-graph__list-none { color:var(--uix-text-muted); font-size:var(--uix-text-meta); }
26
+ .uix-relationship-graph__list-none { margin:0; }
27
+ /* "Connected to", then the names wrapping beside it, comma after comma, then "Show all N" */
28
+ .uix-relationship-graph__list-relations { display:flex; flex-wrap:wrap; align-items:baseline; gap:0 var(--uix-space-1); }
29
+ .uix-relationship-graph__list-relations ul { display:flex; flex:1 1 12rem; flex-wrap:wrap; gap:0 var(--uix-space-1); min-width:0; margin:0; padding:0; list-style:none; }
30
+ .uix-relationship-graph__list-relations li { min-width:0; overflow-wrap:anywhere; }
31
+ .uix-relationship-graph__list-relations li:not(:last-child)::after { content:","; }
32
+ .uix-relationship-graph__list-more { font-size:inherit; }
20
33
  .uix-relationship-graph__list-actions { display:flex; flex-wrap:wrap; gap:var(--uix-space-1); }
21
34
  .uix-relationship-graph--compact .uix-relationship-graph__visual,.uix-relationship-graph--compact .uix-relationship-graph__visual svg { min-height:16rem; height:16rem; }
22
35
  .uix-relationship-graph--state { padding:var(--uix-space-8); border:1px dashed var(--uix-border-strong); border-radius:var(--uix-radius-lg); }
23
- @media (max-width:640px) { .uix-relationship-graph__list li { display:grid; } }
36
+ @media (max-width:640px) { .uix-relationship-graph__list>ul>li { display:grid; } }
24
37
 
25
38
  /* Layered mode (ADR-0002): HTML node buttons over an SVG edge layer, one transformed stage. */
26
39
  .uix-relationship-graph--layered .uix-relationship-graph__toolbar button:disabled { opacity:.5; cursor:not-allowed; }
27
40
  .uix-relationship-graph__viewport { position:relative; height:var(--uix-relationship-graph-height, 22rem); overflow:hidden; border:1px solid var(--uix-border); border-radius:var(--uix-radius-lg); background:var(--uix-bg-subtle); cursor:grab; touch-action:pan-y; }
28
41
  .uix-relationship-graph__viewport:active { cursor:grabbing; }
42
+ /* The map is a pan surface: a press starts a pan, never a text selection or a native drag of the
43
+ words (operator review 2026-10-23: a ghost of the labels followed the pointer). The list below
44
+ stays selectable; screen readers read the names as before. */
45
+ .uix-relationship-graph__viewport { -webkit-user-select:none; user-select:none; -webkit-touch-callout:none; }
46
+ .uix-relationship-graph__viewport :is(img, svg, a) { -webkit-user-drag:none; }
29
47
  .uix-relationship-graph__stage { position:absolute; top:0; left:0; transform-origin:0 0; }
30
48
  .uix-relationship-graph__stage[data-animate] { transition:transform var(--uix-dur) var(--uix-ease-out); }
31
49
  .uix-relationship-graph__edges { position:absolute; top:0; left:0; overflow:visible; pointer-events:none; }
32
- .uix-relationship-graph__link path { fill:none; stroke:color-mix(in srgb, var(--uix-text) 28%, transparent); stroke-width:1.5px; }
33
- .uix-relationship-graph__link polygon { fill:color-mix(in srgb, var(--uix-text) 45%, transparent); }
34
- .uix-relationship-graph__link text { fill:var(--uix-text-muted); font-family:var(--uix-font-sans); font-size:var(--uix-text-eyebrow); paint-order:stroke; stroke:var(--uix-bg-subtle); stroke-width:4px; stroke-linejoin:round; }
50
+ /* Opaque, mixed into the map's own surface: fifty edges sharing a bus trunk stay one grey line
51
+ instead of stacking a translucent stroke to black (HAR-1387). */
52
+ .uix-relationship-graph__link path { fill:none; stroke:color-mix(in srgb, var(--uix-text) 28%, var(--uix-bg-subtle)); stroke-width:1.5px; }
53
+ .uix-relationship-graph__link polygon { fill:color-mix(in srgb, var(--uix-text) 45%, var(--uix-bg-subtle)); }
54
+ /* Edge labels: drawn after every line, each on a chip of the map's surface, in the kit's small
55
+ text at >= 4.5:1 on the chip; a tone is the chip's border, never the words' colour. */
56
+ /* zoomed out, a label keeps its on-screen size: scaled by 1 / zoom about its own centre (React
57
+ sets --uix-graph-label-scale on the stage and places the labels at that size) */
58
+ .uix-relationship-graph__edge-labels { pointer-events:none; }
59
+ .uix-relationship-graph__edge-label { transform-box:fill-box; transform-origin:center; transform:scale(var(--uix-graph-label-scale, 1)); }
60
+ .uix-relationship-graph__edge-label rect { fill:var(--uix-bg-subtle); stroke:var(--uix-border-strong); stroke-width:1px; }
61
+ .uix-relationship-graph__edge-label[data-tone] rect { stroke:var(--uix-graph-tone); }
62
+ .uix-relationship-graph__edge-label text { fill:var(--uix-text-muted); font-family:var(--uix-font-sans); font-size:var(--uix-text-meta); }
63
+ .uix-relationship-graph__edge-label[data-active] rect { stroke:var(--uix-accent); }
64
+ .uix-relationship-graph__edge-label[data-active] text { fill:var(--uix-text); }
65
+ .uix-relationship-graph__edge-label[data-dimmed] { opacity:.35; }
35
66
  .uix-relationship-graph__link[data-kind="lateral"] path, .uix-relationship-graph__link[data-kind="cycle"] path { stroke-dasharray:5 4; }
36
- .uix-relationship-graph__link[data-emphasis] path { stroke:color-mix(in srgb, var(--uix-text) 55%, transparent); }
67
+ .uix-relationship-graph__link[data-emphasis] path { stroke:color-mix(in srgb, var(--uix-text) 55%, var(--uix-bg-subtle)); }
37
68
  .uix-relationship-graph__link[data-active] path { stroke:var(--uix-accent); stroke-width:2.5px; }
38
69
  .uix-relationship-graph__link[data-active] polygon { fill:var(--uix-accent); }
39
- .uix-relationship-graph__link[data-active] text { fill:var(--uix-text); }
40
70
  .uix-relationship-graph__link[data-conflicted] path { stroke:var(--uix-danger); }
41
71
  .uix-relationship-graph__link[data-dimmed] { opacity:.35; }
42
72
  .uix-relationship-graph__column { position:absolute; top:var(--uix-space-4); overflow:hidden; color:var(--uix-text-muted); font-size:var(--uix-text-meta); white-space:nowrap; text-overflow:ellipsis; text-transform:uppercase; letter-spacing:.04em; pointer-events:none; }
@@ -50,6 +80,8 @@
50
80
  .uix-relationship-graph__item[data-kind="cluster"] { border-style:dashed; background:var(--uix-surface-2); }
51
81
  .uix-relationship-graph__item-label { min-width:0; display:-webkit-box; overflow:hidden; -webkit-box-orient:vertical; -webkit-line-clamp:2; overflow-wrap:anywhere; font-size:var(--uix-text-body); line-height:1.25; }
52
82
  .uix-relationship-graph__item-meta { min-width:0; overflow:hidden; color:var(--uix-text-muted); font-size:var(--uix-text-meta); white-space:nowrap; text-overflow:ellipsis; }
83
+ /* The flag sits on the item's top-right border: the first line of the name keeps clear of it. */
84
+ .uix-relationship-graph__item[data-flag] .uix-relationship-graph__item-label { padding-right:3.25rem; }
53
85
  .uix-relationship-graph__item-flag { position:absolute; top:-9px; right:var(--uix-space-2); padding:0 var(--uix-space-1); border:1px solid var(--uix-border); border-radius:var(--uix-radius-pill); background:var(--uix-surface); color:var(--uix-text-muted); font-size:var(--uix-text-meta); line-height:1.4; }
54
86
  .uix-relationship-graph__collapse { position:absolute; height:22px; padding:0 var(--uix-space-2); border:1px dashed var(--uix-border-strong); border-radius:var(--uix-radius-pill); background:var(--uix-surface); color:var(--uix-text-muted); font-size:var(--uix-text-meta); cursor:pointer; }
55
87
  .uix-relationship-graph__collapse:focus-visible { outline:2px solid var(--uix-ring); outline-offset:2px; }
@@ -60,8 +92,11 @@
60
92
  /* The hint, the zoom level and the cluster share one row along the map's bottom edge (TENSOR
61
93
  HAR-2512: positioned apart, "Zoom 75 %" was drawn over the hint on a 500–600 px map). The hint
62
94
  takes only the room the controls leave: it ellipsises, then hides; then the zoom level gives
63
- way; the cluster always stays. The row lets the pointer through to the map. */
64
- .uix-relationship-graph__footer { position:absolute; right:var(--uix-space-3); bottom:var(--uix-space-3); left:var(--uix-space-3); z-index:var(--uix-z-raised, 1); display:flex; align-items:center; justify-content:flex-end; gap:var(--uix-space-3); container:uix-relationship-graph-footer / inline-size; pointer-events:none; }
95
+ way; the cluster always stays. The row lets the pointer through to the map.
96
+ The row is a band across the map's whole bottom edge, on the map's own background with a rule
97
+ above it: the map ends at the rule, so no node is ever half under a pill (PR #166 review:
98
+ "Application 83" and "Rack 27" were). Fit and the first view leave its height free. */
99
+ .uix-relationship-graph__footer { position:absolute; right:0; bottom:0; left:0; z-index:var(--uix-z-raised, 1); display:flex; align-items:center; justify-content:flex-end; gap:var(--uix-space-3); padding:var(--uix-space-2) var(--uix-space-3); border-top:1px solid var(--uix-border); background:var(--uix-bg-subtle); container:uix-relationship-graph-footer / inline-size; pointer-events:none; }
65
100
  .uix-relationship-graph__controls { display:flex; flex:0 1 auto; align-items:center; gap:var(--uix-space-2); min-width:0; pointer-events:auto; }
66
101
  .uix-relationship-graph__control-cluster { display:flex; flex:none; padding:var(--uix-space-1); border:1px solid var(--uix-border-strong); border-radius:var(--uix-radius-md); background:var(--uix-surface); box-shadow:var(--uix-shadow-sm); }
67
102
  .uix-relationship-graph__control { display:inline-grid; place-items:center; width:32px; height:32px; padding:0; border:0; border-radius:var(--uix-radius-sm); background:transparent; color:var(--uix-text-muted); cursor:pointer; }
@@ -69,8 +104,10 @@
69
104
  .uix-relationship-graph__control:focus-visible { outline:2px solid var(--uix-ring); outline-offset:1px; }
70
105
  .uix-relationship-graph__control:disabled { opacity:.45; cursor:not-allowed; }
71
106
  .uix-relationship-graph__control svg { width:16px; height:16px; }
72
- .uix-relationship-graph__zoom-level { flex:0 1 auto; min-width:0; overflow:hidden; padding:0 var(--uix-space-1); border-radius:var(--uix-radius-sm); background:color-mix(in srgb, var(--uix-bg-subtle) 85%, transparent); color:var(--uix-text-muted); font-size:var(--uix-text-meta); font-variant-numeric:tabular-nums; white-space:nowrap; text-overflow:ellipsis; }
73
- .uix-relationship-graph__hint { flex:1 1 0; min-width:0; margin:0; overflow:hidden; color:var(--uix-text-muted); font-size:var(--uix-text-meta); white-space:nowrap; text-overflow:ellipsis; }
107
+ /* The hint and the zoom level sit on a surface of their own: the map scrolls under them, and a
108
+ node's name must not show through the words (HAR-1387). */
109
+ .uix-relationship-graph__zoom-level { flex:0 1 auto; min-width:0; overflow:hidden; padding:var(--uix-space-1) var(--uix-space-2); border:1px solid var(--uix-border); border-radius:var(--uix-radius-sm); background:var(--uix-surface); color:var(--uix-text-muted); font-size:var(--uix-text-meta); font-variant-numeric:tabular-nums; white-space:nowrap; text-overflow:ellipsis; }
110
+ .uix-relationship-graph__hint { flex:0 1000 auto; min-width:0; margin:0 auto 0 0; overflow:hidden; padding:var(--uix-space-1) var(--uix-space-2); border:1px solid var(--uix-border); border-radius:var(--uix-radius-sm); background:var(--uix-surface); color:var(--uix-text-muted); font-size:var(--uix-text-meta); white-space:nowrap; text-overflow:ellipsis; }
74
111
  /* A hint cut to a few words says nothing, so it goes; on the narrowest map so does the zoom level. */
75
112
  @container uix-relationship-graph-footer (max-width:24rem) { .uix-relationship-graph__hint { display:none; } }
76
113
  @container uix-relationship-graph-footer (max-width:17rem) { .uix-relationship-graph__zoom-level { display:none; } }
@@ -78,5 +115,24 @@
78
115
  .uix-relationship-graph--fullscreen { position:fixed; inset:0; z-index:var(--uix-z-overlay, 1000); display:flex; flex-direction:column; padding:var(--uix-space-4); background:var(--uix-bg-app, var(--uix-surface)); }
79
116
  .uix-relationship-graph--fullscreen .uix-relationship-graph__viewport { flex:1; min-height:0; height:auto; }
80
117
  @media (max-width:640px) { .uix-relationship-graph__hint { display:none; } }
81
- @media (forced-colors: active) { .uix-relationship-graph__link path { stroke:CanvasText; } .uix-relationship-graph__link polygon { fill:CanvasText; } .uix-relationship-graph__item { border-color:ButtonText; } }
118
+ /* ── Tones, variants and legends (HAR-1639) ────────────────────────────────────────────────
119
+ A tone is a colour a node or an edge shares with its legend item; it never stands alone (the
120
+ consumer says the same in text). A dashed edge is an expected relation that is missing. */
121
+ .uix-relationship-graph [data-tone="neutral"] { --uix-graph-tone:var(--uix-neutral-solid); }
122
+ .uix-relationship-graph [data-tone="info"] { --uix-graph-tone:var(--uix-info); }
123
+ .uix-relationship-graph [data-tone="success"] { --uix-graph-tone:var(--uix-success); }
124
+ .uix-relationship-graph [data-tone="warning"] { --uix-graph-tone:var(--uix-warning-text); }
125
+ .uix-relationship-graph [data-tone="danger"] { --uix-graph-tone:var(--uix-danger); }
126
+ .uix-relationship-graph [data-tone="accent"] { --uix-graph-tone:var(--uix-accent); }
127
+ .uix-relationship-graph__link[data-tone] path { stroke:var(--uix-graph-tone); stroke-width:2px; }
128
+ .uix-relationship-graph__link[data-tone] polygon { fill:var(--uix-graph-tone); }
129
+ .uix-relationship-graph__link[data-variant="dashed"] path,.uix-relationship-graph__edge[data-variant="dashed"] { stroke-dasharray:6 4; }
130
+ .uix-relationship-graph__edge[data-tone] { stroke:var(--uix-graph-tone); }
131
+ /* a toned node: its border and a bar on its leading edge */
132
+ .uix-relationship-graph__item[data-tone] { border-color:var(--uix-graph-tone); box-shadow:inset 4px 0 0 var(--uix-graph-tone); }
133
+ .uix-relationship-graph__legend li[data-tone] .uix-relationship-graph__legend-mark { border-color:var(--uix-graph-tone); }
134
+ .uix-relationship-graph__legend li:is([data-shape="edge"],[data-variant]):not([data-shape="node"]) .uix-relationship-graph__legend-mark { width:1.5rem; height:2px; border-width:2px 0 0; border-radius:0; }
135
+ .uix-relationship-graph__legend li[data-variant="dashed"] .uix-relationship-graph__legend-mark { border-top-style:dashed; }
136
+ .uix-relationship-graph__legend li[data-shape="node"] .uix-relationship-graph__legend-mark { width:1rem; height:.75rem; border-width:1px 1px 1px 4px; border-radius:var(--uix-radius-xs); }
137
+ @media (forced-colors: active) { .uix-relationship-graph__edge-label rect { fill:Canvas; stroke:CanvasText; } .uix-relationship-graph__edge-label text { fill:CanvasText; } .uix-relationship-graph__link path { stroke:CanvasText; } .uix-relationship-graph__link polygon { fill:CanvasText; } .uix-relationship-graph__item { border-color:ButtonText; } }
82
138
  }
@@ -6,7 +6,8 @@
6
6
  /* a fixed combinator (HAR-1618): the Match choice as plain text, where the select was */
7
7
  .uix-rule-builder__match { font-size:var(--uix-text-meta); font-weight:400; color:var(--uix-text-muted); }
8
8
  .uix-rule-builder__rows { display:grid; gap:var(--uix-space-3); }
9
- .uix-rule-builder__condition,.uix-rule-builder__action { display:grid; grid-template-columns:minmax(9rem,1fr) minmax(9rem,1fr) minmax(10rem,1.4fr) auto; gap:var(--uix-space-2); align-items:start; padding:var(--uix-space-3); border-radius:var(--uix-radius-md); background:var(--uix-bg-subtle); }
9
+ /* a choice column grows to the label it shows before the value takes the rest, so "is greater than" is not cut off (HAR-1575) */
10
+ .uix-rule-builder__condition,.uix-rule-builder__action { display:grid; grid-template-columns:minmax(9rem,max-content) minmax(9rem,max-content) minmax(10rem,1fr) auto; gap:var(--uix-space-2); align-items:start; padding:var(--uix-space-3); border-radius:var(--uix-radius-md); background:var(--uix-bg-subtle); }
10
11
  .uix-rule-builder__nested { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:var(--uix-space-2); align-items:start; }
11
12
  .uix-rule-builder__nested .uix-rule-builder__group { border-left:3px solid var(--uix-accent); }
12
13
  .uix-rule-builder__row-actions,.uix-rule-builder__add { display:flex; flex-wrap:wrap; gap:var(--uix-space-1); }
@@ -16,3 +16,8 @@
16
16
  .uix-save-status[data-state="saving"] .uix-save-status__icon { animation: uix-pulse var(--uix-loop-pulse) var(--uix-ease-in-out) infinite !important; }
17
17
  }
18
18
  }
19
+
20
+ /* the in-app reduced-motion preference (HAR-1372, <html data-motion="reduce">): the same pulse */
21
+ @layer uix.components {
22
+ @media (prefers-reduced-motion: no-preference) { :root[data-motion="reduce"] .uix-save-status[data-state="saving"] .uix-save-status__icon { animation: uix-pulse var(--uix-loop-pulse) var(--uix-ease-in-out) infinite !important; } }
23
+ }