Initialized repository for chat Conviction voting website

Co-authored-by: Jeff Emmett <46964190+Jeff-Emmett@users.noreply.github.com>
This commit is contained in:
v0 2025-11-08 03:22:37 +00:00
commit 4b4fcc6867
34 changed files with 4752 additions and 0 deletions

27
.gitignore vendored Normal file
View File

@ -0,0 +1,27 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
# next.js
/.next/
/out/
# production
/build
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

30
README.md Normal file
View File

@ -0,0 +1,30 @@
# Conviction voting website
*Automatically synced with your [v0.app](https://v0.app) deployments*
[![Deployed on Vercel](https://img.shields.io/badge/Deployed%20on-Vercel-black?style=for-the-badge&logo=vercel)](https://vercel.com/jeff-emmetts-projects/v0-conviction-voting-website)
[![Built with v0](https://img.shields.io/badge/Built%20with-v0.app-black?style=for-the-badge)](https://v0.app/chat/omvQUeqmMUY)
## Overview
This repository will stay in sync with your deployed chats on [v0.app](https://v0.app).
Any changes you make to your deployed app will be automatically pushed to this repository from [v0.app](https://v0.app).
## Deployment
Your project is live at:
**[https://vercel.com/jeff-emmetts-projects/v0-conviction-voting-website](https://vercel.com/jeff-emmetts-projects/v0-conviction-voting-website)**
## Build your app
Continue building your app on:
**[https://v0.app/chat/omvQUeqmMUY](https://v0.app/chat/omvQUeqmMUY)**
## How It Works
1. Create and modify your project using [v0.app](https://v0.app)
2. Deploy your chats from the v0 interface
3. Changes are automatically pushed to this repository
4. Vercel deploys the latest version from this repository

226
app/applications/page.tsx Normal file
View File

@ -0,0 +1,226 @@
import { SiteHeader } from "@/components/site-header"
import { SiteFooter } from "@/components/site-footer"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { ExternalLink, Rocket, CheckCircle2, Github } from "lucide-react"
export default function ApplicationsPage() {
const implementations = [
{
name: "1Hive Gardens",
description:
"1Hive uses Disputable Conviction Voting as the core governance mechanism for their Gardens framework. Community members can create proposals and stake tokens to signal support for funding requests from the shared treasury.",
url: "https://1hive.gitbook.io/gardens/on-chain-governance/protocol-parameters/disputable-conviction-voting",
status: "Production",
blockchain: "Gnosis Chain",
tags: ["DAO", "Treasury Management", "Community Funding"],
},
{
name: "Commons Stack",
description:
"The Commons Stack implements Conviction Voting for commons-based funding decisions, allowing communities to allocate resources to public goods and shared infrastructure in a continuous, democratic way.",
url: "https://cv.commonsstack.org/",
status: "Production",
blockchain: "Ethereum",
tags: ["Public Goods", "Commons", "Funding"],
},
{
name: "Moonbeam Network",
description:
"Moonbeam implements Conviction Voting through a Solidity precompile contract that interacts with Substrate's Conviction Voting Pallet, enabling token holders to vote on governance referenda with conviction multipliers.",
url: "https://docs.moonbeam.network/builders/ethereum/precompiles/features/governance/conviction-voting/",
status: "Production",
blockchain: "Polkadot Parachain",
tags: ["OpenGov", "Polkadot", "Precompile"],
},
{
name: "Ceramic Network (Off-chain)",
description:
"Ceramic Network developed a trust-minimized off-chain implementation of Conviction Voting using IDX and verifiable data structures. This approach avoids high gas costs while maintaining verifiability.",
url: "https://blog.ceramic.network/trust-minimized-off-chain-conviction-voting",
status: "Research",
blockchain: "Off-chain / IPFS",
tags: ["Off-chain", "Gas Efficient", "Verifiable"],
},
{
name: "Giveth",
description:
"Giveth pioneered the exploration of Conviction Voting for decentralized funding allocation, working with Commons Stack and BlockScience to develop and refine the mechanism.",
url: "https://giveth.io/",
status: "Production",
blockchain: "Ethereum / Gnosis",
tags: ["Philanthropy", "Community", "Funding"],
},
{
name: "Token Engineering Commons",
description:
"The TEC uses Conviction Voting as part of their governance toolkit for allocating funds to token engineering projects and public goods in the ecosystem.",
url: "https://token-engineering-commons.gitbook.io/tec-handbook/",
status: "Production",
blockchain: "Gnosis Chain",
tags: ["Token Engineering", "DAO", "Research"],
},
]
const useCases = [
{
title: "DAO Treasury Management",
description:
"Allocate funds from shared treasuries based on community preferences without time-boxed voting rounds",
},
{
title: "Public Goods Funding",
description: "Support commons-based projects and infrastructure with continuous community input",
},
{
title: "Grant Programs",
description: "Run transparent, participatory grant programs where conviction grows with community support",
},
{
title: "Resource Allocation",
description: "Prioritize work proposals and bounties based on sustained community interest",
},
{
title: "Protocol Governance",
description: "Make parameter changes and protocol upgrades with time-weighted community consensus",
},
{
title: "Community Prioritization",
description: "Surface and prioritize community needs through continuous preference signaling",
},
]
return (
<div className="flex min-h-screen flex-col">
<SiteHeader />
<main className="flex-1">
{/* Hero Section */}
<section className="relative overflow-hidden border-b">
<div className="absolute inset-0 bg-gradient-to-br from-muted/30 via-background to-muted/20 -z-10" />
<div className="container mx-auto max-w-screen-xl px-4 py-16 md:py-24">
<div className="max-w-3xl">
<h1 className="text-4xl md:text-6xl font-bold tracking-tight mb-6 text-balance">
Real-World Applications
</h1>
<p className="text-xl text-muted-foreground leading-relaxed">
Discover where Conviction Voting has been implemented and how organizations are using it for continuous,
democratic decision-making.
</p>
</div>
</div>
</section>
{/* Implementations Section */}
<section className="py-24">
<div className="container mx-auto max-w-screen-xl px-4">
<div className="mb-12">
<h2 className="text-3xl md:text-4xl font-bold tracking-tight mb-4">Live Implementations</h2>
<p className="text-lg text-muted-foreground">
Organizations and platforms using Conviction Voting in production
</p>
</div>
<div className="grid grid-cols-1 gap-6">
{implementations.map((impl, index) => (
<Card key={index} className="hover:shadow-lg transition-shadow">
<CardHeader>
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4 mb-2">
<div className="flex-1">
<CardTitle className="text-2xl mb-2">{impl.name}</CardTitle>
<div className="flex flex-wrap items-center gap-2 mb-3">
<Badge variant={impl.status === "Production" ? "default" : "secondary"}>
{impl.status === "Production" ? (
<CheckCircle2 className="h-3 w-3 mr-1" />
) : (
<Rocket className="h-3 w-3 mr-1" />
)}
{impl.status}
</Badge>
<Badge variant="outline">{impl.blockchain}</Badge>
</div>
</div>
<Button asChild variant="outline" size="sm">
<a href={impl.url} target="_blank" rel="noopener noreferrer">
Visit
<ExternalLink className="ml-2 h-4 w-4" />
</a>
</Button>
</div>
</CardHeader>
<CardContent>
<p className="text-muted-foreground leading-relaxed mb-4">{impl.description}</p>
<div className="flex flex-wrap gap-2">
{impl.tags.map((tag, idx) => (
<Badge key={idx} variant="secondary" className="text-xs">
{tag}
</Badge>
))}
</div>
</CardContent>
</Card>
))}
</div>
</div>
</section>
{/* Use Cases Section */}
<section className="py-24 bg-muted/40">
<div className="container mx-auto max-w-screen-xl px-4">
<div className="max-w-3xl mx-auto text-center mb-16">
<h2 className="text-3xl md:text-4xl font-bold tracking-tight mb-4">Common Use Cases</h2>
<p className="text-lg text-muted-foreground leading-relaxed">
Conviction Voting excels in scenarios requiring continuous community input and resource allocation
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{useCases.map((useCase, index) => (
<Card key={index}>
<CardContent className="pt-6">
<h3 className="font-semibold text-lg mb-3">{useCase.title}</h3>
<p className="text-muted-foreground leading-relaxed text-sm">{useCase.description}</p>
</CardContent>
</Card>
))}
</div>
</div>
</section>
{/* Additional Resources */}
<section className="py-24">
<div className="container mx-auto max-w-screen-xl px-4">
<Card className="bg-primary text-primary-foreground">
<CardContent className="py-12 px-8">
<div className="max-w-3xl mx-auto text-center">
<h2 className="text-3xl font-bold mb-4">Want to implement Conviction Voting?</h2>
<p className="text-lg mb-8 opacity-90">
Explore the technical resources, cadCAD models, and documentation to get started with your own
implementation.
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Button asChild variant="secondary" size="lg">
<a href="https://github.com/BlockScience/conviction" target="_blank" rel="noopener noreferrer">
<Github className="mr-2 h-5 w-5" />
GitHub Repository
</a>
</Button>
<Button
asChild
variant="outline"
size="lg"
className="bg-transparent border-primary-foreground text-primary-foreground hover:bg-primary-foreground hover:text-primary"
>
<a href="/#resources">View Resources</a>
</Button>
</div>
</div>
</CardContent>
</Card>
</div>
</section>
</main>
<SiteFooter />
</div>
)
}

128
app/globals.css Normal file
View File

@ -0,0 +1,128 @@
@import "tailwindcss";
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *));
:root {
/* Light mode - Clean minimal palette inspired by design */
--background: oklch(0.99 0.005 240); /* Near white with subtle cool tone */
--foreground: oklch(0.15 0.01 240); /* Deep charcoal */
--card: oklch(1 0 0); /* Pure white */
--card-foreground: oklch(0.15 0.01 240);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.15 0.01 240);
--primary: oklch(0.25 0.02 240); /* Dark slate */
--primary-foreground: oklch(0.99 0.005 240);
--secondary: oklch(0.95 0.01 240); /* Light gray */
--secondary-foreground: oklch(0.15 0.01 240);
--muted: oklch(0.93 0.01 240);
--muted-foreground: oklch(0.5 0.01 240); /* Medium gray */
--accent: oklch(0.92 0.01 240);
--accent-foreground: oklch(0.15 0.01 240);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.99 0.005 240);
--border: oklch(0.89 0.01 240);
--input: oklch(0.89 0.01 240);
--ring: oklch(0.4 0.02 240);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--radius: 0.5rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
/* Dark mode */
--background: oklch(0.12 0.01 240);
--foreground: oklch(0.98 0.005 240);
--card: oklch(0.15 0.01 240);
--card-foreground: oklch(0.98 0.005 240);
--popover: oklch(0.15 0.01 240);
--popover-foreground: oklch(0.98 0.005 240);
--primary: oklch(0.98 0.005 240);
--primary-foreground: oklch(0.15 0.01 240);
--secondary: oklch(0.22 0.01 240);
--secondary-foreground: oklch(0.98 0.005 240);
--muted: oklch(0.22 0.01 240);
--muted-foreground: oklch(0.65 0.01 240);
--accent: oklch(0.22 0.01 240);
--accent-foreground: oklch(0.98 0.005 240);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.98 0.005 240);
--border: oklch(0.22 0.01 240);
--input: oklch(0.22 0.01 240);
--ring: oklch(0.5 0.02 240);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0);
}
@theme inline {
/* optional: --font-sans, --font-serif, --font-mono if they are applied in the layout.tsx */
--font-sans: "Geist", "Geist Fallback";
--font-mono: "Geist Mono", "Geist Mono Fallback";
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

47
app/layout.tsx Normal file
View File

@ -0,0 +1,47 @@
import type React from "react"
import type { Metadata } from "next"
import { Geist, Geist_Mono } from "next/font/google"
import { Analytics } from "@vercel/analytics/next"
import "./globals.css"
const _geist = Geist({ subsets: ["latin"] })
const _geistMono = Geist_Mono({ subsets: ["latin"] })
export const metadata: Metadata = {
title: "Conviction Voting - Continuous Decision Making for DAOs",
description:
"Learn about Conviction Voting, a novel continuous decision-making mechanism where votes accumulate weight over time using a halflife algorithm.",
generator: "v0.app",
icons: {
icon: [
{
url: "/icon-light-32x32.png",
media: "(prefers-color-scheme: light)",
},
{
url: "/icon-dark-32x32.png",
media: "(prefers-color-scheme: dark)",
},
{
url: "/icon.svg",
type: "image/svg+xml",
},
],
apple: "/apple-icon.png",
},
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<body className={`font-sans antialiased`}>
{children}
<Analytics />
</body>
</html>
)
}

23
app/page.tsx Normal file
View File

@ -0,0 +1,23 @@
import { SiteHeader } from "@/components/site-header"
import { SiteFooter } from "@/components/site-footer"
import { HeroSection } from "@/components/hero-section"
import { OverviewSection } from "@/components/overview-section"
import { AlgorithmSection } from "@/components/algorithm-section"
import { BenefitsSection } from "@/components/benefits-section"
import { ResourcesSection } from "@/components/resources-section"
export default function Home() {
return (
<div className="flex min-h-screen flex-col">
<SiteHeader />
<main className="flex-1">
<HeroSection />
<OverviewSection />
<AlgorithmSection />
<BenefitsSection />
<ResourcesSection />
</main>
<SiteFooter />
</div>
)
}

21
components.json Normal file
View File

@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

View File

@ -0,0 +1,83 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
export function AlgorithmSection() {
return (
<section id="algorithm" className="py-24">
<div className="container mx-auto max-w-screen-xl px-4">
<div className="max-w-3xl mx-auto text-center mb-16">
<h2 className="text-4xl md:text-5xl font-bold tracking-tight mb-6">The Halflife Algorithm</h2>
<p className="text-lg text-muted-foreground leading-relaxed">
Conviction Voting uses an exponential decay function (halflife) to manage how support charges up and down
for proposals.
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 max-w-5xl mx-auto">
<Card>
<CardHeader>
<CardTitle>How Conviction Grows</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<p className="text-muted-foreground leading-relaxed">
When you start supporting a proposal, your support (conviction) doesn't immediately apply. Instead, it
charges up over time according to a halflife function.
</p>
<ul className="space-y-2 text-muted-foreground">
<li className="flex items-start gap-2">
<span className="text-foreground font-semibold mt-0.5"></span>
<span>After 2 days (48 hours): conviction reaches 1/2 of potential value</span>
</li>
<li className="flex items-start gap-2">
<span className="text-foreground font-semibold mt-0.5"></span>
<span>After 4 days: conviction reaches 3/4 of potential value</span>
</li>
<li className="flex items-start gap-2">
<span className="text-foreground font-semibold mt-0.5"></span>
<span>After 6 days: conviction reaches 7/8 of potential value</span>
</li>
<li className="flex items-start gap-2">
<span className="text-foreground font-semibold mt-0.5"></span>
<span>The process continues asymptotically toward full value</span>
</li>
</ul>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Key Parameters</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-4">
<div>
<h4 className="font-semibold mb-2">Conviction Growth Rate</h4>
<p className="text-sm text-muted-foreground leading-relaxed">
Determines how quickly support charges up and down (typically 48 hours halflife)
</p>
</div>
<div>
<h4 className="font-semibold mb-2">Spending Limit</h4>
<p className="text-sm text-muted-foreground leading-relaxed">
Maximum proportion of funds that can be requested by any single proposal (e.g., 10%)
</p>
</div>
<div>
<h4 className="font-semibold mb-2">Minimum Conviction</h4>
<p className="text-sm text-muted-foreground leading-relaxed">
Minimum threshold required for small proposals to prevent spam (typically 2.5%)
</p>
</div>
<div>
<h4 className="font-semibold mb-2">Effective Stake</h4>
<p className="text-sm text-muted-foreground leading-relaxed">
Minimum percent of token supply used to calculate thresholds (typically 20%)
</p>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,62 @@
import { Card, CardContent } from "@/components/ui/card"
export function BenefitsSection() {
const benefits = [
{
number: "01",
title: "No Time Pressure",
description: "Vote whenever you want without coordinating around specific voting periods",
},
{
number: "02",
title: "Reduced Voter Fatigue",
description: "Set your preferences once and they persist until you change them",
},
{
number: "03",
title: "Better Signal Quality",
description: "Long-term community members have more influence than short-term participants",
},
{
number: "04",
title: "Attack Resistance",
description: "Time-weighting makes vote-buying and last-minute coordination attacks less effective",
},
{
number: "05",
title: "Continuous Data",
description: "Rich temporal data streams enable responsive and adaptive governance",
},
{
number: "06",
title: "Funds Allocation",
description: "Specifically designed from first principles for allocating shared treasury resources",
},
]
return (
<section id="benefits" className="py-24 bg-muted/40">
<div className="container mx-auto max-w-screen-xl px-4">
<div className="max-w-3xl mx-auto text-center mb-16">
<h2 className="text-4xl md:text-5xl font-bold tracking-tight mb-6">Why Conviction Voting?</h2>
<p className="text-lg text-muted-foreground leading-relaxed">
Traditional time-boxed voting has shown limited effectiveness in distributed communities. Conviction Voting
offers a better alternative for continuous, human-centric decision making.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{benefits.map((benefit) => (
<Card key={benefit.number}>
<CardContent className="pt-6">
<div className="text-4xl font-bold text-primary/20 mb-4">{benefit.number}</div>
<h3 className="font-semibold text-xl mb-3">{benefit.title}</h3>
<p className="text-muted-foreground leading-relaxed">{benefit.description}</p>
</CardContent>
</Card>
))}
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,43 @@
import { Button } from "@/components/ui/button"
import { ArrowRight } from "lucide-react"
import Link from "next/link"
export function HeroSection() {
return (
<section className="relative overflow-hidden">
{/* Background gradient effect */}
<div className="absolute inset-0 bg-gradient-to-br from-muted/30 via-background to-muted/20 -z-10" />
<div className="container mx-auto max-w-screen-xl px-4 py-24 md:py-32">
<div className="max-w-3xl mx-auto text-center">
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-muted/60 text-sm mb-8">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-foreground opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-foreground"></span>
</span>
Continuous governance for the decentralized era
</div>
<h1 className="text-5xl md:text-7xl font-bold tracking-tight mb-6 text-balance">Conviction Voting</h1>
<p className="text-xl md:text-2xl text-muted-foreground leading-relaxed mb-8 text-pretty">
A novel continuous decision-making mechanism that allows votes to accumulate weight over time according to a
halflife algorithm
</p>
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Button asChild size="lg" className="text-base">
<Link href="/#overview">
Learn How It Works
<ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
<Button asChild variant="outline" size="lg" className="text-base bg-transparent">
<Link href="/applications">View Applications</Link>
</Button>
</div>
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,65 @@
import { Card, CardContent } from "@/components/ui/card"
import { Clock, TrendingUp, Shield, Users } from "lucide-react"
export function OverviewSection() {
const features = [
{
icon: Clock,
title: "Continuous Voting",
description:
"Express your preferences continuously, not just during time-boxed voting periods. Change your vote at any time.",
},
{
icon: TrendingUp,
title: "Conviction Growth",
description:
"Your vote weight grows over time according to a halflife algorithm, giving more influence to long-term community members.",
},
{
icon: Shield,
title: "Attack Resistant",
description: "Sidesteps sybil attacks and provides collusion resistance through time-weighted mechanisms.",
},
{
icon: Users,
title: "Human-Centric",
description: "Captures human needs in temporal data flows, ensuring people remain central to automated systems.",
},
]
return (
<section id="overview" className="py-24 bg-muted/40">
<div className="container mx-auto max-w-screen-xl px-4">
<div className="max-w-3xl mx-auto text-center mb-16">
<h2 className="text-4xl md:text-5xl font-bold tracking-tight mb-6">What is Conviction Voting?</h2>
<p className="text-lg text-muted-foreground leading-relaxed">
Conviction Voting is a decision-making process in which voters continuously express their preference by
staking tokens in support of proposals. The conviction (weight) of their vote grows over time, and proposals
pass when they reach an algorithmically-set threshold.
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-12">
{features.map((feature, index) => {
const Icon = feature.icon
return (
<Card key={index}>
<CardContent className="pt-6">
<div className="flex items-start gap-4">
<div className="p-3 rounded-lg bg-primary/10">
<Icon className="h-6 w-6 text-primary" />
</div>
<div className="flex-1">
<h3 className="font-semibold text-lg mb-2">{feature.title}</h3>
<p className="text-muted-foreground leading-relaxed">{feature.description}</p>
</div>
</div>
</CardContent>
</Card>
)
})}
</div>
</div>
</section>
)
}

View File

@ -0,0 +1,113 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { ExternalLink, FileText, Github, BookOpen } from "lucide-react"
export function ResourcesSection() {
const resources = [
{
category: "Core Articles",
icon: FileText,
items: [
{
title: "Conviction Voting: A Novel Continuous Decision Making Alternative",
author: "Jeff Emmett",
url: "https://blog.giveth.io/conviction-voting-a-novel-continuous-decision-making-alternative-to-governance-aa746cfb9475",
},
{
title: "Understanding Real-Time 'Vote Streaming'",
author: "Jeff Emmett",
url: "https://medium.com/commonsstack/announcing-the-conviction-voting-cadcad-model-release-8e907ce67e4e",
},
{
title: "A Brief History of Conviction Voting",
author: "Michael Zargham",
url: "https://medium.com/block-science/a-brief-history-of-conviction-voting-ad4ca4eb4aee",
},
],
},
{
category: "Technical Resources",
icon: Github,
items: [
{
title: "Social Sensor Fusion Paper",
author: "BlockScience",
url: "https://github.com/BlockScience/conviction/blob/master/social-sensorfusion.pdf",
},
{
title: "Conviction Voting cadCAD Model",
author: "BlockScience",
url: "https://github.com/BlockScience/conviction/blob/master/conviction_cadCAD3.ipynb",
},
{
title: "Conviction Voting GitHub Repository",
author: "BlockScience",
url: "https://github.com/BlockScience/conviction",
},
],
},
{
category: "Learning Resources",
icon: BookOpen,
items: [
{
title: "Conviction Voting WTF",
author: "Community Resource",
url: "https://convictionvoting.wtf/",
},
{
title: "Token Engineering Commons Handbook",
author: "TEC",
url: "https://token-engineering-commons.gitbook.io/tec-handbook/",
},
],
},
]
return (
<section id="resources" className="py-24">
<div className="container mx-auto max-w-screen-xl px-4">
<div className="max-w-3xl mx-auto text-center mb-16">
<h2 className="text-4xl md:text-5xl font-bold tracking-tight mb-6">Learn More</h2>
<p className="text-lg text-muted-foreground leading-relaxed">
Explore articles, papers, and technical resources to deepen your understanding of Conviction Voting.
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{resources.map((section) => {
const Icon = section.icon
return (
<Card key={section.category}>
<CardHeader>
<div className="flex items-center gap-3 mb-2">
<Icon className="h-5 w-5 text-primary" />
<CardTitle className="text-lg">{section.category}</CardTitle>
</div>
</CardHeader>
<CardContent>
<ul className="space-y-4">
{section.items.map((item, idx) => (
<li key={idx}>
<a href={item.url} target="_blank" rel="noopener noreferrer" className="group block">
<div className="flex items-start gap-2">
<ExternalLink className="h-4 w-4 mt-0.5 text-muted-foreground group-hover:text-foreground transition-colors flex-shrink-0" />
<div>
<div className="font-medium text-sm group-hover:text-primary transition-colors leading-relaxed">
{item.title}
</div>
<div className="text-xs text-muted-foreground mt-0.5">{item.author}</div>
</div>
</div>
</a>
</li>
))}
</ul>
</CardContent>
</Card>
)
})}
</div>
</div>
</section>
)
}

118
components/site-footer.tsx Normal file
View File

@ -0,0 +1,118 @@
import Link from "next/link"
export function SiteFooter() {
return (
<footer className="border-t border-border bg-muted/40">
<div className="container mx-auto max-w-screen-2xl px-4 py-12">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
<div>
<h3 className="font-semibold text-lg mb-4">Conviction Voting</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
A novel continuous decision-making mechanism for decentralized governance.
</p>
</div>
<div>
<h4 className="font-semibold text-sm mb-4">Learn</h4>
<ul className="space-y-2 text-sm text-muted-foreground">
<li>
<Link href="/#overview" className="hover:text-foreground transition-colors">
Overview
</Link>
</li>
<li>
<Link href="/#algorithm" className="hover:text-foreground transition-colors">
Algorithm
</Link>
</li>
<li>
<Link href="/#benefits" className="hover:text-foreground transition-colors">
Benefits
</Link>
</li>
<li>
<Link href="/applications" className="hover:text-foreground transition-colors">
Applications
</Link>
</li>
</ul>
</div>
<div>
<h4 className="font-semibold text-sm mb-4">Resources</h4>
<ul className="space-y-2 text-sm text-muted-foreground">
<li>
<a
href="https://github.com/BlockScience/conviction"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
GitHub
</a>
</li>
<li>
<a
href="https://medium.com/commonsstack"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
Medium
</a>
</li>
<li>
<a
href="https://1hive.org"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
1Hive
</a>
</li>
</ul>
</div>
<div>
<h4 className="font-semibold text-sm mb-4">Community</h4>
<ul className="space-y-2 text-sm text-muted-foreground">
<li>
<a
href="https://commonsstack.org"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
Commons Stack
</a>
</li>
<li>
<a
href="https://www.blockscience.com"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
BlockScience
</a>
</li>
<li>
<a
href="https://giveth.io"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
Giveth
</a>
</li>
</ul>
</div>
</div>
<div className="mt-8 pt-8 border-t border-border">
<p className="text-sm text-muted-foreground text-center">
Built with contributions from Commons Stack, BlockScience, 1Hive, and the broader web3 community.
</p>
</div>
</div>
</footer>
)
}

View File

@ -0,0 +1,43 @@
import Link from "next/link"
import { Button } from "@/components/ui/button"
export function SiteHeader() {
return (
<header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container mx-auto flex h-16 max-w-screen-2xl items-center justify-between px-4">
<Link href="/" className="flex items-center gap-2 text-xl font-semibold tracking-tight">
Conviction Voting
</Link>
<nav className="hidden md:flex items-center gap-8">
<Link
href="/#overview"
className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
>
Overview
</Link>
<Link
href="/#algorithm"
className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
>
Algorithm
</Link>
<Link
href="/#benefits"
className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
>
Benefits
</Link>
<Link
href="/applications"
className="text-sm font-medium text-muted-foreground hover:text-foreground transition-colors"
>
Applications
</Link>
</nav>
<Button asChild variant="default" size="sm">
<Link href="/#resources">Resources</Link>
</Button>
</div>
</header>
)
}

View File

@ -0,0 +1,11 @@
'use client'
import * as React from 'react'
import {
ThemeProvider as NextThemesProvider,
type ThemeProviderProps,
} from 'next-themes'
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

46
components/ui/badge.tsx Normal file
View File

@ -0,0 +1,46 @@
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const badgeVariants = cva(
'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
{
variants: {
variant: {
default:
'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
secondary:
'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
destructive:
'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline:
'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
},
},
defaultVariants: {
variant: 'default',
},
},
)
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<'span'> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : 'span'
return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
}
export { Badge, badgeVariants }

60
components/ui/button.tsx Normal file
View File

@ -0,0 +1,60 @@
import * as React from 'react'
import { Slot } from '@radix-ui/react-slot'
import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils'
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
outline:
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost:
'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9',
'icon-sm': 'size-8',
'icon-lg': 'size-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
)
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<'button'> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : 'button'
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }

92
components/ui/card.tsx Normal file
View File

@ -0,0 +1,92 @@
import * as React from 'react'
import { cn } from '@/lib/utils'
function Card({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card"
className={cn(
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
className,
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-header"
className={cn(
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
className,
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-title"
className={cn('leading-none font-semibold', className)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-description"
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-action"
className={cn(
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
className,
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-content"
className={cn('px-6', className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-footer"
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}

6
lib/utils.ts Normal file
View File

@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}

14
next.config.mjs Normal file
View File

@ -0,0 +1,14 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
images: {
unoptimized: true,
},
}
export default nextConfig

73
package.json Normal file
View File

@ -0,0 +1,73 @@
{
"name": "my-v0-project",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "next build",
"dev": "next dev",
"lint": "eslint .",
"start": "next start"
},
"dependencies": {
"@hookform/resolvers": "^3.10.0",
"@radix-ui/react-accordion": "1.2.2",
"@radix-ui/react-alert-dialog": "1.1.4",
"@radix-ui/react-aspect-ratio": "1.1.1",
"@radix-ui/react-avatar": "1.1.2",
"@radix-ui/react-checkbox": "1.1.3",
"@radix-ui/react-collapsible": "1.1.2",
"@radix-ui/react-context-menu": "2.2.4",
"@radix-ui/react-dialog": "1.1.4",
"@radix-ui/react-dropdown-menu": "2.1.4",
"@radix-ui/react-hover-card": "1.1.4",
"@radix-ui/react-label": "2.1.1",
"@radix-ui/react-menubar": "1.1.4",
"@radix-ui/react-navigation-menu": "1.2.3",
"@radix-ui/react-popover": "1.1.4",
"@radix-ui/react-progress": "1.1.1",
"@radix-ui/react-radio-group": "1.2.2",
"@radix-ui/react-scroll-area": "1.2.2",
"@radix-ui/react-select": "2.1.4",
"@radix-ui/react-separator": "1.1.1",
"@radix-ui/react-slider": "1.2.2",
"@radix-ui/react-slot": "1.1.1",
"@radix-ui/react-switch": "1.1.2",
"@radix-ui/react-tabs": "1.1.2",
"@radix-ui/react-toast": "1.2.4",
"@radix-ui/react-toggle": "1.1.1",
"@radix-ui/react-toggle-group": "1.1.1",
"@radix-ui/react-tooltip": "1.1.6",
"@vercel/analytics": "latest",
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.0.4",
"date-fns": "4.1.0",
"embla-carousel-react": "8.5.1",
"input-otp": "1.4.1",
"lucide-react": "^0.454.0",
"next": "16.0.0",
"next-themes": "^0.4.6",
"react": "19.2.0",
"react-day-picker": "9.8.0",
"react-dom": "19.2.0",
"react-hook-form": "^7.60.0",
"react-resizable-panels": "^2.1.7",
"recharts": "2.15.4",
"sonner": "^1.7.4",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"vaul": "^0.9.9",
"zod": "3.25.76"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.9",
"@types/node": "^22",
"@types/react": "^19",
"@types/react-dom": "^19",
"postcss": "^8.5",
"tailwindcss": "^4.1.9",
"tw-animate-css": "1.3.3",
"typescript": "^5"
}
}

3233
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

8
postcss.config.mjs Normal file
View File

@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
'@tailwindcss/postcss': {},
},
}
export default config

BIN
public/apple-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
public/icon-dark-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

BIN
public/icon-light-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

26
public/icon.svg Normal file
View File

@ -0,0 +1,26 @@
<svg width="180" height="180" viewBox="0 0 180 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
@media (prefers-color-scheme: light) {
.background { fill: black; }
.foreground { fill: white; }
}
@media (prefers-color-scheme: dark) {
.background { fill: white; }
.foreground { fill: black; }
}
</style>
<g clip-path="url(#clip0_7960_43945)">
<rect class="background" width="180" height="180" rx="37" />
<g style="transform: scale(95%); transform-origin: center">
<path class="foreground"
d="M101.141 53H136.632C151.023 53 162.689 64.6662 162.689 79.0573V112.904H148.112V79.0573C148.112 78.7105 148.098 78.3662 148.072 78.0251L112.581 112.898C112.701 112.902 112.821 112.904 112.941 112.904H148.112V126.672H112.941C98.5504 126.672 86.5638 114.891 86.5638 100.5V66.7434H101.141V100.5C101.141 101.15 101.191 101.792 101.289 102.422L137.56 66.7816C137.255 66.7563 136.945 66.7434 136.632 66.7434H101.141V53Z" />
<path class="foreground"
d="M65.2926 124.136L14 66.7372H34.6355L64.7495 100.436V66.7372H80.1365V118.47C80.1365 126.278 70.4953 129.958 65.2926 124.136Z" />
</g>
</g>
<defs>
<clipPath id="clip0_7960_43945">
<rect width="180" height="180" fill="white" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/placeholder-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="215" height="48" fill="none"><path fill="#000" d="M57.588 9.6h6L73.828 38h-5.2l-2.36-6.88h-11.36L52.548 38h-5.2l10.24-28.4Zm7.16 17.16-4.16-12.16-4.16 12.16h8.32Zm23.694-2.24c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.486-7.72.12 3.4c.534-1.227 1.307-2.173 2.32-2.84 1.04-.693 2.267-1.04 3.68-1.04 1.494 0 2.76.387 3.8 1.16 1.067.747 1.827 1.813 2.28 3.2.507-1.44 1.294-2.52 2.36-3.24 1.094-.747 2.414-1.12 3.96-1.12 1.414 0 2.64.307 3.68.92s1.84 1.52 2.4 2.72c.56 1.2.84 2.667.84 4.4V38h-4.96V25.92c0-1.813-.293-3.187-.88-4.12-.56-.96-1.413-1.44-2.56-1.44-.906 0-1.68.213-2.32.64-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.84-.48 3.04V38h-4.56V25.92c0-1.2-.133-2.213-.4-3.04-.24-.827-.626-1.453-1.16-1.88-.506-.427-1.133-.64-1.88-.64-.906 0-1.68.227-2.32.68-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.827-.48 3V38h-4.96V16.8h4.48Zm26.723 10.6c0-2.24.427-4.187 1.28-5.84.854-1.68 2.067-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.84 0 3.494.413 4.96 1.24 1.467.827 2.64 2.08 3.52 3.76.88 1.653 1.347 3.693 1.4 6.12v1.32h-15.08c.107 1.813.614 3.227 1.52 4.24.907.987 2.134 1.48 3.68 1.48.987 0 1.88-.253 2.68-.76a4.803 4.803 0 0 0 1.84-2.2l5.08.36c-.64 2.027-1.84 3.64-3.6 4.84-1.733 1.173-3.733 1.76-6 1.76-2.08 0-3.906-.453-5.48-1.36-1.573-.907-2.786-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84Zm15.16-2.04c-.213-1.733-.76-3.013-1.64-3.84-.853-.827-1.893-1.24-3.12-1.24-1.44 0-2.6.453-3.48 1.36-.88.88-1.44 2.12-1.68 3.72h9.92ZM163.139 9.6V38h-5.04V9.6h5.04Zm8.322 7.2.24 5.88-.64-.36c.32-2.053 1.094-3.56 2.32-4.52 1.254-.987 2.787-1.48 4.6-1.48 2.32 0 4.107.733 5.36 2.2 1.254 1.44 1.88 3.387 1.88 5.84V38h-4.96V25.92c0-1.253-.12-2.28-.36-3.08-.24-.8-.64-1.413-1.2-1.84-.533-.427-1.253-.64-2.16-.64-1.44 0-2.573.48-3.4 1.44-.8.933-1.2 2.307-1.2 4.12V38h-4.96V16.8h4.48Zm30.003 7.72c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.443 8.16V38h-5.6v-5.32h5.6Z"/><path fill="#171717" fill-rule="evenodd" d="m7.839 40.783 16.03-28.054L20 6 0 40.783h7.839Zm8.214 0H40L27.99 19.894l-4.02 7.032 3.976 6.914H20.02l-3.967 6.943Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
public/placeholder-user.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
public/placeholder.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

1
public/placeholder.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

125
styles/globals.css Normal file
View File

@ -0,0 +1,125 @@
@import 'tailwindcss';
@import 'tw-animate-css';
@custom-variant dark (&:is(.dark *));
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--radius: 0.625rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(0.269 0 0);
--sidebar-ring: oklch(0.439 0 0);
}
@theme inline {
--font-sans: 'Geist', 'Geist Fallback';
--font-mono: 'Geist Mono', 'Geist Mono Fallback';
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
}
}

27
tsconfig.json Normal file
View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"target": "ES6",
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}