| name | clone-website-vanilla |
| description | Reverse-engineer and clone any public website as a pixel-perfect replica using vanilla HTML/CSS/JS. Triggers on "clone <url>", "<url> をクローン", "rebuild this page", "pixel-perfect copy", "reverse-engineer this site". Provide one or more target URLs as arguments. |
| argument-hint | <url1> [<url2> ...] |
| user-invocable | true |
clone-website-vanilla
You are about to reverse-engineer and rebuild $ARGUMENTS as pixel-perfect clones, using only HTML / CSS / vanilla JavaScript (no frameworks, no bundlers, no transpilers). CDN-loaded Swiper / GSAP are allowed.
This is not a two-phase process (inspect then build). You are a foreman walking the job site — as you inspect each section of the page, you write a detailed specification to a file, then hand that file to a specialist builder agent with everything they need. Extraction and construction happen in parallel, but extraction is meticulous and produces auditable artifacts.
When multiple URLs are passed, clone each one in its own sites/<slug>/ directory and keep extraction artifacts isolated per site (sites/<slug>/docs/).
Scope Defaults
The target is whatever page $ARGUMENTS resolves to — clone exactly what's visible at that URL. Unless the user specifies otherwise:
- Fidelity: Pixel-perfect — exact match of colors, spacing, typography, animations
- In scope: Visual layout & styling, component structure, interactions, responsive behavior, real text/images/videos/SVGs from the live page
- Out of scope: Real backend, auth, analytics, A/B testing, SEO/i18n
- Customization: None — pure emulation. Customize only after the 1:1 clone is complete.
If the user provides extra instructions (lower fidelity, custom copy, etc.), honor those over defaults.
Hard rules (web-kage)
- Output goes into
sites/<slug>/ (kebab-case from the domain or page).
- Start from
templates/starter/ — cp -r templates/starter sites/<slug> — never scaffold a Next.js / React / Vite project.
- All scripts must be plain JS (no JSX, no TS). Libraries load via CDN
<script> / <link> tags only. Recommended: Swiper, GSAP/ScrollTrigger.
- Server logic, only when truly needed, goes in
sites/<slug>/functions/ as Cloudflare Pages Functions (onRequest in vanilla JS).
- A new clone is a new git branch (
feat/clone-<slug>). Open a PR, then run /security-review before merge.
Acceptable use
This skill downloads real assets from the target site for personal learning, layout study, and template-cloning of sites the user owns or has permission to study. Not for phishing, impersonation, passing off another's design as your own, or violating a site's terms of service. The PR description must name the original source and link to it.
Pre-Flight
- Browser automation is required. Prefer Claude Code's built-in Claude in Chrome integration (
claude --chrome or /chrome mid-session — tools appear as mcp__claude-in-chrome__* in ToolSearch). Falls back to Chrome DevTools MCP / Playwright MCP / Browserbase MCP if the user has those instead. If none is available, stop and ask the user to enable one — this skill cannot work without browser automation.
- Parse
$ARGUMENTS as one or more URLs. Normalize and validate each. For invalid URLs, ask the user to correct them. For each valid URL, verify it loads via the browser MCP.
- Verify the starter exists:
ls templates/starter/. If missing, stop and tell the user.
- Create the per-site folders that don't yet exist:
sites/<slug>/
├── index.html (from starter)
├── styles/main.css (from starter)
├── scripts/main.js (from starter)
├── assets/
│ ├── images/
│ ├── videos/
│ ├── fonts/
│ ├── icons/
│ └── seo/ # favicons, og images, webmanifest
└── docs/
├── BEHAVIORS.md
├── PAGE_TOPOLOGY.md
├── components/ # one .spec.md per section
└── design-references/ # screenshots
Guiding Principles
These separate a successful clone from a "close enough" mess. Internalize them.
1. Completeness Beats Speed
Every builder agent must receive everything to do its job perfectly: section screenshot, exact CSS values, downloaded assets with local paths, real text content, DOM structure. If a builder has to guess anything — a color, a font size, a padding — you have failed at extraction. Spend the extra minute extracting one more property rather than shipping an incomplete brief.
2. Small Tasks, Perfect Results
"Build the entire features section" → builder approximates spacing and produces something obviously off. A single focused component with exact CSS values → builder nails it.
Complexity budget rule: if a builder's spec exceeds ~150 lines, the section is too complex for one agent. Split it. This is mechanical — don't override with "but it's all related."
3. Real Content, Real Assets
Extract actual text, images, videos, and SVGs from the live site. This is a clone, not a mockup. Use element.textContent for text. Download every <img>, <video>, every background-image URL, every webfont, every favicon.
Layered assets matter. A section that looks like one image is often multiple layers — a background watercolor, a foreground UI mockup PNG, an overlay icon. Enumerate ALL <img> and background-images in each container, including absolutely-positioned overlays. Missing an overlay makes the clone look empty even when the background is right.
4. Foundation First
Nothing can be built until the foundation exists: :root design tokens (colors, fonts, spacing), web fonts loaded, global keyframes & utility classes set, all global assets downloaded. Sequential and non-negotiable. Everything after this can be parallel.
5. Extract How It Looks AND How It Behaves
A website is not a screenshot — it's a living thing. Extract each element's appearance (computed CSS via getComputedStyle()) AND its behavior (what changes, what triggers it, how it transitions). Not "the nav changes on scroll" — record the exact trigger (scroll position, IntersectionObserver threshold), before/after CSS, and transition (duration, easing, CSS vs JS-driven vs animation-timeline).
Behaviors to watch for (illustrative, not exhaustive — the page may do things not on this list):
- Navbar that shrinks / changes background / gains shadow past a scroll threshold
- Elements that animate into view when entering the viewport (fade-up, slide-in, stagger)
scroll-snap-type containers
- Parallax layers that move at different rates
- Hover states that animate (the duration & easing matter, not just the end state)
- Dropdowns / modals / accordions with enter/exit animations
- Scroll-driven progress indicators or opacity transitions
- Auto-playing carousels / cycling content
- Section-to-section theme transitions (dark → light)
- Tabbed/pill content that switches visible cards
- Scroll-driven tab/accordion switching — sidebars whose active item changes via IntersectionObserver, NOT click handlers
- Smooth scroll libraries (Lenis, Locomotive Scroll) — check for
.lenis class, scroll wrappers, non-native scroll feel
6. Identify the Interaction Model BEFORE Building
The single most expensive mistake: building click-driven UI when the original is scroll-driven (or vice versa). Before any builder prompt for an interactive section:
- Don't click first. Scroll the section slowly via browser MCP and watch if anything changes on its own.
- If yes → scroll-driven. Identify the mechanism: IntersectionObserver,
scroll-snap, position: sticky, animation-timeline, scroll listeners.
- If nothing changes on scroll → THEN click/hover to test for click/hover-driven behavior.
- Document explicitly in the spec:
INTERACTION MODEL: scroll-driven via IntersectionObserver or INTERACTION MODEL: click-to-switch with opacity transition.
Wrong interaction model = complete rewrite, not a CSS tweak.
7. Extract Every State, Not Just the Default
Tab bars show different cards per tab. Headers look different at scroll 0 vs 100. Cards have hover effects. Capture all states.
For tabbed/stateful content: click each tab via browser MCP, extract content + computed styles per state, record the transition between states.
For scroll-dependent elements: capture computed styles at scroll 0, scroll past the trigger, capture again, diff the two — the diff IS the behavior spec. Record the exact trigger (px or intersection ratio) and the transition CSS.
8. Spec Files Are the Source of Truth
Every section gets a spec in sites/<slug>/docs/components/<name>.spec.md BEFORE any builder is dispatched. This is the contract between extraction and the builder agent. The builder receives the spec contents inline in its prompt — the file also persists as an auditable artifact.
If you dispatch a builder without a spec file, you are shipping incomplete instructions and the builder will guess.
9. The Page Must Always Render
After every merge, open index.html in a browser (the user runs python3 -m http.server) and verify no console errors and no blank sections. A broken page is never acceptable, even temporarily.
Phase 1: Reconnaissance
Navigate to the target URL with browser MCP.
Screenshots
- Take full-page screenshots at 1440px and 390px viewports.
- Save to
sites/<slug>/docs/design-references/ with descriptive names (fullpage-1440.png, fullpage-390.png).
- These are your master references — builders get section-specific crops later.
Timed load capture (animation-heavy sites)
For sites that obviously use GSAP / Framer / canvas / Lottie / Lenis (you'll see it in the network log or in the DOM), a single static fullpage screenshot misses the load-time motion. Take a timed sequence at the same viewport:
- Reload the page, then capture viewport (NOT full-page) screenshots at
t = 0 / 0.5s / 1s / 2s / 4s / 7s after navigation completes.
- Save as
load-t000ms.png, load-t500ms.png, … load-t7000ms.png.
- Compare. If frames are identical, the load animation either finished before our t=0 capture (Playwright/Chrome MCP usually waits for
load) or there is none — note this in BEHAVIORS.md so you don't waste time looking for it later.
Per-section scroll capture
Scroll to each section's start position via window.scrollTo({ top: Y, behavior: 'instant' }), take a viewport screenshot, then for any is-effect / entry- style class take three: pre-trigger (Y - 200) / at-trigger (Y) / settled (Y, +1.2s wait). This catches scroll-driven entry effects without needing to guess from the rendered HTML.
Global Extraction
Before anything else, extract from the live page:
Fonts — Inspect <link> and @font-face rules. Check computed font-family on headings, body, code, labels. Document every family / weight / style actually used. Either <link> to Google Fonts in index.html OR download the font files to assets/fonts/ and define @font-face in styles/main.css.
Colors — Extract the palette from computed styles across the page (background, text primary/secondary/muted, accent, border, hover, error, success). Define them as CSS custom properties on :root in styles/main.css. Add a [data-theme="dark"] block if the site has dark mode.
Spacing & radius scale — Sample padding/margin values site-wide. Extract the scale (often a 4px or 8px multiple). Define as --space-1..--space-N and --radius-sm/--radius-md/--radius-lg.
Favicons & meta — Download favicons, apple-touch-icons, OG images, webmanifest to assets/seo/. Wire them up in index.html <head>.
Global UI patterns — Custom scrollbar styling, scroll-snap on the page container, global keyframes, backdrop filters, gradient overlays, smooth-scroll libraries. Check for .lenis / .locomotive-scroll classes and non-native scroll feel. Add libraries via CDN <script> and replicate global CSS in main.css.
Mandatory Interaction Sweep
Dedicated pass AFTER screenshots and BEFORE any building. Discovers behaviors that are invisible in static screenshots.
Scroll sweep — scroll top to bottom slowly via browser MCP. At each section, pause and observe:
- Does the header change appearance? Record the trigger scroll position.
- Do elements animate into view? Which ones, what type (fade-up, slide-in, stagger).
- Does a sidebar / tab indicator auto-switch as you scroll? Record the mechanism.
- Are there scroll-snap points? Which containers.
- Is there a smooth-scroll library active?
Click sweep — click every element that looks interactive: buttons, tabs, pills, links, cards. Record what happens — content swap, modal open, dropdown reveal. For tabs/pills: click EACH ONE and record per-state content.
Hover sweep — hover every potentially-interactive element: buttons, cards, links, images, nav items. Record what changes (color, scale, shadow, underline, opacity) and the transition timing.
Responsive sweep — at 1440px / 768px / 390px via browser MCP. For each viewport, note which sections change layout (column → stack, sidebar disappears, etc.) and the approximate breakpoint.
Save findings to sites/<slug>/docs/BEHAVIORS.md. This is your behavior bible — reference it from every component spec.
Page Topology
Map every distinct section top to bottom. For each:
- Working name & visual order
- Fixed/sticky overlay vs flow content
- Page-level layout (scroll container, columns, z-index)
- Cross-section dependencies (e.g., floating nav over everything)
- Interaction model: static / click-driven / scroll-driven / time-driven
Save as sites/<slug>/docs/PAGE_TOPOLOGY.md — this is your assembly blueprint.
Phase 2: Foundation Build
Sequential. Do this yourself — do not delegate.
- Copy the starter:
cp -r templates/starter sites/<slug>. Update <title>, <meta name="description">, <meta charset>, viewport, lang.
- Wire fonts in
index.html <head>. Three cases:
- Google Fonts:
<link> to the fonts.googleapis.com/css2?... URL the source page uses (verbatim from its HTML).
- Source-hosted webfonts under the target's own CDN: download the
.woff2/.ttf/.otf files into assets/fonts/ and define @font-face in styles/main.css.
- Adobe Typekit / Fonts.com / similar proprietary kits (kit URL like
use.typekit.net/<kitid>.js): see "Proprietary font kit handling" below — kit URLs are domain-locked and won't load from localhost, so you must self-host the binaries.
- Set design tokens in
styles/main.css under :root — colors, font families, spacing scale, radii, shadows, typography sizes. Add [data-theme="dark"] if applicable.
- Add global CSS — keyframes, utility classes, scroll behavior (smooth scroll, scroll-snap on the page container if the original has it).
- Enable CDN libraries in
index.html only if the original requires them: Swiper for true carousels, GSAP/ScrollTrigger for sequenced or scroll-driven animations.
- Extract SVG icons — find all inline
<svg> on the page, deduplicate, save as a sprite (assets/icons/sprite.svg with <symbol id="...">) and reference via <svg><use href="assets/icons/sprite.svg#name"/></svg>. Name by visual function (icon-search, icon-arrow-right, icon-logo).
- Download all binary assets — write
sites/<slug>/scripts/download-assets.sh (bash + curl) that fetches every image / video / font / favicon enumerated by the asset-discovery script (below) into assets/. Run it. Preserve a meaningful directory structure under assets/images/, assets/videos/, assets/fonts/.
- Pass
--referer <site-origin>/ and a real User-Agent to curl. Many CDNs (esp. proprietary font kits) refuse hotlinking without these.
- Make it idempotent. Skip files that exist & are non-empty. Lets you re-run after partial failures.
- Parallelism via
xargs -P 6, but: do NOT pre-format url\tdest pairs and pipe through xargs — embedded tabs get mangled when xargs hands the line to bash -c. Pass URL only; compute the dest inside the bash subshell with parameter expansion (dest="${url#https://target.com/}").
- Verify the page renders:
python3 -m http.server in sites/<slug>/, open http://localhost:8000, confirm no console errors and that fonts/colors/global assets are present. Use document.fonts.status and document.fonts.size from DevTools to confirm webfonts loaded.
Proprietary font kit handling (Adobe Typekit / Fonts.com / Hatena / etc.)
These kits gate binaries by Origin / Referer checks tied to the licensee's domain. Hotlinking from localhost is blocked; you MUST self-host the binaries. Workflow:
- Capture binary URLs from the network log. After loading the page in browser MCP, dump
mcp__playwright__browser_network_requests (or equivalent). Filter for the kit host (e.g. use.typekit.net/af/...). Save unique URLs to docs/<vendor>-urls.txt.
- Download with the right headers. Bash:
curl -fsSL \
-A 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 …' \
-H 'Referer: https://<target-domain>/' \
-o "assets/fonts/<vendor>-<id>.otf" \
"<binary-url>"
Verify with file <path> — should report OpenType font data or Web Open Font Format (Version 2).
- Map kit-id ↔ family name. The kit JS (e.g.
use.typekit.net/<kitid>.js) contains the family table inline. It's wrapped in (function(config){…})(KIT_OBJECT). To extract:
node -e "
const fs = require('fs'), vm = require('vm');
const src = fs.readFileSync('/tmp/kit.js', 'utf8')
.replace(/\(function\(config\)\{/, '(function(config){ globalThis.__cfg = config;');
const ctx = { window: {}, document: { documentElement: {}, getElementsByTagName: () => [], head: { appendChild() {} }, createElement: () => ({ style:{} }) }, navigator: { userAgent: 'node' }, setTimeout, clearTimeout, console };
ctx.globalThis = ctx;
vm.createContext(ctx);
try { vm.runInContext(src, ctx, { timeout: 3000 }); } catch {}
function walk(o, fam) {
if (Array.isArray(o)) o.forEach(v => walk(v, fam));
else if (o && typeof o === 'object') {
const f = o.family || fam;
if (typeof o.source === 'string') {
const m = o.source.match(/\\/af\\/([0-9a-f]+)\\//);
if (m) console.log('kit-id=' + m[1] + ' family=' + f + ' fvd=' + (o.fvd || '?'));
}
for (const v of Object.values(o)) walk(v, f);
}
}
walk(ctx.__cfg, null);
"
Output: one line per binary, kit-id=… family=… fvd=…. Use this to write the @font-face declarations with the correct font-family strings (the names actually referenced by the source site's CSS) and weight/style mapped from fvd.
- Self-host: define
@font-face { font-family: "<family>"; src: url("../assets/fonts/<vendor>-<id>.otf") format("opentype"); font-weight: <n>; } for each binary in styles/main.css.
- Caveat — subset fonts: kits often serve
unicode=... query-parameter URLs that contain only the glyphs the page rendered during your visit. If your QA pass shows missing glyphs, re-visit the source while interacting with all states (hover all chars, click all tabs) and re-capture, OR use the kit's chunks=...&order=0 URL form which serves a fuller binary.
.gitignore
The browser MCP scratch dir (.playwright-mcp/) holds per-call transcripts and console logs that bloat the repo and shouldn't be committed. Add it to the project .gitignore once per repo (not per site).
Asset Discovery Script
Run via browser MCP on the live page to enumerate everything to download:
JSON.stringify({
images: [...document.querySelectorAll('img')].map(img => ({
src: img.src || img.currentSrc,
srcset: img.srcset || null,
alt: img.alt,
width: img.naturalWidth,
height: img.naturalHeight,
parentClasses: img.parentElement?.className,
siblings: img.parentElement ? img.parentElement.querySelectorAll('img').length : 0,
position: getComputedStyle(img).position,
zIndex: getComputedStyle(img).zIndex,
})),
videos: [...document.querySelectorAll('video')].map(v => ({
src: v.src || v.querySelector('source')?.src,
poster: v.poster,
autoplay: v.autoplay, loop: v.loop, muted: v.muted,
})),
backgroundImages: [...document.querySelectorAll('*')].filter(el => {
const bg = getComputedStyle(el).backgroundImage;
return bg && bg !== 'none';
}).map(el => ({
url: getComputedStyle(el).backgroundImage,
element: el.tagName + '.' + (el.className?.toString().split(' ')[0] || ''),
})),
svgCount: document.querySelectorAll('svg').length,
fonts: [...new Set([...document.querySelectorAll('*')].slice(0, 200).map(el => getComputedStyle(el).fontFamily))],
fontFaces: [...document.styleSheets].flatMap(s => { try { return [...s.cssRules].filter(r => r.type === 5).map(r => r.cssText); } catch { return []; } }),
favicons: [...document.querySelectorAll('link[rel*="icon"]')].map(l => ({ href: l.href, sizes: l.sizes?.toString(), type: l.type })),
ogImages: [...document.querySelectorAll('meta[property^="og:image"]')].map(m => m.content),
});
Then download-assets.sh should batch-download (4 at a time, with --retry, preserving filenames). Skip files that already exist.
Phase 3: Section Specification & Dispatch
Core loop. For each section in PAGE_TOPOLOGY.md (top to bottom): extract → write spec → dispatch builder.
Step 1: Extract
Per section, via browser MCP:
-
Screenshot the section in isolation (scroll to it, screenshot the viewport). Save to sites/<slug>/docs/design-references/.
-
Extract CSS for every element. Don't hand-measure. Run this once per section container:
(function(selector) {
const el = document.querySelector(selector);
if (!el) return JSON.stringify({ error: 'Not found: ' + selector });
const props = [
'fontSize','fontWeight','fontFamily','lineHeight','letterSpacing','color',
'textTransform','textDecoration','backgroundColor','background','backgroundImage',
'padding','paddingTop','paddingRight','paddingBottom','paddingLeft',
'margin','marginTop','marginRight','marginBottom','marginLeft',
'width','height','maxWidth','minWidth','maxHeight','minHeight',
'display','flexDirection','justifyContent','alignItems','gap',
'gridTemplateColumns','gridTemplateRows','gridTemplateAreas',
'borderRadius','border','borderTop','borderBottom','borderLeft','borderRight',
'boxShadow','overflow','overflowX','overflowY',
'position','top','right','bottom','left','zIndex',
'opacity','transform','transition','animation','cursor',
'objectFit','objectPosition','mixBlendMode','filter','backdropFilter',
'whiteSpace','textOverflow','WebkitLineClamp','aspectRatio',
];
const skip = new Set(['none','normal','auto','0px','rgba(0, 0, 0, 0)','start','content-box']);
function styles(node) {
const cs = getComputedStyle(node);
const out = {};
for (const p of props) { const v = cs[p]; if (v && !skip.has(v)) out[p] = v; }
return out;
}
function walk(node, depth) {
if (depth > 5) return null;
const kids = [...node.children];
return {
tag: node.tagName.toLowerCase(),
classes: node.className?.toString().split(' ').slice(0, 5).join(' ') || '',
id: node.id || null,
text: node.childNodes.length === 1 && node.childNodes[0].nodeType === 3 ? node.textContent.trim().slice(0, 240) : null,
styles: styles(node),
img: node.tagName === 'IMG' ? { src: node.src, alt: node.alt, w: node.naturalWidth, h: node.naturalHeight } : null,
childCount: kids.length,
children: kids.slice(0, 24).map(c => walk(c, depth + 1)).filter(Boolean),
};
}
return JSON.stringify(walk(el, 0), null, 2);
})('SELECTOR');
-
Extract multi-state styles — for any element with multiple states (scroll-triggered, hover, active tab):
- Capture state A (current).
- Trigger the change (scroll, click, hover via browser MCP).
- Re-run extraction → state B.
- Record the diff:
Property X changes VALUE_A → VALUE_B, triggered by TRIGGER, transition: TRANSITION_CSS.
-
Extract real content — all textContent, alt, aria-label, placeholder. For tabbed/stateful content, click each tab and extract content per state.
-
Identify assets the section uses — which downloaded files in assets/, which icon ids from the sprite. Check for layered images (multiple stacked <img> / background-images in the same container).
-
Assess complexity — how many distinct sub-components? A distinct sub-component has its own styling, structure, and behavior (a card variant, a nav item, a search panel).
Step 2: Write the Spec File
Path: sites/<slug>/docs/components/<name>.spec.md. Every section needs one. Template:
# <SectionName> Specification
## Overview
- **Target markup:** new `<section class="<name>">` block in `index.html`
- **Target styles:** appended block in `styles/main.css`
- **Target script:** `scripts/main.js` (additions only) OR `scripts/<name>.js`
- **Screenshot:** `docs/design-references/<screenshot>.png`
- **Interaction model:** <static | click-driven | scroll-driven | time-driven | mixed>
## DOM Structure
<element hierarchy — what contains what, exact tag/class for each>
## Computed Styles (exact values from getComputedStyle)
### Container
- display: ...
- padding: ...
- maxWidth: ...
- (every relevant property)
### <Child element 1>
- (every relevant property)
### <Child element N>
...
## States & Behaviors
### <Behavior name, e.g., "Scroll-triggered floating mode">
- **Trigger:** <scroll position 50px / IntersectionObserver rootMargin -30% 0px / click on .tab-button / hover>
- **State A (before):** maxWidth: 100vw, boxShadow: none, borderRadius: 0
- **State B (after):** maxWidth: 1200px, boxShadow: 0 4px 20px rgba(0,0,0,0.1), borderRadius: 16px
- **Transition:** all 0.3s ease
- **Implementation:** <CSS transition + scroll listener | IntersectionObserver | CSS animation-timeline | GSAP ScrollTrigger>
### Hover states
- **<Element>:** <prop>: <before> → <after>, transition: <value>
## Per-State Content (if applicable)
### State: "Featured"
- Title: ""
- Cards: [{ title, description, image, link }, ...]
### State: "Productivity"
- ...
## Assets
- Background image: `assets/images/<file>.webp`
- Overlay image: `assets/images/<file>.png`
- Icons used: `#icon-arrow-right`, `#icon-search` (from `assets/icons/sprite.svg`)
## Text Content (verbatim)
<all text content from the live site>
## Responsive Behavior
- **Desktop (1440px):** <layout description>
- **Tablet (768px):** <what changes — e.g., "2-col maintained, gap → 16px">
- **Mobile (390px):** <what changes — e.g., "stacks single column, image full-width">
- **Breakpoint:** layout switches at ~<N>px
Fill every section. Mark N/A only after thinking twice — even a footer often has hover states on links.
Step 3: Dispatch Builders (parallel, in worktrees)
Based on complexity:
- Simple section (1–2 sub-components) → one builder agent for the whole section.
- Complex section (3+ distinct sub-components) → split. One builder per sub-component, plus one for the wrapper that composes them. Sub-components first, wrapper after.
Use the Agent tool with isolation: "worktree" so each builder works on an isolated copy of the repo:
Agent({
description: "Build <SectionName>",
subagent_type: "general-purpose",
isolation: "worktree",
prompt: <full spec file contents inline> + <build instructions>
})
Every builder agent must receive, inline in its prompt:
- Full contents of its spec file
- Path to its section screenshot
- Stack constraints: vanilla HTML/CSS/JS only, no
npm install, no frameworks
- Which CDN libs are already wired (Swiper, GSAP), which to use
- Which icon ids are available in
assets/icons/sprite.svg
- Target file paths:
sites/<slug>/index.html (append section markup), sites/<slug>/styles/main.css (append styles), sites/<slug>/scripts/main.js (add behavior)
- For responsive: the breakpoint values and what changes
- A required check before finishing: open
index.html (via python3 -m http.server) — section renders with no console errors, fonts/assets load
Don't wait between dispatches. As soon as one builder is dispatched, move to extracting the next section. Builders work in parallel in their worktrees while you keep extracting.
Step 4: Merge
As builder agents complete:
- Merge their worktree branches into
feat/clone-<slug>. You have full context on everyone's work, so resolve conflicts intelligently.
- After each merge, open the page and verify the section renders, no console errors, page still works end-to-end.
The extract → spec → dispatch → merge cycle continues until every section in PAGE_TOPOLOGY.md is built.
Phase 4: Page Assembly
After all sections are built and merged, wire it together in index.html and scripts/main.js:
- Confirm section markup ordering matches
PAGE_TOPOLOGY.md
- Implement page-level layout (scroll containers, sticky positioning, z-index layering)
- Implement page-level behaviors: scroll-snap, scroll-driven animations, intersection observers, smooth scroll (Lenis via CDN if the original uses it)
- Verify the full page renders cleanly via
python3 -m http.server
Phase 5: Visual QA Diff
Do NOT declare the clone complete after assembly. Compare side-by-side:
- Open the original site and your clone in two windows at 1440px.
- Compare section by section, top to bottom.
- Repeat at 390px.
- For each discrepancy:
- Was the value extracted correctly? Check the spec file.
- If the spec was wrong → re-extract via browser MCP, update the spec, fix the markup/CSS.
- If the spec was right but the implementation diverged → fix the implementation to match the spec.
- Test all interactive behaviors: scroll the page, click every button/tab, hover over interactive elements.
- Verify smooth scroll feels right, header transitions trigger, tabs switch, animations play.
Only after this QA pass is the clone complete.
Pre-Dispatch Checklist
Before dispatching ANY builder, verify every box. If you can't, go back and extract more.
What NOT to do
- Don't build click-based tabs when the original is scroll-driven (or vice versa). Determine the interaction model FIRST by scrolling before clicking.
- Don't extract only the default state. If tabs default to "Featured", click every tab and extract per-state content. If the header changes on scroll, capture styles at position 0 AND past the trigger.
- Don't miss overlay/layered images. Check every container's DOM tree for multiple
<img> and positioned overlays.
- Don't build mockup HTML for content that's actually a video / Lottie / canvas. Check first.
- Don't approximate CSS. "Looks like 18px" is wrong if the computed value is
17px / 26px. Extract exact values.
- Don't reference docs from builder prompts. Each builder gets the spec inline. Never "see DESIGN_TOKENS.md for colors."
- Don't skip asset extraction. Without real images / videos / fonts, the clone will always look fake.
- Don't hand a builder too much scope. If the prompt is getting long because the section is complex, split it.
- Don't skip responsive extraction. Test at 1440 / 768 / 390 during extraction.
- Don't forget smooth-scroll libraries. Default browser scroll feels noticeably different — users spot it immediately.
- Don't dispatch a builder without a spec file. The spec file forces exhaustive extraction and creates an auditable artifact.
- Don't add
package.json, node_modules/, or any bundler config under sites/. This skill is vanilla; if a builder produces a package.json, reject the merge.
- Don't inline a framework runtime (React/Vue/Svelte) — even via CDN.
Slug naming
Lowercased main domain segment + optional path hint, hyphenated:
https://www.apple.com/vision-pro/ → apple-vision-pro
https://stripe.com/ → stripe-landing
https://linear.app/method → linear-method
https://miitsu.city/ → miitsu-city
Branch & PR
git checkout -b feat/clone-<slug>
git push -u origin feat/clone-<slug>
gh pr create --fill
PR description must:
- Link the original target URL
- Note: "Layout study / personal-use clone — assets are downloaded from the original site under fair-use for educational purposes; not affiliated with the rights holder."
Then in Claude Code:
/security-review
Address findings, push fixes, request re-review if needed, then merge.
Completion Report
When done, report:
- Total sections built
- Total spec files written (must equal section count)
- Total assets downloaded (images / videos / fonts / favicons)
- Local preview command (
cd sites/<slug> && python3 -m http.server)
- Visual QA results (any remaining discrepancies)
- Known gaps or limitations
Attribution
This skill's methodology is adapted from JCodesMore/ai-website-cloner-template (MIT). Their Next.js scaffold is intentionally not used — only the workflow logic was kept and rewritten for the web-kage vanilla stack.