design-clone 1.1.1 → 2.1.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 (70) hide show
  1. package/README.md +42 -20
  2. package/SKILL.md +74 -0
  3. package/bin/commands/clone-site.js +75 -10
  4. package/bin/commands/init.js +33 -1
  5. package/bin/commands/verify.js +5 -3
  6. package/bin/utils/validate.js +24 -8
  7. package/docs/cli-reference.md +224 -2
  8. package/docs/codebase-summary.md +309 -0
  9. package/docs/design-clone-architecture.md +290 -45
  10. package/docs/pixel-perfect.md +35 -4
  11. package/docs/project-roadmap.md +382 -0
  12. package/docs/troubleshooting.md +5 -4
  13. package/package.json +12 -6
  14. package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
  15. package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
  16. package/src/ai/analyze-structure.py +73 -3
  17. package/src/ai/extract-design-tokens.py +356 -13
  18. package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  19. package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
  20. package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
  21. package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
  22. package/src/ai/prompts/design_tokens.py +133 -0
  23. package/src/ai/prompts/structure_analysis.py +329 -10
  24. package/src/ai/prompts/ux_audit.py +198 -0
  25. package/src/ai/ux-audit.js +596 -0
  26. package/src/core/animation-extractor.js +526 -0
  27. package/src/core/app-state-snapshot.js +511 -0
  28. package/src/core/content-counter.js +342 -0
  29. package/src/core/cookie-handler.js +1 -1
  30. package/src/core/css-extractor.js +4 -4
  31. package/src/core/dimension-extractor.js +93 -21
  32. package/src/core/dimension-output.js +103 -6
  33. package/src/core/discover-pages.js +242 -14
  34. package/src/core/dom-tree-analyzer.js +298 -0
  35. package/src/core/extract-assets.js +1 -1
  36. package/src/core/framework-detector.js +538 -0
  37. package/src/core/html-extractor.js +45 -4
  38. package/src/core/lazy-loader.js +7 -7
  39. package/src/core/multi-page-screenshot.js +9 -6
  40. package/src/core/page-readiness.js +8 -8
  41. package/src/core/screenshot.js +311 -7
  42. package/src/core/section-cropper.js +209 -0
  43. package/src/core/section-detector.js +386 -0
  44. package/src/core/semantic-enhancer.js +492 -0
  45. package/src/core/state-capture.js +598 -0
  46. package/src/core/tests/test-section-cropper.js +177 -0
  47. package/src/core/tests/test-section-detector.js +55 -0
  48. package/src/core/video-capture.js +546 -0
  49. package/src/route-discoverers/angular-discoverer.js +157 -0
  50. package/src/route-discoverers/astro-discoverer.js +123 -0
  51. package/src/route-discoverers/base-discoverer.js +242 -0
  52. package/src/route-discoverers/index.js +106 -0
  53. package/src/route-discoverers/next-discoverer.js +130 -0
  54. package/src/route-discoverers/nuxt-discoverer.js +138 -0
  55. package/src/route-discoverers/react-discoverer.js +139 -0
  56. package/src/route-discoverers/svelte-discoverer.js +109 -0
  57. package/src/route-discoverers/universal-discoverer.js +227 -0
  58. package/src/route-discoverers/vue-discoverer.js +118 -0
  59. package/src/utils/__init__.py +1 -1
  60. package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  61. package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
  62. package/src/utils/browser.js +11 -37
  63. package/src/utils/playwright.js +213 -0
  64. package/src/verification/generate-audit-report.js +398 -0
  65. package/src/verification/verify-footer.js +493 -0
  66. package/src/verification/verify-header.js +486 -0
  67. package/src/verification/verify-layout.js +2 -2
  68. package/src/verification/verify-menu.js +4 -20
  69. package/src/verification/verify-slider.js +533 -0
  70. package/src/utils/puppeteer.js +0 -281
