@pure-ds/core 0.6.10 → 0.6.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.
Files changed (75) hide show
  1. package/custom-elements.json +803 -16
  2. package/dist/types/pds.d.ts +1 -0
  3. package/dist/types/public/assets/js/pds-manager.d.ts +98 -1
  4. package/dist/types/public/assets/js/pds-manager.d.ts.map +1 -1
  5. package/dist/types/public/assets/js/pds.d.ts.map +1 -1
  6. package/dist/types/public/assets/pds/components/pds-live-converter.d.ts +8 -0
  7. package/dist/types/public/assets/pds/components/pds-live-converter.d.ts.map +1 -0
  8. package/dist/types/public/assets/pds/components/pds-live-importer.d.ts +2 -0
  9. package/dist/types/public/assets/pds/components/pds-live-importer.d.ts.map +1 -0
  10. package/dist/types/public/assets/pds/components/pds-live-template-canvas.d.ts +2 -0
  11. package/dist/types/public/assets/pds/components/pds-live-template-canvas.d.ts.map +1 -0
  12. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts.map +1 -1
  13. package/dist/types/public/assets/pds/components/pds-scrollrow.d.ts +20 -0
  14. package/dist/types/public/assets/pds/components/pds-scrollrow.d.ts.map +1 -1
  15. package/dist/types/public/assets/pds/components/pds-toaster.d.ts +1 -1
  16. package/dist/types/public/assets/pds/components/pds-toaster.d.ts.map +1 -1
  17. package/dist/types/public/assets/pds/components/pds-treeview.d.ts +37 -0
  18. package/dist/types/public/assets/pds/components/pds-treeview.d.ts.map +1 -0
  19. package/dist/types/src/js/common/toast.d.ts +8 -0
  20. package/dist/types/src/js/common/toast.d.ts.map +1 -1
  21. package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -1
  22. package/dist/types/src/js/pds-core/pds-generator.d.ts.map +1 -1
  23. package/dist/types/src/js/pds-core/pds-live.d.ts +2 -1
  24. package/dist/types/src/js/pds-core/pds-live.d.ts.map +1 -1
  25. package/dist/types/src/js/pds-live-manager/conversion-service.d.ts +66 -0
  26. package/dist/types/src/js/pds-live-manager/conversion-service.d.ts.map +1 -0
  27. package/dist/types/src/js/pds-live-manager/import-contract.d.ts +15 -0
  28. package/dist/types/src/js/pds-live-manager/import-contract.d.ts.map +1 -0
  29. package/dist/types/src/js/pds-live-manager/import-history-service.d.ts +32 -0
  30. package/dist/types/src/js/pds-live-manager/import-history-service.d.ts.map +1 -0
  31. package/dist/types/src/js/pds-live-manager/import-service.d.ts +21 -0
  32. package/dist/types/src/js/pds-live-manager/import-service.d.ts.map +1 -0
  33. package/dist/types/src/js/pds-live-manager/template-service.d.ts +17 -0
  34. package/dist/types/src/js/pds-live-manager/template-service.d.ts.map +1 -0
  35. package/dist/types/src/js/pds-manager.d.ts +4 -0
  36. package/dist/types/src/js/pds.d.ts.map +1 -1
  37. package/package.json +6 -2
  38. package/packages/pds-cli/README.md +51 -0
  39. package/packages/pds-cli/bin/pds-import.js +176 -0
  40. package/packages/pds-cli/bin/pds-static.js +15 -0
  41. package/packages/pds-cli/bin/postinstall.mjs +17 -8
  42. package/public/assets/js/app.js +15 -139
  43. package/public/assets/js/pds-manager.js +358 -255
  44. package/public/assets/js/pds.js +7 -7
  45. package/public/assets/pds/components/pds-live-converter.js +47 -0
  46. package/public/assets/pds/components/pds-live-edit.js +760 -43
  47. package/public/assets/pds/components/pds-live-importer.js +772 -0
  48. package/public/assets/pds/components/pds-live-template-canvas.js +171 -0
  49. package/public/assets/pds/components/pds-omnibox.js +136 -2
  50. package/public/assets/pds/components/pds-scrollrow.js +56 -1
  51. package/public/assets/pds/components/pds-toaster.js +50 -5
  52. package/public/assets/pds/components/pds-treeview.js +972 -0
  53. package/public/assets/pds/custom-elements.json +803 -16
  54. package/public/assets/pds/pds-css-complete.json +7 -2
  55. package/public/assets/pds/templates/commerce-scroll-explorer.html +115 -0
  56. package/public/assets/pds/templates/content-brand-showcase.html +110 -0
  57. package/public/assets/pds/templates/feedback-ops-dashboard.html +91 -0
  58. package/public/assets/pds/templates/release-readiness-radar.html +69 -0
  59. package/public/assets/pds/templates/support-command-center.html +92 -0
  60. package/public/assets/pds/templates/templates.json +53 -0
  61. package/public/assets/pds/templates/workspace-settings-lab.html +131 -0
  62. package/public/assets/pds/vscode-custom-data.json +54 -4
  63. package/readme.md +34 -0
  64. package/src/js/pds-core/pds-config.js +9 -9
  65. package/src/js/pds-core/pds-enhancers.js +146 -0
  66. package/src/js/pds-core/pds-generator.js +170 -29
  67. package/src/js/pds-core/pds-live.js +453 -13
  68. package/src/js/pds-live-manager/conversion-service.js +3136 -0
  69. package/src/js/pds-live-manager/import-contract.js +57 -0
  70. package/src/js/pds-live-manager/import-history-service.js +145 -0
  71. package/src/js/pds-live-manager/import-service.js +255 -0
  72. package/src/js/pds-live-manager/tailwind-conversion-rules.json +383 -0
  73. package/src/js/pds-live-manager/template-service.js +170 -0
  74. package/src/js/pds.d.ts +1 -0
  75. package/src/js/pds.js +35 -0
