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
|
@@ -1,526 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Animation Extractor
|
|
3
|
-
*
|
|
4
|
-
* Extract @keyframes definitions, animation properties, and transition values
|
|
5
|
-
* from CSS using css-tree AST walking.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* import { extractAnimations, generateAnimationsCss } from './animation-extractor.js';
|
|
9
|
-
* const animations = await extractAnimations(cssString);
|
|
10
|
-
* const animCss = generateAnimationsCss(animations);
|
|
11
|
-
*
|
|
12
|
-
* @module animation-extractor
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// ============================================================================
|
|
16
|
-
// Constants
|
|
17
|
-
// ============================================================================
|
|
18
|
-
|
|
19
|
-
/** CSS transition property names (standard + vendor prefixed) */
|
|
20
|
-
const TRANSITION_PROPERTIES = new Set([
|
|
21
|
-
'transition',
|
|
22
|
-
'transition-property',
|
|
23
|
-
'transition-duration',
|
|
24
|
-
'transition-timing-function',
|
|
25
|
-
'transition-delay',
|
|
26
|
-
'-webkit-transition'
|
|
27
|
-
]);
|
|
28
|
-
|
|
29
|
-
/** CSS animation property names (standard + vendor prefixed) */
|
|
30
|
-
const ANIMATION_PROPERTIES = new Set([
|
|
31
|
-
'animation',
|
|
32
|
-
'animation-name',
|
|
33
|
-
'animation-duration',
|
|
34
|
-
'animation-timing-function',
|
|
35
|
-
'animation-delay',
|
|
36
|
-
'animation-iteration-count',
|
|
37
|
-
'animation-direction',
|
|
38
|
-
'animation-fill-mode',
|
|
39
|
-
'animation-play-state',
|
|
40
|
-
'-webkit-animation',
|
|
41
|
-
'-webkit-animation-name'
|
|
42
|
-
]);
|
|
43
|
-
|
|
44
|
-
/** Pattern to extract duration values (e.g., "200ms", "1.5s") */
|
|
45
|
-
const DURATION_PATTERN = /(\d+(?:\.\d+)?(?:ms|s))/g;
|
|
46
|
-
|
|
47
|
-
/** Pattern to extract cubic-bezier timing functions */
|
|
48
|
-
const CUBIC_BEZIER_PATTERN = /cubic-bezier\([^)]+\)/g;
|
|
49
|
-
|
|
50
|
-
/** Common timing function keywords to detect in shorthand values */
|
|
51
|
-
const TIMING_KEYWORDS = ['ease', 'linear', 'ease-in-out', 'ease-in', 'ease-out'];
|
|
52
|
-
|
|
53
|
-
// ============================================================================
|
|
54
|
-
// Dependency Management
|
|
55
|
-
// ============================================================================
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* css-tree module reference
|
|
59
|
-
* Loaded dynamically to handle missing dependency gracefully
|
|
60
|
-
* @type {Object|null}
|
|
61
|
-
*/
|
|
62
|
-
let csstree = null;
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
csstree = await import('css-tree');
|
|
66
|
-
} catch (importError) {
|
|
67
|
-
// Log detailed error for debugging while keeping user message simple
|
|
68
|
-
const errorDetails = importError.code === 'ERR_MODULE_NOT_FOUND'
|
|
69
|
-
? 'Module not found in node_modules'
|
|
70
|
-
: importError.message;
|
|
71
|
-
|
|
72
|
-
console.error(
|
|
73
|
-
'[animation-extractor] Failed to load css-tree dependency.\n' +
|
|
74
|
-
` Error: ${errorDetails}\n` +
|
|
75
|
-
' Fix: Run "npm install css-tree" to install the required dependency.\n' +
|
|
76
|
-
' Note: Animation extraction will be disabled until css-tree is available.'
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ============================================================================
|
|
81
|
-
// Type Definitions (JSDoc)
|
|
82
|
-
// ============================================================================
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* @typedef {Object} KeyframeFrame
|
|
86
|
-
* @property {string} offset - Keyframe selector (e.g., "0%", "50%", "100%", "from", "to")
|
|
87
|
-
* @property {Object<string, string>} properties - CSS properties and their values
|
|
88
|
-
*/
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* @typedef {Object} KeyframeData
|
|
92
|
-
* @property {KeyframeFrame[]} frames - Array of keyframe frames
|
|
93
|
-
* @property {string} raw - Original CSS text for regeneration
|
|
94
|
-
* @property {boolean} vendorPrefixed - True if @-webkit-keyframes
|
|
95
|
-
*/
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* @typedef {Object} TransitionData
|
|
99
|
-
* @property {string} selector - CSS selector for the rule
|
|
100
|
-
* @property {string} [transition] - Shorthand transition value
|
|
101
|
-
* @property {string} [transition-property] - Transition property
|
|
102
|
-
* @property {string} [transition-duration] - Transition duration
|
|
103
|
-
* @property {string} [transition-timing-function] - Timing function
|
|
104
|
-
* @property {string} [transition-delay] - Transition delay
|
|
105
|
-
*/
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* @typedef {Object} AnimatedElementData
|
|
109
|
-
* @property {string} selector - CSS selector for the rule
|
|
110
|
-
* @property {string} [animation] - Shorthand animation value
|
|
111
|
-
* @property {string} [animation-name] - Animation name
|
|
112
|
-
* @property {string} [animation-duration] - Animation duration
|
|
113
|
-
* @property {string} [animation-timing-function] - Timing function
|
|
114
|
-
* @property {string} [animation-delay] - Animation delay
|
|
115
|
-
* @property {string} [animation-iteration-count] - Iteration count
|
|
116
|
-
* @property {string} [animation-direction] - Animation direction
|
|
117
|
-
* @property {string} [animation-fill-mode] - Fill mode
|
|
118
|
-
* @property {string} [animation-play-state] - Play state
|
|
119
|
-
*/
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* @typedef {Object} ExtractionResult
|
|
123
|
-
* @property {Object<string, KeyframeData>} keyframes - Map of keyframe name to data
|
|
124
|
-
* @property {TransitionData[]} transitions - Array of transition rules
|
|
125
|
-
* @property {AnimatedElementData[]} animatedElements - Array of animated element rules
|
|
126
|
-
* @property {string} [error] - Error message if extraction failed
|
|
127
|
-
*/
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* @typedef {Object} AnimationTokens
|
|
131
|
-
* @property {string[]} keyframes - List of keyframe names
|
|
132
|
-
* @property {number} keyframeCount - Total number of keyframes
|
|
133
|
-
* @property {number} transitions - Total number of transition rules
|
|
134
|
-
* @property {number} animatedElements - Total number of animated elements
|
|
135
|
-
* @property {string[]} durations - Unique duration values found
|
|
136
|
-
* @property {string[]} timingFunctions - Unique timing functions found
|
|
137
|
-
*/
|
|
138
|
-
|
|
139
|
-
// ============================================================================
|
|
140
|
-
// Single-Pass AST Extraction (Performance Optimized)
|
|
141
|
-
// ============================================================================
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Extract all animation-related data in a single AST walk.
|
|
145
|
-
* Optimized O(n) traversal instead of O(3n) with separate walks.
|
|
146
|
-
*
|
|
147
|
-
* @param {Object} cssAst - css-tree parsed AST
|
|
148
|
-
* @returns {{keyframes: Object<string, KeyframeData>, transitions: TransitionData[], animatedElements: AnimatedElementData[]}}
|
|
149
|
-
*/
|
|
150
|
-
function extractAllFromAst(cssAst) {
|
|
151
|
-
const keyframes = {};
|
|
152
|
-
const transitions = [];
|
|
153
|
-
const animatedElements = [];
|
|
154
|
-
|
|
155
|
-
if (!csstree) {
|
|
156
|
-
return { keyframes, transitions, animatedElements };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
csstree.walk(cssAst, {
|
|
160
|
-
enter(node) {
|
|
161
|
-
// Handle @keyframes rules
|
|
162
|
-
if (node.type === 'Atrule' &&
|
|
163
|
-
(node.name === 'keyframes' || node.name === '-webkit-keyframes')) {
|
|
164
|
-
const keyframeData = processKeyframeRule(node);
|
|
165
|
-
if (keyframeData) {
|
|
166
|
-
keyframes[keyframeData.name] = keyframeData.data;
|
|
167
|
-
}
|
|
168
|
-
return; // Don't descend into keyframes block
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Handle style rules (for transitions and animations)
|
|
172
|
-
if (node.type === 'Rule' && node.prelude && node.block) {
|
|
173
|
-
const ruleData = processStyleRule(node);
|
|
174
|
-
if (ruleData.transition) {
|
|
175
|
-
transitions.push(ruleData.transition);
|
|
176
|
-
}
|
|
177
|
-
if (ruleData.animation) {
|
|
178
|
-
animatedElements.push(ruleData.animation);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
return { keyframes, transitions, animatedElements };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Process a @keyframes at-rule node
|
|
189
|
-
*
|
|
190
|
-
* @param {Object} node - css-tree Atrule node
|
|
191
|
-
* @returns {{name: string, data: KeyframeData}|null}
|
|
192
|
-
*/
|
|
193
|
-
function processKeyframeRule(node) {
|
|
194
|
-
// Get keyframe name from prelude
|
|
195
|
-
let name = null;
|
|
196
|
-
|
|
197
|
-
if (node.prelude) {
|
|
198
|
-
if (node.prelude.type === 'AtrulePrelude') {
|
|
199
|
-
// Walk prelude to find first Identifier
|
|
200
|
-
csstree.walk(node.prelude, {
|
|
201
|
-
visit: 'Identifier',
|
|
202
|
-
enter(idNode) {
|
|
203
|
-
if (!name) name = idNode.name;
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
} else if (node.prelude.type === 'Raw') {
|
|
207
|
-
name = node.prelude.value?.trim();
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (!name || !node.block) return null;
|
|
212
|
-
|
|
213
|
-
// Extract frames from keyframe block
|
|
214
|
-
const frames = [];
|
|
215
|
-
csstree.walk(node.block, {
|
|
216
|
-
visit: 'Rule',
|
|
217
|
-
enter(frameNode) {
|
|
218
|
-
const offset = csstree.generate(frameNode.prelude);
|
|
219
|
-
const properties = {};
|
|
220
|
-
|
|
221
|
-
if (frameNode.block) {
|
|
222
|
-
csstree.walk(frameNode.block, {
|
|
223
|
-
visit: 'Declaration',
|
|
224
|
-
enter(declNode) {
|
|
225
|
-
properties[declNode.property] = csstree.generate(declNode.value);
|
|
226
|
-
}
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (Object.keys(properties).length > 0) {
|
|
231
|
-
frames.push({ offset, properties });
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
return {
|
|
237
|
-
name,
|
|
238
|
-
data: {
|
|
239
|
-
frames,
|
|
240
|
-
raw: csstree.generate(node),
|
|
241
|
-
vendorPrefixed: node.name === '-webkit-keyframes'
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Process a style rule for transition and animation properties
|
|
248
|
-
*
|
|
249
|
-
* @param {Object} node - css-tree Rule node
|
|
250
|
-
* @returns {{transition: TransitionData|null, animation: AnimatedElementData|null}}
|
|
251
|
-
*/
|
|
252
|
-
function processStyleRule(node) {
|
|
253
|
-
const selector = csstree.generate(node.prelude);
|
|
254
|
-
const transitionProps = {};
|
|
255
|
-
const animationProps = {};
|
|
256
|
-
|
|
257
|
-
// Extract declarations in a single walk
|
|
258
|
-
csstree.walk(node.block, {
|
|
259
|
-
visit: 'Declaration',
|
|
260
|
-
enter(declNode) {
|
|
261
|
-
const prop = declNode.property;
|
|
262
|
-
const value = csstree.generate(declNode.value);
|
|
263
|
-
|
|
264
|
-
if (TRANSITION_PROPERTIES.has(prop)) {
|
|
265
|
-
transitionProps[prop] = value;
|
|
266
|
-
}
|
|
267
|
-
if (ANIMATION_PROPERTIES.has(prop)) {
|
|
268
|
-
animationProps[prop] = value;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
return {
|
|
274
|
-
transition: Object.keys(transitionProps).length > 0
|
|
275
|
-
? { selector, ...transitionProps }
|
|
276
|
-
: null,
|
|
277
|
-
animation: Object.keys(animationProps).length > 0
|
|
278
|
-
? { selector, ...animationProps }
|
|
279
|
-
: null
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// ============================================================================
|
|
284
|
-
// Legacy Individual Extractors (Kept for Testing/Backwards Compatibility)
|
|
285
|
-
// ============================================================================
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Extract @keyframes from CSS AST
|
|
289
|
-
* Handles both standard and -webkit- prefixed keyframes
|
|
290
|
-
*
|
|
291
|
-
* @param {Object} cssAst - css-tree AST
|
|
292
|
-
* @returns {Object<string, KeyframeData>} Map of keyframe name to data
|
|
293
|
-
*/
|
|
294
|
-
function extractKeyframes(cssAst) {
|
|
295
|
-
if (!csstree) return {};
|
|
296
|
-
const { keyframes } = extractAllFromAst(cssAst);
|
|
297
|
-
return keyframes;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Extract transition properties from CSS rules
|
|
302
|
-
*
|
|
303
|
-
* @param {Object} cssAst - css-tree AST
|
|
304
|
-
* @returns {TransitionData[]} Array of transition rules
|
|
305
|
-
*/
|
|
306
|
-
function extractTransitions(cssAst) {
|
|
307
|
-
if (!csstree) return [];
|
|
308
|
-
const { transitions } = extractAllFromAst(cssAst);
|
|
309
|
-
return transitions;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Extract animation-* properties from CSS rules
|
|
314
|
-
*
|
|
315
|
-
* @param {Object} cssAst - css-tree AST
|
|
316
|
-
* @returns {AnimatedElementData[]} Array of animated element rules
|
|
317
|
-
*/
|
|
318
|
-
function extractAnimationProps(cssAst) {
|
|
319
|
-
if (!csstree) return [];
|
|
320
|
-
const { animatedElements } = extractAllFromAst(cssAst);
|
|
321
|
-
return animatedElements;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// ============================================================================
|
|
325
|
-
// Main Extraction Function
|
|
326
|
-
// ============================================================================
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* Main extraction function - extract all animation-related CSS data.
|
|
330
|
-
*
|
|
331
|
-
* Uses single-pass AST walking for optimal performance.
|
|
332
|
-
* Falls back to lenient parsing if strict parsing fails.
|
|
333
|
-
*
|
|
334
|
-
* @param {string} cssString - Raw CSS string to parse
|
|
335
|
-
* @returns {Promise<ExtractionResult>} Extraction result with keyframes, transitions, and animated elements
|
|
336
|
-
*
|
|
337
|
-
* @example
|
|
338
|
-
* const css = '@keyframes fadeIn { 0% { opacity: 0; } 100% { opacity: 1; } }';
|
|
339
|
-
* const result = await extractAnimations(css);
|
|
340
|
-
* console.log(result.keyframes.fadeIn.frames.length); // 2
|
|
341
|
-
*/
|
|
342
|
-
export async function extractAnimations(cssString) {
|
|
343
|
-
// Early return for null/undefined/empty input
|
|
344
|
-
if (!cssString || typeof cssString !== 'string') {
|
|
345
|
-
return {
|
|
346
|
-
keyframes: {},
|
|
347
|
-
transitions: [],
|
|
348
|
-
animatedElements: []
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (!csstree) {
|
|
353
|
-
return {
|
|
354
|
-
keyframes: {},
|
|
355
|
-
transitions: [],
|
|
356
|
-
animatedElements: [],
|
|
357
|
-
error: 'css-tree dependency not available. Run: npm install css-tree'
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
let ast;
|
|
362
|
-
try {
|
|
363
|
-
ast = csstree.parse(cssString, {
|
|
364
|
-
parseRulePrelude: true,
|
|
365
|
-
parseValue: true,
|
|
366
|
-
parseAtrulePrelude: true
|
|
367
|
-
});
|
|
368
|
-
} catch (parseError) {
|
|
369
|
-
// Try lenient parse without value/prelude parsing
|
|
370
|
-
try {
|
|
371
|
-
ast = csstree.parse(cssString, {
|
|
372
|
-
parseRulePrelude: true,
|
|
373
|
-
parseValue: false,
|
|
374
|
-
parseAtrulePrelude: false
|
|
375
|
-
});
|
|
376
|
-
} catch (lenientError) {
|
|
377
|
-
return {
|
|
378
|
-
keyframes: {},
|
|
379
|
-
transitions: [],
|
|
380
|
-
animatedElements: [],
|
|
381
|
-
error: `CSS parse error: ${parseError.message}. Lenient parse also failed: ${lenientError.message}`
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
return extractAllFromAst(ast);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// ============================================================================
|
|
390
|
-
// CSS Generation
|
|
391
|
-
// ============================================================================
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Generate animations.css from extracted keyframes.
|
|
395
|
-
*
|
|
396
|
-
* @param {ExtractionResult} animationData - Result from extractAnimations()
|
|
397
|
-
* @returns {string} CSS string with @keyframes definitions
|
|
398
|
-
*
|
|
399
|
-
* @example
|
|
400
|
-
* const result = await extractAnimations(css);
|
|
401
|
-
* const animCss = generateAnimationsCss(result);
|
|
402
|
-
* fs.writeFileSync('animations.css', animCss);
|
|
403
|
-
*/
|
|
404
|
-
export function generateAnimationsCss(animationData) {
|
|
405
|
-
const { keyframes } = animationData || {};
|
|
406
|
-
|
|
407
|
-
if (!keyframes || Object.keys(keyframes).length === 0) {
|
|
408
|
-
return '/* No @keyframes found */\n';
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const lines = [
|
|
412
|
-
'/**',
|
|
413
|
-
' * Extracted CSS Animations',
|
|
414
|
-
' * Generated by design-clone animation-extractor',
|
|
415
|
-
' */\n'
|
|
416
|
-
];
|
|
417
|
-
|
|
418
|
-
for (const [name, data] of Object.entries(keyframes)) {
|
|
419
|
-
const frameCount = data.frames?.length || 0;
|
|
420
|
-
lines.push(`/* Keyframes: ${name} (${frameCount} frames) */`);
|
|
421
|
-
lines.push(data.raw);
|
|
422
|
-
lines.push('');
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return lines.join('\n');
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// ============================================================================
|
|
429
|
-
// Token Generation
|
|
430
|
-
// ============================================================================
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Extract timing values from shorthand transition/animation strings.
|
|
434
|
-
*
|
|
435
|
-
* Limitations:
|
|
436
|
-
* - Only extracts first duration from shorthand (CSS allows multiple)
|
|
437
|
-
* - Timing function detection is keyword-based, may miss complex values
|
|
438
|
-
* - Does not resolve CSS variables (e.g., var(--duration))
|
|
439
|
-
*
|
|
440
|
-
* @param {string} shorthand - Shorthand property value
|
|
441
|
-
* @param {Set<string>} durations - Set to add durations to
|
|
442
|
-
* @param {Set<string>} timings - Set to add timing functions to
|
|
443
|
-
*/
|
|
444
|
-
function extractTimingFromShorthand(shorthand, durations, timings) {
|
|
445
|
-
if (!shorthand) return;
|
|
446
|
-
|
|
447
|
-
// Extract all duration values
|
|
448
|
-
const durationMatches = shorthand.match(DURATION_PATTERN);
|
|
449
|
-
if (durationMatches) {
|
|
450
|
-
durationMatches.forEach(d => durations.add(d));
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// Extract cubic-bezier functions
|
|
454
|
-
const bezierMatches = shorthand.match(CUBIC_BEZIER_PATTERN);
|
|
455
|
-
if (bezierMatches) {
|
|
456
|
-
bezierMatches.forEach(b => timings.add(b));
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Check for timing keywords
|
|
460
|
-
const lowerShorthand = shorthand.toLowerCase();
|
|
461
|
-
for (const keyword of TIMING_KEYWORDS) {
|
|
462
|
-
if (lowerShorthand.includes(keyword)) {
|
|
463
|
-
timings.add(keyword);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Generate animation tokens for design-tokens.json.
|
|
470
|
-
*
|
|
471
|
-
* @param {ExtractionResult} animationData - Result from extractAnimations()
|
|
472
|
-
* @returns {AnimationTokens} Animation tokens structure
|
|
473
|
-
*
|
|
474
|
-
* @example
|
|
475
|
-
* const result = await extractAnimations(css);
|
|
476
|
-
* const tokens = generateAnimationTokens(result);
|
|
477
|
-
* console.log(tokens.durations); // ['200ms', '300ms', '1s']
|
|
478
|
-
*/
|
|
479
|
-
export function generateAnimationTokens(animationData) {
|
|
480
|
-
const { keyframes = {}, transitions = [], animatedElements = [] } = animationData || {};
|
|
481
|
-
|
|
482
|
-
const durations = new Set();
|
|
483
|
-
const timings = new Set();
|
|
484
|
-
|
|
485
|
-
// Extract from transitions
|
|
486
|
-
for (const t of transitions) {
|
|
487
|
-
if (t['transition-duration']) {
|
|
488
|
-
t['transition-duration'].split(',').forEach(d => durations.add(d.trim()));
|
|
489
|
-
}
|
|
490
|
-
if (t['transition-timing-function']) {
|
|
491
|
-
t['transition-timing-function'].split(',').forEach(tf => timings.add(tf.trim()));
|
|
492
|
-
}
|
|
493
|
-
if (t.transition) {
|
|
494
|
-
extractTimingFromShorthand(t.transition, durations, timings);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// Extract from animated elements
|
|
499
|
-
for (const a of animatedElements) {
|
|
500
|
-
if (a['animation-duration']) {
|
|
501
|
-
a['animation-duration'].split(',').forEach(d => durations.add(d.trim()));
|
|
502
|
-
}
|
|
503
|
-
if (a['animation-timing-function']) {
|
|
504
|
-
a['animation-timing-function'].split(',').forEach(tf => timings.add(tf.trim()));
|
|
505
|
-
}
|
|
506
|
-
if (a.animation) {
|
|
507
|
-
extractTimingFromShorthand(a.animation, durations, timings);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
return {
|
|
512
|
-
keyframes: Object.keys(keyframes),
|
|
513
|
-
keyframeCount: Object.keys(keyframes).length,
|
|
514
|
-
transitions: transitions.length,
|
|
515
|
-
animatedElements: animatedElements.length,
|
|
516
|
-
durations: [...durations].sort(),
|
|
517
|
-
timingFunctions: [...timings].sort()
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// ============================================================================
|
|
522
|
-
// Exports
|
|
523
|
-
// ============================================================================
|
|
524
|
-
|
|
525
|
-
// Export individual functions for testing and advanced use
|
|
526
|
-
export { extractKeyframes, extractTransitions, extractAnimationProps };
|