{"slug": "mirrorframe-clone-a-web-page-into-react-verified-per-node-by-pixel-diff", "title": "Mirrorframe – clone a web page into React, verified per-node by pixel diff", "summary": "Mirrorframe is a new open-source tool that clones live web pages into verified, self-contained React apps by capturing the DOM with a headless browser, distilling a design genome, and reconstructing it deterministically without using large language models. The tool verifies the output per node and per interaction state using pixel diff, and is packaged as a Claude Skill for AI agents.", "body_md": "Clone a live web page into a **verified, self-contained React app** — capture it with a\nheadless browser, distill a **Design Genome** (tokens + motion + interaction state\nmachines), reconstruct it deterministically, and prove the result **per node and per\ninteraction state** by visual diff. Built entirely on technology that exists today\n(Playwright, WAAPI, MutationObserver, esbuild, pixelmatch). No LLM in the loop: every\noutput is inspectable and reproducible.\n\n**This project is also packaged as a Claude Skill** — an AI agent should start from\n[ SKILL.md](/byte271/mirrorframe/blob/main/SKILL.md) and the deeper docs in\n\n[. This README is the human-facing overview.](/byte271/mirrorframe/blob/main/references)\n\n`references/`\n\nWhy 0.x:the CLI flags, genome schema, and report format may still change. 0.x is a statement of API instability, not of dishonest numbers — every number below is reproducible from a clean clone.\n\nRequires Node.js ≥ 18. No other machine state is assumed.\n\n```\ngit clone <this-repo> && cd mirrorframe\nnpm install            # postinstall downloads the Playwright Chromium binary\n\n# clone a live URL (only pages you own or that are permissively licensed):\nnode scripts/mirrorframe.js run --url https://your-site.example --out ./mf-out/yours\n\n# or a local page directory containing an index.html (served automatically):\nnode scripts/mirrorframe.js run --dir ./fixtures/tabs --out ./mf-out/tabs\n\n# optional capture viewport: --width 1280 --height 800   (defaults shown)\n\nnpm run verify         # regression gate: full pipeline on all 12 bundled fixtures\n```\n\nOpen the reconstruction at `<out>/recon-app/index.html`\n\n— a self-contained React app\n(`app.jsx`\n\n, `styles.css`\n\n, `bundle.js`\n\n) with all assets bundled locally in `<out>/assets/`\n\n.\n\n**Capture**→`capture.json`\n\n: headless Chromium walks the DOM recording computed styles per node, plus every motion/interaction signal listed below, and downloads all referenced assets.**Genome**→`genome.json`\n\n: clusters design tokens, compiles motion DNA and interaction DNA (recovered state machines), annotates the structure.**Reconstruct**→`recon-app/`\n\n: deterministic React + CSS codegen with the recovered state machines re-wired as real listeners/observers/animations.**Converge**→`convergence.json`\n\n+`summary.json`\n\n: re-renders the reconstruction at the identical viewport, verifies per node and per state, auto-corrects residual drift, and replays every recovered interaction and scroll state.\n\n- Full computed-style capture per node (layout, color, type, borders, shadows, radii, filters, transforms) at a configurable viewport\n**Images**(`<img>`\n\n) and**CSS background-images** downloaded and rewritten to local bundled copies (8MB/asset, 192MB/capture budgets; misses reported, never hidden)**Web fonts**: every`@font-face`\n\nis downloaded, bundled, and re-declared locally**SVG passthrough**: inline SVG markup is preserved verbatim(new in 0.2): source + poster bundled locally (64MB/asset budget), playback attributes (autoplay/loop/muted/playsinline/controls) preserved, real`<video>`\n\n`<video>`\n\nre-emitted; degrades to poster/first-frame still if the source is unfetchable — never an empty box(new in 0.2): the currently painted frame is snapshotted and re-emitted as a pixel-true still`<canvas>`\n\n- Same-origin iframes captured; cross-origin content masked with a fixed reason\n\n- CSS\n`@keyframes`\n\nanimations and transitions (recovered via WAAPI`getAnimations()`\n\n) `:hover`\n\nrules recovered verbatim from same-origin stylesheets;**JS-driven hover effects** recovered by a synthetic hover probe**Pointer choreography**(new in 0.3): mouse-movement effects are recovered by sampling the page over a 3×3 pointer grid and fitting every transform-matrix component as a plane over the pointer (least squares, R² ≥ 0.85) — parallax layers, 3D tilt cards (`matrix3d`\n\n), magnetic offsets, pointer-linked scale.**Smoothing/lag is recovered too**: the exponential time constant of the \"ultra-smooth\" trailing chase is measured from a pointer-jump step response and replayed with the same lerp in the reconstruction. Non-planar pointer physics is reported as unclassified, never guessed**Scroll choreography**: scroll-position–linked style tracks are sampled at multiple scroll depths and re-emitted; verified by scroll-state replay at 25/50/75%**Sequential scroll-reveal stagger**(new in 0.3): reveal mutations are timestamped, so elements that fade/slide in one by one are replayed with their observed per-element offsets; each participant's own computed`transition`\n\n(duration, easing curve,`transition-delay`\n\n) is re-emitted verbatim — CSS`nth-child`\n\ndelay staggers and JS-timer staggers both survive the clone**Chronograph frame sampler**(new in 0.2): time-driven motion with*no declared animation object*(rAF loops, JS tickers) is detected and recorded at**every animation frame** for a bounded window (2400ms, ≤120 tracks) — transform, opacity, filter, clip-path per frame — then replayed verbatim as looping WAAPI animations built from the sampled offsets\n\nRecovered from the page by synthetic probes — never assumed or hardcoded:\n\n`reveal`\n\n— IntersectionObserver scroll-reveal (hidden → visible class machines)`toggle`\n\n— second click reverses the first (accordions, expand/collapse)`exclusive`\n\n— one class moves between siblings (tabs, segmented controls)`pair`\n\n— one trigger adds the class, another removes it (modal open/close, including triggers only reachable*inside*the open state)- Ambient (timer-driven) mutations such as autoplay carousels are detected during an input-free watch and excluded from learning rather than mislearned\n- A probe that crashes on a page-script conflict records\n`probe-error`\n\nfor that candidate and the sweep continues (new in 0.2)\n\n**Per-node**: every tracked node gets its own similarity score and one status —`pass`\n\n/`style-verified`\n\n/`corrected`\n\n/`failed`\n\n/`animated-unstable`\n\n/`hidden-at-capture`\n\n/`time-varying-replicated`\n\n(new in 0.2) /`skipped(reason)`\n\n**Residual auto-correction**: failing nodes get their reconstructed computed styles diffed against capture, patched with a per-node override, and re-verified once**Layout convergence**: rendered rects measured against captured rects; drifting nodes get authored sizes baked in bounded passes** Behavioral replay**: every recovered interaction state is replayed with real clicks and pixel-diffed against a ground-truth screenshot of the original**Scroll replay**: the page is verified at multiple scroll depths, not just the fold** Pointer replay**(new in 0.3): ground-truth screenshots at fixed pointer checkpoints are re-shot on the reconstruction with the same real mouse moves (settle time scaled to the largest recovered smoothing tau) and pixel-diffed; replay shots are settle-aware, waiting until consecutive frames match so staggered reveals and lag-smoothed effects finish before the diff**Time-varying honesty**(new in 0.2): bundled video/canvas and frame-sampled nodes have their*mechanism*replicated, but pixels depend on the playback instant — they are masked from diffs with the fixed reason`time-varying-media`\n\n, itemized, never averaged in or hidden**Fixed skip-reason taxonomy**(`scripts/lib/reasons.js`\n\n, 10 reasons): every skipped node/probe/page carries exactly one reason; skipped elements keep their layout footprint via placeholders and are masked from the diff- Bounded everything: navigation fallback, probe budgets, node/candidate caps, 300s pipeline watchdog with distinct exit codes (0 ok / 3 page skipped / 4 crash)\n\nAll 12 bundled fixtures pass with **0 failed nodes, 0 failed states, 0 failed\nscroll states, and 0 failed pointer states** — including `stress-stagger`\n\n(CSS-delay + JS-timer sequential reveals, new in 0.3), `stress-pointer`\n\n(parallax/tilt/smooth-follower pointer choreography, new in 0.3), `stress-frames`\n\n(rAF inline-style motion + animated canvas), `stress-scale`\n\n(230 nodes / 120\ninteractive), `stress-edgecss`\n\n(container queries, `:has()`\n\n), `stress-iframe`\n\n,\n`stress-flaky`\n\n(404/hung assets), and `stress-carousel`\n\n(autoplay → excluded,\nnot mislearned).\n\nThe pipeline has also been spot-validated against complex real-world production pages (hundreds of nodes; heavy video, scroll choreography, and rAF motion) with aggregate similarities ≥ 99.9%, all scroll states passing, media and web fonts fully bundled, and zero crashes — any sub-threshold node is itemized in the per-node report rather than averaged away. Nothing from third-party pages is bundled in this repo.\n\nSuccess = zero `failed`\n\nnodes and zero failed states. `style-verified`\n\n,\n`animated-unstable`\n\n, `hidden-at-capture`\n\n, `time-varying-replicated`\n\n, and\n`skipped(reason)`\n\nare acceptable statuses when explained — see `SKILL.md`\n\nfor the\nfull interpretation guide. The one-screen summary is `summary.json`\n\n; the full\nper-node table is `convergence.json`\n\n; residual maps are `diff-*.png`\n\n.\n\n| File | Meaning |\n|---|---|\n`capture.json` |\nevidence bundle: node tree, computed styles, animations, scroll/frame tracks, probes |\n`genome.json` |\nDesign Genome: tokens, motion DNA, interaction DNA, annotated structure |\n`assets/` |\nlocally bundled images, fonts, videos, posters, media stills |\n`recon-app/` |\ngenerated React app (jsx + css + bundle + html) |\n`original-*.png` / `state-*.png` / `scroll-*.png` / `pointer-*.png` |\nground-truth screenshots |\n`recon-*.png` / `diff-*.png` |\nreconstruction screenshots + pixelmatch residual maps |\n`convergence.json` |\nper-node table, correction log, state/scroll replay results, aggregates |\n`summary.json` |\none-screen quantitative summary |\n\n```\nSKILL.md                        skill entrypoint: trigger description + agent workflow\nscripts/mirrorframe.js          CLI: capture → genome → reconstruct → converge\nscripts/verify-all.js           release verification: all fixtures + results table\nscripts/batch-verify.js         batch validation against a list of live URLs\nscripts/lib/props.js            shared tracked-property list + color normalization\nscripts/lib/reasons.js          fixed skip-reason taxonomy (10 reasons)\nscripts/lib/assets.js           asset downloader/bundler (images, fonts, video)\nscripts/lib/capture.js          capture substrate: DOM/style walk, motion + interaction probes\nscripts/lib/genome.js           genome compiler: tokens, tracks, behavior classification\nscripts/lib/reconstruct.js      deterministic React + CSS codegen (state machines re-wired)\nscripts/lib/converge.js         per-node + aggregate diff, auto-correction, state replay\nreferences/behavior-patterns.md recovered-pattern catalog (reveal/toggle/exclusive/pair)\nreferences/limitations.md       hard scope limits + expected non-pass statuses\nreferences/roadmap.md           forward planning, feasible-now vs research-level\nfixtures/                       12 self-authored test pages (4 core + 8 stress)\n```\n\n- Canvas/WebGL\n*draw streams*are not reconstructed — a canvas becomes a pixel-true still of its captured frame, not a live re-render - Pointer physics beyond a planar matrix-component model (proximity-gated magnets, springs with overshoot) is reported as unclassified, not approximated; recovered smoothing is a single exponential time constant per node\n- No general physics/inertia/spring recovery; frame tracks replay observed motion verbatim\n- Behavior graphing covers class-toggle machines driven by click/scroll/hover — no drags, keyboard machines, or DOM insertion/removal machines\n- One output target: React + plain CSS\n- Responsive breakpoints are not re-captured at multiple viewports (single-viewport clone)\n- \"Any website\" is explicitly not claimed — read the per-node report before trusting output\n\nMIT (see `LICENSE`\n\n). All fixtures are authored for this repo (system font stacks, no\nexternal assets). Runtime dependencies: playwright (Apache-2.0), esbuild (MIT),\npixelmatch (ISC), pngjs (MIT), react / react-dom (MIT) — all permissive.\n\nOnly run against pages you own or that are permissively licensed; cloning third-party commercial sites raises IP/ToS issues this tool does not resolve for you.\n\n- Recover constraints, not pixels: fixed\n`width`\n\n/`height`\n\nare never baked into base rules — bold text is*allowed*to widen a tab (layout convergence bakes sizes only for measured drift, as a reported correction). - Recover state machines, not frames — except where frames\n*are*the ground truth: the Chronograph tier replays sampled per-frame motion verbatim and says so. - Every claim in this README maps to a check in\n`npm run verify`\n\n; anything that failed or could not be observed is reported per node in`convergence.json`\n\nrather than averaged away.", "url": "https://wpnews.pro/news/mirrorframe-clone-a-web-page-into-react-verified-per-node-by-pixel-diff", "canonical_source": "https://github.com/byte271/mirrorframe", "published_at": "2026-07-08 20:44:12+00:00", "updated_at": "2026-07-08 21:12:49.460720+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents"], "entities": ["Mirrorframe", "Playwright", "Claude", "React"], "alternates": {"html": "https://wpnews.pro/news/mirrorframe-clone-a-web-page-into-react-verified-per-node-by-pixel-diff", "markdown": "https://wpnews.pro/news/mirrorframe-clone-a-web-page-into-react-verified-per-node-by-pixel-diff.md", "text": "https://wpnews.pro/news/mirrorframe-clone-a-web-page-into-react-verified-per-node-by-pixel-diff.txt", "jsonld": "https://wpnews.pro/news/mirrorframe-clone-a-web-page-into-react-verified-per-node-by-pixel-diff.jsonld"}}