@@ -689,14 +689,16 @@ export class Generator {
689
689
  const validBase = Number.isFinite(Number(baseBorderWidth))
690
690
  ? Number(baseBorderWidth)
691
691
  : enums.BorderWidths.medium;
692
- const toPx = (value) => `${Math.max(0, Math.round(value * 100) / 100)}px`;
692
+ // Snap to whole CSS pixels to avoid subpixel border widths that can render
693
+ // identically across browsers/DPIs (e.g. 0.5px and 1.5px both appearing as 1px).
694
+ const toRenderablePx = (value) => `${Math.max(1, Math.ceil(value))}px`;
693
695
 
694
696
  // Generate a derived border width scale based on configured base width
695
697
  return {
696
- hairline: toPx(validBase * 0.25),
697
- thin: toPx(validBase * 0.5),
698
- medium: toPx(validBase),
699
- thick: toPx(validBase * 1.5),
698
+ hairline: toRenderablePx(validBase * 0.25),
699
+ thin: toRenderablePx(validBase * 0.5),
700
+ medium: toRenderablePx(validBase),
701
+ thick: toRenderablePx(validBase * 1.5),
700
702
  };
701
703
  }
702
704
 
@@ -1315,7 +1317,7 @@ export class Generator {
1315
1317
  smartLines.push(`\n`);
1316
1318
  }
1317
1319
 
1318
- const semantic = ` --color-text-primary: var(--color-gray-100);\n --color-text-secondary: var(--color-gray-300);\n --color-text-muted: var(--color-gray-400);\n --color-border: var(--color-gray-700);\n --color-input-bg: var(--color-gray-800);\n --color-input-disabled-bg: var(--color-gray-900);\n --color-input-disabled-text: var(--color-gray-600);\n --color-code-bg: var(--color-gray-800);\n`;
1320
+ const semantic = ` --color-text-primary: var(--color-gray-100);\n --color-text-secondary: var(--color-gray-300);\n --color-text-muted: var(--color-gray-600);\n --color-border: var(--color-gray-700);\n --color-input-bg: var(--color-gray-800);\n --color-input-disabled-bg: var(--color-gray-900);\n --color-input-disabled-text: var(--color-gray-600);\n --color-code-bg: var(--color-gray-800);\n`;
1319
1321
 
1320
1322
  const backdrop = ` /* Backdrop tokens - dark mode */\n --backdrop-bg: linear-gradient(\n 135deg,\n rgba(0, 0, 0, 0.6),\n rgba(0, 0, 0, 0.4)\n );\n --backdrop-blur: 10px;\n --backdrop-saturate: 120%;\n --backdrop-brightness: 0.7;\n --backdrop-filter: blur(var(--backdrop-blur)) saturate(var(--backdrop-saturate)) brightness(var(--backdrop-brightness));\n --backdrop-opacity: 1;\n \n /* Legacy alias for backwards compatibility */\n --backdrop-background: var(--backdrop-bg);\n`;
