diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 17c6c27..8aa09ad 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -796,7 +796,7 @@ export function Layout({ children }: LayoutProps) { to="/supporters" className="hover:text-[#ccff00] transition-colors font-medium" > - Supporters + Collaborators & Friends Not sponsored by diff --git a/src/pages/SupportersPage.tsx b/src/pages/SupportersPage.tsx index fbfdffa..f03902d 100644 --- a/src/pages/SupportersPage.tsx +++ b/src/pages/SupportersPage.tsx @@ -2,12 +2,84 @@ 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 } from 'lucide-react'; +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) + 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: 'Geyser', + logo: 'https://geyser.fund/logo-brand.svg', + url: 'https://geyser.fund/project/skatespots', + description: 'Bitcoin Lightning crowdfunding', + }, + { + name: 'Angor', + logo: '', + url: 'https://angor.io/', + description: 'Bitcoin crowdfunding platform', + }, + { + name: 'Zapstore', + logo: '', + url: 'https://zapstore.dev/apps/org.skatespots.app', + description: 'Open-source app distribution', + }, + { + name: 'ContextVM', + logo: '', + url: 'https://contextvm.org/s/e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530', + description: 'Nostr AI infrastructure', + }, + { + name: 'MKStack', + logo: '', + url: 'https://soapbox.pub/mkstack', + description: 'Nostr client framework', + }, +]; + +const FRIENDS: Friend[] = [ + // Add friends here once logos are ready + // Format: { name: 'Example', logo: 'https://example.com/logo.png', url: 'https://example.com', tagline: 'Short description' } + // Logo requirements: PNG/WebP with transparent background, min 200×200px square preferred +]; + +// ─── Helpers ───────────────────────────────────────────────────────────────── + interface GeyserContributor { user: { id: string; @@ -15,8 +87,7 @@ interface GeyserContributor { imageUrl?: string; heroLink?: string; }; - amount: number; // sats - funder?: boolean; + amount: number; } function formatSats(sats: number): string { @@ -38,12 +109,7 @@ function useGeyserLeaderboard() { projectGet(where: { name: "skatespots" }) { funders(input: { orderBy: { amountFunded: desc }, pagination: { take: 100 } }) { amountFunded - user { - id - username - imageUrl - heroLink - } + user { id username imageUrl heroLink } } } }`, @@ -59,34 +125,63 @@ function useGeyserLeaderboard() { return []; } }, - staleTime: 5 * 60 * 1000, // 5 min cache + 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}

@@ -96,35 +191,141 @@ function SupporterCard({ contributor }: { contributor: GeyserContributor }) { ); 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 ( - - {card} + +
+ {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. Powered by Geyser and Bitcoin Lightning.', + description: 'The community keeping SkateSpots alive — supporters, collaborators, partners and friends.', }); const { data: contributors = [], isLoading } = useGeyserLeaderboard(); return ( -
+
- {/* Hero */} -
+ {/* ── Hero ── */} +
⚡

OUR{' '} - SUPPORTERS + COMMUNITY

-

- SkateSpots is community-powered. These skaters and Bitcoiners help keep the project free, decentralized, and built for the streets. +

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

- {/* Individuals section */} -
-
- -

- Plebs -

-
-
-

- Every sat counts — ordered by cumulative contributions. -

+ {/* ── 1. Supporters ── */} +
+ {isLoading ? (
- {/* Mission section */} -
+ {/* ── 2. Collaborators ── */} +
+ + + {COLLABORATORS.length > 0 ? ( +
+ {COLLABORATORS.map((c) => ( + + ))} +
+ ) : ( +
+ +

Want to collaborate?

+

Developers, designers, skaters with skills — reach out.

+ + + View on GitHub + +
+ )} +
+ + {/* ── 3. Partners ── */} +
+ + +
+ {PARTNERS.map((p) => ( + + ))} +
+
+ + {/* ── 4. Friends ── */} +
+ + + {FRIENDS.length > 0 ? ( +
+ {FRIENDS.map((f) => ( + + ))} +
+ ) : ( + <> + {/* Placeholder grid — remove once real entries are added */} +
+ {Array.from({ length: 5 }).map((_, i) => ( + + ))} +
+
+

Want to be listed here?

+

+ Send a logo (PNG/WebP, transparent background, min 200×200px) and your URL via DM on Nostr. +

+
+ + )} +
+ + {/* ── Mission ── */} +
-
-
@@ -226,11 +486,9 @@ export default function SupportersPage() {

SkateSpots is 100% free, open-source, and built on Nostr — a decentralized protocol with no central server to shut it down. - We don't run ads, sell your data, or answer to investors. Keeping the lights on and building new features relies entirely on the skate community. - Every sat you contribute goes directly toward development, relay hosting, and keeping this tool free for every skater on earth. + 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.' }, @@ -246,29 +504,7 @@ export default function SupportersPage() {
- {/* Payment options */} -
-

- - Send Sats -

-
-

Via Geyser

-

- Contribute via the Geyser campaign — your name goes on the leaderboard above. -

- - Open campaign - -
-
- - {/* Footer links */} + {/* ── Footer ── */}

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

@@ -279,6 +515,7 @@ export default function SupportersPage() { FAQ

+
);