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.
- package/.env.example +14 -0
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/SKILL.md +239 -0
- package/bin/cli.js +45 -0
- package/bin/commands/help.js +29 -0
- package/bin/commands/init.js +126 -0
- package/bin/commands/verify.js +99 -0
- package/bin/utils/copy.js +65 -0
- package/bin/utils/validate.js +122 -0
- package/docs/basic-clone.md +63 -0
- package/docs/cli-reference.md +94 -0
- package/docs/design-clone-architecture.md +247 -0
- package/docs/pixel-perfect.md +86 -0
- package/docs/troubleshooting.md +97 -0
- package/package.json +57 -0
- package/requirements.txt +5 -0
- package/src/ai/analyze-structure.py +305 -0
- package/src/ai/extract-design-tokens.py +439 -0
- package/src/ai/prompts/__init__.py +2 -0
- package/src/ai/prompts/design_tokens.py +183 -0
- package/src/ai/prompts/structure_analysis.py +273 -0
- package/src/core/cookie-handler.js +76 -0
- package/src/core/css-extractor.js +107 -0
- package/src/core/dimension-extractor.js +366 -0
- package/src/core/dimension-output.js +208 -0
- package/src/core/extract-assets.js +468 -0
- package/src/core/filter-css.js +499 -0
- package/src/core/html-extractor.js +102 -0
- package/src/core/lazy-loader.js +188 -0
- package/src/core/page-readiness.js +161 -0
- package/src/core/screenshot.js +380 -0
- package/src/post-process/enhance-assets.js +157 -0
- package/src/post-process/fetch-images.js +398 -0
- package/src/post-process/inject-icons.js +311 -0
- package/src/utils/__init__.py +16 -0
- package/src/utils/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/utils/__pycache__/env.cpython-313.pyc +0 -0
- package/src/utils/browser.js +103 -0
- package/src/utils/env.js +153 -0
- package/src/utils/env.py +134 -0
- package/src/utils/helpers.js +71 -0
- package/src/utils/puppeteer.js +281 -0
- package/src/verification/verify-layout.js +424 -0
- package/src/verification/verify-menu.js +422 -0
- package/templates/base.css +705 -0
- 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
|
+
}
|