1321
1323
 
@@ -1413,7 +1415,7 @@ export class Generator {
1413
1415
  const semantic = [
1414
1416
  ` --color-text-primary: var(--color-gray-100);\n`,
1415
1417
  ` --color-text-secondary: var(--color-gray-300);\n`,
1416
- ` --color-text-muted: var(--color-gray-400);\n`,
1418
+ ` --color-text-muted: var(--color-gray-600);\n`,
1417
1419
  ` --color-border: var(--color-gray-700);\n`,
1418
1420
  ` --color-input-bg: var(--color-gray-800);\n`,
1419
1421
  ` --color-input-disabled-bg: var(--color-gray-900);\n`,
@@ -1641,7 +1643,7 @@ html[data-theme="dark"] .liquid-glass {
1641
1643
  .border-gradient {
1642
1644
  border: var(--border-width-medium) solid transparent;
1643
1645
  background:
1644
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1646
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1645
1647
  linear-gradient(var(--gradient-angle, 135deg),
1646
1648
  var(--color-primary-400),
1647
1649
  var(--color-accent-400)
@@ -1652,7 +1654,7 @@ html[data-theme="dark"] .liquid-glass {
1652
1654
  .border-gradient-primary {
1653
1655
  border: var(--border-width-medium) solid transparent;
1654
1656
  background:
1655
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1657
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1656
1658
  linear-gradient(var(--gradient-angle, 135deg),
1657
1659
  var(--color-primary-300),
1658
1660
  var(--color-primary-600)
@@ -1662,7 +1664,7 @@ html[data-theme="dark"] .liquid-glass {
1662
1664
  .border-gradient-accent {
1663
1665
  border: var(--border-width-medium) solid transparent;
1664
1666
  background:
1665
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1667
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1666
1668
  linear-gradient(var(--gradient-angle, 135deg),
1667
1669
  var(--color-accent-300),
1668
1670
  var(--color-accent-600)
@@ -1672,7 +1674,7 @@ html[data-theme="dark"] .liquid-glass {
1672
1674
  .border-gradient-secondary {
1673
1675
  border: var(--border-width-medium) solid transparent;
1674
1676
  background:
1675
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1677
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1676
1678
  linear-gradient(var(--gradient-angle, 135deg),
1677
1679
  var(--color-secondary-300),
1678
1680
  var(--color-secondary-600)
@@ -1683,7 +1685,7 @@ html[data-theme="dark"] .liquid-glass {
1683
1685
  .border-gradient-soft {
1684
1686
  border: var(--border-width-thin) solid transparent;
1685
1687
  background:
1686
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1688
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1687
1689
  linear-gradient(var(--gradient-angle, 135deg),
1688
1690
  var(--color-primary-400),
1689
1691
  var(--color-accent-400)
@@ -1693,7 +1695,7 @@ html[data-theme="dark"] .liquid-glass {
1693
1695
  .border-gradient-medium {
1694
1696
  border: var(--border-width-medium) solid transparent;
1695
1697
  background:
1696
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1698
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1697
1699
  linear-gradient(var(--gradient-angle, 135deg),
1698
1700
  var(--color-primary-400),
1699
1701
  var(--color-accent-400)
@@ -1703,7 +1705,7 @@ html[data-theme="dark"] .liquid-glass {
1703
1705
  .border-gradient-strong {
1704
1706
  border: var(--border-width-thick) solid transparent;
1705
1707
  background:
1706
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1708
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1707
1709
  linear-gradient(var(--gradient-angle, 135deg),
1708
1710
  var(--color-primary-400),
1709
1711
  var(--color-accent-400)
@@ -1727,7 +1729,7 @@ html[data-theme="dark"] .liquid-glass {
1727
1729
  .border-gradient-glow {
1728
1730
  border: var(--border-width-medium) solid transparent;
1729
1731
  background:
1730
- linear-gradient(var(--color-surface-base), var(--color-surface-base)) padding-box,
1732
+ linear-gradient(var(--border-gradient-fill, var(--color-surface-base)), var(--border-gradient-fill, var(--color-surface-base))) padding-box,
1731
1733
  linear-gradient(135deg,
1732
1734
  var(--color-primary-400),
1733
1735
  var(--color-accent-400)
@@ -2007,7 +2009,7 @@ html[data-theme="dark"] .liquid-glass {
2007
2009
  borderWidthThin || shapeBorderWidth || enums.BorderWidths.thin;
2008
2010
  const sectionSpacingValue =
2009
2011
  spatialRhythm.sectionSpacing ?? sectionSpacing ?? 2.0;
2010
- const minButtonHeight = buttonMinHeight || 44;
2012
+ const minButtonHeight = buttonMinHeight || 30;
2011
2013
  const minInputHeight = inputMinHeight || 40;
2012
2014
 
2013
2015
  return /*css*/ `/* Mobile-First Form Styles - Generated from Design Config */
@@ -3280,12 +3282,15 @@ tbody {
3280
3282
  overflow: hidden;
3281
3283
 
3282
3284
  &[open] {
3285
+ overflow: visible;
3286
+
3283
3287
  & > summary::after {
3284
3288
  transform: rotate(45deg);
3285
3289
  }
3286
3290
 
3287
3291
  &::details-content {
3288
3292
  block-size: auto;
3293
+ overflow: visible;
3289
3294
  }
3290
3295
  }
3291
3296
 
@@ -3359,6 +3364,7 @@ tbody {
3359
3364
 
3360
3365
  &[open] > :not(summary) {
3361
3366
  grid-template-rows: 1fr;
3367
+ overflow: visible;
3362
3368
  }
3363
3369
  }
3364
3370
  }
@@ -3811,9 +3817,8 @@ pds-tabstrip {
3811
3817
  }
3812
3818
 
3813
3819
  #generateIconStyles() {
3814
- const { a11y = {} } = this.options.design;
3815
- const minTouchTarget =
3816
- a11y.minTouchTarget || enums.TouchTargetSizes.standard;
3820
+ const { layout = {} } = this.options.design;
3821
+ const iconOnlySize = layout.buttonMinHeight || 30;
3817
3822
 
3818
3823
  return /*css*/ `/* Icon System */
3819
3824
 
@@ -3860,13 +3865,31 @@ button, a {
3860
3865
 
3861
3866
  &.icon-only {
3862
3867
  padding: var(--spacing-2);
3863
- min-width: ${minTouchTarget}px;
3864
- width: ${minTouchTarget}px;
3865
- height: ${minTouchTarget}px;
3868
+ min-width: ${iconOnlySize}px;
3869
+ width: ${iconOnlySize}px;
3870
+ height: ${iconOnlySize}px;
3866
3871
  display: inline-flex;
3867
3872
  align-items: center;
3868
3873
  justify-content: center;
3869
3874
  }
3875
+
3876
+ &.btn-sm.icon-only {
3877
+ min-width: calc(${iconOnlySize}px * 0.8);
3878
+ width: calc(${iconOnlySize}px * 0.8);
3879
+ height: calc(${iconOnlySize}px * 0.8);
3880
+ }
3881
+
3882
+ &.btn-xs.icon-only {
3883
+ min-width: calc(${iconOnlySize}px * 0.6);
3884
+ width: calc(${iconOnlySize}px * 0.6);
3885
+ height: calc(${iconOnlySize}px * 0.6);
3886
+ }
3887
+
3888
+ &.btn-lg.icon-only {
3889
+ min-width: calc(${iconOnlySize}px * 1.2);
3890
+ width: calc(${iconOnlySize}px * 1.2);
3891
+ height: calc(${iconOnlySize}px * 1.2);
3892
+ }
3870
3893
  }
3871
3894
 
3872
3895
  /* Icon in inputs */
@@ -3917,17 +3940,16 @@ nav[data-dropdown] {
3917
3940
  max-width: none;
3918
3941
  max-inline-size: none;
3919
3942
  opacity: 0;
3920
- scale: 0.95;
3921
3943
  visibility: hidden;
3922
3944
  display: none;
3923
3945
  pointer-events: none;
3924
3946
  transform-origin: top center;
3925
3947
  z-index: var(--z-dropdown, 1050);
3926
3948
  max-height: min(60vh, 24rem);
3949
+ overflow-x: hidden;
3927
3950
  overflow-y: auto;
3928
3951
  transition:
3929
3952
  opacity var(--dropdown-transition-duration) ease,
3930
- scale var(--dropdown-transition-duration) ease,
3931
3953
  visibility 0s linear var(--dropdown-transition-duration),
3932
3954
  display 0s linear var(--dropdown-transition-duration);
3933
3955
  transition-behavior: allow-discrete;
@@ -3936,12 +3958,10 @@ nav[data-dropdown] {
3936
3958
  & > :last-child[aria-hidden="false"] {
3937
3959
  display: inline-block;
3938
3960
  opacity: 1;
3939
- scale: 1;
3940
3961
  visibility: visible;
3941
3962
  pointer-events: auto;
3942
3963
  transition:
3943
3964
  opacity var(--dropdown-transition-duration) ease,
3944
- scale var(--dropdown-transition-duration) ease,
3945
3965
  visibility 0s linear 0s,
3946
3966
  display 0s linear 0s;
3947
3967
  }
@@ -4039,7 +4059,6 @@ nav[data-dropdown] {
4039
4059
  @starting-style {
4040
4060
  nav[data-dropdown] > :last-child[aria-hidden="false"] {
4041
4061
  opacity: 0;
4042
- scale: 0.95;
4043
4062
  }
4044
4063
  }
4045
4064
  `;
@@ -4268,7 +4287,7 @@ nav[data-dropdown] {
4268
4287
  /* Touch device optimizations */
4269
4288
  @media (hover: none) and (pointer: coarse) {
4270
4289
  /* Touch devices - larger touch targets for interactive elements */
4271
- button, a, select, textarea,
4290
+ button:not(.icon-only), a:not(.icon-only), select, textarea,
4272
4291
  input:not([type="radio"]):not([type="checkbox"]) {
4273
4292
  min-height: ${minTouchTarget}px;
4274
4293
  min-width: ${minTouchTarget}px;
@@ -4955,39 +4974,48 @@ ${this.#generateBorderGradientUtilities()}
4955
4974
 
4956
4975
  .surface {
4957
4976
  background-color: var(--color-surface-base);
4977
+ --border-gradient-fill: var(--color-surface-base);
4958
4978
  }
4959
4979
 
4960
4980
  .surface-subtle {
4961
4981
  background-color: var(--color-surface-subtle);
4982
+ --border-gradient-fill: var(--color-surface-subtle);
4962
4983
  }
4963
4984
 
4964
4985
  .surface-elevated {
4965
4986
  background-color: var(--color-surface-elevated);
4987
+ --border-gradient-fill: var(--color-surface-elevated);
4966
4988
  }
4967
4989
 
4968
4990
  .surface-sunken {
4969
4991
  background-color: var(--color-surface-sunken);
4992
+ --border-gradient-fill: var(--color-surface-sunken);
4970
4993
  }
4971
4994
 
4972
4995
  .surface-overlay {
4973
4996
  background-color: var(--color-surface-overlay);
4997
+ --border-gradient-fill: var(--color-surface-overlay);
4974
4998
  }
4975
4999
 
4976
5000
  /* Translucent semantic variants */
4977
5001
  .surface-translucent {
4978
5002
  background-color: var(--color-surface-translucent-50);
5003
+ --border-gradient-fill: var(--color-surface-translucent-50);
4979
5004
  }
4980
5005
 
4981
5006
  .surface-translucent-25 {
4982
5007
  background-color: var(--color-surface-translucent-25);
5008
+ --border-gradient-fill: var(--color-surface-translucent-25);
4983
5009
  }
4984
5010
 
4985
5011
  .surface-translucent-50 {
4986
5012
  background-color: var(--color-surface-translucent-50);
5013
+ --border-gradient-fill: var(--color-surface-translucent-50);
4987
5014
  }
4988
5015
 
4989
5016
  .surface-translucent-75 {
4990
5017
  background-color: var(--color-surface-translucent-75);
5018
+ --border-gradient-fill: var(--color-surface-translucent-75);
4991
5019
  }
4992
5020
 
4993
5021
  /* Legacy utility retained for backwards compatibility (opinionated overlay) */
@@ -5013,6 +5041,7 @@ ${this.#generateBorderGradientUtilities()}
5013
5041
  /* Surface-inverse visual properties (shared, uses smart surface tokens) */
5014
5042
  .surface-inverse {
5015
5043
  background-color: var(--color-surface-inverse);
5044
+ --border-gradient-fill: var(--color-surface-inverse);
5016
5045
  color: var(--surface-inverse-text);
5017
5046
 
5018
5047
  pds-icon {
@@ -5036,7 +5065,7 @@ ${this.#generateBorderGradientUtilities()}
5036
5065
  html:not([data-theme="dark"]) .surface-inverse {
5037
5066
  --color-text-primary: var(--color-gray-100);
5038
5067
  --color-text-secondary: var(--color-gray-300);
5039
- --color-text-muted: var(--color-gray-400);
5068
+ --color-text-muted: var(--color-gray-600);
5040
5069
  --color-border: var(--color-gray-700);
5041
5070
  --color-input-bg: var(--color-gray-800);
5042
5071
  --color-input-disabled-bg: var(--color-gray-900);
@@ -5486,6 +5515,8 @@ export const ${name}CSS = \`${escapedCSS}\`;
5486
5515
  */
5487
5516
  export function validateDesign(designConfig = {}, options = {}) {
5488
5517
  const MIN = Number(options.minContrast || 4.5);
5518
+ const MIN_MUTED = Number(options.minMutedContrast || 3.0);
5519
+ const EXTENDED = Boolean(options.extendedChecks);
5489
5520
 
5490
5521
  // Local helpers (keep public; no dependency on private Generator methods)
5491
5522
  const hexToRgb = (hex) => {
@@ -5526,10 +5557,20 @@ export function validateDesign(designConfig = {}, options = {}) {
5526
5557
  const light = {
5527
5558
  surfaceBg: c.surface?.base,
5528
5559
  surfaceText: c.gray?.[900] || "#000000",
5560
+ surfaceTextSecondary: c.gray?.[700] || c.gray?.[800] || c.gray?.[900],
5561
+ surfaceTextMuted: c.gray?.[500] || c.gray?.[600] || c.gray?.[700],
5562
+ surfaceElevated: c.surface?.elevated || c.surface?.base,
5529
5563
  primaryFill: c.interactive?.light?.fill || c.primary?.[600],
5530
5564
  primaryText: c.interactive?.light?.text || c.primary?.[600],
5565
+ accentFill: c.accent?.[600] || c.accent?.[500],
5566
+ successFill: c.success?.[600] || c.success?.[500],
5567
+ warningFill: c.warning?.[600] || c.warning?.[500],
5568
+ dangerFill: c.danger?.[600] || c.danger?.[500],
5569
+ infoFill: c.info?.[600] || c.info?.[500],
5531
5570
  };
5532
5571
 
5572
+ const bestTextContrast = (bg) => Math.max(contrast(bg, "#ffffff"), contrast(bg, "#000000"));
5573
+
5533
5574
  // Primary button (light): check button fill with white text
5534
5575
  const lightBtnRatio = contrast(light.primaryFill, "#ffffff");
5535
5576
  if (lightBtnRatio < MIN) {
@@ -5558,6 +5599,50 @@ export function validateDesign(designConfig = {}, options = {}) {
5558
5599
  });
5559
5600
  }
5560
5601
 
5602
+ if (EXTENDED) {
5603
+ // Secondary body text (light)
5604
+ const lightSecondaryRatio = contrast(light.surfaceBg, light.surfaceTextSecondary);
5605
+ if (lightSecondaryRatio < MIN) {
5606
+ issues.push({
5607
+ path: "/colors/secondary",
5608
+ message: `Secondary text contrast on base surface (light) is too low (${lightSecondaryRatio.toFixed(
5609
+ 2,
5610
+ )} < ${MIN}).`,
5611
+ ratio: lightSecondaryRatio,
5612
+ min: MIN,
5613
+ context: "light/surface-text-secondary",
5614
+ });
5615
+ }
5616
+
5617
+ // Muted text should still be readable for helper text
5618
+ const lightMutedRatio = contrast(light.surfaceBg, light.surfaceTextMuted);
5619
+ if (lightMutedRatio < MIN_MUTED) {
5620
+ issues.push({
5621
+ path: "/colors/secondary",
5622
+ message: `Muted text contrast on base surface (light) is too low (${lightMutedRatio.toFixed(
5623
+ 2,
5624
+ )} < ${MIN_MUTED}).`,
5625
+ ratio: lightMutedRatio,
5626
+ min: MIN_MUTED,
5627
+ context: "light/surface-text-muted",
5628
+ });
5629
+ }
5630
+
5631
+ // Elevated cards often dominate page UI; enforce readable default text
5632
+ const elevatedTextRatio = contrast(light.surfaceElevated, light.surfaceText);
5633
+ if (elevatedTextRatio < MIN) {
5634
+ issues.push({
5635
+ path: "/colors/background",
5636
+ message: `Elevated surface text contrast (light) is too low (${elevatedTextRatio.toFixed(
5637
+ 2,
5638
+ )} < ${MIN}).`,
5639
+ ratio: elevatedTextRatio,
5640
+ min: MIN,
5641
+ context: "light/surface-elevated-text",
5642
+ });
5643
+ }
5644
+ }
5645
+
5561
5646
  // Primary text for outline/link: check link text on surface
5562
5647
  const lightOutlineRatio = contrast(light.primaryText, light.surfaceBg);
5563
5648
  if (lightOutlineRatio < MIN) {
@@ -5572,11 +5657,39 @@ export function validateDesign(designConfig = {}, options = {}) {
5572
5657
  });
5573
5658
  }
5574
5659
 
5660
+ if (EXTENDED) {
5661
+ // Semantic/accent fills must support readable foreground (white or black)
5662
+ const semanticFills = [
5663
+ { path: "/colors/accent", key: "accent", value: light.accentFill },
5664
+ { path: "/colors/success", key: "success", value: light.successFill },
5665
+ { path: "/colors/warning", key: "warning", value: light.warningFill },
5666
+ { path: "/colors/danger", key: "danger", value: light.dangerFill },
5667
+ { path: "/colors/info", key: "info", value: light.infoFill },
5668
+ ];
5669
+ semanticFills.forEach((entry) => {
5670
+ if (!entry?.value) return;
5671
+ const ratio = bestTextContrast(entry.value);
5672
+ if (ratio < MIN) {
5673
+ issues.push({
5674
+ path: entry.path,
5675
+ message: `${entry.key} fill color cannot achieve accessible text contrast (${ratio.toFixed(
5676
+ 2,
5677
+ )} < ${MIN}) with either white or black text.`,
5678
+ ratio,
5679
+ min: MIN,
5680
+ context: `light/${entry.key}-fill`,
5681
+ });
5682
+ }
5683
+ });
5684
+ }
5685
+
5575
5686
  // Dark theme checks - use computed interactive tokens
5576
5687
  const d = c.dark;
5577
5688
  if (d) {
5578
5689
  const dark = {
5579
5690
  surfaceBg: d.surface?.base || c.surface?.inverse,
5691
+ surfaceText: d.gray?.[50] || d.gray?.[100] || "#ffffff",
5692
+ surfaceTextMuted: d.gray?.[300] || d.gray?.[400] || d.gray?.[500],
5580
5693
  primaryFill: c.interactive?.dark?.fill || d.primary?.[600],
5581
5694
  primaryText: c.interactive?.dark?.text || d.primary?.[600],
5582
5695
  };
@@ -5608,6 +5721,34 @@ export function validateDesign(designConfig = {}, options = {}) {
5608
5721
  context: "dark/outline",
5609
5722
  });
5610
5723
  }
5724
+
5725
+ if (EXTENDED) {
5726
+ const darkTextRatio = contrast(dark.surfaceBg, dark.surfaceText);
5727
+ if (darkTextRatio < MIN) {
5728
+ issues.push({
5729
+ path: "/colors/darkMode/background",
5730
+ message: `Base text contrast on surface (dark) is too low (${darkTextRatio.toFixed(
5731
+ 2,
5732
+ )} < ${MIN}).`,
5733
+ ratio: darkTextRatio,
5734
+ min: MIN,
5735
+ context: "dark/surface-text",
5736
+ });
5737
+ }
5738
+
5739
+ const darkMutedRatio = contrast(dark.surfaceBg, dark.surfaceTextMuted);
5740
+ if (darkMutedRatio < MIN_MUTED) {
5741
+ issues.push({
5742
+ path: "/colors/darkMode/secondary",
5743
+ message: `Muted text contrast on surface (dark) is too low (${darkMutedRatio.toFixed(
5744
+ 2,
5745
+ )} < ${MIN_MUTED}).`,
5746
+ ratio: darkMutedRatio,
5747
+ min: MIN_MUTED,
5748
+ context: "dark/surface-text-muted",
5749
+ });
5750
+ }
5751
+ }
5611
5752
  }
5612
5753
  } catch (err) {
5613
5754
  issues.push({