Skip to content

Commit 205a8c4

Browse files
Ajit Pratap Singhclaude
authored andcommitted
feat(website): migrate to Motion package and add comprehensive animations
Replace framer-motion with the canonical motion package (same v12 codebase, cleaner imports from "motion/react"). Consolidate the dual animation system (FadeInCSS + FadeIn) into a single Motion-based FadeIn component with viewport-triggered scroll animations. Key changes: - Package migration: framer-motion -> motion (7 import files updated) - Shared animation infrastructure: motion-variants.ts with reusable variants - FadeIn component: now supports viewport prop for whileInView scroll triggers - ScrollProgressBar: gradient progress indicator at page top - Button: motion.button with spring hover/tap (scale 1.02/0.97) - GlassCard: hover lift (y: -2) + tap feedback (scale 0.99) - ThemeToggle: AnimatePresence icon swap with rotation - CopyButton: AnimatePresence scale-in icon transitions - SearchModal: AnimatePresence overlay fade + modal scale entrance - Benchmarks: animated chevron + AnimatePresence expand/collapse - Docs index: staggered category card entrance - 404 page: staggered entrance animation - All animations: GPU-accelerated (transform/opacity only) - All animations: respect prefers-reduced-motion - All animations: dark/light mode agnostic (pure transforms) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ce35f70 commit 205a8c4

30 files changed

Lines changed: 449 additions & 170 deletions

website/package-lock.json

Lines changed: 34 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

website/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"@vercel/analytics": "^2.0.1",
2323
"@vercel/speed-insights": "^2.0.0",
2424
"codemirror": "^6.0.2",
25-
"framer-motion": "^12.36.0",
25+
"motion": "^12.36.0",
2626
"fuse.js": "^7.1.0",
2727
"gray-matter": "^4.0.3",
2828
"next": "16.1.7",

website/src/app/benchmarks/BenchmarksContent.tsx

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22

33
import { useState } from 'react';
4+
import { motion, AnimatePresence } from 'motion/react';
45
import { FadeIn } from '@/components/ui/FadeIn';
56
import { GlassCard } from '@/components/ui/GlassCard';
67
import { Button } from '@/components/ui/Button';
@@ -41,22 +42,33 @@ function RawDataToggle({ id, children }: { id: string; children: React.ReactNode
4142
aria-expanded={open}
4243
aria-controls={`raw-data-${id}`}
4344
>
44-
<svg
45-
className={`w-3 h-3 transition-transform duration-200 ${open ? 'rotate-90' : ''}`}
45+
<motion.svg
46+
className="w-3 h-3"
4647
fill="none"
4748
viewBox="0 0 24 24"
4849
stroke="currentColor"
4950
strokeWidth={2}
51+
animate={{ rotate: open ? 90 : 0 }}
52+
transition={{ duration: 0.2 }}
5053
>
5154
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
52-
</svg>
55+
</motion.svg>
5356
{open ? 'Hide' : 'View'} raw data
5457
</button>
55-
{open && (
56-
<div id={`raw-data-${id}`} className="mt-3">
57-
{children}
58-
</div>
59-
)}
58+
<AnimatePresence>
59+
{open && (
60+
<motion.div
61+
id={`raw-data-${id}`}
62+
className="mt-3 overflow-hidden"
63+
initial={{ opacity: 0, height: 0 }}
64+
animate={{ opacity: 1, height: 'auto' }}
65+
exit={{ opacity: 0, height: 0 }}
66+
transition={{ duration: 0.25, ease: [0.25, 0.1, 0.25, 1] }}
67+
>
68+
{children}
69+
</motion.div>
70+
)}
71+
</AnimatePresence>
6072
</div>
6173
);
6274
}

website/src/app/blog/BlogList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import Link from 'next/link';
4-
import { motion } from 'framer-motion';
4+
import { motion } from 'motion/react';
55
import type { BlogPost } from '@/lib/blog';
66

77
export function BlogList({ posts }: { posts: BlogPost[] }) {

website/src/app/docs/page.tsx

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Metadata } from 'next';
22
import Link from 'next/link';
33
import { DOCS_SIDEBAR } from '@/lib/constants';
44
import { DocsSearchTrigger } from '@/components/docs/DocsSearchTrigger';
5+
import { FadeIn } from '@/components/ui/FadeIn';
56

