| 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
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 .gitignored 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
- Parse
$ARGUMENTS: split into baseUrl (first token) and pages[] (second token, comma-separated). If no pages given, extract the path from baseUrl (default to /).
- Parse
--kit flag (for custom output path only):
--kit <path> → set kitPath = <path>
- Not present → set
kitPath = ./design-kit-{domain}/ (default)
- Set mandatory flags (no control):
kitEnabled = true (always run Phase 9)
componentsEnabled = true (always run Phase 10)
- 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/
- Compute the origin from baseUrl (protocol + host).
- Sanitize page paths into directory names:
/ → home, /pricing → pricing, /docs/getting-started → docs-getting-started.
- Create the output directory structure:
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
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).
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:
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:
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:
- First, take a snapshot to identify navigation elements:
agent-browser snapshot -i
- Find the sidebar/nav button or link matching the target page name
- Click it:
agent-browser click @eN (using the ref from the snapshot)
- Wait for the content to update:
agent-browser wait 2000
- 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):
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
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)
agent-browser eval --stdin < ${CLAUDE_PLUGIN_ROOT}/skills/reverse-ui/scripts/extract-page-structure.js
Parse the JSON output:
Save as ./reverse-ui-{domain}/pages/{name}/structure.json.
4d. Extract SVGs (full markup)
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:
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:
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)
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)
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
# 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.
# 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.
# 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:
- Use
mcp__claude-in-chrome__gif_creator to start recording (action: start_recording)
- Take an initial screenshot (captures starting state as first frame)
- 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.
- Scroll back to top —
agent-browser scroll down -6000 && agent-browser wait 1000
- 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.
- Take a final screenshot (captures end state as last frame)
- Stop recording
- Export GIF with options:
{ showClickIndicators: false, showActionLabels: false, showProgressBar: false, showWatermark: false, quality: 8 } — clean recording, no overlays
- 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:
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:
mkdir -p ./reverse-ui-{domain}/images
For each image in assets.json with an external URL:
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.
- Glob
**/*.css, **/*.scss, **/*.html, **/*.svelte, **/*.vue, **/*.astro (exclude node_modules, dist, build, .next, .svelte-kit)
- Grep
@keyframes\s+\w+ — read each matched file, extract the full keyframe block (name + frames)
- Grep
transition: and animation: in component files (*.tsx, *.jsx, *.vue, *.svelte) — extract property, duration, easing
- Check
tailwind.config.* for theme.extend.keyframes and theme.extend.animation
- 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.
- 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])
- Grep
font-family declarations in *.css, *.html <style>, component files — capture the selector → font-family mapping
- Check
tailwind.config.* for theme.extend.fontFamily
- 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.
- Check
tailwind.config.* for theme.screens (canonical breakpoint definitions)
- Grep
@media in *.css, *.scss, *.html <style> blocks — extract width values and rule content
- 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.
- Grep for imports:
from 'ogl', from 'three', from '@react-three', from 'pixi'
- Read each matched component file (cap: 5 files, 500 lines each)
- 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:
{
"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.
- Grep
-- (custom property definitions) in *.css, *.html <style> — filter OUT any --tw-* properties
- Check
tailwind.config.* for theme.extend.colors, theme.extend.spacing, etc.
- Look for dedicated token files:
tokens.css, variables.css, theme.css, design-tokens.*
- 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:
{
"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.
{
"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
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
/* 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
/* 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
/* 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
/* 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
{
"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
{
"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
{
"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:
- If the SVG's
parentSelector or surrounding text content gives a clear label (e.g., "Home", "Trends", "Settings"), use that: home.svg, trends.svg
- Otherwise, use a descriptive name based on the SVG path shape:
chevron-left.svg, search.svg, arrow-right.svg
- 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:
- Landmark-based grouping — HTML5 landmarks are component boundaries:
<aside> = Sidebar, <header> = Header, <nav> = Navigation, <main> = Main Content, <footer> = Footer
- 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)
- Role-based extraction — Elements with
role="button", role="search", role="dialog" etc. = interactive components
- Canvas-based extraction — Each entry in
canvas.json with sourceComponents[] = a WebGL component
Output per component (specs/assets/components/{name}/spec.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):
{
"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):
{
"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:
# {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):
# 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:
{
"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.