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,342 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Content Counter
|
|
3
|
-
*
|
|
4
|
-
* Parse page DOM to extract exact content counts for:
|
|
5
|
-
* - Grid items, list items, cards
|
|
6
|
-
* - Navigation links
|
|
7
|
-
* - Sections/containers
|
|
8
|
-
* - Images, buttons, forms
|
|
9
|
-
*
|
|
10
|
-
* Outputs content-counts.json for use in structure analysis.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Count content items in page DOM
|
|
15
|
-
* @param {Page} page - Playwright page
|
|
16
|
-
* @returns {Promise<object>} Content counts
|
|
17
|
-
*/
|
|
18
|
-
export async function extractContentCounts(page) {
|
|
19
|
-
return await page.evaluate(() => {
|
|
20
|
-
const counts = {
|
|
21
|
-
extractedAt: new Date().toISOString(),
|
|
22
|
-
|
|
23
|
-
// Section counts
|
|
24
|
-
sections: {
|
|
25
|
-
total: 0,
|
|
26
|
-
withBackground: 0,
|
|
27
|
-
details: []
|
|
28
|
-
},
|
|
29
|
-
|
|
30
|
-
// Grid/List containers
|
|
31
|
-
grids: {
|
|
32
|
-
total: 0,
|
|
33
|
-
details: []
|
|
34
|
-
},
|
|
35
|
-
|
|
36
|
-
// Repeated items (cards, list items)
|
|
37
|
-
repeatedItems: {
|
|
38
|
-
total: 0,
|
|
39
|
-
byType: {}
|
|
40
|
-
},
|
|
41
|
-
|
|
42
|
-
// Navigation
|
|
43
|
-
navigation: {
|
|
44
|
-
headerLinks: 0,
|
|
45
|
-
footerLinks: 0,
|
|
46
|
-
allLinks: 0
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
// Media
|
|
50
|
-
media: {
|
|
51
|
-
images: 0,
|
|
52
|
-
videos: 0,
|
|
53
|
-
svgIcons: 0
|
|
54
|
-
},
|
|
55
|
-
|
|
56
|
-
// Interactive
|
|
57
|
-
interactive: {
|
|
58
|
-
buttons: 0,
|
|
59
|
-
inputs: 0,
|
|
60
|
-
forms: 0
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// Helper: check if element is visible (not hidden)
|
|
65
|
-
const isVisible = (el) => {
|
|
66
|
-
if (!el) return false;
|
|
67
|
-
const style = getComputedStyle(el);
|
|
68
|
-
return style.display !== 'none' &&
|
|
69
|
-
style.visibility !== 'hidden' &&
|
|
70
|
-
!el.hasAttribute('hidden');
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// Helper: get meaningful class name for element
|
|
74
|
-
const getSelector = (el) => {
|
|
75
|
-
if (el.id) return `#${el.id}`;
|
|
76
|
-
const classes = [...el.classList].filter(c =>
|
|
77
|
-
!c.match(/^(js-|is-|has-|data-)/) &&
|
|
78
|
-
c.length > 2
|
|
79
|
-
).slice(0, 3).join('.');
|
|
80
|
-
return classes ? `.${classes}` : el.tagName.toLowerCase();
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
// 1. Count sections (major containers with padding/background)
|
|
84
|
-
const sectionSelectors = [
|
|
85
|
-
'section',
|
|
86
|
-
'[class*="section"]',
|
|
87
|
-
'[class*="py-lg"]', '[class*="py-xl"]', '[class*="py-2xl"]',
|
|
88
|
-
'[class*="py-md"]',
|
|
89
|
-
'[class*="bg-background"]',
|
|
90
|
-
'[class*="bg-white"]',
|
|
91
|
-
'[class*="bg-gray"]'
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
const sectionElements = new Set();
|
|
95
|
-
const MAX_SECTION_DETAILS = 30;
|
|
96
|
-
|
|
97
|
-
sectionSelectors.forEach(sel => {
|
|
98
|
-
try {
|
|
99
|
-
document.querySelectorAll(sel).forEach(el => {
|
|
100
|
-
if (sectionElements.size >= MAX_SECTION_DETAILS) return;
|
|
101
|
-
|
|
102
|
-
// Check if element has significant height and width (major section)
|
|
103
|
-
const rect = el.getBoundingClientRect();
|
|
104
|
-
const isSignificant = rect.height > 100 && rect.width > 200;
|
|
105
|
-
|
|
106
|
-
// Count elements that are either:
|
|
107
|
-
// 1. Direct children of body/main/root
|
|
108
|
-
// 2. Have section-like characteristics (padding, bg, significant size)
|
|
109
|
-
const parent = el.parentElement;
|
|
110
|
-
const isTopLevel = parent?.tagName === 'BODY' ||
|
|
111
|
-
parent?.tagName === 'MAIN' ||
|
|
112
|
-
parent?.id === 'root' ||
|
|
113
|
-
parent?.id === '__next' ||
|
|
114
|
-
parent?.classList.contains('container');
|
|
115
|
-
|
|
116
|
-
if (isTopLevel || (isSignificant && isVisible(el))) {
|
|
117
|
-
sectionElements.add(el);
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
} catch (e) { /* invalid selector */ }
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
sectionElements.forEach(el => {
|
|
124
|
-
const style = getComputedStyle(el);
|
|
125
|
-
const hasBg = style.backgroundColor !== 'rgba(0, 0, 0, 0)' &&
|
|
126
|
-
style.backgroundColor !== 'transparent';
|
|
127
|
-
counts.sections.total++;
|
|
128
|
-
if (hasBg) counts.sections.withBackground++;
|
|
129
|
-
|
|
130
|
-
counts.sections.details.push({
|
|
131
|
-
selector: getSelector(el),
|
|
132
|
-
visible: isVisible(el),
|
|
133
|
-
hasBackground: hasBg,
|
|
134
|
-
childCount: el.children.length
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// 2. Count grid/flex containers and their items
|
|
139
|
-
// Only target meaningful containers (not trivial wrappers)
|
|
140
|
-
const gridSelectors = [
|
|
141
|
-
'[class*="grid"]',
|
|
142
|
-
'[style*="display: grid"]'
|
|
143
|
-
];
|
|
144
|
-
|
|
145
|
-
// Separate flex selectors - more selective
|
|
146
|
-
const flexSelectors = [
|
|
147
|
-
'[class*="flex"][class*="gap"]',
|
|
148
|
-
'[class*="flex"][class*="wrap"]',
|
|
149
|
-
'[class*="flex"][class*="col"]'
|
|
150
|
-
];
|
|
151
|
-
|
|
152
|
-
const processedGrids = new Set();
|
|
153
|
-
const MIN_ITEMS_FOR_GRID = 2; // Only count containers with 2+ items
|
|
154
|
-
const MAX_GRID_DETAILS = 50; // Limit output size
|
|
155
|
-
|
|
156
|
-
[...gridSelectors, ...flexSelectors].forEach(sel => {
|
|
157
|
-
try {
|
|
158
|
-
document.querySelectorAll(sel).forEach(el => {
|
|
159
|
-
if (processedGrids.has(el)) return;
|
|
160
|
-
if (counts.grids.details.length >= MAX_GRID_DETAILS) return;
|
|
161
|
-
|
|
162
|
-
const style = getComputedStyle(el);
|
|
163
|
-
if (style.display === 'grid' || style.display === 'flex' ||
|
|
164
|
-
style.display === 'inline-grid' || style.display === 'inline-flex') {
|
|
165
|
-
processedGrids.add(el);
|
|
166
|
-
|
|
167
|
-
// Count direct children (grid items)
|
|
168
|
-
const items = [...el.children].filter(child =>
|
|
169
|
-
child.tagName !== 'SCRIPT' &&
|
|
170
|
-
child.tagName !== 'STYLE'
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
// Skip containers with fewer than MIN_ITEMS
|
|
174
|
-
if (items.length < MIN_ITEMS_FOR_GRID) return;
|
|
175
|
-
|
|
176
|
-
// Count visible vs hidden
|
|
177
|
-
const visibleItems = items.filter(isVisible);
|
|
178
|
-
const hiddenItems = items.filter(i => !isVisible(i));
|
|
179
|
-
|
|
180
|
-
// Only count if has meaningful visible items
|
|
181
|
-
if (visibleItems.length >= MIN_ITEMS_FOR_GRID) {
|
|
182
|
-
counts.grids.total++;
|
|
183
|
-
counts.grids.details.push({
|
|
184
|
-
selector: getSelector(el),
|
|
185
|
-
display: style.display,
|
|
186
|
-
totalItems: items.length,
|
|
187
|
-
visibleItems: visibleItems.length,
|
|
188
|
-
hiddenItems: hiddenItems.length,
|
|
189
|
-
gridCols: style.gridTemplateColumns || null,
|
|
190
|
-
visible: isVisible(el)
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
} catch (e) { /* invalid selector */ }
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
// 3. Count repeated items (cards, list items, etc.)
|
|
199
|
-
const repeatPatterns = [
|
|
200
|
-
{ name: 'cards', selectors: ['[class*="card"]', '[class*="Card"]'] },
|
|
201
|
-
{ name: 'listItems', selectors: ['li', '[class*="item"]', '[class*="Item"]'] },
|
|
202
|
-
{ name: 'services', selectors: ['[class*="service"]', '[class*="Service"]'] },
|
|
203
|
-
{ name: 'features', selectors: ['[class*="feature"]', '[class*="Feature"]'] },
|
|
204
|
-
{ name: 'testimonials', selectors: ['[class*="testimonial"]', '[class*="review"]'] },
|
|
205
|
-
{ name: 'teamMembers', selectors: ['[class*="team"]', '[class*="member"]', '[class*="person"]'] },
|
|
206
|
-
{ name: 'faqItems', selectors: ['[class*="faq"]', '[class*="accordion"]', 'details'] },
|
|
207
|
-
{ name: 'pricingCards', selectors: ['[class*="pricing"]', '[class*="plan"]'] },
|
|
208
|
-
{ name: 'blogPosts', selectors: ['[class*="post"]', '[class*="article"]', 'article'] },
|
|
209
|
-
{ name: 'products', selectors: ['[class*="product"]', '[class*="Product"]'] },
|
|
210
|
-
{ name: 'categories', selectors: ['[class*="category"]', '[class*="Category"]'] }
|
|
211
|
-
];
|
|
212
|
-
|
|
213
|
-
repeatPatterns.forEach(({ name, selectors }) => {
|
|
214
|
-
let totalCount = 0;
|
|
215
|
-
let visibleCount = 0;
|
|
216
|
-
|
|
217
|
-
selectors.forEach(sel => {
|
|
218
|
-
try {
|
|
219
|
-
const elements = document.querySelectorAll(sel);
|
|
220
|
-
elements.forEach(el => {
|
|
221
|
-
totalCount++;
|
|
222
|
-
if (isVisible(el)) visibleCount++;
|
|
223
|
-
});
|
|
224
|
-
} catch (e) { /* invalid selector */ }
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
if (totalCount > 0) {
|
|
228
|
-
counts.repeatedItems.byType[name] = {
|
|
229
|
-
total: totalCount,
|
|
230
|
-
visible: visibleCount,
|
|
231
|
-
hidden: totalCount - visibleCount
|
|
232
|
-
};
|
|
233
|
-
counts.repeatedItems.total += totalCount;
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
// 4. Count navigation links
|
|
238
|
-
const header = document.querySelector('header, [class*="header"], nav');
|
|
239
|
-
const footer = document.querySelector('footer, [class*="footer"]');
|
|
240
|
-
|
|
241
|
-
if (header) {
|
|
242
|
-
counts.navigation.headerLinks = header.querySelectorAll('a').length;
|
|
243
|
-
}
|
|
244
|
-
if (footer) {
|
|
245
|
-
counts.navigation.footerLinks = footer.querySelectorAll('a').length;
|
|
246
|
-
}
|
|
247
|
-
counts.navigation.allLinks = document.querySelectorAll('a').length;
|
|
248
|
-
|
|
249
|
-
// 5. Count media elements
|
|
250
|
-
counts.media.images = document.querySelectorAll('img, picture').length;
|
|
251
|
-
counts.media.videos = document.querySelectorAll('video, iframe[src*="youtube"], iframe[src*="vimeo"]').length;
|
|
252
|
-
counts.media.svgIcons = document.querySelectorAll('svg').length;
|
|
253
|
-
|
|
254
|
-
// 6. Count interactive elements
|
|
255
|
-
counts.interactive.buttons = document.querySelectorAll('button, [role="button"], input[type="submit"], input[type="button"]').length;
|
|
256
|
-
counts.interactive.inputs = document.querySelectorAll('input, textarea, select').length;
|
|
257
|
-
counts.interactive.forms = document.querySelectorAll('form').length;
|
|
258
|
-
|
|
259
|
-
// 7. Calculate summary
|
|
260
|
-
counts.summary = {
|
|
261
|
-
majorSections: counts.sections.total,
|
|
262
|
-
gridContainers: counts.grids.total,
|
|
263
|
-
totalRepeatedItems: counts.repeatedItems.total,
|
|
264
|
-
totalLinks: counts.navigation.allLinks,
|
|
265
|
-
totalImages: counts.media.images,
|
|
266
|
-
totalButtons: counts.interactive.buttons,
|
|
267
|
-
|
|
268
|
-
// Provide estimates for generation
|
|
269
|
-
recommendedItemCounts: {}
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
// Calculate recommended item counts per grid
|
|
273
|
-
counts.grids.details.forEach((grid, idx) => {
|
|
274
|
-
if (grid.visibleItems >= 3) {
|
|
275
|
-
counts.summary.recommendedItemCounts[grid.selector] = grid.visibleItems;
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
// Add repeated items recommendations
|
|
280
|
-
Object.entries(counts.repeatedItems.byType).forEach(([type, data]) => {
|
|
281
|
-
if (data.visible >= 2) {
|
|
282
|
-
counts.summary.recommendedItemCounts[type] = data.visible;
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
return counts;
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Generate concise content summary for prompt injection
|
|
292
|
-
* @param {object} counts - Content counts from extractContentCounts
|
|
293
|
-
* @returns {string} Summary text
|
|
294
|
-
*/
|
|
295
|
-
export function generateContentSummary(counts) {
|
|
296
|
-
const lines = [
|
|
297
|
-
'## EXACT CONTENT COUNTS (from DOM parsing)',
|
|
298
|
-
''
|
|
299
|
-
];
|
|
300
|
-
|
|
301
|
-
// Sections
|
|
302
|
-
lines.push(`### Sections: ${counts.sections.total} total`);
|
|
303
|
-
counts.sections.details.slice(0, 10).forEach(s => {
|
|
304
|
-
lines.push(`- ${s.selector}: ${s.childCount} children${s.visible ? '' : ' (hidden)'}`);
|
|
305
|
-
});
|
|
306
|
-
lines.push('');
|
|
307
|
-
|
|
308
|
-
// Grids with item counts
|
|
309
|
-
lines.push(`### Grid/Flex Containers: ${counts.grids.total} total`);
|
|
310
|
-
counts.grids.details.slice(0, 15).forEach(g => {
|
|
311
|
-
const visibilityNote = g.hiddenItems > 0 ? ` (+${g.hiddenItems} hidden)` : '';
|
|
312
|
-
lines.push(`- ${g.selector}: ${g.visibleItems} visible items${visibilityNote}`);
|
|
313
|
-
});
|
|
314
|
-
lines.push('');
|
|
315
|
-
|
|
316
|
-
// Repeated items
|
|
317
|
-
if (Object.keys(counts.repeatedItems.byType).length > 0) {
|
|
318
|
-
lines.push('### Repeated Items:');
|
|
319
|
-
Object.entries(counts.repeatedItems.byType).forEach(([type, data]) => {
|
|
320
|
-
const hiddenNote = data.hidden > 0 ? ` (+${data.hidden} hidden)` : '';
|
|
321
|
-
lines.push(`- ${type}: ${data.visible} visible${hiddenNote}`);
|
|
322
|
-
});
|
|
323
|
-
lines.push('');
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Links and media
|
|
327
|
-
lines.push('### Navigation & Media:');
|
|
328
|
-
lines.push(`- Header links: ${counts.navigation.headerLinks}`);
|
|
329
|
-
lines.push(`- Footer links: ${counts.navigation.footerLinks}`);
|
|
330
|
-
lines.push(`- Images: ${counts.media.images}`);
|
|
331
|
-
lines.push(`- SVG icons: ${counts.media.svgIcons}`);
|
|
332
|
-
lines.push('');
|
|
333
|
-
|
|
334
|
-
// Critical instruction
|
|
335
|
-
lines.push('### GENERATION INSTRUCTION:');
|
|
336
|
-
lines.push('When generating HTML, use EXACTLY these item counts:');
|
|
337
|
-
Object.entries(counts.summary.recommendedItemCounts).forEach(([selector, count]) => {
|
|
338
|
-
lines.push(`- ${selector}: ${count} items`);
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
return lines.join('\n');
|
|
342
|
-
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Design Tokens Extraction Wrapper
|
|
3
|
-
*
|
|
4
|
-
* Wraps the Python script for extracting design tokens from screenshots.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* import { extractDesignTokens } from './design-tokens.js';
|
|
8
|
-
* const result = await extractDesignTokens(outputDir, cssPath);
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { spawn } from 'child_process';
|
|
12
|
-
import path from 'path';
|
|
13
|
-
import { fileURLToPath } from 'url';
|
|
14
|
-
|
|
15
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
-
const __dirname = path.dirname(__filename);
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Extract design tokens from screenshots using Gemini Vision API
|
|
20
|
-
*
|
|
21
|
-
* @param {string} outputDir - Output directory (contains analysis/desktop/*.png)
|
|
22
|
-
* @param {string} cssPath - Path to merged CSS file for reference
|
|
23
|
-
* @returns {Promise<Object>} Result with { success, tokens_json, tokens_css }
|
|
24
|
-
*/
|
|
25
|
-
export async function extractDesignTokens(outputDir, cssPath = null) {
|
|
26
|
-
const scriptPath = path.resolve(__dirname, '../ai/extract-design-tokens.py');
|
|
27
|
-
const screenshotsDir = path.join(outputDir, 'analysis', 'desktop');
|
|
28
|
-
|
|
29
|
-
// Build args
|
|
30
|
-
const args = [
|
|
31
|
-
scriptPath,
|
|
32
|
-
'--screenshots', screenshotsDir,
|
|
33
|
-
'--output', outputDir
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
if (cssPath) {
|
|
37
|
-
args.push('--css', cssPath);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return new Promise((resolve) => {
|
|
41
|
-
const proc = spawn('python3', args, {
|
|
42
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
43
|
-
env: { ...process.env }
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
let stdout = '';
|
|
47
|
-
let stderr = '';
|
|
48
|
-
|
|
49
|
-
proc.stdout.on('data', (data) => {
|
|
50
|
-
stdout += data.toString();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
proc.stderr.on('data', (data) => {
|
|
54
|
-
stderr += data.toString();
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
proc.on('close', (code) => {
|
|
58
|
-
if (code !== 0) {
|
|
59
|
-
// Try to parse error from stdout (script outputs JSON errors)
|
|
60
|
-
try {
|
|
61
|
-
const errorResult = JSON.parse(stdout);
|
|
62
|
-
resolve({
|
|
63
|
-
success: false,
|
|
64
|
-
error: errorResult.error || 'Unknown error',
|
|
65
|
-
hint: errorResult.hint || null
|
|
66
|
-
});
|
|
67
|
-
} catch {
|
|
68
|
-
resolve({
|
|
69
|
-
success: false,
|
|
70
|
-
error: stderr || `Process exited with code ${code}`
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Parse success result
|
|
77
|
-
try {
|
|
78
|
-
const result = JSON.parse(stdout);
|
|
79
|
-
resolve(result);
|
|
80
|
-
} catch (err) {
|
|
81
|
-
resolve({
|
|
82
|
-
success: false,
|
|
83
|
-
error: `Failed to parse output: ${err.message}`
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
proc.on('error', (err) => {
|
|
89
|
-
if (err.code === 'ENOENT') {
|
|
90
|
-
resolve({
|
|
91
|
-
success: false,
|
|
92
|
-
error: 'Python3 not found',
|
|
93
|
-
hint: 'Install Python 3 to enable AI token extraction'
|
|
94
|
-
});
|
|
95
|
-
} else {
|
|
96
|
-
resolve({
|
|
97
|
-
success: false,
|
|
98
|
-
error: err.message
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
}
|