@@ -220,16 +220,335 @@ For each section describe:
220
220
  [Suggest semantic class names for main components]"""
221
221
 
222
222
 
223
- def build_structure_prompt(html_content: str = None, css_content: str = None, dimensions: dict = None) -> str:
223
+ # Enhanced prompt when DOM hierarchy is available (highest accuracy)
224
+ STRUCTURE_PROMPT_WITH_HIERARCHY = """Analyze this website screenshot using the EXACT extracted dimensions and DOM hierarchy below.
225
+
226
+ ## CRITICAL INSTRUCTIONS
227
+ 1. USE ONLY the exact values provided - DO NOT estimate
228
+ 2. All measurements are from actual DOM via getBoundingClientRect + getComputedStyle
229
+ 3. Typography varies BY SECTION - use section-specific values
230
+ 4. Reference DOM hierarchy for nesting structure
231
+
232
+ ## EXTRACTED DOM HIERARCHY
233
+
234
+ ### Landmarks Found
235
+ - Header: {header_found}
236
+ - Main content: {main_found}
237
+ - Footer: {footer_found}
238
+ - Sidebars: {sidebar_count}
239
+ - Nav elements: {nav_count}
240
+
241
+ ### Heading Hierarchy (by section)
242
+ {heading_hierarchy}
243
+
244
+ ### Section Structure
245
+ {section_structure}
246
+
247
+ ## EXACT DIMENSIONS
248
+
249
+ ### Container Layout
250
+ - Max container width: {container_max_width}
251
+ - Section padding: {section_padding}
252
+ - Element gap: {gap}
253
+
254
+ ### Cards
255
+ - Card width: {card_width}
256
+ - Card height: {card_height}
257
+ - Card padding: {card_padding}
258
+
259
+ ### Typography BY SECTION (use these exact values per context)
260
+
261
+ #### Hero Section
262
+ - H1: {hero_h1}
263
+ - H2: {hero_h2}
264
+ - Body: {hero_body}
265
+
266
+ #### Content Section
267
+ - H2: {content_h2}
268
+ - H3: {content_h3}
269
+ - Body: {content_body}
270
+
271
+ #### Footer Section
272
+ - Body: {footer_body}
273
+
274
+ ### Responsive Breakpoints
275
+ - Desktop: {desktop_breakpoint}
276
+ - Tablet: {tablet_breakpoint}
277
+ - Mobile: {mobile_breakpoint}
278
+
279
+ ### Typography Scaling
280
+ - H1: {hero_h1} → {h1_tablet} (tablet) → {h1_mobile} (mobile)
281
+ - H2: {content_h2} → {h2_tablet} (tablet) → {h2_mobile} (mobile)
282
+
283
+ ---
284
+
285
+ Output a markdown document following this structure.
286
+ IMPORTANT: Use section-specific typography values. Hero H1 differs from Content H1.
287
+
288
+ # Page Structure Analysis
289
+
290
+ ## 1. Header Section
291
+ - Logo: [describe position and layout]
292
+ - Navigation: [count items from hierarchy]
293
+ - CTA: [if present]
294
+
295
+ ## 2. Hero Section
296
+ - Layout: [from section structure]
297
+ - Headline: font-size {hero_h1} (EXACT)
298
+ - Subheadline: [if present]
299
+ - CTA: [button description]
300
+
301
+ ## 3. Content Sections
302
+ For each section in the hierarchy:
303
+ - Heading sizes: Use CONTENT section typography ({content_h2}, {content_h3})
304
+ - Layout: Reference section structure
305
+ - Card dimensions: {card_width} x {card_height} with gap {gap}
306
+ - Components: [describe]
307
+
308
+ ## 4. Footer Section
309
+ - Layout: [from hierarchy]
310
+ - Typography: {footer_body} body text
311
+
312
+ ## 5. EXACT CSS Values (DO NOT MODIFY)
313
+ ### Layout
314
+ - Container max-width: {container_max_width}
315
+ - Section padding: {section_padding}
316
+ - Card dimensions: {card_width} x {card_height}
317
+ - Card padding: {card_padding}
318
+ - Gap: {gap}
319
+
320
+ ### Typography per Section
321
+ - Hero H1: {hero_h1}
322
+ - Hero H2: {hero_h2}
323
+ - Content H2: {content_h2}
324
+ - Content H3: {content_h3}
325
+ - Content Body: {content_body}
326
+ - Footer Body: {footer_body}
327
+
328
+ ### Breakpoints
329
+ - Desktop: {desktop_breakpoint}
330
+ - Tablet: {tablet_breakpoint}
331
+ - Mobile: {mobile_breakpoint}
332
+
333
+ ## 6. Responsive Behavior
334
+ - At {tablet_breakpoint}: [describe layout changes]
335
+ - At {mobile_breakpoint}: [describe layout changes]
336
+ - Typography scales: H1 {hero_h1} → {h1_tablet} → {h1_mobile}
337
+
338
+ ## 7. DOM Nesting Structure
339
+ Reproduce this exact nesting in generated HTML:
340
+ {dom_nesting}
341
+
342
+ ## 8. BEM Class Suggestions
343
+ [Based on hierarchy, suggest semantic names]"""
344
+
345
+
346
+ def format_heading_hierarchy(heading_tree: list) -> str:
347
+ """Format heading tree for prompt.
348
+
349
+ Args:
350
+ heading_tree: List of heading objects with level, section, text
351
+
352
+ Returns:
353
+ Formatted string showing heading hierarchy
354
+ """
355
+ if not heading_tree:
356
+ return "No headings detected"
357
+
358
+ lines = []
359
+ for h in heading_tree[:10]: # Limit for token efficiency
360
+ indent = ' ' * (h.get('level', 1) - 1)
361
+ section = h.get('section', 'content')
362
+ text = h.get('text', '')[:30] if h.get('text') else ''
363
+ lines.append(f"{indent}- H{h.get('level', 1)} ({section}): \"{text}...\"")
364
+
365
+ return '\n'.join(lines)
366
+
367
+
368
+ def format_section_structure(landmarks: dict, sections: dict) -> str:
369
+ """Format section structure for prompt.
370
+
371
+ Args:
372
+ landmarks: W3C landmarks from DOM hierarchy
373
+ sections: Section info from dimensions
374
+
375
+ Returns:
376
+ Formatted string showing section structure
377
+ """
378
+ lines = []
379
+
380
+ if landmarks.get('header'):
381
+ lines.append("- Header: Present (semantic <header>)")
382
+ if sections.get('hero', {}).get('found'):
383
+ width = sections['hero'].get('containerWidth', 'unknown')
384
+ lines.append(f"- Hero: Present (container width: {width}px)")
385
+ if landmarks.get('main'):
386
+ lines.append("- Main: Present (semantic <main>)")
387
+ if sections.get('content', {}).get('found'):
388
+ width = sections['content'].get('containerWidth', 'unknown')
389
+ lines.append(f"- Content: Present (container width: {width}px)")
390
+ if landmarks.get('aside'):
391
+ lines.append(f"- Sidebars: {len(landmarks['aside'])} detected")
392
+ if landmarks.get('footer'):
393
+ lines.append("- Footer: Present (semantic <footer>)")
394
+
395
+ return '\n'.join(lines) if lines else "No sections detected"
396
+
397
+
398
+ def format_dom_nesting(root: dict, max_depth: int = 4) -> str:
399
+ """Format simplified DOM nesting for prompt.
400
+
401
+ Args:
402
+ root: Root node from DOM hierarchy
403
+ max_depth: Maximum depth to traverse
404
+
405
+ Returns:
406
+ Formatted string showing DOM nesting structure
407
+ """
408
+ if not root:
409
+ return "No DOM structure available"
410
+
411
+ lines = []
412
+
413
+ def walk(node, depth):
414
+ if depth > max_depth or not node:
415
+ return
416
+ if not node.get('role'):
417
+ # Skip non-semantic nodes in output
418
+ for child in node.get('children', [])[:5]:
419
+ walk(child, depth)
420
+ return
421
+
422
+ indent = ' ' * depth
423
+ tag = node.get('tagName', 'div')
424
+ role = node.get('role', '')
425
+ lines.append(f"{indent}<{tag}> <!-- {role} -->")
426
+
427
+ for child in node.get('children', [])[:5]: # Limit children
428
+ walk(child, depth + 1)
429
+
430
+ walk(root, 0)
431
+ return '\n'.join(lines[:30]) # Limit total lines
432
+
433
+
434
+ def build_hierarchy_prompt(dimensions: dict, hierarchy: dict) -> str:
435
+ """Build prompt with DOM hierarchy context.
436
+
437
+ Args:
438
+ dimensions: Extracted dimensions summary
439
+ hierarchy: DOM hierarchy data
440
+
441
+ Returns:
442
+ Formatted prompt string with hierarchy context
443
+ """
444
+ exact = dimensions.get('EXACT_DIMENSIONS', {})
445
+ typo = dimensions.get('EXACT_TYPOGRAPHY', {})
446
+ typo_by_section = dimensions.get('TYPOGRAPHY_BY_SECTION', {})
447
+ resp = dimensions.get('RESPONSIVE', {})
448
+ sections = dimensions.get('SECTIONS', {})
449
+ card = exact.get('card_dimensions', {})
450
+ scaling = resp.get('typography_scaling', {})
451
+
452
+ # Extract hierarchy data
453
+ landmarks = hierarchy.get('landmarks', {})
454
+ heading_tree = hierarchy.get('headingTree', [])
455
+
456
+ # Format heading hierarchy
457
+ heading_hierarchy = format_heading_hierarchy(heading_tree)
458
+
459
+ # Format section structure
460
+ section_structure = format_section_structure(landmarks, sections)
461
+
462
+ # Format DOM nesting (simplified)
463
+ dom_nesting = format_dom_nesting(hierarchy.get('root'))
464
+
465
+ # Get section-specific typography
466
+ hero_typo = typo_by_section.get('hero', {})
467
+ content_typo = typo_by_section.get('content', {})
468
+ footer_typo = typo_by_section.get('footer', {})
469
+
470
+ return STRUCTURE_PROMPT_WITH_HIERARCHY.format(
471
+ # Landmarks
472
+ header_found='Yes' if landmarks.get('header') else 'No',
473
+ main_found='Yes' if landmarks.get('main') else 'No',
474
+ footer_found='Yes' if landmarks.get('footer') else 'No',
475
+ sidebar_count=len(landmarks.get('aside', [])),
476
+ nav_count=len(landmarks.get('nav', [])),
477
+
478
+ # Heading hierarchy
479
+ heading_hierarchy=heading_hierarchy,
480
+ section_structure=section_structure,
481
+
482
+ # Dimensions
483
+ container_max_width=exact.get('container_max_width', '1200px'),
484
+ section_padding=exact.get('section_padding', '64px 0'),
485
+ gap=exact.get('gap', '24px'),
486
+ card_width=card.get('width', '380px') if isinstance(card, dict) else '380px',
487
+ card_height=card.get('height', 'auto') if isinstance(card, dict) else 'auto',
488
+ card_padding=card.get('padding', '24px') if isinstance(card, dict) else '24px',
489
+
490
+ # Typography by section
491
+ hero_h1=hero_typo.get('h1', typo.get('h1', '64px')),
492
+ hero_h2=hero_typo.get('h2', typo.get('h2', '48px')),
493
+ hero_body=hero_typo.get('p', typo.get('body', '18px')),
494
+ content_h2=content_typo.get('h2', typo.get('h2', '32px')),
495
+ content_h3=content_typo.get('h3', typo.get('h3', '24px')),
496
+ content_body=content_typo.get('p', typo.get('body', '16px')),
497
+ footer_body=footer_typo.get('p', '14px'),
498
+
499
+ # Breakpoints
500
+ desktop_breakpoint=resp.get('desktop_breakpoint', '1440px'),
501
+ tablet_breakpoint=resp.get('tablet_breakpoint', '768px'),
502
+ mobile_breakpoint=resp.get('mobile_breakpoint', '375px'),
503
+
504
+ # Typography scaling
505
+ h1_tablet=scaling.get('h1', {}).get('tablet', '36px') if isinstance(scaling.get('h1'), dict) else '36px',
506
+ h1_mobile=scaling.get('h1', {}).get('mobile', '28px') if isinstance(scaling.get('h1'), dict) else '28px',
507
+ h2_tablet=scaling.get('h2', {}).get('tablet', '28px') if isinstance(scaling.get('h2'), dict) else '28px',
508
+ h2_mobile=scaling.get('h2', {}).get('mobile', '24px') if isinstance(scaling.get('h2'), dict) else '24px',
509
+
510
+ # DOM nesting
511
+ dom_nesting=dom_nesting
512
+ )
513
+
514
+
515
+ def build_structure_prompt(
516
+ html_content: str = None,
517
+ css_content: str = None,
518
+ dimensions: dict = None,
519
+ content_summary: str = None,
520
+ hierarchy: dict = None
521
+ ) -> str:
224
522
  """Build the appropriate prompt based on available context.
