design-clone 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +13 -34
  2. package/SKILL.md +69 -45
  3. package/bin/cli.js +22 -4
  4. package/bin/commands/clone-site.js +31 -171
  5. package/bin/commands/help.js +19 -6
  6. package/bin/commands/init.js +9 -86
  7. package/bin/commands/uninstall.js +105 -0
  8. package/bin/commands/update.js +70 -0
  9. package/bin/commands/verify.js +7 -14
  10. package/bin/utils/paths.js +28 -0
  11. package/bin/utils/validate.js +2 -22
  12. package/bin/utils/version.js +23 -0
  13. package/docs/code-standards.md +789 -0
  14. package/docs/codebase-summary.md +533 -286
  15. package/docs/index.md +74 -0
  16. package/docs/project-overview-pdr.md +797 -0
  17. package/docs/system-architecture.md +718 -0
  18. package/package.json +14 -17
  19. package/src/ai/prompts/design-tokens/basic.md +80 -0
  20. package/src/ai/prompts/design-tokens/section-with-css.md +41 -0
  21. package/src/ai/prompts/design-tokens/section.md +48 -0
  22. package/src/ai/prompts/design-tokens/with-css.md +87 -0
  23. package/src/ai/prompts/structure-analysis/basic.md +55 -0
  24. package/src/ai/prompts/structure-analysis/with-context.md +59 -0
  25. package/src/ai/prompts/structure-analysis/with-dimensions.md +63 -0
  26. package/src/ai/prompts/structure-analysis/with-hierarchy.md +73 -0
  27. package/src/ai/prompts/ux-audit/aggregation.md +42 -0
  28. package/src/ai/prompts/ux-audit/desktop.md +92 -0
  29. package/src/ai/prompts/ux-audit/mobile.md +93 -0
  30. package/src/ai/prompts/ux-audit/tablet.md +92 -0
  31. package/src/core/animation/animation-extractor-ast.js +183 -0
  32. package/src/core/animation/animation-extractor-output.js +152 -0
  33. package/src/core/animation/animation-extractor.js +178 -0
  34. package/src/core/animation/state-capture-detection.js +200 -0
  35. package/src/core/animation/state-capture.js +193 -0
  36. package/src/core/capture/browser-context-pool.js +96 -0
  37. package/src/core/capture/multi-page-screenshot-page.js +110 -0
  38. package/src/core/capture/multi-page-screenshot.js +208 -0
  39. package/src/core/capture/screenshot-extraction.js +186 -0
  40. package/src/core/capture/screenshot-helpers.js +175 -0
  41. package/src/core/capture/screenshot-orchestrator.js +174 -0
  42. package/src/core/capture/screenshot-viewport.js +93 -0
  43. package/src/core/capture/screenshot.js +192 -0
  44. package/src/core/content/content-counter-dom.js +191 -0
  45. package/src/core/content/content-counter.js +76 -0
  46. package/src/core/css/breakpoint-detector.js +66 -0
  47. package/src/core/css/chromium-defaults.json +23 -0
  48. package/src/core/css/computed-style-extractor.js +102 -0
  49. package/src/core/css/css-chunker.js +103 -0
  50. package/src/core/css/filter-css-dead-code.js +120 -0
  51. package/src/core/css/filter-css-html-analyzer.js +110 -0
  52. package/src/core/css/filter-css-selector-matcher.js +172 -0
  53. package/src/core/css/filter-css.js +206 -0
  54. package/src/core/css/merge-css-atrule-processor.js +158 -0
  55. package/src/core/css/merge-css-file-io.js +68 -0
  56. package/src/core/css/merge-css.js +148 -0
  57. package/src/core/detection/framework-detector-routing.js +68 -0
  58. package/src/core/detection/framework-detector-signals.js +65 -0
  59. package/src/core/detection/framework-detector.js +198 -0
  60. package/src/core/dimension/dimension-extractor-card-detector.js +82 -0
  61. package/src/core/dimension/dimension-extractor.js +317 -0
  62. package/src/core/dimension/dimension-output-ai-summary.js +111 -0
  63. package/src/core/dimension/dimension-output.js +173 -0
  64. package/src/core/dimension/dom-tree-analyzer-tree-builders.js +95 -0
  65. package/src/core/dimension/dom-tree-analyzer.js +191 -0
  66. package/src/core/discovery/app-state-snapshot-capture.js +195 -0
  67. package/src/core/discovery/app-state-snapshot-utils.js +178 -0
  68. package/src/core/discovery/app-state-snapshot.js +131 -0
  69. package/src/core/discovery/discover-pages-routes.js +84 -0
  70. package/src/core/discovery/discover-pages-utils.js +177 -0
  71. package/src/core/discovery/discover-pages.js +191 -0
  72. package/src/core/html/html-extractor-inline-styler.js +70 -0
  73. package/src/core/html/html-extractor.js +147 -0
  74. package/src/core/html/semantic-enhancer-mappings.js +200 -0
  75. package/src/core/html/semantic-enhancer-page.js +148 -0
  76. package/src/core/html/semantic-enhancer.js +135 -0
  77. package/src/core/links/rewrite-links-css-rewriter.js +53 -0
  78. package/src/core/links/rewrite-links.js +173 -0
  79. package/src/core/media/asset-validator.js +118 -0
  80. package/src/core/media/extract-assets-downloader.js +187 -0
  81. package/src/core/media/extract-assets-page-scraper.js +115 -0
  82. package/src/core/media/extract-assets.js +159 -0
  83. package/src/core/media/video-capture-convert.js +200 -0
  84. package/src/core/media/video-capture.js +201 -0
  85. package/src/core/{lazy-loader.js → page-prep/lazy-loader.js} +37 -39
  86. package/src/core/section/section-cropper-helpers.js +43 -0
  87. package/src/core/{section-cropper.js → section/section-cropper.js} +11 -88
  88. package/src/core/section/section-detector-strategies.js +139 -0
  89. package/src/core/section/section-detector-utils.js +100 -0
  90. package/src/core/section/section-detector.js +88 -0
  91. package/src/core/tests/test-section-cropper.js +2 -2
  92. package/src/core/tests/test-section-detector.js +2 -2
  93. package/src/post-process/enhance-assets.js +29 -4
  94. package/src/post-process/fetch-images-unsplash-client.js +123 -0
  95. package/src/post-process/fetch-images.js +60 -263
  96. package/src/post-process/inject-gosnap.js +88 -0
  97. package/src/post-process/inject-icons-svg-replacer.js +76 -0
  98. package/src/post-process/inject-icons.js +47 -200
  99. package/src/route-discoverers/base-discoverer-utils.js +137 -0
  100. package/src/route-discoverers/base-discoverer.js +29 -118
  101. package/src/route-discoverers/index.js +1 -1
  102. package/src/shared/config.js +38 -0
  103. package/src/shared/error-codes.js +31 -0
  104. package/src/shared/viewports.js +46 -0
  105. package/src/utils/browser.js +0 -7
  106. package/src/utils/helpers.js +4 -0
  107. package/src/utils/log.js +12 -0
  108. package/src/utils/playwright-loader.js +76 -0
  109. package/src/utils/playwright.js +3 -69
  110. package/src/utils/progress.js +32 -0
  111. package/src/verification/generate-audit-report-css-fixes.js +52 -0
  112. package/src/verification/generate-audit-report-sections.js +158 -0
  113. package/src/verification/generate-audit-report.js +5 -281
  114. package/src/verification/quality-scorer.js +92 -0
  115. package/src/verification/verify-footer-checks.js +103 -0
  116. package/src/verification/verify-footer-helpers.js +178 -0
  117. package/src/verification/verify-footer.js +23 -381
  118. package/src/verification/verify-header-checks.js +104 -0
  119. package/src/verification/verify-header-helpers.js +156 -0
  120. package/src/verification/verify-header.js +23 -365
  121. package/src/verification/verify-layout-report.js +101 -0
  122. package/src/verification/verify-layout.js +13 -259
  123. package/src/verification/verify-menu-checks.js +104 -0
  124. package/src/verification/verify-menu-helpers.js +112 -0
  125. package/src/verification/verify-menu.js +17 -285
  126. package/src/verification/verify-slider-checks.js +115 -0
  127. package/src/verification/verify-slider-constants.js +65 -0
  128. package/src/verification/verify-slider-helpers.js +164 -0
  129. package/src/verification/verify-slider.js +23 -414
  130. package/.env.example +0 -14
  131. package/docs/basic-clone.md +0 -63
  132. package/docs/cli-reference.md +0 -316
  133. package/docs/design-clone-architecture.md +0 -492
  134. package/docs/pixel-perfect.md +0 -117
  135. package/docs/project-roadmap.md +0 -382
  136. package/docs/troubleshooting.md +0 -170
  137. package/requirements.txt +0 -5
  138. package/src/ai/__pycache__/analyze-structure.cpython-313.pyc +0 -0
  139. package/src/ai/__pycache__/extract-design-tokens.cpython-313.pyc +0 -0
  140. package/src/ai/analyze-structure.py +0 -375
  141. package/src/ai/extract-design-tokens.py +0 -782
  142. package/src/ai/prompts/__init__.py +0 -2
  143. package/src/ai/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  144. package/src/ai/prompts/__pycache__/design_tokens.cpython-313.pyc +0 -0
  145. package/src/ai/prompts/__pycache__/structure_analysis.cpython-313.pyc +0 -0
  146. package/src/ai/prompts/__pycache__/ux_audit.cpython-313.pyc +0 -0
  147. package/src/ai/prompts/design_tokens.py +0 -316
  148. package/src/ai/prompts/structure_analysis.py +0 -592
  149. package/src/ai/prompts/ux_audit.py +0 -198
  150. package/src/ai/ux-audit.js +0 -596
  151. package/src/core/animation-extractor.js +0 -526
  152. package/src/core/app-state-snapshot.js +0 -511
  153. package/src/core/content-counter.js +0 -342
  154. package/src/core/design-tokens.js +0 -103
  155. package/src/core/dimension-extractor.js +0 -438
  156. package/src/core/dimension-output.js +0 -305
  157. package/src/core/discover-pages.js +0 -542
  158. package/src/core/dom-tree-analyzer.js +0 -298
  159. package/src/core/extract-assets.js +0 -468
  160. package/src/core/filter-css.js +0 -499
  161. package/src/core/framework-detector.js +0 -538
  162. package/src/core/html-extractor.js +0 -212
  163. package/src/core/merge-css.js +0 -407
  164. package/src/core/multi-page-screenshot.js +0 -380
  165. package/src/core/rewrite-links.js +0 -226
  166. package/src/core/screenshot.js +0 -701
  167. package/src/core/section-detector.js +0 -386
  168. package/src/core/semantic-enhancer.js +0 -492
  169. package/src/core/state-capture.js +0 -598
  170. package/src/core/video-capture.js +0 -546
  171. package/src/utils/__init__.py +0 -16
  172. package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  173. package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
  174. package/src/utils/env.py +0 -134
  175. /package/src/core/{css-extractor.js → css/css-extractor.js} +0 -0
  176. /package/src/core/{cookie-handler.js → page-prep/cookie-handler.js} +0 -0
  177. /package/src/core/{page-readiness.js → page-prep/page-readiness.js} +0 -0
@@ -1,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 };