RYP / src /components /FeaturePreviewModal.tsx
Soumya79's picture
Upload 1361 files
f91a684 verified
import { useEffect, useState, useRef } from 'react';
import { motion, AnimatePresence, useReducedMotion } from 'motion/react';
import { Monitor, Shuffle, Server, Database, Zap, X, Gift, Lock, Sparkles } from 'lucide-react';
import type { LandingAuthMode } from './LandingPage';
type Feature = { icon: React.ElementType; title: string; desc: string; color: string; accent: string; image: string | null };
// ── 1. DSA Preview ─────────────────────────────────────────────
function DSAPreview({ tier }: { tier: 'free' | 'pro' }) {
const code = `def twoSum(nums, target):\n seen = {}\n for i, num in enumerate(nums):\n comp = target - num\n if comp in seen:\n return [seen[comp], i]\n seen[num] = i`;
const [typed, setTyped] = useState('');
const [done, setDone] = useState(false);
useEffect(() => {
let i = 0;
const t = setInterval(() => {
i++;
setTyped(code.slice(0, i));
if (i >= code.length) { clearInterval(t); setDone(true); }
}, 30);
return () => clearInterval(t);
}, []);
return (
<div className="space-y-4">
<div className="flex items-center gap-3 p-4 rounded-xl bg-white/5 border border-white/10">
<span className="font-bold text-white flex items-center gap-2">Two Sum {tier === 'free' && <span className="text-xs font-normal text-slate-500 bg-black/50 px-2 py-0.5 rounded-full border border-white/10">200 Problems Available</span>}</span>
<span className="px-2 py-0.5 rounded-full bg-emerald-500/20 text-emerald-400 text-xs font-bold">Easy</span>
<div className={`px-2 py-0.5 rounded-full bg-orange-500/10 text-orange-400 text-xs flex items-center gap-1 ${tier === 'free' ? 'blur-sm opacity-50 relative' : ''}`}>
Amazon
{tier === 'free' && <Lock size={10} className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-white blur-none opacity-100" />}
</div>
</div>
<div className="rounded-xl bg-[#0d0d1a] border border-white/10 p-4 font-mono text-sm text-green-300 whitespace-pre min-h-[160px]">{typed}<span className="animate-pulse">β–Š</span></div>
<AnimatePresence>{done && <motion.div initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-emerald-500/20 text-emerald-400 font-bold text-sm">βœ“ Accepted</motion.div>}</AnimatePresence>
</div>
);
}
// ── 2. AI Tutor Preview ─────────────────────────────────────────
function AITutorPreview() {
const [phase, setPhase] = useState(0);
const words = "A HashMap stores key-value pairs using a hash function. When you call put(key, value), Java computes hashCode() of the key, maps it to a bucket index. Average O(1) for get/put!".split(' ');
const [wordIdx, setWordIdx] = useState(0);
useEffect(() => {
const t1 = setTimeout(() => setPhase(1), 500);
const t2 = setTimeout(() => setPhase(2), 1400);
return () => { clearTimeout(t1); clearTimeout(t2); };
}, []);
useEffect(() => {
if (phase !== 2) return;
if (wordIdx >= words.length) return;
const t = setTimeout(() => setWordIdx(w => w + 1), 80);
return () => clearTimeout(t);
}, [phase, wordIdx]);
return (
<div className="rounded-xl bg-[#0d0d1a] border border-white/10 p-4 space-y-3 min-h-[200px]">
<div className="flex justify-end"><div className="max-w-[75%] px-4 py-2 rounded-2xl rounded-tr-sm bg-blue-600 text-white text-sm">Explain how HashMap works in Java</div></div>
{phase >= 1 && (
<div className="flex gap-2 items-start">
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-violet-500 to-purple-700 flex items-center justify-center text-[10px] font-black text-white shrink-0">AI</div>
{phase === 1 ? (
<div className="flex gap-1 items-center px-4 py-3 rounded-2xl rounded-tl-sm border border-violet-500/30 bg-violet-500/10">
{[0,1,2].map(i => <motion.div key={i} className="w-1.5 h-1.5 rounded-full bg-violet-400" animate={{ y: [0,-5,0] }} transition={{ duration: 0.6, repeat: Infinity, delay: i*0.15 }} />)}
</div>
) : (
<div className="px-4 py-2 rounded-2xl rounded-tl-sm border border-violet-500/30 bg-violet-500/10 text-slate-200 text-sm leading-relaxed">{words.slice(0, wordIdx).join(' ')}</div>
)}
</div>
)}
</div>
);
}
// ── 3. Quests Preview ───────────────────────────────────────────
function QuestsPreview() {
const [xp, setXp] = useState(0);
const [coins, setCoins] = useState(0);
useEffect(() => {
const t = setInterval(() => {
setXp(v => Math.min(v + 2, 68));
setCoins(v => Math.min(v + 10, 340));
}, 30);
return () => clearInterval(t);
}, []);
const nodes = [
{ label: 'Arrays Basics', done: true }, { label: 'Sorting Algorithms', done: true },
{ label: 'Binary Search', active: true }, { label: 'Trees & Graphs', locked: true }, { label: 'Dynamic Programming', locked: true }
];
return (
<div className="flex gap-6">
<div className="flex flex-col items-center gap-0">
{nodes.map((n, i) => (
<div key={i} className="flex flex-col items-center">
<motion.div initial={{ scale: 0 }} animate={{ scale: 1 }} transition={{ delay: i * 0.1 }}
className={`w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold border-2 ${n.done ? 'bg-emerald-500/20 border-emerald-500 text-emerald-400' : n.active ? 'bg-amber-500/20 border-amber-400 text-amber-300' : 'bg-white/5 border-white/20 text-slate-500'}`}>
{n.done ? 'βœ“' : n.active ? 'β–Ά' : 'πŸ”’'}
</motion.div>
{i < nodes.length - 1 && <div className={`w-0.5 h-8 ${n.done ? 'bg-emerald-500' : 'bg-white/10'}`} />}
</div>
))}
</div>
<div className="flex-1 space-y-4">
{nodes.map((n, i) => <div key={i} className={`text-sm font-medium ${n.done ? 'text-emerald-300' : n.active ? 'text-amber-300' : 'text-slate-500'}`}>{n.label}</div>)}
<div className="pt-4 border-t border-white/10">
<div className="flex justify-between text-xs text-slate-400 mb-1"><span>XP Progress</span><span>{xp}%</span></div>
<div className="h-2 rounded-full bg-white/10"><motion.div className="h-full rounded-full bg-gradient-to-r from-amber-500 to-orange-400" style={{ width: `${xp}%` }} /></div>
<div className="mt-3 text-amber-400 font-bold text-sm">πŸͺ™ {coins} coins earned</div>
</div>
</div>
</div>
);
}
// ── 4. Daily Contest Preview ────────────────────────────────────
function DailyContestPreview() {
const [secs, setSecs] = useState(2843);
const [submitted, setSubmitted] = useState(false);
const [live, setLive] = useState(1247);
useEffect(() => {
const t = setInterval(() => setSecs(s => Math.max(0, s - 1)), 1000);
const t2 = setInterval(() => setLive(v => v + Math.floor(Math.random() * 3)), 2000);
return () => { clearInterval(t); clearInterval(t2); };
}, []);
const h = Math.floor(secs / 3600), m = Math.floor((secs % 3600) / 60), s = secs % 60;
const fmt = (n: number) => String(n).padStart(2, '0');
const lb = [['#1','user_ace','320','12:45'],['#2','coder99','295','14:20'],['#3','ryp_star','280','16:55'],['#4','devpro','240','18:30'],['#5','you','180','22:10']];
const badges = ['πŸ₯‡','πŸ₯ˆ','πŸ₯‰','',''];
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="font-mono text-2xl font-black text-white">{fmt(h)}:{fmt(m)}:{fmt(s)}</div>
<div className="text-xs text-emerald-400 animate-pulse">● {live.toLocaleString()} solving now</div>
</div>
<div className="rounded-xl overflow-hidden border border-white/10">
{lb.map(([rank, user, score, time], i) => (
<motion.div key={i} initial={{ opacity: 0, x: -10 }} animate={{ opacity: 1, x: 0 }} transition={{ delay: i * 0.08 }}
className="flex items-center gap-3 px-4 py-2.5 border-b border-white/5 text-sm hover:bg-white/5">
<span className="w-8 text-slate-400">{badges[i] || rank}</span>
<span className="flex-1 font-medium text-white">{user}</span>
<span className="text-emerald-400 font-bold">{score}</span>
<span className="text-slate-500">{time}</span>
</motion.div>
))}
</div>
<div className="flex justify-center">
<button onClick={() => setSubmitted(true)} className="px-6 py-2 rounded-xl bg-blue-600 text-white font-bold text-sm hover:bg-blue-500 transition-colors">Submit Solution</button>
</div>
<AnimatePresence>{submitted && <motion.div initial={{ opacity: 0, scale: 0.8 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0 }} className="text-center text-emerald-400 font-bold">βœ“ Accepted! +100 pts</motion.div>}</AnimatePresence>
</div>
);
}
// ── 5. Weekly Arena Preview ─────────────────────────────────────
function WeeklyArenaPreview() {
const prizes = [{ icon: 'πŸ₯‡', label: '1st Place', reward: 'β‚Ή500 + Gold Badge' }, { icon: 'πŸ₯ˆ', label: '2nd Place', reward: 'β‚Ή250' }, { icon: 'πŸ₯‰', label: '3rd Place', reward: 'β‚Ή100' }];
const pts = [[1,850],[2,600],[3,340],[4,120]];
const maxR = 900;
const w = 260, h = 80, padX = 20, padY = 10;
const toX = (i: number) => padX + (i / (pts.length - 1)) * (w - 2 * padX);
const toY = (r: number) => padY + ((r - 120) / (maxR - 120)) * (h - 2 * padY);
const d = pts.map(([,r], i) => `${i === 0 ? 'M' : 'L'}${toX(i)},${toY(r)}`).join(' ');
return (
<div className="space-y-4">
<div className="grid grid-cols-3 gap-3">
{prizes.map((p, i) => (
<motion.div key={i} initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: i * 0.1 }}
className="rounded-xl border border-amber-500/20 bg-amber-500/5 p-3 text-center">
<div className="text-2xl mb-1">{p.icon}</div>
<div className="text-xs text-slate-400">{p.label}</div>
<div className="text-amber-400 font-bold text-xs mt-1">{p.reward}</div>
</motion.div>
))}
</div>
<div className="rounded-xl bg-white/5 border border-white/10 p-4">
<div className="text-xs text-slate-400 mb-2">Your Global Rank (4 weeks)</div>
<svg viewBox={`0 0 ${w} ${h}`} className="w-full">
<motion.path d={d} fill="none" stroke="#10b981" strokeWidth="2" initial={{ pathLength: 0 }} animate={{ pathLength: 1 }} transition={{ duration: 1.2 }} />
{pts.map(([wk, r], i) => <circle key={i} cx={toX(i)} cy={toY(r)} r="4" fill="#10b981" />)}
</svg>
<div className="flex items-center gap-2 mt-2"><span className="text-white font-bold">#342</span><span className="text-emerald-400 text-xs">↑ 18 places this week</span></div>
</div>
</div>
);
}
// ── 6. Compiler Preview ─────────────────────────────────────────
function CompilerPreview({ tier }: { tier: 'free' | 'pro' }) {
const [running, setRunning] = useState(false);
const [lines, setLines] = useState<string[]>([]);
const output = ['Hello, RYP!', 'Count: 0', 'Count: 1', 'Count: 2', 'Count: 3', 'Count: 4'];
const run = () => {
if (running) return;
setRunning(true); setLines([]);
output.forEach((line, i) => setTimeout(() => {
setLines(prev => [...prev, line]);
if (i === output.length - 1) setRunning(false);
}, 800 + i * 200));
};
const code = `print("Hello, RYP!")\nfor i in range(5):\n print(f"Count: {i}")`;
return (
<div className="grid grid-cols-2 gap-3 min-h-[180px]">
<div className="rounded-xl bg-[#0d0d1a] border border-white/10 p-3">
<div className="flex items-center justify-between mb-2">
<div className="flex gap-1 items-center bg-black/50 p-1 rounded-lg border border-white/10">
{['Python', 'Java', 'C'].map(l => <span key={l} className={`px-2 py-0.5 text-[10px] font-bold rounded bg-white/10 ${l==='Python' ? 'text-white' : 'text-slate-400'}`}>{l}</span>)}
{['C++', 'Go', 'Rust'].map(l => (
<span key={l} className={`px-2 py-0.5 text-[10px] font-bold rounded flex items-center gap-1 bg-white/5 text-slate-600 ${tier === 'free' ? 'blur-[1px]' : 'text-slate-400 blur-none'}`}>
{l} {tier === 'free' && <Lock size={8} />}
</span>
))}
</div>
<button onClick={run} className="px-3 py-1 rounded-lg bg-emerald-600 text-white text-xs font-bold hover:bg-emerald-500 transition-colors">
{running ? '⏳' : 'β–Ά Run'}
</button>
</div>
<pre className="font-mono text-xs text-blue-300 whitespace-pre leading-5">{code}</pre>
<div className="mt-3 flex justify-end pt-3 border-t border-white/10">
<button className={`px-3 py-1.5 rounded-lg text-xs font-bold transition-all flex items-center gap-1.5 ${tier === 'free' ? 'bg-white/5 text-slate-500 cursor-not-allowed opacity-70' : 'bg-violet-600 hover:bg-violet-500 text-white shadow-[0_0_10px_rgba(139,92,246,0.5)]'}`}>
<Sparkles size={12} className={tier === 'free' ? 'text-slate-600' : 'text-amber-300'} />
{tier === 'free' ? 'Analyze Code (Pro)' : 'Analyze Code (AI)'}
{tier === 'free' && <Lock size={12} />}
</button>
</div>
</div>
<div className="rounded-xl bg-black border border-white/10 p-3 font-mono text-xs">
<div className="text-slate-500 mb-2">$ output</div>
{lines.map((l, i) => <motion.div key={i} initial={{ opacity: 0, x: -5 }} animate={{ opacity: 1, x: 0 }} className="text-green-400">$ {l}</motion.div>)}
{running && <motion.div animate={{ opacity: [1,0,1] }} transition={{ repeat: Infinity, duration: 0.8 }} className="text-slate-500">_</motion.div>}
</div>
</div>
);
}
// ── 7. System Design Preview ────────────────────────────────────
function SystemDesignPreview({ tier }: { tier: 'free' | 'pro' }) {
const boxes = [
{ label: 'Client', Icon: Monitor, x: 20, y: 80 },
{ label: 'Load Balancer', Icon: Shuffle, x: 160, y: 80 },
{ label: 'API Server 1', Icon: Server, x: 300, y: 30 },
{ label: 'API Server 2', Icon: Server, x: 300, y: 130 },
{ label: 'Redis Cache', Icon: Zap, x: 440, y: 30 },
{ label: 'PostgreSQL', Icon: Database, x: 440, y: 130 },
];
const arrows = [[0,1],[1,2],[1,3],[2,4],[3,5]];
return (
<div>
<svg viewBox="0 0 560 190" className="w-full">
{arrows.map(([a,b], i) => {
const from = boxes[a], to = boxes[b];
return <motion.line key={i} x1={from.x+70} y1={from.y+18} x2={to.x} y2={to.y+18} stroke="rgba(99,102,241,0.5)" strokeWidth="1.5" initial={{ pathLength: 0 }} animate={{ pathLength: 1 }} transition={{ delay: i*0.15, duration: 0.4 }} />;
})}
{boxes.map((box, i) => (
<motion.g key={i} initial={{ opacity: 0, scale: 0.5 }} animate={{ opacity: 1, scale: 1 }} transition={{ delay: i*0.12 }}>
<rect x={box.x} y={box.y} width="70" height="36" rx="8" fill="rgba(99,102,241,0.15)" stroke="rgba(99,102,241,0.4)" strokeWidth="1.5"/>
<text x={box.x+35} y={box.y+22} textAnchor="middle" fill="#a5b4fc" fontSize="8" fontFamily="Inter,sans-serif" fontWeight="600">{box.label}</text>
</motion.g>
))}
</svg>
<div className="text-center text-xs text-slate-500 mt-2">HLD: URL Shortener System</div>
<div className="mt-4 pt-4 border-t border-white/10 space-y-2">
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Available Modules</div>
{['01. Client-Server Model', '02. Network Protocols', '03. Load Balancing', '04. Database Scaling'].map(m => (
<div key={m} className="px-3 py-2 rounded-lg bg-white/5 border border-white/10 text-xs text-slate-300">{m}</div>
))}
{['05. Caching Strategies', '06. Message Queues', '07. Microservices'].map(m => (
<div key={m} className={`px-3 py-2 rounded-lg flex items-center justify-between text-xs ${tier === 'free' ? 'bg-black/30 border border-white/5 text-slate-600 blur-[1px]' : 'bg-white/5 border border-white/10 text-slate-300'}`}>
<span>{m}</span>
{tier === 'free' && <Lock size={12} className="text-slate-500" />}
</div>
))}
</div>
</div>
);
}
// ── 8. CS Fundamentals Preview ──────────────────────────────────
function CSFundamentalsPreview({ tier }: { tier: 'free' | 'pro' }) {
const [tab, setTab] = useState('OS');
const cards: Record<string, { q: string; a: string }> = {
OS: { q: 'What is a deadlock?', a: 'A state where processes wait for each other\'s resources indefinitely. Conditions: Mutual Exclusion, Hold & Wait, No Preemption, Circular Wait.' },
DBMS: { q: 'What is ACID?', a: 'Atomicity, Consistency, Isolation, Durability β€” properties guaranteeing reliable DB transactions.' },
CN: { q: 'TCP vs UDP?', a: 'TCP: reliable, ordered, connection-based. UDP: fast, connectionless, no guarantee.' },
};
return (
<div className="space-y-4">
<div className="flex gap-2">
{['OS','DBMS','CN'].map(t => (
<button key={t} onClick={() => setTab(t)} className={`px-4 py-1.5 rounded-full text-xs font-bold transition-all ${tab===t ? 'bg-violet-500/20 text-violet-300 border border-violet-500/40' : 'text-slate-400 hover:text-white border border-white/10'}`}>{t}</button>
))}
</div>
<AnimatePresence mode="wait">
<motion.div key={tab} initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -8 }} transition={{ duration: 0.2 }}
className="rounded-xl border border-white/10 bg-white/5 p-5 space-y-3">
<div className="text-sm font-bold text-white">{cards[tab].q}</div>
<div className="text-sm text-slate-300 leading-relaxed">{cards[tab].a}</div>
<div className="flex items-center gap-2 pt-2 border-t border-white/10">
<div className="h-1 flex-1 rounded-full bg-white/10"><div className="h-full w-[4%] rounded-full bg-violet-500" /></div>
<span className="text-xs text-slate-500">Card 1 of 24</span>
</div>
</motion.div>
</AnimatePresence>
<div className="mt-4 pt-4 border-t border-white/10 space-y-2">
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Available Modules</div>
{['01. OS Basics', '02. Process Management', '03. Memory Management', '04. File Systems'].map(m => (
<div key={m} className="px-3 py-2 rounded-lg bg-white/5 border border-white/10 text-xs text-slate-300">{m}</div>
))}
{['05. Concurrency', '06. Deadlocks Deep Dive', '07. Networking Layers'].map(m => (
<div key={m} className={`px-3 py-2 rounded-lg flex items-center justify-between text-xs ${tier === 'free' ? 'bg-black/30 border border-white/5 text-slate-600 blur-[1px]' : 'bg-white/5 border border-white/10 text-slate-300'}`}>
<span>{m}</span>
{tier === 'free' && <Lock size={12} className="text-slate-500" />}
</div>
))}
</div>
</div>
);
}
// ── 9. Aptitude Preview ─────────────────────────────────────────
function AptitudePreview({ tier }: { tier: 'free' | 'pro' }) {
const [secs, setSecs] = useState(30);
const [selected, setSelected] = useState<string | null>(null);
const [score, setScore] = useState(0);
const opts = ['A) 20 m/s','B) 25 m/s','C) 30 m/s','D) 35 m/s'];
const correct = 'B) 25 m/s';
useEffect(() => {
if (selected) return;
const t = setInterval(() => setSecs(s => Math.max(0, s - 1)), 1000);
return () => clearInterval(t);
}, [selected]);
const pick = (opt: string) => {
if (selected) return;
setSelected(opt);
if (opt === correct) setScore(10);
};
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="text-xs text-slate-400">⏱ {secs}s remaining</div>
<motion.div animate={score > 0 ? { scale: [1,1.3,1] } : {}} className="text-sm font-bold text-amber-400">Score: {score > 0 ? `+${score}` : '0'}</motion.div>
</div>
<div className="text-sm font-medium text-white leading-relaxed">If a train travels 360 km in 4 hours, what is its speed in m/s?</div>
<div className="grid grid-cols-2 gap-2">
{opts.map(opt => {
const isCorrect = opt === correct;
const isSelected = selected === opt;
let cls = 'border border-white/10 bg-white/5 text-slate-300 hover:border-white/30';
if (selected) cls = isCorrect ? 'border-emerald-500 bg-emerald-500/20 text-emerald-300 font-bold' : isSelected ? 'border-red-500 bg-red-500/10 text-red-400' : 'border-white/5 bg-white/[0.02] text-slate-600';
return <button key={opt} onClick={() => pick(opt)} className={`px-4 py-2.5 rounded-xl text-xs text-left transition-all ${cls}`}>{opt} {selected && isCorrect ? 'βœ“' : ''}</button>;
})}
</div>
<AnimatePresence>{selected && <motion.div initial={{ opacity:0, height:0 }} animate={{ opacity:1, height:'auto' }} className="text-xs text-slate-300 bg-white/5 rounded-xl p-3 border border-white/10">πŸ’‘ 360 km/4h = 90 km/h = 90 Γ— (1000/3600) = <span className="text-emerald-400 font-bold">25 m/s</span></motion.div>}</AnimatePresence>
<div className="mt-4 pt-4 border-t border-white/10 space-y-2">
<div className="text-xs font-bold text-slate-400 mb-2 uppercase tracking-wider">Available Modules</div>
{['01. Time & Distance', '02. Percentages', '03. Profit & Loss', '04. Simple Interest'].map(m => (
<div key={m} className="px-3 py-2 rounded-lg bg-white/5 border border-white/10 text-xs text-slate-300">{m}</div>
))}
{['05. Permutations', '06. Probability', '07. Logical Reasoning'].map(m => (
<div key={m} className={`px-3 py-2 rounded-lg flex items-center justify-between text-xs ${tier === 'free' ? 'bg-black/30 border border-white/5 text-slate-600 blur-[1px]' : 'bg-white/5 border border-white/10 text-slate-300'}`}>
<span>{m}</span>
{tier === 'free' && <Lock size={12} className="text-slate-500" />}
</div>
))}
</div>
</div>
);
}
// ── 10. Swag Store Preview ──────────────────────────────────────
function SwagStorePreview({ tier }: { tier: 'free' | 'pro' }) {
const [coins, setCoins] = useState(0);
const items = [
{ name: 'RYP Pen', price: 100, icon: 'πŸ–ŠοΈ', bg: 'bg-indigo-500/10' },
{ name: 'RYP T-Shirt', price: 1500, icon: 'πŸ‘•', bg: 'bg-blue-500/10' },
{ name: 'Sticker Pack', price: 300, icon: '✨', bg: 'bg-purple-500/10', premium: true },
{ name: 'Coffee Mug', price: 800, icon: 'β˜•', bg: 'bg-orange-500/10', premium: true },
];
useEffect(() => {
const t = setInterval(() => {
setCoins(prev => (prev >= 2000 ? 0 : prev + 50));
}, 100);
return () => clearInterval(t);
}, []);
return (
<div className="space-y-4">
<div className="flex items-center justify-between p-4 rounded-xl border border-white/10 bg-white/5">
<div className="text-sm text-slate-300">Your RYP Balance</div>
<div className="flex items-center gap-2">
<motion.div animate={{ rotateY: 360 }} transition={{ duration: 2, repeat: Infinity, ease: 'linear' }} className="w-5 h-5 rounded-full bg-amber-400 border border-amber-500 shadow-[0_0_10px_rgba(251,191,36,0.5)] flex items-center justify-center">
<span className="text-[10px] font-black text-amber-900">R</span>
</motion.div>
<span className="text-xl font-bold text-amber-400">{coins}</span>
</div>
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
{items.map((item, i) => {
const isLocked = item.premium && tier === 'free';
return (
<motion.div key={i} className={`p-4 rounded-xl border border-white/10 ${item.bg} flex flex-col items-center gap-2 relative overflow-hidden`}>
<div className={`text-3xl mb-1 ${isLocked ? 'blur-sm grayscale' : ''}`}>{item.icon}</div>
<div className={`text-sm font-bold text-center ${isLocked ? 'text-slate-500 blur-[1px]' : 'text-white'}`}>{item.name}</div>
<div className={`text-xs font-medium ${isLocked ? 'text-slate-600 blur-[1px]' : 'text-amber-400/80'}`}>{item.price} Coins</div>
{!isLocked && coins >= item.price && (
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} className="absolute inset-x-0 bottom-0 py-1.5 bg-emerald-500/20 text-emerald-400 text-[10px] font-bold text-center border-t border-emerald-500/30">
Redeemable!
</motion.div>
)}
{!isLocked && <motion.div className="absolute inset-0 bg-white/5 pointer-events-none" initial={{ top: '100%' }} animate={{ top: coins >= item.price ? '0%' : `${100 - (coins / item.price) * 100}%` }} transition={{ duration: 0.2 }} />}
{isLocked && (
<div className="absolute inset-0 bg-black/40 flex flex-col items-center justify-center backdrop-blur-[1px]">
<Lock size={20} className="text-white/50 mb-1" />
<span className="text-[10px] font-bold text-white/50">Pro Only</span>
</div>
)}
</motion.div>
);
})}
</div>
</div>
);
}
// ── PREVIEW MAP ─────────────────────────────────────────────────
const PREVIEW_MAP: Record<string, React.FC<{ tier: 'free' | 'pro' }>> = {
'DSA Practice': DSAPreview,
'RYP AI Tutor': AITutorPreview,
'RYP Quests': QuestsPreview,
'Daily Contest': DailyContestPreview,
'Weekly Arena': WeeklyArenaPreview,
'Online Compiler': CompilerPreview,
'System Design': SystemDesignPreview,
'CS Fundamentals': CSFundamentalsPreview,
'Aptitude': AptitudePreview,
'RYP Swag Store': SwagStorePreview,
};
// ── MODAL SHELL ─────────────────────────────────────────────────
interface FeaturePreviewModalProps {
feature: Feature | null;
onClose: () => void;
onGetStarted: (mode?: LandingAuthMode) => void;
}
export default function FeaturePreviewModal({ feature, onClose, onGetStarted }: FeaturePreviewModalProps) {
const reduced = useReducedMotion();
const [tier, setTier] = useState<'free' | 'pro'>('free');
const PreviewComponent = feature ? PREVIEW_MAP[feature.title] : null;
useEffect(() => {
if (!feature) return;
const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [feature, onClose]);
if (!feature) return null;
const panelAnim = reduced
? {}
: { initial: { opacity: 0, scale: 0.92, y: 30 }, animate: { opacity: 1, scale: 1, y: 0 }, exit: { opacity: 0, scale: 0.95, y: 20 } };
return (
<div className="fixed inset-0 z-[100] flex items-center justify-center md:p-4" onClick={onClose}>
<div className="absolute inset-0 bg-black/80 backdrop-blur-xl" />
<motion.div
{...panelAnim}
transition={{ duration: 0.35, ease: 'easeOut' }}
onClick={e => e.stopPropagation()}
className="relative w-full max-w-3xl mx-4 md:mx-auto bg-[#0a0a12] border border-white/10 rounded-t-[2rem] md:rounded-[2.5rem] shadow-[0_0_80px_rgba(0,0,0,0.8)] max-h-[85vh] md:max-h-[80vh] overflow-y-auto fixed bottom-0 md:relative md:bottom-auto"
>
{/* Close */}
<button onClick={onClose} className="absolute top-4 right-4 text-slate-400 hover:text-white text-2xl z-10 w-8 h-8 flex items-center justify-center rounded-full hover:bg-white/10 transition-colors">Γ—</button>
{/* Header with Toggle */}
<div className={`p-6 pb-4 border-b border-white/10 bg-gradient-to-r ${feature.color} bg-opacity-10`}>
<div className="flex justify-between items-start">
<div>
<div className={`w-12 h-12 rounded-2xl flex items-center justify-center mb-3 bg-gradient-to-br ${feature.color} shadow-lg`}>
<feature.icon size={22} className="text-white" />
</div>
<h3 className="text-xl font-black text-white">{feature.title}</h3>
<p className="text-slate-400 text-sm mt-1">{feature.desc}</p>
</div>
{/* Free vs Pro Toggle */}
<div className="flex items-center gap-1 bg-black/50 p-1 rounded-xl border border-white/10 shrink-0">
<button
onClick={() => setTier('free')}
className={`px-3 py-1.5 rounded-lg text-xs font-bold transition-colors ${tier === 'free' ? 'bg-white/10 text-white' : 'text-slate-500 hover:text-slate-300'}`}
>
Free
</button>
<button
onClick={() => setTier('pro')}
className={`px-3 py-1.5 rounded-lg text-xs font-bold transition-all flex items-center gap-1 ${tier === 'pro' ? 'bg-gradient-to-r from-violet-500 to-fuchsia-500 text-white shadow-[0_0_10px_rgba(139,92,246,0.5)]' : 'text-slate-500 hover:text-slate-300'}`}
>
<Zap size={12} className={tier === 'pro' ? 'text-amber-300' : ''} /> Pro
</button>
</div>
</div>
</div>
{/* Preview */}
<div className="p-6">
{PreviewComponent ? <PreviewComponent tier={tier} /> : <div className="text-slate-400 text-sm">Preview coming soon.</div>}
</div>
{/* CTA */}
<div className="px-6 pb-6">
<button
onClick={() => { onClose(); onGetStarted('register'); }}
className="w-full py-3.5 rounded-xl bg-white text-black font-extrabold hover:scale-[1.02] transition-transform text-sm"
>
Try it Now β†’
</button>
</div>
</motion.div>
</div>
);
}