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

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);
@@ -903,7 +913,7 @@ var ROLES = [
903
913
  ["text/on/accent", "d", "d"],
904
914
  ["text/on/tint", "d", "b"],
905
915
  ["colors/state/error", "l", "l"],
906
- ["colors/state/error-strong", "d", "d"],
916
+ ["colors/state/error-strong", "p", "p"],
907
917
  ["surface/tint/strong", "d", "b"],
908
918
  ["text/on/error", "l", "l"],
909
919
  ["surface/action/default", "d", "b"],
@@ -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"];
@@ -1176,7 +1198,6 @@ function generateRdsTheme(def, opts = {}) {
1176
1198
  "text/on/action-tonal": readableFrom(P.scale[200], P.scale, [L[900]], "lighter", WHITE),
1177
1199
  "surface/lift/action": P.scale[800],
1178
1200
  "surface/lift/action-strong": P.scale[700],
1179
- "colors/state/error-strong": red[400],
1180
1201
  "colors/state/success-strong": green[300],
1181
1202
  "colors/state/warning-strong": orange[400],
1182
1203
  "surface/error": red[900],
@@ -1203,16 +1224,29 @@ function generateRdsTheme(def, opts = {}) {
1203
1224
  "colors/state/neutral-strong": N[400],
1204
1225
  "text/on/primary-subtle": black(30)
1205
1226
  };
1227
+ if (single) {
1228
+ const steps = [200, 300, 100, 400];
1229
+ const s = (_b = steps.find((st) => distinct(N[st], d["colors/secondary/default"]))) != null ? _b : 200;
1230
+ d["colors/primary/default"] = N[s];
1231
+ d["colors/primary/dark"] = N[s === 100 ? 50 : s - 100];
1232
+ d["colors/primary/active"] = N[s + 100];
1233
+ d["colors/primary/light"] = N[s + 200];
1234
+ }
1235
+ d["surface/tint/default"] = readableFrom(d["surface/tint/default"], A.scale, [d["text/body"], d["text/muted"], d["colors/primary/default"]], "darker", N[900]);
1236
+ d["surface/tint/subtle"] = d["surface/tint/default"];
1237
+ d["colors/accent/highlight"] = readableFrom(d["colors/accent/highlight"], A.scale, [d["text/heading"]], "darker", N[900]);
1206
1238
  for (const [fill, text] of [
1207
1239
  ["colors/primary/default", "text/on/primary"],
1208
1240
  ["colors/secondary/default", "text/on/secondary"],
1209
1241
  ["colors/accent/default", "text/on/accent"],
1210
1242
  ["colors/accent/mark", "text/on/accent-mark"],
1211
- ["colors/primary/dark", "text/on/primary-strong"],
1243
+ ["colors/primary/default", "text/on/primary-strong"],
1212
1244
  ["surface/tint/default", "text/on/tint"],
1213
1245
  ["surface/lift/action", "text/on/lift-action"]
1214
1246
  ]) 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"]]);
1247
+ for (const role of ["colors/primary/dark", "colors/primary/light"])
1248
+ d[role] = carry(K, d["colors/primary/default"], d["text/on/primary-strong"], d[role]);
1249
+ [d["colors/primary/active"]] = stateFills(K, d["colors/primary/default"], d["text/on/primary"], [d["colors/primary/active"]]);
1216
1250
  [d["colors/secondary/hover"], d["colors/secondary/active"]] = stateFills(
1217
1251
  S.scale,
1218
1252
  d["colors/secondary/default"],
@@ -1220,16 +1254,25 @@ function generateRdsTheme(def, opts = {}) {
1220
1254
  [d["colors/secondary/hover"], d["colors/secondary/active"]]
1221
1255
  );
1222
1256
  [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);
1257
+ d["border/error"] = readableFrom(red[400], red, [d["surface/card"]], awayFrom(d["surface/card"]), WHITE, NON_TEXT);
1224
1258
  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"];
1259
+ const ink0 = on(P.base);
1260
+ 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
1261
  const ink = on(plate);
1227
1262
  const inkA = ink === WHITE ? white : black;
1228
1263
  const antiInk = ink === WHITE ? black : white;
1229
1264
  const lp = relativeLuminance(plate);
1230
1265
  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]);
1266
+ 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;
1267
+ const plateBgs = [plate, plateCard];
1268
+ const ov = plateBgs.every((bg) => contrastRatio(ink, over(inkA(20), bg)) >= AA) ? inkA : ink === WHITE ? black : white;
1269
+ const under = [...plateBgs, ...plateBgs.map((bg) => over(ov(10), bg))];
1270
+ const quiet = (pct, min = AA, bgs = under) => {
1271
+ for (let p = pct; p < 100; p += 5) if (bgs.every((bg) => contrastRatio(over(inkA(p), bg), bg) >= min)) return inkA(p);
1272
+ return ink;
1273
+ };
1274
+ const fieldBorder = quiet(60, NON_TEXT, plateBgs);
1275
+ const fieldAlpha = fieldBorder.length === 9 ? Math.round(parseInt(fieldBorder.slice(7), 16) / 255 * 100) : 100;
1233
1276
  const br = {
1234
1277
  "colors/primary/light": P.scale[300],
1235
1278
  "colors/primary/default": ink,
@@ -1246,33 +1289,37 @@ function generateRdsTheme(def, opts = {}) {
1246
1289
  "surface/card": plateCard,
1247
1290
  "surface/panel": plate,
1248
1291
  "border/default": inkA(20),
1249
- "surface/tint/default": inkA(10),
1250
- "surface/tint/strong": inkA(20),
1251
- "surface/tint/subtle": inkA(5),
1292
+ "surface/tint/default": ov(10),
1293
+ "surface/tint/strong": ov(20),
1294
+ "surface/tint/subtle": ov(5),
1252
1295
  "text/on/action-tonal": ink,
1253
1296
  "text/on/primary": plate,
1254
1297
  "text/on/secondary": plate,
1255
1298
  "text/on/tint": ink,
1256
- "surface/action/default": inkA(10),
1257
- "surface/action/strong": inkA(20),
1299
+ "surface/action/default": ov(10),
1300
+ "surface/action/strong": ov(20),
1258
1301
  "focus/ring": ink,
1259
1302
  "focus/ring-inset": plate,
1260
1303
  "surface/disabled": inkA(10),
1261
1304
  "text/disabled": inkA(40),
1262
- "text/muted": inkA(70),
1305
+ "text/muted": quiet(70),
1263
1306
  "text/disabled-invert": antiInk(40),
1264
1307
  "text/action": ink,
1265
- "surface/lift/action": inkA(10),
1308
+ "surface/lift/action": ov(10),
1266
1309
  "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),
1310
+ "surface/lift/action-strong": ov(20),
1311
+ // The label of the neutral Button on the plate, whose fill is the ink: the plate colour.
1312
+ "text/on/primary-strong": plate,
1313
+ "text/subtle": quiet(60),
1314
+ "surface/muted": ov(10),
1315
+ "surface/muted-strong": ov(20),
1316
+ "border/strong": fieldBorder,
1317
+ "border/strong-hover": fieldAlpha + 20 < 100 ? inkA(fieldAlpha + 20) : ink,
1274
1318
  "text/on/primary-subtle": inkA(30)
1275
1319
  };
1320
+ for (const role of ["colors/primary/dark", "colors/primary/light"])
1321
+ br[role] = carry(P.scale, br["colors/primary/default"], br["text/on/primary-strong"], br[role]);
1322
+ br["colors/primary/dark"] = readableFrom(br["colors/primary/dark"], P.scale, [plateCard], awayFrom(plateCard), ink, NON_TEXT);
1276
1323
  [br["colors/primary/active"]] = stateFills(P.scale, br["colors/primary/default"], br["text/on/primary"], [br["colors/primary/active"]]);
1277
1324
  [br["colors/secondary/hover"], br["colors/secondary/active"]] = stateFills(
1278
1325
  S.scale,
@@ -1280,11 +1327,24 @@ function generateRdsTheme(def, opts = {}) {
1280
1327
  br["text/on/secondary"],
1281
1328
  [br["colors/secondary/hover"], br["colors/secondary/active"]]
1282
1329
  );
1283
- const brandError = readableFrom(red[300], red, [plateCard], awayFrom(plateCard), ink, NON_TEXT);
1330
+ const brandError = readableFrom(red[300], red, plateBgs.slice().reverse(), awayFrom(plateCard), ink, NON_TEXT);
1331
+ const lightPlate = ink === BLACK;
1332
+ const plateOwn = {};
1333
+ for (const [st, rampOf] of [["error", red], ["success", green], ["info", L], ["warning", orange], ["neutral", N]]) {
1334
+ const from = lightPlate ? l : d;
1335
+ const soft = [`surface/${st}`, ...st === "error" ? ["surface/error-strong"] : []];
1336
+ for (const s of soft) plateOwn[s] = from[s];
1337
+ plateOwn[`text/${st}`] = readableFrom(from[`text/${st}`], rampOf, [...under, ...soft.map((s) => from[s])], awayFrom(plate), ink);
1338
+ }
1339
+ 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);
1340
+ plateOwn["chart/series/1"] = readableFrom(d["chart/series/1"], P.scale, plateBgs, awayFrom(plate), ink, NON_TEXT);
1341
+ plateOwn["colors/state/neutral-strong"] = readableFrom(d["colors/state/neutral-strong"], N, plateBgs, awayFrom(plate), ink, NON_TEXT);
1342
+ for (const logo of ["logo/primary", "logo/signature", "logo/accent", "logo/mono", "social/ink"]) plateOwn[logo] = ink;
1343
+ const fixed = { "colors/state/error-strong": red[700] };
1284
1344
  const out = { light: {}, dark: {}, brand: {} };
1285
1345
  const pick = (src, role) => {
1286
1346
  var _a2;
1287
- const table = src === "l" ? l : src === "d" ? d : br;
1347
+ const table = src === "l" ? l : src === "d" ? d : src === "p" ? fixed : br;
1288
1348
  const v = (_a2 = table[role]) != null ? _a2 : l[role];
1289
1349
  if (v === void 0) throw new Error(`rdsTheme: no value for "${role}"`);
1290
1350
  return v;
@@ -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,56 @@ 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 brand ink as text (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"],
1488
+ ["colors/state/neutral-strong", "surface/panel"],
1489
+ ["colors/state/neutral-strong", "surface/card"]
1389
1490
  ];
1390
1491
  function rdsContrastReport(theme) {
1391
1492
  const out = [];
1392
1493
  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
- }
1494
+ for (const [pairs, min] of [[RDS_CONTRAST_PAIRS, AA], [RDS_NON_TEXT_PAIRS, NON_TEXT]])
1495
+ for (const [fg, bg] of pairs) {
1496
+ const ratio = rdsContrast(theme, mode, fg, bg);
1497
+ if (ratio !== null && ratio < min) out.push({ mode, fg, bg, ratio: Math.round(ratio * 100) / 100, min });
1498
+ }
1397
1499
  return out;
1398
1500
  }
1399
1501
  var isColourRef = (v) => /^[a-z][a-z0-9-]*\/[a-z]?\d+$/i.test(v);
@@ -1470,8 +1572,8 @@ function rdsThemeFromTable(table, opts = {}) {
1470
1572
  const fails = rdsContrastReport(out);
1471
1573
  if (fails.length)
1472
1574
  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 ")
1575
+ `${where}: ${fails.length} pair(s) below WCAG (4.5:1 text, 3:1 non-text)
1576
+ ` + fails.map((f) => `${f.mode}: ${f.fg} on ${f.bg} ${f.ratio}:1 (min ${f.min}:1)`).join("\n ")
1475
1577
  );
1476
1578
  return out;
1477
1579
  }
@@ -1553,10 +1655,10 @@ var CSS_FILE = /^[a-z0-9_@./-]+\.css$/i;
1553
1655
  var CSS_URL = /^https?:\/\/[^\s"'`<>()\\]+$/i;
1554
1656
  var INSTALL = /^(?:pnpm add|yarn add|bun add|npm install|npm i) @rojaostudio\/ds(?:@[a-z0-9][a-z0-9.+-]*)?$/;
1555
1657
  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`)" },
1658
+ { role: "colors/primary/default", use: "neutral ink fill: neutral Button and Badge, bars, Tooltip" },
1659
+ { role: "text/on/primary", use: "text/icon on the neutral ink fill" },
1660
+ { role: "colors/primary/active", use: "pressed neutral ink fill" },
1661
+ { role: "colors/secondary/default", use: "action fill: the default Button, checkbox, switch (with `--text-on-secondary`)" },
1560
1662
  { role: "colors/accent/default", use: "highlight \u2014 sparingly (with `--text-on-accent`)" },
1561
1663
  { role: "surface/page", use: "page background" },
1562
1664
  { role: "surface/card", use: "cards" },
@@ -1665,7 +1767,7 @@ ${themeImport}
1665
1767
  - ${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
1768
  with \`npx rojao-ds init\` instead of editing it by hand.`}
1667
1769
  - 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
1770
+ - Brand plate: \`class="ds-plate"\` on a section paints it with the brand color; the roles inside
1669
1771
  flip so text and components stay legible.
1670
1772
 
1671
1773
  ## Components first
@@ -1700,7 +1802,7 @@ Brand's own variables (same value in every mode): ${own.map((v) => `\`${v}\``).j
1700
1802
  - **Font:** \`font-family: var(${f.font})\` \u2014 never substitute.
1701
1803
 
1702
1804
  ## Rules (do / don't)
1703
- - \u2705 Primary action = \`Button\` (it already paints \`--colors-primary-default\` + \`--text-on-primary\`).
1805
+ - \u2705 Primary action = \`Button\` (tone action paints \`--colors-secondary-default\` + \`--text-on-secondary\`).
1704
1806
  - \u2705 Page uses \`--surface-page\`; cards \`--surface-card\`; body text \`--text-body\`.
1705
1807
  - \u2705 \`--colors-accent-default\` is the highlight \u2014 use it sparingly.
1706
1808
  - \u2705 Spacing, radius and type come ONLY from the foundation tokens above.
@@ -1718,4 +1820,4 @@ Brand's own variables (same value in every mode): ${own.map((v) => `\`${v}\``).j
1718
1820
  return text;
