@weavetab/mcp 2.5.0-beta.5 → 2.5.1
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/AGENTS.md +49 -49
- package/ARCHITECTURE.md +590 -590
- package/README.md +216 -100
- package/SECURITY.md +207 -207
- package/TROUBLESHOOTING.md +373 -365
- package/dist/anticaptcha/detector.js +395 -395
- package/dist/anticaptcha/solver.js +130 -130
- package/dist/cdp/bridge.js +73 -73
- package/dist/cdp/connector.js +46 -22
- package/dist/cdp/extension.js +34 -34
- package/dist/cdp/helpers.js +174 -174
- package/dist/cdp/pruner.js +195 -195
- package/dist/cdp/ref_cache.js +66 -66
- package/dist/cdp/security.js +10 -10
- package/dist/cdp/verify.js +57 -57
- package/dist/cdp/walker.js +563 -525
- package/dist/cli/plugin.js +26 -3
- package/dist/config/loader.d.ts +0 -3
- package/dist/config/loader.js +0 -9
- package/dist/index.js +0 -4
- package/dist/intelligence/intent.js +81 -81
- package/dist/intelligence/sessionMemory.js +2 -2
- package/dist/overlay/ghost.js +22 -22
- package/dist/overlay/injector.js +89 -89
- package/dist/plugin/extension_bridge.js +54 -54
- package/dist/plugin/interface.d.ts +7 -0
- package/dist/plugin/loader.js +3 -4
- package/dist/plugin/schema.d.ts +11 -0
- package/dist/plugin/schema.js +59 -0
- package/dist/security/guard.js +18 -18
- package/dist/sensors/actionability.js +167 -167
- package/dist/sensors/guard.js +29 -29
- package/dist/sensors/network.js +15 -17
- package/dist/server.d.ts +1 -1
- package/dist/server.js +173 -144
- package/dist/state/delta.js +6 -7
- package/dist/tools/automation.js +85 -85
- package/dist/tools/canvas.js +48 -25
- package/dist/tools/checkpoint.js +94 -35
- package/dist/tools/click.js +52 -41
- package/dist/tools/clipboard.js +80 -74
- package/dist/tools/design_extract.js +441 -441
- package/dist/tools/dialog.js +13 -13
- package/dist/tools/emulation.d.ts +2 -0
- package/dist/tools/emulation.js +7 -0
- package/dist/tools/fill.js +344 -347
- package/dist/tools/find.js +78 -52
- package/dist/tools/highlight.js +67 -67
- package/dist/tools/inspect.js +54 -25
- package/dist/tools/key.js +42 -35
- package/dist/tools/navigate.d.ts +1 -1
- package/dist/tools/navigate.js +35 -35
- package/dist/tools/office/parse.js +62 -18
- package/dist/tools/pdf.d.ts +9 -0
- package/dist/tools/pdf.js +7 -4
- package/dist/tools/performance.d.ts +11 -4
- package/dist/tools/performance.js +94 -42
- package/dist/tools/performance_test.d.ts +2 -2
- package/dist/tools/performance_test.js +69 -69
- package/dist/tools/pointer.js +116 -116
- package/dist/tools/read.js +99 -67
- package/dist/tools/recording.js +9 -0
- package/dist/tools/scrape.js +146 -146
- package/dist/tools/screenshot.d.ts +2 -0
- package/dist/tools/screenshot.js +46 -4
- package/dist/tools/scroll.d.ts +3 -1
- package/dist/tools/scroll.js +13 -15
- package/dist/tools/select.d.ts +3 -0
- package/dist/tools/select.js +131 -124
- package/dist/tools/snapshot.js +8 -6
- package/dist/tools/storage.d.ts +5 -1
- package/dist/tools/storage.js +16 -0
- package/dist/tools/tabs.d.ts +7 -5
- package/dist/tools/tabs.js +79 -26
- package/dist/tools/thought.js +8 -8
- package/dist/tools/type.js +274 -274
- package/dist/tools/type_secret.js +23 -23
- package/dist/tools/upload.js +3 -3
- package/dist/tools/video_compiler.js +9 -0
- package/dist/tools/wait.js +35 -35
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/DOCUMENTATION.md +583 -585
- package/docs/PLUGINS.md +139 -121
- package/docs/TOOLS.md +3 -4
- package/docs/architecture/blind-injection.md +83 -83
- package/docs/architecture/overview.md +49 -49
- package/docs/configuration/configuration.md +39 -27
- package/docs/configuration/folders.md +62 -55
- package/docs/desktop-automation/electron-apps.md +225 -214
- package/docs/get-started/installation.md +1 -1
- package/docs/get-started/introduction.md +60 -60
- package/docs/get-started/quickstart.md +2 -2
- package/docs/get-started/setup-wizard.md +1 -1
- package/docs/intelligence/site-profiles.md +151 -151
- package/docs/tools/tools.md +582 -631
- package/docs/troubleshooting/trouble-loop.md +31 -31
- package/ext-dist/background.js +415 -415
- package/ext-dist/content.js +2527 -2527
- package/ext-dist/dashboard.html +583 -583
- package/ext-dist/hud/hud.js +3 -3
- package/ext-dist/hud/index.html +20 -20
- package/ext-dist/manifest.json +73 -73
- package/ext-dist/styles.css +223 -223
- package/mcp.json +796 -805
- package/package.json +8 -8
- package/server.json +23 -23
- package/smithery.yaml +7 -11
- package/dist/cdp/walker.test.d.ts +0 -2
- package/dist/cdp/walker.test.js +0 -172
- package/dist/intelligence/sessionMemory.test.d.ts +0 -2
- package/dist/intelligence/sessionMemory.test.js +0 -62
- package/dist/intelligence/trail.test.d.ts +0 -2
- package/dist/intelligence/trail.test.js +0 -110
- package/dist/state/cache.test.d.ts +0 -2
- package/dist/state/cache.test.js +0 -76
- package/dist/state/delta.test.d.ts +0 -2
- package/dist/state/delta.test.js +0 -279
- package/dist/state/groups.d.ts +0 -15
- package/dist/state/groups.js +0 -80
- package/dist/tools/accessibility.d.ts +0 -14
- package/dist/tools/accessibility.js +0 -32
- package/dist/tools/ask.d.ts +0 -14
- package/dist/tools/ask.js +0 -49
- package/dist/tools/click_and_wait.d.ts +0 -33
- package/dist/tools/click_and_wait.js +0 -149
- package/dist/tools/drag.d.ts +0 -15
- package/dist/tools/drag.js +0 -29
- package/dist/tools/geo.d.ts +0 -19
- package/dist/tools/geo.js +0 -29
- package/dist/tools/github/analyze.d.ts +0 -4
- package/dist/tools/github/analyze.js +0 -113
- package/dist/tools/github/cache.d.ts +0 -21
- package/dist/tools/github/cache.js +0 -28
- package/dist/tools/github/index.d.ts +0 -19
- package/dist/tools/github/index.js +0 -40
- package/dist/tools/github/issues.d.ts +0 -9
- package/dist/tools/github/issues.js +0 -88
- package/dist/tools/github/pr.d.ts +0 -5
- package/dist/tools/github/pr.js +0 -88
- package/dist/tools/github/rateLimiter.d.ts +0 -4
- package/dist/tools/github/rateLimiter.js +0 -46
- package/dist/tools/github/read.d.ts +0 -34
- package/dist/tools/github/read.js +0 -74
- package/dist/tools/github/repoContext.d.ts +0 -20
- package/dist/tools/github/repoContext.js +0 -129
- package/dist/tools/hover.d.ts +0 -19
- package/dist/tools/hover.js +0 -57
- package/dist/tools/integration.test.d.ts +0 -2
- package/dist/tools/integration.test.js +0 -96
- package/dist/tools/macro_compiler.d.ts +0 -7
- package/dist/tools/macro_compiler.js +0 -82
- package/dist/tools/mouse.d.ts +0 -21
- package/dist/tools/mouse.js +0 -62
- package/dist/tools/response.d.ts +0 -129
- package/dist/tools/response.js +0 -5
- package/dist/tools/wait_for.d.ts +0 -23
- package/dist/tools/wait_for.js +0 -30
- package/dist/tools/windows.d.ts +0 -22
- package/dist/tools/windows.js +0 -57
|
@@ -94,424 +94,424 @@ export async function browserExtractDesign(session, options = {}, config) {
|
|
|
94
94
|
}
|
|
95
95
|
try {
|
|
96
96
|
const evalRes = await session.Runtime.evaluate({
|
|
97
|
-
expression: `(function(targetSelector, includeAssetsFlag) {
|
|
98
|
-
// Find target container
|
|
99
|
-
let root = document.querySelector(targetSelector);
|
|
100
|
-
if (!root && targetSelector !== 'body') {
|
|
101
|
-
root = document.querySelector('body') || document.documentElement;
|
|
102
|
-
}
|
|
103
|
-
if (!root) root = document.documentElement;
|
|
104
|
-
|
|
105
|
-
// ── 1. Meta & Environment ──
|
|
106
|
-
const isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
107
|
-
|| document.documentElement.classList.contains('dark')
|
|
108
|
-
|| document.body?.classList.contains('dark')
|
|
109
|
-
|| (window.getComputedStyle(document.body).backgroundColor === 'rgb(0, 0, 0)' || window.getComputedStyle(document.body).backgroundColor.startsWith('rgb(1') || window.getComputedStyle(document.body).backgroundColor.startsWith('rgb(2'));
|
|
110
|
-
|
|
111
|
-
const meta = {
|
|
112
|
-
url: window.location.href,
|
|
113
|
-
title: document.title || '',
|
|
114
|
-
extractedAt: new Date().toISOString(),
|
|
115
|
-
selector: targetSelector,
|
|
116
|
-
viewport: {
|
|
117
|
-
width: window.innerWidth,
|
|
118
|
-
height: window.innerHeight
|
|
119
|
-
},
|
|
120
|
-
isDarkMode: !!isDark
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
// ── 2. CSS Custom Properties (:root / html / body) ──
|
|
124
|
-
const cssVars = {};
|
|
125
|
-
const rootStyles = window.getComputedStyle(document.documentElement);
|
|
126
|
-
const bodyStyles = document.body ? window.getComputedStyle(document.body) : null;
|
|
127
|
-
|
|
128
|
-
// Extract declared variables from styleSheets
|
|
129
|
-
Array.from(document.styleSheets).forEach(sheet => {
|
|
130
|
-
try {
|
|
131
|
-
Array.from(sheet.cssRules || []).forEach(rule => {
|
|
132
|
-
if (rule.style && (rule.selectorText === ':root' || rule.selectorText === 'html' || rule.selectorText === 'body' || rule.selectorText === '.dark' || rule.selectorText === '[data-theme="dark"]')) {
|
|
133
|
-
for (let i = 0; i < rule.style.length; i++) {
|
|
134
|
-
const propName = rule.style[i];
|
|
135
|
-
if (propName.startsWith('--')) {
|
|
136
|
-
const val = rootStyles.getPropertyValue(propName).trim() || (bodyStyles ? bodyStyles.getPropertyValue(propName).trim() : '');
|
|
137
|
-
if (val) cssVars[propName] = val;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
} catch (_) {
|
|
143
|
-
// CORS stylesheet security restriction — ignore
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// ── Helper: Color Parsing & Normalization ──
|
|
148
|
-
function normalizeColor(c) {
|
|
149
|
-
if (!c || c === 'rgba(0, 0, 0, 0)' || c === 'transparent') return null;
|
|
150
|
-
return c.trim();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ── 3. Token Harvesters ──
|
|
154
|
-
const bgColors = new Set();
|
|
155
|
-
const textColors = new Set();
|
|
156
|
-
const borderColors = new Set();
|
|
157
|
-
const fontFamilies = new Set();
|
|
158
|
-
const shadows = new Set();
|
|
159
|
-
const radii = new Set();
|
|
160
|
-
const blurs = new Set();
|
|
161
|
-
const displayTypes = new Set();
|
|
162
|
-
const gaps = new Set();
|
|
163
|
-
const paddings = new Set();
|
|
164
|
-
|
|
165
|
-
const headings = {};
|
|
166
|
-
let bodyTypography = {
|
|
167
|
-
fontFamily: '',
|
|
168
|
-
fontSize: '',
|
|
169
|
-
fontWeight: '',
|
|
170
|
-
lineHeight: '',
|
|
171
|
-
color: ''
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
if (document.body) {
|
|
175
|
-
const bs = window.getComputedStyle(document.body);
|
|
176
|
-
bodyTypography = {
|
|
177
|
-
fontFamily: bs.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
|
|
178
|
-
fontSize: bs.fontSize,
|
|
179
|
-
fontWeight: bs.fontWeight,
|
|
180
|
-
lineHeight: bs.lineHeight,
|
|
181
|
-
color: bs.color
|
|
182
|
-
};
|
|
183
|
-
if (bs.fontFamily) fontFamilies.add(bodyTypography.fontFamily);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Collect heading styles
|
|
187
|
-
['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(tag => {
|
|
188
|
-
const el = root.querySelector(tag);
|
|
189
|
-
if (el) {
|
|
190
|
-
const hs = window.getComputedStyle(el);
|
|
191
|
-
headings[tag] = {
|
|
192
|
-
fontFamily: hs.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
|
|
193
|
-
fontSize: hs.fontSize,
|
|
194
|
-
fontWeight: hs.fontWeight,
|
|
195
|
-
lineHeight: hs.lineHeight,
|
|
196
|
-
letterSpacing: hs.letterSpacing,
|
|
197
|
-
color: hs.color
|
|
198
|
-
};
|
|
199
|
-
if (hs.fontFamily) fontFamilies.add(headings[tag].fontFamily);
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
let totalNodes = 0;
|
|
204
|
-
let svgCount = 0;
|
|
205
|
-
|
|
206
|
-
function walkTokens(node) {
|
|
207
|
-
if (!node || node.nodeType !== 1) return;
|
|
208
|
-
if (node.id === 'wt-shadow-host' || node.hasAttribute('data-wt-ignore')) return;
|
|
209
|
-
|
|
210
|
-
totalNodes++;
|
|
211
|
-
const tag = node.tagName.toLowerCase();
|
|
212
|
-
if (['script', 'style', 'noscript', 'iframe'].includes(tag)) return;
|
|
213
|
-
if (tag === 'svg') svgCount++;
|
|
214
|
-
|
|
215
|
-
const s = window.getComputedStyle(node);
|
|
216
|
-
if (s.display === 'none' || s.visibility === 'hidden') return;
|
|
217
|
-
|
|
218
|
-
const bg = normalizeColor(s.backgroundColor);
|
|
219
|
-
if (bg) bgColors.add(bg);
|
|
220
|
-
|
|
221
|
-
const col = normalizeColor(s.color);
|
|
222
|
-
if (col) textColors.add(col);
|
|
223
|
-
|
|
224
|
-
const bc = normalizeColor(s.borderColor);
|
|
225
|
-
if (bc && s.borderWidth !== '0px') borderColors.add(bc);
|
|
226
|
-
|
|
227
|
-
if (s.fontFamily) {
|
|
228
|
-
const f = s.fontFamily.split(',')[0].replace(/['"]/g, '').trim();
|
|
229
|
-
if (f && f !== 'inherit') fontFamilies.add(f);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (s.boxShadow && s.boxShadow !== 'none') shadows.add(s.boxShadow);
|
|
233
|
-
if (s.borderRadius && s.borderRadius !== '0px') radii.add(s.borderRadius);
|
|
234
|
-
if (s.backdropFilter && s.backdropFilter !== 'none') blurs.add(s.backdropFilter);
|
|
235
|
-
|
|
236
|
-
if (s.display) displayTypes.add(s.display);
|
|
237
|
-
if (s.gap && s.gap !== 'normal' && s.gap !== '0px') gaps.add(s.gap);
|
|
238
|
-
if (s.padding && s.padding !== '0px') paddings.add(s.padding);
|
|
239
|
-
|
|
240
|
-
for (let i = 0; i < node.children.length; i++) {
|
|
241
|
-
walkTokens(node.children[i]);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
walkTokens(root);
|
|
246
|
-
|
|
247
|
-
// ── 4. Intelligent DOM Normalizer & Beautifier ──
|
|
248
|
-
const clone = root.cloneNode(true);
|
|
249
|
-
|
|
250
|
-
// A. Remove useless elements
|
|
251
|
-
clone.querySelectorAll('script, style, noscript, iframe, link, #wt-shadow-host, [data-wt-ignore]').forEach(el => el.remove());
|
|
252
|
-
|
|
253
|
-
// B. Handle Framer Motion / SplitText Spans & Hidden Duplicates
|
|
254
|
-
function normalizeDomTree(el) {
|
|
255
|
-
if (!el || el.nodeType !== 1) return;
|
|
256
|
-
|
|
257
|
-
const tag = el.tagName.toLowerCase();
|
|
258
|
-
|
|
259
|
-
// 1. Check for visually hidden / aria-hidden text duplication (Linear / Framer Motion pattern)
|
|
260
|
-
const srOnlyEl = el.querySelector('.visuallyHidden, [class*="visuallyHidden"], .sr-only, [class*="sr-only"]');
|
|
261
|
-
const ariaHiddenEl = el.querySelector('[aria-hidden="true"]');
|
|
262
|
-
if (srOnlyEl && (ariaHiddenEl || el.querySelectorAll('span').length > 2)) {
|
|
263
|
-
const canonicalText = srOnlyEl.textContent.trim();
|
|
264
|
-
if (canonicalText) {
|
|
265
|
-
el.innerHTML = canonicalText;
|
|
266
|
-
}
|
|
267
|
-
} else if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'button', 'a'].includes(tag)) {
|
|
268
|
-
// If all children are spans with fragmented text or animation styles, unsplit them
|
|
269
|
-
const spans = Array.from(el.children).filter(c => c.tagName.toLowerCase() === 'span');
|
|
270
|
-
const hasOtherContent = Array.from(el.children).some(c => !['span', 'br', 'wbr'].includes(c.tagName.toLowerCase()));
|
|
271
|
-
|
|
272
|
-
if (spans.length >= 2 && !hasOtherContent) {
|
|
273
|
-
// Check if spans have duplicate text due to responsive breakpoints (e.g. show-mobile vs hide-mobile)
|
|
274
|
-
const mobileSpans = spans.filter(s => s.className && s.className.includes('mobile'));
|
|
275
|
-
const hideMobileSpans = spans.filter(s => s.className && s.className.includes('hide-mobile'));
|
|
276
|
-
|
|
277
|
-
if (hideMobileSpans.length > 0) {
|
|
278
|
-
// Desktop active: keep hide-mobile / remove show-mobile
|
|
279
|
-
mobileSpans.filter(s => s.className.includes('show-mobile')).forEach(s => s.remove());
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// If remaining spans are just split words, join them cleanly
|
|
283
|
-
const remainingSpans = Array.from(el.children).filter(c => c.tagName.toLowerCase() === 'span');
|
|
284
|
-
const textContent = remainingSpans.map(s => s.textContent).join(' ').replace(/\\s+/g, ' ').trim();
|
|
285
|
-
if (textContent) {
|
|
286
|
-
el.innerHTML = textContent;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// 2. Clean inline styles (remove animation / matrix / opacity: 1 fluff)
|
|
292
|
-
if (el.hasAttribute('style')) {
|
|
293
|
-
const rawStyle = el.getAttribute('style') || '';
|
|
294
|
-
const meaningfulStyles = [];
|
|
295
|
-
|
|
296
|
-
rawStyle.split(';').forEach(rule => {
|
|
297
|
-
const trimmed = rule.trim();
|
|
298
|
-
if (!trimmed) return;
|
|
299
|
-
const [prop, val] = trimmed.split(':').map(s => s.trim());
|
|
300
|
-
if (!prop || !val) return;
|
|
301
|
-
|
|
302
|
-
// Skip animation artifacts
|
|
303
|
-
if (
|
|
304
|
-
(prop === 'opacity' && val === '1') ||
|
|
305
|
-
(prop === 'filter' && (val === 'blur(0px)' || val === 'none')) ||
|
|
306
|
-
(prop === 'transform' && (val.includes('translateY(0%)') || val.includes('translate3d(0, 0, 0)') || val === 'none' || val.includes('matrix(1, 0, 0, 1, 0, 0)'))) ||
|
|
307
|
-
(prop === 'will-change') ||
|
|
308
|
-
(prop === 'perspective') ||
|
|
309
|
-
(prop === 'backface-visibility')
|
|
310
|
-
) {
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Keep intentional layout styles / CSS variables / spacer heights
|
|
315
|
-
if (prop.startsWith('--height') || prop === 'height') {
|
|
316
|
-
meaningfulStyles.push('height: ' + val);
|
|
317
|
-
} else if (prop.startsWith('--gap') || prop === 'gap') {
|
|
318
|
-
meaningfulStyles.push('gap: ' + val);
|
|
319
|
-
} else if (!prop.startsWith('--')) {
|
|
320
|
-
meaningfulStyles.push(prop + ': ' + val);
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
if (meaningfulStyles.length > 0) {
|
|
325
|
-
el.setAttribute('style', meaningfulStyles.join('; '));
|
|
326
|
-
} else {
|
|
327
|
-
el.removeAttribute('style');
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// 3. Clean and normalize class names (strip random hashes, keep semantic names)
|
|
332
|
-
if (el.hasAttribute('class')) {
|
|
333
|
-
const rawClasses = (el.getAttribute('class') || '').split(/\\s+/);
|
|
334
|
-
const cleanClasses = [];
|
|
335
|
-
|
|
336
|
-
rawClasses.forEach(cls => {
|
|
337
|
-
const c = cls.trim();
|
|
338
|
-
if (!c) return;
|
|
339
|
-
// Skip internal / ephemeral classes
|
|
340
|
-
if (c.startsWith('sc-') || c === 'show-mobile' || c.startsWith('wt-')) return;
|
|
341
|
-
|
|
342
|
-
// De-hash CSS module names: e.g. "ImRUSq_justify-space-between" -> "justify-between"
|
|
343
|
-
if (c.includes('_')) {
|
|
344
|
-
const parts = c.split('_');
|
|
345
|
-
const semanticPart = parts[parts.length - 1];
|
|
346
|
-
if (semanticPart && semanticPart.length > 2 && !semanticPart.match(/^[a-f0-9]{4,}$/i)) {
|
|
347
|
-
let normalized = semanticPart
|
|
348
|
-
.replace('justify-space-between', 'justify-between')
|
|
349
|
-
.replace('align-center', 'items-center');
|
|
350
|
-
if (normalized === 'root') normalized = 'spacer';
|
|
351
|
-
cleanClasses.push(normalized);
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Keep standard semantic / utility class
|
|
357
|
-
cleanClasses.push(c);
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
const uniqueClasses = Array.from(new Set(cleanClasses));
|
|
361
|
-
if (uniqueClasses.length > 0) {
|
|
362
|
-
el.setAttribute('class', uniqueClasses.join(' '));
|
|
363
|
-
} else {
|
|
364
|
-
el.removeAttribute('class');
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// 4. Remove internal / framework attributes
|
|
369
|
-
['data-wt-id', 'data-wt-secret', 'jsaction', 'jscontroller', 'jsname', 'jsmodel', 'data-sentry-component', 'data-sentry-source-file'].forEach(attr => {
|
|
370
|
-
el.removeAttribute(attr);
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
// 5. Clean SVGs
|
|
374
|
-
if (el.tagName.toLowerCase() === 'svg' && !includeAssetsFlag) {
|
|
375
|
-
el.innerHTML = '<!-- svg icon -->';
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Recurse children
|
|
379
|
-
const children = Array.from(el.children);
|
|
380
|
-
for (let i = 0; i < children.length; i++) {
|
|
381
|
-
normalizeDomTree(children[i]);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// 6. Remove empty spacer divs with no styles or classes
|
|
385
|
-
if (tag === 'div' && el.children.length === 0 && !el.textContent.trim() && !el.hasAttribute('style') && !el.hasAttribute('class')) {
|
|
386
|
-
el.remove();
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
normalizeDomTree(clone);
|
|
391
|
-
|
|
392
|
-
// ── 5. Full Real CSS Extraction ──
|
|
393
|
-
const collectedStyleBlocks = [];
|
|
394
|
-
const keyframeMap = new Map();
|
|
395
|
-
const mediaQueryMap = new Map();
|
|
396
|
-
const fontFaceList = [];
|
|
397
|
-
const classRules = [];
|
|
398
|
-
|
|
399
|
-
// A. Extract all <style> tags (includes Tailwind, Emotion, styled-components, Next.js CSS)
|
|
400
|
-
document.querySelectorAll('style').forEach(st => {
|
|
401
|
-
const text = (st.textContent || '').trim();
|
|
402
|
-
if (text) {
|
|
403
|
-
collectedStyleBlocks.push(text);
|
|
404
|
-
}
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
// B. Extract accessible CSS rules from document.styleSheets
|
|
408
|
-
Array.from(document.styleSheets).forEach(sheet => {
|
|
409
|
-
try {
|
|
410
|
-
Array.from(sheet.cssRules || []).forEach(rule => {
|
|
411
|
-
const ruleText = rule.cssText;
|
|
412
|
-
if (!ruleText) return;
|
|
413
|
-
|
|
414
|
-
if (rule.type === 7 || rule.constructor.name === 'CSSKeyframesRule') {
|
|
415
|
-
keyframeMap.set(rule.name || ruleText.slice(0, 30), ruleText);
|
|
416
|
-
} else if (rule.type === 5 || rule.constructor.name === 'CSSFontFaceRule') {
|
|
417
|
-
fontFaceList.push(ruleText);
|
|
418
|
-
} else if (rule.type === 4 || rule.constructor.name === 'CSSMediaRule') {
|
|
419
|
-
mediaQueryMap.set(rule.conditionText || ruleText.slice(0, 30), ruleText);
|
|
420
|
-
} else if (rule.style) {
|
|
421
|
-
const sel = rule.selectorText || '';
|
|
422
|
-
if (sel && !sel.includes('wt-') && !sel.includes('chrome-extension')) {
|
|
423
|
-
classRules.push(ruleText);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
} catch (_) {
|
|
428
|
-
// Cross-origin stylesheet security restriction — ignored
|
|
429
|
-
}
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// C. Build formatted comprehensive styles.css
|
|
433
|
-
let fullCss = '/* ==========================================================================\\n';
|
|
434
|
-
fullCss += ' Design Blueprint Stylesheet: ' + (document.title || window.location.hostname) + '\\n';
|
|
435
|
-
fullCss += ' Extracted via Weavetab browser_extract_design\\n';
|
|
436
|
-
fullCss += ' ========================================================================== */\\n\\n';
|
|
437
|
-
|
|
438
|
-
// 1. CSS Custom Properties
|
|
439
|
-
fullCss += '/* ── 1. CSS Custom Properties (:root / themes) ── */\\n:root {\\n';
|
|
440
|
-
Object.entries(cssVars).forEach(([k, v]) => {
|
|
441
|
-
fullCss += ' ' + k + ': ' + v + ';\\n';
|
|
442
|
-
});
|
|
443
|
-
fullCss += '}\\n\\n';
|
|
444
|
-
|
|
445
|
-
// 2. Font Faces
|
|
446
|
-
if (fontFaceList.length > 0) {
|
|
447
|
-
fullCss += '/* ── 2. Font Faces ── */\\n';
|
|
448
|
-
Array.from(new Set(fontFaceList)).slice(0, 10).forEach(ff => {
|
|
449
|
-
fullCss += ff + '\\n\\n';
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// 3. Keyframe Animations
|
|
454
|
-
if (keyframeMap.size > 0) {
|
|
455
|
-
fullCss += '/* ── 3. Keyframe Animations ── */\\n';
|
|
456
|
-
Array.from(keyframeMap.values()).forEach(kf => {
|
|
457
|
-
fullCss += kf + '\\n\\n';
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// 4. Stylesheet Rules / Style Blocks
|
|
462
|
-
fullCss += '/* ── 4. Layout & Component Styles ── */\\n';
|
|
463
|
-
if (collectedStyleBlocks.length > 0) {
|
|
464
|
-
collectedStyleBlocks.forEach(sb => {
|
|
465
|
-
fullCss += sb + '\\n\\n';
|
|
466
|
-
});
|
|
467
|
-
} else if (classRules.length > 0) {
|
|
468
|
-
Array.from(new Set(classRules)).forEach(cr => {
|
|
469
|
-
fullCss += cr + '\\n';
|
|
470
|
-
});
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// 5. Responsive Media Queries
|
|
474
|
-
if (mediaQueryMap.size > 0 && collectedStyleBlocks.length === 0) {
|
|
475
|
-
fullCss += '\\n/* ── 5. Responsive Media Queries ── */\\n';
|
|
476
|
-
Array.from(mediaQueryMap.values()).forEach(mq => {
|
|
477
|
-
fullCss += mq + '\\n\\n';
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
return {
|
|
482
|
-
tokens: {
|
|
483
|
-
meta,
|
|
484
|
-
cssVariables: cssVars,
|
|
485
|
-
palette: {
|
|
486
|
-
backgrounds: Array.from(bgColors).slice(0, 25),
|
|
487
|
-
text: Array.from(textColors).slice(0, 20),
|
|
488
|
-
borders: Array.from(borderColors).slice(0, 15),
|
|
489
|
-
accents: Array.from(bgColors).filter(c => !c.includes('255, 255, 255') && !c.includes('0, 0, 0') && !c.includes('rgba(0, 0, 0')).slice(0, 10)
|
|
490
|
-
},
|
|
491
|
-
typography: {
|
|
492
|
-
fontFamilies: Array.from(fontFamilies),
|
|
493
|
-
headings,
|
|
494
|
-
body: bodyTypography
|
|
495
|
-
},
|
|
496
|
-
effects: {
|
|
497
|
-
shadows: Array.from(shadows).slice(0, 10),
|
|
498
|
-
radii: Array.from(radii).slice(0, 10),
|
|
499
|
-
backdropBlurs: Array.from(blurs).slice(0, 5)
|
|
500
|
-
},
|
|
501
|
-
layout: {
|
|
502
|
-
displayTypes: Array.from(displayTypes),
|
|
503
|
-
gapValues: Array.from(gaps).slice(0, 10),
|
|
504
|
-
paddingValues: Array.from(paddings).slice(0, 10)
|
|
505
|
-
}
|
|
506
|
-
},
|
|
507
|
-
rawHtml: clone.outerHTML,
|
|
508
|
-
css: fullCss,
|
|
509
|
-
metrics: {
|
|
510
|
-
nodeCount: totalNodes,
|
|
511
|
-
svgCount,
|
|
512
|
-
htmlBytes: clone.outerHTML.length
|
|
513
|
-
}
|
|
514
|
-
};
|
|
97
|
+
expression: `(function(targetSelector, includeAssetsFlag) {
|
|
98
|
+
// Find target container
|
|
99
|
+
let root = document.querySelector(targetSelector);
|
|
100
|
+
if (!root && targetSelector !== 'body') {
|
|
101
|
+
root = document.querySelector('body') || document.documentElement;
|
|
102
|
+
}
|
|
103
|
+
if (!root) root = document.documentElement;
|
|
104
|
+
|
|
105
|
+
// ── 1. Meta & Environment ──
|
|
106
|
+
const isDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
107
|
+
|| document.documentElement.classList.contains('dark')
|
|
108
|
+
|| document.body?.classList.contains('dark')
|
|
109
|
+
|| (window.getComputedStyle(document.body).backgroundColor === 'rgb(0, 0, 0)' || window.getComputedStyle(document.body).backgroundColor.startsWith('rgb(1') || window.getComputedStyle(document.body).backgroundColor.startsWith('rgb(2'));
|
|
110
|
+
|
|
111
|
+
const meta = {
|
|
112
|
+
url: window.location.href,
|
|
113
|
+
title: document.title || '',
|
|
114
|
+
extractedAt: new Date().toISOString(),
|
|
115
|
+
selector: targetSelector,
|
|
116
|
+
viewport: {
|
|
117
|
+
width: window.innerWidth,
|
|
118
|
+
height: window.innerHeight
|
|
119
|
+
},
|
|
120
|
+
isDarkMode: !!isDark
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// ── 2. CSS Custom Properties (:root / html / body) ──
|
|
124
|
+
const cssVars = {};
|
|
125
|
+
const rootStyles = window.getComputedStyle(document.documentElement);
|
|
126
|
+
const bodyStyles = document.body ? window.getComputedStyle(document.body) : null;
|
|
127
|
+
|
|
128
|
+
// Extract declared variables from styleSheets
|
|
129
|
+
Array.from(document.styleSheets).forEach(sheet => {
|
|
130
|
+
try {
|
|
131
|
+
Array.from(sheet.cssRules || []).forEach(rule => {
|
|
132
|
+
if (rule.style && (rule.selectorText === ':root' || rule.selectorText === 'html' || rule.selectorText === 'body' || rule.selectorText === '.dark' || rule.selectorText === '[data-theme="dark"]')) {
|
|
133
|
+
for (let i = 0; i < rule.style.length; i++) {
|
|
134
|
+
const propName = rule.style[i];
|
|
135
|
+
if (propName.startsWith('--')) {
|
|
136
|
+
const val = rootStyles.getPropertyValue(propName).trim() || (bodyStyles ? bodyStyles.getPropertyValue(propName).trim() : '');
|
|
137
|
+
if (val) cssVars[propName] = val;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
} catch (_) {
|
|
143
|
+
// CORS stylesheet security restriction — ignore
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// ── Helper: Color Parsing & Normalization ──
|
|
148
|
+
function normalizeColor(c) {
|
|
149
|
+
if (!c || c === 'rgba(0, 0, 0, 0)' || c === 'transparent') return null;
|
|
150
|
+
return c.trim();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── 3. Token Harvesters ──
|
|
154
|
+
const bgColors = new Set();
|
|
155
|
+
const textColors = new Set();
|
|
156
|
+
const borderColors = new Set();
|
|
157
|
+
const fontFamilies = new Set();
|
|
158
|
+
const shadows = new Set();
|
|
159
|
+
const radii = new Set();
|
|
160
|
+
const blurs = new Set();
|
|
161
|
+
const displayTypes = new Set();
|
|
162
|
+
const gaps = new Set();
|
|
163
|
+
const paddings = new Set();
|
|
164
|
+
|
|
165
|
+
const headings = {};
|
|
166
|
+
let bodyTypography = {
|
|
167
|
+
fontFamily: '',
|
|
168
|
+
fontSize: '',
|
|
169
|
+
fontWeight: '',
|
|
170
|
+
lineHeight: '',
|
|
171
|
+
color: ''
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
if (document.body) {
|
|
175
|
+
const bs = window.getComputedStyle(document.body);
|
|
176
|
+
bodyTypography = {
|
|
177
|
+
fontFamily: bs.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
|
|
178
|
+
fontSize: bs.fontSize,
|
|
179
|
+
fontWeight: bs.fontWeight,
|
|
180
|
+
lineHeight: bs.lineHeight,
|
|
181
|
+
color: bs.color
|
|
182
|
+
};
|
|
183
|
+
if (bs.fontFamily) fontFamilies.add(bodyTypography.fontFamily);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Collect heading styles
|
|
187
|
+
['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(tag => {
|
|
188
|
+
const el = root.querySelector(tag);
|
|
189
|
+
if (el) {
|
|
190
|
+
const hs = window.getComputedStyle(el);
|
|
191
|
+
headings[tag] = {
|
|
192
|
+
fontFamily: hs.fontFamily.split(',')[0].replace(/['"]/g, '').trim(),
|
|
193
|
+
fontSize: hs.fontSize,
|
|
194
|
+
fontWeight: hs.fontWeight,
|
|
195
|
+
lineHeight: hs.lineHeight,
|
|
196
|
+
letterSpacing: hs.letterSpacing,
|
|
197
|
+
color: hs.color
|
|
198
|
+
};
|
|
199
|
+
if (hs.fontFamily) fontFamilies.add(headings[tag].fontFamily);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
let totalNodes = 0;
|
|
204
|
+
let svgCount = 0;
|
|
205
|
+
|
|
206
|
+
function walkTokens(node) {
|
|
207
|
+
if (!node || node.nodeType !== 1) return;
|
|
208
|
+
if (node.id === 'wt-shadow-host' || node.hasAttribute('data-wt-ignore')) return;
|
|
209
|
+
|
|
210
|
+
totalNodes++;
|
|
211
|
+
const tag = node.tagName.toLowerCase();
|
|
212
|
+
if (['script', 'style', 'noscript', 'iframe'].includes(tag)) return;
|
|
213
|
+
if (tag === 'svg') svgCount++;
|
|
214
|
+
|
|
215
|
+
const s = window.getComputedStyle(node);
|
|
216
|
+
if (s.display === 'none' || s.visibility === 'hidden') return;
|
|
217
|
+
|
|
218
|
+
const bg = normalizeColor(s.backgroundColor);
|
|
219
|
+
if (bg) bgColors.add(bg);
|
|
220
|
+
|
|
221
|
+
const col = normalizeColor(s.color);
|
|
222
|
+
if (col) textColors.add(col);
|
|
223
|
+
|
|
224
|
+
const bc = normalizeColor(s.borderColor);
|
|
225
|
+
if (bc && s.borderWidth !== '0px') borderColors.add(bc);
|
|
226
|
+
|
|
227
|
+
if (s.fontFamily) {
|
|
228
|
+
const f = s.fontFamily.split(',')[0].replace(/['"]/g, '').trim();
|
|
229
|
+
if (f && f !== 'inherit') fontFamilies.add(f);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (s.boxShadow && s.boxShadow !== 'none') shadows.add(s.boxShadow);
|
|
233
|
+
if (s.borderRadius && s.borderRadius !== '0px') radii.add(s.borderRadius);
|
|
234
|
+
if (s.backdropFilter && s.backdropFilter !== 'none') blurs.add(s.backdropFilter);
|
|
235
|
+
|
|
236
|
+
if (s.display) displayTypes.add(s.display);
|
|
237
|
+
if (s.gap && s.gap !== 'normal' && s.gap !== '0px') gaps.add(s.gap);
|
|
238
|
+
if (s.padding && s.padding !== '0px') paddings.add(s.padding);
|
|
239
|
+
|
|
240
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
241
|
+
walkTokens(node.children[i]);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
walkTokens(root);
|
|
246
|
+
|
|
247
|
+
// ── 4. Intelligent DOM Normalizer & Beautifier ──
|
|
248
|
+
const clone = root.cloneNode(true);
|
|
249
|
+
|
|
250
|
+
// A. Remove useless elements
|
|
251
|
+
clone.querySelectorAll('script, style, noscript, iframe, link, #wt-shadow-host, [data-wt-ignore]').forEach(el => el.remove());
|
|
252
|
+
|
|
253
|
+
// B. Handle Framer Motion / SplitText Spans & Hidden Duplicates
|
|
254
|
+
function normalizeDomTree(el) {
|
|
255
|
+
if (!el || el.nodeType !== 1) return;
|
|
256
|
+
|
|
257
|
+
const tag = el.tagName.toLowerCase();
|
|
258
|
+
|
|
259
|
+
// 1. Check for visually hidden / aria-hidden text duplication (Linear / Framer Motion pattern)
|
|
260
|
+
const srOnlyEl = el.querySelector('.visuallyHidden, [class*="visuallyHidden"], .sr-only, [class*="sr-only"]');
|
|
261
|
+
const ariaHiddenEl = el.querySelector('[aria-hidden="true"]');
|
|
262
|
+
if (srOnlyEl && (ariaHiddenEl || el.querySelectorAll('span').length > 2)) {
|
|
263
|
+
const canonicalText = srOnlyEl.textContent.trim();
|
|
264
|
+
if (canonicalText) {
|
|
265
|
+
el.innerHTML = canonicalText;
|
|
266
|
+
}
|
|
267
|
+
} else if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'button', 'a'].includes(tag)) {
|
|
268
|
+
// If all children are spans with fragmented text or animation styles, unsplit them
|
|
269
|
+
const spans = Array.from(el.children).filter(c => c.tagName.toLowerCase() === 'span');
|
|
270
|
+
const hasOtherContent = Array.from(el.children).some(c => !['span', 'br', 'wbr'].includes(c.tagName.toLowerCase()));
|
|
271
|
+
|
|
272
|
+
if (spans.length >= 2 && !hasOtherContent) {
|
|
273
|
+
// Check if spans have duplicate text due to responsive breakpoints (e.g. show-mobile vs hide-mobile)
|
|
274
|
+
const mobileSpans = spans.filter(s => s.className && s.className.includes('mobile'));
|
|
275
|
+
const hideMobileSpans = spans.filter(s => s.className && s.className.includes('hide-mobile'));
|
|
276
|
+
|
|
277
|
+
if (hideMobileSpans.length > 0) {
|
|
278
|
+
// Desktop active: keep hide-mobile / remove show-mobile
|
|
279
|
+
mobileSpans.filter(s => s.className.includes('show-mobile')).forEach(s => s.remove());
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// If remaining spans are just split words, join them cleanly
|
|
283
|
+
const remainingSpans = Array.from(el.children).filter(c => c.tagName.toLowerCase() === 'span');
|
|
284
|
+
const textContent = remainingSpans.map(s => s.textContent).join(' ').replace(/\\s+/g, ' ').trim();
|
|
285
|
+
if (textContent) {
|
|
286
|
+
el.innerHTML = textContent;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 2. Clean inline styles (remove animation / matrix / opacity: 1 fluff)
|
|
292
|
+
if (el.hasAttribute('style')) {
|
|
293
|
+
const rawStyle = el.getAttribute('style') || '';
|
|
294
|
+
const meaningfulStyles = [];
|
|
295
|
+
|
|
296
|
+
rawStyle.split(';').forEach(rule => {
|
|
297
|
+
const trimmed = rule.trim();
|
|
298
|
+
if (!trimmed) return;
|
|
299
|
+
const [prop, val] = trimmed.split(':').map(s => s.trim());
|
|
300
|
+
if (!prop || !val) return;
|
|
301
|
+
|
|
302
|
+
// Skip animation artifacts
|
|
303
|
+
if (
|
|
304
|
+
(prop === 'opacity' && val === '1') ||
|
|
305
|
+
(prop === 'filter' && (val === 'blur(0px)' || val === 'none')) ||
|
|
306
|
+
(prop === 'transform' && (val.includes('translateY(0%)') || val.includes('translate3d(0, 0, 0)') || val === 'none' || val.includes('matrix(1, 0, 0, 1, 0, 0)'))) ||
|
|
307
|
+
(prop === 'will-change') ||
|
|
308
|
+
(prop === 'perspective') ||
|
|
309
|
+
(prop === 'backface-visibility')
|
|
310
|
+
) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Keep intentional layout styles / CSS variables / spacer heights
|
|
315
|
+
if (prop.startsWith('--height') || prop === 'height') {
|
|
316
|
+
meaningfulStyles.push('height: ' + val);
|
|
317
|
+
} else if (prop.startsWith('--gap') || prop === 'gap') {
|
|
318
|
+
meaningfulStyles.push('gap: ' + val);
|
|
319
|
+
} else if (!prop.startsWith('--')) {
|
|
320
|
+
meaningfulStyles.push(prop + ': ' + val);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
if (meaningfulStyles.length > 0) {
|
|
325
|
+
el.setAttribute('style', meaningfulStyles.join('; '));
|
|
326
|
+
} else {
|
|
327
|
+
el.removeAttribute('style');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 3. Clean and normalize class names (strip random hashes, keep semantic names)
|
|
332
|
+
if (el.hasAttribute('class')) {
|
|
333
|
+
const rawClasses = (el.getAttribute('class') || '').split(/\\s+/);
|
|
334
|
+
const cleanClasses = [];
|
|
335
|
+
|
|
336
|
+
rawClasses.forEach(cls => {
|
|
337
|
+
const c = cls.trim();
|
|
338
|
+
if (!c) return;
|
|
339
|
+
// Skip internal / ephemeral classes
|
|
340
|
+
if (c.startsWith('sc-') || c === 'show-mobile' || c.startsWith('wt-')) return;
|
|
341
|
+
|
|
342
|
+
// De-hash CSS module names: e.g. "ImRUSq_justify-space-between" -> "justify-between"
|
|
343
|
+
if (c.includes('_')) {
|
|
344
|
+
const parts = c.split('_');
|
|
345
|
+
const semanticPart = parts[parts.length - 1];
|
|
346
|
+
if (semanticPart && semanticPart.length > 2 && !semanticPart.match(/^[a-f0-9]{4,}$/i)) {
|
|
347
|
+
let normalized = semanticPart
|
|
348
|
+
.replace('justify-space-between', 'justify-between')
|
|
349
|
+
.replace('align-center', 'items-center');
|
|
350
|
+
if (normalized === 'root') normalized = 'spacer';
|
|
351
|
+
cleanClasses.push(normalized);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Keep standard semantic / utility class
|
|
357
|
+
cleanClasses.push(c);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const uniqueClasses = Array.from(new Set(cleanClasses));
|
|
361
|
+
if (uniqueClasses.length > 0) {
|
|
362
|
+
el.setAttribute('class', uniqueClasses.join(' '));
|
|
363
|
+
} else {
|
|
364
|
+
el.removeAttribute('class');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// 4. Remove internal / framework attributes
|
|
369
|
+
['data-wt-id', 'data-wt-secret', 'jsaction', 'jscontroller', 'jsname', 'jsmodel', 'data-sentry-component', 'data-sentry-source-file'].forEach(attr => {
|
|
370
|
+
el.removeAttribute(attr);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// 5. Clean SVGs
|
|
374
|
+
if (el.tagName.toLowerCase() === 'svg' && !includeAssetsFlag) {
|
|
375
|
+
el.innerHTML = '<!-- svg icon -->';
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Recurse children
|
|
379
|
+
const children = Array.from(el.children);
|
|
380
|
+
for (let i = 0; i < children.length; i++) {
|
|
381
|
+
normalizeDomTree(children[i]);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// 6. Remove empty spacer divs with no styles or classes
|
|
385
|
+
if (tag === 'div' && el.children.length === 0 && !el.textContent.trim() && !el.hasAttribute('style') && !el.hasAttribute('class')) {
|
|
386
|
+
el.remove();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
normalizeDomTree(clone);
|
|
391
|
+
|
|
392
|
+
// ── 5. Full Real CSS Extraction ──
|
|
393
|
+
const collectedStyleBlocks = [];
|
|
394
|
+
const keyframeMap = new Map();
|
|
395
|
+
const mediaQueryMap = new Map();
|
|
396
|
+
const fontFaceList = [];
|
|
397
|
+
const classRules = [];
|
|
398
|
+
|
|
399
|
+
// A. Extract all <style> tags (includes Tailwind, Emotion, styled-components, Next.js CSS)
|
|
400
|
+
document.querySelectorAll('style').forEach(st => {
|
|
401
|
+
const text = (st.textContent || '').trim();
|
|
402
|
+
if (text) {
|
|
403
|
+
collectedStyleBlocks.push(text);
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// B. Extract accessible CSS rules from document.styleSheets
|
|
408
|
+
Array.from(document.styleSheets).forEach(sheet => {
|
|
409
|
+
try {
|
|
410
|
+
Array.from(sheet.cssRules || []).forEach(rule => {
|
|
411
|
+
const ruleText = rule.cssText;
|
|
412
|
+
if (!ruleText) return;
|
|
413
|
+
|
|
414
|
+
if (rule.type === 7 || rule.constructor.name === 'CSSKeyframesRule') {
|
|
415
|
+
keyframeMap.set(rule.name || ruleText.slice(0, 30), ruleText);
|
|
416
|
+
} else if (rule.type === 5 || rule.constructor.name === 'CSSFontFaceRule') {
|
|
417
|
+
fontFaceList.push(ruleText);
|
|
418
|
+
} else if (rule.type === 4 || rule.constructor.name === 'CSSMediaRule') {
|
|
419
|
+
mediaQueryMap.set(rule.conditionText || ruleText.slice(0, 30), ruleText);
|
|
420
|
+
} else if (rule.style) {
|
|
421
|
+
const sel = rule.selectorText || '';
|
|
422
|
+
if (sel && !sel.includes('wt-') && !sel.includes('chrome-extension')) {
|
|
423
|
+
classRules.push(ruleText);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
} catch (_) {
|
|
428
|
+
// Cross-origin stylesheet security restriction — ignored
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// C. Build formatted comprehensive styles.css
|
|
433
|
+
let fullCss = '/* ==========================================================================\\n';
|
|
434
|
+
fullCss += ' Design Blueprint Stylesheet: ' + (document.title || window.location.hostname) + '\\n';
|
|
435
|
+
fullCss += ' Extracted via Weavetab browser_extract_design\\n';
|
|
436
|
+
fullCss += ' ========================================================================== */\\n\\n';
|
|
437
|
+
|
|
438
|
+
// 1. CSS Custom Properties
|
|
439
|
+
fullCss += '/* ── 1. CSS Custom Properties (:root / themes) ── */\\n:root {\\n';
|
|
440
|
+
Object.entries(cssVars).forEach(([k, v]) => {
|
|
441
|
+
fullCss += ' ' + k + ': ' + v + ';\\n';
|
|
442
|
+
});
|
|
443
|
+
fullCss += '}\\n\\n';
|
|
444
|
+
|
|
445
|
+
// 2. Font Faces
|
|
446
|
+
if (fontFaceList.length > 0) {
|
|
447
|
+
fullCss += '/* ── 2. Font Faces ── */\\n';
|
|
448
|
+
Array.from(new Set(fontFaceList)).slice(0, 10).forEach(ff => {
|
|
449
|
+
fullCss += ff + '\\n\\n';
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// 3. Keyframe Animations
|
|
454
|
+
if (keyframeMap.size > 0) {
|
|
455
|
+
fullCss += '/* ── 3. Keyframe Animations ── */\\n';
|
|
456
|
+
Array.from(keyframeMap.values()).forEach(kf => {
|
|
457
|
+
fullCss += kf + '\\n\\n';
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// 4. Stylesheet Rules / Style Blocks
|
|
462
|
+
fullCss += '/* ── 4. Layout & Component Styles ── */\\n';
|
|
463
|
+
if (collectedStyleBlocks.length > 0) {
|
|
464
|
+
collectedStyleBlocks.forEach(sb => {
|
|
465
|
+
fullCss += sb + '\\n\\n';
|
|
466
|
+
});
|
|
467
|
+
} else if (classRules.length > 0) {
|
|
468
|
+
Array.from(new Set(classRules)).forEach(cr => {
|
|
469
|
+
fullCss += cr + '\\n';
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// 5. Responsive Media Queries
|
|
474
|
+
if (mediaQueryMap.size > 0 && collectedStyleBlocks.length === 0) {
|
|
475
|
+
fullCss += '\\n/* ── 5. Responsive Media Queries ── */\\n';
|
|
476
|
+
Array.from(mediaQueryMap.values()).forEach(mq => {
|
|
477
|
+
fullCss += mq + '\\n\\n';
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
tokens: {
|
|
483
|
+
meta,
|
|
484
|
+
cssVariables: cssVars,
|
|
485
|
+
palette: {
|
|
486
|
+
backgrounds: Array.from(bgColors).slice(0, 25),
|
|
487
|
+
text: Array.from(textColors).slice(0, 20),
|
|
488
|
+
borders: Array.from(borderColors).slice(0, 15),
|
|
489
|
+
accents: Array.from(bgColors).filter(c => !c.includes('255, 255, 255') && !c.includes('0, 0, 0') && !c.includes('rgba(0, 0, 0')).slice(0, 10)
|
|
490
|
+
},
|
|
491
|
+
typography: {
|
|
492
|
+
fontFamilies: Array.from(fontFamilies),
|
|
493
|
+
headings,
|
|
494
|
+
body: bodyTypography
|
|
495
|
+
},
|
|
496
|
+
effects: {
|
|
497
|
+
shadows: Array.from(shadows).slice(0, 10),
|
|
498
|
+
radii: Array.from(radii).slice(0, 10),
|
|
499
|
+
backdropBlurs: Array.from(blurs).slice(0, 5)
|
|
500
|
+
},
|
|
501
|
+
layout: {
|
|
502
|
+
displayTypes: Array.from(displayTypes),
|
|
503
|
+
gapValues: Array.from(gaps).slice(0, 10),
|
|
504
|
+
paddingValues: Array.from(paddings).slice(0, 10)
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
rawHtml: clone.outerHTML,
|
|
508
|
+
css: fullCss,
|
|
509
|
+
metrics: {
|
|
510
|
+
nodeCount: totalNodes,
|
|
511
|
+
svgCount,
|
|
512
|
+
htmlBytes: clone.outerHTML.length
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
515
|
})(${JSON.stringify(selector)}, ${JSON.stringify(includeAssets)})`,
|
|
516
516
|
returnByValue: true
|
|
517
517
|
});
|
|
@@ -581,29 +581,29 @@ export async function browserExtractDesign(session, options = {}, config) {
|
|
|
581
581
|
}
|
|
582
582
|
// Generate Markdown README Guide for AI Agent
|
|
583
583
|
const readmePath = path.join(targetDir, "README.md");
|
|
584
|
-
const readmeContent = `# Design Blueprint: ${tokens.meta.title || tokens.meta.url}
|
|
585
|
-
|
|
586
|
-
- **Extracted At**: ${tokens.meta.extractedAt}
|
|
587
|
-
- **Source URL**: ${tokens.meta.url}
|
|
588
|
-
- **Theme**: ${tokens.meta.isDarkMode ? "Dark Mode" : "Light Mode"}
|
|
589
|
-
- **Selector**: \`${tokens.meta.selector}\`
|
|
590
|
-
|
|
591
|
-
## Design System Tokens
|
|
592
|
-
- **Font Families**: ${tokens.typography.fontFamilies.join(", ") || "System Default"}
|
|
593
|
-
- **Primary Background Colors**: \`${tokens.palette.backgrounds.slice(0, 5).join("`, `")}\`
|
|
594
|
-
- **Text Colors**: \`${tokens.palette.text.slice(0, 5).join("`, `")}\`
|
|
595
|
-
- **Border Radii**: \`${tokens.effects.radii.slice(0, 5).join("`, `")}\`
|
|
596
|
-
- **Shadows**: ${tokens.effects.shadows.length} extracted shadow styles
|
|
597
|
-
|
|
598
|
-
## Included Files
|
|
599
|
-
1. \`tokens.json\` — Full machine-readable design tokens, CSS variables, typography hierarchy, and colors.
|
|
600
|
-
2. \`template.html\` — Clean, semantic, beautified HTML skeleton (${metrics.nodeCount} elements, ${metrics.svgCount} SVGs).
|
|
601
|
-
3. \`styles.css\` — Extracted CSS variables and scoped styles.
|
|
602
|
-
${tailwindPath ? "4. `tailwind.theme.json` — Tailwind CSS `theme.extend` configuration.\n" : ""}
|
|
603
|
-
## Agent Implementation Instructions
|
|
604
|
-
- Open \`tokens.json\` to copy exact hex colors, typography weights, and border radius tokens.
|
|
605
|
-
- Open \`template.html\` to review semantic structure, SVGs, and component layout hierarchy.
|
|
606
|
-
- Recreate the target component in your project using these exact tokens.
|
|
584
|
+
const readmeContent = `# Design Blueprint: ${tokens.meta.title || tokens.meta.url}
|
|
585
|
+
|
|
586
|
+
- **Extracted At**: ${tokens.meta.extractedAt}
|
|
587
|
+
- **Source URL**: ${tokens.meta.url}
|
|
588
|
+
- **Theme**: ${tokens.meta.isDarkMode ? "Dark Mode" : "Light Mode"}
|
|
589
|
+
- **Selector**: \`${tokens.meta.selector}\`
|
|
590
|
+
|
|
591
|
+
## Design System Tokens
|
|
592
|
+
- **Font Families**: ${tokens.typography.fontFamilies.join(", ") || "System Default"}
|
|
593
|
+
- **Primary Background Colors**: \`${tokens.palette.backgrounds.slice(0, 5).join("`, `")}\`
|
|
594
|
+
- **Text Colors**: \`${tokens.palette.text.slice(0, 5).join("`, `")}\`
|
|
595
|
+
- **Border Radii**: \`${tokens.effects.radii.slice(0, 5).join("`, `")}\`
|
|
596
|
+
- **Shadows**: ${tokens.effects.shadows.length} extracted shadow styles
|
|
597
|
+
|
|
598
|
+
## Included Files
|
|
599
|
+
1. \`tokens.json\` — Full machine-readable design tokens, CSS variables, typography hierarchy, and colors.
|
|
600
|
+
2. \`template.html\` — Clean, semantic, beautified HTML skeleton (${metrics.nodeCount} elements, ${metrics.svgCount} SVGs).
|
|
601
|
+
3. \`styles.css\` — Extracted CSS variables and scoped styles.
|
|
602
|
+
${tailwindPath ? "4. `tailwind.theme.json` — Tailwind CSS `theme.extend` configuration.\n" : ""}
|
|
603
|
+
## Agent Implementation Instructions
|
|
604
|
+
- Open \`tokens.json\` to copy exact hex colors, typography weights, and border radius tokens.
|
|
605
|
+
- Open \`template.html\` to review semantic structure, SVGs, and component layout hierarchy.
|
|
606
|
+
- Recreate the target component in your project using these exact tokens.
|
|
607
607
|
`;
|
|
608
608
|
await fs.writeFile(readmePath, readmeContent, "utf-8");
|
|
609
609
|
return {
|