chore: install openagent opencode
Signed-off-by: Dmytro Stanchiev <git@dmytros.dev>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
<!-- Context: ui/scroll-linked-animations | Priority: critical | Version: 1.0 | Updated: 2026-02-15 -->
|
||||
|
||||
# Concept: Scroll-Linked Animations
|
||||
|
||||
**Purpose**: Sync image sequences to scroll position for cinematic product reveals
|
||||
|
||||
**Last Updated**: 2026-01-07
|
||||
|
||||
---
|
||||
|
||||
## Core Idea
|
||||
|
||||
Map scroll position to video frames. As user scrolls, play through image sequence like a scrubbing timeline. Creates illusion of 3D animation controlled by scroll.
|
||||
|
||||
**Formula**: `scrollProgress (0→1) → frameIndex (0→N) → canvas.drawImage()`
|
||||
|
||||
---
|
||||
|
||||
## Essential Parts
|
||||
|
||||
1. **Image sequence** - 60-150 WebP frames from video/3D render
|
||||
2. **Sticky canvas** - Fixed HTML5 canvas, always visible while scrolling
|
||||
3. **Scroll tracker** - Framer Motion `useScroll` hook
|
||||
4. **Preloader** - Load all frames upfront (prevents flicker)
|
||||
5. **Background match** - Page BG = image BG (hides edges)
|
||||
|
||||
---
|
||||
|
||||
## Minimal Example
|
||||
|
||||
```tsx
|
||||
const { scrollYProgress } = useScroll({ target: containerRef })
|
||||
const frameIndex = useTransform(scrollYProgress, [0, 1], [0, 119])
|
||||
|
||||
useEffect(() => {
|
||||
ctx.drawImage(images[Math.round(frameIndex)], 0, 0)
|
||||
}, [frameIndex])
|
||||
```
|
||||
|
||||
**Why canvas?** 10x faster than swapping `<img src>`. DOM updates are slow.
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- examples/scrollytelling-headphone.md - Full code
|
||||
- guides/building-scrollytelling-pages.md - Implementation
|
||||
- lookup/scroll-animation-prompts.md - Generate sequences
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
[Apple AirPods Pro](https://www.apple.com/airpods-pro/) - Production example
|
||||
@@ -0,0 +1,265 @@
|
||||
<!-- Context: ui/scrollytelling-headphone | Priority: high | Version: 1.0 | Updated: 2026-02-15 -->
|
||||
|
||||
---
|
||||
description: "Full Next.js implementation of scroll-linked image sequence animation"
|
||||
---
|
||||
|
||||
# Example: Scrollytelling Headphone Animation
|
||||
|
||||
**Purpose**: Full Next.js implementation of scroll-linked image sequence animation
|
||||
|
||||
**Last Updated**: 2026-01-07
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Complete working example of "Zenith X" headphone scrollytelling page using Next.js 14, Framer Motion, and Canvas.
|
||||
|
||||
**Tech Stack**: Next.js 14 (App Router) + Framer Motion + Canvas + Tailwind CSS
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
app/
|
||||
├── page.tsx
|
||||
├── components/
|
||||
│ └── HeadphoneScroll.tsx
|
||||
└── globals.css
|
||||
public/
|
||||
└── frames/
|
||||
└── frame_0001.webp through frame_0120.webp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. globals.css
|
||||
|
||||
```css
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@apply bg-[#050505] text-white antialiased;
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. app/page.tsx
|
||||
|
||||
```tsx
|
||||
import HeadphoneScroll from './components/HeadphoneScroll'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<main className="bg-[#050505]">
|
||||
<HeadphoneScroll />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. components/HeadphoneScroll.tsx
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { motion, useScroll, useTransform } from 'framer-motion'
|
||||
|
||||
const FRAME_COUNT = 120
|
||||
|
||||
export default function HeadphoneScroll() {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const [images, setImages] = useState<HTMLImageElement[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
// Track scroll progress (0 to 1)
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: containerRef,
|
||||
offset: ['start start', 'end end']
|
||||
})
|
||||
|
||||
// Map scroll progress to frame index
|
||||
const frameIndex = useTransform(scrollYProgress, [0, 1], [0, FRAME_COUNT - 1])
|
||||
const [currentFrame, setCurrentFrame] = useState(0)
|
||||
|
||||
// Update current frame
|
||||
useEffect(() => {
|
||||
return frameIndex.on('change', (latest) => {
|
||||
setCurrentFrame(Math.round(latest))
|
||||
})
|
||||
}, [frameIndex])
|
||||
|
||||
// Preload all images
|
||||
useEffect(() => {
|
||||
const loadImages = async () => {
|
||||
const promises = Array.from({ length: FRAME_COUNT }, (_, i) => {
|
||||
return new Promise<HTMLImageElement>((resolve) => {
|
||||
const img = new Image()
|
||||
const frameNum = String(i + 1).padStart(4, '0')
|
||||
img.src = `/frames/frame_${frameNum}.webp`
|
||||
img.onload = () => resolve(img)
|
||||
})
|
||||
})
|
||||
|
||||
const loaded = await Promise.all(promises)
|
||||
setImages(loaded)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
loadImages()
|
||||
}, [])
|
||||
|
||||
// Render current frame to canvas
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current || !images.length) return
|
||||
|
||||
const canvas = canvasRef.current
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
const img = images[currentFrame]
|
||||
|
||||
// Set canvas size
|
||||
canvas.width = window.innerWidth
|
||||
canvas.height = window.innerHeight
|
||||
|
||||
// Clear and draw centered
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
const scale = Math.min(
|
||||
canvas.width / img.width,
|
||||
canvas.height / img.height
|
||||
)
|
||||
|
||||
const x = (canvas.width - img.width * scale) / 2
|
||||
const y = (canvas.height - img.height * scale) / 2
|
||||
|
||||
ctx.drawImage(img, x, y, img.width * scale, img.height * scale)
|
||||
}, [currentFrame, images])
|
||||
|
||||
// Text overlay opacity transforms
|
||||
const title = useTransform(scrollYProgress, [0, 0.1, 0.2], [1, 1, 0])
|
||||
const text1 = useTransform(scrollYProgress, [0.25, 0.3, 0.4], [0, 1, 0])
|
||||
const text2 = useTransform(scrollYProgress, [0.55, 0.6, 0.7], [0, 1, 0])
|
||||
const cta = useTransform(scrollYProgress, [0.85, 0.9, 1], [0, 1, 1])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-[#050505]">
|
||||
<div className="h-12 w-12 animate-spin rounded-full border-4 border-white/20 border-t-white" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="relative h-[400vh]">
|
||||
{/* Sticky Canvas */}
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="sticky top-0 h-screen w-full"
|
||||
style={{ willChange: 'transform' }}
|
||||
/>
|
||||
|
||||
{/* Text Overlays */}
|
||||
<motion.div
|
||||
style={{ opacity: title }}
|
||||
className="pointer-events-none fixed inset-0 flex items-center justify-center"
|
||||
>
|
||||
<div className="text-center">
|
||||
<h1 className="text-7xl font-bold tracking-tight text-white/90">
|
||||
Zenith X
|
||||
</h1>
|
||||
<p className="mt-4 text-xl text-white/60">Pure Sound.</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
style={{ opacity: text1 }}
|
||||
className="pointer-events-none fixed inset-y-0 left-20 flex items-center"
|
||||
>
|
||||
<p className="text-4xl font-bold tracking-tight text-white/90">
|
||||
Precision Engineering.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
style={{ opacity: text2 }}
|
||||
className="pointer-events-none fixed inset-y-0 right-20 flex items-center"
|
||||
>
|
||||
<p className="text-4xl font-bold tracking-tight text-white/90">
|
||||
Titanium Drivers.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
style={{ opacity: cta }}
|
||||
className="pointer-events-none fixed inset-0 flex items-center justify-center"
|
||||
>
|
||||
<div className="text-center">
|
||||
<h2 className="text-6xl font-bold tracking-tight text-white/90">
|
||||
Hear Everything.
|
||||
</h2>
|
||||
<button className="pointer-events-auto mt-8 rounded-full bg-white px-8 py-3 text-lg font-semibold text-black transition hover:bg-white/90">
|
||||
Pre-Order Now
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Implementation Details
|
||||
|
||||
**Line 15-18**: `useScroll` tracks scroll progress from container start to end
|
||||
**Line 21**: `useTransform` maps 0-1 scroll to 0-119 frame index
|
||||
**Line 32-46**: Preload all 120 frames using Promise.all
|
||||
**Line 49-70**: Draw current frame to canvas, scaled and centered
|
||||
**Line 73-76**: Text opacity transforms for fade in/out at specific scroll positions
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
1. Install dependencies: `bun --bun install framer-motion`
|
||||
2. Place 120 WebP frames in `/public/frames/`
|
||||
3. Copy code into respective files
|
||||
4. Run: `bun --bun run dev`
|
||||
|
||||
---
|
||||
|
||||
## Customization
|
||||
|
||||
**Change frame count**: Update `FRAME_COUNT` constant (line 7)
|
||||
**Adjust scroll length**: Change `h-[400vh]` to `h-[300vh]` or `h-[500vh]` (line 120)
|
||||
**Modify text timing**: Update transform ranges in lines 73-76
|
||||
**Change colors**: Update `bg-[#050505]` to match your image background
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- concepts/scroll-linked-animations.md - Understanding the technique
|
||||
- guides/scrollytelling-setup.md - Getting started
|
||||
- lookup/scroll-animation-prompts.md - Generating image sequences
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Framer Motion Docs](https://www.framer.com/motion/)
|
||||
- [Next.js App Router](https://nextjs.org/docs/app)
|
||||
@@ -0,0 +1,273 @@
|
||||
<!-- Context: ui/building-scrollytelling-pages | Priority: high | Version: 1.0 | Updated: 2026-02-15 -->
|
||||
|
||||
---
|
||||
description: "Step-by-step implementation of scroll-linked image sequence animations"
|
||||
---
|
||||
|
||||
# Guide: Building Scrollytelling Pages
|
||||
|
||||
**Purpose**: Step-by-step implementation of scroll-linked image sequence animations
|
||||
|
||||
**Last Updated**: 2026-01-07
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Next.js 14+ project with App Router
|
||||
- Framer Motion installed (`bun --bun i framer-motion`)
|
||||
- Tailwind CSS configured
|
||||
- Image sequence ready (60-240 WebP frames)
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Generate Image Sequences
|
||||
|
||||
Use nano banana or AI image tools to create start/end frames, then generate interpolation:
|
||||
|
||||
**Start frame prompt**:
|
||||
```
|
||||
Ultra-premium product photography of [product] on matte black surface,
|
||||
minimalistic studio shoot, deep black background with subtle gradient,
|
||||
soft rim lighting, cinematic, high contrast, luxury aesthetic, sharp focus,
|
||||
no clutter, DSLR 85mm f/1.8, photorealistic
|
||||
```
|
||||
|
||||
**End frame prompt**:
|
||||
```
|
||||
Exploded technical diagram of same [product], every component separated
|
||||
and floating in alignment, against deep black studio background, visible
|
||||
internal structure, hyper-realistic, studio rim lighting, cinematic,
|
||||
high contrast, no labels, photorealistic
|
||||
```
|
||||
|
||||
**Generate video**: Use AI video tools (Runway, Pika) to interpolate between frames.
|
||||
|
||||
**Export frames**: Use ffmpeg or ezgif to split video into 120+ WebP images.
|
||||
|
||||
```bash
|
||||
ffmpeg -i animation.mp4 -vf fps=30 frame_%04d.webp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Project Structure
|
||||
|
||||
```
|
||||
app/
|
||||
├── page.tsx # Main landing page
|
||||
├── components/
|
||||
│ └── HeadphoneScroll.tsx # Scroll animation component
|
||||
└── globals.css # Dark theme, Inter font
|
||||
public/
|
||||
└── frames/
|
||||
├── frame_0001.webp # 120+ frames
|
||||
├── frame_0002.webp
|
||||
└── ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Setup globals.css
|
||||
|
||||
```css
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@apply bg-[#050505] text-white antialiased;
|
||||
font-family: 'Inter', -apple-system, sans-serif;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Create Scroll Component
|
||||
|
||||
**Key patterns**:
|
||||
- Container with `h-[400vh]` for long scroll
|
||||
- Canvas with `sticky top-0` stays fixed
|
||||
- `useScroll` tracks scroll progress (0-1)
|
||||
- `useTransform` maps progress to frame index
|
||||
- `useEffect` preloads all images
|
||||
|
||||
**Core logic**:
|
||||
```tsx
|
||||
const { scrollYProgress } = useScroll({ target: containerRef })
|
||||
const frameIndex = useTransform(scrollYProgress, [0, 1], [0, 119])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Implement Preloader
|
||||
|
||||
Always preload images before starting animation:
|
||||
|
||||
```tsx
|
||||
useEffect(() => {
|
||||
const loadImages = async () => {
|
||||
const promises = Array.from({ length: 120 }, (_, i) => {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image()
|
||||
img.src = `/frames/frame_${String(i + 1).padStart(4, '0')}.webp`
|
||||
img.onload = () => resolve(img)
|
||||
})
|
||||
})
|
||||
|
||||
const loaded = await Promise.all(promises)
|
||||
setImages(loaded)
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
loadImages()
|
||||
}, [])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Canvas Rendering
|
||||
|
||||
Draw current frame to canvas on every scroll update:
|
||||
|
||||
```tsx
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current || !images.length) return
|
||||
|
||||
const canvas = canvasRef.current
|
||||
const ctx = canvas.getContext('2d')
|
||||
const img = images[Math.round(currentFrame)]
|
||||
|
||||
// Scale canvas to window
|
||||
canvas.width = window.innerWidth
|
||||
canvas.height = window.innerHeight
|
||||
|
||||
// Draw centered
|
||||
ctx.drawImage(img,
|
||||
(canvas.width - img.width) / 2,
|
||||
(canvas.height - img.height) / 2
|
||||
)
|
||||
}, [currentFrame, images])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Add Text Overlays
|
||||
|
||||
Fade text in/out at specific scroll positions:
|
||||
|
||||
```tsx
|
||||
<motion.div
|
||||
style={{
|
||||
opacity: useTransform(scrollYProgress,
|
||||
[0.25, 0.30, 0.35], // Fade in 25-30%, out 35%
|
||||
[0, 1, 0]
|
||||
)
|
||||
}}
|
||||
className="absolute left-20 text-4xl font-bold"
|
||||
>
|
||||
Precision Engineering.
|
||||
</motion.div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Match Backgrounds
|
||||
|
||||
**CRITICAL**: Page background MUST match image background exactly.
|
||||
|
||||
1. Open first frame in image editor
|
||||
2. Use eyedropper tool on background (e.g., `#050505`)
|
||||
3. Set page background to exact same color in globals.css
|
||||
4. Test: Image edges should be invisible
|
||||
|
||||
---
|
||||
|
||||
## Step 9: Optimize Performance
|
||||
|
||||
```tsx
|
||||
// Add GPU hint
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="sticky top-0 h-screen w-full"
|
||||
style={{ willChange: 'transform' }}
|
||||
/>
|
||||
|
||||
// Throttle redraws on mobile
|
||||
useEffect(() => {
|
||||
let rafId
|
||||
const render = () => {
|
||||
// Draw logic here
|
||||
rafId = requestAnimationFrame(render)
|
||||
}
|
||||
render()
|
||||
return () => cancelAnimationFrame(rafId)
|
||||
}, [])
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 10: Add Loading State
|
||||
|
||||
Show spinner while frames load:
|
||||
|
||||
```tsx
|
||||
{loading && (
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-[#050505]">
|
||||
<div className="animate-spin h-12 w-12 border-4 border-white/20 border-t-white rounded-full" />
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Issues & Fixes
|
||||
|
||||
### Images not loading
|
||||
- Check file paths match exactly (case-sensitive)
|
||||
- Verify all frames exist in `/public/frames/`
|
||||
- Open browser console for 404 errors
|
||||
|
||||
### Stuttering animation
|
||||
- Ensure all images preloaded before starting
|
||||
- Use WebP (not PNG/JPEG)
|
||||
- Check canvas size isn't too large
|
||||
|
||||
### Visible image edges
|
||||
- Background colors don't match exactly
|
||||
- Use eyedropper tool, not guessing
|
||||
- Check for gradients in image background
|
||||
|
||||
### Mobile performance
|
||||
- Reduce frame count (use every 2nd frame)
|
||||
- Debounce with requestAnimationFrame
|
||||
- Consider disabling on small screens
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] All frames load without 404s
|
||||
- [ ] Animation smooth from 0-100% scroll
|
||||
- [ ] Text fades in/out at correct positions
|
||||
- [ ] Background seamlessly blends with images
|
||||
- [ ] Loading spinner shows before animation
|
||||
- [ ] Works on mobile (or gracefully disabled)
|
||||
- [ ] No console errors
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- concepts/scroll-linked-animations.md - Understanding the technique
|
||||
- examples/headphone-scrollytelling.md - Full code example
|
||||
- lookup/animation-image-prompts.md - Prompts for frame generation
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Next.js Image Optimization](https://nextjs.org/docs/app/building-your-application/optimizing/images)
|
||||
- [Framer Motion useScroll](https://www.framer.com/motion/use-scroll/)
|
||||
@@ -0,0 +1,215 @@
|
||||
<!-- Context: ui/scroll-animation-prompts | Priority: high | Version: 1.0 | Updated: 2026-02-15 -->
|
||||
|
||||
---
|
||||
description: "AI prompts for generating start/end frames and video sequences for scrollytelling"
|
||||
---
|
||||
|
||||
# Lookup: Scroll Animation Image Generation Prompts
|
||||
|
||||
**Purpose**: AI prompts for generating start/end frames and video sequences for scrollytelling
|
||||
|
||||
**Last Updated**: 2026-01-07
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Use these prompts with nano banana, Runway, Pika, or other AI tools to create image sequences for scroll animations.
|
||||
|
||||
**Workflow**: Start frame → End frame → Video interpolation → Frame extraction
|
||||
|
||||
---
|
||||
|
||||
## Start Frame Prompts
|
||||
|
||||
### Product Hero Shot
|
||||
|
||||
```
|
||||
Ultra-premium product photography of [PRODUCT NAME] placed on a matte black
|
||||
surface, minimalistic studio photoshoot. Deep black background (#050505) with
|
||||
subtle gradient falloff, soft rim lighting outlining edges, controlled
|
||||
reflections on smooth textures. Cinematic lighting, high contrast, luxury tech
|
||||
aesthetic, sharp focus, shallow depth of field. No clutter, no text, no logos
|
||||
emphasized. Shot with professional DSLR, 85mm lens, f/1.8, ultra-high
|
||||
resolution, photorealistic, Apple-level product shoot, dramatic mood, modern
|
||||
and elegant.
|
||||
```
|
||||
|
||||
**Variables**: Replace [PRODUCT NAME] with your product
|
||||
**Output**: Starting position, fully assembled, hero angle
|
||||
|
||||
---
|
||||
|
||||
### Variations by Product Type
|
||||
|
||||
| Product Type | Additional Details |
|
||||
|--------------|-------------------|
|
||||
| **Headphones** | "over-ear headphones with leather cushions and metal headband" |
|
||||
| **Smartphone** | "smartphone with edge-to-edge OLED display, aluminum frame" |
|
||||
| **Watch** | "luxury smartwatch with titanium case and sapphire crystal" |
|
||||
| **Laptop** | "thin laptop with aluminum unibody, open at 45 degrees" |
|
||||
| **Camera** | "mirrorless camera with prime lens attached, side profile" |
|
||||
|
||||
---
|
||||
|
||||
## End Frame Prompts
|
||||
|
||||
### Exploded Technical View
|
||||
|
||||
```
|
||||
Exploded technical diagram view of [PRODUCT NAME], every component precisely
|
||||
separated and floating in perfect alignment, suspended in mid-air against deep
|
||||
black studio background (#050505). Visible internal structure including
|
||||
[INTERNAL COMPONENTS], all parts evenly spaced showing assembly order.
|
||||
Hyper-realistic product visualization, ultra-sharp focus, studio rim lighting
|
||||
identical to hero shot, soft highlights tracing each component, controlled
|
||||
reflections on matte and metal surfaces. Cinematic lighting, high contrast,
|
||||
luxury engineering aesthetic, no labels, no annotations, no text.
|
||||
Photorealistic, ultra-high resolution, Apple-style industrial design render,
|
||||
dramatic and clean.
|
||||
```
|
||||
|
||||
**Variables**:
|
||||
- [PRODUCT NAME]: Your product
|
||||
- [INTERNAL COMPONENTS]: Specific parts to show
|
||||
|
||||
---
|
||||
|
||||
### Internal Components by Product
|
||||
|
||||
| Product | Internal Components Examples |
|
||||
|---------|------------------------------|
|
||||
| **Headphones** | "copper wiring, titanium drivers, magnets, circuit boards, padding layers, metal frame" |
|
||||
| **Smartphone** | "battery, logic board, cameras, OLED panel, antenna bands, frame" |
|
||||
| **Watch** | "watch movement, gears, battery, sensors, display, crown mechanism" |
|
||||
| **Laptop** | "keyboard assembly, trackpad, battery cells, logic board, cooling fans, display panel" |
|
||||
| **Camera** | "sensor, shutter mechanism, lens elements, mirror assembly, circuit boards" |
|
||||
|
||||
---
|
||||
|
||||
## Video Interpolation Prompts
|
||||
|
||||
### For Runway/Pika
|
||||
|
||||
```
|
||||
Smoothly transition from fully assembled [PRODUCT] to exploded view.
|
||||
Components separate slowly and precisely, maintaining perfect alignment.
|
||||
Camera stays locked, product rotates slightly clockwise. Cinematic motion,
|
||||
professional product animation, 4-5 seconds duration, 30fps.
|
||||
```
|
||||
|
||||
**Settings**:
|
||||
- Duration: 4-5 seconds
|
||||
- FPS: 30 (yields 120-150 frames)
|
||||
- Camera: Static or slow orbit
|
||||
- Motion: Smooth, controlled separation
|
||||
|
||||
---
|
||||
|
||||
## Frame Extraction
|
||||
|
||||
### Using ffmpeg
|
||||
|
||||
```bash
|
||||
# Extract as WebP (best for web)
|
||||
ffmpeg -i animation.mp4 -vf fps=30 frame_%04d.webp
|
||||
|
||||
# Extract as PNG (higher quality, larger)
|
||||
ffmpeg -i animation.mp4 -vf fps=30 frame_%04d.png
|
||||
|
||||
# Extract with quality control
|
||||
ffmpeg -i animation.mp4 -vf fps=30 -quality 90 frame_%04d.webp
|
||||
```
|
||||
|
||||
### Using ezgif.com
|
||||
|
||||
1. Upload MP4 video
|
||||
2. Choose "Video to GIF" → "Split to frames"
|
||||
3. Select WebP format
|
||||
4. Download all frames as ZIP
|
||||
5. Rename: `frame_0001.webp`, `frame_0002.webp`, etc.
|
||||
|
||||
---
|
||||
|
||||
## Background Color Matching
|
||||
|
||||
**CRITICAL**: Page background MUST match image background exactly
|
||||
|
||||
### Recommended Dark Backgrounds
|
||||
|
||||
| Color Code | Usage |
|
||||
|------------|-------|
|
||||
| `#050505` | Pure black with slight lift (recommended) |
|
||||
| `#0a0a0a` | Slightly lighter, less harsh |
|
||||
| `#000000` | True black (only if images are true black) |
|
||||
| `#1a1a1a` | Dark gray (for lighter renders) |
|
||||
|
||||
**Pro tip**: Use eyedropper tool on first frame background, use exact hex in CSS
|
||||
|
||||
---
|
||||
|
||||
## Alternative Animation Styles
|
||||
|
||||
### Rotation (360° spin)
|
||||
|
||||
**Start**: Front view
|
||||
**End**: Front view (after 360° rotation)
|
||||
**Prompt**: "Rotate product 360 degrees on turntable, maintain lighting"
|
||||
|
||||
### Zoom In (Feature reveal)
|
||||
|
||||
**Start**: Full product view
|
||||
**End**: Close-up of key feature
|
||||
**Prompt**: "Smooth camera push-in focusing on [FEATURE], maintain focus"
|
||||
|
||||
### Morph (Color/style change)
|
||||
|
||||
**Start**: Product in color A
|
||||
**End**: Product in color B
|
||||
**Prompt**: "Seamlessly morph product color from [A] to [B], maintain form"
|
||||
|
||||
---
|
||||
|
||||
## Quality Settings
|
||||
|
||||
### For High-End Results
|
||||
|
||||
- **Resolution**: 1920x1080 minimum (4K for high-DPI)
|
||||
- **Format**: WebP (compression) or PNG (quality)
|
||||
- **Frame count**: 90-150 frames (3-5 seconds at 30fps)
|
||||
- **Total size**: Target <50MB for all frames combined
|
||||
|
||||
### Optimization Tips
|
||||
|
||||
1. Use WebP format (70% smaller than PNG)
|
||||
2. Compress with quality 85-90
|
||||
3. Resize images to max 2000px width
|
||||
4. Use consistent aspect ratio (16:9 or 1:1)
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Background color matches exactly (no visible edges)
|
||||
- [ ] All frames same dimensions
|
||||
- [ ] Smooth motion (no jumps between frames)
|
||||
- [ ] Consistent lighting across sequence
|
||||
- [ ] File names sequential (`frame_0001` to `frame_0120`)
|
||||
- [ ] Total file size reasonable (<50MB)
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- concepts/scroll-linked-animations.md - Understanding the technique
|
||||
- examples/scrollytelling-headphone.md - Full implementation
|
||||
- guides/scrollytelling-setup.md - Setup instructions
|
||||
|
||||
---
|
||||
|
||||
## Tool References
|
||||
|
||||
- [Runway ML](https://runwayml.com) - AI video generation
|
||||
- [Pika Labs](https://pika.art) - AI video interpolation
|
||||
- [ezgif](https://ezgif.com/split-video) - Frame extraction
|
||||
- [FFmpeg](https://ffmpeg.org) - Video processing
|
||||
95
.opencode/context/ui/web/design/navigation.md
Normal file
95
.opencode/context/ui/web/design/navigation.md
Normal file
@@ -0,0 +1,95 @@
|
||||
<!-- Context: ui/navigation | Priority: critical | Version: 1.0 | Updated: 2026-02-15 -->
|
||||
|
||||
---
|
||||
description: "Advanced web UI patterns - scroll animations, visual effects, and interactive design"
|
||||
---
|
||||
|
||||
# Web Design Patterns
|
||||
|
||||
**Purpose**: Advanced web UI patterns - scroll animations, visual effects, and interactive design
|
||||
|
||||
**Last Updated**: 2026-01-31
|
||||
|
||||
---
|
||||
|
||||
## Quick Navigation
|
||||
|
||||
### Concepts
|
||||
| File | Description | Priority |
|
||||
|------|-------------|----------|
|
||||
| [navigation.md](concepts/navigation.md) | Concepts navigation | high |
|
||||
| [scroll-linked-animations.md](concepts/scroll-linked-animations.md) | Scroll-synced image sequences (scrollytelling) | high |
|
||||
|
||||
### Examples
|
||||
| File | Description | Priority |
|
||||
|------|-------------|----------|
|
||||
| [navigation.md](examples/navigation.md) | Examples navigation | high |
|
||||
| [scrollytelling-headphone.md](examples/scrollytelling-headphone.md) | Full Next.js scroll animation example | high |
|
||||
|
||||
### Guides
|
||||
| File | Description | Priority |
|
||||
|------|-------------|----------|
|
||||
| [navigation.md](guides/navigation.md) | Guides navigation | high |
|
||||
| [building-scrollytelling-pages.md](guides/building-scrollytelling-pages.md) | Complete implementation guide | high |
|
||||
| [premium-dark-ui-visual-reference.md](guides/premium-dark-ui-visual-reference.md) | Visual reference for premium dark UI | medium |
|
||||
|
||||
### Lookup
|
||||
| File | Description | Priority |
|
||||
|------|-------------|----------|
|
||||
| [navigation.md](lookup/navigation.md) | Lookup navigation | high |
|
||||
| [scroll-animation-prompts.md](lookup/scroll-animation-prompts.md) | AI prompts for generating animation sequences | medium |
|
||||
|
||||
### Errors
|
||||
| File | Description | Priority |
|
||||
|------|-------------|----------|
|
||||
| _(No error files yet)_ | | |
|
||||
|
||||
---
|
||||
|
||||
## Loading Strategy
|
||||
|
||||
**For scroll animation work**:
|
||||
1. Load `concepts/scroll-linked-animations.md` (understand technique)
|
||||
2. Load `lookup/scroll-animation-prompts.md` (generate image sequences)
|
||||
3. Load `examples/scrollytelling-headphone.md` (see full code)
|
||||
4. Reference `guides/building-scrollytelling-pages.md` (step-by-step)
|
||||
|
||||
**For premium dark UI design**:
|
||||
1. Load `guides/premium-dark-ui-visual-reference.md` (visual patterns and implementation)
|
||||
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
This subcategory covers:
|
||||
- ✅ Scroll-linked animations (scrollytelling)
|
||||
- ✅ Canvas-based rendering
|
||||
- ✅ Framer Motion patterns
|
||||
- ✅ Image sequence generation
|
||||
- ✅ Premium dark UI design system
|
||||
- ✅ Glassmorphism patterns
|
||||
- ⏳ CSS animations (future)
|
||||
- ⏳ SVG animations (future)
|
||||
- ⏳ WebGL effects (future)
|
||||
|
||||
---
|
||||
|
||||
## Related Categories
|
||||
|
||||
- `ui/web/` - Core web UI patterns (parent directory)
|
||||
- `ui/web/animation-patterns.md` - CSS animations and transitions
|
||||
- `development/` - General development patterns
|
||||
|
||||
---
|
||||
|
||||
## Used By
|
||||
|
||||
**Agents**: frontend-specialist, design-specialist, animation-expert
|
||||
|
||||
## Statistics
|
||||
- Concepts: 1 + navigation
|
||||
- Examples: 1 + navigation
|
||||
- Guides: 6 + navigation
|
||||
- Lookup: 1 + navigation
|
||||
- Errors: 0
|
||||
- **Total**: 13 files
|
||||
Reference in New Issue
Block a user