Skip to content

Commit 55a287c

Browse files
authored
feat(ui): enhance landing page with new demos and custom cursor functionality (#265)
- Implement mobile detection to disable custom cursor on touch devices. - Add new demo components: DashboardDemo, ObserveDemo, SkillsDemo, StoreDemo, and shared components for animations and tool call logs. - Refactor FeaturesOverview to integrate new demo components and improve tab management. - Update cursor behavior based on device type for better user experience.
1 parent 9ea26cf commit 55a287c

7 files changed

Lines changed: 1903 additions & 248 deletions

File tree

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
'use client'
2+
3+
import { useState, useEffect } from 'react'
4+
import { motion, AnimatePresence } from 'motion/react'
5+
import {
6+
TrendingUp,
7+
ListChecks,
8+
Coins,
9+
Users,
10+
Activity,
11+
Lightbulb,
12+
Check,
13+
} from 'lucide-react'
14+
import { cn } from '@/lib/utils'
15+
import { CountUp } from './shared'
16+
17+
// ─── Timeline stages ────────────────────────────────────────────────────────
18+
19+
type Stage = 'init' | 'metrics' | 'chart' | 'activity' | 'insight' | 'settled'
20+
21+
const TIMELINE: Record<Stage, number> = {
22+
'init': 0,
23+
'metrics': 500,
24+
'chart': 3000,
25+
'activity': 5000,
26+
'insight': 7500,
27+
'settled': 9500,
28+
}
29+
30+
const STAGES: Stage[] = Object.keys(TIMELINE) as Stage[]
31+
32+
// ─── Metric definitions ─────────────────────────────────────────────────────
33+
34+
const METRICS = [
35+
{ label: 'Success Rate', value: 97.2, suffix: '%', decimals: 1, icon: TrendingUp, color: 'text-emerald-400' },
36+
{ label: 'Total Tasks', value: 1247, suffix: '', decimals: 0, icon: ListChecks, color: 'text-cyan-400' },
37+
{ label: 'Token Usage', value: 842, suffix: 'K', decimals: 0, icon: Coins, color: 'text-amber-400' },
38+
{ label: 'Active Sessions', value: 23, suffix: '', decimals: 0, icon: Users, color: 'text-violet-400' },
39+
]
40+
41+
// ─── Chart data ─────────────────────────────────────────────────────────────
42+
43+
const CHART_DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
44+
const CHART_VALUES = [65, 82, 55, 90, 78, 45, 88] // percentages of max height
45+
46+
// ─── Activity feed ──────────────────────────────────────────────────────────
47+
48+
const ACTIVITY_ITEMS = [
49+
{ text: 'Session #1247 completed', time: '2m ago', color: 'bg-emerald-500' },
50+
{ text: 'Skill "api-deploy" executed', time: '5m ago', color: 'bg-violet-500' },
51+
{ text: '3 tasks extracted from #1245', time: '8m ago', color: 'bg-cyan-500' },
52+
{ text: 'New session by alice@acme.com', time: '12m ago', color: 'bg-blue-500' },
53+
]
54+
55+
// ─── Main Dashboard Demo ────────────────────────────────────────────────────
56+
57+
export function DashboardDemo() {
58+
const [stage, setStage] = useState<Stage>('init')
59+
60+
useEffect(() => {
61+
setStage('init')
62+
63+
const timers: ReturnType<typeof setTimeout>[] = []
64+
for (const [s, delay] of Object.entries(TIMELINE)) {
65+
if (delay > 0) {
66+
timers.push(setTimeout(() => setStage(s as Stage), delay))
67+
}
68+
}
69+
return () => timers.forEach(clearTimeout)
70+
}, [])
71+
72+
const si = STAGES.indexOf(stage)
73+
const showMetrics = si >= STAGES.indexOf('metrics')
74+
const showChart = si >= STAGES.indexOf('chart')
75+
const showActivity = si >= STAGES.indexOf('chart')
76+
const showInsight = si >= STAGES.indexOf('insight')
77+
78+
return (
79+
<div className="h-full flex items-center justify-center p-3 sm:p-4 lg:p-6">
80+
<div className="w-full max-w-4xl space-y-3 sm:space-y-4">
81+
{/* Metrics row */}
82+
<div className="grid grid-cols-2 lg:grid-cols-4 gap-2 sm:gap-3">
83+
{METRICS.map((metric, i) => {
84+
const Icon = metric.icon
85+
return (
86+
<motion.div
87+
key={metric.label}
88+
initial={{ opacity: 0, y: 16 }}
89+
animate={showMetrics ? { opacity: 1, y: 0 } : {}}
90+
transition={{ delay: i * 0.1, type: 'spring', stiffness: 300, damping: 25 }}
91+
className="border border-zinc-200 dark:border-zinc-700 bg-zinc-50/50 dark:bg-zinc-900/50 p-2.5 sm:p-4 rounded-lg"
92+
>
93+
<div className="flex items-center gap-1.5 mb-1 sm:mb-2">
94+
<Icon className={cn('w-3.5 h-3.5 sm:w-4 sm:h-4', metric.color)} />
95+
<span className="text-[10px] sm:text-xs text-zinc-400 dark:text-zinc-500">{metric.label}</span>
96+
</div>
97+
<div className="text-lg sm:text-2xl font-bold text-zinc-800 dark:text-zinc-200 font-mono">
98+
{showMetrics ? (
99+
<CountUp
100+
end={metric.value}
101+
suffix={metric.suffix}
102+
decimals={metric.decimals}
103+
duration={2000}
104+
/>
105+
) : (
106+
<span className="text-zinc-300 dark:text-zinc-700">--</span>
107+
)}
108+
</div>
109+
</motion.div>
110+
)
111+
})}
112+
</div>
113+
114+
{/* Chart + Activity row */}
115+
<div className="grid grid-cols-1 lg:grid-cols-5 gap-2 sm:gap-3">
116+
{/* Bar chart */}
117+
<motion.div
118+
initial={{ opacity: 0, y: 16 }}
119+
animate={showChart ? { opacity: 1, y: 0 } : {}}
120+
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
121+
className="lg:col-span-3 border border-zinc-200 dark:border-zinc-700 bg-zinc-50/50 dark:bg-zinc-900/50 p-3 sm:p-4 rounded-lg flex flex-col"
122+
>
123+
<div className="flex items-center justify-between mb-3">
124+
<span className="text-xs sm:text-sm text-zinc-600 dark:text-zinc-400 font-medium">Weekly Activity</span>
125+
<Activity className="w-3.5 h-3.5 text-zinc-400 dark:text-zinc-500" />
126+
</div>
127+
<div className="flex items-stretch gap-1.5 sm:gap-2 flex-1 min-h-[80px] sm:min-h-[120px]">
128+
{CHART_DAYS.map((day, i) => (
129+
<div key={day} className="flex-1 flex flex-col items-center gap-1">
130+
<div className="w-full flex-1 bg-zinc-200 dark:bg-zinc-800 rounded-sm overflow-hidden relative">
131+
<motion.div
132+
className="absolute bottom-0 left-0 right-0 bg-emerald-500/80 dark:bg-emerald-500/60 rounded-sm"
133+
initial={{ height: 0 }}
134+
animate={showChart ? { height: `${CHART_VALUES[i]}%` } : { height: 0 }}
135+
transition={{ duration: 0.8, delay: i * 0.1, ease: 'easeOut' }}
136+
/>
137+
</div>
138+
<span className="text-[9px] sm:text-[10px] text-zinc-400 dark:text-zinc-600">{day}</span>
139+
</div>
140+
))}
141+
</div>
142+
</motion.div>
143+
144+
{/* Activity feed */}
145+
<motion.div
146+
initial={{ opacity: 0, y: 16 }}
147+
animate={showActivity ? { opacity: 1, y: 0 } : {}}
148+
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
149+
className="lg:col-span-2 border border-zinc-200 dark:border-zinc-700 bg-zinc-50/50 dark:bg-zinc-900/50 p-3 sm:p-4 rounded-lg"
150+
>
151+
<div className="flex items-center justify-between mb-3">
152+
<span className="text-xs sm:text-sm text-zinc-600 dark:text-zinc-400 font-medium">Recent</span>
153+
</div>
154+
<div className="space-y-2">
155+
<AnimatePresence>
156+
{showActivity &&
157+
ACTIVITY_ITEMS.map((item, i) => (
158+
<motion.div
159+
key={i}
160+
initial={{ opacity: 0, x: 12 }}
161+
animate={{ opacity: 1, x: 0 }}
162+
transition={{ delay: i * 0.2, type: 'spring', stiffness: 300, damping: 25 }}
163+
className="flex items-start gap-2"
164+
>
165+
<div className={cn('w-1.5 h-1.5 rounded-full mt-1.5 shrink-0', item.color)} />
166+
<div className="min-w-0">
167+
<p className="text-[10px] sm:text-xs text-zinc-700 dark:text-zinc-300 truncate">{item.text}</p>
168+
<p className="text-[9px] sm:text-[10px] text-zinc-400 dark:text-zinc-600">{item.time}</p>
169+
</div>
170+
</motion.div>
171+
))}
172+
</AnimatePresence>
173+
</div>
174+
</motion.div>
175+
</div>
176+
177+
{/* Insight notification */}
178+
<AnimatePresence>
179+
{showInsight && (
180+
<motion.div
181+
initial={{ opacity: 0, y: 12 }}
182+
animate={{ opacity: 1, y: 0 }}
183+
exit={{ opacity: 0, y: -8 }}
184+
transition={{ type: 'spring', stiffness: 300, damping: 25 }}
185+
className="border border-amber-300/50 dark:border-amber-700/50 bg-amber-100/30 dark:bg-amber-950/20 rounded-lg p-2.5 sm:p-3 flex items-center gap-2 sm:gap-3"
186+
>
187+
<Lightbulb className="w-4 h-4 sm:w-5 sm:h-5 text-amber-500 dark:text-amber-400 shrink-0" />
188+
<div className="flex-1 min-w-0">
189+
<p className="text-xs sm:text-sm text-amber-700 dark:text-amber-300 font-medium">
190+
Task completion rate up 12% this week
191+
</p>
192+
<p className="text-[10px] sm:text-xs text-amber-600/70 dark:text-amber-500/70">
193+
Driven by improved skill reuse across 8 agents
194+
</p>
195+
</div>
196+
<Check className="w-4 h-4 text-amber-500/50 dark:text-amber-500/50 shrink-0" />
197+
</motion.div>
198+
)}
199+
</AnimatePresence>
200+
</div>
201+
</div>
202+
)
203+
}

0 commit comments

Comments
 (0)