Skip to content
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ eggs/
.eggs/
lib/
lib64/
!frontend/src/lib/
!frontend/src/lib/**
parts/
sdist/
var/
Expand Down
24 changes: 11 additions & 13 deletions frontend/src/components/bounty/BountyCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,37 +61,35 @@ export function BountyCard({ bounty }: BountyCardProps) {
initial="rest"
whileHover="hover"
onClick={() => navigate(`/bounties/${bounty.id}`)}
className="relative rounded-xl border border-border bg-forge-900 p-5 cursor-pointer transition-colors duration-200 overflow-hidden group"
className="relative h-full rounded-xl border border-border bg-forge-900 p-4 sm:p-5 cursor-pointer transition-colors duration-200 overflow-hidden group"
>
{/* Row 1: Repo + Tier */}
<div className="flex items-center justify-between text-sm">
<div className="flex items-start justify-between gap-3 text-sm">
<div className="flex items-center gap-2 min-w-0">
{bounty.org_avatar_url && (
<img src={bounty.org_avatar_url} className="w-5 h-5 rounded-full flex-shrink-0" alt="" />
)}
<span className="text-text-muted font-mono text-xs truncate">
{orgName}/{repoName}
{issueNumber && <span className="ml-1">#{issueNumber}</span>}
</span>
<span className="text-text-muted font-mono text-xs truncate">{orgName}/{repoName}</span>
{issueNumber && <span className="text-text-muted font-mono text-xs flex-shrink-0">#{issueNumber}</span>}
</div>
<TierBadge tier={bounty.tier ?? 'T1'} />
</div>

{/* Row 2: Title */}
<h3 className="mt-3 font-sans text-base font-semibold text-text-primary leading-snug line-clamp-2">
<h3 className="mt-3 font-sans text-base font-semibold text-text-primary leading-snug line-clamp-2 break-words">
{bounty.title}
</h3>

{/* Row 3: Language dots */}
{skills.length > 0 && (
<div className="flex items-center gap-3 mt-3">
<div className="flex items-center gap-x-3 gap-y-2 mt-3 flex-wrap">
{skills.map((lang) => (
<span key={lang} className="inline-flex items-center gap-1.5 text-xs text-text-muted">
<span
className="w-2.5 h-2.5 rounded-full"
style={{ backgroundColor: LANG_COLORS[lang] ?? '#888' }}
/>
{lang}
<span className="break-all">{lang}</span>
</span>
))}
</div>
Expand All @@ -101,11 +99,11 @@ export function BountyCard({ bounty }: BountyCardProps) {
<div className="mt-4 border-t border-border/50" />

{/* Row 4: Reward + Meta */}
<div className="flex items-center justify-between mt-3">
<span className="font-mono text-lg font-semibold text-emerald">
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-2 mt-3 pb-8 sm:pb-0">
<span className="font-mono text-lg font-semibold text-emerald break-all">
{formatCurrency(bounty.reward_amount, bounty.reward_token)}
</span>
<div className="flex items-center gap-3 text-xs text-text-muted">
<div className="flex items-center gap-x-3 gap-y-1 text-xs text-text-muted flex-wrap sm:justify-end">
<span className="inline-flex items-center gap-1">
<GitPullRequest className="w-3.5 h-3.5" />
{bounty.submission_count} PRs
Expand All @@ -120,7 +118,7 @@ export function BountyCard({ bounty }: BountyCardProps) {
</div>

{/* Status badge */}
<span className={`absolute bottom-4 right-5 text-xs font-medium inline-flex items-center gap-1 ${statusColor}`}>
<span className={`absolute bottom-4 left-4 sm:left-auto right-4 sm:right-5 text-xs font-medium inline-flex items-center gap-1 ${statusColor}`}>
<span className={`w-1.5 h-1.5 rounded-full ${dotColor}`} />
{statusLabel}
</span>
Expand Down
18 changes: 9 additions & 9 deletions frontend/src/components/bounty/BountyGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,25 @@ export function BountyGrid() {
const allBounties = data?.pages.flatMap((p) => p.items) ?? [];

return (
<section id="bounties" className="py-16 md:py-24">
<div className="max-w-7xl mx-auto px-4">
<section id="bounties" className="py-14 md:py-24">
<div className="max-w-7xl mx-auto px-3 sm:px-4">
{/* Header row */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
<h2 className="font-sans text-2xl font-semibold text-text-primary">Open Bounties</h2>
<div className="flex items-center gap-2">
<div className="flex flex-col sm:flex-row sm:items-center gap-2 w-full sm:w-auto">
<Link
to="/bounties/create"
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-emerald text-forge-950 font-semibold text-sm hover:bg-emerald/90 transition-colors duration-150"
className="inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-lg bg-emerald text-forge-950 font-semibold text-sm hover:bg-emerald/90 transition-colors duration-150 whitespace-nowrap"
>
<Plus className="w-4 h-4" />
Post a Bounty
</Link>
{/* Status filter */}
<div className="relative">
<div className="relative w-full sm:w-auto">
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="appearance-none bg-forge-800 border border-border rounded-lg px-3 py-1.5 pr-8 text-sm text-text-secondary font-medium focus:border-emerald outline-none transition-colors duration-150 cursor-pointer"
className="appearance-none w-full bg-forge-800 border border-border rounded-lg px-3 py-1.5 pr-8 text-sm text-text-secondary font-medium focus:border-emerald outline-none transition-colors duration-150 cursor-pointer"
>
<option value="open">Open</option>
<option value="funded">Funded</option>
Expand All @@ -59,7 +59,7 @@ export function BountyGrid() {
<button
key={skill}
onClick={() => setActiveSkill(skill)}
className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors duration-150 ${
className={`px-3 py-1.5 rounded-md text-sm font-medium transition-colors duration-150 whitespace-nowrap ${
activeSkill === skill
? 'bg-forge-700 text-text-primary'
: 'text-text-muted hover:text-text-secondary bg-forge-800'
Expand All @@ -72,7 +72,7 @@ export function BountyGrid() {

{/* Loading state */}
{isLoading && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-5">
{Array.from({ length: 6 }).map((_, i) => (
<div
key={i}
Expand Down Expand Up @@ -109,7 +109,7 @@ export function BountyGrid() {
initial="initial"
whileInView="animate"
viewport={{ once: true, margin: '-50px' }}
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5"
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-5"
>
{allBounties.map((bounty) => (
<motion.div key={bounty.id} variants={staggerItem}>
Expand Down
13 changes: 6 additions & 7 deletions frontend/src/components/home/ActivityFeed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ interface ActivityEvent {
timestamp: string;
}

// Mock events for when API doesn't return activity
const MOCK_EVENTS: ActivityEvent[] = [
{
id: '1',
Expand All @@ -32,14 +31,14 @@ const MOCK_EVENTS: ActivityEvent[] = [
id: '3',
type: 'posted',
username: 'SolanaLabs',
detail: 'Bounty #145 $3,500 USDC',
detail: 'Bounty #145 - $3,500 USDC',
timestamp: new Date(Date.now() - 45 * 60 * 1000).toISOString(),
},
{
id: '4',
type: 'review',
username: 'AI Review',
detail: 'Bounty #42 8.5/10',
detail: 'Bounty #42 - 8.5/10',
timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
},
];
Expand All @@ -57,20 +56,20 @@ function getActionText(type: ActivityEvent['type']) {
function EventItem({ event }: { event: ActivityEvent }) {
const isMagenta = event.type === 'review';
return (
<div className="flex items-center gap-3 py-2 px-3 rounded-lg hover:bg-forge-850 transition-colors duration-150">
<div className="flex flex-wrap sm:flex-nowrap items-center gap-x-3 gap-y-1 py-2 px-3 rounded-lg hover:bg-forge-850 transition-colors duration-150">
{event.avatar_url ? (
<img src={event.avatar_url} className="w-6 h-6 rounded-full flex-shrink-0" alt="" />
) : (
<div className="w-6 h-6 rounded-full bg-forge-700 flex-shrink-0 flex items-center justify-center">
<span className="font-mono text-xs text-text-muted">{event.username[0]?.toUpperCase()}</span>
</div>
)}
<p className="text-sm text-text-secondary flex-1 truncate">
<p className="text-sm text-text-secondary flex-1 min-w-0 truncate">
<span className="font-medium text-text-primary">{event.username}</span>
{' '}{getActionText(event.type)}{' '}
<span className={`font-mono ${isMagenta ? 'text-magenta' : 'text-emerald'}`}>{event.detail}</span>
</p>
<span className="font-mono text-xs text-text-muted flex-shrink-0">{timeAgo(event.timestamp)}</span>
<span className="font-mono text-xs text-text-muted flex-shrink-0 ml-9 sm:ml-0">{timeAgo(event.timestamp)}</span>
</div>
);
}
Expand All @@ -85,7 +84,7 @@ export function ActivityFeed({ events }: { events?: ActivityEvent[] }) {

return (
<section className="w-full border-y border-border bg-forge-900/50 py-4 overflow-hidden">
<div className="max-w-7xl mx-auto px-4">
<div className="max-w-7xl mx-auto px-3 sm:px-4">
<div className="flex items-center gap-3 mb-3">
<span className="w-2 h-2 rounded-full bg-emerald animate-pulse-glow" />
<span className="font-mono text-xs text-text-muted uppercase tracking-wider">Recent Activity</span>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/home/FeaturedBounties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ export function FeaturedBounties() {
const { data, isLoading, isError } = useBounties({ limit: 4, status: 'open' });

return (
<section className="py-16 md:py-24 px-4 bg-forge-900/30">
<section className="py-14 md:py-24 px-3 sm:px-4 bg-forge-900/30">
<div className="max-w-7xl mx-auto">
<motion.div
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.4 }}
className="flex items-end justify-between mb-10"
className="flex flex-col sm:flex-row sm:items-end sm:justify-between gap-4 mb-10"
>
<div>
<div className="min-w-0">
<h2 className="font-display text-2xl md:text-3xl font-bold text-text-primary tracking-wide">
Open Bounties
</h2>
Expand Down
49 changes: 20 additions & 29 deletions frontend/src/components/home/HeroSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ export function HeroSection() {
const [resultLinesVisible, setResultLinesVisible] = useState(false);

useEffect(() => {
// Typewriter takes ~3s (0.5s delay + 2.5s), then show result lines
const t1 = setTimeout(() => setTypewriterDone(true), 3100);
const t2 = setTimeout(() => setResultLinesVisible(true), 3400);
return () => { clearTimeout(t1); clearTimeout(t2); };
Expand All @@ -87,34 +86,30 @@ export function HeroSection() {
};

return (
<section className="relative min-h-[90vh] flex flex-col items-center justify-center px-4 pt-24 pb-16 overflow-hidden">
{/* Background layers */}
<div className="absolute inset-0 bg-grid-forge bg-grid-forge pointer-events-none" style={{ backgroundSize: '40px 40px' }} />
<section className="relative min-h-[90vh] flex flex-col items-center justify-center px-3 sm:px-4 pt-24 pb-14 md:pb-16 overflow-hidden">
<div className="absolute inset-0 bg-grid-forge pointer-events-none" style={{ backgroundSize: '40px 40px' }} />
<div className="absolute inset-0 bg-gradient-hero pointer-events-none" />
<EmberParticles count={5} />

{/* Terminal card */}
<motion.div
variants={fadeIn}
initial="initial"
animate="animate"
className="w-full max-w-xl rounded-xl border border-border bg-forge-900/90 backdrop-blur-sm overflow-hidden shadow-2xl shadow-black/50"
>
{/* Title bar */}
<div className="flex items-center gap-2 px-4 py-2.5 bg-forge-800 border-b border-border">
<div className="flex gap-1.5">
<div className="flex items-center gap-2 px-3 sm:px-4 py-2.5 bg-forge-800 border-b border-border">
<div className="flex gap-1.5 flex-shrink-0">
<span className="w-3 h-3 rounded-full bg-status-error/80" />
<span className="w-3 h-3 rounded-full bg-status-warning/80" />
<span className="w-3 h-3 rounded-full bg-status-success/80" />
</div>
<span className="font-mono text-xs text-text-muted ml-2">solfoundry terminal</span>
<span className="font-mono text-xs text-text-muted ml-2 truncate">solfoundry - terminal</span>
</div>

{/* Terminal body */}
<div className="p-5 font-mono text-sm leading-relaxed">
<div className="p-4 sm:p-5 font-mono text-xs sm:text-sm leading-relaxed">
<div className="overflow-hidden">
<span className="text-emerald">$ </span>
<span className="text-text-secondary overflow-hidden whitespace-nowrap inline-block animate-typewriter">
<span className="text-text-secondary overflow-hidden break-all sm:break-normal sm:whitespace-nowrap inline-block max-w-full sm:animate-typewriter">
forge bounty --reward 100 --lang typescript --tier 2
</span>
{typewriterDone && (
Expand All @@ -130,9 +125,9 @@ export function HeroSection() {
className="mt-3 space-y-1.5"
>
{[
{ text: 'Bounty created: #142', delay: 0 },
{ text: 'Escrow funded: 100 USDC', delay: 0.3 },
{ text: '3 contributors notified', delay: 0.6 },
{ text: 'Bounty created: #142', delay: 0 },
{ text: 'Escrow funded: 100 USDC', delay: 0.3 },
{ text: '3 contributors notified', delay: 0.6 },
].map((line, i) => (
<motion.div
key={i}
Expand All @@ -149,37 +144,34 @@ export function HeroSection() {
</div>
</motion.div>

{/* Headline */}
<motion.h1
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3, duration: 0.5 }}
className="font-display text-4xl md:text-5xl font-bold text-text-primary tracking-wider text-center mt-10"
className="font-display text-3xl sm:text-4xl md:text-5xl font-bold text-text-primary tracking-wider text-center mt-8 sm:mt-10 max-w-4xl"
>
THE AI-POWERED BOUNTY{' '}
<span className="text-emerald">FORGE</span>
THE AI-POWERED BOUNTY <span className="text-emerald">FORGE</span>
</motion.h1>

<motion.p
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.45, duration: 0.5 }}
className="font-sans text-lg text-text-secondary text-center mt-4 max-w-lg"
className="font-sans text-base sm:text-lg text-text-secondary text-center mt-4 max-w-lg"
>
Fund bounties. Ship code. Earn rewards.
</motion.p>

{/* CTAs */}
<motion.div
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.6, duration: 0.5 }}
className="flex flex-wrap items-center justify-center gap-4 mt-8"
className="flex flex-col sm:flex-row flex-wrap items-stretch sm:items-center justify-center gap-3 sm:gap-4 mt-8 w-full sm:w-auto"
>
<motion.div variants={buttonHover} initial="rest" whileHover="hover" whileTap="tap">
<Link
to="/bounties"
className="px-6 py-3 rounded-lg bg-emerald text-text-inverse font-semibold text-sm hover:bg-emerald-light transition-colors duration-200 shadow-lg shadow-emerald/20 inline-block"
className="px-6 py-3 rounded-lg bg-emerald text-text-inverse font-semibold text-sm hover:bg-emerald-light transition-colors duration-200 shadow-lg shadow-emerald/20 inline-flex justify-center whitespace-nowrap w-full"
>
Browse Bounties
</Link>
Expand All @@ -188,7 +180,7 @@ export function HeroSection() {
<motion.div variants={buttonHover} initial="rest" whileHover="hover" whileTap="tap">
<Link
to="/bounties/create"
className="px-6 py-3 rounded-lg border border-emerald text-emerald font-semibold text-sm hover:bg-emerald-bg transition-colors duration-200 inline-block"
className="px-6 py-3 rounded-lg border border-emerald text-emerald font-semibold text-sm hover:bg-emerald-bg transition-colors duration-200 inline-flex justify-center whitespace-nowrap w-full"
>
Post a Bounty
</Link>
Expand All @@ -198,35 +190,34 @@ export function HeroSection() {
<motion.div variants={buttonHover} initial="rest" whileHover="hover" whileTap="tap">
<button
onClick={handleSignIn}
className="px-6 py-3 rounded-lg border border-border text-text-secondary font-medium text-sm hover:border-border-hover hover:text-text-primary transition-all duration-200 inline-flex items-center gap-2"
className="px-6 py-3 rounded-lg border border-border text-text-secondary font-medium text-sm hover:border-border-hover hover:text-text-primary transition-all duration-200 inline-flex items-center justify-center gap-2 whitespace-nowrap w-full sm:w-auto"
>
<GitHubIcon /> GitHub
</button>
</motion.div>
)}
</motion.div>

{/* Live stats strip */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.8, duration: 0.5 }}
className="flex items-center justify-center gap-6 mt-8 font-mono text-sm text-text-muted"
className="flex flex-wrap items-center justify-center gap-x-4 gap-y-2 sm:gap-x-6 mt-8 font-mono text-xs sm:text-sm text-text-muted"
>
<span>
<span className="text-text-primary font-semibold">
<CountUp target={stats?.open_bounties ?? 142} />
</span>
{' '}open bounties
</span>
<span className="text-text-muted">·</span>
<span className="hidden sm:inline text-text-muted">-</span>
<span>
<span className="text-text-primary font-semibold">
$<CountUp target={stats?.total_paid_usdc ?? 24500} />
</span>
{' '}paid
</span>
<span className="text-text-muted">·</span>
<span className="hidden sm:inline text-text-muted">-</span>
<span>
<span className="text-text-primary font-semibold">
<CountUp target={stats?.total_contributors ?? 89} />
Expand Down
Loading
Loading