feat(theme): barnyard redesign with rustic crypto aesthetic

- New color palette: barn red primary, cream backgrounds, hay gold
- Added Abril Fatface display font for headlines
- Wood grain texture backgrounds via CSS gradients
- Barnyard personality in copy: 'Join the Flock', 'Enter the Coop'
- Replaced hardcoded slate colors with theme variables
- Updated landing, login, dashboard, onboarding, leaderboard
This commit is contained in:
2026-04-21 14:14:48 -04:00
parent 185018a42e
commit 5db61212dc
8 changed files with 919 additions and 701 deletions
+1 -1
View File
@@ -49,7 +49,7 @@ export default function AuthCallbackPage() {
}, [router, setUser, setToken]); }, [router, setUser, setToken]);
return ( return (
<div className="min-h-screen flex items-center justify-center bg-slate-950 p-4"> <div className="min-h-screen flex items-center justify-center bg-background p-4">
<div className="text-center"> <div className="text-center">
<Loader2 className="mx-auto h-12 w-12 animate-spin text-primary mb-4" /> <Loader2 className="mx-auto h-12 w-12 animate-spin text-primary mb-4" />
<p className="text-lg text-foreground">{status}</p> <p className="text-lg text-foreground">{status}</p>
@@ -1,114 +1,144 @@
'use client'; "use client";
import { useEffect, useState } from 'react'; import { Clock, Coins, Medal, Star, Trophy } from "lucide-react";
import { userApi } from '@/lib/api'; import { useEffect, useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { formatNumber, formatDuration } from '@/lib/utils'; import { userApi } from "@/lib/api";
import { Trophy, Medal, Star, Clock, Coins } from 'lucide-react'; import { formatDuration, formatNumber } from "@/lib/utils";
interface LeaderboardUser { interface LeaderboardUser {
id: string; id: string;
plexUsername: string; plexUsername: string;
totalEarned: number; totalEarned: number;
watchTimeMinutes: number; watchTimeMinutes: number;
} }
export function Leaderboard() { export function Leaderboard() {
const [data, setData] = useState<{ topEarners: LeaderboardUser[]; topWatchers: LeaderboardUser[] }>({ const [data, setData] = useState<{
topEarners: [], topEarners: LeaderboardUser[];
topWatchers: [], topWatchers: LeaderboardUser[];
}); }>({
const [isLoading, setIsLoading] = useState(true); topEarners: [],
topWatchers: [],
});
const [isLoading, setIsLoading] = useState(true);
useEffect(() => { useEffect(() => {
loadLeaderboard(); loadLeaderboard();
}, []); }, []);
const loadLeaderboard = async () => { const loadLeaderboard = async () => {
try { try {
const response = await userApi.getLeaderboard(); const response = await userApi.getLeaderboard();
setData(response.data); setData(response.data);
} catch (error) { } catch (error) {
console.error('Failed to load leaderboard:', error); console.error("Failed to load leaderboard:", error);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
} }
}; };
const getRankIcon = (index: number) => { const getRankIcon = (index: number) => {
switch (index) { switch (index) {
case 0: return <Trophy className="h-4 w-4 text-yellow-500" />; case 0:
case 1: return <Medal className="h-4 w-4 text-slate-400" />; return <Trophy className="h-4 w-4 text-yellow-500" />;
case 2: return <Medal className="h-4 w-4 text-amber-600" />; case 1:
default: return <span className="w-4 text-center text-xs text-muted-foreground">{index + 1}</span>; return <Medal className="h-4 w-4 text-muted-foreground" />;
} case 2:
}; return <Medal className="h-4 w-4 text-amber-600" />;
default:
return (
<span className="w-4 text-center text-xs text-muted-foreground">
{index + 1}
</span>
);
}
};
if (isLoading) { if (isLoading) {
return ( return (
<Card> <Card>
<CardContent className="py-8 text-center text-muted-foreground"> <CardContent className="py-8 text-center text-muted-foreground">
<div className="animate-pulse space-y-4"> <div className="animate-pulse space-y-4">
{[1, 2, 3, 4, 5].map((i) => ( {[1, 2, 3, 4, 5].map((i) => (
<div key={i} className="h-10 bg-muted rounded-md" /> <div key={i} className="h-10 bg-muted rounded-md" />
))} ))}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
); );
} }
return ( return (
<Card className="border-none bg-muted/30 shadow-none"> <Card className="border-none bg-muted/30 shadow-none">
<CardHeader className="pb-2"> <CardHeader className="pb-2">
<CardTitle className="text-lg font-bold flex items-center gap-2"> <CardTitle className="text-lg font-bold flex items-center gap-2">
<Trophy className="h-5 w-5 text-yellow-500" /> <Trophy className="h-5 w-5 text-yellow-500" />
Leaderboard Leaderboard
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Tabs defaultValue="earners" className="w-full"> <Tabs defaultValue="earners" className="w-full">
<TabsList className="grid w-full grid-cols-2 mb-4 h-8 bg-background/50"> <TabsList className="grid w-full grid-cols-2 mb-4 h-8 bg-background/50">
<TabsTrigger value="earners" className="text-xs py-1">Top Earners</TabsTrigger> <TabsTrigger value="earners" className="text-xs py-1">
<TabsTrigger value="watchers" className="text-xs py-1">Top Watchers</TabsTrigger> Top Earners
</TabsList> </TabsTrigger>
<TabsTrigger value="watchers" className="text-xs py-1">
<TabsContent value="earners" className="mt-0"> Top Watchers
<div className="space-y-2"> </TabsTrigger>
{data.topEarners.map((user, index) => ( </TabsList>
<div key={user.id} className="flex items-center justify-between p-2 rounded-lg bg-background/40 hover:bg-background/60 transition-colors">
<div className="flex items-center gap-3"> <TabsContent value="earners" className="mt-0">
<div className="w-6 flex justify-center">{getRankIcon(index)}</div> <div className="space-y-2">
<span className="text-sm font-medium">{user.plexUsername}</span> {data.topEarners.map((user, index) => (
</div> <div
<div className="flex items-center gap-1 text-xs font-bold text-green-500"> key={user.id}
<Coins className="h-3 w-3" /> className="flex items-center justify-between p-2 rounded-lg bg-background/40 hover:bg-background/60 transition-colors"
{formatNumber(user.totalEarned)} >
</div> <div className="flex items-center gap-3">
</div> <div className="w-6 flex justify-center">
))} {getRankIcon(index)}
</div> </div>
</TabsContent> <span className="text-sm font-medium">
{user.plexUsername}
<TabsContent value="watchers" className="mt-0"> </span>
<div className="space-y-2"> </div>
{data.topWatchers.map((user, index) => ( <div className="flex items-center gap-1 text-xs font-bold text-green-500">
<div key={user.id} className="flex items-center justify-between p-2 rounded-lg bg-background/40 hover:bg-background/60 transition-colors"> <Coins className="h-3 w-3" />
<div className="flex items-center gap-3"> {formatNumber(user.totalEarned)}
<div className="w-6 flex justify-center">{getRankIcon(index)}</div> </div>
<span className="text-sm font-medium">{user.plexUsername}</span> </div>
</div> ))}
<div className="flex items-center gap-1 text-xs font-medium text-muted-foreground"> </div>
<Clock className="h-3 w-3" /> </TabsContent>
{Math.floor(user.watchTimeMinutes / 60)}h {user.watchTimeMinutes % 60}m
</div> <TabsContent value="watchers" className="mt-0">
</div> <div className="space-y-2">
))} {data.topWatchers.map((user, index) => (
</div> <div
</TabsContent> key={user.id}
</Tabs> className="flex items-center justify-between p-2 rounded-lg bg-background/40 hover:bg-background/60 transition-colors"
</CardContent> >
</Card> <div className="flex items-center gap-3">
); <div className="w-6 flex justify-center">
{getRankIcon(index)}
</div>
<span className="text-sm font-medium">
{user.plexUsername}
</span>
</div>
<div className="flex items-center gap-1 text-xs font-medium text-muted-foreground">
<Clock className="h-3 w-3" />
{Math.floor(user.watchTimeMinutes / 60)}h{" "}
{user.watchTimeMinutes % 60}m
</div>
</div>
))}
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
);
} }
@@ -1,92 +1,116 @@
'use client'; "use client";
import { Card, CardContent } from '@/components/ui/card'; import { useWallet } from "@solana/wallet-adapter-react";
import { Button } from '@/components/ui/button'; import { WalletMultiButton } from "@solana/wallet-adapter-react-ui";
import { Wallet, Sparkles, ShieldCheck, Zap, ExternalLink } from 'lucide-react'; import {
import { useWallet } from '@solana/wallet-adapter-react'; Egg,
import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'; ExternalLink,
import { useEffect } from 'react'; ShieldCheck,
import { walletApi } from '@/lib/api'; Sparkles,
import { toast } from 'sonner'; Wallet,
Zap,
} from "lucide-react";
import { useEffect } from "react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { walletApi } from "@/lib/api";
interface WelcomeOnboardingProps { interface WelcomeOnboardingProps {
onStart: () => void; onStart: () => void;
onConnected: () => void; onConnected: () => void;
} }
export function WelcomeOnboarding({ onStart, onConnected }: WelcomeOnboardingProps) { export function WelcomeOnboarding({
const { publicKey, connected } = useWallet(); onStart,
onConnected,
}: WelcomeOnboardingProps) {
const { publicKey, connected } = useWallet();
useEffect(() => { useEffect(() => {
if (connected && publicKey) { if (connected && publicKey) {
handleConnectWallet(publicKey.toString()); handleConnectWallet(publicKey.toString());
} }
}, [connected, publicKey]); }, [connected, publicKey]);
const handleConnectWallet = async (address: string) => { const handleConnectWallet = async (address: string) => {
try { try {
await walletApi.connectWallet(address); await walletApi.connectWallet(address);
toast.success('Wallet connected to your account!'); toast.success("Wallet connected to your account!");
onConnected(); onConnected();
} catch (error) { } catch (error) {
toast.error('Failed to link wallet'); toast.error("Failed to link wallet");
} }
}; };
return ( return (
<Card className="mb-8 overflow-hidden border-primary/20 bg-gradient-to-br from-primary/5 via-background to-background"> <Card className="mb-8 overflow-hidden border-2 border-primary/20 bg-gradient-to-br from-primary/5 via-background to-background">
<CardContent className="p-0"> <CardContent className="p-0">
<div className="flex flex-col md:flex-row"> <div className="flex flex-col md:flex-row">
<div className="flex-1 p-8 space-y-6"> <div className="flex-1 p-8 space-y-6">
<div className="space-y-2"> <div className="space-y-2">
<h2 className="text-3xl font-bold tracking-tight">Welcome to the Ecosystem!</h2> <h2 className="font-display text-3xl tracking-tight">
<p className="text-muted-foreground text-lg"> Welcome to the Coop!
You're just one step away from earning rewards for your watch time. </h2>
</p> <p className="text-muted-foreground text-lg">
</div> You are one step away from earning golden eggs for your watch
time.
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6"> </p>
<div className="flex gap-3"> </div>
<div className="mt-1 bg-primary/10 p-2 rounded-lg h-fit">
<Zap className="h-4 w-4 text-primary" />
</div>
<div>
<h4 className="font-semibold">Automatic Rewards</h4>
<p className="text-sm text-muted-foreground">Credits are minted directly to your wallet while you watch.</p>
</div>
</div>
<div className="flex gap-3">
<div className="mt-1 bg-primary/10 p-2 rounded-lg h-fit">
<ShieldCheck className="h-4 w-4 text-primary" />
</div>
<div>
<h4 className="font-semibold">Secure & Private</h4>
<p className="text-sm text-muted-foreground">Your wallet is personal and secured on the Solana blockchain.</p>
</div>
</div>
</div>
<div className="flex flex-wrap gap-4 pt-2"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
<Button size="lg" onClick={onStart} className="h-12 px-8 rounded-full shadow-lg shadow-primary/20"> <div className="flex gap-3">
<Wallet className="mr-2 h-5 w-5" /> <div className="mt-1 bg-primary/10 p-2 rounded-lg h-fit">
Create Managed Wallet <Zap className="h-4 w-4 text-primary" />
</Button> </div>
<div>
<div className="wallet-adapter-custom-wrapper"> <h4 className="font-semibold">Automatic Rewards</h4>
<WalletMultiButton className="h-12 !rounded-full !bg-secondary !text-secondary-foreground hover:!bg-secondary/80" /> <p className="text-sm text-muted-foreground">
</div> Credits are minted directly to your wallet while you watch.
</div> </p>
<p className="text-xs text-muted-foreground italic"> </div>
Choose "Create Managed Wallet" for an easy start, or "Select Wallet" to use your own (Phantom, Solflare, etc.) </div>
</p>
</div> <div className="flex gap-3">
<div className="mt-1 bg-primary/10 p-2 rounded-lg h-fit">
<div className="hidden md:flex flex-none w-72 bg-primary/10 items-center justify-center border-l border-primary/10"> <ShieldCheck className="h-4 w-4 text-primary" />
<Sparkles className="h-32 w-32 text-primary opacity-20 animate-pulse" /> </div>
</div> <div>
</div> <h4 className="font-semibold">Secure & Private</h4>
</CardContent> <p className="text-sm text-muted-foreground">
</Card> Your wallet is personal and secured on the Solana
); blockchain.
</p>
</div>
</div>
</div>
<div className="flex flex-wrap gap-4 pt-2">
<Button
size="lg"
onClick={onStart}
className="h-12 px-8 rounded-full shadow-lg shadow-primary/20"
>
<Wallet className="mr-2 h-5 w-5" />
Create Managed Wallet
</Button>
<div className="wallet-adapter-custom-wrapper">
<WalletMultiButton className="h-12 !rounded-full !bg-secondary !text-secondary-foreground hover:!bg-secondary/80" />
</div>
</div>
<p className="text-xs text-muted-foreground italic">
Choose &ldquo;Create Managed Wallet&rdquo; for an easy start, or
&ldquo;Select Wallet&rdquo; to use your own (Phantom, Solflare,
etc.)
</p>
</div>
<div className="hidden md:flex flex-none w-72 bg-primary/10 items-center justify-center border-l border-primary/10">
<Egg className="h-32 w-32 text-primary opacity-20 animate-pulse" />
</div>
</div>
</CardContent>
</Card>
);
} }
+296 -261
View File
@@ -1,285 +1,320 @@
'use client'; "use client";
import { useEffect, useState } from 'react'; import { WalletMultiButton } from "@solana/wallet-adapter-react-ui";
import { useRouter } from 'next/navigation'; import {
import { useStore } from '@/lib/store'; Clock,
import { walletApi, transactionApi, overseerApi } from '@/lib/api'; Egg,
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; ExternalLink,
import { Button } from '@/components/ui/button'; Film,
import { Badge } from '@/components/ui/badge'; History,
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; TrendingDown,
import { TrendingUp,
Wallet, Wallet,
TrendingUp, } from "lucide-react";
TrendingDown, import { useRouter } from "next/navigation";
Clock, import { useEffect, useState } from "react";
Film, import { toast } from "sonner";
ExternalLink, import { Badge } from "@/components/ui/badge";
Plus, import { Button } from "@/components/ui/button";
History import {
} from 'lucide-react'; Card,
import { formatNumber, truncateAddress } from '@/lib/utils'; CardContent,
import { toast } from 'sonner'; CardDescription,
import { useSocket } from '@/lib/socket'; CardHeader,
import { WalletMultiButton } from '@solana/wallet-adapter-react-ui'; CardTitle,
import { TransactionList } from './components/TransactionList'; } from "@/components/ui/card";
import { WatchHistory } from './components/WatchHistory'; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { CreateWalletModal } from './components/CreateWalletModal'; import { overseerApi, transactionApi, walletApi } from "@/lib/api";
import { WelcomeOnboarding } from './components/WelcomeOnboarding'; import { useSocket } from "@/lib/socket";
import { ActivityFeed } from './components/ActivityFeed'; import { useStore } from "@/lib/store";
import { SearchRequestModal } from './components/SearchRequestModal'; import { formatNumber, truncateAddress } from "@/lib/utils";
import { RequestHistory } from './components/RequestHistory'; import { ActivityFeed } from "./components/ActivityFeed";
import { Leaderboard } from './components/Leaderboard'; import { CreateWalletModal } from "./components/CreateWalletModal";
import { Leaderboard } from "./components/Leaderboard";
import { RequestHistory } from "./components/RequestHistory";
import { SearchRequestModal } from "./components/SearchRequestModal";
import { TransactionList } from "./components/TransactionList";
import { WatchHistory } from "./components/WatchHistory";
import { WelcomeOnboarding } from "./components/WelcomeOnboarding";
interface WalletData { interface WalletData {
hasWallet: boolean; hasWallet: boolean;
address?: string; address?: string;
balance: number; balance: number;
totalEarned: number; totalEarned: number;
totalSpent: number; totalSpent: number;
explorerUrl?: string; explorerUrl?: string;
} }
export default function DashboardPage() { export default function DashboardPage() {
const router = useRouter(); const router = useRouter();
const { user, isAuthenticated, logout } = useStore(); const { user, isAuthenticated, logout } = useStore();
const [wallet, setWallet] = useState<WalletData | null>(null); const [wallet, setWallet] = useState<WalletData | null>(null);
const [requestCosts, setRequestCosts] = useState({ movie: 100, tv: 200 }); const [requestCosts, setRequestCosts] = useState({ movie: 100, tv: 200 });
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); const [isSearchModalOpen, setIsSearchModalOpen] = useState(false);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const socket = useSocket(); const socket = useSocket();
useEffect(() => { useEffect(() => {
if (!isAuthenticated) { if (!isAuthenticated) {
router.push('/login'); router.push("/login");
return; return;
} }
loadData(); loadData();
}, [isAuthenticated, router]); }, [isAuthenticated, router]);
useEffect(() => { useEffect(() => {
if (socket) { if (socket) {
socket.on('credits_earned', (data) => { socket.on("credits_earned", (data) => {
toast.success(`You earned ${data.amount} $COOP!`, { toast.success(`You earned ${data.amount} $COOP!`, {
description: `Watched: ${data.title}`, description: `Watched: ${data.title}`,
}); });
loadData(); loadData();
}); });
socket.on('bonus_received', (data) => { socket.on("bonus_received", (data) => {
toast.success(`Bonus Received: ${data.amount} $COOP!`, { toast.success(`Bonus Received: ${data.amount} $COOP!`, {
description: data.reason, description: data.reason,
}); });
loadData(); loadData();
}); });
socket.on('credits_spent', (data) => { socket.on("credits_spent", (data) => {
toast.info(`Requested: ${data.title}`, { toast.info(`Requested: ${data.title}`, {
description: `Spent ${data.amount} $COOP`, description: `Spent ${data.amount} $COOP`,
}); });
loadData(); loadData();
}); });
return () => { return () => {
socket.off('credits_earned'); socket.off("credits_earned");
socket.off('bonus_received'); socket.off("bonus_received");
socket.off('credits_spent'); socket.off("credits_spent");
}; };
} }
}, [socket]); }, [socket]);
const loadData = async () => { const loadData = async () => {
try { try {
const [walletRes, costsRes] = await Promise.all([ const [walletRes, costsRes] = await Promise.all([
walletApi.getWallet(), walletApi.getWallet(),
overseerApi.getCosts().catch(() => ({ data: { movie: 100, tv: 200 } })) overseerApi.getCosts().catch(() => ({ data: { movie: 100, tv: 200 } })),
]); ]);
setWallet(walletRes.data);
setRequestCosts(costsRes.data);
} catch (error) {
toast.error('Failed to load wallet data');
} finally {
setIsLoading(false);
}
};
if (!isAuthenticated || !user) { setWallet(walletRes.data);
return null; setRequestCosts(costsRes.data);
} } catch (error) {
toast.error("Failed to load wallet data");
} finally {
setIsLoading(false);
}
};
return ( if (!isAuthenticated || !user) {
<div className="min-h-screen bg-background"> return null;
{/* Header */} }
<header className="border-b bg-card">
<div className="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<h1 className="text-2xl font-bold">CoopCredits</h1>
<Badge variant="secondary">{user.plexUsername}</Badge>
{user.isAdmin && (
<Button variant="outline" size="sm" onClick={() => router.push('/admin')}>
Admin
</Button>
)}
</div>
<div className="flex items-center gap-4">
<div className="wallet-adapter-custom-wrapper hidden md:block">
<WalletMultiButton className="!h-9 !px-4 !text-sm !rounded-md !bg-secondary !text-secondary-foreground hover:!bg-secondary/80" />
</div>
<Button variant="ghost" onClick={logout}>
Sign Out
</Button>
</div>
</div>
</header>
<main className="max-w-7xl mx-auto px-4 py-8"> return (
{/* Onboarding for new users */} <div className="min-h-screen bg-background">
{!wallet?.hasWallet && !isLoading && ( {/* Header */}
<WelcomeOnboarding onStart={() => setIsCreateModalOpen(true)} onConnected={loadData} /> <header className="border-b-2 bg-card">
)} <div className="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<Egg className="w-6 h-6 text-primary" />
<h1 className="font-display text-2xl">The Coop</h1>
</div>
<Badge variant="secondary">{user.plexUsername}</Badge>
{user.isAdmin && (
<Button
variant="outline"
size="sm"
onClick={() => router.push("/admin")}
>
Admin
</Button>
)}
</div>
<div className="flex items-center gap-4">
<div className="wallet-adapter-custom-wrapper hidden md:block">
<WalletMultiButton className="!h-9 !px-4 !text-sm !rounded-md !bg-secondary !text-secondary-foreground hover:!bg-secondary/80" />
</div>
<Button variant="ghost" onClick={logout}>
Sign Out
</Button>
</div>
</div>
</header>
{/* Stats Cards */} <main className="max-w-7xl mx-auto px-4 py-8">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8"> {/* Onboarding for new users */}
<Card> {!wallet?.hasWallet && !isLoading && (
<CardHeader className="flex flex-row items-center justify-between pb-2"> <WelcomeOnboarding
<CardTitle className="text-sm font-medium">Balance</CardTitle> onStart={() => setIsCreateModalOpen(true)}
<Wallet className="h-4 w-4 text-muted-foreground" /> onConnected={loadData}
</CardHeader> />
<CardContent> )}
<div className="text-2xl font-bold">
{wallet ? formatNumber(wallet.balance) : '--'} $COOP
</div>
<p className="text-xs text-muted-foreground">
{wallet?.hasWallet ? 'Ready to spend' : 'Create wallet to start'}
</p>
</CardContent>
</Card>
<Card> {/* Stats Cards */}
<CardHeader className="flex flex-row items-center justify-between pb-2"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-8">
<CardTitle className="text-sm font-medium">Total Earned</CardTitle> <Card className="border-2">
<TrendingUp className="h-4 w-4 text-green-500" /> <CardHeader className="flex flex-row items-center justify-between pb-2">
</CardHeader> <CardTitle className="text-sm font-medium">Nest Egg</CardTitle>
<CardContent> <Wallet className="h-4 w-4 text-muted-foreground" />
<div className="text-2xl font-bold"> </CardHeader>
{wallet ? formatNumber(wallet.totalEarned) : '--'} $COOP <CardContent>
</div> <div className="text-2xl font-bold">
<p className="text-xs text-muted-foreground"> {wallet ? formatNumber(wallet.balance) : "--"} $COOP
From watching content </div>
</p> <p className="text-xs text-muted-foreground">
</CardContent> {wallet?.hasWallet
</Card> ? "Ready to spend"
: "Create wallet to start"}
</p>
</CardContent>
</Card>
<Card> <Card className="border-2">
<CardHeader className="flex flex-row items-center justify-between pb-2"> <CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">Total Spent</CardTitle> <CardTitle className="text-sm font-medium">
<TrendingDown className="h-4 w-4 text-red-500" /> Total Gathered
</CardHeader> </CardTitle>
<CardContent> <TrendingUp className="h-4 w-4 text-green-500" />
<div className="text-2xl font-bold"> </CardHeader>
{wallet ? formatNumber(wallet.totalSpent) : '--'} $COOP <CardContent>
</div> <div className="text-2xl font-bold">
<p className="text-xs text-muted-foreground"> {wallet ? formatNumber(wallet.totalEarned) : "--"} $COOP
On content requests </div>
</p> <p className="text-xs text-muted-foreground">
</CardContent> From watching content
</Card> </p>
</CardContent>
</Card>
<Card className="cursor-pointer hover:border-primary/50 transition-colors group" onClick={() => setIsSearchModalOpen(true)}> <Card className="border-2">
<CardHeader className="flex flex-row items-center justify-between pb-2"> <CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">Request Cost</CardTitle> <CardTitle className="text-sm font-medium">Total Spent</CardTitle>
<Film className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" /> <TrendingDown className="h-4 w-4 text-red-500" />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-2xl font-bold">{requestCosts.movie} $COOP</div> <div className="text-2xl font-bold">
<p className="text-xs text-muted-foreground"> {wallet ? formatNumber(wallet.totalSpent) : "--"} $COOP
Click to request content </div>
</p> <p className="text-xs text-muted-foreground">
</CardContent> On content requests
</Card> </p>
</div> </CardContent>
</Card>
{/* Wallet Section removed from here if redundant, or kept if we want it to show after creation */} <Card
{wallet?.hasWallet && ( className="border-2 cursor-pointer hover:border-primary/50 transition-colors group"
<Card className="mb-8"> onClick={() => setIsSearchModalOpen(true)}
<CardHeader> >
<CardTitle className="flex items-center gap-2"> <CardHeader className="flex flex-row items-center justify-between pb-2">
<Wallet className="h-5 w-5" /> <CardTitle className="text-sm font-medium">
Your Wallet Request Cost
</CardTitle> </CardTitle>
<CardDescription> <Film className="h-4 w-4 text-muted-foreground group-hover:text-primary transition-colors" />
Manage your Solana wallet and $COOP tokens </CardHeader>
</CardDescription> <CardContent>
</CardHeader> <div className="text-2xl font-bold">
<CardContent> {requestCosts.movie} $COOP
<div className="flex items-center justify-between p-4 bg-muted rounded-lg"> </div>
<div> <p className="text-xs text-muted-foreground">
<p className="text-sm text-muted-foreground">Address</p> Click to request content
<p className="font-mono font-medium"> </p>
{truncateAddress(wallet.address!)} </CardContent>
</p> </Card>
</div> </div>
<div className="flex gap-2">
<Button variant="outline" size="sm" onClick={() => setIsCreateModalOpen(true)}>
<History className="mr-2 h-4 w-4" />
Backup
</Button>
<Button variant="outline" size="sm" asChild>
<a href={wallet.explorerUrl} target="_blank" rel="noopener noreferrer">
<ExternalLink className="mr-2 h-4 w-4" />
Explorer
</a>
</Button>
</div>
</div>
</CardContent>
</Card>
)}
{/* Tabs */} {/* Wallet Section */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8"> {wallet?.hasWallet && (
<div className="lg:col-span-2"> <Card className="mb-8 border-2">
<Tabs defaultValue="transactions" className="space-y-4"> <CardHeader>
<TabsList> <CardTitle className="flex items-center gap-2">
<TabsTrigger value="transactions">Transactions</TabsTrigger> <Wallet className="h-5 w-5" />
<TabsTrigger value="history">Watch History</TabsTrigger> Your Coop Wallet
<TabsTrigger value="requests">My Requests</TabsTrigger> </CardTitle>
</TabsList> <CardDescription>
Manage your Solana wallet and $COOP tokens
<TabsContent value="transactions"> </CardDescription>
<TransactionList /> </CardHeader>
</TabsContent> <CardContent>
<div className="flex items-center justify-between p-4 bg-muted rounded-lg">
<TabsContent value="history"> <div>
<WatchHistory /> <p className="text-sm text-muted-foreground">Address</p>
</TabsContent> <p className="font-mono font-medium">
<TabsContent value="requests"> {truncateAddress(wallet.address!)}
<RequestHistory /> </p>
</TabsContent> </div>
</Tabs> <div className="flex gap-2">
</div> <Button
<div className="lg:col-span-1 space-y-8"> variant="outline"
<ActivityFeed /> size="sm"
<Leaderboard /> onClick={() => setIsCreateModalOpen(true)}
</div> >
</div> <History className="mr-2 h-4 w-4" />
</main> Backup
</Button>
<Button variant="outline" size="sm" asChild>
<a
href={wallet.explorerUrl}
target="_blank"
rel="noopener noreferrer"
>
<ExternalLink className="mr-2 h-4 w-4" />
Explorer
</a>
</Button>
</div>
</div>
</CardContent>
</Card>
)}
<CreateWalletModal {/* Tabs */}
open={isCreateModalOpen} <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
onClose={() => setIsCreateModalOpen(false)} <div className="lg:col-span-2">
onCreated={loadData} <Tabs defaultValue="transactions" className="space-y-4">
/> <TabsList className="border-2">
<SearchRequestModal <TabsTrigger value="transactions">Transactions</TabsTrigger>
open={isSearchModalOpen} <TabsTrigger value="history">Watch History</TabsTrigger>
onClose={() => setIsSearchModalOpen(false)} <TabsTrigger value="requests">My Requests</TabsTrigger>
onRequested={loadData} </TabsList>
costs={requestCosts}
balance={wallet?.balance || 0} <TabsContent value="transactions">
/> <TransactionList />
</div> </TabsContent>
);
<TabsContent value="history">
<WatchHistory />
</TabsContent>
<TabsContent value="requests">
<RequestHistory />
</TabsContent>
</Tabs>
</div>
<div className="lg:col-span-1 space-y-8">
<ActivityFeed />
<Leaderboard />
</div>
</div>
</main>
<CreateWalletModal
open={isCreateModalOpen}
onClose={() => setIsCreateModalOpen(false)}
onCreated={loadData}
/>
<SearchRequestModal
open={isSearchModalOpen}
onClose={() => setIsSearchModalOpen(false)}
onRequested={loadData}
costs={requestCosts}
balance={wallet?.balance || 0}
/>
</div>
);
} }
+92 -53
View File
@@ -1,6 +1,9 @@
@import "tailwindcss"; @import "tailwindcss";
@theme { @theme {
--font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
--font-display: var(--font-abril), Georgia, "Times New Roman", serif;
--color-border: hsl(var(--border)); --color-border: hsl(var(--border));
--color-input: hsl(var(--input)); --color-input: hsl(var(--input));
--color-ring: hsl(var(--ring)); --color-ring: hsl(var(--ring));
@@ -28,61 +31,97 @@
} }
@layer base { @layer base {
:root { :root {
--background: 45 100% 97%; --background: 48 100% 96%;
--foreground: 24 33% 14%; --foreground: 20 6% 16%;
--card: 0 0% 100%; --card: 30 100% 97%;
--card-foreground: 24 33% 14%; --card-foreground: 20 6% 16%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 24 33% 14%; --popover-foreground: 20 6% 16%;
--primary: 34 92% 52%; --primary: 0 72% 42%;
--primary-foreground: 45 100% 97%; --primary-foreground: 48 100% 96%;
--secondary: 36 45% 92%; --secondary: 30 47% 90%;
--secondary-foreground: 24 33% 14%; --secondary-foreground: 20 6% 16%;
--muted: 36 33% 94%; --muted: 28 47% 90%;
--muted-foreground: 24 10% 42%; --muted-foreground: 24 6% 47%;
--accent: 29 60% 90%; --accent: 28 52% 64%;
--accent-foreground: 24 33% 14%; --accent-foreground: 20 6% 16%;
--destructive: 0 84.2% 60.2%; --destructive: 0 72% 50%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 48 100% 96%;
--border: 31 28% 84%; --border: 28 24% 87%;
--input: 31 28% 84%; --input: 28 24% 87%;
--ring: 34 92% 52%; --ring: 0 72% 42%;
--radius: 0.5rem; --radius: 0.5rem;
} }
.dark { .dark {
--background: 24 33% 10%; --background: 20 9% 10%;
--foreground: 45 100% 96%; --foreground: 40 33% 94%;
--card: 24 29% 14%; --card: 24 9% 14%;
--card-foreground: 45 100% 96%; --card-foreground: 40 33% 94%;
--popover: 24 29% 14%; --popover: 24 9% 14%;
--popover-foreground: 45 100% 96%; --popover-foreground: 40 33% 94%;
--primary: 34 92% 52%; --primary: 0 72% 50%;
--primary-foreground: 24 33% 10%; --primary-foreground: 20 9% 10%;
--secondary: 24 20% 20%; --secondary: 24 13% 22%;
--secondary-foreground: 45 100% 96%; --secondary-foreground: 40 33% 94%;
--muted: 24 18% 18%; --muted: 24 6% 25%;
--muted-foreground: 28 15% 66%; --muted-foreground: 24 6% 64%;
--accent: 24 20% 20%; --accent: 28 82% 32%;
--accent-foreground: 45 100% 96%; --accent-foreground: 40 33% 94%;
--destructive: 0 62.8% 30.6%; --destructive: 0 84% 60%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 20 9% 10%;
--border: 24 18% 24%; --border: 24 6% 33%;
--input: 24 18% 24%; --input: 24 6% 33%;
--ring: 34 92% 52%; --ring: 0 72% 50%;
} }
} }
@layer base { @layer base {
* { * {
border-color: hsl(var(--border)); border-color: hsl(var(--border));
} }
body { body {
background-color: hsl(var(--background)); background-color: hsl(var(--background));
color: hsl(var(--foreground)); color: hsl(var(--foreground));
background-image: background-image:
radial-gradient(circle at top, rgba(255, 188, 66, 0.10), transparent 34%), repeating-linear-gradient(
radial-gradient(circle at bottom right, rgba(122, 69, 22, 0.08), transparent 28%); 90deg,
} transparent,
transparent 2px,
rgba(120, 53, 15, 0.015) 2px,
rgba(120, 53, 15, 0.015) 4px
),
radial-gradient(
circle at 15% 50%,
rgba(217, 119, 6, 0.06) 0%,
transparent 50%
),
radial-gradient(
circle at 85% 80%,
rgba(185, 28, 28, 0.04) 0%,
transparent 40%
);
}
.dark body {
background-image:
repeating-linear-gradient(
90deg,
transparent,
transparent 2px,
rgba(251, 191, 36, 0.012) 2px,
rgba(251, 191, 36, 0.012) 4px
),
radial-gradient(
circle at 15% 50%,
rgba(217, 119, 6, 0.05) 0%,
transparent 50%
),
radial-gradient(
circle at 85% 80%,
rgba(185, 28, 28, 0.03) 0%,
transparent 40%
);
}
} }
+28 -20
View File
@@ -1,29 +1,37 @@
import type { Metadata } from 'next'; import type { Metadata } from "next";
import { Inter } from 'next/font/google'; import { Abril_Fatface, Inter } from "next/font/google";
import './globals.css'; import "./globals.css";
import { ProvidersWrapper } from '@/components/providers-wrapper'; import { Toaster } from "sonner";
import { Toaster } from 'sonner'; import { ProvidersWrapper } from "@/components/providers-wrapper";
const inter = Inter({ subsets: ['latin'] }); const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
const abril = Abril_Fatface({
weight: "400",
subsets: ["latin"],
variable: "--font-abril",
});
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'CoopCredits - Media Rewards', title: "CoopCredits — Watch, Earn, Cluck",
description: 'Earn $COOP tokens for watching content on Plex', description:
"Earn $COOP tokens for watching content on Plex. The Hoboken Chicken coop rewards its flock.",
}; };
export default function RootLayout({ export default function RootLayout({
children, children,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
return ( return (
<html lang="en" className="dark"> <html lang="en" className="dark">
<body className={`${inter.className} dark bg-slate-950 text-foreground`}> <body
<ProvidersWrapper> className={`${inter.variable} ${abril.variable} font-sans dark bg-background text-foreground antialiased`}
{children} >
<Toaster position="top-right" /> <ProvidersWrapper>
</ProvidersWrapper> {children}
</body> <Toaster position="top-right" />
</html> </ProvidersWrapper>
); </body>
</html>
);
} }
+16 -14
View File
@@ -1,9 +1,9 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { Egg, Loader2, Tv } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { authApi } from "@/lib/api"; import { useEffect, useState } from "react";
import { useStore } from "@/lib/store"; import { toast } from "sonner";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Card, Card,
@@ -12,8 +12,8 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { Loader2, Tv } from "lucide-react"; import { authApi } from "@/lib/api";
import { toast } from "sonner"; import { useStore } from "@/lib/store";
export default function LoginPage() { export default function LoginPage() {
const router = useRouter(); const router = useRouter();
@@ -46,13 +46,13 @@ export default function LoginPage() {
if (!isMounted) { if (!isMounted) {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-slate-950 p-4"> <div className="min-h-screen flex items-center justify-center bg-background p-4">
<Card className="w-full max-w-md"> <Card className="w-full max-w-md">
<CardHeader className="text-center"> <CardHeader className="text-center">
<div className="mx-auto w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mb-4"> <div className="mx-auto w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mb-4">
<Tv className="w-8 h-8 text-primary" /> <Egg className="w-8 h-8 text-primary" />
</div> </div>
<CardTitle className="text-2xl">CoopCoins</CardTitle> <CardTitle className="text-2xl font-display">The Coop</CardTitle>
<CardDescription>Loading...</CardDescription> <CardDescription>Loading...</CardDescription>
</CardHeader> </CardHeader>
</Card> </Card>
@@ -61,15 +61,17 @@ export default function LoginPage() {
} }
return ( return (
<div className="min-h-screen flex items-center justify-center bg-slate-950 p-4"> <div className="min-h-screen flex items-center justify-center bg-background p-4">
<Card className="w-full max-w-md"> <Card className="w-full max-w-md border-2">
<CardHeader className="text-center"> <CardHeader className="text-center">
<div className="mx-auto w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mb-4"> <div className="mx-auto w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mb-4">
<Tv className="w-8 h-8 text-primary" /> <Egg className="w-8 h-8 text-primary" />
</div> </div>
<CardTitle className="text-2xl">CoopCoins</CardTitle> <CardTitle className="text-2xl font-display">
Enter the Coop
</CardTitle>
<CardDescription> <CardDescription>
Earn CoopCoins ($COOP) for watching content on Plex Sign in with Plex to start earning $COOP tokens
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@@ -91,7 +93,7 @@ export default function LoginPage() {
)} )}
</Button> </Button>
<p className="mt-4 text-center text-sm text-muted-foreground"> <p className="mt-4 text-center text-sm text-muted-foreground">
Sign in with your Plex account to start earning rewards Join the flock and get rewarded for your watch time
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
+248 -168
View File
@@ -1,184 +1,264 @@
'use client'; "use client";
import Link from 'next/link'; import {
import { Button } from '@/components/ui/button'; ChevronRight,
import { Tv, Wallet, Zap, Shield, ChevronRight, PlayCircle, Info } from 'lucide-react'; Coins,
Egg,
Film,
PlayCircle,
Shield,
Tv,
Wallet,
Zap,
} from "lucide-react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
export default function LandingPage() { export default function LandingPage() {
return ( return (
<div className="min-h-screen bg-background flex flex-col selection:bg-primary selection:text-primary-foreground"> <div className="min-h-screen bg-background flex flex-col selection:bg-primary selection:text-primary-foreground">
{/* Grid Pattern Overlay */} {/* Grain Pattern Overlay */}
<div className="fixed inset-0 z-0 opacity-[0.08] pointer-events-none" <div
style={{ backgroundImage: 'radial-gradient(circle at 2px 2px, rgba(217, 119, 6, 0.6) 1px, transparent 0)', backgroundSize: '42px 42px' }} /> className="fixed inset-0 z-0 opacity-[0.06] pointer-events-none"
style={{
{/* Header */} backgroundImage:
<header className="sticky top-0 z-50 w-full border-b bg-background/80 backdrop-blur-md"> "radial-gradient(circle at 2px 2px, rgba(185, 28, 28, 0.5) 1px, transparent 0)",
<div className="container mx-auto px-4 h-16 flex items-center justify-between"> backgroundSize: "38px 38px",
<div className="flex items-center gap-2 group cursor-pointer"> }}
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center transition-transform group-hover:rotate-12"> />
<Tv className="w-5 h-5 text-primary-foreground" />
</div>
<span className="font-bold text-xl tracking-tight">CoopCoins</span>
</div>
<nav className="hidden md:flex items-center gap-8">
<a href="#features" className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors">Features</a>
<a href="#how-it-works" className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors">How it Works</a>
<Link href="/login">
<Button variant="outline" size="sm" className="rounded-full px-6">
Dashboard
</Button>
</Link>
</nav>
<Link href="/login" className="md:hidden"> {/* Header */}
<Button size="sm" className="rounded-full">Login</Button> <header className="sticky top-0 z-50 w-full border-b bg-background/80 backdrop-blur-md">
</Link> <div className="container mx-auto px-4 h-16 flex items-center justify-between">
</div> <div className="flex items-center gap-2 group cursor-pointer">
</header> <div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center transition-transform group-hover:rotate-12">
<Egg className="w-5 h-5 text-primary-foreground" />
</div>
<span className="font-display text-xl tracking-tight">
CoopCredits
</span>
</div>
<main className="flex-1 relative z-10"> <nav className="hidden md:flex items-center gap-8">
{/* Hero Section */} <a
<section className="py-20 md:py-32 overflow-hidden"> href="#features"
<div className="container mx-auto px-4"> className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
<div className="max-w-4xl mx-auto text-center space-y-8"> >
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary/10 text-primary text-xs font-bold uppercase tracking-widest border border-primary/20 animate-in fade-in slide-in-from-bottom-4 duration-500"> Features
<Zap className="w-3 h-3" /> </a>
Powered by Solana! <a
</div> href="#how-it-works"
className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
<h1 className="text-5xl md:text-7xl font-extrabold tracking-tighter leading-[1.1] animate-in fade-in slide-in-from-bottom-8 duration-700 delay-100"> >
Earn CoopCoins for Your Watch Time! How it Works
</h1> </a>
<Link href="/login">
<p className="text-xl text-muted-foreground max-w-2xl mx-auto animate-in fade-in slide-in-from-bottom-12 duration-1000 delay-200"> <Button variant="outline" size="sm" className="rounded-full px-6">
Earn CoopCoins ($COOP) automatically for enjoying shows on Plex. Then, use them to request new favorites on Overseer! Dashboard
</p> </Button>
</Link>
</nav>
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4 animate-in fade-in slide-in-from-bottom-16 duration-1000 delay-300"> <Link href="/login" className="md:hidden">
<Link href="/login"> <Button size="sm" className="rounded-full">
<Button size="lg" className="h-14 px-8 text-lg rounded-full shadow-lg shadow-primary/20 group"> Login
Get Started Now </Button>
<ChevronRight className="ml-2 w-5 h-5 transition-transform group-hover:translate-x-1" /> </Link>
</Button> </div>
</Link> </header>
<a href="#how-it-works">
<Button variant="ghost" size="lg" className="h-14 px-8 text-lg rounded-full">
Learn More
</Button>
</a>
</div>
</div>
</div>
{/* Abstract background element */}
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[400px] bg-primary/15 blur-[120px] rounded-full -z-10 pointer-events-none" />
</section>
{/* Features Section */} <main className="flex-1 relative z-10">
<section id="features" className="py-24 border-y bg-amber-50/80 dark:bg-stone-950/40"> {/* Hero Section */}
<div className="container mx-auto px-4"> <section className="py-20 md:py-32 overflow-hidden">
<div className="grid grid-cols-1 md:grid-cols-3 gap-12"> <div className="container mx-auto px-4">
<div className="space-y-4 p-6 rounded-2xl bg-background border transition-all hover:shadow-xl hover:-translate-y-1"> <div className="max-w-4xl mx-auto text-center space-y-8">
<div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center mb-6"> <div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary/10 text-primary text-xs font-bold uppercase tracking-widest border border-primary/20 animate-in fade-in slide-in-from-bottom-4 duration-500">
<PlayCircle className="w-6 h-6 text-primary" /> <Coins className="w-3 h-3" />
</div> Blockchain-Powered Barnyard
<h3 className="text-xl font-bold">Watch to Earn</h3> </div>
<p className="text-muted-foreground leading-relaxed">
Every minute you watch on Plex earns you CoopCoins ($COOP)! No extra steps, just enjoy.
</p>
</div>
<div className="space-y-4 p-6 rounded-2xl bg-background border transition-all hover:shadow-xl hover:-translate-y-1"> <h1 className="font-display text-5xl md:text-7xl tracking-tight leading-[1.1] animate-in fade-in slide-in-from-bottom-8 duration-700 delay-100">
<div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center mb-6"> Welcome to the Coop, Friend
<Shield className="w-6 h-6 text-primary" /> </h1>
</div>
<h3 className="text-xl font-bold">Solana Powered</h3>
<p className="text-muted-foreground leading-relaxed">
Your CoopCoins are securely handled on the Solana blockchain all transparent and super fast.
</p>
</div>
<div className="space-y-4 p-6 rounded-2xl bg-background border transition-all hover:shadow-xl hover:-translate-y-1"> <p className="text-xl text-muted-foreground max-w-2xl mx-auto animate-in fade-in slide-in-from-bottom-12 duration-1000 delay-200">
<div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center mb-6"> Earn $COOP tokens automatically for every minute you watch on
<Zap className="w-6 h-6 text-primary" /> Plex. Then spend them like golden eggs to request new movies &
</div> shows.
<h3 className="text-xl font-bold">Spend Credits</h3> </p>
<p className="text-muted-foreground leading-relaxed">
Got enough CoopCoins ($COOP)? Use them to request new movies or shows on Overseer. You help shape our library!
</p>
</div>
</div>
</div>
</section>
{/* How it Works Section */} <div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4 animate-in fade-in slide-in-from-bottom-16 duration-1000 delay-300">
<section id="how-it-works" className="py-24"> <Link href="/login">
<div className="container mx-auto px-4"> <Button
<div className="max-w-3xl mx-auto space-y-16"> size="lg"
<div className="text-center space-y-4"> className="h-14 px-8 text-lg rounded-full shadow-lg shadow-primary/20 group"
<h2 className="text-3xl md:text-5xl font-bold tracking-tight">Simple. Transparent. Fun.</h2> >
<p className="text-muted-foreground text-lg italic">"Our way of saying thanks for being part of the Hoboken Chicken family!"</p> Join the Flock
</div> <ChevronRight className="ml-2 w-5 h-5 transition-transform group-hover:translate-x-1" />
</Button>
</Link>
<a href="#how-it-works">
<Button
variant="ghost"
size="lg"
className="h-14 px-8 text-lg rounded-full"
>
How It Works
</Button>
</a>
</div>
</div>
</div>
<div className="space-y-12"> {/* Warm glow background element */}
<div className="flex gap-6 items-start"> <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[800px] h-[400px] bg-primary/10 blur-[120px] rounded-full -z-10 pointer-events-none" />
<div className="flex-none w-10 h-10 rounded-full bg-primary flex items-center justify-center font-bold text-primary-foreground">1</div> </section>
<div className="space-y-2 pt-1">
<h4 className="text-xl font-bold">Connect your Plex Account</h4>
<p className="text-muted-foreground">Just log in with Plex. We'll set up your Solana wallet automatically in the background.</p>
</div>
</div>
<div className="flex gap-6 items-start"> {/* Features Section */}
<div className="flex-none w-10 h-10 rounded-full bg-primary flex items-center justify-center font-bold text-primary-foreground">2</div> <section id="features" className="py-24 border-y bg-secondary/40">
<div className="space-y-2 pt-1"> <div className="container mx-auto px-4">
<h4 className="text-xl font-bold">Watch your Favorite Content</h4> <div className="text-center mb-16">
<p className="text-muted-foreground">Enjoy movies and shows as usual. Tautulli tracks your fun, and CoopCoins ($COOP) land in your wallet.</p> <h2 className="font-display text-3xl md:text-5xl tracking-tight">
</div> What the Coop Offers
</div> </h2>
<p className="text-muted-foreground mt-4 max-w-xl mx-auto">
A little barnyard magic mixed with blockchain tech.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-12">
<div className="space-y-4 p-6 rounded-2xl bg-background border-2 transition-all hover:shadow-xl hover:-translate-y-1">
<div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center mb-6">
<PlayCircle className="w-6 h-6 text-primary" />
</div>
<h3 className="text-xl font-bold">Watch & Cluck</h3>
<p className="text-muted-foreground leading-relaxed">
Every minute on Plex earns $COOP. No extra steps just kick
back and enjoy the show.
</p>
</div>
<div className="flex gap-6 items-start"> <div className="space-y-4 p-6 rounded-2xl bg-background border-2 transition-all hover:shadow-xl hover:-translate-y-1">
<div className="flex-none w-10 h-10 rounded-full bg-primary flex items-center justify-center font-bold text-primary-foreground">3</div> <div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center mb-6">
<div className="space-y-2 pt-1"> <Shield className="w-6 h-6 text-primary" />
<h4 className="text-xl font-bold">Redeem and Repeat</h4> </div>
<p className="text-muted-foreground">Spend your CoopCoins ($COOP) to pick new things for the server via our Overseer integration. Happy requesting!</p> <h3 className="text-xl font-bold">Solana Strong</h3>
</div> <p className="text-muted-foreground leading-relaxed">
</div> Your $COOP lives on the Solana blockchain fast, transparent,
</div> and secure as a locked coop.
</p>
</div>
<div className="bg-card border p-8 rounded-3xl flex flex-col md:flex-row items-center gap-8 shadow-2xl shadow-primary/5"> <div className="space-y-4 p-6 rounded-2xl bg-background border-2 transition-all hover:shadow-xl hover:-translate-y-1">
<div className="flex-1 space-y-4 text-center md:text-left"> <div className="w-12 h-12 rounded-xl bg-primary/10 flex items-center justify-center mb-6">
<h3 className="text-2xl font-bold">Ready to start earning?</h3> <Zap className="w-6 h-6 text-primary" />
<p className="text-muted-foreground">Come join our family and friends earning rewards for their watch time!</p> </div>
</div> <h3 className="text-xl font-bold">Spend Your Eggs</h3>
<Link href="/login"> <p className="text-muted-foreground leading-relaxed">
<Button size="lg" className="rounded-full px-8 h-12">Start Now</Button> Cash in $COOP to request new movies or shows on Overseer. You
</Link> help build the library.
</div> </p>
</div> </div>
</div> </div>
</section> </div>
</main> </section>
{/* Footer */} {/* How it Works Section */}
<footer className="border-t py-12 bg-amber-100/60 dark:bg-stone-950/70"> <section id="how-it-works" className="py-24">
<div className="container mx-auto px-4"> <div className="container mx-auto px-4">
<div className="flex flex-col md:flex-row justify-between items-center gap-8"> <div className="max-w-3xl mx-auto space-y-16">
<div className="flex items-center gap-2 grayscale opacity-50"> <div className="text-center space-y-4">
<Tv className="w-5 h-5" /> <h2 className="font-display text-3xl md:text-5xl tracking-tight">
<span className="font-bold">CoopCoins</span> Simple as Counting Chickens
</div> </h2>
<p className="text-muted-foreground text-lg italic">
<div className="text-sm text-muted-foreground text-center md:text-right"> &ldquo;Our way of saying thanks for being part of the Hoboken
<p>© {new Date().getFullYear()} Hoboken Chicken. All rights reserved.</p> Chicken family!&rdquo;
<p className="mt-1 flex items-center justify-center md:justify-end gap-1"> </p>
Built with <Zap className="w-3 h-3 text-yellow-500 fill-yellow-500" /> on Solana </div>
</p>
</div> <div className="space-y-12">
</div> <div className="flex gap-6 items-start">
</div> <div className="flex-none w-10 h-10 rounded-full bg-primary flex items-center justify-center font-bold text-primary-foreground font-display">
</footer> 1
</div> </div>
); <div className="space-y-2 pt-1">
<h4 className="text-xl font-bold">
Connect Your Plex Account
</h4>
<p className="text-muted-foreground">
Just log in with Plex. We set up your Solana wallet
quietly in the background.
</p>
</div>
</div>
<div className="flex gap-6 items-start">
<div className="flex-none w-10 h-10 rounded-full bg-primary flex items-center justify-center font-bold text-primary-foreground font-display">
2
</div>
<div className="space-y-2 pt-1">
<h4 className="text-xl font-bold">Watch Your Favorites</h4>
<p className="text-muted-foreground">
Enjoy movies and shows as usual. Tautulli tracks your
time, and $COOP lands in your wallet.
</p>
</div>
</div>
<div className="flex gap-6 items-start">
<div className="flex-none w-10 h-10 rounded-full bg-primary flex items-center justify-center font-bold text-primary-foreground font-display">
3
</div>
<div className="space-y-2 pt-1">
<h4 className="text-xl font-bold">Redeem & Repeat</h4>
<p className="text-muted-foreground">
Spend $COOP to pick new things for the server via
Overseer. Happy requesting!
</p>
</div>
</div>
</div>
<div className="bg-card border-2 p-8 rounded-3xl flex flex-col md:flex-row items-center gap-8 shadow-2xl shadow-primary/5">
<div className="flex-1 space-y-4 text-center md:text-left">
<h3 className="font-display text-2xl">Ready to roost?</h3>
<p className="text-muted-foreground">
Come join the flock and earn rewards for your watch time.
</p>
</div>
<Link href="/login">
<Button size="lg" className="rounded-full px-8 h-12">
Start Now
</Button>
</Link>
</div>
</div>
</div>
</section>
</main>
{/* Footer */}
<footer className="border-t-2 py-12 bg-secondary/40">
<div className="container mx-auto px-4">
<div className="flex flex-col md:flex-row justify-between items-center gap-8">
<div className="flex items-center gap-2 opacity-60">
<Egg className="w-5 h-5" />
<span className="font-display">CoopCredits</span>
</div>
<div className="text-sm text-muted-foreground text-center md:text-right">
<p>
&copy; {new Date().getFullYear()} Hoboken Chicken. All rights
reserved.
</p>
<p className="mt-1 flex items-center justify-center md:justify-end gap-1">
Built with <Zap className="w-3 h-3 text-primary fill-primary" />{" "}
on Solana
</p>
</div>
</div>
</div>
</footer>
</div>
);
} }