22
33import { AnimatePresence , motion } from 'framer-motion'
44import {
5+ Check ,
56 ChevronDown ,
7+ Copy ,
68} from 'lucide-react'
79import Image from 'next/image'
810import Link from 'next/link'
9- import { useState } from 'react'
11+ import { useMemo , useState } from 'react'
1012
1113import { BackgroundBeams } from '@/components/background-beams'
1214import { CopyButton } from '@/components/copy-button'
@@ -120,21 +122,107 @@ function SetupGuide() {
120122 )
121123}
122124
125+ const PARTICLE_COUNT = 14
126+
123127function InstallCommand ( { className } : { className ?: string } ) {
128+ const [ copied , setCopied ] = useState ( false )
129+ const [ copyCount , setCopyCount ] = useState ( 0 )
130+
131+ const particles = useMemo ( ( ) =>
132+ Array . from ( { length : PARTICLE_COUNT } ) . map ( ( _ , i ) => ( {
133+ angle : ( i / PARTICLE_COUNT ) * 360 + ( Math . random ( ) - 0.5 ) * 25 ,
134+ distance : 35 + Math . random ( ) * 35 ,
135+ size : 3 + Math . random ( ) * 4 ,
136+ durationExtra : Math . random ( ) * 0.3 ,
137+ } ) ) ,
138+ [ copyCount ] ,
139+ )
140+
141+ const handleCopy = ( ) => {
142+ navigator . clipboard . writeText ( INSTALL_COMMAND )
143+ setCopied ( true )
144+ setCopyCount ( c => c + 1 )
145+ setTimeout ( ( ) => setCopied ( false ) , 1800 )
146+ }
147+
124148 return (
125- < div
126- className = { cn (
127- 'flex items-center gap-2 bg-zinc-900/80 border border-zinc-700/50 rounded-lg px-4 py-3 font-mono text-sm' ,
128- 'hover:border-acid-matrix/50 hover:shadow-[0_0_20px_rgba(124,255,63,0.12)] transition-all duration-300' ,
129- 'gradient-border-shine' ,
130- className ,
131- ) }
132- >
133- < span className = "text-acid-matrix select-none" > $</ span >
134- < code className = "text-white/90 select-all flex-1" >
135- { INSTALL_COMMAND }
136- </ code >
137- < CopyButton value = { INSTALL_COMMAND } />
149+ < div className = "relative" >
150+ < div
151+ className = { cn (
152+ 'flex items-center gap-2 bg-zinc-900/80 border rounded-lg px-4 py-3 font-mono text-sm' ,
153+ 'gradient-border-shine' ,
154+ copied
155+ ? 'border-acid-matrix shadow-[0_0_30px_rgba(124,255,63,0.45),0_0_60px_rgba(124,255,63,0.2)]'
156+ : 'border-acid-matrix/60 install-box-glow hover:border-acid-matrix hover:shadow-[0_0_30px_rgba(124,255,63,0.35),0_0_60px_rgba(124,255,63,0.15)]' ,
157+ 'transition-all duration-300' ,
158+ className ,
159+ ) }
160+ >
161+ < span className = "text-acid-matrix select-none" > $</ span >
162+ < code className = "text-white/90 select-all flex-1" >
163+ { INSTALL_COMMAND }
164+ </ code >
165+ < button
166+ onClick = { handleCopy }
167+ className = "p-1.5 rounded-md transition-colors hover:bg-white/10 cursor-pointer"
168+ aria-label = { `Copy: ${ INSTALL_COMMAND } ` }
169+ >
170+ < AnimatePresence mode = "wait" initial = { false } >
171+ { copied ? (
172+ < motion . span
173+ key = "check"
174+ initial = { { scale : 0 , rotate : - 90 } }
175+ animate = { { scale : 1 , rotate : 0 } }
176+ exit = { { scale : 0 , rotate : 90 } }
177+ transition = { { duration : 0.2 } }
178+ className = "block"
179+ >
180+ < Check className = "h-4 w-4 text-acid-matrix" />
181+ </ motion . span >
182+ ) : (
183+ < motion . span
184+ key = "copy"
185+ initial = { { scale : 0 } }
186+ animate = { { scale : 1 } }
187+ exit = { { scale : 0 } }
188+ transition = { { duration : 0.15 } }
189+ className = "block"
190+ >
191+ < Copy className = "h-4 w-4 text-white/60" />
192+ </ motion . span >
193+ ) }
194+ </ AnimatePresence >
195+ </ button >
196+ </ div >
197+
198+ { /* Celebration particles */ }
199+ < AnimatePresence >
200+ { copied &&
201+ particles . map ( ( p , i ) => {
202+ const rad = ( p . angle * Math . PI ) / 180
203+ return (
204+ < motion . span
205+ key = { i }
206+ initial = { { opacity : 1 , scale : 1 , x : 0 , y : 0 } }
207+ animate = { {
208+ opacity : 0 ,
209+ scale : 0 ,
210+ x : Math . cos ( rad ) * p . distance ,
211+ y : Math . sin ( rad ) * p . distance ,
212+ } }
213+ exit = { { opacity : 0 } }
214+ transition = { { duration : 0.5 + p . durationExtra , ease : 'easeOut' } }
215+ className = "absolute right-5 top-1/2 rounded-full pointer-events-none"
216+ style = { {
217+ width : p . size ,
218+ height : p . size ,
219+ backgroundColor :
220+ i % 3 === 0 ? '#7CFF3F' : i % 3 === 1 ? '#a8ff7a' : '#ffffff' ,
221+ } }
222+ />
223+ )
224+ } ) }
225+ </ AnimatePresence >
138226 </ div >
139227 )
140228}
@@ -143,28 +231,50 @@ function FAQList() {
143231 const [ openIndex , setOpenIndex ] = useState < number | null > ( null )
144232
145233 return (
146- < div className = "space-y-3 " >
234+ < div className = "divide-y divide-zinc-800/60 " >
147235 { faqs . map ( ( faq , i ) => {
148236 const isOpen = openIndex === i
149237 return (
150238 < motion . div
151239 key = { i }
152- initial = { { opacity : 0 , y : 15 } }
153- whileInView = { { opacity : 1 , y : 0 } }
154- viewport = { { once : true } }
155- transition = { { duration : 0.4 , delay : i * 0.08 } }
240+ initial = { { opacity : 0 , filter : 'blur(8px)' , x : 20 } }
241+ whileInView = { { opacity : 1 , filter : 'blur(0px)' , x : 0 } }
242+ viewport = { { once : true , amount : 0.5 } }
243+ transition = { { duration : 0.5 , delay : i * 0.1 } }
244+ className = { cn (
245+ 'transition-all duration-300' ,
246+ isOpen && 'bg-acid-matrix/[0.03]' ,
247+ ) }
156248 >
157249 < button
158250 onClick = { ( ) => setOpenIndex ( isOpen ? null : i ) }
159- className = "w-full flex items-center justify-between gap-4 bg-zinc-900/50 border border-zinc-800 rounded-xl px-6 py-4 text-left hover:border-acid-matrix/30 hover:bg-zinc-900/80 transition-all duration-300 cursor-pointer"
251+ className = "w-full flex items-center gap-4 px-4 py-5 text-left transition-all duration-300 cursor-pointer group "
160252 >
161- < span className = "font-semibold text-white" > { faq . question } </ span >
253+ < span
254+ className = { cn (
255+ 'flex-shrink-0 font-mono text-xs transition-colors duration-300' ,
256+ isOpen ? 'text-acid-matrix' : 'text-zinc-600 group-hover:text-zinc-400' ,
257+ ) }
258+ >
259+ { String ( i + 1 ) . padStart ( 2 , '0' ) }
260+ </ span >
261+ < span
262+ className = { cn (
263+ 'font-semibold flex-1 transition-colors duration-300' ,
264+ isOpen ? 'text-white' : 'text-zinc-300 group-hover:text-white' ,
265+ ) }
266+ >
267+ { faq . question }
268+ </ span >
162269 < motion . span
163270 animate = { { rotate : isOpen ? 180 : 0 } }
164271 transition = { { duration : 0.25 } }
165- className = "flex-shrink-0 text-zinc-400"
272+ className = { cn (
273+ 'flex-shrink-0 transition-colors duration-300' ,
274+ isOpen ? 'text-acid-matrix' : 'text-zinc-600' ,
275+ ) }
166276 >
167- < ChevronDown className = "h-5 w-5 " />
277+ < ChevronDown className = "h-4 w-4 " />
168278 </ motion . span >
169279 </ button >
170280 < AnimatePresence initial = { false } >
@@ -176,9 +286,14 @@ function FAQList() {
176286 transition = { { duration : 0.25 , ease : 'easeInOut' } }
177287 className = "overflow-hidden"
178288 >
179- < p className = "px-6 pt-3 pb-1 text-zinc-400 leading-relaxed" >
180- { faq . answer }
181- </ p >
289+ < div className = "flex gap-4 px-4 pb-5" >
290+ < span className = "flex-shrink-0 w-[1.5ch]" > </ span >
291+ < div className = "border-l-2 border-acid-matrix/40 pl-4" >
292+ < p className = "text-zinc-300 leading-relaxed text-sm" >
293+ { faq . answer }
294+ </ p >
295+ </ div >
296+ </ div >
182297 </ motion . div >
183298 ) }
184299 </ AnimatePresence >
@@ -190,9 +305,9 @@ function FAQList() {
190305}
191306
192307const PHILOSOPHY_WORDS = [
193- { word : 'SIMPLE' , description : 'No modes. No config. Just code .' } ,
308+ { word : 'SIMPLE' , description : 'No modes. No config. Just works .' } ,
194309 { word : 'FAST' , description : 'Up to 3× the speed of Claude Code' } ,
195- { word : 'LOADED' , description : 'Built in web research, browser use, and more' } ,
310+ { word : 'LOADED' , description : 'Built- in web research, browser use, and more' } ,
196311]
197312
198313function PhilosophySection ( ) {
@@ -215,34 +330,32 @@ function PhilosophySection() {
215330 }
216331
217332 return (
218- < div className = "relative z-10 container mx-auto max-w-5xl px-4 pt-16 md:pt-24 pb-24 md:pb-32" >
219- < div className = "flex flex-col gap-12 md:gap-16" >
220- { PHILOSOPHY_WORDS . map ( ( item , i ) => (
333+ < div className = "flex flex-col gap-12 md:gap-16" >
334+ { PHILOSOPHY_WORDS . map ( ( item , i ) => (
335+ < motion . div
336+ key = { item . word }
337+ initial = { { opacity : 0 , filter : 'blur(12px)' } }
338+ whileInView = { { opacity : 1 , filter : 'blur(0px)' } }
339+ viewport = { { once : true , amount : 0.5 } }
340+ transition = { { duration : 0.7 , delay : i * 0.1 } }
341+ className = "group"
342+ >
221343 < motion . div
222- key = { item . word }
223- initial = { { opacity : 0 , filter : 'blur(12px)' } }
224- whileInView = { { opacity : 1 , filter : 'blur(0px)' } }
225- viewport = { { once : true , amount : 0.5 } }
226- transition = { { duration : 0.7 , delay : i * 0.1 } }
227- className = "group"
344+ onViewportEnter = { ( ) => lightUp ( i ) }
345+ onViewportLeave = { ( ) => dimDown ( i ) }
346+ viewport = { { margin : '0px 0px -50% 0px' } }
347+ className = { cn (
348+ 'font-dm-mono text-7xl md:text-[8rem] lg:text-[6rem] xl:text-[8rem] font-medium leading-[0.85] tracking-tighter select-none transition-all duration-500' ,
349+ litWords . has ( i ) ? 'keyword-filled' : 'keyword-hollow' ,
350+ ) }
228351 >
229- < motion . div
230- onViewportEnter = { ( ) => lightUp ( i ) }
231- onViewportLeave = { ( ) => dimDown ( i ) }
232- viewport = { { margin : '0px 0px -55% 0px' } }
233- className = { cn (
234- 'font-dm-mono text-7xl md:text-[8rem] lg:text-[10rem] font-medium leading-[0.85] tracking-tighter select-none transition-all duration-500' ,
235- litWords . has ( i ) ? 'keyword-filled' : 'keyword-hollow' ,
236- ) }
237- >
238- { item . word }
239- </ motion . div >
240- < p className = "mt-3 md:mt-4 text-zinc-500 text-sm md:text-base font-mono tracking-wide" >
241- { item . description }
242- </ p >
352+ { item . word }
243353 </ motion . div >
244- ) ) }
245- </ div >
354+ < p className = "mt-3 md:mt-4 text-zinc-500 text-sm md:text-base font-mono tracking-wide" >
355+ { item . description }
356+ </ p >
357+ </ motion . div >
358+ ) ) }
246359 </ div >
247360 )
248361}
@@ -282,7 +395,7 @@ export default function HomeClient() {
282395 >
283396 < Link
284397 href = "/"
285- className = "flex items-center space-x-2 group transition-all duration-300 hover:scale-105 "
398+ className = "flex items-center space-x-2 group transition-all duration-300 hover:translate-x-0.5 "
286399 >
287400 < Image
288401 src = "/logo-icon.png"
@@ -301,7 +414,7 @@ export default function HomeClient() {
301414 href = "https://github.com/CodebuffAI/codebuff"
302415 target = "_blank"
303416 rel = "noopener noreferrer"
304- className = "relative font-medium px-3 py-2 rounded-md transition-all duration-200 hover:bg-white/10 text-zinc-400 hover:text-white flex items-center gap-2 text-sm"
417+ className = "relative font-medium px-3 py-2 rounded-md transition-all duration-200 text-zinc-400 hover:text-white flex items-center gap-2 text-sm"
305418 >
306419 < Icons . github className = "h-4 w-4" />
307420 < span className = "hidden sm:inline" > GitHub</ span >
@@ -327,7 +440,7 @@ export default function HomeClient() {
327440 < motion . span
328441 key = { i }
329442 variants = { wordVariant }
330- className = { word === 'free' ? 'inline-block mr-[0.3em] text-acid-matrix neon-text animate-glow-pulse' : 'inline-block mr-[0.3em] text-white' }
443+ className = { word === 'free' ? 'inline-block mr-[0.3em] text-acid-matrix neon-text animate-glow-pulse cursor-default hover-glow-flare ' : 'inline-block mr-[0.3em] text-white' }
331444 >
332445 { word }
333446 </ motion . span >
@@ -365,25 +478,30 @@ export default function HomeClient() {
365478 </ motion . div >
366479 </ div >
367480
368- { /* Philosophy content — same background, continuous flow */ }
369- < PhilosophySection />
370-
371- { /* ─── FAQ Section ─── */ }
372- < div className = "relative z-10 py-24 px-4" >
373- < div className = "container mx-auto max-w-2xl" >
374- < motion . div
375- initial = { { opacity : 0 , y : 20 } }
376- whileInView = { { opacity : 1 , y : 0 } }
377- viewport = { { once : true , amount : 0.3 } }
378- transition = { { duration : 0.6 } }
379- className = "text-center mb-12"
380- >
381- < h2 className = "text-3xl md:text-4xl font-bold mb-4" >
382- Frequently asked questions
383- </ h2 >
384- </ motion . div >
481+ { /* ─── Philosophy + FAQ: side-by-side on large screens ─── */ }
482+ < div className = "relative z-10 container mx-auto max-w-7xl px-4 pt-16 md:pt-24 pb-24 md:pb-32 lg:pb-[25vh]" >
483+ < div className = "flex flex-col lg:flex-row lg:gap-16 xl:gap-24" >
484+ { /* Philosophy — left side */ }
485+ < div className = "lg:flex-1 min-w-0" >
486+ < PhilosophySection />
487+ </ div >
488+
489+ { /* FAQ — right side (sticky on lg) */ }
490+ < div className = "lg:flex-1 min-w-0 mt-20 lg:mt-0 lg:sticky lg:top-24 lg:self-start lg:max-h-[calc(100vh-6rem)] lg:overflow-y-auto" >
491+ < motion . div
492+ initial = { { opacity : 0 , y : 20 } }
493+ whileInView = { { opacity : 1 , y : 0 } }
494+ viewport = { { once : true , amount : 0.3 } }
495+ transition = { { duration : 0.6 } }
496+ className = "text-center lg:text-left mb-12"
497+ >
498+ < h2 className = "text-3xl md:text-4xl font-bold mb-4" >
499+ FAQ
500+ </ h2 >
501+ </ motion . div >
385502
386- < FAQList />
503+ < FAQList />
504+ </ div >
387505 </ div >
388506 </ div >
389507 </ div >
0 commit comments