@veyralabs/webcloner 0.1.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.
@@ -0,0 +1,209 @@
1
+ # Component Detection — WebCloner Reference
2
+
3
+ Algorithm for detecting component boundaries from `docs/site-manifest.json` → `sections`.
4
+
5
+ ---
6
+
7
+ ## Core Rule
8
+
9
+ **One component = one repeating unit OR one structurally isolated block.**
10
+
11
+ Don't split by DOM depth. Split by visual/functional independence.
12
+
13
+ ---
14
+
15
+ ## Detection Algorithm
16
+
17
+ ### Step 1 — Map top-level sections
18
+
19
+ Start from `manifest.sections`. Each entry in the array is a candidate component boundary.
20
+
21
+ ```
22
+ For each section in manifest.sections:
23
+ - tag is <header> → HeaderNav component
24
+ - tag is <footer> → Footer component
25
+ - tag is <main> → recurse into children
26
+ - tag is <section> or <article> → candidate section component
27
+ ```
28
+
29
+ ### Step 2 — Detect repeating patterns
30
+
31
+ Within a section's `children`, look for repeating structure:
32
+
33
+ ```
34
+ If section.children has N items where N >= 2:
35
+ AND all items share the same tag
36
+ AND all items have similar class patterns
37
+ → This is a list/grid component with repeated cards
38
+ → One parent component + one card sub-component
39
+ ```
40
+
41
+ **Signal:** Items with identical class prefixes at depth 1:
42
+ - `feature-card`, `feature-card`, `feature-card` → `FeatureCard` component
43
+ - `team-member`, `team-member` → `TeamMember` component
44
+ - `pricing-plan`, `pricing-plan` → `PricingCard` component
45
+
46
+ ### Step 3 — Detect functional groups
47
+
48
+ Look for elements that only appear together and serve one function:
49
+
50
+ ```
51
+ headline + subheadline + CTA buttons
52
+ → NOT 3 components. ONE hero content block.
53
+
54
+ tab-nav + tab-panels
55
+ → NOT separate. ONE tabs component with internal state.
56
+
57
+ form label + input + error message + submit
58
+ → ONE form component.
59
+ ```
60
+
61
+ **Rule:** If removing one element makes the others meaningless → they're one component.
62
+
63
+ ### Step 4 — Detect overlay components
64
+
65
+ Elements positioned absolutely or with high z-index are separate components:
66
+ - `position: fixed` + `z-index > 10` → overlay/modal
67
+ - `position: sticky` → sticky bar (separate from section it's in)
68
+ - `overflow: hidden` on parent + transformed child → carousel/slider
69
+
70
+ ---
71
+
72
+ ## Component Boundary Signals
73
+
74
+ | Signal | Component type |
75
+ |--------|---------------|
76
+ | `<header>` tag | HeaderNav |
77
+ | `<footer>` tag | Footer |
78
+ | `<nav>` tag | Navigation (may be inside HeaderNav) |
79
+ | Full-width bg change | New section boundary |
80
+ | `position: fixed`, high `z-index` | Overlay / Modal |
81
+ | `position: sticky` | Sticky bar / floating CTA |
82
+ | N identical sibling structures | List + Card components |
83
+ | Form elements grouped | Form component |
84
+ | `overflow: hidden` + transform | Carousel / Slider |
85
+ | `[data-modal]`, `[role="dialog"]` | Modal component |
86
+ | `[role="tablist"]` + `[role="tab"]` | Tabs component |
87
+
88
+ ---
89
+
90
+ ## Common Sections → Component Names
91
+
92
+ ```
93
+ hero → Hero.tsx
94
+ features → Features.tsx (+ FeatureCard.tsx if cards)
95
+ pricing → Pricing.tsx (+ PricingCard.tsx)
96
+ testimonials → Testimonials.tsx (+ TestimonialCard.tsx)
97
+ team → Team.tsx (+ TeamMember.tsx)
98
+ faq → FAQ.tsx (+ FAQItem.tsx)
99
+ cta-section → CTASection.tsx
100
+ logos / trusted-by → LogoStrip.tsx
101
+ stats → Stats.tsx (+ StatItem.tsx)
102
+ blog-preview → BlogPreview.tsx (+ BlogCard.tsx)
103
+ contact-form → ContactForm.tsx
104
+ newsletter → NewsletterSignup.tsx
105
+ header / nav → HeaderNav.tsx
106
+ footer → Footer.tsx
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Nesting Decision
112
+
113
+ **Flatten aggressively.** The goal is readable components, not a perfect DOM mirror.
114
+
115
+ ```
116
+ BAD — too granular:
117
+ <Section>
118
+ <SectionInner>
119
+ <SectionContent>
120
+ <SectionHeadline />
121
+ ```
122
+
123
+ ```
124
+ GOOD — right level:
125
+ <Features>
126
+ <h2>Headline</h2>
127
+ {features.map(f => <FeatureCard key={f.id} {...f} />)}
128
+ ```
129
+
130
+ **Rule:** Create a sub-component only if:
131
+ 1. It repeats (N >= 2 instances), OR
132
+ 2. It has its own behavior state, OR
133
+ 3. It's independently reusable across the page
134
+
135
+ ---
136
+
137
+ ## Props Extraction
138
+
139
+ For each component, identify what varies vs what's fixed:
140
+
141
+ **Fixed** (hardcode in component):
142
+ - Layout structure
143
+ - CSS classes / styles
144
+ - Animation parameters
145
+
146
+ **Variable** (extract as props):
147
+ - Text content
148
+ - Images/icons
149
+ - URLs/hrefs
150
+ - Counts/numbers
151
+ - Boolean toggles
152
+
153
+ Example for FeatureCard:
154
+ ```typescript
155
+ interface FeatureCardProps {
156
+ icon: string; // path to public/images/icon-*.svg
157
+ title: string;
158
+ description: string;
159
+ href?: string;
160
+ }
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Complexity Score
166
+
167
+ Rate each component before dispatching builder agents:
168
+
169
+ | Score | Criteria | Estimated build time |
170
+ |-------|----------|---------------------|
171
+ | 1 — Simple | Static, no behavior, <= 3 elements | 10-20 min |
172
+ | 2 — Medium | Hover states OR responsive reflow | 20-40 min |
173
+ | 3 — Complex | Click behavior + multiple states | 40-90 min |
174
+ | 4 — Hard | Scroll animation + GSAP/Framer | 90-180 min |
175
+ | 5 — Very hard | Multiple behaviors + complex responsive | 3+ hours |
176
+
177
+ Build order: simple first (1-2), then medium (3), then complex (4-5).
178
+ Complex components should get extra spec detail before dispatch.
179
+
180
+ ---
181
+
182
+ ## Red Flags — Components to Watch
183
+
184
+ **Tabs with scroll-driven content:**
185
+ Some designs show tabs that switch based on scroll position. Confirm by checking:
186
+ ```javascript
187
+ // In browser console
188
+ ScrollTrigger.getAll().forEach(st => console.log(st.trigger, st.vars))
189
+ ```
190
+ If trigger is the tab content area → scroll-driven.
191
+ If not → click-driven. Decision critical before building.
192
+
193
+ **Carousels with touch:**
194
+ Mobile swipe carousels need extra handling. Check:
195
+ ```javascript
196
+ document.querySelectorAll('[class*="swiper"], [class*="splide"], [class*="glide"]')
197
+ ```
198
+ If found → use the same library rather than rebuilding from scratch.
199
+
200
+ **Sticky + scroll behavior:**
201
+ Components with both `position: sticky` AND scroll-triggered style changes are two behaviors on one element.
202
+ Document both separately in the behavior spec.
203
+
204
+ **Lazy-loaded sections:**
205
+ Some sections only render after scroll. Scrapling's scroll-and-wait handles this, but verify:
206
+ ```javascript
207
+ manifest.sections.length // should match visible section count on page
208
+ ```
209
+ If low count → re-run `extract.py` with longer scroll delay.
@@ -0,0 +1,328 @@
1
+ # Stack Presets — WebCloner Reference
2
+
3
+ Output configs when the clone target is NOT Next.js.
4
+ Default stack is Next.js 15 + TypeScript + Tailwind v4 + shadcn.
5
+ Use these presets when the user requests a different framework.
6
+
7
+ ---
8
+
9
+ ## When to Use Alternate Stacks
10
+
11
+ | Use case | Recommended stack |
12
+ |----------|------------------|
13
+ | Default / most cases | Next.js 15 |
14
+ | Static site, no JS needed | Astro |
15
+ | Vue ecosystem / Nuxt site clone | Nuxt 3 |
16
+ | Svelte preference | SvelteKit |
17
+ | Shopify storefront | Hydrogen (Remix-based) |
18
+ | Existing React project | Vite + React |
19
+
20
+ ---
21
+
22
+ ## Astro
23
+
24
+ **Best for:** Static marketing sites, portfolios, blogs. Zero JS by default.
25
+
26
+ ### Setup
27
+
28
+ ```bash
29
+ npm create astro@latest clone -- --template minimal --typescript strict --install --no-git
30
+ cd clone
31
+ npx astro add tailwind
32
+ npx astro add react # only if interactive components needed
33
+ ```
34
+
35
+ ### File structure
36
+
37
+ ```
38
+ src/
39
+ layouts/
40
+ Layout.astro ← base layout (fonts, globals)
41
+ components/
42
+ Hero.astro ← static sections as .astro
43
+ Features.astro
44
+ FeatureCard.astro
45
+ Header.astro
46
+ Footer.astro
47
+ Tabs.tsx ← interactive components as .tsx (React island)
48
+ pages/
49
+ index.astro ← assemble all sections
50
+ public/
51
+ images/
52
+ fonts/
53
+ ```
54
+
55
+ ### Component template
56
+
57
+ ```astro
58
+ ---
59
+ // Hero.astro
60
+ interface Props {
61
+ headline: string;
62
+ subheadline: string;
63
+ ctaText: string;
64
+ ctaHref: string;
65
+ }
66
+ const { headline, subheadline, ctaText, ctaHref } = Astro.props;
67
+ ---
68
+
69
+ <section class="hero">
70
+ <h1>{headline}</h1>
71
+ <p>{subheadline}</p>
72
+ <a href={ctaHref}>{ctaText}</a>
73
+ </section>
74
+
75
+ <style>
76
+ /* scoped styles */
77
+ </style>
78
+ ```
79
+
80
+ ### Interactive islands
81
+
82
+ Components that need JS (tabs, carousels, modals) use React with `client:load`:
83
+
84
+ ```astro
85
+ ---
86
+ import Tabs from '../components/Tabs.tsx';
87
+ ---
88
+ <Tabs client:load items={tabData} />
89
+ ```
90
+
91
+ ### Animation libraries
92
+
93
+ - Lenis: use `client:only="react"` wrapper
94
+ - GSAP: inline `<script>` tag in the Astro component
95
+ - AOS: add `data-aos` attributes, init in `<script>` in Layout.astro
96
+
97
+ ### Build + preview
98
+
99
+ ```bash
100
+ npm run build
101
+ npm run preview
102
+ ```
103
+
104
+ ---
105
+
106
+ ## Nuxt 3
107
+
108
+ **Best for:** Cloning Vue/Nuxt sites, SSR marketing sites.
109
+
110
+ ### Setup
111
+
112
+ ```bash
113
+ npx nuxi@latest init clone
114
+ cd clone
115
+ npm install
116
+ npx nuxi@latest module add tailwindcss
117
+ ```
118
+
119
+ ### File structure
120
+
121
+ ```
122
+ components/
123
+ sections/
124
+ Hero.vue
125
+ Features.vue
126
+ FeatureCard.vue
127
+ layout/
128
+ Header.vue
129
+ Footer.vue
130
+ pages/
131
+ index.vue ← assemble sections
132
+ app.vue ← root (fonts, global providers)
133
+ assets/
134
+ css/
135
+ main.css ← globals, tokens
136
+ public/
137
+ images/
138
+ fonts/
139
+ ```
140
+
141
+ ### Component template
142
+
143
+ ```vue
144
+ <!-- components/sections/Hero.vue -->
145
+ <script setup lang="ts">
146
+ defineProps<{
147
+ headline: string
148
+ subheadline: string
149
+ ctaText: string
150
+ ctaHref: string
151
+ }>()
152
+ </script>
153
+
154
+ <template>
155
+ <section class="hero">
156
+ <h1>{{ headline }}</h1>
157
+ <p>{{ subheadline }}</p>
158
+ <a :href="ctaHref">{{ ctaText }}</a>
159
+ </section>
160
+ </template>
161
+ ```
162
+
163
+ ### Page assembly
164
+
165
+ ```vue
166
+ <!-- pages/index.vue -->
167
+ <template>
168
+ <main>
169
+ <Hero v-bind="heroData" />
170
+ <Features :items="features" />
171
+ <Footer />
172
+ </main>
173
+ </template>
174
+ ```
175
+
176
+ ### Animation libraries
177
+
178
+ - Lenis: create `plugins/lenis.client.ts`
179
+ - GSAP: `npm install gsap`, use in `onMounted` hook
180
+ - Framer Motion: NOT available for Vue — use Motion One (`npm install motion`) instead
181
+
182
+ ### Build
183
+
184
+ ```bash
185
+ npm run build
186
+ npm run preview
187
+ ```
188
+
189
+ ---
190
+
191
+ ## SvelteKit
192
+
193
+ **Best for:** Performance-sensitive sites, minimal bundle preference.
194
+
195
+ ### Setup
196
+
197
+ ```bash
198
+ npm create svelte@latest clone
199
+ # Choose: skeleton, TypeScript, ESLint, Prettier
200
+ cd clone
201
+ npm install
202
+ npx svelte-add@latest tailwindcss
203
+ ```
204
+
205
+ ### File structure
206
+
207
+ ```
208
+ src/
209
+ lib/
210
+ components/
211
+ sections/
212
+ Hero.svelte
213
+ Features.svelte
214
+ FeatureCard.svelte
215
+ layout/
216
+ Header.svelte
217
+ Footer.svelte
218
+ routes/
219
+ +layout.svelte ← root layout (fonts, providers)
220
+ +page.svelte ← assemble sections
221
+ static/
222
+ images/
223
+ fonts/
224
+ ```
225
+
226
+ ### Component template
227
+
228
+ ```svelte
229
+ <!-- src/lib/components/sections/Hero.svelte -->
230
+ <script lang="ts">
231
+ export let headline: string;
232
+ export let subheadline: string;
233
+ export let ctaText: string;
234
+ export let ctaHref: string;
235
+ </script>
236
+
237
+ <section class="hero">
238
+ <h1>{headline}</h1>
239
+ <p>{subheadline}</p>
240
+ <a href={ctaHref}>{ctaText}</a>
241
+ </section>
242
+ ```
243
+
244
+ ### Animation
245
+
246
+ - Svelte has built-in `transition:` and `animate:` directives — use for simple animations
247
+ - GSAP: works normally in `onMount`
248
+ - Lenis: instantiate in `+layout.svelte` `onMount`
249
+
250
+ ### Build
251
+
252
+ ```bash
253
+ npm run build
254
+ npm run preview
255
+ ```
256
+
257
+ ---
258
+
259
+ ## Vite + React (no Next.js)
260
+
261
+ **Best for:** Adding to existing React project, no SSR needed.
262
+
263
+ ### Setup
264
+
265
+ ```bash
266
+ npm create vite@latest clone -- --template react-ts
267
+ cd clone
268
+ npm install
269
+ npm install -D tailwindcss postcss autoprefixer
270
+ npx tailwindcss init -p
271
+ ```
272
+
273
+ ### Key differences from Next.js
274
+
275
+ | Next.js | Vite + React |
276
+ |---------|-------------|
277
+ | `next/image` | `<img>` with manual width/height |
278
+ | `app/page.tsx` | `src/App.tsx` |
279
+ | `app/layout.tsx` | `src/main.tsx` (providers here) |
280
+ | `public/` | `public/` (same) |
281
+ | Server components | Client only — everything is `'use client'` |
282
+
283
+ ### No file-based routing
284
+
285
+ Single page → `src/App.tsx` assembles sections directly.
286
+ Multi-page → add `react-router-dom`.
287
+
288
+ ---
289
+
290
+ ## Shopify Hydrogen
291
+
292
+ **Best for:** Ecommerce storefronts (product pages, collections, cart).
293
+
294
+ ### Setup
295
+
296
+ ```bash
297
+ npm create @shopify/hydrogen@latest clone
298
+ # Choose: hello-world template, TypeScript
299
+ ```
300
+
301
+ ### Key concepts
302
+
303
+ - Hydrogen is Remix-based — routes in `app/routes/`
304
+ - Product data from Storefront API (GraphQL)
305
+ - Use `<Image>` from `@shopify/hydrogen` for optimized images
306
+ - Cart via `useCart()` hook
307
+
308
+ ### Scope warning
309
+
310
+ Hydrogen is complex. WebCloner for Shopify = **visual layer only**.
311
+ Use clone for: storefront layout, product card design, typography, colors.
312
+ Do NOT clone: checkout flow, payment processing, account pages.
313
+
314
+ ---
315
+
316
+ ## Switching Stacks Mid-Project
317
+
318
+ If user requests a stack change after spec files are written:
319
+ 1. Specs are stack-agnostic — reuse them completely
320
+ 2. Only the builder agent prompt changes (stack section)
321
+ 3. Global tokens (colors, fonts, spacing) copy directly to any stack's equivalent globals file
322
+ 4. Assets in `public/` are universal — no changes needed
323
+
324
+ Update Phase 4 builder prompt constraint line:
325
+ ```
326
+ - Stack: [Astro / Nuxt 3 / SvelteKit / Vite+React] + TypeScript + Tailwind
327
+ ```
328
+ Everything else in the prompt stays the same.
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * compare.mjs — Visual regression: screenshot original vs clone side by side.
4
+ *
5
+ * Usage:
6
+ * node scripts/compare.mjs <original-url> <clone-url>
7
+ *
8
+ * Requires: playwright
9
+ * npm install playwright && npx playwright install chromium
10
+ *
11
+ * Output: docs/qa/compare-desktop.png, docs/qa/compare-mobile.png, docs/qa/report.json
12
+ */
13
+
14
+ import { chromium } from 'playwright';
15
+ import fs from 'fs';
16
+ import path from 'path';
17
+
18
+ const [,, originalUrl, cloneUrl] = process.argv;
19
+
20
+ if (!originalUrl || !cloneUrl) {
21
+ console.error('Usage: node scripts/compare.mjs <original-url> <clone-url>');
22
+ process.exit(1);
23
+ }
24
+
25
+ const VIEWPORTS = [
26
+ { name: 'desktop', width: 1440, height: 900 },
27
+ { name: 'mobile', width: 390, height: 844 },
28
+ ];
29
+
30
+ fs.mkdirSync('docs/qa', { recursive: true });
31
+
32
+ const browser = await chromium.launch();
33
+ const report = { original: originalUrl, clone: cloneUrl, viewports: [] };
34
+
35
+ for (const vp of VIEWPORTS) {
36
+ console.log(`Capturing ${vp.name} (${vp.width}x${vp.height})...`);
37
+
38
+ const context = await browser.newContext({ viewport: { width: vp.width, height: vp.height } });
39
+
40
+ const [pageA, pageB] = await Promise.all([context.newPage(), context.newPage()]);
41
+
42
+ await Promise.all([
43
+ pageA.goto(originalUrl, { waitUntil: 'networkidle' }),
44
+ pageB.goto(cloneUrl, { waitUntil: 'networkidle' }),
45
+ ]);
46
+
47
+ await Promise.all([pageA.waitForTimeout(1500), pageB.waitForTimeout(1500)]);
48
+
49
+ const [shotA, shotB] = await Promise.all([
50
+ pageA.screenshot({ fullPage: true }),
51
+ pageB.screenshot({ fullPage: true }),
52
+ ]);
53
+
54
+ const origPath = `docs/qa/original-${vp.name}.png`;
55
+ const clonePath = `docs/qa/clone-${vp.name}.png`;
56
+ fs.writeFileSync(origPath, shotA);
57
+ fs.writeFileSync(clonePath, shotB);
58
+
59
+ // Basic size comparison
60
+ const heightDiff = Math.abs(shotA.length - shotB.length);
61
+ const sizePct = Math.round((heightDiff / shotA.length) * 100);
62
+
63
+ report.viewports.push({
64
+ viewport: vp.name,
65
+ original: origPath,
66
+ clone: clonePath,
67
+ originalSize: shotA.length,
68
+ cloneSize: shotB.length,
69
+ sizeDiffPct: sizePct,
70
+ note: sizePct > 20 ? 'LARGE DIFF — review manually' : 'within range',
71
+ });
72
+
73
+ console.log(` ✓ ${vp.name}: original=${origPath} clone=${clonePath} diff=${sizePct}%`);
74
+ await context.close();
75
+ }
76
+
77
+ await browser.close();
78
+
79
+ fs.writeFileSync('docs/qa/report.json', JSON.stringify(report, null, 2));
80
+
81
+ console.log('\n--- QA Report ---');
82
+ for (const vp of report.viewports) {
83
+ const flag = vp.sizeDiffPct > 20 ? '⚠' : '✓';
84
+ console.log(`${flag} ${vp.viewport}: ${vp.note} (${vp.sizeDiffPct}% size diff)`);
85
+ }
86
+ console.log('\nOpen docs/qa/ to compare screenshots side by side.');
87
+ console.log('Full report: docs/qa/report.json');