@judarn/ui 0.7.12 → 0.8.2

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/README.md CHANGED
@@ -21,6 +21,10 @@ export function Status() {
21
21
 
22
22
  `react` 18 or later is the only peer dependency. The package ships compiled ESM and CommonJS. Other entry points: `@judarn/ui/colors` (CSS custom properties only), `@judarn/ui/theme` and `@judarn/ui/tailwind` (Tailwind v4), and `@judarn/ui/sdui` (server-driven UI).
23
23
 
24
+ ## Class names
25
+
26
+ `@judarn/ui/styles` and `@judarn/ui/components.css` style only `jd-` classes (`jd-btn`, `jd-badge`, `jd-tag`, `jd-card`, `jd-field`, `jd-navbar`, `jd-footer`, `jd-table`, `jd-matrix`, `jd-lockup`, `jd-list`), so a project's own `.btn` or `.card` stays its own. Buttons, badges, and tags are set apart by fill, with a transparent border that high-contrast modes turn into an outline. Put `jd-list` on a `ul` or `ol` of text items for bullets or numbers in secondary ink with a hanging indent, and `jd-list jd-list--plain` with `role="list"` on rows of links or data.
27
+
24
28
  ## Accent colors
25
29
 
26
30
  A project that sets its own `--jd-accent` also sets `--jd-accent-ink` to `#000000` or `#ffffff`, whichever stays readable on that fill. Accent buttons and badges read the resolved `--accent-ink`. The `--jd-accent*` hooks have no default, so a value set on `:root` carries into nested scopes.
@@ -2,9 +2,9 @@ import * as React from "react";
2
2
 
3
3
  export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
4
4
  children?: React.ReactNode;
5
- /** @default "outline" */
6
- variant?: "outline" | "solid" | "accent" | "accent-outline";
7
- /** Optional custom accent color hex or rgb string. Automatically resolves accessible contrasting text ink. */
5
+ /** Neutral is a well fill, accent-tint a light tint of the accent (neutral in dark mode). @default "neutral" */
6
+ variant?: "neutral" | "solid" | "accent" | "accent-tint";
7
+ /** Optional custom accent color (#hex, rgb() or oklch()). The accent variant fills with it and resolves contrasting ink; accent-tint tints with it. */
8
8
  accentColor?: string;
9
9
  }
10
10
 
@@ -3,7 +3,7 @@ import * as React from "react";
3
3
  export interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "type"> {
4
4
  children?: React.ReactNode;
5
5
  /** Visual style. @default "primary" */
6
- variant?: "primary" | "outline" | "secondary" | "ghost" | "accent";
6
+ variant?: "primary" | "secondary" | "ghost" | "accent";
7
7
  /** @default "md" */
8
8
  size?: "sm" | "md" | "lg";
9
9
  /** Stretch to full container width. @default false */
@@ -14,6 +14,6 @@ export interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonE
14
14
  onClick?: React.MouseEventHandler<HTMLButtonElement>;
15
15
  }
16
16
 
17
- /** Judarn button – flat fill, border, and hover inversion. A consumer `className` extends the base classes. */
17
+ /** Judarn button – set apart by fill alone, with hover inversion. A consumer `className` extends the base classes. */
18
18
  export function Button(props: ButtonProps): React.JSX.Element;
19
19
  export default Button;
package/dist/index.cjs CHANGED
@@ -115,29 +115,27 @@ function getContrastingInk(color) {
115
115
  const perceptualCrossover = 0.5;
116
116
  return lum > perceptualCrossover ? "#000000" : "#ffffff";
117
117
  }
118
- function Badge({ children, variant = "outline", accentColor, style, className, ...rest }) {
118
+ function Badge({ children, variant = "neutral", accentColor, style, className, ...rest }) {
119
119
  const cls = cx(
120
- "badge",
121
- variant === "solid" && "badge--solid",
122
- variant === "accent" && "badge--accent",
123
- variant === "accent-outline" && "badge--accent-outline",
120
+ "jd-badge",
121
+ variant === "solid" && "jd-badge--solid",
122
+ variant === "accent" && "jd-badge--accent",
123
+ variant === "accent-tint" && "jd-badge--accent-tint",
124
124
  className
125
125
  );
126
126
  const inlineStyle = { ...style };
127
127
  if (accentColor) {
128
128
  if (variant === "accent") {
129
129
  inlineStyle.backgroundColor = accentColor;
130
- inlineStyle.borderColor = accentColor;
131
130
  inlineStyle.color = getContrastingInk(accentColor);
132
- } else if (variant === "accent-outline") {
133
- inlineStyle.borderColor = accentColor;
134
- inlineStyle.color = accentColor;
131
+ } else if (variant === "accent-tint") {
132
+ inlineStyle["--jd-badge-accent"] = accentColor;
135
133
  }
136
134
  }
137
135
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...rest, className: cls, style: Object.keys(inlineStyle).length > 0 ? inlineStyle : void 0, children });
138
136
  }
139
137
  function Tag({ children, className, ...rest }) {
140
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...rest, className: cx("tag", className), children });
138
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { ...rest, className: cx("jd-tag", className), children });
141
139
  }
142
140
  var Badge_default = Badge;
143
141
 
@@ -155,14 +153,13 @@ function Button({
155
153
  ...rest
156
154
  }) {
157
155
  const cls = cx(
158
- "btn",
159
- variant === "outline" && "btn--outline",
160
- variant === "secondary" && "btn--secondary",
161
- variant === "ghost" && "btn--ghost",
162
- variant === "accent" && "btn--accent",
163
- size === "sm" && "btn--sm",
164
- size === "lg" && "btn--lg",
165
- block && "btn--block",
156
+ "jd-btn",
157
+ variant === "secondary" && "jd-btn--secondary",
158
+ variant === "ghost" && "jd-btn--ghost",
159
+ variant === "accent" && "jd-btn--accent",
160
+ size === "sm" && "jd-btn--sm",
161
+ size === "lg" && "jd-btn--lg",
162
+ block && "jd-btn--block",
166
163
  className
167
164
  );
168
165
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { ...rest, className: cls, type, disabled, onClick, children });
@@ -183,12 +180,12 @@ function Card({
183
180
  className,
184
181
  ...rest
185
182
  }) {
186
- const cls = cx("card", flat && "card--flat", compact && "card--compact", className);
183
+ const cls = cx("jd-card", flat && "jd-card--flat", compact && "jd-card--compact", className);
187
184
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { ...rest, className: cls, children: [
188
- media ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "card__media", children: media }) : null,
189
- eyebrow ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: cx("card__eyebrow", "t-label", eyebrowCasing === "asWritten" && "t-as-written"), children: eyebrow }) : null,
190
- title ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "card__title", children: title }) : null,
191
- children ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "card__body", children }) : null,
185
+ media ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "jd-card__media", children: media }) : null,
186
+ eyebrow ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: cx("jd-card__eyebrow", "t-label", eyebrowCasing === "asWritten" && "t-as-written"), children: eyebrow }) : null,
187
+ title ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "jd-card__title", children: title }) : null,
188
+ children ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "jd-card__body", children }) : null,
192
189
  actions ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "flex", gap: "var(--gap-tight)" }, children: actions }) : null
193
190
  ] });
194
191
  }
@@ -204,7 +201,7 @@ function DataMatrix({ columns: columns2 = [], rows: rows2 = [], label = "Data Ma
204
201
  "table",
205
202
  {
206
203
  ...rest,
207
- className: cx("matrix", className),
204
+ className: cx("jd-matrix", className),
208
205
  role: "table",
209
206
  "aria-label": label,
210
207
  "aria-colcount": totalColumns,
@@ -275,7 +272,7 @@ var import_jsx_runtime5 = require("react/jsx-runtime");
275
272
  var headerClass = (c) => [c.numeric && "num", c.casing === "asWritten" && "t-as-written"].filter(Boolean).join(" ") || void 0;
276
273
  function DataTable({ columns: columns2 = [], rows: rows2 = [], className, ...rest }) {
277
274
  const cols = columns2.map((c) => typeof c === "string" ? { key: c, label: c } : c);
278
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("table", { ...rest, className: cx("table", className), children: [
275
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("table", { ...rest, className: cx("jd-table", className), children: [
279
276
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("tr", { children: cols.map((c) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("th", { className: headerClass(c), children: c.label }, c.key)) }) }),
280
277
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("tbody", { children: rows2.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("tr", { children: cols.map((c) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("td", { className: c.numeric ? "num" : void 0, children: row[c.key] }, c.key)) }, i)) })
281
278
  ] });
@@ -285,18 +282,18 @@ var DataTable_default = DataTable;
285
282
  // src/Footer/Footer.jsx
286
283
  var import_jsx_runtime6 = require("react/jsx-runtime");
287
284
  function Footer({ brand = "JUDARN", tagline, columns: columns2 = [], headCasing = "uppercase", className, ...rest }) {
288
- const headClass = headCasing === "asWritten" ? "footer__head t-label t-as-written" : "footer__head t-label";
289
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("footer", { ...rest, className: cx("footer", className), children: [
290
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "footer__col", children: [
291
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "navbar__brand", children: brand }),
292
- tagline ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "footer__link", style: { marginTop: "8px" }, children: tagline }) : null
285
+ const headClass = headCasing === "asWritten" ? "jd-footer__head t-label t-as-written" : "jd-footer__head t-label";
286
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("footer", { ...rest, className: cx("jd-footer", className), children: [
287
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "jd-footer__col", children: [
288
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "jd-navbar__brand", children: brand }),
289
+ tagline ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "jd-footer__link", style: { marginTop: "8px" }, children: tagline }) : null
293
290
  ] }),
294
- columns2.map((col) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "footer__col", children: [
291
+ columns2.map((col) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "jd-footer__col", children: [
295
292
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { className: headClass, children: col.head }),
296
293
  (col.links || []).map((l) => {
297
294
  const label = typeof l === "string" ? l : l.label;
298
295
  const href = typeof l === "string" ? "#" : l.href || "#";
299
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { className: "footer__link", href, children: label }, label);
296
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { className: "jd-footer__link", href, children: label }, label);
300
297
  })
301
298
  ] }, col.head))
