// extract-web-tokens.js // Injectar via mcp__claude-in-chrome__javascript_tool ou mcp__chrome-devtools__evaluate_script // Devolve JSON com cores, fontes, espaçamentos, raios, sombras e amostras de componentes. // Funciona em qualquer site (incluindo JS-rendered) porque usa getComputedStyle(). (() => { const all = document.querySelectorAll('*'); const colorCounts = {}; const fontFamilies = {}; const fontSizes = {}; const fontWeights = {}; const lineHeights = {}; const radii = {}; const shadows = {}; const spacings = {}; const bgColors = {}; const norm = (v) => (v || '').trim(); const inc = (obj, k) => { if (!k || k === 'none' || k === '0px') return; obj[k] = (obj[k] || 0) + 1; }; // Helper: rgb(a) → hex (ignora transparente) const toHex = (rgb) => { if (!rgb || rgb === 'transparent' || rgb === 'rgba(0, 0, 0, 0)') return null; const m = rgb.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); if (!m) return null; const a = m[4] !== undefined ? parseFloat(m[4]) : 1; if (a < 0.1) return null; const h = (n) => parseInt(n).toString(16).padStart(2, '0'); return '#' + h(m[1]) + h(m[2]) + h(m[3]); }; for (const el of all) { if (!(el instanceof Element)) continue; const cs = getComputedStyle(el); // Cores inc(colorCounts, toHex(cs.color)); const bg = toHex(cs.backgroundColor); if (bg) { inc(bgColors, bg); inc(colorCounts, bg); } inc(colorCounts, toHex(cs.borderColor)); // Fontes inc(fontFamilies, norm(cs.fontFamily)); inc(fontSizes, norm(cs.fontSize)); inc(fontWeights, norm(cs.fontWeight)); inc(lineHeights, norm(cs.lineHeight)); // Raios inc(radii, norm(cs.borderRadius)); // Sombras if (cs.boxShadow && cs.boxShadow !== 'none') inc(shadows, cs.boxShadow); // Espaçamentos (paddings + margins + gap) ['paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft', 'gap', 'rowGap', 'columnGap'].forEach((p) => { inc(spacings, norm(cs[p])); }); } // Top N por frequência const topN = (obj, n) => Object.entries(obj).sort((a, b) => b[1] - a[1]).slice(0, n).map(([k, v]) => ({ value: k, count: v })); // Detectar grid base (espaçamento mais comum) const numericSpacings = Object.entries(spacings) .map(([k, v]) => [parseFloat(k), v]) .filter(([n]) => !isNaN(n) && n > 0 && n < 200) .sort((a, b) => b[1] - a[1]); const gridBase = numericSpacings[0]?.[0] || 8; // Capturar samples HTML de componentes-chave (pelo selector) const sample = (sel) => { const el = document.querySelector(sel); if (!el) return null; return { html: el.outerHTML.substring(0, 800), computed: { bg: toHex(getComputedStyle(el).backgroundColor), color: toHex(getComputedStyle(el).color), radius: getComputedStyle(el).borderRadius, padding: getComputedStyle(el).padding, font: getComputedStyle(el).fontFamily, } }; }; return { url: location.href, title: document.title, timestamp: new Date().toISOString(), colors: { top: topN(colorCounts, 12), backgrounds: topN(bgColors, 6), dominant: topN(colorCounts, 1)[0]?.value || null, }, fonts: { families: topN(fontFamilies, 5), sizes: topN(fontSizes, 8), weights: topN(fontWeights, 6), lineHeights: topN(lineHeights, 6), }, borderRadius: topN(radii, 6), shadows: topN(shadows, 5), spacing: { gridBaseGuess: gridBase, top: topN(spacings, 12), }, components: { button: sample('button, .btn, [class*="button" i]'), card: sample('.card, [class*="card" i]'), nav: sample('nav, header, [class*="nav" i]'), hero: sample('main > section:first-child, .hero, [class*="hero" i]'), }, meta: { colorMode: getComputedStyle(document.body).backgroundColor.includes('255') ? 'LIGHT' : 'DARK', lang: document.documentElement.lang || 'unknown', viewport: document.querySelector('meta[name=viewport]')?.content || null, } }; })();