Back to skills
SKILL.md
Reverse Ui
BSecurityReverse-engineer any web application into a complete pixel-perfect reproduction kit. Extracts design tokens, DOM structure, text content, assets, and responsive screenshots across multiple pages. Use when the user asks to "reverse-engineer a UI", "extract a design system", "analyze the design of a website", or wants to reproduce a site pixel-perfect.
- 2 stars
- 0 votes
- 0 copies
- 5 views
- Added September 6, 2026
Works with
Security analysis
80/100- Exfiltrates credentials via HTTP — exact pattern from Snyk ToxicSkills study
- Installs packages at runtime which could introduce malicious dependencies
Pro scans all 9 files and shows the line behind each finding
npx -y skills add big-emotion/agent-atelier --skill reverse-ui --agent claude-codeAre you the author of Reverse Ui?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/big-emotion-reverse-ui)---
name: reverse-ui
description: Reverse-engineer any web application into a complete pixel-perfect reproduction kit. Extracts design tokens, DOM structure, text content, assets, and responsive screenshots across multiple pages. Use when the user asks to "reverse-engineer a UI", "extract a design system", "analyze the design of a website", or wants to reproduce a site pixel-perfect.
metadata:
author: Big Emotion
version: "3.0.0"
argument-hint: <url> [/page1,/page2,...] [--kit <path>]
allowed-tools: Bash(agent-browser:*), Bash(npx agent-browser:*), Bash(cat:*), Bash(wc:*), Bash(mkdir:*), Bash(ls:*), Bash(curl:*), Write, Read, Grep, Glob, mcp__claude-in-chrome__gif_creator, mcp__claude-in-chrome__javascript_tool, mcp__claude-in-chrome__screenshot_mcp, mcp__claude-in-chrome__tabs_context_mcp, mcp__claude-in-chrome__tabs_create_mcp
---
# Reverse-Engineer UI — Pixel-Perfect Reproduction Kit
Extract everything needed to reproduce a web application pixel-perfect: design tokens, DOM structure per page, text content, asset inventory, and responsive screenshots.
**Input:** `$ARGUMENTS` = `<base-url> [/path1,/path2,...] [--kit <path>]`
- First token: base URL (required). If empty, ask for one.
- Second token: comma-separated route paths (optional). Defaults to the path of the base URL (or `/` if none).
- `--kit` flag (optional): custom Design Kit output path.
- Omitted → generates kit at `./design-kit-{domain}/` (default)
- `--kit ./my-folder` → generates kit at the specified path
- **Always generated**: Design Kit (Phase 9) and Component Kit (Phase 10) — no way to skip either.
- **Always published**: Publishes to `./specs/assets/` — automatically creates `specs/` directory if it doesn't exist.
- Examples:
- `/reverse-ui https://example.com` → extracts `/` + generates kits + publishes to specs/assets (creates specs/ if needed)
- `/reverse-ui https://example.com /,/pricing,/about` → extracts 3 pages + generates kits + publishes
- `/reverse-ui http://localhost:3000 /,/ideation` → extracts Home + Ideation + publishes
- `/reverse-ui http://localhost:3000 / --kit ./my-design` → kit output to `./my-design/` + publishes
## Prerequisites
- `agent-browser` CLI must be installed (`npm i -g agent-browser`)
- For authenticated pages, pre-authenticate:
```bash
agent-browser --session-name myapp open https://app.example.com/login
# ... login manually ...
```
## Output Structure
Two directories are generated: a temporary raw extraction (for reference) and a permanent, unified `specs/assets/` directory:
```
reverse-ui-{domain}/ # Temporary raw extraction — JSON + screenshots (AI reference, can be .gitignored)
manifest.json # Index tying everything together
design-tokens.json # Global: colors, typography, spacing, shadows, animations, breakpoints, components, layout
assets.json # Global: images, SVGs, fonts, favicons, background images, logos
images/ # Downloaded image assets
photo-1580273916550.jpg
...
pages/
{page-name}/ # One directory per page ("/" → "home", "/pricing" → "pricing")
structure.json # Semantic DOM tree + text content map (desktop 1440px)
structure-768.json # Semantic DOM tree at tablet breakpoint
structure-375.json # Semantic DOM tree at mobile breakpoint
svgs.json # Full SVG outerHTML for every inline icon/illustration
interactions.json # Animation libraries, scroll triggers, data attributes
animations.gif # GIF recording of page scroll + hover interactions
screenshot-1440.png # Desktop viewport
screenshot-1440-full.png # Desktop full page
annotated-1440.png # Desktop with labeled interactive elements
screenshot-768.png # Tablet viewport
screenshot-375.png # Mobile viewport
specs/assets/ # Canonical, unified destination — design system + components + raw extraction (Phase 9.5 published here directly)
manifest.json # Unified index (designSystem, components, layouts, tokens, pages sections)
design-system/ # Design tokens — CSS + JSON (Phase 9 output, versionable)
tokens.css # CSS custom properties (ready for @theme or :root)
typography.css # @import font URLs + @font-face + font-family mappings
animations.css # @keyframes definitions + animation utility classes
shadows.css # Box-shadow and drop-shadow token definitions
colors.json # Semantic color palette (name → hex/rgba, grouped by role)
breakpoints.json # Named breakpoints with Tailwind prefix mapping
z-index.json # Z-index scale with layer names
icons/ # Individual SVG files (deduplicated across pages)
home.svg
trends.svg
...
components/ # Component blueprints + co-located docs (Phase 10 output, versionable)
sidebar/
spec.json # Component specification
README.md # Component documentation
module-card/
spec.json
README.md
header/
spec.json
README.md
...
layout/ # Page composition blueprints (Phase 10 output)
home.json # Which components, what grid, what order
ideation.json
...
tokens/ # Semantic token mappings (Phase 10 output)
semantic-tokens.json # Mapped: "sidebar-bg" → rgba(2,4,16,0.8)
pages/ # Raw extraction data (Phase 5 output, organized by page)
home/
structure.json
structure-768.json
structure-375.json
svgs.json
interactions.json
canvas.json
screenshot-1440.png
screenshot-1440-full.png
annotated-1440.png
screenshot-768.png
screenshot-375.png
ideation/
(same set)
images/ # Downloaded asset images
photo-*.jpg
design-tokens.json # Raw browser extraction (full token data with enrichment metadata)
assets.json # Images/SVGs inventory from raw extraction
```
The `reverse-ui-{domain}/` folder can be `.gitignore`d since it's regenerable. The `specs/assets/` folder is the canonical, versionable destination — design tokens, components, and raw extraction data all live here for unified access.
## Workflow
### Phase 0: Parse Input
1. Parse `$ARGUMENTS`: split into `baseUrl` (first token) and `pages[]` (second token, comma-separated). If no pages given, extract the path from baseUrl (default to `/`).
2. Parse `--kit` flag (for custom output path only):
- `--kit <path>` → set `kitPath = <path>`
- Not present → set `kitPath = ./design-kit-{domain}/` (default)
3. Set mandatory flags (no control):
- `kitEnabled = true` (always run Phase 9)
- `componentsEnabled = true` (always run Phase 10)
4. Set `publishPath` (always publish):
- If `specs/` directory exists in cwd → set `publishPath = ./specs/assets/`
- If `specs/` does NOT exist → create it, then set `publishPath = ./specs/assets/`
5. Compute the origin from baseUrl (protocol + host).
6. Sanitize page paths into directory names: `/` → `home`, `/pricing` → `pricing`, `/docs/getting-started` → `docs-getting-started`.
7. Create the output directory structure:
```bash
mkdir -p ./reverse-ui-{domain}/pages/{page1}
mkdir -p ./reverse-ui-{domain}/pages/{page2}
# ... for each page
# Always create design-system directories (Phase 9 output):
mkdir -p ./specs/assets/design-system/icons
# Always create component directories (Phase 10 output):
mkdir -p ./specs/assets/components
mkdir -p ./specs/assets/layout
mkdir -p ./specs/assets/tokens
# Always create specs structure:
mkdir -p ./specs/assets/pages
mkdir -p ./specs/assets/images
mkdir -p ./specs/assets/raw
```
### Phase 1: Browser Init
```bash
agent-browser set viewport 1440 900
agent-browser open <origin + pages[0]> && agent-browser wait --load networkidle
```
### Phase 2: Iframe Detection
Check if the real application lives inside an iframe (common with preview modes, embedded apps).
```bash
agent-browser eval --stdin <<'EVALEOF'
JSON.stringify({
iframes: Array.from(document.querySelectorAll('iframe')).map(f => ({
src: f.src, id: f.id, name: f.name,
width: f.offsetWidth, height: f.offsetHeight,
ratio: Math.round((f.offsetWidth * f.offsetHeight) / (window.innerWidth * window.innerHeight) * 100)
})),
bodyChildCount: document.body.children.length
})
EVALEOF
```
**Decision logic:**
- If an iframe has `ratio > 80` (occupies >80% of viewport), the app is inside that iframe
- Take a snapshot (`agent-browser snapshot -i`) to get the iframe ref (e.g., `@e2`)
- Enter the iframe: `agent-browser frame @eN`
- All subsequent commands will target the iframe content
- If no dominant iframe exists, proceed on the main page
### Phase 3: Global Design Token Extraction (once, on first page)
Scroll to trigger lazy-loaded content:
```bash
agent-browser scroll down 2000 && agent-browser wait 1000
agent-browser scroll down 2000 && agent-browser wait 1000
agent-browser scroll down 2000 && agent-browser wait 1000
agent-browser scroll down -6000 && agent-browser wait 500
```
Extract design tokens:
```bash
agent-browser eval --stdin < ${CLAUDE_PLUGIN_ROOT}/skills/reverse-ui/scripts/extract-design-system.js
```
Parse the JSON output and save as `./reverse-ui-{domain}/design-tokens.json`.
### Phase 4: Per-Page Extraction Loop
For each page in `pages[]`:
#### 4a. Navigate (skip for first page — already there)
**IMPORTANT — SPA Navigation:**
SPAs (React, Vue, Svelte, SvelteKit) use client-side routing. Navigating via `agent-browser open <url>` causes a full page reload that may redirect to `/` or lose SPA state. Instead:
1. First, take a snapshot to identify navigation elements: `agent-browser snapshot -i`
2. Find the sidebar/nav button or link matching the target page name
3. Click it: `agent-browser click @eN` (using the ref from the snapshot)
4. Wait for the content to update: `agent-browser wait 2000`
5. Verify navigation landed correctly: check the current URL or page title matches the expected target. If navigation failed (404, redirect to login, stuck on previous page), log a warning and skip this page — do not let a failed navigation corrupt remaining pages.
**Fallback for non-SPA sites** (traditional multi-page apps):
```bash
agent-browser open <origin + page> && agent-browser wait --load networkidle
```
**How to decide:** If the first page load revealed a sidebar/nav with links to other pages (buttons, anchors), it's an SPA — use in-page clicks. If the site has no persistent navigation shell, use direct URL navigation.
#### 4b. Scroll to Trigger Lazy Content
```bash
agent-browser scroll down 2000 && agent-browser wait 1000
agent-browser scroll down 2000 && agent-browser wait 1000
agent-browser scroll down 2000 && agent-browser wait 1000
agent-browser scroll down -6000 && agent-browser wait 500
```
#### 4c. Extract Page Structure (desktop)
```bash
agent-browser eval --stdin < ${CLAUDE_PLUGIN_ROOT}/skills/reverse-ui/scripts/extract-page-structure.js
```
Parse the JSON output:
- If `{ chunked: true, totalChunks: N }`: retrieve chunks one by one:
```bash
agent-browser eval "window.__REVERSE_UI_CHUNKS[0]"
agent-browser eval "window.__REVERSE_UI_CHUNKS[1]"
# ... up to totalChunks - 1
```
The first chunk contains `page` metadata + `textContent`. Subsequent chunks are individual sections. Reassemble into the full structure.json.
- If not chunked: use the JSON directly.
Save as `./reverse-ui-{domain}/pages/{name}/structure.json`.
#### 4d. Extract SVGs (full markup)
```bash
agent-browser eval --stdin < ${CLAUDE_PLUGIN_ROOT}/skills/reverse-ui/scripts/extract-svgs.js
```
Save as `./reverse-ui-{domain}/pages/{name}/svgs.json`. This contains the complete `outerHTML` for every unique inline SVG — enough to reproduce icons pixel-perfect.
#### 4e. Extract Canvas/WebGL (if present)
If the page has `<canvas>` elements (detected in the structure.json), extract WebGL context info, shader sources, and related scripts:
```bash
agent-browser eval --stdin < ${CLAUDE_PLUGIN_ROOT}/skills/reverse-ui/scripts/extract-canvas.js
```
If WebGL canvases are detected (check `summary.webglCanvases > 0` in the output), also extract shader source code from JS bundles:
```bash
agent-browser eval --stdin < ${CLAUDE_PLUGIN_ROOT}/skills/reverse-ui/scripts/extract-shaders.js
```
Merge both outputs into a single `./reverse-ui-{domain}/pages/{name}/canvas.json`. The combined file will contain:
- Canvas metadata (dimensions, context type, positioning)
- Detected WebGL libraries (OGL, Three.js, etc.)
- **Full GLSL shader source code** (vertex + fragment shaders extracted from JS bundles)
- Uniform declarations and default values
- A static frame snapshot (base64 PNG)
This is enough for the implementation agent to reconstruct the WebGL component using the detected library + extracted shaders.
**Limitations:** Cross-origin scripts cannot be fetched (CORS). Heavily minified production builds may obscure shader strings. WASM-based renderers (Unity, Unreal) store shaders outside JS bundles. Dev builds work best for extraction.
#### 4f. Extract Interactions (animation libraries, scroll triggers)
```bash
agent-browser eval --stdin < ${CLAUDE_PLUGIN_ROOT}/skills/reverse-ui/scripts/extract-interactions.js
```
Parse the JSON output and save as `./reverse-ui-{domain}/pages/{name}/interactions.json`. This detects:
- Animation library globals (GSAP, AOS, ScrollReveal, Framer Motion, anime.js, etc.)
- Animation data attributes (`data-aos`, `data-scroll`, `data-parallax`, etc.)
- Scroll-triggered CSS classes (`aos-animate`, `is-inview`, `animate__animated`, etc.)
- Native CSS scroll timelines (`animation-timeline`, `scroll-timeline-name`, `view-timeline-name`)
#### 4g. Extract Assets (accumulates across pages)
```bash
agent-browser eval --stdin < ${CLAUDE_PLUGIN_ROOT}/skills/reverse-ui/scripts/extract-assets.js
```
This accumulates on `window.__REVERSE_UI_ASSETS`. Don't save yet — it will be finalized after all pages.
#### 4h. Responsive Screenshots + Structure
```bash
# Desktop viewport (already at 1440×900)
agent-browser screenshot ./reverse-ui-{domain}/pages/{name}/screenshot-1440.png
agent-browser screenshot --full ./reverse-ui-{domain}/pages/{name}/screenshot-1440-full.png
agent-browser screenshot --annotate ./reverse-ui-{domain}/pages/{name}/annotated-1440.png
# Tablet
agent-browser set viewport 768 1024
agent-browser wait 1000
agent-browser screenshot ./reverse-ui-{domain}/pages/{name}/screenshot-768.png
agent-browser eval --stdin < ${CLAUDE_PLUGIN_ROOT}/skills/reverse-ui/scripts/extract-page-structure.js
```
Save tablet structure as `./reverse-ui-{domain}/pages/{name}/structure-768.json`.
```bash
# Mobile
agent-browser set viewport 375 812
agent-browser wait 1000
agent-browser screenshot ./reverse-ui-{domain}/pages/{name}/screenshot-375.png
agent-browser eval --stdin < ${CLAUDE_PLUGIN_ROOT}/skills/reverse-ui/scripts/extract-page-structure.js
```
Save mobile structure as `./reverse-ui-{domain}/pages/{name}/structure-375.json`.
```bash
# Reset to desktop for next page
agent-browser set viewport 1440 900
```
#### 4i. Animation GIF Capture
Record a GIF of the page's animations as a visual reference artifact for AI consumption. This runs after all JSON extraction is complete for the page.
**Workflow:**
1. Use `mcp__claude-in-chrome__gif_creator` to start recording (action: `start_recording`)
2. Take an initial screenshot (captures starting state as first frame)
3. **Slow scroll** — scroll down the full page in 3-4 increments with pauses between each (`agent-browser scroll down 1500 && agent-browser wait 2000`). This triggers scroll-activated animations, parallax effects, and fade-ins.
4. **Scroll back to top** — `agent-browser scroll down -6000 && agent-browser wait 1000`
5. **Hover key interactive elements** — find buttons, links, cards with transitions. Use elements from `interactions.json` or heuristic selectors: `button`, `a`, `[role="button"]`, elements with non-default `transition`. Hover 3-5 elements max via `agent-browser hover @eN`.
6. Take a final screenshot (captures end state as last frame)
7. Stop recording
8. Export GIF with options: `{ showClickIndicators: false, showActionLabels: false, showProgressBar: false, showWatermark: false, quality: 8 }` — clean recording, no overlays
9. Save to `./reverse-ui-{domain}/pages/{name}/animations.gif`
**Notes:**
- The GIF is a reference for AI consumption, not a demo video — clean frames are better for visual analysis
- Adds ~15-30s per page
- Output size: ~2-5MB per page (acceptable since screenshots already add ~1-3MB per page)
- If GIF recording fails (e.g., `mcp__claude-in-chrome` not available), skip gracefully and note in the summary
### Phase 5: Finalize Assets
After all pages have been visited, retrieve the accumulated assets:
```bash
agent-browser eval "JSON.stringify(window.__REVERSE_UI_ASSETS)"
```
Parse and save as `./reverse-ui-{domain}/assets.json`.
### Phase 5b: Download Images
Download all image assets to a local `images/` directory:
```bash
mkdir -p ./reverse-ui-{domain}/images
```
For each image in `assets.json` with an external URL:
```bash
curl -sL "<image-url>" -o ./reverse-ui-{domain}/images/{filename}
```
Generate the filename from the URL (last path segment, or a sanitized version). Update `assets.json` entries with a `localPath` field pointing to the downloaded file.
**Skip** images from placeholder services (picsum.photos, placeholder.com, via.placeholder.com) — note them as placeholders instead.
**Skip** data: URIs (already embedded).
### Phase 5c: Source-Code Enrichment
**Gate check:** Only run when `baseUrl` contains `localhost`, `127.0.0.1`, or `0.0.0.0`. Skip for remote URLs. The project root is the current working directory.
If no source files are found (no `*.tsx`, `*.jsx`, `*.vue`, `*.svelte`, `*.css`, `*.html` outside `node_modules`), log a warning in Phase 8 and skip.
**Provenance:** Every enriched entry gets a `_source` field: `"browser"` (existing data), `"source-code"` (from local files), or `"merged"` (combined from both).
#### Sub-phase A: CSS Animations & Transitions
Fill the gap left by CORS-blocked stylesheets and single-snapshot timing.
1. Glob `**/*.css`, `**/*.scss`, `**/*.html`, `**/*.svelte`, `**/*.vue`, `**/*.astro` (exclude `node_modules`, `dist`, `build`, `.next`, `.svelte-kit`)
2. Grep `@keyframes\s+\w+` — read each matched file, extract the full keyframe block (name + frames)
3. Grep `transition:` and `animation:` in component files (`*.tsx`, `*.jsx`, `*.vue`, `*.svelte`) — extract property, duration, easing
4. Check `tailwind.config.*` for `theme.extend.keyframes` and `theme.extend.animation`
5. Check `index.html` `<style>` blocks for inline `@keyframes`
**Merge into `design-tokens.json` → `animations.keyframes[]`:**
- Append source-code keyframes not already present (match by name)
- Mark each with `_source: "source-code"` and `sourceFile: "<relative path>"`
- If a keyframe with the same name exists in browser data, keep the source-code version (unminified) and mark `_source: "merged"`
#### Sub-phase B: Typography / Font Scale
Fill the gap left by CORS-blocked Google Fonts / Fontshare CSS.
1. Parse `<link>` tags in `index.html` for Google Fonts / Fontshare URLs — extract families and weights from URL query parameters (e.g., `family=Montserrat:wght@300;400;500;600;700` → family `"Montserrat"`, weights `[300,400,500,600,700]`)
2. Grep `font-family` declarations in `*.css`, `*.html` `<style>`, component files — capture the selector → font-family mapping
3. Check `tailwind.config.*` for `theme.extend.fontFamily`
4. Grep `@font-face` in local CSS files (self-hosted fonts)
**Merge into `design-tokens.json` → `typography`:**
- Populate `fontFaces[]` with parsed CDN font data (family, weights, source URL, display)
- Add new `fontFamilyMap` object mapping selectors to font stacks (e.g., `{ "body": "Noto Sans, sans-serif", "h1,h2,h3": "Montserrat, sans-serif" }`)
- Mark `_source: "source-code"`
#### Sub-phase C: Breakpoints / Media Queries
Fill the gap left by hardcoded viewport widths and CORS-blocked stylesheets.
1. Check `tailwind.config.*` for `theme.screens` (canonical breakpoint definitions)
2. Grep `@media` in `*.css`, `*.scss`, `*.html` `<style>` blocks — extract width values and rule content
3. If Tailwind CDN detected (no config file, `cdn.tailwindcss.com` in `index.html`): check for inline `tailwind.config = { ... }` in a `<script>` tag, else use Tailwind defaults (`sm: 640px, md: 768px, lg: 1024px, xl: 1280px, 2xl: 1536px`)
**Merge into `design-tokens.json` → `breakpoints`:**
- Add new `namedBreakpoints` object: `{ "sm": "640px", "md": "768px", ... }`
- Extend existing `widths[]` with any new values from source
- Mark `_source: "source-code"`
#### Sub-phase D: WebGL/3D Component Wiring
Fill the gap left by minified bundle analysis — extract clean component source.
**Skip** if no `canvas.json` exists for any page.
1. Grep for imports: `from 'ogl'`, `from 'three'`, `from '@react-three'`, `from 'pixi'`
2. Read each matched component file (cap: 5 files, 500 lines each)
3. Extract: component name, props with defaults, renderer config, geometry type, uniform declarations with initial values, event handlers, animation loop presence
**Merge into each page's `canvas.json`:**
- Add new `sourceComponents[]` array with structured component data:
```json
{
"filePath": "components/Threads.tsx",
"componentName": "Threads",
"library": "ogl",
"props": { "color": { "type": "array", "default": [0.2, 0.3, 0.5] } },
"uniforms": { "iTime": { "type": "float", "initial": 0 } },
"eventHandlers": ["resize", "mousemove"],
"animationLoop": true,
"_source": "source-code"
}
```
- Keep existing `canvases[]`, `shaders`, `relatedScripts` untouched
#### Sub-phase E: CSS Custom Properties (Clean Tokens)
Separate intentional design tokens from Tailwind internal variables.
1. Grep `--` (custom property definitions) in `*.css`, `*.html` `<style>` — filter OUT any `--tw-*` properties
2. Check `tailwind.config.*` for `theme.extend.colors`, `theme.extend.spacing`, etc.
3. Look for dedicated token files: `tokens.css`, `variables.css`, `theme.css`, `design-tokens.*`
4. Parse `:root { }` blocks in `index.html` for intentional CSS custom properties
**Merge into `design-tokens.json` → `colors`:**
- Add new `intentionalTokens[]` array (separate from existing `cssCustomProperties`)
- Each entry: `{ "name": "--primary", "value": "#1a2b3c", "scope": ":root", "_source": "source-code" }`
#### Finalize Enrichment
After all sub-phases complete, add enrichment metadata to `design-tokens.json`:
```json
{
"meta": {
"sourceCodeEnrichment": {
"enabled": true,
"projectRoot": "<cwd>",
"filesAnalyzed": 12,
"enrichedCategories": ["animations", "typography", "breakpoints", "webgl", "cssCustomProperties"]
}
}
}
```
Write back all modified JSON files (`design-tokens.json`, per-page `canvas.json`).
### Phase 6: Write Manifest (Unified Format for `reverse-ui-{domain}/` and `specs/assets/`)
Build `manifest.json` programmatically and save to **both** locations. This unified format is ready to be published directly.
**Note:** Phase 9 and Phase 10 will add `designSystem`, `components`, `layouts`, `tokens` sections to the manifest after they run. The manifest starts with the raw extraction structure below, then gets enriched.
```json
{
"meta": {
"baseUrl": "<base-url>",
"extractedAt": "<ISO timestamp>",
"pages": ["/", "/ideation"],
"sourceCodeEnrichment": true
},
"designTokens": "design-tokens.json",
"assets": "assets.json",
"designSystem": {
"directory": "design-system/",
"tokens": "design-system/tokens.css",
"typography": "design-system/typography.css",
"animations": "design-system/animations.css",
"shadows": "design-system/shadows.css",
"colors": "design-system/colors.json",
"breakpoints": "design-system/breakpoints.json",
"zIndex": "design-system/z-index.json"
},
"components": [
{ "name": "sidebar", "spec": "components/sidebar/spec.json", "doc": "components/sidebar/README.md" },
{ "name": "module-card", "spec": "components/module-card/spec.json", "doc": "components/module-card/README.md" }
],
"layouts": [
{ "page": "/", "file": "layout/home.json" },
{ "page": "/ideation", "file": "layout/ideation.json" }
],
"tokens": "tokens/semantic-tokens.json",
"pages": {
"/": {
"directory": "pages/home",
"structure": "pages/home/structure.json",
"structure-tablet": "pages/home/structure-768.json",
"structure-mobile": "pages/home/structure-375.json",
"interactions": "pages/home/interactions.json",
"canvas": "pages/home/canvas.json",
"svgs": "pages/home/svgs.json",
"screenshots": {
"desktop": "pages/home/screenshot-1440.png",
"desktopFull": "pages/home/screenshot-1440-full.png",
"annotated": "pages/home/annotated-1440.png",
"tablet": "pages/home/screenshot-768.png",
"mobile": "pages/home/screenshot-375.png"
}
}
}
}
```
### Phase 7: Cleanup
```bash
agent-browser close
```
### Phase 8: Summary
Display a summary:
- Pages extracted (count + names)
- Design tokens: CSS custom properties, color palette size, font families, breakpoints
- Per page: section count, headings, buttons, links, form inputs
- Animations: detected libraries (name + version), keyframe count, animated element count, GIF capture status
- Assets: images, SVGs, fonts, logos found
- Screenshots + GIFs: total count
- Source-code enrichment: categories enriched, files analyzed (or "skipped — remote URL" / "skipped — no source files found")
- Component kit (if generated): components identified, pages laid out, docs generated
- Publish (if enabled): files published to `{publishPath}`, file count
- Warnings: inaccessible stylesheets, truncated categories, chunked pages
### Phase 9: Design Kit Generation
**Skip** this phase if `kitPath` is `null` (`--kit false`).
Generate versionable, directly-importable design artifacts from the raw extraction. All files are derived from `design-tokens.json`, per-page `svgs.json`, and `assets.json`.
#### 9a. `specs/assets/design-system/tokens.css` — CSS Custom Properties
Read `design-tokens.json`. Generate a CSS file with all design tokens as custom properties, organized by category. Filter OUT `--tw-*` internal variables — only include intentional tokens and semantic mappings.
Write to: `./specs/assets/design-system/tokens.css`
```css
/* Design System — generated from reverse-ui extraction */
/* Source: reverse-ui-{domain}/design-tokens.json */
:root {
/* === Colors === */
--color-navy-950: #020410;
--color-slate-50: #f8fafc;
/* ... mapped from colors.backgroundColors + textColors → hex values ... */
/* === Shadows === */
--shadow-sidebar: rgba(0,0,0,0.5) 10px 0px 30px -10px;
--shadow-card: rgba(0,0,0,0.2) 0px 0px 15px 0px;
/* ... from shadows.boxShadows ... */
/* === Z-Index === */
--z-content: 0;
--z-elevated: 10;
--z-header: 40;
--z-sidebar: 50;
/* === Spacing (notable values) === */
/* ... from spacing.values, only non-standard ones ... */
}
```
**Mapping rules:**
- `colors.backgroundColors` + `colors.textColors` + `colors.borderColors` → deduplicate, convert `rgb(r,g,b)` to hex, assign semantic names where possible (match against Tailwind color palette names)
- `shadows.boxShadows` → name by usage context from component data
- `zIndex.values` → assign layer names (content, elevated, header, sidebar) based on component usage
- `colors.gradients` → include as `--gradient-*` custom properties
#### 9b. `specs/assets/design-system/typography.css` — Font Imports & Scale
Write to: `./specs/assets/design-system/typography.css`
```css
/* Typography — font imports and scale */
@import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700;800;900&family=Noto+Sans:wght@300;400;500;600;700&display=swap");
@import url("https://api.fontshare.com/v2/css?f[]=satoshi@900,700,600,500,400,300&display=swap");
:root {
/* Font families */
--font-heading: 'Montserrat', sans-serif;
--font-body: 'Noto Sans', sans-serif;
--font-ui: 'Satoshi', sans-serif;
/* Font size scale */
--text-4xs: 8px;
--text-3xs: 9px;
/* ... from typography.fontSizeScale ... */
/* Font weights */
--font-light: 300;
--font-normal: 400;
/* ... from typography.fontWeights ... */
/* Line heights */
/* ... from typography.lineHeights ... */
/* Letter spacings */
/* ... from typography.letterSpacings ... */
}
```
**Source:** `typography.fontFaces` for `@import` URLs, `typography.fontFamilyMap` for family assignments, `typography.fontSizeScale` / `fontWeights` / `lineHeights` / `letterSpacings` for scale values.
#### 9c. `specs/assets/design-system/animations.css` — Keyframes & Utilities
Write to: `./specs/assets/design-system/animations.css`
```css
/* Animations — keyframes and utility class definitions */
@keyframes fade-in {
0% { opacity: 0; transform: translateY(10px); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes slide-up {
0% { opacity: 0; transform: translateY(20px); }
100% { opacity: 1; transform: translateY(0); }
}
/* ... all keyframes from animations.keyframes + sourceCodeKeyframes ... */
/* Animation utilities */
.animate-fade-in { animation: fade-in 0.6s ease-out forwards; }
.animate-slide-up { animation: slide-up 0.4s ease-out forwards; }
/* ... from animations.sourceCodeAnimations ... */
/* Transition utilities */
/* ... from animations.sourceCodeTransitions — document durations/counts as comments ... */
```
**Source:** `animations.keyframes` for frame definitions (prefer `sourceCodeKeyframes` — unminified), `animations.sourceCodeAnimations` for utility class definitions with durations/easing.
#### 9d. `specs/assets/design-system/shadows.css` — Shadow Tokens
Write to: `./specs/assets/design-system/shadows.css`
```css
/* Shadows — box-shadow and drop-shadow tokens */
:root {
--shadow-sidebar: rgba(0,0,0,0.5) 10px 0px 30px -10px;
--shadow-card: rgba(0,0,0,0.2) 0px 0px 15px 0px;
--shadow-md: rgba(0,0,0,0.1) 0px 10px 15px -3px, rgba(0,0,0,0.1) 0px 4px 6px -4px;
--shadow-sm: rgba(0,0,0,0.05) 0px 1px 2px 0px;
--shadow-glass: rgba(0,0,0,0.05) 0px 20px 40px -15px, rgba(0,0,0,0.02) 0px 0px 15px -5px;
}
```
**Source:** `shadows.boxShadows` — each shadow gets a semantic name derived from the component where it's most frequently used.
#### 9e. `specs/assets/design-system/colors.json` — Semantic Color Palette
Write to: `./specs/assets/design-system/colors.json`
```json
{
"palette": {
"blue-400": { "hex": "#60a5fa", "rgb": "rgb(96,165,250)", "role": "accent" },
"slate-800": { "hex": "#1e293b", "rgb": "rgb(30,41,59)", "role": "text-primary" }
},
"backgrounds": [ ... ],
"borders": [ ... ],
"gradients": [ ... ]
}
```
**Mapping:** Convert all `rgb()`/`rgba()` values from `colors.backgroundColors`, `textColors`, `borderColors` to hex. Group by role (background, text, border, gradient). Match against Tailwind palette names where possible.
#### 9f. `specs/assets/design-system/breakpoints.json` — Named Breakpoints
Write to: `./specs/assets/design-system/breakpoints.json`
```json
{
"sm": { "width": "640px", "tailwind": "sm:" },
"md": { "width": "768px", "tailwind": "md:" },
"lg": { "width": "1024px", "tailwind": "lg:" },
"xl": { "width": "1280px", "tailwind": "xl:" },
"2xl": { "width": "1536px", "tailwind": "2xl:" }
}
```
**Source:** `breakpoints.namedBreakpoints`.
#### 9g. `specs/assets/design-system/z-index.json` — Z-Index Scale
Write to: `./specs/assets/design-system/z-index.json`
```json
{
"layers": [
{ "name": "content", "value": 0, "usage": "Default stacking context" },
{ "name": "elevated", "value": 10, "usage": "Cards, overlays within content" },
{ "name": "header", "value": 40, "usage": "Sticky header bar" },
{ "name": "sidebar", "value": 50, "usage": "Navigation sidebar" }
],
"max": 50
}
```
**Source:** `zIndex.values`. Layer names are inferred from `components` data — match z-index values to the components that use them.
#### 9h. `specs/assets/design-system/icons/` — Individual SVG Files
Write to: `./specs/assets/design-system/icons/`
For each page's `svgs.json`, extract the `outerHTML` of each unique SVG. Deduplicate across pages (same SVG path data = same icon). Write each as an individual `.svg` file.
**Naming convention:**
1. If the SVG's `parentSelector` or surrounding text content gives a clear label (e.g., "Home", "Trends", "Settings"), use that: `home.svg`, `trends.svg`
2. Otherwise, use a descriptive name based on the SVG path shape: `chevron-left.svg`, `search.svg`, `arrow-right.svg`
3. For unidentifiable shapes, use `icon-{n}.svg`
**Deduplication:** Compare SVG `outerHTML` (stripped of class attributes and whitespace) across all pages. Same path data = same file, referenced from multiple pages.
#### 9i. Summary Line
Add the design-system output to the Phase 8 summary:
```
Design system: specs/assets/design-system/ — {n} CSS files, {n} JSON files, {n} SVG icons
```
### Phase 9.5: DELETED
Phase 9.5 (Publish to specs/assets) is no longer needed. Design tokens and components are now written directly to `specs/assets/` in Phases 9 and 10, making publish-as-copy obsolete. The canonical output location is unified.
### Phase 10: Component Kit Generation
**Skip** this phase if `componentsEnabled` is `false` (`--components false`).
Generate structured component blueprints and documentation from the raw extraction. This is an **LLM reasoning phase** — it reads existing JSON outputs and generates the component-kit. No new browser extraction scripts required.
#### 10a. Component Identification
**Input:** Per-page `structure.json` (all viewports), `design-tokens.json`, `canvas.json`, `svgs.json`
**Algorithm:**
1. **Landmark-based grouping** — HTML5 landmarks are component boundaries: `<aside>` = Sidebar, `<header>` = Header, `<nav>` = Navigation, `<main>` = Main Content, `<footer>` = Footer
2. **Repeated-structure detection** — In `structure.json`, look for `_repeated` markers and sibling groups with identical tag+class patterns. Each group = a card/list-item component (e.g., nav items, module cards, creation cards)
3. **Role-based extraction** — Elements with `role="button"`, `role="search"`, `role="dialog"` etc. = interactive components
4. **Canvas-based extraction** — Each entry in `canvas.json` with `sourceComponents[]` = a WebGL component
**Output per component** (`specs/assets/components/{name}/spec.json`):
```json
{
"name": "module-card",
"type": "repeated",
"source": {
"page": "/",
"selector": "div.grid > div",
"structureFile": "pages/home/structure.json"
},
"instances": 6,
"dimensions": {
"desktop": { "width": 322, "height": 105 },
"tablet": { "width": "auto", "height": 105 },
"mobile": { "width": "100%", "height": 106 }
},
"layout": {
"parentGrid": {
"columns": 3,
"rows": 2,
"gap": "12px",
"computed": "322px 322px 322px"
},
"iconPosition": "right",
"contentFlow": "column"
},
"styles": {
"background": "rgba(255, 255, 255, 0.2)",
"border": "1px solid rgba(255, 255, 255, 0.4)",
"borderRadius": "12px",
"backdropFilter": "blur(24px)"
},
"variants": [
{
"name": "active",
"trigger": "enabled module",
"styleOverrides": { "cursor": "pointer" }
},
{
"name": "disabled",
"trigger": "coming-soon module",
"styleOverrides": { "opacity": "0.5", "cursor": "not-allowed" }
}
],
"content": [
{ "label": "Trends", "subtitle": "MARKET INTELLIGENCE", "icon": "trends.svg" },
{ "label": "Ideation", "subtitle": "GENERATE CONCEPTS", "icon": "ideation.svg" },
{ "label": "Production", "subtitle": "SCALE & TRANSLATE", "icon": "production.svg" },
{ "label": "Library", "subtitle": "INSPIRATION & ASSETS", "icon": "library.svg" },
{ "label": "Governance", "subtitle": "COMPLIANCE CHECK", "icon": "governance.svg" },
{ "label": "My Folder", "subtitle": "SAVED PROJECTS", "icon": "folder.svg" }
]
}
```
#### 10b. Page Layout Extraction
**Input:** Per-page `structure.json`, identified components from 10a
**Output per page** (`specs/assets/layout/{page}.json`):
```json
{
"page": "/",
"title": "Home",
"sections": [
{
"name": "hero",
"order": 1,
"heading": { "text": "AcmeStudio", "tag": "h1", "font": "Montserrat 900 + light" },
"subtitle": { "text": "Welcome to your Acme creative assistant...", "style": "italic serif" }
},
{
"name": "search-bar",
"order": 2,
"component": "search-bar"
},
{
"name": "module-grid",
"order": 3,
"component": "module-card",
"grid": { "columns": 3, "rows": 2, "gap": "12px" }
},
{
"name": "latest-creations",
"order": 4,
"component": "latest-creations-card",
"grid": { "columns": 4, "gap": "16px" },
"header": { "title": "LATEST CREATIONS", "action": "VIEW ALL" }
}
],
"background": {
"type": "webgl",
"component": "webgl-threads",
"fallback": "linear-gradient(135deg, rgba(59,130,246,0.1), rgba(168,85,247,0.1))"
}
}
```
#### 10c. Semantic Token Mapping
**Input:** `design-tokens.json`, component JSONs from 10a
**Algorithm:** For each raw token value used in components, assign a semantic name based on the component context where it appears most frequently.
**Output** (`specs/assets/tokens/semantic-tokens.json`):
```json
{
"colors": {
"sidebar-bg": { "value": "rgba(2, 4, 16, 0.8)", "usage": "sidebar background" },
"sidebar-border": { "value": "rgba(255, 255, 255, 0.05)", "usage": "sidebar right border" },
"card-bg": { "value": "rgba(255, 255, 255, 0.2)", "usage": "module card, creation card" },
"card-border": { "value": "rgba(255, 255, 255, 0.4)", "usage": "module card, creation card" },
"nav-active-bg": { "value": "rgba(59, 130, 246, 0.1)", "usage": "active nav item background" },
"accent-blue": { "value": "#60a5fa", "usage": "active states, logo highlight" }
},
"layout": {
"sidebar-width-expanded": { "value": "256px" },
"sidebar-width-collapsed": { "value": "64px" },
"header-height": { "value": "64px" },
"module-grid-columns": { "value": 3 },
"module-grid-gap": { "value": "12px" },
"creations-grid-columns": { "value": 4 },
"creations-grid-gap": { "value": "16px" }
},
"effects": {
"sidebar-blur": { "value": "blur(30px)" },
"card-blur": { "value": "blur(24px)" },
"sidebar-shadow": { "value": "rgba(0,0,0,0.5) 10px 0px 30px -10px" }
}
}
```
#### 10d. Component Documentation
**Input:** Component JSONs, layout JSONs, `canvas.json`, screenshots
**Output per component** (`specs/assets/components/{name}/README.md`):
Each doc file follows a strict template:
```markdown
# {Component Name}
## Structure
{2-4 sentences describing what this component is and its role in the page}
## Layout
{Grid/flex details, responsive behavior across breakpoints}
## States
{Variants: default, active, hover, disabled — what changes between them}
## Content
{Text content, labels, counts — the actual data}
## Animation
{Transitions, WebGL behavior, timing — if applicable}
## Key Tokens
{List of semantic tokens used by this component}
## Source Reference
- Structure: `pages/{page}/structure.json` selector: `{selector}`
- Screenshot: `pages/{page}/screenshot-1440.png`
```
For WebGL components specifically (`docs/webgl-threads.md`):
```markdown
# WebGL Threads Background
## Visual Description
40 animated Perlin-noise lines flowing horizontally across the viewport.
Blue-tinted (RGB 0.2, 0.3, 0.5), semi-transparent, mouse-reactive.
Lines converge on the left and spread/undulate on the right.
## Behavior
- Time-based animation: slow wave at time/10.0 speed
- Mouse X influence: offsets time-based animation
- Mouse Y influence: modulates wave amplitude (±20%)
- Mouse smoothing: 0.05 lerp factor per frame
- Mouseleave: resets to center (0.5, 0.5)
## Shader Constants
- Line count: 40
- Line width: 7.0px
- Line blur: 10.0px
## Performance
- Library: OGL (not Three.js)
- Blend: SRC_ALPHA / ONE_MINUS_SRC_ALPHA
- DPR: window.devicePixelRatio
- Reduced motion: static single frame, no animation loop
## Source Reference
- Shader source: `pages/home/canvas.json` → shaders[1] (vertex), shaders[2] (fragment)
- Component source: `pages/home/canvas.json` → sourceComponents[0] (Threads.tsx)
```
**Critical rule for doc generation:** Every fact in a doc file MUST be traceable to a specific value in the raw JSON. No invented properties. Each doc includes a "Source Reference" section pointing to the exact JSON file + path.
#### 10e. Component Kit Manifest
Write `component-kit-{domain}/manifest.json`:
```json
{
"meta": {
"generatedAt": "<ISO timestamp>",
"sourceExtraction": "reverse-ui-{domain}/",
"pages": ["/", "/ideation"]
},
"components": [
{ "name": "sidebar", "file": "components/sidebar.json", "doc": "docs/sidebar.md" },
{ "name": "header", "file": "components/header.json", "doc": "docs/header.md" },
{ "name": "module-card", "file": "components/module-card.json", "doc": "docs/module-card.md" }
],
"layouts": [
{ "page": "/", "file": "layout/home.json" },
{ "page": "/ideation", "file": "layout/ideation.json" }
],
"tokens": "tokens/semantic-tokens.json"
}
```
#### 10f. Summary Line
Add the component kit output to the Phase 8 summary:
```
Component kit: {kitPath}/ — {n} components, {n} page layouts, {n} docs, {n} semantic tokens
```
---
## Design Token Categories
The `design-tokens.json` contains (see `scripts/extract-design-system.js`):
- `colors` — CSS custom properties, background/text/border colors, gradients. **Source-code enrichment** adds `intentionalTokens[]` — clean custom properties filtered from `--tw-*` noise, with scope and provenance
- `typography` — font faces, families, size scale, weights, line-heights, letter-spacings, detected scale ratio. **Source-code enrichment** populates `fontFaces[]` from Google Fonts/Fontshare URL parsing and adds `fontFamilyMap` mapping selectors to font stacks
- `spacing` — values, detected base unit, gap values
- `borders` — widths, styles, radii
- `shadows` — box-shadows, text-shadows, drop-shadows
- `animations` — keyframes (with `usedBy[]` element linkage), transitions, durations, easing functions. **Source-code enrichment** adds keyframes from `<style>` blocks, CSS files, and Tailwind config that CORS-blocked extraction missed
- `breakpoints` — media queries with rule counts, extracted width values. **Source-code enrichment** adds `namedBreakpoints` from Tailwind config or defaults (e.g., `{ "sm": "640px", "md": "768px" }`)
- `zIndex` — all values, max
- `components` — inferred components with selector, occurrences, DOM structure, key styles
- `layout` — grid patterns, flex patterns, container max-widths
- `icons` — inline SVG count/sizes, icon font families
- `states` — hover/focus/active/disabled rules from stylesheets (with `transition` timing from base rule when available)
## Page Structure Schema
Each `structure.json` contains (see `scripts/extract-page-structure.js`):
- `page` — URL, title, meta description, viewport
- `sections[]` — semantic DOM tree rooted at landmark elements (header, nav, main, section, aside, footer)
- Each node: tag, selector, role, boundingBox, styles (~40 key properties including animation/transition sub-properties), text, attributes (including animation data attrs), asset ref, pseudoElements (`::before`/`::after` with content + styles), children
- Repetitive siblings (5+ same structure) are collapsed: 2 examples + `_repeated` marker with count
- `textContent` — flat maps: headings, paragraphs, buttons, links, labels, inputs (quick access to all copy)
## Interactions Schema
Each `interactions.json` contains (see `scripts/extract-interactions.js`):
- `animationLibraries[]` — detected runtime libraries (GSAP, AOS, ScrollReveal, Framer Motion, anime.js, etc.) with name and version
- `scrollAnimations.dataAttributes[]` — animation data attributes found on elements (`data-aos`, `data-scroll`, `data-parallax`, etc.) with value and count
- `scrollAnimations.scrollClasses[]` — scroll-triggered CSS classes (`aos-animate`, `is-inview`, `animate__animated`, etc.) with count
- `scrollAnimations.cssScrollTimelines[]` — native CSS `animation-timeline`, `scroll-timeline-name`, `view-timeline-name` properties
- `summary` — `hasScrollAnimations`, `primaryLibrary`, `animatedElementCount`
## Limitations
- CORS-restricted external stylesheets cannot be read (logged in `meta.inaccessibleStylesheets`)
- Closed Shadow DOM is not accessible
- Component detection uses class frequency heuristics — may miss CSS-in-JS with hashed class names
- DOM tree is capped at depth 6 per section
- Repetitive sibling groups show only 2 examples
- Element sampling capped at 500 unique tag+class combinations for token extraction
- Pseudo-element extraction capped at 200 per page
- Animation library detection requires globals on `window` — libraries that don't expose globals (e.g., bundled ES modules with no side effects) may be missed
- GIF capture requires `mcp__claude-in-chrome__gif_creator` — skipped gracefully if unavailable
- JavaScript-driven animations (e.g., GSAP timelines, requestAnimationFrame loops) are detected by library presence but their specific timeline/sequence logic is not extracted
- Source-code enrichment (Phase 5c) requires a localhost/127.0.0.1/0.0.0.0 base URL and accessible source files in the working directory. For remote URLs, only browser extraction is available
- Component kit (Phase 10) is LLM-generated from extraction data — component boundaries are heuristic (landmark + repeated-structure detection) and may not match the original component architecture exactly
For detailed edge cases and troubleshooting, see `references/extraction-guide.md`.
Files in this skill
- SKILL.md
- references/extraction-guide.md
- scripts/extract-assets.js
- scripts/extract-canvas.js
- scripts/extract-design-system.js
- scripts/extract-interactions.js
- scripts/extract-page-structure.js
- scripts/extract-shaders.js
- scripts/extract-svgs.js
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.