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 (
Two Sum {tier === 'free' && 200 Problems Available}
Easy
Amazon
{tier === 'free' && }
{typed}▊
{done && ✓ Accepted}
);
}
// ── 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 (
Explain how HashMap works in Java
{phase >= 1 && (
AI
{phase === 1 ? (
{[0,1,2].map(i => )}
) : (
{words.slice(0, wordIdx).join(' ')}
)}
)}
);
}
// ── 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 (
{nodes.map((n, i) => (
{n.done ? '✓' : n.active ? '▶' : '🔒'}
{i < nodes.length - 1 &&
}
))}
{nodes.map((n, i) =>
{n.label}
)}
XP Progress{xp}%
🪙 {coins} coins earned
);
}
// ── 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 (
{fmt(h)}:{fmt(m)}:{fmt(s)}
● {live.toLocaleString()} solving now
{lb.map(([rank, user, score, time], i) => (
{badges[i] || rank}
{user}
{score}
{time}
))}
{submitted && ✓ Accepted! +100 pts}
);
}
// ── 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 (
{prizes.map((p, i) => (
{p.icon}
{p.label}
{p.reward}
))}
Your Global Rank (4 weeks)
#342↑ 18 places this week
);
}
// ── 6. Compiler Preview ─────────────────────────────────────────
function CompilerPreview({ tier }: { tier: 'free' | 'pro' }) {
const [running, setRunning] = useState(false);
const [lines, setLines] = useState([]);
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 (
{['Python', 'Java', 'C'].map(l => {l})}
{['C++', 'Go', 'Rust'].map(l => (
{l} {tier === 'free' && }
))}
{code}
$ output
{lines.map((l, i) =>
$ {l})}
{running &&
_}
);
}
// ── 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 (
HLD: URL Shortener System
Available Modules
{['01. Client-Server Model', '02. Network Protocols', '03. Load Balancing', '04. Database Scaling'].map(m => (
{m}
))}
{['05. Caching Strategies', '06. Message Queues', '07. Microservices'].map(m => (
{m}
{tier === 'free' && }
))}
);
}
// ── 8. CS Fundamentals Preview ──────────────────────────────────
function CSFundamentalsPreview({ tier }: { tier: 'free' | 'pro' }) {
const [tab, setTab] = useState('OS');
const cards: Record = {
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 (
{['OS','DBMS','CN'].map(t => (
))}
{cards[tab].q}
{cards[tab].a}
Available Modules
{['01. OS Basics', '02. Process Management', '03. Memory Management', '04. File Systems'].map(m => (
{m}
))}
{['05. Concurrency', '06. Deadlocks Deep Dive', '07. Networking Layers'].map(m => (
{m}
{tier === 'free' && }
))}
);
}
// ── 9. Aptitude Preview ─────────────────────────────────────────
function AptitudePreview({ tier }: { tier: 'free' | 'pro' }) {
const [secs, setSecs] = useState(30);
const [selected, setSelected] = useState(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 (
⏱ {secs}s remaining
0 ? { scale: [1,1.3,1] } : {}} className="text-sm font-bold text-amber-400">Score: {score > 0 ? `+${score}` : '0'}
If a train travels 360 km in 4 hours, what is its speed in m/s?
{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 ;
})}
{selected && 💡 360 km/4h = 90 km/h = 90 × (1000/3600) = 25 m/s}
Available Modules
{['01. Time & Distance', '02. Percentages', '03. Profit & Loss', '04. Simple Interest'].map(m => (
{m}
))}
{['05. Permutations', '06. Probability', '07. Logical Reasoning'].map(m => (
{m}
{tier === 'free' && }
))}
);
}
// ── 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 (
Your RYP Balance
R
{coins}
{items.map((item, i) => {
const isLocked = item.premium && tier === 'free';
return (
{item.icon}
{item.name}
{item.price} Coins
{!isLocked && coins >= item.price && (
Redeemable!
)}
{!isLocked && = item.price ? '0%' : `${100 - (coins / item.price) * 100}%` }} transition={{ duration: 0.2 }} />}
{isLocked && (
Pro Only
)}
);
})}
);
}
// ── PREVIEW MAP ─────────────────────────────────────────────────
const PREVIEW_MAP: Record> = {
'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 (
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 */}
{/* Header with Toggle */}
{feature.title}
{feature.desc}
{/* Free vs Pro Toggle */}
{/* Preview */}
{PreviewComponent ?
:
Preview coming soon.
}
{/* CTA */}
);
}