302
299
  ] });
@@ -322,19 +319,19 @@ function LogoLockup({
322
319
  }) {
323
320
  const isIcon = variant === "icon";
324
321
  const cls = [
325
- "lockup",
326
- variant === "vertical" && "lockup--vertical",
327
- variant === "reversed" && "lockup--reversed"
322
+ "jd-lockup",
323
+ variant === "vertical" && "jd-lockup--vertical",
324
+ variant === "reversed" && "jd-lockup--reversed"
328
325
  ].filter(Boolean).join(" ");
329
326
  const theMark = mark || /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PlaceholderMark, { size: markSize || (variant === "vertical" ? 64 : 48) });
330
327
  if (isIcon) {
331
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "lockup__mark", role: "img", "aria-label": name, children: mark || /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PlaceholderMark, { size: markSize || 64 }) });
328
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "jd-lockup__mark", role: "img", "aria-label": name, children: mark || /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PlaceholderMark, { size: markSize || 64 }) });
332
329
  }
333
330
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: cls, role: "img", "aria-label": tagline ? `${name} \u2013 ${tagline}` : name, children: [
334
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "lockup__mark", children: theMark }),
335
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "lockup__words", children: [
336
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "lockup__name", children: name }),
337
- tagline ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: taglineCasing === "asWritten" ? "lockup__tag t-label t-as-written" : "lockup__tag t-label", children: tagline }) : null
331
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "jd-lockup__mark", children: theMark }),
332
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "jd-lockup__words", children: [
333
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "jd-lockup__name", children: name }),
334
+ tagline ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: taglineCasing === "asWritten" ? "jd-lockup__tag t-label t-as-written" : "jd-lockup__tag t-label", children: tagline }) : null
338
335
  ] })
339
336
  ] });
340
337
  }
@@ -343,16 +340,16 @@ var LogoLockup_default = LogoLockup;
343
340
  // src/Navbar/Navbar.jsx
344
341
  var import_jsx_runtime8 = require("react/jsx-runtime");
