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.
- package/README.md +42 -20
- package/SKILL.md +74 -0
- package/bin/commands/clone-site.js +75 -10
- package/bin/commands/init.js +33 -1
- package/bin/commands/verify.js +5 -3
- package/bin/utils/validate.js +24 -8
- package/docs/cli-reference.md +224 -2
- package/docs/codebase-summary.md +309 -0
- package/docs/design-clone-architecture.md +290 -45
- package/docs/pixel-perfect.md +35 -4
- package/docs/project-roadmap.md +382 -0
- package/docs/troubleshooting.md +5 -4
- package/package.json +12 -6
- package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
- package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
- package/src/ai/analyze-structure.py +73 -3
- package/src/ai/extract-design-tokens.py +356 -13
- package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
- package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
- package/src/ai/prompts/design_tokens.py +133 -0
- package/src/ai/prompts/structure_analysis.py +329 -10
- package/src/ai/prompts/ux_audit.py +198 -0
- package/src/ai/ux-audit.js +596 -0
- package/src/core/animation-extractor.js +526 -0
- package/src/core/app-state-snapshot.js +511 -0
- package/src/core/content-counter.js +342 -0
- package/src/core/cookie-handler.js +1 -1
- package/src/core/css-extractor.js +4 -4
- package/src/core/dimension-extractor.js +93 -21
- package/src/core/dimension-output.js +103 -6
- package/src/core/discover-pages.js +242 -14
- package/src/core/dom-tree-analyzer.js +298 -0
- package/src/core/extract-assets.js +1 -1
- package/src/core/framework-detector.js +538 -0
- package/src/core/html-extractor.js +45 -4
- package/src/core/lazy-loader.js +7 -7
- package/src/core/multi-page-screenshot.js +9 -6
- package/src/core/page-readiness.js +8 -8
- package/src/core/screenshot.js +311 -7
- package/src/core/section-cropper.js +209 -0
- package/src/core/section-detector.js +386 -0
- package/src/core/semantic-enhancer.js +492 -0
- package/src/core/state-capture.js +598 -0
- package/src/core/tests/test-section-cropper.js +177 -0
- package/src/core/tests/test-section-detector.js +55 -0
- package/src/core/video-capture.js +546 -0
- package/src/route-discoverers/angular-discoverer.js +157 -0
- package/src/route-discoverers/astro-discoverer.js +123 -0
- package/src/route-discoverers/base-discoverer.js +242 -0
- package/src/route-discoverers/index.js +106 -0
- package/src/route-discoverers/next-discoverer.js +130 -0
- package/src/route-discoverers/nuxt-discoverer.js +138 -0
- package/src/route-discoverers/react-discoverer.js +139 -0
- package/src/route-discoverers/svelte-discoverer.js +109 -0
- package/src/route-discoverers/universal-discoverer.js +227 -0
- package/src/route-discoverers/vue-discoverer.js +118 -0
- package/src/utils/__init__.py +1 -1
- package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
- package/src/utils/browser.js +11 -37
- package/src/utils/playwright.js +213 -0
- package/src/verification/generate-audit-report.js +398 -0
- package/src/verification/verify-footer.js +493 -0
- package/src/verification/verify-header.js +486 -0
- package/src/verification/verify-layout.js +2 -2
- package/src/verification/verify-menu.js +4 -20
- package/src/verification/verify-slider.js +533 -0
- 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
|
-
|
|
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 -
|
|
228
|
-
2. With
|
|
229
|
-
3.
|
|
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
|
-
#
|
|
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
|
+
)
|