feat: redesign /supporters with 4 sections + footer label update
- Supporters: existing Geyser leaderboard (unchanged) - Collaborators: new section, empty placeholder with GitHub CTA - Partners: Geyser, Angor, Zapstore, ContextVM, MKStack - Friends: placeholder grid (5 slots) + logo spec instructions - Footer: "Supporters" → "Collaborators & Friends" - Static COLLABORATORS/PARTNERS/FRIENDS arrays at top of file for easy editing Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
5dffa5cac3
commit
baf4c5e10d
2 changed files with 336 additions and 99 deletions
|
|
@ -796,7 +796,7 @@ export function Layout({ children }: LayoutProps) {
|
||||||
to="/supporters"
|
to="/supporters"
|
||||||
className="hover:text-[#ccff00] transition-colors font-medium"
|
className="hover:text-[#ccff00] transition-colors font-medium"
|
||||||
>
|
>
|
||||||
Supporters
|
Collaborators & Friends
|
||||||
</Link>
|
</Link>
|
||||||
<span className="flex items-center gap-1.5">
|
<span className="flex items-center gap-1.5">
|
||||||
<span>Not sponsored by</span>
|
<span>Not sponsored by</span>
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,84 @@ import { useSeoMeta } from '@unhead/react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
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 } 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_PROJECT_URL = 'https://geyser.fund/project/skatespots?hero=satsnfacts';
|
||||||
const GEYSER_LEADERBOARD_URL = 'https://geyser.fund/project/skatespots/leaderboard';
|
const GEYSER_LEADERBOARD_URL = 'https://geyser.fund/project/skatespots/leaderboard';
|
||||||
const GEYSER_API_URL = 'https://api.geyser.fund/graphql';
|
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 {
|
interface GeyserContributor {
|
||||||
user: {
|
user: {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -15,8 +87,7 @@ interface GeyserContributor {
|
||||||
imageUrl?: string;
|
imageUrl?: string;
|
||||||
heroLink?: string;
|
heroLink?: string;
|
||||||
};
|
};
|
||||||
amount: number; // sats
|
amount: number;
|
||||||
funder?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatSats(sats: number): string {
|
function formatSats(sats: number): string {
|
||||||
|
|
@ -38,12 +109,7 @@ function useGeyserLeaderboard() {
|
||||||
projectGet(where: { name: "skatespots" }) {
|
projectGet(where: { name: "skatespots" }) {
|
||||||
funders(input: { orderBy: { amountFunded: desc }, pagination: { take: 100 } }) {
|
funders(input: { orderBy: { amountFunded: desc }, pagination: { take: 100 } }) {
|
||||||
amountFunded
|
amountFunded
|
||||||
user {
|
user { id username imageUrl heroLink }
|
||||||
id
|
|
||||||
username
|
|
||||||
imageUrl
|
|
||||||
heroLink
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`,
|
}`,
|
||||||
|
|
@ -59,34 +125,63 @@ function useGeyserLeaderboard() {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
staleTime: 5 * 60 * 1000, // 5 min cache
|
staleTime: 5 * 60 * 1000,
|
||||||
retry: false,
|
retry: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Section header ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function SectionHeader({
|
||||||
|
icon: Icon,
|
||||||
|
title,
|
||||||
|
subtitle,
|
||||||
|
color = '#ccff00',
|
||||||
|
}: {
|
||||||
|
icon: React.ComponentType<{ className?: string }>;
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
color?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="mb-6 md:mb-8">
|
||||||
|
<div className="flex items-center gap-3 mb-2">
|
||||||
|
<Icon className="h-5 w-5 flex-shrink-0" style={{ color }} />
|
||||||
|
<h2 className="text-2xl md:text-3xl font-black uppercase tracking-wide" style={{ color }}>
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
<div className="flex-1 h-px" style={{ background: `linear-gradient(to right, ${color}40, transparent)` }} />
|
||||||
|
</div>
|
||||||
|
{subtitle && <p className="text-gray-400 text-sm ml-8">{subtitle}</p>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Supporter card (Geyser contributors) ────────────────────────────────────
|
||||||
|
|
||||||
function SupporterCard({ contributor }: { contributor: GeyserContributor }) {
|
function SupporterCard({ contributor }: { contributor: GeyserContributor }) {
|
||||||
const isAnon = !contributor.user.username || contributor.user.username.toLowerCase().startsWith('anon');
|
const isAnon = !contributor.user.username || contributor.user.username.toLowerCase().startsWith('anon');
|
||||||
const profileUrl = contributor.user.heroLink || `https://geyser.fund/user/${contributor.user.id}`;
|
const profileUrl = contributor.user.heroLink || `https://geyser.fund/user/${contributor.user.id}`;
|
||||||
|
|
||||||
const card = (
|
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 min-w-0">
|
<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">
|
<div className="relative">
|
||||||
{contributor.user.imageUrl ? (
|
{contributor.user.imageUrl ? (
|
||||||
<img
|
<img
|
||||||
src={contributor.user.imageUrl}
|
src={contributor.user.imageUrl}
|
||||||
alt={contributor.user.username || 'Anon'}
|
alt={contributor.user.username || 'Anon'}
|
||||||
className="w-14 h-14 rounded-full object-cover border-2 border-white/10 group-hover:border-[#ccff00]/50 transition-all"
|
className="w-12 h-12 rounded-full object-cover border-2 border-white/10 group-hover:border-[#ccff00]/50 transition-all"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-14 h-14 rounded-full bg-gradient-to-br from-[#ff0040]/20 to-[#ccff00]/20 border-2 border-white/10 flex items-center justify-center text-2xl">
|
<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>
|
||||||
)}
|
)}
|
||||||
<div className="absolute -bottom-1 -right-1 bg-[#f7931a] rounded-full w-5 h-5 flex items-center justify-center">
|
<div className="absolute -bottom-1 -right-1 bg-[#f7931a] rounded-full w-4 h-4 flex items-center justify-center">
|
||||||
<Zap className="h-2.5 w-2.5 text-white" />
|
<Zap className="h-2 w-2 text-white" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs font-bold text-center text-white truncate max-w-full">
|
<p className="text-xs font-bold text-center text-white truncate max-w-full w-full text-center">
|
||||||
{isAnon ? 'Anon 🛹' : contributor.user.username}
|
{isAnon ? 'Anon 🛹' : contributor.user.username}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] font-bold text-[#f7931a]">
|
<p className="text-[10px] font-bold text-[#f7931a]">
|
||||||
|
|
@ -96,35 +191,141 @@ function SupporterCard({ contributor }: { contributor: GeyserContributor }) {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isAnon) return card;
|
if (isAnon) return card;
|
||||||
|
return <a href={profileUrl} target="_blank" rel="noopener noreferrer">{card}</a>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Collaborator card ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function CollaboratorCard({ collab }: { collab: Collaborator }) {
|
||||||
|
const href = collab.npub
|
||||||
|
? `/${collab.npub}`
|
||||||
|
: collab.url;
|
||||||
|
|
||||||
|
const inner = (
|
||||||
|
<div className="flex items-center gap-3 p-4 rounded-xl bg-black/60 border border-white/10 hover:border-[#ff0040]/40 transition-all group">
|
||||||
|
<div className="relative flex-shrink-0">
|
||||||
|
{collab.avatar ? (
|
||||||
|
<img
|
||||||
|
src={collab.avatar}
|
||||||
|
alt={collab.name}
|
||||||
|
className="w-12 h-12 rounded-full object-cover border-2 border-white/10 group-hover:border-[#ff0040]/50 transition-all"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-[#ff0040]/20 to-[#ff6600]/20 border-2 border-white/10 flex items-center justify-center text-xl">
|
||||||
|
🛹
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="font-bold text-sm text-white truncate">{collab.name}</p>
|
||||||
|
<p className="text-xs text-[#ff0040]/80 font-medium">{collab.role}</p>
|
||||||
|
</div>
|
||||||
|
{href && <ExternalLink className="h-3.5 w-3.5 text-gray-600 group-hover:text-[#ff0040] flex-shrink-0 transition-colors" />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!href) return inner;
|
||||||
|
if (collab.npub) return <Link to={`/${collab.npub}`}>{inner}</Link>;
|
||||||
|
return <a href={href} target="_blank" rel="noopener noreferrer">{inner}</a>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Partner card ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function PartnerCard({ partner }: { partner: Partner }) {
|
||||||
return (
|
return (
|
||||||
<a href={profileUrl} target="_blank" rel="noopener noreferrer">
|
<a
|
||||||
{card}
|
href={partner.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex items-center gap-3 p-4 rounded-xl bg-black/60 border border-white/10 hover:border-[#ff6600]/50 hover:bg-[#ff6600]/5 transition-all group"
|
||||||
|
>
|
||||||
|
<div className="w-10 h-10 rounded-lg bg-white/5 border border-white/10 flex items-center justify-center flex-shrink-0 overflow-hidden">
|
||||||
|
{partner.logo ? (
|
||||||
|
<img
|
||||||
|
src={partner.logo}
|
||||||
|
alt={partner.name}
|
||||||
|
className="w-7 h-7 object-contain brightness-0 invert opacity-70 group-hover:opacity-100 transition-opacity"
|
||||||
|
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Building2 className="h-5 w-5 text-gray-600 group-hover:text-[#ff6600] transition-colors" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="font-bold text-sm text-white group-hover:text-[#ff6600] transition-colors">{partner.name}</p>
|
||||||
|
<p className="text-xs text-gray-500 truncate">{partner.description}</p>
|
||||||
|
</div>
|
||||||
|
<ExternalLink className="h-3.5 w-3.5 text-gray-600 group-hover:text-[#ff6600] flex-shrink-0 transition-colors" />
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Friend card ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function FriendCard({ friend }: { friend: Friend }) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={friend.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="flex flex-col items-center gap-3 p-5 rounded-xl bg-black/60 border border-white/10 hover:border-[#ccff00]/40 hover:bg-[#ccff00]/5 transition-all group text-center"
|
||||||
|
>
|
||||||
|
<div className="w-16 h-16 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center overflow-hidden group-hover:border-[#ccff00]/40 transition-all">
|
||||||
|
{friend.logo ? (
|
||||||
|
<img
|
||||||
|
src={friend.logo}
|
||||||
|
alt={friend.name}
|
||||||
|
className="w-12 h-12 object-contain"
|
||||||
|
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span className="text-2xl">🛹</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-bold text-sm text-white group-hover:text-[#ccff00] transition-colors">{friend.name}</p>
|
||||||
|
{friend.tagline && <p className="text-xs text-gray-500 mt-0.5">{friend.tagline}</p>}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Placeholder card (empty slot) ───────────────────────────────────────────
|
||||||
|
|
||||||
|
function PlaceholderCard({ label }: { label: string }) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center gap-3 p-5 rounded-xl border border-dashed border-white/10 text-center opacity-40">
|
||||||
|
<div className="w-16 h-16 rounded-2xl border border-dashed border-white/20 flex items-center justify-center">
|
||||||
|
<span className="text-2xl">?</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-gray-600 font-medium">{label}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Main page ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
export default function SupportersPage() {
|
export default function SupportersPage() {
|
||||||
useSeoMeta({
|
useSeoMeta({
|
||||||
title: 'Supporters - SkateSpots',
|
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();
|
const { data: contributors = [], isLoading } = useGeyserLeaderboard();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<div className="container max-w-4xl px-4 py-12 md:py-16">
|
<div className="container max-w-4xl px-4 py-12 md:py-16 space-y-16">
|
||||||
|
|
||||||
{/* Hero */}
|
{/* ── Hero ── */}
|
||||||
<div className="text-center mb-14">
|
<div className="text-center">
|
||||||
<div className="text-6xl mb-4">⚡</div>
|
<div className="text-6xl mb-4">⚡</div>
|
||||||
<h1 className="text-5xl md:text-6xl font-black uppercase mb-3 leading-none">
|
<h1 className="text-5xl md:text-6xl font-black uppercase mb-3 leading-none">
|
||||||
<span className="neon-red">OUR</span>{' '}
|
<span className="neon-red">OUR</span>{' '}
|
||||||
<span className="neon-lime">SUPPORTERS</span>
|
<span className="neon-lime">COMMUNITY</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-400 max-w-lg mx-auto text-base mb-6">
|
<p className="text-gray-400 max-w-lg mx-auto text-base mb-8">
|
||||||
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.
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
href={GEYSER_PROJECT_URL}
|
href={GEYSER_PROJECT_URL}
|
||||||
|
|
@ -138,26 +339,22 @@ export default function SupportersPage() {
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Individuals section */}
|
{/* ── 1. Supporters ── */}
|
||||||
<section className="mb-16">
|
<section>
|
||||||
<div className="flex items-center gap-3 mb-6">
|
<SectionHeader
|
||||||
<Users className="h-5 w-5 text-[#ccff00]" />
|
icon={Zap}
|
||||||
<h2 className="text-2xl font-black uppercase">
|
title="Supporters"
|
||||||
<span className="neon-lime">Plebs</span>
|
subtitle="Bitcoin contributors via Geyser — ordered by cumulative sats."
|
||||||
</h2>
|
color="#f7931a"
|
||||||
<div className="flex-1 h-px bg-gradient-to-r from-[#ccff00]/30 to-transparent" />
|
/>
|
||||||
</div>
|
|
||||||
<p className="text-gray-400 text-sm mb-6">
|
|
||||||
Every sat counts — ordered by cumulative contributions.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-3">
|
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 gap-3">
|
||||||
{Array.from({ length: 12 }).map((_, i) => (
|
{Array.from({ length: 12 }).map((_, i) => (
|
||||||
<div key={i} className="flex flex-col items-center gap-2 p-4 rounded-xl bg-black/40 border border-white/5 animate-pulse">
|
<div key={i} className="flex flex-col items-center gap-2 p-4 rounded-xl bg-black/40 border border-white/5 animate-pulse">
|
||||||
<div className="w-14 h-14 rounded-full bg-white/10" />
|
<div className="w-12 h-12 rounded-full bg-white/10" />
|
||||||
<div className="h-3 w-16 bg-white/10 rounded" />
|
<div className="h-3 w-14 bg-white/10 rounded" />
|
||||||
<div className="h-3 w-12 bg-white/5 rounded" />
|
<div className="h-3 w-10 bg-white/5 rounded" />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -171,53 +368,116 @@ export default function SupportersPage() {
|
||||||
<div className="text-center py-12 rounded-2xl border border-dashed border-white/10">
|
<div className="text-center py-12 rounded-2xl border border-dashed border-white/10">
|
||||||
<div className="text-4xl mb-3">🛹</div>
|
<div className="text-4xl mb-3">🛹</div>
|
||||||
<p className="text-gray-500 mb-2">Be the first supporter!</p>
|
<p className="text-gray-500 mb-2">Be the first supporter!</p>
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600">The leaderboard updates once contributions come in.</p>
|
||||||
The leaderboard will update once contributions come in.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Geyser leaderboard link */}
|
<div className="mt-6 flex flex-wrap items-center justify-between gap-4">
|
||||||
<div className="mt-6 text-center">
|
|
||||||
<a
|
<a
|
||||||
href={GEYSER_LEADERBOARD_URL}
|
href={GEYSER_LEADERBOARD_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-[#ccff00]/30 text-[#ccff00] font-bold text-sm hover:bg-[#ccff00]/10 transition-all"
|
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"
|
||||||
>
|
>
|
||||||
<ExternalLink className="h-4 w-4" />
|
<ExternalLink className="h-4 w-4" />
|
||||||
Full Leaderboard on Geyser
|
Full Leaderboard on Geyser
|
||||||
</a>
|
</a>
|
||||||
</div>
|
<div className="flex items-center gap-2 text-xs text-gray-500">
|
||||||
|
<span>Powered by</span>
|
||||||
{/* Powered by Geyser */}
|
<a href={GEYSER_PROJECT_URL} target="_blank" rel="noopener noreferrer"
|
||||||
<div className="mt-8 flex items-center justify-center gap-3">
|
className="font-bold text-gray-400 hover:text-white transition-colors">
|
||||||
<span className="text-xs text-gray-500">Powered by</span>
|
Geyser ⚡
|
||||||
<a
|
|
||||||
href={GEYSER_PROJECT_URL}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="flex items-center gap-2 hover:opacity-80 transition-opacity"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="https://geyser.fund/logo-brand.svg"
|
|
||||||
alt="Geyser"
|
|
||||||
className="h-5 brightness-0 invert opacity-60 hover:opacity-90 transition-opacity"
|
|
||||||
onError={(e) => {
|
|
||||||
(e.target as HTMLImageElement).style.display = 'none';
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span className="text-sm font-bold text-gray-400 hover:text-white transition-colors">Geyser</span>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Mission section */}
|
{/* ── 2. Collaborators ── */}
|
||||||
<section className="mb-16">
|
<section>
|
||||||
|
<SectionHeader
|
||||||
|
icon={Users}
|
||||||
|
title="Collaborators"
|
||||||
|
subtitle="People actively building SkateSpots — developers, designers, relay operators."
|
||||||
|
color="#ff0040"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{COLLABORATORS.length > 0 ? (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
|
{COLLABORATORS.map((c) => (
|
||||||
|
<CollaboratorCard key={c.name} collab={c} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="rounded-xl border border-dashed border-[#ff0040]/20 p-8 text-center">
|
||||||
|
<Users className="h-10 w-10 mx-auto mb-3 text-[#ff0040]/30" />
|
||||||
|
<p className="text-gray-500 text-sm mb-1">Want to collaborate?</p>
|
||||||
|
<p className="text-xs text-gray-600 mb-4">Developers, designers, skaters with skills — reach out.</p>
|
||||||
|
<a
|
||||||
|
href="https://github.com/soapbox-pub/mkstack"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-flex items-center gap-1.5 text-xs font-bold text-[#ff0040]/70 hover:text-[#ff0040] transition-colors"
|
||||||
|
>
|
||||||
|
<ExternalLink className="h-3 w-3" />
|
||||||
|
View on GitHub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── 3. Partners ── */}
|
||||||
|
<section>
|
||||||
|
<SectionHeader
|
||||||
|
icon={Handshake}
|
||||||
|
title="Partners"
|
||||||
|
subtitle="Organizations and projects we work closely with."
|
||||||
|
color="#ff6600"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
|
{PARTNERS.map((p) => (
|
||||||
|
<PartnerCard key={p.name} partner={p} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── 4. Friends ── */}
|
||||||
|
<section>
|
||||||
|
<SectionHeader
|
||||||
|
icon={Star}
|
||||||
|
title="Friends"
|
||||||
|
subtitle="Aligned projects and people who share our values."
|
||||||
|
color="#ccff00"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{FRIENDS.length > 0 ? (
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3">
|
||||||
|
{FRIENDS.map((f) => (
|
||||||
|
<FriendCard key={f.name} friend={f} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* Placeholder grid — remove once real entries are added */}
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3 mb-4">
|
||||||
|
{Array.from({ length: 5 }).map((_, i) => (
|
||||||
|
<PlaceholderCard key={i} label="Coming soon" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="rounded-xl border border-dashed border-[#ccff00]/20 p-6 text-center">
|
||||||
|
<p className="text-gray-500 text-sm mb-1">Want to be listed here?</p>
|
||||||
|
<p className="text-xs text-gray-600">
|
||||||
|
Send a logo (PNG/WebP, transparent background, min 200×200px) and your URL via DM on Nostr.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ── Mission ── */}
|
||||||
|
<section>
|
||||||
<div className="relative rounded-2xl overflow-hidden border-2 border-[#ff0040]/30 bg-black/80 p-8 md:p-10">
|
<div className="relative rounded-2xl overflow-hidden border-2 border-[#ff0040]/30 bg-black/80 p-8 md:p-10">
|
||||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-[#ff0040] via-[#ff6600] to-[#ccff00]" />
|
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-[#ff0040] via-[#ff6600] to-[#ccff00]" />
|
||||||
<div className="absolute -top-16 -right-16 w-48 h-48 bg-[#ff0040] rounded-full opacity-5 blur-3xl pointer-events-none" />
|
|
||||||
|
|
||||||
<div className="flex justify-center mb-5">
|
<div className="flex justify-center mb-5">
|
||||||
<Heart className="h-10 w-10 text-[#ff0040]" />
|
<Heart className="h-10 w-10 text-[#ff0040]" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -226,11 +486,9 @@ export default function SupportersPage() {
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-400 text-sm text-center max-w-2xl mx-auto mb-6 leading-relaxed">
|
<p className="text-gray-400 text-sm text-center max-w-2xl mx-auto mb-6 leading-relaxed">
|
||||||
SkateSpots is 100% free, open-source, and built on Nostr — a decentralized protocol with no central server to shut it down.
|
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.
|
No ads, no data selling, no investors. Every sat goes directly toward development, relay hosting, and keeping this free for every skater on earth.
|
||||||
Every sat you contribute goes directly toward development, relay hosting, and keeping this tool free for every skater on earth.
|
|
||||||
</p>
|
</p>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mt-6">
|
|
||||||
{[
|
{[
|
||||||
{ emoji: '🛹', title: 'Nostr-native', desc: 'No central server. Your data lives on relays you choose.' },
|
{ 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: 'Bitcoin powered', desc: 'Earn sats for mapping spots. Spend them at local shops.' },
|
||||||
|
|
@ -246,29 +504,7 @@ export default function SupportersPage() {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Payment options */}
|
{/* ── Footer ── */}
|
||||||
<section className="mb-12">
|
|
||||||
<h2 className="text-xl font-black uppercase mb-5 flex items-center gap-2">
|
|
||||||
<Zap className="h-5 w-5 text-[#f7931a]" />
|
|
||||||
<span>Send Sats</span>
|
|
||||||
</h2>
|
|
||||||
<div className="p-5 rounded-xl border-2 border-[#ff0040]/20 bg-black/60 flex flex-col justify-between gap-3">
|
|
||||||
<p className="text-xs font-bold uppercase text-gray-400 tracking-widest">Via Geyser</p>
|
|
||||||
<p className="text-sm text-gray-400">
|
|
||||||
Contribute via the Geyser campaign — your name goes on the leaderboard above.
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
href={GEYSER_PROJECT_URL}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="inline-flex items-center gap-2 text-sm font-bold text-[#ccff00] hover:text-[#ccff00]/80 transition-colors"
|
|
||||||
>
|
|
||||||
Open campaign <ExternalLink className="h-3.5 w-3.5" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* Footer links */}
|
|
||||||
<div className="border-t border-gray-800 pt-8 text-center space-y-2 text-sm text-gray-500">
|
<div className="border-t border-gray-800 pt-8 text-center space-y-2 text-sm text-gray-500">
|
||||||
<p>Thank you. Every sat keeps the spots on the map. 🛹⚡</p>
|
<p>Thank you. Every sat keeps the spots on the map. 🛹⚡</p>
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -279,6 +515,7 @@ export default function SupportersPage() {
|
||||||
<Link to="/faq" className="text-[#ccff00]/70 hover:text-[#ccff00] transition-colors underline">FAQ</Link>
|
<Link to="/faq" className="text-[#ccff00]/70 hover:text-[#ccff00] transition-colors underline">FAQ</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue