design-clone 1.0.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 (47) hide show
  1. package/.env.example +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +166 -0
  4. package/SKILL.md +239 -0
  5. package/bin/cli.js +45 -0
  6. package/bin/commands/help.js +29 -0
  7. package/bin/commands/init.js +126 -0
  8. package/bin/commands/verify.js +99 -0
  9. package/bin/utils/copy.js +65 -0
  10. package/bin/utils/validate.js +122 -0
  11. package/docs/basic-clone.md +63 -0
  12. package/docs/cli-reference.md +94 -0
  13. package/docs/design-clone-architecture.md +247 -0
  14. package/docs/pixel-perfect.md +86 -0
  15. package/docs/troubleshooting.md +97 -0
  16. package/package.json +57 -0
  17. package/requirements.txt +5 -0
  18. package/src/ai/analyze-structure.py +305 -0
  19. package/src/ai/extract-design-tokens.py +439 -0
  20. package/src/ai/prompts/__init__.py +2 -0
  21. package/src/ai/prompts/design_tokens.py +183 -0
  22. package/src/ai/prompts/structure_analysis.py +273 -0
  23. package/src/core/cookie-handler.js +76 -0
  24. package/src/core/css-extractor.js +107 -0
  25. package/src/core/dimension-extractor.js +366 -0
  26. package/src/core/dimension-output.js +208 -0
  27. package/src/core/extract-assets.js +468 -0
  28. package/src/core/filter-css.js +499 -0
  29. package/src/core/html-extractor.js +102 -0
  30. package/src/core/lazy-loader.js +188 -0
  31. package/src/core/page-readiness.js +161 -0
  32. package/src/core/screenshot.js +380 -0
  33. package/src/post-process/enhance-assets.js +157 -0
  34. package/src/post-process/fetch-images.js +398 -0
  35. package/src/post-process/inject-icons.js +311 -0
  36. package/src/utils/__init__.py +16 -0
  37. package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
  38. package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
  39. package/src/utils/browser.js +103 -0
  40. package/src/utils/env.js +153 -0
  41. package/src/utils/env.py +134 -0
  42. package/src/utils/helpers.js +71 -0
  43. package/src/utils/puppeteer.js +281 -0
  44. package/src/verification/verify-layout.js +424 -0
  45. package/src/verification/verify-menu.js +422 -0
  46. package/templates/base.css +705 -0
  47. package/templates/base.html +293 -0
