421 lines
18 KiB
TypeScript
421 lines
18 KiB
TypeScript
import { useSeoMeta } from '@unhead/react';
|
||
import { Layout } from '@/components/Layout';
|
||
import { Link } from 'react-router-dom';
|
||
import { ExternalLink, Zap, Heart, Users, Handshake, Building2, Star } from 'lucide-react';
|
||
|
||
const ANGOR_PROJECT_URL = 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z';
|
||
|
||
// ─── 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ó',
|
||
},
|
||
{
|
||
name: 'RK69 DIY Skatepark',
|
||
logo: 'https://blossom.ditto.pub/0d1d3855e7922b4ad7c10332e14723a16525e7aa26d95c58ea1dc1ec75514c3c.png',
|
||
url: 'https://www.instagram.com/rk69diyskatepark',
|
||
tagline: 'DIY Skatepark 101%',
|
||
},
|
||
{
|
||
name: 'Suan Luang Backyard',
|
||
logo: 'https://blossom.ditto.pub/8ac40ffbf9db9c9bff20e9b34d627a31b9c2044fe4f534948292516319684523.jpeg',
|
||
url: 'https://www.slbskate.com/',
|
||
tagline: 'Backyard skate spot & community',
|
||
},
|
||
// Add more friends here
|
||
];
|
||
|
||
// ─── Section header ───────────────────────────────────────────────────────────
|
||
|
||
function SectionHeader({
|
||
icon: Icon,
|
||
title,
|
||
subtitle,
|
||
color = '#ccff00',
|
||
}: {
|
||
icon: React.ComponentType<{ className?: string; style?: React.CSSProperties }>;
|
||
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>
|
||
);
|
||
}
|
||
|
||
// ─── 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 (
|
||
<a
|
||
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>
|
||
);
|
||
}
|
||
|
||
// ─── 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${friend.invertLogo ? ' invert brightness-200' : ''}`}
|
||
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() {
|
||
useSeoMeta({
|
||
title: 'Supporters - SkateSpots',
|
||
description: 'The community keeping SkateSpots alive — supporters, collaborators, partners and friends.',
|
||
});
|
||
|
||
return (
|
||
<Layout>
|
||
<div className="container max-w-4xl px-4 py-12 md:py-16 space-y-16">
|
||
|
||
{/* ── Hero ── */}
|
||
<div className="text-center">
|
||
<div className="text-6xl mb-4">⚡</div>
|
||
<h1 className="text-5xl md:text-6xl font-black uppercase mb-3 leading-none">
|
||
<span className="neon-red">OUR</span>{' '}
|
||
<span className="neon-lime">COMMUNITY</span>
|
||
</h1>
|
||
<p className="text-gray-400 max-w-lg mx-auto text-base mb-8">
|
||
SkateSpots is built by and for the community. Supporters, collaborators, partners and friends keeping this free and decentralized.
|
||
</p>
|
||
<a
|
||
href={ANGOR_PROJECT_URL}
|
||
target="_blank"
|
||
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"
|
||
>
|
||
<Zap className="h-4 w-4" />
|
||
Back us on Angor
|
||
<ExternalLink className="h-3.5 w-3.5 opacity-70" />
|
||
</a>
|
||
</div>
|
||
|
||
{/* ── 1. Support ── */}
|
||
<section>
|
||
<SectionHeader
|
||
icon={Zap}
|
||
title="Support"
|
||
subtitle="Fund SkateSpots on Angor — decentralized, on-chain Bitcoin crowdfunding. No middleman, no KYC."
|
||
color="#f7931a"
|
||
/>
|
||
|
||
<div className="rounded-2xl border-2 border-[#f7931a]/30 bg-black/60 p-8 text-center">
|
||
<div className="text-5xl mb-4">🧡⚡</div>
|
||
<h3 className="text-xl font-black uppercase mb-2 text-white">Back the project on Angor</h3>
|
||
<p className="text-sm text-gray-400 max-w-lg mx-auto mb-6 leading-relaxed">
|
||
Angor is a decentralized Bitcoin crowdfunding platform built on Nostr — your funds stay
|
||
in your control, released in stages, with no custodian holding the pot. Every sat goes
|
||
to development, relay hosting, and keeping SkateSpots free for every skater on earth.
|
||
</p>
|
||
<p className="text-xs text-gray-600 font-mono mb-4 break-all">
|
||
⛓ bc1q2hdfyqa7utrxrd95rlr27shsdr6gtk7dzdqres
|
||
</p>
|
||
<div className="flex flex-wrap items-center justify-center gap-3">
|
||
<a
|
||
href={ANGOR_PROJECT_URL}
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
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" />
|
||
Open Angor
|
||
</a>
|
||
<span className="text-xs text-gray-500">
|
||
or ⚡ zap any spot, crew or creator directly in the app
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* ── 2. Collaborators ── */}
|
||
<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://nostrhub.io/naddr1qvzqqqrhnypzqg88e9f588nx7wyz4wt0ura23w3yd4hmztqrkr95utwyvgachjepqyd8wumn8ghj7emfwsh8x6rpddjhxur9v9ex2tnyd9uj7qq22d4kzar92dcx7arnhyhdwn"
|
||
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 NostrHub
|
||
</a>
|
||
</div>
|
||
)}
|
||
</section>
|
||
|
||
{/* ── 3. Partners ── */}
|
||
<section>
|
||
<SectionHeader
|
||
icon={Handshake}
|
||
title="Partners"
|
||
subtitle="Organizations and projects we work closely with."
|
||
color="#ff6600"
|
||
/>
|
||
|
||
{PARTNERS.length > 0 ? (
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||
{PARTNERS.map((p) => (
|
||
<PartnerCard key={p.name} partner={p} />
|
||
))}
|
||
</div>
|
||
) : (
|
||
<>
|
||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 mb-4">
|
||
{Array.from({ length: 4 }).map((_, i) => (
|
||
<PlaceholderCard key={i} label="Coming soon" />
|
||
))}
|
||
</div>
|
||
<div className="rounded-xl border border-dashed border-[#ff6600]/20 p-6 text-center">
|
||
<p className="text-gray-500 text-sm">Want to partner with SkateSpots? Reach out on Nostr.</p>
|
||
</div>
|
||
</>
|
||
)}
|
||
</section>
|
||
|
||
{/* ── 4. Friends ── */}
|
||
<section>
|
||
<SectionHeader
|
||
icon={Star}
|
||
title="Friends"
|
||
subtitle="People we vibe with."
|
||
color="#ccff00"
|
||
/>
|
||
|
||
<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} />
|
||
))}
|
||
{/* Fill remaining slots up to 5 with placeholders */}
|
||
{FRIENDS.length < 5 && Array.from({ length: 5 - FRIENDS.length }).map((_, i) => (
|
||
<PlaceholderCard key={`ph-${i}`} label="Coming soon" />
|
||
))}
|
||
</div>
|
||
<div className="mt-4 rounded-xl border border-dashed border-[#ccff00]/20 p-5 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, only if you have the vibes.
|
||
</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="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-[#ff0040] via-[#ff6600] to-[#ccff00]" />
|
||
<div className="flex justify-center mb-5">
|
||
<Heart className="h-10 w-10 text-[#ff0040]" />
|
||
</div>
|
||
<h2 className="text-2xl font-black uppercase text-center mb-3">
|
||
Why We Need <span className="neon-red">Support</span>
|
||
</h2>
|
||
<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.
|
||
No ads, no data selling, no investors. Every sat goes directly toward development, relay hosting, and keeping this free for every skater on earth.
|
||
</p>
|
||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||
{[
|
||
{ 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 }) => (
|
||
<div key={title} className="p-4 rounded-xl bg-white/5 border border-white/10 text-center">
|
||
<div className="text-3xl mb-2">{emoji}</div>
|
||
<p className="font-bold text-sm mb-1">{title}</p>
|
||
<p className="text-xs text-gray-500">{desc}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* ── Footer ── */}
|
||
<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>
|
||
<Link to="/license" className="text-[#ccff00]/70 hover:text-[#ccff00] transition-colors underline">License</Link>
|
||
{' '}·{' '}
|
||
<Link to="/about" className="text-[#ccff00]/70 hover:text-[#ccff00] transition-colors underline">About</Link>
|
||
{' '}·{' '}
|
||
<Link to="/faq" className="text-[#ccff00]/70 hover:text-[#ccff00] transition-colors underline">FAQ</Link>
|
||
</p>
|
||
</div>
|
||
|
||
</div>
|
||
</Layout>
|
||
);
|
||
}
|