initial commit

daviddao 2f296b52 6d5fb7c1

+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 + [![Next.js](https://img.shields.io/badge/Next.js-15.5.2-black)](https://nextjs.org/) 4 + [![React](https://img.shields.io/badge/React-19.1.0-blue)](https://reactjs.org/) 5 + [![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue)](https://www.typescriptlang.org/) 6 + [![Tailwind CSS](https://img.shields.io/badge/Tailwind_CSS-4.0-38B2AC)](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
··· 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

This is a binary file and will not be displayed.

+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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

This is a binary file and will not be displayed.