345
342
  function Navbar({ brand = "JUDARN", links = [], current, actions, className, ...rest }) {
346
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("nav", { ...rest, className: cx("navbar", className), children: [
347
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "navbar__brand", children: brand }),
348
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("ul", { className: "navbar__links", children: [
343
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("nav", { ...rest, className: cx("jd-navbar", className), children: [
344
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "jd-navbar__brand", children: brand }),
345
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("ul", { className: "jd-navbar__links", children: [
349
346
  links.map((l) => {
350
347
  const label = typeof l === "string" ? l : l.label;
351
348
  const href = typeof l === "string" ? "#" : l.href || "#";
352
349
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
353
350
  "a",
354
351
  {
355
- className: "navbar__link",
352
+ className: "jd-navbar__link",
356
353
  href,
357
354
  "aria-current": label === current ? "page" : void 0,
358
355
  children: label
@@ -390,7 +387,7 @@ function TextField({
390
387
  const common = {
391
388
  ...rest,
392
389
  id: fid,
393
- className: cx("field__input", className),
390
+ className: cx("jd-field__input", className),
394
391
  "aria-invalid": invalid || error ? true : void 0,
395
392
  "aria-describedby": describedBy,
396
393
  disabled
@@ -399,11 +396,11 @@ function TextField({
399
396
  if (as === "textarea") control = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("textarea", { ...common });
400
397
  else if (as === "select") control = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("select", { ...common, children });
401
398
  else control = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("input", { ...common });
402
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "field", children: [
403
- label ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: cx("field__label", labelCasing === "asWritten" && "t-as-written"), htmlFor: fid, children: label }) : null,
399
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "jd-field", children: [
400
+ label ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: cx("jd-field__label", labelCasing === "asWritten" && "t-as-written"), htmlFor: fid, children: label }) : null,
404
401
  control,
405
- hint ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "field__hint", id: hintId, children: hint }) : null,
406
- error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "field__hint is-error", id: errorId, children: error }) : null
402
+ hint ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "jd-field__hint", id: hintId, children: hint }) : null,
403
+ error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "jd-field__hint is-error", id: errorId, children: error }) : null
407
404
  ] });
408
405
  }
409
406
  var TextField_default = TextField;
@@ -433,8 +430,8 @@ var VALID_NODE_TYPES = /* @__PURE__ */ new Set([
433
430
  "container",
434
431
  "text"
435
432
  ]);
436
- var BUTTON_VARIANTS = ["primary", "outline", "secondary", "ghost", "accent"];
437
- var BADGE_VARIANTS = ["outline", "solid", "accent", "accent-outline"];
433
+ var BUTTON_VARIANTS = ["primary", "secondary", "ghost", "accent"];
434
+ var BADGE_VARIANTS = ["neutral", "solid", "accent", "accent-tint"];
438
435
  var SIZES = ["sm", "md", "lg"];
439
436
  var CASINGS = ["uppercase", "asWritten"];
440
437
  var VALID_TOKEN_NAMES = /* @__PURE__ */ new Set([
package/dist/index.mjs CHANGED
@@ -75,29 +75,27 @@ function getContrastingInk(color) {
75
75
  const perceptualCrossover = 0.5;
76
76
  return lum > perceptualCrossover ? "#000000" : "#ffffff";
77
77
  }
78
- function Badge({ children, variant = "outline", accentColor, style, className, ...rest }) {
78
+ function Badge({ children, variant = "neutral", accentColor, style, className, ...rest }) {
79
79
  const cls = cx(
80
- "badge",
81
- variant === "solid" && "badge--solid",
82
- variant === "accent" && "badge--accent",
83
- variant === "accent-outline" && "badge--accent-outline",
80
+ "jd-badge",
81
+ variant === "solid" && "jd-badge--solid",
82
+ variant === "accent" && "jd-badge--accent",
83
+ variant === "accent-tint" && "jd-badge--accent-tint",
84
84
  className
85
85
  );
86
86
  const inlineStyle = { ...style };
87
87
  if (accentColor) {
88
88
  if (variant === "accent") {
89
89
  inlineStyle.backgroundColor = accentColor;
90
- inlineStyle.borderColor = accentColor;
91
90
  inlineStyle.color = getContrastingInk(accentColor);
92
- } else if (variant === "accent-outline") {
93
- inlineStyle.borderColor = accentColor;
94
- inlineStyle.color = accentColor;
91
+ } else if (variant === "accent-tint") {
92
+ inlineStyle["--jd-badge-accent"] = accentColor;
95
93
  }
96
94
  }
97
95
  return /* @__PURE__ */ jsx("span", { ...rest, className: cls, style: Object.keys(inlineStyle).length > 0 ? inlineStyle : void 0, children });
98
96
  }
99
97
  function Tag({ children, className, ...rest }) {
100
- return /* @__PURE__ */ jsx("span", { ...rest, className: cx("tag", className), children });
98
+ return /* @__PURE__ */ jsx("span", { ...rest, className: cx("jd-tag", className), children });
101
99
  }
102
100
  var Badge_default = Badge;
103
101
 
@@ -115,14 +113,13 @@ function Button({
115
113
  ...rest
116
114
  }) {
117
115
  const cls = cx(
118
- "btn",
119
- variant === "outline" && "btn--outline",
120
- variant === "secondary" && "btn--secondary",
121
- variant === "ghost" && "btn--ghost",
122
- variant === "accent" && "btn--accent",
123
- size === "sm" && "btn--sm",
124
- size === "lg" && "btn--lg",
125
- block && "btn--block",
116
+ "jd-btn",
117
+ variant === "secondary" && "jd-btn--secondary",
118
+ variant === "ghost" && "jd-btn--ghost",
119
+ variant === "accent" && "jd-btn--accent",
120
+ size === "sm" && "jd-btn--sm",
121
+ size === "lg" && "jd-btn--lg",
122
+ block && "jd-btn--block",
126
123
  className
127
124
  );
128
125
  return /* @__PURE__ */ jsx2("button", { ...rest, className: cls, type, disabled, onClick, children });
@@ -143,12 +140,12 @@ function Card({
143
140
  className,
144
141
  ...rest
145
142
  }) {
146
- const cls = cx("card", flat && "card--flat", compact && "card--compact", className);
143
+ const cls = cx("jd-card", flat && "jd-card--flat", compact && "jd-card--compact", className);
147
144
  return /* @__PURE__ */ jsxs("div", { ...rest, className: cls, children: [
148
- media ? /* @__PURE__ */ jsx3("div", { className: "card__media", children: media }) : null,
149
- eyebrow ? /* @__PURE__ */ jsx3("span", { className: cx("card__eyebrow", "t-label", eyebrowCasing === "asWritten" && "t-as-written"), children: eyebrow }) : null,
150
- title ? /* @__PURE__ */ jsx3("div", { className: "card__title", children: title }) : null,
151
- children ? /* @__PURE__ */ jsx3("div", { className: "card__body", children }) : null,
145
+ media ? /* @__PURE__ */ jsx3("div", { className: "jd-card__media", children: media }) : null,
146
+ eyebrow ? /* @__PURE__ */ jsx3("span", { className: cx("jd-card__eyebrow", "t-label", eyebrowCasing === "asWritten" && "t-as-written"), children: eyebrow }) : null,
147
+ title ? /* @__PURE__ */ jsx3("div", { className: "jd-card__title", children: title }) : null,
148
+ children ? /* @__PURE__ */ jsx3("div", { className: "jd-card__body", children }) : null,
152
149
  actions ? /* @__PURE__ */ jsx3("div", { style: { display: "flex", gap: "var(--gap-tight)" }, children: actions }) : null
153
150
  ] });
154
151
  }
@@ -164,7 +161,7 @@ function DataMatrix({ columns: columns2 = [], rows: rows2 = [], label = "Data Ma
164
161
  "table",
165
162
  {
166
163
  ...rest,
167
- className: cx("matrix", className),
164
+ className: cx("jd-matrix", className),
168
165
  role: "table",
169
166
  "aria-label": label,
170
167
  "aria-colcount": totalColumns,
@@ -235,7 +232,7 @@ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
235
232
  var headerClass = (c) => [c.numeric && "num", c.casing === "asWritten" && "t-as-written"].filter(Boolean).join(" ") || void 0;
236
233
  function DataTable({ columns: columns2 = [], rows: rows2 = [], className, ...rest }) {
237
234
  const cols = columns2.map((c) => typeof c === "string" ? { key: c, label: c } : c);
238
- return /* @__PURE__ */ jsxs3("table", { ...rest, className: cx("table", className), children: [
235
+ return /* @__PURE__ */ jsxs3("table", { ...rest, className: cx("jd-table", className), children: [
239
236
  /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsx5("tr", { children: cols.map((c) => /* @__PURE__ */ jsx5("th", { className: headerClass(c), children: c.label }, c.key)) }) }),
240
237
  /* @__PURE__ */ jsx5("tbody", { children: rows2.map((row, i) => /* @__PURE__ */ jsx5("tr", { children: cols.map((c) => /* @__PURE__ */ jsx5("td", { className: c.numeric ? "num" : void 0, children: row[c.key] }, c.key)) }, i)) })
241
238
  ] });
@@ -245,18 +242,18 @@ var DataTable_default = DataTable;
245
242
  // src/Footer/Footer.jsx
246
243
  import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
247
244
  function Footer({ brand = "JUDARN", tagline, columns: columns2 = [], headCasing = "uppercase", className, ...rest }) {
248
- const headClass = headCasing === "asWritten" ? "footer__head t-label t-as-written" : "footer__head t-label";
249
- return /* @__PURE__ */ jsxs4("footer", { ...rest, className: cx("footer", className), children: [
250
- /* @__PURE__ */ jsxs4("div", { className: "footer__col", children: [
251
- /* @__PURE__ */ jsx6("div", { className: "navbar__brand", children: brand }),
252
- tagline ? /* @__PURE__ */ jsx6("p", { className: "footer__link", style: { marginTop: "8px" }, children: tagline }) : null
245
+ const headClass = headCasing === "asWritten" ? "jd-footer__head t-label t-as-written" : "jd-footer__head t-label";
246
+ return /* @__PURE__ */ jsxs4("footer", { ...rest, className: cx("jd-footer", className), children: [
247
+ /* @__PURE__ */ jsxs4("div", { className: "jd-footer__col", children: [
248
+ /* @__PURE__ */ jsx6("div", { className: "jd-navbar__brand", children: brand }),
249
+ tagline ? /* @__PURE__ */ jsx6("p", { className: "jd-footer__link", style: { marginTop: "8px" }, children: tagline }) : null
253
250
  ] }),
254
- columns2.map((col) => /* @__PURE__ */ jsxs4("div", { className: "footer__col", children: [
251
+ columns2.map((col) => /* @__PURE__ */ jsxs4("div", { className: "jd-footer__col", children: [
255
252
  /* @__PURE__ */ jsx6("h2", { className: headClass, children: col.head }),
256
253
  (col.links || []).map((l) => {
257
254
  const label = typeof l === "string" ? l : l.label;
258
255
  const href = typeof l === "string" ? "#" : l.href || "#";
259
- return /* @__PURE__ */ jsx6("a", { className: "footer__link", href, children: label }, label);
256
+ return /* @__PURE__ */ jsx6("a", { className: "jd-footer__link", href, children: label }, label);
260
257
  })
261
258
  ] }, col.head))
262
259
  ] });
@@ -282,19 +279,19 @@ function LogoLockup({
282
279
  }) {
283
280
  const isIcon = variant === "icon";
284
281
  const cls = [
285
- "lockup",
286
- variant === "vertical" && "lockup--vertical",
287
- variant === "reversed" && "lockup--reversed"
282
+ "jd-lockup",
283
+ variant === "vertical" && "jd-lockup--vertical",
284
+ variant === "reversed" && "jd-lockup--reversed"
288
285
  ].filter(Boolean).join(" ");
289
286
  const theMark = mark || /* @__PURE__ */ jsx7(PlaceholderMark, { size: markSize || (variant === "vertical" ? 64 : 48) });
290
287
  if (isIcon) {
291
- return /* @__PURE__ */ jsx7("span", { className: "lockup__mark", role: "img", "aria-label": name, children: mark || /* @__PURE__ */ jsx7(PlaceholderMark, { size: markSize || 64 }) });
288
+ return /* @__PURE__ */ jsx7("span", { className: "jd-lockup__mark", role: "img", "aria-label": name, children: mark || /* @__PURE__ */ jsx7(PlaceholderMark, { size: markSize || 64 }) });
292
289
  }
293
290
  return /* @__PURE__ */ jsxs5("div", { className: cls, role: "img", "aria-label": tagline ? `${name} \u2013 ${tagline}` : name, children: [
294
- /* @__PURE__ */ jsx7("span", { className: "lockup__mark", children: theMark }),
295
- /* @__PURE__ */ jsxs5("span", { className: "lockup__words", children: [
296
- /* @__PURE__ */ jsx7("span", { className: "lockup__name", children: name }),
297
- tagline ? /* @__PURE__ */ jsx7("span", { className: taglineCasing === "asWritten" ? "lockup__tag t-label t-as-written" : "lockup__tag t-label", children: tagline }) : null
291
+ /* @__PURE__ */ jsx7("span", { className: "jd-lockup__mark", children: theMark }),
292
+ /* @__PURE__ */ jsxs5("span", { className: "jd-lockup__words", children: [
293
+ /* @__PURE__ */ jsx7("span", { className: "jd-lockup__name", children: name }),
294
+ tagline ? /* @__PURE__ */ jsx7("span", { className: taglineCasing === "asWritten" ? "jd-lockup__tag t-label t-as-written" : "jd-lockup__tag t-label", children: tagline }) : null
298
295
  ] })
299
296
  ] });
300
297
  }
@@ -303,16 +300,16 @@ var LogoLockup_default = LogoLockup;
303
300
  // src/Navbar/Navbar.jsx
304
301
  import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
305
302
  function Navbar({ brand = "JUDARN", links = [], current, actions, className, ...rest }) {
306
- return /* @__PURE__ */ jsxs6("nav", { ...rest, className: cx("navbar", className), children: [
307
- /* @__PURE__ */ jsx8("div", { className: "navbar__brand", children: brand }),
308
- /* @__PURE__ */ jsxs6("ul", { className: "navbar__links", children: [
303
+ return /* @__PURE__ */ jsxs6("nav", { ...rest, className: cx("jd-navbar", className), children: [
304
+ /* @__PURE__ */ jsx8("div", { className: "jd-navbar__brand", children: brand }),
305
+ /* @__PURE__ */ jsxs6("ul", { className: "jd-navbar__links", children: [
309
306
  links.map((l) => {
310
307
  const label = typeof l === "string" ? l : l.label;
311
308
  const href = typeof l === "string" ? "#" : l.href || "#";
312
309
  return /* @__PURE__ */ jsx8("li", { children: /* @__PURE__ */ jsx8(
313
310
  "a",
314
311
  {
315
- className: "navbar__link",
312
+ className: "jd-navbar__link",
316
313
  href,
317
314
  "aria-current": label === current ? "page" : void 0,
318
315
  children: label
@@ -350,7 +347,7 @@ function TextField({
350
347
  const common = {
351
348
  ...rest,
352
349
  id: fid,
353
- className: cx("field__input", className),
350
+ className: cx("jd-field__input", className),
354
351
  "aria-invalid": invalid || error ? true : void 0,
355
352
  "aria-describedby": describedBy,
356
353
  disabled
@@ -359,11 +356,11 @@ function TextField({
359
356
  if (as === "textarea") control = /* @__PURE__ */ jsx9("textarea", { ...common });
360
357
  else if (as === "select") control = /* @__PURE__ */ jsx9("select", { ...common, children });
361
358
  else control = /* @__PURE__ */ jsx9("input", { ...common });
362
- return /* @__PURE__ */ jsxs7("div", { className: "field", children: [
363
- label ? /* @__PURE__ */ jsx9("label", { className: cx("field__label", labelCasing === "asWritten" && "t-as-written"), htmlFor: fid, children: label }) : null,
359
+ return /* @__PURE__ */ jsxs7("div", { className: "jd-field", children: [
360
+ label ? /* @__PURE__ */ jsx9("label", { className: cx("jd-field__label", labelCasing === "asWritten" && "t-as-written"), htmlFor: fid, children: label }) : null,
364
361
  control,
365
- hint ? /* @__PURE__ */ jsx9("span", { className: "field__hint", id: hintId, children: hint }) : null,
366
- error ? /* @__PURE__ */ jsx9("span", { className: "field__hint is-error", id: errorId, children: error }) : null
362
+ hint ? /* @__PURE__ */ jsx9("span", { className: "jd-field__hint", id: hintId, children: hint }) : null,
363
+ error ? /* @__PURE__ */ jsx9("span", { className: "jd-field__hint is-error", id: errorId, children: error }) : null
367
364
  ] });
368
365
  }
369
366
  var TextField_default = TextField;
@@ -393,8 +390,8 @@ var VALID_NODE_TYPES = /* @__PURE__ */ new Set([
393
390
  "container",
394
391
  "text"
395
392
  ]);
396
- var BUTTON_VARIANTS = ["primary", "outline", "secondary", "ghost", "accent"];
397
- var BADGE_VARIANTS = ["outline", "solid", "accent", "accent-outline"];
393
+ var BUTTON_VARIANTS = ["primary", "secondary", "ghost", "accent"];
394
+ var BADGE_VARIANTS = ["neutral", "solid", "accent", "accent-tint"];
398
395
  var SIZES = ["sm", "md", "lg"];
399
396
  var CASINGS = ["uppercase", "asWritten"];
400
397
  var VALID_TOKEN_NAMES = /* @__PURE__ */ new Set([
@@ -61,14 +61,13 @@ function Button({
61
61
  ...rest
62
62
  }) {
63
63
  const cls = cx(
64
- "btn",
65
- variant === "outline" && "btn--outline",
66
- variant === "secondary" && "btn--secondary",
67
- variant === "ghost" && "btn--ghost",
68
- variant === "accent" && "btn--accent",
69
- size === "sm" && "btn--sm",
70
- size === "lg" && "btn--lg",
71
- block && "btn--block",
64
+ "jd-btn",
65
+ variant === "secondary" && "jd-btn--secondary",
66
+ variant === "ghost" && "jd-btn--ghost",
67
+ variant === "accent" && "jd-btn--accent",
68
+ size === "sm" && "jd-btn--sm",
69
+ size === "lg" && "jd-btn--lg",
70
+ block && "jd-btn--block",
72
71
  className
73
72
  );
74
73
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { ...rest, className: cls, type, disabled, onClick, children });
@@ -140,29 +139,27 @@ function getContrastingInk(color) {
140
139
  const perceptualCrossover = 0.5;
141
140
  return lum > perceptualCrossover ? "#000000" : "#ffffff";
142
141
  }
143
- function Badge({ children, variant = "outline", accentColor, style, className, ...rest }) {
142
+ function Badge({ children, variant = "neutral", accentColor, style, className, ...rest }) {
144
143
  const cls = cx(
145
- "badge",
146
- variant === "solid" && "badge--solid",
147
- variant === "accent" && "badge--accent",
148
- variant === "accent-outline" && "badge--accent-outline",
144
+ "jd-badge",
145
+ variant === "solid" && "jd-badge--solid",
146
+ variant === "accent" && "jd-badge--accent",
147
+ variant === "accent-tint" && "jd-badge--accent-tint",
149
148
  className
150
149
  );
151
150
  const inlineStyle = { ...style };
152
151
  if (accentColor) {
153
152
  if (variant === "accent") {
154
153
  inlineStyle.backgroundColor = accentColor;
155
- inlineStyle.borderColor = accentColor;
156
154
  inlineStyle.color = getContrastingInk(accentColor);
157
- } else if (variant === "accent-outline") {
158
- inlineStyle.borderColor = accentColor;
159
- inlineStyle.color = accentColor;
155
+ } else if (variant === "accent-tint") {
156
+ inlineStyle["--jd-badge-accent"] = accentColor;
160
157
  }
161
158
  }
162
159
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { ...rest, className: cls, style: Object.keys(inlineStyle).length > 0 ? inlineStyle : void 0, children });
163
160
  }
164
161
  function Tag({ children, className, ...rest }) {
165
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { ...rest, className: cx("tag", className), children });
162
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { ...rest, className: cx("jd-tag", className), children });
166
163
  }
167
164
 
168
165
  // src/Card/Card.jsx
@@ -179,12 +176,12 @@ function Card({
179
176
  className,
180
177
  ...rest
181
178
  }) {
182
- const cls = cx("card", flat && "card--flat", compact && "card--compact", className);
179
+ const cls = cx("jd-card", flat && "jd-card--flat", compact && "jd-card--compact", className);
183
180
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { ...rest, className: cls, children: [
184
- media ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "card__media", children: media }) : null,
185
- eyebrow ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: cx("card__eyebrow", "t-label", eyebrowCasing === "asWritten" && "t-as-written"), children: eyebrow }) : null,
186
- title ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "card__title", children: title }) : null,
187
- children ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "card__body", children }) : null,
181
+ media ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "jd-card__media", children: media }) : null,
182
+ eyebrow ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: cx("jd-card__eyebrow", "t-label", eyebrowCasing === "asWritten" && "t-as-written"), children: eyebrow }) : null,
183
+ title ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "jd-card__title", children: title }) : null,
184
+ children ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "jd-card__body", children }) : null,
188
185
  actions ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { display: "flex", gap: "var(--gap-tight)" }, children: actions }) : null
189
186
  ] });
190
187
  }
@@ -199,7 +196,7 @@ function DataMatrix({ columns: columns2 = [], rows: rows2 = [], label = "Data Ma
199
196
  "table",
200
197
  {
201
198
  ...rest,
202
- className: cx("matrix", className),
199
+ className: cx("jd-matrix", className),
203
200
  role: "table",
204
201
  "aria-label": label,
205
202
  "aria-colcount": totalColumns,
@@ -269,7 +266,7 @@ var import_jsx_runtime5 = require("react/jsx-runtime");
269
266
  var headerClass = (c) => [c.numeric && "num", c.casing === "asWritten" && "t-as-written"].filter(Boolean).join(" ") || void 0;
270
267
  function DataTable({ columns: columns2 = [], rows: rows2 = [], className, ...rest }) {
271
268
  const cols = columns2.map((c) => typeof c === "string" ? { key: c, label: c } : c);
272
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("table", { ...rest, className: cx("table", className), children: [
269
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("table", { ...rest, className: cx("jd-table", className), children: [
273
270
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("tr", { children: cols.map((c) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("th", { className: headerClass(c), children: c.label }, c.key)) }) }),
274
271
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("tbody", { children: rows2.map((row, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("tr", { children: cols.map((c) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("td", { className: c.numeric ? "num" : void 0, children: row[c.key] }, c.key)) }, i)) })
275
272
  ] });
@@ -300,7 +297,7 @@ function TextField({
300
297
  const common = {
301
298
  ...rest,
302
299
  id: fid,
303
- className: cx("field__input", className),
300
+ className: cx("jd-field__input", className),
304
301
  "aria-invalid": invalid || error ? true : void 0,
305
302
  "aria-describedby": describedBy,
306
303
  disabled
@@ -309,11 +306,11 @@ function TextField({
309
306
  if (as === "textarea") control = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("textarea", { ...common });
310
307
  else if (as === "select") control = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("select", { ...common, children });
311
308
  else control = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { ...common });
312
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "field", children: [
313
- label ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("label", { className: cx("field__label", labelCasing === "asWritten" && "t-as-written"), htmlFor: fid, children: label }) : null,
309
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "jd-field", children: [
310
+ label ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("label", { className: cx("jd-field__label", labelCasing === "asWritten" && "t-as-written"), htmlFor: fid, children: label }) : null,
314
311
  control,
315
- hint ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "field__hint", id: hintId, children: hint }) : null,
316
- error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "field__hint is-error", id: errorId, children: error }) : null
312
+ hint ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "jd-field__hint", id: hintId, children: hint }) : null,
313
+ error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "jd-field__hint is-error", id: errorId, children: error }) : null
317
314
  ] });
318
315
  }
319
316
 
@@ -329,8 +326,8 @@ var VALID_NODE_TYPES = /* @__PURE__ */ new Set([
329
326
  "container",
330
327
  "text"
331
328
  ]);
332
- var BUTTON_VARIANTS = ["primary", "outline", "secondary", "ghost", "accent"];
333
- var BADGE_VARIANTS = ["outline", "solid", "accent", "accent-outline"];
329
+ var BUTTON_VARIANTS = ["primary", "secondary", "ghost", "accent"];
330
+ var BADGE_VARIANTS = ["neutral", "solid", "accent", "accent-tint"];
334
331
  var SIZES = ["sm", "md", "lg"];
335
332
  var CASINGS = ["uppercase", "asWritten"];
336
333
  var VALID_TOKEN_NAMES = /* @__PURE__ */ new Set([
@@ -20,14 +20,13 @@ function Button({
20
20
  ...rest
21
21
  }) {
22
22
  const cls = cx(
23
- "btn",
24
- variant === "outline" && "btn--outline",
25
- variant === "secondary" && "btn--secondary",
26
- variant === "ghost" && "btn--ghost",
27
- variant === "accent" && "btn--accent",
28
- size === "sm" && "btn--sm",
29
- size === "lg" && "btn--lg",
30
- block && "btn--block",
23
+ "jd-btn",
24
+ variant === "secondary" && "jd-btn--secondary",
25
+ variant === "ghost" && "jd-btn--ghost",
26
+ variant === "accent" && "jd-btn--accent",
27
+ size === "sm" && "jd-btn--sm",
28
+ size === "lg" && "jd-btn--lg",
29
+ block && "jd-btn--block",
31
30
  className
32
31
  );
33
32
  return /* @__PURE__ */ jsx("button", { ...rest, className: cls, type, disabled, onClick, children });
@@ -99,29 +98,27 @@ function getContrastingInk(color) {
99
98
  const perceptualCrossover = 0.5;
100
99
  return lum > perceptualCrossover ? "#000000" : "#ffffff";
101
100
  }
102
- function Badge({ children, variant = "outline", accentColor, style, className, ...rest }) {
101
+ function Badge({ children, variant = "neutral", accentColor, style, className, ...rest }) {
103
102
  const cls = cx(
104
- "badge",
105
- variant === "solid" && "badge--solid",
106
- variant === "accent" && "badge--accent",
107
- variant === "accent-outline" && "badge--accent-outline",
103
+ "jd-badge",
104
+ variant === "solid" && "jd-badge--solid",
105
+ variant === "accent" && "jd-badge--accent",
106
+ variant === "accent-tint" && "jd-badge--accent-tint",
108
107
  className
109
108
  );
110
109
  const inlineStyle = { ...style };
111
110
  if (accentColor) {
112
111
  if (variant === "accent") {
113
112
  inlineStyle.backgroundColor = accentColor;
114
- inlineStyle.borderColor = accentColor;
115
113
  inlineStyle.color = getContrastingInk(accentColor);
116
- } else if (variant === "accent-outline") {
117
- inlineStyle.borderColor = accentColor;
118
- inlineStyle.color = accentColor;
114
+ } else if (variant === "accent-tint") {
115
+ inlineStyle["--jd-badge-accent"] = accentColor;
119
116
  }
120
117
  }
121
118
  return /* @__PURE__ */ jsx2("span", { ...rest, className: cls, style: Object.keys(inlineStyle).length > 0 ? inlineStyle : void 0, children });
122
119
  }
123
120
  function Tag({ children, className, ...rest }) {
124
- return /* @__PURE__ */ jsx2("span", { ...rest, className: cx("tag", className), children });
121
+ return /* @__PURE__ */ jsx2("span", { ...rest, className: cx("jd-tag", className), children });
125
122
  }
126
123
 
127
124
  // src/Card/Card.jsx
@@ -138,12 +135,12 @@ function Card({
138
135
  className,
139
136
  ...rest
140
137
  }) {
141
- const cls = cx("card", flat && "card--flat", compact && "card--compact", className);
138
+ const cls = cx("jd-card", flat && "jd-card--flat", compact && "jd-card--compact", className);
142
139
  return /* @__PURE__ */ jsxs("div", { ...rest, className: cls, children: [
143
- media ? /* @__PURE__ */ jsx3("div", { className: "card__media", children: media }) : null,
144
- eyebrow ? /* @__PURE__ */ jsx3("span", { className: cx("card__eyebrow", "t-label", eyebrowCasing === "asWritten" && "t-as-written"), children: eyebrow }) : null,
145
- title ? /* @__PURE__ */ jsx3("div", { className: "card__title", children: title }) : null,
146
- children ? /* @__PURE__ */ jsx3("div", { className: "card__body", children }) : null,
140
+ media ? /* @__PURE__ */ jsx3("div", { className: "jd-card__media", children: media }) : null,
141
+ eyebrow ? /* @__PURE__ */ jsx3("span", { className: cx("jd-card__eyebrow", "t-label", eyebrowCasing === "asWritten" && "t-as-written"), children: eyebrow }) : null,
142
+ title ? /* @__PURE__ */ jsx3("div", { className: "jd-card__title", children: title }) : null,
143
+ children ? /* @__PURE__ */ jsx3("div", { className: "jd-card__body", children }) : null,
147
144
  actions ? /* @__PURE__ */ jsx3("div", { style: { display: "flex", gap: "var(--gap-tight)" }, children: actions }) : null
148
145
  ] });
149
146
  }
@@ -158,7 +155,7 @@ function DataMatrix({ columns: columns2 = [], rows: rows2 = [], label = "Data Ma
158
155
  "table",
159
156
  {
160
157
  ...rest,
161
- className: cx("matrix", className),
158
+ className: cx("jd-matrix", className),
162
159
  role: "table",
163
160
  "aria-label": label,
164
161
  "aria-colcount": totalColumns,
@@ -228,7 +225,7 @@ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
228
225
  var headerClass = (c) => [c.numeric && "num", c.casing === "asWritten" && "t-as-written"].filter(Boolean).join(" ") || void 0;
229
226
  function DataTable({ columns: columns2 = [], rows: rows2 = [], className, ...rest }) {
230
227
  const cols = columns2.map((c) => typeof c === "string" ? { key: c, label: c } : c);
231
- return /* @__PURE__ */ jsxs3("table", { ...rest, className: cx("table", className), children: [
228
+ return /* @__PURE__ */ jsxs3("table", { ...rest, className: cx("jd-table", className), children: [
232
229
  /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsx5("tr", { children: cols.map((c) => /* @__PURE__ */ jsx5("th", { className: headerClass(c), children: c.label }, c.key)) }) }),
233
230
  /* @__PURE__ */ jsx5("tbody", { children: rows2.map((row, i) => /* @__PURE__ */ jsx5("tr", { children: cols.map((c) => /* @__PURE__ */ jsx5("td", { className: c.numeric ? "num" : void 0, children: row[c.key] }, c.key)) }, i)) })
234
231
  ] });
@@ -259,7 +256,7 @@ function TextField({
259
256
  const common = {
260
257
  ...rest,
261
258
  id: fid,
262
- className: cx("field__input", className),
259
+ className: cx("jd-field__input", className),
263
260
  "aria-invalid": invalid || error ? true : void 0,
264
261
  "aria-describedby": describedBy,
265
262
  disabled
@@ -268,11 +265,11 @@ function TextField({
268
265
  if (as === "textarea") control = /* @__PURE__ */ jsx6("textarea", { ...common });
269
266
  else if (as === "select") control = /* @__PURE__ */ jsx6("select", { ...common, children });
270
267
  else control = /* @__PURE__ */ jsx6("input", { ...common });
271
- return /* @__PURE__ */ jsxs4("div", { className: "field", children: [
272
- label ? /* @__PURE__ */ jsx6("label", { className: cx("field__label", labelCasing === "asWritten" && "t-as-written"), htmlFor: fid, children: label }) : null,
268
+ return /* @__PURE__ */ jsxs4("div", { className: "jd-field", children: [
269
+ label ? /* @__PURE__ */ jsx6("label", { className: cx("jd-field__label", labelCasing === "asWritten" && "t-as-written"), htmlFor: fid, children: label }) : null,
273
270
  control,
274
- hint ? /* @__PURE__ */ jsx6("span", { className: "field__hint", id: hintId, children: hint }) : null,
275
- error ? /* @__PURE__ */ jsx6("span", { className: "field__hint is-error", id: errorId, children: error }) : null
271
+ hint ? /* @__PURE__ */ jsx6("span", { className: "jd-field__hint", id: hintId, children: hint }) : null,
272
+ error ? /* @__PURE__ */ jsx6("span", { className: "jd-field__hint is-error", id: errorId, children: error }) : null
276
273
  ] });
277
274
  }
278
275
 
@@ -288,8 +285,8 @@ var VALID_NODE_TYPES = /* @__PURE__ */ new Set([
288
285
  "container",
289
286
  "text"
290
287
  ]);
291
- var BUTTON_VARIANTS = ["primary", "outline", "secondary", "ghost", "accent"];
292
- var BADGE_VARIANTS = ["outline", "solid", "accent", "accent-outline"];
288
+ var BUTTON_VARIANTS = ["primary", "secondary", "ghost", "accent"];
289
+ var BADGE_VARIANTS = ["neutral", "solid", "accent", "accent-tint"];
293
290
  var SIZES = ["sm", "md", "lg"];
294
291
  var CASINGS = ["uppercase", "asWritten"];
295
292
  var VALID_TOKEN_NAMES = /* @__PURE__ */ new Set([
@@ -57,7 +57,6 @@
57
57
  "type": "string",
58
58
  "enum": [
59
59
  "primary",
60
- "outline",
61
60
  "secondary",
62
61
  "ghost",
63
62
  "accent"
@@ -139,10 +138,10 @@
139
138
  "variant": {
140
139
  "type": "string",
141
140
  "enum": [
142
- "outline",
141
+ "neutral",
143
142
  "solid",
144
143
  "accent",
145
- "accent-outline"
144
+ "accent-tint"
146
145
  ]
147
146
  },
148
147
  "accentColor": {
@@ -13,8 +13,8 @@ export type SDUINodeType =
13
13
 
14
14
  /** Props accepted per node type. Any other prop is rejected by `validateSDUINode`. */
15
15
  export interface SDUIPropsByType {
16
- button: { variant?: "primary" | "outline" | "secondary" | "ghost" | "accent"; size?: "sm" | "md" | "lg"; block?: boolean; disabled?: boolean; type?: "button" | "submit" | "reset"; token?: string };
17
- badge: { variant?: "outline" | "solid" | "accent" | "accent-outline"; accentColor?: string; token?: string };
16
+ button: { variant?: "primary" | "secondary" | "ghost" | "accent"; size?: "sm" | "md" | "lg"; block?: boolean; disabled?: boolean; type?: "button" | "submit" | "reset"; token?: string };
17
+ badge: { variant?: "neutral" | "solid" | "accent" | "accent-tint"; accentColor?: string; token?: string };
18
18
  tag: { token?: string };
19
19
  card: { eyebrow?: string; eyebrowCasing?: "uppercase" | "asWritten"; title?: string; flat?: boolean; compact?: boolean; token?: string };
20
20
  "data-matrix": { columns?: SDUIColumn[]; rows?: SDUIRow[]; label?: string; token?: string };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@judarn/ui",
3
- "version": "0.7.12",
3
+ "version": "0.8.2",
4
4
  "description": "Judarn design foundation tokens and React components for the web",
5
5
  "author": "Ruben Luthman",
6
6
  "license": "MIT",
@@ -1,12 +1,15 @@
1
1
  /* =========================================================================
2
2
  JUDARN DESIGN SYSTEM
3
3
  components.css – component styles (flat, geometric, no decoration)
4
- Interaction model: COLOR INVERSION on hover, flat grey on active.
5
- No shadows, no gradients, no radius (unless structurally motivated).
4
+ Every class carries the jd- prefix, so a project's own .btn or .card never picks these up.
5
+ Controls are set apart by fill alone (DESIGN.md, Surface Hierarchy). Hover inverts the fill,
6
+ press is flat grey. No shadows, no gradients, no radius (unless structurally motivated).
6
7
  ========================================================================= */
7
8
 
8
9
  /* ---- BUTTON ------------------------------------------------------------ */
9
- .btn {
10
+ /* The resting border is transparent: it keeps the control's size, and Windows high-contrast mode
11
+ still draws it. High-contrast modes turn it into an ink-100 outline (end of file). */
12
+ .jd-btn {
10
13
  font-family: var(--font-sans);
11
14
  font-weight: var(--weight-medium);
12
15
  letter-spacing: var(--track-body);
@@ -16,7 +19,7 @@
16
19
  align-items: center;
17
20
  justify-content: center;
18
21
  gap: var(--gap-tight);
19
- border: var(--rule) solid var(--black);
22
+ border: var(--rule) solid transparent;
20
23
  background: var(--black);
21
24
  color: var(--paper);
22
25
  cursor: pointer;
@@ -28,38 +31,35 @@
28
31
  text-align: center;
29
32
  white-space: nowrap;
30
33
  }
31
- .btn:hover { background: var(--white); color: var(--black); border-color: var(--black); } /* invert */
32
- .btn:active { background: var(--ink-60); border-color: var(--ink-60); color: var(--paper); }
33
- .btn:focus-visible { outline: var(--rule-bold) solid var(--accent); outline-offset: 2px; }
34
-
35
- .btn--outline { background: transparent; color: var(--black); border: var(--rule) solid var(--black); }
36
- .btn--outline:hover { background: var(--black); color: var(--paper); }
37
- .btn--outline:active { background: var(--ink-60); border-color: var(--ink-60); color: var(--paper); }
34
+ .jd-btn:hover { background: var(--jd-surface-recessed); color: var(--black); } /* invert by fill */
35
+ .jd-btn:active { background: var(--ink-60); color: var(--paper); }
36
+ .jd-btn:focus-visible { outline: var(--rule-bold) solid var(--accent); outline-offset: 2px; }
38
37
 
39
- .btn--secondary { background: var(--jd-surface-recessed); color: var(--black); border: var(--rule) solid var(--ink-30); }
40
- .btn--secondary:hover { background: var(--white); color: var(--black); border-color: var(--ink-60); }
41
- .btn--secondary:active { background: var(--ink-60); border-color: var(--ink-60); color: var(--paper); }
38
+ /* A secondary button is a well like a text field: surfaceRecessed on the page, substrate inside a card. */
39
+ .jd-btn--secondary { background: var(--jd-well, var(--jd-surface-recessed)); color: var(--black); }
40
+ .jd-btn--secondary:hover { background: var(--black); color: var(--paper); }
41
+ .jd-btn--secondary:active { background: var(--ink-60); color: var(--paper); }
42
42
 
43
- .btn--ghost { background: transparent; color: var(--black); border-color: transparent; padding-left: 0; padding-right: 0; }
44
- .btn--ghost:hover { background: transparent; color: var(--black); border-bottom-color: var(--black); }
45
- .btn--ghost:active { background: transparent; color: var(--ink-60); border-bottom-color: var(--ink-60); }
43
+ .jd-btn--ghost { background: transparent; color: var(--black); padding-left: 0; padding-right: 0; }
44
+ .jd-btn--ghost:hover { background: transparent; color: var(--black); border-bottom-color: var(--black); }
45
+ .jd-btn--ghost:active { background: transparent; color: var(--ink-60); border-bottom-color: var(--ink-60); }
46
46
 
47
- .btn--accent { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
48
- .btn--accent:hover { background: var(--white); color: var(--accent-fg); border-color: var(--accent-fg); }
49
- .btn--accent:active { filter: none; background: var(--accent); color: var(--accent-ink); }
47
+ .jd-btn--accent { background: var(--accent); color: var(--accent-ink); }
48
+ .jd-btn--accent:hover { background: var(--jd-surface-recessed); color: var(--accent-fg); }
49
+ .jd-btn--accent:active { filter: none; background: var(--accent); color: var(--accent-ink); }
50
50
 
51
- .btn--sm { padding: var(--space-1) var(--space-2); font-size: var(--size-small); min-height: var(--space-touch); }
52
- .btn--lg { padding: var(--space-3) var(--space-4); font-size: var(--size-body-lg); }
53
- .btn--block { display: flex; width: 100%; }
51
+ .jd-btn--sm { padding: var(--space-1) var(--space-2); font-size: var(--size-small); min-height: var(--space-touch); }
52
+ .jd-btn--lg { padding: var(--space-3) var(--space-4); font-size: var(--size-body-lg); }
53
+ .jd-btn--block { display: flex; width: 100%; }
54
54
 
55
- .btn:disabled, .btn[aria-disabled="true"] {
56
- background: var(--jd-surface-recessed); color: var(--ink-30); border-color: var(--ink-30);
55
+ .jd-btn:disabled, .jd-btn[aria-disabled="true"] {
56
+ background: var(--jd-well, var(--jd-surface-recessed)); color: var(--ink-30);
57
57
  cursor: not-allowed; pointer-events: none;
58
58
  }
59
59
 
60
60
  /* ---- FIELD (text input / textarea / select) --------------------------- */
61
- .field { display: flex; flex-direction: column; gap: var(--gap-tight); }
62
- .field__label {
61
+ .jd-field { display: flex; flex-direction: column; gap: var(--gap-tight); }
62
+ .jd-field__label {
63
63
  font-family: var(--font-sans);
64
64
  font-weight: var(--weight-bold);
65
65
  font-size: var(--size-label);
@@ -70,7 +70,7 @@
70
70
  /* A field is a well set apart by fill alone (DESIGN.md, Surface Hierarchy): surfaceRecessed on the
71
71
  page, substrate inside a card. The resting border is transparent: it keeps the field's size, and
72
72
  Windows high-contrast mode still draws it. High-contrast modes turn it into an ink-100 outline. */
73
- .field__input {
73
+ .jd-field__input {
74
74
  font-family: var(--font-sans);
75
75
  font-size: var(--size-body);
76
76
  color: var(--black);
@@ -81,23 +81,28 @@
81
81
  width: 100%;
82
82
  transition: border-color 120ms linear, box-shadow 120ms linear;
83
83
  }
84
- .card .field__input { background: var(--paper); }
84
+ .jd-card .jd-field__input { background: var(--paper); }
85
85
  @media (prefers-contrast: more) {
86
- :where(:root:not([data-contrast="normal"])) .field__input { border-color: var(--ink-100); }
87
- }
88
- :where([data-contrast="high"]) .field__input { border-color: var(--ink-100); }
89
- .field__input::placeholder { color: var(--ink-60); }
90
- .field__input:focus { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
91
- .field__input:focus-visible { outline: var(--rule-bold) solid var(--accent); outline-offset: 2px; }
92
- .field__input:disabled { color: var(--ink-30); cursor: not-allowed; }
93
- .field__input[aria-invalid="true"], .field__input.is-invalid { border-color: var(--signal-error); box-shadow: 0 0 0 1px var(--signal-error); }
94
- .field__hint { font-size: var(--size-small); color: var(--ink-60); }
95
- .field__hint.is-error, .field__hint--error { color: var(--signal-error); font-weight: var(--weight-medium); }
96
- textarea.field__input { resize: vertical; min-height: var(--space-12); line-height: var(--lh-body); }
97
- select.field__input { cursor: pointer; } /* native appearance keeps the platform chevron */
86
+ :where(:root:not([data-contrast="normal"])) .jd-field__input { border-color: var(--ink-100); }
87
+ }
88
+ :where([data-contrast="high"]) .jd-field__input { border-color: var(--ink-100); }
89
+ @media (prefers-contrast: more) {
90
+ :where(:root:not([data-contrast="normal"])) :is(.jd-btn:not(.jd-btn--ghost), .jd-badge, .jd-tag) { border-color: var(--ink-100); }
91
+ }
92
+ :where([data-contrast="high"]) :is(.jd-btn:not(.jd-btn--ghost), .jd-badge, .jd-tag) { border-color: var(--ink-100); }
93
+ .jd-field__input::placeholder { color: var(--ink-60); }
94
+ .jd-field__input:focus { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
95
+ .jd-field__input:focus-visible { outline: var(--rule-bold) solid var(--accent); outline-offset: 2px; }
96
+ .jd-field__input:disabled { color: var(--ink-30); cursor: not-allowed; }
97
+ .jd-field__input[aria-invalid="true"], .jd-field__input.is-invalid { border-color: var(--signal-error); box-shadow: 0 0 0 1px var(--signal-error); }
98
+ .jd-field__hint { font-size: var(--size-small); color: var(--ink-60); }
99
+ .jd-field__hint.is-error, .jd-field__hint--error { color: var(--signal-error); font-weight: var(--weight-medium); }
100
+ textarea.jd-field__input { resize: vertical; min-height: var(--space-12); line-height: var(--lh-body); }
101
+ select.jd-field__input { cursor: pointer; } /* native appearance keeps the platform chevron */
98
102
 
99
103
  /* ---- CARD -------------------------------------------------------------- */
100
- .card {
104
+ .jd-card {
105
+ --jd-well: var(--paper); /* wells inside a card (secondary buttons, neutral badges, tags) fill with substrate */
101
106
  background: var(--white);
102
107
  border: none;
103
108
  border-radius: var(--radius-card);
@@ -106,97 +111,111 @@ select.field__input { cursor: pointer; } /* native appearance keeps the platform
106
111
  flex-direction: column;
107
112
  gap: var(--gap-normal);
108
113
  }
109
- .card--flat { border: none; padding: 0; border-radius: 0; }
110
- .card--compact { padding: var(--space-2); }
111
- .card--field { background: var(--bg2); border: none; }
112
- .card__eyebrow { color: var(--accent); } /* type comes from t-label */
113
- .card__title { font-size: var(--size-h4); font-weight: var(--weight-bold); letter-spacing: var(--track-head); }
114
- .card__body { font-size: var(--size-body); line-height: var(--lh-body); }
115
- .card__media { aspect-ratio: 3 / 2; background: var(--jd-surface-recessed); border-radius: var(--radius-control); overflow: hidden; }
114
+ .jd-card--flat { border: none; padding: 0; border-radius: 0; }
115
+ .jd-card--compact { padding: var(--space-2); }
116
+ .jd-card--field { background: var(--bg2); border: none; }
117
+ .jd-card__eyebrow { color: var(--accent); } /* type comes from t-label */
118
+ .jd-card__title { font-size: var(--size-h4); font-weight: var(--weight-bold); letter-spacing: var(--track-head); }
119
+ .jd-card__body { font-size: var(--size-body); line-height: var(--lh-body); }
120
+ .jd-card__media { aspect-ratio: 3 / 2; background: var(--jd-surface-recessed); border-radius: var(--radius-control); overflow: hidden; }
116
121
 
117
122
  /* ---- BADGE / TAG ------------------------------------------------------- */
118
- .badge {
123
+ /* Fill only, like buttons: a transparent resting border, an ink-100 outline in high-contrast modes.
124
+ The neutral badge and the tag are wells: surfaceRecessed on the page, substrate inside a card. */
125
+ .jd-badge {
119
126
  display: inline-flex; align-items: center; gap: var(--gap-tight);
120
127
  font-family: var(--font-sans); font-weight: var(--weight-bold);
121
128
  font-size: var(--size-label); letter-spacing: var(--track-body);
122
- padding: 4px var(--space-1); border: var(--rule) solid var(--line);
123
- background: var(--white); color: var(--black); border-radius: var(--radius-badge); line-height: 1;
129
+ padding: 4px var(--space-1); border: var(--rule) solid transparent;
130
+ background: var(--jd-well, var(--jd-surface-recessed)); color: var(--black);
131
+ border-radius: var(--radius-badge); line-height: 1;
124
132
  }
125
- .badge--solid { background: var(--black); color: var(--paper); }
126
- .badge--accent { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
127
- .badge--accent-outline { color: var(--accent-fg); border-color: var(--accent-fg); }
133
+ .jd-badge--solid { background: var(--black); color: var(--paper); }
134
+ .jd-badge--accent { background: var(--accent); color: var(--accent-ink); }
135
+ /* A light tint of the accent (or of --jd-badge-accent) with ink-100 text. Tints belong to light mode,
136
+ so dark mode falls back to the neutral well. */
137
+ .jd-badge--accent-tint {
138
+ background: color-mix(in oklch, var(--jd-badge-accent, var(--accent)) 14%, white);
139
+ color: var(--ink-100);
140
+ }
141
+ @media (prefers-color-scheme: dark) {
142
+ :root:not([data-theme="light"]) .jd-badge--accent-tint { background: var(--jd-well, var(--jd-surface-recessed)); }
143
+ }
144
+ [data-theme="dark"] .jd-badge--accent-tint,
145
+ .palette-dark .jd-badge--accent-tint { background: var(--jd-well, var(--jd-surface-recessed)); }
128
146
 
129
- .tag {
147
+ .jd-tag {
130
148
  display: inline-flex; align-items: center;
131
149
  font-size: var(--size-small); font-weight: var(--weight-medium);
132
- padding: 4px var(--space-1); border: var(--rule) solid var(--line);
133
- background: var(--white); border-radius: var(--radius-badge); line-height: 1.2;
150
+ padding: 4px var(--space-1); border: var(--rule) solid transparent;
151
+ background: var(--jd-well, var(--jd-surface-recessed)); color: var(--black);
152
+ border-radius: var(--radius-badge); line-height: 1.2;
134
153
  }
135
154
 
136
155
  /* ---- NAVBAR ------------------------------------------------------------ */
137
- .navbar {
156
+ .jd-navbar {
138
157
  display: flex; align-items: center; justify-content: space-between;
139
158
  padding: var(--space-2) var(--space-3);
140
159
  border-bottom: var(--rule-bold) solid var(--line);
141
160
  background: var(--white);
142
161
  }
143
- .navbar__brand { font-weight: var(--weight-bold); font-size: var(--size-h4); letter-spacing: var(--track-head); }
144
- .navbar__links { display: flex; align-items: center; gap: var(--space-3); list-style: none; margin: 0; padding: 0; }
145
- .navbar__link {
162
+ .jd-navbar__brand { font-weight: var(--weight-bold); font-size: var(--size-h4); letter-spacing: var(--track-head); }
163
+ .jd-navbar__links { display: flex; align-items: center; gap: var(--space-3); list-style: none; margin: 0; padding: 0; }
164
+ .jd-navbar__link {
146
165
  font-size: var(--size-small); font-weight: var(--weight-medium);
147
166
  border-bottom: var(--rule-bold) solid transparent;
148
167
  padding-bottom: 2px;
149
168
  transition: border-color 120ms linear;
150
169
  }
151
- .navbar__link:hover { border-bottom-color: var(--black); }
152
- .navbar__link[aria-current="page"] { border-bottom-color: var(--accent); }
170
+ .jd-navbar__link:hover { border-bottom-color: var(--black); }
171
+ .jd-navbar__link[aria-current="page"] { border-bottom-color: var(--accent); }
153
172
 
154
173
  /* ---- FOOTER ------------------------------------------------------------ */
155
- .footer {
174
+ .jd-footer {
156
175
  border-top: var(--rule-bold) solid var(--line);
157
176
  padding: var(--space-6) var(--space-3) var(--space-3);
158
177
  background: var(--white);
159
178
  display: grid; grid-template-columns: 2fr 1fr 1fr; gap: var(--space-4);
160
179
  }
161
- .footer__col { display: flex; flex-direction: column; gap: var(--gap-tight); }
162
- .footer__head { margin: 0 0 var(--space-1); color: var(--black); } /* type comes from t-label */
163
- .footer__link { font-size: var(--size-small); color: var(--fg2); }
164
- .footer__link:hover { color: var(--black); text-decoration: underline; text-underline-offset: 3px; }
180
+ .jd-footer__col { display: flex; flex-direction: column; gap: var(--gap-tight); }
181
+ .jd-footer__head { margin: 0 0 var(--space-1); color: var(--black); } /* type comes from t-label */
182
+ .jd-footer__link { font-size: var(--size-small); color: var(--fg2); }
183
+ .jd-footer__link:hover { color: var(--black); text-decoration: underline; text-underline-offset: 3px; }
165
184
 
166
185
  /* ---- DATA TABLE -------------------------------------------------------- */
167
- .table { width: 100%; border-collapse: collapse; font-size: var(--size-small); }
168
- .table th {
186
+ .jd-table { width: 100%; border-collapse: collapse; font-size: var(--size-small); }
187
+ .jd-table th {
169
188
  text-align: left; font-weight: var(--weight-bold); font-size: var(--size-label);
170
189
  letter-spacing: var(--track-label); text-transform: uppercase;
171
190
  padding: var(--space-1) var(--space-2); border-bottom: var(--rule-bold) solid var(--line); color: var(--black);
172
191
  }
173
- .table td { padding: var(--space-2); border-bottom: var(--rule) solid var(--ink-30); color: var(--fg1); }
174
- .table tbody tr:hover { background: var(--jd-surface-recessed); }
175
- .table .num { text-align: right; font-variant-numeric: tabular-nums; }
192
+ .jd-table td { padding: var(--space-2); border-bottom: var(--rule) solid var(--ink-30); color: var(--fg1); }
193
+ .jd-table tbody tr:hover { background: var(--jd-surface-recessed); }
194
+ .jd-table .num { text-align: right; font-variant-numeric: tabular-nums; }
176
195
 
177
196
  /* As-written casing for a field label or column header that holds a unit, a mixed-case term,
178
197
  or more than two words. */
179
- .field__label.t-as-written,
180
- .table th.t-as-written,
181
- .matrix th.t-as-written { text-transform: none; letter-spacing: var(--track-body); }
198
+ .jd-field__label.t-as-written,
199
+ .jd-table th.t-as-written,
200
+ .jd-matrix th.t-as-written { text-transform: none; letter-spacing: var(--track-body); }
182
201
 
183
202
  /* ---- LOGO LOCKUP FRAME ------------------------------------------------- */
184
- .lockup { display: inline-flex; align-items: center; gap: var(--space-2); }
185
- .lockup--vertical { flex-direction: column; align-items: flex-start; gap: var(--space-2); }
186
- .lockup--reversed { background: var(--black); color: var(--paper); padding: var(--space-3); }
187
- .lockup__mark { display: block; flex: none; }
188
- .lockup__words { display: flex; flex-direction: column; }
189
- .lockup__name { font-weight: var(--weight-bold); font-size: var(--size-h4); line-height: 1; letter-spacing: var(--track-head); }
190
- .lockup__tag { color: var(--fg2); margin-top: 4px; } /* type comes from t-label */
191
- .lockup--reversed .lockup__tag { color: var(--ink-30); }
203
+ .jd-lockup { display: inline-flex; align-items: center; gap: var(--space-2); }
204
+ .jd-lockup--vertical { flex-direction: column; align-items: flex-start; gap: var(--space-2); }
205
+ .jd-lockup--reversed { background: var(--black); color: var(--paper); padding: var(--space-3); }
206
+ .jd-lockup__mark { display: block; flex: none; }
207
+ .jd-lockup__words { display: flex; flex-direction: column; }
208
+ .jd-lockup__name { font-weight: var(--weight-bold); font-size: var(--size-h4); line-height: 1; letter-spacing: var(--track-head); }
209
+ .jd-lockup__tag { color: var(--fg2); margin-top: 4px; } /* type comes from t-label */
210
+ .jd-lockup--reversed .jd-lockup__tag { color: var(--ink-30); }
192
211
 
193
212
  /* ---- DATA MATRIX ------------------------------------------------------- */
194
- .matrix {
213
+ .jd-matrix {
195
214
  width: 100%;
196
215
  border-collapse: collapse;
197
216
  font-size: var(--size-small);
198
217
  }
199
- .matrix th {
218
+ .jd-matrix th {
200
219
  text-align: left;
201
220
  font-weight: var(--weight-bold);
202
221
  font-size: var(--size-label);
@@ -208,44 +227,66 @@ select.field__input { cursor: pointer; } /* native appearance keeps the platform
208
227
  background: var(--white);
209
228
  color: var(--black);
210
229
  }
211
- .matrix th:last-child { border-right: none; }
212
- .matrix td {
230
+ .jd-matrix th:last-child { border-right: none; }
231
+ .jd-matrix td {
213
232
  padding: var(--space-1) var(--space-2);
214
233
  border-right: var(--rule) solid var(--ink-30);
215
234
  border-bottom: var(--rule) solid var(--ink-30);
216
235
  color: var(--fg1);
217
236
  }
218
- .matrix td:last-child { border-right: none; }
219
- .matrix tbody tr:last-child td { border-bottom: none; }
220
- .matrix tbody tr:hover td { background: var(--jd-surface-recessed); }
221
- .matrix .num { text-align: right; font-variant-numeric: tabular-nums; }
237
+ .jd-matrix td:last-child { border-right: none; }
238
+ .jd-matrix tbody tr:last-child td { border-bottom: none; }
239
+ .jd-matrix tbody tr:hover td { background: var(--jd-surface-recessed); }
240
+ .jd-matrix .num { text-align: right; font-variant-numeric: tabular-nums; }
241
+
242
+ /* ---- LIST -------------------------------------------------------------- */
243
+ /* Items of text get markers (DESIGN.md, Lists): bullets on ul, numbers on ol, in secondary ink,
244
+ hanging so wrapped lines align with the text. Items are spaced with margins, not gap,
245
+ so the list sits inside .flow as one block. The indent fits two-digit numbers on ol.
246
+ The margin reset has zero specificity, so .flow's space before and after the list still applies. */
247
+ :where(.jd-list) { margin: 0; }
248
+ .jd-list {
249
+ padding-inline-start: var(--jd-space-3);
250
+ max-width: var(--jd-measure-body);
251
+ list-style-type: disc;
252
+ }
253
+ ol.jd-list { list-style-type: decimal; padding-inline-start: var(--jd-space-4); }
254
+ .jd-list > li + li { margin-block-start: var(--jd-space-1); }
255
+ .jd-list > li::marker { color: var(--jd-ink-60); font-variant-numeric: tabular-nums; }
256
+ .jd-card .jd-list { max-width: var(--jd-measure-compact); }
257
+ /* One nested level: hollow bullets, same ink, spacing and indent. */
258
+ .jd-list .jd-list { margin-block-start: var(--jd-space-1); max-width: none; }
259
+ ul.jd-list ul.jd-list { list-style-type: circle; }
260
+ /* Rows of links, data or controls: no markers, no indent, set apart by spacing.
261
+ Give the element role="list": Safari and VoiceOver drop list semantics without markers. */
262
+ .jd-list.jd-list--plain { list-style: none; padding-inline-start: 0; }
222
263
 
223
264
  /* ---- HIGH CONTRAST RULE SCALING ---------------------------------------- */
224
265
  @media (prefers-contrast: more) {
225
- .matrix th,
226
- .matrix td {
266
+ .jd-matrix th,
267
+ .jd-matrix td {
227
268
  border-right-width: var(--jd-rule-bold);
228
269
  border-bottom-width: var(--jd-rule-bold);
229
270
  }
230
- .table th,
231
- .table td {
271
+ .jd-table th,
272
+ .jd-table td {
232
273
  border-bottom-width: var(--jd-rule-bold);
233
274
  }
234
- .field__input {
275
+ .jd-field__input {
235
276
  border-width: var(--jd-rule-bold);
236
277
  }
237
278
  }
238
279
 
239
- [data-contrast="high"] .matrix th,
240
- [data-contrast="high"] .matrix td {
280
+ [data-contrast="high"] .jd-matrix th,
281
+ [data-contrast="high"] .jd-matrix td {
241
282
  border-right-width: var(--jd-rule-bold);
242
283
  border-bottom-width: var(--jd-rule-bold);
243
284
  }
244
- [data-contrast="high"] .table th,
245
- [data-contrast="high"] .table td {
285
+ [data-contrast="high"] .jd-table th,
286
+ [data-contrast="high"] .jd-table td {
246
287
  border-bottom-width: var(--jd-rule-bold);
247
288
  }
248
- [data-contrast="high"] .field__input {
289
+ [data-contrast="high"] .jd-field__input {
249
290
  border-width: var(--jd-rule-bold);
250
291
  }
251
292