diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts index d25f1c2..5286967 100644 --- a/e2e/smoke.spec.ts +++ b/e2e/smoke.spec.ts @@ -4,8 +4,9 @@ test.describe('foundation smoke', () => { test('home page renders the hero', async ({ page }) => { await page.goto('/'); + // Scope to the h1 hero; other sections also use "Discover Opportunities". await expect( - page.getByRole('heading', { name: /discover opportunities/i }) + page.getByRole('heading', { name: /discover opportunities/i, level: 1 }) ).toBeVisible(); }); diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx index 66bcbc9..bc5d8c3 100644 --- a/src/app/(public)/page.tsx +++ b/src/app/(public)/page.tsx @@ -3,7 +3,6 @@ import Link from 'next/link'; import { CtaSection, Faq, - FourSteps, FundingPaths, HeroSection, MarketingButton, @@ -55,7 +54,6 @@ export default function HomePage() { - diff --git a/src/app/globals.css b/src/app/globals.css index a584c85..7903392 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -180,6 +180,10 @@ /* Marketing: pillar card. Steps through four rows, holding on each, then loops via a duplicated first row. */ --animate-card-rotate: card-rotate 10s ease-in-out infinite; + /* Marketing: testimonials. Two duplicated rows slide horizontally in + opposite directions for a seamless loop (-50% = one copy). */ + --animate-marquee-left: marquee-left 48s linear infinite; + --animate-marquee-right: marquee-right 48s linear infinite; } @keyframes prize-scroll { @@ -191,6 +195,24 @@ } } +@keyframes marquee-left { + from { + transform: translateX(0); + } + to { + transform: translateX(-50%); + } +} + +@keyframes marquee-right { + from { + transform: translateX(-50%); + } + to { + transform: translateX(0); + } +} + @keyframes card-rotate { 0%, 20% { diff --git a/src/features/marketing/components/escrow-flow.tsx b/src/features/marketing/components/escrow-flow.tsx new file mode 100644 index 0000000..614f977 --- /dev/null +++ b/src/features/marketing/components/escrow-flow.tsx @@ -0,0 +1,57 @@ +import { cn } from '@/lib/utils'; + +import { Glow } from './glow'; + +type Step = { title: string; description: string }; + +const STEPS: Step[] = [ + { + title: 'Match', + description: 'Connect with the right contributors or participants.', + }, + { + title: 'Secure', + description: 'Lock funding into escrow before work starts.', + }, +]; + +/** "Track Progress" illustration: a glassy card over a soft primary glow. */ +export function EscrowFlow({ className }: { className?: string }) { + return ( +
+ {/* Border glow concentrated at the centre of the left edge (only the + border line glows here), fading to the figma's faint white border. */} +
+
+ {/* Green patch near the centre of the right edge (inside, not on the + border). */} + + +
+ {/* Indicator rail */} +
+ + + +
+ +
+ {STEPS.map(step => ( +
+

+ {step.title} +

+

{step.description}

+
+ ))} +
+
+
+
+ + {/* Top glow: overlays the card from its top edge and bursts out above, + centred and kept transparent so it reads as a soft burst. */} + +
+ ); +} diff --git a/src/features/marketing/components/faq-accordion.tsx b/src/features/marketing/components/faq-accordion.tsx index f78d564..88685bb 100644 --- a/src/features/marketing/components/faq-accordion.tsx +++ b/src/features/marketing/components/faq-accordion.tsx @@ -46,7 +46,7 @@ export function FaqAccordion({ className='flex w-full items-start gap-4 text-left' > {isOpen ? ( - + ) : ( )} diff --git a/src/features/marketing/components/faq.tsx b/src/features/marketing/components/faq.tsx index 43dbe39..8292418 100644 --- a/src/features/marketing/components/faq.tsx +++ b/src/features/marketing/components/faq.tsx @@ -1,11 +1,37 @@ -import { Placeholder } from './placeholder'; +import { ArrowRight } from 'lucide-react'; +import Link from 'next/link'; + +import { FaqAccordion } from './faq-accordion'; +import { FAQ_ITEMS } from './faq-data'; import { Section } from './section'; -/** Frequently asked questions. Plain scaffold pending design. */ +// The home section previews the first few questions; the rest live on /faq. +const HOME_FAQS = FAQ_ITEMS.slice(0, 4); + +/** Home "Frequently Asked Questions (FAQs)": heading beside a preview accordion. */ export function Faq() { return ( -
- +
+
+

+ Frequently Asked + + Questions (FAQs) + +

+ +
+ + + + See More FAQs + + +
+
); } diff --git a/src/features/marketing/components/funding-paths.tsx b/src/features/marketing/components/funding-paths.tsx index d0517c5..2d9fd37 100644 --- a/src/features/marketing/components/funding-paths.tsx +++ b/src/features/marketing/components/funding-paths.tsx @@ -1,17 +1,92 @@ +import { Play } from 'lucide-react'; +import { type ReactNode } from 'react'; + import { CtaTicker } from './cta-ticker'; +import { EscrowFlow } from './escrow-flow'; import { ExplorePath } from './explore-path'; +import { Glow } from './glow'; import { PrizePool } from './prize-pool'; import { Section } from './section'; -/** Funding paths overview. Plain scaffold pending design. */ +type Step = { + title: string; + description: string; + illustration: ReactNode; +}; + +const STEPS: Step[] = [ + { + title: 'Discover Opportunities', + description: + 'Find grants, hackathons, bounties and campaigns aligned with your goals.', + illustration: , + }, + { + title: 'Apply or Participate', + description: 'Submit proposals, join teams, or contribute your skills.', + illustration: ( +
+ {/* Tight green glow around the highlighted (centre) action. */} + + {/* Arrows pointing at the centre slot. */} + + + +
+ ), + }, + { + title: 'Earn Funding or Rewards', + description: + 'Receive support, prizes or payments for successful contributions.', + illustration: , + }, + { + title: 'Track Progress', + description: + 'Stay informed with transparent updates and milestone tracking.', + illustration: , + }, +]; + +/** "Work flows through Boundless in 4 steps": four animated workflow columns. */ export function FundingPaths() { return ( -
- {/* */} -
- - - +
+

+ Work flows through{' '} + Boundless in 4 steps +

+ +
+ {STEPS.map(step => ( +
+
+

+ {step.title} +

+

+ {step.description} +

+
+ +
+ {step.illustration} +
+
+ ))}
); diff --git a/src/features/marketing/components/glow.tsx b/src/features/marketing/components/glow.tsx new file mode 100644 index 0000000..ec13330 --- /dev/null +++ b/src/features/marketing/components/glow.tsx @@ -0,0 +1,18 @@ +import { cn } from '@/lib/utils'; + +/** + * Soft ambient glow: the primary green with a touch of the explore-path badge + * accent (#66c589) mixed in. Size, position, opacity and blur are set by the + * caller (the colour falls off naturally through the blur). + */ +export function Glow({ className }: { className?: string }) { + return ( +
+ ); +} diff --git a/src/features/marketing/components/news-section.tsx b/src/features/marketing/components/news-section.tsx index 6ac87d6..dca4312 100644 --- a/src/features/marketing/components/news-section.tsx +++ b/src/features/marketing/components/news-section.tsx @@ -1,11 +1,51 @@ -import { Placeholder } from './placeholder'; +import { ArrowRight } from 'lucide-react'; +import Link from 'next/link'; + +import { cn } from '@/lib/utils'; + +import { BlogCard } from './blog/blog-card'; +import { BLOG_POSTS } from './blog/blog-data'; import { Section } from './section'; -/** Latest news/blog teaser. Plain scaffold pending design. */ +// Preview the three latest posts; the rest live on /blog. +const FEATURED = BLOG_POSTS.slice(0, 3); + +function MoreLink({ className }: { className?: string }) { + return ( + + See more Articles + + + ); +} + +/** Home "Insights, Stories & Opportunities": heading + three blog previews. */ export function NewsSection() { return ( -
- +
+
+

+ Insights, Stories & Opportunities +

+ +
+ +
+ {FEATURED.map(post => ( + + ))} +
+ +
); } diff --git a/src/features/marketing/components/personas.tsx b/src/features/marketing/components/personas.tsx index 730f8d7..07716ef 100644 --- a/src/features/marketing/components/personas.tsx +++ b/src/features/marketing/components/personas.tsx @@ -1,11 +1,120 @@ -import { Placeholder } from './placeholder'; +'use client'; + +import { useEffect, useState } from 'react'; + +import { cn } from '@/lib/utils'; + import { Section } from './section'; -/** Audience personas. Plain scaffold pending design. */ +type Persona = { title: string; description: string }; + +const PERSONAS: Persona[] = [ + { + title: 'Designers', + description: + 'Contribute creative work, collaborate with teams and get rewarded.', + }, + { + title: 'Developers', + description: + 'Turn ideas into products and earn through challenges and opportunities.', + }, + { + title: 'Communities', + description: 'Launch programs that engage and support contributors.', + }, + { + title: 'Startups & Founders', + description: + 'Access funding, visibility and opportunities to accelerate growth.', + }, +]; + +const STEP_MS = 3200; + +/** + * "Built for Builders, Creators & Communities": four audience personas as an + * auto-advancing stepper. The active persona's indicator lights up and its + * progress bar fills before moving to the next. + */ export function Personas() { + const [active, setActive] = useState(0); + + useEffect(() => { + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; + const id = setInterval( + () => setActive(current => (current + 1) % PERSONAS.length), + STEP_MS + ); + return () => clearInterval(id); + }, []); + return ( -
- +
+
+

+ Built for{' '} + + Builders, Creators & Communities + +

+ +
+ {PERSONAS.map((persona, index) => { + const isActive = index === active; + + return ( +
+ {/* Oversized faded index, clipped (desktop only). */} +
+ + {String(index + 1).padStart(2, '0')} + +
+ + {/* Progress: indicator + fill bar. */} +
+ + + + + + +
+ + {/* Step copy. */} +
+

+ {persona.title} +

+

+ {persona.description} +

+
+
+ ); + })} +
+
); } diff --git a/src/features/marketing/components/prize-pool.tsx b/src/features/marketing/components/prize-pool.tsx index 9a0a22e..fa5ad88 100644 --- a/src/features/marketing/components/prize-pool.tsx +++ b/src/features/marketing/components/prize-pool.tsx @@ -35,25 +35,49 @@ function PrizeRow({ {amount} {currency}
- {/* Dot straddles the timeline line to the left of the row. */} + {/* Dot centered on the 2px timeline (34px padding + 1px to its center, + minus half the dot). */}
); } -function PrizesBlock({ - total, - currency, - label, +function TiersBlock({ tiers, + currency, ariaHidden, -}: Required> & { ariaHidden?: boolean }) { +}: { + tiers: PrizeTier[]; + currency: string; + ariaHidden?: boolean; +}) { + return ( +
+ {tiers.map((tier, index) => ( + + ))} +
+ ); +} + +/** Total prize pool: a fixed header above an infinitely scrolling tier list. */ +export function PrizePool({ + total = '300,000', + currency = 'USDC', + label = 'Total Prize Pool', + tiers = DEFAULT_TIERS, + className, +}: PrizePoolProps) { return ( -
-
+
+ {/* Fixed header */} +
-
-
- {tiers.map((tier, index) => ( - - ))} + {/* Only the tiers scroll, in two copies for a seamless loop. */} +
+
+ +
); } - -/** Total prize pool with an infinitely scrolling tier breakdown. */ -export function PrizePool({ - total = '300,000', - currency = 'USDC', - label = 'Total Prize Pool', - tiers = DEFAULT_TIERS, - className, -}: PrizePoolProps) { - const block = { total, currency, label, tiers }; - return ( -
- {/* Track holds two identical copies so the loop is seamless. */} -
- - -
-
- ); -} diff --git a/src/features/marketing/components/testimonials.tsx b/src/features/marketing/components/testimonials.tsx index e4b2811..ad58d04 100644 --- a/src/features/marketing/components/testimonials.tsx +++ b/src/features/marketing/components/testimonials.tsx @@ -1,11 +1,177 @@ -import { Placeholder } from './placeholder'; -import { Section } from './section'; +import { ArrowUpRight } from 'lucide-react'; +import Image from 'next/image'; -/** Testimonials. Plain scaffold pending design. */ +import { SocialGlyph } from '@/components/layout/brand-icons'; +import { cn } from '@/lib/utils'; + +type Testimonial = { + name: string; + handle: string; + avatar: string; + quote: string; +}; + +// Two rows that scroll in opposite directions. Avatars reuse the team photos. +const ROW_ONE: Testimonial[] = [ + { + name: 'Alex Carter', + handle: '@alexcarterx', + avatar: '/team/team1.svg', + quote: + 'I have used multiple bounty platforms before, but @boundless_fi was the first one that actually enforced delivery and payment through escrow instead of blind trust.', + }, + { + name: 'Daniel Kim', + handle: '@danielbuilds', + avatar: '/team/team2.svg', + quote: + 'Winning a hackathon on @boundless_fi did not feel like the end of the journey. The milestone continuation model helped our team keep building after the event.', + }, + { + name: 'Maya Thompson', + handle: '@mayathompson', + avatar: '/team/team3.svg', + quote: + 'The reputation layer on @boundless_fi makes each milestone meaningful. Your work history actually compounds into something you own.', + }, +]; + +const ROW_TWO: Testimonial[] = [ + { + name: 'James Okoro', + handle: '@jamesbuilds', + avatar: '/team/team4.svg', + quote: + 'What sold me on @boundless_fi was how simple the experience is for the team, while the blockchain infrastructure stays underneath.', + }, + { + name: 'Marcus Reid', + handle: '@marcusreidx', + avatar: '/team/team2.svg', + quote: + 'Most platforms only help you find work. @boundless_fi feels like infrastructure for building long-term reputation in Web3.', + }, + { + name: 'Chloe Bennett', + handle: '@chloebennett', + avatar: '/team/team1.svg', + quote: + 'The milestone tracking UI on @boundless_fi gave our community full visibility into project progress and fund releases.', + }, +]; + +function Card({ name, handle, avatar, quote }: Testimonial) { + return ( +
+ {/* Border accent lines (exact Figma specs), over a faint base rim. + Top: a white gradient line across the top edge, fading at both ends. */} + + {/* Bottom: a shorter white gradient line set toward the left. */} + + {/* Green accent on the left edge (Figma: 53px line, gradient fading at + both ends, #4BDD74 at ~0.6 in the middle). */} + +
+ {name} +
+ {name} + {handle} +
+ +
+

{quote}

+
+ ); +} + +function Row({ + items, + direction, +}: { + items: Testimonial[]; + direction: 'left' | 'right'; +}) { + // Duplicate the row so the -50% loop lands on an identical copy. + const loop = [...items, ...items]; + return ( +
+ {loop.map((item, index) => ( + + ))} +
+ ); +} + +/** + * "Trusted by Builders Worldwide": two horizontally scrolling rows of social + * testimonials. Hidden on mobile per the design. + */ export function Testimonials() { return ( -
- -
+
+
+
+
+

+ Trusted by{' '} + Builders Worldwide +

+

+ Hear how communities, founders and contributors are creating + impact through Boundless. +

+
+ + {/* Liquid-glass CTA, scaled from a 282px reference to this ~44px + pill. The lit border runs only along the top-left -> bottom-right + diagonal (where light enters and exits): a 135deg gradient ring, + bright at those two corners and transparent at the top-right and + bottom-left, rendered via a masked overlay. A top-left radial + highlight sets the light direction and diagonal inset bevels form + the refractive lens edge. The fill stays dark/translucent over the + 10px backdrop blur, with only a faint inner frost. */} + + + + Follow Boundless + + +
+
+ + {/* Full-bleed marquee with a soft fade at both edges. */} +
+ + +
+
); }