# Craft Recipes

The generator never improvises motion, depth, or 3D. It composes from this vocabulary. Match recipes to the cluster's motion descriptors.

## Motion recipes

### Hero staggered reveal on load
- Split headline into lines via `.line > span` pattern, overflow hidden, translate-Y 110% → 0
- Trigger via class toggle on `DOMContentLoaded`, staggered by ~140ms per line
- Easing: `cubic-bezier(0.19, 1, 0.22, 1)` (expo out)
- Duration: 1200ms per line
- Gate with `html.js` class so no-JS state doesn't hide content

### Magnetic cursor (use sparingly, not on every link)
- Apply only to primary CTAs or hero-scale interactive elements
- Max offset 8–12px toward cursor, with spring damping
- Never on body text links

### Scroll-linked parallax
- Apply to hero image or display text
- Ratio range: 0.05–0.2 — subtle, not theatrical
- Clamp to prevent overlap with elements below
- Use `requestAnimationFrame`-throttled scroll handler

### Split text reveal on scroll
- Per-line or per-word reveal as element enters viewport
- Stagger: 40–80ms per unit
- Pair with clip-path or translate-Y for the reveal shape
- IntersectionObserver trigger with rootMargin `-80px` to fire at edge

### Marquee infinite scroll
- For back issues, case studies, logo walls, press mentions
- Duplicate content once, use `animation: marquee Xs linear infinite`
- Pause on hover with `animation-play-state: paused`
- Speed 40–80s per loop depending on content length

### View Transitions API for route changes
- `@view-transition { navigation: auto }` in CSS
- Custom `::view-transition-old/new(root)` for crossfade or slide
- Fallback gracefully in browsers without support

### Clip-path image reveal
- On section enter, animate `clip-path: inset(0 100% 0 0)` → `inset(0 0 0 0)`
- Duration 1200–1600ms, ease-out-expo
- Good for photograph reveals in editorial sections

### Cursor-linked rotation / tilt
- On hero scene: rotate or translate the visual element subtly toward cursor
- Max rotation: 6–14° depending on scale
- Lerp-smoothed (factor 0.08) so it feels mechanical, not snappy

## Depth recipes

### Elevation tokens (6 tiers)
```
--elev-0: none (flat / ink-on-surface)
--elev-1: 0 1px 2px rgba(0,0,0,0.04)              // hairline
--elev-2: 0 2px 8px rgba(0,0,0,0.06)              // card resting
--elev-3: 0 8px 24px rgba(0,0,0,0.10)             // card hover
--elev-4: 0 16px 48px rgba(0,0,0,0.16)            // modal
--elev-5: 0 32px 96px rgba(0,0,0,0.22)            // hero lifted
```
Never apply the same elevation across all cards. Pick 2–3 tiers per project.

### Atmospheric depth
- Radial vignette: `radial-gradient(ellipse at 50% 50%, transparent 40%, rgba(0,0,0,0.55) 100%)` fixed overlay
- Grain: SVG noise data URI, 35% opacity, `mix-blend-mode: multiply` on light bg or `overlay` on dark
- Fog on distant elements: `filter: blur(1.5px)` + reduced opacity at scroll distance
- Warm glow pools for instrument/object scenes: radial `rgba(255,140,59,0.06)` under the subject

### Parallax layer grammar
- Background layer: 0.02–0.05 scroll ratio (almost still)
- Mid layer (text): 0 (normal scroll)
- Subject: 0.05–0.1 scroll ratio
- Foreground ornament: 0.15–0.3 scroll ratio

## 3D / WebGL recipes (gated — only if cluster warrants)

Invoke only when matched cluster includes Bruno Simon / Igloo / WebGL-tagged references AND the domain would plausibly ship 3D.

### Three.js / R3F scene primitives
- Floating product (camera lens, watch, object): single 3D model at center, orbit-controls disabled, slow auto-rotate ~0.02 rad/s, environment map from HDRi
- Scroll-driven camera: camera position interpolates along a spline as user scrolls
- Particle field: 500–2000 particles with cursor-reactive force field

### Fallback to SVG when 3D is too heavy
- Render the same visual concept in layered SVG with gradients
- Use `feGaussianBlur` for soft edges, multiple radial gradients for pseudo-3D shading
- Animate via transform on the SVG group, not individual elements

### Lens / aperture as functional graphic
- Multi-layer SVG with gradient-filled blades
- Pupil glow via inner radial gradient with warm hue
- Mask with scroll-linked scale for iris-opening effect
- Example implementation: see the Aperture project in `demos/eye-library-proof/generated/aperture/`

### Canvas 2D noise / shader backgrounds
- Simplex noise displacement for liquid gradient backgrounds
- Sample noise at time-varying coordinates for ambient motion
- Render to offscreen canvas, composite via `mix-blend-mode: screen` or `soft-light`

## Typography recipes

### Massive display type as hero visual
- Font size: `clamp(72px, 9vw, 156px)` or larger
- Line height: 0.86–0.96 (tight)
- Letter spacing: -0.02em to -0.04em
- Use variable font axes (optical sizing, grade, weight) to give the type character
- Mix weights within one word for emphasis: `The case studies leave <em>out</em>.`

### Drop cap
- `::first-letter { float: left; font-size: 6em; line-height: 0.82; }`
- Reserve for genuinely editorial contexts

### Structural caption (monospace small-caps)
- `font-family: [mono]; font-size: 10-11px; letter-spacing: 0.18-0.28em; text-transform: uppercase;`
- Use for: annotations, labels, kickers, page numbers, meta

### Oversized ornamental punctuation
- Quote marks, ampersands, section marks at 280–520px, 10–15% opacity
- Positioned absolute as background ornament
- Pairs well with large display serif pull quotes

## Non-basic defaults — always scaffold these

Every new project starts with:
- Lenis smooth scroll (or `scroll-behavior: smooth` fallback)
- Curated easing bank in `:root` CSS vars (no `ease-linear` unless explicitly needed)
- `prefers-reduced-motion` respect on every animation
- View Transitions API polyfill
- Grain overlay on major visual sections
- Off-black + off-white (never pure black/white unless intentional for a specific aesthetic like Swiss)

User has to actively strip motion / grain / depth to get a static page. Default is non-basic.
