+210
-21
README.md
+210
-21
README.md
···
1
-
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
1
+
# Ethereum Impact Index
2
+
3
+
[](https://nextjs.org/)
4
+
[](https://reactjs.org/)
5
+
[](https://www.typescriptlang.org/)
6
+
[](https://tailwindcss.com/)
7
+
8
+
A comprehensive dashboard for tracking and visualizing the real-world impact of Ethereum ecosystem projects. Built with Next.js 15, this platform provides transparent, data-driven insights into financial sustainability, geographic reach, and measurable outcomes across Ethereum projects.
9
+
10
+
## ๐ Features
11
+
12
+
### ๐ Dashboard Overview
13
+
- **Real-time Metrics**: Total funding, distributed funds, sustainability percentages, and global beneficiaries
14
+
- **Interactive Charts**: Trend analysis with historical data visualization using Recharts
15
+
- **Project Directory**: Comprehensive table of Ethereum projects with sortable and filterable data
16
+
- **Geographic Insights**: Global distribution maps showing project reach and impact
2
17
3
-
## Getting Started
18
+
### ๐ Project Details
19
+
- **Individual Project Pages**: Detailed breakdown of each project's impact metrics
20
+
- **Funding History**: Complete funding timeline with grant, investment, and revenue sources
21
+
- **Geographic Distribution**: Country-by-country breakdown of beneficiaries and value distributed
22
+
- **Impact Metrics**: Project-specific KPIs and measurable outcomes
4
23
5
-
First, run the development server:
24
+
### ๐ Data Visualization
25
+
- **World Map Integration**: Interactive Leaflet maps showing global project distribution
26
+
- **Trend Charts**: Historical funding and impact trend analysis
27
+
- **Responsive Design**: Optimized for desktop and mobile viewing
28
+
- **Dark/Light Theme**: Automatic theme switching with next-themes
29
+
30
+
### ๐ Educational Content
31
+
- **About Page**: Mission, values, and project goals
32
+
- **Methodology Page**: Transparent data collection and measurement framework
33
+
- **Project Submission**: Community-driven data contribution system
34
+
35
+
## ๐ Technology Stack
36
+
37
+
- **Framework**: [Next.js 15](https://nextjs.org/) with App Router
38
+
- **Frontend**: [React 19](https://reactjs.org/) with TypeScript
39
+
- **Styling**: [Tailwind CSS 4](https://tailwindcss.com/)
40
+
- **Charts**: [Recharts](https://recharts.org/) for data visualization
41
+
- **Maps**: [Leaflet](https://leafletjs.com/) with React integration
42
+
- **Icons**: [Lucide React](https://lucide.dev/)
43
+
- **Theme**: [next-themes](https://github.com/pacocoursey/next-themes) for dark/light mode
44
+
45
+
## ๐ Data Structure
46
+
47
+
The platform tracks comprehensive metrics for each Ethereum project:
48
+
49
+
### Core Metrics
50
+
- **Financial Data**: Funding received, sustainable revenue percentage, annual budget
51
+
- **Team Information**: Full-time equivalent team size and efficiency ratios
52
+
- **Distribution**: Value distributed externally through grants, UBI, or other mechanisms
53
+
54
+
### Geographic Impact
55
+
- **Country Distribution**: Beneficiaries and value distributed by country
56
+
- **Global Reach**: Worldwide project impact visualization
57
+
58
+
### Impact Categories
59
+
- Climate Positive
60
+
- Financial Inclusion
61
+
- Community
62
+
- Public Goods
63
+
- Developer Ecosystem
64
+
- Education
65
+
- Healthcare
66
+
- UBI (Universal Basic Income)
67
+
68
+
## ๐ Getting Started
69
+
70
+
### Prerequisites
71
+
- Node.js 18+ and npm/yarn/pnpm
72
+
- Git
73
+
74
+
### Installation
75
+
76
+
1. **Clone the repository**
77
+
```bash
78
+
git clone https://github.com/your-username/ethereum-impact-index.git
79
+
cd ethereum-impact-index
80
+
```
81
+
82
+
2. **Install dependencies**
83
+
```bash
84
+
npm install
85
+
# or
86
+
yarn install
87
+
# or
88
+
pnpm install
89
+
```
90
+
91
+
3. **Start the development server**
92
+
```bash
93
+
npm run dev
94
+
# or
95
+
yarn dev
96
+
# or
97
+
pnpm dev
98
+
```
99
+
100
+
4. **Open your browser**
101
+
Navigate to [http://localhost:3000](http://localhost:3000) to see the application.
102
+
103
+
### Build for Production
6
104
7
105
```bash
8
-
npm run dev
9
-
# or
10
-
yarn dev
11
-
# or
12
-
pnpm dev
13
-
# or
14
-
bun dev
106
+
npm run build
107
+
npm start
108
+
```
109
+
110
+
## ๐ Project Structure
111
+
112
+
```
113
+
ethereum-impact-index/
114
+
โโโ app/ # Next.js App Router pages
115
+
โ โโโ about/ # About page
116
+
โ โโโ methodology/ # Methodology documentation
117
+
โ โโโ project/ # Dynamic project detail pages
118
+
โ โโโ submit/ # Project submission form
119
+
โ โโโ globals.css # Global styles
120
+
โ โโโ layout.tsx # Root layout
121
+
โ โโโ page.tsx # Homepage
122
+
โโโ components/ # React components
123
+
โ โโโ FundingChart.tsx # Funding trend charts
124
+
โ โโโ MainContent.tsx # Main dashboard layout
125
+
โ โโโ ProjectTable.tsx # Projects data table
126
+
โ โโโ Sidebar.tsx # Navigation sidebar
127
+
โ โโโ WorldMap.tsx # Geographic visualization
128
+
โ โโโ ...
129
+
โโโ lib/ # Utility functions and data
130
+
โ โโโ data.ts # Sample project data
131
+
โ โโโ types.ts # TypeScript type definitions
132
+
โโโ public/ # Static assets
133
+
โ โโโ logo.png # Project logo
134
+
โ โโโ ...
135
+
โโโ package.json # Dependencies and scripts
15
136
```
16
137
17
-
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
138
+
## ๐ Available Scripts
139
+
140
+
- `npm run dev` - Start development server
141
+
- `npm run build` - Build for production
142
+
- `npm run start` - Start production server
143
+
- `npm run lint` - Run ESLint
144
+
145
+
## ๐ฏ Methodology
146
+
147
+
### Data Sources
148
+
- **Project Self-Reporting**: Standardized submission forms with public verification
149
+
- **Public Records**: Financial disclosures, grant databases, and annual reports
150
+
- **On-Chain Data**: Transaction verification for distribution tracking
151
+
- **Community Verification**: Cross-referencing with public funding databases
152
+
153
+
### Key Metrics
154
+
- **Funding Received**: Total lifetime funding from all sources
155
+
- **Sustainable Revenue %**: Recurring revenue vs. one-time funding ratio
156
+
- **Geographic Distribution**: Countries served with beneficiary counts
157
+
- **Distribution Efficiency**: External funding distributed vs. received ratio
158
+
159
+
### Verification Process
160
+
- โ
Cross-reference with public funding databases
161
+
- โ
On-chain verification where applicable
162
+
- โ
Community review and correction process
163
+
- โ
Regular data updates and refresh cycles
164
+
165
+
## ๐ค Contributing
166
+
167
+
We welcome contributions from the community! Here's how you can help:
168
+
169
+
### Ways to Contribute
170
+
1. **Submit Project Data**: Add your Ethereum project's impact metrics
171
+
2. **Improve Methodology**: Suggest enhancements to our metrics framework
172
+
3. **Bug Reports**: Report issues and help us improve the platform
173
+
4. **Feature Requests**: Propose new functionality and improvements
174
+
5. **Code Contributions**: Submit pull requests for bug fixes and features
175
+
176
+
### Development Guidelines
177
+
1. Fork the repository
178
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
179
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
180
+
4. Push to the branch (`git push origin feature/amazing-feature`)
181
+
5. Open a Pull Request
182
+
183
+
### Project Submission Process
184
+
- Visit the `/submit` page to add your project
185
+
- Provide comprehensive impact metrics and funding data
186
+
- Include geographic distribution and beneficiary information
187
+
- Submit verifiable sources for all data points
18
188
19
-
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
189
+
## ๐ License
20
190
21
-
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
191
+
This project is open source and available under the [MIT License](LICENSE).
192
+
193
+
## ๐โโ๏ธ Support
194
+
195
+
### Getting Help
196
+
- **Documentation**: Check our [Methodology](/methodology) page for detailed information
197
+
- **Issues**: Report bugs and request features on [GitHub Issues](https://github.com/your-username/ethereum-impact-index/issues)
198
+
- **Discussions**: Join community discussions on [GitHub Discussions](https://github.com/your-username/ethereum-impact-index/discussions)
22
199
23
-
## Learn More
200
+
### Contact
201
+
- **Email**: hello@ethereumimpactindex.com
202
+
- **Twitter**: [@EthImpactIndex](https://twitter.com/EthImpactIndex)
203
+
- **Discord**: [Ethereum Impact Index Community](https://discord.gg/ethereumimpactindex)
24
204
25
-
To learn more about Next.js, take a look at the following resources:
205
+
## ๐ฎ Future Roadmap
26
206
27
-
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
-
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
207
+
### Planned Features
208
+
- [ ] Real-time data updates via API integration
209
+
- [ ] Advanced filtering and search capabilities
210
+
- [ ] Export functionality for data analysis
211
+
- [ ] Integration with blockchain analytics platforms
212
+
- [ ] Mobile application companion
213
+
- [ ] Multi-language support
29
214
30
-
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
215
+
### Research Initiatives
216
+
- [ ] Longitudinal impact studies
217
+
- [ ] Comparative analysis frameworks
218
+
- [ ] Sustainability prediction models
219
+
- [ ] Geographic impact correlation analysis
31
220
32
-
## Deploy on Vercel
221
+
---
33
222
34
-
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
223
+
**Built with โค๏ธ for the Ethereum ecosystem**
35
224
36
-
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
225
+
*Helping stakeholders make informed decisions about where to direct resources for the greatest collective benefit.*
+125
app/about/page.tsx
+125
app/about/page.tsx
···
1
+
import { Heart, Users, Globe, Target } from 'lucide-react'
2
+
3
+
export default function AboutPage() {
4
+
return (
5
+
<div className="space-y-8">
6
+
{/* Page Header */}
7
+
<div className="mb-8">
8
+
<h1 className="text-5xl font-serif font-semibold text-primary mb-4 tracking-tight">
9
+
About
10
+
</h1>
11
+
<p className="text-lg text-secondary leading-relaxed max-w-2xl">
12
+
Understanding the Ethereum ecosystem's impact on global positive-sum outcomes
13
+
</p>
14
+
</div>
15
+
16
+
{/* Mission Card */}
17
+
<div className="bg-surface p-8 border-subtle">
18
+
<div className="max-w-4xl">
19
+
<h2 className="text-3xl font-serif font-medium text-primary mb-6 tracking-tight">
20
+
Our Mission
21
+
</h2>
22
+
<p className="text-lg text-secondary leading-relaxed mb-6">
23
+
The Ethereum Impact Index provides transparent, data-driven insights into how Ethereum
24
+
projects are creating positive-sum outcomes across the globe. We believe that
25
+
understanding the real-world impact of blockchain technology is essential for
26
+
accelerating adoption and maximizing societal benefits.
27
+
</p>
28
+
<p className="text-base text-secondary leading-relaxed">
29
+
By tracking financial sustainability, geographic reach, and measurable outcomes,
30
+
we help stakeholders make informed decisions about where to direct resources
31
+
and attention for the greatest collective benefit.
32
+
</p>
33
+
</div>
34
+
</div>
35
+
36
+
{/* Values Grid */}
37
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
38
+
<div className="bg-surface p-6 border-subtle">
39
+
<div className="flex items-start space-x-4">
40
+
<div className="w-12 h-12 bg-accent-light rounded-lg flex items-center justify-center flex-shrink-0">
41
+
<Heart className="h-6 w-6 text-accent" />
42
+
</div>
43
+
<div>
44
+
<h3 className="text-xl font-serif font-medium text-primary mb-3">Public Goods Focus</h3>
45
+
<p className="text-secondary leading-relaxed">
46
+
We prioritize projects that create positive externalities and shared value
47
+
for the broader Ethereum ecosystem and society at large.
48
+
</p>
49
+
</div>
50
+
</div>
51
+
</div>
52
+
53
+
<div className="bg-surface p-6 border-subtle">
54
+
<div className="flex items-start space-x-4">
55
+
<div className="w-12 h-12 bg-accent-light rounded-lg flex items-center justify-center flex-shrink-0">
56
+
<Users className="h-6 w-6 text-accent" />
57
+
</div>
58
+
<div>
59
+
<h3 className="text-xl font-serif font-medium text-primary mb-3">Community Driven</h3>
60
+
<p className="text-secondary leading-relaxed">
61
+
Our methodology and data collection processes are developed in collaboration
62
+
with the communities we aim to serve and support.
63
+
</p>
64
+
</div>
65
+
</div>
66
+
</div>
67
+
68
+
<div className="bg-surface p-6 border-subtle">
69
+
<div className="flex items-start space-x-4">
70
+
<div className="w-12 h-12 bg-accent-light rounded-lg flex items-center justify-center flex-shrink-0">
71
+
<Globe className="h-6 w-6 text-accent" />
72
+
</div>
73
+
<div>
74
+
<h3 className="text-xl font-serif font-medium text-primary mb-3">Global Perspective</h3>
75
+
<p className="text-secondary leading-relaxed">
76
+
We recognize that blockchain's impact transcends borders, and we strive to
77
+
capture outcomes across diverse geographic and cultural contexts.
78
+
</p>
79
+
</div>
80
+
</div>
81
+
</div>
82
+
83
+
<div className="bg-surface p-6 border-subtle">
84
+
<div className="flex items-start space-x-4">
85
+
<div className="w-12 h-12 bg-accent-light rounded-lg flex items-center justify-center flex-shrink-0">
86
+
<Target className="h-6 w-6 text-accent" />
87
+
</div>
88
+
<div>
89
+
<h3 className="text-xl font-serif font-medium text-primary mb-3">Actionable Insights</h3>
90
+
<p className="text-secondary leading-relaxed">
91
+
We focus on metrics that matter for decision-making, helping funders,
92
+
builders, and policymakers direct resources effectively.
93
+
</p>
94
+
</div>
95
+
</div>
96
+
</div>
97
+
</div>
98
+
99
+
{/* Contact/Contribute Card */}
100
+
<div className="bg-surface p-8 border-subtle">
101
+
<div className="max-w-2xl">
102
+
<h2 className="text-3xl font-serif font-medium text-primary mb-6 tracking-tight">
103
+
Get Involved
104
+
</h2>
105
+
<p className="text-lg text-secondary leading-relaxed mb-6">
106
+
The Ethereum Impact Index is an open-source initiative that welcomes contributions
107
+
from the community. Whether you're a project maintainer looking to share your impact
108
+
or a researcher interested in improving our methodology, we encourage participation.
109
+
</p>
110
+
<div className="space-y-3">
111
+
<p className="text-base text-secondary">
112
+
<strong className="text-primary">Submit a project:</strong> Help us expand our coverage by submitting your Ethereum project's impact data.
113
+
</p>
114
+
<p className="text-base text-secondary">
115
+
<strong className="text-primary">Contribute to methodology:</strong> Suggest improvements to our metrics and data collection processes.
116
+
</p>
117
+
<p className="text-base text-secondary">
118
+
<strong className="text-primary">Share feedback:</strong> Help us better understand what information would be most valuable to you.
119
+
</p>
120
+
</div>
121
+
</div>
122
+
</div>
123
+
</div>
124
+
)
125
+
}
app/favicon.ico
app/favicon.ico
This is a binary file and will not be displayed.
+80
-11
app/globals.css
+80
-11
app/globals.css
···
2
2
3
3
:root {
4
4
--background: #ffffff;
5
-
--foreground: #171717;
5
+
--surface: #ffffff;
6
+
--surface-hover: #fafafa;
7
+
--border: #e5e7eb;
8
+
--text-primary: #111827;
9
+
--text-secondary: #6b7280;
10
+
--text-muted: #9ca3af;
11
+
--accent: #374151;
12
+
--accent-light: #f9fafb;
13
+
}
14
+
15
+
.dark {
16
+
--background: #0a0a0a;
17
+
--surface: #171717;
18
+
--surface-hover: #262626;
19
+
--border: #404040;
20
+
--text-primary: #fafafa;
21
+
--text-secondary: #a3a3a3;
22
+
--text-muted: #737373;
23
+
--accent: #d4d4d8;
24
+
--accent-light: #262626;
6
25
}
7
26
8
27
@theme inline {
28
+
--font-sans: var(--font-inter);
29
+
--font-serif: var(--font-garamond);
9
30
--color-background: var(--background);
10
-
--color-foreground: var(--foreground);
11
-
--font-sans: var(--font-geist-sans);
12
-
--font-mono: var(--font-geist-mono);
31
+
--color-surface: var(--surface);
32
+
--color-surface-hover: var(--surface-hover);
33
+
--color-primary: var(--text-primary);
34
+
--color-secondary: var(--text-secondary);
35
+
--color-muted: var(--text-muted);
36
+
--color-accent: var(--accent);
37
+
--color-accent-light: var(--accent-light);
38
+
--color-border: var(--border);
13
39
}
14
40
15
-
@media (prefers-color-scheme: dark) {
16
-
:root {
17
-
--background: #0a0a0a;
18
-
--foreground: #ededed;
19
-
}
41
+
* {
42
+
box-sizing: border-box;
20
43
}
21
44
22
45
body {
46
+
font-family: var(--font-inter), -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
23
47
background: var(--background);
24
-
color: var(--foreground);
25
-
font-family: Arial, Helvetica, sans-serif;
48
+
color: var(--text-primary);
49
+
line-height: 1.6;
50
+
-webkit-font-smoothing: antialiased;
51
+
-moz-osx-font-smoothing: grayscale;
52
+
}
53
+
54
+
h1, h2, h3, h4, h5, h6 {
55
+
font-family: var(--font-garamond), 'Times New Roman', serif;
56
+
font-weight: 500;
57
+
line-height: 1.2;
58
+
letter-spacing: -0.01em;
59
+
color: var(--text-primary);
60
+
}
61
+
62
+
/* Minimal scrollbar */
63
+
::-webkit-scrollbar {
64
+
width: 4px;
65
+
}
66
+
67
+
::-webkit-scrollbar-track {
68
+
background: transparent;
69
+
}
70
+
71
+
::-webkit-scrollbar-thumb {
72
+
background: var(--border);
73
+
border-radius: 2px;
74
+
}
75
+
76
+
::-webkit-scrollbar-thumb:hover {
77
+
background: var(--accent);
78
+
}
79
+
80
+
/* Minimal shadows */
81
+
.shadow-card {
82
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
26
83
}
84
+
85
+
/* Smooth transitions */
86
+
.transition-elegant {
87
+
transition: all 0.2s ease;
88
+
}
89
+
90
+
/* Clean borders */
91
+
.border-subtle {
92
+
border: 1px solid var(--border);
93
+
}
94
+
95
+
+36
-12
app/layout.tsx
+36
-12
app/layout.tsx
···
1
1
import type { Metadata } from "next";
2
-
import { Geist, Geist_Mono } from "next/font/google";
2
+
import { Inter, EB_Garamond } from "next/font/google";
3
3
import "./globals.css";
4
+
import Sidebar from "@/components/Sidebar";
5
+
import { ThemeProvider } from "@/components/ThemeProvider";
6
+
import { SidebarProvider } from "@/components/SidebarContext";
7
+
import MainContent from "@/components/MainContent";
4
8
5
-
const geistSans = Geist({
6
-
variable: "--font-geist-sans",
9
+
const inter = Inter({
7
10
subsets: ["latin"],
11
+
variable: "--font-inter",
8
12
});
9
13
10
-
const geistMono = Geist_Mono({
11
-
variable: "--font-geist-mono",
14
+
const ebGaramond = EB_Garamond({
12
15
subsets: ["latin"],
16
+
variable: "--font-garamond",
17
+
weight: ["400", "500", "600", "700"],
13
18
});
14
19
15
20
export const metadata: Metadata = {
16
-
title: "Create Next App",
17
-
description: "Generated by create next app",
21
+
title: "Ethereum Impact Index",
22
+
description: "Making Ethereum projects' positive-sum contributions legible",
18
23
};
19
24
20
25
export default function RootLayout({
···
23
28
children: React.ReactNode;
24
29
}>) {
25
30
return (
26
-
<html lang="en">
27
-
<body
28
-
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
29
-
>
30
-
{children}
31
+
<html lang="en" suppressHydrationWarning>
32
+
<body className={`${inter.variable} ${ebGaramond.variable} font-sans antialiased bg-background text-primary min-h-screen transition-colors duration-300`}>
33
+
<ThemeProvider
34
+
attribute="class"
35
+
defaultTheme="system"
36
+
enableSystem
37
+
disableTransitionOnChange
38
+
>
39
+
<SidebarProvider>
40
+
<div className="min-h-screen flex flex-col">
41
+
<Sidebar />
42
+
<main className="pt-20 px-4 sm:px-6 max-w-7xl mx-auto flex-1 w-full">
43
+
{children}
44
+
</main>
45
+
<footer className="border-t border-border bg-surface/50 backdrop-blur-sm mt-8">
46
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 pt-6 pb-4">
47
+
<p className="text-center text-muted text-sm font-serif">
48
+
ยฉ 2025 Eval.Science
49
+
</p>
50
+
</div>
51
+
</footer>
52
+
</div>
53
+
</SidebarProvider>
54
+
</ThemeProvider>
31
55
</body>
32
56
</html>
33
57
);
+235
app/methodology/page.tsx
+235
app/methodology/page.tsx
···
1
+
import { BookOpen, Target, BarChart3, Globe } from 'lucide-react'
2
+
3
+
export default function MethodologyPage() {
4
+
return (
5
+
<div className="space-y-8">
6
+
{/* Header */}
7
+
<div className="bg-card-bg p-8">
8
+
<div className="text-center">
9
+
<h1 className="text-4xl font-serif font-semibold text-gray-800 dark:text-white mb-4">
10
+
Methodology
11
+
</h1>
12
+
<p className="text-xl text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
13
+
Our approach to measuring and presenting Ethereum project impact through transparent,
14
+
comparable metrics that capture both financial sustainability and real-world outcomes.
15
+
</p>
16
+
</div>
17
+
</div>
18
+
19
+
{/* Core Principles */}
20
+
<div className="bg-card-bg p-8">
21
+
<h2 className="text-2xl font-serif font-medium text-gray-800 dark:text-white mb-6">Core Principles</h2>
22
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
23
+
<div className="flex items-start space-x-4">
24
+
<div className="flex-shrink-0">
25
+
<Target className="h-8 w-8 text-blue-600" />
26
+
</div>
27
+
<div>
28
+
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-2">Transparency</h3>
29
+
<p className="text-gray-700 dark:text-gray-300">
30
+
All metrics are self-reported by projects with public verification where possible.
31
+
We encourage open data sharing and provide clear attribution for all information sources.
32
+
</p>
33
+
</div>
34
+
</div>
35
+
36
+
<div className="flex items-start space-x-4">
37
+
<div className="flex-shrink-0">
38
+
<BarChart3 className="h-8 w-8 text-green-600" />
39
+
</div>
40
+
<div>
41
+
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-2">Comparability</h3>
42
+
<p className="text-gray-700 dark:text-gray-300">
43
+
Standardized metrics enable fair comparison across different types of projects,
44
+
from infrastructure to applications to public goods funding mechanisms.
45
+
</p>
46
+
</div>
47
+
</div>
48
+
49
+
<div className="flex items-start space-x-4">
50
+
<div className="flex-shrink-0">
51
+
<Globe className="h-8 w-8 text-orange-600" />
52
+
</div>
53
+
<div>
54
+
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-2">Global Impact</h3>
55
+
<p className="text-gray-700 dark:text-gray-300">
56
+
We measure real-world outcomes across geographic regions, focusing on
57
+
beneficiaries served and value distributed to communities worldwide.
58
+
</p>
59
+
</div>
60
+
</div>
61
+
62
+
<div className="flex items-start space-x-4">
63
+
<div className="flex-shrink-0">
64
+
<BookOpen className="h-8 w-8 text-purple-600" />
65
+
</div>
66
+
<div>
67
+
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-2">Sustainability</h3>
68
+
<p className="text-gray-700 dark:text-gray-300">
69
+
We track funding models to assess long-term viability, distinguishing
70
+
between grant-dependent and revenue-generating sustainability approaches.
71
+
</p>
72
+
</div>
73
+
</div>
74
+
</div>
75
+
</div>
76
+
77
+
{/* Metrics Framework */}
78
+
<div className="bg-card-bg p-8">
79
+
<h2 className="text-2xl font-semibold text-gray-800 dark:text-white mb-6">Metrics Framework</h2>
80
+
81
+
<div className="space-y-8">
82
+
{/* Financial Metrics */}
83
+
<div>
84
+
<h3 className="text-xl font-semibold text-gray-800 dark:text-white mb-4">Financial Metrics</h3>
85
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
86
+
<div className="border border-gray-200 rounded-lg p-4">
87
+
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">Funding Received</h4>
88
+
<p className="text-sm text-gray-700 dark:text-gray-300 mb-2">
89
+
Total lifetime funding from all sources including grants, investments, and donations.
90
+
</p>
91
+
<div className="text-xs text-gray-500">Source: Project self-reporting + public records</div>
92
+
</div>
93
+
94
+
<div className="border border-gray-200 rounded-lg p-4">
95
+
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">Sustainable Revenue %</h4>
96
+
<p className="text-sm text-gray-700 dark:text-gray-300 mb-2">
97
+
Percentage of funding from recurring revenue models vs. one-time grants or investments.
98
+
</p>
99
+
<div className="text-xs text-gray-500">Calculation: (Revenue / Total Funding) ร 100</div>
100
+
</div>
101
+
102
+
<div className="border border-gray-200 rounded-lg p-4">
103
+
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">Annual Budget</h4>
104
+
<p className="text-sm text-gray-700 dark:text-gray-300 mb-2">
105
+
Current annual operating budget including team salaries, infrastructure, and operations.
106
+
</p>
107
+
<div className="text-xs text-gray-500">Source: Project financial reporting</div>
108
+
</div>
109
+
110
+
<div className="border border-gray-200 rounded-lg p-4">
111
+
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">Funding Distributed</h4>
112
+
<p className="text-sm text-gray-700 dark:text-gray-300 mb-2">
113
+
Total value distributed externally through grants, UBI, loans, or other funding mechanisms.
114
+
</p>
115
+
<div className="text-xs text-gray-500">Source: Public distribution records</div>
116
+
</div>
117
+
</div>
118
+
</div>
119
+
120
+
{/* Impact Metrics */}
121
+
<div>
122
+
<h3 className="text-xl font-semibold text-gray-800 dark:text-white mb-4">Impact Metrics</h3>
123
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
124
+
<div className="border border-gray-200 rounded-lg p-4">
125
+
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">Geographic Distribution</h4>
126
+
<p className="text-sm text-gray-700 dark:text-gray-300 mb-2">
127
+
Countries served with user/beneficiary counts and value distributed per region.
128
+
</p>
129
+
<div className="text-xs text-gray-500">Source: Project analytics + user surveys</div>
130
+
</div>
131
+
132
+
<div className="border border-gray-200 rounded-lg p-4">
133
+
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">Team Efficiency</h4>
134
+
<p className="text-sm text-gray-700 dark:text-gray-300 mb-2">
135
+
Full-time equivalent team members and funding-per-team-member ratios.
136
+
</p>
137
+
<div className="text-xs text-gray-500">Calculation: Total Funding / Team Size</div>
138
+
</div>
139
+
140
+
<div className="border border-gray-200 rounded-lg p-4">
141
+
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">Quantifiable Outcomes</h4>
142
+
<p className="text-sm text-gray-700 dark:text-gray-300 mb-2">
143
+
Project-specific metrics like users served, emissions reduced, or democratic participation.
144
+
</p>
145
+
<div className="text-xs text-gray-500">Source: Project-defined KPIs</div>
146
+
</div>
147
+
148
+
<div className="border border-gray-200 rounded-lg p-4">
149
+
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">Distribution Efficiency</h4>
150
+
<p className="text-sm text-gray-700 dark:text-gray-300 mb-2">
151
+
Ratio of funding distributed externally vs. total funding received.
152
+
</p>
153
+
<div className="text-xs text-gray-500">Calculation: (Distributed / Received) ร 100</div>
154
+
</div>
155
+
</div>
156
+
</div>
157
+
</div>
158
+
</div>
159
+
160
+
{/* Data Sources */}
161
+
<div className="bg-card-bg p-8">
162
+
<h2 className="text-2xl font-semibold text-gray-800 dark:text-white mb-6">Data Sources & Verification</h2>
163
+
164
+
<div className="space-y-6">
165
+
<div>
166
+
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-3">Primary Sources</h3>
167
+
<ul className="space-y-2 text-gray-700 dark:text-gray-300">
168
+
<li className="flex items-start">
169
+
<span className="text-blue-600 mr-2">โข</span>
170
+
<span>Project self-reporting through standardized submission forms</span>
171
+
</li>
172
+
<li className="flex items-start">
173
+
<span className="text-blue-600 mr-2">โข</span>
174
+
<span>Public financial disclosures and annual reports</span>
175
+
</li>
176
+
<li className="flex items-start">
177
+
<span className="text-blue-600 mr-2">โข</span>
178
+
<span>On-chain transaction data for distribution tracking</span>
179
+
</li>
180
+
<li className="flex items-start">
181
+
<span className="text-blue-600 mr-2">โข</span>
182
+
<span>Grant registry databases (Gitcoin, Ethereum Foundation, etc.)</span>
183
+
</li>
184
+
</ul>
185
+
</div>
186
+
187
+
<div>
188
+
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-3">Verification Process</h3>
189
+
<ul className="space-y-2 text-gray-700 dark:text-gray-300">
190
+
<li className="flex items-start">
191
+
<span className="text-green-600 mr-2">โ</span>
192
+
<span>Cross-reference with public funding databases</span>
193
+
</li>
194
+
<li className="flex items-start">
195
+
<span className="text-green-600 mr-2">โ</span>
196
+
<span>On-chain verification where applicable</span>
197
+
</li>
198
+
<li className="flex items-start">
199
+
<span className="text-green-600 mr-2">โ</span>
200
+
<span>Community review and correction process</span>
201
+
</li>
202
+
<li className="flex items-start">
203
+
<span className="text-green-600 mr-2">โ</span>
204
+
<span>Regular updates and data refresh cycles</span>
205
+
</li>
206
+
</ul>
207
+
</div>
208
+
</div>
209
+
</div>
210
+
211
+
{/* Limitations */}
212
+
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-6">
213
+
<h2 className="text-xl font-semibold text-gray-800 dark:text-white mb-4">Limitations & Considerations</h2>
214
+
<div className="space-y-3 text-gray-700">
215
+
<p>
216
+
<strong>Self-Reported Data:</strong> Most metrics rely on project self-reporting,
217
+
which may introduce bias or inconsistencies despite verification efforts.
218
+
</p>
219
+
<p>
220
+
<strong>Metric Standardization:</strong> Different project types require different
221
+
impact measurements, making perfect comparability challenging.
222
+
</p>
223
+
<p>
224
+
<strong>Attribution Complexity:</strong> Real-world impact often involves multiple
225
+
factors beyond a single project's contribution.
226
+
</p>
227
+
<p>
228
+
<strong>Temporal Considerations:</strong> Impact metrics may lag behind funding,
229
+
and long-term sustainability requires ongoing assessment.
230
+
</p>
231
+
</div>
232
+
</div>
233
+
</div>
234
+
)
235
+
}
+223
-95
app/page.tsx
+223
-95
app/page.tsx
···
1
-
import Image from "next/image";
1
+
'use client'
2
+
3
+
import ProjectTable from "@/components/ProjectTable";
4
+
import { projects, formatCurrency } from "@/lib/data";
5
+
import { TrendingUp, Globe, Users, DollarSign } from "lucide-react";
6
+
import { LineChart, Line, ResponsiveContainer, Tooltip } from 'recharts';
2
7
3
8
export default function Home() {
9
+
const totalFunding = projects.reduce((sum, project) => sum + project.fundingReceived, 0);
10
+
const totalDistributed = projects.reduce((sum, project) => sum + project.fundingGivenOut, 0);
11
+
const avgSustainability = projects.reduce((sum, project) => sum + project.sustainableRevenuePercent, 0) / projects.length;
12
+
const totalBeneficiaries = projects.reduce((sum, project) =>
13
+
sum + project.geographicDistribution.reduce((countSum, country) => countSum + country.beneficiaries, 0), 0
14
+
);
15
+
16
+
// Mock yearly data for trends
17
+
const fundingTrend = [
18
+
{ year: '2021', value: totalFunding * 0.3 },
19
+
{ year: '2022', value: totalFunding * 0.6 },
20
+
{ year: '2023', value: totalFunding * 0.8 },
21
+
{ year: '2024', value: totalFunding }
22
+
];
23
+
24
+
const distributedTrend = [
25
+
{ year: '2021', value: totalDistributed * 0.25 },
26
+
{ year: '2022', value: totalDistributed * 0.55 },
27
+
{ year: '2023', value: totalDistributed * 0.75 },
28
+
{ year: '2024', value: totalDistributed }
29
+
];
30
+
31
+
const sustainabilityTrend = [
32
+
{ year: '2021', value: avgSustainability * 0.7 },
33
+
{ year: '2022', value: avgSustainability * 0.8 },
34
+
{ year: '2023', value: avgSustainability * 0.9 },
35
+
{ year: '2024', value: avgSustainability }
36
+
];
37
+
38
+
const beneficiariesTrend = [
39
+
{ year: '2021', value: totalBeneficiaries * 0.4 },
40
+
{ year: '2022', value: totalBeneficiaries * 0.65 },
41
+
{ year: '2023', value: totalBeneficiaries * 0.8 },
42
+
{ year: '2024', value: totalBeneficiaries }
43
+
];
44
+
4
45
return (
5
-
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
6
-
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
7
-
<Image
8
-
className="dark:invert"
9
-
src="/next.svg"
10
-
alt="Next.js logo"
11
-
width={180}
12
-
height={38}
13
-
priority
14
-
/>
15
-
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
16
-
<li className="mb-2 tracking-[-.01em]">
17
-
Get started by editing{" "}
18
-
<code className="bg-black/[.05] dark:bg-white/[.06] font-mono font-semibold px-1 py-0.5 rounded">
19
-
app/page.tsx
20
-
</code>
21
-
.
22
-
</li>
23
-
<li className="tracking-[-.01em]">
24
-
Save and see your changes instantly.
25
-
</li>
26
-
</ol>
46
+
<div className="space-y-6">
47
+
{/* Page Header */}
48
+
<div className="mb-6 sm:mb-8">
49
+
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-serif font-semibold text-primary mb-3 sm:mb-4 tracking-tight">
50
+
Overview
51
+
</h1>
52
+
<p className="text-base sm:text-lg text-secondary leading-relaxed max-w-2xl">
53
+
Real-time insights into Ethereum ecosystem impact and sustainability
54
+
</p>
55
+
</div>
27
56
28
-
<div className="flex gap-4 items-center flex-col sm:flex-row">
29
-
<a
30
-
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
31
-
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
32
-
target="_blank"
33
-
rel="noopener noreferrer"
34
-
>
35
-
<Image
36
-
className="dark:invert"
37
-
src="/vercel.svg"
38
-
alt="Vercel logomark"
39
-
width={20}
40
-
height={20}
41
-
/>
42
-
Deploy now
43
-
</a>
44
-
<a
45
-
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
46
-
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
47
-
target="_blank"
48
-
rel="noopener noreferrer"
49
-
>
50
-
Read our docs
51
-
</a>
57
+
{/* Key Metrics Grid */}
58
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-6 mb-6 sm:mb-8">
59
+
<div className="bg-surface p-4 sm:p-6 border-subtle transition-elegant hover:bg-surface-hover">
60
+
<div className="flex items-center justify-between mb-4">
61
+
<div>
62
+
<p className="text-sm font-medium text-muted mb-2">Total Funding</p>
63
+
<p className="text-2xl font-semibold text-primary">{formatCurrency(totalFunding)}</p>
64
+
<p className="text-xs text-muted mt-1">Active ecosystem</p>
65
+
</div>
66
+
<div className="w-12 h-12 bg-accent-light rounded-lg flex items-center justify-center">
67
+
<DollarSign className="h-6 w-6 text-accent" />
68
+
</div>
69
+
</div>
70
+
<div className="h-12">
71
+
<ResponsiveContainer width="100%" height="100%">
72
+
<LineChart data={fundingTrend}>
73
+
<Tooltip
74
+
formatter={(value: number) => [formatCurrency(value), 'Funding']}
75
+
labelFormatter={(label, payload) => payload?.[0]?.payload?.year || label}
76
+
contentStyle={{
77
+
backgroundColor: 'var(--surface)',
78
+
border: '1px solid var(--border)',
79
+
borderRadius: '6px',
80
+
color: 'var(--text-primary)',
81
+
fontSize: '12px'
82
+
}}
83
+
labelStyle={{ color: 'var(--text-secondary)' }}
84
+
/>
85
+
<Line
86
+
type="monotone"
87
+
dataKey="value"
88
+
stroke="var(--accent)"
89
+
strokeWidth={2}
90
+
dot={false}
91
+
/>
92
+
</LineChart>
93
+
</ResponsiveContainer>
94
+
</div>
52
95
</div>
53
-
</main>
54
-
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
55
-
<a
56
-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
57
-
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
58
-
target="_blank"
59
-
rel="noopener noreferrer"
60
-
>
61
-
<Image
62
-
aria-hidden
63
-
src="/file.svg"
64
-
alt="File icon"
65
-
width={16}
66
-
height={16}
67
-
/>
68
-
Learn
69
-
</a>
70
-
<a
71
-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
72
-
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
73
-
target="_blank"
74
-
rel="noopener noreferrer"
75
-
>
76
-
<Image
77
-
aria-hidden
78
-
src="/window.svg"
79
-
alt="Window icon"
80
-
width={16}
81
-
height={16}
82
-
/>
83
-
Examples
84
-
</a>
85
-
<a
86
-
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
87
-
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
88
-
target="_blank"
89
-
rel="noopener noreferrer"
90
-
>
91
-
<Image
92
-
aria-hidden
93
-
src="/globe.svg"
94
-
alt="Globe icon"
95
-
width={16}
96
-
height={16}
97
-
/>
98
-
Go to nextjs.org โ
99
-
</a>
100
-
</footer>
96
+
97
+
<div className="bg-surface p-4 sm:p-6 border-subtle transition-elegant hover:bg-surface-hover">
98
+
<div className="flex items-center justify-between mb-4">
99
+
<div>
100
+
<p className="text-sm font-medium text-muted mb-2">Distributed</p>
101
+
<p className="text-2xl font-semibold text-primary">{formatCurrency(totalDistributed)}</p>
102
+
<p className="text-xs text-muted mt-1">Growing impact</p>
103
+
</div>
104
+
<div className="w-12 h-12 bg-accent-light rounded-lg flex items-center justify-center">
105
+
<TrendingUp className="h-6 w-6 text-accent" />
106
+
</div>
107
+
</div>
108
+
<div className="h-12">
109
+
<ResponsiveContainer width="100%" height="100%">
110
+
<LineChart data={distributedTrend}>
111
+
<Tooltip
112
+
formatter={(value: number) => [formatCurrency(value), 'Distributed']}
113
+
labelFormatter={(label, payload) => payload?.[0]?.payload?.year || label}
114
+
contentStyle={{
115
+
backgroundColor: 'var(--surface)',
116
+
border: '1px solid var(--border)',
117
+
borderRadius: '6px',
118
+
color: 'var(--text-primary)',
119
+
fontSize: '12px'
120
+
}}
121
+
labelStyle={{ color: 'var(--text-secondary)' }}
122
+
/>
123
+
<Line
124
+
type="monotone"
125
+
dataKey="value"
126
+
stroke="var(--accent)"
127
+
strokeWidth={2}
128
+
dot={false}
129
+
/>
130
+
</LineChart>
131
+
</ResponsiveContainer>
132
+
</div>
133
+
</div>
134
+
135
+
<div className="bg-surface p-4 sm:p-6 border-subtle transition-elegant hover:bg-surface-hover">
136
+
<div className="flex items-center justify-between mb-4">
137
+
<div>
138
+
<p className="text-sm font-medium text-muted mb-2">Sustainability</p>
139
+
<p className="text-2xl font-semibold text-primary">{avgSustainability.toFixed(0)}%</p>
140
+
<p className="text-xs text-muted mt-1">Improving</p>
141
+
</div>
142
+
<div className="w-12 h-12 bg-accent-light rounded-lg flex items-center justify-center">
143
+
<Globe className="h-6 w-6 text-accent" />
144
+
</div>
145
+
</div>
146
+
<div className="h-12">
147
+
<ResponsiveContainer width="100%" height="100%">
148
+
<LineChart data={sustainabilityTrend}>
149
+
<Tooltip
150
+
formatter={(value: number) => [`${value.toFixed(1)}%`, 'Sustainability']}
151
+
labelFormatter={(label, payload) => payload?.[0]?.payload?.year || label}
152
+
contentStyle={{
153
+
backgroundColor: 'var(--surface)',
154
+
border: '1px solid var(--border)',
155
+
borderRadius: '6px',
156
+
color: 'var(--text-primary)',
157
+
fontSize: '12px'
158
+
}}
159
+
labelStyle={{ color: 'var(--text-secondary)' }}
160
+
/>
161
+
<Line
162
+
type="monotone"
163
+
dataKey="value"
164
+
stroke="var(--accent)"
165
+
strokeWidth={2}
166
+
dot={false}
167
+
/>
168
+
</LineChart>
169
+
</ResponsiveContainer>
170
+
</div>
171
+
</div>
172
+
173
+
<div className="bg-surface p-4 sm:p-6 border-subtle transition-elegant hover:bg-surface-hover">
174
+
<div className="flex items-center justify-between mb-4">
175
+
<div>
176
+
<p className="text-sm font-medium text-muted mb-2">Beneficiaries</p>
177
+
<p className="text-2xl font-semibold text-primary">{totalBeneficiaries.toLocaleString()}</p>
178
+
<p className="text-xs text-muted mt-1">Global reach</p>
179
+
</div>
180
+
<div className="w-12 h-12 bg-accent-light rounded-lg flex items-center justify-center">
181
+
<Users className="h-6 w-6 text-accent" />
182
+
</div>
183
+
</div>
184
+
<div className="h-12">
185
+
<ResponsiveContainer width="100%" height="100%">
186
+
<LineChart data={beneficiariesTrend}>
187
+
<Tooltip
188
+
formatter={(value: number) => [value.toLocaleString(), 'Beneficiaries']}
189
+
labelFormatter={(label, payload) => payload?.[0]?.payload?.year || label}
190
+
contentStyle={{
191
+
backgroundColor: 'var(--surface)',
192
+
border: '1px solid var(--border)',
193
+
borderRadius: '6px',
194
+
color: 'var(--text-primary)',
195
+
fontSize: '12px'
196
+
}}
197
+
labelStyle={{ color: 'var(--text-secondary)' }}
198
+
/>
199
+
<Line
200
+
type="monotone"
201
+
dataKey="value"
202
+
stroke="var(--accent)"
203
+
strokeWidth={2}
204
+
dot={false}
205
+
/>
206
+
</LineChart>
207
+
</ResponsiveContainer>
208
+
</div>
209
+
</div>
210
+
</div>
211
+
212
+
{/* Main Table Card */}
213
+
<div className="bg-surface border-subtle shadow-card overflow-hidden">
214
+
<div className="px-4 sm:px-6 py-4 sm:py-6">
215
+
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
216
+
<div>
217
+
<h2 className="text-2xl sm:text-3xl font-serif font-medium text-primary tracking-tight">Projects</h2>
218
+
<p className="text-sm sm:text-base text-secondary mt-2 leading-relaxed">
219
+
{projects.length} projects driving positive-sum outcomes across the Ethereum ecosystem
220
+
</p>
221
+
</div>
222
+
<div className="text-xs sm:text-sm text-muted self-start sm:self-center">
223
+
Last updated: {new Date().toLocaleDateString()}
224
+
</div>
225
+
</div>
226
+
</div>
227
+
<ProjectTable projects={projects} />
228
+
</div>
101
229
</div>
102
230
);
103
231
}
+206
app/project/[id]/page.tsx
+206
app/project/[id]/page.tsx
···
1
+
import { notFound } from 'next/navigation'
2
+
import Link from 'next/link'
3
+
import { ArrowLeft, ExternalLink, Users, DollarSign, TrendingUp, MapPin } from 'lucide-react'
4
+
import { projects, formatCurrency, getSustainabilityColor, getSustainabilityBgColor } from '@/lib/data'
5
+
import FundingChart from '@/components/FundingChart'
6
+
7
+
interface ProjectPageProps {
8
+
params: Promise<{ id: string }>
9
+
}
10
+
11
+
export default async function ProjectPage({ params }: ProjectPageProps) {
12
+
const { id } = await params
13
+
const project = projects.find(p => p.id === id)
14
+
15
+
if (!project) {
16
+
notFound()
17
+
}
18
+
19
+
return (
20
+
<div className="space-y-6">
21
+
{/* Back Navigation */}
22
+
<Link
23
+
href="/"
24
+
className="inline-flex items-center text-sm text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 transition-colors mb-6"
25
+
>
26
+
<ArrowLeft className="h-4 w-4 mr-2" />
27
+
Back to Dashboard
28
+
</Link>
29
+
30
+
{/* Project Header */}
31
+
<div className="bg-surface border-subtle shadow-card p-4 sm:p-6">
32
+
<div className="flex flex-col sm:flex-row sm:items-start justify-between mb-4 sm:mb-6 gap-4">
33
+
<div className="flex flex-col sm:flex-row items-start sm:items-center space-y-3 sm:space-y-0 sm:space-x-4">
34
+
<div className="h-12 w-12 bg-surface-hover flex items-center justify-center flex-shrink-0">
35
+
<span className="text-lg font-medium text-secondary">
36
+
{project.name.substring(0, 2).toUpperCase()}
37
+
</span>
38
+
</div>
39
+
<div className="min-w-0 flex-1">
40
+
<h1 className="text-2xl sm:text-3xl font-serif font-semibold text-primary">{project.name}</h1>
41
+
<p className="text-sm sm:text-lg text-secondary mt-1 leading-relaxed">{project.description}</p>
42
+
<a
43
+
href={project.website}
44
+
target="_blank"
45
+
rel="noopener noreferrer"
46
+
className="inline-flex items-center text-muted hover:text-primary mt-2 transition-colors text-sm"
47
+
>
48
+
Visit Website
49
+
<ExternalLink className="h-4 w-4 ml-1" />
50
+
</a>
51
+
</div>
52
+
</div>
53
+
</div>
54
+
55
+
{/* Categories */}
56
+
<div className="flex flex-wrap gap-1 mb-4 sm:mb-6">
57
+
{project.categories.map((category) => (
58
+
<span
59
+
key={category}
60
+
className="inline-flex items-center px-2 py-1 text-xs font-medium bg-accent-light text-secondary"
61
+
>
62
+
{category}
63
+
</span>
64
+
))}
65
+
</div>
66
+
67
+
{/* Key Metrics Grid */}
68
+
<div className="grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4">
69
+
<div className="bg-surface-hover p-3 sm:p-4">
70
+
<div className="flex items-center">
71
+
<DollarSign className="h-5 w-5 sm:h-6 sm:w-6 text-muted" />
72
+
<div className="ml-2 sm:ml-3 min-w-0 flex-1">
73
+
<div className="text-lg sm:text-xl font-semibold text-primary">
74
+
{formatCurrency(project.fundingReceived)}
75
+
</div>
76
+
<div className="text-xs sm:text-sm text-muted">Funding Received</div>
77
+
</div>
78
+
</div>
79
+
</div>
80
+
81
+
<div className="bg-surface-hover p-3 sm:p-4">
82
+
<div className="flex items-center">
83
+
<TrendingUp className="h-5 w-5 sm:h-6 sm:w-6 text-muted" />
84
+
<div className="ml-2 sm:ml-3 min-w-0 flex-1">
85
+
<div className="text-lg sm:text-xl font-semibold text-primary">
86
+
{project.sustainableRevenuePercent}%
87
+
</div>
88
+
<div className="text-xs sm:text-sm text-muted">
89
+
Sustainable Revenue
90
+
</div>
91
+
</div>
92
+
</div>
93
+
</div>
94
+
95
+
<div className="bg-surface-hover p-3 sm:p-4">
96
+
<div className="flex items-center">
97
+
<DollarSign className="h-5 w-5 sm:h-6 sm:w-6 text-muted" />
98
+
<div className="ml-2 sm:ml-3 min-w-0 flex-1">
99
+
<div className="text-lg sm:text-xl font-semibold text-primary">
100
+
{formatCurrency(project.fundingGivenOut)}
101
+
</div>
102
+
<div className="text-xs sm:text-sm text-muted">Funding Distributed</div>
103
+
</div>
104
+
</div>
105
+
</div>
106
+
107
+
<div className="bg-surface-hover p-3 sm:p-4">
108
+
<div className="flex items-center">
109
+
<Users className="h-5 w-5 sm:h-6 sm:w-6 text-muted" />
110
+
<div className="ml-2 sm:ml-3 min-w-0 flex-1">
111
+
<div className="text-lg sm:text-xl font-semibold text-primary">
112
+
{project.teamSize}
113
+
</div>
114
+
<div className="text-xs sm:text-sm text-muted">Team Members</div>
115
+
</div>
116
+
</div>
117
+
</div>
118
+
</div>
119
+
</div>
120
+
121
+
{/* Charts */}
122
+
<FundingChart
123
+
fundingHistory={project.fundingHistory}
124
+
sustainableRevenuePercent={project.sustainableRevenuePercent}
125
+
/>
126
+
127
+
{/* Impact Metrics & Geographic Distribution */}
128
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6">
129
+
{/* Impact Metrics */}
130
+
<div className="bg-surface border-subtle shadow-card p-4 sm:p-6">
131
+
<h2 className="text-lg sm:text-xl font-serif font-medium text-primary mb-4 sm:mb-6">Impact Metrics</h2>
132
+
<div className="space-y-3 sm:space-y-4">
133
+
{project.impactMetrics.map((metric, index) => (
134
+
<div key={index} className="border-l-2 border-border pl-3 sm:pl-4">
135
+
<div className="text-xl sm:text-2xl font-semibold text-primary mb-1">
136
+
{metric.value}
137
+
</div>
138
+
<div className="text-sm sm:text-base font-medium text-secondary mb-1">
139
+
{metric.label}
140
+
</div>
141
+
<div className="text-xs sm:text-sm text-muted">
142
+
{metric.description}
143
+
</div>
144
+
</div>
145
+
))}
146
+
</div>
147
+
</div>
148
+
149
+
{/* Geographic Distribution */}
150
+
<div className="bg-surface border-subtle shadow-card p-4 sm:p-6">
151
+
<h2 className="text-lg sm:text-xl font-serif font-medium text-primary mb-4 sm:mb-6">Geographic Distribution</h2>
152
+
<div className="space-y-3">
153
+
{project.geographicDistribution
154
+
.sort((a, b) => b.valueDistributed - a.valueDistributed)
155
+
.map((country) => (
156
+
<div key={country.countryCode} className="flex items-center justify-between p-3 bg-surface-hover">
157
+
<div className="flex items-center space-x-2 sm:space-x-3 min-w-0 flex-1">
158
+
<MapPin className="h-4 w-4 text-muted flex-shrink-0" />
159
+
<div className="min-w-0 flex-1">
160
+
<div className="font-medium text-primary truncate">{country.country}</div>
161
+
<div className="text-xs text-muted">
162
+
{country.beneficiaries.toLocaleString()} beneficiaries
163
+
</div>
164
+
</div>
165
+
</div>
166
+
<div className="text-right flex-shrink-0 ml-2">
167
+
<div className="font-semibold text-primary text-sm sm:text-base">
168
+
{formatCurrency(country.valueDistributed)}
169
+
</div>
170
+
<div className="text-xs text-muted">distributed</div>
171
+
</div>
172
+
</div>
173
+
))}
174
+
</div>
175
+
</div>
176
+
</div>
177
+
178
+
{/* Additional Details */}
179
+
<div className="bg-surface border-subtle shadow-card p-4 sm:p-6">
180
+
<h2 className="text-lg sm:text-xl font-serif font-medium text-primary mb-4 sm:mb-6">Financial Overview</h2>
181
+
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 sm:gap-4">
182
+
<div className="text-center p-3 sm:p-4 bg-surface-hover">
183
+
<div className="text-lg sm:text-xl font-semibold text-primary mb-1">
184
+
{formatCurrency(project.annualBudget)}
185
+
</div>
186
+
<div className="text-xs sm:text-sm text-muted">Annual Budget</div>
187
+
</div>
188
+
189
+
<div className="text-center p-3 sm:p-4 bg-surface-hover">
190
+
<div className="text-lg sm:text-xl font-semibold text-primary mb-1">
191
+
{formatCurrency(project.fundingReceived / project.teamSize)}
192
+
</div>
193
+
<div className="text-xs sm:text-sm text-muted">Funding per Team Member</div>
194
+
</div>
195
+
196
+
<div className="text-center p-3 sm:p-4 bg-surface-hover">
197
+
<div className="text-lg sm:text-xl font-semibold text-primary mb-1">
198
+
{((project.fundingGivenOut / project.fundingReceived) * 100).toFixed(0)}%
199
+
</div>
200
+
<div className="text-xs sm:text-sm text-muted">Distribution Efficiency</div>
201
+
</div>
202
+
</div>
203
+
</div>
204
+
</div>
205
+
)
206
+
}
+349
app/submit/page.tsx
+349
app/submit/page.tsx
···
1
+
import { Send, AlertCircle, CheckCircle } from 'lucide-react'
2
+
3
+
export default function SubmitPage() {
4
+
return (
5
+
<div className="space-y-8">
6
+
{/* Header */}
7
+
<div className="bg-card-bg p-8">
8
+
<div className="text-center">
9
+
<h1 className="text-4xl font-bold text-gray-800 dark:text-white mb-4">
10
+
Submit Your Project
11
+
</h1>
12
+
<p className="text-xl text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
13
+
Add your Ethereum ecosystem project to the Impact Index. Help make positive-sum
14
+
contributions visible and comparable across the ecosystem.
15
+
</p>
16
+
</div>
17
+
</div>
18
+
19
+
{/* Submission Form */}
20
+
<div className="bg-card-bg p-8">
21
+
<div className="max-w-2xl mx-auto">
22
+
<h2 className="text-2xl font-semibold text-gray-800 dark:text-white mb-6">Project Information</h2>
23
+
24
+
<form className="space-y-6">
25
+
{/* Basic Information */}
26
+
<div className="space-y-4">
27
+
<h3 className="text-lg font-medium text-gray-800 dark:text-white">Basic Information</h3>
28
+
29
+
<div>
30
+
<label htmlFor="name" className="block text-sm font-medium text-gray-700 mb-1">
31
+
Project Name *
32
+
</label>
33
+
<input
34
+
type="text"
35
+
id="name"
36
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
37
+
placeholder="Enter your project name"
38
+
/>
39
+
</div>
40
+
41
+
<div>
42
+
<label htmlFor="website" className="block text-sm font-medium text-gray-700 mb-1">
43
+
Website URL *
44
+
</label>
45
+
<input
46
+
type="url"
47
+
id="website"
48
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
49
+
placeholder="https://yourproject.com"
50
+
/>
51
+
</div>
52
+
53
+
<div>
54
+
<label htmlFor="description" className="block text-sm font-medium text-gray-700 mb-1">
55
+
Description *
56
+
</label>
57
+
<textarea
58
+
id="description"
59
+
rows={3}
60
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
61
+
placeholder="Brief description of your project's mission and approach"
62
+
/>
63
+
</div>
64
+
</div>
65
+
66
+
{/* Financial Metrics */}
67
+
<div className="space-y-4">
68
+
<h3 className="text-lg font-medium text-gray-800 dark:text-white">Financial Metrics</h3>
69
+
70
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
71
+
<div>
72
+
<label htmlFor="funding-received" className="block text-sm font-medium text-gray-700 mb-1">
73
+
Total Funding Received (USD) *
74
+
</label>
75
+
<input
76
+
type="number"
77
+
id="funding-received"
78
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
79
+
placeholder="0"
80
+
/>
81
+
</div>
82
+
83
+
<div>
84
+
<label htmlFor="sustainable-revenue" className="block text-sm font-medium text-gray-700 mb-1">
85
+
Sustainable Revenue % *
86
+
</label>
87
+
<input
88
+
type="number"
89
+
id="sustainable-revenue"
90
+
min="0"
91
+
max="100"
92
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
93
+
placeholder="0"
94
+
/>
95
+
</div>
96
+
97
+
<div>
98
+
<label htmlFor="annual-budget" className="block text-sm font-medium text-gray-700 mb-1">
99
+
Annual Budget (USD) *
100
+
</label>
101
+
<input
102
+
type="number"
103
+
id="annual-budget"
104
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
105
+
placeholder="0"
106
+
/>
107
+
</div>
108
+
109
+
<div>
110
+
<label htmlFor="team-size" className="block text-sm font-medium text-gray-700 mb-1">
111
+
Team Size (FTE) *
112
+
</label>
113
+
<input
114
+
type="number"
115
+
id="team-size"
116
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
117
+
placeholder="0"
118
+
/>
119
+
</div>
120
+
</div>
121
+
122
+
<div>
123
+
<label htmlFor="funding-distributed" className="block text-sm font-medium text-gray-700 mb-1">
124
+
Funding Distributed Externally (USD)
125
+
</label>
126
+
<input
127
+
type="number"
128
+
id="funding-distributed"
129
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
130
+
placeholder="0"
131
+
/>
132
+
<p className="text-sm text-gray-500 mt-1">
133
+
Grants, UBI, loans, or donations your project has distributed to others
134
+
</p>
135
+
</div>
136
+
</div>
137
+
138
+
{/* Categories */}
139
+
<div className="space-y-4">
140
+
<h3 className="text-lg font-medium text-gray-800 dark:text-white">Categories</h3>
141
+
<div className="grid grid-cols-2 md:grid-cols-3 gap-3">
142
+
{[
143
+
'Climate Positive',
144
+
'Financial Inclusion',
145
+
'Community',
146
+
'Public Goods',
147
+
'Developer Ecosystem',
148
+
'Education',
149
+
'Healthcare',
150
+
'UBI'
151
+
].map((category) => (
152
+
<label key={category} className="flex items-center">
153
+
<input
154
+
type="checkbox"
155
+
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
156
+
/>
157
+
<span className="ml-2 text-sm text-gray-700">{category}</span>
158
+
</label>
159
+
))}
160
+
</div>
161
+
</div>
162
+
163
+
{/* Impact Metrics */}
164
+
<div className="space-y-4">
165
+
<h3 className="text-lg font-medium text-gray-800 dark:text-white">Impact Metrics</h3>
166
+
<div className="space-y-3">
167
+
<div>
168
+
<label htmlFor="impact-1" className="block text-sm font-medium text-gray-700 mb-1">
169
+
Key Impact Metric 1
170
+
</label>
171
+
<div className="grid grid-cols-2 gap-2">
172
+
<input
173
+
type="text"
174
+
placeholder="Metric name (e.g., 'Users Served')"
175
+
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
176
+
/>
177
+
<input
178
+
type="text"
179
+
placeholder="Value (e.g., '50,000+')"
180
+
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
181
+
/>
182
+
</div>
183
+
</div>
184
+
185
+
<div>
186
+
<label htmlFor="impact-2" className="block text-sm font-medium text-gray-700 mb-1">
187
+
Key Impact Metric 2
188
+
</label>
189
+
<div className="grid grid-cols-2 gap-2">
190
+
<input
191
+
type="text"
192
+
placeholder="Metric name"
193
+
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
194
+
/>
195
+
<input
196
+
type="text"
197
+
placeholder="Value"
198
+
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
199
+
/>
200
+
</div>
201
+
</div>
202
+
203
+
<div>
204
+
<label htmlFor="impact-3" className="block text-sm font-medium text-gray-700 mb-1">
205
+
Key Impact Metric 3
206
+
</label>
207
+
<div className="grid grid-cols-2 gap-2">
208
+
<input
209
+
type="text"
210
+
placeholder="Metric name"
211
+
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
212
+
/>
213
+
<input
214
+
type="text"
215
+
placeholder="Value"
216
+
className="px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
217
+
/>
218
+
</div>
219
+
</div>
220
+
</div>
221
+
</div>
222
+
223
+
{/* Contact Information */}
224
+
<div className="space-y-4">
225
+
<h3 className="text-lg font-medium text-gray-800 dark:text-white">Contact Information</h3>
226
+
227
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
228
+
<div>
229
+
<label htmlFor="contact-name" className="block text-sm font-medium text-gray-700 mb-1">
230
+
Contact Name *
231
+
</label>
232
+
<input
233
+
type="text"
234
+
id="contact-name"
235
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
236
+
placeholder="Your name"
237
+
/>
238
+
</div>
239
+
240
+
<div>
241
+
<label htmlFor="contact-email" className="block text-sm font-medium text-gray-700 mb-1">
242
+
Contact Email *
243
+
</label>
244
+
<input
245
+
type="email"
246
+
id="contact-email"
247
+
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
248
+
placeholder="your@email.com"
249
+
/>
250
+
</div>
251
+
</div>
252
+
</div>
253
+
254
+
{/* Submit Button */}
255
+
<div className="pt-6">
256
+
<button
257
+
type="submit"
258
+
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-4 rounded-md transition-colors flex items-center justify-center"
259
+
>
260
+
<Send className="h-5 w-5 mr-2" />
261
+
Submit Project for Review
262
+
</button>
263
+
</div>
264
+
</form>
265
+
</div>
266
+
</div>
267
+
268
+
{/* Submission Process */}
269
+
<div className="bg-card-bg p-8">
270
+
<h2 className="text-2xl font-semibold text-gray-800 dark:text-white mb-6">Submission Process</h2>
271
+
272
+
<div className="space-y-6">
273
+
<div className="flex items-start space-x-4">
274
+
<div className="flex-shrink-0">
275
+
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
276
+
<span className="text-sm font-medium text-blue-600">1</span>
277
+
</div>
278
+
</div>
279
+
<div>
280
+
<h3 className="font-medium text-gray-800 dark:text-white">Submit Information</h3>
281
+
<p className="text-gray-700 dark:text-gray-300 text-sm">
282
+
Complete the form above with accurate project information and metrics.
283
+
</p>
284
+
</div>
285
+
</div>
286
+
287
+
<div className="flex items-start space-x-4">
288
+
<div className="flex-shrink-0">
289
+
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
290
+
<span className="text-sm font-medium text-blue-600">2</span>
291
+
</div>
292
+
</div>
293
+
<div>
294
+
<h3 className="font-medium text-gray-800 dark:text-white">Review & Verification</h3>
295
+
<p className="text-gray-700 dark:text-gray-300 text-sm">
296
+
Our team reviews submissions and verifies data against public sources where possible.
297
+
</p>
298
+
</div>
299
+
</div>
300
+
301
+
<div className="flex items-start space-x-4">
302
+
<div className="flex-shrink-0">
303
+
<div className="w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center">
304
+
<span className="text-sm font-medium text-blue-600">3</span>
305
+
</div>
306
+
</div>
307
+
<div>
308
+
<h3 className="font-medium text-gray-800 dark:text-white">Publication</h3>
309
+
<p className="text-gray-700 dark:text-gray-300 text-sm">
310
+
Approved projects are added to the public dashboard within 1-2 weeks.
311
+
</p>
312
+
</div>
313
+
</div>
314
+
</div>
315
+
</div>
316
+
317
+
{/* Guidelines */}
318
+
<div className="bg-blue-50 border border-blue-200 rounded-lg p-6">
319
+
<div className="flex items-start space-x-3">
320
+
<CheckCircle className="h-6 w-6 text-blue-600 flex-shrink-0 mt-0.5" />
321
+
<div>
322
+
<h3 className="font-medium text-blue-900 mb-2">Submission Guidelines</h3>
323
+
<ul className="space-y-1 text-blue-800 text-sm">
324
+
<li>โข Provide accurate, verifiable financial and impact data</li>
325
+
<li>โข Projects must be actively contributing to the Ethereum ecosystem</li>
326
+
<li>โข Public goods and impact-focused projects are prioritized</li>
327
+
<li>โข Updates to existing project data are welcome</li>
328
+
</ul>
329
+
</div>
330
+
</div>
331
+
</div>
332
+
333
+
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-6">
334
+
<div className="flex items-start space-x-3">
335
+
<AlertCircle className="h-6 w-6 text-yellow-600 flex-shrink-0 mt-0.5" />
336
+
<div>
337
+
<h3 className="font-medium text-yellow-900 mb-2">Important Notes</h3>
338
+
<ul className="space-y-1 text-yellow-800 text-sm">
339
+
<li>โข All submitted information may be made publicly available</li>
340
+
<li>โข Metrics will be cross-referenced with available public data</li>
341
+
<li>โข Projects may be contacted for additional verification</li>
342
+
<li>โข Regular updates to maintain accuracy are encouraged</li>
343
+
</ul>
344
+
</div>
345
+
</div>
346
+
</div>
347
+
</div>
348
+
)
349
+
}
+115
components/FundingChart.tsx
+115
components/FundingChart.tsx
···
1
+
'use client'
2
+
3
+
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts'
4
+
import { FundingHistory } from '@/lib/types'
5
+
6
+
interface FundingChartProps {
7
+
fundingHistory: FundingHistory[]
8
+
sustainableRevenuePercent: number
9
+
}
10
+
11
+
export default function FundingChart({ fundingHistory, sustainableRevenuePercent }: FundingChartProps) {
12
+
// Process funding history for time series
13
+
const timeSeriesData = fundingHistory.map(item => ({
14
+
date: new Date(item.date).toLocaleDateString('en-US', { month: 'short', year: 'numeric' }),
15
+
amount: item.amount / 1000000, // Convert to millions
16
+
type: item.type
17
+
}))
18
+
19
+
// Prepare pie chart data
20
+
const pieData = [
21
+
{ name: 'Sustainable Revenue', value: sustainableRevenuePercent, color: '#64748b' },
22
+
{ name: 'Grants & Investments', value: 100 - sustainableRevenuePercent, color: '#e2e8f0' }
23
+
]
24
+
25
+
return (
26
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-6">
27
+
{/* Funding Timeline */}
28
+
<div className="bg-surface p-4 sm:p-6 border-subtle shadow-card">
29
+
<h3 className="text-lg sm:text-2xl font-serif font-medium text-primary tracking-tight mb-4 sm:mb-6">Funding Timeline</h3>
30
+
<div className="h-64 sm:h-80">
31
+
<ResponsiveContainer width="100%" height="100%">
32
+
<LineChart data={timeSeriesData}>
33
+
<CartesianGrid strokeDasharray="3 3" stroke="var(--border)" />
34
+
<XAxis dataKey="date" tick={{ fill: 'var(--text-secondary)', fontSize: '12px' }} />
35
+
<YAxis
36
+
label={{ value: 'Amount ($M)', angle: -90, position: 'insideLeft', fill: 'var(--text-secondary)', fontSize: '12px' }}
37
+
tick={{ fill: 'var(--text-secondary)', fontSize: '12px' }}
38
+
/>
39
+
<Tooltip
40
+
formatter={(value: number) => [`$${value.toFixed(1)}M`]}
41
+
labelFormatter={(label) => `${label}`}
42
+
contentStyle={{
43
+
backgroundColor: 'var(--surface)',
44
+
border: '1px solid var(--border)',
45
+
borderRadius: '6px',
46
+
color: 'var(--text-primary)',
47
+
fontSize: '12px'
48
+
}}
49
+
labelStyle={{ color: 'var(--text-secondary)' }}
50
+
/>
51
+
<Line
52
+
type="monotone"
53
+
dataKey="amount"
54
+
stroke="var(--accent)"
55
+
strokeWidth={2}
56
+
dot={{ fill: 'var(--accent)', strokeWidth: 2, r: 3 }}
57
+
/>
58
+
</LineChart>
59
+
</ResponsiveContainer>
60
+
</div>
61
+
</div>
62
+
63
+
{/* Revenue Sustainability */}
64
+
<div className="bg-surface p-4 sm:p-6 border-subtle shadow-card">
65
+
<h3 className="text-lg sm:text-2xl font-serif font-medium text-primary tracking-tight mb-4 sm:mb-6">Revenue Sustainability</h3>
66
+
<div className="h-48 sm:h-64 flex items-center justify-center">
67
+
<ResponsiveContainer width="100%" height="100%">
68
+
<PieChart>
69
+
<Pie
70
+
data={pieData}
71
+
cx="50%"
72
+
cy="50%"
73
+
innerRadius={40}
74
+
outerRadius={80}
75
+
paddingAngle={5}
76
+
dataKey="value"
77
+
>
78
+
{pieData.map((entry, index) => (
79
+
<Cell key={`cell-${index}`} fill={entry.color} />
80
+
))}
81
+
</Pie>
82
+
<Tooltip
83
+
formatter={(value: number) => `${value}%`}
84
+
contentStyle={{
85
+
backgroundColor: 'var(--surface)',
86
+
border: '1px solid var(--border)',
87
+
borderRadius: '6px',
88
+
color: 'var(--text-primary)',
89
+
fontSize: '12px'
90
+
}}
91
+
labelStyle={{ color: 'var(--text-secondary)' }}
92
+
/>
93
+
</PieChart>
94
+
</ResponsiveContainer>
95
+
</div>
96
+
<div className="mt-4 sm:mt-6 space-y-2 sm:space-y-3">
97
+
<div className="flex items-center justify-between">
98
+
<div className="flex items-center space-x-2 sm:space-x-3">
99
+
<div className="w-3 h-3 rounded-full bg-accent"></div>
100
+
<span className="text-xs sm:text-sm text-secondary">Sustainable Revenue</span>
101
+
</div>
102
+
<span className="text-xs sm:text-sm font-medium text-primary">{sustainableRevenuePercent}%</span>
103
+
</div>
104
+
<div className="flex items-center justify-between">
105
+
<div className="flex items-center space-x-2 sm:space-x-3">
106
+
<div className="w-3 h-3 rounded-full bg-border"></div>
107
+
<span className="text-xs sm:text-sm text-secondary">Grants & Investments</span>
108
+
</div>
109
+
<span className="text-xs sm:text-sm font-medium text-primary">{100 - sustainableRevenuePercent}%</span>
110
+
</div>
111
+
</div>
112
+
</div>
113
+
</div>
114
+
)
115
+
}
+15
components/MainContent.tsx
+15
components/MainContent.tsx
···
1
+
'use client'
2
+
3
+
import { ReactNode } from 'react'
4
+
5
+
interface MainContentProps {
6
+
children: ReactNode
7
+
}
8
+
9
+
export default function MainContent({ children }: MainContentProps) {
10
+
return (
11
+
<main className="pt-20 px-4 sm:px-6 max-w-7xl mx-auto w-full">
12
+
{children}
13
+
</main>
14
+
)
15
+
}
+257
components/ProjectTable.tsx
+257
components/ProjectTable.tsx
···
1
+
'use client'
2
+
3
+
import { useState } from 'react'
4
+
import Link from 'next/link'
5
+
import { ArrowUpDown, ExternalLink, Search, X } from 'lucide-react'
6
+
import { PieChart, Pie, ResponsiveContainer } from 'recharts'
7
+
import { Project, SortConfig } from '@/lib/types'
8
+
import { formatCurrency } from '@/lib/data'
9
+
10
+
interface ProjectTableProps {
11
+
projects: Project[]
12
+
}
13
+
14
+
export default function ProjectTable({ projects }: ProjectTableProps) {
15
+
const [sortConfig, setSortConfig] = useState<SortConfig>({
16
+
key: 'fundingReceived',
17
+
direction: 'desc'
18
+
})
19
+
const [searchQuery, setSearchQuery] = useState('')
20
+
21
+
// Filter projects based on search query
22
+
const filteredProjects = projects.filter(project => {
23
+
if (!searchQuery) return true
24
+
25
+
const query = searchQuery.toLowerCase()
26
+
return (
27
+
project.name.toLowerCase().includes(query) ||
28
+
project.description.toLowerCase().includes(query) ||
29
+
project.categories.some(category => category.toLowerCase().includes(query))
30
+
)
31
+
})
32
+
33
+
// Sort filtered projects
34
+
const sortedProjects = [...filteredProjects].sort((a, b) => {
35
+
const aValue = a[sortConfig.key]
36
+
const bValue = b[sortConfig.key]
37
+
38
+
if (typeof aValue === 'number' && typeof bValue === 'number') {
39
+
return sortConfig.direction === 'asc' ? aValue - bValue : bValue - aValue
40
+
}
41
+
42
+
const aString = String(aValue).toLowerCase()
43
+
const bString = String(bValue).toLowerCase()
44
+
45
+
if (sortConfig.direction === 'asc') {
46
+
return aString.localeCompare(bString)
47
+
} else {
48
+
return bString.localeCompare(aString)
49
+
}
50
+
})
51
+
52
+
const handleSort = (key: keyof Project) => {
53
+
setSortConfig({
54
+
key,
55
+
direction: sortConfig.key === key && sortConfig.direction === 'desc' ? 'asc' : 'desc'
56
+
})
57
+
}
58
+
59
+
60
+
const SortButton = ({ column, label }: { column: keyof Project; label: string }) => (
61
+
<button
62
+
onClick={() => handleSort(column)}
63
+
className="group flex items-center space-x-1 text-xs font-medium text-secondary uppercase tracking-wider hover:text-primary transition-colors"
64
+
>
65
+
<span>{label}</span>
66
+
<ArrowUpDown className="h-3 w-3 opacity-60 group-hover:opacity-100" />
67
+
</button>
68
+
)
69
+
70
+
return (
71
+
<div className="space-y-4">
72
+
{/* Search Bar */}
73
+
<div className="relative w-full sm:max-w-sm mb-4 sm:mb-0">
74
+
<input
75
+
type="text"
76
+
placeholder="Search projects..."
77
+
value={searchQuery}
78
+
onChange={(e) => setSearchQuery(e.target.value)}
79
+
className="w-full px-4 py-2 bg-transparent border-0 border-b border-border text-primary placeholder-muted focus:outline-none focus:border-accent transition-elegant"
80
+
/>
81
+
{searchQuery && (
82
+
<button
83
+
onClick={() => setSearchQuery('')}
84
+
className="absolute right-1 top-1/2 transform -translate-y-1/2 p-1 text-muted hover:text-accent transition-colors"
85
+
>
86
+
<X className="h-3 w-3" />
87
+
</button>
88
+
)}
89
+
</div>
90
+
91
+
{/* Results count */}
92
+
{searchQuery && (
93
+
<div className="text-sm text-secondary mb-4">
94
+
{sortedProjects.length} of {projects.length} projects found
95
+
</div>
96
+
)}
97
+
98
+
<div className="overflow-x-auto">
99
+
<table className="w-full min-w-[800px]">
100
+
<thead>
101
+
<tr className="bg-accent-light border-b border-border">
102
+
<th className="text-left py-3 px-2 sm:px-4">
103
+
<SortButton column="name" label="Project" />
104
+
</th>
105
+
<th className="text-left py-3 px-2 sm:px-4 hidden sm:table-cell">
106
+
<SortButton column="fundingReceived" label="Funding" />
107
+
</th>
108
+
<th className="text-left py-3 px-2 sm:px-4 hidden md:table-cell">
109
+
<SortButton column="sustainableRevenuePercent" label="Sustainability" />
110
+
</th>
111
+
<th className="text-left py-3 px-2 sm:px-4 hidden lg:table-cell">
112
+
<SortButton column="annualBudget" label="Budget" />
113
+
</th>
114
+
<th className="text-left py-3 px-2 sm:px-4 hidden lg:table-cell">
115
+
<SortButton column="teamSize" label="Team" />
116
+
</th>
117
+
<th className="text-left py-3 px-2 sm:px-4 hidden md:table-cell">
118
+
<SortButton column="fundingGivenOut" label="Distributed" />
119
+
</th>
120
+
<th className="text-left py-3 px-2 sm:px-4 hidden sm:table-cell">
121
+
<span className="text-xs font-medium text-secondary uppercase tracking-wider">Categories</span>
122
+
</th>
123
+
</tr>
124
+
</thead>
125
+
<tbody>
126
+
{sortedProjects.map((project) => (
127
+
<tr
128
+
key={project.id}
129
+
className="hover:bg-surface-hover transition-colors group border-b border-border/30"
130
+
>
131
+
<td className="py-2 px-2 sm:px-4">
132
+
<div className="flex items-center space-x-3 sm:space-x-4">
133
+
<div className="flex-shrink-0">
134
+
<div className="w-8 h-8 bg-gray-100 dark:bg-gray-800 flex items-center justify-center">
135
+
<span className="text-xs font-semibold text-gray-700 dark:text-gray-300">
136
+
{project.name.substring(0, 2).toUpperCase()}
137
+
</span>
138
+
</div>
139
+
</div>
140
+
<div className="min-w-0 flex-1">
141
+
<div className="flex items-center space-x-2">
142
+
<Link
143
+
href={`/project/${project.id}`}
144
+
className="text-sm font-semibold text-primary hover:text-accent transition-colors"
145
+
>
146
+
{project.name}
147
+
</Link>
148
+
<a
149
+
href={project.website}
150
+
target="_blank"
151
+
rel="noopener noreferrer"
152
+
className="opacity-0 group-hover:opacity-100 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-400 transition-all"
153
+
>
154
+
<ExternalLink className="h-3 w-3" />
155
+
</a>
156
+
</div>
157
+
<p className="text-xs text-secondary mt-1 max-w-xs truncate">
158
+
{project.description}
159
+
</p>
160
+
{/* Mobile-only funding info */}
161
+
<div className="sm:hidden mt-2 flex flex-wrap gap-2 text-xs">
162
+
<span className="font-medium text-primary">{formatCurrency(project.fundingReceived)}</span>
163
+
<span className="text-muted">โข</span>
164
+
<span className="text-secondary">{project.sustainableRevenuePercent}% sustainable</span>
165
+
</div>
166
+
</div>
167
+
</div>
168
+
</td>
169
+
<td className="py-2 px-2 sm:px-4 hidden sm:table-cell">
170
+
<div className="text-xs font-semibold text-primary">
171
+
{formatCurrency(project.fundingReceived)}
172
+
</div>
173
+
<div className="text-xs text-muted">
174
+
Total raised
175
+
</div>
176
+
</td>
177
+
<td className="py-2 px-2 sm:px-4 hidden md:table-cell">
178
+
<div className="flex items-center space-x-3">
179
+
<div className="w-8 h-8">
180
+
<ResponsiveContainer width="100%" height="100%">
181
+
<PieChart>
182
+
<Pie
183
+
data={[
184
+
{ value: project.sustainableRevenuePercent, fill: 'var(--accent)' },
185
+
{ value: 100 - project.sustainableRevenuePercent, fill: 'var(--border)' }
186
+
]}
187
+
cx="50%"
188
+
cy="50%"
189
+
innerRadius={8}
190
+
outerRadius={16}
191
+
startAngle={90}
192
+
endAngle={450}
193
+
dataKey="value"
194
+
>
195
+
</Pie>
196
+
</PieChart>
197
+
</ResponsiveContainer>
198
+
</div>
199
+
<div>
200
+
<div className="text-xs font-semibold text-primary">
201
+
{project.sustainableRevenuePercent}%
202
+
</div>
203
+
<div className="text-xs text-muted">
204
+
Sustainable
205
+
</div>
206
+
</div>
207
+
</div>
208
+
</td>
209
+
<td className="py-2 px-2 sm:px-4 hidden lg:table-cell">
210
+
<div className="text-xs font-semibold text-primary">
211
+
{formatCurrency(project.annualBudget)}
212
+
</div>
213
+
<div className="text-xs text-muted">
214
+
Annual
215
+
</div>
216
+
</td>
217
+
<td className="py-2 px-2 sm:px-4 hidden lg:table-cell">
218
+
<div className="text-xs font-semibold text-primary">
219
+
{project.teamSize}
220
+
</div>
221
+
<div className="text-xs text-muted">
222
+
{project.teamSize === 1 ? 'person' : 'people'}
223
+
</div>
224
+
</td>
225
+
<td className="py-2 px-2 sm:px-4 hidden md:table-cell">
226
+
<div className="text-xs font-semibold text-primary">
227
+
{formatCurrency(project.fundingGivenOut)}
228
+
</div>
229
+
<div className="text-xs text-muted">
230
+
{((project.fundingGivenOut / project.fundingReceived) * 100).toFixed(0)}% of received
231
+
</div>
232
+
</td>
233
+
<td className="py-2 px-2 sm:px-4 hidden sm:table-cell">
234
+
<div className="flex flex-wrap gap-0.5 max-w-32">
235
+
{project.categories.slice(0, 2).map((category) => (
236
+
<span
237
+
key={category}
238
+
className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600"
239
+
>
240
+
{category}
241
+
</span>
242
+
))}
243
+
{project.categories.length > 2 && (
244
+
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400 border border-gray-200 dark:border-gray-600">
245
+
+{project.categories.length - 2}
246
+
</span>
247
+
)}
248
+
</div>
249
+
</td>
250
+
</tr>
251
+
))}
252
+
</tbody>
253
+
</table>
254
+
</div>
255
+
</div>
256
+
)
257
+
}
+118
components/Sidebar.tsx
+118
components/Sidebar.tsx
···
1
+
'use client'
2
+
3
+
import { useState } from 'react'
4
+
import Link from 'next/link'
5
+
import Image from 'next/image'
6
+
import { Moon, Sun, Menu, X } from 'lucide-react'
7
+
import { useTheme } from 'next-themes'
8
+
9
+
export default function Sidebar() {
10
+
const { theme, setTheme } = useTheme()
11
+
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
12
+
13
+
return (
14
+
<nav className="fixed top-0 left-0 right-0 z-50 bg-surface/80 backdrop-blur-md border-b border-border/50">
15
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 py-4">
16
+
<div className="flex items-center justify-between">
17
+
{/* Logo */}
18
+
<Link href="/" className="flex items-center space-x-3 hover:opacity-80 transition-opacity">
19
+
<Image
20
+
src="/logo.png"
21
+
alt="Ethereum Impact Index"
22
+
width={56}
23
+
height={56}
24
+
className={`${theme === 'dark' ? 'invert' : ''} transition-all duration-300 -my-2`}
25
+
style={{ width: '56px', height: '56px' }}
26
+
/>
27
+
<span className="text-xl font-serif font-medium text-primary">Ethereum Impact Index</span>
28
+
</Link>
29
+
30
+
{/* Desktop Navigation */}
31
+
<div className="hidden md:flex items-center space-x-8">
32
+
<Link href="/" className="text-base font-medium text-secondary hover:text-primary transition-colors">
33
+
Dashboard
34
+
</Link>
35
+
<Link href="/methodology" className="text-base font-medium text-secondary hover:text-primary transition-colors">
36
+
Methodology
37
+
</Link>
38
+
<Link href="/about" className="text-base font-medium text-secondary hover:text-primary transition-colors">
39
+
About
40
+
</Link>
41
+
<Link href="/submit" className="text-base font-medium text-secondary hover:text-primary transition-colors">
42
+
Submit Project
43
+
</Link>
44
+
</div>
45
+
46
+
{/* Mobile Menu Button and Theme Toggle Group */}
47
+
<div className="flex items-center space-x-2">
48
+
{/* Mobile Menu Button */}
49
+
<button
50
+
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
51
+
className="md:hidden p-2 rounded-lg bg-accent-light hover:bg-surface-hover border-subtle transition-elegant"
52
+
aria-label="Toggle mobile menu"
53
+
>
54
+
{isMobileMenuOpen ? (
55
+
<X className="h-5 w-5 text-accent" />
56
+
) : (
57
+
<Menu className="h-5 w-5 text-accent" />
58
+
)}
59
+
</button>
60
+
61
+
{/* Theme Toggle */}
62
+
<button
63
+
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
64
+
className="group relative p-2 rounded-full bg-accent-light hover:bg-surface-hover border-subtle transition-elegant"
65
+
aria-label="Toggle theme"
66
+
>
67
+
<div className="relative w-5 h-5 flex items-center justify-center">
68
+
{theme === 'light' ? (
69
+
<Sun className="h-5 w-5 text-amber-500 transition-transform duration-300 ease-in-out group-hover:scale-110" />
70
+
) : (
71
+
<Moon className="h-5 w-5 text-blue-400 transition-transform duration-300 ease-in-out group-hover:scale-110" />
72
+
)}
73
+
</div>
74
+
</button>
75
+
</div>
76
+
</div>
77
+
</div>
78
+
79
+
{/* Mobile Menu */}
80
+
{isMobileMenuOpen && (
81
+
<div className="md:hidden bg-surface/95 backdrop-blur-md border-b border-border/50">
82
+
<div className="max-w-7xl mx-auto px-4 sm:px-6 py-4">
83
+
<div className="flex flex-col space-y-4">
84
+
<Link
85
+
href="/"
86
+
className="text-base font-medium text-secondary hover:text-primary transition-colors py-2"
87
+
onClick={() => setIsMobileMenuOpen(false)}
88
+
>
89
+
Dashboard
90
+
</Link>
91
+
<Link
92
+
href="/methodology"
93
+
className="text-base font-medium text-secondary hover:text-primary transition-colors py-2"
94
+
onClick={() => setIsMobileMenuOpen(false)}
95
+
>
96
+
Methodology
97
+
</Link>
98
+
<Link
99
+
href="/about"
100
+
className="text-base font-medium text-secondary hover:text-primary transition-colors py-2"
101
+
onClick={() => setIsMobileMenuOpen(false)}
102
+
>
103
+
About
104
+
</Link>
105
+
<Link
106
+
href="/submit"
107
+
className="text-base font-medium text-secondary hover:text-primary transition-colors py-2"
108
+
onClick={() => setIsMobileMenuOpen(false)}
109
+
>
110
+
Submit Project
111
+
</Link>
112
+
</div>
113
+
</div>
114
+
</div>
115
+
)}
116
+
</nav>
117
+
)
118
+
}
+28
components/SidebarContext.tsx
+28
components/SidebarContext.tsx
···
1
+
'use client'
2
+
3
+
import { createContext, useContext, useState, ReactNode } from 'react'
4
+
5
+
interface SidebarContextType {
6
+
isCollapsed: boolean
7
+
setIsCollapsed: (collapsed: boolean) => void
8
+
}
9
+
10
+
const SidebarContext = createContext<SidebarContextType | undefined>(undefined)
11
+
12
+
export function SidebarProvider({ children }: { children: ReactNode }) {
13
+
const [isCollapsed, setIsCollapsed] = useState(false)
14
+
15
+
return (
16
+
<SidebarContext.Provider value={{ isCollapsed, setIsCollapsed }}>
17
+
{children}
18
+
</SidebarContext.Provider>
19
+
)
20
+
}
21
+
22
+
export function useSidebar() {
23
+
const context = useContext(SidebarContext)
24
+
if (context === undefined) {
25
+
throw new Error('useSidebar must be used within a SidebarProvider')
26
+
}
27
+
return context
28
+
}
+8
components/ThemeProvider.tsx
+8
components/ThemeProvider.tsx
···
1
+
'use client'
2
+
3
+
import { ThemeProvider as NextThemesProvider } from 'next-themes'
4
+
import { type ThemeProviderProps } from 'next-themes/dist/types'
5
+
6
+
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
7
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
8
+
}
+31
components/ThemeToggle.tsx
+31
components/ThemeToggle.tsx
···
1
+
'use client'
2
+
3
+
import { useTheme } from 'next-themes'
4
+
import { useEffect, useState } from 'react'
5
+
import { Moon, Sun } from 'lucide-react'
6
+
7
+
export default function ThemeToggle() {
8
+
const { theme, setTheme } = useTheme()
9
+
const [mounted, setMounted] = useState(false)
10
+
11
+
useEffect(() => {
12
+
setMounted(true)
13
+
}, [])
14
+
15
+
if (!mounted) {
16
+
return null
17
+
}
18
+
19
+
return (
20
+
<button
21
+
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
22
+
className="group relative p-2 rounded-lg bg-gray-100 hover:bg-gray-200 transition-all duration-300 ease-in-out border border-gray-400 dark:border-gray-600"
23
+
aria-label="Toggle theme"
24
+
>
25
+
<div className="relative w-5 h-5 flex items-center justify-center">
26
+
<Sun className="h-5 w-5 text-gray-600 dark:text-gray-400 absolute transition-all duration-300 ease-in-out group-hover:text-gray-800 dark:group-hover:text-gray-200 rotate-0 scale-100 dark:-rotate-90 dark:scale-0" />
27
+
<Moon className="h-5 w-5 text-gray-600 dark:text-gray-400 absolute transition-all duration-300 ease-in-out group-hover:text-gray-800 dark:group-hover:text-gray-200 rotate-90 scale-0 dark:rotate-0 dark:scale-100" />
28
+
</div>
29
+
</button>
30
+
)
31
+
}
+125
components/WorldMap.tsx
+125
components/WorldMap.tsx
···
1
+
'use client'
2
+
3
+
import { useEffect, useState } from 'react'
4
+
import dynamic from 'next/dynamic'
5
+
import { CountryData } from '@/lib/types'
6
+
7
+
// Dynamically import the map component to avoid SSR issues
8
+
const MapContainer = dynamic(() => import('react-leaflet').then((m) => m.MapContainer), { ssr: false })
9
+
const TileLayer = dynamic(() => import('react-leaflet').then((m) => m.TileLayer), { ssr: false })
10
+
const GeoJSON = dynamic(() => import('react-leaflet').then((m) => m.GeoJSON), { ssr: false })
11
+
12
+
interface WorldMapProps {
13
+
countryData: CountryData[]
14
+
}
15
+
16
+
export default function WorldMap({ countryData }: WorldMapProps) {
17
+
const [mounted, setMounted] = useState(false)
18
+
const [geoData, setGeoData] = useState(null)
19
+
20
+
useEffect(() => {
21
+
setMounted(true)
22
+
// In a real implementation, you would load GeoJSON data for world countries
23
+
// For now, we'll use a simple visualization
24
+
}, [])
25
+
26
+
if (!mounted) {
27
+
return (
28
+
<div className="h-96 bg-gray-100 dark:bg-gray-800 rounded-lg flex items-center justify-center">
29
+
<div className="text-gray-500 dark:text-gray-400">Loading map...</div>
30
+
</div>
31
+
)
32
+
}
33
+
34
+
// Create a lookup for country data
35
+
const countryLookup = countryData.reduce((acc, country) => {
36
+
acc[country.countryCode] = country
37
+
return acc
38
+
}, {} as Record<string, CountryData>)
39
+
40
+
return (
41
+
<div className="space-y-4">
42
+
{/* Map placeholder - in a real implementation, this would be a proper world map */}
43
+
<div className="h-96 bg-card-bg p-4">
44
+
<div className="text-center mb-4">
45
+
<h3 className="text-2xl font-serif font-medium text-gray-800 dark:text-white tracking-tight">Global Impact Distribution</h3>
46
+
<p className="text-base text-gray-700 dark:text-gray-400 leading-relaxed">Interactive map showing project beneficiaries worldwide</p>
47
+
</div>
48
+
49
+
{/* Map visualization placeholder */}
50
+
<div className="h-64 bg-table-bg flex items-center justify-center mb-4">
51
+
<div className="text-center">
52
+
<div className="text-6xl mb-2">๐</div>
53
+
<div className="text-sm text-gray-500 dark:text-gray-400">Interactive world map visualization</div>
54
+
<div className="text-xs text-gray-400 dark:text-gray-500 mt-1">
55
+
Real implementation would use Leaflet/Mapbox with GeoJSON
56
+
</div>
57
+
</div>
58
+
</div>
59
+
</div>
60
+
61
+
{/* Country Stats Grid */}
62
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
63
+
{countryData
64
+
.sort((a, b) => b.valueDistributed - a.valueDistributed)
65
+
.slice(0, 12)
66
+
.map((country) => (
67
+
<div
68
+
key={country.countryCode}
69
+
className="bg-card-bg p-3 hover:bg-table-hover transition-colors"
70
+
>
71
+
<div className="flex items-center justify-between mb-2">
72
+
<div className="flex items-center space-x-2">
73
+
<div className="text-2xl">
74
+
{getCountryFlag(country.countryCode)}
75
+
</div>
76
+
<div className="font-medium text-gray-800 dark:text-white">
77
+
{country.country}
78
+
</div>
79
+
</div>
80
+
</div>
81
+
<div className="space-y-1 text-sm">
82
+
<div className="flex justify-between">
83
+
<span className="text-gray-700 dark:text-gray-400">Beneficiaries:</span>
84
+
<span className="font-medium text-gray-800 dark:text-white">{country.beneficiaries.toLocaleString()}</span>
85
+
</div>
86
+
<div className="flex justify-between">
87
+
<span className="text-gray-700 dark:text-gray-400">Value Distributed:</span>
88
+
<span className="font-medium text-gray-800 dark:text-white">
89
+
${(country.valueDistributed / 1000000).toFixed(1)}M
90
+
</span>
91
+
</div>
92
+
</div>
93
+
</div>
94
+
))}
95
+
</div>
96
+
</div>
97
+
)
98
+
}
99
+
100
+
// Helper function to get country flag emoji
101
+
function getCountryFlag(countryCode: string): string {
102
+
const flagMap: Record<string, string> = {
103
+
'US': '๐บ๐ธ',
104
+
'DE': '๐ฉ๐ช',
105
+
'GB': '๐ฌ๐ง',
106
+
'CA': '๐จ๐ฆ',
107
+
'NL': '๐ณ๐ฑ',
108
+
'IN': '๐ฎ๐ณ',
109
+
'BR': '๐ง๐ท',
110
+
'ES': '๐ช๐ธ',
111
+
'AR': '๐ฆ๐ท',
112
+
'NG': '๐ณ๐ฌ',
113
+
'KE': '๐ฐ๐ช',
114
+
'AU': '๐ฆ๐บ',
115
+
'SG': '๐ธ๐ฌ',
116
+
'CH': '๐จ๐ญ',
117
+
'ID': '๐ฎ๐ฉ',
118
+
'PE': '๐ต๐ช',
119
+
'GT': '๐ฌ๐น',
120
+
'VE': '๐ป๐ช',
121
+
'MX': '๐ฒ๐ฝ',
122
+
'CO': '๐จ๐ด'
123
+
}
124
+
return flagMap[countryCode] || '๐'
125
+
}
+274
lib/data.ts
+274
lib/data.ts
···
1
+
import { Project } from './types'
2
+
3
+
export const projects: Project[] = [
4
+
{
5
+
id: 'protocol-guild',
6
+
name: 'Protocol Guild',
7
+
logo: '/projects/protocol-guild.png',
8
+
description: 'Supporting Ethereum core protocol development through funding mechanisms',
9
+
website: 'https://protocol-guild.readthedocs.io/',
10
+
fundingReceived: 12760000,
11
+
sustainableRevenuePercent: 95,
12
+
annualBudget: 3115000,
13
+
teamSize: 180,
14
+
fundingGivenOut: 12000000,
15
+
geographicDistribution: [
16
+
{ country: 'United States', countryCode: 'US', beneficiaries: 45, valueDistributed: 3500000 },
17
+
{ country: 'Germany', countryCode: 'DE', beneficiaries: 25, valueDistributed: 2100000 },
18
+
{ country: 'United Kingdom', countryCode: 'GB', beneficiaries: 20, valueDistributed: 1800000 },
19
+
{ country: 'Canada', countryCode: 'CA', beneficiaries: 15, valueDistributed: 1200000 },
20
+
{ country: 'Netherlands', countryCode: 'NL', beneficiaries: 12, valueDistributed: 950000 },
21
+
],
22
+
impactMetrics: [
23
+
{ label: 'Core Developers Funded', value: '180+', description: 'Protocol developers receiving recurring funding' },
24
+
{ label: 'Protocol Contributions', value: '95%', description: 'Percentage of Ethereum core development covered' },
25
+
{ label: 'Years of Sustainability', value: '4+', description: 'Projected funding runway' },
26
+
],
27
+
categories: ['Developer Ecosystem', 'Public Goods'],
28
+
fundingHistory: [
29
+
{ date: '2024-01', amount: 2500000, type: 'grant', source: 'Ethereum Foundation' },
30
+
{ date: '2023-06', amount: 4200000, type: 'grant', source: 'Gitcoin' },
31
+
{ date: '2023-01', amount: 6060000, type: 'grant', source: 'Community Donations' },
32
+
]
33
+
},
34
+
{
35
+
id: 'gitcoin',
36
+
name: 'Gitcoin',
37
+
logo: '/projects/gitcoin.png',
38
+
description: 'Funding public goods and building the future of work through quadratic funding',
39
+
website: 'https://gitcoin.co/',
40
+
fundingReceived: 50000000,
41
+
sustainableRevenuePercent: 65,
42
+
annualBudget: 8500000,
43
+
teamSize: 85,
44
+
fundingGivenOut: 72000000,
45
+
geographicDistribution: [
46
+
{ country: 'United States', countryCode: 'US', beneficiaries: 2500, valueDistributed: 28000000 },
47
+
{ country: 'India', countryCode: 'IN', beneficiaries: 1800, valueDistributed: 12000000 },
48
+
{ country: 'Germany', countryCode: 'DE', beneficiaries: 950, valueDistributed: 8500000 },
49
+
{ country: 'United Kingdom', countryCode: 'GB', beneficiaries: 750, valueDistributed: 7200000 },
50
+
{ country: 'Canada', countryCode: 'CA', beneficiaries: 650, valueDistributed: 6800000 },
51
+
{ country: 'Brazil', countryCode: 'BR', beneficiaries: 580, valueDistributed: 4200000 },
52
+
],
53
+
impactMetrics: [
54
+
{ label: 'Projects Funded', value: '3,200+', description: 'Public goods projects receiving funding' },
55
+
{ label: 'Developers Supported', value: '15,000+', description: 'Open source developers funded' },
56
+
{ label: 'Total Distributed', value: '$72M', description: 'Lifetime funding distributed to projects' },
57
+
],
58
+
categories: ['Public Goods', 'Developer Ecosystem'],
59
+
fundingHistory: [
60
+
{ date: '2024-01', amount: 12000000, type: 'revenue', source: 'Platform Fees' },
61
+
{ date: '2023-06', amount: 18000000, type: 'investment', source: 'Series A' },
62
+
{ date: '2023-01', amount: 20000000, type: 'grant', source: 'Ethereum Foundation' },
63
+
]
64
+
},
65
+
{
66
+
id: 'giveth',
67
+
name: 'Giveth',
68
+
logo: '/projects/giveth.png',
69
+
description: 'Building the future of giving through blockchain technology and community-driven funding',
70
+
website: 'https://giveth.io/',
71
+
fundingReceived: 8500000,
72
+
sustainableRevenuePercent: 45,
73
+
annualBudget: 1200000,
74
+
teamSize: 25,
75
+
fundingGivenOut: 15000000,
76
+
geographicDistribution: [
77
+
{ country: 'United States', countryCode: 'US', beneficiaries: 850, valueDistributed: 4500000 },
78
+
{ country: 'Spain', countryCode: 'ES', beneficiaries: 450, valueDistributed: 2800000 },
79
+
{ country: 'Argentina', countryCode: 'AR', beneficiaries: 380, valueDistributed: 2200000 },
80
+
{ country: 'Nigeria', countryCode: 'NG', beneficiaries: 320, valueDistributed: 1800000 },
81
+
{ country: 'Kenya', countryCode: 'KE', beneficiaries: 280, valueDistributed: 1500000 },
82
+
],
83
+
impactMetrics: [
84
+
{ label: 'Verified Projects', value: '1,800+', description: 'Projects verified for transparent funding' },
85
+
{ label: 'Donors Active', value: '25,000+', description: 'Community members contributing' },
86
+
{ label: 'Countries Served', value: '45+', description: 'Global reach of funded projects' },
87
+
],
88
+
categories: ['Public Goods', 'Community'],
89
+
fundingHistory: [
90
+
{ date: '2024-01', amount: 450000, type: 'revenue', source: 'Platform Revenue' },
91
+
{ date: '2023-08', amount: 2100000, type: 'grant', source: 'Ethereum Foundation' },
92
+
{ date: '2023-03', amount: 5950000, type: 'grant', source: 'Community Donations' },
93
+
]
94
+
},
95
+
{
96
+
id: 'coordinape',
97
+
name: 'Coordinape',
98
+
logo: '/projects/coordinape.png',
99
+
description: 'Decentralized payroll and community resource allocation through social coordination',
100
+
website: 'https://coordinape.com/',
101
+
fundingReceived: 15000000,
102
+
sustainableRevenuePercent: 75,
103
+
annualBudget: 2800000,
104
+
teamSize: 35,
105
+
fundingGivenOut: 8500000,
106
+
geographicDistribution: [
107
+
{ country: 'United States', countryCode: 'US', beneficiaries: 1200, valueDistributed: 3200000 },
108
+
{ country: 'Germany', countryCode: 'DE', beneficiaries: 450, valueDistributed: 1800000 },
109
+
{ country: 'United Kingdom', countryCode: 'GB', beneficiaries: 380, valueDistributed: 1500000 },
110
+
{ country: 'Canada', countryCode: 'CA', beneficiaries: 280, valueDistributed: 1200000 },
111
+
{ country: 'Australia', countryCode: 'AU', beneficiaries: 220, valueDistributed: 800000 },
112
+
],
113
+
impactMetrics: [
114
+
{ label: 'Organizations Served', value: '450+', description: 'DAOs and organizations using the platform' },
115
+
{ label: 'Contributors Paid', value: '12,000+', description: 'Individual contributors compensated' },
116
+
{ label: 'Allocation Rounds', value: '2,800+', description: 'Successful compensation rounds completed' },
117
+
],
118
+
categories: ['Community', 'Public Goods'],
119
+
fundingHistory: [
120
+
{ date: '2024-01', amount: 1800000, type: 'revenue', source: 'SaaS Subscriptions' },
121
+
{ date: '2023-09', amount: 8200000, type: 'investment', source: 'Series A' },
122
+
{ date: '2023-02', amount: 5000000, type: 'grant', source: 'Gitcoin Grants' },
123
+
]
124
+
},
125
+
{
126
+
id: 'klima-dao',
127
+
name: 'KlimaDAO',
128
+
logo: '/projects/klima-dao.png',
129
+
description: 'Accelerating climate action through carbon-backed currency and transparent carbon markets',
130
+
website: 'https://klimadao.finance/',
131
+
fundingReceived: 25000000,
132
+
sustainableRevenuePercent: 40,
133
+
annualBudget: 4500000,
134
+
teamSize: 45,
135
+
fundingGivenOut: 18000000,
136
+
geographicDistribution: [
137
+
{ country: 'Brazil', countryCode: 'BR', beneficiaries: 850, valueDistributed: 5500000 },
138
+
{ country: 'Indonesia', countryCode: 'ID', beneficiaries: 650, valueDistributed: 4200000 },
139
+
{ country: 'Peru', countryCode: 'PE', beneficiaries: 480, valueDistributed: 3100000 },
140
+
{ country: 'Kenya', countryCode: 'KE', beneficiaries: 420, valueDistributed: 2800000 },
141
+
{ country: 'Guatemala', countryCode: 'GT', beneficiaries: 350, valueDistributed: 2400000 },
142
+
],
143
+
impactMetrics: [
144
+
{ label: 'Carbon Retired', value: '18M tonnes', description: 'Total carbon credits permanently retired' },
145
+
{ label: 'Green Projects', value: '120+', description: 'Environmental projects directly funded' },
146
+
{ label: 'Market Transparency', value: '95%', description: 'Carbon credit provenance tracked on-chain' },
147
+
],
148
+
categories: ['Climate Positive', 'Public Goods'],
149
+
fundingHistory: [
150
+
{ date: '2024-01', amount: 2200000, type: 'revenue', source: 'Carbon Credit Trading' },
151
+
{ date: '2023-07', amount: 12800000, type: 'investment', source: 'Community Treasury' },
152
+
{ date: '2023-01', amount: 10000000, type: 'grant', source: 'Climate Initiatives' },
153
+
]
154
+
},
155
+
{
156
+
id: 'proof-of-humanity',
157
+
name: 'Proof of Humanity',
158
+
logo: '/projects/poh.png',
159
+
description: 'Decentralized identity verification enabling Universal Basic Income and democratic governance',
160
+
website: 'https://proofofhumanity.id/',
161
+
fundingReceived: 8800000,
162
+
sustainableRevenuePercent: 35,
163
+
annualBudget: 1800000,
164
+
teamSize: 22,
165
+
fundingGivenOut: 6500000,
166
+
geographicDistribution: [
167
+
{ country: 'Argentina', countryCode: 'AR', beneficiaries: 12000, valueDistributed: 2200000 },
168
+
{ country: 'Venezuela', countryCode: 'VE', beneficiaries: 8500, valueDistributed: 1800000 },
169
+
{ country: 'Mexico', countryCode: 'MX', beneficiaries: 6800, valueDistributed: 1400000 },
170
+
{ country: 'Colombia', countryCode: 'CO', beneficiaries: 4200, valueDistributed: 900000 },
171
+
{ country: 'Nigeria', countryCode: 'NG', beneficiaries: 3800, valueDistributed: 200000 },
172
+
],
173
+
impactMetrics: [
174
+
{ label: 'Verified Humans', value: '35,000+', description: 'Unique humans verified on the registry' },
175
+
{ label: 'UBI Recipients', value: '18,000+', description: 'People receiving Universal Basic Income' },
176
+
{ label: 'Democratic Participation', value: '85%', description: 'Verified humans participating in governance' },
177
+
],
178
+
categories: ['UBI', 'Community', 'Financial Inclusion'],
179
+
fundingHistory: [
180
+
{ date: '2024-01', amount: 450000, type: 'revenue', source: 'Registration Fees' },
181
+
{ date: '2023-10', amount: 3350000, type: 'grant', source: 'Ethereum Foundation' },
182
+
{ date: '2023-04', amount: 5000000, type: 'grant', source: 'Gitcoin Grants' },
183
+
]
184
+
},
185
+
{
186
+
id: 'ethereum-foundation',
187
+
name: 'Ethereum Foundation',
188
+
logo: '/projects/ef.png',
189
+
description: 'Supporting Ethereum ecosystem research, development, and community growth',
190
+
website: 'https://ethereum.foundation/',
191
+
fundingReceived: 2000000000,
192
+
sustainableRevenuePercent: 90,
193
+
annualBudget: 180000000,
194
+
teamSize: 120,
195
+
fundingGivenOut: 450000000,
196
+
geographicDistribution: [
197
+
{ country: 'United States', countryCode: 'US', beneficiaries: 2500, valueDistributed: 180000000 },
198
+
{ country: 'Germany', countryCode: 'DE', beneficiaries: 950, valueDistributed: 85000000 },
199
+
{ country: 'Singapore', countryCode: 'SG', beneficiaries: 450, valueDistributed: 65000000 },
200
+
{ country: 'Switzerland', countryCode: 'CH', beneficiaries: 380, valueDistributed: 55000000 },
201
+
{ country: 'United Kingdom', countryCode: 'GB', beneficiaries: 720, valueDistributed: 45000000 },
202
+
{ country: 'Canada', countryCode: 'CA', beneficiaries: 380, valueDistributed: 20000000 },
203
+
],
204
+
impactMetrics: [
205
+
{ label: 'Grants Distributed', value: '2,500+', description: 'Research and development grants awarded' },
206
+
{ label: 'Researchers Funded', value: '800+', description: 'Academic and independent researchers supported' },
207
+
{ label: 'Conference Attendees', value: '50,000+', description: 'Annual Devcon and community event participants' },
208
+
],
209
+
categories: ['Developer Ecosystem', 'Public Goods', 'Education'],
210
+
fundingHistory: [
211
+
{ date: '2024-01', amount: 45000000, type: 'revenue', source: 'ETH Holdings' },
212
+
{ date: '2023-06', amount: 85000000, type: 'revenue', source: 'Asset Appreciation' },
213
+
{ date: '2023-01', amount: 1870000000, type: 'revenue', source: 'Genesis Allocation' },
214
+
]
215
+
},
216
+
{
217
+
id: 'optimism-foundation',
218
+
name: 'Optimism Foundation',
219
+
logo: '/projects/optimism.png',
220
+
description: 'Building sustainable funding for public goods through retroactive public goods funding',
221
+
website: 'https://optimism.io/',
222
+
fundingReceived: 850000000,
223
+
sustainableRevenuePercent: 85,
224
+
annualBudget: 120000000,
225
+
teamSize: 95,
226
+
fundingGivenOut: 380000000,
227
+
geographicDistribution: [
228
+
{ country: 'United States', countryCode: 'US', beneficiaries: 1800, valueDistributed: 150000000 },
229
+
{ country: 'Germany', countryCode: 'DE', beneficiaries: 650, valueDistributed: 85000000 },
230
+
{ country: 'United Kingdom', countryCode: 'GB', beneficiaries: 450, valueDistributed: 55000000 },
231
+
{ country: 'Singapore', countryCode: 'SG', beneficiaries: 320, valueDistributed: 45000000 },
232
+
{ country: 'Canada', countryCode: 'CA', beneficiaries: 280, valueDistributed: 35000000 },
233
+
{ country: 'Australia', countryCode: 'AU', beneficiaries: 220, valueDistributed: 10000000 },
234
+
],
235
+
impactMetrics: [
236
+
{ label: 'Public Goods Funded', value: '1,200+', description: 'Projects receiving retroactive funding' },
237
+
{ label: 'Transaction Cost Savings', value: '95%', description: 'Gas fee reduction for users' },
238
+
{ label: 'Developer Adoption', value: '2,800+', description: 'Developers building on Optimism' },
239
+
],
240
+
categories: ['Public Goods', 'Developer Ecosystem'],
241
+
fundingHistory: [
242
+
{ date: '2024-01', amount: 85000000, type: 'revenue', source: 'Sequencer Revenue' },
243
+
{ date: '2023-08', amount: 180000000, type: 'revenue', source: 'Token Appreciation' },
244
+
{ date: '2023-02', amount: 585000000, type: 'revenue', source: 'Initial Token Allocation' },
245
+
]
246
+
}
247
+
]
248
+
249
+
// Helper function to format currency
250
+
export const formatCurrency = (amount: number): string => {
251
+
if (amount >= 1000000000) {
252
+
return `$${(amount / 1000000000).toFixed(1)}B`
253
+
} else if (amount >= 1000000) {
254
+
return `$${(amount / 1000000).toFixed(1)}M`
255
+
} else if (amount >= 1000) {
256
+
return `$${(amount / 1000).toFixed(0)}K`
257
+
} else {
258
+
return `$${amount.toLocaleString()}`
259
+
}
260
+
}
261
+
262
+
// Helper function to get sustainability color
263
+
export const getSustainabilityColor = (percent: number): string => {
264
+
if (percent >= 70) return 'text-green-600'
265
+
if (percent >= 50) return 'text-yellow-600'
266
+
return 'text-red-600'
267
+
}
268
+
269
+
// Helper function to get sustainability background color
270
+
export const getSustainabilityBgColor = (percent: number): string => {
271
+
if (percent >= 70) return 'bg-green-50'
272
+
if (percent >= 50) return 'bg-yellow-50'
273
+
return 'bg-red-50'
274
+
}
+52
lib/types.ts
+52
lib/types.ts
···
1
+
export interface Project {
2
+
id: string
3
+
name: string
4
+
logo: string
5
+
description: string
6
+
website: string
7
+
fundingReceived: number // USD
8
+
sustainableRevenuePercent: number // percentage (0-100)
9
+
annualBudget: number // USD
10
+
teamSize: number // FTE
11
+
fundingGivenOut: number // USD
12
+
geographicDistribution: CountryData[]
13
+
impactMetrics: ImpactMetric[]
14
+
categories: ProjectCategory[]
15
+
fundingHistory: FundingHistory[]
16
+
}
17
+
18
+
export interface CountryData {
19
+
country: string
20
+
countryCode: string // ISO 2-letter code
21
+
beneficiaries: number
22
+
valueDistributed: number // USD
23
+
}
24
+
25
+
export interface ImpactMetric {
26
+
label: string
27
+
value: string
28
+
description: string
29
+
icon?: string
30
+
}
31
+
32
+
export interface FundingHistory {
33
+
date: string
34
+
amount: number
35
+
type: 'grant' | 'revenue' | 'investment'
36
+
source?: string
37
+
}
38
+
39
+
export type ProjectCategory =
40
+
| 'Climate Positive'
41
+
| 'Financial Inclusion'
42
+
| 'Community'
43
+
| 'Public Goods'
44
+
| 'Developer Ecosystem'
45
+
| 'Education'
46
+
| 'Healthcare'
47
+
| 'UBI'
48
+
49
+
export interface SortConfig {
50
+
key: keyof Project
51
+
direction: 'asc' | 'desc'
52
+
}
+417
-3
package-lock.json
+417
-3
package-lock.json
···
8
8
"name": "ethereum-impact-index",
9
9
"version": "0.1.0",
10
10
"dependencies": {
11
+
"@types/leaflet": "^1.9.20",
12
+
"leaflet": "^1.9.4",
13
+
"lucide-react": "^0.542.0",
11
14
"next": "15.5.2",
15
+
"next-themes": "^0.4.6",
12
16
"react": "19.1.0",
13
-
"react-dom": "19.1.0"
17
+
"react-dom": "19.1.0",
18
+
"react-leaflet": "^5.0.0",
19
+
"recharts": "^3.1.2"
14
20
},
15
21
"devDependencies": {
16
22
"@tailwindcss/postcss": "^4",
···
620
626
"node": ">= 10"
621
627
}
622
628
},
629
+
"node_modules/@react-leaflet/core": {
630
+
"version": "3.0.0",
631
+
"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-3.0.0.tgz",
632
+
"integrity": "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ==",
633
+
"peerDependencies": {
634
+
"leaflet": "^1.9.0",
635
+
"react": "^19.0.0",
636
+
"react-dom": "^19.0.0"
637
+
}
638
+
},
639
+
"node_modules/@reduxjs/toolkit": {
640
+
"version": "2.8.2",
641
+
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz",
642
+
"integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==",
643
+
"dependencies": {
644
+
"@standard-schema/spec": "^1.0.0",
645
+
"@standard-schema/utils": "^0.3.0",
646
+
"immer": "^10.0.3",
647
+
"redux": "^5.0.1",
648
+
"redux-thunk": "^3.1.0",
649
+
"reselect": "^5.1.0"
650
+
},
651
+
"peerDependencies": {
652
+
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
653
+
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
654
+
},
655
+
"peerDependenciesMeta": {
656
+
"react": {
657
+
"optional": true
658
+
},
659
+
"react-redux": {
660
+
"optional": true
661
+
}
662
+
}
663
+
},
664
+
"node_modules/@standard-schema/spec": {
665
+
"version": "1.0.0",
666
+
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
667
+
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="
668
+
},
669
+
"node_modules/@standard-schema/utils": {
670
+
"version": "0.3.0",
671
+
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
672
+
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="
673
+
},
623
674
"node_modules/@swc/helpers": {
624
675
"version": "0.5.15",
625
676
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
···
889
940
"tailwindcss": "4.1.12"
890
941
}
891
942
},
943
+
"node_modules/@types/d3-array": {
944
+
"version": "3.2.1",
945
+
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz",
946
+
"integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="
947
+
},
948
+
"node_modules/@types/d3-color": {
949
+
"version": "3.1.3",
950
+
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
951
+
"integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="
952
+
},
953
+
"node_modules/@types/d3-ease": {
954
+
"version": "3.0.2",
955
+
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz",
956
+
"integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="
957
+
},
958
+
"node_modules/@types/d3-interpolate": {
959
+
"version": "3.0.4",
960
+
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
961
+
"integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
962
+
"dependencies": {
963
+
"@types/d3-color": "*"
964
+
}
965
+
},
966
+
"node_modules/@types/d3-path": {
967
+
"version": "3.1.1",
968
+
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
969
+
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="
970
+
},
971
+
"node_modules/@types/d3-scale": {
972
+
"version": "4.0.9",
973
+
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
974
+
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
975
+
"dependencies": {
976
+
"@types/d3-time": "*"
977
+
}
978
+
},
979
+
"node_modules/@types/d3-shape": {
980
+
"version": "3.1.7",
981
+
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
982
+
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
983
+
"dependencies": {
984
+
"@types/d3-path": "*"
985
+
}
986
+
},
987
+
"node_modules/@types/d3-time": {
988
+
"version": "3.0.4",
989
+
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
990
+
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="
991
+
},
992
+
"node_modules/@types/d3-timer": {
993
+
"version": "3.0.2",
994
+
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
995
+
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
996
+
},
997
+
"node_modules/@types/geojson": {
998
+
"version": "7946.0.16",
999
+
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
1000
+
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="
1001
+
},
1002
+
"node_modules/@types/leaflet": {
1003
+
"version": "1.9.20",
1004
+
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.20.tgz",
1005
+
"integrity": "sha512-rooalPMlk61LCaLOvBF2VIf9M47HgMQqi5xQ9QRi7c8PkdIe0WrIi5IxXUXQjAdL0c+vcQ01mYWbthzmp9GHWw==",
1006
+
"dependencies": {
1007
+
"@types/geojson": "*"
1008
+
}
1009
+
},
892
1010
"node_modules/@types/node": {
893
1011
"version": "20.19.11",
894
1012
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.11.tgz",
···
902
1020
"version": "19.1.12",
903
1021
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
904
1022
"integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
905
-
"dev": true,
1023
+
"devOptional": true,
906
1024
"dependencies": {
907
1025
"csstype": "^3.0.2"
908
1026
}
···
916
1034
"@types/react": "^19.0.0"
917
1035
}
918
1036
},
1037
+
"node_modules/@types/use-sync-external-store": {
1038
+
"version": "0.0.6",
1039
+
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
1040
+
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="
1041
+
},
919
1042
"node_modules/caniuse-lite": {
920
1043
"version": "1.0.30001739",
921
1044
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001739.tgz",
···
948
1071
"version": "0.0.1",
949
1072
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
950
1073
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
1074
+
},
1075
+
"node_modules/clsx": {
1076
+
"version": "2.1.1",
1077
+
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
1078
+
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
1079
+
"engines": {
1080
+
"node": ">=6"
1081
+
}
951
1082
},
952
1083
"node_modules/color": {
953
1084
"version": "4.2.3",
···
994
1125
"version": "3.1.3",
995
1126
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
996
1127
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
997
-
"dev": true
1128
+
"devOptional": true
1129
+
},
1130
+
"node_modules/d3-array": {
1131
+
"version": "3.2.4",
1132
+
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
1133
+
"integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
1134
+
"dependencies": {
1135
+
"internmap": "1 - 2"
1136
+
},
1137
+
"engines": {
1138
+
"node": ">=12"
1139
+
}
1140
+
},
1141
+
"node_modules/d3-color": {
1142
+
"version": "3.1.0",
1143
+
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
1144
+
"integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
1145
+
"engines": {
1146
+
"node": ">=12"
1147
+
}
1148
+
},
1149
+
"node_modules/d3-ease": {
1150
+
"version": "3.0.1",
1151
+
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
1152
+
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
1153
+
"engines": {
1154
+
"node": ">=12"
1155
+
}
1156
+
},
1157
+
"node_modules/d3-format": {
1158
+
"version": "3.1.0",
1159
+
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
1160
+
"integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
1161
+
"engines": {
1162
+
"node": ">=12"
1163
+
}
1164
+
},
1165
+
"node_modules/d3-interpolate": {
1166
+
"version": "3.0.1",
1167
+
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
1168
+
"integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
1169
+
"dependencies": {
1170
+
"d3-color": "1 - 3"
1171
+
},
1172
+
"engines": {
1173
+
"node": ">=12"
1174
+
}
1175
+
},
1176
+
"node_modules/d3-path": {
1177
+
"version": "3.1.0",
1178
+
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
1179
+
"integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
1180
+
"engines": {
1181
+
"node": ">=12"
1182
+
}
1183
+
},
1184
+
"node_modules/d3-scale": {
1185
+
"version": "4.0.2",
1186
+
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
1187
+
"integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
1188
+
"dependencies": {
1189
+
"d3-array": "2.10.0 - 3",
1190
+
"d3-format": "1 - 3",
1191
+
"d3-interpolate": "1.2.0 - 3",
1192
+
"d3-time": "2.1.1 - 3",
1193
+
"d3-time-format": "2 - 4"
1194
+
},
1195
+
"engines": {
1196
+
"node": ">=12"
1197
+
}
1198
+
},
1199
+
"node_modules/d3-shape": {
1200
+
"version": "3.2.0",
1201
+
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
1202
+
"integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
1203
+
"dependencies": {
1204
+
"d3-path": "^3.1.0"
1205
+
},
1206
+
"engines": {
1207
+
"node": ">=12"
1208
+
}
1209
+
},
1210
+
"node_modules/d3-time": {
1211
+
"version": "3.1.0",
1212
+
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
1213
+
"integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
1214
+
"dependencies": {
1215
+
"d3-array": "2 - 3"
1216
+
},
1217
+
"engines": {
1218
+
"node": ">=12"
1219
+
}
1220
+
},
1221
+
"node_modules/d3-time-format": {
1222
+
"version": "4.1.0",
1223
+
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
1224
+
"integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
1225
+
"dependencies": {
1226
+
"d3-time": "1 - 3"
1227
+
},
1228
+
"engines": {
1229
+
"node": ">=12"
1230
+
}
1231
+
},
1232
+
"node_modules/d3-timer": {
1233
+
"version": "3.0.1",
1234
+
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
1235
+
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
1236
+
"engines": {
1237
+
"node": ">=12"
1238
+
}
1239
+
},
1240
+
"node_modules/decimal.js-light": {
1241
+
"version": "2.5.1",
1242
+
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
1243
+
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
998
1244
},
999
1245
"node_modules/detect-libc": {
1000
1246
"version": "2.0.4",
···
1018
1264
"node": ">=10.13.0"
1019
1265
}
1020
1266
},
1267
+
"node_modules/es-toolkit": {
1268
+
"version": "1.39.10",
1269
+
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz",
1270
+
"integrity": "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w=="
1271
+
},
1272
+
"node_modules/eventemitter3": {
1273
+
"version": "5.0.1",
1274
+
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
1275
+
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
1276
+
},
1021
1277
"node_modules/graceful-fs": {
1022
1278
"version": "4.2.11",
1023
1279
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
1024
1280
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
1025
1281
"dev": true
1026
1282
},
1283
+
"node_modules/immer": {
1284
+
"version": "10.1.3",
1285
+
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.3.tgz",
1286
+
"integrity": "sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==",
1287
+
"funding": {
1288
+
"type": "opencollective",
1289
+
"url": "https://opencollective.com/immer"
1290
+
}
1291
+
},
1292
+
"node_modules/internmap": {
1293
+
"version": "2.0.3",
1294
+
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
1295
+
"integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
1296
+
"engines": {
1297
+
"node": ">=12"
1298
+
}
1299
+
},
1027
1300
"node_modules/is-arrayish": {
1028
1301
"version": "0.3.2",
1029
1302
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
···
1038
1311
"bin": {
1039
1312
"jiti": "lib/jiti-cli.mjs"
1040
1313
}
1314
+
},
1315
+
"node_modules/leaflet": {
1316
+
"version": "1.9.4",
1317
+
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
1318
+
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
1041
1319
},
1042
1320
"node_modules/lightningcss": {
1043
1321
"version": "1.30.1",
···
1267
1545
"url": "https://opencollective.com/parcel"
1268
1546
}
1269
1547
},
1548
+
"node_modules/lucide-react": {
1549
+
"version": "0.542.0",
1550
+
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.542.0.tgz",
1551
+
"integrity": "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==",
1552
+
"peerDependencies": {
1553
+
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
1554
+
}
1555
+
},
1270
1556
"node_modules/magic-string": {
1271
1557
"version": "0.30.18",
1272
1558
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz",
···
1380
1666
}
1381
1667
}
1382
1668
},
1669
+
"node_modules/next-themes": {
1670
+
"version": "0.4.6",
1671
+
"resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
1672
+
"integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
1673
+
"peerDependencies": {
1674
+
"react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
1675
+
"react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
1676
+
}
1677
+
},
1383
1678
"node_modules/next/node_modules/postcss": {
1384
1679
"version": "8.4.31",
1385
1680
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
···
1459
1754
"react": "^19.1.0"
1460
1755
}
1461
1756
},
1757
+
"node_modules/react-is": {
1758
+
"version": "19.1.1",
1759
+
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz",
1760
+
"integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==",
1761
+
"peer": true
1762
+
},
1763
+
"node_modules/react-leaflet": {
1764
+
"version": "5.0.0",
1765
+
"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-5.0.0.tgz",
1766
+
"integrity": "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw==",
1767
+
"dependencies": {
1768
+
"@react-leaflet/core": "^3.0.0"
1769
+
},
1770
+
"peerDependencies": {
1771
+
"leaflet": "^1.9.0",
1772
+
"react": "^19.0.0",
1773
+
"react-dom": "^19.0.0"
1774
+
}
1775
+
},
1776
+
"node_modules/react-redux": {
1777
+
"version": "9.2.0",
1778
+
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
1779
+
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
1780
+
"dependencies": {
1781
+
"@types/use-sync-external-store": "^0.0.6",
1782
+
"use-sync-external-store": "^1.4.0"
1783
+
},
1784
+
"peerDependencies": {
1785
+
"@types/react": "^18.2.25 || ^19",
1786
+
"react": "^18.0 || ^19",
1787
+
"redux": "^5.0.0"
1788
+
},
1789
+
"peerDependenciesMeta": {
1790
+
"@types/react": {
1791
+
"optional": true
1792
+
},
1793
+
"redux": {
1794
+
"optional": true
1795
+
}
1796
+
}
1797
+
},
1798
+
"node_modules/recharts": {
1799
+
"version": "3.1.2",
1800
+
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.1.2.tgz",
1801
+
"integrity": "sha512-vhNbYwaxNbk/IATK0Ki29k3qvTkGqwvCgyQAQ9MavvvBwjvKnMTswdbklJpcOAoMPN/qxF3Lyqob0zO+ZXkZ4g==",
1802
+
"dependencies": {
1803
+
"@reduxjs/toolkit": "1.x.x || 2.x.x",
1804
+
"clsx": "^2.1.1",
1805
+
"decimal.js-light": "^2.5.1",
1806
+
"es-toolkit": "^1.39.3",
1807
+
"eventemitter3": "^5.0.1",
1808
+
"immer": "^10.1.1",
1809
+
"react-redux": "8.x.x || 9.x.x",
1810
+
"reselect": "5.1.1",
1811
+
"tiny-invariant": "^1.3.3",
1812
+
"use-sync-external-store": "^1.2.2",
1813
+
"victory-vendor": "^37.0.2"
1814
+
},
1815
+
"engines": {
1816
+
"node": ">=18"
1817
+
},
1818
+
"peerDependencies": {
1819
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
1820
+
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
1821
+
"react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
1822
+
}
1823
+
},
1824
+
"node_modules/redux": {
1825
+
"version": "5.0.1",
1826
+
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
1827
+
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
1828
+
},
1829
+
"node_modules/redux-thunk": {
1830
+
"version": "3.1.0",
1831
+
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
1832
+
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
1833
+
"peerDependencies": {
1834
+
"redux": "^5.0.0"
1835
+
}
1836
+
},
1837
+
"node_modules/reselect": {
1838
+
"version": "5.1.1",
1839
+
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
1840
+
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="
1841
+
},
1462
1842
"node_modules/scheduler": {
1463
1843
"version": "0.26.0",
1464
1844
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
···
1593
1973
"node": ">=18"
1594
1974
}
1595
1975
},
1976
+
"node_modules/tiny-invariant": {
1977
+
"version": "1.3.3",
1978
+
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
1979
+
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
1980
+
},
1596
1981
"node_modules/tslib": {
1597
1982
"version": "2.8.1",
1598
1983
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
···
1616
2001
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
1617
2002
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
1618
2003
"dev": true
2004
+
},
2005
+
"node_modules/use-sync-external-store": {
2006
+
"version": "1.5.0",
2007
+
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
2008
+
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
2009
+
"peerDependencies": {
2010
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
2011
+
}
2012
+
},
2013
+
"node_modules/victory-vendor": {
2014
+
"version": "37.3.6",
2015
+
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
2016
+
"integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==",
2017
+
"dependencies": {
2018
+
"@types/d3-array": "^3.0.3",
2019
+
"@types/d3-ease": "^3.0.0",
2020
+
"@types/d3-interpolate": "^3.0.1",
2021
+
"@types/d3-scale": "^4.0.2",
2022
+
"@types/d3-shape": "^3.1.0",
2023
+
"@types/d3-time": "^3.0.0",
2024
+
"@types/d3-timer": "^3.0.0",
2025
+
"d3-array": "^3.1.6",
2026
+
"d3-ease": "^3.0.1",
2027
+
"d3-interpolate": "^3.0.1",
2028
+
"d3-scale": "^4.0.2",
2029
+
"d3-shape": "^3.1.0",
2030
+
"d3-time": "^3.0.0",
2031
+
"d3-timer": "^3.0.1"
2032
+
}
1619
2033
},
1620
2034
"node_modules/yallist": {
1621
2035
"version": "5.0.0",
+10
-4
package.json
+10
-4
package.json
···
8
8
"start": "next start"
9
9
},
10
10
"dependencies": {
11
+
"@types/leaflet": "^1.9.20",
12
+
"leaflet": "^1.9.4",
13
+
"lucide-react": "^0.542.0",
14
+
"next": "15.5.2",
15
+
"next-themes": "^0.4.6",
11
16
"react": "19.1.0",
12
17
"react-dom": "19.1.0",
13
-
"next": "15.5.2"
18
+
"react-leaflet": "^5.0.0",
19
+
"recharts": "^3.1.2"
14
20
},
15
21
"devDependencies": {
16
-
"typescript": "^5",
22
+
"@tailwindcss/postcss": "^4",
17
23
"@types/node": "^20",
18
24
"@types/react": "^19",
19
25
"@types/react-dom": "^19",
20
-
"@tailwindcss/postcss": "^4",
21
-
"tailwindcss": "^4"
26
+
"tailwindcss": "^4",
27
+
"typescript": "^5"
22
28
}
23
29
}
public/logo.png
public/logo.png
This is a binary file and will not be displayed.