@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,292 @@
1
+ # Animation Playbook — WebCloner Reference
2
+
3
+ How to detect, extract, and recreate animations from the 5 most common animation libraries.
4
+
5
+ ---
6
+
7
+ ## 1. CSS Animations (no library)
8
+
9
+ **Detection:** `manifest.animations.libraries` is empty, but `@keyframes` exist in stylesheets.
10
+
11
+ **Extraction from manifest:**
12
+ Look for `animation` or `transition` properties in element `styles`. Example:
13
+ ```
14
+ "animation": "fadeIn 0.6s ease-out forwards"
15
+ "transition": "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
16
+ ```
17
+
18
+ **Recreation:**
19
+ Copy `@keyframes` from the original stylesheet into `globals.css`. Apply via Tailwind's `animate-*`
20
+ or direct CSS class.
21
+
22
+ ```css
23
+ @keyframes fadeIn {
24
+ from { opacity: 0; transform: translateY(20px); }
25
+ to { opacity: 1; transform: translateY(0); }
26
+ }
27
+
28
+ .animate-fade-in {
29
+ animation: fadeIn 0.6s ease-out forwards;
30
+ }
31
+ ```
32
+
33
+ **Scroll-triggered CSS animations:**
34
+ If `IntersectionObserver` is driving class toggles, recreate with:
35
+ ```typescript
36
+ 'use client';
37
+ import { useInView } from 'react-intersection-observer';
38
+
39
+ const { ref, inView } = useInView({ threshold: 0.2, triggerOnce: true });
40
+ return <div ref={ref} className={inView ? 'animate-fade-in' : 'opacity-0'} />;
41
+ ```
42
+
43
+ ---
44
+
45
+ ## 2. Lenis (smooth scroll)
46
+
47
+ **Detection:** `manifest.animations.libraries` includes `"lenis"`.
48
+
49
+ **What it does:** Replaces native browser scroll with a physics-based smooth scroll.
50
+ Without it, the clone will feel "snappy" vs the original's "buttery" feel.
51
+
52
+ **Installation:**
53
+ ```bash
54
+ npm install lenis
55
+ ```
56
+
57
+ **Recreation:**
58
+ ```typescript
59
+ // src/components/LenisProvider.tsx
60
+ 'use client';
61
+ import { useEffect } from 'react';
62
+ import Lenis from 'lenis';
63
+
64
+ export function LenisProvider({ children }: { children: React.ReactNode }) {
65
+ useEffect(() => {
66
+ const lenis = new Lenis({
67
+ duration: 1.2, // match original — inspect window.lenis?.options
68
+ easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
69
+ orientation: 'vertical',
70
+ smoothWheel: true,
71
+ });
72
+
73
+ function raf(time: number) {
74
+ lenis.raf(time);
75
+ requestAnimationFrame(raf);
76
+ }
77
+ requestAnimationFrame(raf);
78
+
79
+ return () => lenis.destroy();
80
+ }, []);
81
+
82
+ return <>{children}</>;
83
+ }
84
+ ```
85
+
86
+ Wrap `app/layout.tsx` body with `<LenisProvider>`.
87
+
88
+ **Getting original config:** In browser console on original site:
89
+ ```javascript
90
+ window.lenis?.options // duration, easing, lerp, etc.
91
+ ```
92
+
93
+ ---
94
+
95
+ ## 3. GSAP + ScrollTrigger
96
+
97
+ **Detection:** `manifest.animations.libraries` includes `"gsap"`.
98
+
99
+ **Installation:**
100
+ ```bash
101
+ npm install gsap
102
+ ```
103
+
104
+ **Extraction approach:**
105
+ In browser console on original site:
106
+ ```javascript
107
+ // List all active tweens
108
+ gsap.globalTimeline.getChildren().forEach(t => {
109
+ console.log({
110
+ target: t.targets?.(),
111
+ duration: t.duration(),
112
+ vars: t.vars,
113
+ });
114
+ });
115
+
116
+ // List all ScrollTrigger instances
117
+ ScrollTrigger.getAll().forEach(st => {
118
+ console.log({
119
+ trigger: st.trigger,
120
+ start: st.start,
121
+ end: st.end,
122
+ scrub: st.vars.scrub,
123
+ pin: st.vars.pin,
124
+ });
125
+ });
126
+ ```
127
+
128
+ **Recreation pattern:**
129
+ ```typescript
130
+ 'use client';
131
+ import { useEffect, useRef } from 'react';
132
+ import gsap from 'gsap';
133
+ import { ScrollTrigger } from 'gsap/ScrollTrigger';
134
+
135
+ gsap.registerPlugin(ScrollTrigger);
136
+
137
+ export function AnimatedSection() {
138
+ const ref = useRef<HTMLDivElement>(null);
139
+
140
+ useEffect(() => {
141
+ const ctx = gsap.context(() => {
142
+ gsap.from('.headline', {
143
+ y: 60,
144
+ opacity: 0,
145
+ duration: 0.8,
146
+ ease: 'power2.out',
147
+ scrollTrigger: {
148
+ trigger: ref.current,
149
+ start: 'top 80%',
150
+ toggleActions: 'play none none none',
151
+ },
152
+ });
153
+ }, ref);
154
+
155
+ return () => ctx.revert();
156
+ }, []);
157
+
158
+ return <div ref={ref}>...</div>;
159
+ }
160
+ ```
161
+
162
+ **Common GSAP patterns to look for:**
163
+ - `scrub: true` — animation tied to scroll position (not time-based)
164
+ - `pin: true` — section stays fixed while scroll animation plays
165
+ - `stagger` — sequential animation of multiple elements
166
+ - `timeline` — chained sequence of animations
167
+
168
+ ---
169
+
170
+ ## 4. Framer Motion
171
+
172
+ **Detection:** `manifest.animations.libraries` includes `"framer-motion"`.
173
+
174
+ **Installation:**
175
+ ```bash
176
+ npm install framer-motion
177
+ ```
178
+
179
+ **Extraction:** Framer Motion animations are React component props. In React DevTools,
180
+ inspect the component tree and look for `motion.*` elements with `initial`, `animate`,
181
+ `whileInView`, `variants` props.
182
+
183
+ If DevTools not available, infer from visual behavior:
184
+ - Fade in on scroll → `whileInView={{ opacity: 1 }}` with `initial={{ opacity: 0 }}`
185
+ - Slide up on scroll → add `y: 0` animate, `y: 40` initial
186
+ - Hover scale → `whileHover={{ scale: 1.05 }}`
187
+ - Exit animation → `exit={{ opacity: 0 }}`
188
+
189
+ **Recreation pattern:**
190
+ ```typescript
191
+ import { motion } from 'framer-motion';
192
+
193
+ const fadeUp = {
194
+ hidden: { opacity: 0, y: 40 },
195
+ visible: { opacity: 1, y: 0, transition: { duration: 0.6, ease: 'easeOut' } },
196
+ };
197
+
198
+ export function FeatureCard() {
199
+ return (
200
+ <motion.div
201
+ variants={fadeUp}
202
+ initial="hidden"
203
+ whileInView="visible"
204
+ viewport={{ once: true, margin: '-100px' }}
205
+ >
206
+ ...
207
+ </motion.div>
208
+ );
209
+ }
210
+ ```
211
+
212
+ **Stagger children:**
213
+ ```typescript
214
+ const container = {
215
+ hidden: {},
216
+ visible: { transition: { staggerChildren: 0.1 } },
217
+ };
218
+ ```
219
+
220
+ ---
221
+
222
+ ## 5. AOS (Animate on Scroll)
223
+
224
+ **Detection:** `manifest.animations.libraries` includes `"aos"` or elements have `data-aos` attributes.
225
+
226
+ **Extraction:**
227
+ ```javascript
228
+ // In browser console — get all AOS attributes
229
+ document.querySelectorAll('[data-aos]').forEach(el => {
230
+ console.log({
231
+ selector: el.className,
232
+ aos: el.dataset.aos,
233
+ duration: el.dataset.aosDuration,
234
+ delay: el.dataset.aosDelay,
235
+ easing: el.dataset.aosEasing,
236
+ });
237
+ });
238
+ ```
239
+
240
+ **Recreation options:**
241
+
242
+ Option A — Use AOS directly:
243
+ ```bash
244
+ npm install aos
245
+ ```
246
+ ```typescript
247
+ 'use client';
248
+ import { useEffect } from 'react';
249
+ import AOS from 'aos';
250
+ import 'aos/dist/aos.css';
251
+
252
+ export function AOSProvider({ children }) {
253
+ useEffect(() => { AOS.init({ duration: 800, once: true }); }, []);
254
+ return <>{children}</>;
255
+ }
256
+ ```
257
+ Then add `data-aos="fade-up"` attributes to match original.
258
+
259
+ Option B — Replace with Framer Motion (cleaner, no extra dependency):
260
+ Map AOS animation names to Framer Motion variants.
261
+ `fade-up` → `{ hidden: { opacity: 0, y: 30 }, visible: { opacity: 1, y: 0 } }`
262
+
263
+ ---
264
+
265
+ ## Animation Decision Tree
266
+
267
+ ```
268
+ Is the animation triggered by scroll position?
269
+ → YES: Is it scrubbed (tied to scroll position, not time)?
270
+ → YES: GSAP with scrub: true
271
+ → NO: GSAP ScrollTrigger OR Framer Motion whileInView OR IntersectionObserver
272
+ → NO: Is it triggered on click?
273
+ → YES: CSS transition OR Framer Motion AnimatePresence
274
+ → NO: Is it on hover?
275
+ → YES: CSS :hover transition OR Framer Motion whileHover
276
+ → NO: Is it on page load?
277
+ → YES: CSS animation OR Framer Motion initial/animate
278
+ ```
279
+
280
+ ---
281
+
282
+ ## Timing Extraction
283
+
284
+ If you can't get library config, estimate from visual inspection:
285
+
286
+ | Visual feel | Duration estimate | Easing |
287
+ |-------------|------------------|--------|
288
+ | Instant snap | 150-200ms | linear |
289
+ | Quick and clean | 200-300ms | ease-out |
290
+ | Smooth and polished | 300-500ms | cubic-bezier(0.4, 0, 0.2, 1) |
291
+ | Deliberate and dramatic | 600-900ms | power2.out (GSAP) |
292
+ | Scroll-scrubbed | no duration | scrub: true |
@@ -0,0 +1,259 @@
1
+ # Behavior Spec Format — WebCloner Reference
2
+
3
+ YAML schema for describing interactive behaviors in component specs.
4
+ Paste into the `## States & Behaviors` section of each `docs/specs/*.spec.md` file.
5
+
6
+ ---
7
+
8
+ ## Schema
9
+
10
+ ```yaml
11
+ behaviors:
12
+ - name: string # human label, e.g. "Tab switch"
13
+ trigger:
14
+ type: click | hover | scroll | load | resize
15
+ selector: string # CSS selector of the interactive element
16
+ condition: string # optional — e.g. "scrollY > 80", "viewport < 768"
17
+ states:
18
+ [state-name]:
19
+ [property]: [value] # visual/content properties in this state
20
+ transition:
21
+ duration: string # e.g. "200ms"
22
+ easing: string # e.g. "ease-out", "cubic-bezier(0.4, 0, 0.2, 1)"
23
+ property: string # optional — which CSS property animates (all = default)
24
+ notes: string # optional — anything that doesn't fit above
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Examples by Behavior Type
30
+
31
+ ### Click — Tab Switch
32
+
33
+ ```yaml
34
+ behaviors:
35
+ - name: "Feature tabs"
36
+ trigger:
37
+ type: click
38
+ selector: ".tab-button"
39
+ states:
40
+ default:
41
+ activeIndex: 0
42
+ content: "First tab content visible"
43
+ activeTab:
44
+ background: "#000"
45
+ color: "#fff"
46
+ inactiveTab:
47
+ background: "transparent"
48
+ color: "#666"
49
+ tab-2:
50
+ activeIndex: 1
51
+ content: "Second tab content visible"
52
+ tab-3:
53
+ activeIndex: 2
54
+ content: "Third tab content visible"
55
+ transition:
56
+ duration: 200ms
57
+ easing: ease-out
58
+ property: opacity
59
+ ```
60
+
61
+ ### Scroll — Header Transform
62
+
63
+ ```yaml
64
+ behaviors:
65
+ - name: "Sticky header"
66
+ trigger:
67
+ type: scroll
68
+ condition: "scrollY > 80"
69
+ states:
70
+ default:
71
+ position: fixed
72
+ top: 0
73
+ background: transparent
74
+ backdropFilter: none
75
+ padding: "24px 80px"
76
+ logo: large
77
+ scrolled:
78
+ background: "rgba(255,255,255,0.9)"
79
+ backdropFilter: "blur(12px)"
80
+ padding: "12px 80px"
81
+ boxShadow: "0 1px 0 rgba(0,0,0,0.08)"
82
+ logo: small
83
+ transition:
84
+ duration: 300ms
85
+ easing: "cubic-bezier(0.4, 0, 0.2, 1)"
86
+ property: all
87
+ ```
88
+
89
+ ### Hover — Card
90
+
91
+ ```yaml
92
+ behaviors:
93
+ - name: "Feature card hover"
94
+ trigger:
95
+ type: hover
96
+ selector: ".feature-card"
97
+ states:
98
+ default:
99
+ transform: none
100
+ boxShadow: "0 2px 8px rgba(0,0,0,0.08)"
101
+ borderColor: "#e5e7eb"
102
+ hovered:
103
+ transform: "translateY(-4px)"
104
+ boxShadow: "0 12px 32px rgba(0,0,0,0.12)"
105
+ borderColor: "#000"
106
+ transition:
107
+ duration: 250ms
108
+ easing: ease-out
109
+ property: "transform, box-shadow, border-color"
110
+ ```
111
+
112
+ ### Load — Hero Entrance
113
+
114
+ ```yaml
115
+ behaviors:
116
+ - name: "Hero entrance"
117
+ trigger:
118
+ type: load
119
+ condition: "DOMContentLoaded"
120
+ states:
121
+ before:
122
+ opacity: 0
123
+ transform: "translateY(30px)"
124
+ after:
125
+ opacity: 1
126
+ transform: "translateY(0)"
127
+ transition:
128
+ duration: 800ms
129
+ easing: "cubic-bezier(0.16, 1, 0.3, 1)"
130
+ notes: "Headline first (delay 0), subtitle +200ms, CTA +400ms (stagger)"
131
+ ```
132
+
133
+ ### Scroll-driven — Parallax
134
+
135
+ ```yaml
136
+ behaviors:
137
+ - name: "Hero background parallax"
138
+ trigger:
139
+ type: scroll
140
+ condition: "element in viewport"
141
+ states:
142
+ top:
143
+ backgroundPositionY: "0%"
144
+ bottom:
145
+ backgroundPositionY: "30%"
146
+ transition:
147
+ duration: scrub # scrub = tied to scroll position, not time
148
+ easing: linear
149
+ notes: "GSAP scrub:true — background moves at 0.3x scroll speed"
150
+ ```
151
+
152
+ ### Click — Accordion
153
+
154
+ ```yaml
155
+ behaviors:
156
+ - name: "FAQ accordion"
157
+ trigger:
158
+ type: click
159
+ selector: ".accordion-trigger"
160
+ states:
161
+ closed:
162
+ contentHeight: 0
163
+ overflow: hidden
164
+ icon: "+"
165
+ open:
166
+ contentHeight: auto # animate max-height from 0 to auto
167
+ overflow: visible
168
+ icon: "−"
169
+ transition:
170
+ duration: 300ms
171
+ easing: ease-out
172
+ property: max-height
173
+ notes: "Only one item open at a time. Opening one closes others."
174
+ ```
175
+
176
+ ### Hover — Navigation Dropdown
177
+
178
+ ```yaml
179
+ behaviors:
180
+ - name: "Nav dropdown"
181
+ trigger:
182
+ type: hover
183
+ selector: ".nav-item[data-has-dropdown]"
184
+ states:
185
+ closed:
186
+ dropdownOpacity: 0
187
+ dropdownTransform: "translateY(-8px)"
188
+ dropdownPointerEvents: none
189
+ open:
190
+ dropdownOpacity: 1
191
+ dropdownTransform: "translateY(0)"
192
+ dropdownPointerEvents: auto
193
+ transition:
194
+ duration: 200ms
195
+ easing: ease-out
196
+ notes: "Close on mouseleave with 100ms delay to allow cursor travel to dropdown"
197
+ ```
198
+
199
+ ---
200
+
201
+ ## Multi-state Components
202
+
203
+ When a component has more than 2 states, list all states explicitly:
204
+
205
+ ```yaml
206
+ behaviors:
207
+ - name: "Carousel"
208
+ trigger:
209
+ type: click
210
+ selector: ".carousel-arrow"
211
+ states:
212
+ slide-1: { activeIndex: 0, transform: "translateX(0%)" }
213
+ slide-2: { activeIndex: 1, transform: "translateX(-100%)" }
214
+ slide-3: { activeIndex: 2, transform: "translateX(-200%)" }
215
+ transition:
216
+ duration: 400ms
217
+ easing: "cubic-bezier(0.4, 0, 0.2, 1)"
218
+ notes: "Loop: last → first. Dots sync with activeIndex."
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Scroll-triggered Entrance Animations
224
+
225
+ These use IntersectionObserver (or ScrollTrigger) — not pure scroll position.
226
+
227
+ ```yaml
228
+ behaviors:
229
+ - name: "Section entrance"
230
+ trigger:
231
+ type: scroll
232
+ condition: "element enters viewport at threshold 0.2"
233
+ states:
234
+ before:
235
+ opacity: 0
236
+ transform: "translateY(40px)"
237
+ visible:
238
+ opacity: 1
239
+ transform: "translateY(0)"
240
+ transition:
241
+ duration: 600ms
242
+ easing: "cubic-bezier(0.16, 1, 0.3, 1)"
243
+ notes: "triggerOnce: true — does not reverse on scroll up"
244
+ ```
245
+
246
+ ---
247
+
248
+ ## Implementation Mapping
249
+
250
+ | Behavior type | Recommended implementation |
251
+ |---------------|---------------------------|
252
+ | CSS-only hover/focus | Tailwind `hover:` / `focus:` classes |
253
+ | Click toggle (React state) | `useState` + conditional className |
254
+ | Scroll position check | `useEffect` + scroll event listener |
255
+ | Scroll entrance animation | `react-intersection-observer` useInView |
256
+ | Scroll-scrubbed | GSAP + `scrub: true` |
257
+ | Smooth scroll feel | Lenis |
258
+ | Complex sequence/exit | Framer Motion `AnimatePresence` |
259
+ | Stagger children | Framer Motion `staggerChildren` or GSAP `stagger` |