Visual Brand Extractor
Render a site with Playwright, harvest its raw visual signals (CSS custom properties,
color declarations, fonts, theme-color meta, Tailwind classes, layout patterns), capture
a hero screenshot, and sample its dominant colors numerically. You then classify color
roles, pick the typography, and synthesize the vibe into a reusable preset. Scripts do
only extraction/measurement.
When to use
- "Extract visual branding from [url] for [client]."
- Onboarding a client with no brand/style guide when downstream skills (slides, carousels,
content assets) need consistent colors/fonts.
- A feeder for any visual-output skill that takes a brand config (theme, fonts, accent).
How to run
The Playwright extractor needs Chromium; the Python helpers are stdlib-first (Pillow used
if present, with a stdlib PNG fallback so they run with no install).
1. Render + harvest signals (+ hero screenshot)
# one-time: npm --prefix ${SKILL_DIR}/scripts install && npx playwright install chromium
node ${SKILL_DIR}/scripts/extract_brand.mjs \
--url https://acme.com \
--client "Acme" \
--pages https://acme.com/product https://acme.com/blog \
--screenshot ${WORKSPACE}/hero.png \
--output ${WORKSPACE}/signals.json
signals.json carries, per page: custom_properties (--color-*/--bg-*/--accent),
meta_colors (theme-color), colors_ranked (by on-page frequency), fonts, font_links
(Google/Fontshare/Typekit), tailwind_classes, tailwind_config_snippet, and layout
(border-radius / gradient / shadow / animation signals).
2. Sample dominant colors from the hero screenshot (numeric cross-check)
python3 ${SKILL_DIR}/scripts/sample_colors.py \
--image ${WORKSPACE}/hero.png \
--num-colors 8 \
--output ${WORKSPACE}/palette.json
Returns ranked dominant colors with share + luminance, a theme_type_guess
(dark/light/mixed from background luminance), and a background_guess. Use this to resolve
ties and confirm the accent/CTA color when CSS is sparse.
3. Resolve Tailwind color classes to hex (if the site is Tailwind)
python3 ${SKILL_DIR}/scripts/tailwind_map.py \
--signals ${WORKSPACE}/signals.json \
--output ${WORKSPACE}/tw_colors.json
Maps harvested bg-*/text-*/border-*/... classes against the default Tailwind palette;
custom theme tokens it can't resolve are listed under unresolved_custom.
4. Synthesize the Visual Brand Identity (you, the agent)
Combine signals.json + palette.json (+ tw_colors.json + the hero.png you can view)
and write the artifact. Extract in priority order: CSS custom properties → meta tags →
ranked color declarations → Tailwind classes, cross-checked against the screenshot's
dominant colors. Then:
- Color roles — classify into bg-primary/secondary, text-primary/secondary, accent,
accent-secondary, card. Determine theme type from bg-primary luminance. Buttons/CTAs and
SVG
fill reveal the accent. Aim for 5–7 colors — don't over-extract.
- Typography — display vs body from
fonts/font_links; for proprietary fonts not on
Google/Fontshare, map to the closest web-available equivalent. Aim for 1–2 fonts.
- Visual patterns — vibe (2–4 adjectives), one-line layout description, and 3–4
reproducible signature CSS elements, using the
layout signals + the screenshot.
- Emit two formats in one Markdown artifact:
- Slide preset — vibe, layout, display/body typography, a
:root CSS custom-property
color block, 3–4 signature CSS elements, and a Google Fonts / Fontshare load tag.
- Brand config JSON —
{name, primary_color, secondary_color, accent_color, background, text_color, font_heading, font_body, logo_url?}.
- Extraction Notes — proprietary-font mappings, multiple/dark-mode themes, sparse-CSS
caveats.
Outputs
signals.json, hero.png, palette.json, tw_colors.json — deterministic extraction
artifacts.
- Visual Brand Identity Markdown (
<client>/brand/visual-identity.md) with the slide
preset + brand-config JSON + extraction notes — your synthesis, returned as the result
and saved to the workspace; for team use, post to the Agent Teams channel.
Credentials / env
- Required: none. Rendering and color sampling are keyless (the default). Role
classification, font choice, and vibe synthesis are done by you (the agent) — no LLM key is
consumed by scripts.
- Optional (paid upgrade, with a keyless fallback):
APIFY_API_TOKEN — if set → route a too-hostile site through an Apify rendering/extractor
actor and screenshot. If not set → default keyless path: extract_brand.mjs (Playwright
render + hero screenshot) and sample_colors.py; if even Playwright is blocked, ask the
user for a screenshot/brand guidelines and sample that. Last resort, never required.
- See
env.optional.
Notes & edge cases
- JS-rendered sites (Next.js/React) return thin static CSS —
extract_brand.mjs renders
first (networkidle) so computed styles and webfonts are real. It also captures any
inlined tailwind.config from <script> tags.
- Light/dark mode: the extractor reads the default (non-media-query) computed theme; note
any
prefers-color-scheme dark variant in Extraction Notes.
- Too many colors (enterprise sites): the hero screenshot's
dominant_colors (frequency-
ranked) + the button/CTA colors define the brand — sample_colors.py resolves ties.
sample_colors.py prefers Pillow but falls back to a built-in PNG decoder, so it runs with
no pip install on the PNG extract_brand.mjs writes.
- If a site is unrenderable even via Playwright, ask the user for a screenshot or brand
guidelines and run
sample_colors.py against that screenshot as the rescue path.
- Don't over-extract — a 3-color palette + one font family at two weights is a clean preset.
1---2name: visual-brand-extractor3description: Visual Brand Extractor4---56# Visual Brand Extractor78Render a site with Playwright, harvest its raw visual signals (CSS custom properties,9color declarations, fonts, theme-color meta, Tailwind classes, layout patterns), capture10a hero screenshot, and sample its dominant colors numerically. **You then classify color11roles, pick the typography, and synthesize the vibe** into a reusable preset. Scripts do12only extraction/measurement.1314## When to use1516- "Extract visual branding from [url] for [client]."17- Onboarding a client with no brand/style guide when downstream skills (slides, carousels,18 content assets) need consistent colors/fonts.19- A feeder for any visual-output skill that takes a brand config (theme, fonts, accent).2021## How to run2223The Playwright extractor needs Chromium; the Python helpers are stdlib-first (Pillow used24if present, with a stdlib PNG fallback so they run with no install).2526### 1. Render + harvest signals (+ hero screenshot)2728```bash29# one-time: npm --prefix ${SKILL_DIR}/scripts install && npx playwright install chromium30node ${SKILL_DIR}/scripts/extract_brand.mjs \31 --url https://acme.com \32 --client "Acme" \33 --pages https://acme.com/product https://acme.com/blog \34 --screenshot ${WORKSPACE}/hero.png \35 --output ${WORKSPACE}/signals.json36```3738`signals.json` carries, per page: `custom_properties` (`--color-*`/`--bg-*`/`--accent`),39`meta_colors` (theme-color), `colors_ranked` (by on-page frequency), `fonts`, `font_links`40(Google/Fontshare/Typekit), `tailwind_classes`, `tailwind_config_snippet`, and `layout`41(border-radius / gradient / shadow / animation signals).4243### 2. Sample dominant colors from the hero screenshot (numeric cross-check)4445```bash46python3 ${SKILL_DIR}/scripts/sample_colors.py \47 --image ${WORKSPACE}/hero.png \48 --num-colors 8 \49 --output ${WORKSPACE}/palette.json50```5152Returns ranked dominant colors with `share` + `luminance`, a `theme_type_guess`53(dark/light/mixed from background luminance), and a `background_guess`. Use this to resolve54ties and confirm the accent/CTA color when CSS is sparse.5556### 3. Resolve Tailwind color classes to hex (if the site is Tailwind)5758```bash59python3 ${SKILL_DIR}/scripts/tailwind_map.py \60 --signals ${WORKSPACE}/signals.json \61 --output ${WORKSPACE}/tw_colors.json62```6364Maps harvested `bg-*/text-*/border-*/...` classes against the default Tailwind palette;65custom theme tokens it can't resolve are listed under `unresolved_custom`.6667### 4. Synthesize the Visual Brand Identity (you, the agent)6869Combine `signals.json` + `palette.json` (+ `tw_colors.json` + the `hero.png` you can view)70and write the artifact. Extract in priority order: **CSS custom properties → meta tags →71ranked color declarations → Tailwind classes**, cross-checked against the screenshot's72dominant colors. Then:73741. **Color roles** — classify into bg-primary/secondary, text-primary/secondary, accent,75 accent-secondary, card. Determine theme type from bg-primary luminance. Buttons/CTAs and76 SVG `fill` reveal the accent. Aim for **5–7 colors** — don't over-extract.772. **Typography** — display vs body from `fonts`/`font_links`; for proprietary fonts not on78 Google/Fontshare, map to the closest web-available equivalent. Aim for **1–2 fonts**.793. **Visual patterns** — vibe (2–4 adjectives), one-line layout description, and **3–4**80 reproducible signature CSS elements, using the `layout` signals + the screenshot.814. **Emit two formats** in one Markdown artifact:82 - **Slide preset** — vibe, layout, display/body typography, a `:root` CSS custom-property83 color block, 3–4 signature CSS elements, and a Google Fonts / Fontshare load tag.84 - **Brand config JSON** — `{name, primary_color, secondary_color, accent_color,85 background, text_color, font_heading, font_body, logo_url?}`.865. **Extraction Notes** — proprietary-font mappings, multiple/dark-mode themes, sparse-CSS87 caveats.8889## Outputs9091- `signals.json`, `hero.png`, `palette.json`, `tw_colors.json` — deterministic extraction92 artifacts.93- **Visual Brand Identity** Markdown (`<client>/brand/visual-identity.md`) with the slide94 preset + brand-config JSON + extraction notes — your synthesis, returned as the result95 and saved to the workspace; for team use, post to the Agent Teams channel.9697## Credentials / env9899- **Required:** none. Rendering and color sampling are keyless (the default). Role100 classification, font choice, and vibe synthesis are done by you (the agent) — no LLM key is101 consumed by scripts.102- **Optional (paid upgrade, with a keyless fallback):**103 - `APIFY_API_TOKEN` — if set → route a too-hostile site through an Apify rendering/extractor104 actor and screenshot. If not set → default keyless path: `extract_brand.mjs` (Playwright105 render + hero screenshot) and `sample_colors.py`; if even Playwright is blocked, ask the106 user for a screenshot/brand guidelines and sample that. Last resort, never required.107 - See `env.optional`.108109## Notes & edge cases110111- **JS-rendered sites** (Next.js/React) return thin static CSS — `extract_brand.mjs` renders112 first (`networkidle`) so computed styles and webfonts are real. It also captures any113 inlined `tailwind.config` from `<script>` tags.114- **Light/dark mode:** the extractor reads the default (non-media-query) computed theme; note115 any `prefers-color-scheme` dark variant in Extraction Notes.116- **Too many colors** (enterprise sites): the hero screenshot's `dominant_colors` (frequency-117 ranked) + the button/CTA colors define the brand — `sample_colors.py` resolves ties.118- `sample_colors.py` prefers Pillow but falls back to a built-in PNG decoder, so it runs with119 no pip install on the PNG `extract_brand.mjs` writes.120- If a site is unrenderable even via Playwright, ask the user for a screenshot or brand121 guidelines and run `sample_colors.py` against that screenshot as the rescue path.122- Don't over-extract — a 3-color palette + one font family at two weights is a clean preset.