225
523
 
226
524
  Priority:
227
- 1. With dimensions (most accurate - uses exact extracted values)
228
- 2. With HTML/CSS (accurate - extracts from source)
229
- 3. Screenshot only (least accurate - estimates)
525
+ 1. With hierarchy + dimensions (most accurate - full DOM context)
526
+ 2. With dimensions (accurate - uses exact extracted values)
527
+ 3. With HTML/CSS (accurate - extracts from source)
528
+ 4. Screenshot only (least accurate - estimates)
529
+
530
+ Args:
531
+ html_content: Source HTML (optional)
532
+ css_content: Source CSS (optional)
533
+ dimensions: Extracted dimensions summary (optional)
534
+ content_summary: Content counts markdown (optional)
535
+ hierarchy: DOM hierarchy data (optional)
536
+
537
+ Returns:
538
+ Appropriate prompt string based on available context
230
539
  """
231
540
 
232
- # Priority 1: Use dimensions if available (highest accuracy)
541
+ # Helper to append content summary
542
+ def append_content_counts(prompt: str) -> str:
543
+ if content_summary:
544
+ return prompt + "\n\n---\n\n" + content_summary + "\n\nIMPORTANT: Use the EXACT item counts above when describing sections. Do NOT estimate."
545
+ return prompt
546
+
547
+ # Priority 1: Use hierarchy + dimensions if available (highest accuracy)
548
+ if hierarchy and dimensions:
549
+ return append_content_counts(build_hierarchy_prompt(dimensions, hierarchy))
550
+
551
+ # Priority 2: Use dimensions if available
233
552
  if dimensions:
234
553
  exact = dimensions.get('EXACT_DIMENSIONS', {})
235
554
  typo = dimensions.get('EXACT_TYPOGRAPHY', {})
@@ -237,7 +556,7 @@ def build_structure_prompt(html_content: str = None, css_content: str = None, di
237
556
  card = exact.get('card_dimensions', {})
238
557
  scaling = resp.get('typography_scaling', {})
239
558
 
240
- return STRUCTURE_PROMPT_WITH_DIMENSIONS.format(
559
+ return append_content_counts(STRUCTURE_PROMPT_WITH_DIMENSIONS.format(
241
560
  container_max_width=exact.get('container_max_width', '1200px'),
242
561
  section_padding=exact.get('section_padding', '64px 0'),
243
562
  gap=exact.get('gap', '24px'),
@@ -255,7 +574,7 @@ def build_structure_prompt(html_content: str = None, css_content: str = None, di
255
574
  h1_mobile=scaling.get('h1', {}).get('mobile', '28px') if isinstance(scaling.get('h1'), dict) else '28px',
256
575
  h2_tablet=scaling.get('h2', {}).get('tablet', '28px') if isinstance(scaling.get('h2'), dict) else '28px',
257
576
  h2_mobile=scaling.get('h2', {}).get('mobile', '24px') if isinstance(scaling.get('h2'), dict) else '24px'
258
- )
577
+ ))
259
578
 
260
579
  # Priority 2: Use HTML/CSS if available
261
580
  if html_content and css_content:
@@ -264,10 +583,10 @@ def build_structure_prompt(html_content: str = None, css_content: str = None, di
264
583
  html_snippet = html_content[:100000] if len(html_content) > 100000 else html_content
265
584
  css_snippet = css_content[:100000] if len(css_content) > 100000 else css_content
266
585
 
267
- return STRUCTURE_PROMPT_WITH_CONTEXT.format(
586
+ return append_content_counts(STRUCTURE_PROMPT_WITH_CONTEXT.format(
268
587
  html_snippet=html_snippet,
269
588
  css_snippet=css_snippet
270
- )
589
+ ))
271
590
 
272
591
  # Priority 3: Screenshot only (fallback)
273
- return STRUCTURE_PROMPT
592
+ return append_content_counts(STRUCTURE_PROMPT)
@@ -0,0 +1,198 @@
1
+ """
2
+ UX Audit Prompts
3
+
4
+ Prompts for analyzing screenshots with Gemini Vision for UX quality assessment.
5
+ Evaluates visual hierarchy, navigation, typography, spacing, interactivity, and responsive design.
6
+ """
7
+
8
+ # Main UX audit prompt
9
+ UX_AUDIT_PROMPT = """Analyze this website screenshot for UX quality.
10
+
11
+ Evaluate these categories (score 0-100 each):
12
+
13
+ 1. VISUAL HIERARCHY
14
+ - Primary content prominence
15
+ - Clear scanning patterns (F/Z pattern)
16
+ - Call-to-action visibility
17
+ - Information grouping and prioritization
18
+ - White space utilization
19
+
20
+ 2. NAVIGATION
21
+ - Tappable area size (44x44px minimum for mobile)
22
+ - Current page indicator clarity
23
+ - Menu discoverability
24
+ - Breadcrumb/location awareness
25
+ - Navigation consistency
26
+
27
+ 3. TYPOGRAPHY
28
+ - Body text size (16px+ recommended)
29
+ - Line height (1.4-1.6 ideal)
30
+ - Contrast ratio (WCAG AA: 4.5:1 for text)
31
+ - Font hierarchy clarity
32
+ - Readability at viewport size
33
+
34
+ 4. SPACING
35
+ - Consistent padding/margins
36
+ - Element breathing room
37
+ - Touch target spacing (8px minimum between)
38
+ - Grid alignment
39
+ - Section separation
40
+
41
+ 5. INTERACTIVE ELEMENTS
42
+ - Button affordance (looks clickable)
43
+ - Link distinguishability
44
+ - Focus state visibility
45
+ - Hover state indication
46
+ - Form field clarity
47
+
48
+ 6. RESPONSIVE
49
+ - Content reflow appropriateness
50
+ - No horizontal scroll
51
+ - Image scaling quality
52
+ - Text truncation handling
53
+ - Breakpoint transitions
54
+
55
+ Return ONLY valid JSON in this exact format:
56
+ {
57
+ "viewport": "<mobile|tablet|desktop>",
58
+ "scores": {
59
+ "visual_hierarchy": <0-100>,
60
+ "navigation": <0-100>,
61
+ "typography": <0-100>,
62
+ "spacing": <0-100>,
63
+ "interactivity": <0-100>,
64
+ "responsive": <0-100>
65
+ },
66
+ "overall_ux_score": <0-100>,
67
+ "accessibility_score": <0-100>,
68
+ "issues": [
69
+ {
70
+ "category": "<visual_hierarchy|navigation|typography|spacing|interactivity|responsive>",
71
+ "severity": "<critical|major|minor>",
72
+ "issue": "<concise description>",
73
+ "fix": "<actionable suggestion>"
74
+ }
75
+ ],
76
+ "recommendations": ["<actionable improvement item>"]
77
+ }
78
+
79
+ SEVERITY GUIDELINES:
80
+ - critical: Blocks user tasks or causes confusion (0-30 score range issues)
81
+ - major: Degrades experience significantly (31-60 score range issues)
82
+ - minor: Polish improvements (61-80 score range issues)
83
+
84
+ SCORING GUIDELINES:
85
+ - 90-100: Excellent, industry-leading UX
86
+ - 70-89: Good, meets modern standards
87
+ - 50-69: Adequate, room for improvement
88
+ - 30-49: Poor, significant issues
89
+ - 0-29: Critical, requires immediate attention"""
90
+
91
+
92
+ # Viewport-specific prompt suffix
93
+ VIEWPORT_CONTEXT = {
94
+ 'mobile': """
95
+ MOBILE-SPECIFIC CHECKS:
96
+ - Touch targets minimum 44x44px
97
+ - Thumb zone accessibility
98
+ - Single-column layout efficiency
99
+ - Mobile navigation pattern (hamburger/tab bar)
100
+ - Text readable without zooming
101
+ - Forms optimized for mobile input""",
102
+
103
+ 'tablet': """
104
+ TABLET-SPECIFIC CHECKS:
105
+ - Two-column layout utilization
106
+ - Touch and mouse input support
107
+ - Landscape/portrait adaptability
108
+ - Sidebar vs content balance
109
+ - Split-view readiness""",
110
+
111
+ 'desktop': """
112
+ DESKTOP-SPECIFIC CHECKS:
113
+ - Maximum content width (1200-1440px ideal)
114
+ - Multi-column layout efficiency
115
+ - Hover states and micro-interactions
116
+ - Keyboard navigation support
117
+ - Large screen real estate utilization"""
118
+ }
119
+
120
+
121
+ def build_ux_audit_prompt(viewport: str = 'desktop') -> str:
122
+ """Build viewport-specific UX audit prompt.
123
+
124
+ Args:
125
+ viewport: 'mobile', 'tablet', or 'desktop'
126
+
127
+ Returns:
128
+ Complete prompt string with viewport-specific checks
129
+ """
130
+ base_prompt = UX_AUDIT_PROMPT
131
+
132
+ viewport_lower = viewport.lower()
133
+ if viewport_lower in VIEWPORT_CONTEXT:
134
+ base_prompt += f"\n\n{VIEWPORT_CONTEXT[viewport_lower]}"
135
+
136
+ return base_prompt
137
+
138
+
139
+ # Aggregation prompt for combining viewport results
140
+ AGGREGATION_PROMPT = """Combine these viewport-specific UX audit results into a final assessment.
141
+
142
+ Desktop results: {desktop_results}
143
+ Tablet results: {tablet_results}
144
+ Mobile results: {mobile_results}
145
+
146
+ Create a unified report that:
147
+ 1. Averages scores across viewports (weighted: desktop 40%, tablet 30%, mobile 30%)
148
+ 2. Prioritizes issues by severity and viewport impact
149
+ 3. Consolidates duplicate issues
150
+ 4. Ranks recommendations by impact
151
+
152
+ Return JSON:
153
+ {
154
+ "overall_scores": {
155
+ "visual_hierarchy": <weighted_avg>,
156
+ "navigation": <weighted_avg>,
157
+ "typography": <weighted_avg>,
158
+ "spacing": <weighted_avg>,
159
+ "interactivity": <weighted_avg>,
160
+ "responsive": <weighted_avg>
161
+ },
162
+ "overall_ux_score": <weighted_avg>,
163
+ "accessibility_score": <weighted_avg>,
164
+ "viewport_breakdown": {
165
+ "desktop": <ux_score>,
166
+ "tablet": <ux_score>,
167
+ "mobile": <ux_score>
168
+ },
169
+ "top_issues": [
170
+ {
171
+ "category": "<category>",
172
+ "severity": "<severity>",
173
+ "issue": "<description>",
174
+ "fix": "<suggestion>",
175
+ "viewports_affected": ["<viewport>"]
176
+ }
177
+ ],
178
+ "prioritized_recommendations": ["<ranked by impact>"]
179
+ }"""
180
+
181
+
182
+ def build_aggregation_prompt(desktop: dict, tablet: dict, mobile: dict) -> str:
183
+ """Build prompt for aggregating viewport results.
184
+
185
+ Args:
186
+ desktop: Desktop viewport audit results
187
+ tablet: Tablet viewport audit results
188
+ mobile: Mobile viewport audit results
189
+
190
+ Returns:
191
+ Aggregation prompt with results embedded
192
+ """
193
+ import json
194
+ return AGGREGATION_PROMPT.format(
195
+ desktop_results=json.dumps(desktop, indent=2) if desktop else "Not available",
196
+ tablet_results=json.dumps(tablet, indent=2) if tablet else "Not available",
197
+ mobile_results=json.dumps(mobile, indent=2) if mobile else "Not available"
198
+ )