Skip to content

Commit 1a18622

Browse files
committed
Twestimonials tweaks
1 parent 2f33e77 commit 1a18622

File tree

3 files changed

+116
-184
lines changed

3 files changed

+116
-184
lines changed

apps/web-roo-code/src/components/homepage/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,5 @@ export * from "./company-logos"
44
export * from "./faq-section"
55
export * from "./features"
66
export * from "./install-section"
7-
export * from "./testimonials-mobile"
87
export * from "./testimonials"
98
export * from "./whats-new-button"

apps/web-roo-code/src/components/homepage/testimonials-mobile.tsx

Lines changed: 0 additions & 63 deletions
This file was deleted.

apps/web-roo-code/src/components/homepage/testimonials.tsx

Lines changed: 116 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
"use client"
22

3-
import { useRef } from "react"
3+
import { useRef, useCallback, useEffect } from "react"
44
import { motion } from "framer-motion"
5-
import Image from "next/image"
6-
import { TestimonialsMobile } from "./testimonials-mobile"
5+
import useEmblaCarousel from "embla-carousel-react"
6+
import AutoPlay from "embla-carousel-autoplay"
7+
import { ChevronLeft, ChevronRight } from "lucide-react"
78

89
export interface Testimonial {
910
id: number
@@ -47,150 +48,145 @@ export const testimonials: Testimonial[] = [
4748

4849
export function Testimonials() {
4950
const containerRef = useRef<HTMLDivElement>(null)
51+
const [emblaRef, emblaApi] = useEmblaCarousel(
52+
{
53+
loop: true,
54+
align: "center",
55+
skipSnaps: false,
56+
containScroll: false,
57+
},
58+
[
59+
AutoPlay({
60+
playOnInit: true,
61+
delay: 4000,
62+
stopOnInteraction: true,
63+
stopOnMouseEnter: true,
64+
stopOnFocusIn: true,
65+
}),
66+
],
67+
)
68+
69+
const scrollPrev = useCallback(() => {
70+
if (emblaApi) emblaApi.scrollPrev()
71+
}, [emblaApi])
72+
73+
const scrollNext = useCallback(() => {
74+
if (emblaApi) emblaApi.scrollNext()
75+
}, [emblaApi])
76+
77+
// Re-init auto-play on user interaction
78+
useEffect(() => {
79+
if (!emblaApi) return
80+
81+
const autoPlay = emblaApi?.plugins()?.autoPlay as
82+
| {
83+
isPlaying?: () => boolean
84+
play?: () => void
85+
}
86+
| undefined
87+
if (!autoPlay) return
88+
89+
const handleInteraction = () => {
90+
const isPlaying = autoPlay.isPlaying && autoPlay.isPlaying()
91+
if (!isPlaying) {
92+
setTimeout(() => {
93+
if (autoPlay.play) {
94+
autoPlay.play()
95+
}
96+
}, 2000)
97+
}
98+
}
99+
100+
emblaApi.on("pointerUp", handleInteraction)
101+
102+
return () => {
103+
emblaApi.off("pointerUp", handleInteraction)
104+
}
105+
}, [emblaApi])
50106

51107
const containerVariants = {
52108
hidden: { opacity: 0 },
53109
visible: {
54110
opacity: 1,
55-
transition: {
56-
staggerChildren: 0.15,
57-
delayChildren: 0.3,
58-
},
59-
},
60-
}
61-
62-
const itemVariants = {
63-
hidden: {
64-
opacity: 0,
65-
y: 20,
66-
},
67-
visible: {
68-
opacity: 1,
69-
y: 0,
70111
transition: {
71112
duration: 0.6,
72113
ease: [0.21, 0.45, 0.27, 0.9],
73114
},
74115
},
75116
}
76117

77-
const backgroundVariants = {
78-
hidden: {
79-
opacity: 0,
80-
},
81-
visible: {
82-
opacity: 1,
83-
transition: {
84-
duration: 1.2,
85-
ease: "easeOut",
86-
},
87-
},
88-
}
89-
90118
return (
91119
<section ref={containerRef} className="relative overflow-hidden border-t border-border py-32">
92-
<motion.div
93-
className="absolute inset-0"
94-
initial="hidden"
95-
whileInView="visible"
96-
viewport={{ once: true }}
97-
variants={backgroundVariants}>
98-
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2">
99-
<div className="absolute left-1/2 top-1/2 h-[800px] w-full -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-blue-500/10 blur-[120px]" />
100-
</div>
101-
</motion.div>
120+
<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2">
121+
<div className="absolute left-1/2 top-1/2 h-[400px] w-full -translate-x-1/2 -translate-y-1/2 rounded-[100%] bg-violet-500/10 dark:bg-violet-700/30 blur-[120px]" />
122+
</div>
123+
102124
<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
103-
<div className="mx-auto mb-24 max-w-3xl text-center">
104-
<motion.div
105-
initial={{ opacity: 0, y: 20 }}
106-
whileInView={{ opacity: 1, y: 0 }}
107-
viewport={{ once: true }}
108-
transition={{
109-
duration: 0.6,
110-
ease: [0.21, 0.45, 0.27, 0.9],
111-
}}>
112-
<h2 className="text-4xl font-bold tracking-tight sm:text-5xl">
113-
Empowering developers worldwide.
114-
</h2>
115-
<p className="mt-6 text-lg text-muted-foreground">
116-
Join thousands of developers who are revolutionizing their workflow with AI-powered
117-
assistance.
118-
</p>
119-
</motion.div>
125+
<div className="mx-auto mb-8 max-w-5xl text-center">
126+
<h2 className="text-4xl font-bold tracking-tight sm:text-5xl">
127+
AI-forward developers are using Roo Code
128+
</h2>
129+
<p className="mt-6 text-lg text-muted-foreground">
130+
Join more than 800k people revolutionizing their workflow worldwide
131+
</p>
120132
</div>
121133

122-
{/* Mobile Carousel */}
123-
<TestimonialsMobile />
124-
125-
{/* Desktop Grid */}
126134
<motion.div
127-
className="relative mx-auto hidden max-w-[1200px] md:block"
135+
className="relative mx-auto max-w-[1400px]"
128136
variants={containerVariants}
129137
initial="hidden"
130138
whileInView="visible"
131139
viewport={{ once: true }}>
132-
<div className="relative grid grid-cols-1 gap-12 md:grid-cols-2">
133-
{testimonials.map((testimonial, index) => (
134-
<motion.div
135-
key={testimonial.id}
136-
variants={itemVariants}
137-
className={`group relative ${index % 2 === 0 ? "md:translate-y-4" : "md:translate-y-12"}`}>
138-
<div className="absolute -inset-px rounded-2xl bg-gradient-to-r from-blue-500/30 via-cyan-500/30 to-purple-500/30 opacity-0 blur-sm transition-all duration-500 ease-out group-hover:opacity-100 dark:from-blue-400/40 dark:via-cyan-400/40 dark:to-purple-400/40" />
139-
<div className="relative flex h-full flex-col rounded-2xl border border-border/50 bg-background/30 backdrop-blur-xl transition-all duration-500 ease-out group-hover:scale-[1.02] group-hover:border-border group-hover:bg-background/40 group-hover:shadow-2xl dark:border-border/70 dark:bg-background/40 dark:group-hover:border-border dark:group-hover:bg-background/60 dark:group-hover:shadow-[0_20px_50px_rgba(59,130,246,0.15)]">
140-
{testimonial.image && (
141-
<div className="absolute -right-3 -top-3 h-16 w-16 overflow-hidden rounded-xl border border-border/50 bg-background/50 p-1.5 backdrop-blur-xl transition-all duration-500 ease-out group-hover:scale-110 dark:border-border/70 dark:bg-background/60">
142-
<div className="relative h-full w-full overflow-hidden rounded-lg">
143-
<Image
144-
src={testimonial.image || "/placeholder_pfp.png"}
145-
alt={testimonial.name}
146-
fill
147-
className="object-cover"
148-
/>
149-
</div>
150-
</div>
151-
)}
152-
153-
<div className="flex flex-1 flex-col p-8">
154-
<div className="flex-1">
155-
<div className="mb-6">
156-
<svg
157-
className="h-8 w-8 text-blue-500/20 transition-all duration-500 group-hover:text-blue-500/30 dark:text-blue-400/40 dark:group-hover:text-blue-400/60"
158-
fill="currentColor"
159-
viewBox="0 0 32 32">
160-
<defs>
161-
<filter id="glow">
162-
<feGaussianBlur stdDeviation="3" result="coloredBlur" />
163-
<feMerge>
164-
<feMergeNode in="coloredBlur" />
165-
<feMergeNode in="SourceGraphic" />
166-
</feMerge>
167-
</filter>
168-
</defs>
169-
<path
170-
d="M9.352 4C4.456 7.456 1 13.12 1 19.36c0 5.088 3.072 8.064 6.624 8.064 3.36 0 5.856-2.688 5.856-5.856 0-3.168-2.208-5.472-5.088-5.472-.576 0-1.344.096-1.536.192.48-3.264 3.552-7.104 6.624-9.024L9.352 4zm16.512 0c-4.8 3.456-8.256 9.12-8.256 15.36 0 5.088 3.072 8.064 6.624 8.064 3.264 0 5.856-2.688 5.856-5.856 0-3.168-2.304-5.472-5.184-5.472-.576 0-1.248.096-1.44.192.48-3.264 3.456-7.104 6.528-9.024L25.864 4z"
171-
className="dark:filter dark:drop-shadow-[0_0_8px_rgba(96,165,250,0.4)]"
172-
/>
173-
</svg>
174-
</div>
140+
{/* Previous Button */}
141+
<button
142+
onClick={scrollPrev}
143+
className="absolute left-0 top-1/2 z-20 -translate-y-1/2 rounded-full border border-border/50 bg-background/80 p-2 backdrop-blur-xl transition-all duration-300 hover:scale-110 hover:shadow-lg md:left-4 md:p-3 lg:left-8"
144+
aria-label="Previous testimonial">
145+
<ChevronLeft className="h-5 w-5 text-muted-foreground transition-colors hover:text-foreground md:h-6 md:w-6" />
146+
</button>
175147

176-
<p className="relative text-lg leading-relaxed text-muted-foreground transition-colors duration-300 group-hover:text-foreground/80 dark:text-foreground/70 dark:group-hover:text-foreground/90">
177-
{testimonial.quote}
178-
</p>
179-
</div>
148+
{/* Next Button */}
149+
<button
150+
onClick={scrollNext}
151+
className="absolute right-0 top-1/2 z-20 -translate-y-1/2 rounded-full border border-border/50 bg-background/80 p-2 backdrop-blur-xl transition-all duration-300 hover:scale-110 hover:shadow-lg md:right-4 md:p-3 lg:right-8"
152+
aria-label="Next testimonial">
153+
<ChevronRight className="h-5 w-5 text-muted-foreground transition-colors hover:text-foreground md:h-6 md:w-6" />
154+
</button>
155+
156+
{/* Gradient Overlays */}
157+
<div className="absolute inset-y-0 left-0 z-10 w-[10%] bg-gradient-to-r from-background to-transparent pointer-events-none md:w-[15%]" />
158+
<div className="absolute inset-y-0 right-0 z-10 w-[10%] bg-gradient-to-l from-background to-transparent pointer-events-none md:w-[15%]" />
180159

181-
<div className="relative mt-6">
182-
<div className="mb-4 h-px w-12 bg-gradient-to-r from-blue-500/50 to-transparent transition-all duration-500 group-hover:w-16 group-hover:from-blue-500/70 dark:from-blue-400/70 dark:group-hover:from-blue-400/90" />
183-
<h3 className="font-medium text-foreground/90 transition-colors duration-300 dark:text-foreground">
184-
{testimonial.name}
185-
</h3>
186-
<p className="text-sm text-muted-foreground transition-colors duration-300 dark:text-muted-foreground/80">
187-
{testimonial.role} at {testimonial.company}
188-
</p>
160+
{/* Embla Carousel Container */}
161+
<div className="overflow-hidden" ref={emblaRef}>
162+
<div className="flex">
163+
{testimonials.map((testimonial) => (
164+
<div
165+
key={testimonial.id}
166+
className="relative min-w-0 flex-[0_0_85%] px-2 md:flex-[0_0_70%] md:px-4 lg:flex-[0_0_60%]">
167+
<div className="group relative py-10 h-full">
168+
<div className="relative flex h-full flex-col rounded-2xl border border-border bg-background transition-all duration-500 ease-out group-hover:scale-[1.02] group-hover:border-border group-hover:bg-background/40 group-hover:shadow-xl dark:border-border/70 dark:bg-background/40 dark:group-hover:border-border dark:group-hover:bg-background/60 dark:group-hover:shadow-[0_20px_50px_rgba(59,130,246,0.15)]">
169+
<div className="flex flex-1 flex-col p-6 md:p-8">
170+
<div className="flex-1">
171+
<p className="relative text-sm leading-relaxed text-muted-foreground transition-colors duration-300 group-hover:text-foreground/80 dark:text-foreground/70 dark:group-hover:text-foreground/90 md:text-lg">
172+
{testimonial.quote}
173+
</p>
174+
</div>
175+
176+
<div className="relative mt-4 md:mt-6">
177+
<h3 className="font-medium text-foreground/90 transition-colors duration-300 dark:text-foreground">
178+
{testimonial.name}
179+
</h3>
180+
<p className="text-sm text-muted-foreground transition-colors duration-300 dark:text-muted-foreground/80">
181+
{testimonial.role} at {testimonial.company}
182+
</p>
183+
</div>
184+
</div>
189185
</div>
190186
</div>
191187
</div>
192-
</motion.div>
193-
))}
188+
))}
189+
</div>
194190
</div>
195191
</motion.div>
196192
</div>

0 commit comments

Comments
 (0)