@veyralabs/skills 0.1.0 → 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.
- package/bin/cli.js +66 -41
- package/package.json +3 -3
- package/skills/webcloner/SKILL.md +565 -0
- package/skills/webcloner/references/animation-playbook.md +292 -0
- package/skills/webcloner/references/behavior-spec-format.md +259 -0
- package/skills/webcloner/references/component-detection.md +209 -0
- package/skills/webcloner/references/stack-presets.md +328 -0
- package/skills/webcloner/scripts/compare.mjs +87 -0
- package/skills/webcloner/scripts/download-assets.mjs +160 -0
- package/skills/webcloner/scripts/extract.py +344 -0
- package/validate.js +14 -4
- /package/skills/{brandaudit → naming-suite/brandaudit}/SKILL.md +0 -0
- /package/skills/{brandaudit → naming-suite/brandaudit}/references/audit-framework.md +0 -0
- /package/skills/{brandaudit → naming-suite/brandaudit}/references/examples/sample-audits.md +0 -0
- /package/skills/{brandaudit → naming-suite/brandaudit}/references/rebrand-decisions.md +0 -0
- /package/skills/{brandaudit → naming-suite/brandaudit}/references/weakness-patterns.md +0 -0
- /package/skills/{competitornames → naming-suite/competitornames}/SKILL.md +0 -0
- /package/skills/{competitornames → naming-suite/competitornames}/references/examples/sample-analyses.md +0 -0
- /package/skills/{competitornames → naming-suite/competitornames}/references/pattern-analysis.md +0 -0
- /package/skills/{competitornames → naming-suite/competitornames}/references/whitespace-mapping.md +0 -0
- /package/skills/{domainforge → naming-suite/domainforge}/SKILL.md +0 -0
- /package/skills/{domainforge → naming-suite/domainforge}/references/brand-archetypes.md +0 -0
- /package/skills/{domainforge → naming-suite/domainforge}/references/examples/sample-outputs.md +0 -0
- /package/skills/{domainforge → naming-suite/domainforge}/references/naming-patterns.md +0 -0
- /package/skills/{domainforge → naming-suite/domainforge}/references/scoring-rubric.md +0 -0
- /package/skills/{domainforge → naming-suite/domainforge}/references/tld-strategy.md +0 -0
- /package/skills/{namingguide → naming-suite/namingguide}/SKILL.md +0 -0
- /package/skills/{namingguide → naming-suite/namingguide}/references/examples/sample-guides.md +0 -0
- /package/skills/{namingguide → naming-suite/namingguide}/references/guide-structure.md +0 -0
|
@@ -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');
|