@jirawatpyk/aura-react 5.73.0 → 5.75.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.
@@ -4,7 +4,18 @@ import { createPortal } from "react-dom";
4
4
  import { FIELD_KEYS, Field, describedBy } from "./Field.js";
5
5
  import { Icon } from "./Icon.js";
6
6
  import { useDensity } from "./locale.js";
7
- import { cx, devWarnOnce, omit, touchClass, uid, useIsoLayoutEffect, useMergedRef, useMounted } from "./internal.js";
7
+ import {
8
+ cx,
9
+ devWarnOnce,
10
+ useInputModality,
11
+ omit,
12
+ pointerModality,
13
+ touchClass,
14
+ uid,
15
+ useIsoLayoutEffect,
16
+ useMergedRef,
17
+ useMounted
18
+ } from "./internal.js";
8
19
  import { IconCheck, IconChevronDown } from "./icons.js";
9
20
  const h = React.createElement;
10
21
  const FILTER_KEY = "__auraFilter";
@@ -86,6 +97,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
86
97
  const rest = omit(props, FIELD_KEYS.concat(["children", FILTER_KEY, "readOnly"]));
87
98
  const ro = !!props.readOnly && !props.disabled;
88
99
  const mounted = useMounted();
100
+ useInputModality();
89
101
  const kept = React.useRef(null);
90
102
  const current = [].concat(props.value != null ? props.value : props.defaultValue != null ? props.defaultValue : []).map(String);
91
103
  const opts = (props.options || []).map(function(o) {
@@ -498,6 +510,7 @@ const Select = React.forwardRef(function Select2(props, ref) {
498
510
  e.preventDefault();
499
511
  },
500
512
  onPointerMove: function() {
513
+ pointerModality();
501
514
  if (!it.disabled && active !== it.index) activeState[1](it.index);
502
515
  },
503
516
  onClick: function() {
package/dist/esm/Tag.js CHANGED
@@ -92,7 +92,7 @@ const Tag = React.forwardRef(function Tag2(props, ref) {
92
92
  "aria-label": props.removeLabel || t.remove(name),
93
93
  onClick: props.onRemove
94
94
  },
95
- /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconX, null), size: 14 })
95
+ /* @__PURE__ */ React.createElement(Icon, { name: /* @__PURE__ */ React.createElement(IconX, null), size: 16 })
96
96
  );
97
97
  return /* @__PURE__ */ React.createElement(Tooltip, { content: fullText, open: fullText ? void 0 : false }, btn);
98
98
  }() : null);
@@ -1,7 +1,17 @@
1
1
  'use client';
2
2
  import * as React from "react";
3
3
  import { createPortal } from "react-dom";
4
- import { cx, uid, useMaybeControlled, useMounted, useIsoLayoutEffect, useMergedRef, touchClass } from "./internal.js";
4
+ import {
5
+ cx,
6
+ uid,
7
+ useMaybeControlled,
8
+ useMounted,
9
+ useIsoLayoutEffect,
10
+ useMergedRef,
11
+ touchClass,
12
+ useInputModality,
13
+ pointerModality
14
+ } from "./internal.js";
5
15
  import { useStrings } from "./locale.js";
6
16
  import { Icon } from "./Icon.js";
7
17
  import { Field } from "./Field.js";
