accessibility-server-mcp 1.0.0

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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +762 -0
  3. package/config/wcag-rules.json +252 -0
  4. package/dist/index.d.ts +59 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +437 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/test-manual.d.ts +6 -0
  9. package/dist/test-manual.d.ts.map +1 -0
  10. package/dist/test-manual.js +66 -0
  11. package/dist/test-manual.js.map +1 -0
  12. package/dist/tools/accessibility-tester.d.ts +56 -0
  13. package/dist/tools/accessibility-tester.d.ts.map +1 -0
  14. package/dist/tools/accessibility-tester.js +317 -0
  15. package/dist/tools/accessibility-tester.js.map +1 -0
  16. package/dist/tools/color-contrast.d.ts +49 -0
  17. package/dist/tools/color-contrast.d.ts.map +1 -0
  18. package/dist/tools/color-contrast.js +237 -0
  19. package/dist/tools/color-contrast.js.map +1 -0
  20. package/dist/tools/wcag-validator.d.ts +43 -0
  21. package/dist/tools/wcag-validator.d.ts.map +1 -0
  22. package/dist/tools/wcag-validator.js +249 -0
  23. package/dist/tools/wcag-validator.js.map +1 -0
  24. package/dist/tools/website-accessibility-tester.d.ts +103 -0
  25. package/dist/tools/website-accessibility-tester.d.ts.map +1 -0
  26. package/dist/tools/website-accessibility-tester.js +228 -0
  27. package/dist/tools/website-accessibility-tester.js.map +1 -0
  28. package/dist/types/accessibility.d.ts +172 -0
  29. package/dist/types/accessibility.d.ts.map +1 -0
  30. package/dist/types/accessibility.js +5 -0
  31. package/dist/types/accessibility.js.map +1 -0
  32. package/dist/types/index.d.ts +6 -0
  33. package/dist/types/index.d.ts.map +1 -0
  34. package/dist/types/index.js +8 -0
  35. package/dist/types/index.js.map +1 -0
  36. package/dist/types/mcp.d.ts +70 -0
  37. package/dist/types/mcp.d.ts.map +1 -0
  38. package/dist/types/mcp.js +5 -0
  39. package/dist/types/mcp.js.map +1 -0
  40. package/dist/utils/browser-manager.d.ts +83 -0
  41. package/dist/utils/browser-manager.d.ts.map +1 -0
  42. package/dist/utils/browser-manager.js +292 -0
  43. package/dist/utils/browser-manager.js.map +1 -0
  44. package/dist/utils/index.d.ts +8 -0
  45. package/dist/utils/index.d.ts.map +1 -0
  46. package/dist/utils/index.js +8 -0
  47. package/dist/utils/index.js.map +1 -0
  48. package/dist/utils/logger.d.ts +36 -0
  49. package/dist/utils/logger.d.ts.map +1 -0
  50. package/dist/utils/logger.js +107 -0
  51. package/dist/utils/logger.js.map +1 -0
  52. package/dist/utils/report-generator.d.ts +31 -0
  53. package/dist/utils/report-generator.d.ts.map +1 -0
  54. package/dist/utils/report-generator.js +252 -0
  55. package/dist/utils/report-generator.js.map +1 -0
  56. package/dist/utils/website-crawler.d.ts +66 -0
  57. package/dist/utils/website-crawler.d.ts.map +1 -0
  58. package/dist/utils/website-crawler.js +306 -0
  59. package/dist/utils/website-crawler.js.map +1 -0
  60. package/package.json +80 -0
