Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

Expand Down
2 changes: 0 additions & 2 deletions src/app/(public)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Link from 'next/link';
import {
CtaSection,
Faq,
FourSteps,
FundingPaths,
HeroSection,
MarketingButton,
Expand Down Expand Up @@ -55,7 +54,6 @@ export default function HomePage() {
</HeroSection>
<TrustBar />
<FundingPaths />
<FourSteps />
<Personas />
<Testimonials />
<Faq />
Expand Down
22 changes: 22 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment on lines +183 to +186

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Respect reduced-motion preferences for infinite marquee animations.

The new marquee animations run infinitely but there is no prefers-reduced-motion fallback. Add a reduced-motion override so desktop users who opt out of motion do not get continuous scrolling.

Proposed fix
 `@keyframes` marquee-right {
   from {
     transform: translateX(-50%);
   }
   to {
     transform: translateX(0);
   }
 }
+
+@media (prefers-reduced-motion: reduce) {
+  .animate-marquee-left,
+  .animate-marquee-right {
+    animation: none !important;
+    transform: none !important;
+  }
+}

Also applies to: 198-214

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/globals.css` around lines 183 - 186, Add a prefers-reduced-motion
override for the infinite marquee animations by detecting the CSS
variables/animations (--animate-marquee-left, --animate-marquee-right and the
keyframes marquee-left/marquee-right) and disabling or pausing them inside an
`@media` (prefers-reduced-motion: reduce) rule; set the animation to none (or
animation-duration: 0s / animation-play-state: paused) for those
variables/selectors so users who opt out of motion won't see continuous
scrolling—and apply the same override for the other marquee definitions
referenced later in the file (the block around the other occurrences).

}

@keyframes prize-scroll {
Expand All @@ -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% {
Expand Down
57 changes: 57 additions & 0 deletions src/features/marketing/components/escrow-flow.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className={cn('relative w-[260px]', className)}>
{/* Border glow concentrated at the centre of the left edge (only the
border line glows here), fading to the figma's faint white border. */}
<div className='relative rounded-[14px] bg-[radial-gradient(circle_at_left,rgba(46,237,170,0.85),rgba(255,255,255,0.09)_18%)] p-px'>
<div className='relative overflow-hidden rounded-[14px] bg-ink/70 p-[15px] backdrop-blur-[35px]'>
{/* Green patch near the centre of the right edge (inside, not on the
border). */}
<Glow className='absolute top-1/2 right-0 size-24 translate-x-[45%] -translate-y-1/2 opacity-50 blur-[26px]' />

<div className='relative flex gap-2'>
{/* Indicator rail */}
<div className='flex flex-col items-center gap-1 pt-1'>
<span className='size-8 shrink-0 rounded-full bg-white/15' />
<span className='h-11 w-1 rounded-full bg-white/10' />
<span className='size-8 shrink-0 rounded-full bg-white/15' />
</div>

<div className='flex flex-1 flex-col gap-8'>
{STEPS.map(step => (
<div key={step.title} className='flex flex-col gap-2'>
<p className='text-[15px] leading-none font-medium text-white'>
{step.title}
</p>
<p className='text-xs text-white/50'>{step.description}</p>
</div>
))}
</div>
</div>
</div>
</div>

{/* Top glow: overlays the card from its top edge and bursts out above,
centred and kept transparent so it reads as a soft burst. */}
<Glow className='absolute -top-6 left-1/2 h-14 w-36 -translate-x-1/2 opacity-40 blur-[26px]' />
</div>
);
}
2 changes: 1 addition & 1 deletion src/features/marketing/components/faq-accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function FaqAccordion({
className='flex w-full items-start gap-4 text-left'
>
{isOpen ? (
<Minus className='mt-0.5 size-6 shrink-0 text-white' />
<Minus className='mt-0.5 size-6 shrink-0 text-primary' />
) : (
<Plus className='mt-0.5 size-6 shrink-0 text-primary' />
)}
Expand Down
34 changes: 30 additions & 4 deletions src/features/marketing/components/faq.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Section>
<Placeholder label='FAQ' />
<Section className='bg-ink bg-[linear-gradient(180deg,rgba(13,17,17,0)_50%,rgba(46,237,170,0.08)_100%)]'>
<div className='flex flex-col gap-10 lg:flex-row lg:gap-16'>
<h2 className='font-heading text-3xl leading-none font-semibold tracking-tight text-white sm:text-4xl lg:shrink-0 lg:text-5xl lg:tracking-[-1.92px]'>
<span className='block whitespace-nowrap'>Frequently Asked</span>
<span className='block whitespace-nowrap'>
Questions <span className='text-white/60'>(FAQs)</span>
</span>
</h2>

<div className='flex flex-1 flex-col gap-8'>
<FaqAccordion items={HOME_FAQS} defaultOpen={[0]} />

<Link
href='/faq'
className='inline-flex items-center gap-2 self-center text-sm font-semibold text-primary transition-colors hover:text-primary-400 lg:self-start'
>
See More FAQs
<ArrowRight className='size-5' />
</Link>
</div>
</div>
</Section>
);
}
89 changes: 82 additions & 7 deletions src/features/marketing/components/funding-paths.tsx
Original file line number Diff line number Diff line change
@@ -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: <ExplorePath />,
},
{
title: 'Apply or Participate',
description: 'Submit proposals, join teams, or contribute your skills.',
illustration: (
<div className='relative'>
{/* Tight green glow around the highlighted (centre) action. */}
<Glow className='absolute top-1/2 left-1/2 h-24 w-44 -translate-x-1/2 -translate-y-1/2 opacity-55 blur-[36px]' />
{/* Arrows pointing at the centre slot. */}
<Play
aria-hidden
className='absolute top-1/2 -left-6 size-4 -translate-y-1/2 fill-primary text-primary'
/>
<Play
aria-hidden
className='absolute top-1/2 -right-6 size-4 -translate-y-1/2 rotate-180 fill-primary text-primary'
/>
<CtaTicker className='relative' />
</div>
),
},
{
title: 'Earn Funding or Rewards',
description:
'Receive support, prizes or payments for successful contributions.',
illustration: <PrizePool />,
},
{
title: 'Track Progress',
description:
'Stay informed with transparent updates and milestone tracking.',
illustration: <EscrowFlow />,
},
];

/** "Work flows through Boundless in 4 steps": four animated workflow columns. */
export function FundingPaths() {
return (
<Section>
{/* <Placeholder label='Funding Paths' /> */}
<div className='flex items-center justify-center'>
<ExplorePath />
<PrizePool />
<CtaTicker />
<Section
className='bg-ink bg-[linear-gradient(180deg,rgba(46,237,170,0.08)_0%,rgba(13,17,17,0)_50%)]'
innerClassName='flex flex-col gap-8'
>
<h2 className='font-heading text-3xl leading-none font-semibold tracking-tight text-white sm:text-4xl lg:text-5xl lg:tracking-[-1.92px]'>
Work flows through{' '}
<span className='block text-primary-400'>Boundless in 4 steps</span>
</h2>

<div className='flex flex-col gap-8 lg:flex-row lg:gap-0 lg:overflow-hidden lg:rounded-2xl lg:border lg:border-[#1d1f20]'>
{STEPS.map(step => (
<div
key={step.title}
className='relative flex flex-col gap-6 overflow-hidden rounded-2xl border border-[#1d1f20] bg-ink lg:flex-1 lg:gap-0 lg:rounded-none lg:border-0 lg:border-[#1d1f20] lg:not-first:border-l'
>
<div className='flex flex-col gap-2 p-5'>
<h3 className='font-heading text-xl font-semibold tracking-[-0.4px] text-white'>
{step.title}
</h3>
<p className='text-sm leading-[1.45] text-text-muted'>
{step.description}
</p>
</div>

<div className='flex flex-1 items-center justify-center pb-6 lg:h-[268px] lg:pb-0'>
{step.illustration}
</div>
</div>
))}
</div>
</Section>
);
Expand Down
18 changes: 18 additions & 0 deletions src/features/marketing/components/glow.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
aria-hidden
className={cn(
'pointer-events-none rounded-full bg-[color-mix(in_srgb,var(--color-primary)_65%,#66c589)] blur-3xl',
className
)}
/>
);
}
48 changes: 44 additions & 4 deletions src/features/marketing/components/news-section.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Link
href='/blog'
className={cn(
'inline-flex items-center gap-2 text-sm font-semibold text-primary transition-colors hover:text-primary-400',
className
)}
>
See more Articles
<ArrowRight className='size-5' />
</Link>
);
}

/** Home "Insights, Stories & Opportunities": heading + three blog previews. */
export function NewsSection() {
return (
<Section>
<Placeholder label='News' />
<Section
className='bg-ink bg-[linear-gradient(180deg,rgba(46,237,170,0.08)_0%,rgba(13,17,17,0)_50.07%)]'
innerClassName='flex flex-col gap-8'
>
<div className='flex items-center justify-between gap-6'>
<h2 className='font-heading text-3xl leading-none font-semibold tracking-tight text-primary-50 sm:text-4xl lg:text-5xl lg:tracking-[-1.92px]'>
Insights, Stories &amp; Opportunities
</h2>
<MoreLink className='hidden shrink-0 lg:inline-flex' />
</div>

<div className='grid gap-8 lg:grid-cols-3'>
{FEATURED.map(post => (
<BlogCard key={post.slug} post={post} />
))}
</div>

<MoreLink className='self-center lg:hidden' />
</Section>
);
}
Loading
Loading