1719
1821
  }
1720
1822
 
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 };
1823
+ 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 };
@@ -14,9 +14,10 @@ type RdsThemeOptions = {
14
14
  /**
15
15
  * Roles of the [RDS] theme collection, in Figma order: [name, dark source, brand source].
16
16
  * The light mode always reads the plain base token. In dark and brand, Figma points either to the
17
- * mode's own variant (`d` = base dark/…, `b` = base brand/…) or back to another one (`l` = plain).
17
+ * mode's own variant (`d` = base dark/…, `b` = base brand/…) or back to another one (`l` = plain), or straight
18
+ * to a primitive of the [RDS] Primitives library (`p`: the same colour for every brand, see FIXED in the generator).
18
19
  */
19
- declare const ROLES: ReadonlyArray<readonly [string, "l" | "d", "l" | "d" | "b"]>;
20
+ declare const ROLES: ReadonlyArray<readonly [string, "l" | "d" | "p", "l" | "d" | "b" | "p"]>;
20
21
  /** CSS custom property of a role: the Figma path with hyphens (the [RDS] codeSyntax rule). */
21
22
  declare const roleVar: (role: string) => string;
22
23
  declare function generateRdsTheme(def: BrandDef, opts?: RdsThemeOptions): RdsTheme;
@@ -62,13 +63,23 @@ declare function rdsContrast(theme: RdsTheme, mode: RdsMode, fg: string, bg: str
62
63
  * alpha colour is skipped (its contrast depends on what is below).
63
64
  */
64
65
  declare const RDS_CONTRAST_PAIRS: ReadonlyArray<readonly [string, string]>;
66
+ /**
67
+ * 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
68
+ * ring, the brand's logo and its chart series, on the surfaces they are placed on.
69
+ */
70
+ declare const RDS_NON_TEXT_PAIRS: ReadonlyArray<readonly [string, string]>;
71
+ /** A pair below its minimum: 4.5:1 for text (RDS_CONTRAST_PAIRS), 3:1 for the rest (RDS_NON_TEXT_PAIRS). */
65
72
  type RdsContrastFailure = {
66
73
  mode: RdsMode;
67
74
  fg: string;
68
75
  bg: string;
69
76
  ratio: number;
77
+ min: number;
70
78
  };
71
- /** The pairs of RDS_CONTRAST_PAIRS that fail WCAG AA (4.5:1), mode by mode. Empty means every pair passes. */
79
+ /**
80
+ * 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
81
+ * means every pair passes. A pair with an alpha colour is skipped (rdsContrast).
82
+ */
72
83
  declare function rdsContrastReport(theme: RdsTheme): RdsContrastFailure[];
73
84
  /**
74
85
  * A brand as drawn in the [RDS] Base Tokens file: for each theme mode, every role points to a primitive of the
@@ -218,4 +229,4 @@ declare function isNeutralBrand(def: {
218
229
  palettes?: PaletteDefs;
219
230
  }): boolean;
220
231
 
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 };
232
+ 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-NVYJME6V.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-NVYJME6V.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-NVYJME6V.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.12",
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)",