design-clone 2.1.0 → 2.3.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 (177) hide show
  1. package/README.md +13 -34
  2. package/SKILL.md +69 -45
  3. package/bin/cli.js +22 -4
  4. package/bin/commands/clone-site.js +31 -171
  5. package/bin/commands/help.js +19 -6
  6. package/bin/commands/init.js +9 -86
  7. package/bin/commands/uninstall.js +105 -0
  8. package/bin/commands/update.js +70 -0
  9. package/bin/commands/verify.js +7 -14
  10. package/bin/utils/paths.js +28 -0
  11. package/bin/utils/validate.js +2 -22
  12. package/bin/utils/version.js +23 -0
  13. package/docs/code-standards.md +789 -0
  14. package/docs/codebase-summary.md +533 -286
  15. package/docs/index.md +74 -0
  16. package/docs/project-overview-pdr.md +797 -0
  17. package/docs/system-architecture.md +718 -0
  18. package/package.json +14 -17
  19. package/src/ai/prompts/design-tokens/basic.md +80 -0
  20. package/src/ai/prompts/design-tokens/section-with-css.md +41 -0
  21. package/src/ai/prompts/design-tokens/section.md +48 -0
  22. package/src/ai/prompts/design-tokens/with-css.md +87 -0
  23. package/src/ai/prompts/structure-analysis/basic.md +55 -0
  24. package/src/ai/prompts/structure-analysis/with-context.md +59 -0
  25. package/src/ai/prompts/structure-analysis/with-dimensions.md +63 -0
  26. package/src/ai/prompts/structure-analysis/with-hierarchy.md +73 -0
  27. package/src/ai/prompts/ux-audit/aggregation.md +42 -0
  28. package/src/ai/prompts/ux-audit/desktop.md +92 -0
  29. package/src/ai/prompts/ux-audit/mobile.md +93 -0
  30. package/src/ai/prompts/ux-audit/tablet.md +92 -0
  31. package/src/core/animation/animation-extractor-ast.js +183 -0
  32. package/src/core/animation/animation-extractor-output.js +152 -0
  33. package/src/core/animation/animation-extractor.js +178 -0
  34. package/src/core/animation/state-capture-detection.js +200 -0
  35. package/src/core/animation/state-capture.js +193 -0
  36. package/src/core/capture/browser-context-pool.js +96 -0
  37. package/src/core/capture/multi-page-screenshot-page.js +110 -0
  38. package/src/core/capture/multi-page-screenshot.js +208 -0
  39. package/src/core/capture/screenshot-extraction.js +186 -0
  40. package/src/core/capture/screenshot-helpers.js +175 -0
  41. package/src/core/capture/screenshot-orchestrator.js +174 -0
  42. package/src/core/capture/screenshot-viewport.js +93 -0
  43. package/src/core/capture/screenshot.js +192 -0
  44. package/src/core/content/content-counter-dom.js +191 -0
  45. package/src/core/content/content-counter.js +76 -0
  46. package/src/core/css/breakpoint-detector.js +66 -0
  47. package/src/core/css/chromium-defaults.json +23 -0
  48. package/src/core/css/computed-style-extractor.js +102 -0
  49. package/src/core/css/css-chunker.js +103 -0
  50. package/src/core/css/filter-css-dead-code.js +120 -0
  51. package/src/core/css/filter-css-html-analyzer.js +110 -0
  52. package/src/core/css/filter-css-selector-matcher.js +172 -0
  53. package/src/core/css/filter-css.js +206 -0
  54. package/src/core/css/merge-css-atrule-processor.js +158 -0
  55. package/src/core/css/merge-css-file-io.js +68 -0
  56. package/src/core/css/merge-css.js +148 -0
  57. package/src/core/detection/framework-detector-routing.js +68 -0
  58. package/src/core/detection/framework-detector-signals.js +65 -0
  59. package/src/core/detection/framework-detector.js +198 -0
  60. package/src/core/dimension/dimension-extractor-card-detector.js +82 -0
  61. package/src/core/dimension/dimension-extractor.js +317 -0
  62. package/src/core/dimension/dimension-output-ai-summary.js +111 -0
  63. package/src/core/dimension/dimension-output.js +173 -0
  64. package/src/core/dimension/dom-tree-analyzer-tree-builders.js +95 -0
  65. package/src/core/dimension/dom-tree-analyzer.js +191 -0
  66. package/src/core/discovery/app-state-snapshot-capture.js +195 -0
  67. package/src/core/discovery/app-state-snapshot-utils.js +178 -0
  68. package/src/core/discovery/app-state-snapshot.js +131 -0
  69. package/src/core/discovery/discover-pages-routes.js +84 -0
  70. package/src/core/discovery/discover-pages-utils.js +177 -0
  71. package/src/core/discovery/discover-pages.js +191 -0
  72. package/src/core/html/html-extractor-inline-styler.js +70 -0
  73. package/src/core/html/html-extractor.js +147 -0
  74. package/src/core/html/semantic-enhancer-mappings.js +200 -0
  75. package/src/core/html/semantic-enhancer-page.js +148 -0
  76. package/src/core/html/semantic-enhancer.js +135 -0
  77. package/src/core/links/rewrite-links-css-rewriter.js +53 -0
  78. package/src/core/links/rewrite-links.js +173 -0
  79. package/src/core/media/asset-validator.js +118 -0
  80. package/src/core/media/extract-assets-downloader.js +187 -0
  81. package/src/core/media/extract-assets-page-scraper.js +115 -0
  82. package/src/core/media/extract-assets.js +159 -0
  83. package/src/core/media/video-capture-convert.js +200 -0
  84. package/src/core/media/video-capture.js +201 -0
  85. package/src/core/{lazy-loader.js → page-prep/lazy-loader.js} +37 -39
  86. package/src/core/section/section-cropper-helpers.js +43 -0
  87. package/src/core/{section-cropper.js → section/section-cropper.js} +11 -88
  88. package/src/core/section/section-detector-strategies.js +139 -0
  89. package/src/core/section/section-detector-utils.js +100 -0
  90. package/src/core/section/section-detector.js +88 -0
  91. package/src/core/tests/test-section-cropper.js +2 -2
  92. package/src/core/tests/test-section-detector.js +2 -2
  93. package/src/post-process/enhance-assets.js +29 -4
  94. package/src/post-process/fetch-images-unsplash-client.js +123 -0
  95. package/src/post-process/fetch-images.js +60 -263
  96. package/src/post-process/inject-gosnap.js +88 -0
  97. package/src/post-process/inject-icons-svg-replacer.js +76 -0
  98. package/src/post-process/inject-icons.js +47 -200
  99. package/src/route-discoverers/base-discoverer-utils.js +137 -0
  100. package/src/route-discoverers/base-discoverer.js +29 -118
  101. package/src/route-discoverers/index.js +1 -1
  102. package/src/shared/config.js +38 -0
  103. package/src/shared/error-codes.js +31 -0
  104. package/src/shared/viewports.js +46 -0
  105. package/src/utils/browser.js +0 -7
  106. package/src/utils/helpers.js +4 -0
  107. package/src/utils/log.js +12 -0
  108. package/src/utils/playwright-loader.js +76 -0
  109. package/src/utils/playwright.js +3 -69
  110. package/src/utils/progress.js +32 -0
  111. package/src/verification/generate-audit-report-css-fixes.js +52 -0
  112. package/src/verification/generate-audit-report-sections.js +158 -0
  113. package/src/verification/generate-audit-report.js +5 -281
  114. package/src/verification/quality-scorer.js +92 -0
  115. package/src/verification/verify-footer-checks.js +103 -0
  116. package/src/verification/verify-footer-helpers.js +178 -0
  117. package/src/verification/verify-footer.js +23 -381
  118. package/src/verification/verify-header-checks.js +104 -0
  119. package/src/verification/verify-header-helpers.js +156 -0
  120. package/src/verification/verify-header.js +23 -365
  121. package/src/verification/verify-layout-report.js +101 -0
  122. package/src/verification/verify-layout.js +13 -259
  123. package/src/verification/verify-menu-checks.js +104 -0
  124. package/src/verification/verify-menu-helpers.js +112 -0
  125. package/src/verification/verify-menu.js +17 -285
  126. package/src/verification/verify-slider-checks.js +115 -0
  127. package/src/verification/verify-slider-constants.js +65 -0
  128. package/src/verification/verify-slider-helpers.js +164 -0
  129. package/src/verification/verify-slider.js +23 -414
  130. package/.env.example +0 -14
  131. package/docs/basic-clone.md +0 -63
  132. package/docs/cli-reference.md +0 -316
  133. package/docs/design-clone-architecture.md +0 -492
  134. package/docs/pixel-perfect.md +0 -117
  135. package/docs/project-roadmap.md +0 -382
  136. package/docs/troubleshooting.md +0 -170
  137. package/requirements.txt +0 -5
  138. package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
  139. package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
  140. package/src/ai/analyze-structure.py +0 -375
  141. package/src/ai/extract-design-tokens.py +0 -782
  142. package/src/ai/prompts/__init__.py +0 -2
  143. package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  144. package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
  145. package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
  146. package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
  147. package/src/ai/prompts/design_tokens.py +0 -316
  148. package/src/ai/prompts/structure_analysis.py +0 -592
  149. package/src/ai/prompts/ux_audit.py +0 -198
  150. package/src/ai/ux-audit.js +0 -596
  151. package/src/core/animation-extractor.js +0 -526
  152. package/src/core/app-state-snapshot.js +0 -511
  153. package/src/core/content-counter.js +0 -342
  154. package/src/core/design-tokens.js +0 -103
  155. package/src/core/dimension-extractor.js +0 -438
  156. package/src/core/dimension-output.js +0 -305
  157. package/src/core/discover-pages.js +0 -542
  158. package/src/core/dom-tree-analyzer.js +0 -298
  159. package/src/core/extract-assets.js +0 -468
  160. package/src/core/filter-css.js +0 -499
  161. package/src/core/framework-detector.js +0 -538
  162. package/src/core/html-extractor.js +0 -212
  163. package/src/core/merge-css.js +0 -407
  164. package/src/core/multi-page-screenshot.js +0 -380
  165. package/src/core/rewrite-links.js +0 -226
  166. package/src/core/screenshot.js +0 -701
  167. package/src/core/section-detector.js +0 -386
  168. package/src/core/semantic-enhancer.js +0 -492
  169. package/src/core/state-capture.js +0 -598
  170. package/src/core/video-capture.js +0 -546
  171. package/src/utils/__init__.py +0 -16
  172. package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  173. package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
  174. package/src/utils/env.py +0 -134
  175. /package/src/core/{css-extractor.js → css/css-extractor.js} +0 -0
  176. /package/src/core/{cookie-handler.js → page-prep/cookie-handler.js} +0 -0
  177. /package/src/core/{page-readiness.js → page-prep/page-readiness.js} +0 -0
