SkateSpots/src/pages/GlossaryPage.tsx
SkateSpots 0b796440f1 feat(i18n): Spanish batch 2 — nav/chrome, events/shops/tours, crews/DMs, settings/auth, wallet/stash/newsletter/stats
~3,470 new keys (5,770 total, EN=ES), ~115 more files wired.
- nav + chromeUi: header, More menu, burger menu, footer, map toolbar widgets
  (sort/trust/weather), spot-type label helper.
- placesUi: events, shops, tours, food. socialUi: crews, DMs, notifications,
  collectives, crew chat/coordinator. settingsUi: settings, auth/signup,
  onboarding, profile edit, legal. walletUi: wallet/Cashu, stash, mints,
  app shop, newsletter, community stats, leaderboards, profile.
- Fixed 738 usages where an agent dropped the socialUi. prefix (caught by check:i18n).
2026-10-02 10:11:06 +07:00

200 lines
7.9 KiB
TypeScript

import { useState, useMemo } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Input } from '@/components/ui/input';
import { cn } from '@/lib/utils';
import {
GLOSSARY,
TOTAL_TRICKS,
searchTricks,
type FlatTrick,
} from '@/lib/glossary';
import { BookOpen, Search, X } from 'lucide-react';
import { useTranslation } from '@/contexts/language';
function TrickRow({ trick }: { trick: { name: string; aka?: string[]; desc: string } }) {
return (
<div className="rounded-xl border border-white/8 bg-white/[0.02] p-4 transition-colors hover:border-red-500/25 hover:bg-white/[0.04]">
<h3 className="text-base font-black text-white tracking-tight">{trick.name}</h3>
{trick.aka && trick.aka.length > 0 && (
<p className="mt-1 text-xs text-red-400/80">
<span className="text-gray-500">a.k.a. </span>
{trick.aka.join(' · ')}
</p>
)}
<p className="mt-2 text-sm leading-relaxed text-gray-300">{trick.desc}</p>
</div>
);
}
export default function GlossaryPage() {
const { t } = useTranslation();
const [query, setQuery] = useState('');
const [activeCat, setActiveCat] = useState<string | null>(null);
useSeoMeta({
title: t('settingsUi.glossary.seoTitle'),
description: t('settingsUi.glossary.seoDescription', { count: TOTAL_TRICKS }),
});
const isSearching = query.trim().length > 0;
const searchResults: FlatTrick[] = useMemo(
() => (isSearching ? searchTricks(query) : []),
[query, isSearching],
);
const visibleCategories = useMemo(
() => (activeCat ? GLOSSARY.filter((c) => c.id === activeCat) : GLOSSARY),
[activeCat],
);
return (
<>
<div className="min-h-screen">
{/* Hero */}
<div className="relative overflow-hidden border-b border-red-500/20 bg-gradient-to-b from-red-950/20 to-transparent">
<div className="container px-4 py-10 md:py-14">
<div className="flex items-center gap-3">
<div className="flex h-12 w-12 items-center justify-center rounded-2xl bg-red-500/15 text-red-400 shadow-lg shadow-red-500/20">
<BookOpen className="h-6 w-6" />
</div>
<div>
<h1
className="font-display text-3xl md:text-4xl neon-red tracking-wide"
style={{ textShadow: '0 0 10px #ff0040, 0 0 20px #ff0040' }}
>
{t('settingsUi.glossary.heading')}
</h1>
<p className="text-sm text-gray-400">
{t('settingsUi.glossary.subheading', { count: TOTAL_TRICKS })}
</p>
</div>
</div>
{/* Search */}
<div className="relative mt-6 max-w-xl">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-500" />
<Input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder={t('settingsUi.glossary.searchPlaceholder')}
className="h-11 rounded-xl border-white/10 bg-black/40 pl-9 pr-9 text-white placeholder:text-gray-500 focus-visible:ring-red-500/40"
/>
{query && (
<button
onClick={() => setQuery('')}
aria-label={t('settingsUi.glossary.clearSearch')}
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500 hover:text-white"
>
<X className="h-4 w-4" />
</button>
)}
</div>
</div>
</div>
<div className="container px-4 py-8">
{/* Category filter pills — hidden while searching */}
{!isSearching && (
<div className="mb-8 flex flex-wrap gap-2">
<button
onClick={() => setActiveCat(null)}
className={cn(
'rounded-full border px-3.5 py-1.5 text-xs font-bold uppercase tracking-wide transition-colors',
activeCat === null
? 'border-red-500/50 bg-red-500/15 text-red-400'
: 'border-white/10 text-gray-400 hover:border-white/25 hover:text-white',
)}
>
{t('settingsUi.glossary.all')}
</button>
{GLOSSARY.map((cat) => (
<button
key={cat.id}
onClick={() => setActiveCat(cat.id)}
className={cn(
'rounded-full border px-3.5 py-1.5 text-xs font-bold uppercase tracking-wide transition-colors',
activeCat === cat.id
? 'border-red-500/50 bg-red-500/15 text-red-400'
: 'border-white/10 text-gray-400 hover:border-white/25 hover:text-white',
)}
>
<span className="mr-1">{cat.glyph}</span>
{cat.title}
</button>
))}
</div>
)}
{/* Search results */}
{isSearching ? (
<div>
<p className="mb-4 text-sm text-gray-400">
{t('settingsUi.glossary.results', { count: searchResults.length, s: searchResults.length === 1 ? '' : 's' })}
<span className="ml-1 font-bold text-white">“{query}”</span>
</p>
{searchResults.length === 0 ? (
<div className="rounded-2xl border border-dashed border-white/10 p-10 text-center">
<p className="text-gray-500">
{t('settingsUi.glossary.noMatch')}
</p>
</div>
) : (
<div className="grid gap-3 md:grid-cols-2">
{searchResults.map((t) => (
<div key={`${t.categoryId}-${t.name}`}>
<p className="mb-1 text-[10px] font-bold uppercase tracking-widest text-gray-600">
{t.categoryGlyph} {t.categoryTitle}
</p>
<TrickRow trick={t} />
</div>
))}
</div>
)}
</div>
) : (
/* Category sections */
<div className="space-y-12">
{visibleCategories.map((cat) => (
<section key={cat.id} id={cat.id}>
<div className="mb-4 flex items-baseline gap-3">
<h2 className="text-xl font-black uppercase tracking-wide text-white">
<span className="mr-2">{cat.glyph}</span>
{cat.title}
</h2>
<span className="text-xs text-gray-500">{t('settingsUi.glossary.tricksCount', { count: cat.tricks.length })}</span>
</div>
<p className="mb-5 text-sm text-gray-400">{cat.blurb}</p>
<div className="grid gap-3 md:grid-cols-2">
{cat.tricks.map((t) => (
<TrickRow key={t.name} trick={t} />
))}
</div>
</section>
))}
</div>
)}
{/* Footer note */}
<div className="mt-14 rounded-2xl border border-white/8 bg-white/[0.02] p-5 text-center">
<p className="text-sm text-gray-400">
{t('settingsUi.glossary.footerBefore')}{' '}
<button
onClick={() =>
window.dispatchEvent(
new CustomEvent('sasquatch:ask', {
detail: { question: 'How do I suggest a trick for the glossary?' },
}),
)
}
className="font-bold text-red-400 hover:text-red-300"
>
{t('settingsUi.glossary.sasquatch')}
</button>{' '}
{t('settingsUi.glossary.footerAfter')}
</p>
</div>
</div>
</div>
</>
);
}