medesign 0.0.0-stage → 1.2.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 (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +128 -2
  3. package/bin/medesign.js +200 -0
  4. package/integrations/claude-code/SKILL.md +20 -0
  5. package/integrations/cursor/medesign.mdc +26 -0
  6. package/medesign.config.js +46 -0
  7. package/package.json +62 -4
  8. package/presets/apple/design-tokens.css +115 -0
  9. package/presets/apple/guidelines.md +19 -0
  10. package/presets/apple/preset.json +53 -0
  11. package/presets/metha/design-tokens.css +127 -0
  12. package/presets/metha/guidelines.md +23 -0
  13. package/presets/metha/preset.json +53 -0
  14. package/presets/schema.json +102 -0
  15. package/presets/tesla/design-tokens.css +112 -0
  16. package/presets/tesla/guidelines.md +17 -0
  17. package/presets/tesla/preset.json +53 -0
  18. package/references/ai-tells.md +127 -0
  19. package/references/appendices.md +220 -0
  20. package/references/architecture.md +39 -0
  21. package/references/argon-design-archetypes.md +317 -0
  22. package/references/banner-sizes-and-styles.md +118 -0
  23. package/references/block-library.md +61 -0
  24. package/references/brand-design-tokens.md +42 -0
  25. package/references/catalog.json +108 -0
  26. package/references/cip-deliverable-guide.md +95 -0
  27. package/references/cip-design.md +121 -0
  28. package/references/cip-prompt-engineering.md +84 -0
  29. package/references/cip-style-guide.md +68 -0
  30. package/references/cli-architecture.md +146 -0
  31. package/references/components.md +240 -0
  32. package/references/dark-mode.md +23 -0
  33. package/references/design-core.md +42 -0
  34. package/references/design-md-protocol.md +75 -0
  35. package/references/design-routing.md +207 -0
  36. package/references/eng-imagery-content.md +71 -0
  37. package/references/eng-layout.md +28 -0
  38. package/references/eng-other.md +26 -0
  39. package/references/eng-states-theme.md +22 -0
  40. package/references/eng-type-color.md +44 -0
  41. package/references/icon-design.md +122 -0
  42. package/references/image-mockup-direction.md +53 -0
  43. package/references/image-to-code.md +46 -0
  44. package/references/impeccable-polish.md +85 -0
  45. package/references/intake.md +90 -0
  46. package/references/logo-color-psychology.md +101 -0
  47. package/references/logo-design.md +110 -0
  48. package/references/logo-prompt-engineering.md +158 -0
  49. package/references/logo-style-guide.md +109 -0
  50. package/references/mesurv-dna.md +104 -0
  51. package/references/micro-interactions-and-auto-animate.md +71 -0
  52. package/references/modern-craft-invariants.md +332 -0
  53. package/references/motion-tokens.md +99 -0
  54. package/references/preflight.md +110 -0
  55. package/references/proactive-motion.md +167 -0
  56. package/references/quality-gate.md +147 -0
  57. package/references/redesign-audit.md +38 -0
  58. package/references/redesign.md +52 -0
  59. package/references/slides-copywriting-formulas.md +84 -0
  60. package/references/slides-create.md +4 -0
  61. package/references/slides-html-template.md +295 -0
  62. package/references/slides-layout-patterns.md +137 -0
  63. package/references/slides-strategies.md +94 -0
  64. package/references/slides.md +42 -0
  65. package/references/social-photos-design.md +329 -0
  66. package/references/spatial-canvas-engine.md +160 -0
  67. package/references/stitch-system.md +184 -0
  68. package/references/style-extraction.md +17 -0
  69. package/references/styles/brutalist.md +88 -0
  70. package/references/styles/minimalist.md +81 -0
  71. package/references/styles/soft.md +94 -0
  72. package/references/threeui-spatial-engine.md +224 -0
  73. package/references/ui-styling-shadcn.md +336 -0
  74. package/references/ui-ux-vocabulary-vault.json +1223 -0
  75. package/references/ui-ux-vocabulary-vault.md +438 -0
  76. package/references/vocab-protocol.md +52 -0
  77. package/references/vocabulary.md +78 -0
  78. package/scripts/export-tokens.js +403 -0
  79. package/tests/browser-audit.js +442 -0
  80. package/tests/linter.js +412 -0
  81. package/tests/schemas/sarif-2.1.0.schema.json +2915 -0
  82. package/tests/visual-testbed.html +681 -0
@@ -0,0 +1,442 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ME-DESIGN Phase 2 — Rendered DOM & Headless Browser Verification Engine
5
+ * Direct Chrome DevTools Protocol (CDP) client over native WebSocket.
6
+ * Zero external npm dependencies.
7
+ *
8
+ * Verifies:
9
+ * 1. Real Computed Contrast Ratio across backgrounds
10
+ * 2. Real Keyboard Tab Navigation & Focus Ring rendering (:focus-visible)
11
+ * 3. Windows High Contrast Mode (forced-colors: active) physical outline persistence
12
+ * 4. Hit-Target Bounding Dimensions (44px min touch standard)
13
+ * 5. Responsive Viewports (Mobile 390px, Tablet 820px, Desktop 1440px)
14
+ * 6. Theme Modes (Dark & Light)
15
+ *
16
+ * Usage: node tests/browser-audit.js <filepath> [--json]
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const { spawn } = require('child_process');
22
+
23
+ const ANSI_RESET = '\x1b[0m';
24
+ const ANSI_BOLD = '\x1b[1m';
25
+ const ANSI_GREEN = '\x1b[32m';
26
+ const ANSI_RED = '\x1b[31m';
27
+ const ANSI_YELLOW = '\x1b[33m';
28
+ const ANSI_CYAN = '\x1b[36m';
29
+ const ANSI_GRAY = '\x1b[90m';
30
+
31
+ const args = process.argv.slice(2);
32
+ let targetFile = null;
33
+ let isJson = false;
34
+
35
+ for (let i = 0; i < args.length; i++) {
36
+ if (args[i] === '--json') {
37
+ isJson = true;
38
+ } else if (!targetFile && !args[i].startsWith('--')) {
39
+ targetFile = args[i];
40
+ }
41
+ }
42
+
43
+ targetFile = targetFile || path.join(__dirname, 'fixtures', 'valid', 'metha-cadastre-dashboard.html');
44
+
45
+ if (!fs.existsSync(targetFile)) {
46
+ console.error(`Error: File not found: ${targetFile}`);
47
+ process.exit(2);
48
+ }
49
+
50
+ const resolvedPath = path.resolve(targetFile);
51
+ const fileUrl = 'file:///' + resolvedPath.replace(/\\/g, '/');
52
+
53
+ // Locate Chromium executable across Windows, Linux, and macOS
54
+ function findChromium() {
55
+ const envPath = process.env.CHROME_PATH || process.env.CHROME_BIN;
56
+ if (envPath && fs.existsSync(envPath)) return envPath;
57
+
58
+ const localApp = process.env.LOCALAPPDATA || '';
59
+ const candidate = path.join(localApp, 'ms-playwright', 'chromium-1248', 'chrome-win64', 'chrome.exe');
60
+ if (fs.existsSync(candidate)) return candidate;
61
+
62
+ const programFiles = process.env['ProgramFiles'] || 'C:\\Program Files';
63
+ const programFilesX86 = process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)';
64
+
65
+ const alternatives = [
66
+ // Windows
67
+ path.join(programFiles, 'Google', 'Chrome', 'Application', 'chrome.exe'),
68
+ path.join(programFilesX86, 'Google', 'Chrome', 'Application', 'chrome.exe'),
69
+ path.join(programFilesX86, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
70
+ // Linux (CI & Debian/Ubuntu)
71
+ '/usr/bin/google-chrome',
72
+ '/usr/bin/google-chrome-stable',
73
+ '/usr/bin/chromium',
74
+ '/usr/bin/chromium-browser',
75
+ '/snap/bin/chromium',
76
+ // macOS
77
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
78
+ '/Applications/Chromium.app/Contents/MacOS/Chromium'
79
+ ];
80
+
81
+ for (const p of alternatives) {
82
+ if (fs.existsSync(p)) return p;
83
+ }
84
+ return null;
85
+ }
86
+
87
+ const chromePath = findChromium();
88
+ if (!chromePath) {
89
+ console.error('Error: Chromium/Chrome binary not found on system.');
90
+ process.exit(2);
91
+ }
92
+
93
+ // Minimal JSON-RPC CDP Client over Node's native global WebSocket
94
+ class CDPClient {
95
+ constructor(port) {
96
+ this.port = port;
97
+ this.ws = null;
98
+ this.msgId = 0;
99
+ this.callbacks = new Map();
100
+ }
101
+
102
+ async connect(targetUrl) {
103
+ const res = await fetch(`http://127.0.0.1:${this.port}/json/new`, { method: 'PUT' });
104
+ const target = await res.json();
105
+ this.targetId = target.id;
106
+ this.ws = new WebSocket(target.webSocketDebuggerUrl);
107
+
108
+ await new Promise((resolve, reject) => {
109
+ this.ws.onopen = resolve;
110
+ this.ws.onerror = reject;
111
+ });
112
+
113
+ this.ws.onmessage = (event) => {
114
+ const msg = JSON.parse(event.data);
115
+ if (msg.id && this.callbacks.has(msg.id)) {
116
+ const { resolve, reject } = this.callbacks.get(msg.id);
117
+ this.callbacks.delete(msg.id);
118
+ if (msg.error) reject(msg.error);
119
+ else resolve(msg.result);
120
+ }
121
+ };
122
+ }
123
+
124
+ send(method, params = {}) {
125
+ return new Promise((resolve, reject) => {
126
+ const id = ++this.msgId;
127
+ this.callbacks.set(id, { resolve, reject });
128
+ this.ws.send(JSON.stringify({ id, method, params }));
129
+ });
130
+ }
131
+
132
+ async close() {
133
+ if (this.ws) {
134
+ try { this.ws.close(); } catch (e) {}
135
+ }
136
+ try {
137
+ await fetch(`http://127.0.0.1:${this.port}/json/close/${this.targetId}`, { method: 'PUT' });
138
+ } catch (e) {}
139
+ }
140
+ }
141
+
142
+ async function runAudit() {
143
+ const cdpPort = 9444 + Math.floor(Math.random() * 200);
144
+ const chromeProcess = spawn(chromePath, [
145
+ '--headless=new',
146
+ `--remote-debugging-port=${cdpPort}`,
147
+ '--no-first-run',
148
+ '--disable-gpu',
149
+ '--no-sandbox',
150
+ '--disable-extensions',
151
+ '--mute-audio',
152
+ '--hide-scrollbars'
153
+ ]);
154
+
155
+ // Wait for CDP readiness
156
+ let ready = false;
157
+ for (let i = 0; i < 25; i++) {
158
+ try {
159
+ const r = await fetch(`http://127.0.0.1:${cdpPort}/json/version`);
160
+ if (r.ok) { ready = true; break; }
161
+ } catch (e) {}
162
+ await new Promise(res => setTimeout(res, 100));
163
+ }
164
+
165
+ if (!ready) {
166
+ chromeProcess.kill();
167
+ console.error('Failed to connect to headless Chromium instance.');
168
+ process.exit(1);
169
+ }
170
+
171
+ const client = new CDPClient(cdpPort);
172
+ await client.connect();
173
+
174
+ // Full 7-Condition Test Matrix:
175
+ // 3 Viewports (Mobile, Tablet, Desktop) × 2 Themes (Dark, Light) + Windows High Contrast (forced-colors: active)
176
+ const testMatrix = [
177
+ { viewport: { name: 'Mobile', width: 390, height: 844, isMobile: true }, theme: 'dark', forcedColors: false },
178
+ { viewport: { name: 'Mobile', width: 390, height: 844, isMobile: true }, theme: 'light', forcedColors: false },
179
+ { viewport: { name: 'Tablet', width: 820, height: 1180, isMobile: true }, theme: 'dark', forcedColors: false },
180
+ { viewport: { name: 'Tablet', width: 820, height: 1180, isMobile: true }, theme: 'light', forcedColors: false },
181
+ { viewport: { name: 'Desktop', width: 1440, height: 900, isMobile: false }, theme: 'dark', forcedColors: false },
182
+ { viewport: { name: 'Desktop', width: 1440, height: 900, isMobile: false }, theme: 'light', forcedColors: false },
183
+ // Condition 7: Windows High Contrast Emulation (forced-colors: active)
184
+ { viewport: { name: 'Desktop', width: 1440, height: 900, isMobile: false }, theme: 'forced-colors', forcedColors: true }
185
+ ];
186
+
187
+ const auditResults = [];
188
+
189
+ try {
190
+ await client.send('Page.enable');
191
+ await client.send('DOM.enable');
192
+ await client.send('Runtime.enable');
193
+
194
+ for (const testCase of testMatrix) {
195
+ const vp = testCase.viewport;
196
+ const th = testCase.theme;
197
+
198
+ // Set Device Metrics
199
+ await client.send('Emulation.setDeviceMetricsOverride', {
200
+ width: vp.width,
201
+ height: vp.height,
202
+ deviceScaleFactor: 2,
203
+ mobile: vp.isMobile
204
+ });
205
+
206
+ // Set Emulated Color Scheme and Forced Colors
207
+ const features = [
208
+ { name: 'prefers-color-scheme', value: testCase.forcedColors ? 'dark' : th }
209
+ ];
210
+ if (testCase.forcedColors) {
211
+ features.push({ name: 'forced-colors', value: 'active' });
212
+ } else {
213
+ features.push({ name: 'forced-colors', value: 'none' });
214
+ }
215
+
216
+ await client.send('Emulation.setEmulatedMedia', {
217
+ media: 'screen',
218
+ features
219
+ });
220
+
221
+ // Navigate
222
+ await client.send('Page.navigate', { url: fileUrl });
223
+ await new Promise(res => setTimeout(res, 250)); // Wait for render and styles to settle
224
+
225
+ // Dispatch real keyboard Tab navigation event
226
+ await client.send('Input.dispatchKeyEvent', { type: 'rawKeyDown', key: 'Tab', code: 'Tab', windowsVirtualKeyCode: 9 });
227
+ await client.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Tab', code: 'Tab', windowsVirtualKeyCode: 9 });
228
+ await new Promise(res => setTimeout(res, 50));
229
+
230
+ // Perform in-page inspection
231
+ const inspectExpr = `
232
+ (() => {
233
+ // Relative luminance calculator
234
+ function getLuminance(r, g, b) {
235
+ const [rs, gs, bs] = [r, g, b].map(c => {
236
+ c = c / 255;
237
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
238
+ });
239
+ return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
240
+ }
241
+
242
+ // Shared 1x1 canvas for hardware-exact pixel color rasterization
243
+ const pixelCanvas = document.createElement('canvas');
244
+ pixelCanvas.width = 1;
245
+ pixelCanvas.height = 1;
246
+ const pixelCtx = pixelCanvas.getContext('2d', { willReadFrequently: true });
247
+
248
+ function parseRgba(colorStr) {
249
+ if (!colorStr) return [0, 0, 0, 1];
250
+ try {
251
+ pixelCtx.clearRect(0, 0, 1, 1);
252
+ pixelCtx.fillStyle = colorStr;
253
+ pixelCtx.fillRect(0, 0, 1, 1);
254
+ const d = pixelCtx.getImageData(0, 0, 1, 1).data;
255
+ return [d[0], d[1], d[2], d[3] / 255];
256
+ } catch (e) {
257
+ return [255, 255, 255, 1];
258
+ }
259
+ }
260
+
261
+ function getContrastRatio(fgRgb, bgRgb) {
262
+ const l1 = getLuminance(fgRgb[0], fgRgb[1], fgRgb[2]);
263
+ const l2 = getLuminance(bgRgb[0], bgRgb[1], bgRgb[2]);
264
+ const lighter = Math.max(l1, l2);
265
+ const darker = Math.min(l1, l2);
266
+ return (lighter + 0.05) / (darker + 0.05);
267
+ }
268
+
269
+ function isDemoAntiPattern(el) {
270
+ return el.closest('.con') || el.classList.contains('con') || el.closest('[data-anti-pattern]') || el.classList.contains('btn-slop') || el.closest('.cmp-col:has(.btn-slop)') || el.closest('.cmp-col:has(.thai-crop-bad)');
271
+ }
272
+
273
+ // Test 1: Hit-Target Dimensions (min 44x44px for touch)
274
+ const buttons = Array.from(document.querySelectorAll('button, [role="button"], a.btn, .action-btn, .pill-btn-container, .telemetry-btn, .btn-ring')).filter(b => !isDemoAntiPattern(b));
275
+ const hitTargetViolations = [];
276
+ buttons.forEach(b => {
277
+ const rect = b.getBoundingClientRect();
278
+ if (rect.width > 0 && rect.height > 0) {
279
+ if (rect.width < 43.5 || rect.height < 43.5) {
280
+ hitTargetViolations.push({
281
+ tag: b.tagName.toLowerCase(),
282
+ text: b.textContent.trim().slice(0, 20),
283
+ width: Math.round(rect.width),
284
+ height: Math.round(rect.height)
285
+ });
286
+ }
287
+ }
288
+ });
289
+
290
+ // Test 2: Real Computed Contrast Ratio against actual background
291
+ const texts = Array.from(document.querySelectorAll('h1, h2, h3, p, td, th, span, label, .visual-surface')).filter(el => {
292
+ return !isDemoAntiPattern(el) && el.children.length === 0 && el.textContent.trim().length > 0;
293
+ }).slice(0, 20);
294
+
295
+ let minContrast = 21.0;
296
+ const debugContrast = [];
297
+
298
+ texts.forEach(el => {
299
+ const cs = window.getComputedStyle(el);
300
+ if (cs.display === 'none' || cs.visibility === 'hidden' || parseFloat(cs.opacity) === 0) return;
301
+
302
+ const fg = parseRgba(cs.color);
303
+ let solidBg = [9, 9, 11];
304
+ const overlays = [];
305
+
306
+ let p = el;
307
+ while (p && p !== document) {
308
+ const bgStr = window.getComputedStyle(p).backgroundColor;
309
+ if (bgStr && bgStr !== 'transparent') {
310
+ const rgba = parseRgba(bgStr);
311
+ if (rgba[3] >= 0.90) {
312
+ solidBg = [rgba[0], rgba[1], rgba[2]];
313
+ break;
314
+ } else if (rgba[3] > 0) {
315
+ overlays.unshift(rgba);
316
+ }
317
+ }
318
+ p = p.parentElement;
319
+ }
320
+
321
+ let compositeBg = [...solidBg];
322
+ overlays.forEach(([r, g, b, a]) => {
323
+ compositeBg[0] = Math.round(r * a + compositeBg[0] * (1 - a));
324
+ compositeBg[1] = Math.round(g * a + compositeBg[1] * (1 - a));
325
+ compositeBg[2] = Math.round(b * a + compositeBg[2] * (1 - a));
326
+ });
327
+ const cr = getContrastRatio(fg, compositeBg);
328
+ debugContrast.push({ tag: el.tagName, text: el.textContent.trim().slice(0, 20), fg: fg.slice(0,3), bg: compositeBg, cr: Math.round(cr * 10) / 10 });
329
+ if (cr < minContrast) minContrast = cr;
330
+ });
331
+
332
+ // Test 3: Active Element Tab Focus Outline
333
+ let focusVisibleRendered = false;
334
+ let highContrastOutlineRendered = false;
335
+ const targetEl = (document.activeElement && document.activeElement !== document.body)
336
+ ? document.activeElement
337
+ : buttons[0];
338
+
339
+ if (targetEl) {
340
+ targetEl.focus();
341
+ const fs = window.getComputedStyle(targetEl);
342
+ const hasOutline = fs.outlineStyle !== 'none' && parseFloat(fs.outlineWidth) > 0;
343
+ const hasShadow = fs.boxShadow && fs.boxShadow !== 'none';
344
+ focusVisibleRendered = hasOutline || hasShadow;
345
+ // Physical outline required for forced-colors: active
346
+ highContrastOutlineRendered = hasOutline;
347
+ }
348
+
349
+ return {
350
+ buttonCount: buttons.length,
351
+ hitTargetViolations,
352
+ minContrastRatio: Math.round(minContrast * 10) / 10,
353
+ focusVisibleRendered,
354
+ highContrastOutlineRendered,
355
+ debugContrast
356
+ };
357
+ })()
358
+ `;
359
+
360
+ const evalRes = await client.send('Runtime.evaluate', {
361
+ expression: inspectExpr,
362
+ returnByValue: true
363
+ });
364
+
365
+ const data = evalRes.result.value || {};
366
+ auditResults.push({
367
+ viewport: vp.name,
368
+ dimensions: `${vp.width}x${vp.height}`,
369
+ theme: th,
370
+ ...data
371
+ });
372
+ }
373
+ } finally {
374
+ await client.close();
375
+ chromeProcess.kill();
376
+ }
377
+
378
+ let overallPass = true;
379
+ auditResults.forEach(r => {
380
+ const isForced = r.theme === 'forced-colors';
381
+ const contrastPass = isForced ? true : r.minContrastRatio >= 4.5;
382
+ const hitTargetPass = r.hitTargetViolations.length === 0;
383
+ const focusPass = isForced
384
+ ? (r.highContrastOutlineRendered || r.buttonCount === 0)
385
+ : (r.focusVisibleRendered || r.buttonCount === 0);
386
+
387
+ if (!contrastPass || !hitTargetPass || !focusPass) overallPass = false;
388
+ });
389
+
390
+ if (isJson) {
391
+ console.log(JSON.stringify({
392
+ target: targetFile,
393
+ passed: overallPass,
394
+ matrix: auditResults
395
+ }, null, 2));
396
+ process.exit(overallPass ? 0 : 1);
397
+ }
398
+
399
+ // Render Matrix Card
400
+ console.log(`\n${ANSI_BOLD}ME-DESIGN Phase 2 — Browser Verification Matrix${ANSI_RESET}`);
401
+ console.log(`${ANSI_CYAN}Headless Chromium Runtime Engine (CDP v156)${ANSI_RESET}`);
402
+ console.log(`${ANSI_GRAY}────────────────────────────────────────────────────────────────────────${ANSI_RESET}`);
403
+ console.log(`Target: ${path.basename(targetFile)}\n`);
404
+
405
+ console.log(`${'VIEWPORT'.padEnd(10)} | ${'MODE'.padEnd(14)} | ${'CONTRAST'.padEnd(10)} | ${'HIT-TARGET (≥44px)'.padEnd(20)} | ${'TAB FOCUS'.padEnd(10)}`);
406
+ console.log(`${ANSI_GRAY}------------------------------------------------------------------------${ANSI_RESET}`);
407
+
408
+ auditResults.forEach(r => {
409
+ const isForced = r.theme === 'forced-colors';
410
+ const contrastPass = isForced ? true : r.minContrastRatio >= 4.5;
411
+ const hitTargetPass = r.hitTargetViolations.length === 0;
412
+ const focusPass = isForced
413
+ ? (r.highContrastOutlineRendered || r.buttonCount === 0)
414
+ : (r.focusVisibleRendered || r.buttonCount === 0);
415
+
416
+ const contrastStr = isForced
417
+ ? ANSI_CYAN + 'SYS-HIGH' + ANSI_RESET
418
+ : (contrastPass ? ANSI_GREEN : ANSI_RED) + `${r.minContrastRatio}:1` + ANSI_RESET;
419
+
420
+ const hitStr = hitTargetPass
421
+ ? ANSI_GREEN + 'OK (All ≥44px)' + ANSI_RESET
422
+ : ANSI_RED + `FAIL (${r.hitTargetViolations.length} under 44px)` + ANSI_RESET;
423
+
424
+ const focusStr = (focusPass ? ANSI_GREEN : ANSI_RED) + (focusPass ? (isForced ? 'OUTLINE OK' : 'VERIFIED') : (isForced ? 'NO OUTLINE' : 'NO FOCUS')) + ANSI_RESET;
425
+
426
+ console.log(`${r.viewport.padEnd(10)} | ${r.theme.padEnd(14)} | ${contrastStr.padEnd(19)} | ${hitStr.padEnd(31)} | ${focusStr}`);
427
+ });
428
+
429
+ console.log(`${ANSI_GRAY}────────────────────────────────────────────────────────────────────────${ANSI_RESET}`);
430
+ if (overallPass) {
431
+ console.log(`${ANSI_GREEN}${ANSI_BOLD}BROWSER MATRIX VERIFICATION: PASS (Exit Code: 0)${ANSI_RESET}\n`);
432
+ process.exit(0);
433
+ } else {
434
+ console.log(`${ANSI_RED}${ANSI_BOLD}BROWSER MATRIX VERIFICATION: FAIL (Exit Code: 1)${ANSI_RESET}\n`);
435
+ process.exit(1);
436
+ }
437
+ }
438
+
439
+ runAudit().catch(err => {
440
+ console.error('Browser Audit Error:', err);
441
+ process.exit(1);
442
+ });