Owner feedback: the route dialog's map still dominated the phone screen, and
the 'nothing leaves to Google or Apple Maps' tagline cluttered the chrome.
- Map: 50vh -> 34vh on mobile (min-h 300 -> 220); desktop 440 -> 400. Follow
mode 60vh -> 54vh, so the bigger map is kept only when you need it.
- Drop the visible tagline from the route header (kept sr-only for a11y); the
promise now reads on /privacy -> 'What leaves your device' -> Directions
('routing stays in the app; start/destination are never handed off to Google
or Apple Maps').
Docs: AGENTS.md map-layout note.
241 lines
12 KiB
TypeScript
241 lines
12 KiB
TypeScript
import { useSeoMeta } from '@unhead/react';
|
|
import { Link } from 'react-router-dom';
|
|
import {
|
|
Shield,
|
|
MapPin,
|
|
Globe,
|
|
Eye,
|
|
Lock,
|
|
Wifi,
|
|
Key,
|
|
CheckCircle2,
|
|
AlertTriangle,
|
|
} from 'lucide-react';
|
|
|
|
// ─── Small building blocks ────────────────────────────────────────────────────
|
|
|
|
function Section({
|
|
icon: Icon,
|
|
color,
|
|
title,
|
|
children,
|
|
}: {
|
|
icon: React.ComponentType<{ className?: string; style?: React.CSSProperties }>;
|
|
color: string;
|
|
title: string;
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<section className="rounded-2xl border border-white/8 bg-white/3 p-5">
|
|
<div className="flex items-center gap-3 mb-3">
|
|
<div
|
|
className="w-9 h-9 rounded-xl flex items-center justify-center flex-shrink-0"
|
|
style={{ background: `${color}1a`, border: `1px solid ${color}40` }}
|
|
>
|
|
<Icon className="h-5 w-5" style={{ color }} />
|
|
</div>
|
|
<h2 className="font-black text-white uppercase tracking-wide text-lg">{title}</h2>
|
|
</div>
|
|
<div className="text-sm text-gray-400 leading-relaxed space-y-3">{children}</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
function GoodPoint({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<div className="flex items-start gap-2.5">
|
|
<CheckCircle2 className="h-4 w-4 text-green-400 flex-shrink-0 mt-0.5" />
|
|
<span>{children}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Caveat({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<div className="flex items-start gap-2.5">
|
|
<AlertTriangle className="h-4 w-4 text-amber-400 flex-shrink-0 mt-0.5" />
|
|
<span>{children}</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function PrivacyPage() {
|
|
useSeoMeta({
|
|
title: 'Privacy — SkateSpots',
|
|
description:
|
|
'How SkateSpots handles your data: your GPS stays on your device, spots you publish are public, and how to hide your IP with a VPN or Tor if you want to.',
|
|
});
|
|
|
|
return (
|
|
<>
|
|
<div className="container max-w-2xl px-4 py-10 md:py-16">
|
|
{/* Hero */}
|
|
<div className="text-center mb-10">
|
|
<div
|
|
className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-[#ccff00]/10 border-2 border-[#ccff00]/40 mb-5"
|
|
>
|
|
<Shield className="h-8 w-8 text-[#ccff00]" />
|
|
</div>
|
|
<h1 className="text-4xl md:text-5xl font-black uppercase leading-none mb-4">
|
|
<span className="text-white">Your </span>
|
|
<span style={{ color: '#ccff00' }}>Privacy</span>
|
|
</h1>
|
|
<p className="text-gray-400 text-base max-w-lg mx-auto leading-relaxed">
|
|
No tracking, no ads, no data selling. Here's exactly what stays on your device,
|
|
what's public, and how to lock things down further if you want to.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
{/* Location stays local */}
|
|
<Section icon={MapPin} color="#ff0040" title="Your location stays on your device">
|
|
<p>
|
|
When you tap <strong className="text-white">"Near Me,"</strong> your GPS location is
|
|
read on your device and used only to center the map and sort nearby spots.
|
|
</p>
|
|
<GoodPoint>It is <strong className="text-white">never</strong> published to Nostr.</GoodPoint>
|
|
<GoodPoint>It is <strong className="text-white">never</strong> sent to SkateSpots — there is no SkateSpots server collecting it.</GoodPoint>
|
|
<GoodPoint>It is <strong className="text-white">never</strong> shared with any third party.</GoodPoint>
|
|
<GoodPoint>
|
|
On the native app we use your phone's own location system directly and skip Google
|
|
Play Services — so it works on de-Googled phones (GrapheneOS, /e/OS) and Google
|
|
never sees it.
|
|
</GoodPoint>
|
|
<p className="text-xs text-gray-500">
|
|
Weather is off by default. When you turn it on, it looks up conditions for the
|
|
<em> spot's </em> public location — not yours.
|
|
</p>
|
|
</Section>
|
|
|
|
{/* Public by design */}
|
|
<Section icon={Eye} color="#ff6600" title="What's public by design">
|
|
<p>
|
|
SkateSpots is a shared map, so some things are meant to be seen by everyone. When you
|
|
choose to publish them, these are public on the Nostr network:
|
|
</p>
|
|
<Caveat>
|
|
<strong className="text-white">Spots you add</strong> — including their coordinates.
|
|
If you add a spot at your exact location, that location becomes public. Think before
|
|
you drop a pin on your own driveway.
|
|
</Caveat>
|
|
<Caveat><strong className="text-white">Reviews, comments, and your public profile</strong> (name, picture).</Caveat>
|
|
<Caveat>Who you follow, and public reactions.</Caveat>
|
|
<Caveat>
|
|
<strong className="text-white">Zaps</strong> are public by default: a zap ties your
|
|
account — often your lightning address — to the post you paid and to the tip itself.
|
|
Your wallet can send it anonymously if you'd rather it didn't.
|
|
</Caveat>
|
|
<p className="text-xs text-gray-500">
|
|
A VPN or Tor does <strong>not</strong> hide the content of what you publish — a spot's
|
|
coordinates are public regardless. Privacy tools hide your network identity (your IP),
|
|
not the things you choose to post.
|
|
</p>
|
|
</Section>
|
|
|
|
{/* Encrypted */}
|
|
<Section icon={Lock} color="#14b8a6" title="What's private & encrypted">
|
|
<GoodPoint><strong className="text-white">Direct messages</strong> and <strong className="text-white">crew chats</strong> are end-to-end encrypted (NIP-17). Only the participants can read them.</GoodPoint>
|
|
<GoodPoint><strong className="text-white">My Stash</strong> — your private gear inventory — is encrypted to your own key.</GoodPoint>
|
|
<GoodPoint><strong className="text-white">Crew-only members</strong> can choose to be visible only to their crew-mates.</GoodPoint>
|
|
</Section>
|
|
|
|
{/* IP / network */}
|
|
<Section icon={Wifi} color="#a855f7" title="What leaves your device: your IP">
|
|
<p>
|
|
Like every website, the servers SkateSpots talks to can see your{' '}
|
|
<strong className="text-white">IP address</strong> (a rough indicator of your region).
|
|
This is normal for any web or map app and isn't unique to us. Those servers are:
|
|
</p>
|
|
<ul className="list-disc pl-5 space-y-1 text-gray-400">
|
|
<li><strong className="text-white">Map tiles</strong> (OpenStreetMap) — see your IP and which area of the map you look at.</li>
|
|
<li><strong className="text-white">Bitcoin places</strong> (BTCMap, when you switch that layer on) — see your IP and the area you're viewing.</li>
|
|
<li><strong className="text-white">Nostr relays</strong> — see your IP when the app reads or publishes.</li>
|
|
<li><strong className="text-white">Cashu mints</strong> — see your IP whenever your wallet sends, receives or melts.</li>
|
|
<li><strong className="text-white">Directions</strong> (OSRM, and Transitous for transit) — receive the start and end of any route you ask for, not only your IP. Routing stays <strong className="text-white">in the app</strong>: your start and destination are never handed off to Google or Apple Maps.</li>
|
|
<li><strong className="text-white">Weather</strong> (Open-Meteo, opt-in) and <strong className="text-white">image uploads</strong> (Blossom), when you use them.</li>
|
|
<li><strong className="text-white">Sasquatch AI</strong> — your message goes to our own server, then to the model provider.</li>
|
|
</ul>
|
|
<p>
|
|
Because you send your public key (npub) on the same connection, a relay can link your IP
|
|
to <em>you</em> — not just "someone connected", but "this person, from this address".
|
|
</p>
|
|
<p>
|
|
One thing we do shield: the <strong className="text-white">mints directory</strong> is
|
|
fetched through our server, so MintRadar never sees your IP.
|
|
</p>
|
|
</Section>
|
|
|
|
{/* VPN / Tor */}
|
|
<Section icon={Globe} color="#06b6d4" title="Want more privacy? VPN or Tor (optional)">
|
|
<p>
|
|
For most skaters this isn't necessary — but if you want to hide your IP and rough
|
|
location from the servers above, you have two free options:
|
|
</p>
|
|
<div className="grid sm:grid-cols-2 gap-3 my-1">
|
|
<div className="p-3.5 rounded-xl bg-white/4 border border-white/8">
|
|
<p className="font-bold text-white text-sm mb-1">🛡️ A reputable VPN</p>
|
|
<p className="text-xs text-gray-500">
|
|
Hides your IP behind the VPN's servers. Easy and fast. Use one you pay for and trust;
|
|
free VPNs usually monetise your traffic, so treat "free" as a warning sign.
|
|
</p>
|
|
</div>
|
|
<div className="p-3.5 rounded-xl bg-white/4 border border-white/8">
|
|
<p className="font-bold text-white text-sm mb-1">🧅 Tor Browser</p>
|
|
<p className="text-xs text-gray-500">
|
|
Free, maximum IP privacy, routes through multiple relays. Slower — maps and some
|
|
Nostr relays may lag or block Tor exit nodes. Great for the extra-cautious.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="rounded-xl bg-amber-500/8 border border-amber-500/20 p-3.5 space-y-2">
|
|
<p className="text-xs font-black uppercase tracking-wide text-amber-400 flex items-center gap-1.5">
|
|
<AlertTriangle className="h-3.5 w-3.5" /> Read this so you're not fooled
|
|
</p>
|
|
<Caveat>
|
|
A VPN/Tor hides your <strong className="text-white">IP</strong> — not the spots you
|
|
publish. A pin at your home is public whether or not you use a VPN.
|
|
</Caveat>
|
|
<Caveat>
|
|
Don't enable <strong className="text-white">"Near Me" / browser GPS</strong> if your
|
|
goal is to stay anonymous — precise GPS overrides any IP hiding.
|
|
</Caveat>
|
|
<Caveat>
|
|
No tool makes you fully "anonymous." These reduce what servers can infer about your
|
|
network location — that's it.
|
|
</Caveat>
|
|
</div>
|
|
</Section>
|
|
|
|
{/* Keys */}
|
|
<Section icon={Key} color="#facc15" title="Your keys, your account">
|
|
<p>
|
|
Your account is a cryptographic key pair that you own. The safest way to log in is a{' '}
|
|
<strong className="text-white">signer</strong> (Alby, nos2x, or Amber on Android) so
|
|
your secret key never gets pasted into the app.
|
|
</p>
|
|
<Caveat>
|
|
If you paste your <code className="text-[#ccff00] text-xs bg-white/5 px-1 py-0.5 rounded">nsec</code>{' '}
|
|
directly, it's stored in your browser. Use a signer extension instead when you can, and
|
|
keep your nsec backup somewhere safe offline.
|
|
</Caveat>
|
|
</Section>
|
|
|
|
{/* Footer */}
|
|
<div className="text-center pt-4 pb-2">
|
|
<p className="text-xs text-gray-600">
|
|
Questions? Ask{' '}
|
|
<span className="text-[#ccff00]">The Sasquatch</span> in-app, or read the{' '}
|
|
<Link to="/onboarding" className="text-[#ccff00]/80 hover:text-[#ccff00] underline">
|
|
getting started guide
|
|
</Link>
|
|
{' '}·{' '}
|
|
<Link to="/about" className="text-[#ccff00]/80 hover:text-[#ccff00] underline">
|
|
About
|
|
</Link>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|