feat(skills): port 17 generic frontend skills from ~/.claude/skills/ (a11y-audit, design-system, figma-to-code, form-builder, frontend-design, landing-page, motion-design, perf-audit, responsive-audit, scroll-animation, seo-audit, site-builder, site-teardown, ui-component, web-assets, web-deploy, web-effects)

This commit is contained in:
Parfii-bot 2026-04-21 21:08:14 +08:00
parent 8c60085862
commit fd81aae515
17 changed files with 2629 additions and 0 deletions

102
skills/a11y-audit/SKILL.md Normal file
View file

@ -0,0 +1,102 @@
---
name: a11y-audit
description: Use when auditing accessibility — WCAG 2.2 AA compliance, contrast checks, keyboard navigation, screen reader support, prefers-reduced-motion. Triggers on "accessibility", "a11y", "wcag", "screen reader", "contrast check".
arguments:
- name: command
description: "Command: scan, fix, contrast, checklist, report"
required: false
- name: target
description: URL or file path to audit
required: false
---
# Accessibility Audit — WCAG 2.2 AA
## Legal Context
- **EAA (EU):** In force since June 2025. Penalties: up to 100K EUR / 4% revenue
- **ADA (US):** References WCAG 2.2 AA
- **Standard:** WCAG 2.2 AA is minimum for any commercial site targeting US/EU
## Top 10 Violations
1. Missing alt text on images
2. Low contrast text (4.5:1 normal, 3:1 large text)
3. Keyboard traps in menus
4. Missing form labels
5. Skipped heading levels
6. No skip links
7. Non-semantic HTML (`<div>` instead of `<nav>`, `<main>`)
8. Missing video captions
9. Invisible focus styles
10. Touch targets <24x24px (WCAG 2.2 new)
**Automated tools catch only 30-40%.** Manual audit required.
## Automated Testing
```bash
# Lighthouse CLI
npx lighthouse <url> --output=json --only-categories=accessibility
# axe-core
npx @axe-core/cli <url> --tags wcag2a,wcag2aa,wcag22aa
```
Playwright integration:
```javascript
import AxeBuilder from '@axe-core/playwright';
const results = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag22aa']).analyze();
expect(results.violations).toEqual([]);
```
## CSS Media Queries
### prefers-reduced-motion
```css
@media (prefers-reduced-motion: reduce) {
.animated { animation: fade-in 0.2s ease; transition: opacity 0.2s ease; }
.parallax { transform: none !important; }
.scroll-animation { animation: none; }
}
```
Replace motion (slide, scale) with non-motion (fade, opacity). Keep transitions <200ms.
### prefers-color-scheme / prefers-contrast / forced-colors
Always support dark mode, high contrast, and Windows forced colors.
## WCAG 2.2 New Criteria
- **2.5.8:** Touch targets min 24x24 CSS px
- **2.4.11/12:** Focus not obscured by sticky elements
- **3.3.7:** No redundant entry (don't re-ask info)
- **3.3.8:** No cognitive tests for auth (allow password managers)
- **2.5.7:** Dragging has non-drag alternative
## Semantic HTML Reference
```html
<a href="#main" class="skip-link">Skip to content</a>
<header><nav aria-label="Main">...</nav></header>
<main id="main">
<section aria-labelledby="heading"><h2 id="heading">...</h2></section>
</main>
<footer>...</footer>
```
## Manual Checklist
- [ ] Keyboard-only: Tab through entire page, verify focus order
- [ ] Skip link visible on focus
- [ ] All interactive elements: visible focus indicator
- [ ] Heading hierarchy: one h1, no skipped levels
- [ ] All images: meaningful alt OR aria-hidden="true" (decorative)
- [ ] Color contrast: 4.5:1 normal, 3:1 large (18px+ bold or 24px+)
- [ ] Forms: visible labels, errors linked with aria-describedby
- [ ] ARIA landmarks: header, nav, main, footer
- [ ] Touch targets: 24x24px minimum
- [ ] Animations: respect prefers-reduced-motion
- [ ] Dark mode: all elements visible and contrasted
- [ ] Video: captions present, controls accessible
- [ ] `lang` attribute on `<html>`
- [ ] Link text descriptive (not "click here")
- [ ] Errors announced to screen readers (aria-live)

View file

@ -0,0 +1,61 @@
---
name: design-system
description: Use when building a design system — tokens, base components, Tailwind config, dark mode, docs
arguments:
- name: name
description: Design system / project name
required: true
- name: style
description: "Visual direction: minimal, playful, corporate, editorial"
required: false
---
# Design System Workflow
## Step 1: Define Tokens
Design tokens (adapt to existing project if any):
### Colors
- Primary: main brand color + 50-950 scale
- Secondary: accent color + scale
- Neutral: gray scale for text, borders, backgrounds
- Semantic: success, warning, error, info
- Surface: background levels (0, 1, 2, 3)
### Typography
- Font families: heading, body, mono
- Size scale: xs, sm, base, lg, xl, 2xl, 3xl, 4xl
- Weight: normal, medium, semibold, bold
- Line heights: tight, normal, relaxed
### Spacing
- Scale: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24
### Other
- Border radius: none, sm, md, lg, full
- Shadows: sm, md, lg, xl
- Transitions: fast (150ms), normal (300ms), slow (500ms)
## Step 2: Tailwind Config
- For Tailwind 4: declare tokens in CSS via `@theme { ... }` (no `tailwind.config.ts`)
- For Tailwind 3: generate `tailwind.config.ts` with all tokens
- CSS custom properties for runtime theming
- Dark mode: `class` strategy with token overrides
## Step 3: Base Components
Core primitives (adjust to framework):
- Button (primary, secondary, ghost, destructive + sizes)
- Input (text, textarea, select + states)
- Card (surface levels, padding variants)
- Badge (status colors + sizes)
- Typography components (Heading, Text, Label)
## Step 4: Dark Mode
- Map all color tokens to dark equivalents
- Test contrast ratios (WCAG AA minimum: 4.5:1 text, 3:1 large text)
- Surface hierarchy inverts (dark bg, lighter cards)
## Step 5: Documentation
- Token reference table
- Component API (props, variants, examples)
- Usage guidelines (do's and don'ts)

View file

@ -0,0 +1,55 @@
---
name: figma-to-code
description: Use when converting Figma designs to code — screenshot, context, tokens, responsive implementation
arguments:
- name: url
description: Figma URL (figma.com/design/... or figma.com/make/...)
required: true
---
# Figma to Code Workflow
## Step 1: Extract from Figma
- Parse URL to get fileKey and nodeId
- Call `get_design_context` with fileKey and nodeId (Figma MCP / REST)
- Call `get_screenshot` for visual reference
- Review returned code, tokens, and component mappings
## Step 2: Analyze Design
From the Figma output, identify:
- **Layout:** flex/grid structure, spacing, alignment
- **Typography:** font family, size, weight, line-height, color
- **Colors:** map to project's design tokens or CSS variables
- **Components:** map to existing project components
- **Responsive hints:** auto-layout direction, min/max widths
- **Interactions:** hover states, transitions, animations
## Step 3: Map to Project
- Match Figma colors → project design tokens
- Match Figma fonts → project typography scale
- Match Figma components → existing project components
- Identify gaps: new components or tokens needed
- If Code Connect mappings exist, use them directly
## Step 4: Implement
- Use project's stack (React, Next.js, Astro, SvelteKit, etc.)
- Mobile-first responsive implementation
- Match pixel-perfect on design breakpoint
- Adapt gracefully to other breakpoints
- Use design system components where they match
### Responsive Breakpoints
- Mobile: 375px (default)
- Tablet: 768px
- Desktop: 1024px
- Wide: 1280px
## Step 5: Verify
- Compare screenshot with implementation side-by-side
- Check all breakpoints
- Verify interactive states (hover, focus, active)
- Accessibility check (contrast, keyboard nav, ARIA)
- Cross-browser check (Chrome, Safari, Firefox)
## Step 6: Commit
- `feat: implement <component/page> from Figma design`

View file

@ -0,0 +1,111 @@
---
name: form-builder
description: Use when building forms — multi-step wizards, Zod validation, anti-spam (Turnstile), serverless backends, file upload, progressive enhancement. Triggers on "form", "contact form", "wizard", "form validation", "turnstile".
arguments:
- name: command
description: "Command: create, validate, backend, spam, analytics, audit"
required: false
- name: type
description: "Form type: contact, multi-step, file-upload, survey"
required: false
---
# Form Construction & Submission
Progressive enhancement by default. Forms MUST work without JavaScript.
## Architecture
```
User → Client Validation (Zod) → Submit
↓ (JS disabled: standard POST)
Server Action/Worker → Server Validation (same Zod schema)
Anti-spam (Turnstile) → Process → Email (Resend) / Webhook / D1
```
## Validation: Zod v4 + react-hook-form v7
**Single schema shared between client and server (SSoT):**
```typescript
// schemas/contact.ts
import { z } from 'zod';
export const contactSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
company: z.string().optional(),
message: z.string().min(10).max(5000),
budget: z.enum(['<5k', '5k-15k', '15k-50k', '50k+']),
});
export type ContactFormData = z.infer<typeof contactSchema>;
```
**Client form:**
```tsx
const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<ContactFormData>({
resolver: zodResolver(contactSchema),
});
// method="POST" action="/api/contact" — works without JS
// noValidate — use Zod, not browser
// aria-describedby + aria-invalid + role="alert" for a11y
```
**WARNING:** react-hook-form v8 in beta with breaking changes. Stick to v7.
## Multi-Step Wizard
- Schema per step, merged for final validation
- `sessionStorage` for persistence across refreshes
- Progress indicator, back navigation, summary before submit
- Validate current step before "Next"
## Anti-Spam
### Cloudflare Turnstile (DEFAULT — free, unlimited, privacy-friendly)
```html
<div class="cf-turnstile" data-sitekey="YOUR_KEY"></div>
```
Server: verify via `challenges.cloudflare.com/turnstile/v0/siteverify`
### Honeypot (always layer with Turnstile)
```html
<div style="position:absolute;left:-9999px" aria-hidden="true">
<input type="text" name="website" tabindex="-1" autocomplete="off" />
</div>
```
### Rate Limiting
5 submissions/IP/hour via Cloudflare KV.
## Backends
| Backend | Best For |
|---------|----------|
| CF Worker + Resend | Email notifications (DEFAULT) |
| Webhook | Slack/Discord/Zapier/n8n |
| D1 | Persistent storage + analytics |
| R2 presigned URL | File uploads (>5MB use multipart) |
## Form Types
| Type | Fields | Anti-Spam | Backend |
|------|--------|-----------|---------|
| Contact | name, email, message, budget? | Turnstile + honeypot | Resend + webhook |
| Multi-step | per-step schemas | Turnstile on final | D1 + Resend |
| File upload | name, email, file(s) | Turnstile + rate limit | R2 presigned |
| Survey | rating, category, text | honeypot + rate limit | D1 |
## Audit Checklist
- [ ] All fields: visible `<label>`, aria-describedby for errors
- [ ] Works without JS (method + action set)
- [ ] Server validation matches client (same Zod schema)
- [ ] Anti-spam: honeypot minimum, Turnstile preferred
- [ ] Rate limiting on endpoint
- [ ] File uploads: presigned URLs (not Worker proxy)
- [ ] Input types match data (email, tel, url)
- [ ] Autocomplete attributes set
- [ ] Submit disabled during submission
- [ ] Success/error announced to screen readers
- [ ] Mobile: 44x44px touch targets, appropriate keyboards

View file

@ -0,0 +1,144 @@
---
name: frontend-design
description: Use when designing web UI before coding — anti-AI-slop aesthetic philosophy, typography pairing, color theory, spatial composition, motion guidelines, design archetypes. Triggers on "design", "UI design", "frontend design", "anti-slop", "make it look premium", "design thinking".
arguments:
- name: archetype
description: "Archetype: editorial, swiss, brutalist, minimal, maximalist, retro-futuristic, organic, industrial, art-deco, lo-fi (auto-suggest if omitted)"
required: false
- name: differentiator
description: "The ONE thing someone will remember about this design"
required: false
---
# Frontend Design — Think Before You Code
> Design-first, code-second. Every implementation starts with a design decision, not a div.
## Phase Gate (MANDATORY before writing any UI code)
1. **Purpose** — What problem? Who uses it? (1 sentence)
2. **Archetype** — Pick from 10 below (sets the aesthetic DNA)
3. **Differentiator** — "The one thing someone remembers" (1 sentence)
4. **Anti-references** — Name 3 sites/patterns this is NOT
5. **Tokens** — Define palette + fonts + spacing in CSS variables
Skip this gate = skip the skill. Code without design intent = AI slop.
## Hard Bans (Anti-AI-Slop)
**Typography:**
- Inter, Roboto, Arial, system font stacks
- Space Grotesk (overused in AI-generated sites)
- Same font for heading and body
**Color:**
- Purple gradients on white backgrounds
- Evenly distributed palettes (everything gets equal weight)
- Pure #000 or #fff without tinting
**Layout:**
- Centered card grids as default composition
- Hero → Cards → Testimonials → Footer (the template trap)
- Even spacing everywhere (no rhythm)
**Motion:**
- `linear` easing on UI transitions
- `scale(0)` animation origins
- Default `ease` without custom cubic-bezier
## 10 Archetypes
| # | Name | Typography | Color | Layout | Motion |
|---|------|-----------|-------|--------|--------|
| 1 | **Editorial** | Serif display + sans body | Warm neutrals + 1 accent | Asymmetric columns, pull quotes | Subtle parallax, text reveals |
| 2 | **Swiss** | Geometric sans (Helvetica Now, Neue Haas) | Black/white + 1 primary | Strict grid, mathematical spacing | Minimal, precision timing |
| 3 | **Brutalist** | Monospace or system | Raw, high contrast | Exposed structure, raw HTML aesthetic | Glitch, intentional jank |
| 4 | **Minimal** | 1 refined sans, extreme weight contrast | 2 colors max + neutral | Massive whitespace, single column | Fade only, ultra-slow |
| 5 | **Maximalist** | Mixed display fonts, decorative | Saturated, 4+ colors | Layered, overlapping, collage | Everything moves, scroll-driven |
| 6 | **Retro-Futuristic** | Futuristic display + mono | Neon on dark, CRT glow | Scanlines, terminal aesthetic | Typing effects, flicker |
| 7 | **Organic** | Rounded sans + handwritten accent | Earth tones, muted | Curved containers, blob shapes | Fluid, spring physics |
| 8 | **Industrial** | Condensed bold sans | Dark grays + safety yellow/orange | Dense info, data-heavy | Mechanical, step-based |
| 9 | **Art Deco** | Geometric display, high contrast weight | Gold/brass + deep navy/black | Symmetrical, ornamental borders | Elegant reveals, fade + scale |
| 10 | **Lo-Fi** | Hand-drawn or pixel font | Paper/notebook palette | Sketch-like borders, tape/sticker elements | Wobbly, imperfect |
## Typography Rules
- Max 2 fonts: 1 display (headings) + 1 body (text)
- Use `clamp()` for fluid scaling: `font-size: clamp(1rem, 2.5vw, 1.5rem)`
- Body `line-height`: 1.4-1.6 | Display `line-height`: 1.0-1.2
- 3-5 clear hierarchy levels with dramatic size contrast (4:1 heading-to-body)
- Tune `letter-spacing` per size: tighter for large, looser for small caps
- `font-feature-settings` for ligatures, tabular numbers where needed
## Color System (OKLCH)
```css
@theme {
--brand-hue: 250;
--color-primary: oklch(0.6 0.2 var(--brand-hue));
--color-surface: oklch(0.995 0.005 var(--brand-hue));
--color-text: oklch(0.15 0.02 var(--brand-hue));
--color-muted: oklch(0.55 0.01 var(--brand-hue));
--color-accent: oklch(0.7 0.25 calc(var(--brand-hue) + 30));
--color-border: oklch(0.9 0.01 var(--brand-hue));
}
```
**60-30-10 rule:** 60% dominant (surface/bg), 30% secondary (text/containers), 10% accent (CTAs, highlights).
OKLCH = perceptually uniform. One `--brand-hue` controls entire palette.
## Spatial Composition
- Consistent scale: `--space-xs: 0.25rem` through `--space-3xl: 4rem`
- Whitespace is structural, not leftover
- At least ONE grid-breaking moment per page (full-bleed, overlap, offset)
- 8px base grid for alignment
- Dramatic rhythm changes between sections (dense → spacious → dense)
## Visual Depth & Texture
- Noise/grain via SVG `<feTurbulence>` filter or CSS pseudo-element
- Multi-value `box-shadow` for realistic depth
- `backdrop-filter: blur()` for glass effects
- `clip-path` for non-rectangular shapes
- Background: gradients, patterns, grain — never flat solid white
## Motion Guidelines
- Custom `cubic-bezier()` per element — never default `ease`
- Staggered page-load: 50-100ms increments between elements
- Duration: productivity UI <300ms, creative 200-500ms
- Spring physics for interactive elements (bounce: 0, no jello)
- Exit animations subtler than enter
- `prefers-reduced-motion`: replace motion with fade, keep <200ms
- Keyboard-initiated actions: NO animation
### Enter Animation Recipe (Motion/Framer Motion)
```jsx
initial={{ opacity: 0, y: 8, filter: "blur(4px)" }}
animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
transition={{ type: "spring", duration: 0.45, bounce: 0 }}
```
## Output Contract
Every frontend-design invocation MUST produce:
1. **Stated direction** — archetype + differentiator + anti-references
2. **Design tokens** — CSS custom properties (colors, type, spacing)
3. **Typography selection** — 2 fonts with Google Fonts / Fontsource links
4. **Working code** — implementation matching the stated direction
5. **Responsiveness** — mobile-first, tested at 375px and 1280px
## The Blur Test
At 20% visibility, the layout silhouette should be distinguishable from anti-references. If blurred Stripe and blurred Your-Page look the same → composition is not distinctive.
## Diverge-Kill-Mutate
If output feels generic:
1. **Diverge** — generate 3 structurally different variants (different spatial logic, not color swaps)
2. **Kill** — binary: alive or dead. NO blending (blending = averaging = AI slop)
3. **Mutate** — within survivor, introduce named "breaks" (violations of convention)
4. **Repeat** — each cycle moves further from center

View file

@ -0,0 +1,188 @@
---
name: landing-page
description: Use when creating a landing page — orchestrates design, copy, assets, animations, SEO. Supports recipe system for specific page types (apple-product, saas, portfolio, ecommerce). Triggers on "landing page", "create page", "website".
arguments:
- name: product
description: Product/service name and brief description
required: true
- name: recipe
description: "Recipe: apple-product, saas, portfolio, ecommerce, agency, startup (auto-suggest if omitted)"
required: false
- name: goal
description: "Page goal: signups, downloads, waitlist, sales, portfolio showcase"
required: false
---
# Landing Page Orchestrator
Creates premium landing pages by composing specialized skills.
## Step 1: Design Direction
Invoke `/frontend-design` with product context:
- Suggest archetype based on recipe (see matrix below)
- Define differentiator, anti-references, tokens
- Output: design direction + CSS custom properties
## Step 2: Research & Copy
- Understand product: features, audience, value proposition
- WebSearch 3-5 competitors for positioning
- Write copy: headline (<10 words, benefit-driven), subheadline, CTAs, feature descriptions
- Tone matches archetype from Step 1
## Step 3: Page Structure
Adapt structure to recipe (see below). Core sections:
1. **Hero** — headline, subheadline, CTA, visual
2. **Problem** — pain point (empathy)
3. **Solution** — how product solves it (3 features max)
4. **Social proof** — testimonials, metrics, logos
5. **How it works** — 3-step process
6. **Pricing** (if applicable)
7. **FAQ** (3-5 questions)
8. **Final CTA** — repeat conversion action
## Step 4: Implementation
- Framework: Astro 6 (default for marketing) or project's stack
- Invoke skills per recipe (see matrix)
- Mobile-first responsive design
- Performance: lazy load below-fold, optimize all assets
## Step 5: Quality Pipeline
Sequential audit chain:
1. `/web-assets audit` — image formats, sizes, fonts
2. `/a11y-audit scan` — WCAG 2.2 AA compliance
3. `/seo-audit` — meta, headings, schema, OG tags
4. `/responsive-audit` — 6 breakpoints
5. `/perf-audit` — Lighthouse >90
## Step 6: Deploy
`/web-deploy deploy` — Cloudflare Pages (default)
---
## Recipe System
### `apple-product` — Premium Product Reveal
**Archetype:** Minimal or Swiss
**Skills invoked:** ai-animation, scroll-animation, video-gen, 3d-scene, web-assets, motion-design
**Structure:**
1. Hero: product floating in space, minimal text
2. Video scrub section: product rotation/reveal on scroll (frame sequence or 3D)
3. Feature deep-dives: pin + scrub with parallax text
4. Specs grid: bento layout with micro-animations
5. CTA: clean, single action
**Key techniques:**
- Frame sequence (120-180 WebP frames) or Three.js model with ScrollControls
- GSAP ScrollTrigger pin + scrub
- Lenis smooth scroll
- Staggered text reveals with blur-in animation
- Dark background, dramatic lighting
### `saas` — SaaS Product Landing
**Archetype:** Minimal or Editorial
**Skills invoked:** motion-design, ui-component, web-assets, form-builder
**Structure:**
1. Hero: headline + product screenshot/video + CTA
2. Logo bar: client/integration logos
3. Features: bento grid (3-6 cards) with hover micro-interactions
4. Demo: embedded video or interactive preview
5. Testimonials: carousel or grid with photos
6. Pricing: 2-3 tier comparison table
7. FAQ: accordion
8. CTA: signup form (Turnstile + Zod)
**Key techniques:**
- Bento grid layout with staggered entrance
- View Transitions for page navigation
- Dark/light mode toggle
- Micro-interactions on every card (hover scale, shadow elevation)
- Auto-animate for list/grid transitions
### `portfolio` — Creative Portfolio
**Archetype:** Editorial or Maximalist
**Skills invoked:** scroll-animation, web-effects, motion-design, 3d-scene
**Structure:**
1. Hero: kinetic typography (name/title animates on load)
2. Project showcase: horizontal scroll or masonry grid
3. Project detail: image distortion on hover (WebGL)
4. About: asymmetric editorial layout
5. Contact: minimal form
**Key techniques:**
- Custom cursor that reacts to content
- Image distortion on hover (curtains.js displacement)
- GSAP horizontal scroll for project gallery
- SVG line drawing for decorative elements
- Kinetic typography with SplitText
### `ecommerce` — Product E-Commerce
**Archetype:** Minimal or Organic
**Skills invoked:** ui-component, web-assets, form-builder, motion-design
**Structure:**
1. Hero: product lifestyle image + CTA
2. Product grid: filterable with auto-animate transitions
3. Product detail: gallery + variant selector + add-to-cart
4. Reviews: social proof grid
5. Related products: horizontal scroll
6. Trust: shipping, returns, secure payment badges
**Key techniques:**
- Image zoom on hover
- Variant selector with instant preview update
- Add-to-cart animation (fly to cart icon)
- Skeleton loading states
- Optimistic UI updates
### `agency` — Creative Agency
**Archetype:** Brutalist or Swiss
**Skills invoked:** scroll-animation, web-effects, 3d-scene, motion-design
**Structure:**
1. Hero: bold statement + reel/showreel video
2. Services: icon grid with hover reveals
3. Case studies: full-bleed image + overlay text
4. Team: grid with playful hover effects
5. Process: timeline with scroll-linked progress
6. Contact: multi-step form
**Key techniques:**
- Full-screen video hero (AV1 + H.264 fallback)
- Noise/grain texture overlay
- Scroll-driven timeline with pin sections
- Magnetic cursor on interactive elements
- Page transitions with View Transitions API
### `startup` — Early-Stage Startup
**Archetype:** Minimal or Retro-Futuristic
**Skills invoked:** motion-design, form-builder, web-assets
**Structure:**
1. Hero: problem statement + waitlist CTA
2. Pain points: 3 illustrated scenarios
3. Solution: how it works (3 steps)
4. Early metrics/traction (if available)
5. Founder story (optional)
6. Waitlist form with social proof counter
**Key techniques:**
- Simple fade-in animations (AutoAnimate)
- Email capture with Turnstile
- Social proof: "Join 1,234 others" counter
- Minimal JavaScript, maximum speed
- Ship fast: Astro + Tailwind + Cloudflare Pages

View file

@ -0,0 +1,347 @@
---
name: motion-design
description: Use when implementing motion design — page transitions, element animations, micro-interactions, layout animations. Covers Motion (ex Framer Motion), View Transitions API, auto-animate, SVG animation (Rive, Lottie), and accessibility.
arguments:
- name: type
description: "Type: page-transition, micro-interaction, layout-animation, svg-animation, loading, hover (auto-detect if omitted)"
required: false
- name: framework
description: "Framework: react, next, astro, vue, svelte, vanilla (auto-detect if omitted)"
required: false
---
# Motion Design Skill
## Decision Matrix — Pick Library
| Need | Library | Bundle | Why |
|------|---------|--------|-----|
| React component animations | Motion 12 | ~32KB gzip | Best React DX, layout animations |
| Page transitions (MPA) | View Transitions API | 0KB | Native browser API |
| Page transitions (Astro) | Astro View Transitions | 0KB | Built-in, zero JS |
| Zero-config list animations | AutoAnimate | ~2KB gzip | One line, FLIP-based |
| Interactive vector graphics | Rive | ~78KB WASM | State machines, 60fps |
| After Effects exports | Lottie/dotLottie | ~50KB runtime | Huge asset library |
| SVG path morphing | GSAP MorphSVG | included in gsap | Now free, best morph engine |
| Line drawing | CSS stroke-dasharray | 0KB | Pure CSS, no library |
---
## 1. Motion (ex Framer Motion)
**Install:** `npm i motion`
**Bundle:** ~32KB min+gzip
### Core API
```jsx
import { motion, AnimatePresence } from "motion/react";
// Basic animation
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3, ease: "easeOut" }}
>
Content
</motion.div>
// Layout animation (FLIP under the hood)
<motion.div layout layoutId="card-expand">
{isExpanded ? <ExpandedCard /> : <CompactCard />}
</motion.div>
// AnimatePresence — exit animations
<AnimatePresence mode="wait">
{items.map(item => (
<motion.div
key={item.id}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
/>
))}
</AnimatePresence>
```
### Gestures
```jsx
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
>
Click me
</motion.button>
// Drag
<motion.div
drag="x"
dragConstraints={{ left: -200, right: 200 }}
dragElastic={0.1}
/>
```
### Scroll-Linked
```jsx
import { useScroll, useTransform, motion } from "motion/react";
function ParallaxHero() {
const { scrollYProgress } = useScroll();
const y = useTransform(scrollYProgress, [0, 1], [0, -300]);
const opacity = useTransform(scrollYProgress, [0, 0.5], [1, 0]);
return (
<motion.div style={{ y, opacity }}>
Hero Content
</motion.div>
);
}
```
### AnimateView (View Transitions integration)
```jsx
import { AnimateView } from "motion/react";
<AnimateView>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</AnimateView>
```
---
## 2. View Transitions API
### Vanilla Implementation
```js
// Single-document transition
document.startViewTransition(() => {
container.innerHTML = newContent;
});
```
```css
::view-transition-old(root) { animation: fade-out 0.2s ease-out; }
::view-transition-new(root) { animation: fade-in 0.3s ease-in; }
.hero-image { view-transition-name: hero; }
```
### Astro Integration (Built-in)
```astro
---
import { ViewTransitions } from "astro:transitions";
---
<html>
<head><ViewTransitions /></head>
<body><slot /></body>
</html>
<img transition:name="hero" src="/hero.jpg" />
<h1 transition:animate="slide">Page Title</h1>
```
Built-in animation presets: `fade`, `slide`, `morph`, `none`.
---
## 3. AutoAnimate
**Install:** `npm i @formkit/auto-animate`
**Zero config.** Uses FLIP technique internally.
```jsx
import { useAutoAnimate } from "@formkit/auto-animate/react";
function TodoList({ items }) {
const [parent] = useAutoAnimate();
return (
<ul ref={parent}>
{items.map(item => <li key={item.id}>{item.text}</li>)}
</ul>
);
}
// Vanilla JS
import autoAnimate from "@formkit/auto-animate";
autoAnimate(document.getElementById("list"));
```
**Best for:** List reordering, add/remove items, accordion expand/collapse.
---
## 4. SVG Animation
### Rive
```jsx
import Rive from "@rive-app/react-canvas";
<Rive
src="/animations/hero.riv"
stateMachines="MainState"
style={{ width: 400, height: 400 }}
/>
```
**Key features:** State Machines, layout engine, scroll-linked via data inputs.
**When to use:** Interactive illustrations, mascots, loading states, onboarding flows.
### Lottie / dotLottie
```jsx
import { DotLottieReact } from "@lottiefiles/dotlottie-react";
<DotLottieReact
src="/animations/hero.lottie"
loop
autoplay
style={{ width: 300, height: 300 }}
/>
```
**Rive vs Lottie:**
| Factor | Rive | Lottie |
|--------|------|--------|
| Interactivity | Built-in state machine | Manual JS coding |
| Design tool | Rive editor | After Effects + plugin |
| File size | Smaller (binary) | Larger (JSON) |
| Asset ecosystem | Growing | Massive marketplace |
### SVG Morphing
**GSAP MorphSVG** (now free with gsap):
```js
gsap.to("#star", { morphSVG: "#circle", duration: 1, ease: "power2.inOut" });
```
**SVG points limit:** Keep under 200 points for smooth 60fps morphing.
### Line Drawing (Pure CSS)
```css
.svg-line {
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: draw 2s ease forwards;
}
@keyframes draw { to { stroke-dashoffset: 0; } }
```
Get path length: `document.querySelector("path").getTotalLength()`.
---
## 5. Micro-Interaction Patterns
### Button Hover/Tap
```jsx
<motion.button
whileHover={{ scale: 1.03, boxShadow: "0 4px 20px rgba(0,0,0,0.15)" }}
whileTap={{ scale: 0.97 }}
transition={{ type: "spring", stiffness: 500, damping: 25 }}
/>
```
### Toast/Notification Enter
```jsx
<AnimatePresence>
{toast && (
<motion.div
initial={{ opacity: 0, y: 50, scale: 0.9 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 20, scale: 0.95 }}
transition={{ type: "spring", damping: 20 }}
/>
)}
</AnimatePresence>
```
### Staggered List
```jsx
const container = { animate: { transition: { staggerChildren: 0.06 } } };
const item = { initial: { opacity: 0, y: 15 }, animate: { opacity: 1, y: 0 } };
<motion.ul variants={container} initial="initial" animate="animate">
{items.map(i => <motion.li key={i} variants={item} />)}
</motion.ul>
```
---
## 6. Timing & Easing Reference
### Duration Guidelines
| Element | Duration | Easing |
|---------|----------|--------|
| Button hover | 150-200ms | ease-out |
| Tooltip appear | 100-150ms | ease-out |
| Modal enter | 200-300ms | ease-out / spring |
| Modal exit | 150-200ms | ease-in |
| Page transition | 200-400ms | ease-in-out |
| Layout shift | 200-350ms | ease-out / spring |
| Scroll reveal | 400-600ms | ease-out |
### Spring Presets (Motion)
```js
// Snappy UI feedback
{ type: "spring", stiffness: 500, damping: 25 }
// Smooth layout
{ type: "spring", stiffness: 300, damping: 30 }
// Bouncy/playful
{ type: "spring", stiffness: 400, damping: 10 }
```
---
## 7. Accessibility
### prefers-reduced-motion
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
```
```jsx
// Motion respects prefers-reduced-motion by default
const prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
```
### Guidelines
- Never rely on animation alone to convey information
- Ensure all animated content is accessible via keyboard
- Provide static fallback for critical content
- Test with reduced motion enabled in OS settings
---
## Workflow
1. **Identify animation type** — page transition, reveal, micro-interaction, SVG
2. **Pick library** — use Decision Matrix above
3. **Define timing** — use Duration Guidelines, spring presets
4. **Implement** — start with `initial` + `animate` states
5. **Add exit** — wrap in AnimatePresence for unmount animations
6. **Add a11y** — prefers-reduced-motion, keyboard testing
7. **Performance audit** — Chrome DevTools, check for layout thrashing

View file

@ -0,0 +1,51 @@
---
name: perf-audit
description: Use when auditing performance — baseline, profile, identify top 3 bottlenecks, fix, remeasure
arguments:
- name: target
description: "What to audit: endpoint, page, function, or 'full'"
required: true
---
# Performance Audit Workflow
## Step 1: Establish Baseline
- Measure current performance:
- API: response time (p50, p95, p99), throughput
- Frontend: LCP, FID, CLS, bundle size
- Function: execution time, memory usage
- Record numbers BEFORE any changes
- Use project's existing tools or:
- Python: `time`, `cProfile`, `memory_profiler`
- JS/TS: `performance.now()`, Lighthouse, `webpack-bundle-analyzer`
- API: `curl -w @-` timing, `ab`, `wrk`
## Step 2: Profile
- Identify WHERE time is spent:
- Database queries (N+1, missing indexes, full scans)
- Network calls (sequential vs parallel, caching)
- CPU (algorithmic complexity, unnecessary computation)
- Memory (leaks, large allocations, unnecessary copies)
- I/O (file reads, disk writes)
## Step 3: Identify Top 3 Bottlenecks
- Rank by impact (% of total time)
- Focus on top 3 — don't optimize everything
- For each: document what, why slow, potential fix
## Step 4: Checkpoint
- `checkpoint: before perf-audit $target`
## Step 5: Fix (One at a Time)
- Fix #1 bottleneck → measure → confirm improvement
- Fix #2 bottleneck → measure → confirm improvement
- Fix #3 bottleneck → measure → confirm improvement
- After each fix: run tests — no regressions
## Step 6: Final Measurement
- Re-run baseline measurements
- Compare before/after
- Report: metric, before, after, improvement %
## Step 7: Commit
- `perf: optimize $target — <summary of improvements>`

View file

@ -0,0 +1,65 @@
---
name: responsive-audit
description: Use when auditing responsive design — 6 breakpoints, layout, touch targets, overflow, images
arguments:
- name: target
description: Page or component path to audit
required: true
---
# Responsive Audit Workflow
## Step 1: Identify Target
- Read the target file(s)
- Understand the layout structure (flex, grid, absolute, etc.)
- List all breakpoint-dependent styles
## Step 2: Audit Each Breakpoint
### Mobile (375px)
- [ ] Single column layout where appropriate
- [ ] Touch targets min 44x44px
- [ ] No horizontal scroll
- [ ] Font size min 16px for body text
- [ ] Adequate spacing between interactive elements
### Small Mobile (320px)
- [ ] No content overflow or truncation breaking layout
- [ ] Navigation still usable
- [ ] Forms still fillable
### Tablet (768px)
- [ ] Layout adapts (2-column where appropriate)
- [ ] Images scale properly
- [ ] Navigation adapts (hamburger → tabs or vice versa)
### Desktop (1024px)
- [ ] Full layout utilizes space
- [ ] Max content width set (not stretching to infinity)
- [ ] Sidebar/aside content visible if applicable
### Wide (1280px)
- [ ] Content centered or max-width contained
- [ ] No excessive whitespace
- [ ] Images don't pixelate
### Ultra-wide (1920px+)
- [ ] Layout doesn't break
- [ ] Content doesn't stretch uncomfortably
## Step 3: Common Issues Check
- [ ] Images: `srcset` / responsive sizing, proper aspect ratios
- [ ] Typography: readable at all sizes, proper line lengths (45-75 chars)
- [ ] Spacing: consistent with design system tokens
- [ ] Overflow: no `overflow: hidden` hiding important content
- [ ] Z-index: modals/dropdowns work on all sizes
- [ ] Inputs: don't zoom on iOS (font-size >= 16px)
## Step 4: Issues Report
For each issue:
- Breakpoint where it occurs
- File and line number
- Screenshot description or CSS selector
- Suggested fix
Prioritize: broken layout > usability > polish

View file

@ -0,0 +1,304 @@
---
name: scroll-animation
description: Use when building scroll-driven animations — GSAP ScrollTrigger, CSS scroll-timeline, frame sequences, parallax, pin/scrub effects. Covers Apple-style scroll playback, progress-linked animations, and smooth scroll integration.
arguments:
- name: technique
description: "Technique: gsap, css-native, frame-sequence, parallax, hybrid (auto-detect if omitted)"
required: false
- name: framework
description: "Framework: react, next, astro, vue, svelte, vanilla (auto-detect if omitted)"
required: false
---
# Scroll Animation Skill
## Decision Matrix — Pick Technique
| Need | Technique | Why |
|------|-----------|-----|
| Pin + scrub + snap | GSAP ScrollTrigger | Most mature, free since Webflow acquisition |
| Simple fade/slide on scroll | CSS `animation-timeline` | Zero JS, native performance |
| Apple-style frame playback | Canvas frame sequence | Smoothest result for product reveals |
| Parallax layers | CSS or GSAP | CSS for simple, GSAP for complex |
| Smooth scroll feel | Lenis + GSAP | Industry standard combo |
---
## 1. GSAP ScrollTrigger
**License:** 100% FREE including all plugins
**Install:** `npm i gsap`
### Core API
```js
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
// Pin + Scrub
gsap.to(".hero-content", {
y: -100,
opacity: 0,
scrollTrigger: {
trigger: ".hero",
start: "top top",
end: "bottom top",
pin: true,
scrub: 1,
snap: { snapTo: 1 / 4, duration: 0.3, ease: "power1.inOut" }
}
});
// Batch — stagger elements entering viewport
ScrollTrigger.batch(".card", {
onEnter: (elements) => {
gsap.to(elements, { opacity: 1, y: 0, stagger: 0.1 });
},
start: "top 85%"
});
```
### React Integration (useGSAP hook)
```jsx
import { useGSAP } from "@gsap/react";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
function Section({ children }) {
const container = useRef(null);
useGSAP(() => {
gsap.from(".animate-in", {
y: 50,
opacity: 0,
stagger: 0.2,
scrollTrigger: { trigger: container.current, start: "top 80%" }
});
}, { scope: container });
return <section ref={container}>{children}</section>;
}
```
**Key:** `useGSAP` = drop-in for `useEffect`, auto-cleanup via `gsap.context()`.
### Astro Integration
```astro
<section id="scroll-section">
<div class="pin-target">Content</div>
</section>
<script>
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
gsap.to(".pin-target", {
x: 500,
scrollTrigger: { trigger: "#scroll-section", pin: true, scrub: true }
});
</script>
```
### Performance Best Practices
- Use `will-change: transform` on pinned elements
- Prefer `transform` and `opacity` — GPU-composited, no layout recalc
- `scrub: 1` (or higher) smooths jank vs `scrub: true` (instant)
- `invalidateOnRefresh: true` for responsive layouts
- Call `ScrollTrigger.refresh()` after dynamic content loads
- Avoid animating `width`, `height`, `top`, `left` — triggers reflow
---
## 2. CSS Scroll-Driven Animations (Native)
### Scroll Progress Timeline
```css
@keyframes fade-in {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-on-scroll {
animation: fade-in linear both;
animation-timeline: scroll();
animation-range: entry 0% entry 100%;
}
```
### View Progress Timeline
```css
.reveal {
animation: fade-in linear both;
animation-timeline: view();
animation-range: entry 25% cover 50%;
}
```
### Progressive Enhancement
```css
@supports (animation-timeline: scroll()) {
.animate { animation-timeline: scroll(); }
}
/* Fallback: use IntersectionObserver + classList toggle */
```
### What CSS Can Replace from GSAP
| Feature | CSS Native | Still Need GSAP |
|---------|-----------|-----------------|
| Fade/slide on scroll | Yes | No |
| Progress-linked animation | Yes | No |
| View-enter/exit triggers | Yes | No |
| Pin element | No | Yes |
| Snap to sections | No (scroll-snap is separate) | Yes (integrated) |
| Batch stagger | No | Yes |
| Timeline sequencing | Limited | Yes |
| Complex easing curves | Limited | Yes |
| JS callbacks on progress | No | Yes |
**Rule of thumb:** CSS for simple reveal animations. GSAP for anything with pin, snap, stagger, or JS logic.
---
## 3. Lenis Smooth Scroll
**Install:** `npm i lenis`
**Bundle:** ~14KB min+gzip (no dependencies)
```js
import Lenis from "lenis";
const lenis = new Lenis({
duration: 1.2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
orientation: "vertical",
smoothWheel: true,
});
// Connect to GSAP ticker for sync
gsap.ticker.add((time) => { lenis.raf(time * 1000); });
gsap.ticker.lagSmoothing(0);
// Connect to ScrollTrigger
lenis.on("scroll", ScrollTrigger.update);
```
**When to use:** Agency-style smooth scroll feel. Pairs with GSAP ScrollTrigger.
**When NOT to use:** Content-heavy sites, accessibility-first projects.
---
## 4. Frame Sequence on Scroll (Apple-Style)
### Pipeline
```
Video (MP4/MOV)
→ FFmpeg frame extraction (PNG)
→ Convert to WebP (90% size reduction vs PNG)
→ Canvas playback synced to scroll
```
### FFmpeg Extraction
```bash
ffmpeg -i source.mp4 -vf "fps=30,scale=1280:720" frames/frame_%04d.png
for f in frames/*.png; do cwebp -q 80 "$f" -o "${f%.png}.webp"; done
```
### Optimal Parameters
| Parameter | Desktop | Mobile |
|-----------|---------|--------|
| Frame count | 120-180 | 60-90 |
| Resolution | 1920x1080 | 960x540 |
| Format | WebP q80 | WebP q75 |
| Total budget | 2-4 MB | 1-2 MB |
### Canvas Implementation
```js
const canvas = document.getElementById("sequence-canvas");
const ctx = canvas.getContext("2d");
const frameCount = 150;
const frames = [];
function preloadFrames() {
for (let i = 1; i <= frameCount; i++) {
const img = new Image();
img.src = `/frames/frame_${String(i).padStart(4, "0")}.webp`;
frames.push(img);
}
}
gsap.to({ frame: 0 }, {
frame: frameCount - 1,
snap: "frame",
ease: "none",
scrollTrigger: {
trigger: "#sequence-section", start: "top top", end: "+=3000", pin: true, scrub: 0.5,
},
onUpdate: function() {
const index = Math.round(this.targets()[0].frame);
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (frames[index]?.complete) {
ctx.drawImage(frames[index], 0, 0, canvas.width, canvas.height);
}
}
});
```
### Alternative: Video Scrub
```js
const video = document.getElementById("scrub-video");
gsap.to(video, {
currentTime: video.duration,
ease: "none",
scrollTrigger: { trigger: "#video-section", start: "top top", end: "+=4000", pin: true, scrub: true }
});
```
**Tradeoff:** Video scrub = smaller payload, less smooth on mobile. Frame sequence = more bytes, smoother everywhere.
---
## Accessibility
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
```
```js
const prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (prefersReduced) { ScrollTrigger.getAll().forEach(st => st.kill()); }
```
---
## Workflow
1. **Define scroll sections** — wireframe which content pins, reveals, or plays
2. **Pick technique** — use Decision Matrix above
3. **Implement with GSAP** — pin/scrub/snap for complex, CSS for simple reveals
4. **Add Lenis** — only if smooth scroll feel is required
5. **Test performance** — Chrome DevTools Performance panel, aim for <16.6ms/frame
6. **Add a11y**`prefers-reduced-motion`, keyboard nav still works
7. **Test mobile** — reduce frame counts, disable heavy effects on low-end

49
skills/seo-audit/SKILL.md Normal file
View file

@ -0,0 +1,49 @@
---
name: seo-audit
description: Use when auditing SEO — technical + content analysis via WebFetch and code inspection
arguments:
- name: url
description: URL or project path to audit
required: true
---
# SEO Audit Workflow
## Step 1: Technical SEO
Fetch and analyze the page:
- **Meta tags:** title (<60 chars), description (<155 chars), viewport, robots
- **Headings:** proper H1-H6 hierarchy, single H1
- **URLs:** clean, descriptive, no query params for content pages
- **Canonical:** present and correct
- **Sitemap:** exists at /sitemap.xml
- **Robots.txt:** exists, not blocking important pages
- **HTTPS:** enforced, no mixed content
- **Mobile:** responsive meta tag, no horizontal scroll
## Step 2: Performance Impact
- Image optimization: format (WebP/AVIF), size, lazy loading, alt text
- Core Web Vitals indicators:
- LCP: largest element load time
- CLS: layout shift from images/fonts without dimensions
- FID/INP: heavy JS blocking interaction
- Bundle size check if applicable
## Step 3: Content SEO
- Keyword presence in: title, H1, first paragraph, URL
- Content length (>300 words for ranking)
- Internal links (to other pages on same domain)
- External links (to authoritative sources)
- Structured data (JSON-LD): Article, Product, FAQ, etc.
- Open Graph + Twitter Card meta tags
## Step 4: Issues Report
Format as prioritized list:
- **Critical:** blocks indexing or ranking (missing title, noindex, broken canonical)
- **Important:** significant ranking impact (no meta description, missing alt text, slow LCP)
- **Nice-to-have:** minor improvements (schema markup, additional links)
Each issue: what's wrong, where, how to fix, impact level.
## Step 5: Action Items
- Generate fix list ordered by impact
- For code changes: specific file + line + suggested edit

View file

@ -0,0 +1,330 @@
---
name: site-builder
description: Build a website from block recipes via interactive wizard. Asks stack/type/style/sections via AskUserQuestion, generates one section at a time, enforces WYSIWYD (what you see in the mock is byte-identical to what gets deployed) via the mock-render primitive.
---
# /site-builder — WYSIWYD website builder
> **Core promise:** every section you approve in the preview IS the file that gets deployed. No "approximately like this". Byte-for-byte.
## When to use
Triggers: `/site-builder`, "create website", "build a site", "landing page", "portfolio site", "SaaS site", "docs site".
## Output contract
Produces a complete website project as local code:
```
<project-root>/
├── src/
│ ├── pages/index.astro (or app/page.tsx for Next)
│ ├── sections/
│ │ ├── Nav.astro — one file per section (Constructor Pattern)
│ │ ├── Hero.astro
│ │ ├── Features.astro
│ │ ├── Pricing.astro
│ │ └── ...
│ ├── layouts/Base.astro
│ └── tokens.css — CSS custom properties
├── public/
│ └── <brand assets>
├── astro.config.mjs (or next.config.js)
├── package.json
├── site-state.json — mock-render lock file (WYSIWYD)
└── mocks/
├── Hero.png — locked screenshots per section
├── Features.png
└── ...
```
Every `sections/*.astro` is independently regeneratable — editing one never touches the others.
## Prerequisites
- Node 20+ with `npx` available
- `mock-render` Rust primitive installed (built by `install.sh` into `$HOME/.claude/agents/_primitives/_rust/target/release/mock-render`)
- Playwright installed (`npx playwright install chromium` — the skill will prompt if missing)
## Phase 0 — Intake via AskUserQuestion
Send questions in AskUserQuestion calls (max 4 per call; use 2 calls if more).
### Call 1 — 4 questions
- **Site archetype?** SaaS landing / Multi-page marketing / Portfolio / Docs site
- **Framework?** Astro 6 (marketing default) / Next.js 16 (SaaS/app) / Static HTML
- **Visual archetype?** Premium minimalist / Dark moody tech / Editorial long-form / Brutalist anti-design
- **Motion tier?** None / Subtle (Motion LazyMotion) / Rich (GSAP ScrollTrigger + Motion) / Experimental (3D + shaders)
### Call 2 — 3 follow-up questions
- **Deploy target?** Cloudflare Pages (recommended) / Vercel / Local only
- **Brand assets?** User provides / Generate with AI / Minimal (text logo + neutral palette)
- **Include a contact form?** Yes (wire via /form-builder) / No
## Phase 1 — Section selection
After Phase 0, pick SECTIONS based on site type.
Defaults per archetype:
| Archetype | Default sections |
|---|---|
| SaaS landing | Nav, Hero, LogoBar, Features, Testimonials, Pricing, FAQ, CTA, Footer |
| Multi-page marketing | Nav, Hero, Features, CTA, Footer — plus routes `/about`, `/pricing`, `/contact` |
| Portfolio | Nav, Hero, Features (case grid), Testimonials, Contact, Footer |
| Docs site | NavSidebar, content layout, Footer-minimal |
Ask via AskUserQuestion: "Pick sections to include" with multi-select — show the defaults checked, user can add/remove.
For each section selected, ask: "Variant?" (A/B/C).
## Phase 2 — Foundation
Create project scaffold (Astro example):
```bash
npm create astro@latest <project-root> -- --template minimal --typescript strict --no-install --no-git
cd <project-root>
npm install
npm install motion @radix-ui/react-tabs lucide-astro # per block deps
```
Write `src/tokens.css` using answers from Phase 0:
```css
:root {
--color-bg: ...;
--color-fg: ...;
--color-accent: ...;
--color-border: ...;
--radius-card: 0.75rem;
--space-section: clamp(4rem, 8vw, 8rem);
--font-display: ...;
--font-body: ...;
}
@media (prefers-color-scheme: dark) { :root { ... } }
```
If user chose "brand assets: I'll provide", ask free-text for logo path + 2-3 hex colors.
Commit checkpoint: `checkpoint: scaffold + tokens`.
## Phase 3 — WYSIWYD block-by-block build (THE CORE LOOP)
For EACH section in the approved list:
### 3.1 Generate the section file
Write `<project-root>/src/sections/<Name>.astro`:
- Copy from Phase 0 brand answers (or placeholder with user's domain words)
- Tokens from `src/tokens.css` (no hardcoded colors)
- Motion hooks matching Phase 0 motion tier
- Brand assets if provided
**Anti-patterns to enforce:**
- No "AI-powered X" headlines
- No centered gradient + default Inter + purple/blue palette combo
- No 5+ CTAs / tiers / features on one block
### 3.2 Render mock
Start dev server (once per session):
```bash
npm run dev & # Astro on :4321 or Next on :3000
```
Wait for port to respond:
```bash
for i in {1..30}; do
curl -s http://localhost:4321 > /dev/null && break
sleep 1
done
```
Screenshot via the `mock-render` primitive:
```bash
$HOME/.claude/agents/_primitives/_rust/target/release/mock-render screenshot \
"http://localhost:4321/_block-preview?block=<Name>" \
--out "<project-root>/mocks/<Name>.png" \
--viewport 1440x900
```
Create a simple `_block-preview.astro` route that imports and renders one section by query param — include this in the scaffold.
### 3.3 Show user + get approval
Display the screenshot. Ask via AskUserQuestion:
- Approve — lock and move on
- Iterate — tell me what to change
- Switch variant (A/B/C)
- Swap block entirely
### 3.4 Act on approval
**Approve:**
```bash
$HOME/.claude/agents/_primitives/_rust/target/release/mock-render lock \
--project <project-root> \
--section src/sections/<Name>.astro \
--screenshot mocks/<Name>.png
```
Commit: `feat: lock <Name> section`. Move to next section.
**Iterate:** Ask free-text "What to change?", apply surgical edit to `<Name>.astro` only. Re-render. Loop.
**Switch variant:** Re-run 3.1 with different variant flag.
**Swap block:** Go back to Phase 1 for this section slot.
### 3.5 WYSIWYD invariant check before any later write
Before writing to ANY already-locked section:
```bash
$HOME/.claude/agents/_primitives/_rust/target/release/mock-render verify \
--project <project-root> \
--section src/sections/<Name>.astro
# Exit 0: file unchanged since lock, OK to proceed
# Exit 2: DRIFT — re-render + re-approve before continuing
```
This invariant means the final deploy is guaranteed to look like the last screenshot the user approved.
## Phase 4 — Audit (parallel)
After all sections locked, run 4 audits in parallel:
```
/a11y-audit scan src/
/seo-audit <project-root>
/responsive-audit src/pages/index.astro
/perf-audit src/
```
Report findings grouped by severity. For each fix proposed:
1. Run `mock-render verify` on the affected section
2. If verify passes AND fix is minor (e.g., add `alt=""`, tweak meta tag) — apply
3. If fix alters layout — ask user to re-approve (back to 3.2 for that section)
4. If fix spans multiple sections — STOP, report, let user decide
## Phase 5 — Preview
Spin up a preview URL before production deploy:
- Cloudflare Pages: `npx wrangler pages deploy <build-dir> --project-name=<slug>-preview`
- Vercel: `npx vercel --preview` (returns preview URL)
- Local: `npm run preview` on :4321
Send URL to user.
## Phase 6 — Deploy
Only after user explicitly confirms preview:
```
/web-deploy deploy --target=<chosen-in-Phase-0> --project=<project-root>
```
Final output:
- Production URL
- Git commit SHA
- Screenshot grid of all locked sections (from `mocks/*.png`)
## State file — site-state.json
Maintained by `mock-render lock/verify`. Shape:
```json
{
"sections": {
"Hero": {"path": "src/sections/Hero.astro", "sha256": "6a48...", "locked": true, "screenshot": "mocks/Hero.png"},
"Features": {"path": "src/sections/Features.astro", "sha256": "...", "locked": true, "screenshot": "mocks/Features.png"},
"Pricing": {"path": "src/sections/Pricing.astro", "sha256": "...", "locked": false, "screenshot": null}
}
}
```
Commands:
- `mock-render lock` — freeze current hash after user approves mock
- `mock-render verify` — assert source unchanged before any new write
- `mock-render status` — list sections, lock state, drift check
- `mock-render screenshot` — Playwright wrapper
## Handoffs (sub-skills called)
| Sub-skill | When |
|---|---|
| `/frontend-design` | Phase 2 — if archetype picked but user wants custom tokens |
| `/design-inspiration` | Phase 0 alt — if user wants to see refs before picking style |
| `/site-teardown` | Phase 0 alt — if user provides a ref site URL to clone-style |
| `/ai-animation` | Phase 3 — video-bg hero or scroll loop |
| `/scroll-animation` | Phase 3 — rich/experimental motion tier with pin/scrub |
| `/3d-scene` | Phase 3 — experimental motion tier with R3F hero |
| `/web-effects` | Phase 3 — shader bg, particles |
| `/motion-design` | Phase 3 — subtle motion tier (Motion library) |
| `/form-builder` | Phase 3 — if contact form yes (Phase 0 Call 2 Q3) |
| `/ui-component` | Phase 3 — novel primitive not in blocks |
| `/web-assets` | Phase 4 — image/font/video optimization |
| `/a11y-audit` | Phase 4 — parallel |
| `/seo-audit` | Phase 4 — parallel |
| `/responsive-audit` | Phase 4 — parallel |
| `/perf-audit` | Phase 4 — parallel |
| `/web-deploy` | Phase 6 — production |
## Forbidden
- Generating a section file without immediately rendering a screenshot of it
- Approving a section without calling `mock-render lock`
- Editing a locked section file without first running `mock-render verify`
- Cascading edits that touch multiple sections at once (violates Constructor Pattern for UI)
- Deploying before user approves the preview URL (Phase 5)
- AI-slop anti-patterns
- Hardcoded colors / fonts / spacing outside `tokens.css`
- Breaking the WYSIWYD invariant — the last screenshot the user approved MUST match what's deployed
## Anti-patterns (AI slop guards)
Enforced at generation time — block the section and regenerate if detected:
1. Generic centered hero + gradient + "AI-powered X" subhead
2. Stock 3D isometric illustrations
3. Lorem-ipsum-tier feature copy
4. Every section animated (motion fatigue)
5. 5+ pricing tiers
6. No specific outcome claim (numbers like "47 seconds", "12x faster")
7. Default stack tell: Inter + Slate/Zinc + rounded-lg + Lucide — pick one deviation
## Output report format
```
=== /SITE-BUILDER REPORT ===
Project: <project-root>
Stack: <Astro 6 / Next 16 / static>
Archetype: <SaaS / multi-page / portfolio / docs>
Style: <premium / dark-tech / editorial / brutalist>
Motion tier: <none / subtle / rich / experimental>
Sections built: <N>
- Nav locked, 23 KB screenshot
- Hero locked, 87 KB screenshot
- ...
WYSIWYD check: all locked, 0 drift
Audits: a11y=pass seo=2-minor perf=LCP 1.2s responsive=pass
Preview: <url>
Deploy: <url or "pending user confirm">
```
## References
- `$HOME/.claude/agents/_primitives/_rust/mock-render/` — WYSIWYD enforcer (Rust)
- `skills/landing-page/SKILL.md` — predecessor (single-page only)
- `skills/frontend-design/SKILL.md` — archetype philosophy
- `skills/motion-design/SKILL.md` — motion library choices
- `skills/scroll-animation/SKILL.md` — GSAP / scroll-timeline patterns
- `skills/web-deploy/SKILL.md` — CF Pages / Vercel deploy

View file

@ -0,0 +1,230 @@
---
name: site-teardown
description: "Deconstruct any live website into reusable recipe — extract HTML, CSS, JS, design tokens, animations. Use when user says: teardown, deconstruct, clone site, reverse engineer, how is this site built."
arguments:
- name: url
description: URL of the website to deconstruct
required: true
- name: depth
description: "quick = tokens + screenshots only, full = complete teardown (default: full)"
required: false
---
# Site Teardown — Deconstruct Any Website into a Reusable Recipe
Extracts design tokens, layout structure, animation techniques, and library stack from a live website.
Output: structured recipe that can be fed into `/frontend-design`, `/landing-page`, `/design-system`.
## Phase 1 — Navigate & Screenshot
```
1. browser_navigate → {url}
2. browser_resize → width: 1280, height: 900
3. browser_take_screenshot → fullPage: true, filename: "teardown-desktop.png"
4. browser_resize → width: 375, height: 812
5. browser_take_screenshot → fullPage: true, filename: "teardown-mobile.png"
6. browser_resize → width: 1280, height: 900 (restore)
```
Save screenshots to `teardown/{domain}/` in the project directory (relative to `$PWD`).
## Phase 2 — Extract HTML Structure
Run `browser_evaluate` with:
```javascript
() => {
const sections = Array.from(document.querySelectorAll('section, [class*="section"], main > div'));
const nav = document.querySelector('nav, header');
const footer = document.querySelector('footer');
const headings = Array.from(document.querySelectorAll('h1, h2, h3')).map(h => ({
tag: h.tagName, text: h.textContent.trim().slice(0, 80)
}));
return {
title: document.title,
sectionCount: sections.length,
hasNav: !!nav,
navType: nav?.classList?.toString() || 'unknown',
hasFooter: !!footer,
headings,
bodyClasses: document.body.classList.toString(),
htmlLang: document.documentElement.lang
};
}
```
Also extract full HTML for deep analysis:
```javascript
() => document.documentElement.outerHTML
```
Save to `teardown/{domain}/index.html`.
## Phase 3 — Extract Design Tokens
Run `browser_evaluate` to extract computed styles from key elements:
```javascript
() => {
const get = (sel) => {
const el = document.querySelector(sel);
return el ? getComputedStyle(el) : null;
};
const body = get('body');
const h1 = get('h1');
const btn = get('a[class*="btn"], button[class*="btn"], .cta, a[class*="cta"]');
const card = get('[class*="card"], [class*="Card"]');
const props = {};
const root = getComputedStyle(document.documentElement);
for (const name of ['--primary', '--secondary', '--accent', '--background', '--foreground',
'--radius', '--font-sans', '--font-mono', '--brand']) {
const val = root.getPropertyValue(name).trim();
if (val) props[name] = val;
}
return {
colors: {
background: body?.backgroundColor,
text: body?.color,
heading: h1?.color,
button: btn ? { bg: btn.backgroundColor, text: btn.color, radius: btn.borderRadius } : null,
card: card ? { bg: card.backgroundColor, border: card.borderColor, radius: card.borderRadius, shadow: card.boxShadow } : null
},
typography: {
bodyFont: body?.fontFamily,
bodySize: body?.fontSize,
h1Font: h1?.fontFamily,
h1Size: h1?.fontSize,
h1Weight: h1?.fontWeight,
lineHeight: body?.lineHeight
},
spacing: {
bodyPadding: body?.padding,
sectionPadding: get('section')?.padding
},
customProperties: props
};
}
```
**Output:** Save as `teardown/{domain}/tokens.json`.
## Phase 4 — Fetch CSS & JS Sources
### 4a. Collect resource URLs
```javascript
() => {
const css = Array.from(document.querySelectorAll('link[rel="stylesheet"]')).map(l => l.href);
const js = Array.from(document.querySelectorAll('script[src]')).map(s => s.src);
const inlineStyles = document.querySelectorAll('style').length;
return { css, js, inlineStyleBlocks: inlineStyles };
}
```
### 4b. Fetch each CSS file via WebFetch
For each CSS URL: `WebFetch` with prompt:
> "Extract ALL design-relevant CSS from this stylesheet: custom properties (--vars), @keyframes, @font-face, color values, gradient definitions, backdrop-filter, box-shadow patterns, border-radius values, transition/animation properties. Return as structured list."
### 4c. Detect JS libraries
```javascript
() => ({
gsap: typeof gsap !== 'undefined',
ScrollTrigger: typeof ScrollTrigger !== 'undefined',
lenis: !!document.querySelector('[data-lenis-prevent]') || typeof Lenis !== 'undefined',
framerMotion: !!document.querySelector('[data-framer-component-type]'),
three: typeof THREE !== 'undefined',
curtains: typeof Curtains !== 'undefined',
particles: typeof tsParticles !== 'undefined',
aos: typeof AOS !== 'undefined',
locomotive: !!document.querySelector('[data-scroll-container]'),
swiper: typeof Swiper !== 'undefined',
tailwind: !!document.querySelector('[class*="bg-"], [class*="text-"], [class*="flex"]'),
react: typeof __NEXT_DATA__ !== 'undefined' || !!document.getElementById('__next'),
astro: !!document.querySelector('[data-astro-source-file]'),
vue: !!document.getElementById('__nuxt') || !!document.querySelector('[data-v-]')
})
```
### 4d. Network analysis (supplementary)
`browser_network_requests` with `filter: "\\.css$|\\.js$"`, `static: false` — cross-reference with DOM-extracted URLs.
## Phase 5 — Animation Catalog
```javascript
() => {
const anims = [];
const allEls = document.querySelectorAll('*');
const seen = new Set();
allEls.forEach(el => {
const s = getComputedStyle(el);
if (s.animationName && s.animationName !== 'none' && !seen.has(s.animationName)) {
seen.add(s.animationName);
anims.push({ type: 'css-animation', name: s.animationName, duration: s.animationDuration });
}
if (s.transition && s.transition !== 'all 0s ease 0s' && s.transition !== 'none') {
const key = s.transition.slice(0, 60);
if (!seen.has(key)) { seen.add(key); anims.push({ type: 'transition', value: s.transition.slice(0, 120) }); }
}
});
const canvases = document.querySelectorAll('canvas').length;
const videos = document.querySelectorAll('video').length;
const svgAnims = document.querySelectorAll('animate, animateTransform').length;
return { animations: anims, canvasCount: canvases, videoCount: videos, svgAnimations: svgAnims };
}
```
**Output:** Save analysis as `teardown/{domain}/animations.md`.
If `depth=quick` → STOP here with tokens + screenshots only.
## Phase 6 — Compile Recipe
Assemble `teardown/{domain}/recipe.md`:
```markdown
# Site Teardown: {domain}
Date: {date}
## Layout Structure
{section map from Phase 2}
## Design Tokens
{from Phase 3 — colors, typography, spacing}
## Tech Stack
- Framework: {React/Next/Astro/Vue from Phase 4c}
- CSS: {Tailwind/custom/styled-components}
- Animation: {GSAP/Framer Motion/CSS/AOS from Phase 4c}
- Scroll: {Lenis/Locomotive/native from Phase 4c}
- 3D/WebGL: {Three.js/curtains.js/none from Phase 4c}
## Animation Techniques
{catalog from Phase 5}
## Reproduction Steps
1. Set up {framework} project with {css approach}
2. Apply design tokens: {token summary}
3. Implement layout: {section sequence}
4. Add animations: {technique list with skill references}
5. Optimize: /web-assets → /a11y-audit → /perf-audit
## Recommended Skills
- /frontend-design archetype={suggested}
- /scroll-animation technique={if GSAP detected}
- /web-effects effect={if WebGL detected}
- /motion-design {if Framer Motion detected}
```
## Chaining
| Direction | Skill | How |
|-----------|-------|-----|
| FROM | `/design-inspiration` | User picks best reference → teardown |
| FROM | `/competitor-analysis` | Deep-dive competitor's site |
| TO | `/frontend-design` | Feed tokens → suggest archetype |
| TO | `/landing-page` | Use recipe as template |
| TO | `/design-system` | Generate token system from extracted tokens |
| TO | `/scroll-animation` | Reproduce detected scroll effects |
| TO | `/web-effects` | Reproduce detected WebGL/particle effects |

View file

@ -0,0 +1,66 @@
---
name: ui-component
description: Use when building a UI component — API design, variants, accessibility, animations, tests
arguments:
- name: component
description: Component name and description
required: true
- name: framework
description: "Framework: react, next, astro, svelte, vue (auto-detect if omitted)"
required: false
---
# UI Component Workflow
## Step 1: Research
- Check if component exists in project already (Glob/Grep)
- Check existing component library for similar components
- Review design system tokens if available
- Identify the component's role and variations needed
## Step 2: API Design (Props First)
Define before implementing:
```
interface ComponentProps {
// Required props
// Optional props with defaults
// Event handlers
// Composition slots (children, render props)
// Style overrides (className, style)
}
```
- Keep API minimal — only props that are actually needed
- Use discriminated unions for variant props
- Sensible defaults for all optional props
## Step 3: Implementation
- Follow project's component patterns exactly
- Compose from existing primitives when possible
- Variants via props, not separate components
### Accessibility
- Semantic HTML elements
- ARIA attributes where needed
- Keyboard navigation (Tab, Enter, Escape, Arrow keys)
- Focus management and visible focus styles
- Screen reader announcements for dynamic content
- Color contrast WCAG AA (4.5:1 text, 3:1 large/UI)
### Animations
- Use CSS transitions/animations over JS when possible
- Respect `prefers-reduced-motion`
- Consistent timing from design system tokens
- Enter/exit animations for conditional rendering
## Step 4: Tests
- Render test (mounts without error)
- Props test (each variant renders correctly)
- Interaction test (click, hover, keyboard)
- Accessibility test (axe-core or similar)
## Step 5: Examples
- Default usage
- All variants
- With different content lengths
- Responsive behavior
- Dark mode

110
skills/web-assets/SKILL.md Normal file
View file

@ -0,0 +1,110 @@
---
name: web-assets
description: Use when optimizing images, fonts, and video for web — AVIF pipeline, responsive srcset, font subsetting, video codec selection, Sharp.js processing. Triggers on "optimize images", "web assets", "image pipeline", "font optimization".
arguments:
- name: command
description: "Command: optimize, picture, fonts, video, audit, pipeline"
required: false
- name: target
description: Directory or file path to process
required: false
---
# Image & Asset Optimization Pipeline
Optimize images, fonts, and video for premium web performance.
## Decision Matrix
| Asset | Tool | Format | Quality |
|-------|------|--------|---------|
| Photos | Sharp.js | AVIF primary, WebP fallback | avif:50, webp:75, jpg:80 |
| Icons | SVG sprite | `<symbol>` + `<use>` | N/A |
| Fonts | glyphhanger | WOFF2 only, subset | variable font preferred |
| Video | FFmpeg | AV1 > H.265 > H.264 | CRF 28-32 |
| AI-generated images | External generator (e.g. fal.ai) + Sharp | Process through Sharp | per above |
## Image Pipeline (Sharp.js)
```bash
npm ls sharp 2>/dev/null || npm install sharp
```
Breakpoints: 400, 640, 768, 1024, 1280, 1920px. Max 2560px for Retina.
```javascript
const sharp = require('sharp');
const WIDTHS = [400, 640, 768, 1024, 1280, 1920];
const FORMATS = ['avif', 'webp', 'jpg'];
const QUALITY = { avif: 50, webp: 75, jpg: 80 };
async function processImage(inputPath, outputDir) {
const name = path.parse(inputPath).name;
fs.mkdirSync(outputDir, { recursive: true });
for (const width of WIDTHS) {
for (const format of FORMATS) {
await sharp(inputPath)
.resize(width, null, { withoutEnlargement: true })
.toFormat(format, { quality: QUALITY[format] })
.toFile(path.join(outputDir, `${name}-${width}.${format}`));
}
}
}
```
### Picture Element
```html
<picture>
<source type="image/avif"
srcset="img/hero-400.avif 400w, img/hero-768.avif 768w, img/hero-1280.avif 1280w, img/hero-1920.avif 1920w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 60vw" />
<source type="image/webp"
srcset="img/hero-400.webp 400w, img/hero-768.webp 768w, img/hero-1280.webp 1280w, img/hero-1920.webp 1920w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 80vw, 60vw" />
<img src="img/hero-1280.jpg" alt="Descriptive alt text"
width="1280" height="720" loading="lazy" decoding="async" />
</picture>
```
Hero/LCP image: `fetchpriority="high"`, NO `loading="lazy"`.
## Font Optimization
- Variable fonts = industry standard. WOFF2 only (97%+ support)
- Subset with glyphhanger: `glyphhanger --US_ASCII --subset=font.woff2 --formats=woff2` (60%+ reduction)
- `font-display: swap` + preload critical: `<link rel="preload" href="/fonts/heading.woff2" as="font" type="font/woff2" crossorigin />`
## SVG Sprites
```html
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="icon-arrow" viewBox="0 0 24 24"><path d="M5 12h14M12 5l7 7-7 7"/></symbol>
</svg>
<svg class="icon" aria-hidden="true"><use href="/sprites.svg#icon-arrow"/></svg>
```
## Video
AV1 primary (30-50% better than H.264), H.265 fallback, H.264 universal. Always set poster, width/height.
```html
<video autoplay muted loop playsinline poster="hero-poster.avif" preload="none" width="1920" height="1080">
<source src="hero.av1.mp4" type='video/mp4; codecs="av01.0.08M.08"' />
<source src="hero.h265.mp4" type='video/mp4; codecs="hvc1"' />
<source src="hero.h264.mp4" type="video/mp4" />
</video>
```
Lazy load via IntersectionObserver (no native `loading="lazy"` for `<video>`).
## Audit Checklist
- [ ] All images: AVIF + WebP + fallback, responsive srcset
- [ ] All `<img>`: explicit width/height (prevents CLS)
- [ ] Hero/LCP: `fetchpriority="high"`, no lazy loading
- [ ] Below-fold: `loading="lazy" decoding="async"`
- [ ] Fonts: WOFF2, subsetted, font-display: swap, critical preloaded
- [ ] Icons: SVG sprites (not individual files or icon fonts)
- [ ] Video: AV1 > H.265 > H.264 cascade, poster image
- [ ] No images >500KB, total page <1.5MB ideal

101
skills/web-deploy/SKILL.md Normal file
View file

@ -0,0 +1,101 @@
---
name: web-deploy
description: Use when deploying websites — Cloudflare Pages, Vercel, edge functions, caching strategy, Core Web Vitals, CI/CD pipeline, DNS setup. Triggers on "deploy", "hosting", "cloudflare pages", "web vitals", "caching strategy".
arguments:
- name: command
description: "Command: init, deploy, perf, cache, dns, ci, compare"
required: false
- name: framework
description: "Framework: astro, next, sveltekit, react-router (auto-detect if omitted)"
required: false
---
# Web Deployment & Performance
Default target: Cloudflare Pages. Default framework: Astro 6.
## Platform Decision
| Platform | Free Tier | Pro Price | Best For |
|----------|-----------|-----------|----------|
| **Cloudflare Pages** | Unlimited BW, 500 builds/mo | $5/mo | Content sites, marketing (DEFAULT) |
| Vercel | 100GB BW, 100 deploys/day | $20/user/mo | Next.js full-stack apps |
| Netlify | 100GB BW, 300 build min | $19/user/mo | Static + built-in forms |
Cloudflare ecosystem: Workers, D1, R2, KV, Turnstile, Analytics — all free tier.
## Framework Decision
| Framework | Zero JS | Islands | Best For |
|-----------|---------|---------|----------|
| **Astro 6** | Yes | Yes | Content/marketing (DEFAULT) |
| Next.js 16 | No | No | Full-stack React apps |
| SvelteKit | Compiles | No | Animation-heavy, mobile-first |
Astro 6 static output: typical LCP <500ms on CF Pages.
## CDN Caching Strategy
| Asset Type | Cache-Control |
|-----------|---------------|
| Hashed JS/CSS/fonts | `public, max-age=31536000, immutable` |
| HTML pages | `public, max-age=0, s-maxage=3600, stale-while-revalidate=86400` |
| API/dynamic | `public, s-maxage=60, stale-while-revalidate=300` |
| Images | `public, max-age=86400, s-maxage=604800` |
## Core Web Vitals
| Metric | Good | Key Fix |
|--------|------|---------|
| LCP | <2.5s | Preload hero: `fetchpriority="high"`, inline critical CSS, preload fonts |
| INP | <200ms | Break tasks >50ms, `requestIdleCallback`, defer 3rd-party |
| CLS | <0.1 | Width/height on all images/video, `aspect-ratio`, font size-adjust |
## GitHub Actions CI/CD
```yaml
name: Deploy
on: { push: { branches: [main] } }
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: npm }
- run: npm ci && npm run build && npm test
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=my-site
```
Secrets: `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID`.
## Cloudflare DNS + SSL
1. Add domain, change nameservers
2. `A @ <ip> Proxied` + `CNAME www @ Proxied`
3. SSL: Full (Strict), Always HTTPS, HSTS
4. www→apex redirect rule (301)
## Edge Functions
| Feature | CF Workers | Vercel Edge |
|---------|-----------|-------------|
| Locations | 330+ | 30+ |
| Cold start | <1ms | <50ms |
| Free | 100K req/day | 1M/month |
## Deploy Checklist
- [ ] Build succeeds, tests pass
- [ ] Lighthouse Performance >90
- [ ] Core Web Vitals green
- [ ] Caching headers per asset type
- [ ] SSL/HTTPS enforced
- [ ] www/apex redirect
- [ ] Error pages (404, 500) configured
- [ ] Security headers: CSP, X-Frame-Options, Referrer-Policy
- [ ] Environment variables in dashboard

315
skills/web-effects/SKILL.md Normal file
View file

@ -0,0 +1,315 @@
---
name: web-effects
description: Use when building visual web effects — WebGL shaders, image distortion, particles, noise/grain, hover effects, displacement maps. Covers curtains.js, OGL, tsParticles, custom WebGL, and CSS-only effects.
arguments:
- name: effect
description: "Effect: distortion, particles, noise, hover, displacement, gradient, blur (auto-detect if omitted)"
required: false
- name: approach
description: "Approach: css-only, webgl, canvas, library (auto-detect by complexity)"
required: false
---
# Web Effects Skill
## Decision Matrix — Pick Approach
| Effect | CSS Only | Canvas 2D | WebGL (library) | Custom WebGL |
|--------|----------|-----------|-----------------|--------------|
| Image hover distortion | No | No | curtains.js | Possible |
| Particles (decorative) | Limited | Possible | tsParticles | Best perf |
| Noise/grain overlay | Yes | Yes | Shader | Overkill |
| Gradient animation | Yes | Possible | Unnecessary | No |
| Blur/glassmorphism | Yes | No | No | No |
| Displacement on scroll | No | No | curtains.js/OGL | Possible |
| Liquid/fluid effects | No | No | OGL | Yes |
| Image reveal/transition | CSS clip-path | Canvas | curtains.js | Possible |
**Rule:** Start with CSS. Escalate to Canvas/WebGL only when CSS cannot achieve the effect.
---
## 1. Curtains.js — DOM-Driven WebGL
**Bundle:** ~30KB min+gzip
**What it does:** Converts HTML images/videos/canvases into WebGL textured planes that stay positioned with DOM layout.
**Best for:** Image hover distortion, displacement effects, WebGL transitions between slides.
```js
import { Curtains, Plane } from "curtainsjs";
const curtains = new Curtains({ container: "#canvas" });
const plane = new Plane(curtains, document.querySelector(".image-wrapper"), {
vertexShader: vertexShaderSource,
fragmentShader: fragmentShaderSource,
uniforms: {
uMouse: { name: "uMouse", type: "2f", value: [0, 0] },
uTime: { name: "uTime", type: "1f", value: 0 },
}
});
plane.onRender(() => { plane.uniforms.uTime.value++; });
document.querySelector(".image-wrapper").addEventListener("mousemove", (e) => {
const rect = e.target.getBoundingClientRect();
plane.uniforms.uMouse.value = [
(e.clientX - rect.left) / rect.width,
1 - (e.clientY - rect.top) / rect.height
];
});
```
### Displacement Shader (Hover Distortion)
```glsl
precision mediump float;
varying vec2 vTextureCoord;
uniform sampler2D uSampler0;
uniform sampler2D uDisplacement;
uniform vec2 uMouse;
void main() {
vec2 uv = vTextureCoord;
vec4 disp = texture2D(uDisplacement, uv);
float dist = distance(uv, uMouse);
float strength = smoothstep(0.3, 0.0, dist) * 0.05;
uv += disp.rg * strength;
gl_FragColor = texture2D(uSampler0, uv);
}
```
**Note:** `gpu-curtains` is a WebGPU successor worth watching.
---
## 2. OGL — Minimal WebGL
**Bundle:** ~8KB gzip, zero dependencies
**What it does:** Thin WebGL abstraction, you write your own shaders.
**Best for:** Custom shader effects, fullscreen post-processing, when curtains.js is too opinionated.
```js
import { Renderer, Camera, Program, Mesh, Plane } from "ogl";
const renderer = new Renderer();
const gl = renderer.gl;
document.body.appendChild(gl.canvas);
const camera = new Camera(gl);
camera.position.z = 1;
const geometry = new Plane(gl);
const program = new Program(gl, {
vertex: `
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUv;
void main() { vUv = uv; gl_Position = vec4(position, 1.0); }
`,
fragment: `
precision highp float;
varying vec2 vUv;
uniform float uTime;
void main() {
gl_FragColor = vec4(vec3(sin(uTime + vUv.x * 6.28) * 0.5 + 0.5), 1.0);
}
`,
uniforms: { uTime: { value: 0 } }
});
const mesh = new Mesh(gl, { geometry, program });
function update(t) {
requestAnimationFrame(update);
program.uniforms.uTime.value = t * 0.001;
renderer.render({ scene: mesh, camera });
}
requestAnimationFrame(update);
```
**OGL vs Three.js:** OGL is 8KB vs Three.js ~150KB. Use OGL for shader effects where you do not need a scene graph, models, or physics.
---
## 3. Particles
### tsParticles (Library)
**Install:** `npm i tsparticles`
**Bundle:** ~40KB min+gzip (core), modular
**Frameworks:** React, Vue, Svelte, Angular, Solid, vanilla
```jsx
import Particles from "@tsparticles/react";
import { loadSlim } from "@tsparticles/slim";
function Background() {
const init = useCallback(async (engine) => { await loadSlim(engine); }, []);
return (
<Particles
init={init}
options={{
particles: {
number: { value: 50 },
size: { value: { min: 1, max: 3 } },
move: { enable: true, speed: 0.5 },
opacity: { value: { min: 0.1, max: 0.5 } },
links: { enable: true, distance: 150, opacity: 0.2 },
},
detectRetina: true,
}}
/>
);
}
```
### Custom WebGL Particles (Performance-Critical)
When you need 10K+ particles at 60fps, do everything in shaders:
```glsl
attribute vec3 position;
attribute vec2 velocity;
attribute float life;
uniform float uTime;
uniform float uDelta;
void main() {
vec3 pos = position + vec3(velocity * uDelta, 0.0);
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
gl_PointSize = mix(3.0, 0.0, life);
}
```
**Decision:** tsParticles for <1000 particles with config flexibility. Custom WebGL for >1000 particles or specific visual needs.
---
## 4. CSS-Only Effects
### Animated Gradient
```css
.gradient-bg {
background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
background-size: 400% 400%;
animation: gradient-shift 15s ease infinite;
}
@keyframes gradient-shift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
```
### Noise/Grain Overlay (CSS)
```css
.grain::after {
content: "";
position: fixed;
inset: 0;
background-image: url("data:image/svg+xml,...");
opacity: 0.05;
pointer-events: none;
z-index: 9999;
mix-blend-mode: overlay;
}
```
### Glassmorphism
```css
.glass {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(12px) saturate(150%);
-webkit-backdrop-filter: blur(12px) saturate(150%);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 16px;
}
```
### Image Reveal (Clip-Path)
```css
.reveal {
clip-path: inset(0 100% 0 0);
transition: clip-path 0.8s cubic-bezier(0.77, 0, 0.175, 1);
}
.reveal.visible { clip-path: inset(0 0 0 0); }
```
### Hover Magnetic Effect (JS Required)
```js
const btn = document.querySelector(".magnetic-btn");
btn.addEventListener("mousemove", (e) => {
const rect = btn.getBoundingClientRect();
const x = e.clientX - rect.left - rect.width / 2;
const y = e.clientY - rect.top - rect.height / 2;
btn.style.transform = `translate(${x * 0.3}px, ${y * 0.3}px)`;
});
btn.addEventListener("mouseleave", () => {
btn.style.transform = "translate(0, 0)";
btn.style.transition = "transform 0.5s ease";
});
```
---
## 5. Performance Rules
### GPU-Composited Properties (animate these)
```
transform — translate, rotate, scale
opacity — fade in/out
filter — blur, brightness
clip-path — reveal/hide
```
### Layout-Triggering Properties (avoid animating)
```
width, height, top, left, right, bottom
margin, padding, border-width
font-size, line-height
```
### will-change
```css
.about-to-animate { will-change: transform, opacity; }
/* Do NOT: * { will-change: transform; } */
```
### Frame Budget
- **60fps target:** 16.66ms per frame
- **Pause offscreen:** IntersectionObserver to stop animations outside viewport
```js
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) startRenderLoop();
else stopRenderLoop();
});
observer.observe(canvasElement);
```
---
## Workflow
1. **Define the effect** — what visual result is needed?
2. **Try CSS first** — gradient, blur, clip-path, mix-blend-mode
3. **Escalate to Canvas/WebGL** — only if CSS cannot achieve it
4. **Pick library** — curtains.js for DOM-synced, OGL for custom shaders
5. **Write shader** — keep fragment shaders simple, profile on mobile
6. **Add IntersectionObserver** — pause offscreen effects
7. **Test performance** — Chrome DevTools Performance, GPU memory
8. **Add prefers-reduced-motion** — disable or simplify effects