multiclone 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/README.md +78 -2
  2. package/agents/section-builder.md +37 -0
  3. package/commands/multiclone.md +15 -0
  4. package/dist/adapters/cli.js +129 -0
  5. package/dist/adapters/cli.js.map +1 -0
  6. package/dist/adapters/cliArgs.js +63 -0
  7. package/dist/adapters/cliArgs.js.map +1 -0
  8. package/dist/adapters/mcp.js +83 -0
  9. package/dist/adapters/mcp.js.map +1 -0
  10. package/dist/analyze/analyze.js +85 -0
  11. package/dist/analyze/analyze.js.map +1 -0
  12. package/dist/analyze/animations.js +340 -0
  13. package/dist/analyze/animations.js.map +1 -0
  14. package/dist/analyze/ast.js +182 -0
  15. package/dist/analyze/ast.js.map +1 -0
  16. package/dist/analyze/chunks.js +160 -0
  17. package/dist/analyze/chunks.js.map +1 -0
  18. package/dist/analyze/jobs/code.js +117 -0
  19. package/dist/analyze/jobs/code.js.map +1 -0
  20. package/dist/analyze/jobs/sections.js +356 -0
  21. package/dist/analyze/jobs/sections.js.map +1 -0
  22. package/dist/analyze/pretty.js +20 -0
  23. package/dist/analyze/pretty.js.map +1 -0
  24. package/dist/analyze/report.js +101 -0
  25. package/dist/analyze/report.js.map +1 -0
  26. package/dist/analyze/worker.js +71 -0
  27. package/dist/analyze/worker.js.map +1 -0
  28. package/dist/capture/capture.js +340 -0
  29. package/dist/capture/capture.js.map +1 -0
  30. package/dist/capture/collectors/a11y.js +92 -0
  31. package/dist/capture/collectors/a11y.js.map +1 -0
  32. package/dist/capture/collectors/animations.js +113 -0
  33. package/dist/capture/collectors/animations.js.map +1 -0
  34. package/dist/capture/collectors/breakpoints.js +31 -0
  35. package/dist/capture/collectors/breakpoints.js.map +1 -0
  36. package/dist/capture/collectors/consent.js +39 -0
  37. package/dist/capture/collectors/consent.js.map +1 -0
  38. package/dist/capture/collectors/coverage.js +121 -0
  39. package/dist/capture/collectors/coverage.js.map +1 -0
  40. package/dist/capture/collectors/dom.js +16 -0
  41. package/dist/capture/collectors/dom.js.map +1 -0
  42. package/dist/capture/collectors/fonts.js +154 -0
  43. package/dist/capture/collectors/fonts.js.map +1 -0
  44. package/dist/capture/collectors/frames.js +93 -0
  45. package/dist/capture/collectors/frames.js.map +1 -0
  46. package/dist/capture/collectors/index.js +61 -0
  47. package/dist/capture/collectors/index.js.map +1 -0
  48. package/dist/capture/collectors/interactions.js +207 -0
  49. package/dist/capture/collectors/interactions.js.map +1 -0
  50. package/dist/capture/collectors/intro.js +84 -0
  51. package/dist/capture/collectors/intro.js.map +1 -0
  52. package/dist/capture/collectors/listeners.js +84 -0
  53. package/dist/capture/collectors/listeners.js.map +1 -0
  54. package/dist/capture/collectors/motion.js +91 -0
  55. package/dist/capture/collectors/motion.js.map +1 -0
  56. package/dist/capture/collectors/network.js +288 -0
  57. package/dist/capture/collectors/network.js.map +1 -0
  58. package/dist/capture/collectors/probes.js +34 -0
  59. package/dist/capture/collectors/probes.js.map +1 -0
  60. package/dist/capture/collectors/runtime.js +38 -0
  61. package/dist/capture/collectors/runtime.js.map +1 -0
  62. package/dist/capture/collectors/scripts.js +98 -0
  63. package/dist/capture/collectors/scripts.js.map +1 -0
  64. package/dist/capture/collectors/sections.js +17 -0
  65. package/dist/capture/collectors/sections.js.map +1 -0
  66. package/dist/capture/collectors/shots.js +58 -0
  67. package/dist/capture/collectors/shots.js.map +1 -0
  68. package/dist/capture/collectors/snapshot.js +47 -0
  69. package/dist/capture/collectors/snapshot.js.map +1 -0
  70. package/dist/capture/collectors/sourcemaps.js +153 -0
  71. package/dist/capture/collectors/sourcemaps.js.map +1 -0
  72. package/dist/capture/collectors/storage.js +72 -0
  73. package/dist/capture/collectors/storage.js.map +1 -0
  74. package/dist/capture/context.js +40 -0
  75. package/dist/capture/context.js.map +1 -0
  76. package/dist/capture/hooks/candidates.js +104 -0
  77. package/dist/capture/hooks/candidates.js.map +1 -0
  78. package/dist/capture/hooks/consent.js +82 -0
  79. package/dist/capture/hooks/consent.js.map +1 -0
  80. package/dist/capture/hooks/recorders.js +507 -0
  81. package/dist/capture/hooks/recorders.js.map +1 -0
  82. package/dist/capture/hooks/sections.js +62 -0
  83. package/dist/capture/hooks/sections.js.map +1 -0
  84. package/dist/capture/hooks/styles.js +86 -0
  85. package/dist/capture/hooks/styles.js.map +1 -0
  86. package/dist/capture/probes.js +491 -0
  87. package/dist/capture/probes.js.map +1 -0
  88. package/dist/capture/report.js +115 -0
  89. package/dist/capture/report.js.map +1 -0
  90. package/dist/compare/compare.js +391 -0
  91. package/dist/compare/compare.js.map +1 -0
  92. package/dist/compare/image.js +105 -0
  93. package/dist/compare/image.js.map +1 -0
  94. package/dist/compare/probes.js +129 -0
  95. package/dist/compare/probes.js.map +1 -0
  96. package/dist/compare/report.js +99 -0
  97. package/dist/compare/report.js.map +1 -0
  98. package/dist/compare/session.js +139 -0
  99. package/dist/compare/session.js.map +1 -0
  100. package/dist/compare/shoot.js +62 -0
  101. package/dist/compare/shoot.js.map +1 -0
  102. package/dist/contracts/catalog.js +29 -0
  103. package/dist/contracts/catalog.js.map +1 -0
  104. package/dist/contracts/context.js +19 -0
  105. package/dist/contracts/context.js.map +1 -0
  106. package/dist/contracts/fields.js +16 -0
  107. package/dist/contracts/fields.js.map +1 -0
  108. package/dist/contracts/tools/analyze.js +38 -0
  109. package/dist/contracts/tools/analyze.js.map +1 -0
  110. package/dist/contracts/tools/capture.js +87 -0
  111. package/dist/contracts/tools/capture.js.map +1 -0
  112. package/dist/contracts/tools/compare.js +142 -0
  113. package/dist/contracts/tools/compare.js.map +1 -0
  114. package/dist/contracts/tools/doctor.js +23 -0
  115. package/dist/contracts/tools/doctor.js.map +1 -0
  116. package/dist/contracts/tools/inspect.js +41 -0
  117. package/dist/contracts/tools/inspect.js.map +1 -0
  118. package/dist/contracts/tools/method.js +60 -0
  119. package/dist/contracts/tools/method.js.map +1 -0
  120. package/dist/contracts/tools/mirror.js +53 -0
  121. package/dist/contracts/tools/mirror.js.map +1 -0
  122. package/dist/contracts/tools/project.js +61 -0
  123. package/dist/contracts/tools/project.js.map +1 -0
  124. package/dist/contracts/tools/session.js +195 -0
  125. package/dist/contracts/tools/session.js.map +1 -0
  126. package/dist/contracts/types.js +20 -0
  127. package/dist/contracts/types.js.map +1 -0
  128. package/dist/core/cdp.js +146 -0
  129. package/dist/core/cdp.js.map +1 -0
  130. package/dist/core/chrome.js +185 -0
  131. package/dist/core/chrome.js.map +1 -0
  132. package/dist/core/files.js +31 -0
  133. package/dist/core/files.js.map +1 -0
  134. package/dist/core/inpage.js +24 -0
  135. package/dist/core/inpage.js.map +1 -0
  136. package/dist/core/log.js +10 -0
  137. package/dist/core/log.js.map +1 -0
  138. package/dist/core/page.js +269 -0
  139. package/dist/core/page.js.map +1 -0
  140. package/dist/doctor.js +37 -0
  141. package/dist/doctor.js.map +1 -0
  142. package/dist/domain/breakpoints.js +47 -0
  143. package/dist/domain/breakpoints.js.map +1 -0
  144. package/dist/domain/budget.js +38 -0
  145. package/dist/domain/budget.js.map +1 -0
  146. package/dist/domain/compare.js +138 -0
  147. package/dist/domain/compare.js.map +1 -0
  148. package/dist/domain/coverage.js +96 -0
  149. package/dist/domain/coverage.js.map +1 -0
  150. package/dist/domain/motion.js +142 -0
  151. package/dist/domain/motion.js.map +1 -0
  152. package/dist/domain/paths.js +55 -0
  153. package/dist/domain/paths.js.map +1 -0
  154. package/dist/domain/result.js +38 -0
  155. package/dist/domain/result.js.map +1 -0
  156. package/dist/domain/routes.js +160 -0
  157. package/dist/domain/routes.js.map +1 -0
  158. package/dist/domain/sections.js +94 -0
  159. package/dist/domain/sections.js.map +1 -0
  160. package/dist/domain/snapshot.js +268 -0
  161. package/dist/domain/snapshot.js.map +1 -0
  162. package/dist/domain/sourcemap.js +95 -0
  163. package/dist/domain/sourcemap.js.map +1 -0
  164. package/dist/domain/stack.js +342 -0
  165. package/dist/domain/stack.js.map +1 -0
  166. package/dist/domain/tokens.js +183 -0
  167. package/dist/domain/tokens.js.map +1 -0
  168. package/dist/domain/types.js +2 -0
  169. package/dist/domain/types.js.map +1 -0
  170. package/dist/domain/viewports.js +45 -0
  171. package/dist/domain/viewports.js.map +1 -0
  172. package/dist/mirror/rewrite.js +56 -0
  173. package/dist/mirror/rewrite.js.map +1 -0
  174. package/dist/mirror/server.js +191 -0
  175. package/dist/mirror/server.js.map +1 -0
  176. package/dist/mirror/store.js +86 -0
  177. package/dist/mirror/store.js.map +1 -0
  178. package/dist/project/assets.js +69 -0
  179. package/dist/project/assets.js.map +1 -0
  180. package/dist/project/draft.js +860 -0
  181. package/dist/project/draft.js.map +1 -0
  182. package/dist/project/method.js +154 -0
  183. package/dist/project/method.js.map +1 -0
  184. package/dist/project/scaffold.js +142 -0
  185. package/dist/project/scaffold.js.map +1 -0
  186. package/dist/session/discover.js +53 -0
  187. package/dist/session/discover.js.map +1 -0
  188. package/dist/session/proc.js +113 -0
  189. package/dist/session/proc.js.map +1 -0
  190. package/dist/session/progress.js +83 -0
  191. package/dist/session/progress.js.map +1 -0
  192. package/dist/session/session.js +450 -0
  193. package/dist/session/session.js.map +1 -0
  194. package/dist/session/state.js +47 -0
  195. package/dist/session/state.js.map +1 -0
  196. package/dist/store/inspect.js +212 -0
  197. package/dist/store/inspect.js.map +1 -0
  198. package/dist/store/run.js +112 -0
  199. package/dist/store/run.js.map +1 -0
  200. package/dist/store/summary.js +69 -0
  201. package/dist/store/summary.js.map +1 -0
  202. package/dist/version.js +2 -0
  203. package/dist/version.js.map +1 -0
  204. package/install.sh +29 -0
  205. package/package.json +67 -4
  206. package/skills/multiclone/SKILL.md +123 -0
  207. package/skills/multiclone/references/capture-format.md +73 -0
  208. package/skills/multiclone/references/fidelity.md +54 -0
  209. package/skills/multiclone/references/quality.md +47 -0
  210. package/skills/multiclone/references/stacks.md +156 -0
  211. package/templates/react-vite/.prettierignore +5 -0
  212. package/templates/react-vite/.prettierrc.json +6 -0
  213. package/templates/react-vite/eslint.config.js +38 -0
  214. package/templates/react-vite/gitignore +4 -0
  215. package/templates/react-vite/index.html +12 -0
  216. package/templates/react-vite/package.json +36 -0
  217. package/templates/react-vite/src/app/App.tsx +15 -0
  218. package/templates/react-vite/src/app/Page.tsx +11 -0
  219. package/templates/react-vite/src/app/routes.ts +7 -0
  220. package/templates/react-vite/src/components/README.md +7 -0
  221. package/templates/react-vite/src/content/README.md +2 -0
  222. package/templates/react-vite/src/hooks/useActiveSection.ts +37 -0
  223. package/templates/react-vite/src/hooks/useInView.ts +45 -0
  224. package/templates/react-vite/src/hooks/useMediaQuery.ts +15 -0
  225. package/templates/react-vite/src/lib/cn.ts +4 -0
  226. package/templates/react-vite/src/lib/math.ts +21 -0
  227. package/templates/react-vite/src/lib/mc.ts +13 -0
  228. package/templates/react-vite/src/lib/media.ts +4 -0
  229. package/templates/react-vite/src/main.tsx +13 -0
  230. package/templates/react-vite/src/providers/SmoothScroll.tsx +24 -0
  231. package/templates/react-vite/src/styles/base.css +42 -0
  232. package/templates/react-vite/src/styles/draft.css +1 -0
  233. package/templates/react-vite/src/styles/fonts.css +1 -0
  234. package/templates/react-vite/src/styles/index.css +4 -0
  235. package/templates/react-vite/src/styles/tokens.css +3 -0
  236. package/templates/react-vite/src/vite-env.d.ts +1 -0
  237. package/templates/react-vite/tsconfig.app.json +29 -0
  238. package/templates/react-vite/tsconfig.json +4 -0
  239. package/templates/react-vite/tsconfig.node.json +19 -0
  240. package/templates/react-vite/vite.config.ts +37 -0
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: multiclone
3
+ description: Clone a website from a link into a clean React + Vite + TypeScript project with the same design, animations and effects, whatever stack the original uses. Use when the user gives a URL and asks to clone, copy, recreate, rebuild or replicate a site, page, landing page or section, or to match a site's animations, hover states or scroll effects.
4
+ ---
5
+
6
+ # multiclone: clone any website from a link
7
+
8
+ The goal is a rebuild that a visitor can't tell apart from the original at any scroll position, on desktop and on mobile, including the intro, the scroll motion and the hover states. It must be clean, typed React that a senior engineer would be happy to own, not a pile of the original's minified code.
9
+
10
+ ## Fast path (use this)
11
+
12
+ 1. **Ask the scope first**, in one short question: just the landing page, specific pages (which ones), or the whole site. Don't start before the user answers.
13
+ 2. **Start:** `multiclone start <url> --scope landing|pages|site [--pages /pricing,/about]`. It returns in seconds with a **progress link** (for example `http://127.0.0.1:4300/`). Give that link to the user straight away: it shows live progress and then the clone itself.
14
+ 3. **Wait for the draft:** `multiclone status --wait draft`. In about 2–3 minutes the session has a quick capture (desktop and mobile in parallel), a React project with the captured fonts, tokens and media, a **first draft** (one component and CSS module per section with the real text, links, images and video), the clone running on localhost and an offline copy of the original. Status lists all the links.
15
+ 4. **Polish, don't rewrite.** The draft is in `<workspace>/clone/src/sections/<page>/`. Compare each section with its crop in `captures/<page>/sections/<id>/`, fix layout, make it responsive and clean, add the motion and hovers. Keep the dev server running; it hot-reloads.
16
+ 5. **Deep data arrives in the background.** Other pages (scope pages/site) and the deep pass (motion map, hovers, clicks, coverage, source maps, in `captures/<page>-full`) finish while you work. Check with `multiclone status`. Use the deep capture for motion and hovers when it's done.
17
+ 6. **Compare and fix** (stage 5 below), then hand over. `multiclone stop` ends the servers; the files stay.
18
+
19
+ Run `start` again to resume after a crash or restart: finished captures are reused and drafted pages are never overwritten.
20
+
21
+ Every command prints a short summary; details live in files. Read big things with `inspect` instead of opening whole files.
22
+
23
+ ## The stages in detail
24
+
25
+ The fast path runs stages 1 and 3 and a first pass of 4 for you. Use the tools below directly for anything it doesn't cover, or when you need more control.
26
+
27
+ 1. **Capture** the original (one call also analyses it into section bundles).
28
+ 2. **Understand** the page from `REPORT.md` and the section bundles.
29
+ 3. **Scaffold** the project from the capture: fonts, tokens, media and the mock API come along.
30
+ 4. **Rebuild** (or polish the draft) section by section, tagging elements with the `data-mc-*` markers.
31
+ 5. **Compare and fix** until it is close, then hand over the localhost link.
32
+
33
+ Do not skip the measuring: the difference between a lookalike and a clone is the compare loop at the end.
34
+
35
+ ## Tools
36
+
37
+ Run them from the terminal: `multiclone <command>`, and `multiclone <command> --help` lists the flags (use `npx multiclone` if it isn't installed globally). The same tools are available over MCP (`multiclone mcp`) for agents that prefer it.
38
+
39
+ | Tool | What it does |
40
+ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
41
+ | `start` | The fast path above: background session with quick capture, project, first draft, live clone, offline original, other pages and the deep pass. Returns the progress link. |
42
+ | `status` | Where the session is, with all links. `--wait draft`, `--wait pages` or `--wait deep` blocks until that point. |
43
+ | `stop` | Stops the session's servers. Files stay. |
44
+ | `draft` | Turns a capture into section components inside a scaffolded project (`start` runs it for you). |
45
+ | `capture` | Real Chrome loads the page per viewport, dismisses consent banners, scrolls like a visitor and records the network, every script and its source maps, the computed-style DOM snapshot, fonts, storage, the intro filmstrip, the scroll motion map, runtime animation libraries, WebGL shaders, event listeners, hover/focus/click states and CSS breakpoints. `--depth quick` keeps only what a draft needs (1–2 minutes). Then it runs `analyze`. |
46
+ | `analyze` | Rebuilds the derived files from a capture in an isolated worker: section bundles, design tokens, prettified code split into app and vendor, animation calls found in the code, and `REPORT.md`. |
47
+ | `inspect` | Pages through anything in a capture by facet: `summary`, `section`, `dom`, `styles`, `network`, `motion`, `animations`, `runtime`, `interactions`, `code`, `fonts`, `tokens`. Use `selector`, `viewport`, `query`, `offset` and `limit` to keep pages small. |
48
+ | `serve_mirror` | Serves a capture offline on localhost. `live` mode runs the original scripts with the captured storage seeded, so the animations work. `snapshot` mode serves the frozen DOM. Captured API calls become a mock API under `/__mc/api`. |
49
+ | `scaffold` | Creates the strict React 19 + Vite + TS starter. With `capture` it installs the captured fonts, `tokens.css`, the media files and a dev proxy to the mock API. |
50
+ | `assets` | Copies captured media into `public/media/<host>/…` and writes `src/content/asset-map.json` (scaffold with `capture` already does this). |
51
+ | `shoot` | Screenshots at exact scroll offsets, scrolling like a visitor so Lenis and GSAP move. A `probe` JS expression runs after each scroll: element rects, CSS variables, computed styles. A slow page or a failed shot becomes a warning, not a failure. |
52
+ | `compare` | Compares the clone with the original per viewport: frames, sections aligned by `data-mc-section`, structure, scroll motion by `data-mc-track`, hovers by `data-mc-hover`, and the intro with `intro`. Writes an HTML report and returns the worst sections and frames. |
53
+ | `doctor` | Checks Node, Chrome and headless WebGL. |
54
+
55
+ The capture folder layout is documented in [references/capture-format.md](references/capture-format.md).
56
+
57
+ ## 1. Capture
58
+
59
+ - Run `doctor` once if this machine hasn't used multiclone before.
60
+ - Capture with `out` next to the rebuild, for example `<workspace>/<site>-capture`. Viewports default to desktop 1440×900 and mobile 390×844; `viewports: "auto"` adds one per major CSS breakpoint.
61
+ - Raise `settleMs` for long preloaders. Use `headed: true` and a `profile` folder for bot checks or logins: pass the check once in the window and the profile reuses it.
62
+ - If the result looks wrong, look at `shots/` and `intro/` first. A bot wall or blank WebGL means recapture headed. `capture.json` lists any collector that failed or ran out of time; the rest of the capture is still good.
63
+
64
+ ## 2. Understand: read before you write
65
+
66
+ Read `REPORT.md` top to bottom. It has the stack with rebuild advice, the sections table, the design tokens, the fonts, the animation calls found in the code, the motion summary and anything that was skipped or truncated. Then:
67
+
68
+ - **Section bundles (`sections/<id>/`).** Each holds `section.md` (the brief), crops per viewport, a DOM outline with boxes and styles, local tokens, the motion tracks, animations, interactions, assets and the hot code that ran while it moved. A section bundle is everything needed to rebuild that section without reading the rest of the page.
69
+ - **Stack recipes.** Load [references/stacks.md](references/stacks.md) whenever the stack includes anything beyond plain CSS.
70
+ - **Content data (`data/`).** Hydration state and JSON scripts hold the real copy, links and image URLs. Turn them into typed content files. Never retype copy you can extract.
71
+ - **Code (`code/`, `sources/`).** When source maps were published, `sources/` has the original source: read that first. Otherwise `code/` has the prettified app chunks with vendor libraries named. `animations-code.json` lists every `gsap.to`, `ScrollTrigger.create`, `animate()`, keyframes and shader found, with file and line.
72
+ - **Runtime (`runtime/`).** What the libraries reported live: GSAP tweens and ScrollTriggers with their start/end, Lenis options, WAAPI animations, IntersectionObserver thresholds, WebGL shader sources, Lottie/Rive files.
73
+ - **Motion (`motion.json`).** Every element whose transform, opacity, clip, filter, colours, size or text changes with scroll, with exact curves and a stable `id`.
74
+
75
+ Use `inspect` to page through anything large instead of reading whole files.
76
+
77
+ Before coding, write a short plan: the sections and their components, the technique for each moving part and why, which original CSS you'll reuse, and what can't be replicated exactly (auth, live search, third-party widgets).
78
+
79
+ ## 3. Scaffold
80
+
81
+ - Run `scaffold` with `capture` pointing at the capture folder. It copies the captured fonts with matching `@font-face` rules, the capture `tokens.css`, the media files and the asset map. When the page called its own API, it writes `mc.config.json` so `npm run dev` proxies those paths to `multiclone serve` on port 4400.
82
+ - Give each clone its own port so several can run at once.
83
+ - Install only what the stack needs: `gsap`, `motion`, `three` with `@react-three/fiber`, `@rive-app/canvas` or `lottie-web`. Pin exact versions. Keep the starter's Lenis only if the original smooth-scrolls, and use the captured Lenis options.
84
+ - If a font file couldn't be captured, use the same family from Fontsource or the closest free look-alike, and tune `size-adjust` and `letter-spacing` until line breaks match. Text that wraps differently shifts everything below it.
85
+
86
+ ## 4. Rebuild
87
+
88
+ Work top to bottom, one section bundle at a time. For pages with many sections, hand independent sections to a sub-agent using the `section-builder` brief ([agents/section-builder.md](../../agents/section-builder.md)), one section each, after the shell is in place.
89
+
90
+ 1. **Shell first:** fonts, tokens, header, footer, smooth scroll and any global background.
91
+ 2. **Then each section:** read its `section.md`. If a draft exists (`src/sections/<page>/<Name>/`), polish it in place: turn repeated markup into data plus a map, rename classes to meaningful names, replace fixed widths with responsive layout, and delete wrappers that only existed for the original's framework. Otherwise rebuild it as one component under `src/sections/<page>/`. Look at it next to its crop before moving on.
92
+ 3. **Tag for compare** with the helpers in `@/lib/mc`:
93
+ - `<section {...mcSection('03-pricing')}>` with the section id;
94
+ - `{...mcTrack(id)}` on every element listed in the section's `motion.json`;
95
+ - `{...mcHover(id)}` on every element listed in its `interactions.json`.
96
+ Untagged motion and hovers count as missing in the score.
97
+ 4. **Motion:** follow the stack recipes and the captured runtime values (ScrollTrigger start/end, durations, easings). Match the curves in `motion.json`, not only the endpoints.
98
+ 5. **Heavy parts** (canvas, WebGL, Rive, video) load lazily, pause off-screen and clean up on unmount. StrictMode stays on.
99
+ 6. **Determinism:** seed anything random so the compare loop is meaningful.
100
+ 7. **Things you can't run locally:** render their visual state statically and keep a list for the hand-over.
101
+
102
+ Run `npm run check` whenever a section is done.
103
+
104
+ ## 5. Compare and fix
105
+
106
+ The full playbook is in [references/fidelity.md](references/fidelity.md).
107
+
108
+ 1. Serve the capture with `serve_mirror` (`live`) as the stable original (a `start` session already serves it), and run the clone. For long compare runs prefer `npm run build && npm run preview` over the dev server: it loads much faster and is what ships.
109
+ 2. Run `compare` with `original` set to the **capture folder** and `clone` to the dev URL. The capture gives compare its sections, tracks and hovers; it shoots the mirror for pixels. Add `intro: true` when the page has an intro animation.
110
+ 3. Fix in this order: missing sections, page height, the worst section, untagged tracks and hovers, then the worst frames. Open the images yourself: the score guides, it doesn't judge.
111
+ 4. Measure with `shoot` and a `probe` on both sides instead of guessing.
112
+ 5. Repeat until every section is visually right on both viewports. The score stays below 100 while anything couldn't be compared, so read `completeness` too.
113
+
114
+ ## Hand-over
115
+
116
+ - `npm run check` is clean.
117
+ - Give the localhost link, `npm run build && npm run preview` for production, the final score and the report path.
118
+ - List honestly what is approximated or missing. Never claim something works that you didn't see in a screenshot or a probe.
119
+ - Keep replies short and plain. Don't paste code into the chat.
120
+
121
+ ## Quality bar
122
+
123
+ [references/quality.md](references/quality.md): strict TypeScript, no `any`, no non-null `!`, ESLint strictTypeChecked, Prettier, typed content data, reusable hooks, StrictMode on, accessible markup, lazy heavy parts.
@@ -0,0 +1,73 @@
1
+ # Capture folder format
2
+
3
+ Everything one `capture` writes, relative to the capture folder. Files from a collector that was skipped or failed are simply missing; `capture.json` says which ones and why. Read big files with the `inspect` tool rather than whole.
4
+
5
+ ## Top level
6
+
7
+ | File | What it holds |
8
+ | ---------------------- | ----------------------------------------------------------------------------------------------------------- |
9
+ | `REPORT.md` | Start here: stack, sections, tokens, fonts, animation calls, motion summary, gaps. |
10
+ | `capture.json` | Manifest: URL, final URL, viewports, page heights, stack signals, outputs per collector, budgets, failures. |
11
+ | `network.json` | Every response: URL, method, status, MIME type, bytes, initiator, saved file under `assets/`. |
12
+ | `redirects.json` | Redirect chains. |
13
+ | `websockets.json` | WebSocket URLs and sampled frames. |
14
+ | `frames.json` | Iframes and workers that were attached. |
15
+ | `fingerprint.json` | Globals, DOM markers, CSS features, keyframes, canvases and videos used for stack detection. |
16
+ | `consent.json` | The consent banner that was dismissed, if any. |
17
+ | `fonts.json` | `faces` (every @font-face with status and saved files) and `usage` (what text rendered with which font). |
18
+ | `storage.json` | Cookies, localStorage and sessionStorage, seeded into the mirror. |
19
+ | `breakpoints.json` | Media-query breakpoints found in the stylesheets. |
20
+ | `motion.json` | Scroll motion tracks: `id`, `path`, scroll `range`, `timeBased`, and `curves` per property. |
21
+ | `motion-vars.json` | CSS custom properties on the root that change with scroll. |
22
+ | `animations.json` | CSS animations and transitions from the Animation domain, with keyframes and timing. |
23
+ | `animations-code.json` | Animation calls found in the code (GSAP, ScrollTrigger, Motion, WAAPI, keyframes, shaders) with file/line. |
24
+ | `tokens.json` | Design scales: colours, font families, sizes, weights, spacing, radii, shadows, easings, durations. |
25
+ | `tokens.css` | The same as CSS custom properties; scaffold copies it into the project. |
26
+
27
+ ## Folders
28
+
29
+ | Folder | What it holds |
30
+ | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |
31
+ | `assets/` | Response bodies, named by host and path. |
32
+ | `data/` | Hydration state and JSON scripts (`__NEXT_DATA__`, Remix, Nuxt, Apollo, Redux). |
33
+ | `dom/<viewport>.html` | Hydrated DOM snapshot taken at the top of the page. |
34
+ | `snapshot/<viewport>.json` | Computed-style DOM snapshot: nodes with stable `path`, boxes, paint order and key styles. |
35
+ | `sections-raw/<viewport>.json` | The vertical sections the page splits into (label, path, top, bottom). |
36
+ | `shots/<viewport>-<y>.png` | Viewport screenshots at scroll offsets. |
37
+ | `intro/<viewport>-<ms>.png` | Intro filmstrip at 0–4000 ms after load; `intro/index.json` lists the frames. |
38
+ | `scripts/` | Every parsed script's source; `scripts/index.json` lists URL, hash, size and source map URL. |
39
+ | `sources/` | Original sources recovered from source maps; `sources/index.json` maps them to scripts. |
40
+ | `code/` | Prettified app chunks with vendor libraries identified; `code/index.json` lists them. |
41
+ | `runtime/<viewport>.json` | Live library state: GSAP tweens and ScrollTriggers, Lenis options, WAAPI, IntersectionObservers, WebGL shaders, Lottie/Rive. |
42
+ | `listeners/` | Event listeners per element with handler locations. |
43
+ | `motion-code/` | Code that executed while each part of the page moved (coverage diff). |
44
+ | `interactions/<viewport>.json` | Hover, focus-visible and active states, real hover crops and click results, per element. |
45
+ | `sections/<id>/` | One bundle per section, below. |
46
+
47
+ ## Section bundles
48
+
49
+ `sections/index.json` lists every section with its id (`03-pricing`), label, folder, position per viewport and counts. Each `sections/<id>/` holds:
50
+
51
+ | File | What it holds |
52
+ | ------------------------ | ------------------------------------------------------------------------- |
53
+ | `section.md` | The brief: what to build, which files to read, the markers to add. |
54
+ | `<viewport>.png` | Crop of the section. |
55
+ | `dom-<viewport>.txt` | Indented element outline with boxes and key styles. |
56
+ | `styles-<viewport>.json` | Computed styles of its elements. |
57
+ | `tokens-<viewport>.json` | Tokens used inside the section. |
58
+ | `motion.json` | Its motion tracks (tag each element with `data-mc-track="<id>"`). |
59
+ | `animations.json` | Its CSS animations and transitions. |
60
+ | `animations-code.json` | Animation calls that target it. |
61
+ | `interactions.json` | Its hover/focus/click states with `id` (tag with `data-mc-hover="<id>"`). |
62
+ | `assets.json` | Media it uses, with saved files. |
63
+ | `hot-code.json` | Code that ran while it moved. |
64
+
65
+ ## Markers the rebuild adds
66
+
67
+ | Attribute | Where | Read by |
68
+ | ------------------------ | ------------------------------------------------------ | --------------------------- |
69
+ | `data-mc-section="<id>"` | Root element of each rebuilt section | `compare` section alignment |
70
+ | `data-mc-track="<id>"` | Each element listed in a section's `motion.json` | `compare` motion diff |
71
+ | `data-mc-hover="<id>"` | Each element listed in a section's `interactions.json` | `compare` hover diff |
72
+
73
+ The scaffold's `@/lib/mc` helpers (`mcSection`, `mcTrack`, `mcHover`) produce these attributes.
@@ -0,0 +1,54 @@
1
+ # Fidelity playbook: from close to identical
2
+
3
+ ## The loop
4
+
5
+ 1. Serve the capture with `serve_mirror` (`live`) and run the clone's dev server.
6
+ 2. Run `compare` with `original` set to the capture folder. Compare then knows the sections, motion tracks and hover states, and shoots the mirror for pixels. Use fixed `positions` (top, the start, middle and end of each pinned or animated section, the footer) and reuse them every round. Add `intro: true` when the page animates in.
7
+ 3. Read the result in this order:
8
+ - **completeness:** missing sections and size mismatches cap the score below 100;
9
+ - **missing sections:** the clone has no `data-mc-section` with that id;
10
+ - **page heights**, then the **worst section** (its row shows original | clone | diff, text, fonts and height);
11
+ - **untagged tracks and hovers:** add `data-mc-track` / `data-mc-hover`, they count as misses;
12
+ - **motion:** per property error along the scroll; **hovers:** hovered values on both sides;
13
+ - **intro** frames, then the **worst frames**.
14
+ 4. Re-run. Stop when every section looks right by eye and the remaining diffs come from video, randomness or live data.
15
+
16
+ Compare against the live URL only when the mirror misbehaves (`/__mc/misses` lists what was not captured). A URL original has no capture data, so it scores frames and auto-detected sections only.
17
+
18
+ ## Measuring with probes
19
+
20
+ `shoot` accepts a `probe`: a JavaScript expression evaluated after each scroll. Run the same probe on both the original and the clone and compare the numbers.
21
+
22
+ - **Element box:** `JSON.stringify(document.querySelector('h1').getBoundingClientRect())`
23
+ - **Section tops:** `[...document.querySelectorAll('section')].map(s => Math.round(s.getBoundingClientRect().top + scrollY))`
24
+ - **A runtime variable:** `getComputedStyle(document.documentElement).getPropertyValue('--intro-delay')`
25
+ - **Animated values:** `getComputedStyle(el).transform`, `.opacity`, `.clipPath`
26
+ - **Fonts actually used:** `[...document.fonts].filter(f => f.status === 'loaded').map(f => f.family + ' ' + f.weight)`
27
+
28
+ For a value that depends on the viewport, shoot the original at several sizes, for example the `name:1440x613`, `name:1440x813` and `name:1440x1013` viewports. Then fit the formula. It is usually linear in `innerHeight` or clamped.
29
+
30
+ ## Common diffs and their causes
31
+
32
+ | What you see | Usual cause | Fix |
33
+ | ------------------------------------------------------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
34
+ | Everything below some point is shifted | A section height is off: a pinned wrapper, a margin, or text wrapping differently | Probe section tops on both pages; fix the first one that differs |
35
+ | Text is the right size but wraps differently | The font isn't a close enough substitute, or the letter-spacing is off | Tune `size-adjust`, `letter-spacing` and the weight; check `font-feature-settings` |
36
+ | Text looks lighter or heavier | A variable font weight axis, or a missing `-webkit-font-smoothing: antialiased` | Copy the root font smoothing and `font-variation-settings` |
37
+ | An animation is at the wrong stage at the same scroll | The start or end offsets or a runtime variable are wrong | Use the ScrollTrigger start/end in `runtime/`; probe the variable at three heights |
38
+ | The animation matches at the ends but not in the middle | The easing or scrub smoothing differs | Compare intermediate frames; copy the `ease` names and `scrub` values |
39
+ | Mobile layout is very different | The original serves a different structure below a breakpoint | Use the mobile DOM snapshot and mobile shots as the source, not the desktop ones |
40
+ | The header or nav is missing in the shots | Fixed elements with an intro delay, or hide-on-scroll | Match the intro timing and the scroll-direction logic |
41
+ | Images are blank in the clone | A lazy image never intersected, or the asset map path is wrong | Check `asset-map.json`; use `loading="lazy"` only below the fold |
42
+ | The canvas or WebGL is blank in shots | Not ready at screenshot time, or no WebGL | Raise `settleMs`; check `doctor`; wait for the first frame before revealing |
43
+ | The colours are slightly off everywhere | Colour space or tone mapping (WebGL), or wrong theme variables | Copy the renderer colour settings; check `color-scheme` and the root variables |
44
+ | Scores swing between runs | Randomness, time-based loops, video | Seed the randomness; judge those frames by eye |
45
+
46
+ ## Mobile
47
+
48
+ - The mobile capture is a separate source of truth: different DOM, heights, images and sometimes animations. Compare mobile with as many positions as desktop.
49
+ - Portrait canvases often need their own layout. Thumbnails and labels are often hidden on small screens.
50
+ - Touch: `hover` styles must not stick on tap. Use `@media (hover: hover)` around hover effects that the original scopes that way.
51
+
52
+ ## When to stop
53
+
54
+ Stop when both viewports look right by eye at every listed position, page heights are within about 1%, and the scroll motion feels the same when you scrub. List the remaining known gaps honestly in the hand-over.
@@ -0,0 +1,47 @@
1
+ # Project standards for every clone
2
+
3
+ The clone should read like it was written by hand by a senior React developer.
4
+
5
+ ## Stack
6
+
7
+ - React 19, Vite and TypeScript, starting from the `scaffold` template. StrictMode stays on.
8
+ - ESLint `strictTypeChecked` and `stylisticTypeChecked`, react-hooks, react-refresh and Prettier.
9
+ - `npm run check` runs the typecheck, lint, format check and build. It must be clean at hand-over.
10
+ - Pin every dependency to an exact version. Add only what the stack needs.
11
+
12
+ ## TypeScript
13
+
14
+ - `strict`, `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes` are on.
15
+ - No `any`. No non-null `!`. Prefer `satisfies` over `as`.
16
+ - Content is typed data in `src/content/*.ts`, with types in `src/types/`. Components never hard-code copy that came from the original's data.
17
+
18
+ ## Layout of the code
19
+
20
+ - `src/app/`: App and Page composition.
21
+ - `src/components/<area>/`: layout, hero, sections and ui. One component per file.
22
+ - `src/hooks/`: reusable behaviour: `useInView`, `useActiveSection`, `useMediaQuery`, plus scroll progress and timing hooks.
23
+ - `src/lib/`: pure helpers and engines: math, the canvas renderer, the graph layout.
24
+ - `src/styles/`: `index.css` for imports, `base.css`, `app.css` for your additions, and `original/` for reused compiled CSS.
25
+ - `public/media/<host>/`: assets copied by `multiclone assets`.
26
+
27
+ ## Behaviour
28
+
29
+ - Effects clean up everything: observers, RAF loops, listeners, GSAP contexts and WebGL resources. StrictMode runs them twice.
30
+ - Heavy parts (canvas, WebGL, Rive, Lottie, carousels) are `lazy()` and loaded when near the viewport.
31
+ - Media plays only in view.
32
+ - Under `prefers-reduced-motion`, ambient motion stops and content stays readable and in place.
33
+
34
+ ## Accessibility
35
+
36
+ - Landmarks: one `<h1>`, then headings in order.
37
+ - Real `<button>` and `<a>` elements, alt text for meaningful images, and empty `alt` for decoration.
38
+ - Dialogs and menus have labels, focus management, Escape to close and `aria-expanded`.
39
+ - Visible focus styles. Split-text animations keep the readable text available to screen readers.
40
+
41
+ ## Hand-over checklist
42
+
43
+ - [ ] `npm run check` is clean
44
+ - [ ] `compare` on desktop and mobile; final score and report path noted
45
+ - [ ] Page heights match within about 1%
46
+ - [ ] The localhost link is given; `npm run build && npm run preview` works
47
+ - [ ] A short, honest list of what is approximated or missing
@@ -0,0 +1,156 @@
1
+ # Rebuild recipes by stack
2
+
3
+ Use the section for each technology in the capture's stack list. Several usually apply at once, for example Remix, Tailwind, CSS scroll timelines and Lenis together.
4
+
5
+ ## Contents
6
+
7
+ - [Frameworks and platforms](#frameworks-and-platforms)
8
+ - [CSS scroll-driven animations](#css-scroll-driven-animations)
9
+ - [Smooth scroll: Lenis, Locomotive, ScrollSmoother](#smooth-scroll)
10
+ - [GSAP and ScrollTrigger](#gsap-and-scrolltrigger)
11
+ - [Framer and Motion](#framer-and-motion)
12
+ - [Webflow IX2](#webflow-ix2)
13
+ - [Reveal-on-scroll libraries (AOS, Squarespace, WordPress plugins)](#reveal-on-scroll)
14
+ - [Split text](#split-text)
15
+ - [Sticky and pinned sections](#sticky-and-pinned-sections)
16
+ - [Canvas drawing](#canvas-drawing)
17
+ - [three.js, OGL and raw WebGL shaders](#webgl)
18
+ - [Rive, Lottie, Spline](#rive-lottie-spline)
19
+ - [Video](#video)
20
+ - [Carousels](#carousels)
21
+ - [Hover and interaction states](#hover-and-interaction-states)
22
+
23
+ ## Frameworks and platforms
24
+
25
+ The original framework only matters for finding content and component boundaries. The rebuild is always Vite + React.
26
+
27
+ - **Next.js:**
28
+ - `data/__NEXT_DATA__.json` (pages router) or `data/next-rsc-payload.json` (app router) holds page props.
29
+ - RSC payloads are line-based, in the form `id:JSON`. Split them on newlines and parse what you need.
30
+ - **Remix / React Router:** `data/__remixContext.json` holds `state.loaderData`, keyed by route ID. Island props are often there in full.
31
+ - **Nuxt:** `data/__NUXT__.json`. Vue `<Transition>` classes map to CSS transitions on mount and unmount.
32
+ - **Webflow, Framer, Wix, Squarespace:**
33
+ - No useful data blob, so take content from `dom/desktop.html`.
34
+ - Webflow and Squarespace CSS are usually reusable.
35
+ - Framer and Wix CSS are not: they use generated class names and absolute layout. Rebuild their layout by hand from the screenshots and the structure files.
36
+ - **Shopify themes, WordPress:**
37
+ - Each theme section is one component.
38
+ - The theme CSS is reusable.
39
+ - jQuery effects are small, so rewrite them as hooks.
40
+
41
+ ## CSS scroll-driven animations
42
+
43
+ The capture lists them as `animation-timeline`, `scroll()`, `view()`, `view-timeline` and `animation-range`. This is the easiest case.
44
+
45
+ - **Reuse the original rules as they are.** Copy the stylesheet, or the relevant rules, into `src/styles/original/`. Keep the original class names and custom property names.
46
+ - **Rebuild the timeline structure exactly.** Named timelines (`view-timeline-name: --x`) must sit on the same kind of element. `timeline-scope` must be on a shared ancestor. Pinned wrappers need the same heights.
47
+ - **Calibrate runtime variables.** Values on `<html style>` are set by script, often from `innerHeight`. Probe them in the original at several viewport heights, fit the formula, and set them from a hook on resize.
48
+ - **Keep native scroll authoritative.** If Lenis is also present, it must drive the real window scroll (the default). Otherwise CSS timelines stop updating.
49
+
50
+ ## Smooth scroll
51
+
52
+ - **Lenis** (`window.lenis`, `html.lenis` class): use the starter's `SmoothScroll` provider. Tune `lerp` (around 0.08–0.12) or `duration` by scrubbing both pages with the wheel. Copy the original's options from the bundle when you can find `new Lenis(`.
53
+ - **Locomotive Scroll:**
54
+ - `data-scroll-speed` parallax becomes Lenis plus a scroll-linked translate. The offset is `(elementCenter - viewportCenter) * speed * factor`.
55
+ - Calibrate the factor with probes.
56
+ - **GSAP ScrollSmoother:** Lenis gives the same feel. Map `data-speed` and `data-lag` to scroll-linked transforms.
57
+ - **No smooth scroll in the original:** remove the provider. Adding inertia the original doesn't have is a fidelity bug.
58
+
59
+ ## GSAP and ScrollTrigger
60
+
61
+ - Install `gsap` and register the plugins once: `ScrollTrigger`, plus `SplitText` and `Flip` if the original uses them. All GSAP plugins are free on npm.
62
+ - **Setup:**
63
+ - Create timelines in `useLayoutEffect` inside `gsap.context(() => { … }, ref)`.
64
+ - Call `ctx.revert()` on cleanup so StrictMode's double run doesn't stack triggers.
65
+ - With Lenis, call `lenis.on('scroll', ScrollTrigger.update)` and drive Lenis from `gsap.ticker`.
66
+ - **Reading the original:**
67
+ - Search the prettified bundle for `scrollTrigger:`, `ScrollTrigger.create`, `.timeline(`, `pin:`, `scrub:`, `start:`, `end:` and `ease:`. Copy the numbers and easing names exactly.
68
+ - If the code is too minified to read, use the motion map. The first and last scroll offsets where an element changes give `start` and `end`. The sampled values in between reveal the easing curve and whether it scrubs.
69
+ - **Pinning:**
70
+ - `pin: true` adds pin spacing, and the page height depends on it.
71
+ - Compare page heights after every pinned section.
72
+ - Use `pinSpacing` exactly as the original does.
73
+ - **Breakpoints:** `gsap.matchMedia()` and `ScrollTrigger.matchMedia` give mobile and desktop different timelines. Check both captures.
74
+
75
+ ## Framer and Motion
76
+
77
+ - Use `motion/react`.
78
+ - `whileInView` with `viewport={{ once, amount }}` covers appear effects.
79
+ - `useScroll({ target, offset })` with `useTransform` covers scroll-linked transforms. Take the input and output ranges from the motion map.
80
+ - Framer sites ship `data-framer-appear-id` and inline `style` starting values (opacity, transform) in the DOM snapshot. Those starting values are the `initial` state.
81
+ - **Springs:** search the bundle for `stiffness`, `damping` and `mass`. The defaults are `stiffness: 100, damping: 10`.
82
+
83
+ ## Webflow IX2
84
+
85
+ - Interactions are compiled into the `webflow.js` bundle as JSON (`"ix2"`, `"events"`, `"actionLists"`). Search for `actionItemGroups` to find the keyframes, durations, easings and triggers: scroll into view, while scrolling, hover, click, page load.
86
+ - `data-w-id` on an element links it to its interaction.
87
+ - The compiled site CSS is reusable. It contains the layout and the starting states.
88
+ - Rebuild "while scrolling in view" interactions as scroll-linked transforms over the element's view progress, and "scroll into view" as `useInViewOnce` plus a CSS transition. Hover maps to CSS `:hover` or a small state hook.
89
+
90
+ ## Reveal on scroll
91
+
92
+ AOS (`data-aos`), Squarespace section animations and most WordPress animation plugins are simple: a class toggles when an element enters the viewport.
93
+
94
+ - Use one `useInViewOnce` hook and CSS transitions.
95
+ - Copy the durations, delays and offsets from the attributes or the plugin CSS.
96
+
97
+ ## Split text
98
+
99
+ Headings animate per line, word or character, and the capture flags split markers or a SplitText import.
100
+
101
+ - Split in React. Render spans with `aria-hidden` and keep the full text for screen readers.
102
+ - Stagger the spans with CSS `transition-delay` or a GSAP stagger.
103
+ - Line splitting depends on the font, so match the fonts first.
104
+
105
+ ## Sticky and pinned sections
106
+
107
+ - A pinned section is a tall wrapper holding a sticky, viewport-sized child. Progress is the scroll through the wrapper divided by (wrapper height − viewport height).
108
+ - Match the wrapper heights per viewport. They are listed in the layout outline. Mobile often uses different heights or no pin at all.
109
+ - `useScrollProgress(ref)` drives JS effects. For pure CSS effects, a view timeline on the wrapper is enough.
110
+
111
+ ## Canvas drawing
112
+
113
+ Canvas output is produced by code, not markup.
114
+
115
+ - Find the drawing code: search the scripts for `getContext('2d')`, `requestAnimationFrame` and `drawImage`. Prettify it and read the data source, which is often a JSON endpoint in the capture's `assets/`.
116
+ - Port the logic into a typed module under `src/lib/<feature>/`: layout, renderer and input handling. Wrap it in a lazy component that owns the canvas, the resize observer, the device pixel ratio and cleanup.
117
+ - **Make it deterministic:** seed the PRNG and fix the simulation steps. Then iterate against screenshots at the same offsets until the composition, density and colours match.
118
+ - **Portrait screens** often need a different layout, such as a rotated or re-fit composition. Check the mobile shots.
119
+
120
+ ## WebGL
121
+
122
+ - **Locate the shaders:** search for `gl_FragColor`, `precision highp float`, `void main` and `#version 300 es`. Then find the uniforms (`uTime`, `uMouse`, `uScroll`) and how they are driven.
123
+ - **Port the shaders word for word** into `.glsl` strings. Rebuild only the JavaScript around them.
124
+ - **three.js:**
125
+ - Use `@react-three/fiber` with `@react-three/drei`, or plain three in a component.
126
+ - Copy the camera FOV and position, the renderer settings (`toneMapping`, `outputColorSpace`, `antialias`) and the material parameters from the bundle. These are what make colours match.
127
+ - Models (`.glb`, `.gltf`) and textures are in the capture, so reuse them.
128
+ - **OGL or raw WebGL:** write a small component that owns the program, buffers and RAF loop.
129
+ - **Mobile:** many sites drop to a still image or a cheaper scene. Mirror what the mobile capture shows.
130
+ - **Verification:** headless Chrome renders with SwiftShader. Screenshots are correct but slow, so allow a longer settle.
131
+
132
+ ## Rive, Lottie, Spline
133
+
134
+ - **Rive:** reuse the captured `.riv` with `@rive-app/canvas`.
135
+ - Find the artboard, the state machine name and the input names in the bundle (`stateMachines:`, `artboard:`), and drive the inputs the same way: scroll, hover or in view.
136
+ - Pause it off-screen.
137
+ - **Lottie:** reuse the JSON or `.lottie` with `lottie-web` or `@lottiefiles/dotlottie-react`. Match `loop`, `autoplay` and any scroll-scrubbed `goToAndStop`.
138
+ - **Spline:** reuse the `.splinecode` URL with `@splinetool/react-spline`, lazy-loaded.
139
+
140
+ ## Video
141
+
142
+ - Reuse the captured files and posters with `muted`, `playsInline`, `loop` and `preload="metadata"`.
143
+ - Play only in view and pause off-screen. Under reduced motion, show the poster.
144
+ - Range-request videos are re-downloaded whole at capture time. If one is missing, the mirror's misses list shows it.
145
+ - In compare, video frames always differ, so judge those frames by eye.
146
+
147
+ ## Carousels
148
+
149
+ - Use the original library (Swiper, Splide) when it is detected, or `embla-carousel-react`.
150
+ - Copy the breakpoints, slides per view, gap, speed and autoplay timing from the init call in the bundle.
151
+
152
+ ## Hover and interaction states
153
+
154
+ - `fingerprint.json → hoverRules` lists the `:hover` rules found in the stylesheets. Reused CSS brings them along for free.
155
+ - JS hover effects, such as magnetic buttons, cursor followers and tilt cards, are in the scripts. Search for `mousemove` and `pointermove`.
156
+ - Rebuild menus, dropdowns and dialogs with real semantics: a native `<dialog>`, `aria-expanded`, a focus trap and Escape to close.
@@ -0,0 +1,5 @@
1
+ dist
2
+ node_modules
3
+ package-lock.json
4
+ public
5
+ src/styles/original
@@ -0,0 +1,6 @@
1
+ {
2
+ "singleQuote": true,
3
+ "semi": true,
4
+ "trailingComma": "all",
5
+ "printWidth": 100
6
+ }
@@ -0,0 +1,38 @@
1
+ import { defineConfig, globalIgnores } from 'eslint/config';
2
+ import js from '@eslint/js';
3
+ import globals from 'globals';
4
+ import reactHooks from 'eslint-plugin-react-hooks';
5
+ import { reactRefresh } from 'eslint-plugin-react-refresh';
6
+ import tseslint from 'typescript-eslint';
7
+
8
+ export default defineConfig(
9
+ globalIgnores(['dist', 'node_modules']),
10
+ {
11
+ files: ['**/*.{ts,tsx}'],
12
+ extends: [
13
+ js.configs.recommended,
14
+ tseslint.configs.strictTypeChecked,
15
+ tseslint.configs.stylisticTypeChecked,
16
+ reactHooks.configs.flat['recommended-latest'],
17
+ reactRefresh.configs.vite(),
18
+ ],
19
+ languageOptions: {
20
+ ecmaVersion: 2022,
21
+ globals: globals.browser,
22
+ parserOptions: {
23
+ projectService: true,
24
+ tsconfigRootDir: import.meta.dirname,
25
+ },
26
+ },
27
+ rules: {
28
+ '@typescript-eslint/consistent-type-imports': 'error',
29
+ '@typescript-eslint/restrict-template-expressions': ['error', { allowNumber: true }],
30
+ 'no-console': ['warn', { allow: ['warn', 'error'] }],
31
+ },
32
+ },
33
+ {
34
+ files: ['eslint.config.js'],
35
+ extends: [js.configs.recommended],
36
+ languageOptions: { globals: globals.node },
37
+ },
38
+ );
@@ -0,0 +1,4 @@
1
+ node_modules
2
+ dist
3
+ *.local
4
+ .DS_Store
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{title}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "preview": "vite preview --port {{previewPort}} --strictPort",
10
+ "typecheck": "tsc -b",
11
+ "lint": "eslint .",
12
+ "format": "prettier --write .",
13
+ "format:check": "prettier --check .",
14
+ "check": "npm run typecheck && npm run lint && npm run format:check && vite build"
15
+ },
16
+ "dependencies": {
17
+ "lenis": "1.3.27",
18
+ "react": "19.3.1",
19
+ "react-dom": "19.3.1"
20
+ },
21
+ "devDependencies": {
22
+ "@eslint/js": "10.0.1",
23
+ "@types/node": "26.6.6",
24
+ "@types/react": "19.3.0",
25
+ "@types/react-dom": "19.3.0",
26
+ "@vitejs/plugin-react": "6.1.2",
27
+ "eslint": "10.14.0",
28
+ "eslint-plugin-react-hooks": "7.1.1",
29
+ "eslint-plugin-react-refresh": "0.5.7",
30
+ "globals": "17.13.0",
31
+ "prettier": "3.9.10",
32
+ "typescript": "6.0.3",
33
+ "typescript-eslint": "8.72.1",
34
+ "vite": "8.3.5"
35
+ }
36
+ }
@@ -0,0 +1,15 @@
1
+ import { SmoothScroll } from '@/providers/SmoothScroll';
2
+ import { Page } from '@/app/Page';
3
+ import { routes } from '@/app/routes';
4
+
5
+ // Links are plain anchors (full page loads), so the route is read once per load.
6
+ const path = window.location.pathname.replace(/\/+$/, '') || '/';
7
+ const Current = routes[path] ?? Page;
8
+
9
+ export function App() {
10
+ return (
11
+ <SmoothScroll>
12
+ <Current />
13
+ </SmoothScroll>
14
+ );
15
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Fallback page. `multiclone draft` writes real pages to src/pages and sections to src/sections,
3
+ * then lists them in src/app/routes.ts.
4
+ */
5
+ export function Page() {
6
+ return (
7
+ <main id="main" style={{ padding: '4rem', fontFamily: 'system-ui, sans-serif' }}>
8
+ <p>multiclone is capturing the page. The first draft appears here in a minute or two.</p>
9
+ </main>
10
+ );
11
+ }
@@ -0,0 +1,7 @@
1
+ import type { ComponentType } from 'react';
2
+ import { Page } from '@/app/Page';
3
+
4
+ /** Path → page component. `multiclone draft` regenerates this file for every drafted page. */
5
+ export const routes: Partial<Record<string, ComponentType>> = {
6
+ '/': Page,
7
+ };