@@ -46,6 +56,7 @@ const TimePicker = React.forwardRef(function TimePicker2(props, ref) {
46
56
  const pos = React.useState(null);
47
57
  const boxRef = React.useRef(null), inputRef = React.useRef(null), listRef = React.useRef(null), inputMerged = useMergedRef(ref, inputRef);
48
58
  const mounted = useMounted();
59
+ useInputModality();
49
60
  function nearest(v) {
50
61
  const n = toMin(v);
51
62
  if (n == null) return 0;
@@ -217,6 +228,7 @@ const TimePicker = React.forwardRef(function TimePicker2(props, ref) {
217
228
  choose(i);
218
229
  },
219
230
  onPointerMove: function() {
231
+ pointerModality();
220
232
  if (active !== i) setActive(i);
221
233
  }
222
234
  },
@@ -102,13 +102,19 @@ const PILL_ICON = {
102
102
  };
103
103
  function statusPillElement(props, ref) {
104
104
  const tone = props.tone || toneFor(props.children);
105
- const rest = omit(props, ["tone", "className", "children", "wrap", "busy"]);
105
+ const rest = omit(props, ["tone", "className", "children", "wrap", "busy", "variant"]);
106
106
  return /* @__PURE__ */ React.createElement(
107
107
  "span",
108
108
  {
109
109
  ...rest,
110
110
  ref,
111
- className: cx("aura-pill", "aura-pill--" + tone, props.wrap && "is-wrap", props.className)
111
+ className: cx(
112
+ "aura-pill",
113
+ "aura-pill--" + tone,
114
+ props.variant === "solid" && "is-solid",
115
+ props.wrap && "is-wrap",
116
+ props.className
117
+ )
112
118
  },
113
119
  props.busy ? /* @__PURE__ */ React.createElement(Icon, { name: h(IconLoaderCircle), size: 12, className: "aura-spin" }) : /* @__PURE__ */ React.createElement(Icon, { name: h(PILL_ICON[tone] || IconCircle), size: 12 }),
114
120
  props.wrap ? /* @__PURE__ */ React.createElement("span", { className: "aura-pill__text" }, props.children) : props.children
@@ -124,7 +124,7 @@ function onScreen(el) {
124
124
  const v = getComputedStyle(el).visibility;
125
125
  return v !== "hidden" && v !== "collapse";
126
126
  }
127
- function barStack(name, onMany) {
127
+ function barStack(name, onMany, onRead) {
128
128
  const els = [];
129
129
  let frame = 0;
130
130
  const docEvents = ["click", "keydown", "focusin", "transitionend", "beforematch"];
@@ -132,6 +132,7 @@ function barStack(name, onMany) {
132
132
  function write() {
133
133
  if (frame) cancelAnimationFrame(frame);
134
134
  frame = 0;
135
+ if (onRead) onRead();
135
136
  const root = document.documentElement;
136
137
  let shown = null, boxed = null, pinned = false, many = 0;
137
138
  for (let i = els.length - 1; i >= 0; i--) {
@@ -186,6 +187,36 @@ function barStack(name, onMany) {
186
187
  write
187
188
  };
188
189
  }
190
+ let modalityOn = false;
191
+ function setModality(m) {
192
+ const root = document.documentElement;
193
+ if (m === "pointer") {
194
+ if (root.getAttribute("data-aura-modality") !== "pointer") root.setAttribute("data-aura-modality", "pointer");
195
+ } else if (root.hasAttribute("data-aura-modality")) root.removeAttribute("data-aura-modality");
196
+ }
197
+ function useInputModality() {
198
+ React.useEffect(function() {
199
+ if (modalityOn) return;
200
+ modalityOn = true;
201
+ document.addEventListener(
202
+ "keydown",
203
+ function() {
204
+ setModality("key");
205
+ },
206
+ true
207
+ );
208
+ document.addEventListener(
209
+ "pointerdown",
210
+ function() {
211
+ setModality("pointer");
212
+ },
213
+ true
214
+ );
215
+ }, []);
216
+ }
217
+ function pointerModality() {
218
+ setModality("pointer");
219
+ }
189
220
  export {
190
221
  FOCUSABLE,
191
222
  FilterSheet,
@@ -201,10 +232,12 @@ export {
201
232
  isTabStop,
202
233
  omit,
203
234
  plainClick,
235
+ pointerModality,
204
236
  tone,
205
237
  touchClass,
206
238
  trapTab,
207
239
  uid,
240
+ useInputModality,
208
241
  useIsoLayoutEffect,
209
242
  useMaybeControlled,
210
243
  useMergedRef,
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
  const TONE_WORDS = {
3
- ready: ["ready", "done", "complete", "completed", "approved", "live", "passed"],
3
+ ready: ["ready", "done", "complete", "completed", "approved", "live", "passed", "active", "up", "online", "resolved"],
4
4
  progress: ["in progress", "in review", "review", "syncing", "running", "pending"],
5
5
  warning: ["warning", "problem", "degraded", "at risk"],
6
- blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled"]
6
+ blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled", "down", "offline", "outage"]
7
7
  };
8
8
  function toneFor(status) {
9
9
  const s = String(status == null ? "" : status).trim().toLowerCase();
package/dist/esm/theme.js CHANGED
@@ -148,7 +148,7 @@ function createTheme(opts) {
148
148
  D["fg-accent"] = fit(b[300], darkGrounds, 4.5, 1);
149
149
  D["accent-violet"] = fit(b[400], darkGrounds, 3, 1);
150
150
  D["focus-ring"] = fit(b[400], darkGrounds, 3, 1);
151
- D["bg-selected"] = mix(ZINC[900], b[500], 0.16);
151
+ D["bg-selected"] = mix(ZINC[900], b[500], 0.2);
152
152
  D["alert-info-bg"] = D["bg-selected"];
153
153
  D["alert-info-fg"] = fit(b[300], [D["alert-info-bg"]], 4.5, 1);
154
154
  D["alert-info-border"] = mix(ZINC[900], b[400], 0.4);
@@ -169,6 +169,7 @@ function createTheme(opts) {
169
169
  L["control-checked-fg"] = onFill(L["control-checked-bg"]);
170
170
  D["control-checked-bg"] = fit(b[300], darkGrounds, 3, 1);
171
171
  D["control-checked-fg"] = onFill(D["control-checked-bg"]);
172
+ D["nav-active-edge"] = D["control-checked-bg"];
172
173
  const shadowLight = INK, shadowDark = D["accent-violet"];
173
174
  if (o.primary === "brand") {
174
175
  L["button-primary-bg"] = fit(b[600], [ZINC[0]], 4.5, -1);
package/dist/index.d.cts CHANGED
@@ -335,7 +335,11 @@ export interface StatusPillProps extends Omit<React$1.HTMLAttributes<HTMLSpanEle
335
335
  /** 5.70 (Chamber-OS 189): the spinner in the icon slot, for a state still in progress ("Waiting for payment").
336
336
  * The words still say it; the tone is unchanged. */
337
337
  busy?: boolean | undefined;
338
- /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked, anything else → neutral). */
338
+ /** 5.75 (visual audit): `"soft"` (default) is a tint with a darker word, like Alert and Badge, so a column of statuses
339
+ * reads evenly. `"solid"` is the strong fill — for the one status on a screen that must catch the eye (an overdue
340
+ * total, a failed payment), not for every row. */
341
+ variant?: "soft" | "solid" | undefined;
342
+ /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked; 5.75: Active/Up/Online/Resolved → ready, Down/Offline/Outage → blocked; anything else → neutral). */
339
343
  tone?: StatusTone | undefined;
340
344
  className?: string | undefined;
341
345
  }
package/dist/index.d.ts CHANGED
@@ -335,7 +335,11 @@ export interface StatusPillProps extends Omit<React$1.HTMLAttributes<HTMLSpanEle
335
335
  /** 5.70 (Chamber-OS 189): the spinner in the icon slot, for a state still in progress ("Waiting for payment").
336
336
  * The words still say it; the tone is unchanged. */
337
337
  busy?: boolean | undefined;
338
- /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked, anything else → neutral). */
338
+ /** 5.75 (visual audit): `"soft"` (default) is a tint with a darker word, like Alert and Badge, so a column of statuses
339
+ * reads evenly. `"solid"` is the strong fill — for the one status on a screen that must catch the eye (an overdue
340
+ * total, a failed payment), not for every row. */
341
+ variant?: "soft" | "solid" | undefined;
342
+ /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked; 5.75: Active/Up/Online/Resolved → ready, Down/Offline/Outage → blocked; anything else → neutral). */
339
343
  tone?: StatusTone | undefined;
340
344
  className?: string | undefined;
341
345
  }
@@ -203,10 +203,10 @@ function parseTime(text) {
203
203
 
204
204
  // src/status.ts
205
205
  var TONE_WORDS = {
206
- ready: ["ready", "done", "complete", "completed", "approved", "live", "passed"],
206
+ ready: ["ready", "done", "complete", "completed", "approved", "live", "passed", "active", "up", "online", "resolved"],
207
207
  progress: ["in progress", "in review", "review", "syncing", "running", "pending"],
208
208
  warning: ["warning", "problem", "degraded", "at risk"],
209
- blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled"]
209
+ blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled", "down", "offline", "outage"]
210
210
  };
211
211
  function toneFor(status) {
212
212
  const s = String(status == null ? "" : status).trim().toLowerCase();
@@ -797,7 +797,7 @@ function createTheme(opts) {
797
797
  D["fg-accent"] = fit(b[300], darkGrounds, 4.5, 1);
798
798
  D["accent-violet"] = fit(b[400], darkGrounds, 3, 1);
799
799
  D["focus-ring"] = fit(b[400], darkGrounds, 3, 1);
800
- D["bg-selected"] = mix(ZINC[900], b[500], 0.16);
800
+ D["bg-selected"] = mix(ZINC[900], b[500], 0.2);
801
801
  D["alert-info-bg"] = D["bg-selected"];
802
802
  D["alert-info-fg"] = fit(b[300], [D["alert-info-bg"]], 4.5, 1);
803
803
  D["alert-info-border"] = mix(ZINC[900], b[400], 0.4);
@@ -818,6 +818,7 @@ function createTheme(opts) {
818
818
  L["control-checked-fg"] = onFill(L["control-checked-bg"]);
819
819
  D["control-checked-bg"] = fit(b[300], darkGrounds, 3, 1);
820
820
  D["control-checked-fg"] = onFill(D["control-checked-bg"]);
821
+ D["nav-active-edge"] = D["control-checked-bg"];
821
822
  const shadowLight = INK, shadowDark = D["accent-violet"];
822
823
  if (o.primary === "brand") {
823
824
  L["button-primary-bg"] = fit(b[600], [ZINC[0]], 4.5, -1);
@@ -1268,13 +1269,19 @@ var PILL_ICON = {
1268
1269
  };
1269
1270
  function statusPillElement(props, ref) {
1270
1271
  const tone2 = props.tone || toneFor(props.children);
1271
- const rest = omit(props, ["tone", "className", "children", "wrap", "busy"]);
1272
+ const rest = omit(props, ["tone", "className", "children", "wrap", "busy", "variant"]);
1272
1273
  return /* @__PURE__ */ React3.createElement(
1273
1274
  "span",
1274
1275
  {
1275
1276
  ...rest,
1276
1277
  ref,
1277
- className: cx("aura-pill", "aura-pill--" + tone2, props.wrap && "is-wrap", props.className)
1278
+ className: cx(
1279
+ "aura-pill",
1280
+ "aura-pill--" + tone2,
1281
+ props.variant === "solid" && "is-solid",
1282
+ props.wrap && "is-wrap",
1283
+ props.className
1284
+ )
1278
1285
  },
1279
1286
  props.busy ? /* @__PURE__ */ React3.createElement(Icon, { name: h2(IconLoaderCircle), size: 12, className: "aura-spin" }) : /* @__PURE__ */ React3.createElement(Icon, { name: h2(PILL_ICON[tone2] || IconCircle), size: 12 }),
1280
1287
  props.wrap ? /* @__PURE__ */ React3.createElement("span", { className: "aura-pill__text" }, props.children) : props.children
@@ -54,7 +54,11 @@ export interface StatusPillProps extends Omit<React$1.HTMLAttributes<HTMLSpanEle
54
54
  /** 5.70 (Chamber-OS 189): the spinner in the icon slot, for a state still in progress ("Waiting for payment").
55
55
  * The words still say it; the tone is unchanged. */
56
56
  busy?: boolean | undefined;
57
- /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked, anything else → neutral). */
57
+ /** 5.75 (visual audit): `"soft"` (default) is a tint with a darker word, like Alert and Badge, so a column of statuses
58
+ * reads evenly. `"solid"` is the strong fill — for the one status on a screen that must catch the eye (an overdue
59
+ * total, a failed payment), not for every row. */
60
+ variant?: "soft" | "solid" | undefined;
61
+ /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked; 5.75: Active/Up/Online/Resolved → ready, Down/Offline/Outage → blocked; anything else → neutral). */
58
62
  tone?: StatusTone | undefined;
59
63
  className?: string | undefined;
60
64
  }
@@ -54,7 +54,11 @@ export interface StatusPillProps extends Omit<React$1.HTMLAttributes<HTMLSpanEle
54
54
  /** 5.70 (Chamber-OS 189): the spinner in the icon slot, for a state still in progress ("Waiting for payment").
55
55
  * The words still say it; the tone is unchanged. */
56
56
  busy?: boolean | undefined;
57
- /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked, anything else → neutral). */
57
+ /** 5.75 (visual audit): `"soft"` (default) is a tint with a darker word, like Alert and Badge, so a column of statuses
58
+ * reads evenly. `"solid"` is the strong fill — for the one status on a screen that must catch the eye (an overdue
59
+ * total, a failed payment), not for every row. */
60
+ variant?: "soft" | "solid" | undefined;
61
+ /** Force a tone. Default: matched from the word (Ready/Done → ready, In Progress/In Review → progress, Warning/Problem/Degraded/At risk → warning (5.1), Blocked/Failed → blocked; 5.75: Active/Up/Online/Resolved → ready, Down/Offline/Outage → blocked; anything else → neutral). */
58
62
  tone?: StatusTone | undefined;
59
63
  className?: string | undefined;
60
64
  }
@@ -136,10 +136,10 @@ function parseTime(text) {
136
136
 
137
137
  // src/status.ts
138
138
  var TONE_WORDS = {
139
- ready: ["ready", "done", "complete", "completed", "approved", "live", "passed"],
139
+ ready: ["ready", "done", "complete", "completed", "approved", "live", "passed", "active", "up", "online", "resolved"],
140
140
  progress: ["in progress", "in review", "review", "syncing", "running", "pending"],
141
141
  warning: ["warning", "problem", "degraded", "at risk"],
142
- blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled"]
142
+ blocked: ["blocked", "failed", "error", "rejected", "on hold", "cancelled", "down", "offline", "outage"]
143
143
  };
144
144
  function toneFor(status) {
145
145
  const s = String(status == null ? "" : status).trim().toLowerCase();
@@ -730,7 +730,7 @@ function createTheme(opts) {
730
730
  D["fg-accent"] = fit(b[300], darkGrounds, 4.5, 1);
731
731
  D["accent-violet"] = fit(b[400], darkGrounds, 3, 1);
732
732
  D["focus-ring"] = fit(b[400], darkGrounds, 3, 1);
733
- D["bg-selected"] = mix(ZINC[900], b[500], 0.16);
733
+ D["bg-selected"] = mix(ZINC[900], b[500], 0.2);
734
734
  D["alert-info-bg"] = D["bg-selected"];
735
735
  D["alert-info-fg"] = fit(b[300], [D["alert-info-bg"]], 4.5, 1);
736
736
  D["alert-info-border"] = mix(ZINC[900], b[400], 0.4);
@@ -751,6 +751,7 @@ function createTheme(opts) {
751
751
  L["control-checked-fg"] = onFill(L["control-checked-bg"]);
752
752
  D["control-checked-bg"] = fit(b[300], darkGrounds, 3, 1);
753
753
  D["control-checked-fg"] = onFill(D["control-checked-bg"]);
754
+ D["nav-active-edge"] = D["control-checked-bg"];
754
755
  const shadowLight = INK, shadowDark = D["accent-violet"];
755
756
  if (o.primary === "brand") {
756
757
  L["button-primary-bg"] = fit(b[600], [ZINC[0]], 4.5, -1);
@@ -1201,13 +1202,19 @@ var PILL_ICON = {
1201
1202
  };
1202
1203
  function statusPillElement(props, ref) {
1203
1204
  const tone2 = props.tone || toneFor(props.children);
1204
- const rest = omit(props, ["tone", "className", "children", "wrap", "busy"]);
1205
+ const rest = omit(props, ["tone", "className", "children", "wrap", "busy", "variant"]);
1205
1206
  return /* @__PURE__ */ React3.createElement(
1206
1207
  "span",
1207
1208
  {
1208
1209
  ...rest,
1209
1210
  ref,
1210
- className: cx("aura-pill", "aura-pill--" + tone2, props.wrap && "is-wrap", props.className)
1211
+ className: cx(
1212
+ "aura-pill",
1213
+ "aura-pill--" + tone2,
1214
+ props.variant === "solid" && "is-solid",
1215
+ props.wrap && "is-wrap",
1216
+ props.className
1217
+ )
1211
1218
  },
1212
1219
  props.busy ? /* @__PURE__ */ React3.createElement(Icon, { name: h2(IconLoaderCircle), size: 12, className: "aura-spin" }) : /* @__PURE__ */ React3.createElement(Icon, { name: h2(PILL_ICON[tone2] || IconCircle), size: 12 }),
1213
1220
  props.wrap ? /* @__PURE__ */ React3.createElement("span", { className: "aura-pill__text" }, props.children) : props.children
package/dist/styles.css CHANGED
@@ -245,7 +245,7 @@ button.aura-btn--creative[aria-disabled='true']:hover { box-shadow: var(--aura-s
245
245
  /* Sortable header */
246
246
  .aura-table__sort {
247
247
  display: inline-flex; align-items: center; gap: var(--aura-space-1); min-width: 0;
248
- margin: 0 calc(-1 * var(--aura-space-1)); padding: var(--aura-space-1);
248
+ margin: calc(-1 * var(--aura-space-1)); padding: var(--aura-space-1); /* 5.75: negative on all sides, so a sortable label sits on the line of a plain one */
249
249
  border: 0; border-radius: var(--aura-radius-xs); background: transparent;
250
250
  font: inherit; color: inherit; letter-spacing: inherit; cursor: pointer; white-space: nowrap;
251
251
  transition: color var(--aura-duration-fast) var(--aura-ease);
@@ -481,6 +481,14 @@ a.aura-icon-btn { text-decoration: none; }
481
481
  .aura-pill--ready { background: var(--aura-status-ready-bg); color: var(--aura-status-ready-fg); }
482
482
  .aura-pill--warning { background: var(--aura-status-warning-bg); color: var(--aura-status-warning-fg); }
483
483
  .aura-pill--blocked { background: var(--aura-status-blocked-bg); color: var(--aura-status-blocked-fg); }
484
+ /* 5.75 (visual audit): every pill is soft — a tint and its own darker word, as Alert and Badge — so a table of
485
+ * statuses reads evenly and Blocked or Warning don't shout in every row. variant="solid" is the strong fill, for the
486
+ * one status on a screen that must catch the eye. */
487
+ .aura-pill--neutral.is-solid { background: var(--aura-status-neutral-solid-bg); color: var(--aura-status-neutral-solid-fg); }
488
+ .aura-pill--progress.is-solid { background: var(--aura-status-progress-solid-bg); color: var(--aura-status-progress-solid-fg); }
489
+ .aura-pill--ready.is-solid { background: var(--aura-status-ready-solid-bg); color: var(--aura-status-ready-solid-fg); }
490
+ .aura-pill--warning.is-solid { background: var(--aura-status-warning-solid-bg); color: var(--aura-status-warning-solid-fg); }
491
+ .aura-pill--blocked.is-solid { background: var(--aura-status-blocked-solid-bg); color: var(--aura-status-blocked-solid-fg); }
484
492
 
485
493
  /* Icon — Lucide, 24-grid, stroke in currentColor */
486
494
  .aura-icon { flex: none; display: inline-block; vertical-align: middle; }
@@ -878,7 +886,8 @@ a.aura-tab { text-decoration: none; }
878
886
  .aura-cal__popover, .aura-menu, .aura-toast, .aura-command { --aura-hover: var(--aura-bg-surface-hover); }
879
887
  /* Canvas-coloured bands inside those surfaces: SideNav in the drawer, the DataTable header, picker corner and totals. */
880
888
  .aura-drawer .aura-nav, .aura-table__head, .aura-table__picker, .aura-table__total { --aura-hover: var(--aura-bg-canvas-hover); }
881
- .aura-nav__item.is-active { background: var(--aura-bg-selected); color: var(--aura-fg-primary); }
889
+ .aura-nav__item.is-active { background: var(--aura-bg-selected); color: var(--aura-fg-primary); box-shadow: inset 3px 0 0 var(--aura-nav-active-edge); } /* 5.75: the edge, in dark */
890
+ [dir='rtl'] .aura-nav__item.is-active, .aura-nav[dir='rtl'] .aura-nav__item.is-active { box-shadow: inset -3px 0 0 var(--aura-nav-active-edge); }
882
891
  .aura-nav__item.is-active .aura-icon { color: var(--aura-fg-accent); }
883
892
  .aura-nav__item:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: -2px; }
884
893
  /* Touch (5.7): rows are 44px on coarse pointers (AppShell's phone drawer); desktop keeps 36px. */
@@ -918,7 +927,8 @@ a.aura-tab { text-decoration: none; }
918
927
  }
919
928
  .aura-nav--collapsed .aura-nav__chevron { display: none; }
920
929
  .aura-nav--collapsed .aura-nav__section + .aura-nav__section { margin-top: var(--aura-space-3); padding-top: var(--aura-space-3); border-top: 1px solid var(--aura-border-default); }
921
- .aura-nav--collapsed .aura-nav__item--group.has-active { background: var(--aura-bg-selected); }
930
+ .aura-nav--collapsed .aura-nav__item--group.has-active { background: var(--aura-bg-selected); box-shadow: inset 3px 0 0 var(--aura-nav-active-edge); } /* 5.75: as the expanded item */
931
+ [dir='rtl'] .aura-nav--collapsed .aura-nav__item--group.has-active, .aura-nav--collapsed[dir='rtl'] .aura-nav__item--group.has-active { box-shadow: inset -3px 0 0 var(--aura-nav-active-edge); }
922
932
  .aura-nav--collapsed .aura-nav__item--group.has-active .aura-icon { color: var(--aura-fg-accent); }
923
933
  .aura-nav__initial {
924
934
  display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: var(--aura-radius-sm);
@@ -1013,6 +1023,14 @@ a.aura-tab { text-decoration: none; }
1013
1023
  }
1014
1024
  .aura-combo__option > .aura-icon { color: var(--aura-fg-secondary); }
1015
1025
  .aura-combo__option.is-active { background: var(--aura-hover, var(--aura-bg-surface-hover)); outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: calc(-1 * var(--aura-focus-ring-width)); }
1026
+ /* 5.75 (visual audit): under the pointer the option is only shaded, as a Menu item, so the list doesn't show a second
1027
+ * ring under the field's. Moving by keys brings the ring back (the field keeps focus; the option is aria-activedescendant,
1028
+ * which :focus-visible can't see). */
1029
+ :root[data-aura-modality='pointer'] .aura-combo__option.is-active { outline: none; }
1030
+ @media (forced-colors: active) {
1031
+ /* the shade is dropped in forced colours: keep a thin mark on the row under the pointer */
1032
+ :root[data-aura-modality='pointer'] .aura-combo__option.is-active { outline: 1px solid Highlight; }
1033
+ }
1016
1034
  .aura-combo__option.is-selected { font-weight: 500; }
1017
1035
  .aura-combo__option.is-disabled { opacity: var(--aura-disabled-opacity); cursor: default; }
1018
1036
  .aura-combo__text { flex: 1; min-width: 0; display: flex; flex-direction: column; }
@@ -1357,6 +1375,10 @@ summary.aura-link::-webkit-details-marker { display: none; }
1357
1375
  .aura-page-header__actions.is-split:not(:has(> :not(.aura-page-header__bar))) { display: contents; }
1358
1376
  @media (max-width: 639.98px) and (min-height: 30.00125rem) {
1359
1377
  .aura-page-header--actions-bar-below-sm > .aura-page-header__actions > .aura-page-header__bar { display: flex; flex: none; min-width: 0; }
1378
+ /* 5.74 (Chamber-OS 193): nothing but the status shown (Pay now hidden after paying): no strip, and no bar padding below. */
1379
+ .aura-page-header--actions-bar-below-sm[data-aura-bar="empty"] > .aura-page-header__actions:not(.is-split), .aura-page-header--actions-bar-below-sm[data-aura-bar="empty"] > .aura-page-header__actions > .aura-page-header__bar { display: none; }
1380
+ /* …and before hydration, from the markup alone (actionsBar's strip whose element children but the status are all `hidden` or visually hidden): it already goes; the page padding follows once the client has looked. */
1381
+ .aura-page-header--actions-bar-below-sm:not([data-aura-bar="shown"]) > .aura-page-header__actions > .aura-page-header__bar:not(:has(> :not(.aura-page-header__status, [hidden], .sr-only, .aura-sr-only, .visually-hidden, .screen-reader-text))):has(> *) { display: none; }
1360
1382
  :is(.aura-page-header--actions-bar-below-sm > .aura-page-header__actions:not(.is-split), .aura-page-header--actions-bar-below-sm > .aura-page-header__actions > .aura-page-header__bar) { position: fixed; z-index: var(--aura-z-bar); left: 0; right: 0; bottom: var(--aura-bottomnav-offset, 0px); box-sizing: border-box; min-height: 60px; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--aura-space-2); margin: 0; padding: var(--aura-space-2) var(--aura-space-3) calc(var(--aura-space-2) + max(0px, env(safe-area-inset-bottom, 0px) - var(--aura-bottomnav-offset, 0px))) var(--aura-space-4); background: var(--aura-bg-surface); color: var(--aura-fg-primary); border: 0; border-top: 1px solid var(--aura-border-default); border-radius: 0; box-shadow: none; transition: bottom var(--aura-duration-base) var(--aura-ease); }
1361
1383
  /* 5.54 (Chamber-OS 166): the bar's buttons are 44px whatever the density, as ActionBar's on a phone. */
1362
1384
  :is(.aura-page-header--actions-bar-below-sm > .aura-page-header__actions:not(.is-split), .aura-page-header--actions-bar-below-sm > .aura-page-header__actions > .aura-page-header__bar) { --aura-button-height: var(--aura-touch-target); --aura-button-height-sm: var(--aura-touch-target); }
@@ -1372,13 +1394,17 @@ summary.aura-link::-webkit-details-marker { display: none; }
1372
1394
  :is(.aura-page-header--actions-bar-below-sm > .aura-page-header__actions:not(.is-split), .aura-page-header--actions-bar-below-sm > .aura-page-header__actions > .aura-page-header__bar):has(> .aura-icon-btn, > * > .aura-icon-btn) > :is(.aura-icon-btn, :has(> .aura-icon-btn)) { align-self: center; }
1373
1395
  :is(.aura-page-header--actions-bar-below-sm > .aura-page-header__actions:not(.is-split), .aura-page-header--actions-bar-below-sm > .aura-page-header__actions > .aura-page-header__bar):has(> .aura-icon-btn, > * > .aura-icon-btn) > :is(.aura-btn, * > .aura-btn):not(.aura-icon-btn) { height: auto; min-height: var(--aura-button-height); white-space: normal; padding: var(--aura-space-2) var(--aura-space-3); line-height: 1.35; text-align: center; overflow-wrap: anywhere; hyphens: auto; border-radius: min(var(--aura-button-radius), 22px); }
1374
1396
  :is(.aura-page-header--actions-bar-below-sm > .aura-page-header__actions:not(.is-split), .aura-page-header--actions-bar-below-sm > .aura-page-header__actions > .aura-page-header__bar):not(:has(> .aura-btn, > * > .aura-btn)) > .aura-page-header__status { flex: 1 1 0; align-self: center; }
1375
- :root:has(.aura-page-header--actions-bar-below-sm) { --aura-page-actions-space: calc(var(--aura-page-actions-height, 7rem) + var(--aura-bottomnav-offset, 0px) + var(--aura-space-3)); scroll-padding-bottom: var(--aura-page-actions-space); }
1376
- :root:has(.aura-page-header--actions-bar-below-sm.aura-page-header--actions-bar-floating) { --aura-page-actions-space: calc(var(--aura-page-actions-height, 7rem) + var(--aura-bottomnav-offset, 0px) + max(var(--aura-space-3), env(safe-area-inset-bottom, 0px)) + var(--aura-space-3)); }
1377
- :root:has(.aura-page-header--actions-bar-below-sm) .aura-shell__content { padding-bottom: calc(var(--aura-space-6) + var(--aura-page-actions-space)); }
1378
- :root:has(.aura-page-header--actions-bar-below-sm):not(:has(.aura-shell__content)) body { padding-bottom: var(--aura-page-actions-space); }
1397
+ :root:has(.aura-page-header--actions-bar-below-sm:not([data-aura-bar="empty"])) { --aura-page-actions-space: calc(var(--aura-page-actions-height, 7rem) + var(--aura-bottomnav-offset, 0px) + var(--aura-space-3)); scroll-padding-bottom: var(--aura-page-actions-space); }
1398
+ :root:has(.aura-page-header--actions-bar-below-sm.aura-page-header--actions-bar-floating:not([data-aura-bar="empty"])) { --aura-page-actions-space: calc(var(--aura-page-actions-height, 7rem) + var(--aura-bottomnav-offset, 0px) + max(var(--aura-space-3), env(safe-area-inset-bottom, 0px)) + var(--aura-space-3)); }
1399
+ :root:has(.aura-page-header--actions-bar-below-sm:not([data-aura-bar="empty"])) .aura-shell__content { padding-bottom: calc(var(--aura-space-6) + var(--aura-page-actions-space)); }
1400
+ :root:has(.aura-page-header--actions-bar-below-sm:not([data-aura-bar="empty"])):not(:has(.aura-shell__content)) body { padding-bottom: var(--aura-page-actions-space); }
1379
1401
  }
1380
1402
  @media (max-width: 767.98px) and (min-height: 30.00125rem) {
1381
1403
  .aura-page-header--actions-bar-below-md > .aura-page-header__actions > .aura-page-header__bar { display: flex; flex: none; min-width: 0; }
1404
+ /* 5.74 (Chamber-OS 193): nothing but the status shown (Pay now hidden after paying): no strip, and no bar padding below. */
1405
+ .aura-page-header--actions-bar-below-md[data-aura-bar="empty"] > .aura-page-header__actions:not(.is-split), .aura-page-header--actions-bar-below-md[data-aura-bar="empty"] > .aura-page-header__actions > .aura-page-header__bar { display: none; }
1406
+ /* …and before hydration, from the markup alone (actionsBar's strip whose element children but the status are all `hidden` or visually hidden): it already goes; the page padding follows once the client has looked. */
1407
+ .aura-page-header--actions-bar-below-md:not([data-aura-bar="shown"]) > .aura-page-header__actions > .aura-page-header__bar:not(:has(> :not(.aura-page-header__status, [hidden], .sr-only, .aura-sr-only, .visually-hidden, .screen-reader-text))):has(> *) { display: none; }
1382
1408
  :is(.aura-page-header--actions-bar-below-md > .aura-page-header__actions:not(.is-split), .aura-page-header--actions-bar-below-md > .aura-page-header__actions > .aura-page-header__bar) { position: fixed; z-index: var(--aura-z-bar); left: 0; right: 0; bottom: var(--aura-bottomnav-offset, 0px); box-sizing: border-box; min-height: 60px; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: var(--aura-space-2); margin: 0; padding: var(--aura-space-2) var(--aura-space-3) calc(var(--aura-space-2) + max(0px, env(safe-area-inset-bottom, 0px) - var(--aura-bottomnav-offset, 0px))) var(--aura-space-4); background: var(--aura-bg-surface); color: var(--aura-fg-primary); border: 0; border-top: 1px solid var(--aura-border-default); border-radius: 0; box-shadow: none; transition: bottom var(--aura-duration-base) var(--aura-ease); }
1383
1409
  /* 5.54 (Chamber-OS 166): the bar's buttons are 44px whatever the density, as ActionBar's on a phone. */
1384
1410
  :is(.aura-page-header--actions-bar-below-md > .aura-page-header__actions:not(.is-split), .aura-page-header--actions-bar-below-md > .aura-page-header__actions > .aura-page-header__bar) { --aura-button-height: var(--aura-touch-target); --aura-button-height-sm: var(--aura-touch-target); }
@@ -1394,10 +1420,10 @@ summary.aura-link::-webkit-details-marker { display: none; }
1394
1420
  :is(.aura-page-header--actions-bar-below-md > .aura-page-header__actions:not(.is-split), .aura-page-header--actions-bar-below-md > .aura-page-header__actions > .aura-page-header__bar):has(> .aura-icon-btn, > * > .aura-icon-btn) > :is(.aura-icon-btn, :has(> .aura-icon-btn)) { align-self: center; }
1395
1421
  :is(.aura-page-header--actions-bar-below-md > .aura-page-header__actions:not(.is-split), .aura-page-header--actions-bar-below-md > .aura-page-header__actions > .aura-page-header__bar):has(> .aura-icon-btn, > * > .aura-icon-btn) > :is(.aura-btn, * > .aura-btn):not(.aura-icon-btn) { height: auto; min-height: var(--aura-button-height); white-space: normal; padding: var(--aura-space-2) var(--aura-space-3); line-height: 1.35; text-align: center; overflow-wrap: anywhere; hyphens: auto; border-radius: min(var(--aura-button-radius), 22px); }
1396
1422
  :is(.aura-page-header--actions-bar-below-md > .aura-page-header__actions:not(.is-split), .aura-page-header--actions-bar-below-md > .aura-page-header__actions > .aura-page-header__bar):not(:has(> .aura-btn, > * > .aura-btn)) > .aura-page-header__status { flex: 1 1 0; align-self: center; }
1397
- :root:has(.aura-page-header--actions-bar-below-md) { --aura-page-actions-space: calc(var(--aura-page-actions-height, 7rem) + var(--aura-bottomnav-offset, 0px) + var(--aura-space-3)); scroll-padding-bottom: var(--aura-page-actions-space); }
1398
- :root:has(.aura-page-header--actions-bar-below-md.aura-page-header--actions-bar-floating) { --aura-page-actions-space: calc(var(--aura-page-actions-height, 7rem) + var(--aura-bottomnav-offset, 0px) + max(var(--aura-space-3), env(safe-area-inset-bottom, 0px)) + var(--aura-space-3)); }
1399
- :root:has(.aura-page-header--actions-bar-below-md) .aura-shell__content { padding-bottom: calc(var(--aura-space-6) + var(--aura-page-actions-space)); }
1400
- :root:has(.aura-page-header--actions-bar-below-md):not(:has(.aura-shell__content)) body { padding-bottom: var(--aura-page-actions-space); }
1423
+ :root:has(.aura-page-header--actions-bar-below-md:not([data-aura-bar="empty"])) { --aura-page-actions-space: calc(var(--aura-page-actions-height, 7rem) + var(--aura-bottomnav-offset, 0px) + var(--aura-space-3)); scroll-padding-bottom: var(--aura-page-actions-space); }
1424
+ :root:has(.aura-page-header--actions-bar-below-md.aura-page-header--actions-bar-floating:not([data-aura-bar="empty"])) { --aura-page-actions-space: calc(var(--aura-page-actions-height, 7rem) + var(--aura-bottomnav-offset, 0px) + max(var(--aura-space-3), env(safe-area-inset-bottom, 0px)) + var(--aura-space-3)); }
1425
+ :root:has(.aura-page-header--actions-bar-below-md:not([data-aura-bar="empty"])) .aura-shell__content { padding-bottom: calc(var(--aura-space-6) + var(--aura-page-actions-space)); }
1426
+ :root:has(.aura-page-header--actions-bar-below-md:not([data-aura-bar="empty"])):not(:has(.aura-shell__content)) body { padding-bottom: var(--aura-page-actions-space); }
1401
1427
  }
1402
1428
  /* 5.71: as specific as the bar rules (and after them), and on actionsBar's box too, so reduced motion stops the slide. */
1403
1429
  @media (prefers-reduced-motion: reduce) { .aura-page-header.aura-page-header > .aura-page-header__actions, .aura-page-header.aura-page-header > .aura-page-header__actions > .aura-page-header__bar { transition: none; } }
@@ -1597,7 +1623,7 @@ a.aura-upload__name:focus-visible { outline: var(--aura-focus-ring-width) solid
1597
1623
  .aura-badge.is-outline { background: transparent; border-color: currentColor; }
1598
1624
  .aura-badge--neutral.is-outline { border-color: var(--aura-border-control); }
1599
1625
  .aura-badge.is-solid { background: var(--aura-button-primary-bg); color: var(--aura-button-primary-fg); }
1600
- .aura-badge--danger.is-solid { background: var(--aura-status-blocked-bg); color: var(--aura-status-blocked-fg); }
1626
+ .aura-badge--danger.is-solid { background: var(--aura-status-blocked-solid-bg); color: var(--aura-status-blocked-solid-fg); }
1601
1627
 
1602
1628
  /* ---------- Tag ---------- */
1603
1629
  .aura-tag {
@@ -1612,7 +1638,7 @@ a.aura-upload__name:focus-visible { outline: var(--aura-focus-ring-width) solid
1612
1638
  .aura-tag.is-selectable:focus-visible, .aura-tag__remove:focus-visible { outline: var(--aura-focus-ring-width) solid var(--aura-focus-ring); outline-offset: var(--aura-focus-ring-offset); }
1613
1639
  .aura-tag.is-selectable:disabled, .aura-tag.is-disabled { opacity: var(--aura-disabled-opacity); cursor: default; }
1614
1640
  .aura-tag__remove {
1615
- display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin-right: -6px; padding: 0; border: 0; border-radius: var(--aura-radius-full);
1641
+ display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; margin-inline-end: -8px; /* 5.75: a 16px × in a 24px button (was 14 in 20) */ padding: 0; border: 0; border-radius: var(--aura-radius-full);
1616
1642
  background: transparent; color: var(--aura-fg-secondary); cursor: pointer;
1617
1643
  }
1618
1644
  .aura-tag__remove:hover { background: var(--aura-hover, var(--aura-bg-surface-hover)); color: var(--aura-fg-primary); }
@@ -1630,7 +1656,7 @@ a.aura-upload__name:focus-visible { outline: var(--aura-focus-ring-width) solid
1630
1656
  .aura-progress__key { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
1631
1657
  .aura-progress__swatch { flex: none; width: 12px; height: 8px; border-radius: 2px; background: var(--aura-fg-accent); }
1632
1658
  .aura-progress--success :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-fg-positive); }
1633
- .aura-progress--warning :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-alert-warning-fg); }
1659
+ .aura-progress--warning :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-progress-warning-fill); } /* 5.75: amber, not rust */
1634
1660
  .aura-progress--danger :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-fg-danger); }
1635
1661
  .aura-progress--neutral :is(.aura-progress__bar, .aura-progress__swatch) { background: var(--aura-fg-secondary); }
1636
1662
  /* 5.15 (Chamber-OS 89): a reserved amount after the value — the same tone, striped, so it reads without colour. */
@@ -2026,6 +2052,11 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
2026
2052
  .aura-filterbar__search .aura-input__control::-webkit-search-cancel-button { display: none; }
2027
2053
  .aura-filterbar__clear-search { margin-right: 6px; }
2028
2054
  .aura-filterbar__controls { display: flex; flex-wrap: wrap; align-items: center; gap: var(--aura-space-3); }
2055
+ /* 5.75 (visual audit): a toggle Tag among the controls is as tall as the search and selects beside it. */
2056
+ .aura-filterbar__controls .aura-tag.is-selectable { min-height: var(--aura-input-height); }
2057
+ @media (max-width: 639.98px), (pointer: coarse) {
2058
+ .aura-filterbar__controls .aura-tag.aura-tag--touch { min-height: max(var(--aura-input-height), var(--aura-touch-target)); } /* a touchHeight Tag keeps 44px (5.25) */
2059
+ }
2029
2060
  .aura-filterbar__spacer { flex: 1 1 0; }
2030
2061
  .aura-filterbar__count { font-size: max(13px, .8125rem); color: var(--aura-fg-secondary); font-variant-numeric: tabular-nums; white-space: nowrap; }
2031
2062
  .aura-filterbar__actions { display: flex; align-items: center; gap: var(--aura-space-2); }
@@ -2666,6 +2697,6 @@ button.aura-stepper__step:hover .aura-stepper__label { text-decoration: underlin
2666
2697
 
2667
2698
  /* 5.30: a plain link (no class) inside AURA's surfaces takes the accent colour instead of the browser's blue. Zero
2668
2699
  * specificity, so any rule of yours for links wins. */
2669
- :where(.aura-card, .aura-stat, .aura-alert, .aura-empty, .aura-field, .aura-tabs__panel, .aura-shell__main, .aura-tbl, .aura-table, .aura-dialog) :where(a:not([class])) {
2670
- color: var(--aura-fg-accent); text-underline-offset: 2px;
2700
+ :where(.aura-card, .aura-stat, .aura-alert, .aura-empty, .aura-field, .aura-tabs__panel, .aura-shell__main, .aura-tbl, .aura-table, .aura-dialog, .aura-page-header, .aura-popover, .aura-drawer, .aura-prose, .aura-text-body, .aura-text-caption) :where(a:not([class])) {
2701
+ color: var(--aura-fg-accent); text-decoration-line: underline; text-decoration-thickness: 1px; text-underline-offset: 2px; /* 5.75: more surfaces, and always underlined */
2671
2702
  }