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,386 +0,0 @@
1
- /**
2
- * Section Detector
3
- *
4
- * Detect semantic page sections from DOM hierarchy for section-based
5
- * screenshot analysis. Returns bounding boxes for cropping.
6
- *
7
- * Usage:
8
- * import { detectSections } from './section-detector.js';
9
- * const sections = await detectSections(page, { padding: 40 });
10
- *
11
- * Strategies (in order):
12
- * 1. Semantic HTML: <header>, <main>, <section>, <footer>
13
- * 2. data-section attributes
14
- * 3. Class patterns: hero, services, features, about, contact
15
- * 4. Large direct children of <main> or <body> (>200px height)
16
- * 5. Fallback: viewport chunking if <minSections detected
17
- */
18
-
19
- import { extractDOMHierarchy } from './dom-tree-analyzer.js';
20
-
21
- // Section class patterns to match
22
- const SECTION_CLASS_PATTERNS = [
23
- 'hero', 'banner', 'header', 'navigation', 'nav',
24
- 'services', 'features', 'about', 'team', 'portfolio',
25
- 'testimonials', 'reviews', 'pricing', 'plans',
26
- 'faq', 'questions', 'blog', 'news', 'articles',
27
- 'contact', 'cta', 'call-to-action', 'newsletter',
28
- 'footer', 'partners', 'clients', 'gallery', 'showcase'
29
- ];
30
-
31
- // Default configuration
32
- const DEFAULT_OPTIONS = {
33
- minSections: 3,
34
- maxSections: 20,
35
- padding: 40,
36
- fallbackToViewport: true,
37
- viewportHeight: 900,
38
- minSectionHeight: 150,
39
- overlapRatio: 0.1 // 10% overlap for viewport fallback
40
- };
41
-
42
- /**
43
- * Detect page sections from DOM hierarchy
44
- * @param {import('playwright').Page} page - Playwright page instance
45
- * @param {Object} options - Configuration options
46
- * @returns {Promise<Array>} Array of section objects with bounds
47
- */
48
- export async function detectSections(page, options = {}) {
49
- const config = { ...DEFAULT_OPTIONS, ...options };
50
-
51
- // Get page dimensions
52
- const pageDimensions = await page.evaluate(() => ({
53
- width: document.documentElement.clientWidth,
54
- height: Math.max(
55
- document.body.scrollHeight,
56
- document.documentElement.scrollHeight
57
- )
58
- }));
59
-
60
- // Strategy 1: Find semantic sections directly from page
61
- let sections = await findSemanticSections(page, pageDimensions, config);
62
-
63
- // Strategy 2: If not enough sections, try class pattern matching
64
- if (sections.length < config.minSections) {
65
- const classSections = await findClassPatternSections(page, pageDimensions, config);
66
- sections = mergeSections(sections, classSections);
67
- }
68
-
69
- // Strategy 3: If still not enough, find large direct children
70
- if (sections.length < config.minSections) {
71
- const largeSections = await findLargeChildSections(page, pageDimensions, config);
72
- sections = mergeSections(sections, largeSections);
73
- }
74
-
75
- // Strategy 4: Fallback to viewport chunking
76
- if (sections.length < config.minSections && config.fallbackToViewport) {
77
- sections = generateViewportChunks(pageDimensions, config);
78
- }
79
-
80
- // Apply padding and validate bounds
81
- sections = sections.map((section, idx) => ({
82
- ...section,
83
- index: idx,
84
- bounds: applyPadding(section.bounds, config.padding, pageDimensions)
85
- }));
86
-
87
- // Sort by Y position and limit
88
- sections = sections
89
- .sort((a, b) => a.bounds.y - b.bounds.y)
90
- .slice(0, config.maxSections);
91
-
92
- // Re-index after sort
93
- return sections.map((section, idx) => ({ ...section, index: idx }));
94
- }
95
-
96
- /**
97
- * Find semantic HTML sections (header, main, section, footer)
98
- */
99
- async function findSemanticSections(page, pageDimensions, config) {
100
- return await page.evaluate(({ minHeight }) => {
101
- const sections = [];
102
- const processed = new Set();
103
-
104
- // Selectors for semantic sections
105
- const selectors = [
106
- 'header:not(header header)', // Top-level header only
107
- 'main > section',
108
- 'main > article',
109
- 'body > section',
110
- 'body > article',
111
- '[data-section]',
112
- 'footer:not(footer footer)' // Top-level footer only
113
- ];
114
-
115
- for (const selector of selectors) {
116
- const elements = document.querySelectorAll(selector);
117
-
118
- for (const el of elements) {
119
- // Skip if already processed (nested elements)
120
- if (processed.has(el)) continue;
121
-
122
- const rect = el.getBoundingClientRect();
123
- const absoluteY = rect.y + window.scrollY;
124
-
125
- // Skip tiny sections
126
- if (rect.height < minHeight) continue;
127
-
128
- // Determine section name
129
- let name = el.tagName.toLowerCase();
130
- if (el.hasAttribute('data-section')) {
131
- name = el.getAttribute('data-section');
132
- } else if (el.id) {
133
- name = el.id;
134
- } else if (el.className) {
135
- // Try to extract meaningful class name
136
- const cls = el.className.toString().toLowerCase();
137
- const match = cls.match(/\b(hero|about|services|features|contact|footer|header|nav|cta|testimonials|pricing|faq|team|blog|news)\b/);
138
- if (match) name = match[1];
139
- }
140
-
141
- sections.push({
142
- name,
143
- role: el.tagName.toLowerCase(),
144
- selector: el.id ? `#${el.id}` : `${el.tagName.toLowerCase()}`,
145
- bounds: {
146
- x: Math.round(rect.x),
147
- y: Math.round(absoluteY),
148
- width: Math.round(rect.width),
149
- height: Math.round(rect.height)
150
- }
151
- });
152
-
153
- processed.add(el);
154
- }
155
- }
156
-
157
- return sections;
158
- }, { minHeight: config.minSectionHeight });
159
- }
160
-
161
- /**
162
- * Find sections by class pattern matching
163
- */
164
- async function findClassPatternSections(page, pageDimensions, config) {
165
- return await page.evaluate(({ patterns, minHeight }) => {
166
- const sections = [];
167
- const processed = new Set();
168
-
169
- // Build selector from patterns
170
- const classSelectors = patterns.map(p => `[class*="${p}"]`).join(', ');
171
- const elements = document.querySelectorAll(classSelectors);
172
-
173
- for (const el of elements) {
174
- // Only consider direct children of body or main
175
- const parent = el.parentElement;
176
- if (!parent || (parent.tagName !== 'BODY' && parent.tagName !== 'MAIN')) {
177
- continue;
178
- }
179
-
180
- // Skip if inside another matched element
181
- if (processed.has(el)) continue;
182
-
183
- const rect = el.getBoundingClientRect();
184
- const absoluteY = rect.y + window.scrollY;
185
-
186
- if (rect.height < minHeight) continue;
187
-
188
- // Extract pattern name from class
189
- const cls = el.className.toString().toLowerCase();
190
- let name = 'section';
191
- for (const pattern of patterns) {
192
- if (cls.includes(pattern)) {
193
- name = pattern;
194
- break;
195
- }
196
- }
197
-
198
- sections.push({
199
- name,
200
- role: 'class-pattern',
201
- selector: el.id ? `#${el.id}` : `.${el.className.toString().split(' ')[0]}`,
202
- bounds: {
203
- x: Math.round(rect.x),
204
- y: Math.round(absoluteY),
205
- width: Math.round(rect.width),
206
- height: Math.round(rect.height)
207
- }
208
- });
209
-
210
- processed.add(el);
211
- }
212
-
213
- return sections;
214
- }, { patterns: SECTION_CLASS_PATTERNS, minHeight: config.minSectionHeight });
215
- }
216
-
217
- /**
218
- * Find large direct children of main/body as sections
219
- */
220
- async function findLargeChildSections(page, pageDimensions, config) {
221
- return await page.evaluate(({ minHeight }) => {
222
- const sections = [];
223
-
224
- // Check direct children of main, then body
225
- const containers = [
226
- document.querySelector('main'),
227
- document.body
228
- ].filter(Boolean);
229
-
230
- for (const container of containers) {
231
- const children = Array.from(container.children);
232
-
233
- for (const child of children) {
234
- // Skip script, style, noscript
235
- if (['SCRIPT', 'STYLE', 'NOSCRIPT', 'LINK', 'META'].includes(child.tagName)) {
236
- continue;
237
- }
238
-
239
- const rect = child.getBoundingClientRect();
240
- const absoluteY = rect.y + window.scrollY;
241
-
242
- // Only large sections (>300px or >20% viewport height)
243
- const threshold = Math.max(300, window.innerHeight * 0.2);
244
- if (rect.height < threshold) continue;
245
-
246
- // Skip if already covered by semantic detection
247
- if (child.tagName === 'HEADER' || child.tagName === 'FOOTER' ||
248
- child.tagName === 'SECTION' || child.tagName === 'ARTICLE') {
249
- continue;
250
- }
251
-
252
- // Generate descriptive name based on position
253
- let name = child.id || '';
254
- if (!name && child.className) {
255
- const cls = child.className.toString();
256
- const firstClass = cls.split(' ')[0].toLowerCase();
257
- // Skip generic framework classes
258
- const genericPatterns = ['sd', 'container', 'wrapper', 'div', 'block', 'row', 'col', 'section'];
259
- if (!genericPatterns.includes(firstClass)) {
260
- name = firstClass;
261
- }
262
- }
263
- if (!name) {
264
- // Name based on Y position relative to page
265
- const yRatio = absoluteY / (document.body.scrollHeight || 1);
266
- if (yRatio < 0.15) name = 'top-section';
267
- else if (yRatio < 0.35) name = 'upper-content';
268
- else if (yRatio < 0.55) name = 'middle-content';
269
- else if (yRatio < 0.75) name = 'lower-content';
270
- else name = 'bottom-section';
271
- name = `${name}-${sections.length}`;
272
- }
273
-
274
- sections.push({
275
- name: name.toLowerCase().replace(/[^a-z0-9-]/g, '-'),
276
- role: 'large-block',
277
- selector: child.id ? `#${child.id}` : child.tagName.toLowerCase(),
278
- bounds: {
279
- x: Math.round(rect.x),
280
- y: Math.round(absoluteY),
281
- width: Math.round(rect.width),
282
- height: Math.round(rect.height)
283
- }
284
- });
285
- }
286
-
287
- // If we found sections in main, don't check body
288
- if (sections.length > 0 && container.tagName === 'MAIN') break;
289
- }
290
-
291
- return sections;
292
- }, { minHeight: config.minSectionHeight });
293
- }
294
-
295
- /**
296
- * Generate viewport chunks as fallback
297
- */
298
- function generateViewportChunks(pageDimensions, config) {
299
- const { width, height } = pageDimensions;
300
- const { viewportHeight, overlapRatio } = config;
301
-
302
- const sections = [];
303
- const overlap = Math.round(viewportHeight * overlapRatio);
304
- const step = viewportHeight - overlap;
305
-
306
- let y = 0;
307
- let index = 0;
308
-
309
- while (y < height) {
310
- const chunkHeight = Math.min(viewportHeight, height - y);
311
-
312
- sections.push({
313
- name: `viewport-${index}`,
314
- role: 'viewport-chunk',
315
- selector: null,
316
- bounds: {
317
- x: 0,
318
- y: y,
319
- width: width,
320
- height: chunkHeight
321
- }
322
- });
323
-
324
- y += step;
325
- index++;
326
-
327
- // Safety limit
328
- if (index > 50) break;
329
- }
330
-
331
- return sections;
332
- }
333
-
334
- /**
335
- * Merge sections, removing duplicates based on Y overlap
336
- */
337
- function mergeSections(existing, newSections) {
338
- const result = [...existing];
339
-
340
- for (const section of newSections) {
341
- // Check if this section overlaps significantly with existing
342
- const overlaps = result.some(s => {
343
- const yOverlap = Math.max(0,
344
- Math.min(s.bounds.y + s.bounds.height, section.bounds.y + section.bounds.height) -
345
- Math.max(s.bounds.y, section.bounds.y)
346
- );
347
- const minHeight = Math.min(s.bounds.height, section.bounds.height);
348
- return yOverlap > minHeight * 0.5; // >50% overlap
349
- });
350
-
351
- if (!overlaps) {
352
- result.push(section);
353
- }
354
- }
355
-
356
- return result;
357
- }
358
-
359
- /**
360
- * Apply padding to bounds, clamping to page dimensions
361
- */
362
- function applyPadding(bounds, padding, pageDimensions) {
363
- return {
364
- x: Math.max(0, bounds.x - padding),
365
- y: Math.max(0, bounds.y - padding),
366
- width: Math.min(pageDimensions.width, bounds.width + padding * 2),
367
- height: Math.min(
368
- pageDimensions.height - Math.max(0, bounds.y - padding),
369
- bounds.height + padding * 2
370
- )
371
- };
372
- }
373
-
374
- /**
375
- * Get section summary for logging
376
- */
377
- export function getSectionSummary(sections) {
378
- return {
379
- count: sections.length,
380
- names: sections.map(s => s.name),
381
- totalHeight: sections.reduce((sum, s) => sum + s.bounds.height, 0),
382
- hasViewportFallback: sections.some(s => s.role === 'viewport-chunk')
383
- };
384
- }
385
-
386
- export { DEFAULT_OPTIONS, SECTION_CLASS_PATTERNS };