@@ -0,0 +1,273 @@
1
+ """
2
+ Structure Analysis Prompts
3
+
4
+ Prompts for analyzing website structure from screenshots using Gemini Vision.
5
+ """
6
+
7
+ # Structure analysis prompt (basic - screenshot only)
8
+ STRUCTURE_PROMPT = """Analyze this website screenshot and describe the page structure in detail.
9
+
10
+ Output a markdown document with the following sections:
11
+
12
+ # Page Structure Analysis
13
+
14
+ ## 1. Header Section
15
+ - Logo: [position, size, type (text/image)]
16
+ - Navigation: [number of items, layout (horizontal/vertical)]
17
+ - CTA Button: [text, position, style]
18
+ - Mobile menu: [hamburger icon visible?]
19
+
20
+ ## 2. Hero Section
21
+ - Layout: [centered/left-aligned/split]
22
+ - Headline: [estimated font size, weight, color]
23
+ - Subheadline: [if present, describe]
24
+ - Primary CTA: [button text, style]
25
+ - Secondary CTA: [if present]
26
+ - Background: [solid color/gradient/image]
27
+ - Visual elements: [images, illustrations, icons]
28
+
29
+ ## 3. Content Sections
30
+ For each distinct section, describe:
31
+ - Section name/purpose: [features, testimonials, pricing, etc.]
32
+ - Layout pattern: [grid columns, cards, alternating left-right]
33
+ - Number of items: [e.g., 3 feature cards, 4 testimonials]
34
+ - Key components: [icons, images, headings, descriptions]
35
+
36
+ ## 4. Footer Section
37
+ - Layout: [columns, stacked]
38
+ - Content blocks: [logo, links, social, newsletter]
39
+ - Copyright: [position, content]
40
+
41
+ ## 5. Global Patterns
42
+ - Container max-width: [estimated px]
43
+ - Section padding: [estimated vertical spacing]
44
+ - Card/component style: [shadows, borders, rounded corners]
45
+ - Color scheme: [light/dark mode, accent colors]
46
+ - Typography style: [modern/classic, serif/sans-serif]
47
+
48
+ ## 6. Responsive Hints
49
+ - Mobile-friendly indicators
50
+ - Collapsible elements
51
+ - Stack vs grid on small screens
52
+
53
+ ## 7. BEM Class Suggestions
54
+ Suggest semantic BEM class names for main components:
55
+ - header, header__logo, header__nav, header__cta
56
+ - hero, hero__title, hero__subtitle, hero__cta
57
+ - section--features, feature-card, feature-card__icon
58
+ - footer, footer__links, footer__social
59
+
60
+ Be specific and detailed. This analysis will be used to generate HTML/CSS."""
61
+
62
+
63
+ # Enhanced prompt when HTML/CSS context is available
64
+ STRUCTURE_PROMPT_WITH_CONTEXT = """Analyze this website using the screenshot AND the provided source HTML/CSS.
65
+
66
+ You have access to:
67
+ 1. A screenshot showing the visual design
68
+ 2. The actual HTML structure of the page
69
+ 3. The CSS rules used on the page
70
+
71
+ IMPORTANT: Use the actual HTML/CSS to provide ACCURATE information, not estimates.
72
+
73
+ ## Source HTML Structure
74
+ ```html
75
+ {html_snippet}
76
+ ```
77
+
78
+ ## Source CSS (key rules)
79
+ ```css
80
+ {css_snippet}
81
+ ```
82
+
83
+ ---
84
+
85
+ Based on the above context, output a markdown document:
86
+
87
+ # Page Structure Analysis
88
+
89
+ ## 1. Header Section
90
+ - Logo: [exact class/id from HTML, position from CSS]
91
+ - Navigation: [exact nav structure from HTML]
92
+ - CTA Button: [exact button text and classes]
93
+ - Mobile menu: [presence and class name if exists]
94
+
95
+ ## 2. Hero Section
96
+ - Layout: [from CSS flexbox/grid rules]
97
+ - Headline: [exact text and classes from HTML]
98
+ - Subheadline: [exact text if present]
99
+ - Primary CTA: [exact button text and styles]
100
+ - Background: [from CSS background rules]
101
+ - Visual elements: [images/icons from HTML]
102
+
103
+ ## 3. Content Sections
104
+ For each section found in HTML, describe:
105
+ - Section class/id: [exact from HTML]
106
+ - Layout: [from CSS grid/flex rules]
107
+ - Items: [exact count from HTML]
108
+ - Components: [exact structure]
109
+
110
+ ## 4. Footer Section
111
+ - Layout: [from CSS]
112
+ - Content blocks: [exact from HTML]
113
+ - Links: [exact href patterns]
114
+
115
+ ## 5. Actual CSS Values (from source)
116
+ - Container max-width: [exact from CSS]
117
+ - Section padding: [exact from CSS]
118
+ - Border-radius values: [exact from CSS]
119
+ - Primary color: [exact hex from CSS]
120
+ - Font-family: [exact from CSS]
121
+ - Font-sizes: [exact from CSS]
122
+
123
+ ## 6. Responsive Breakpoints (from CSS @media queries)
124
+ - Breakpoint values: [exact from CSS]
125
+ - Layout changes: [what changes at each breakpoint]
126
+
127
+ ## 7. Recommended BEM Classes
128
+ Based on the actual HTML structure, suggest clean BEM names that map to existing classes.
129
+
130
+ CRITICAL: Extract EXACT values from the CSS where possible. Do not estimate."""
131
+
132
+
133
+ # Enhanced prompt when extracted dimensions are available (highest accuracy)
134
+ STRUCTURE_PROMPT_WITH_DIMENSIONS = """Analyze this website screenshot using the EXACT extracted dimensions below.
135
+
136
+ ## CRITICAL INSTRUCTIONS
137
+ 1. USE ONLY the exact values provided - DO NOT estimate or approximate
138
+ 2. All measurements below are extracted from the actual DOM via getBoundingClientRect + getComputedStyle
139
+ 3. When describing layout, reference these exact numbers
140
+ 4. Section 5 MUST repeat these exact values verbatim
141
+
142
+ ## EXACT EXTRACTED DIMENSIONS
143
+
144
+ ### Layout
145
+ - Container max-width: {container_max_width}
146
+ - Section padding: {section_padding}
147
+ - Gap between elements: {gap}
148
+
149
+ ### Cards
150
+ - Card width: {card_width}
151
+ - Card height: {card_height}
152
+ - Card padding: {card_padding}
153
+
154
+ ### Typography (EXACT from source)
155
+ - H1 font-size: {h1}
156
+ - H2 font-size: {h2}
157
+ - H3 font-size: {h3}
158
+ - Body font-size: {body}
159
+
160
+ ### Responsive Breakpoints
161
+ - Desktop: {desktop_breakpoint}
162
+ - Tablet: {tablet_breakpoint}
163
+ - Mobile: {mobile_breakpoint}
164
+
165
+ ### Typography Scaling
166
+ - H1: {h1} → {h1_tablet} (tablet) → {h1_mobile} (mobile)
167
+ - H2: {h2} → {h2_tablet} (tablet) → {h2_mobile} (mobile)
168
+
169
+ ---
170
+
171
+ Now output a markdown document following this structure.
172
+ IMPORTANT: In section 5, you MUST repeat the exact values above - do not change them.
173
+
174
+ # Page Structure Analysis
175
+
176
+ ## 1. Header Section
177
+ - Logo: [describe position and layout]
178
+ - Navigation: [describe navigation structure]
179
+ - CTA Button: [if present]
180
+ - Mobile menu: [hamburger toggle if visible]
181
+
182
+ ## 2. Hero Section
183
+ - Layout: [describe arrangement]
184
+ - Headline: font-size {h1} (EXACT), [describe style]
185
+ - Subheadline: [if present]
186
+ - Primary CTA: [button description]
187
+ - Background: [describe]
188
+
189
+ ## 3. Content Sections
190
+ For each section describe:
191
+ - Section name/purpose
192
+ - Layout pattern using the EXACT gap value: {gap}
193
+ - Card dimensions: {card_width} x {card_height} (EXACT)
194
+ - Components within
195
+
196
+ ## 4. Footer Section
197
+ - Layout: [describe]
198
+ - Content blocks
199
+
200
+ ## 5. EXACT CSS Values (use these for generation - DO NOT MODIFY)
201
+ - Container max-width: {container_max_width}
202
+ - Section padding: {section_padding}
203
+ - Card dimensions: {card_width} x {card_height}
204
+ - Card padding: {card_padding}
205
+ - Gap: {gap}
206
+ - H1: {h1}
207
+ - H2: {h2}
208
+ - H3: {h3}
209
+ - Body: {body}
210
+ - Desktop breakpoint: {desktop_breakpoint}
211
+ - Tablet breakpoint: {tablet_breakpoint}
212
+ - Mobile breakpoint: {mobile_breakpoint}
213
+
214
+ ## 6. Responsive Behavior
215
+ - At {tablet_breakpoint}: [describe layout changes]
216
+ - At {mobile_breakpoint}: [describe layout changes]
217
+ - Typography scales: H1 {h1} → {h1_tablet} → {h1_mobile}
218
+
219
+ ## 7. BEM Class Suggestions
220
+ [Suggest semantic class names for main components]"""
221
+
222
+
223
+ def build_structure_prompt(html_content: str = None, css_content: str = None, dimensions: dict = None) -> str:
224
+ """Build the appropriate prompt based on available context.
225
+
226
+ Priority:
227
+ 1. With dimensions (most accurate - uses exact extracted values)
228
+ 2. With HTML/CSS (accurate - extracts from source)
229
+ 3. Screenshot only (least accurate - estimates)
230
+ """
231
+
232
+ # Priority 1: Use dimensions if available (highest accuracy)
233
+ if dimensions:
234
+ exact = dimensions.get('EXACT_DIMENSIONS', {})
235
+ typo = dimensions.get('EXACT_TYPOGRAPHY', {})
236
+ resp = dimensions.get('RESPONSIVE', {})
237
+ card = exact.get('card_dimensions', {})
238
+ scaling = resp.get('typography_scaling', {})
239
+
240
+ return STRUCTURE_PROMPT_WITH_DIMENSIONS.format(
241
+ container_max_width=exact.get('container_max_width', '1200px'),
242
+ section_padding=exact.get('section_padding', '64px 0'),
243
+ gap=exact.get('gap', '24px'),
244
+ card_width=card.get('width', '380px') if isinstance(card, dict) else '380px',
245
+ card_height=card.get('height', 'auto') if isinstance(card, dict) else 'auto',
246
+ card_padding=card.get('padding', '24px') if isinstance(card, dict) else '24px',
247
+ h1=typo.get('h1', '48px'),
248
+ h2=typo.get('h2', '36px'),
249
+ h3=typo.get('h3', '28px'),
250
+ body=typo.get('body', '16px'),
251
+ desktop_breakpoint=resp.get('desktop_breakpoint', '1440px'),
252
+ tablet_breakpoint=resp.get('tablet_breakpoint', '768px'),
253
+ mobile_breakpoint=resp.get('mobile_breakpoint', '375px'),
254
+ h1_tablet=scaling.get('h1', {}).get('tablet', '36px') if isinstance(scaling.get('h1'), dict) else '36px',
255
+ h1_mobile=scaling.get('h1', {}).get('mobile', '28px') if isinstance(scaling.get('h1'), dict) else '28px',
256
+ h2_tablet=scaling.get('h2', {}).get('tablet', '28px') if isinstance(scaling.get('h2'), dict) else '28px',
257
+ h2_mobile=scaling.get('h2', {}).get('mobile', '24px') if isinstance(scaling.get('h2'), dict) else '24px'
258
+ )
259
+
260
+ # Priority 2: Use HTML/CSS if available
261
+ if html_content and css_content:
262
+ # Truncate for token limits - Gemini 2.5 Flash supports 1M tokens (~4MB)
263
+ # Using 100KB each to capture full page structure while staying well within limits
264
+ html_snippet = html_content[:100000] if len(html_content) > 100000 else html_content
265
+ css_snippet = css_content[:100000] if len(css_content) > 100000 else css_content
266
+
267
+ return STRUCTURE_PROMPT_WITH_CONTEXT.format(
268
+ html_snippet=html_snippet,
269
+ css_snippet=css_snippet
270
+ )
271
+
272
+ # Priority 3: Screenshot only (fallback)
273
+ return STRUCTURE_PROMPT
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Cookie Banner Handler
3
+ *
4
+ * Utilities for dismissing cookie consent banners.
5
+ * Tries clicking accept buttons first, then removes elements as fallback.
6
+ */
7
+
8
+ // Cookie banner accept button selectors (priority order)
9
+ export const COOKIE_SELECTORS = [
10
+ // Common accept buttons
11
+ '[aria-label*="accept" i]',
12
+ '[aria-label*="Accept" i]',
13
+ 'button[class*="accept" i]',
14
+ 'button[id*="accept" i]',
15
+ '[data-testid*="accept" i]',
16
+ // Specific CMPs
17
+ '#onetrust-accept-btn-handler',
18
+ '.cc-accept',
19
+ '.cookie-accept',
20
+ '#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll',
21
+ // Generic patterns
22
+ '.cookie-banner button:first-of-type',
23
+ '.cookie-consent button:first-of-type',
24
+ '[class*="cookie"] button[class*="primary"]'
25
+ ];
26
+
27
+ // Elements to remove as fallback
28
+ export const COOKIE_REMOVE_SELECTORS = [
29
+ '[class*="cookie-banner"]',
30
+ '[class*="cookie-consent"]',
31
+ '[class*="cookie-notice"]',
32
+ '[id*="cookie-banner"]',
33
+ '[id*="cookie-consent"]',
34
+ '#onetrust-banner-sdk',
35
+ '.cc-banner',
36
+ '[class*="gdpr"]'
37
+ ];
38
+
39
+ /**
40
+ * Dismiss cookie banners by clicking accept or removing elements
41
+ * @param {Page} page - Puppeteer page
42
+ * @returns {Promise<{method: string, selector?: string, count?: number}>}
43
+ */
44
+ export async function dismissCookieBanner(page) {
45
+ // Try clicking accept buttons first
46
+ for (const selector of COOKIE_SELECTORS) {
47
+ try {
48
+ const element = await page.$(selector);
49
+ if (element) {
50
+ await element.click();
51
+ await new Promise(r => setTimeout(r, 500));
52
+ return { method: 'click', selector };
53
+ }
54
+ } catch {
55
+ continue;
56
+ }
57
+ }
58
+
59
+ // Fallback: remove cookie elements from DOM
60
+ const removed = await page.evaluate((selectors) => {
61
+ let count = 0;
62
+ for (const sel of selectors) {
63
+ document.querySelectorAll(sel).forEach(el => {
64
+ el.remove();
65
+ count++;
66
+ });
67
+ }
68
+ return count;
69
+ }, COOKIE_REMOVE_SELECTORS);
70
+
71
+ if (removed > 0) {
72
+ return { method: 'remove', count: removed };
73
+ }
74
+
75
+ return { method: 'none' };
76
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * CSS Extractor
3
+ *
4
+ * Extract all CSS from page including inline styles,
5
+ * external stylesheets, and computed styles.
6
+ */
7
+
8
+ // Size limits
9
+ export const MAX_CSS_SIZE = 5 * 1024 * 1024; // 5MB limit
10
+ export const MAX_CSS_RULES_WARN = 5000; // Warn on large stylesheets
11
+
12
+ /**
13
+ * Extract all CSS from page
14
+ * @param {Page} page - Puppeteer page
15
+ * @param {string} baseUrl - Base URL for resolving relative paths
16
+ * @returns {Promise<{cssBlocks: Array, corsBlocked: Array, computedStyles: Object, totalRules: number, warnings: Array}>}
17
+ */
18
+ export async function extractAllCss(page, baseUrl) {
19
+ return await page.evaluate((url) => {
20
+ const cssBlocks = [];
21
+ const corsBlocked = [];
22
+ const warnings = [];
23
+
24
+ // Collect from all stylesheets
25
+ for (const sheet of document.styleSheets) {
26
+ try {
27
+ const source = sheet.href || 'inline';
28
+ const rulesArray = sheet.cssRules || sheet.rules || [];
29
+ const rules = Array.from(rulesArray).map(rule => rule.cssText);
30
+
31
+ if (rules.length > 0) {
32
+ const resolvedSource = source === 'inline'
33
+ ? 'inline'
34
+ : new URL(source, url).href;
35
+
36
+ if (rules.length > 5000) {
37
+ warnings.push(`Large stylesheet: ${rules.length} rules from ${resolvedSource}`);
38
+ }
39
+
40
+ cssBlocks.push({
41
+ source: resolvedSource,
42
+ css: rules.join('\n'),
43
+ ruleCount: rules.length
44
+ });
45
+ }
46
+ } catch (e) {
47
+ if (sheet.href) {
48
+ corsBlocked.push(sheet.href);
49
+ }
50
+ }
51
+ }
52
+
53
+ // Dynamic computed styles: semantic elements + top classes
54
+ const baseSelectors = [
55
+ 'body', 'header', 'nav', 'main', 'footer', 'section', 'article',
56
+ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
57
+ 'p', 'a', 'button', 'input', 'select', 'textarea'
58
+ ];
59
+
60
+ // Find top 10 most used classes
61
+ const classCounts = {};
62
+ document.querySelectorAll('[class]').forEach(el => {
63
+ el.classList.forEach(cls => {
64
+ classCounts[cls] = (classCounts[cls] || 0) + 1;
65
+ });
66
+ });
67
+ const topClasses = Object.entries(classCounts)
68
+ .sort((a, b) => b[1] - a[1])
69
+ .slice(0, 10)
70
+ .map(([cls]) => `.${cls}`);
71
+
72
+ const keySelectors = [...baseSelectors, ...topClasses];
73
+
74
+ const computedStyles = {};
75
+ keySelectors.forEach(selector => {
76
+ try {
77
+ const el = document.querySelector(selector);
78
+ if (el) {
79
+ const style = getComputedStyle(el);
80
+ computedStyles[selector] = {
81
+ color: style.color,
82
+ backgroundColor: style.backgroundColor,
83
+ fontFamily: style.fontFamily,
84
+ fontSize: style.fontSize,
85
+ fontWeight: style.fontWeight,
86
+ lineHeight: style.lineHeight,
87
+ padding: style.padding,
88
+ margin: style.margin,
89
+ borderRadius: style.borderRadius
90
+ };
91
+ }
92
+ } catch (e) {
93
+ // Ignore invalid selectors
94
+ }
95
+ });
96
+
97
+ const totalRules = cssBlocks.reduce((sum, b) => sum + b.ruleCount, 0);
98
+
99
+ return {
100
+ cssBlocks,
101
+ corsBlocked,
102
+ computedStyles,
103
+ totalRules,
104
+ warnings
105
+ };
106
+ }, baseUrl);
107
+ }