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).--kitflag (optional): custom Design Kit output path.- Omitted → generates kit at
./design-kit-{domain}/(default) --kit ./my-folder→ generates kit at the specified path
- Omitted → generates kit at
Always generated: Design Kit (Phase 9) and Component Kit (Phase 10) — no way to skip either.
Always published: Publishes to
./specs/assets/— automatically createsspecs/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-browserCLI must be installed (npm i -g agent-browser)- For authenticated pages, pre-authenticate:
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 .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 intobaseUrl(first token) andpages[](second token, comma-separated). If no pages given, extract the path from baseUrl (default to/). - Parse
--kitflag (for custom output path only):--kit <path>→ setkitPath = <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 → setpublishPath = ./specs/assets/ - If
specs/does NOT exist → create it, then setpublishPath = ./specs/assets/
- If
- 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:
- If
{ chunked: true, totalChunks: N }: retrieve chunks one by one:
The first chunk containsagent-browser eval "window.__REVERSE_UI_CHUNKS[0]" agent-browser eval "window.__REVERSE_UI_CHUNKS[1]" # ... up to totalChunks - 1pagemetadata +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)
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_creatorto 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.jsonor heuristic selectors:button,a,[role="button"], elements with non-defaulttransition. Hover 3-5 elements max viaagent-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-chromenot 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(excludenode_modules,dist,build,.next,.svelte-kit) - Grep
@keyframes\s+\w+— read each matched file, extract the full keyframe block (name + frames) - Grep
transition:andanimation:in component files (*.tsx,*.jsx,*.vue,*.svelte) — extract property, duration, easing - Check
tailwind.config.*fortheme.extend.keyframesandtheme.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"andsourceFile: "<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 inindex.htmlfor 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-familydeclarations in*.css,*.html<style>, component files — capture the selector → font-family mapping - Check
tailwind.config.*fortheme.extend.fontFamily - Grep
@font-facein 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
fontFamilyMapobject 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.*fortheme.screens(canonical breakpoint definitions) - Grep
@mediain*.css,*.scss,*.html<style>blocks — extract width values and rule content - If Tailwind CDN detected (no config file,
cdn.tailwindcss.cominindex.html): check for inlinetailwind.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
namedBreakpointsobject:{ "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,relatedScriptsuntouched
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.*fortheme.extend.colors,theme.extend.spacing, etc. - Look for dedicated token files:
tokens.css,variables.css,theme.css,design-tokens.* - Parse
:root { }blocks inindex.htmlfor intentional CSS custom properties
Merge into design-tokens.json → colors:
- Add new
intentionalTokens[]array (separate from existingcssCustomProperties) - 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, convertrgb(r,g,b)to hex, assign semantic names where possible (match against Tailwind color palette names)shadows.boxShadows→ name by usage context from component datazIndex.values→ assign layer names (content, elevated, header, sidebar) based on component usagecolors.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
parentSelectoror 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_repeatedmarkers 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.jsonwithsourceComponents[]= 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_
…(truncated)