@rojaostudio/ds-core 1.1.0-next.10 → 1.1.0-next.11

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.
@@ -874,6 +874,16 @@ function stateFills(scale, fill, text, preferred) {
874
874
  return (_a = ok[i]) != null ? _a : p;
875
875
  });
876
876
  }
877
+ var carry = (scale, fill, text, value) => contrastRatio(text, value) >= AA ? value : stateFills(scale, fill, text, [value])[0];
878
+ function over(top, below) {
879
+ const ch = (h, i) => parseInt(h.slice(1 + 2 * i, 3 + 2 * i), 16);
880
+ const a = top.length === 9 ? ch(top, 3) / 255 : 1;
881
+ return `#${[0, 1, 2].map((i) => Math.round(ch(top, i) * a + ch(below, i) * (1 - a)).toString(16).padStart(2, "0")).join("")}`;
882
+ }
883
+ var PLATE_MIN = 6;
884
+ function distinct(a, b) {
885
+ return contrastRatio(a, b) >= 1.5 || Math.abs(hexToHsl(a)[1] - hexToHsl(b)[1]) >= 40;
886
+ }
877
887
  var defaultWarn = (m) => {
878
888
  var _a;
879
889
  return (_a = globalThis.console) == null ? void 0 : _a.warn(m);
@@ -989,7 +999,7 @@ var TINT_DEFAULT = 200;
989
999
  var lighterStep = (s) => s - 100 ;
990
1000
  var roleVar = (role) => `--${role.replaceAll("/", "-")}`;
991
1001
  function generateRdsTheme(def, opts = {}) {
992
- var _a, _b;
1002
+ var _a, _b, _c, _d;
993
1003
  const warn = (_a = opts.warn) != null ? _a : defaultWarn;
994
1004
  const where = `generateRdsTheme(${preview(def == null ? void 0 : def.name)})`;
995
1005
  if (!(def == null ? void 0 : def.brand) || typeof def.brand !== "object") throw new Error(`${where}: brand is missing`);
@@ -1003,13 +1013,13 @@ function generateRdsTheme(def, opts = {}) {
1003
1013
  const L = palette("blue");
1004
1014
  const red = palette("red"), green = palette("green"), orange = palette("orange");
1005
1015
  const teal = palette("teal"), purple = palette("purple");
1006
- const lightBgs = [WHITE, N[100]];
1016
+ const lightBgs = [WHITE, N[100], N[200], A.scale[200]];
1007
1017
  let heading;
1008
1018
  if (b.heading) {
1009
1019
  heading = ramp(b.heading, own).base;
1010
1020
  if (!readsOn(heading, lightBgs)) {
1011
1021
  const worst = Math.min(...lightBgs.map((bg) => contrastRatio(heading, bg)));
1012
- warn(`generateRdsTheme(${def.name}): heading ${heading} is ${worst.toFixed(2)}:1 on surface/card or surface/page (AA asks 4.5:1). Kept as given.`);
1022
+ warn(`generateRdsTheme(${def.name}): heading ${heading} is ${worst.toFixed(2)}:1 on surface/card, surface/page, surface/muted-strong or colors/accent/highlight (AA asks 4.5:1). Kept as given.`);
1013
1023
  }
1014
1024
  } else heading = readableFrom(P.base, P.scale, lightBgs, "darker", BLACK);
1015
1025
  const invert = b.invert ? ramp(b.invert, own).base : WHITE;
@@ -1101,6 +1111,16 @@ function generateRdsTheme(def, opts = {}) {
1101
1111
  "logo/inverse-signature": WHITE,
1102
1112
  "social/ink": BLACK
1103
1113
  };
1114
+ const single = !b.secondary;
1115
+ const K = single ? N : P.scale;
1116
+ if (single) {
1117
+ const steps = [900, 800, 700, 600, 500];
1118
+ const k = Math.max(0, steps.findIndex((s) => distinct(N[s], l["colors/secondary/default"])));
1119
+ l["colors/primary/default"] = N[steps[Math.min(k, 2)]];
1120
+ l["colors/primary/dark"] = N[steps[Math.min(k, 2) + 1]];
1121
+ l["colors/primary/active"] = N[steps[Math.min(k, 2) + 2]];
1122
+ l["colors/primary/light"] = N[500];
1123
+ }
1104
1124
  for (const [fill, text] of [
1105
1125
  ["colors/primary/default", "text/on/primary"],
1106
1126
  ["colors/secondary/default", "text/on/secondary"],
@@ -1113,10 +1133,12 @@ function generateRdsTheme(def, opts = {}) {
1113
1133
  ["colors/state/neutral", "text/on/neutral"],
1114
1134
  ["surface/cover", "text/on/cover"],
1115
1135
  ["surface/lift/action", "text/on/lift-action"],
1116
- ["colors/primary/dark", "text/on/primary-strong"],
1136
+ ["colors/primary/default", "text/on/primary-strong"],
1117
1137
  ["surface/tint/default", "text/on/tint"]
1118
1138
  ]) l[text] = on(l[fill]);
1119
- [l["colors/primary/active"]] = stateFills(P.scale, l["colors/primary/default"], l["text/on/primary"], [l["colors/primary/active"]]);
1139
+ for (const role of ["colors/primary/dark", "colors/primary/light"])
1140
+ l[role] = carry(K, l["colors/primary/default"], l["text/on/primary-strong"], l[role]);
1141
+ [l["colors/primary/active"]] = stateFills(K, l["colors/primary/default"], l["text/on/primary"], [l["colors/primary/active"]]);
1120
1142
  [l["colors/secondary/hover"], l["colors/secondary/active"]] = stateFills(
1121
1143
  S.scale,
1122
1144
  l["colors/secondary/default"],
@@ -1125,9 +1147,9 @@ function generateRdsTheme(def, opts = {}) {
1125
1147
  );
1126
1148
  [l["colors/accent/hover"]] = stateFills(A.scale, l["colors/accent/default"], l["text/on/accent"], [l["colors/accent/hover"]]);
1127
1149
  l["border/error"] = readableFrom(l["colors/state/error"], red, [l["surface/card"]], awayFrom(l["surface/card"]), BLACK, NON_TEXT);
1128
- l["chart/series/1"] = readableFrom(P.scale[600], P.scale, [l["surface/card"]], awayFrom(l["surface/card"]), BLACK, NON_TEXT);
1129
- l["logo/primary"] = l["colors/primary/default"];
1130
- l["logo/signature"] = l["colors/primary/default"];
1150
+ l["chart/series/1"] = readableFrom(P.scale[600], P.scale, [l["surface/card"], l["surface/page"]], awayFrom(l["surface/card"]), BLACK, NON_TEXT);
1151
+ l["logo/primary"] = readableFrom(P.base, P.scale, [l["surface/card"], l["surface/page"], l["surface/panel"]], "darker", BLACK, NON_TEXT);
1152
+ l["logo/signature"] = l["logo/primary"];
1131
1153
  l["logo/accent"] = l["colors/accent/logo"];
1132
1154
  l["logo/accent-2"] = l["colors/secondary/default"];
1133
1155
  l["logo/accent-3"] = l["colors/accent/default"];
@@ -1203,16 +1225,29 @@ function generateRdsTheme(def, opts = {}) {
1203
1225
  "colors/state/neutral-strong": N[400],
1204
1226
  "text/on/primary-subtle": black(30)
1205
1227
  };
1228
+ if (single) {
1229
+ const steps = [200, 300, 100, 400];
1230
+ const s = (_b = steps.find((st) => distinct(N[st], d["colors/secondary/default"]))) != null ? _b : 200;
1231
+ d["colors/primary/default"] = N[s];
1232
+ d["colors/primary/dark"] = N[s === 100 ? 50 : s - 100];
1233
+ d["colors/primary/active"] = N[s + 100];
1234
+ d["colors/primary/light"] = N[s + 200];
1235
+ }
1236
+ d["surface/tint/default"] = readableFrom(d["surface/tint/default"], A.scale, [d["text/body"], d["text/muted"], d["colors/primary/default"]], "darker", N[900]);
1237
+ d["surface/tint/subtle"] = d["surface/tint/default"];
1238
+ d["colors/accent/highlight"] = readableFrom(d["colors/accent/highlight"], A.scale, [d["text/heading"]], "darker", N[900]);
1206
1239
  for (const [fill, text] of [
1207
1240
  ["colors/primary/default", "text/on/primary"],
1208
1241
  ["colors/secondary/default", "text/on/secondary"],
1209
1242
  ["colors/accent/default", "text/on/accent"],
1210
1243
  ["colors/accent/mark", "text/on/accent-mark"],
1211
- ["colors/primary/dark", "text/on/primary-strong"],
1244
+ ["colors/primary/default", "text/on/primary-strong"],
1212
1245
  ["surface/tint/default", "text/on/tint"],
1213
1246
  ["surface/lift/action", "text/on/lift-action"]
1214
1247
  ]) d[text] = on(d[fill]);
1215
- [d["colors/primary/active"]] = stateFills(P.scale, d["colors/primary/default"], d["text/on/primary"], [d["colors/primary/active"]]);
1248
+ for (const role of ["colors/primary/dark", "colors/primary/light"])
1249
+ d[role] = carry(K, d["colors/primary/default"], d["text/on/primary-strong"], d[role]);
1250
+ [d["colors/primary/active"]] = stateFills(K, d["colors/primary/default"], d["text/on/primary"], [d["colors/primary/active"]]);
1216
1251
  [d["colors/secondary/hover"], d["colors/secondary/active"]] = stateFills(
1217
1252
  S.scale,
1218
1253
  d["colors/secondary/default"],
@@ -1220,16 +1255,26 @@ function generateRdsTheme(def, opts = {}) {
1220
1255
  [d["colors/secondary/hover"], d["colors/secondary/active"]]
1221
1256
  );
1222
1257
  [d["colors/accent/hover"]] = stateFills(A.scale, d["colors/accent/default"], d["text/on/accent"], [d["colors/accent/hover"]]);
1223
- d["border/error"] = readableFrom(d["colors/state/error-strong"], red, [d["surface/card"]], awayFrom(d["surface/card"]), WHITE, NON_TEXT);
1258
+ d["border/error"] = readableFrom(red[400], red, [d["surface/card"]], awayFrom(d["surface/card"]), WHITE, NON_TEXT);
1224
1259
  d["chart/series/1"] = readableFrom(P.scale[400], P.scale, [d["surface/card"]], awayFrom(d["surface/card"]), WHITE, NON_TEXT);
1225
- const plate = l["colors/primary/default"];
1260
+ d["colors/state/error-strong"] = carry(red, l["colors/state/error"], l["text/on/error"], d["colors/state/error-strong"]);
1261
+ const ink0 = on(P.base);
1262
+ const plate = contrastRatio(ink0, P.base) >= PLATE_MIN ? P.base : readableFrom(P.base, P.scale, [ink0], ink0 === WHITE ? "darker" : "lighter", ink0 === WHITE ? BLACK : WHITE, PLATE_MIN);
1226
1263
  const ink = on(plate);
1227
1264
  const inkA = ink === WHITE ? white : black;
1228
1265
  const antiInk = ink === WHITE ? black : white;
1229
1266
  const lp = relativeLuminance(plate);
1230
1267
  const isPlate = (c) => c.toLowerCase() === plate.toLowerCase();
1231
- const plateCard = P.scale[800] && !isPlate(P.scale[800]) && contrastRatio(ink, P.scale[800]) >= AA ? P.scale[800] : (_b = SCALE_STEPS.map((st) => P.scale[st]).filter((c) => !!c && isHex(c) && !isPlate(c)).sort((a, b2) => Math.abs(relativeLuminance(a) - lp) - Math.abs(relativeLuminance(b2) - lp)).find((c) => contrastRatio(ink, c) >= AA)) != null ? _b : plate;
1232
- const onPrimaryStrong = contrastRatio(plate, P.scale[100]) >= AA ? plate : on(P.scale[100]);
1268
+ const plateCard = P.scale[800] && !isPlate(P.scale[800]) && contrastRatio(ink, P.scale[800]) >= PLATE_MIN ? P.scale[800] : (_c = SCALE_STEPS.map((st) => P.scale[st]).filter((c) => !!c && isHex(c) && !isPlate(c)).sort((a, b2) => Math.abs(relativeLuminance(a) - lp) - Math.abs(relativeLuminance(b2) - lp)).find((c) => contrastRatio(ink, c) >= PLATE_MIN)) != null ? _c : plate;
1269
+ const plateBgs = [plate, plateCard];
1270
+ const ov = plateBgs.every((bg) => contrastRatio(ink, over(inkA(20), bg)) >= AA) ? inkA : ink === WHITE ? black : white;
1271
+ const under = [...plateBgs, ...plateBgs.map((bg) => over(ov(10), bg))];
1272
+ const quiet = (pct, min = AA, bgs = under) => {
1273
+ for (let p = pct; p < 100; p += 5) if (bgs.every((bg) => contrastRatio(over(inkA(p), bg), bg) >= min)) return inkA(p);
1274
+ return ink;
1275
+ };
1276
+ const fieldBorder = quiet(60, NON_TEXT, plateBgs);
1277
+ const fieldAlpha = fieldBorder.length === 9 ? Math.round(parseInt(fieldBorder.slice(7), 16) / 255 * 100) : 100;
1233
1278
  const br = {
1234
1279
  "colors/primary/light": P.scale[300],
1235
1280
  "colors/primary/default": ink,
@@ -1246,33 +1291,37 @@ function generateRdsTheme(def, opts = {}) {
1246
1291
  "surface/card": plateCard,
1247
1292
  "surface/panel": plate,
1248
1293
  "border/default": inkA(20),
1249
- "surface/tint/default": inkA(10),
1250
- "surface/tint/strong": inkA(20),
1251
- "surface/tint/subtle": inkA(5),
1294
+ "surface/tint/default": ov(10),
1295
+ "surface/tint/strong": ov(20),
1296
+ "surface/tint/subtle": ov(5),
1252
1297
  "text/on/action-tonal": ink,
1253
1298
  "text/on/primary": plate,
1254
1299
  "text/on/secondary": plate,
1255
1300
  "text/on/tint": ink,
1256
- "surface/action/default": inkA(10),
1257
- "surface/action/strong": inkA(20),
1301
+ "surface/action/default": ov(10),
1302
+ "surface/action/strong": ov(20),
1258
1303
  "focus/ring": ink,
1259
1304
  "focus/ring-inset": plate,
1260
1305
  "surface/disabled": inkA(10),
1261
1306
  "text/disabled": inkA(40),
1262
- "text/muted": inkA(70),
1307
+ "text/muted": quiet(70),
1263
1308
  "text/disabled-invert": antiInk(40),
1264
1309
  "text/action": ink,
1265
- "surface/lift/action": inkA(10),
1310
+ "surface/lift/action": ov(10),
1266
1311
  "text/on/lift-action": ink,
1267
- "surface/lift/action-strong": inkA(20),
1268
- "text/on/primary-strong": onPrimaryStrong,
1269
- "text/subtle": inkA(60),
1270
- "surface/muted": inkA(10),
1271
- "surface/muted-strong": inkA(20),
1272
- "border/strong": inkA(60),
1273
- "border/strong-hover": inkA(80),
1312
+ "surface/lift/action-strong": ov(20),
1313
+ // The label of the neutral Button on the plate, whose fill is the ink: the plate colour.
1314
+ "text/on/primary-strong": plate,
1315
+ "text/subtle": quiet(60),
1316
+ "surface/muted": ov(10),
1317
+ "surface/muted-strong": ov(20),
1318
+ "border/strong": fieldBorder,
1319
+ "border/strong-hover": fieldAlpha + 20 < 100 ? inkA(fieldAlpha + 20) : ink,
1274
1320
  "text/on/primary-subtle": inkA(30)
1275
1321
  };
1322
+ for (const role of ["colors/primary/dark", "colors/primary/light"])
1323
+ br[role] = carry(P.scale, br["colors/primary/default"], br["text/on/primary-strong"], br[role]);
1324
+ br["colors/primary/dark"] = readableFrom(br["colors/primary/dark"], P.scale, [plateCard], awayFrom(plateCard), ink, NON_TEXT);
1276
1325
  [br["colors/primary/active"]] = stateFills(P.scale, br["colors/primary/default"], br["text/on/primary"], [br["colors/primary/active"]]);
1277
1326
  [br["colors/secondary/hover"], br["colors/secondary/active"]] = stateFills(
1278
1327
  S.scale,
@@ -1280,7 +1329,18 @@ function generateRdsTheme(def, opts = {}) {
1280
1329
  br["text/on/secondary"],
1281
1330
  [br["colors/secondary/hover"], br["colors/secondary/active"]]
1282
1331
  );
1283
- const brandError = readableFrom(red[300], red, [plateCard], awayFrom(plateCard), ink, NON_TEXT);
1332
+ const brandError = readableFrom(red[300], red, plateBgs.slice().reverse(), awayFrom(plateCard), ink, NON_TEXT);
1333
+ const lightPlate = ink === BLACK;
1334
+ const plateOwn = {};
1335
+ for (const [st, rampOf] of [["error", red], ["success", green], ["info", L], ["warning", orange], ["neutral", N]]) {
1336
+ const from = lightPlate ? l : d;
1337
+ const soft = [`surface/${st}`, ...st === "error" ? ["surface/error-strong"] : []];
1338
+ for (const s of soft) plateOwn[s] = from[s];
1339
+ plateOwn[`text/${st}`] = readableFrom(from[`text/${st}`], rampOf, [...under, ...soft.map((s) => from[s])], awayFrom(plate), ink);
1340
+ }
1341
+ plateOwn["colors/accent/highlight"] = (_d = [d["colors/accent/highlight"], l["colors/accent/highlight"]].find((c) => contrastRatio(ink, c) >= AA)) != null ? _d : readableFrom(l["colors/accent/highlight"], A.scale, [ink], lightPlate ? "lighter" : "darker", ink === WHITE ? BLACK : WHITE);
1342
+ plateOwn["chart/series/1"] = readableFrom(d["chart/series/1"], P.scale, plateBgs, awayFrom(plate), ink, NON_TEXT);
1343
+ for (const logo of ["logo/primary", "logo/signature", "logo/accent", "logo/mono", "social/ink"]) plateOwn[logo] = ink;
1284
1344
  const out = { light: {}, dark: {}, brand: {} };
1285
1345
  const pick = (src, role) => {
1286
1346
  var _a2;
@@ -1295,6 +1355,7 @@ function generateRdsTheme(def, opts = {}) {
1295
1355
  out.brand[roleVar(role)] = pick(brandSrc, role);
1296
1356
  }
1297
1357
  out.brand[roleVar("border/error")] = brandError;
1358
+ for (const [role, v] of Object.entries(plateOwn)) out.brand[roleVar(role)] = v;
1298
1359
  assertSafeTheme(out, where);
1299
1360
  return out;
1300
1361
  }
@@ -1385,15 +1446,54 @@ var RDS_CONTRAST_PAIRS = [
1385
1446
  ["text/on/info", "colors/state/info"],
1386
1447
  ["text/on/warning", "colors/state/warning"],
1387
1448
  ["text/on/neutral", "colors/state/neutral"],
1388
- ["text/error", "surface/error"]
1449
+ ["text/error", "surface/error"],
1450
+ // The pairs the components join (packages/ds, styles/rds/components.css): the label of the neutral Button and
1451
+ // Badge on its fill and hovers, the inverse label (colors/primary/default) on the card, the pressed outline Button,
1452
+ // the Badge highlight, the selected entry, quiet text and state text where they are placed.
1453
+ ["text/on/primary-strong", "colors/primary/default"],
1454
+ ["text/on/primary-strong", "colors/primary/light"],
1455
+ ["text/on/primary-strong", "colors/primary/active"],
1456
+ ["colors/primary/default", "surface/card"],
1457
+ ["colors/primary/default", "surface/tint/default"],
1458
+ ["text/heading", "surface/muted-strong"],
1459
+ ["text/heading", "colors/accent/highlight"],
1460
+ ["text/body", "surface/tint/default"],
1461
+ ["text/muted", "surface/tint/default"],
1462
+ ["text/on/tint", "surface/tint/subtle"],
1463
+ ["text/action", "surface/action/strong"],
1464
+ ["text/muted", "surface/page"],
1465
+ ["text/muted", "surface/panel"],
1466
+ ["text/subtle", "surface/card"],
1467
+ ["text/subtle", "surface/panel"],
1468
+ ["text/error", "surface/card"],
1469
+ ["text/error", "surface/page"],
1470
+ ["text/error", "surface/error-strong"],
1471
+ ["text/success", "surface/success"],
1472
+ ["text/success", "surface/card"],
1473
+ ["text/info", "surface/info"],
1474
+ ["text/warning", "surface/warning"],
1475
+ ["text/neutral", "surface/neutral"]
1476
+ ];
1477
+ var RDS_NON_TEXT_PAIRS = [
1478
+ ["border/strong", "surface/card"],
1479
+ ["border/strong", "surface/page"],
1480
+ ["border/error", "surface/card"],
1481
+ ["focus/ring", "surface/page"],
1482
+ ["focus/ring", "surface/card"],
1483
+ ["logo/primary", "surface/card"],
1484
+ ["logo/primary", "surface/page"],
1485
+ ["logo/primary", "surface/panel"],
1486
+ ["chart/series/1", "surface/card"],
1487
+ ["chart/series/1", "surface/page"]
1389
1488
  ];
1390
1489
  function rdsContrastReport(theme) {
1391
1490
  const out = [];
1392
1491
  for (const mode of ["light", "dark", "brand"])
1393
- for (const [fg, bg] of RDS_CONTRAST_PAIRS) {
1394
- const ratio = rdsContrast(theme, mode, fg, bg);
1395
- if (ratio !== null && ratio < AA) out.push({ mode, fg, bg, ratio: Math.round(ratio * 100) / 100 });
1396
- }
1492
+ for (const [pairs, min] of [[RDS_CONTRAST_PAIRS, AA], [RDS_NON_TEXT_PAIRS, NON_TEXT]])
1493
+ for (const [fg, bg] of pairs) {
1494
+ const ratio = rdsContrast(theme, mode, fg, bg);
1495
+ if (ratio !== null && ratio < min) out.push({ mode, fg, bg, ratio: Math.round(ratio * 100) / 100, min });
1496
+ }
1397
1497
  return out;
1398
1498
  }
1399
1499
  var isColourRef = (v) => /^[a-z][a-z0-9-]*\/[a-z]?\d+$/i.test(v);
@@ -1470,8 +1570,8 @@ function rdsThemeFromTable(table, opts = {}) {
1470
1570
  const fails = rdsContrastReport(out);
1471
1571
  if (fails.length)
1472
1572
  warn(
1473
- `${where}: ${fails.length} text pair(s) below 4.5:1
1474
- ` + fails.map((f) => `${f.mode}: ${f.fg} on ${f.bg} ${f.ratio}:1`).join("\n ")
1573
+ `${where}: ${fails.length} pair(s) below WCAG (4.5:1 text, 3:1 non-text)
1574
+ ` + fails.map((f) => `${f.mode}: ${f.fg} on ${f.bg} ${f.ratio}:1 (min ${f.min}:1)`).join("\n ")
1475
1575
  );
1476
1576
  return out;
1477
1577
  }
@@ -1553,10 +1653,10 @@ var CSS_FILE = /^[a-z0-9_@./-]+\.css$/i;
1553
1653
  var CSS_URL = /^https?:\/\/[^\s"'`<>()\\]+$/i;
1554
1654
  var INSTALL = /^(?:pnpm add|yarn add|bun add|npm install|npm i) @rojaostudio\/ds(?:@[a-z0-9][a-z0-9.+-]*)?$/;
1555
1655
  var ROLE_DOCS = [
1556
- { role: "colors/primary/default", use: "primary action, brand fills" },
1557
- { role: "text/on/primary", use: "text/icon on the primary fill" },
1558
- { role: "colors/primary/active", use: "pressed primary" },
1559
- { role: "colors/secondary/default", use: "secondary brand fill (with `--text-on-secondary`)" },
1656
+ { role: "colors/primary/default", use: "neutral ink fill: neutral Button and Badge, bars, Tooltip" },
1657
+ { role: "text/on/primary", use: "text/icon on the neutral ink fill" },
1658
+ { role: "colors/primary/active", use: "pressed neutral ink fill" },
1659
+ { role: "colors/secondary/default", use: "action fill: the default Button, checkbox, switch (with `--text-on-secondary`)" },
1560
1660
  { role: "colors/accent/default", use: "highlight \u2014 sparingly (with `--text-on-accent`)" },
1561
1661
  { role: "surface/page", use: "page background" },
1562
1662
  { role: "surface/card", use: "cards" },
@@ -1665,7 +1765,7 @@ ${themeImport}
1665
1765
  - ${opts.cssUrl ? `The theme is served from \`${opts.cssUrl}\`.` : `\`${cssFile}\` is the brand theme, generated \u2014 a file **you own**; commit it. Regenerate it
1666
1766
  with \`npx rojao-ds init\` instead of editing it by hand.`}
1667
1767
  - Dark mode: \`class="dark"\` on \`<html>\` (or any element). Light AND dark are first-class.
1668
- - Brand plate: \`class="ds-plate"\` on a section paints it with the primary color; the roles inside
1768
+ - Brand plate: \`class="ds-plate"\` on a section paints it with the brand color; the roles inside
1669
1769
  flip so text and components stay legible.
1670
1770
 
1671
1771
  ## Components first
@@ -1700,7 +1800,7 @@ Brand's own variables (same value in every mode): ${own.map((v) => `\`${v}\``).j
1700
1800
  - **Font:** \`font-family: var(${f.font})\` \u2014 never substitute.
1701
1801
 
1702
1802
  ## Rules (do / don't)
1703
- - \u2705 Primary action = \`Button\` (it already paints \`--colors-primary-default\` + \`--text-on-primary\`).
1803
+ - \u2705 Primary action = \`Button\` (tone action paints \`--colors-secondary-default\` + \`--text-on-secondary\`).
1704
1804
  - \u2705 Page uses \`--surface-page\`; cards \`--surface-card\`; body text \`--text-body\`.
1705
1805
  - \u2705 \`--colors-accent-default\` is the highlight \u2014 use it sparingly.
1706
1806
  - \u2705 Spacing, radius and type come ONLY from the foundation tokens above.
@@ -1718,4 +1818,4 @@ Brand's own variables (same value in every mode): ${own.map((v) => `\`${v}\``).j
1718
1818
  return text;
1719
1819
  }
1720
1820
 
1721
- export { RDS_CONTRAST_PAIRS, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, ROLES, RdsValidationError, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, isSafeColor, isSafeFont, isSafeVarName, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar };
1821
+ export { RDS_CONTRAST_PAIRS, RDS_NON_TEXT_PAIRS, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, ROLES, RdsValidationError, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, isSafeColor, isSafeFont, isSafeVarName, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar };
@@ -62,13 +62,23 @@ declare function rdsContrast(theme: RdsTheme, mode: RdsMode, fg: string, bg: str
62
62
  * alpha colour is skipped (its contrast depends on what is below).
63
63
  */
64
64
  declare const RDS_CONTRAST_PAIRS: ReadonlyArray<readonly [string, string]>;
65
+ /**
66
+ * What is not text but has to be seen (WCAG 1.4.11, 3:1): the border of a field and of a field in error, the focus
67
+ * ring, the brand's logo and its chart series, on the surfaces they are placed on.
68
+ */
69
+ declare const RDS_NON_TEXT_PAIRS: ReadonlyArray<readonly [string, string]>;
70
+ /** A pair below its minimum: 4.5:1 for text (RDS_CONTRAST_PAIRS), 3:1 for the rest (RDS_NON_TEXT_PAIRS). */
65
71
  type RdsContrastFailure = {
66
72
  mode: RdsMode;
67
73
  fg: string;
68
74
  bg: string;
69
75
  ratio: number;
76
+ min: number;
70
77
  };
71
- /** The pairs of RDS_CONTRAST_PAIRS that fail WCAG AA (4.5:1), mode by mode. Empty means every pair passes. */
78
+ /**
79
+ * The pairs of RDS_CONTRAST_PAIRS below WCAG AA (4.5:1) and of RDS_NON_TEXT_PAIRS below 3:1, mode by mode. Empty
80
+ * means every pair passes. A pair with an alpha colour is skipped (rdsContrast).
81
+ */
72
82
  declare function rdsContrastReport(theme: RdsTheme): RdsContrastFailure[];
73
83
  /**
74
84
  * A brand as drawn in the [RDS] Base Tokens file: for each theme mode, every role points to a primitive of the
@@ -218,4 +228,4 @@ declare function isNeutralBrand(def: {
218
228
  palettes?: PaletteDefs;
219
229
  }): boolean;
220
230
 
221
- export { BrandDef, type ClaudeMdOptions, type ClaudeMdTarget, RDS_CONTRAST_PAIRS, ROLES as RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, type RdsBrandTable, type RdsContrastFailure, type RdsCssOptions, type RdsMode, type RdsTheme, type RdsThemeOptions, RdsValidationError, SCALE_STEPS, type Scale, type ScaleStep, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, isSafeColor, isSafeFont, isSafeVarName, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, roleVar };
231
+ export { BrandDef, type ClaudeMdOptions, type ClaudeMdTarget, RDS_CONTRAST_PAIRS, RDS_NON_TEXT_PAIRS, ROLES as RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, type RdsBrandTable, type RdsContrastFailure, type RdsCssOptions, type RdsMode, type RdsTheme, type RdsThemeOptions, RdsValidationError, SCALE_STEPS, type Scale, type ScaleStep, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, isSafeColor, isSafeFont, isSafeVarName, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, roleVar };
package/dist/generate.js CHANGED
@@ -1,3 +1,3 @@
1
- export { RDS_CONTRAST_PAIRS, ROLES as RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, RdsValidationError, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, isSafeColor, isSafeFont, isSafeVarName, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-LP6UA4EW.js';
1
+ export { RDS_CONTRAST_PAIRS, RDS_NON_TEXT_PAIRS, ROLES as RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, RdsValidationError, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, isSafeColor, isSafeFont, isSafeVarName, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-D6HJEVJK.js';
2
2
  import './chunk-FRWRLCYM.js';
3
3
  import './chunk-ORMEWXMH.js';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { G as GenResult, R as ResolvedTheme, T as TokenMap, g as generateTheme, r as resolveTheme } from './generateTheme-CUfNpsKs.js';
2
- export { ClaudeMdOptions, ClaudeMdTarget, RDS_CONTRAST_PAIRS, RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, RdsBrandTable, RdsContrastFailure, RdsCssOptions, RdsMode, RdsTheme, RdsThemeOptions, RdsValidationError, SCALE_STEPS, Scale, ScaleStep, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, isSafeColor, isSafeFont, isSafeVarName, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, roleVar } from './generate.js';
2
+ export { ClaudeMdOptions, ClaudeMdTarget, RDS_CONTRAST_PAIRS, RDS_NON_TEXT_PAIRS, RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, RdsBrandTable, RdsContrastFailure, RdsCssOptions, RdsMode, RdsTheme, RdsThemeOptions, RdsValidationError, SCALE_STEPS, Scale, ScaleStep, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, isSafeColor, isSafeFont, isSafeVarName, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, roleVar } from './generate.js';
3
3
  export { A as Archetype, B as BrandColors, a as BrandDef, C as ColorRef, D as DarkStrategy, F as FontDef, P as PaletteRef, S as ScaleToken, T as TextStyle, b as TextStyleName, c as TypeScale } from './recipe.schema-Cq7e82nx.js';
4
4
  export { primitives, tokens } from './tokens.js';
5
5
  export { RecipeName, recipes } from './recipes.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { recipes } from './chunk-PV6GFEAM.js';
2
- export { RDS_CONTRAST_PAIRS, ROLES as RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, RdsValidationError, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, isSafeColor, isSafeFont, isSafeVarName, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-LP6UA4EW.js';
2
+ export { RDS_CONTRAST_PAIRS, RDS_NON_TEXT_PAIRS, ROLES as RDS_ROLES, RDS_SCOPE_SELECTORS, RDS_TOKEN_SCOPE, RdsValidationError, SCALE_STEPS, brandTones, buildScale, contrastRatio, emitClaudeMd, emitCss, emitRdsCss, generateRdsTheme, generateTheme, hexToHsl, hslToHex, isHex, isNeutralBrand, isSafeColor, isSafeFont, isSafeVarName, onColor, rdsContrast, rdsContrastReport, rdsThemeFromTable, refToHex, relativeLuminance, resolveTheme, roleVar } from './chunk-D6HJEVJK.js';
3
3
  export { primitives, tokens } from './chunk-FRWRLCYM.js';
4
4
  import './chunk-ORMEWXMH.js';
package/dist/themes.js CHANGED
@@ -1,4 +1,4 @@
1
- import { generateTheme } from './chunk-LP6UA4EW.js';
1
+ import { generateTheme } from './chunk-D6HJEVJK.js';
2
2
  import './chunk-FRWRLCYM.js';
3
3
  import { __spreadValues, __objRest } from './chunk-ORMEWXMH.js';
4
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rojaostudio/ds-core",
3
- "version": "1.1.0-next.10",
3
+ "version": "1.1.0-next.11",
4
4
  "description": "O motor do Rojão DS — tokens, derivação de tema e emissores. Sem React, sem Tailwind, sem CSS.",
5
5
  "license": "MIT",
6
6
  "author": "Rojão Studio (https://rojao.studio)",