From Design System
Extracts brand colors, fonts, and copy tone from a landing page URL using headless Playwright. Useful when recreating a site's visual style.
How this skill is triggered — by the user, by Claude, or both
Slash command
/design-system:brand-extractorWhen to use
Юзер: "сделай в стиле как у нас на сайте", показывает URL. Чтобы не угадывать.
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Заходим на URL в headless, парсим computed-styles ключевых элементов, собираем гипотезу о бренде.
Заходим на URL в headless, парсим computed-styles ключевых элементов, собираем гипотезу о бренде.
npm i -D playwright
npx playwright install chromium
templates/extract.mjs:
import { chromium } from 'playwright';
import fs from 'node:fs/promises';
const url = process.argv[2];
if (!url) { console.error('Usage: node extract.mjs <url>'); process.exit(1); }
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
const data = await page.evaluate(() => {
const get = (sel, prop) => {
const el = document.querySelector(sel);
if (!el) return null;
return getComputedStyle(el)[prop];
};
// Цвета — пробежать по видимым элементам и посчитать частоту
const colors = new Map();
document.querySelectorAll('*').forEach(el => {
const cs = getComputedStyle(el);
const r = el.getBoundingClientRect();
if (r.width < 30 || r.height < 30) return;
[cs.backgroundColor, cs.color].forEach(c => {
if (!c || c === 'rgba(0, 0, 0, 0)' || c === 'transparent') return;
colors.set(c, (colors.get(c) || 0) + 1);
});
});
const topColors = [...colors.entries()].sort((a,b) => b[1]-a[1]).slice(0, 10);
// Шрифты по hierarchy
const fonts = {
h1: { family: get('h1', 'fontFamily'), weight: get('h1', 'fontWeight'), size: get('h1', 'fontSize') },
h2: { family: get('h2', 'fontFamily'), weight: get('h2', 'fontWeight'), size: get('h2', 'fontSize') },
body: { family: get('p', 'fontFamily'), weight: get('p', 'fontWeight'), size: get('p', 'fontSize') },
button:{ family: get('button, .button, .btn', 'fontFamily'), weight: get('button, .button, .btn', 'fontWeight'), size: get('button, .button, .btn', 'fontSize') },
};
// Копирайт
const copy = {
title: document.querySelector('h1')?.innerText?.slice(0, 100),
subtitle: document.querySelector('h1 + p, h1 ~ p')?.innerText?.slice(0, 200),
cta: [...document.querySelectorAll('button, .button, .btn, a.cta')]
.map(b => b.innerText.trim()).filter(Boolean).slice(0, 5),
};
return { topColors, fonts, copy };
});
await page.screenshot({ path: 'brand-snapshot.png', fullPage: false });
await browser.close();
await fs.writeFile('brand.json', JSON.stringify(data, null, 2));
console.log('✓ brand.json + brand-snapshot.png');
console.log('\nTop colors:'); data.topColors.forEach(([c, n]) => console.log(` ${c.padEnd(28)} ×${n}`));
console.log('\nFonts:'); console.table(data.fonts);
console.log('\nCopy:'); console.log(data.copy);
node extract.mjs https://example.com
# → brand.json + brand-snapshot.png + табличка
playwright-extra + stealth или скриншот вручную.await page.waitForTimeout(2000) или waitForSelector.colorScheme: 'dark' в context.--var: вытаскиваются как var(--font), надо разрешить через getComputedStyle(document.documentElement).getPropertyValue('--font').content-policy — нельзя выдавать брендированную айдентику чужого продукта за свой.color-system-builder:
node build-palette.mjs "$(jq -r '.topColors[0][0]' brand.json)"
type-scale — выбери base size из fonts.body.size.frontend-design — определи, какое из 5 направлений ближе.Если URL под anti-bot — попроси юзера прислать скриншот hero-секции. Дальше — sketch-to-html подход, но цели — извлечь палитру и шрифты, а не структуру.
npx claudepluginhub jhamidun/claude-code-config-pack --plugin design-systemBuilds a throwaway prototype to answer a design question about UI appearance or state/logic behavior. Guides you through two branches: interactive terminal app for logic validation, or multiple UI variations for visual exploration.