67
export const metadata: Metadata = {
78
title: 'Documentation',
@@ -43,30 +44,31 @@ export default function DocsPage() {
4344
<DocsSearchTrigger />
4445

4546
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
46-
{DOCS_SIDEBAR.map((group) => (
47-
<Link
48-
key={group.category}
49-
href={`/docs/${group.items[0].slug}`}
50-
className="glass glass-hover block rounded-xl p-6 transition-colors"
51-
>
52-
<div className="mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-accent-indigo/10">
53-
<svg className="h-5 w-5 text-accent-indigo" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
54-
<path strokeLinecap="round" strokeLinejoin="round" d={CATEGORY_ICONS[group.category] || CATEGORY_ICONS['Core']} />
55-
</svg>
56-
</div>
57-
<h2 className="text-lg font-semibold text-white">{group.category}</h2>
58-
<p className="mt-1 text-sm text-zinc-500">
59-
{group.items.length} {group.items.length === 1 ? 'article' : 'articles'}
60-
</p>
61-
<ul className="mt-3 space-y-1">
62-
{group.items.slice(0, 3).map((item) => (
63-
<li key={item.slug} className="text-sm text-zinc-400">{item.label}</li>
64-
))}
65-
{group.items.length > 3 && (
66-
<li className="text-sm text-zinc-600">+{group.items.length - 3} more</li>
67-
)}
68-
</ul>
69-
</Link>
47+
{DOCS_SIDEBAR.map((group, i) => (
48+
<FadeIn viewport key={group.category} delay={i * 0.06}>
49+
<Link
50+
href={`/docs/${group.items[0].slug}`}
51+
className="glass glass-hover block rounded-xl p-6 transition-colors h-full"
52+
>
53+
<div className="mb-3 flex h-10 w-10 items-center justify-center rounded-lg bg-accent-indigo/10">
54+
<svg className="h-5 w-5 text-accent-indigo" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
55+
<path strokeLinecap="round" strokeLinejoin="round" d={CATEGORY_ICONS[group.category] || CATEGORY_ICONS['Core']} />
56+
</svg>
57+
</div>
58+
<h2 className="text-lg font-semibold text-white">{group.category}</h2>
59+
<p className="mt-1 text-sm text-zinc-500">
60+
{group.items.length} {group.items.length === 1 ? 'article' : 'articles'}
61+
</p>
62+
<ul className="mt-3 space-y-1">
63+
{group.items.slice(0, 3).map((item) => (
64+
<li key={item.slug} className="text-sm text-zinc-400">{item.label}</li>
65+
))}
66+
{group.items.length > 3 && (
67+
<li className="text-sm text-zinc-600">+{group.items.length - 3} more</li>
68+
)}
69+
</ul>
70+
</Link>
71+
</FadeIn>
7072
))}
7173
</div>
7274
</main>

website/src/app/layout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { spaceGrotesk, inter, jetbrainsMono } from '@/lib/fonts';
33
import { Navbar } from '@/components/layout/Navbar';
44
import { Footer } from '@/components/layout/Footer';
55
import { ServiceWorkerRegister } from '@/components/ServiceWorkerRegister';
6+
import { ScrollProgressBar } from '@/components/ui/ScrollProgressBar';
67
import { Analytics } from '@vercel/analytics/next';
78
import { SpeedInsights } from '@vercel/speed-insights/next';
89
import './globals.css';
@@ -96,6 +97,7 @@ export default function RootLayout({
9697
}),
9798
}}
9899
/>
100+
<ScrollProgressBar />
99101
<Navbar />
100102
<main id="main-content" className="pt-16">{children}</main>
101103
<Footer />

website/src/app/not-found.tsx

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Metadata } from 'next';
22
import Link from 'next/link';
3+
import { FadeIn } from '@/components/ui/FadeIn';
34

45
export const metadata: Metadata = {
56
title: '404 - Page Not Found',
@@ -11,19 +12,27 @@ export default function NotFound() {
1112
return (
1213
<div className="min-h-screen flex items-center justify-center">
1314
<div className="text-center">
14-
<p className="text-sm font-semibold text-emerald-400 uppercase tracking-wider mb-4">404</p>
15-
<h1 className="text-4xl font-bold tracking-tight text-white mb-4">
16-
Page not found
17-
</h1>
18-
<p className="text-lg text-zinc-400 mb-8">
19-
The page you&apos;re looking for doesn&apos;t exist.
20-
</p>
21-
<Link
22-
href="/"
23-
className="inline-flex items-center gap-2 rounded-lg bg-emerald-500 px-5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-emerald-400 transition-colors"
24-
>
25-
Back to Home
26-
</Link>
15+
<FadeIn>
16+
<p className="text-sm font-semibold text-emerald-400 uppercase tracking-wider mb-4">404</p>
17+
</FadeIn>
18+
<FadeIn delay={0.1}>
19+
<h1 className="text-4xl font-bold tracking-tight text-white mb-4">
20+
Page not found
21+
</h1>
22+
</FadeIn>
23+
<FadeIn delay={0.2}>
24+
<p className="text-lg text-zinc-400 mb-8">
25+
The page you&apos;re looking for doesn&apos;t exist.
26+
</p>
27+
</FadeIn>
28+
<FadeIn delay={0.3}>
29+
<Link
30+
href="/"
31+
className="inline-flex items-center gap-2 rounded-lg bg-emerald-500 px-5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-emerald-400 transition-colors"
32+
>
33+
Back to Home
34+
</Link>
35+
</FadeIn>
2736
</div>
2837
</div>
2938
);
Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22

33
import { useState } from 'react';
4+
import { motion, AnimatePresence } from 'motion/react';
45

56
export function CopyButton({ text }: { text: string }) {
67
const [copied, setCopied] = useState(false);
@@ -16,26 +17,41 @@ export function CopyButton({ text }: { text: string }) {
1617
});
1718
};
1819

20+
const iconKey = failed ? 'failed' : copied ? 'copied' : 'copy';
21+
1922
return (
20-
<button
23+
<motion.button
2124
onClick={copy}
22-
className="absolute right-2 top-2 flex h-8 w-8 items-center justify-center rounded-md bg-white/10 text-zinc-400 opacity-0 transition-all hover:bg-white/15 hover:text-white group-hover:opacity-100"
25+
className="absolute right-2 top-2 flex h-8 w-8 items-center justify-center rounded-md bg-white/10 text-zinc-400 opacity-0 transition-opacity hover:bg-white/15 hover:text-white group-hover:opacity-100"
2326
aria-label={failed ? 'Copy failed - try Ctrl+C' : copied ? 'Copied' : 'Copy code'}
2427
title={failed ? 'Clipboard access denied. Try Ctrl+C to copy.' : undefined}
28+
whileTap={{ scale: 0.85 }}
29+
transition={{ type: 'spring', stiffness: 400, damping: 17 }}
2530
>
26-
{copied ? (
27-
<svg className="h-4 w-4 text-accent-green" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
28-
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
29-
</svg>
30-
) : failed ? (
31-
<svg className="h-4 w-4 text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
32-
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
33-
</svg>
34-
) : (
35-
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
36-
<path strokeLinecap="round" strokeLinejoin="round" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
37-
</svg>
38-
)}
39-
</button>
31+
<AnimatePresence mode="wait" initial={false}>
32+
<motion.span
33+
key={iconKey}
34+
initial={{ scale: 0, opacity: 0 }}
35+
animate={{ scale: 1, opacity: 1 }}
36+
exit={{ scale: 0, opacity: 0 }}
37+
transition={{ duration: 0.15 }}
38+
className="block"
39+
>
40+
{copied ? (
41+
<svg className="h-4 w-4 text-accent-green" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
42+
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
43+
</svg>
44+
) : failed ? (
45+
<svg className="h-4 w-4 text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
46+
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
47+
</svg>
48+
) : (
49+
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
50+
<path strokeLinecap="round" strokeLinejoin="round" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
51+
</svg>
52+
)}
53+
</motion.span>
54+
</AnimatePresence>
55+
</motion.button>
4056
);
4157
}

website/src/components/home/CodeExamples.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
'use client';
22

33
import { useState } from 'react';
4-
import { motion, AnimatePresence } from 'framer-motion';
4+
import { motion, AnimatePresence } from 'motion/react';
55
import { GlassCard } from '@/components/ui/GlassCard';
6-
import { FadeInCSS } from '@/components/ui/FadeInCSS';
6+
import { FadeIn } from '@/components/ui/FadeIn';
77

88
type Segment = { text: string; cls: string };
99
type CodeLine = Segment[];
@@ -103,12 +103,12 @@ export function CodeExamples() {
103103
return (
104104
<section className="py-20 border-t border-white/[0.06]">
105105
<div className="max-w-3xl mx-auto px-4">
106-
<FadeInCSS>
106+
<FadeIn viewport>
107107
<h2 className="text-3xl font-bold text-white text-center mb-10">
108108
Simple, Powerful API
109109
</h2>
110-
</FadeInCSS>
111-
<FadeInCSS delay={0.1}>
110+
</FadeIn>
111+
<FadeIn viewport delay={0.1}>
112112
<div className="flex flex-wrap gap-1 mb-4">
113113
{tabs.map((tab, i) => (
114114
<button
@@ -150,7 +150,7 @@ export function CodeExamples() {
150150
</AnimatePresence>
151151
</div>
152152
</GlassCard>
153-
</FadeInCSS>
153+
</FadeIn>
154154
</div>
155155
</section>
156156
);

website/src/components/home/CtaBanner.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FadeInCSS } from '@/components/ui/FadeInCSS';
1+
import { FadeIn } from '@/components/ui/FadeIn';
22
import { Button } from '@/components/ui/Button';
33

44
export function CtaBanner() {
@@ -12,7 +12,7 @@ export function CtaBanner() {
1212
</div>
1313

1414
<div className="max-w-6xl mx-auto px-4 text-center">
15-
<FadeInCSS>
15+
<FadeIn viewport>
1616
<h2 className="text-3xl md:text-4xl font-bold text-white mb-6">
1717
Ready to parse SQL at the speed of Go?
1818
</h2>
@@ -24,7 +24,7 @@ export function CtaBanner() {
2424
Try Playground
2525
</Button>
2626
</div>
27-
</FadeInCSS>
27+
</FadeIn>
2828
</div>
2929
</section>
3030
);

0 commit comments

Comments
 (0)