diff --git a/app/(landing)/about/page.tsx b/app/(landing)/about/page.tsx index 472028009..1e1fdc475 100644 --- a/app/(landing)/about/page.tsx +++ b/app/(landing)/about/page.tsx @@ -1,13 +1,15 @@ import React from 'react'; import { Metadata } from 'next'; import { generatePageMetadata } from '@/lib/metadata'; +import TestimonialsSection from '@/components/testimonials/TestimonialsSection'; +import { testimonials } from '@/components/testimonials/data/testimonial'; export const metadata: Metadata = generatePageMetadata('about'); const AboutPage = () => { return (
- About Page +
); }; diff --git a/components/testimonials/TestimonialCard.tsx b/components/testimonials/TestimonialCard.tsx new file mode 100644 index 000000000..50dea25da --- /dev/null +++ b/components/testimonials/TestimonialCard.tsx @@ -0,0 +1,49 @@ +import { ReactNode } from 'react'; +import Image from 'next/image'; + +interface TestimonialCardProps { + avatarSrc?: string; + avatarFallback?: string; + name: string; + username: string; + content: string; + icon?: ReactNode; +} + +const TestimonialCard: React.FC = ({ + avatarSrc = '/avatar-placeholder.png', + // eslint-disable-next-line @typescript-eslint/no-unused-vars + avatarFallback = 'U', + name, + username, + content, + icon, +}) => { + return ( +
+
+
+
+ {`${name} +
+
+

{name}

+

@{username}

+
+
+
{icon}
+
+

+ {content} +

+
+ ); +}; + +export default TestimonialCard; diff --git a/components/testimonials/TestimonialsSection.tsx b/components/testimonials/TestimonialsSection.tsx new file mode 100644 index 000000000..0afc6e016 --- /dev/null +++ b/components/testimonials/TestimonialsSection.tsx @@ -0,0 +1,192 @@ +'use client'; + +import { Testimonial } from '@/components/testimonials/data/testimonial'; +import { useRef, useEffect } from 'react'; +import { Linkedin } from 'lucide-react'; +import { BoundlessButton } from '../buttons'; +import { gsap } from 'gsap'; +import TestimonialCard from './TestimonialCard'; + +type TestimonialsWallProps = { + testimonials: Testimonial[]; +}; + +export default function TestimonialsSection({ + testimonials, +}: TestimonialsWallProps) { + const scrollContainerRef = useRef(null); + const columnRefs = useRef([]); + const animations = useRef([]); + + useEffect(() => { + // Kill previous animations + animations.current.forEach(anim => anim.kill()); + animations.current = []; + + columnRefs.current.forEach((column, index) => { + if (!column) return; + + const wrapper = column.querySelector('.scroll-wrapper'); + if (!wrapper) return; + + const children = Array.from(wrapper.children) as HTMLElement[]; + const totalHeight = children.reduce( + (acc, child) => acc + child.offsetHeight + 32, + 0 + ); + + // Clone and append to create the continuous loop + const clone = wrapper.cloneNode(true) as HTMLElement; + wrapper.appendChild(clone); + + gsap.set(wrapper, { y: 0, force3D: true, willChange: 'transform' }); + + const anim = gsap.to(wrapper, { + y: -totalHeight, + duration: 30, // Slower for better readability + ease: 'none', // Linear for consistent speed + repeat: -1, // Infinite repeat + // Advanced performance optimizations + force3D: true, // Enable hardware acceleration + transformOrigin: 'center center', + // Use GSAP's built-in performance features + immediateRender: false, // Don't render immediately + lazy: false, // Don't use lazy rendering for smooth animation + delay: index * 0.5, + }); + + animations.current.push(anim); + }); + + const container = scrollContainerRef.current; + if (!container) return; + + const handleMouseEnter = () => + animations.current.forEach(anim => anim.pause()); + const handleMouseLeave = () => + animations.current.forEach(anim => anim.resume()); + + container.addEventListener('mouseenter', handleMouseEnter); + container.addEventListener('mouseleave', handleMouseLeave); + + return () => { + container.removeEventListener('mouseenter', handleMouseEnter); + container.removeEventListener('mouseleave', handleMouseLeave); + animations.current.forEach(anim => anim.kill()); + }; + }, [testimonials]); + + return ( +
+
+

+ What our users say +

+ +
+
+
+
+
+
+
+
+ +
+
+ +
+ {[0, 1, 2, 3].map(colIdx => ( +
{ + if (el) columnRefs.current[colIdx] = el; + }} + className='flex flex-col gap-8 overflow-hidden' + > +
+ {testimonials.slice(colIdx * 5, colIdx * 5 + 5).map(t => ( + + } + /> + ))} +
+
+ ))} +
+
+
+
+
+

+ Shape the Future
With Us +

+
+
+

+ We're building a future where ideas are truly boundless, + unlocking opportunities for innovators. Be part of it! +

+
+ + Explore Projects + + + Subscribe for Updates + +
+
+
+
+
+
+ ); +} diff --git a/components/testimonials/data/testimonial.ts b/components/testimonials/data/testimonial.ts new file mode 100644 index 000000000..48a066bd4 --- /dev/null +++ b/components/testimonials/data/testimonial.ts @@ -0,0 +1,170 @@ +export type Testimonial = { + id: number; + name: string; + username: string; + message: string; + avatarUrl?: string; +}; + +export const testimonials: Testimonial[] = [ + { + id: 1, + name: 'Amira', + username: 'amira123', + message: + 'Raising funds on Boundless was simple, transparent, and faster than I imagined.', + avatarUrl: 'https://i.pravatar.cc/150?img=11', + }, + { + id: 2, + name: 'James', + username: 'james_dev', + message: + 'Community voting gave us early validation before launch. It felt amazing to know backers believed in our vision.', + avatarUrl: 'https://i.pravatar.cc/150?img=22', + }, + { + id: 3, + name: 'Winston', + username: 'winston_design', + message: + 'Every startup needs this kind of system. Boundless makes collaboration feel effortless.', + avatarUrl: 'https://i.pravatar.cc/150?img=33', + }, + { + id: 4, + name: 'Sophia', + username: 'sophia_ui', + message: + 'The milestone-based escrow changed everything. Backers trusted us because they knew funds would only unlock on real progress.', + avatarUrl: 'https://i.pravatar.cc/150?img=44', + }, + { + id: 5, + name: 'Michael', + username: 'mike_builds', + message: + 'Before Boundless, raising funds was overwhelming. Now, it feels like building the platform hand-in-hand with supporters.', + avatarUrl: 'https://i.pravatar.cc/150?img=55', + }, + { + id: 6, + name: 'Olivia', + username: 'olivia_dev', + message: + 'Boundless made networking with investors seamless. I felt supported every step of the way.', + avatarUrl: 'https://i.pravatar.cc/150?img=66', + }, + { + id: 7, + name: 'Liam', + username: 'liam_creator', + message: + 'The feedback from early backers helped us pivot in the right direction quickly.', + avatarUrl: 'https://i.pravatar.cc/150?img=77', + }, + { + id: 8, + name: 'Emma', + username: 'emma_studio', + message: + 'I loved how transparent the platform was. Every milestone unlocked brought peace of mind to my team.', + avatarUrl: 'https://i.pravatar.cc/150?img=88', + }, + { + id: 9, + name: 'Noah', + username: 'noah_tech', + message: + 'The Boundless community is incredibly supportive. It gave our startup credibility we never expected.', + avatarUrl: 'https://i.pravatar.cc/150?img=99', + }, + { + id: 10, + name: 'Ava', + username: 'ava_innovates', + message: + 'Funding used to be a nightmare. Boundless turned it into a structured, clear process.', + avatarUrl: 'https://i.pravatar.cc/150?img=10', + }, + { + id: 11, + name: 'Ethan', + username: 'ethan_dev', + message: + 'I appreciated the milestone system. It encouraged us to stay accountable and focused.', + avatarUrl: 'https://i.pravatar.cc/150?img=12', + }, + { + id: 12, + name: 'Isabella', + username: 'bella_creates', + message: + 'Boundless empowered our team to reach goals faster and with confidence.', + avatarUrl: 'https://i.pravatar.cc/150?img=13', + }, + { + id: 13, + name: 'Lucas', + username: 'lucas_code', + message: + 'The platform made investor communication simple and transparent. Loved it!', + avatarUrl: 'https://i.pravatar.cc/150?img=14', + }, + { + id: 14, + name: 'Mia', + username: 'mia_starts', + message: + 'Boundless helped validate our idea before spending months building. Incredible experience.', + avatarUrl: 'https://i.pravatar.cc/150?img=15', + }, + { + id: 15, + name: 'Logan', + username: 'logan_dev', + message: + 'The smooth onboarding process and clear milestones helped our team stay motivated.', + avatarUrl: 'https://i.pravatar.cc/150?img=16', + }, + { + id: 16, + name: 'Charlotte', + username: 'charlotte_ui', + message: + 'I loved seeing backers support our project confidently because of Boundless’s structured approach.', + avatarUrl: 'https://i.pravatar.cc/150?img=17', + }, + { + id: 17, + name: 'Jackson', + username: 'jackson_builds', + message: + 'Boundless is a game-changer for startups. Milestones and community voting made a huge difference.', + avatarUrl: 'https://i.pravatar.cc/150?img=18', + }, + { + id: 18, + name: 'Amelia', + username: 'amelia_creates', + message: + 'It felt amazing to have transparency and accountability built into the platform.', + avatarUrl: 'https://i.pravatar.cc/150?img=19', + }, + { + id: 19, + name: 'Benjamin', + username: 'ben_dev', + message: + 'The platform gave us structure and confidence. Backers trusted our roadmap completely.', + avatarUrl: 'https://i.pravatar.cc/150?img=20', + }, + { + id: 20, + name: 'Harper', + username: 'harper_studio', + message: + 'Boundless made raising funds feel human and manageable. We actually enjoyed the process!', + avatarUrl: 'https://i.pravatar.cc/150?img=21', + }, +]; diff --git a/next.config.ts b/next.config.ts index 416b60e29..9388f803a 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,6 +3,7 @@ import type { NextConfig } from 'next'; const nextConfig: NextConfig = { /* config options here */ images: { + domains: ['i.pravatar.cc'], remotePatterns: [ { protocol: 'https',