Redesign demo page with Reddit-style voting UX and improve ELI5 section
Demo page changes: - Reddit-style vote arrows on left (orange upvote, blue downvote) - More realistic governance proposals (treasury, moderation, research, security, town halls) - Prominent color-coded vote states and confirmation flow - Cleaner card layout with progress bars Homepage ELI5 section: - Three distinct colored cards (orange/blue/purple) for each mechanism - Icon badges for visual hierarchy - Clearer explanations with emphasized takeaways Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9fdf122145
commit
eebd6a4349
|
|
@ -34,9 +34,9 @@ interface DemoProposal {
|
|||
const initialProposals: DemoProposal[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: "Add dark mode to the dashboard",
|
||||
description: "Implement a dark theme option for better nighttime usage",
|
||||
score: 45,
|
||||
title: "Allocate 15% of treasury to ecosystem grants program",
|
||||
description: "Fund community developers building tools and integrations for the ecosystem over the next 6 months",
|
||||
score: 72,
|
||||
userVote: 0,
|
||||
pendingVote: 0,
|
||||
stage: "ranking",
|
||||
|
|
@ -45,9 +45,9 @@ const initialProposals: DemoProposal[] = [
|
|||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "Weekly community calls",
|
||||
description: "Host weekly video calls to discuss proposals and progress",
|
||||
score: 43,
|
||||
title: "Establish a community moderation council",
|
||||
description: "Elect 7 members to handle disputes, enforce guidelines, and maintain community standards",
|
||||
score: 58,
|
||||
userVote: 0,
|
||||
pendingVote: 0,
|
||||
stage: "ranking",
|
||||
|
|
@ -56,9 +56,31 @@ const initialProposals: DemoProposal[] = [
|
|||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Create a mobile app",
|
||||
description: "Build native iOS and Android apps for on-the-go voting",
|
||||
score: 44,
|
||||
title: "Partner with University research lab for governance study",
|
||||
description: "Collaborate with academic researchers to analyze and improve our decision-making processes",
|
||||
score: 41,
|
||||
userVote: 0,
|
||||
pendingVote: 0,
|
||||
stage: "ranking",
|
||||
yesVotes: 0,
|
||||
noVotes: 0,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "Create bounty program for security audits",
|
||||
description: "Reward external security researchers who identify vulnerabilities in our smart contracts",
|
||||
score: 35,
|
||||
userVote: 0,
|
||||
pendingVote: 0,
|
||||
stage: "ranking",
|
||||
yesVotes: 0,
|
||||
noVotes: 0,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "Host quarterly virtual town halls",
|
||||
description: "Regular video conferences for community updates, Q&A sessions, and open discussion",
|
||||
score: 23,
|
||||
userVote: 0,
|
||||
pendingVote: 0,
|
||||
stage: "ranking",
|
||||
|
|
@ -173,7 +195,7 @@ export default function DemoPage() {
|
|||
|
||||
const rankingProposals = proposals
|
||||
.filter((p) => p.stage === "ranking")
|
||||
.sort((a, b) => b.score - a.score); // Sort by score descending
|
||||
.sort((a, b) => b.score - a.score);
|
||||
const votingProposals = proposals.filter((p) => p.stage === "voting");
|
||||
|
||||
return (
|
||||
|
|
@ -184,25 +206,26 @@ export default function DemoPage() {
|
|||
</Badge>
|
||||
<h1 className="text-4xl font-bold">Try Quadratic Proposal Ranking</h1>
|
||||
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
|
||||
Experience how rVote works without creating an account. Click the arrows
|
||||
to add votes and see the quadratic cost increase in real-time.
|
||||
Experience how rVote works without creating an account. Click the vote
|
||||
arrows to rank proposals—watch how quadratic costs scale in real-time.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Credits display */}
|
||||
<Card className="border-primary/50 bg-primary/5">
|
||||
<Card className="border-2 border-orange-500/30 bg-gradient-to-r from-orange-500/10 to-amber-500/10">
|
||||
<CardContent className="py-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Coins className="h-5 w-5 text-primary" />
|
||||
<span className="font-semibold text-lg">{credits} credits</span>
|
||||
<Coins className="h-6 w-6 text-orange-500" />
|
||||
<span className="font-bold text-2xl text-orange-600">{credits}</span>
|
||||
<span className="text-muted-foreground">credits</span>
|
||||
</div>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Max vote weight: {maxWeight}
|
||||
</span>
|
||||
<Badge variant="outline" className="border-orange-500/30 text-orange-600">
|
||||
Max vote: ±{maxWeight}
|
||||
</Badge>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" onClick={resetDemo}>
|
||||
<Button variant="outline" size="sm" onClick={resetDemo} className="border-orange-500/30 hover:bg-orange-500/10">
|
||||
<RotateCcw className="h-4 w-4 mr-2" />
|
||||
Reset Demo
|
||||
</Button>
|
||||
|
|
@ -211,14 +234,14 @@ export default function DemoPage() {
|
|||
</Card>
|
||||
|
||||
{/* Quadratic cost explainer */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Card className="border-muted">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<TrendingUp className="h-5 w-5" />
|
||||
Quadratic Ranking Cost
|
||||
<TrendingUp className="h-5 w-5 text-orange-500" />
|
||||
Quadratic Voting Cost
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Click the arrows to add votes. Each additional vote costs more!
|
||||
Each additional vote costs exponentially more credits
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
|
|
@ -226,174 +249,203 @@ export default function DemoPage() {
|
|||
{[1, 2, 3, 4, 5].map((w) => (
|
||||
<div
|
||||
key={w}
|
||||
className={`p-3 rounded-lg border ${
|
||||
className={`p-3 rounded-lg border-2 transition-all ${
|
||||
w <= maxWeight
|
||||
? "bg-primary/10 border-primary/30"
|
||||
: "bg-muted border-muted"
|
||||
? "bg-orange-500/10 border-orange-500/40 text-orange-700"
|
||||
: "bg-muted/50 border-muted text-muted-foreground"
|
||||
}`}
|
||||
>
|
||||
<div className="font-bold text-lg">{w}</div>
|
||||
<div className="text-muted-foreground">vote{w > 1 ? "s" : ""}</div>
|
||||
<div className="font-mono text-xs mt-1">{w * w} credits</div>
|
||||
<div className="font-bold text-xl">{w > 0 ? "+" : ""}{w}</div>
|
||||
<div className="text-xs opacity-70">vote{w > 1 ? "s" : ""}</div>
|
||||
<div className="font-mono text-sm mt-1 font-semibold">{w * w}¢</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-4 text-center">
|
||||
This prevents wealthy voters from dominating. Spreading votes across
|
||||
proposals is more efficient than concentrating them.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Ranking stage */}
|
||||
<section className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge>Stage 1</Badge>
|
||||
<h2 className="text-xl font-semibold">Ranking</h2>
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge className="bg-orange-500 hover:bg-orange-600">Stage 1</Badge>
|
||||
<h2 className="text-xl font-semibold">Quadratic Ranking</h2>
|
||||
<span className="text-muted-foreground text-sm">
|
||||
Proposals need +100 to advance
|
||||
Score +100 to advance →
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{rankingProposals.map((proposal, index) => {
|
||||
const hasPending = proposal.pendingVote !== 0;
|
||||
const hasVoted = proposal.userVote !== 0;
|
||||
const pendingCost = proposal.pendingVote * proposal.pendingVote;
|
||||
const previewScore = proposal.score + proposal.pendingVote;
|
||||
const rank = index + 1;
|
||||
<div className="space-y-2">
|
||||
{rankingProposals.map((proposal) => {
|
||||
const hasPending = proposal.pendingVote !== 0;
|
||||
const hasVoted = proposal.userVote !== 0;
|
||||
const pendingCost = proposal.pendingVote * proposal.pendingVote;
|
||||
const previewScore = proposal.score + proposal.pendingVote;
|
||||
const progressPercent = Math.min((proposal.score / 100) * 100, 100);
|
||||
const previewPercent = Math.min((previewScore / 100) * 100, 100);
|
||||
|
||||
return (
|
||||
<Card key={proposal.id} className="transition-all duration-300">
|
||||
<div className="flex">
|
||||
{/* Rank indicator */}
|
||||
<div className="flex items-center justify-center px-3 bg-muted/50 border-r font-bold text-2xl text-muted-foreground min-w-[50px]">
|
||||
#{rank}
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center px-4 border-r bg-muted/30 min-w-[100px]">
|
||||
{/* Up arrow */}
|
||||
<Button
|
||||
variant={proposal.userVote > 0 ? "default" : proposal.pendingVote > 0 ? "outline" : "ghost"}
|
||||
size="sm"
|
||||
className={`h-8 w-8 p-0 ${proposal.pendingVote > 0 ? "border-primary bg-primary/10" : ""}`}
|
||||
onClick={() => {
|
||||
if (proposal.userVote > 0) {
|
||||
removeVote(proposal.id);
|
||||
} else if (!hasPending || proposal.pendingVote > 0) {
|
||||
incrementVote(proposal.id);
|
||||
}
|
||||
}}
|
||||
disabled={hasVoted && proposal.userVote < 0}
|
||||
>
|
||||
<ChevronUp className="h-5 w-5" />
|
||||
</Button>
|
||||
return (
|
||||
<Card
|
||||
key={proposal.id}
|
||||
className={`transition-all duration-200 overflow-hidden ${
|
||||
hasPending
|
||||
? proposal.pendingVote > 0
|
||||
? "ring-2 ring-orange-500/50 bg-orange-500/5"
|
||||
: "ring-2 ring-blue-500/50 bg-blue-500/5"
|
||||
: hasVoted
|
||||
? proposal.userVote > 0
|
||||
? "bg-orange-500/5"
|
||||
: "bg-blue-500/5"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex">
|
||||
{/* Reddit-style vote column */}
|
||||
<div className="flex flex-col items-center justify-center py-4 px-3 bg-muted/40 min-w-[70px] gap-1">
|
||||
{/* Upvote button */}
|
||||
<button
|
||||
onClick={() => {
|
||||
if (hasVoted && proposal.userVote > 0) {
|
||||
removeVote(proposal.id);
|
||||
} else if (!hasVoted || proposal.pendingVote >= 0) {
|
||||
incrementVote(proposal.id);
|
||||
}
|
||||
}}
|
||||
disabled={hasVoted && proposal.userVote < 0}
|
||||
className={`p-1 rounded transition-all hover:scale-110 ${
|
||||
(hasVoted && proposal.userVote > 0) || proposal.pendingVote > 0
|
||||
? "text-orange-500"
|
||||
: "text-muted-foreground hover:text-orange-500"
|
||||
} ${hasVoted && proposal.userVote < 0 ? "opacity-30 cursor-not-allowed" : "cursor-pointer"}`}
|
||||
>
|
||||
<ChevronUp className="h-8 w-8" strokeWidth={3} />
|
||||
</button>
|
||||
|
||||
{/* Score display */}
|
||||
<Badge
|
||||
variant={hasPending ? "default" : "outline"}
|
||||
className={`font-mono text-lg my-1 min-w-[4rem] justify-center transition-all ${
|
||||
{/* Score display */}
|
||||
<div className={`font-bold text-xl tabular-nums min-w-[3ch] text-center ${
|
||||
hasPending
|
||||
? proposal.pendingVote > 0
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-destructive text-destructive-foreground"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{hasPending ? (
|
||||
<span className="flex items-center gap-1 text-sm">
|
||||
<span className="opacity-70">{proposal.score}</span>
|
||||
<span>→</span>
|
||||
<span>{previewScore}</span>
|
||||
</span>
|
||||
) : (
|
||||
proposal.score
|
||||
)}
|
||||
</Badge>
|
||||
? "text-orange-500"
|
||||
: "text-blue-500"
|
||||
: hasVoted
|
||||
? proposal.userVote > 0
|
||||
? "text-orange-500"
|
||||
: "text-blue-500"
|
||||
: "text-foreground"
|
||||
}`}>
|
||||
{hasPending ? previewScore : proposal.score}
|
||||
</div>
|
||||
|
||||
{/* Down arrow */}
|
||||
<Button
|
||||
variant={proposal.userVote < 0 ? "destructive" : proposal.pendingVote < 0 ? "outline" : "ghost"}
|
||||
size="sm"
|
||||
className={`h-8 w-8 p-0 ${proposal.pendingVote < 0 ? "border-destructive bg-destructive/10" : ""}`}
|
||||
onClick={() => {
|
||||
if (proposal.userVote < 0) {
|
||||
removeVote(proposal.id);
|
||||
} else if (!hasPending || proposal.pendingVote < 0) {
|
||||
decrementVote(proposal.id);
|
||||
}
|
||||
}}
|
||||
disabled={hasVoted && proposal.userVote > 0}
|
||||
>
|
||||
<ChevronDown className="h-5 w-5" />
|
||||
</Button>
|
||||
{/* Downvote button */}
|
||||
<button
|
||||
onClick={() => {
|
||||
if (hasVoted && proposal.userVote < 0) {
|
||||
removeVote(proposal.id);
|
||||
} else if (!hasVoted || proposal.pendingVote <= 0) {
|
||||
decrementVote(proposal.id);
|
||||
}
|
||||
}}
|
||||
disabled={hasVoted && proposal.userVote > 0}
|
||||
className={`p-1 rounded transition-all hover:scale-110 ${
|
||||
(hasVoted && proposal.userVote < 0) || proposal.pendingVote < 0
|
||||
? "text-blue-500"
|
||||
: "text-muted-foreground hover:text-blue-500"
|
||||
} ${hasVoted && proposal.userVote > 0 ? "opacity-30 cursor-not-allowed" : "cursor-pointer"}`}
|
||||
>
|
||||
<ChevronDown className="h-8 w-8" strokeWidth={3} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Pending vote info */}
|
||||
{hasPending && (
|
||||
<div className="flex flex-col items-center gap-0.5 mt-2">
|
||||
<span className="text-xs font-medium">
|
||||
{proposal.pendingVote > 0 ? "+" : ""}{proposal.pendingVote} vote{Math.abs(proposal.pendingVote) !== 1 ? "s" : ""}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{pendingCost} credit{pendingCost !== 1 ? "s" : ""}
|
||||
</span>
|
||||
<div className="flex gap-1 mt-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0"
|
||||
onClick={() => cancelPending(proposal.id)}
|
||||
title="Cancel"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
variant={proposal.pendingVote > 0 ? "default" : "destructive"}
|
||||
size="sm"
|
||||
className="h-6 px-2 text-xs"
|
||||
onClick={() => castVote(proposal.id)}
|
||||
title="Confirm vote"
|
||||
>
|
||||
<Check className="h-3 w-3 mr-1" />
|
||||
Cast
|
||||
</Button>
|
||||
{/* Proposal content */}
|
||||
<div className="flex-1 p-4 min-w-0">
|
||||
<h3 className="font-semibold text-base leading-tight">{proposal.title}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1 line-clamp-2">
|
||||
{proposal.description}
|
||||
</p>
|
||||
|
||||
{/* Progress bar */}
|
||||
<div className="mt-3">
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground mb-1">
|
||||
<span>Progress to voting stage</span>
|
||||
<span className={hasPending ? (proposal.pendingVote > 0 ? "text-orange-500" : "text-blue-500") : ""}>
|
||||
{hasPending ? previewScore : proposal.score}/100
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 rounded-full bg-muted overflow-hidden">
|
||||
<div
|
||||
className={`h-full rounded-full transition-all duration-300 ${
|
||||
hasPending
|
||||
? proposal.pendingVote > 0
|
||||
? "bg-orange-500"
|
||||
: "bg-blue-500"
|
||||
: "bg-primary"
|
||||
}`}
|
||||
style={{ width: `${hasPending ? previewPercent : progressPercent}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Existing vote display */}
|
||||
{hasVoted && !hasPending && (
|
||||
<span className="text-xs text-muted-foreground mt-2">
|
||||
Your vote: {proposal.userVote > 0 ? "+" : ""}{proposal.userVote}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 p-4">
|
||||
<h3 className="font-semibold">{proposal.title}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{proposal.description}
|
||||
</p>
|
||||
<div className="mt-3">
|
||||
<div className="flex items-center justify-between text-xs mb-1">
|
||||
<span>Progress to voting</span>
|
||||
<span>{hasPending ? previewScore : proposal.score}/100</span>
|
||||
</div>
|
||||
<Progress
|
||||
value={Math.min(((hasPending ? previewScore : proposal.score) / 100) * 100, 100)}
|
||||
className="h-2"
|
||||
/>
|
||||
{/* Vote status / pending confirmation */}
|
||||
{(hasPending || hasVoted) && (
|
||||
<div className="mt-3 flex items-center gap-3">
|
||||
{hasPending ? (
|
||||
<>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={proposal.pendingVote > 0
|
||||
? "border-orange-500/50 text-orange-600 bg-orange-500/10"
|
||||
: "border-blue-500/50 text-blue-600 bg-blue-500/10"
|
||||
}
|
||||
>
|
||||
{proposal.pendingVote > 0 ? "+" : ""}{proposal.pendingVote} vote = {pendingCost} credits
|
||||
</Badge>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-7 px-2 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => cancelPending(proposal.id)}
|
||||
>
|
||||
<X className="h-4 w-4 mr-1" />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
className={`h-7 ${
|
||||
proposal.pendingVote > 0
|
||||
? "bg-orange-500 hover:bg-orange-600"
|
||||
: "bg-blue-500 hover:bg-blue-600"
|
||||
}`}
|
||||
onClick={() => castVote(proposal.id)}
|
||||
>
|
||||
<Check className="h-4 w-4 mr-1" />
|
||||
Confirm
|
||||
</Button>
|
||||
</>
|
||||
) : hasVoted && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className={proposal.userVote > 0
|
||||
? "bg-orange-500/20 text-orange-600 border-orange-500/30"
|
||||
: "bg-blue-500/20 text-blue-600 border-blue-500/30"
|
||||
}
|
||||
>
|
||||
You voted: {proposal.userVote > 0 ? "+" : ""}{proposal.userVote}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Voting stage - only show if proposals have been promoted */}
|
||||
{/* Voting stage */}
|
||||
{votingProposals.length > 0 && (
|
||||
<section className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline">Stage 2</Badge>
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<Badge variant="outline" className="border-green-500/50 text-green-600">Stage 2</Badge>
|
||||
<h2 className="text-xl font-semibold">Pass/Fail Voting</h2>
|
||||
<span className="text-muted-foreground text-sm">
|
||||
One member = one vote
|
||||
|
|
@ -401,79 +453,79 @@ export default function DemoPage() {
|
|||
</div>
|
||||
|
||||
{votingProposals.map((proposal) => {
|
||||
const total = proposal.yesVotes + proposal.noVotes;
|
||||
const yesPercent = total > 0 ? (proposal.yesVotes / total) * 100 : 50;
|
||||
return (
|
||||
<Card key={proposal.id}>
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-lg">{proposal.title}</CardTitle>
|
||||
<div className="flex items-center gap-1 text-sm text-yellow-600">
|
||||
<Clock className="h-4 w-4" />
|
||||
<span>6 days left</span>
|
||||
const total = proposal.yesVotes + proposal.noVotes;
|
||||
const yesPercent = total > 0 ? (proposal.yesVotes / total) * 100 : 50;
|
||||
return (
|
||||
<Card key={proposal.id} className="border-green-500/30 bg-green-500/5">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-lg">{proposal.title}</CardTitle>
|
||||
<div className="flex items-center gap-1 text-sm text-amber-600">
|
||||
<Clock className="h-4 w-4" />
|
||||
<span>6 days left</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CardDescription>{proposal.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Vote bar */}
|
||||
<div className="space-y-2">
|
||||
<div className="h-4 rounded-full overflow-hidden bg-red-500/20 flex">
|
||||
<div
|
||||
className="h-full bg-green-500 transition-all"
|
||||
style={{ width: `${yesPercent}%` }}
|
||||
/>
|
||||
<div
|
||||
className="h-full bg-red-500 transition-all"
|
||||
style={{ width: `${100 - yesPercent}%` }}
|
||||
/>
|
||||
<CardDescription>{proposal.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Vote bar */}
|
||||
<div className="space-y-2">
|
||||
<div className="h-4 rounded-full overflow-hidden bg-muted flex">
|
||||
<div
|
||||
className="h-full bg-green-500 transition-all"
|
||||
style={{ width: `${yesPercent}%` }}
|
||||
/>
|
||||
<div
|
||||
className="h-full bg-red-500 transition-all"
|
||||
style={{ width: `${100 - yesPercent}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm font-medium">
|
||||
<span className="text-green-600">
|
||||
{proposal.yesVotes} Yes ({Math.round(yesPercent)}%)
|
||||
</span>
|
||||
<span className="text-red-600">
|
||||
{proposal.noVotes} No ({Math.round(100 - yesPercent)}%)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between text-sm">
|
||||
<span className="text-green-600">
|
||||
{proposal.yesVotes} Yes ({Math.round(yesPercent)}%)
|
||||
</span>
|
||||
<span className="text-red-600">
|
||||
{proposal.noVotes} No ({Math.round(100 - yesPercent)}%)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Vote buttons */}
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex-col h-auto py-3 hover:bg-green-500/10 hover:border-green-500"
|
||||
onClick={() => castFinalVote(proposal.id, "yes")}
|
||||
>
|
||||
<Check className="h-5 w-5 text-green-500" />
|
||||
<span className="text-xs mt-1">Yes</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex-col h-auto py-3 hover:bg-red-500/10 hover:border-red-500"
|
||||
onClick={() => castFinalVote(proposal.id, "no")}
|
||||
>
|
||||
<X className="h-5 w-5 text-red-500" />
|
||||
<span className="text-xs mt-1">No</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex-col h-auto py-3"
|
||||
onClick={() => castFinalVote(proposal.id, "abstain")}
|
||||
>
|
||||
<Minus className="h-5 w-5" />
|
||||
<span className="text-xs mt-1">Abstain</span>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
{/* Vote buttons */}
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex-col h-auto py-3 border-green-500/30 hover:bg-green-500/10 hover:border-green-500"
|
||||
onClick={() => castFinalVote(proposal.id, "yes")}
|
||||
>
|
||||
<Check className="h-5 w-5 text-green-500" />
|
||||
<span className="text-xs mt-1">Yes</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex-col h-auto py-3 border-red-500/30 hover:bg-red-500/10 hover:border-red-500"
|
||||
onClick={() => castFinalVote(proposal.id, "no")}
|
||||
>
|
||||
<X className="h-5 w-5 text-red-500" />
|
||||
<span className="text-xs mt-1">No</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="flex-col h-auto py-3"
|
||||
onClick={() => castFinalVote(proposal.id, "abstain")}
|
||||
>
|
||||
<Minus className="h-5 w-5" />
|
||||
<span className="text-xs mt-1">Abstain</span>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* CTA */}
|
||||
<Card className="border-primary bg-primary/5">
|
||||
<Card className="border-2 border-primary/30 bg-gradient-to-r from-primary/10 to-accent/10">
|
||||
<CardContent className="py-8 text-center space-y-4">
|
||||
<h2 className="text-2xl font-bold">Ready to try it for real?</h2>
|
||||
<p className="text-muted-foreground">
|
||||
|
|
@ -481,7 +533,7 @@ export default function DemoPage() {
|
|||
You'll get 50 credits to start and earn 10 more each day.
|
||||
</p>
|
||||
<div className="flex justify-center gap-4">
|
||||
<Button asChild size="lg">
|
||||
<Button asChild size="lg" className="bg-orange-500 hover:bg-orange-600">
|
||||
<Link href="/auth/signup">
|
||||
Create Account <ArrowRight className="ml-2 h-4 w-4" />
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {
|
|||
ListOrdered,
|
||||
Target,
|
||||
Layers,
|
||||
ChevronUp,
|
||||
} from "lucide-react";
|
||||
|
||||
export default async function HomePage() {
|
||||
|
|
@ -117,42 +118,68 @@ export default async function HomePage() {
|
|||
|
||||
{/* ELI5 Section */}
|
||||
<section className="py-8">
|
||||
<Card className="border-2 border-accent/30 bg-gradient-to-r from-accent/5 via-primary/5 to-accent/5">
|
||||
<CardHeader className="text-center pb-2">
|
||||
<Badge variant="secondary" className="w-fit mx-auto mb-2 bg-accent/10 text-accent-foreground border-accent/20">
|
||||
ELI5
|
||||
</Badge>
|
||||
<CardTitle className="text-2xl">rVote in 30 Seconds</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-center max-w-3xl mx-auto">
|
||||
<p className="text-lg text-muted-foreground leading-relaxed">
|
||||
<strong className="text-foreground">rVote</strong> is a{" "}
|
||||
<strong className="text-primary">quadratic Reddit-style ranking system</strong>{" "}
|
||||
with <strong className="text-accent">time-delayed vote decay</strong>{" "}
|
||||
for proposal prioritization.
|
||||
</p>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6 text-sm">
|
||||
<div className="p-3 rounded-lg bg-primary/10">
|
||||
<strong className="text-primary">Quadratic</strong>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Voting more costs exponentially more credits (1→1, 2→4, 3→9), preventing any single voice from dominating.
|
||||
</p>
|
||||
<div className="text-center mb-6">
|
||||
<Badge variant="secondary" className="mb-3 bg-muted text-muted-foreground">
|
||||
ELI5
|
||||
</Badge>
|
||||
<h2 className="text-2xl font-bold">rVote in 30 Seconds</h2>
|
||||
<p className="text-lg text-muted-foreground mt-2 max-w-2xl mx-auto">
|
||||
A <strong className="text-orange-500">quadratic</strong>{" "}
|
||||
<strong className="text-blue-500">Reddit-style ranking system</strong>{" "}
|
||||
with <strong className="text-purple-500">time-delayed vote decay</strong>{" "}
|
||||
for proposal prioritization.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
{/* Quadratic */}
|
||||
<Card className="border-2 border-orange-500/40 bg-gradient-to-br from-orange-500/10 to-orange-500/5 overflow-hidden">
|
||||
<CardContent className="pt-5 pb-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<div className="h-8 w-8 rounded-full bg-orange-500 flex items-center justify-center">
|
||||
<span className="text-white font-bold text-sm">x²</span>
|
||||
</div>
|
||||
<h3 className="font-bold text-orange-600 text-lg">Quadratic</h3>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-secondary/10">
|
||||
<strong className="text-secondary">Reddit-style</strong>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Upvote or downvote proposals. The best ideas rise to the top through collective ranking.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
Voting more costs exponentially more credits. 1 vote = 1 credit, 2 votes = 4, 3 votes = 9.
|
||||
<strong className="text-foreground block mt-2">No single voice can dominate.</strong>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Reddit-style */}
|
||||
<Card className="border-2 border-blue-500/40 bg-gradient-to-br from-blue-500/10 to-blue-500/5 overflow-hidden">
|
||||
<CardContent className="pt-5 pb-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<div className="h-8 w-8 rounded-full bg-blue-500 flex items-center justify-center">
|
||||
<ChevronUp className="h-5 w-5 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-blue-600 text-lg">Reddit-style</h3>
|
||||
</div>
|
||||
<div className="p-3 rounded-lg bg-accent/10">
|
||||
<strong className="text-accent-foreground">Vote Decay</strong>
|
||||
<p className="text-muted-foreground mt-1">
|
||||
Votes fade after 30-60 days, ensuring rankings reflect current community priorities, not ancient history.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
Upvote or downvote proposals. Scores aggregate from all community votes.
|
||||
<strong className="text-foreground block mt-2">Best ideas rise to the top.</strong>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Vote Decay */}
|
||||
<Card className="border-2 border-purple-500/40 bg-gradient-to-br from-purple-500/10 to-purple-500/5 overflow-hidden">
|
||||
<CardContent className="pt-5 pb-4">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<div className="h-8 w-8 rounded-full bg-purple-500 flex items-center justify-center">
|
||||
<Clock className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<h3 className="font-bold text-purple-600 text-lg">Vote Decay</h3>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
||||
Votes fade after 30-60 days. Old support expires, requiring renewed interest.
|
||||
<strong className="text-foreground block mt-2">Rankings stay fresh and relevant.</strong>
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* What is Quadratic Proposal Ranking */}
|
||||
|
|
|
|||
Loading…
Reference in New Issue