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.
- package/README.md +13 -34
- package/SKILL.md +69 -45
- package/bin/cli.js +22 -4
- package/bin/commands/clone-site.js +31 -171
- package/bin/commands/help.js +19 -6
- package/bin/commands/init.js +9 -86
- package/bin/commands/uninstall.js +105 -0
- package/bin/commands/update.js +70 -0
- package/bin/commands/verify.js +7 -14
- package/bin/utils/paths.js +28 -0
- package/bin/utils/validate.js +2 -22
- package/bin/utils/version.js +23 -0
- package/docs/code-standards.md +789 -0
- package/docs/codebase-summary.md +533 -286
- package/docs/index.md +74 -0
- package/docs/project-overview-pdr.md +797 -0
- package/docs/system-architecture.md +718 -0
- package/package.json +14 -17
- package/src/ai/prompts/design-tokens/basic.md +80 -0
- package/src/ai/prompts/design-tokens/section-with-css.md +41 -0
- package/src/ai/prompts/design-tokens/section.md +48 -0
- package/src/ai/prompts/design-tokens/with-css.md +87 -0
- package/src/ai/prompts/structure-analysis/basic.md +55 -0
- package/src/ai/prompts/structure-analysis/with-context.md +59 -0
- package/src/ai/prompts/structure-analysis/with-dimensions.md +63 -0
- package/src/ai/prompts/structure-analysis/with-hierarchy.md +73 -0
- package/src/ai/prompts/ux-audit/aggregation.md +42 -0
- package/src/ai/prompts/ux-audit/desktop.md +92 -0
- package/src/ai/prompts/ux-audit/mobile.md +93 -0
- package/src/ai/prompts/ux-audit/tablet.md +92 -0
- package/src/core/animation/animation-extractor-ast.js +183 -0
- package/src/core/animation/animation-extractor-output.js +152 -0
- package/src/core/animation/animation-extractor.js +178 -0
- package/src/core/animation/state-capture-detection.js +200 -0
- package/src/core/animation/state-capture.js +193 -0
- package/src/core/capture/browser-context-pool.js +96 -0
- package/src/core/capture/multi-page-screenshot-page.js +110 -0
- package/src/core/capture/multi-page-screenshot.js +208 -0
- package/src/core/capture/screenshot-extraction.js +186 -0
- package/src/core/capture/screenshot-helpers.js +175 -0
- package/src/core/capture/screenshot-orchestrator.js +174 -0
- package/src/core/capture/screenshot-viewport.js +93 -0
- package/src/core/capture/screenshot.js +192 -0
- package/src/core/content/content-counter-dom.js +191 -0
- package/src/core/content/content-counter.js +76 -0
- package/src/core/css/breakpoint-detector.js +66 -0
- package/src/core/css/chromium-defaults.json +23 -0
- package/src/core/css/computed-style-extractor.js +102 -0
- package/src/core/css/css-chunker.js +103 -0
- package/src/core/css/filter-css-dead-code.js +120 -0
- package/src/core/css/filter-css-html-analyzer.js +110 -0
- package/src/core/css/filter-css-selector-matcher.js +172 -0
- package/src/core/css/filter-css.js +206 -0
- package/src/core/css/merge-css-atrule-processor.js +158 -0
- package/src/core/css/merge-css-file-io.js +68 -0
- package/src/core/css/merge-css.js +148 -0
- package/src/core/detection/framework-detector-routing.js +68 -0
- package/src/core/detection/framework-detector-signals.js +65 -0
- package/src/core/detection/framework-detector.js +198 -0
- package/src/core/dimension/dimension-extractor-card-detector.js +82 -0
- package/src/core/dimension/dimension-extractor.js +317 -0
- package/src/core/dimension/dimension-output-ai-summary.js +111 -0
- package/src/core/dimension/dimension-output.js +173 -0
- package/src/core/dimension/dom-tree-analyzer-tree-builders.js +95 -0
- package/src/core/dimension/dom-tree-analyzer.js +191 -0
- package/src/core/discovery/app-state-snapshot-capture.js +195 -0
- package/src/core/discovery/app-state-snapshot-utils.js +178 -0
- package/src/core/discovery/app-state-snapshot.js +131 -0
- package/src/core/discovery/discover-pages-routes.js +84 -0
- package/src/core/discovery/discover-pages-utils.js +177 -0
- package/src/core/discovery/discover-pages.js +191 -0
- package/src/core/html/html-extractor-inline-styler.js +70 -0
- package/src/core/html/html-extractor.js +147 -0
- package/src/core/html/semantic-enhancer-mappings.js +200 -0
- package/src/core/html/semantic-enhancer-page.js +148 -0
- package/src/core/html/semantic-enhancer.js +135 -0
- package/src/core/links/rewrite-links-css-rewriter.js +53 -0
- package/src/core/links/rewrite-links.js +173 -0
- package/src/core/media/asset-validator.js +118 -0
- package/src/core/media/extract-assets-downloader.js +187 -0
- package/src/core/media/extract-assets-page-scraper.js +115 -0
- package/src/core/media/extract-assets.js +159 -0
- package/src/core/media/video-capture-convert.js +200 -0
- package/src/core/media/video-capture.js +201 -0
- package/src/core/{lazy-loader.js → page-prep/lazy-loader.js} +37 -39
- package/src/core/section/section-cropper-helpers.js +43 -0
- package/src/core/{section-cropper.js → section/section-cropper.js} +11 -88
- package/src/core/section/section-detector-strategies.js +139 -0
- package/src/core/section/section-detector-utils.js +100 -0
- package/src/core/section/section-detector.js +88 -0
- package/src/core/tests/test-section-cropper.js +2 -2
- package/src/core/tests/test-section-detector.js +2 -2
- package/src/post-process/enhance-assets.js +29 -4
- package/src/post-process/fetch-images-unsplash-client.js +123 -0
- package/src/post-process/fetch-images.js +60 -263
- package/src/post-process/inject-gosnap.js +88 -0
- package/src/post-process/inject-icons-svg-replacer.js +76 -0
- package/src/post-process/inject-icons.js +47 -200
- package/src/route-discoverers/base-discoverer-utils.js +137 -0
- package/src/route-discoverers/base-discoverer.js +29 -118
- package/src/route-discoverers/index.js +1 -1
- package/src/shared/config.js +38 -0
- package/src/shared/error-codes.js +31 -0
- package/src/shared/viewports.js +46 -0
- package/src/utils/browser.js +0 -7
- package/src/utils/helpers.js +4 -0
- package/src/utils/log.js +12 -0
- package/src/utils/playwright-loader.js +76 -0
- package/src/utils/playwright.js +3 -69
- package/src/utils/progress.js +32 -0
- package/src/verification/generate-audit-report-css-fixes.js +52 -0
- package/src/verification/generate-audit-report-sections.js +158 -0
- package/src/verification/generate-audit-report.js +5 -281
- package/src/verification/quality-scorer.js +92 -0
- package/src/verification/verify-footer-checks.js +103 -0
- package/src/verification/verify-footer-helpers.js +178 -0
- package/src/verification/verify-footer.js +23 -381
- package/src/verification/verify-header-checks.js +104 -0
- package/src/verification/verify-header-helpers.js +156 -0
- package/src/verification/verify-header.js +23 -365
- package/src/verification/verify-layout-report.js +101 -0
- package/src/verification/verify-layout.js +13 -259
- package/src/verification/verify-menu-checks.js +104 -0
- package/src/verification/verify-menu-helpers.js +112 -0
- package/src/verification/verify-menu.js +17 -285
- package/src/verification/verify-slider-checks.js +115 -0
- package/src/verification/verify-slider-constants.js +65 -0
- package/src/verification/verify-slider-helpers.js +164 -0
- package/src/verification/verify-slider.js +23 -414
- package/.env.example +0 -14
- package/docs/basic-clone.md +0 -63
- package/docs/cli-reference.md +0 -316
- package/docs/design-clone-architecture.md +0 -492
- package/docs/pixel-perfect.md +0 -117
- package/docs/project-roadmap.md +0 -382
- package/docs/troubleshooting.md +0 -170
- package/requirements.txt +0 -5
- 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 +0 -375
- package/src/ai/extract-design-tokens.py +0 -782
- package/src/ai/prompts/__init__.py +0 -2
- 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 +0 -316
- package/src/ai/prompts/structure_analysis.py +0 -592
- package/src/ai/prompts/ux_audit.py +0 -198
- package/src/ai/ux-audit.js +0 -596
- package/src/core/animation-extractor.js +0 -526
- package/src/core/app-state-snapshot.js +0 -511
- package/src/core/content-counter.js +0 -342
- package/src/core/design-tokens.js +0 -103
- package/src/core/dimension-extractor.js +0 -438
- package/src/core/dimension-output.js +0 -305
- package/src/core/discover-pages.js +0 -542
- package/src/core/dom-tree-analyzer.js +0 -298
- package/src/core/extract-assets.js +0 -468
- package/src/core/filter-css.js +0 -499
- package/src/core/framework-detector.js +0 -538
- package/src/core/html-extractor.js +0 -212
- package/src/core/merge-css.js +0 -407
- package/src/core/multi-page-screenshot.js +0 -380
- package/src/core/rewrite-links.js +0 -226
- package/src/core/screenshot.js +0 -701
- package/src/core/section-detector.js +0 -386
- package/src/core/semantic-enhancer.js +0 -492
- package/src/core/state-capture.js +0 -598
- package/src/core/video-capture.js +0 -546
- package/src/utils/__init__.py +0 -16
- package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
- package/src/utils/env.py +0 -134
- /package/src/core/{css-extractor.js → css/css-extractor.js} +0 -0
- /package/src/core/{cookie-handler.js → page-prep/cookie-handler.js} +0 -0
- /package/src/core/{page-readiness.js → page-prep/page-readiness.js} +0 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# UX Audit Prompt (Desktop Viewport)
|
|
2
|
+
|
|
3
|
+
Analyze this website screenshot for UX quality.
|
|
4
|
+
|
|
5
|
+
Evaluate these categories (score 0-100 each):
|
|
6
|
+
|
|
7
|
+
1. VISUAL HIERARCHY
|
|
8
|
+
- Primary content prominence
|
|
9
|
+
- Clear scanning patterns (F/Z pattern)
|
|
10
|
+
- Call-to-action visibility
|
|
11
|
+
- Information grouping and prioritization
|
|
12
|
+
- White space utilization
|
|
13
|
+
|
|
14
|
+
2. NAVIGATION
|
|
15
|
+
- Tappable area size (44x44px minimum for mobile)
|
|
16
|
+
- Current page indicator clarity
|
|
17
|
+
- Menu discoverability
|
|
18
|
+
- Breadcrumb/location awareness
|
|
19
|
+
- Navigation consistency
|
|
20
|
+
|
|
21
|
+
3. TYPOGRAPHY
|
|
22
|
+
- Body text size (16px+ recommended)
|
|
23
|
+
- Line height (1.4-1.6 ideal)
|
|
24
|
+
- Contrast ratio (WCAG AA: 4.5:1 for text)
|
|
25
|
+
- Font hierarchy clarity
|
|
26
|
+
- Readability at viewport size
|
|
27
|
+
|
|
28
|
+
4. SPACING
|
|
29
|
+
- Consistent padding/margins
|
|
30
|
+
- Element breathing room
|
|
31
|
+
- Touch target spacing (8px minimum between)
|
|
32
|
+
- Grid alignment
|
|
33
|
+
- Section separation
|
|
34
|
+
|
|
35
|
+
5. INTERACTIVE ELEMENTS
|
|
36
|
+
- Button affordance (looks clickable)
|
|
37
|
+
- Link distinguishability
|
|
38
|
+
- Focus state visibility
|
|
39
|
+
- Hover state indication
|
|
40
|
+
- Form field clarity
|
|
41
|
+
|
|
42
|
+
6. RESPONSIVE
|
|
43
|
+
- Content reflow appropriateness
|
|
44
|
+
- No horizontal scroll
|
|
45
|
+
- Image scaling quality
|
|
46
|
+
- Text truncation handling
|
|
47
|
+
- Breakpoint transitions
|
|
48
|
+
|
|
49
|
+
Return ONLY valid JSON in this exact format:
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"viewport": "desktop",
|
|
53
|
+
"scores": {
|
|
54
|
+
"visual_hierarchy": 0,
|
|
55
|
+
"navigation": 0,
|
|
56
|
+
"typography": 0,
|
|
57
|
+
"spacing": 0,
|
|
58
|
+
"interactivity": 0,
|
|
59
|
+
"responsive": 0
|
|
60
|
+
},
|
|
61
|
+
"overall_ux_score": 0,
|
|
62
|
+
"accessibility_score": 0,
|
|
63
|
+
"issues": [
|
|
64
|
+
{
|
|
65
|
+
"category": "<visual_hierarchy|navigation|typography|spacing|interactivity|responsive>",
|
|
66
|
+
"severity": "<critical|major|minor>",
|
|
67
|
+
"issue": "<concise description>",
|
|
68
|
+
"fix": "<actionable suggestion>"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"recommendations": ["<actionable improvement item>"]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
SEVERITY GUIDELINES:
|
|
76
|
+
- critical: Blocks user tasks or causes confusion (0-30 score range issues)
|
|
77
|
+
- major: Degrades experience significantly (31-60 score range issues)
|
|
78
|
+
- minor: Polish improvements (61-80 score range issues)
|
|
79
|
+
|
|
80
|
+
SCORING GUIDELINES:
|
|
81
|
+
- 90-100: Excellent, industry-leading UX
|
|
82
|
+
- 70-89: Good, meets modern standards
|
|
83
|
+
- 50-69: Adequate, room for improvement
|
|
84
|
+
- 30-49: Poor, significant issues
|
|
85
|
+
- 0-29: Critical, requires immediate attention
|
|
86
|
+
|
|
87
|
+
DESKTOP-SPECIFIC CHECKS:
|
|
88
|
+
- Maximum content width (1200-1440px ideal)
|
|
89
|
+
- Multi-column layout efficiency
|
|
90
|
+
- Hover states and micro-interactions
|
|
91
|
+
- Keyboard navigation support
|
|
92
|
+
- Large screen real estate utilization
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# UX Audit Prompt (Mobile Viewport)
|
|
2
|
+
|
|
3
|
+
Analyze this website screenshot for UX quality.
|
|
4
|
+
|
|
5
|
+
Evaluate these categories (score 0-100 each):
|
|
6
|
+
|
|
7
|
+
1. VISUAL HIERARCHY
|
|
8
|
+
- Primary content prominence
|
|
9
|
+
- Clear scanning patterns (F/Z pattern)
|
|
10
|
+
- Call-to-action visibility
|
|
11
|
+
- Information grouping and prioritization
|
|
12
|
+
- White space utilization
|
|
13
|
+
|
|
14
|
+
2. NAVIGATION
|
|
15
|
+
- Tappable area size (44x44px minimum for mobile)
|
|
16
|
+
- Current page indicator clarity
|
|
17
|
+
- Menu discoverability
|
|
18
|
+
- Breadcrumb/location awareness
|
|
19
|
+
- Navigation consistency
|
|
20
|
+
|
|
21
|
+
3. TYPOGRAPHY
|
|
22
|
+
- Body text size (16px+ recommended)
|
|
23
|
+
- Line height (1.4-1.6 ideal)
|
|
24
|
+
- Contrast ratio (WCAG AA: 4.5:1 for text)
|
|
25
|
+
- Font hierarchy clarity
|
|
26
|
+
- Readability at viewport size
|
|
27
|
+
|
|
28
|
+
4. SPACING
|
|
29
|
+
- Consistent padding/margins
|
|
30
|
+
- Element breathing room
|
|
31
|
+
- Touch target spacing (8px minimum between)
|
|
32
|
+
- Grid alignment
|
|
33
|
+
- Section separation
|
|
34
|
+
|
|
35
|
+
5. INTERACTIVE ELEMENTS
|
|
36
|
+
- Button affordance (looks clickable)
|
|
37
|
+
- Link distinguishability
|
|
38
|
+
- Focus state visibility
|
|
39
|
+
- Hover state indication
|
|
40
|
+
- Form field clarity
|
|
41
|
+
|
|
42
|
+
6. RESPONSIVE
|
|
43
|
+
- Content reflow appropriateness
|
|
44
|
+
- No horizontal scroll
|
|
45
|
+
- Image scaling quality
|
|
46
|
+
- Text truncation handling
|
|
47
|
+
- Breakpoint transitions
|
|
48
|
+
|
|
49
|
+
Return ONLY valid JSON in this exact format:
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"viewport": "mobile",
|
|
53
|
+
"scores": {
|
|
54
|
+
"visual_hierarchy": 0,
|
|
55
|
+
"navigation": 0,
|
|
56
|
+
"typography": 0,
|
|
57
|
+
"spacing": 0,
|
|
58
|
+
"interactivity": 0,
|
|
59
|
+
"responsive": 0
|
|
60
|
+
},
|
|
61
|
+
"overall_ux_score": 0,
|
|
62
|
+
"accessibility_score": 0,
|
|
63
|
+
"issues": [
|
|
64
|
+
{
|
|
65
|
+
"category": "<visual_hierarchy|navigation|typography|spacing|interactivity|responsive>",
|
|
66
|
+
"severity": "<critical|major|minor>",
|
|
67
|
+
"issue": "<concise description>",
|
|
68
|
+
"fix": "<actionable suggestion>"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"recommendations": ["<actionable improvement item>"]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
SEVERITY GUIDELINES:
|
|
76
|
+
- critical: Blocks user tasks or causes confusion (0-30 score range issues)
|
|
77
|
+
- major: Degrades experience significantly (31-60 score range issues)
|
|
78
|
+
- minor: Polish improvements (61-80 score range issues)
|
|
79
|
+
|
|
80
|
+
SCORING GUIDELINES:
|
|
81
|
+
- 90-100: Excellent, industry-leading UX
|
|
82
|
+
- 70-89: Good, meets modern standards
|
|
83
|
+
- 50-69: Adequate, room for improvement
|
|
84
|
+
- 30-49: Poor, significant issues
|
|
85
|
+
- 0-29: Critical, requires immediate attention
|
|
86
|
+
|
|
87
|
+
MOBILE-SPECIFIC CHECKS:
|
|
88
|
+
- Touch targets minimum 44x44px
|
|
89
|
+
- Thumb zone accessibility
|
|
90
|
+
- Single-column layout efficiency
|
|
91
|
+
- Mobile navigation pattern (hamburger/tab bar)
|
|
92
|
+
- Text readable without zooming
|
|
93
|
+
- Forms optimized for mobile input
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# UX Audit Prompt (Tablet Viewport)
|
|
2
|
+
|
|
3
|
+
Analyze this website screenshot for UX quality.
|
|
4
|
+
|
|
5
|
+
Evaluate these categories (score 0-100 each):
|
|
6
|
+
|
|
7
|
+
1. VISUAL HIERARCHY
|
|
8
|
+
- Primary content prominence
|
|
9
|
+
- Clear scanning patterns (F/Z pattern)
|
|
10
|
+
- Call-to-action visibility
|
|
11
|
+
- Information grouping and prioritization
|
|
12
|
+
- White space utilization
|
|
13
|
+
|
|
14
|
+
2. NAVIGATION
|
|
15
|
+
- Tappable area size (44x44px minimum for mobile)
|
|
16
|
+
- Current page indicator clarity
|
|
17
|
+
- Menu discoverability
|
|
18
|
+
- Breadcrumb/location awareness
|
|
19
|
+
- Navigation consistency
|
|
20
|
+
|
|
21
|
+
3. TYPOGRAPHY
|
|
22
|
+
- Body text size (16px+ recommended)
|
|
23
|
+
- Line height (1.4-1.6 ideal)
|
|
24
|
+
- Contrast ratio (WCAG AA: 4.5:1 for text)
|
|
25
|
+
- Font hierarchy clarity
|
|
26
|
+
- Readability at viewport size
|
|
27
|
+
|
|
28
|
+
4. SPACING
|
|
29
|
+
- Consistent padding/margins
|
|
30
|
+
- Element breathing room
|
|
31
|
+
- Touch target spacing (8px minimum between)
|
|
32
|
+
- Grid alignment
|
|
33
|
+
- Section separation
|
|
34
|
+
|
|
35
|
+
5. INTERACTIVE ELEMENTS
|
|
36
|
+
- Button affordance (looks clickable)
|
|
37
|
+
- Link distinguishability
|
|
38
|
+
- Focus state visibility
|
|
39
|
+
- Hover state indication
|
|
40
|
+
- Form field clarity
|
|
41
|
+
|
|
42
|
+
6. RESPONSIVE
|
|
43
|
+
- Content reflow appropriateness
|
|
44
|
+
- No horizontal scroll
|
|
45
|
+
- Image scaling quality
|
|
46
|
+
- Text truncation handling
|
|
47
|
+
- Breakpoint transitions
|
|
48
|
+
|
|
49
|
+
Return ONLY valid JSON in this exact format:
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"viewport": "tablet",
|
|
53
|
+
"scores": {
|
|
54
|
+
"visual_hierarchy": 0,
|
|
55
|
+
"navigation": 0,
|
|
56
|
+
"typography": 0,
|
|
57
|
+
"spacing": 0,
|
|
58
|
+
"interactivity": 0,
|
|
59
|
+
"responsive": 0
|
|
60
|
+
},
|
|
61
|
+
"overall_ux_score": 0,
|
|
62
|
+
"accessibility_score": 0,
|
|
63
|
+
"issues": [
|
|
64
|
+
{
|
|
65
|
+
"category": "<visual_hierarchy|navigation|typography|spacing|interactivity|responsive>",
|
|
66
|
+
"severity": "<critical|major|minor>",
|
|
67
|
+
"issue": "<concise description>",
|
|
68
|
+
"fix": "<actionable suggestion>"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"recommendations": ["<actionable improvement item>"]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
SEVERITY GUIDELINES:
|
|
76
|
+
- critical: Blocks user tasks or causes confusion (0-30 score range issues)
|
|
77
|
+
- major: Degrades experience significantly (31-60 score range issues)
|
|
78
|
+
- minor: Polish improvements (61-80 score range issues)
|
|
79
|
+
|
|
80
|
+
SCORING GUIDELINES:
|
|
81
|
+
- 90-100: Excellent, industry-leading UX
|
|
82
|
+
- 70-89: Good, meets modern standards
|
|
83
|
+
- 50-69: Adequate, room for improvement
|
|
84
|
+
- 30-49: Poor, significant issues
|
|
85
|
+
- 0-29: Critical, requires immediate attention
|
|
86
|
+
|
|
87
|
+
TABLET-SPECIFIC CHECKS:
|
|
88
|
+
- Two-column layout utilization
|
|
89
|
+
- Touch and mouse input support
|
|
90
|
+
- Landscape/portrait adaptability
|
|
91
|
+
- Sidebar vs content balance
|
|
92
|
+
- Split-view readiness
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AST-based CSS animation extraction helpers.
|
|
3
|
+
*
|
|
4
|
+
* Single-pass css-tree AST walker that extracts @keyframes definitions,
|
|
5
|
+
* transition properties, and animation properties from parsed CSS.
|
|
6
|
+
* Used by animation-extractor.js (main module).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Constants
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/** CSS transition property names (standard + vendor prefixed) */
|
|
14
|
+
export const TRANSITION_PROPERTIES = new Set([
|
|
15
|
+
'transition',
|
|
16
|
+
'transition-property',
|
|
17
|
+
'transition-duration',
|
|
18
|
+
'transition-timing-function',
|
|
19
|
+
'transition-delay',
|
|
20
|
+
'-webkit-transition'
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
/** CSS animation property names (standard + vendor prefixed) */
|
|
24
|
+
export const ANIMATION_PROPERTIES = new Set([
|
|
25
|
+
'animation',
|
|
26
|
+
'animation-name',
|
|
27
|
+
'animation-duration',
|
|
28
|
+
'animation-timing-function',
|
|
29
|
+
'animation-delay',
|
|
30
|
+
'animation-iteration-count',
|
|
31
|
+
'animation-direction',
|
|
32
|
+
'animation-fill-mode',
|
|
33
|
+
'animation-play-state',
|
|
34
|
+
'-webkit-animation',
|
|
35
|
+
'-webkit-animation-name'
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// Single-Pass AST Extraction (Performance Optimized)
|
|
40
|
+
// ============================================================================
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Extract all animation-related data in a single AST walk.
|
|
44
|
+
* Optimized O(n) traversal instead of O(3n) with separate walks.
|
|
45
|
+
*
|
|
46
|
+
* @param {Object} csstree - css-tree module reference
|
|
47
|
+
* @param {Object} cssAst - css-tree parsed AST
|
|
48
|
+
* @returns {{keyframes: Object, transitions: Array, animatedElements: Array}}
|
|
49
|
+
*/
|
|
50
|
+
export function extractAllFromAst(csstree, cssAst) {
|
|
51
|
+
const keyframes = {};
|
|
52
|
+
const transitions = [];
|
|
53
|
+
const animatedElements = [];
|
|
54
|
+
|
|
55
|
+
if (!csstree) {
|
|
56
|
+
return { keyframes, transitions, animatedElements };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
csstree.walk(cssAst, {
|
|
60
|
+
enter(node) {
|
|
61
|
+
// Handle @keyframes rules
|
|
62
|
+
if (node.type === 'Atrule' &&
|
|
63
|
+
(node.name === 'keyframes' || node.name === '-webkit-keyframes')) {
|
|
64
|
+
const keyframeData = processKeyframeRule(csstree, node);
|
|
65
|
+
if (keyframeData) {
|
|
66
|
+
keyframes[keyframeData.name] = keyframeData.data;
|
|
67
|
+
}
|
|
68
|
+
return; // Don't descend into keyframes block
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Handle style rules (for transitions and animations)
|
|
72
|
+
if (node.type === 'Rule' && node.prelude && node.block) {
|
|
73
|
+
const ruleData = processStyleRule(csstree, node);
|
|
74
|
+
if (ruleData.transition) {
|
|
75
|
+
transitions.push(ruleData.transition);
|
|
76
|
+
}
|
|
77
|
+
if (ruleData.animation) {
|
|
78
|
+
animatedElements.push(ruleData.animation);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return { keyframes, transitions, animatedElements };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Process a @keyframes at-rule node.
|
|
89
|
+
*
|
|
90
|
+
* @param {Object} csstree - css-tree module reference
|
|
91
|
+
* @param {Object} node - css-tree Atrule node
|
|
92
|
+
* @returns {{name: string, data: import('./animation-extractor.js').KeyframeData}|null}
|
|
93
|
+
*/
|
|
94
|
+
export function processKeyframeRule(csstree, node) {
|
|
95
|
+
// Get keyframe name from prelude
|
|
96
|
+
let name = null;
|
|
97
|
+
|
|
98
|
+
if (node.prelude) {
|
|
99
|
+
if (node.prelude.type === 'AtrulePrelude') {
|
|
100
|
+
// Walk prelude to find first Identifier
|
|
101
|
+
csstree.walk(node.prelude, {
|
|
102
|
+
visit: 'Identifier',
|
|
103
|
+
enter(idNode) {
|
|
104
|
+
if (!name) name = idNode.name;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
} else if (node.prelude.type === 'Raw') {
|
|
108
|
+
name = node.prelude.value?.trim();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!name || !node.block) return null;
|
|
113
|
+
|
|
114
|
+
// Extract frames from keyframe block
|
|
115
|
+
const frames = [];
|
|
116
|
+
csstree.walk(node.block, {
|
|
117
|
+
visit: 'Rule',
|
|
118
|
+
enter(frameNode) {
|
|
119
|
+
const offset = csstree.generate(frameNode.prelude);
|
|
120
|
+
const properties = {};
|
|
121
|
+
|
|
122
|
+
if (frameNode.block) {
|
|
123
|
+
csstree.walk(frameNode.block, {
|
|
124
|
+
visit: 'Declaration',
|
|
125
|
+
enter(declNode) {
|
|
126
|
+
properties[declNode.property] = csstree.generate(declNode.value);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (Object.keys(properties).length > 0) {
|
|
132
|
+
frames.push({ offset, properties });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
name,
|
|
139
|
+
data: {
|
|
140
|
+
frames,
|
|
141
|
+
raw: csstree.generate(node),
|
|
142
|
+
vendorPrefixed: node.name === '-webkit-keyframes'
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Process a style rule for transition and animation properties.
|
|
149
|
+
*
|
|
150
|
+
* @param {Object} csstree - css-tree module reference
|
|
151
|
+
* @param {Object} node - css-tree Rule node
|
|
152
|
+
* @returns {{transition: Object|null, animation: Object|null}}
|
|
153
|
+
*/
|
|
154
|
+
export function processStyleRule(csstree, node) {
|
|
155
|
+
const selector = csstree.generate(node.prelude);
|
|
156
|
+
const transitionProps = {};
|
|
157
|
+
const animationProps = {};
|
|
158
|
+
|
|
159
|
+
// Extract declarations in a single walk
|
|
160
|
+
csstree.walk(node.block, {
|
|
161
|
+
visit: 'Declaration',
|
|
162
|
+
enter(declNode) {
|
|
163
|
+
const prop = declNode.property;
|
|
164
|
+
const value = csstree.generate(declNode.value);
|
|
165
|
+
|
|
166
|
+
if (TRANSITION_PROPERTIES.has(prop)) {
|
|
167
|
+
transitionProps[prop] = value;
|
|
168
|
+
}
|
|
169
|
+
if (ANIMATION_PROPERTIES.has(prop)) {
|
|
170
|
+
animationProps[prop] = value;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return {
|
|
176
|
+
transition: Object.keys(transitionProps).length > 0
|
|
177
|
+
? { selector, ...transitionProps }
|
|
178
|
+
: null,
|
|
179
|
+
animation: Object.keys(animationProps).length > 0
|
|
180
|
+
? { selector, ...animationProps }
|
|
181
|
+
: null
|
|
182
|
+
};
|
|
183
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS animation output generators.
|
|
3
|
+
*
|
|
4
|
+
* Generates animations.css content from extracted keyframes and
|
|
5
|
+
* produces animation design tokens (durations, timing functions)
|
|
6
|
+
* for design-tokens.json. Used by animation-extractor.js (main module).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Constants
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/** Pattern to extract duration values (e.g., "200ms", "1.5s") */
|
|
14
|
+
const DURATION_PATTERN = /(\d+(?:\.\d+)?(?:ms|s))/g;
|
|
15
|
+
|
|
16
|
+
/** Pattern to extract cubic-bezier timing functions */
|
|
17
|
+
const CUBIC_BEZIER_PATTERN = /cubic-bezier\([^)]+\)/g;
|
|
18
|
+
|
|
19
|
+
/** Common timing function keywords to detect in shorthand values */
|
|
20
|
+
const TIMING_KEYWORDS = ['ease', 'linear', 'ease-in-out', 'ease-in', 'ease-out'];
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// CSS Generation
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generate animations.css from extracted keyframes.
|
|
28
|
+
*
|
|
29
|
+
* @param {import('./animation-extractor.js').ExtractionResult} animationData - Result from extractAnimations()
|
|
30
|
+
* @returns {string} CSS string with @keyframes definitions
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* const result = await extractAnimations(css);
|
|
34
|
+
* const animCss = generateAnimationsCss(result);
|
|
35
|
+
* fs.writeFileSync('animations.css', animCss);
|
|
36
|
+
*/
|
|
37
|
+
export function generateAnimationsCss(animationData) {
|
|
38
|
+
const { keyframes } = animationData || {};
|
|
39
|
+
|
|
40
|
+
if (!keyframes || Object.keys(keyframes).length === 0) {
|
|
41
|
+
return '/* No @keyframes found */\n';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const lines = [
|
|
45
|
+
'/**',
|
|
46
|
+
' * Extracted CSS Animations',
|
|
47
|
+
' * Generated by design-clone animation-extractor',
|
|
48
|
+
' */\n'
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
for (const [name, data] of Object.entries(keyframes)) {
|
|
52
|
+
const frameCount = data.frames?.length || 0;
|
|
53
|
+
lines.push(`/* Keyframes: ${name} (${frameCount} frames) */`);
|
|
54
|
+
lines.push(data.raw);
|
|
55
|
+
lines.push('');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ============================================================================
|
|
62
|
+
// Token Generation
|
|
63
|
+
// ============================================================================
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Extract timing values from shorthand transition/animation strings.
|
|
67
|
+
*
|
|
68
|
+
* Limitations:
|
|
69
|
+
* - Only extracts first duration from shorthand (CSS allows multiple)
|
|
70
|
+
* - Timing function detection is keyword-based, may miss complex values
|
|
71
|
+
* - Does not resolve CSS variables (e.g., var(--duration))
|
|
72
|
+
*
|
|
73
|
+
* @param {string} shorthand - Shorthand property value
|
|
74
|
+
* @param {Set<string>} durations - Set to add durations to
|
|
75
|
+
* @param {Set<string>} timings - Set to add timing functions to
|
|
76
|
+
*/
|
|
77
|
+
export function extractTimingFromShorthand(shorthand, durations, timings) {
|
|
78
|
+
if (!shorthand) return;
|
|
79
|
+
|
|
80
|
+
// Extract all duration values
|
|
81
|
+
const durationMatches = shorthand.match(DURATION_PATTERN);
|
|
82
|
+
if (durationMatches) {
|
|
83
|
+
durationMatches.forEach(d => durations.add(d));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Extract cubic-bezier functions
|
|
87
|
+
const bezierMatches = shorthand.match(CUBIC_BEZIER_PATTERN);
|
|
88
|
+
if (bezierMatches) {
|
|
89
|
+
bezierMatches.forEach(b => timings.add(b));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check for timing keywords
|
|
93
|
+
const lowerShorthand = shorthand.toLowerCase();
|
|
94
|
+
for (const keyword of TIMING_KEYWORDS) {
|
|
95
|
+
if (lowerShorthand.includes(keyword)) {
|
|
96
|
+
timings.add(keyword);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Generate animation tokens for design-tokens.json.
|
|
103
|
+
*
|
|
104
|
+
* @param {import('./animation-extractor.js').ExtractionResult} animationData - Result from extractAnimations()
|
|
105
|
+
* @returns {import('./animation-extractor.js').AnimationTokens} Animation tokens structure
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* const result = await extractAnimations(css);
|
|
109
|
+
* const tokens = generateAnimationTokens(result);
|
|
110
|
+
* console.log(tokens.durations); // ['200ms', '300ms', '1s']
|
|
111
|
+
*/
|
|
112
|
+
export function generateAnimationTokens(animationData) {
|
|
113
|
+
const { keyframes = {}, transitions = [], animatedElements = [] } = animationData || {};
|
|
114
|
+
|
|
115
|
+
const durations = new Set();
|
|
116
|
+
const timings = new Set();
|
|
117
|
+
|
|
118
|
+
// Extract from transitions
|
|
119
|
+
for (const t of transitions) {
|
|
120
|
+
if (t['transition-duration']) {
|
|
121
|
+
t['transition-duration'].split(',').forEach(d => durations.add(d.trim()));
|
|
122
|
+
}
|
|
123
|
+
if (t['transition-timing-function']) {
|
|
124
|
+
t['transition-timing-function'].split(',').forEach(tf => timings.add(tf.trim()));
|
|
125
|
+
}
|
|
126
|
+
if (t.transition) {
|
|
127
|
+
extractTimingFromShorthand(t.transition, durations, timings);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Extract from animated elements
|
|
132
|
+
for (const a of animatedElements) {
|
|
133
|
+
if (a['animation-duration']) {
|
|
134
|
+
a['animation-duration'].split(',').forEach(d => durations.add(d.trim()));
|
|
135
|
+
}
|
|
136
|
+
if (a['animation-timing-function']) {
|
|
137
|
+
a['animation-timing-function'].split(',').forEach(tf => timings.add(tf.trim()));
|
|
138
|
+
}
|
|
139
|
+
if (a.animation) {
|
|
140
|
+
extractTimingFromShorthand(a.animation, durations, timings);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
keyframes: Object.keys(keyframes),
|
|
146
|
+
keyframeCount: Object.keys(keyframes).length,
|
|
147
|
+
transitions: transitions.length,
|
|
148
|
+
animatedElements: animatedElements.length,
|
|
149
|
+
durations: [...durations].sort(),
|
|
150
|
+
timingFunctions: [...timings].sort()
|
|
151
|
+
};
|
|
152
|
+
}
|