diff --git a/app/(landing)/page.tsx b/app/(landing)/page.tsx index 622fca23f..c43fe26b4 100644 --- a/app/(landing)/page.tsx +++ b/app/(landing)/page.tsx @@ -11,6 +11,7 @@ import HowBoundlessWork from '@/components/landing-page/HowBoundlessWork'; import WhyBoundless from '@/components/landing-page/WhyBoundless'; import BackedBy from '@/components/landing-page/BackedBy'; import NewsLetter from '@/components/landing-page/NewsLetter'; +import BlogSection from '@/components/landing-page/blog/BlogSection'; export default function LandingPage() { const containerRef = useRef(null); @@ -95,11 +96,15 @@ export default function LandingPage() { return (
-
+
+
diff --git a/app/globals.css b/app/globals.css index eafefab75..cb10f508f 100644 --- a/app/globals.css +++ b/app/globals.css @@ -357,6 +357,8 @@ input[type='number'] { #3ae6b2 73.28% ); -webkit-background-clip: text; + background-clip: text; + color: transparent; -webkit-text-fill-color: transparent; } diff --git a/components/landing-page/BackedBy.tsx b/components/landing-page/BackedBy.tsx index fb04808c1..1cca4f07e 100644 --- a/components/landing-page/BackedBy.tsx +++ b/components/landing-page/BackedBy.tsx @@ -128,7 +128,7 @@ const BackedBy = () => { return (
diff --git a/components/landing-page/BeamBackground.tsx b/components/landing-page/BeamBackground.tsx index 48d02d0ee..4ff7fa1dc 100644 --- a/components/landing-page/BeamBackground.tsx +++ b/components/landing-page/BeamBackground.tsx @@ -304,14 +304,14 @@ export default function BeamBackground() {
{/* Pattern Placeholder */} -
+
diff --git a/components/landing-page/Hero.tsx b/components/landing-page/Hero.tsx index d96433c02..6aadeaf47 100644 --- a/components/landing-page/Hero.tsx +++ b/components/landing-page/Hero.tsx @@ -84,7 +84,7 @@ export default function Hero() { return (
@@ -116,13 +116,13 @@ export default function Hero() { width={1920} height={1080} /> -
+ /> */}

diff --git a/components/landing-page/NewsLetter.tsx b/components/landing-page/NewsLetter.tsx index 55f64c3d8..1e44448e5 100644 --- a/components/landing-page/NewsLetter.tsx +++ b/components/landing-page/NewsLetter.tsx @@ -1,7 +1,9 @@ -import React from 'react'; +import React, { useState } from 'react'; import { BoundlessButton } from '../buttons'; +import Newsletter from '../overview/Newsletter'; const NewsLetter = () => { + const [open, setOpen] = useState(false); return (
{ variant='secondary' className='bg-background hover:text-background' size='xl' + onClick={() => setOpen(true)} > Subscribe to Our Newsletter
+

); }; diff --git a/components/landing-page/WhyBoundless.tsx b/components/landing-page/WhyBoundless.tsx index a42f6de1f..5c6c3b566 100644 --- a/components/landing-page/WhyBoundless.tsx +++ b/components/landing-page/WhyBoundless.tsx @@ -5,7 +5,7 @@ import React from 'react'; const WhyBoundless = () => { return (
{/* Heading & Description */} diff --git a/components/landing-page/blog/BlogCard.tsx b/components/landing-page/blog/BlogCard.tsx new file mode 100644 index 000000000..049f5466b --- /dev/null +++ b/components/landing-page/blog/BlogCard.tsx @@ -0,0 +1,75 @@ +import { + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from '@/components/ui/card'; +import { Clock } from 'lucide-react'; +import Link from 'next/link'; +import Image from 'next/image'; +import React from 'react'; + +type Blog = { + id: number; + title: string; + excerpt: string; + image: string; + date: string; + slug: string; + category: string; +}; + +const BlogCard = ({ blog }: { blog: Blog }) => { + return ( + + + + {blog.category} + +
+ {blog.title} +
+
+ +

+ {blog.title} +

+

+ {blog.excerpt} +

+
+ +
+
+ + {blog.date} +
+ + Continue Reading + +
+
+
+ ); +}; + +export default BlogCard; diff --git a/components/landing-page/blog/BlogSection.tsx b/components/landing-page/blog/BlogSection.tsx new file mode 100644 index 000000000..c3bac8bd0 --- /dev/null +++ b/components/landing-page/blog/BlogSection.tsx @@ -0,0 +1,334 @@ +import { ArrowRight, ChevronLeft, ChevronRight } from 'lucide-react'; +import Link from 'next/link'; +import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import { motion } from 'framer-motion'; +import BlogCard from './BlogCard'; + +// Types +interface BlogPost { + id: number; + title: string; + excerpt: string; + image: string; + date: string; + slug: string; + category: string; +} + +interface CardStackItem { + id: number; + name: string; + designation: string; + content: React.ReactNode; +} + +// Mock blog data +const mockBlogs: BlogPost[] = [ + { + id: 1, + title: 'Why Validation Before Funding Protects Everyone', + excerpt: + 'Validation ensures that only strong ideas move forward. By allowing the community and admins to review projects before funding, Boundless protects backers from risky proposals and gives creators the chance to refine their concepts. This process builds trust, reduces wasted resources, and creates a safer, more transparent environment where everyone benefits.', + image: '/blog1.jpg', + date: '29, Jul, 2025', + slug: 'why-validation-before-funding-protects-everyone', + category: 'Blog', + }, + { + id: 2, + title: 'The Future of Decentralized Crowdfunding', + excerpt: + 'Explore how blockchain technology is revolutionizing the way projects get funded. From smart contracts to community governance, discover the innovations that are making crowdfunding more transparent and efficient than ever before.', + image: '/blog2.jpg', + date: '25, Jul, 2025', + slug: 'future-decentralized-crowdfunding', + category: 'Web3', + }, + { + id: 3, + title: 'Building Trust in Web3 Communities', + excerpt: + 'Trust is the foundation of any successful community. Learn about the mechanisms and best practices that help build and maintain trust in decentralized communities, from reputation systems to transparent governance.', + image: '/blog3.jpg', + date: '22, Jul, 2025', + slug: 'building-trust-web3-communities', + category: 'Community', + }, + { + id: 4, + title: 'Grant Programs That Actually Work', + excerpt: + 'Not all grant programs are created equal. Discover what makes grant programs successful and how to design them to maximize impact while ensuring fair distribution of resources to deserving projects.', + image: '/blog4.jpg', + date: '18, Jul, 2025', + slug: 'grant-programs-that-actually-work', + category: 'Grants', + }, + { + id: 5, + title: 'The Psychology of Backing Projects', + excerpt: + 'Understanding what motivates people to back projects is crucial for creators. Dive into the psychological factors that influence backing decisions and how to create compelling project proposals.', + image: '/blog5.jpg', + date: '15, Jul, 2025', + slug: 'psychology-of-backing-projects', + category: 'Psychology', + }, + { + id: 6, + title: 'Smart Contracts for Crowdfunding', + excerpt: + 'Learn how smart contracts are automating and securing crowdfunding processes. From automatic fund distribution to milestone-based releases, explore the technical innovations driving the future of funding.', + image: '/blog6.jpg', + date: '12, Jul, 2025', + slug: 'smart-contracts-crowdfunding', + category: 'Technology', + }, +]; + +// Constants +const CARD_TRANSITION_DURATION = 0.5; +const AUTO_SLIDE_INTERVAL = 5000; +const MOBILE_BREAKPOINT = 640; + +// CardStack Component +interface CardStackProps { + items: CardStackItem[]; + offset?: number; + scaleFactor?: number; +} + +const CardStack: React.FC = ({ + items, + offset = 20, + scaleFactor = 0.1, +}) => { + const [currentIndex, setCurrentIndex] = useState(0); + const [intervalId, setIntervalId] = useState(null); + + const nextSlide = useCallback(() => { + setCurrentIndex(prev => (prev + 1) % items.length); + }, [items.length]); + + const prevSlide = useCallback(() => { + setCurrentIndex(prev => (prev - 1 + items.length) % items.length); + }, [items.length]); + + const goToSlide = useCallback((index: number) => { + setCurrentIndex(index); + }, []); + + const startAutoSlide = useCallback(() => { + if (intervalId) { + clearInterval(intervalId); + } + const id = setInterval(nextSlide, AUTO_SLIDE_INTERVAL); + setIntervalId(id); + }, [nextSlide, intervalId]); + + useEffect(() => { + startAutoSlide(); + return () => { + if (intervalId) { + clearInterval(intervalId); + } + }; + }, [startAutoSlide, intervalId]); + + const calculateCardPosition = useCallback( + (index: number) => { + const isActive = index === currentIndex; + const isLeft = index < currentIndex; + const isRight = index > currentIndex; + const distanceFromActive = Math.abs(index - currentIndex); + + if (isActive) { + return { + xPosition: 0, + scale: 1, + zIndex: items.length + 1, + opacity: 1, + }; + } + + if (isLeft) { + return { + xPosition: -offset * distanceFromActive - 20, + scale: 1 - scaleFactor * distanceFromActive, + zIndex: items.length - distanceFromActive, + opacity: 1, + }; + } + + if (isRight) { + return { + xPosition: offset * distanceFromActive + 20, + scale: 1 - scaleFactor * distanceFromActive, + zIndex: items.length - distanceFromActive, + opacity: 1, + }; + } + + return { + xPosition: 0, + scale: 1, + zIndex: items.length, + opacity: 1, + }; + }, + [currentIndex, items.length, offset, scaleFactor] + ); + + return ( +
+ {/* Cards Container */} +
+ {items.map((card, index) => { + const { xPosition, scale, zIndex, opacity } = + calculateCardPosition(index); + + return ( + + {card.content} + + ); + })} +
+ + {/* Navigation Arrows */} + + + + {/* Pagination Indicators */} +
+ {items.map((_, index) => ( + + ))} +
+ + {/* Read More Link */} +
+ + Read More Articles + + +
+
+ ); +}; + +const BlogSection: React.FC = () => { + const [isMobile, setIsMobile] = useState(false); + + // Check if mobile on mount and resize + useEffect(() => { + const checkMobile = () => { + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); + }; + + checkMobile(); + window.addEventListener('resize', checkMobile); + return () => window.removeEventListener('resize', checkMobile); + }, []); + + // Transform blog data to CardStack format + const cardStackItems: CardStackItem[] = useMemo( + () => + mockBlogs.map(blog => ({ + id: blog.id, + name: blog.title, + designation: blog.date, + content: , + })), + [] + ); + + return ( +
+ {/* Header */} +
+
+

+ From the Blog +

+

+ Ideas that shape the future +

+

+ Discover stories, tips, and updates on crowdfunding, grants, and + Web3. Learn from builders and backers driving real impact. +

+
+
+ + Read More Articles + + +
+
+ + {/* Content */} + {isMobile ? ( +
+ +
+ ) : ( +
+ {mockBlogs.map(blog => ( + + ))} +
+ )} +
+ ); +}; + +export default BlogSection; diff --git a/components/overview/Newsletter.tsx b/components/overview/Newsletter.tsx new file mode 100644 index 000000000..c90209c37 --- /dev/null +++ b/components/overview/Newsletter.tsx @@ -0,0 +1,228 @@ +'use client'; +import React, { useRef, useState } from 'react'; +import { + Dialog, + DialogClose, + DialogContent, + DialogHeader, + DialogTitle, +} from '../ui/dialog'; +import Image from 'next/image'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '../ui/form'; + +import { z } from 'zod'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { User, Mail } from 'lucide-react'; +import { BoundlessButton } from '../buttons'; +import { Input } from '../ui/input'; +import gsap from 'gsap'; +import { useGSAP } from '@gsap/react'; +import { addToWaitlist } from '@/lib/api/waitlist'; +import { Button } from '../ui/button'; + +const formSchema = z.object({ + name: z.string().min(2).max(50), + email: z.email(), +}); + +const Newsletter = ({ + open, + onOpenChange, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; +}) => { + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + name: '', + email: '', + }, + }); + gsap.registerPlugin(useGSAP); + const nameFieldRef = useRef(null); + const emailFieldRef = useRef(null); + + const animateFieldFocus = ( + fieldRef: React.RefObject + ) => { + if (fieldRef.current) { + gsap.to(fieldRef.current, { + duration: 0.3, + scale: 1.02, + boxShadow: '0 0 0 1px rgb(167,249,80)', + ease: 'power2.out', + }); + } + }; + + const animateFieldBlur = ( + fieldRef: React.RefObject + ) => { + if (fieldRef.current) { + gsap.to(fieldRef.current, { + duration: 0.3, + scale: 1, + boxShadow: 'none', + ease: 'power2.out', + }); + } + }; + + const onSubmit = async (values: z.infer) => { + setError(null); + setIsSubmitting(true); + + try { + // Split name into firstName and lastName, handling edge cases + const nameParts = values.name + .trim() + .split(' ') + .filter(part => part.length > 0); + const firstName = nameParts[0] || ''; + const lastName = nameParts.slice(1).join(' ') || ''; + + await addToWaitlist({ + email: values.email, + firstName, + lastName, + }); + } catch { + setError('Failed to submit form. Please try again.'); + setIsSubmitting(false); + } finally { + setIsSubmitting(false); + } + }; + return ( + + +
+
+ + +
+ newsletter +
+
+

+ Join Our Newsletter +

+

+ Stay Boundless! Never miss updates on grants, hackathons, + and projects. +

+
+
+
+
+ + ( + + Name + +
animateFieldFocus(nameFieldRef)} + onBlur={() => animateFieldBlur(nameFieldRef)} + > + + +
+
+ +
+ )} + /> + ( + + Email + +
animateFieldFocus(emailFieldRef)} + onBlur={() => animateFieldBlur(emailFieldRef)} + > + + +
+
+ +
+ )} + /> + + Subscribe + + + +
+ {error && ( +

{error}

+ )} + + + +
+
+
+
+
+ ); +}; + +export default Newsletter; diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx index e9409d20c..1dff5f24b 100644 --- a/components/ui/dialog.tsx +++ b/components/ui/dialog.tsx @@ -38,7 +38,7 @@ function DialogOverlay({ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/sdf.svg b/public/sdf.svg new file mode 100644 index 000000000..4d8ade187 --- /dev/null +++ b/public/sdf.svg @@ -0,0 +1,43 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/public/stellar.svg b/public/stellar.svg new file mode 100644 index 000000000..033a63499 --- /dev/null +++ b/public/stellar.svg @@ -0,0 +1,26 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/public/trustless.svg b/public/trustless.svg new file mode 100644 index 000000000..7a0e7f1e7 --- /dev/null +++ b/public/trustless.svg @@ -0,0 +1,30 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/public/whyb.gif b/public/whyb.gif new file mode 100644 index 000000000..b9ebe0d46 Binary files /dev/null and b/public/whyb.gif differ