@design-guard/core 0.3.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.
Files changed (133) hide show
  1. package/dist/index.d.ts +20 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +18 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/research/business-researcher.d.ts +42 -0
  6. package/dist/research/business-researcher.d.ts.map +1 -0
  7. package/dist/research/business-researcher.js +888 -0
  8. package/dist/research/business-researcher.js.map +1 -0
  9. package/dist/research/design-synthesizer.d.ts +47 -0
  10. package/dist/research/design-synthesizer.d.ts.map +1 -0
  11. package/dist/research/design-synthesizer.js +628 -0
  12. package/dist/research/design-synthesizer.js.map +1 -0
  13. package/dist/research/research-cache.d.ts +7 -0
  14. package/dist/research/research-cache.d.ts.map +1 -0
  15. package/dist/research/research-cache.js +62 -0
  16. package/dist/research/research-cache.js.map +1 -0
  17. package/dist/research/types.d.ts +99 -0
  18. package/dist/research/types.d.ts.map +1 -0
  19. package/dist/research/types.js +6 -0
  20. package/dist/research/types.js.map +1 -0
  21. package/dist/templates/design-md.d.ts +53 -0
  22. package/dist/templates/design-md.d.ts.map +1 -0
  23. package/dist/templates/design-md.js +315 -0
  24. package/dist/templates/design-md.js.map +1 -0
  25. package/dist/templates/prompts.d.ts +32 -0
  26. package/dist/templates/prompts.d.ts.map +1 -0
  27. package/dist/templates/prompts.js +39 -0
  28. package/dist/templates/prompts.js.map +1 -0
  29. package/dist/tokens/dtcg-converter.d.ts +62 -0
  30. package/dist/tokens/dtcg-converter.d.ts.map +1 -0
  31. package/dist/tokens/dtcg-converter.js +385 -0
  32. package/dist/tokens/dtcg-converter.js.map +1 -0
  33. package/dist/utils/prompt-enhancer.d.ts +22 -0
  34. package/dist/utils/prompt-enhancer.d.ts.map +1 -0
  35. package/dist/utils/prompt-enhancer.js +104 -0
  36. package/dist/utils/prompt-enhancer.js.map +1 -0
  37. package/dist/utils/validators.d.ts +126 -0
  38. package/dist/utils/validators.d.ts.map +1 -0
  39. package/dist/utils/validators.js +110 -0
  40. package/dist/utils/validators.js.map +1 -0
  41. package/dist/validation/design-validator.d.ts +45 -0
  42. package/dist/validation/design-validator.d.ts.map +1 -0
  43. package/dist/validation/design-validator.js +396 -0
  44. package/dist/validation/design-validator.js.map +1 -0
  45. package/dist/validation/output-validator.d.ts +46 -0
  46. package/dist/validation/output-validator.d.ts.map +1 -0
  47. package/dist/validation/output-validator.js +146 -0
  48. package/dist/validation/output-validator.js.map +1 -0
  49. package/dist/validation/rules/alt-text.d.ts +8 -0
  50. package/dist/validation/rules/alt-text.d.ts.map +1 -0
  51. package/dist/validation/rules/alt-text.js +35 -0
  52. package/dist/validation/rules/alt-text.js.map +1 -0
  53. package/dist/validation/rules/business-alignment.d.ts +7 -0
  54. package/dist/validation/rules/business-alignment.d.ts.map +1 -0
  55. package/dist/validation/rules/business-alignment.js +98 -0
  56. package/dist/validation/rules/business-alignment.js.map +1 -0
  57. package/dist/validation/rules/color-adherence.d.ts +8 -0
  58. package/dist/validation/rules/color-adherence.d.ts.map +1 -0
  59. package/dist/validation/rules/color-adherence.js +92 -0
  60. package/dist/validation/rules/color-adherence.js.map +1 -0
  61. package/dist/validation/rules/empty-body.d.ts +6 -0
  62. package/dist/validation/rules/empty-body.d.ts.map +1 -0
  63. package/dist/validation/rules/empty-body.js +24 -0
  64. package/dist/validation/rules/empty-body.js.map +1 -0
  65. package/dist/validation/rules/heading-hierarchy.d.ts +8 -0
  66. package/dist/validation/rules/heading-hierarchy.d.ts.map +1 -0
  67. package/dist/validation/rules/heading-hierarchy.js +48 -0
  68. package/dist/validation/rules/heading-hierarchy.js.map +1 -0
  69. package/dist/validation/rules/index.d.ts +21 -0
  70. package/dist/validation/rules/index.d.ts.map +1 -0
  71. package/dist/validation/rules/index.js +66 -0
  72. package/dist/validation/rules/index.js.map +1 -0
  73. package/dist/validation/rules/no-centered-everything.d.ts +6 -0
  74. package/dist/validation/rules/no-centered-everything.d.ts.map +1 -0
  75. package/dist/validation/rules/no-centered-everything.js +59 -0
  76. package/dist/validation/rules/no-centered-everything.js.map +1 -0
  77. package/dist/validation/rules/no-default-fonts.d.ts +8 -0
  78. package/dist/validation/rules/no-default-fonts.d.ts.map +1 -0
  79. package/dist/validation/rules/no-default-fonts.js +111 -0
  80. package/dist/validation/rules/no-default-fonts.js.map +1 -0
  81. package/dist/validation/rules/no-div-soup.d.ts +6 -0
  82. package/dist/validation/rules/no-div-soup.d.ts.map +1 -0
  83. package/dist/validation/rules/no-div-soup.js +45 -0
  84. package/dist/validation/rules/no-div-soup.js.map +1 -0
  85. package/dist/validation/rules/no-duplicate-ctas.d.ts +6 -0
  86. package/dist/validation/rules/no-duplicate-ctas.d.ts.map +1 -0
  87. package/dist/validation/rules/no-duplicate-ctas.js +32 -0
  88. package/dist/validation/rules/no-duplicate-ctas.js.map +1 -0
  89. package/dist/validation/rules/no-generic-hero.d.ts +7 -0
  90. package/dist/validation/rules/no-generic-hero.d.ts.map +1 -0
  91. package/dist/validation/rules/no-generic-hero.js +46 -0
  92. package/dist/validation/rules/no-generic-hero.js.map +1 -0
  93. package/dist/validation/rules/no-icon-grid.d.ts +8 -0
  94. package/dist/validation/rules/no-icon-grid.d.ts.map +1 -0
  95. package/dist/validation/rules/no-icon-grid.js +43 -0
  96. package/dist/validation/rules/no-icon-grid.js.map +1 -0
  97. package/dist/validation/rules/no-lorem-ipsum.d.ts +6 -0
  98. package/dist/validation/rules/no-lorem-ipsum.d.ts.map +1 -0
  99. package/dist/validation/rules/no-lorem-ipsum.js +66 -0
  100. package/dist/validation/rules/no-lorem-ipsum.js.map +1 -0
  101. package/dist/validation/rules/no-missing-meta.d.ts +6 -0
  102. package/dist/validation/rules/no-missing-meta.d.ts.map +1 -0
  103. package/dist/validation/rules/no-missing-meta.js +49 -0
  104. package/dist/validation/rules/no-missing-meta.js.map +1 -0
  105. package/dist/validation/rules/no-missing-responsive.d.ts +6 -0
  106. package/dist/validation/rules/no-missing-responsive.d.ts.map +1 -0
  107. package/dist/validation/rules/no-missing-responsive.js +30 -0
  108. package/dist/validation/rules/no-missing-responsive.js.map +1 -0
  109. package/dist/validation/rules/no-placeholder-images.d.ts +6 -0
  110. package/dist/validation/rules/no-placeholder-images.d.ts.map +1 -0
  111. package/dist/validation/rules/no-placeholder-images.js +73 -0
  112. package/dist/validation/rules/no-placeholder-images.js.map +1 -0
  113. package/dist/validation/rules/no-saas-speak.d.ts +6 -0
  114. package/dist/validation/rules/no-saas-speak.d.ts.map +1 -0
  115. package/dist/validation/rules/no-saas-speak.js +74 -0
  116. package/dist/validation/rules/no-saas-speak.js.map +1 -0
  117. package/dist/validation/rules/no-slop-gradients.d.ts +6 -0
  118. package/dist/validation/rules/no-slop-gradients.d.ts.map +1 -0
  119. package/dist/validation/rules/no-slop-gradients.js +24 -0
  120. package/dist/validation/rules/no-slop-gradients.js.map +1 -0
  121. package/dist/validation/rules/no-uniform-spacing.d.ts +6 -0
  122. package/dist/validation/rules/no-uniform-spacing.d.ts.map +1 -0
  123. package/dist/validation/rules/no-uniform-spacing.js +64 -0
  124. package/dist/validation/rules/no-uniform-spacing.js.map +1 -0
  125. package/dist/validation/rules/types.d.ts +36 -0
  126. package/dist/validation/rules/types.d.ts.map +1 -0
  127. package/dist/validation/rules/types.js +7 -0
  128. package/dist/validation/rules/types.js.map +1 -0
  129. package/dist/validation/tailwind-parser.d.ts +36 -0
  130. package/dist/validation/tailwind-parser.d.ts.map +1 -0
  131. package/dist/validation/tailwind-parser.js +214 -0
  132. package/dist/validation/tailwind-parser.js.map +1 -0
  133. package/package.json +45 -0
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Checks for missing img alt attributes.
3
+ * Bug Fix (Frente A): detects data-alt without alt (Stitch pattern),
4
+ * separately counts data-alt-only vs fully missing alt.
5
+ */
6
+ export const altText = {
7
+ id: 'alt-text',
8
+ name: 'Alt Text',
9
+ description: 'Checks that all <img> elements have an alt attribute for accessibility.',
10
+ severity: 'error',
11
+ category: 'accessibility',
12
+ check(context) {
13
+ const issues = [];
14
+ const { $ } = context;
15
+ const imgsWithoutAlt = $('img:not([alt])').length;
16
+ const imgsWithDataAltOnly = $('img[data-alt]:not([alt])').length;
17
+ if (imgsWithDataAltOnly > 0) {
18
+ issues.push({
19
+ type: 'error',
20
+ category: 'accessibility',
21
+ message: `${imgsWithDataAltOnly} image(s) have data-alt but no alt attribute. Move data-alt value to alt for accessibility.`,
22
+ });
23
+ }
24
+ const imgsWithoutAnyAlt = imgsWithoutAlt - imgsWithDataAltOnly;
25
+ if (imgsWithoutAnyAlt > 0) {
26
+ issues.push({
27
+ type: 'error',
28
+ category: 'accessibility',
29
+ message: `${imgsWithoutAnyAlt} image(s) missing alt attribute.`,
30
+ });
31
+ }
32
+ return issues;
33
+ },
34
+ };
35
+ //# sourceMappingURL=alt-text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alt-text.js","sourceRoot":"","sources":["../../../src/validation/rules/alt-text.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAa;IAC/B,EAAE,EAAE,UAAU;IACd,IAAI,EAAE,UAAU;IAChB,WAAW,EAAE,yEAAyE;IACtF,QAAQ,EAAE,OAAO;IACjB,QAAQ,EAAE,eAAe;IAEzB,KAAK,CAAC,OAAoB;QACxB,MAAM,MAAM,GAAsB,EAAE,CAAC;QACrC,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC;QAEtB,MAAM,cAAc,GAAG,CAAC,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAAC;QAClD,MAAM,mBAAmB,GAAG,CAAC,CAAC,0BAA0B,CAAC,CAAC,MAAM,CAAC;QAEjE,IAAI,mBAAmB,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,OAAO;gBACb,QAAQ,EAAE,eAAe;gBACzB,OAAO,EAAE,GAAG,mBAAmB,6FAA6F;aAC7H,CAAC,CAAC;QACL,CAAC;QAED,MAAM,iBAAiB,GAAG,cAAc,GAAG,mBAAmB,CAAC;QAC/D,IAAI,iBAAiB,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,OAAO;gBACb,QAAQ,EAAE,eAAe;gBACzB,OAAO,EAAE,GAAG,iBAAiB,kCAAkC;aAChE,CAAC,CAAC;QACL,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { LintRule } from './types.js';
2
+ /**
3
+ * Validates generated HTML against business model context from DESIGN.md.
4
+ * Bug Fix (Frente A): pricing, login, enterprise, SaaS CTAs detection.
5
+ */
6
+ export declare const businessAlignment: LintRule;
7
+ //# sourceMappingURL=business-alignment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"business-alignment.d.ts","sourceRoot":"","sources":["../../../src/validation/rules/business-alignment.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,YAAY,CAAC;AAGxD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAkI/B,CAAC"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Validates generated HTML against business model context from DESIGN.md.
3
+ * Bug Fix (Frente A): pricing, login, enterprise, SaaS CTAs detection.
4
+ */
5
+ export const businessAlignment = {
6
+ id: 'business-alignment',
7
+ name: 'Business Alignment',
8
+ description: 'Validates that generated HTML aligns with the business model described in DESIGN.md.',
9
+ severity: 'error',
10
+ category: 'structure',
11
+ requiresDesign: true,
12
+ check(context) {
13
+ const issues = [];
14
+ const { html, designMdContent } = context;
15
+ if (!designMdContent)
16
+ return issues;
17
+ const designLower = designMdContent.toLowerCase();
18
+ const htmlLower = html.toLowerCase();
19
+ // 7a. If NOT e-commerce, check for cart/checkout in generated HTML
20
+ if (designLower.includes('not an e-commerce') ||
21
+ designLower.includes('not e-commerce') ||
22
+ designLower.includes('no online purchasing')) {
23
+ if (/\b(add.?to.?cart|shopping.?cart|checkout|buy.?now|carrito)\b/i.test(htmlLower)) {
24
+ issues.push({
25
+ type: 'error',
26
+ category: 'structure',
27
+ message: 'Generated HTML contains e-commerce elements (cart/checkout) but DESIGN.md specifies this is NOT an e-commerce site.',
28
+ });
29
+ }
30
+ }
31
+ // 7b. If store locator is key, check for location-related elements
32
+ if (designLower.includes('store locator') ||
33
+ designLower.includes('find nearest store')) {
34
+ const hasLocationElements = /\b(location|store.?finder|find.?store|sucursal|ubicaci|postal|zip.?code|mapa|map)\b/i.test(htmlLower);
35
+ if (!hasLocationElements) {
36
+ issues.push({
37
+ type: 'info',
38
+ category: 'structure',
39
+ message: 'DESIGN.md specifies store locator as key feature but no location/finder elements detected in output.',
40
+ });
41
+ }
42
+ }
43
+ // 7c. If "free" or "open source", flag pricing/subscription elements
44
+ if (designLower.includes('free to use') ||
45
+ designLower.includes('open source') ||
46
+ designLower.includes('open-source') ||
47
+ designLower.includes('mit licensed')) {
48
+ if (/\b(pricing|subscription|per.?month|\/mo|\/year|premium.?plan|upgrade.?to.?pro|paid.?tier)\b/i.test(htmlLower)) {
49
+ issues.push({
50
+ type: 'error',
51
+ category: 'structure',
52
+ message: 'Generated HTML contains pricing/subscription elements but DESIGN.md specifies this is free/open-source software.',
53
+ });
54
+ }
55
+ }
56
+ // 7d. If "no accounts" or "no authentication" or "no signup", flag login/signup forms
57
+ if (designLower.includes('no signup') ||
58
+ designLower.includes('no user accounts') ||
59
+ designLower.includes('no login') ||
60
+ designLower.includes('no authentication') ||
61
+ designLower.includes('not a saas')) {
62
+ if (/\b(log.?in|sign.?up|sign.?in|create.?account|register|forgot.?password|reset.?password)\b/i.test(htmlLower)) {
63
+ issues.push({
64
+ type: 'error',
65
+ category: 'structure',
66
+ message: 'Generated HTML contains login/signup elements but DESIGN.md specifies no user accounts or authentication.',
67
+ });
68
+ }
69
+ }
70
+ // 7e. If B2C/open-source, flag enterprise sales CTAs
71
+ if (designLower.includes('open source') ||
72
+ designLower.includes('open-source') ||
73
+ designLower.includes('b2c') ||
74
+ designLower.includes('community-driven')) {
75
+ if (/\b(contact.?sales|enterprise.?plan|talk.?to.?sales|request.?demo|schedule.?a.?demo|book.?a.?call)\b/i.test(htmlLower)) {
76
+ issues.push({
77
+ type: 'warning',
78
+ category: 'structure',
79
+ message: 'Generated HTML contains enterprise/sales CTAs but DESIGN.md indicates a B2C or open-source project.',
80
+ });
81
+ }
82
+ }
83
+ // 7f. If NOT a SaaS, flag SaaS-specific CTAs
84
+ if (designLower.includes('not a saas') ||
85
+ designLower.includes('not saas') ||
86
+ designLower.includes('open source')) {
87
+ if (/\b(start.?free.?trial|launch.?dashboard|go.?to.?dashboard|my.?account|manage.?subscription)\b/i.test(htmlLower)) {
88
+ issues.push({
89
+ type: 'error',
90
+ category: 'structure',
91
+ message: 'Generated HTML contains SaaS CTAs (trial/dashboard) but DESIGN.md specifies this is NOT a SaaS product.',
92
+ });
93
+ }
94
+ }
95
+ return issues;
96
+ },
97
+ };
98
+ //# sourceMappingURL=business-alignment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"business-alignment.js","sourceRoot":"","sources":["../../../src/validation/rules/business-alignment.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAa;IACzC,EAAE,EAAE,oBAAoB;IACxB,IAAI,EAAE,oBAAoB;IAC1B,WAAW,EAAE,sFAAsF;IACnG,QAAQ,EAAE,OAAO;IACjB,QAAQ,EAAE,WAAW;IACrB,cAAc,EAAE,IAAI;IAEpB,KAAK,CAAC,OAAoB;QACxB,MAAM,MAAM,GAAsB,EAAE,CAAC;QACrC,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC;QAE1C,IAAI,CAAC,eAAe;YAAE,OAAO,MAAM,CAAC;QAEpC,MAAM,WAAW,GAAG,eAAe,CAAC,WAAW,EAAE,CAAC;QAClD,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAErC,mEAAmE;QACnE,IACE,WAAW,CAAC,QAAQ,CAAC,mBAAmB,CAAC;YACzC,WAAW,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YACtC,WAAW,CAAC,QAAQ,CAAC,sBAAsB,CAAC,EAC5C,CAAC;YACD,IACE,+DAA+D,CAAC,IAAI,CAAC,SAAS,CAAC,EAC/E,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,OAAO;oBACb,QAAQ,EAAE,WAAW;oBACrB,OAAO,EACL,qHAAqH;iBACxH,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,mEAAmE;QACnE,IACE,WAAW,CAAC,QAAQ,CAAC,eAAe,CAAC;YACrC,WAAW,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAC1C,CAAC;YACD,MAAM,mBAAmB,GACvB,sFAAsF,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACzG,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBACzB,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,MAAM;oBACZ,QAAQ,EAAE,WAAW;oBACrB,OAAO,EACL,sGAAsG;iBACzG,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,qEAAqE;QACrE,IACE,WAAW,CAAC,QAAQ,CAAC,aAAa,CAAC;YACnC,WAAW,CAAC,QAAQ,CAAC,aAAa,CAAC;YACnC,WAAW,CAAC,QAAQ,CAAC,aAAa,CAAC;YACnC,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,EACpC,CAAC;YACD,IACE,8FAA8F,CAAC,IAAI,CAAC,SAAS,CAAC,EAC9G,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,OAAO;oBACb,QAAQ,EAAE,WAAW;oBACrB,OAAO,EACL,kHAAkH;iBACrH,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,sFAAsF;QACtF,IACE,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC;YACjC,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC;YACxC,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC;YAChC,WAAW,CAAC,QAAQ,CAAC,mBAAmB,CAAC;YACzC,WAAW,CAAC,QAAQ,CAAC,YAAY,CAAC,EAClC,CAAC;YACD,IACE,4FAA4F,CAAC,IAAI,CAAC,SAAS,CAAC,EAC5G,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,OAAO;oBACb,QAAQ,EAAE,WAAW;oBACrB,OAAO,EACL,2GAA2G;iBAC9G,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,qDAAqD;QACrD,IACE,WAAW,CAAC,QAAQ,CAAC,aAAa,CAAC;YACnC,WAAW,CAAC,QAAQ,CAAC,aAAa,CAAC;YACnC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC;YAC3B,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EACxC,CAAC;YACD,IACE,sGAAsG,CAAC,IAAI,CAAC,SAAS,CAAC,EACtH,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,SAAS;oBACf,QAAQ,EAAE,WAAW;oBACrB,OAAO,EACL,qGAAqG;iBACxG,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,6CAA6C;QAC7C,IACE,WAAW,CAAC,QAAQ,CAAC,YAAY,CAAC;YAClC,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC;YAChC,WAAW,CAAC,QAAQ,CAAC,aAAa,CAAC,EACnC,CAAC;YACD,IACE,gGAAgG,CAAC,IAAI,CAAC,SAAS,CAAC,EAChH,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,OAAO;oBACb,QAAQ,EAAE,WAAW;oBACrB,OAAO,EACL,yGAAyG;iBAC5G,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { LintRule } from './types.js';
2
+ /**
3
+ * Checks that colors used in the HTML match the DESIGN.md palette.
4
+ * Bug Fix (Frente A): lower threshold (>1 = error, =1 = warning),
5
+ * Tailwind arbitrary values, rgb() parsing, tighter neutral exemption.
6
+ */
7
+ export declare const colorAdherence: LintRule;
8
+ //# sourceMappingURL=color-adherence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-adherence.d.ts","sourceRoot":"","sources":["../../../src/validation/rules/color-adherence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,YAAY,CAAC;AAGxD;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,QA8C5B,CAAC"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Checks that colors used in the HTML match the DESIGN.md palette.
3
+ * Bug Fix (Frente A): lower threshold (>1 = error, =1 = warning),
4
+ * Tailwind arbitrary values, rgb() parsing, tighter neutral exemption.
5
+ */
6
+ export const colorAdherence = {
7
+ id: 'color-adherence',
8
+ name: 'Color Adherence',
9
+ description: 'Checks that colors used in the generated output match the DESIGN.md palette.',
10
+ severity: 'warning',
11
+ category: 'color',
12
+ requiresDesign: true,
13
+ check(context) {
14
+ const issues = [];
15
+ const { allStyles, allClasses, designMdContent } = context;
16
+ if (!designMdContent)
17
+ return issues;
18
+ const designColors = extractDesignColors(designMdContent);
19
+ if (designColors.length === 0)
20
+ return issues;
21
+ // Extract from CSS styles
22
+ const usedColorsFromStyles = extractUsedColors(allStyles);
23
+ // Extract from Tailwind arbitrary value classes
24
+ const tailwindArbitraryColors = extractTailwindArbitraryColors(allClasses);
25
+ // Extract rgb/rgba from inline styles
26
+ const rgbColors = extractRgbColors(allStyles);
27
+ const allUsedColors = [...new Set([...usedColorsFromStyles, ...tailwindArbitraryColors, ...rgbColors])];
28
+ const unmatchedColors = allUsedColors.filter(c => !designColors.some(dc => dc.toLowerCase() === c.toLowerCase()) &&
29
+ !isNeutralColor(c));
30
+ if (unmatchedColors.length > 1) {
31
+ issues.push({
32
+ type: 'error',
33
+ category: 'color',
34
+ message: `${unmatchedColors.length} colors used that aren't in DESIGN.md palette: ${unmatchedColors.slice(0, 5).join(', ')}. Output deviates from design system.`,
35
+ });
36
+ }
37
+ else if (unmatchedColors.length === 1) {
38
+ issues.push({
39
+ type: 'warning',
40
+ category: 'color',
41
+ message: `1 color used that isn't in DESIGN.md palette: ${unmatchedColors[0]}. Output may deviate from design system.`,
42
+ });
43
+ }
44
+ return issues;
45
+ },
46
+ };
47
+ function extractDesignColors(content) {
48
+ const hexMatches = content.match(/#[0-9A-Fa-f]{6}/g) || [];
49
+ return [...new Set(hexMatches)];
50
+ }
51
+ function extractUsedColors(styles) {
52
+ const hexMatches = styles.match(/#[0-9A-Fa-f]{6}/g) || [];
53
+ return [...new Set(hexMatches)];
54
+ }
55
+ /**
56
+ * Extract hex colors from Tailwind arbitrary value syntax in class attributes.
57
+ * Matches patterns like: text-[#5af9f3], bg-[#aca3ff], border-[#123456]
58
+ */
59
+ function extractTailwindArbitraryColors(classes) {
60
+ const matches = classes.match(/(?:text|bg|border|ring|shadow|outline|accent|fill|stroke|from|to|via)-\[#([0-9A-Fa-f]{6})\]/g) || [];
61
+ return [...new Set(matches.map(m => {
62
+ const hexMatch = m.match(/#[0-9A-Fa-f]{6}/);
63
+ return hexMatch ? hexMatch[0] : '';
64
+ }).filter(Boolean))];
65
+ }
66
+ /**
67
+ * Extract hex colors from rgb() and rgba() in style attributes.
68
+ * Converts rgb(R, G, B) to hex for comparison against DESIGN.md palette.
69
+ */
70
+ function extractRgbColors(styles) {
71
+ const rgbMatches = styles.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})/g) || [];
72
+ return [...new Set(rgbMatches.map(m => {
73
+ const parts = m.match(/(\d{1,3})/g);
74
+ if (!parts || parts.length < 3)
75
+ return '';
76
+ const r = Math.min(255, parseInt(parts[0], 10));
77
+ const g = Math.min(255, parseInt(parts[1], 10));
78
+ const b = Math.min(255, parseInt(parts[2], 10));
79
+ return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
80
+ }).filter(Boolean))];
81
+ }
82
+ function isNeutralColor(hex) {
83
+ const normalized = hex.startsWith('#') ? hex : `#${hex}`;
84
+ if (normalized.length < 7)
85
+ return false;
86
+ const r = parseInt(normalized.slice(1, 3), 16);
87
+ const g = parseInt(normalized.slice(3, 5), 16);
88
+ const b = parseInt(normalized.slice(5, 7), 16);
89
+ const maxDiff = Math.max(Math.abs(r - g), Math.abs(g - b), Math.abs(r - b));
90
+ return maxDiff < 10; // Tighter threshold: nearly grayscale (was 20, now 10)
91
+ }
92
+ //# sourceMappingURL=color-adherence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-adherence.js","sourceRoot":"","sources":["../../../src/validation/rules/color-adherence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { LintRule } from './types.js';
2
+ /**
3
+ * Checks for empty or minimal HTML body.
4
+ */
5
+ export declare const emptyBody: LintRule;
6
+ //# sourceMappingURL=empty-body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-body.d.ts","sourceRoot":"","sources":["../../../src/validation/rules/empty-body.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,YAAY,CAAC;AAGxD;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,QAsBvB,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Checks for empty or minimal HTML body.
3
+ */
4
+ export const emptyBody = {
5
+ id: 'empty-body',
6
+ name: 'Empty Body',
7
+ description: 'Checks that the generated HTML has content in the body element.',
8
+ severity: 'error',
9
+ category: 'structure',
10
+ check(context) {
11
+ const issues = [];
12
+ const { $ } = context;
13
+ const bodyText = $('body').text().trim();
14
+ if (!bodyText && $('body *').length === 0) {
15
+ issues.push({
16
+ type: 'error',
17
+ category: 'structure',
18
+ message: 'Generated HTML is empty — no content in body.',
19
+ });
20
+ }
21
+ return issues;
22
+ },
23
+ };
24
+ //# sourceMappingURL=empty-body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty-body.js","sourceRoot":"","sources":["../../../src/validation/rules/empty-body.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,CAAC,MAAM,SAAS,GAAa;IACjC,EAAE,EAAE,YAAY;IAChB,IAAI,EAAE,YAAY;IAClB,WAAW,EAAE,iEAAiE;IAC9E,QAAQ,EAAE,OAAO;IACjB,QAAQ,EAAE,WAAW;IAErB,KAAK,CAAC,OAAoB;QACxB,MAAM,MAAM,GAAsB,EAAE,CAAC;QACrC,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC;QAEtB,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC;QACzC,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1C,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,OAAO;gBACb,QAAQ,EAAE,WAAW;gBACrB,OAAO,EAAE,+CAA+C;aACzD,CAAC,CAAC;QACL,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { LintRule } from './types.js';
2
+ /**
3
+ * Validates h1 -> h2 -> h3 heading order (no skipping levels).
4
+ * Bug Fix (Frente A): reports ALL skips (not just the first),
5
+ * flags missing h1 and multiple h1 elements.
6
+ */
7
+ export declare const headingHierarchy: LintRule;
8
+ //# sourceMappingURL=heading-hierarchy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heading-hierarchy.d.ts","sourceRoot":"","sources":["../../../src/validation/rules/heading-hierarchy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,YAAY,CAAC;AAGxD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAgD9B,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Validates h1 -> h2 -> h3 heading order (no skipping levels).
3
+ * Bug Fix (Frente A): reports ALL skips (not just the first),
4
+ * flags missing h1 and multiple h1 elements.
5
+ */
6
+ export const headingHierarchy = {
7
+ id: 'heading-hierarchy',
8
+ name: 'Heading Hierarchy',
9
+ description: 'Validates that heading levels are not skipped and that exactly one h1 exists.',
10
+ severity: 'warning',
11
+ category: 'structure',
12
+ check(context) {
13
+ const issues = [];
14
+ const { $ } = context;
15
+ const headings = $('h1, h2, h3, h4, h5, h6')
16
+ .map((_i, el) => parseInt(el.tagName[1]))
17
+ .get();
18
+ if (headings.length === 0)
19
+ return issues;
20
+ const h1Count = headings.filter(h => h === 1).length;
21
+ if (h1Count === 0) {
22
+ issues.push({
23
+ type: 'warning',
24
+ category: 'structure',
25
+ message: 'Page has headings but no <h1> element. Every page should have exactly one <h1>.',
26
+ });
27
+ }
28
+ if (h1Count > 1) {
29
+ issues.push({
30
+ type: 'warning',
31
+ category: 'structure',
32
+ message: `Page has ${h1Count} <h1> elements. There should be exactly one <h1> per page.`,
33
+ });
34
+ }
35
+ // Report ALL heading skips, not just the first one
36
+ for (let i = 1; i < headings.length; i++) {
37
+ if (headings[i] > headings[i - 1] + 1) {
38
+ issues.push({
39
+ type: 'warning',
40
+ category: 'structure',
41
+ message: `Heading hierarchy skip: h${headings[i - 1]} → h${headings[i]}. Should not skip levels.`,
42
+ });
43
+ }
44
+ }
45
+ return issues;
46
+ },
47
+ };
48
+ //# sourceMappingURL=heading-hierarchy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heading-hierarchy.js","sourceRoot":"","sources":["../../../src/validation/rules/heading-hierarchy.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAa;IACxC,EAAE,EAAE,mBAAmB;IACvB,IAAI,EAAE,mBAAmB;IACzB,WAAW,EAAE,+EAA+E;IAC5F,QAAQ,EAAE,SAAS;IACnB,QAAQ,EAAE,WAAW;IAErB,KAAK,CAAC,OAAoB;QACxB,MAAM,MAAM,GAAsB,EAAE,CAAC;QACrC,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC;QAEtB,MAAM,QAAQ,GAAG,CAAC,CAAC,wBAAwB,CAAC;aACzC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;aACxC,GAAG,EAAc,CAAC;QAErB,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC;QAEzC,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;QAErD,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;YAClB,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,WAAW;gBACrB,OAAO,EAAE,iFAAiF;aAC3F,CAAC,CAAC;QACL,CAAC;QAED,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,WAAW;gBACrB,OAAO,EAAE,YAAY,OAAO,4DAA4D;aACzF,CAAC,CAAC;QACL,CAAC;QAED,mDAAmD;QACnD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtC,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,SAAS;oBACf,QAAQ,EAAE,WAAW;oBACrB,OAAO,EAAE,4BAA4B,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,2BAA2B;iBAClG,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Rule registry -- manages all lint rules.
3
+ *
4
+ * Rules are individually testable, configurable, and extensible.
5
+ * 18 total rules: 8 original (with bug fixes) + 10 new slop detection.
6
+ */
7
+ import type { LintRule } from './types.js';
8
+ export type { LintRule, LintContext } from './types.js';
9
+ /**
10
+ * Get all registered rules.
11
+ */
12
+ export declare function getAllRules(): LintRule[];
13
+ /**
14
+ * Get a single rule by ID.
15
+ */
16
+ export declare function getRule(id: string): LintRule | undefined;
17
+ /**
18
+ * Get multiple rules by their IDs.
19
+ */
20
+ export declare function getRulesByIds(ids: string[]): LintRule[];
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/validation/rules/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAoB3C,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA0BxD;;GAEG;AACH,wBAAgB,WAAW,IAAI,QAAQ,EAAE,CAExC;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAExD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,CAEvD"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Rule registry -- manages all lint rules.
3
+ *
4
+ * Rules are individually testable, configurable, and extensible.
5
+ * 18 total rules: 8 original (with bug fixes) + 10 new slop detection.
6
+ */
7
+ import { emptyBody } from './empty-body.js';
8
+ import { noDefaultFonts } from './no-default-fonts.js';
9
+ import { noSlopGradients } from './no-slop-gradients.js';
10
+ import { headingHierarchy } from './heading-hierarchy.js';
11
+ import { altText } from './alt-text.js';
12
+ import { colorAdherence } from './color-adherence.js';
13
+ import { noIconGrid } from './no-icon-grid.js';
14
+ import { businessAlignment } from './business-alignment.js';
15
+ import { noLoremIpsum } from './no-lorem-ipsum.js';
16
+ import { noSaasSpeak } from './no-saas-speak.js';
17
+ import { noDuplicateCtas } from './no-duplicate-ctas.js';
18
+ import { noCenteredEverything } from './no-centered-everything.js';
19
+ import { noMissingResponsive } from './no-missing-responsive.js';
20
+ import { noUniformSpacing } from './no-uniform-spacing.js';
21
+ import { noDivSoup } from './no-div-soup.js';
22
+ import { noMissingMeta } from './no-missing-meta.js';
23
+ import { noGenericHero } from './no-generic-hero.js';
24
+ import { noPlaceholderImages } from './no-placeholder-images.js';
25
+ /**
26
+ * All built-in rules in execution order.
27
+ */
28
+ const ALL_RULES = [
29
+ emptyBody,
30
+ noDefaultFonts,
31
+ noSlopGradients,
32
+ headingHierarchy,
33
+ altText,
34
+ colorAdherence,
35
+ noIconGrid,
36
+ businessAlignment,
37
+ noLoremIpsum,
38
+ noSaasSpeak,
39
+ noDuplicateCtas,
40
+ noCenteredEverything,
41
+ noMissingResponsive,
42
+ noUniformSpacing,
43
+ noDivSoup,
44
+ noMissingMeta,
45
+ noGenericHero,
46
+ noPlaceholderImages,
47
+ ];
48
+ /**
49
+ * Get all registered rules.
50
+ */
51
+ export function getAllRules() {
52
+ return [...ALL_RULES];
53
+ }
54
+ /**
55
+ * Get a single rule by ID.
56
+ */
57
+ export function getRule(id) {
58
+ return ALL_RULES.find((r) => r.id === id);
59
+ }
60
+ /**
61
+ * Get multiple rules by their IDs.
62
+ */
63
+ export function getRulesByIds(ids) {
64
+ return ALL_RULES.filter((r) => ids.includes(r.id));
65
+ }
66
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/validation/rules/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAIjE;;GAEG;AACH,MAAM,SAAS,GAAe;IAC5B,SAAS;IACT,cAAc;IACd,eAAe;IACf,gBAAgB;IAChB,OAAO;IACP,cAAc;IACd,UAAU;IACV,iBAAiB;IACjB,YAAY;IACZ,WAAW;IACX,eAAe;IACf,oBAAoB;IACpB,mBAAmB;IACnB,gBAAgB;IAChB,SAAS;IACT,aAAa;IACb,aAAa;IACb,mBAAmB;CACpB,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,GAAa;IACzC,OAAO,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACrD,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { LintRule } from './types.js';
2
+ /**
3
+ * Detects when most content is center-aligned.
4
+ */
5
+ export declare const noCenteredEverything: LintRule;
6
+ //# sourceMappingURL=no-centered-everything.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-centered-everything.d.ts","sourceRoot":"","sources":["../../../src/validation/rules/no-centered-everything.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,YAAY,CAAC;AAGxD;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,QAkElC,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Detects when most content is center-aligned.
3
+ */
4
+ export const noCenteredEverything = {
5
+ id: 'no-centered-everything',
6
+ name: 'No Centered Everything',
7
+ description: 'Flags pages where over 60% of text-containing elements are center-aligned.',
8
+ severity: 'warning',
9
+ category: 'layout',
10
+ check(context) {
11
+ const issues = [];
12
+ const { $, allStyles } = context;
13
+ // Text-containing elements to check
14
+ const textElements = $('h1, h2, h3, h4, h5, h6, p, li, span, a, button, td, th, label, blockquote');
15
+ const totalTextElements = textElements.length;
16
+ if (totalTextElements === 0)
17
+ return issues;
18
+ let centeredCount = 0;
19
+ textElements.each((_i, el) => {
20
+ const $el = $(el);
21
+ // Check inline style for text-align: center
22
+ const style = $el.attr('style') || '';
23
+ if (/text-align\s*:\s*center/i.test(style)) {
24
+ centeredCount++;
25
+ return;
26
+ }
27
+ // Check Tailwind text-center class
28
+ const classes = $el.attr('class') || '';
29
+ if (/\btext-center\b/.test(classes)) {
30
+ centeredCount++;
31
+ return;
32
+ }
33
+ // Check if a parent has text-center or text-align: center
34
+ const parents = $el.parents();
35
+ parents.each((_j, parent) => {
36
+ const parentStyle = $(parent).attr('style') || '';
37
+ const parentClasses = $(parent).attr('class') || '';
38
+ if (/text-align\s*:\s*center/i.test(parentStyle) ||
39
+ /\btext-center\b/.test(parentClasses)) {
40
+ centeredCount++;
41
+ return false; // break parent loop
42
+ }
43
+ });
44
+ });
45
+ // Also count CSS rules for text-align: center in <style> blocks
46
+ const cssCenter = (allStyles.match(/text-align\s*:\s*center/gi) || []).length;
47
+ const ratio = centeredCount / totalTextElements;
48
+ if (ratio > 0.6) {
49
+ const pct = Math.round(ratio * 100);
50
+ issues.push({
51
+ type: 'warning',
52
+ category: 'layout',
53
+ message: `${pct}% of text elements are center-aligned — creates a monotonous layout.`,
54
+ });
55
+ }
56
+ return issues;
57
+ },
58
+ };
59
+ //# sourceMappingURL=no-centered-everything.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-centered-everything.js","sourceRoot":"","sources":["../../../src/validation/rules/no-centered-everything.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAa;IAC5C,EAAE,EAAE,wBAAwB;IAC5B,IAAI,EAAE,wBAAwB;IAC9B,WAAW,EAAE,4EAA4E;IACzF,QAAQ,EAAE,SAAS;IACnB,QAAQ,EAAE,QAAQ;IAElB,KAAK,CAAC,OAAoB;QACxB,MAAM,MAAM,GAAsB,EAAE,CAAC;QACrC,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;QAEjC,oCAAoC;QACpC,MAAM,YAAY,GAAG,CAAC,CAAC,2EAA2E,CAAC,CAAC;QACpG,MAAM,iBAAiB,GAAG,YAAY,CAAC,MAAM,CAAC;QAE9C,IAAI,iBAAiB,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC;QAE3C,IAAI,aAAa,GAAG,CAAC,CAAC;QAEtB,YAAY,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE;YAC3B,MAAM,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;YAElB,4CAA4C;YAC5C,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YACtC,IAAI,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC3C,aAAa,EAAE,CAAC;gBAChB,OAAO;YACT,CAAC;YAED,mCAAmC;YACnC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YACxC,IAAI,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBACpC,aAAa,EAAE,CAAC;gBAChB,OAAO;YACT,CAAC;YAED,0DAA0D;YAC1D,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE;gBAC1B,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;gBAClD,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;gBACpD,IACE,0BAA0B,CAAC,IAAI,CAAC,WAAW,CAAC;oBAC5C,iBAAiB,CAAC,IAAI,CAAC,aAAa,CAAC,EACrC,CAAC;oBACD,aAAa,EAAE,CAAC;oBAChB,OAAO,KAAK,CAAC,CAAC,oBAAoB;gBACpC,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,gEAAgE;QAChE,MAAM,SAAS,GAAG,CAAC,SAAS,CAAC,KAAK,CAAC,2BAA2B,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;QAE9E,MAAM,KAAK,GAAG,aAAa,GAAG,iBAAiB,CAAC;QAChD,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;YAChB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC;YACpC,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,SAAS;gBACf,QAAQ,EAAE,QAAQ;gBAClB,OAAO,EAAE,GAAG,GAAG,sEAAsE;aACtF,CAAC,CAAC;QACL,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { LintRule } from './types.js';
2
+ /**
3
+ * Flags Inter, Poppins, and other common AI-default fonts.
4
+ * Bug Fix (Frente A): expanded font list, Google Fonts link detection,
5
+ * system-ui sole font-family detection, Section 3 parsing for intentional fonts.
6
+ */
7
+ export declare const noDefaultFonts: LintRule;
8
+ //# sourceMappingURL=no-default-fonts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-default-fonts.d.ts","sourceRoot":"","sources":["../../../src/validation/rules/no-default-fonts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAe,MAAM,YAAY,CAAC;AA2CxD;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,QA8E5B,CAAC"}