The /lab profile card and /stats Top Mappers were overflowing off the right edge: Tailwind `grid md:grid-cols-2` (no base column) renders one implicit `auto` track on mobile that can exceed the container when a name is long, so the whole card got pushed off-screen. Added the base `grid-cols-1` repo-wide (plus `min-w-0` on truncating flex/grid children). /stats also gained a "Top Shops — most shops added" leaderboard (kind 30017) beside Top Mappers (spots): `topShops` in useGrowthStats, a generalized MapperRow (count + unit), and EN/ES copy. PERSIST_BUSTER → v6 (new persisted field).
222 lines
10 KiB
TypeScript
222 lines
10 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';
|
|
import { useTranslation } from '@/contexts/language';
|
|
|
|
// ─── 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() {
|
|
const { t } = useTranslation();
|
|
useSeoMeta({
|
|
title: t('settingsUi.privacy.seoTitle'),
|
|
description: t('settingsUi.privacy.seoDescription'),
|
|
});
|
|
|
|
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">{t('settingsUi.privacy.heroBefore')} </span>
|
|
<span style={{ color: '#ccff00' }}>{t('settingsUi.privacy.heroHighlight')}</span>
|
|
</h1>
|
|
<p className="text-gray-400 text-base max-w-lg mx-auto leading-relaxed">
|
|
{t('settingsUi.privacy.heroDesc')}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-4">
|
|
{/* Location stays local */}
|
|
<Section icon={MapPin} color="#ff0040" title={t('settingsUi.privacy.locationTitle')}>
|
|
<p>
|
|
{t('settingsUi.privacy.locationBefore')} <strong className="text-white">{t('settingsUi.privacy.nearMe')}</strong> {t('settingsUi.privacy.locationAfter')}
|
|
</p>
|
|
<GoodPoint>{t('settingsUi.privacy.locationGood1')}</GoodPoint>
|
|
<GoodPoint>{t('settingsUi.privacy.locationGood2')}</GoodPoint>
|
|
<GoodPoint>{t('settingsUi.privacy.locationGood3')}</GoodPoint>
|
|
<GoodPoint>
|
|
{t('settingsUi.privacy.locationGood4')}
|
|
</GoodPoint>
|
|
<p className="text-xs text-gray-500">
|
|
{t('settingsUi.privacy.weatherBefore')}
|
|
<em> {t('settingsUi.privacy.weatherEm')} </em>{t('settingsUi.privacy.weatherAfter')}
|
|
</p>
|
|
</Section>
|
|
|
|
{/* Public by design */}
|
|
<Section icon={Eye} color="#ff6600" title={t('settingsUi.privacy.publicTitle')}>
|
|
<p>
|
|
{t('settingsUi.privacy.publicDesc')}
|
|
</p>
|
|
<Caveat>
|
|
<strong className="text-white">{t('settingsUi.privacy.public1Strong')}</strong> {t('settingsUi.privacy.public1')}
|
|
</Caveat>
|
|
<Caveat><strong className="text-white">{t('settingsUi.privacy.public2Strong')}</strong> {t('settingsUi.privacy.public2')}</Caveat>
|
|
<Caveat>{t('settingsUi.privacy.public3')}</Caveat>
|
|
<Caveat>
|
|
<strong className="text-white">{t('settingsUi.privacy.public4Strong')}</strong> {t('settingsUi.privacy.public4')}
|
|
</Caveat>
|
|
<p className="text-xs text-gray-500">
|
|
{t('settingsUi.privacy.publicNoteBefore')} <strong>{t('settingsUi.privacy.notWord')}</strong> {t('settingsUi.privacy.publicNoteAfter')}
|
|
</p>
|
|
</Section>
|
|
|
|
{/* Encrypted */}
|
|
<Section icon={Lock} color="#14b8a6" title={t('settingsUi.privacy.encryptedTitle')}>
|
|
<GoodPoint><strong className="text-white">{t('settingsUi.privacy.enc1Strong')}</strong> {t('settingsUi.privacy.enc1Mid')} <strong className="text-white">{t('settingsUi.privacy.enc1Strong2')}</strong> {t('settingsUi.privacy.enc1')}</GoodPoint>
|
|
<GoodPoint><strong className="text-white">{t('settingsUi.privacy.enc2Strong')}</strong> {t('settingsUi.privacy.enc2')}</GoodPoint>
|
|
<GoodPoint><strong className="text-white">{t('settingsUi.privacy.enc3Strong')}</strong> {t('settingsUi.privacy.enc3')}</GoodPoint>
|
|
</Section>
|
|
|
|
{/* IP / network */}
|
|
<Section icon={Wifi} color="#a855f7" title={t('settingsUi.privacy.ipTitle')}>
|
|
<p>
|
|
{t('settingsUi.privacy.ipBefore')}{' '}
|
|
<strong className="text-white">{t('settingsUi.privacy.ipAddress')}</strong> {t('settingsUi.privacy.ipAfter')}
|
|
</p>
|
|
<ul className="list-disc pl-5 space-y-1 text-gray-400">
|
|
<li><strong className="text-white">{t('settingsUi.privacy.ipTileStrong')}</strong> {t('settingsUi.privacy.ipTile')}</li>
|
|
<li><strong className="text-white">{t('settingsUi.privacy.ipGeoStrong')}</strong> {t('settingsUi.privacy.ipGeo')}</li>
|
|
<li><strong className="text-white">{t('settingsUi.privacy.ipPushStrong')}</strong> {t('settingsUi.privacy.ipPush')}</li>
|
|
<li><strong className="text-white">{t('settingsUi.privacy.ipBtcStrong')}</strong> {t('settingsUi.privacy.ipBtc')}</li>
|
|
<li><strong className="text-white">{t('settingsUi.privacy.ipRelayStrong')}</strong> {t('settingsUi.privacy.ipRelay')}</li>
|
|
<li><strong className="text-white">{t('settingsUi.privacy.ipMintStrong')}</strong> {t('settingsUi.privacy.ipMint')}</li>
|
|
<li><strong className="text-white">{t('settingsUi.privacy.ipRouteStrong')}</strong> {t('settingsUi.privacy.ipRoute')}</li>
|
|
<li><strong className="text-white">{t('settingsUi.privacy.ipWeatherStrong')}</strong> {t('settingsUi.privacy.ipWeatherMid')} <strong className="text-white">{t('settingsUi.privacy.ipUploadStrong')}</strong>{t('settingsUi.privacy.ipWeather')}</li>
|
|
<li><strong className="text-white">{t('settingsUi.privacy.ipAiStrong')}</strong> {t('settingsUi.privacy.ipAi')}</li>
|
|
</ul>
|
|
<p>
|
|
{t('settingsUi.privacy.ipLink1')}
|
|
</p>
|
|
<p>
|
|
{t('settingsUi.privacy.ipShieldBefore')} <strong className="text-white">{t('settingsUi.privacy.ipMintsStrong')}</strong> {t('settingsUi.privacy.ipShieldAfter')}
|
|
</p>
|
|
</Section>
|
|
|
|
{/* VPN / Tor */}
|
|
<Section icon={Globe} color="#06b6d4" title={t('settingsUi.privacy.vpnTitle')}>
|
|
<p>
|
|
{t('settingsUi.privacy.vpnDesc')}
|
|
</p>
|
|
<div className="grid grid-cols-1 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">{t('settingsUi.privacy.vpnReputable')}</p>
|
|
<p className="text-xs text-gray-500">
|
|
{t('settingsUi.privacy.vpnReputableDesc')}
|
|
</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">{t('settingsUi.privacy.torBrowser')}</p>
|
|
<p className="text-xs text-gray-500">
|
|
{t('settingsUi.privacy.torDesc')}
|
|
</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" /> {t('settingsUi.privacy.readThis')}
|
|
</p>
|
|
<Caveat>
|
|
{t('settingsUi.privacy.caveat1Before')} <strong className="text-white">{t('settingsUi.privacy.ipWord')}</strong> {t('settingsUi.privacy.caveat1')}
|
|
</Caveat>
|
|
<Caveat>
|
|
{t('settingsUi.privacy.caveat2Before')} <strong className="text-white">{t('settingsUi.privacy.nearMeGps')}</strong> {t('settingsUi.privacy.caveat2')}
|
|
</Caveat>
|
|
<Caveat>
|
|
{t('settingsUi.privacy.caveat3')}
|
|
</Caveat>
|
|
</div>
|
|
</Section>
|
|
|
|
{/* Keys */}
|
|
<Section icon={Key} color="#facc15" title={t('settingsUi.privacy.keysTitle')}>
|
|
<p>
|
|
{t('settingsUi.privacy.keysBefore')}{' '}
|
|
<strong className="text-white">{t('settingsUi.privacy.signer')}</strong> {t('settingsUi.privacy.keysAfter')}
|
|
</p>
|
|
<Caveat>
|
|
{t('settingsUi.privacy.keysCaveatBefore')} <code className="text-[#ccff00] text-xs bg-white/5 px-1 py-0.5 rounded">nsec</code>{' '}
|
|
{t('settingsUi.privacy.keysCaveatAfter')}
|
|
</Caveat>
|
|
</Section>
|
|
|
|
{/* Footer */}
|
|
<div className="text-center pt-4 pb-2">
|
|
<p className="text-xs text-gray-600">
|
|
{t('settingsUi.privacy.footerBefore')}{' '}
|
|
<span className="text-[#ccff00]">{t('settingsUi.privacy.sasquatchName')}</span> {t('settingsUi.privacy.footerMid')}{' '}
|
|
<Link to="/onboarding" className="text-[#ccff00]/80 hover:text-[#ccff00] underline">
|
|
{t('settingsUi.privacy.gettingStarted')}
|
|
</Link>
|
|
{' '}·{' '}
|
|
<Link to="/about" className="text-[#ccff00]/80 hover:text-[#ccff00] underline">
|
|
{t('settingsUi.privacy.about')}
|
|
</Link>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|