@conductor-by-89soone/tokens 0.1.0 → 0.2.1

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.
@@ -26,6 +26,8 @@ var EXIT_CODES = {
26
26
  "TOK-CP-USAGE": 1,
27
27
  "TOK-CP-COLOR": 1,
28
28
  "TOK-CP-ALPHA": 1,
29
+ "TOK-CP-FORBIDDEN": 1,
30
+ "TOK-THEME-KEY": 1,
29
31
  "TOK-LITERAL": 1,
30
32
  "TOK-ARG": 3
31
33
  };
@@ -270,12 +272,96 @@ var badgePairs = [
270
272
  "badge.marker.background",
271
273
  "nonText",
272
274
  "tokens spec 8.2 names the foreground `badge.marker.dot`; that key is a variant slot rather than a token, and it resolves to `status.queued` for the only declared variant."
275
+ ),
276
+ pair(
277
+ "CP-042",
278
+ "status.neutralEnd",
279
+ "badge.marker.background",
280
+ "nonText",
281
+ "CR-035. The second marker variant, measurable again now that the dark end state is `slate.400`. New id: CP-025 is permanently retired (tokens spec 8.2, 8.5)."
273
282
  )
274
283
  ];
275
284
  var disabledPairs = [
276
285
  pair("CP-040", "text.muted", "state.disabled", "body"),
277
286
  pair("CP-041", "button.policyDisabled.text", "state.disabledPolicy", "body")
278
287
  ];
