123 lines
5.6 KiB
TypeScript
123 lines
5.6 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Lock, Flame } from 'lucide-react';
|
|
import { MOCK_CHARACTERS } from '../constants';
|
|
import { Character } from '../types';
|
|
|
|
interface DiscoveryProps {
|
|
onSelectCharacter: (char: Character) => void;
|
|
}
|
|
|
|
const Discovery: React.FC<DiscoveryProps> = ({ onSelectCharacter }) => {
|
|
const [activeFilter, setActiveFilter] = useState('all');
|
|
|
|
const filters = [
|
|
{ id: 'all', label: '全部' },
|
|
{ id: 'gentle', label: '温柔治愈' },
|
|
{ id: 'dom', label: '主导强势' },
|
|
{ id: 'wild', label: '反差/猎奇' },
|
|
{ id: 'voice', label: '语音陪聊' },
|
|
{ id: 'scenario', label: '场景扮演' },
|
|
{ id: 'exclusive', label: '会员限定' },
|
|
];
|
|
|
|
const filteredCharacters = activeFilter === 'all'
|
|
? MOCK_CHARACTERS
|
|
: MOCK_CHARACTERS.filter(c => {
|
|
const tags = c.tags.join('');
|
|
if (activeFilter === 'gentle') return tags.includes('治愈') || tags.includes('温顺') || tags.includes('医疗');
|
|
if (activeFilter === 'dom') return tags.includes('强势') || tags.includes('调教') || tags.includes('指令');
|
|
if (activeFilter === 'wild') return tags.includes('病娇') || tags.includes('神秘') || tags.includes('不稳定') || tags.includes('极乐');
|
|
if (activeFilter === 'exclusive') return c.isLocked;
|
|
return false;
|
|
});
|
|
|
|
return (
|
|
<div className="pb-24 px-4 h-full">
|
|
{/* Filter Bar */}
|
|
<div className="sticky top-0 z-20 pt-2 pb-2 -mx-4 mb-4">
|
|
<div className="relative">
|
|
{/* Scroll Container */}
|
|
<div className="flex items-center px-6 gap-3 overflow-x-auto no-scrollbar pr-12">
|
|
{filters.map(filter => {
|
|
const isActive = activeFilter === filter.id;
|
|
return (
|
|
<button
|
|
key={filter.id}
|
|
onClick={() => setActiveFilter(filter.id)}
|
|
className={`relative px-4 py-1.5 rounded-full border transition-all duration-300 shrink-0 ${
|
|
isActive
|
|
? 'bg-white text-[#2e1065] font-bold border-white shadow-[0_0_15px_rgba(255,255,255,0.4)]'
|
|
: 'bg-white/5 text-white/70 border-white/10 hover:bg-white/10 backdrop-blur-sm'
|
|
}`}
|
|
>
|
|
<span className="text-sm">{filter.label}</span>
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
{/* Right Fade */}
|
|
<div className="absolute top-0 right-0 h-full w-12 bg-gradient-to-l from-[#4c1d95]/0 to-transparent pointer-events-none"></div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Grid Layout */}
|
|
<div className="grid grid-cols-2 gap-4">
|
|
{filteredCharacters.map((char) => (
|
|
<div
|
|
key={char.id}
|
|
onClick={() => !char.isLocked && onSelectCharacter(char)}
|
|
className={`relative w-full aspect-[3/4] rounded-3xl overflow-hidden transition-all duration-300 border border-white/20 group ${
|
|
char.isLocked ? 'grayscale opacity-70' : 'active:scale-[0.98] shadow-lg hover:shadow-[0_0_25px_rgba(192,132,252,0.3)] hover:border-white/40'
|
|
}`}
|
|
>
|
|
{/* Background Image */}
|
|
<img src={char.avatar} alt={char.name} className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110" />
|
|
|
|
{/* Gradient Overlay - Lighter/Purple based */}
|
|
<div className="absolute inset-0 bg-gradient-to-t from-[#2e1065] via-transparent to-transparent opacity-80"></div>
|
|
|
|
{/* Top Left: Popularity Badge */}
|
|
{!char.isLocked && (
|
|
<div className="absolute top-3 left-3 flex items-center gap-1 bg-black/40 backdrop-blur-md border border-white/10 pl-2 pr-2.5 py-1 rounded-full shadow-lg z-10">
|
|
<Flame size={12} className="text-[#F472B6] fill-[#F472B6]" />
|
|
<span className="text-xs font-mono font-bold text-white tracking-wide">
|
|
{char.compatibility}%
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Lock Icon */}
|
|
{char.isLocked && (
|
|
<div className="absolute top-3 right-3 w-8 h-8 rounded-full bg-black/40 backdrop-blur-md flex items-center justify-center border border-white/20 z-10">
|
|
<Lock size={14} className="text-white/80" />
|
|
</div>
|
|
)}
|
|
|
|
{/* Content info */}
|
|
<div className="absolute bottom-0 left-0 w-full p-4 flex flex-col items-start">
|
|
<div className="w-full">
|
|
<h2 className="text-lg font-bold text-white leading-tight mb-1 drop-shadow-md">{char.name}</h2>
|
|
<div className="flex flex-wrap gap-1.5 opacity-90">
|
|
{char.tags.slice(0, 2).map((tag, i) => (
|
|
<span key={i} className="text-[10px] text-white bg-white/10 border border-white/10 px-2 py-0.5 rounded-md backdrop-blur-md">
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Empty State */}
|
|
{filteredCharacters.length === 0 && (
|
|
<div className="flex flex-col items-center justify-center py-12 text-white/50">
|
|
<p className="text-sm">暂无匹配角色</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Discovery; |