feat: replace Geyser with Angor across the entire app

Remove all Geyser.fund references and replace with the SkateSpots Angor
project (angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z).

- SupportersPage: remove Geyser leaderboard + API calls entirely; replace
  Supporters section with an Angor CTA card including the on-chain wallet
  address (bc1q2hdfyqa7utrxrd95rlr27shsdr6gtk7dzdqres)
- useGrowthStats: delete useGeyserStats hook + GEYSER_API_URL constant
- CommunityStatsPage: remove Geyser stats widget, replace with Angor CTA
- SasquatchGuide: update system prompt + FAQ (is it free, supporters)
- NewsletterPage: update both Geyser links (body + footer) to Angor
- AboutPage: remove Geyser row, update Angor entry with real project URL
- README.md: update crowdfunding section + credits

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-07-30 13:23:55 +07:00
parent d6b50de349
commit ef9bbea465
7 changed files with 48 additions and 213 deletions

View file

@ -24,7 +24,7 @@ A decentralized skateboarding community platform built on Nostr. Discover skate
- 🌤️ **Weather Integration** - Check conditions before you skate - 🌤️ **Weather Integration** - Check conditions before you skate
- 🔐 **Nostr Authentication** - Decentralized login with your Nostr keys - 🔐 **Nostr Authentication** - Decentralized login with your Nostr keys
- 📱 **PWA Support** - Install as a native app on any device - 📱 **PWA Support** - Install as a native app on any device
- ❤️ **Supporters** - Community funding via Geyser (Bitcoin Lightning) - ❤️ **Supporters** - Community funding via Angor (decentralized Bitcoin crowdfunding)
## 🛠️ Technology Stack ## 🛠️ Technology Stack
@ -231,7 +231,7 @@ See `/license` in the app for the full license page.
SkateSpots is 100% free, open-source, and community-funded. No ads, no data selling, no investors. SkateSpots is 100% free, open-source, and community-funded. No ads, no data selling, no investors.
- **Geyser**: https://geyser.fund/project/skatespots - **Angor**: https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z
- **Supporters page**: https://skate-spots.org/supporters - **Supporters page**: https://skate-spots.org/supporters
## 🙏 Credits ## 🙏 Credits
@ -239,7 +239,7 @@ SkateSpots is 100% free, open-source, and community-funded. No ads, no data sell
- **Built with**: [MKStack](https://soapbox.pub/mkstack) - Nostr client framework - **Built with**: [MKStack](https://soapbox.pub/mkstack) - Nostr client framework
- **Vibed with**: [Shakespeare](https://shakespeare.diy) - AI-powered development - **Vibed with**: [Shakespeare](https://shakespeare.diy) - AI-powered development
- **Powered by**: [Nostr](https://nostr.com) - Decentralized protocol - **Powered by**: [Nostr](https://nostr.com) - Decentralized protocol
- **Funded by**: [Geyser](https://geyser.fund/project/skatespots) - Bitcoin Lightning crowdfunding - **Funded by**: [Angor](https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z) - Decentralized Bitcoin crowdfunding
## 🔗 Links ## 🔗 Links

View file

@ -213,7 +213,7 @@ Note: this returns a rich itinerary CARD — after calling it, add a short one-l
- Nutzaps (NIP-61) = ecash tips sent over Nostr; you can set up nutzap RECEIVING in the wallet (sending full nutzaps is rolling out — use Lightning zaps for now). - Nutzaps (NIP-61) = ecash tips sent over Nostr; you can set up nutzap RECEIVING in the wallet (sending full nutzaps is rolling out — use Lightning zaps for now).
- CLINK (noffer) = an optional reusable Nostr-native payment code a shop/crew/Lab pro can add so you can pay them without a Lightning address. Shows a "Pay via CLINK" button. - CLINK (noffer) = an optional reusable Nostr-native payment code a shop/crew/Lab pro can add so you can pay them without a Lightning address. Shows a "Pay via CLINK" button.
- goskatespots.nostr1.com = primary community relay - goskatespots.nostr1.com = primary community relay
- 100% free, no ads, no tracking, community-funded via Geyser (see Supporters). 🛹`; - 100% free, no ads, no tracking, community-funded via Angor (see Supporters). 🛹`;
// ─── Personal tools (Sasquatch Pro) — only offered when the user is logged in ─ // ─── Personal tools (Sasquatch Pro) — only offered when the user is logged in ─
// These read the user's OWN data client-side (Stash is decrypted locally). // These read the user's OWN data client-side (Stash is decrypted locally).
@ -418,7 +418,7 @@ const STATIC_FAQ: Record<string, string> = {
'how to login': "Two ways:\n1. **Browser extension** (Desktop): Install Alby or nos2x, then click 'Log in'\n2. **New user**: Click 'Sign up' to generate keys. Download your nsec (secret key) and SAVE IT — this is your account forever!", 'how to login': "Two ways:\n1. **Browser extension** (Desktop): Install Alby or nos2x, then click 'Log in'\n2. **New user**: Click 'Sign up' to generate keys. Download your nsec (secret key) and SAVE IT — this is your account forever!",
'what is cashu': "Built-in Bitcoin wallet! Cashu ecash tokens backed by Lightning — works offline at skate spots, privacy-focused (mints can't track payments). Go to Wallet in Settings to use it. 🥜", 'what is cashu': "Built-in Bitcoin wallet! Cashu ecash tokens backed by Lightning — works offline at skate spots, privacy-focused (mints can't track payments). Go to Wallet in Settings to use it. 🥜",
'what is nwc': "Nostr Wallet Connect — like USB-C for Lightning wallets. Connect Alby, Minibits, Zeus, or Coinos once, and zap with one click forever. Encrypted, you control spending limits. 🔌⚡", 'what is nwc': "Nostr Wallet Connect — like USB-C for Lightning wallets. Connect Alby, Minibits, Zeus, or Coinos once, and zap with one click forever. Encrypted, you control spending limits. 🔌⚡",
'is it free': "100% free. No ads, no subscriptions, no corporate BS. Built by skaters for skaters. Community-funded via Geyser — check the Supporters page. You can also earn Bitcoin through community zaps for contributing quality spots. 🛹", 'is it free': "100% free. No ads, no subscriptions, no corporate BS. Built by skaters for skaters. Community-funded via Angor — check the Supporters page. You can also earn Bitcoin through community zaps for contributing quality spots. 🛹",
'what can i do': "Discover spots worldwide on the map, hire pros in The Lab (coaches, filmers, photographers, editors, ramp builders), roll with a crew, join events, take tours, buy/sell gear, track your private Stash, zap creators, check spot weather, and build the decentralized skate community!", 'what can i do': "Discover spots worldwide on the map, hire pros in The Lab (coaches, filmers, photographers, editors, ramp builders), roll with a crew, join events, take tours, buy/sell gear, track your private Stash, zap creators, check spot weather, and build the decentralized skate community!",
'what is the lab': "The Lab is where you hire skate-scene pros: Coaches, Filmers, Photographers, Video Editors, and ramp/park/DIY Builders. Each can be a solo person or a collective (crew/studio) — members confirm with their own keys so it's legit. All on Nostr, no fake accounts. 🎓", 'what is the lab': "The Lab is where you hire skate-scene pros: Coaches, Filmers, Photographers, Video Editors, and ramp/park/DIY Builders. Each can be a solo person or a collective (crew/studio) — members confirm with their own keys so it's legit. All on Nostr, no fake accounts. 🎓",
'what is a crew': "Crews are your map-anchored skate family — who you skate WITH, not who you hire. Each crew has home turf on the map (teal pins), home spots, members, vibes and a manifesto. Members confirm from their own key. Start one at Crews → 'Start a Crew'. 🤘", 'what is a crew': "Crews are your map-anchored skate family — who you skate WITH, not who you hire. Each crew has home turf on the map (teal pins), home spots, members, vibes and a manifesto. Members confirm from their own key. Start one at Crews → 'Start a Crew'. 🤘",
@ -433,7 +433,7 @@ const STATIC_FAQ: Record<string, string> = {
'what are tours': "Tours are guided skate trips — local sessions, national road trips, or world destruction tours. Find them on the map or the Tours section, meet up, and skate new terrain with locals. 🗺️🛹", 'what are tours': "Tours are guided skate trips — local sessions, national road trips, or world destruction tours. Find them on the map or the Tours section, meet up, and skate new terrain with locals. 🗺️🛹",
'what is a shop': "Skate Shops are local shops listed on the map. Discover and support real brick-and-mortar (or online) skate shops in your area — some even accept Bitcoin. 🏪", 'what is a shop': "Skate Shops are local shops listed on the map. Discover and support real brick-and-mortar (or online) skate shops in your area — some even accept Bitcoin. 🏪",
'how to sell gear': "Head to Gear Swap / Marketplace and create a listing (NIP-99). Add photos, price in sats, condition, and optionally pin it to a spot so it shows on the map. Buyers zap or message you directly. 🛹💸", 'how to sell gear': "Head to Gear Swap / Marketplace and create a listing (NIP-99). Add photos, price in sats, condition, and optionally pin it to a spot so it shows on the map. Buyers zap or message you directly. 🛹💸",
'supporters': "SkateSpots is community-funded via Geyser (Bitcoin Lightning crowdfunding) — no ads, no investors, no data selling. Check the Supporters page to chip in and keep the streets free. ❤️⚡", 'supporters': "SkateSpots is community-funded via Angor (decentralized Bitcoin crowdfunding) — no ads, no investors, no data selling. Check the Supporters page to back the project and keep the streets free. ❤️⚡",
'trust score': "Trust scores (0-100) powered by Relatr. Analyzes your Nostr network: followers, activity, engagement, zaps. Higher = more trusted. Helps filter bots and spam. Check any profile to see their score. 🛡️", 'trust score': "Trust scores (0-100) powered by Relatr. Analyzes your Nostr network: followers, activity, engagement, zaps. Higher = more trusted. Helps filter bots and spam. Check any profile to see their score. 🛡️",
'what relay': "SkateSpots uses: 🛹 goskatespots.nostr1.com (primary, allowlisted for quality) + relay.ditto.pub (global) + purplepag.es (profile aggregator). Your data is replicated across all of them for redundancy.", 'what relay': "SkateSpots uses: 🛹 goskatespots.nostr1.com (primary, allowlisted for quality) + relay.ditto.pub (global) + purplepag.es (profile aggregator). Your data is replicated across all of them for redundancy.",
'trip planner': "Tell me a city and I'll build a day-by-day skate trip 🗺️ — spots, events, coaches and local crews, weather-aware (rainy day = crew tip for covered spots). Try 'plan me 3 days in Lisbon'. I can save it all to a Collection, and hand you off to flights/stays on the real sites (no KYC, no middleman). 🛹", 'trip planner': "Tell me a city and I'll build a day-by-day skate trip 🗺️ — spots, events, coaches and local crews, weather-aware (rainy day = crew tip for covered spots). Try 'plan me 3 days in Lisbon'. I can save it all to a Collection, and hand you off to flights/stays on the real sites (no KYC, no middleman). 🛹",

View file

@ -291,43 +291,4 @@ export function useGrowthStats() {
}); });
} }
// ─── Geyser supporter count ──────────────────────────────────────────────────
const GEYSER_API_URL = 'https://api.geyser.fund/graphql';
export interface GeyserStats {
supporterCount: number;
totalSats: number;
}
export function useGeyserStats() {
return useQuery<GeyserStats>({
queryKey: ['geyser-stats'],
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" }) {
fundersCount
balance
}
}`,
}),
});
const json = await res.json();
const project = json?.data?.projectGet;
return {
supporterCount: project?.fundersCount ?? 0,
totalSats: project?.balance ?? 0,
};
} catch {
return { supporterCount: 0, totalSats: 0 };
}
},
staleTime: 15 * 60 * 1000,
retry: false,
});
}

View file

@ -263,8 +263,7 @@ Version: v0.1.0`;
{ label: 'Built with', name: 'MKStack', url: 'https://soapbox.pub/mkstack', desc: 'Nostr client framework' }, { label: 'Built with', name: 'MKStack', url: 'https://soapbox.pub/mkstack', desc: 'Nostr client framework' },
{ label: 'Vibed with', name: 'Shakespeare', url: 'https://shakespeare.diy', desc: 'AI-powered development' }, { label: 'Vibed with', name: 'Shakespeare', url: 'https://shakespeare.diy', desc: 'AI-powered development' },
{ label: 'Powered by', name: 'Nostr', url: 'https://nostr.com', desc: 'Decentralized protocol' }, { label: 'Powered by', name: 'Nostr', url: 'https://nostr.com', desc: 'Decentralized protocol' },
{ label: 'Crowdfunding', name: 'Geyser', url: 'https://geyser.fund/project/skatespots', desc: 'Centralized Bitcoin Lightning crowdfunding' }, { label: 'Crowdfunding', name: 'Angor', url: 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z', desc: 'Decentralized Bitcoin crowdfunding' },
{ label: 'Crowdfunding', name: 'Angor', url: 'https://angor.io/', desc: 'Bitcoin crowdfunding platform' },
{ label: 'Distributed on', name: 'Zapstore', url: 'https://zapstore.dev/apps/org.skatespots.app', desc: 'Open-source app store' }, { label: 'Distributed on', name: 'Zapstore', url: 'https://zapstore.dev/apps/org.skatespots.app', desc: 'Open-source app store' },
{ label: 'AI powered by', name: 'ContextVM', url: 'https://contextvm.org/s/e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530', desc: 'Nostr AI infrastructure' }, { label: 'AI powered by', name: 'ContextVM', url: 'https://contextvm.org/s/e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530', desc: 'Nostr AI infrastructure' },
].map((item) => ( ].map((item) => (

View file

@ -7,7 +7,9 @@ import { Skeleton } from '@/components/ui/skeleton';
import { useAuthor } from '@/hooks/useAuthor'; import { useAuthor } from '@/hooks/useAuthor';
import { genUserName } from '@/lib/genUserName'; import { genUserName } from '@/lib/genUserName';
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
import { useGrowthStats, useGeyserStats } from '@/hooks/useGrowthStats'; import { useGrowthStats } from '@/hooks/useGrowthStats';
const ANGOR_PROJECT_URL = 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z';
import { SKATE_RELAY } from '@/lib/types'; import { SKATE_RELAY } from '@/lib/types';
import { import {
AreaChart, Area, AreaChart, Area,
@ -152,7 +154,7 @@ export default function CommunityStatsPage() {
}); });
const { data: stats, isLoading, isError } = useGrowthStats(); const { data: stats, isLoading, isError } = useGrowthStats();
const { data: geyser } = useGeyserStats();
// Content mix for donut chart // Content mix for donut chart
const contentMix = useMemo(() => { const contentMix = useMemo(() => {
@ -460,44 +462,27 @@ export default function CommunityStatsPage() {
<div> <div>
<h3 className="text-xl font-black uppercase text-[#ccff00] mb-1">Funded by the Community</h3> <h3 className="text-xl font-black uppercase text-[#ccff00] mb-1">Funded by the Community</h3>
<p className="text-sm text-gray-500 max-w-sm mb-4"> <p className="text-sm text-gray-500 max-w-sm mb-4">
Bitcoin Lightning supporters via Geyser Fund.<br /> Decentralized Bitcoin crowdfunding via Angor.<br />
No ads, no investors, no data selling. No ads, no investors, no data selling.
</p> </p>
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
<a <a
href="https://geyser.fund/project/skatespots" href={ANGOR_PROJECT_URL}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-[#ff0040]/15 border border-[#ff0040]/40 text-[#ff0040] text-xs font-bold hover:bg-[#ff0040]/25 transition-colors" className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-[#ff0040]/15 border border-[#ff0040]/40 text-[#ff0040] text-xs font-bold hover:bg-[#ff0040]/25 transition-colors"
> >
<Zap className="h-3.5 w-3.5" /> Support on Geyser <Zap className="h-3.5 w-3.5" /> Back us on Angor
<ExternalLink className="h-3 w-3 opacity-60" /> <ExternalLink className="h-3 w-3 opacity-60" />
</a> </a>
<Link <Link
to="/supporters" to="/supporters"
className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-[#ccff00]/8 border border-[#ccff00]/30 text-[#ccff00] text-xs font-bold hover:bg-[#ccff00]/15 transition-colors" className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-[#ccff00]/8 border border-[#ccff00]/30 text-[#ccff00] text-xs font-bold hover:bg-[#ccff00]/15 transition-colors"
> >
<Users className="h-3.5 w-3.5" /> View all supporters → <Users className="h-3.5 w-3.5" /> Community →
</Link> </Link>
</div> </div>
</div> </div>
<div className="text-right flex-shrink-0">
{geyser ? (
<>
<p className="text-5xl font-black text-[#ccff00] leading-none">
{geyser.supporterCount}
</p>
<p className="text-sm text-gray-500 mt-1">supporters</p>
{geyser.totalSats > 0 && (
<p className="text-xs text-[#f7931a] font-bold mt-1">
⚡ {formatSats(geyser.totalSats)} sats raised
</p>
)}
</>
) : (
<Skeleton className="w-20 h-14" />
)}
</div>
</div> </div>
</div> </div>
</section> </section>

View file

@ -835,16 +835,16 @@ function IssueArticle({ issue }: { issue: NewsletterIssue }) {
<SectionHeader emoji="⚡" label="Support the Project" color="#f7931a" /> <SectionHeader emoji="⚡" label="Support the Project" color="#f7931a" />
<div className="mt-4 p-5 rounded-2xl border-2 border-[#f7931a]/30 bg-[#f7931a]/5"> <div className="mt-4 p-5 rounded-2xl border-2 border-[#f7931a]/30 bg-[#f7931a]/5">
<p className="text-sm text-gray-300 mb-3"> <p className="text-sm text-gray-300 mb-3">
100% open source, community funded, no ads, no investors. If it's useful zap it or back it on Geyser. 100% open source, community funded, no ads, no investors. If it's useful zap it or back it on Angor.
</p> </p>
<a <a
href="https://geyser.fund/project/skatespots" href="https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 text-[#f7931a] text-sm font-bold hover:opacity-80 transition-opacity" className="inline-flex items-center gap-2 text-[#f7931a] text-sm font-bold hover:opacity-80 transition-opacity"
> >
<Zap className="h-4 w-4" /> <Zap className="h-4 w-4" />
geyser.fund/project/skatespots angor.io — SkateSpots
<ArrowRight className="h-3.5 w-3.5" /> <ArrowRight className="h-3.5 w-3.5" />
</a> </a>
</div> </div>
@ -906,7 +906,7 @@ export default function NewsletterPage() {
Open source Open source
</Link> </Link>
{' · '} {' · '}
<a href="https://geyser.fund/project/skatespots" target="_blank" rel="noopener noreferrer" className="text-gray-500 hover:text-[#ccff00] transition-colors"> <a href="https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z" target="_blank" rel="noopener noreferrer" className="text-gray-500 hover:text-[#ccff00] transition-colors">
Support us Support us
</a> </a>
</p> </p>

View file

@ -1,12 +1,9 @@
import { useSeoMeta } from '@unhead/react'; import { useSeoMeta } from '@unhead/react';
import { useQuery } from '@tanstack/react-query';
import { Layout } from '@/components/Layout'; import { Layout } from '@/components/Layout';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { ExternalLink, Zap, Heart, Users, Handshake, Building2, Star } 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 ANGOR_PROJECT_URL = 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z';
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 ────────────────── // ─── Static data — edit these arrays to add/remove entries ──────────────────
@ -69,58 +66,6 @@ const FRIENDS: Friend[] = [
// Add more friends here // 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<GeyserContributor[]>({
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 ─────────────────────────────────────────────────────────── // ─── Section header ───────────────────────────────────────────────────────────
function SectionHeader({ function SectionHeader({
@ -148,43 +93,6 @@ function SectionHeader({
); );
} }
// ─── 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 = (
<div className="flex flex-col items-center gap-2 p-4 rounded-xl bg-black/60 border border-white/10 hover:border-[#ccff00]/40 transition-all group">
<div className="relative">
{contributor.user.imageUrl ? (
<img
src={contributor.user.imageUrl}
alt={contributor.user.username || 'Anon'}
className="w-12 h-12 rounded-full object-cover border-2 border-white/10 group-hover:border-[#ccff00]/50 transition-all"
/>
) : (
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-[#ff0040]/20 to-[#ccff00]/20 border-2 border-white/10 flex items-center justify-center text-xl">
🛹
</div>
)}
<div className="absolute -bottom-1 -right-1 bg-[#f7931a] rounded-full w-4 h-4 flex items-center justify-center">
<Zap className="h-2 w-2 text-white" />
</div>
</div>
<p className="text-xs font-bold text-center text-white truncate max-w-full w-full text-center">
{isAnon ? 'Anon 🛹' : contributor.user.username}
</p>
<p className="text-[10px] font-bold text-[#f7931a]">
{formatSats(contributor.amount)} sats
</p>
</div>
);
if (isAnon) return card;
return <a href={profileUrl} target="_blank" rel="noopener noreferrer">{card}</a>;
}
// ─── Collaborator card ──────────────────────────────────────────────────────── // ─── Collaborator card ────────────────────────────────────────────────────────
function CollaboratorCard({ collab }: { collab: Collaborator }) { function CollaboratorCard({ collab }: { collab: Collaborator }) {
@ -302,8 +210,6 @@ export default function SupportersPage() {
description: 'The community keeping SkateSpots alive — supporters, collaborators, partners and friends.', description: 'The community keeping SkateSpots alive — supporters, collaborators, partners and friends.',
}); });
const { data: contributors = [], isLoading } = useGeyserLeaderboard();
return ( return (
<Layout> <Layout>
<div className="container max-w-4xl px-4 py-12 md:py-16 space-y-16"> <div className="container max-w-4xl px-4 py-12 md:py-16 space-y-16">
@ -319,66 +225,50 @@ export default function SupportersPage() {
SkateSpots is built by and for the community. Supporters, collaborators, partners and friends keeping this free and decentralized. SkateSpots is built by and for the community. Supporters, collaborators, partners and friends keeping this free and decentralized.
</p> </p>
<a <a
href={GEYSER_PROJECT_URL} href={ANGOR_PROJECT_URL}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-6 py-3 rounded-full bg-gradient-to-r from-[#ff0040] to-[#ff6600] text-white font-black uppercase text-sm hover:opacity-90 transition-all shadow-lg shadow-[#ff0040]/30" className="inline-flex items-center gap-2 px-6 py-3 rounded-full bg-gradient-to-r from-[#ff0040] to-[#ff6600] text-white font-black uppercase text-sm hover:opacity-90 transition-all shadow-lg shadow-[#ff0040]/30"
> >
<Zap className="h-4 w-4" /> <Zap className="h-4 w-4" />
Become a Supporter Back us on Angor
<ExternalLink className="h-3.5 w-3.5 opacity-70" /> <ExternalLink className="h-3.5 w-3.5 opacity-70" />
</a> </a>
</div> </div>
{/* ── 1. Supporters ── */} {/* ── 1. Support ── */}
<section> <section>
<SectionHeader <SectionHeader
icon={Zap} icon={Zap}
title="Supporters" title="Support"
subtitle="Bitcoin contributors via Geyser — ordered by cumulative sats." subtitle="Fund SkateSpots on Angor — decentralized, on-chain Bitcoin crowdfunding. No middleman, no KYC."
color="#f7931a" color="#f7931a"
/> />
{isLoading ? ( <div className="rounded-2xl border-2 border-[#f7931a]/30 bg-black/60 p-8 text-center">
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-3"> <div className="text-5xl mb-4">🧡⚡</div>
{Array.from({ length: 12 }).map((_, i) => ( <h3 className="text-xl font-black uppercase mb-2 text-white">Back the project on Angor</h3>
<div key={i} className="flex flex-col items-center gap-2 p-4 rounded-xl bg-black/40 border border-white/5 animate-pulse"> <p className="text-sm text-gray-400 max-w-lg mx-auto mb-6 leading-relaxed">
<div className="w-12 h-12 rounded-full bg-white/10" /> Angor is a decentralized Bitcoin crowdfunding platform built on Nostr — your funds stay
<div className="h-3 w-14 bg-white/10 rounded" /> in your control, released in stages, with no custodian holding the pot. Every sat goes
<div className="h-3 w-10 bg-white/5 rounded" /> to development, relay hosting, and keeping SkateSpots free for every skater on earth.
</div> </p>
))} <p className="text-xs text-gray-600 font-mono mb-4 break-all">
</div> ⛓ bc1q2hdfyqa7utrxrd95rlr27shsdr6gtk7dzdqres
) : contributors.length > 0 ? ( </p>
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-3"> <div className="flex flex-wrap items-center justify-center gap-3">
{contributors.map((c, i) => (
<SupporterCard key={`${c.user.id}-${i}`} contributor={c} />
))}
</div>
) : (
<div className="text-center py-12 rounded-2xl border border-dashed border-white/10">
<div className="text-4xl mb-3">🛹</div>
<p className="text-gray-500 mb-2">Be the first supporter!</p>
<p className="text-sm text-gray-600">The leaderboard updates once contributions come in.</p>
</div>
)}
<div className="mt-6 flex flex-wrap items-center justify-between gap-4">
<a <a
href={GEYSER_LEADERBOARD_URL} href={ANGOR_PROJECT_URL}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-full border-2 border-[#f7931a]/30 text-[#f7931a] font-bold text-sm hover:bg-[#f7931a]/10 transition-all" className="inline-flex items-center gap-2 px-5 py-2.5 rounded-full border-2 border-[#f7931a]/40 text-[#f7931a] font-bold text-sm hover:bg-[#f7931a]/10 transition-all"
> >
<ExternalLink className="h-4 w-4" /> <ExternalLink className="h-4 w-4" />
Full Leaderboard on Geyser Open Angor
</a>
<div className="flex items-center gap-2 text-xs text-gray-500">
<span>Powered by</span>
<a href={GEYSER_PROJECT_URL} target="_blank" rel="noopener noreferrer"
className="font-bold text-gray-400 hover:text-white transition-colors">
Geyser 🧦
</a> </a>
<span className="text-xs text-gray-500">
or ⚡ zap any spot, crew or creator directly in the app
</span>
</div> </div>
</div> </div>
</section> </section>