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,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 };
|