@@ -1,596 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * UX Audit Runner
4
- *
5
- * Analyzes website screenshots using Gemini Vision to assess UX quality.
6
- * Generates detailed reports with scores, issues, and recommendations.
7
- *
8
- * Usage:
9
- * node ux-audit.js --screenshots <dir> [--output <dir>] [--verbose]
10
- *
11
- * Options:
12
- * --screenshots Directory containing viewport screenshots (desktop.png, tablet.png, mobile.png)
13
- * --output Output directory for report (default: same as screenshots)
14
- * --verbose Show detailed progress
15
- * --url Original URL (for report metadata)
16
- */
17
-
18
- import fs from 'fs/promises';
19
- import path from 'path';
20
- import { fileURLToPath } from 'url';
21
-
22
- // API key detection
23
- const GEMINI_API_KEY = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
24
-
25
- // Viewport configurations
26
- const VIEWPORTS = {
27
- desktop: { width: 1920, height: 1080 },
28
- tablet: { width: 768, height: 1024 },
29
- mobile: { width: 375, height: 812 }
30
- };
31
-
32
- // Score weights for aggregation
33
- const VIEWPORT_WEIGHTS = {
34
- desktop: 0.4,
35
- tablet: 0.3,
36
- mobile: 0.3
37
- };
38
-
39
- /**
40
- * Parse CLI arguments
41
- */
42
- function parseArgs(args) {
43
- const options = {
44
- screenshots: null,
45
- output: null,
46
- verbose: false,
47
- url: null
48
- };
49
-
50
- for (let i = 0; i < args.length; i++) {
51
- const arg = args[i];
52
-
53
- if (arg === '--screenshots' && args[i + 1]) {
54
- options.screenshots = args[++i];
55
- } else if (arg === '--output' && args[i + 1]) {
56
- options.output = args[++i];
57
- } else if (arg === '--verbose') {
58
- options.verbose = true;
59
- } else if (arg === '--url' && args[i + 1]) {
60
- options.url = args[++i];
61
- }
62
- }
63
-
64
- return options;
65
- }
66
-
67
- /**
68
- * Build viewport-specific UX audit prompt
69
- *
70
- * Note: This prompt mirrors src/ai/prompts/ux_audit.py for standalone JS execution.
71
- * The Python file is the canonical source for prompt content.
72
- */
73
- function buildUXAuditPrompt(viewport) {
74
- const basePrompt = `Analyze this website screenshot for UX quality.
75
-
76
- Evaluate these categories (score 0-100 each):
77
-
78
- 1. VISUAL HIERARCHY
79
- - Primary content prominence
80
- - Clear scanning patterns (F/Z pattern)
81
- - Call-to-action visibility
82
- - Information grouping and prioritization
83
- - White space utilization
84
-
85
- 2. NAVIGATION
86
- - Tappable area size (44x44px minimum for mobile)
87
- - Current page indicator clarity
88
- - Menu discoverability
89
- - Breadcrumb/location awareness
90
- - Navigation consistency
91
-
92
- 3. TYPOGRAPHY
93
- - Body text size (16px+ recommended)
94
- - Line height (1.4-1.6 ideal)
95
- - Contrast ratio (WCAG AA: 4.5:1 for text)
96
- - Font hierarchy clarity
97
- - Readability at viewport size
98
-
99
- 4. SPACING
100
- - Consistent padding/margins
101
- - Element breathing room
102
- - Touch target spacing (8px minimum between)
103
- - Grid alignment
104
- - Section separation
105
-
106
- 5. INTERACTIVE ELEMENTS
107
- - Button affordance (looks clickable)
108
- - Link distinguishability
109
- - Focus state visibility
110
- - Hover state indication
111
- - Form field clarity
112
-
113
- 6. RESPONSIVE
114
- - Content reflow appropriateness
115
- - No horizontal scroll
116
- - Image scaling quality
117
- - Text truncation handling
118
- - Breakpoint transitions
119
-
120
- Return ONLY valid JSON in this exact format:
121
- {
122
- "viewport": "${viewport}",
123
- "scores": {
124
- "visual_hierarchy": <0-100>,
125
- "navigation": <0-100>,
126
- "typography": <0-100>,
127
- "spacing": <0-100>,
128
- "interactivity": <0-100>,
129
- "responsive": <0-100>
130
- },
131
- "overall_ux_score": <0-100>,
132
- "accessibility_score": <0-100>,
133
- "issues": [
134
- {
135
- "category": "<visual_hierarchy|navigation|typography|spacing|interactivity|responsive>",
136
- "severity": "<critical|major|minor>",
137
- "issue": "<concise description>",
138
- "fix": "<actionable suggestion>"
139
- }
140
- ],
141
- "recommendations": ["<actionable improvement item>"]
142
- }
143
-
144
- SEVERITY GUIDELINES:
145
- - critical: Blocks user tasks or causes confusion (0-30 score range issues)
146
- - major: Degrades experience significantly (31-60 score range issues)
147
- - minor: Polish improvements (61-80 score range issues)`;
148
-
149
- // Add viewport-specific context
150
- const viewportContext = {
151
- mobile: `
152
-
153
- MOBILE-SPECIFIC CHECKS:
154
- - Touch targets minimum 44x44px
155
- - Thumb zone accessibility
156
- - Single-column layout efficiency
157
- - Mobile navigation pattern (hamburger/tab bar)
158
- - Text readable without zooming
159
- - Forms optimized for mobile input`,
160
-
161
- tablet: `
162
-
163
- TABLET-SPECIFIC CHECKS:
164
- - Two-column layout utilization
165
- - Touch and mouse input support
166
- - Landscape/portrait adaptability
167
- - Sidebar vs content balance
168
- - Split-view readiness`,
169
-
170
- desktop: `
171
-
172
- DESKTOP-SPECIFIC CHECKS:
173
- - Maximum content width (1200-1440px ideal)
174
- - Multi-column layout efficiency
175
- - Hover states and micro-interactions
176
- - Keyboard navigation support
177
- - Large screen real estate utilization`
178
- };
179
-
180
- return basePrompt + (viewportContext[viewport] || '');
181
- }
182
-
183
- /**
184
- * Analyze screenshot with Gemini Vision
185
- */
186
- async function analyzeViewport(screenshotPath, viewport, verbose = false) {
187
- if (!GEMINI_API_KEY) {
188
- return {
189
- success: false,
190
- error: 'GEMINI_API_KEY not set',
191
- viewport
192
- };
193
- }
194
-
195
- try {
196
- const { GoogleGenerativeAI } = await import('@google/generative-ai');
197
- const genai = new GoogleGenerativeAI(GEMINI_API_KEY);
198
- const model = genai.getGenerativeModel({ model: 'gemini-2.5-flash' });
199
-
200
- // Read screenshot as base64
201
- const imageBuffer = await fs.readFile(screenshotPath);
202
- const base64 = imageBuffer.toString('base64');
203
-
204
- const prompt = buildUXAuditPrompt(viewport);
205
-
206
- if (verbose) console.error(` Sending to Gemini...`);
207
-
208
- const response = await model.generateContent([
209
- prompt,
210
- {
211
- inlineData: {
212
- mimeType: 'image/png',
213
- data: base64
214
- }
215
- }
216
- ]);
217
-
218
- const text = response.response.text();
219
-
220
- // Extract JSON from response
221
- const jsonMatch = text.match(/\{[\s\S]*\}/);
222
- if (!jsonMatch) {
223
- return {
224
- success: false,
225
- error: 'Could not parse Gemini response',
226
- viewport,
227
- raw: text
228
- };
229
- }
230
-
231
- const result = JSON.parse(jsonMatch[0]);
232
- return {
233
- success: true,
234
- viewport,
235
- ...result
236
- };
237
-
238
- } catch (error) {
239
- return {
240
- success: false,
241
- error: error.message,
242
- viewport
243
- };
244
- }
245
- }
246
-
247
- /**
248
- * Aggregate results from all viewports
249
- */
250
- function aggregateResults(viewportResults) {
251
- const categories = ['visual_hierarchy', 'navigation', 'typography', 'spacing', 'interactivity', 'responsive'];
252
-
253
- const aggregated = {
254
- overall_scores: {},
255
- overall_ux_score: 0,
256
- accessibility_score: 0,
257
- viewport_breakdown: {},
258
- top_issues: [],
259
- prioritized_recommendations: []
260
- };
261
-
262
- // Calculate weighted averages
263
- let totalWeight = 0;
264
- let weightedUxScore = 0;
265
- let weightedAccessScore = 0;
266
-
267
- for (const [viewport, result] of Object.entries(viewportResults)) {
268
- if (!result.success) continue;
269
-
270
- const weight = VIEWPORT_WEIGHTS[viewport] || 0.33;
271
- totalWeight += weight;
272
-
273
- weightedUxScore += (result.overall_ux_score || 0) * weight;
274
- weightedAccessScore += (result.accessibility_score || 0) * weight;
275
-
276
- aggregated.viewport_breakdown[viewport] = result.overall_ux_score || 0;
277
-
278
- // Aggregate category scores
279
- for (const category of categories) {
280
- const score = result.scores?.[category] || 0;
281
- if (!aggregated.overall_scores[category]) {
282
- aggregated.overall_scores[category] = 0;
283
- }
284
- aggregated.overall_scores[category] += score * weight;
285
- }
286
-
287
- // Collect issues
288
- if (result.issues) {
289
- for (const issue of result.issues) {
290
- aggregated.top_issues.push({
291
- ...issue,
292
- viewports_affected: [viewport]
293
- });
294
- }
295
- }
296
-
297
- // Collect recommendations
298
- if (result.recommendations) {
299
- aggregated.prioritized_recommendations.push(...result.recommendations);
300
- }
301
- }
302
-
303
- // Normalize scores
304
- if (totalWeight > 0) {
305
- aggregated.overall_ux_score = Math.round(weightedUxScore / totalWeight);
306
- aggregated.accessibility_score = Math.round(weightedAccessScore / totalWeight);
307
-
308
- for (const category of categories) {
309
- aggregated.overall_scores[category] = Math.round(aggregated.overall_scores[category] / totalWeight);
310
- }
311
- }
312
-
313
- // Sort issues by severity
314
- const severityOrder = { critical: 0, major: 1, minor: 2 };
315
- aggregated.top_issues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
316
-
317
- // Deduplicate recommendations
318
- aggregated.prioritized_recommendations = [...new Set(aggregated.prioritized_recommendations)];
319
-
320
- return aggregated;
321
- }
322
-
323
- /**
324
- * Generate markdown report
325
- */
326
- function generateReport(aggregated, viewportResults, url = null) {
327
- const timestamp = new Date().toISOString();
328
-
329
- let report = `# UX Audit Report
330
-
331
- **Generated:** ${timestamp}
332
- **URL:** ${url || 'N/A'}
333
-
334
- ## Overall Scores
335
-
336
- | Metric | Score |
337
- |--------|-------|
338
- | Overall UX | ${aggregated.overall_ux_score}% |
339
- | Accessibility | ${aggregated.accessibility_score}% |
340
-
341
- ## Category Breakdown
342
-
343
- | Category | Score | Desktop | Tablet | Mobile |
344
- |----------|-------|---------|--------|--------|
345
- `;
346
-
347
- const categories = [
348
- { key: 'visual_hierarchy', name: 'Visual Hierarchy' },
349
- { key: 'navigation', name: 'Navigation' },
350
- { key: 'typography', name: 'Typography' },
351
- { key: 'spacing', name: 'Spacing' },
352
- { key: 'interactivity', name: 'Interactivity' },
353
- { key: 'responsive', name: 'Responsive' }
354
- ];
355
-
356
- for (const cat of categories) {
357
- const overall = aggregated.overall_scores[cat.key] || 0;
358
- const desktop = viewportResults.desktop?.scores?.[cat.key] || '-';
359
- const tablet = viewportResults.tablet?.scores?.[cat.key] || '-';
360
- const mobile = viewportResults.mobile?.scores?.[cat.key] || '-';
361
- const icon = overall >= 80 ? '✅' : overall >= 60 ? '⚠️' : '❌';
362
-
363
- report += `| ${cat.name} | ${icon} ${overall}% | ${desktop}% | ${tablet}% | ${mobile}% |\n`;
364
- }
365
-
366
- // Viewport breakdown
367
- report += `
368
- ## Viewport Scores
369
-
370
- | Viewport | UX Score |
371
- |----------|----------|
372
- `;
373
-
374
- for (const [viewport, score] of Object.entries(aggregated.viewport_breakdown)) {
375
- const icon = score >= 80 ? '✅' : score >= 60 ? '⚠️' : '❌';
376
- report += `| ${viewport.charAt(0).toUpperCase() + viewport.slice(1)} | ${icon} ${score}% |\n`;
377
- }
378
-
379
- // Issues
380
- if (aggregated.top_issues.length > 0) {
381
- report += `
382
- ## Issues Found
383
-
384
- `;
385
-
386
- // Group by severity
387
- const critical = aggregated.top_issues.filter(i => i.severity === 'critical');
388
- const major = aggregated.top_issues.filter(i => i.severity === 'major');
389
- const minor = aggregated.top_issues.filter(i => i.severity === 'minor');
390
-
391
- if (critical.length > 0) {
392
- report += `### Critical Issues 🔴
393
-
394
- `;
395
- for (const issue of critical) {
396
- report += `- **${issue.category}**: ${issue.issue}\n - *Fix:* ${issue.fix}\n - *Viewports:* ${issue.viewports_affected.join(', ')}\n\n`;
397
- }
398
- }
399
-
400
- if (major.length > 0) {
401
- report += `### Major Issues 🟠
402
-
403
- `;
404
- for (const issue of major) {
405
- report += `- **${issue.category}**: ${issue.issue}\n - *Fix:* ${issue.fix}\n - *Viewports:* ${issue.viewports_affected.join(', ')}\n\n`;
406
- }
407
- }
408
-
409
- if (minor.length > 0) {
410
- report += `### Minor Issues 🟡
411
-
412
- `;
413
- for (const issue of minor) {
414
- report += `- **${issue.category}**: ${issue.issue}\n - *Fix:* ${issue.fix}\n - *Viewports:* ${issue.viewports_affected.join(', ')}\n\n`;
415
- }
416
- }
417
- }
418
-
419
- // Recommendations
420
- if (aggregated.prioritized_recommendations.length > 0) {
421
- report += `## Recommendations
422
-
423
- `;
424
- for (const rec of aggregated.prioritized_recommendations) {
425
- report += `- ${rec}\n`;
426
- }
427
- }
428
-
429
- report += `
430
- ---
431
-
432
- *Report generated by design-clone UX Audit*
433
- `;
434
-
435
- return report;
436
- }
437
-
438
- /**
439
- * Run UX audit on screenshots
440
- * @param {Object} screenshotPaths - { desktop: path, tablet: path, mobile: path }
441
- * @param {Object} options - { output, verbose, url }
442
- */
443
- export async function runUXAudit(screenshotPaths, options = {}) {
444
- const { output, verbose = false, url = null } = options;
445
-
446
- if (!GEMINI_API_KEY) {
447
- return {
448
- success: false,
449
- error: 'GEMINI_API_KEY not set. Set environment variable to enable UX audit.'
450
- };
451
- }
452
-
453
- const viewportResults = {};
454
-
455
- // Analyze each viewport
456
- for (const [viewport, screenshotPath] of Object.entries(screenshotPaths)) {
457
- if (!screenshotPath) continue;
458
-
459
- try {
460
- await fs.access(screenshotPath);
461
- } catch {
462
- if (verbose) console.error(` ⚠ Missing ${viewport} screenshot`);
463
- continue;
464
- }
465
-
466
- if (verbose) console.error(` Analyzing ${viewport}...`);
467
-
468
- const result = await analyzeViewport(screenshotPath, viewport, verbose);
469
- viewportResults[viewport] = result;
470
-
471
- if (result.success) {
472
- if (verbose) console.error(` ✓ UX Score: ${result.overall_ux_score}%`);
473
- } else {
474
- if (verbose) console.error(` ✗ Error: ${result.error}`);
475
- }
476
- }
477
-
478
- // Check if we have any results
479
- const successfulResults = Object.values(viewportResults).filter(r => r.success);
480
- if (successfulResults.length === 0) {
481
- return {
482
- success: false,
483
- error: 'No viewport analysis succeeded',
484
- viewportResults
485
- };
486
- }
487
-
488
- // Aggregate results
489
- const aggregated = aggregateResults(viewportResults);
490
-
491
- // Generate report
492
- const report = generateReport(aggregated, viewportResults, url);
493
-
494
- // Write report if output specified
495
- let reportPath = null;
496
- if (output) {
497
- await fs.mkdir(output, { recursive: true });
498
- reportPath = path.join(output, 'ux-audit.md');
499
- await fs.writeFile(reportPath, report);
500
-
501
- // Also save JSON results
502
- const jsonPath = path.join(output, 'ux-audit.json');
503
- await fs.writeFile(jsonPath, JSON.stringify({
504
- aggregated,
505
- viewportResults,
506
- url,
507
- timestamp: new Date().toISOString()
508
- }, null, 2));
509
- }
510
-
511
- return {
512
- success: true,
513
- aggregated,
514
- viewportResults,
515
- report,
516
- reportPath,
517
- summary: {
518
- uxScore: aggregated.overall_ux_score,
519
- accessibilityScore: aggregated.accessibility_score,
520
- issueCount: aggregated.top_issues.length,
521
- criticalCount: aggregated.top_issues.filter(i => i.severity === 'critical').length
522
- }
523
- };
524
- }
525
-
526
- /**
527
- * CLI entry point
528
- */
529
- async function main() {
530
- const args = parseArgs(process.argv.slice(2));
531
-
532
- if (!args.screenshots) {
533
- console.error('Usage: node ux-audit.js --screenshots <dir> [--output <dir>] [--verbose] [--url <url>]');
534
- console.error('\nError: --screenshots is required');
535
- process.exit(1);
536
- }
537
-
538
- const verbose = args.verbose;
539
- const outputDir = args.output || args.screenshots;
540
-
541
- if (verbose) console.error('\n🔍 Starting UX Audit...\n');
542
-
543
- // Find screenshots
544
- const screenshotPaths = {};
545
- for (const viewport of ['desktop', 'tablet', 'mobile']) {
546
- const screenshotPath = path.join(args.screenshots, `${viewport}.png`);
547
- try {
548
- await fs.access(screenshotPath);
549
- screenshotPaths[viewport] = screenshotPath;
550
- if (verbose) console.error(` ✓ Found ${viewport}.png`);
551
- } catch {
552
- if (verbose) console.error(` ⚠ Missing ${viewport}.png`);
553
- }
554
- }
555
-
556
- if (Object.keys(screenshotPaths).length === 0) {
557
- console.error('Error: No screenshots found in directory');
558
- process.exit(1);
559
- }
560
-
561
- // Run audit
562
- const result = await runUXAudit(screenshotPaths, {
563
- output: outputDir,
564
- verbose,
565
- url: args.url
566
- });
567
-
568
- if (!result.success) {
569
- console.error(`\n❌ UX Audit failed: ${result.error}`);
570
- process.exit(1);
571
- }
572
-
573
- if (verbose) {
574
- console.error('\n📊 Summary:');
575
- console.error(` UX Score: ${result.summary.uxScore}%`);
576
- console.error(` Accessibility: ${result.summary.accessibilityScore}%`);
577
- console.error(` Issues: ${result.summary.issueCount} (${result.summary.criticalCount} critical)`);
578
- if (result.reportPath) {
579
- console.error(` Report: ${result.reportPath}`);
580
- }
581
- console.error();
582
- }
583
-
584
- // Output JSON to stdout
585
- console.log(JSON.stringify(result, null, 2));
586
- process.exit(0);
587
- }
588
-
589
- // Run if called directly
590
- const __filename = fileURLToPath(import.meta.url);
591
- if (process.argv[1] === __filename) {
592
- main().catch(err => {
593
- console.error('Fatal error:', err);
594
- process.exit(1);
595
- });
596
- }