import { useSeoMeta } from '@unhead/react'; import { useQuery } from '@tanstack/react-query'; import { Layout } from '@/components/Layout'; import { Link } from 'react-router-dom'; import { ExternalLink, Zap, Heart, Users, Handshake, Building2, Star } from 'lucide-react'; const GEYSER_PROJECT_URL = 'https://geyser.fund/project/skatespots?hero=satsnfacts'; const GEYSER_LEADERBOARD_URL = 'https://geyser.fund/project/skatespots/leaderboard'; const GEYSER_API_URL = 'https://api.geyser.fund/graphql'; // ─── Static data — edit these arrays to add/remove entries ────────────────── interface Collaborator { name: string; role: string; avatar?: string; // URL or leave blank for placeholder npub?: string; // Nostr npub — links to their profile url?: string; // fallback link } interface Partner { name: string; logo?: string; // URL to logo (SVG/PNG transparent preferred) url: string; description: string; } interface Friend { name: string; logo?: string; // URL to logo (SVG/PNG transparent preferred, min 200×200px) invertLogo?: boolean; // true if logo is dark and needs inversion for dark backgrounds url: string; tagline?: string; } const COLLABORATORS: Collaborator[] = [ // Add collaborators here — mix of Nostr users and external people // Example: // { name: 'Alice', role: 'Developer', npub: 'npub1...', avatar: 'https://...' }, // { name: 'Bob', role: 'Relay Operator', url: 'https://...' }, ]; const PARTNERS: Partner[] = [ { name: 'SNT4EVER', logo: 'https://blossom.ditto.pub/f6e1d75ed8123b860ca139ee2d43a0a1238b89a0bec24752d9fe48cc41aa5d4c.png', url: 'https://www.snt4ever.cat/', description: 'Bringing back up Sants Estació', }, // Add partners here // Format: { name: 'Example', logo: 'https://...', url: 'https://...', description: 'Short description' } // Logo: PNG/WebP transparent background preferred ]; const FRIENDS: Friend[] = [ { name: 'No Teeth Skateboarding', logo: 'https://blossom.dreamith.to/61bb7055930c442dfe1dfe5b8a8be5417f9b62f68ed79dc4ffbbddce5d4f122c.png', url: 'https://www.instagram.com/noteethskateboarding/', tagline: 'Hand-made Long Lasting Skateboards', }, { name: 'SNT4EVER', logo: 'https://blossom.ditto.pub/f6e1d75ed8123b860ca139ee2d43a0a1238b89a0bec24752d9fe48cc41aa5d4c.png', invertLogo: true, url: 'https://www.snt4ever.cat/', tagline: 'Bringing back up Sants Estació', }, // Add more friends here ]; // ─── Helpers ───────────────────────────────────────────────────────────────── interface GeyserContributor { user: { id: string; username: string; imageUrl?: string; heroLink?: string; }; amount: number; } function formatSats(sats: number): string { if (sats >= 1_000_000) return `${(sats / 1_000_000).toFixed(2)}M`; if (sats >= 1_000) return `${Math.round(sats / 1_000).toLocaleString()}k`; return sats.toLocaleString(); } function useGeyserLeaderboard() { return useQuery({ queryKey: ['geyser-leaderboard'], queryFn: async () => { try { const res = await fetch(GEYSER_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: `{ projectGet(where: { name: "skatespots" }) { funders(input: { orderBy: { amountFunded: desc }, pagination: { take: 100 } }) { amountFunded user { id username imageUrl heroLink } } } }`, }), }); const json = await res.json(); const funders = json?.data?.projectGet?.funders ?? []; return funders.map((f: { amountFunded: number; user: GeyserContributor['user'] }) => ({ user: f.user, amount: f.amountFunded, })); } catch { return []; } }, staleTime: 5 * 60 * 1000, retry: false, }); } // ─── Section header ─────────────────────────────────────────────────────────── function SectionHeader({ icon: Icon, title, subtitle, color = '#ccff00', }: { icon: React.ComponentType<{ className?: string }>; title: string; subtitle?: string; color?: string; }) { return (

{title}