288
+ var datavizPairs = [
289
+ pair("CP-043", "dataviz.series.1", "surface.base", "nonText"),
290
+ pair("CP-044", "dataviz.series.1", "surface.canvas", "nonText"),
291
+ pair("CP-045", "dataviz.series.1", "surface.raised", "nonText"),
292
+ pair("CP-046", "dataviz.series.2", "surface.base", "nonText"),
293
+ pair("CP-047", "dataviz.series.2", "surface.canvas", "nonText"),
294
+ pair("CP-048", "dataviz.series.2", "surface.raised", "nonText"),
295
+ pair("CP-049", "dataviz.series.3", "surface.base", "nonText"),
296
+ pair("CP-050", "dataviz.series.3", "surface.canvas", "nonText"),
297
+ pair("CP-051", "dataviz.series.3", "surface.raised", "nonText"),
298
+ pair("CP-052", "dataviz.series.4", "surface.base", "nonText"),
299
+ pair("CP-053", "dataviz.series.4", "surface.canvas", "nonText"),
300
+ pair("CP-054", "dataviz.series.4", "surface.raised", "nonText"),
301
+ pair("CP-055", "dataviz.series.5", "surface.base", "nonText"),
302
+ pair("CP-056", "dataviz.series.5", "surface.canvas", "nonText"),
303
+ pair("CP-057", "dataviz.series.5", "surface.raised", "nonText"),
304
+ pair("CP-058", "dataviz.series.6", "surface.base", "nonText"),
305
+ pair("CP-059", "dataviz.series.6", "surface.canvas", "nonText"),
306
+ pair("CP-060", "dataviz.series.6", "surface.raised", "nonText"),
307
+ pair("CP-061", "dataviz.series.7", "surface.base", "nonText"),
308
+ pair("CP-062", "dataviz.series.7", "surface.canvas", "nonText"),
309
+ pair("CP-063", "dataviz.series.7", "surface.raised", "nonText"),
310
+ pair("CP-064", "dataviz.series.8", "surface.base", "nonText"),
311
+ pair("CP-065", "dataviz.series.8", "surface.canvas", "nonText"),
312
+ pair("CP-066", "dataviz.series.8", "surface.raised", "nonText"),
313
+ pair("CP-067", "dataviz.series.9", "surface.base", "nonText"),
314
+ pair("CP-068", "dataviz.series.9", "surface.canvas", "nonText"),
315
+ pair("CP-069", "dataviz.series.9", "surface.raised", "nonText"),
316
+ pair("CP-070", "dataviz.series.10", "surface.base", "nonText"),
317
+ pair("CP-071", "dataviz.series.10", "surface.canvas", "nonText"),
318
+ pair("CP-072", "dataviz.series.10", "surface.raised", "nonText"),
319
+ pair("CP-073", "dataviz.series.11", "surface.base", "nonText"),
320
+ pair("CP-074", "dataviz.series.11", "surface.canvas", "nonText"),
321
+ pair("CP-075", "dataviz.series.11", "surface.raised", "nonText"),
322
+ pair("CP-076", "dataviz.series.12", "surface.base", "nonText"),
323
+ pair("CP-077", "dataviz.series.12", "surface.canvas", "nonText"),
324
+ pair("CP-078", "dataviz.series.12", "surface.raised", "nonText"),
325
+ pair("CP-079", "dataviz.series.13", "surface.base", "nonText"),
326
+ pair("CP-080", "dataviz.series.13", "surface.canvas", "nonText"),
327
+ pair("CP-081", "dataviz.series.13", "surface.raised", "nonText"),
328
+ pair("CP-082", "dataviz.series.14", "surface.base", "nonText"),
329
+ pair("CP-083", "dataviz.series.14", "surface.canvas", "nonText"),
330
+ pair("CP-084", "dataviz.series.14", "surface.raised", "nonText"),
331
+ pair("CP-085", "dataviz.series.15", "surface.base", "nonText"),
332
+ pair("CP-086", "dataviz.series.15", "surface.canvas", "nonText"),
333
+ pair("CP-087", "dataviz.series.15", "surface.raised", "nonText"),
334
+ pair("CP-088", "dataviz.series.16", "surface.base", "nonText"),
335
+ pair("CP-089", "dataviz.series.16", "surface.canvas", "nonText"),
336
+ pair("CP-090", "dataviz.series.16", "surface.raised", "nonText"),
337
+ pair("CP-091", "dataviz.series.17", "surface.base", "nonText"),
338
+ pair("CP-092", "dataviz.series.17", "surface.canvas", "nonText"),
339
+ pair("CP-093", "dataviz.series.17", "surface.raised", "nonText"),
340
+ pair("CP-094", "dataviz.series.18", "surface.base", "nonText"),
341
+ pair("CP-095", "dataviz.series.18", "surface.canvas", "nonText"),
342
+ pair("CP-096", "dataviz.series.18", "surface.raised", "nonText"),
343
+ pair("CP-097", "dataviz.series.19", "surface.base", "nonText"),
344
+ pair("CP-098", "dataviz.series.19", "surface.canvas", "nonText"),
345
+ pair("CP-099", "dataviz.series.19", "surface.raised", "nonText"),
346
+ pair("CP-100", "dataviz.series.20", "surface.base", "nonText"),
347
+ pair("CP-101", "dataviz.series.20", "surface.canvas", "nonText"),
348
+ pair("CP-102", "dataviz.series.20", "surface.raised", "nonText"),
349
+ pair("CP-103", "dataviz.sequential.1", "surface.base", "nonText"),
350
+ pair("CP-104", "dataviz.sequential.1", "surface.canvas", "nonText"),
351
+ pair("CP-105", "dataviz.sequential.1", "surface.raised", "nonText"),
352
+ pair("CP-106", "dataviz.sequential.2", "surface.base", "nonText"),
353
+ pair("CP-107", "dataviz.sequential.2", "surface.canvas", "nonText"),
354
+ pair("CP-108", "dataviz.sequential.2", "surface.raised", "nonText"),
355
+ pair("CP-109", "dataviz.sequential.3", "surface.base", "nonText"),
356
+ pair("CP-110", "dataviz.sequential.3", "surface.canvas", "nonText"),
357
+ pair("CP-111", "dataviz.sequential.3", "surface.raised", "nonText"),
358
+ pair("CP-112", "dataviz.sequential.4", "surface.base", "nonText"),
359
+ pair("CP-113", "dataviz.sequential.4", "surface.canvas", "nonText"),
360
+ pair("CP-114", "dataviz.sequential.4", "surface.raised", "nonText"),
361
+ pair("CP-115", "dataviz.sequential.5", "surface.base", "nonText"),
362
+ pair("CP-116", "dataviz.sequential.5", "surface.canvas", "nonText"),
363
+ pair("CP-117", "dataviz.sequential.5", "surface.raised", "nonText")
364
+ ];
279
365
  var contrastPairs = [
280
366
  ...textOnSurface,
281
367
  ...accentPairs,
@@ -284,7 +370,25 @@ var contrastPairs = [
284
370
  ...meterPairs,
285
371
  ...severityPairs,
286
372
  ...badgePairs,
287
- ...disabledPairs
373
+ ...disabledPairs,
374
+ ...datavizPairs
375
+ ];
376
+ var forbiddenPairs = [
377
+ {
378
+ id: "FP-001",
379
+ foreground: "text.faint",
380
+ background: "surface.elevated",
381
+ reason: "`text.faint` is `decorative` and may not be painted on this surface \u2014 2.94:1 in dark, still short of body 4.5:1 in light (FR-THM-005 AC-3)"
382
+ },
383
+ {
384
+ id: "FP-002",
385
+ foreground: "accent",
386
+ background: "surface.elevated",
387
+ reason: "the accent is a body foreground on `surface.base` and `surface.raised` only; on the elevated surface it measures 4.40:1 in dark, below body 4.5:1 (tokens spec 8.4)",
388
+ // Body only. 4.40:1 clears both the `large` 3:1 and the `nonText` 3:1 thresholds, and the
389
+ // SRS forbids only body use on this surface.
390
+ usages: ["body"]
391
+ }
288
392
  ];
289
393
 
290
394
  // src/scales.ts
@@ -347,6 +451,11 @@ var radius = [
347
451
  aliased("radius.xl", "curve.24", "Largest radius."),
348
452
  semantic("radius.pill", "9999px", "Fully rounded badge, toggle, marker and progress geometry.")
349
453
  ];
454
+ var borderWidth = [
455
+ aliased("border.width.hairline", "stroke.1", "\uAE30\uBCF8 1px \uC5E3\uC9C0."),
456
+ aliased("border.width.emphasis", "stroke.2", "\uD45C \uD5E4\uB354 \uADDC\uCE59, \uB9C8\uCEE4 \uB9C1."),
457
+ aliased("border.width.rail", "stroke.3", "\uBC30\uB108 \uC0C1\uD0DC \uB808\uC77C, \uB0B4\uBE44 \uD65C\uC131 \uC778\uB514\uCF00\uC774\uD130.")
458
+ ];
350
459
  var Z_LAYERS = {
351
460
  base: 0,
352
461
  raised: 10,
@@ -389,6 +498,7 @@ var scaleTokens = [
389
498
  ...fontLineHeight,
390
499
  ...space,
391
500
  ...radius,
501
+ ...borderWidth,
392
502
  ...z,
393
503
  ...breakpoint,
394
504
  ...motion
@@ -472,14 +582,14 @@ var badge = [
472
582
  component("badge.marker.border", ref("border.strong"), "Marker-shape edge."),
473
583
  component("badge.marker.dotSize", value("9px"), "Status dot diameter."),
474
584
  component("badge.marker.dotRing", ref("surface.raised"), "Marker dot ring."),
475
- component("badge.marker.dotRingWidth", value("2px"), "Marker dot ring width."),
585
+ component("badge.marker.dotRingWidth", ref("border.width.emphasis"), "Marker dot ring width."),
476
586
  component("badge.severity.text", ref("text.primary"), "Label on a severity fill. 4.67:1 to 9.32:1.", "body")
477
587
  ];
478
588
  var table = [
479
589
  component("table.headerText", ref("text.muted"), "Column header text.", "body"),
480
590
  component("table.headerFontSize", ref("font.size.base"), "Column header size."),
481
591
  component("table.headerBorder", ref("border.default"), "Header rule."),
482
- component("table.headerBorderWidth", value("2px"), "Header rule width."),
592
+ component("table.headerBorderWidth", ref("border.width.emphasis"), "Header rule width."),
483
593
  component(
484
594
  "table.cellBorder",
485
595
  ref("border.default"),
@@ -591,6 +701,9 @@ var overlay = [
591
701
  ];
592
702
  var HEADING_MIN_PX = FONT_SIZE_PX.xl * 1.2;
593
703
  var HEADING_MAX_PX = FONT_SIZE_PX.xl * 1.6;
704
+ var H2_PX = Math.round(FONT_SIZE_PX.xl * 1.3);
705
+ var H3_PX = Math.round(FONT_SIZE_PX.xl * 1.1);
706
+ var HEADING_LINE_HEIGHT_RATIO = 1.3;
594
707
  var page = [
595
708
  component(
596
709
  "page.headingSize",
@@ -602,6 +715,26 @@ var page = [
602
715
  value("1.16"),
603
716
  "The only unitless line height: `clamp()` makes a px value impossible here."
604
717
  ),
718
+ component(
719
+ "page.sectionHeadingSize",
720
+ value(`${H2_PX}px`),
721
+ "`h2`. `font.size.xl` \xD7 1.3."
722
+ ),
723
+ component(
724
+ "page.sectionHeadingLineHeight",
725
+ value(`${roundHalfUp(H2_PX * HEADING_LINE_HEIGHT_RATIO)}px`),
726
+ "Line height paired with `page.sectionHeadingSize`, at the 1.30 heading ratio."
727
+ ),
728
+ component(
729
+ "page.subHeadingSize",
730
+ value(`${H3_PX}px`),
731
+ "`h3`. `font.size.xl` \xD7 1.1."
732
+ ),
733
+ component(
734
+ "page.subHeadingLineHeight",
735
+ value(`${roundHalfUp(H3_PX * HEADING_LINE_HEIGHT_RATIO)}px`),
736
+ "Line height paired with `page.subHeadingSize`, at the 1.30 heading ratio."
737
+ ),
605
738
  component("page.stackGap", ref("space.6"), "Gap between page sections."),
606
739
  component("page.stackGapCompact", ref("space.5"), "Section gap below `breakpoint.sm`."),
607
740
  component("page.contentStackGap", ref("space.5"), "Gap inside a content stack."),
@@ -614,12 +747,21 @@ var page = [
614
747
  component("page.skipLinkZ", ref("z.popover"), "Skip link stacking layer.")
615
748
  ];
616
749
  var shell = [
750
+ component(
751
+ "appShell.background",
752
+ value("radial-gradient(circle at 82% -10%, {surface.tint.1}, transparent 34rem), radial-gradient(circle at 10% 85%, {surface.tint.2}, transparent 30rem), {surface.base}"),
753
+ "Ambient app-shell surface derived from the source application background."
754
+ ),
617
755
  component("appShell.navWidth", ref("page.shellNavWidth"), "AppShell desktop navigation width."),
618
756
  component("appShell.mainMaxWidth", value("1480px"), "AppShell main content maximum width."),
619
757
  component("appShell.overlayBackground", ref("surface.overlay"), "Mobile navigation scrim."),
620
758
  component("skipLink.background", ref("accent.DEFAULT"), "Skip-link fill."),
621
759
  component("skipLink.text", ref("text.inverse"), "Skip-link label."),
622
- component("navList.background", ref("surface.raised"), "Navigation surface."),
760
+ component(
761
+ "navList.background",
762
+ value("linear-gradient(180deg, {surface.glass}, {surface.canvas})"),
763
+ "Glass navigation surface over the app-shell ambient background."
764
+ ),
623
765
  component("navList.border", ref("border.subtle"), "Navigation surface edge."),
624
766
  component("navItem.text", ref("text.muted"), "Navigation item label."),
625
767
  component("navItem.textActive", ref("text.primary"), "Active navigation item label."),
@@ -717,6 +859,18 @@ var surface = [
717
859
  usage: "decorative",
718
860
  value: "rgba(255, 255, 255, 0.05)",
719
861
  description: "Progress and meter track, matching the source 1:1."
862
+ }),
863
+ darkToken({
864
+ key: "surface.tint.1",
865
+ usage: "decorative",
866
+ value: "rgba(109, 124, 255, 0.11)",
867
+ description: "Primary ambient glow from the source application background. Decorative only."
868
+ }),
869
+ darkToken({
870
+ key: "surface.tint.2",
871
+ usage: "decorative",
872
+ value: "rgba(20, 184, 166, 0.055)",
873
+ description: "Secondary ambient glow from the source application background. Decorative only."
720
874
  })
721
875
  ];
722
876
  var text = [
@@ -860,10 +1014,10 @@ var status = [
860
1014
  }),
861
1015
  darkToken({
862
1016
  key: "status.neutralEnd",
863
- usage: "decorative",
864
- value: "#475569",
1017
+ usage: "nonText",
1018
+ value: "#94a3b8",
865
1019
  icon: "circle-slash",
866
- description: "Cancelled or superseded. Contrast-exempt per CR-006 (`srs_final.md` 12.1): 2.04:1 to 2.60:1 reaches non-text 3:1 on no surface. WCAG 1.4.11 exception \u2014 FR-THM-005 AC-7 forces an icon and a label alongside the colour, and the source's surface-coloured ring supplies the marker's shape boundary, so the dot's fill carries no identification on its own. Known cost: the dot reads faintly against a dark background."
1020
+ description: "Cancelled or superseded. `slate.400` at 6.61:1 on `surface.raised` (CP-042). The marker is drawn as a ring rather than a fill, so it stays distinguishable from queued's filled dot by shape as well as colour. CR-035 retires the CR-006 contrast exemption at this value: the old `#475569` measured 2.04:1 to 2.60:1 and could only be classified `decorative`, which meant the end-state dot read faintly against every dark surface."
867
1021
  })
868
1022
  ];
869
1023
  var meter = [
@@ -982,6 +1136,158 @@ var interaction = [
982
1136
  description: "Policy-blocked fill. Contrast-exempt as a fill; checked beneath the label (5.27:1)."
983
1137
  })
984
1138
  ];
1139
+ var dataviz = [
1140
+ darkToken({
1141
+ key: "dataviz.series.1",
1142
+ usage: "nonText",
1143
+ value: "#e88171",
1144
+ description: "Categorical chart series 1 of 20. Fill only; non-text 3:1 on chart surfaces (CP-043~CP-045). Series identity only; carries no magnitude."
1145
+ }),
1146
+ darkToken({
1147
+ key: "dataviz.series.2",
1148
+ usage: "nonText",
1149
+ value: "#1db198",
1150
+ description: "Categorical chart series 2 of 20. Fill only; non-text 3:1 on chart surfaces (CP-046~CP-048). Series identity only; carries no magnitude."
1151
+ }),
1152
+ darkToken({
1153
+ key: "dataviz.series.3",
1154
+ usage: "nonText",
1155
+ value: "#e979ad",
1156
+ description: "Categorical chart series 3 of 20. Fill only; non-text 3:1 on chart surfaces (CP-049~CP-051). Series identity only; carries no magnitude."
1157
+ }),
1158
+ darkToken({
1159
+ key: "dataviz.series.4",
1160
+ usage: "nonText",
1161
+ value: "#1eb641",
1162
+ description: "Categorical chart series 4 of 20. Fill only; non-text 3:1 on chart surfaces (CP-052~CP-054). Series identity only; carries no magnitude."
1163
+ }),
1164
+ darkToken({
1165
+ key: "dataviz.series.5",
1166
+ usage: "nonText",
1167
+ value: "#e073e8",
1168
+ description: "Categorical chart series 5 of 20. Fill only; non-text 3:1 on chart surfaces (CP-055~CP-057). Series identity only; carries no magnitude."
1169
+ }),
1170
+ darkToken({
1171
+ key: "dataviz.series.6",
1172
+ usage: "nonText",
1173
+ value: "#54b21d",
1174
+ description: "Categorical chart series 6 of 20. Fill only; non-text 3:1 on chart surfaces (CP-058~CP-060). Series identity only; carries no magnitude."
1175
+ }),
1176
+ darkToken({
1177
+ key: "dataviz.series.7",
1178
+ usage: "nonText",
1179
+ value: "#ad8dec",
1180
+ description: "Categorical chart series 7 of 20. Fill only; non-text 3:1 on chart surfaces (CP-061~CP-063). Series identity only; carries no magnitude."
1181
+ }),
1182
+ darkToken({
1183
+ key: "dataviz.series.8",
1184
+ usage: "nonText",
1185
+ value: "#9fa41b",
1186
+ description: "Categorical chart series 8 of 20. Fill only; non-text 3:1 on chart surfaces (CP-064~CP-066). Series identity only; carries no magnitude."
1187
+ }),
1188
+ darkToken({
1189
+ key: "dataviz.series.9",
1190
+ usage: "nonText",
1191
+ value: "#7f9bea",
1192
+ description: "Categorical chart series 9 of 20. Fill only; non-text 3:1 on chart surfaces (CP-067~CP-069). Series identity only; carries no magnitude."
1193
+ }),
1194
+ darkToken({
1195
+ key: "dataviz.series.10",
1196
+ usage: "nonText",
1197
+ value: "#e18844",
1198
+ description: "Categorical chart series 10 of 20. Fill only; non-text 3:1 on chart surfaces (CP-070~CP-072). Series identity only; carries no magnitude."
1199
+ }),
1200
+ darkToken({
1201
+ key: "dataviz.series.11",
1202
+ usage: "nonText",
1203
+ value: "#20adc2",
1204
+ description: "Categorical chart series 11 of 20. Fill only; non-text 3:1 on chart surfaces (CP-073~CP-075). Series identity only; carries no magnitude."
1205
+ }),
1206
+ darkToken({
1207
+ key: "dataviz.series.12",
1208
+ usage: "nonText",
1209
+ value: "#ea7d8f",
1210
+ description: "Categorical chart series 12 of 20. Fill only; non-text 3:1 on chart surfaces (CP-076~CP-078). Series identity only; carries no magnitude."
1211
+ }),
1212
+ darkToken({
1213
+ key: "dataviz.series.13",
1214
+ usage: "nonText",
1215
+ value: "#1db46e",
1216
+ description: "Categorical chart series 13 of 20. Fill only; non-text 3:1 on chart surfaces (CP-079~CP-081). Series identity only; carries no magnitude."
1217
+ }),
1218
+ darkToken({
1219
+ key: "dataviz.series.14",
1220
+ usage: "nonText",
1221
+ value: "#e874cd",
1222
+ description: "Categorical chart series 14 of 20. Fill only; non-text 3:1 on chart surfaces (CP-082~CP-084). Series identity only; carries no magnitude."
1223
+ }),
1224
+ darkToken({
1225
+ key: "dataviz.series.15",
1226
+ usage: "nonText",
1227
+ value: "#28b61e",
1228
+ description: "Categorical chart series 15 of 20. Fill only; non-text 3:1 on chart surfaces (CP-085~CP-087). Series identity only; carries no magnitude."
1229
+ }),
1230
+ darkToken({
1231
+ key: "dataviz.series.16",
1232
+ usage: "nonText",
1233
+ value: "#c583eb",
1234
+ description: "Categorical chart series 16 of 20. Fill only; non-text 3:1 on chart surfaces (CP-088~CP-090). Series identity only; carries no magnitude."
1235
+ }),
1236
+ darkToken({
1237
+ key: "dataviz.series.17",
1238
+ usage: "nonText",
1239
+ value: "#7cac1c",
1240
+ description: "Categorical chart series 17 of 20. Fill only; non-text 3:1 on chart surfaces (CP-091~CP-093). Series identity only; carries no magnitude."
1241
+ }),
1242
+ darkToken({
1243
+ key: "dataviz.series.18",
1244
+ usage: "nonText",
1245
+ value: "#9794ee",
1246
+ description: "Categorical chart series 18 of 20. Fill only; non-text 3:1 on chart surfaces (CP-094~CP-096). Series identity only; carries no magnitude."
1247
+ }),
1248
+ darkToken({
1249
+ key: "dataviz.series.19",
1250
+ usage: "nonText",
1251
+ value: "#c39720",
1252
+ description: "Categorical chart series 19 of 20. Fill only; non-text 3:1 on chart surfaces (CP-097~CP-099). Series identity only; carries no magnitude."
1253
+ }),
1254
+ darkToken({
1255
+ key: "dataviz.series.20",
1256
+ usage: "nonText",
1257
+ value: "#52a4e3",
1258
+ description: "Categorical chart series 20 of 20. Fill only; non-text 3:1 on chart surfaces (CP-100~CP-102). Series identity only; carries no magnitude."
1259
+ }),
1260
+ darkToken({
1261
+ key: "dataviz.sequential.1",
1262
+ usage: "nonText",
1263
+ value: "#6960d1",
1264
+ description: "Sequential distribution step 1 of 5 (least prominent), single-hue luminance ramp. Fill only; non-text 3:1 on chart surfaces (CP-103~CP-105)."
1265
+ }),
1266
+ darkToken({
1267
+ key: "dataviz.sequential.2",
1268
+ usage: "nonText",
1269
+ value: "#847dd9",
1270
+ description: "Sequential distribution step 2 of 5 (step), single-hue luminance ramp. Fill only; non-text 3:1 on chart surfaces (CP-106~CP-108)."
1271
+ }),
1272
+ darkToken({
1273
+ key: "dataviz.sequential.3",
1274
+ usage: "nonText",
1275
+ value: "#9e98e1",
1276
+ description: "Sequential distribution step 3 of 5 (step), single-hue luminance ramp. Fill only; non-text 3:1 on chart surfaces (CP-109~CP-111)."
1277
+ }),
1278
+ darkToken({
1279
+ key: "dataviz.sequential.4",
1280
+ usage: "nonText",
1281
+ value: "#b7b3e9",
1282
+ description: "Sequential distribution step 4 of 5 (step), single-hue luminance ramp. Fill only; non-text 3:1 on chart surfaces (CP-112~CP-114)."
1283
+ }),
1284
+ darkToken({
1285
+ key: "dataviz.sequential.5",
1286
+ usage: "nonText",
1287
+ value: "#cecbf0",
1288
+ description: "Sequential distribution step 5 of 5 (most prominent), single-hue luminance ramp. Fill only; non-text 3:1 on chart surfaces (CP-115~CP-117)."
1289
+ })
1290
+ ];
985
1291
  var darkPalette = [
986
1292
  ...surface,
987
1293
  ...text,
@@ -992,7 +1298,8 @@ var darkPalette = [
992
1298
  ...severity,
993
1299
  ...fontFamily,
994
1300
  ...elevation,
995
- ...interaction
1301
+ ...interaction,
1302
+ ...dataviz
996
1303
  ];
997
1304
 
998
1305
  // src/palette.light.ts
@@ -1006,6 +1313,8 @@ var values = {
1006
1313
  "surface.glass": "rgba(251, 252, 253, 0.86)",
1007
1314
  "surface.timeline": "#f1f4f8",
1008
1315
  "surface.track": "rgba(12, 18, 28, 0.08)",
1316
+ "surface.tint.1": "rgba(79, 91, 213, 0.1)",
1317
+ "surface.tint.2": "rgba(4, 120, 87, 0.05)",
1009
1318
  "text.primary": "#0c121c",
1010
1319
  "text.secondary": "#33415a",
1011
1320
  "text.muted": "#4d5a6e",
@@ -1036,12 +1345,58 @@ var values = {
1036
1345
  "state.hover": "rgba(12, 18, 28, 0.05)",
1037
1346
  "state.selected": "rgba(79, 91, 213, 0.12)",
1038
1347
  "state.disabled": "#e2e8f0",
1039
- "state.disabledPolicy": "#fef3c7"
1348
+ "state.disabledPolicy": "#fef3c7",
1349
+ // dataviz — chart series colours, darkened for light surfaces (CR-036).
1350
+ "dataviz.series.1": "#d45946",
1351
+ "dataviz.series.2": "#228f7d",
1352
+ "dataviz.series.3": "#d54e8d",
1353
+ "dataviz.series.4": "#22933d",
1354
+ "dataviz.series.5": "#ca45d3",
1355
+ "dataviz.series.6": "#4a9022",
1356
+ "dataviz.series.7": "#906adc",
1357
+ "dataviz.series.8": "#81851f",
1358
+ "dataviz.series.9": "#5a7cd8",
1359
+ "dataviz.series.10": "#bd6b2c",
1360
+ "dataviz.series.11": "#258c9c",
1361
+ "dataviz.series.12": "#d75369",
1362
+ "dataviz.series.13": "#22915d",
1363
+ "dataviz.series.14": "#d446b2",
1364
+ "dataviz.series.15": "#2a9322",
1365
+ "dataviz.series.16": "#ab5dd9",
1366
+ "dataviz.series.17": "#688b21",
1367
+ "dataviz.series.18": "#7773de",
1368
+ "dataviz.series.19": "#9b7c24",
1369
+ "dataviz.series.20": "#2f85c7",
1370
+ "dataviz.sequential.1": "#7d74dc",
1371
+ "dataviz.sequential.2": "#6e65d8",
1372
+ "dataviz.sequential.3": "#5c52d4",
1373
+ "dataviz.sequential.4": "#4539cd",
1374
+ "dataviz.sequential.5": "#3329a4"
1040
1375
  };
1041
- var lightPalette = darkPalette.map((token) => {
1042
- const value2 = values[token.key];
1043
- return value2 === void 0 ? token : { ...token, value: value2, alias: void 0 };
1044
- });
1376
+ var lightAdditions = [];
1377
+ function derivePalette(base, overrides, additions = []) {
1378
+ const baseKeys = new Set(base.map((token) => token.key));
1379
+ const unmatched = Object.keys(overrides).filter((key) => !baseKeys.has(key));
1380
+ if (unmatched.length > 0) {
1381
+ throw new TokenBuildError("TOK-THEME-KEY", "theme override names a key the base palette does not declare", [
1382
+ ...unmatched.map((key) => `override: ${key}`),
1383
+ "hint: the override would be dropped and the token would keep its dark value, which the key-set contract check cannot see. Fix the spelling, or declare the key in the base palette."
1384
+ ]);
1385
+ }
1386
+ const notExempt = additions.filter((token) => token.themeSpecific !== true);
1387
+ if (notExempt.length > 0) {
1388
+ throw new TokenBuildError("TOK-THEME-KEY", "theme-only token is not marked `themeSpecific`", [
1389
+ ...notExempt.map((token) => `addition: ${token.key}`),
1390
+ "hint: FR-QA-001 exempts a key from the cross-theme contract only when the token declares `themeSpecific: true`, so the exemption stays visible in the report."
1391
+ ]);
1392
+ }
1393
+ const derived = base.map((token) => {
1394
+ const value2 = overrides[token.key];
1395
+ return value2 === void 0 ? token : { ...token, value: value2, alias: void 0 };
1396
+ });
1397
+ return [...derived, ...additions];
1398
+ }
1399
+ var lightPalette = derivePalette(darkPalette, values, lightAdditions);
1045
1400
 
1046
1401
  // src/primitives.ts
1047
1402
  function primitive(key, value2, description) {
@@ -1085,9 +1440,17 @@ var indigo = [
1085
1440
  primitive("indigo.800", "#3f4ac0", "Light `accent.strong`.")
1086
1441
  ];
1087
1442
  var slate = [
1088
- primitive("slate.400", "#94a3b8", "Base colour of all four dark border tokens and the scrollbar."),
1443
+ primitive(
1444
+ "slate.400",
1445
+ "#94a3b8",
1446
+ "Base colour of all four dark border tokens and the scrollbar; dark `status.neutralEnd` (CR-035)."
1447
+ ),
1089
1448
  primitive("slate.500", "#64748b", "Dark `status.queued`; light `border.control`."),
1090
- primitive("slate.600", "#475569", "Dark `status.neutralEnd`."),
1449
+ primitive(
1450
+ "slate.600",
1451
+ "#475569",
1452
+ "The source's `--status-neutral-end`. No consumer since CR-035 raised the dark end-state to `slate.400`; kept as the record of the source value that decision replaced."
1453
+ ),
1091
1454
  primitive("slate.700", "#3f4b5f", "Light `status.neutralEnd`."),
1092
1455
  primitive("slate.750", "#52607a", "Light `status.queued`.")
1093
1456
  ];
@@ -1131,6 +1494,9 @@ var nonColour = [
1131
1494
  primitive("scale.32", "32px", "Spacing step."),
1132
1495
  primitive("scale.40", "40px", "Spacing step."),
1133
1496
  primitive("scale.48", "48px", "Spacing step."),
1497
+ primitive("stroke.1", "1px", "Hairline width."),
1498
+ primitive("stroke.2", "2px", "Emphasis rule width."),
1499
+ primitive("stroke.3", "3px", "Status rail and active indicator width."),
1134
1500
  primitive("curve.6", "6px", "Corner radius step."),
1135
1501
  primitive("curve.9", "9px", "Corner radius step."),
1136
1502
  primitive("curve.12", "12px", "Corner radius step."),
@@ -1391,22 +1757,71 @@ function measure(pair2, theme, index, values2) {
1391
1757
  pass: ratio >= threshold
1392
1758
  };
1393
1759
  }
1760
+ function aliasTerminus(index, reference) {
1761
+ let key = resolveReferenceTarget(index, reference);
1762
+ const seen = /* @__PURE__ */ new Set();
1763
+ while (key !== void 0 && !seen.has(key)) {
1764
+ seen.add(key);
1765
+ const alias = index.get(key)?.alias;
1766
+ if (alias === void 0) return key;
1767
+ key = resolveReferenceTarget(index, alias);
1768
+ }
1769
+ return key;
1770
+ }
1771
+ function assertNotForbidden(pair2, index, forbidden) {
1772
+ const resolvedForeground = aliasTerminus(index, pair2.foreground);
1773
+ const resolvedBackground = aliasTerminus(index, pair2.background);
1774
+ for (const banned of forbidden) {
1775
+ const bannedForeground = aliasTerminus(index, banned.foreground);
1776
+ const bannedBackground = aliasTerminus(index, banned.background);
1777
+ if (resolvedForeground !== bannedForeground || resolvedBackground !== bannedBackground) continue;
1778
+ if (banned.usages !== void 0 && !banned.usages.includes(pair2.usage)) continue;
1779
+ throw new TokenBuildError("TOK-CP-FORBIDDEN", "contrast pair declares a forbidden combination", [
1780
+ `pair: ${pair2.id} (${pair2.foreground} on ${pair2.background})`,
1781
+ `forbidden: ${banned.id} (${banned.foreground} on ${banned.background})`,
1782
+ `reason: ${banned.reason}`,
1783
+ "hint: the pair resolves to a combination that must not render. Lifting the ban needs a CR."
1784
+ ]);
1785
+ }
1786
+ }
1787
+ function measureForbidden(forbidden, theme, index, values2) {
1788
+ return forbidden.map((banned) => {
1789
+ const asPair = { ...banned, usage: "nonText", themes: [theme] };
1790
+ const foreground = lookup(index, asPair, "foreground");
1791
+ const background = lookup(index, asPair, "background");
1792
+ const backgroundColor = colorOf(asPair, background, values2.get(background.key), "background");
1793
+ const foregroundColor = colorOf(asPair, foreground, values2.get(foreground.key), "foreground");
1794
+ const ratio = contrastRatio(compositeOver(foregroundColor, backgroundColor), backgroundColor);
1795
+ return {
1796
+ id: banned.id,
1797
+ theme,
1798
+ foreground: banned.foreground,
1799
+ background: banned.background,
1800
+ ratio: Number(ratio.toFixed(4)),
1801
+ reason: banned.reason
1802
+ };
1803
+ });
1804
+ }
1394
1805
  function exclusionsFor(tokens) {
1395
1806
  return tokens.filter((token) => token.tier !== "primitive" && token.usage === "decorative").map((token) => ({ key: token.key, usage: "decorative", reason: token.description }));
1396
1807
  }
1397
1808
  function checkContrast(options = {}) {
1398
1809
  const themes = options.themes ?? THEME_SOURCES;
1399
1810
  const pairs = options.pairs ?? contrastPairs;
1811
+ const forbidden = options.forbidden ?? forbiddenPairs;
1400
1812
  assertUniqueIds(pairs);
1401
1813
  const results = [];
1402
1814
  const exclusions = /* @__PURE__ */ new Map();
1815
+ const forbiddenResults = [];
1403
1816
  for (const theme of themes) {
1404
1817
  const tokens = tokensForTheme(theme);
1405
1818
  const index = buildTokenIndex(tokens);
1406
1819
  const { values: values2 } = resolveTokens(index);
1407
1820
  for (const exclusion of exclusionsFor(tokens)) exclusions.set(exclusion.key, exclusion);
1821
+ forbiddenResults.push(...measureForbidden(forbidden, theme.theme, index, values2));
1408
1822
  for (const pair2 of pairs) {
1409
1823
  if (!pair2.themes.includes(theme.theme)) continue;
1824
+ assertNotForbidden(pair2, index, forbidden);
1410
1825
  results.push(measure(pair2, theme.theme, index, values2));
1411
1826
  }
1412
1827
  }
@@ -1416,12 +1831,14 @@ function checkContrast(options = {}) {
1416
1831
  thresholds: CONTRAST_THRESHOLDS,
1417
1832
  results,
1418
1833
  exclusions: [...exclusions.values()],
1834
+ forbidden: forbiddenResults,
1419
1835
  summary: {
1420
1836
  declaredPairs: pairs.length,
1421
1837
  checks: results.length,
1422
1838
  passed: results.length - failed,
1423
1839
  failed,
1424
- excludedTokens: exclusions.size
1840
+ excludedTokens: exclusions.size,
1841
+ forbiddenPairs: forbidden.length
1425
1842
  }
1426
1843
  };
1427
1844
  }
@@ -1455,6 +1872,11 @@ function formatExclusions(report) {
1455
1872
  lines.push(` ${exclusion.key}`);
1456
1873
  lines.push(` reason: ${exclusion.reason}`);
1457
1874
  }
1875
+ lines.push(`[contrast] ${report.summary.forbiddenPairs} combination(s) declared forbidden`);
1876
+ for (const banned of report.forbidden) {
1877
+ lines.push(` ${banned.id} ${banned.foreground} on ${banned.background} (theme=${banned.theme}, ${formatRatio(banned.ratio)}:1)`);
1878
+ lines.push(` reason: ${banned.reason}`);
1879
+ }
1458
1880
  lines.push("[contrast] --report: no files written");
1459
1881
  return lines;
1460
1882
  }
@@ -1466,7 +1888,8 @@ function renderReportJson(report) {
1466
1888
  thresholds: report.thresholds,
1467
1889
  summary: report.summary,
1468
1890
  results: report.results,
1469
- exclusions: report.exclusions
1891
+ exclusions: report.exclusions,
1892
+ forbidden: report.forbidden
1470
1893
  },
1471
1894
  null,
1472
1895
  2