Three outputs from one site — behave, look, edit
Most tools give you a still. UIForge gives you the behavior first, then the look and the editable code:
| what it is | for | |
|---|---|---|
⭐ Archive (uiforge-archive) |
the complete behavior — the site's own code + every recorded response, replayed offline from a browsable, editable mirror (real files, real filenames — not opaque blobs) | a copy that works: tabs, filters, lists, client-side transitions, motion, scroll |
Freeze (uiforge-freeze) |
a self-contained, pixel-faithful still — real CSS/fonts/assets kept, scripts stripped, time frozen | an exact offline still — and the oracle the rebuild is measured against |
Restore (uiforge-restore · -sourcemap) |
editable source that renders pixel-identical — the real DOM componentized into React with classes kept byte-exact, or the site's real original source recovered when it shipped source maps | editing, developing, shipping with your content — without the fidelity drift |
The archive runs the real JavaScript against cached data, so every interaction behaves exactly as the original — no reconstruction, because it is the behavior. (This is why it beats "Save As": a naive save of a modern app never records the XHR/API data the app fetches, so it opens as a broken shell — the archive records and replays that data.) The freeze renders identical to the live site (it is its CSS); the restore keeps the real DOM + real CSS, so it's pixel-identical by construction — verified: ui.shadcn.com restores to 0.00% pixel difference — and where a site ships source maps it recovers the actual original .tsx.
Restore it into editable code — that renders pixel-identical
Point Restore at the archive and get an editable React project that renders exactly like the original — because it keeps the real DOM, the real class strings (byte-for-byte), and the real compiled CSS. Fidelity is the default, not something chased with an LLM. Every result is proven by an offline pixel-gate against the freeze:
| site | restored as | pixel diff vs the original |
|---|---|---|
| ui.shadcn.com | React, classes kept verbatim | 0.00% — pixel-identical |
| vercel.com | ″ | 1.8% (only the WebGL canvas hero — it needs JS) |
| linear.app | ″ | 5% (a JS-state menu — recovered as real source by Tier A ↓) |
And when a site ships or leaks source maps, the top tier recovers its actual original source — real .tsx with the original names, comments, and types. linear.app → 421 real source files, including the exact MobileMenu.tsx (with its useState open-logic) that a static render can't reproduce. Two tiers, best-first, everything from the archive — offline:
/uiforge:clone ui.shadcn.com --restore
Cloned — and it actually works. Five flagship sites.
Each site below was archived and replayed offline by the plugin (uiforge-archive --explore). The GIFs are the clone running with no network — the interactions are genuine, driven by the site's own code against recorded data.
And each is pixel-faithful as a Freeze — original (left) vs freeze (right):
It clones motion and interaction too — not just a static snapshot
Install
Prerequisites: Claude Code, Node, and
Playwright for the rendering tiers (npm i -D playwright && npx playwright install chromium).
/plugin marketplace add TaewoooPark/UIForge
/plugin install uiforge@uiforge
Or locally: git clone https://github.com/TaewoooPark/UIForge.git && claude --plugin-dir ./UIForge.
Clone a site
The command — one command, three modes
/uiforge:clone <url│file.html> [--archive] [--restore] [--content path.md] [--explore] [--headed] [--profile dir]
| you want… | say | what it does |
|---|---|---|
| a copy that works / behaves | --archive, or "clone it so it actually works" |
⭐ records the real code + data → an offline replay where tabs/filters/lists/transitions work |
| editable source, pixel-identical | --restore (or --react), or "…for my product (product.md)" |
real DOM → React with classes byte-exact + real source recovered from source maps — proven by pixel-gate |
| a pixel-faithful still | (default) / --freeze |
a self-contained freeze |
Or just say it — the agent picks the mode:
Clone vercel.com so it actually works — the tabs and pricing should switch. → Archive
Make a site with the same design as stripe.com, but for my product (product.md). → Restore
Give me an exact offline copy of linear.app's landing page. → Freeze
Behind Cloudflare or a login? add --headed --profile ./prof (a persistent profile logs in once and reuses the session). The Archive mode always runs --explore — it clicks in-page controls and scrolls during capture so the data those interactions fetch is recorded; the more it explores, the more that works offline.
Running the tools directly: Archive —
node tools/uiforge-archive.mjs <url> --out-dir ./archive --explorethennode ./archive/serve.mjs. Restore —uiforge-detect → uiforge-sourcemap → uiforge-restore, proven byuiforge-visualgate. Freeze —uiforge-freeze <url> --inline. See The tools.
The pipeline
Two editable paths — Restore is the current one. The capture → theme → export pipeline shown below is the older re-derive-from-computed-styles rebuild; it still works (and is handy for a from-scratch, fully token-mapped Tailwind refactor), but it can drift on complex sites. The Restore path (
uiforge-detect → uiforge-sourcemap → uiforge-restore, proven byuiforge-visualgate) preserves the real DOM + real compiled CSS, so it's pixel-identical by construction — prefer it for fidelity. Restore Tier B is a static snapshot (look + structure, no JS behavior); for behavior use the Archive. See §B Restore.
reference URL (--headed to clear a Cloudflare / bot wall)
│
├─ uiforge-freeze ──────────► freeze.html the PIXEL-FAITHFUL replica: real CSS/fonts/
│ assets kept, scripts stripped — the ORACLE
│
▼ uiforge-capture extract EVERY element's exact styles, geometry, text, SVGs +
capture.json real @font-face, @keyframes, :hover/:focus, dropdown open-states,
│ (opt-in) canvas video, JS motion, and a --responsive mobile pass
│ uiforge-theme infer roles (bg/fg/accent/…) → a Tailwind v4 @theme
│ uiforge-segment find semantic sections + repeated components (structural hashing)
│ uiforge-tailwindify map every computed style → Tailwind utility classes
▼
│ uiforge-export → a CLEAN, componentized Vite + React + Tailwind project:
▼ components/*.tsx, content.ts, hover CSS, motion, toggle runtime
│ uiforge-assets --assets downloads images/fonts → /public (self-contained)
│ uiforge-diff score the rebuild against freeze.html — PER SECTION (SSIM +
▼ structure + element-presence), offline & deterministic
clone/ (npm install && npm run dev) · content swap: your copy, same components & tokens
What comes across
| captured | how | |
|---|---|---|
| Structure · layout · geometry | ✓ | getBoundingClientRect per element |
| Color · gradient · shadow · border · radius | ✓ | getComputedStyle per element |
| Typography — the real webfont | ✓ | @font-face fetched server-side (past CORS) |
| Text — incl. mixed inline (links in a sentence) | ✓ | ordered pre/text/post per node |
| SVG icons & logos | ✓ | captured whole |
| CSS animations (spinners, entrances) | ✓ | @keyframes fetched server-side |
| Hover / focus / active states | ✓ | :hover rules → .uif-<i>:hover companion CSS |
| Dropdowns / menus / accordions | ✓ | clicked during capture → open-state + click runtime |
| Scroll-reveal states & lazy media | ✓ | full scroll-through before snapshot |
| Canvas / WebGL heroes | ✓ (opt-in) | captureStream() → a looping <video> |
| JS motion (Framer / GSAP) | ~ (opt-in) | sampled over time → approximating @keyframes |
| A pixel-faithful offline replica | ✓ | uiforge-freeze keeps the real CSS/fonts/assets |
| A clean componentized rebuild | ✓ | segment + tailwindify → components/*.tsx, Tailwind classes |
| Self-contained assets (offline) | ✓ (opt-in) | export --assets, or freeze --inline → one data-URI file |
| Responsive (mobile) variants | ✓ (opt-in) | --responsive mobile pass → max-sm: classes |
| Exact JS animation (Framer / Motion) | ✓ | Element.animate() hook → real @keyframes + curve/stagger/fill |
| Scroll-linked animation | ✓ | native ScrollTimeline/ViewTimeline → animation-timeline |
| Deterministic on carousels / rotators | ✓ | time-frozen at the snapshot instant (Playwright clock) |
Real <video> (hero / background) |
✓ | source + poster + loop/muted captured |
| Sites behind Cloudflare / bot walls | ✓ (opt-in) | --headed --profile (persistent cf_clearance + challenge wait) |
| Content behind a login | ✓ (opt-in) | --profile / --storage-state (saved session) |
| A whole site (many pages) | ✓ | uiforge-site crawls → one React-Router project |
Every value in the reconstruction is produced by the tools, not guessed — the
signature by uiforge-theme, the layout and styles by uiforge-reconstruct, the
match verified by uiforge-diff. And the clone is QA'd for accessibility
(uiforge-render-audit): the same look, but it passes the WCAG contrast the original
may not.
How faithful is it, honestly
Measured (deterministic reconstruction, no hand-authoring), full-page pixel overlay of reference vs reconstruction:
| site | nodes | similarity |
|---|---|---|
| a simple static page | 14 | 93% |
| vercel.com | 425 | 95.8% |
| linear.app | 1023 | 92.8% |
| tailwindcss.com | 1122 | 71% |
Clean and medium sites reproduce at 93–96%, and even a dense marketing homepage like
linear.app now lands at 92.8% — nav, color, copy, and the headline in Linear's own
Inter Variable (recovered by re-declaring its @font-face) are effectively identical,
as the hero above shows. The number falls on the longest, most layered pages
(tailwindcss.com, ~11,000px) for one honest reason: the score is a full-page pixel
overlay, so it's dominated by cumulative vertical drift — flow layout can't perfectly
reproduce a section whose height came from absolutely-positioned art, and a few pixels per
section compound over a very long page. That metric actually penalizes a structurally
complete reconstruction versus a broken-but-shorter one, so read it as a floor, not a
ceiling: the visible design matches more closely than the tail number suggests. A running
canvas/WebGL hero (Vercel's spinning triangle) can't be reproduced from computed
styles — but it is now recorded to a looping video (--record-canvas), so it's in the
clone too; fonts and CSS motion, once on the "can't" list, aren't anymore. Reproduce any of this:
node tools/uiforge-capture.mjs <url> then
node tools/uiforge-reconstruct.mjs capture.json then node tools/uiforge-diff.mjs <url> index.html.
Ethics — a redesign scaffold, not a passing-off clone
This makes a design reproduction: structure and styling, with your content and substituted brand assets (logo, photos). Do not deploy it under the original's brand (passing off), do not lift the original's copyrighted images or copy into a shipped product, and never build a credential/login clone. Fonts follow their license.
The tools
Ten command-line tools; every one prints --help. The capture/diff tiers are zero
external-dependency Node; rendering uses Playwright.
Clone pipeline
node tools/uiforge-freeze.mjs <url│file> [--out freeze.html] [--inline] [--headed] [--profile dir] [--shot ref.png]
# the PIXEL-FAITHFUL oracle: inline the real stylesheets (server-side), strip scripts, freeze
# time at the snapshot instant. --inline embeds every asset as data: URIs (one offline file);
# --shot saves the live screenshot at that frozen instant (the aligned proof pair).
node tools/uiforge-capture.mjs <url│file> [--record-canvas] [--sample-motion] [--responsive] [--headed] [--profile dir]
# extract every element's exact computed styles, geometry, text, SVGs, real videos + a token set.
# Recovers real @font-face, @keyframes, :hover/:focus rules + EXACT WAAPI keyframes (curve, stagger,
# scroll-linked), explores dropdowns, freezes time; emits a coverage manifest (found→captured→why).
# --record-canvas <canvas> → looping .webm --sample-motion rAF motion → @keyframes (fallback)
# --responsive mobile → max-sm: + stability --headed/--profile clear a bot wall / a login session
node tools/uiforge-theme.mjs capture.json [--out-css theme.css] [--out-json theme.json]
# infer semantic roles by usage → a Tailwind v4 @theme (bg/fg/muted/surface/border/accent)
node tools/uiforge-segment.mjs capture.json [--out segment.json]
# detect semantic sections + repeated components (structural hashing) — the componentization map
node tools/uiforge-tailwindify.mjs capture.json theme.json [--sample N]
# map computed styles → Tailwind utility classes against the @theme (arbitrary [values] otherwise)
node tools/uiforge-export.mjs capture.json --out-dir ./clone [--assets] [--flat]
# DEFAULT: a componentized React + Tailwind project (components/*.tsx, content.ts, Tailwind classes)
# --assets download images/fonts → /public, rewrite refs (self-contained) --flat the old single-file dump
node tools/uiforge-assets.mjs capture.json --dest ./clone/public
# download every external image/font/bg a capture references (bounded, self-contained)
node tools/uiforge-diff.mjs <ref> <out> [--segments segment.json] [--json]
# localized fidelity: per-section similarity + SSIM + structure (height/node) + element-presence
node tools/uiforge-site.mjs <start-url> --out-dir ./site [--max 6] [--assets] [--headed] [--profile dir]
# clone a WHOLE site: crawl same-origin pages, capture each, stitch into ONE React-Router
# project (a route per page, namespaced components, one shared @theme). The "one page" limit, gone.
node tools/uiforge-archive.mjs <url> --out-dir ./archive [--explore] [--headed] [--profile dir]
# the COMPLETE behavior clone: record the real code + every network response (incl. XHR/API),
# warmed by clicking in-page controls + scrolling → a folder + zero-dep replay server. Open it
# and the site's own JS runs offline against cached data: tabs, filters, lists, transitions WORK.
# node ./archive/serve.mjs → open the printed http://localhost URL
Accessibility & craft QA (so the clone is better than a scrape)
node tools/uiforge-render-audit.mjs <url│file> [--spec sig.json] [--json]
# per-node WCAG contrast, accent surface-area, spacing rhythm, type coherence, layout tells
node tools/uiforge-attention.mjs <url│file> [--overlay out.png]
# predict the gaze order + flag a flat hierarchy
node tools/uiforge-lint.mjs <dir> [--strict]
# the fast source gate: default fonts, AI purple, gradient headlines, hype copy…
How it works
Ask any LLM to "build a site like Stripe" and it approximates from memory — the
result rhymes with Stripe but isn't Stripe. UIForge doesn't approximate. It renders
the real page and measures it: getComputedStyle on every element gives the exact
color, gradient, shadow, border, font, and box; getBoundingClientRect gives the
exact geometry; SVGs are captured whole. uiforge-reconstruct replays that verbatim,
so the baseline is faithful by construction — the styling is the site's own values,
not a guess. uiforge-diff then renders the reconstruction next to the original and
reports, region by region, where it still differs, and the loop closes those gaps.
The result is the opposite of "vibes": a copy you can measure (95.9% on vercel.com),
delivered as an editable React + Tailwind project with your content and the design
system extracted into an @theme.
The design system, extracted
uiforge-theme doesn't just dump colors — it infers roles by how they're used:
the background is the color painting the most area, the foreground the most-used text
color that clears contrast, the accent the most-used saturated color. On linear.app it
recovered bg #08090a, accent #6366f1 (their indigo), and the exact fonts (Inter
Variable + Berkeley Mono) — the real design system, as a Tailwind v4 @theme you own.
Honest limits
Most of what used to be here is now handled (see the grid): exact JS animation, native
scroll-linked motion, determinism on carousels, real videos, webfonts (even CORS-locked, via
--inline), auth, Cloudflare, and whole-site crawl. The honest residuals that remain:
- Behavior lives in the archive, not the freeze/rebuild. JS-mounted heroes (openai's
ChatGPT prompt), JS-driven scroll scrub, physics/particle motion, and any click-swaps-content
interaction are only fully reproduced by
uiforge-archive, which runs the real code. The freeze (scripts stripped) and the still rebuild capture the state, not the running logic. - The archive's own residual: a server-dependent action (a search that hits an API, a
"load more" that fetches) only works if that response was recorded — run
--exploreand interact more to widen coverage; a request never made during capture has nothing to replay. - "Idiomatic" is the last mile (rebuild): it's genuinely componentized and Tailwind-classed,
but mapping every arbitrary
[value]to a design-token utility and naming components semantically is the/cloneagent's polish step, not fully automatic.
Repository layout
UIForge/
├── README.md · README.ko.md · LICENSE
├── docs/ # proof images + reproducible fixtures
├── .claude-plugin/ · .mcp.json # plugin manifest + self-install marketplace + shadcn MCP
├── commands/ # clone · forge · reskin · setup · critique · score
├── skills/ # design-director · design-tokens · motion · content
└── tools/ # the clone pipeline + QA tools
├── uiforge-capture.mjs # extract a reference's full design
├── uiforge-theme.mjs # → a Tailwind v4 @theme (roles inferred)
├── uiforge-reconstruct.mjs # replay the capture into a faithful page
├── uiforge-diff.mjs # visual fidelity gate (similarity % + worst regions)
├── uiforge-export.mjs # → a Vite + React + Tailwind project
├── uiforge-render-audit.mjs # WCAG/craft QA on the clone
├── uiforge-attention.mjs # gaze order + hierarchy
├── uiforge-lint.mjs # the fast source gate
├── uiforge-catalog.mjs · uiforge-source.mjs · … # component catalog (294 items)
└── catalog/ · kits/ # the asset DB + five ready design kits
Attribution & license
Rendering by Playwright; output targets React + Tailwind CSS v4 + Vite. The craft/QA layer draws on Refactoring UI, Practical Typography, Material / Radix / Tailwind tokens, and Anthropic's frontend-design guidance. MIT for the plugin, skills, commands, and tools — not the sites it captures, the assets it downloads, or the fonts it references.
No comments yet
Be the first to share your take.