{subtitle &&

{subtitle}

}
); } // ─── Supporter card (Geyser contributors) ──────────────────────────────────── function SupporterCard({ contributor }: { contributor: GeyserContributor }) { const isAnon = !contributor.user.username || contributor.user.username.toLowerCase().startsWith('anon'); const profileUrl = contributor.user.heroLink || `https://geyser.fund/user/${contributor.user.id}`; const card = (
{contributor.user.imageUrl ? ( {contributor.user.username ) : (
🛹
)}

{isAnon ? 'Anon 🛹' : contributor.user.username}

{formatSats(contributor.amount)} sats

); if (isAnon) return card; return {card}; } // ─── Collaborator card ──────────────────────────────────────────────────────── function CollaboratorCard({ collab }: { collab: Collaborator }) { const href = collab.npub ? `/${collab.npub}` : collab.url; const inner = (
{collab.avatar ? ( {collab.name} ) : (
🛹
)}

{collab.name}

{collab.role}

{href && }
); if (!href) return inner; if (collab.npub) return {inner}; return {inner}; } // ─── Partner card ───────────────────────────────────────────────────────────── function PartnerCard({ partner }: { partner: Partner }) { return (
{partner.logo ? ( {partner.name} { (e.target as HTMLImageElement).style.display = 'none'; }} /> ) : ( )}

{partner.name}

{partner.description}

); } // ─── Friend card ────────────────────────────────────────────────────────────── function FriendCard({ friend }: { friend: Friend }) { return (
{friend.logo ? ( {friend.name} { (e.target as HTMLImageElement).style.display = 'none'; }} /> ) : ( 🛹 )}

{friend.name}

{friend.tagline &&

{friend.tagline}

}
); } // ─── Placeholder card (empty slot) ─────────────────────────────────────────── function PlaceholderCard({ label }: { label: string }) { return (
?

{label}

); } // ─── Main page ──────────────────────────────────────────────────────────────── export default function SupportersPage() { useSeoMeta({ title: 'Supporters - SkateSpots', description: 'The community keeping SkateSpots alive — supporters, collaborators, partners and friends.', }); const { data: contributors = [], isLoading } = useGeyserLeaderboard(); return (
{/* ── Hero ── */}
⚡

OUR{' '} COMMUNITY

SkateSpots is built by and for the community. Supporters, collaborators, partners and friends keeping this free and decentralized.

Become a Supporter
{/* ── 1. Supporters ── */}
{isLoading ? (
{Array.from({ length: 12 }).map((_, i) => (
))}
) : contributors.length > 0 ? (
{contributors.map((c, i) => ( ))}
) : (
🛹

Be the first supporter!

The leaderboard updates once contributions come in.

)}
{/* ── 2. Collaborators ── */}
{COLLABORATORS.length > 0 ? (
{COLLABORATORS.map((c) => ( ))}
) : (

Want to collaborate?

Developers, designers, skaters with skills — reach out.

View on NostrHub
)}
{/* ── 3. Partners ── */}
{PARTNERS.length > 0 ? (
{PARTNERS.map((p) => ( ))}
) : ( <>
{Array.from({ length: 4 }).map((_, i) => ( ))}

Want to partner with SkateSpots? Reach out on Nostr.

)}
{/* ── 4. Friends ── */}
{FRIENDS.map((f) => ( ))} {/* Fill remaining slots up to 5 with placeholders */} {FRIENDS.length < 5 && Array.from({ length: 5 - FRIENDS.length }).map((_, i) => ( ))}

Want to be listed here?

Send a logo (PNG/WebP, transparent background, min 200×200px) and your URL via DM on Nostr, only if you have the vibes.

{/* ── Mission ── */}

Why We Need Support

SkateSpots is 100% free, open-source, and built on Nostr — a decentralized protocol with no central server to shut it down. No ads, no data selling, no investors. Every sat goes directly toward development, relay hosting, and keeping this free for every skater on earth.

{[ { emoji: '🛹', title: 'Nostr-native', desc: 'No central server. Your data lives on relays you choose.' }, { emoji: '⚡', title: 'Bitcoin powered', desc: 'Earn sats for mapping spots. Spend them at local shops.' }, { emoji: '🌍', title: 'Free forever', desc: 'Open source, no paywalls, no tracking, no VC funding.' }, ].map(({ emoji, title, desc }) => (
{emoji}

{title}

{desc}

))}
{/* ── Footer ── */}

Thank you. Every sat keeps the spots on the map. 🛹⚡

License {' '}·{' '} About {' '}·{' '} FAQ

); }