@@ -0,0 +1,317 @@
1
+ import { JSDOM } from 'jsdom';
2
+ import { Logger, ReportGenerator } from '../utils/index.js';
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ /**
6
+ * Unified accessibility tester for both URLs and HTML content
7
+ */
8
+ export class AccessibilityTester {
9
+ logger;
10
+ browserManager;
11
+ constructor(browserManager) {
12
+ this.logger = new Logger().child({ component: 'AccessibilityTester' });
13
+ this.browserManager = browserManager;
14
+ }
15
+ /**
16
+ * Test accessibility for either URL or HTML content
17
+ */
18
+ async testTarget(input) {
19
+ const startTime = Date.now();
20
+ try {
21
+ this.logger.info('Starting accessibility test', {
22
+ type: input.type,
23
+ level: input.level,
24
+ wcagLevel: input.wcagLevel
25
+ });
26
+ let result;
27
+ if (input.type === 'url') {
28
+ result = await this.testURL(input.target, input.wcagLevel, input.level === 'full');
29
+ }
30
+ else {
31
+ result = await this.testHTML(input.target, input.wcagLevel, input.level === 'full');
32
+ }
33
+ result.testDuration = Date.now() - startTime;
34
+ this.logger.info('Accessibility test completed', {
35
+ type: input.type,
36
+ violations: result.violations.length,
37
+ score: result.summary.score
38
+ });
39
+ return result;
40
+ }
41
+ catch (error) {
42
+ this.logger.error('Error during accessibility test', error instanceof Error ? error : new Error(String(error)));
43
+ throw error;
44
+ }
45
+ }
46
+ /**
47
+ * Test URL accessibility using Puppeteer and axe-core
48
+ */
49
+ async testURL(url, wcagLevel, fullAnalysis) {
50
+ let page = null;
51
+ try {
52
+ // Create page with default viewport
53
+ page = await this.browserManager.createPage({
54
+ viewport: { width: 1920, height: 1080 },
55
+ timeout: 30000
56
+ });
57
+ // Navigate to the URL
58
+ await this.browserManager.navigateToURL(page, url, {
59
+ timeout: 30000,
60
+ waitUntil: 'domcontentloaded'
61
+ });
62
+ // Wait for dynamic content
63
+ await page.waitForTimeout(1000);
64
+ // Inject axe-core
65
+ await this.injectAxeCore(page);
66
+ // Wait for page to be ready
67
+ await page.waitForFunction(() => document.readyState === 'complete', {
68
+ timeout: 5000
69
+ }).catch(() => {
70
+ this.logger.warn('Page did not reach complete state within 5 seconds');
71
+ });
72
+ // Run axe scan
73
+ const axeResults = await this.runAxeScan(page, wcagLevel, fullAnalysis);
74
+ // Get metadata
75
+ const metadata = await this.getPageMetadata(page, wcagLevel);
76
+ // Transform results
77
+ const result = this.transformAxeResults(axeResults, url, metadata);
78
+ return result;
79
+ }
80
+ finally {
81
+ if (page) {
82
+ await this.browserManager.closePage(page);
83
+ }
84
+ }
85
+ }
86
+ /**
87
+ * Test HTML content using JSDOM and axe-core
88
+ */
89
+ async testHTML(htmlContent, wcagLevel, fullAnalysis) {
90
+ try {
91
+ // Create JSDOM instance
92
+ const dom = new JSDOM(htmlContent, {
93
+ runScripts: 'dangerously',
94
+ resources: 'usable',
95
+ pretendToBeVisual: true
96
+ });
97
+ // Inject axe-core into JSDOM
98
+ const axeSource = await this.getAxeSource();
99
+ // Create script element and inject axe
100
+ const script = dom.window.document.createElement('script');
101
+ script.text = axeSource;
102
+ dom.window.document.head.appendChild(script);
103
+ // Wait for axe to be available
104
+ await new Promise((resolve, reject) => {
105
+ const checkAxe = () => {
106
+ if (typeof dom.window.axe !== 'undefined') {
107
+ resolve();
108
+ }
109
+ else {
110
+ setTimeout(checkAxe, 100);
111
+ }
112
+ };
113
+ checkAxe();
114
+ // Timeout after 5 seconds
115
+ setTimeout(() => reject(new Error('Timeout waiting for axe in JSDOM')), 5000);
116
+ });
117
+ // Configure and run axe
118
+ const axeConfig = this.buildAxeConfig(wcagLevel, fullAnalysis);
119
+ const axeResults = await new Promise((resolve, reject) => {
120
+ dom.window.axe.run(dom.window.document, axeConfig, (err, results) => {
121
+ if (err)
122
+ reject(err);
123
+ else
124
+ resolve(results);
125
+ });
126
+ });
127
+ // Create metadata
128
+ const metadata = {
129
+ userAgent: 'JSDOM',
130
+ viewport: { width: 1024, height: 768 },
131
+ wcagLevel
132
+ };
133
+ // Transform results
134
+ return this.transformAxeResults(axeResults, undefined, metadata);
135
+ }
136
+ catch (error) {
137
+ this.logger.error('Error testing HTML content', error instanceof Error ? error : new Error(String(error)));
138
+ throw new Error(`HTML testing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
139
+ }
140
+ }
141
+ /**
142
+ * Inject axe-core into a Puppeteer page
143
+ */
144
+ async injectAxeCore(page) {
145
+ try {
146
+ // Check if axe already exists
147
+ const axeExists = await page.evaluate(() => typeof window.axe !== 'undefined');
148
+ if (axeExists) {
149
+ return;
150
+ }
151
+ // Try local file first
152
+ try {
153
+ const axePath = path.resolve('node_modules', 'axe-core', 'axe.min.js');
154
+ await page.addScriptTag({ path: axePath });
155
+ await page.waitForFunction(() => typeof window.axe !== 'undefined', {
156
+ timeout: 5000
157
+ });
158
+ this.logger.debug('Injected axe-core from local file');
159
+ return;
160
+ }
161
+ catch {
162
+ // Fall back to CDN
163
+ await page.addScriptTag({
164
+ url: 'https://unpkg.com/axe-core@latest/axe.min.js'
165
+ });
166
+ await page.waitForFunction(() => typeof window.axe !== 'undefined', {
167
+ timeout: 10000
168
+ });
169
+ this.logger.debug('Injected axe-core from CDN');
170
+ }
171
+ }
172
+ catch (error) {
173
+ throw new Error(`Failed to inject axe-core: ${error instanceof Error ? error.message : 'Unknown error'}`);
174
+ }
175
+ }
176
+ /**
177
+ * Get axe-core source code for JSDOM injection
178
+ */
179
+ async getAxeSource() {
180
+ try {
181
+ const axePath = path.resolve('node_modules', 'axe-core', 'axe.min.js');
182
+ return await fs.readFile(axePath, 'utf8');
183
+ }
184
+ catch (error) {
185
+ throw new Error(`Failed to read axe-core source: ${error instanceof Error ? error.message : 'Unknown error'}`);
186
+ }
187
+ }
188
+ /**
189
+ * Build axe configuration
190
+ */
191
+ buildAxeConfig(wcagLevel, fullAnalysis) {
192
+ const config = {
193
+ resultTypes: ['violations', 'passes'],
194
+ reporter: 'v2'
195
+ };
196
+ // Add WCAG level tags
197
+ const tags = [];
198
+ switch (wcagLevel) {
199
+ case 'AAA':
200
+ tags.push('wcag2aaa');
201
+ // fallthrough
202
+ case 'AA':
203
+ tags.push('wcag2aa');
204
+ // fallthrough
205
+ case 'A':
206
+ tags.push('wcag2a');
207
+ break;
208
+ }
209
+ // Add version tags - default to 2.1
210
+ tags.push('wcag21');
211
+ // Add analysis depth tags
212
+ if (fullAnalysis) {
213
+ tags.push('best-practice');
214
+ }
215
+ config.tags = tags;
216
+ return config;
217
+ }
218
+ /**
219
+ * Run axe accessibility scan on a page
220
+ */
221
+ async runAxeScan(page, wcagLevel, fullAnalysis) {
222
+ const axeConfig = this.buildAxeConfig(wcagLevel, fullAnalysis);
223
+ return await page.evaluate((config) => {
224
+ return window.axe.run(document, config);
225
+ }, axeConfig);
226
+ }
227
+ /**
228
+ * Get page metadata
229
+ */
230
+ async getPageMetadata(page, wcagLevel) {
231
+ const [userAgent, viewport] = await Promise.all([
232
+ page.evaluate(() => navigator.userAgent),
233
+ page.viewport()
234
+ ]);
235
+ return {
236
+ userAgent,
237
+ viewport: viewport || { width: 1920, height: 1080 },
238
+ wcagLevel
239
+ };
240
+ }
241
+ /**
242
+ * Transform axe results to our format
243
+ */
244
+ transformAxeResults(axeResults, url, metadata) {
245
+ const violations = axeResults.violations.map((violation) => ({
246
+ id: violation.id,
247
+ impact: violation.impact || 'moderate',
248
+ description: violation.description,
249
+ help: violation.help,
250
+ helpUrl: violation.helpUrl,
251
+ nodes: violation.nodes.map((node) => this.transformNode(node)),
252
+ tags: violation.tags || [],
253
+ wcagLevel: this.getWcagLevelFromTags(violation.tags),
254
+ wcagVersions: ['2.1'] // Simplified to just 2.1
255
+ }));
256
+ const passes = axeResults.passes.map((rule) => ({
257
+ id: rule.id,
258
+ description: rule.description,
259
+ impact: rule.impact,
260
+ tags: rule.tags || [],
261
+ nodes: rule.nodes?.map((node) => this.transformNode(node)) || []
262
+ }));
263
+ const result = {
264
+ url,
265
+ timestamp: new Date().toISOString(),
266
+ testDuration: 0, // Set by caller
267
+ violations,
268
+ passes,
269
+ summary: {
270
+ totalViolations: 0,
271
+ violationsBySeverity: { critical: 0, serious: 0, moderate: 0, minor: 0 },
272
+ passedRules: 0,
273
+ score: 0,
274
+ compliance: { A: false, AA: false, AAA: false }
275
+ },
276
+ metadata: metadata || {
277
+ userAgent: 'Unknown',
278
+ viewport: { width: 1920, height: 1080 },
279
+ wcagLevel: 'AA'
280
+ }
281
+ };
282
+ // Generate summary
283
+ result.summary = ReportGenerator.generateTestSummary(result);
284
+ return result;
285
+ }
286
+ /**
287
+ * Transform axe node to our format
288
+ */
289
+ transformNode(node) {
290
+ return {
291
+ html: node.html,
292
+ target: node.target,
293
+ xpath: node.xpath,
294
+ ancestry: node.ancestry,
295
+ failureSummary: node.failureSummary,
296
+ any: node.any?.map((check) => ({
297
+ id: check.id,
298
+ data: check.data,
299
+ relatedNodes: check.relatedNodes?.map((rn) => ({
300
+ html: rn.html,
301
+ target: rn.target
302
+ }))
303
+ }))
304
+ };
305
+ }
306
+ /**
307
+ * Extract WCAG level from tags
308
+ */
309
+ getWcagLevelFromTags(tags) {
310
+ if (tags.includes('wcag2aaa'))
311
+ return 'AAA';
312
+ if (tags.includes('wcag2aa'))
313
+ return 'AA';
314
+ return 'A';
315
+ }
316
+ }
317
+ //# sourceMappingURL=accessibility-tester.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accessibility-tester.js","sourceRoot":"","sources":["../../src/tools/accessibility-tester.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAC9B,OAAO,EAAkB,MAAM,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAU5E,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB;;GAEG;AACH,MAAM,OAAO,mBAAmB;IACb,MAAM,CAAS;IACf,cAAc,CAAiB;IAEhD,YAAY,cAA8B;QACxC,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,qBAAqB,EAAE,CAAC,CAAC;QACvE,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,UAAU,CAAC,KAA6B;QAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAE7B,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,EAAE;gBAC9C,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,SAAS,EAAE,KAAK,CAAC,SAAS;aAC3B,CAAC,CAAC;YAEH,IAAI,MAA+B,CAAC;YAEpC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;gBACzB,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC;YACrF,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC;YACtF,CAAC;YAED,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;YAE7C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,8BAA8B,EAAE;gBAC/C,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,MAAM;gBACpC,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK;aAC5B,CAAC,CAAC;YAEH,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,iCAAiC,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAChH,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,OAAO,CAAC,GAAW,EAAE,SAAoB,EAAE,YAAqB;QAC5E,IAAI,IAAI,GAAgB,IAAI,CAAC;QAE7B,IAAI,CAAC;YACH,oCAAoC;YACpC,IAAI,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC;gBAC1C,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;gBACvC,OAAO,EAAE,KAAK;aACf,CAAC,CAAC;YAEH,sBAAsB;YACtB,MAAM,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,EAAE;gBACjD,OAAO,EAAE,KAAK;gBACd,SAAS,EAAE,kBAAkB;aAC9B,CAAC,CAAC;YAEH,2BAA2B;YAC3B,MAAM,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;YAEhC,kBAAkB;YAClB,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YAE/B,4BAA4B;YAC5B,MAAM,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,KAAK,UAAU,EAAE;gBACnE,OAAO,EAAE,IAAI;aACd,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;gBACZ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;YACzE,CAAC,CAAC,CAAC;YAEH,eAAe;YACf,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;YAExE,eAAe;YACf,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YAE7D,oBAAoB;YACpB,MAAM,MAAM,GAAG,IAAI,CAAC,mBAAmB,CAAC,UAAU,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;YAEnE,OAAO,MAAM,CAAC;QAEhB,CAAC;gBAAS,CAAC;YACT,IAAI,IAAI,EAAE,CAAC;gBACT,MAAM,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,QAAQ,CAAC,WAAmB,EAAE,SAAoB,EAAE,YAAqB;QACrF,IAAI,CAAC;YACH,wBAAwB;YACxB,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,WAAW,EAAE;gBACjC,UAAU,EAAE,aAAa;gBACzB,SAAS,EAAE,QAAQ;gBACnB,iBAAiB,EAAE,IAAI;aACxB,CAAC,CAAC;YAEH,6BAA6B;YAC7B,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;YAE5C,uCAAuC;YACvC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC3D,MAAM,CAAC,IAAI,GAAG,SAAS,CAAC;YACxB,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YAE7C,+BAA+B;YAC/B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC1C,MAAM,QAAQ,GAAG,GAAG,EAAE;oBACpB,IAAI,OAAQ,GAAG,CAAC,MAAc,CAAC,GAAG,KAAK,WAAW,EAAE,CAAC;wBACnD,OAAO,EAAE,CAAC;oBACZ,CAAC;yBAAM,CAAC;wBACN,UAAU,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;oBAC5B,CAAC;gBACH,CAAC,CAAC;gBACF,QAAQ,EAAE,CAAC;gBAEX,0BAA0B;gBAC1B,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;YAChF,CAAC,CAAC,CAAC;YAEH,wBAAwB;YACxB,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;YAC/D,MAAM,UAAU,GAAG,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACtD,GAAG,CAAC,MAAc,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,GAAQ,EAAE,OAAY,EAAE,EAAE;oBACrF,IAAI,GAAG;wBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;wBAChB,OAAO,CAAC,OAAO,CAAC,CAAC;gBACxB,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,kBAAkB;YAClB,MAAM,QAAQ,GAAiB;gBAC7B,SAAS,EAAE,OAAO;gBAClB,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;gBACtC,SAAS;aACV,CAAC;YAEF,oBAAoB;YACpB,OAAO,IAAI,CAAC,mBAAmB,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;QAEnE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC3G,MAAM,IAAI,KAAK,CAAC,wBAAwB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;QACtG,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,aAAa,CAAC,IAAU;QACpC,IAAI,CAAC;YACH,8BAA8B;YAC9B,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAQ,MAAc,CAAC,GAAG,KAAK,WAAW,CAAC,CAAC;YACxF,IAAI,SAAS,EAAE,CAAC;gBACd,OAAO;YACT,CAAC;YAED,uBAAuB;YACvB,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;gBACvE,MAAM,IAAI,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;gBAE3C,MAAM,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,CAAC,OAAQ,MAAc,CAAC,GAAG,KAAK,WAAW,EAAE;oBAC3E,OAAO,EAAE,IAAI;iBACd,CAAC,CAAC;gBAEH,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;gBACvD,OAAO;YACT,CAAC;YAAC,MAAM,CAAC;gBACP,mBAAmB;gBACnB,MAAM,IAAI,CAAC,YAAY,CAAC;oBACtB,GAAG,EAAE,8CAA8C;iBACpD,CAAC,CAAC;gBAEH,MAAM,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,CAAC,OAAQ,MAAc,CAAC,GAAG,KAAK,WAAW,EAAE;oBAC3E,OAAO,EAAE,KAAK;iBACf,CAAC,CAAC;gBAEH,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,8BAA8B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;QAC5G,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,YAAY;QACxB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC;YACvE,OAAO,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,mCAAmC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;QACjH,CAAC;IACH,CAAC;IAED;;OAEG;IACK,cAAc,CAAC,SAAoB,EAAE,YAAqB;QAChE,MAAM,MAAM,GAAQ;YAClB,WAAW,EAAE,CAAC,YAAY,EAAE,QAAQ,CAAC;YACrC,QAAQ,EAAE,IAAI;SACf,CAAC;QAEF,sBAAsB;QACtB,MAAM,IAAI,GAAa,EAAE,CAAC;QAC1B,QAAQ,SAAS,EAAE,CAAC;YAClB,KAAK,KAAK;gBACR,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACtB,cAAc;YAChB,KAAK,IAAI;gBACP,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACrB,cAAc;YAChB,KAAK,GAAG;gBACN,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACpB,MAAM;QACV,CAAC;QAED,oCAAoC;QACpC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAEpB,0BAA0B;QAC1B,IAAI,YAAY,EAAE,CAAC;YACjB,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC7B,CAAC;QAED,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC;QACnB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,UAAU,CAAC,IAAU,EAAE,SAAoB,EAAE,YAAqB;QAC9E,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;QAE/D,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,EAAE;YACpC,OAAQ,MAAc,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACnD,CAAC,EAAE,SAAS,CAAC,CAAC;IAChB,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,eAAe,CAAC,IAAU,EAAE,SAAoB;QAC5D,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YAC9C,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC;YACxC,IAAI,CAAC,QAAQ,EAAE;SAChB,CAAC,CAAC;QAEH,OAAO;YACL,SAAS;YACT,QAAQ,EAAE,QAAQ,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;YACnD,SAAS;SACV,CAAC;IACJ,CAAC;IAED;;OAEG;IACK,mBAAmB,CACzB,UAAe,EACf,GAAY,EACZ,QAAuB;QAEvB,MAAM,UAAU,GAA6B,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,SAAc,EAAE,EAAE,CAAC,CAAC;YAC1F,EAAE,EAAE,SAAS,CAAC,EAAE;YAChB,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,UAAU;YACtC,WAAW,EAAE,SAAS,CAAC,WAAW;YAClC,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,OAAO,EAAE,SAAS,CAAC,OAAO;YAC1B,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;YACnE,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,EAAE;YAC1B,SAAS,EAAE,IAAI,CAAC,oBAAoB,CAAC,SAAS,CAAC,IAAI,CAAC;YACpD,YAAY,EAAE,CAAC,KAAK,CAAC,CAAC,yBAAyB;SAChD,CAAC,CAAC,CAAC;QAEJ,MAAM,MAAM,GAAwB,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE,CAAC,CAAC;YACxE,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;YACrB,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,IAAS,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE;SACtE,CAAC,CAAC,CAAC;QAEJ,MAAM,MAAM,GAA4B;YACtC,GAAG;YACH,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,YAAY,EAAE,CAAC,EAAE,gBAAgB;YACjC,UAAU;YACV,MAAM;YACN,OAAO,EAAE;gBACP,eAAe,EAAE,CAAC;gBAClB,oBAAoB,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;gBACxE,WAAW,EAAE,CAAC;gBACd,KAAK,EAAE,CAAC;gBACR,UAAU,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE;aAChD;YACD,QAAQ,EAAE,QAAQ,IAAI;gBACpB,SAAS,EAAE,SAAS;gBACpB,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE;gBACvC,SAAS,EAAE,IAAI;aAChB;SACF,CAAC;QAEF,mBAAmB;QACnB,MAAM,CAAC,OAAO,GAAG,eAAe,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAE7D,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,IAAS;QAC7B,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAU,EAAE,EAAE,CAAC,CAAC;gBAClC,EAAE,EAAE,KAAK,CAAC,EAAE;gBACZ,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC,EAAO,EAAE,EAAE,CAAC,CAAC;oBAClD,IAAI,EAAE,EAAE,CAAC,IAAI;oBACb,MAAM,EAAE,EAAE,CAAC,MAAM;iBAClB,CAAC,CAAC;aACJ,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;IAED;;OAEG;IACK,oBAAoB,CAAC,IAAc;QACzC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC;YAAE,OAAO,KAAK,CAAC;QAC5C,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,OAAO,IAAI,CAAC;QAC1C,OAAO,GAAG,CAAC;IACb,CAAC;CACF"}
@@ -0,0 +1,49 @@
1
+ import { ColorContrastResult } from '../types/index.js';
2
+ import type { ColorContrastInput } from '../types/mcp.js';
3
+ /**
4
+ * Color contrast tester for WCAG compliance
5
+ */
6
+ export declare class ColorContrastTester {
7
+ private readonly logger;
8
+ private readonly contrastRequirements;
9
+ constructor();
10
+ /**
11
+ * Check color contrast for WCAG compliance
12
+ */
13
+ checkContrast(input: ColorContrastInput): Promise<ColorContrastResult>;
14
+ /**
15
+ * Parse color string to Color object
16
+ */
17
+ private parseColor;
18
+ /**
19
+ * Calculate contrast ratio between two colors
20
+ * Formula from WCAG: (L1 + 0.05) / (L2 + 0.05)
21
+ * where L1 is the relative luminance of the lighter color
22
+ * and L2 is the relative luminance of the darker color
23
+ */
24
+ private calculateContrastRatio;
25
+ /**
26
+ * Calculate relative luminance of a color
27
+ * Formula from WCAG: L = 0.2126 * R + 0.7152 * G + 0.0722 * B
28
+ */
29
+ private getRelativeLuminance;
30
+ /**
31
+ * Determine if text is considered "large" according to WCAG
32
+ * Large text is 18pt+ normal or 14pt+ bold
33
+ * 18pt = 24px, 14pt = ~18.7px
34
+ */
35
+ private isLargeText;
36
+ /**
37
+ * Calculate accessibility score (0-100)
38
+ */
39
+ private calculateAccessibilityScore;
40
+ /**
41
+ * Generate recommendations for improving color contrast
42
+ */
43
+ private generateRecommendations;
44
+ /**
45
+ * Adjust color to meet contrast requirements
46
+ */
47
+ private adjustColorForContrast;
48
+ }
49
+ //# sourceMappingURL=color-contrast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-contrast.d.ts","sourceRoot":"","sources":["../../src/tools/color-contrast.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAa,MAAM,mBAAmB,CAAC;AACnE,OAAO,KAAK,EAAE,kBAAkB,EAAgB,MAAM,iBAAiB,CAAC;AAExE;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAGhC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAanC;;IAMF;;OAEG;IACG,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA+D5E;;OAEG;IACH,OAAO,CAAC,UAAU;IASlB;;;;;OAKG;IACH,OAAO,CAAC,sBAAsB;IAU9B;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAW5B;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAMnB;;OAEG;IACH,OAAO,CAAC,2BAA2B;IAanC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IA0D/B;;OAEG;IACH,OAAO,CAAC,sBAAsB;CAwD/B"}
@@ -0,0 +1,237 @@
1
+ import Color from 'color';
2
+ import { Logger } from '../utils/index.js';
3
+ /**
4
+ * Color contrast tester for WCAG compliance
5
+ */
6
+ export class ColorContrastTester {
7
+ logger;
8
+ // WCAG contrast requirements
9
+ contrastRequirements = {
10
+ A: {
11
+ normal: 3.0,
12
+ large: 3.0
13
+ },
14
+ AA: {
15
+ normal: 4.5,
16
+ large: 3.0
17
+ },
18
+ AAA: {
19
+ normal: 7.0,
20
+ large: 4.5
21
+ }
22
+ };
23
+ constructor() {
24
+ this.logger = new Logger().child({ component: 'ColorContrastTester' });
25
+ }
26
+ /**
27
+ * Check color contrast for WCAG compliance
28
+ */
29
+ async checkContrast(input) {
30
+ this.logger.info('Starting color contrast analysis', {
31
+ foreground: input.foreground,
32
+ background: input.background,
33
+ fontSize: input.fontSize,
34
+ isBold: input.isBold
35
+ });
36
+ // Parse colors
37
+ const foregroundColor = this.parseColor(input.foreground);
38
+ const backgroundColor = this.parseColor(input.background);
39
+ if (!foregroundColor || !backgroundColor) {
40
+ throw new Error('Invalid color format provided');
41
+ }
42
+ // Calculate contrast ratio
43
+ const contrastRatio = this.calculateContrastRatio(foregroundColor, backgroundColor);
44
+ // Determine if text is considered "large" according to WCAG
45
+ const isLargeText = this.isLargeText(input.fontSize, input.isBold);
46
+ // Check WCAG compliance
47
+ const wcagAA = {
48
+ normal: contrastRatio >= this.contrastRequirements.AA.normal,
49
+ large: contrastRatio >= this.contrastRequirements.AA.large
50
+ };
51
+ const wcagAAA = {
52
+ normal: contrastRatio >= this.contrastRequirements.AAA.normal,
53
+ large: contrastRatio >= this.contrastRequirements.AAA.large
54
+ };
55
+ // Calculate accessibility score using AA as default
56
+ const score = this.calculateAccessibilityScore(contrastRatio, isLargeText, 'AA');
57
+ // Generate recommendations if needed
58
+ const recommendations = this.generateRecommendations(contrastRatio, isLargeText, 'AA', foregroundColor, backgroundColor);
59
+ const result = {
60
+ foreground: foregroundColor.hex(),
61
+ background: backgroundColor.hex(),
62
+ ratio: Math.round(contrastRatio * 100) / 100,
63
+ wcagAA,
64
+ wcagAAA,
65
+ score,
66
+ recommendations: recommendations.length > 0 ? recommendations : undefined
67
+ };
68
+ this.logger.info('Color contrast analysis completed', {
69
+ ratio: result.ratio,
70
+ score: result.score
71
+ });
72
+ return result;
73
+ }
74
+ /**
75
+ * Parse color string to Color object
76
+ */
77
+ parseColor(colorString) {
78
+ try {
79
+ return Color(colorString);
80
+ }
81
+ catch (error) {
82
+ this.logger.warn('Failed to parse color', { color: colorString, error });
83
+ return null;
84
+ }
85
+ }
86
+ /**
87
+ * Calculate contrast ratio between two colors
88
+ * Formula from WCAG: (L1 + 0.05) / (L2 + 0.05)
89
+ * where L1 is the relative luminance of the lighter color
90
+ * and L2 is the relative luminance of the darker color
91
+ */
92
+ calculateContrastRatio(color1, color2) {
93
+ const luminance1 = this.getRelativeLuminance(color1);
94
+ const luminance2 = this.getRelativeLuminance(color2);
95
+ const lighter = Math.max(luminance1, luminance2);
96
+ const darker = Math.min(luminance1, luminance2);
97
+ return (lighter + 0.05) / (darker + 0.05);
98
+ }
99
+ /**
100
+ * Calculate relative luminance of a color
101
+ * Formula from WCAG: L = 0.2126 * R + 0.7152 * G + 0.0722 * B
102
+ */
103
+ getRelativeLuminance(color) {
104
+ const rgb = color.rgb().array();
105
+ const [r = 0, g = 0, b = 0] = rgb.map(c => {
106
+ c = c / 255;
107
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
108
+ });
109
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
110
+ }
111
+ /**
112
+ * Determine if text is considered "large" according to WCAG
113
+ * Large text is 18pt+ normal or 14pt+ bold
114
+ * 18pt = 24px, 14pt = ~18.7px
115
+ */
116
+ isLargeText(fontSize, isBold) {
117
+ if (isBold && fontSize >= 19)
118
+ return true; // 14pt bold
119
+ if (!isBold && fontSize >= 24)
120
+ return true; // 18pt normal
121
+ return false;
122
+ }
123
+ /**
124
+ * Calculate accessibility score (0-100)
125
+ */
126
+ calculateAccessibilityScore(ratio, isLargeText, wcagLevel) {
127
+ const requirements = this.contrastRequirements[wcagLevel];
128
+ const targetRatio = isLargeText ? requirements.large : requirements.normal;
129
+ if (ratio >= targetRatio) {
130
+ // Excellent score if meets or exceeds requirements
131
+ return Math.min(100, Math.round((ratio / targetRatio) * 90 + 10));
132
+ }
133
+ else {
134
+ // Poor score if doesn't meet requirements
135
+ return Math.round((ratio / targetRatio) * 60);
136
+ }
137
+ }
138
+ /**
139
+ * Generate recommendations for improving color contrast
140
+ */
141
+ generateRecommendations(ratio, isLargeText, wcagLevel, foregroundColor, backgroundColor) {
142
+ const recommendations = [];
143
+ const requirements = this.contrastRequirements[wcagLevel];
144
+ const targetRatio = isLargeText ? requirements.large : requirements.normal;
145
+ if (ratio < targetRatio) {
146
+ const improvement = targetRatio / ratio;
147
+ recommendations.push(`Current ratio ${ratio.toFixed(2)}:1 does not meet WCAG ${wcagLevel} requirements (${targetRatio}:1 needed)`);
148
+ // Suggest darkening foreground or lightening background
149
+ const fgLuminance = this.getRelativeLuminance(foregroundColor);
150
+ const bgLuminance = this.getRelativeLuminance(backgroundColor);
151
+ if (fgLuminance > bgLuminance) {
152
+ // Light text on dark background
153
+ recommendations.push('Try darkening the background color or lightening the text color');
154
+ }
155
+ else {
156
+ // Dark text on light background
157
+ recommendations.push('Try darkening the text color or lightening the background color');
158
+ }
159
+ // Specific color suggestions
160
+ try {
161
+ const improvedForeground = this.adjustColorForContrast(foregroundColor, backgroundColor, targetRatio, 'foreground');
162
+ const improvedBackground = this.adjustColorForContrast(backgroundColor, foregroundColor, targetRatio, 'background');
163
+ if (improvedForeground) {
164
+ recommendations.push(`Suggested text color: ${improvedForeground.hex()}`);
165
+ }
166
+ if (improvedBackground) {
167
+ recommendations.push(`Suggested background color: ${improvedBackground.hex()}`);
168
+ }
169
+ }
170
+ catch (error) {
171
+ this.logger.warn('Failed to generate color suggestions', { error: String(error) });
172
+ }
173
+ // Font size suggestions
174
+ if (!isLargeText) {
175
+ const largeTextRequirement = requirements.large;
176
+ if (ratio >= largeTextRequirement) {
177
+ recommendations.push('Alternatively, increase font size to 24px+ or use 19px+ bold text');
178
+ }
179
+ }
180
+ }
181
+ return recommendations;
182
+ }
183
+ /**
184
+ * Adjust color to meet contrast requirements
185
+ */
186
+ adjustColorForContrast(colorToAdjust, fixedColor, targetRatio, type) {
187
+ try {
188
+ const fixedLuminance = this.getRelativeLuminance(fixedColor);
189
+ let targetLuminance;
190
+ if (type === 'foreground') {
191
+ // Adjust foreground color
192
+ if (fixedLuminance > 0.5) {
193
+ // Light background, need dark foreground
194
+ targetLuminance = (fixedLuminance + 0.05) / targetRatio - 0.05;
195
+ }
196
+ else {
197
+ // Dark background, need light foreground
198
+ targetLuminance = targetRatio * (fixedLuminance + 0.05) - 0.05;
199
+ }
200
+ }
201
+ else {
202
+ // Adjust background color
203
+ const fgLuminance = this.getRelativeLuminance(colorToAdjust);
204
+ if (fgLuminance > 0.5) {
205
+ // Light foreground, need dark background
206
+ targetLuminance = (fgLuminance + 0.05) / targetRatio - 0.05;
207
+ }
208
+ else {
209
+ // Dark foreground, need light background
210
+ targetLuminance = targetRatio * (fgLuminance + 0.05) - 0.05;
211
+ }
212
+ }
213
+ // Clamp luminance to valid range
214
+ targetLuminance = Math.max(0, Math.min(1, targetLuminance));
215
+ // Convert luminance back to color
216
+ const hsl = colorToAdjust.hsl().object();
217
+ const h = hsl.h ?? 0;
218
+ const s = hsl.s ?? 0;
219
+ let l = hsl.l ?? 0;
220
+ const adjustmentFactor = targetLuminance / this.getRelativeLuminance(colorToAdjust);
221
+ if (adjustmentFactor < 1) {
222
+ // Need to darken
223
+ l = Math.max(0, l * adjustmentFactor);
224
+ }
225
+ else {
226
+ // Need to lighten
227
+ l = Math.min(100, l + (100 - l) * (adjustmentFactor - 1));
228
+ }
229
+ return Color.hsl(h, s, l);
230
+ }
231
+ catch (error) {
232
+ this.logger.warn('Failed to adjust color', { error: String(error) });
233
+ return null;
234
+ }
235
+ }
236
+ }
237
+ //# sourceMappingURL=color-contrast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-contrast.js","sourceRoot":"","sources":["../../src/tools/color-contrast.ts"],"names":[],"mappings":"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"}