feat: initial frontend build for RIT Freshers Hub

This commit is contained in:
Amudieshwar A G
2026-07-21 12:21:53 +05:30
commit e2c86755ef
40 changed files with 8544 additions and 0 deletions

View File

@@ -0,0 +1,252 @@
import { useState, useRef, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Bot, Send, Sparkles, RefreshCw, Mic, X, ChevronRight } from 'lucide-react';
import { AI_SUGGESTED_PROMPTS } from '@/constants';
import type { ChatMessage } from '@/types';
const INITIAL_MESSAGES: ChatMessage[] = [
{
id: '1',
role: 'assistant',
content: "👋 Hi there! I'm your **RIT AI Assistant**, powered by Google Gemini. I have comprehensive knowledge about Rajalakshmi Institute of Technology — from admission procedures to campus facilities.\n\nHow can I help you today?",
timestamp: new Date().toISOString(),
},
];
function formatContent(text: string) {
return text
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
.replace(/\n/g, '<br/>');
}
export default function AIAssistant() {
const [messages, setMessages] = useState<ChatMessage[]>(INITIAL_MESSAGES);
const [input, setInput] = useState('');
const [isTyping, setIsTyping] = useState(false);
const messagesEndRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages, isTyping]);
const sendMessage = async (text: string) => {
if (!text.trim()) return;
const userMsg: ChatMessage = {
id: Date.now().toString(),
role: 'user',
content: text,
timestamp: new Date().toISOString(),
};
setMessages((prev) => [...prev, userMsg]);
setInput('');
setIsTyping(true);
// Simulate AI response
await new Promise((r) => setTimeout(r, 1500));
setIsTyping(false);
const responses: Record<string, string> = {
hostel: "**RIT Hostel Facilities:**\n\n• Separate hostels for boys and girls\n• Wi-Fi connectivity in all rooms\n• 24/7 security and CCTV\n• Hygienic canteen with vegetarian & non-vegetarian options\n• Common rooms with TV and recreation\n• In-house medical facility\n\nFor hostel admission, contact the hostel office with your Aadhaar card, medical certificate, and filled hostel application form.",
bus: "**RIT Bus Routes:**\n\nRIT operates **15+ bus routes** covering major areas of Chennai:\n\n• Route 01: Chennai Central → Koyambedu → Porur → RIT (7:00 AM)\n• Route 02: Tambaram → Chrompet → Pallavaram → RIT (7:15 AM)\n• Route 03: Anna Nagar → Vadapalani → RIT (7:20 AM)\n\nAll buses depart from respective stops by 7:30 AM. Return buses leave RIT at 4:30 PM and 6:00 PM.",
library: "**RIT Library Information:**\n\n• **Timings:** Mon-Sat 8:00 AM 8:00 PM, Sunday 10:00 AM 5:00 PM\n• **Collection:** Over 50,000 books, 200+ journals\n• **Digital Access:** IEEE Xplore, ACM Digital Library, Scopus\n• **Services:** Book borrowing (4 books, 14 days), Reference, Photocopying\n• **Wi-Fi:** Available throughout the library\n\nYou'll need your student ID card to access the library.",
};
const lower = text.toLowerCase();
let responseText = '';
if (lower.includes('hostel')) responseText = responses.hostel;
else if (lower.includes('bus') || lower.includes('route')) responseText = responses.bus;
else if (lower.includes('library')) responseText = responses.library;
else {
responseText = `Thank you for your question about **"${text}"**!\n\nI'm connected to RIT's knowledge base and can help you with:\n\n• 📚 Academic information & syllabus\n• 🏠 Hostel & accommodation\n• 🚌 Bus routes & timings\n• 📋 Admission procedures\n• 🏛️ Campus facilities\n• 👩‍🏫 Faculty information\n• 🎉 Events & clubs\n\nCould you be more specific about what you'd like to know? I'll give you the most accurate information!`;
}
const aiMsg: ChatMessage = {
id: (Date.now() + 1).toString(),
role: 'assistant',
content: responseText,
timestamp: new Date().toISOString(),
};
setMessages((prev) => [...prev, aiMsg]);
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage(input);
}
};
const clearChat = () => {
setMessages(INITIAL_MESSAGES);
};
return (
<div className="min-h-screen flex flex-col" style={{ backgroundColor: '#FAFAFA' }}>
{/* Header */}
<div className="bg-white border-b border-[#E5E7EB] py-4 sticky top-16 z-40">
<div className="container-custom flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
<Bot className="w-5 h-5 text-white" />
</div>
<div>
<h1 className="font-semibold text-[#1E293B] text-sm" style={{ fontFamily: 'Poppins, sans-serif' }}>RIT AI Assistant</h1>
<div className="flex items-center gap-1.5 text-xs text-[#94A3B8]">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
Powered by Gemini AI · Campus Knowledge Enabled
</div>
</div>
</div>
<div className="flex items-center gap-2">
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={clearChat}
className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs text-[#94A3B8] hover:text-[#475569] hover:bg-gray-100 transition-all"
>
<RefreshCw className="w-3.5 h-3.5" />
New Chat
</motion.button>
</div>
</div>
</div>
<div className="flex-1 container-custom py-6 flex flex-col gap-4 max-w-4xl mx-auto w-full">
{/* Features strip */}
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="flex flex-wrap gap-2"
>
{[
{ icon: Sparkles, label: 'Gemini AI' },
{ icon: Bot, label: 'RAG Enabled' },
{ icon: ChevronRight, label: 'Campus Knowledge' },
].map((item, i) => (
<div key={i} className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-white border border-[#E5E7EB] text-xs text-[#475569]">
<item.icon className="w-3.5 h-3.5 text-[#F97316]" />
{item.label}
</div>
))}
</motion.div>
{/* Messages */}
<div
className="flex-1 bg-white rounded-3xl border border-[#E5E7EB] p-6 flex flex-col gap-4 overflow-y-auto"
style={{ minHeight: '400px', maxHeight: '65vh', boxShadow: '0 4px 25px -5px rgba(0,0,0,0.08)' }}
>
{messages.map((msg) => (
<motion.div
key={msg.id}
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className={`flex gap-3 ${msg.role === 'user' ? 'flex-row-reverse' : ''}`}
>
{msg.role === 'assistant' && (
<div className="w-8 h-8 rounded-xl flex items-center justify-center shrink-0 mt-1" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
<Bot className="w-4 h-4 text-white" />
</div>
)}
<div
className="max-w-[80%] px-4 py-3 text-sm leading-relaxed"
style={{
background: msg.role === 'user' ? 'linear-gradient(135deg, #F97316, #FB923C)' : '#F8FAFC',
color: msg.role === 'user' ? 'white' : '#1E293B',
borderRadius: msg.role === 'user' ? '20px 20px 4px 20px' : '20px 20px 20px 4px',
border: msg.role === 'ai' ? '1px solid #E5E7EB' : 'none',
fontFamily: 'Inter, sans-serif',
}}
dangerouslySetInnerHTML={{ __html: formatContent(msg.content) }}
/>
</motion.div>
))}
{/* Typing */}
<AnimatePresence>
{isTyping && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0 }}
className="flex gap-3"
>
<div className="w-8 h-8 rounded-xl flex items-center justify-center shrink-0" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
<Bot className="w-4 h-4 text-white" />
</div>
<div className="flex items-center gap-1 px-4 py-3 bg-[#F8FAFC] rounded-2xl border border-[#E5E7EB]">
{[0, 1, 2].map((i) => (
<motion.span
key={i}
className="w-2 h-2 bg-[#94A3B8] rounded-full"
animate={{ y: [0, -5, 0] }}
transition={{ duration: 0.8, repeat: Infinity, delay: i * 0.15 }}
/>
))}
</div>
</motion.div>
)}
</AnimatePresence>
<div ref={messagesEndRef} />
</div>
{/* Suggested Prompts */}
<div className="flex flex-wrap gap-2">
{AI_SUGGESTED_PROMPTS.slice(0, 4).map((prompt, i) => (
<motion.button
key={i}
whileHover={{ scale: 1.02, backgroundColor: '#FFF7ED' }}
whileTap={{ scale: 0.98 }}
onClick={() => sendMessage(prompt)}
className="px-3.5 py-2 rounded-xl border border-[#E5E7EB] text-xs text-[#475569] bg-white transition-all hover:border-[#F97316] hover:text-[#F97316]"
style={{ fontFamily: 'Inter, sans-serif' }}
>
{prompt}
</motion.button>
))}
</div>
{/* Input */}
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-3 flex items-center gap-3" style={{ boxShadow: '0 4px 20px -4px rgba(0,0,0,0.08)' }}>
<input
ref={inputRef}
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Ask anything about RIT..."
className="flex-1 text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none bg-transparent"
style={{ fontFamily: 'Inter, sans-serif' }}
/>
{input && (
<motion.button
initial={{ scale: 0 }}
animate={{ scale: 1 }}
whileTap={{ scale: 0.9 }}
onClick={() => setInput('')}
className="w-8 h-8 rounded-xl flex items-center justify-center text-[#94A3B8] hover:bg-gray-100 transition-all"
>
<X className="w-4 h-4" />
</motion.button>
)}
<button className="w-9 h-9 rounded-xl flex items-center justify-center text-[#94A3B8] hover:bg-gray-100 transition-all">
<Mic className="w-4.5 h-4.5" />
</button>
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => sendMessage(input)}
disabled={!input.trim()}
className="w-10 h-10 rounded-xl flex items-center justify-center text-white transition-all disabled:opacity-50"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
>
<Send className="w-4 h-4" />
</motion.button>
</div>
</div>
</div>
);
}

230
src/pages/Campus/Campus.tsx Normal file
View File

@@ -0,0 +1,230 @@
import { useState } from 'react';
import { motion } from 'framer-motion';
import { Search, ChevronRight, MapPin } from 'lucide-react';
import SectionTitle from '@/components/SectionTitle/SectionTitle';
import BusCard from '@/components/BusCard/BusCard';
import FacultyCard from '@/components/FacultyCard/FacultyCard';
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
import { BUS_ROUTES, FACULTY_DATA, CAMPUS_LOCATIONS, DEPARTMENTS } from '@/constants';
import * as LucideIcons from 'lucide-react';
type Tab = 'map' | 'bus' | 'faculty';
export default function Campus() {
const [activeTab, setActiveTab] = useState<Tab>('map');
const [searchFaculty, setSearchFaculty] = useState('');
const [selectedDept, setSelectedDept] = useState('All Departments');
const [busSearch, setBusSearch] = useState('');
const filteredFaculty = FACULTY_DATA.filter((f) => {
const matchSearch = f.name.toLowerCase().includes(searchFaculty.toLowerCase()) ||
f.department.toLowerCase().includes(searchFaculty.toLowerCase());
const matchDept = selectedDept === 'All Departments' || f.department === selectedDept;
return matchSearch && matchDept;
});
const filteredRoutes = BUS_ROUTES.filter((r) =>
r.name.toLowerCase().includes(busSearch.toLowerCase()) ||
r.from.toLowerCase().includes(busSearch.toLowerCase()) ||
r.number.toLowerCase().includes(busSearch.toLowerCase())
);
const TABS: { id: Tab; label: string; icon: string }[] = [
{ id: 'map', label: 'Campus Map', icon: 'Map' },
{ id: 'bus', label: 'Bus Routes', icon: 'Bus' },
{ id: 'faculty', label: 'Faculty Directory', icon: 'Users' },
];
return (
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
{/* Header */}
<div className="bg-white border-b border-[#E5E7EB] py-10">
<div className="container-custom">
<div className="flex items-center gap-2 text-xs text-[#94A3B8] mb-3">
<span>Home</span>
<ChevronRight className="w-3 h-3" />
<span className="text-[#F97316]">Campus</span>
</div>
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
Explore{' '}
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
Campus
</span>
</h1>
<p className="text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
Navigate campus, find bus routes, and connect with faculty.
</p>
</div>
</div>
<div className="container-custom py-8">
{/* Tabs */}
<div className="flex gap-2 bg-white rounded-2xl p-2 border border-[#E5E7EB] mb-8 w-fit" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
{TABS.map((tab) => {
const Icon = (LucideIcons as Record<string, React.ComponentType<{ className?: string }>>)[tab.icon];
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className="relative flex items-center gap-2 px-5 py-2.5 rounded-xl text-sm font-medium transition-all"
style={{
fontFamily: 'Poppins, sans-serif',
color: activeTab === tab.id ? 'white' : '#475569',
}}
>
{activeTab === tab.id && (
<motion.div
layoutId="campus-tab"
className="absolute inset-0 rounded-xl"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
transition={{ type: 'spring', bounce: 0.2, duration: 0.4 }}
/>
)}
<span className="relative z-10 flex items-center gap-2">
{Icon && <Icon className="w-4 h-4" />}
{tab.label}
</span>
</button>
);
})}
</div>
{/* Map Tab */}
{activeTab === 'map' && (
<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
<div className="bg-white rounded-3xl border border-[#E5E7EB] overflow-hidden mb-8" style={{ boxShadow: '0 4px 25px -5px rgba(0,0,0,0.1)' }}>
<div
className="h-96 flex items-center justify-center relative overflow-hidden"
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)' }}
>
<div
className="absolute inset-0 opacity-10"
style={{
backgroundImage: 'linear-gradient(rgba(249,115,22,0.5) 1px, transparent 1px), linear-gradient(90deg, rgba(249,115,22,0.5) 1px, transparent 1px)',
backgroundSize: '40px 40px',
}}
/>
{/* Mock campus blocks */}
{[
{ label: 'CSE Block', x: '20%', y: '25%', w: '14%', h: '18%' },
{ label: 'ECE Block', x: '38%', y: '25%', w: '12%', h: '18%' },
{ label: 'Library', x: '58%', y: '30%', w: '16%', h: '14%' },
{ label: 'Canteen', x: '65%', y: '58%', w: '12%', h: '12%' },
{ label: 'Hostel', x: '18%', y: '58%', w: '14%', h: '20%' },
{ label: 'Auditorium', x: '40%', y: '55%', w: '18%', h: '14%' },
].map((block, i) => (
<motion.div
key={i}
whileHover={{ scale: 1.05 }}
className="absolute flex items-center justify-center rounded-lg cursor-pointer group"
style={{
left: block.x,
top: block.y,
width: block.w,
height: block.h,
backgroundColor: 'rgba(249,115,22,0.15)',
border: '1px solid rgba(249,115,22,0.4)',
}}
>
<span className="text-[10px] text-orange-300 font-medium text-center leading-tight px-1" style={{ fontFamily: 'Inter, sans-serif' }}>
{block.label}
</span>
</motion.div>
))}
{/* Pin */}
<div className="relative z-10 text-center">
<div className="flex items-center gap-2 text-white/40 text-sm">
<MapPin className="w-4 h-4" />
<span>RIT Campus, Kuthambakkam, Chennai</span>
</div>
</div>
</div>
{/* Quick Nav */}
<div className="p-6 grid grid-cols-4 sm:grid-cols-8 gap-4">
{CAMPUS_LOCATIONS.map((loc) => {
const Icon = (LucideIcons as Record<string, React.ComponentType<{ className?: string }>>)[loc.icon];
return (
<motion.button
key={loc.id}
whileHover={{ y: -3, backgroundColor: '#FFF7ED' }}
className="flex flex-col items-center gap-2 p-3 rounded-xl transition-all hover:border-[#FED7AA] border border-transparent"
>
<div className="w-10 h-10 rounded-xl bg-[#F8FAFC] flex items-center justify-center">
{Icon && <Icon className="w-5 h-5 text-[#F97316]" />}
</div>
<span className="text-[11px] text-[#475569] text-center leading-tight" style={{ fontFamily: 'Inter, sans-serif' }}>
{loc.name}
</span>
</motion.button>
);
})}
</div>
</div>
</motion.div>
)}
{/* Bus Routes Tab */}
{activeTab === 'bus' && (
<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
<div className="relative mb-6">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-[#94A3B8]" />
<input
type="text"
placeholder="Search bus routes..."
value={busSearch}
onChange={(e) => setBusSearch(e.target.value)}
className="w-full pl-11 pr-4 py-3 rounded-xl border border-[#E5E7EB] bg-white text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] transition-colors"
style={{ fontFamily: 'Inter, sans-serif' }}
/>
</div>
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{filteredRoutes.map((route) => (
<StaggerItem key={route.id}>
<BusCard route={route} />
</StaggerItem>
))}
</StaggerContainer>
</motion.div>
)}
{/* Faculty Tab */}
{activeTab === 'faculty' && (
<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
<div className="flex flex-col sm:flex-row gap-4 mb-6">
<div className="relative flex-1">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-[#94A3B8]" />
<input
type="text"
placeholder="Search faculty by name or department..."
value={searchFaculty}
onChange={(e) => setSearchFaculty(e.target.value)}
className="w-full pl-11 pr-4 py-3 rounded-xl border border-[#E5E7EB] bg-white text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] transition-colors"
style={{ fontFamily: 'Inter, sans-serif' }}
/>
</div>
<select
value={selectedDept}
onChange={(e) => setSelectedDept(e.target.value)}
className="px-4 py-3 rounded-xl border border-[#E5E7EB] bg-white text-sm text-[#475569] focus:outline-none focus:border-[#F97316] transition-colors"
style={{ fontFamily: 'Inter, sans-serif' }}
>
{DEPARTMENTS.map((dept) => (
<option key={dept} value={dept}>{dept}</option>
))}
</select>
</div>
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{filteredFaculty.map((faculty) => (
<StaggerItem key={faculty.id}>
<FacultyCard faculty={faculty} />
</StaggerItem>
))}
</StaggerContainer>
</motion.div>
)}
</div>
</div>
);
}

View File

@@ -0,0 +1,394 @@
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
MessageCircle, Heart, TrendingUp, ThumbsUp, Send,
Shield, Lock, Smile, ChevronRight, Search, Plus
} from 'lucide-react';
import SectionTitle from '@/components/SectionTitle/SectionTitle';
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer';
import { QUESTIONS_DATA, CONFESSIONS_DATA } from '@/constants';
import { formatDate } from '@/lib/utils';
type Tab = 'qa' | 'confession';
const TRENDING_TAGS = ['hostel', 'academics', 'clubs', 'campus', 'canteen', 'sports', 'placement', 'library'];
export default function Community() {
const [activeTab, setActiveTab] = useState<Tab>('qa');
const [confessionText, setConfessionText] = useState('');
const [questionText, setQuestionText] = useState('');
const [confessionPosted, setConfessionPosted] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [likedIds, setLikedIds] = useState<Set<string>>(new Set());
const filteredQuestions = QUESTIONS_DATA.filter((q) =>
q.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
q.tags.some((t) => t.includes(searchQuery.toLowerCase()))
);
const handleConfess = () => {
if (!confessionText.trim()) return;
setConfessionPosted(true);
setConfessionText('');
setTimeout(() => setConfessionPosted(false), 3000);
};
const toggleLike = (id: string) => {
setLikedIds((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id); else next.add(id);
return next;
});
};
return (
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
{/* Header */}
<div className="bg-white border-b border-[#E5E7EB] py-10">
<div className="container-custom">
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
RIT{' '}
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
Community
</span>
</h1>
<p className="text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
Ask questions, share confessions, and connect with fellow RIT students.
</p>
</div>
</div>
<div className="container-custom py-8">
{/* Tabs */}
<div className="flex gap-2 bg-white rounded-2xl p-2 border border-[#E5E7EB] mb-8 w-fit" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
{[
{ id: 'qa' as Tab, label: 'Freshers Q&A', icon: MessageCircle },
{ id: 'confession' as Tab, label: 'Confessions', icon: Heart },
].map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className="relative flex items-center gap-2 px-5 py-2.5 rounded-xl text-sm font-medium transition-all"
style={{ fontFamily: 'Poppins, sans-serif', color: activeTab === tab.id ? 'white' : '#475569' }}
>
{activeTab === tab.id && (
<motion.div
layoutId="community-tab"
className="absolute inset-0 rounded-xl"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
transition={{ type: 'spring', bounce: 0.2, duration: 0.4 }}
/>
)}
<span className="relative z-10 flex items-center gap-2">
<tab.icon className="w-4 h-4" />
{tab.label}
</span>
</button>
))}
</div>
<AnimatePresence mode="wait">
{/* Q&A Tab */}
{activeTab === 'qa' && (
<motion.div
key="qa"
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -16 }}
transition={{ duration: 0.3 }}
>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2">
{/* Ask Question Box */}
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-5 mb-6" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
<h3 className="text-sm font-semibold text-[#1E293B] mb-3" style={{ fontFamily: 'Poppins, sans-serif' }}>Ask a Question</h3>
<textarea
value={questionText}
onChange={(e) => setQuestionText(e.target.value)}
placeholder="What's on your mind? Ask your seniors anything about RIT..."
rows={3}
className="w-full border border-[#E5E7EB] rounded-xl p-3 text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] resize-none transition-colors mb-3"
style={{ fontFamily: 'Inter, sans-serif' }}
/>
<div className="flex items-center justify-between">
<span className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>
Your question will be visible to all students
</span>
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
className="flex items-center gap-2 px-4 py-2 rounded-xl text-white text-sm font-semibold"
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
>
<Plus className="w-4 h-4" />
Post Question
</motion.button>
</div>
</div>
{/* Search */}
<div className="relative mb-5">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-[#94A3B8]" />
<input
type="text"
placeholder="Search questions..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2.5 rounded-xl border border-[#E5E7EB] bg-white text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] transition-colors"
style={{ fontFamily: 'Inter, sans-serif' }}
/>
</div>
{/* Questions */}
<StaggerContainer className="flex flex-col gap-4">
{filteredQuestions.map((q) => (
<StaggerItem key={q.id}>
<motion.div
whileHover={{ y: -2 }}
className="bg-white rounded-2xl border border-[#E5E7EB] p-5"
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
>
<div className="flex items-start gap-4">
{/* Votes */}
<div className="flex flex-col items-center gap-1 shrink-0">
<button
onClick={() => toggleLike(q.id)}
className="w-8 h-8 rounded-lg flex items-center justify-center transition-all"
style={{ backgroundColor: likedIds.has(q.id) ? '#FFF7ED' : '#F8FAFC' }}
>
<ThumbsUp className="w-4 h-4" style={{ color: likedIds.has(q.id) ? '#F97316' : '#94A3B8' }} />
</button>
<span className="text-xs font-semibold text-[#475569]">{q.votes + (likedIds.has(q.id) ? 1 : 0)}</span>
</div>
<div className="flex-1">
<h3 className="text-sm font-semibold text-[#1E293B] mb-1.5 hover:text-[#F97316] cursor-pointer transition-colors"
style={{ fontFamily: 'Poppins, sans-serif' }}>
{q.title}
</h3>
<p className="text-xs text-[#94A3B8] mb-3 line-clamp-2" style={{ fontFamily: 'Inter, sans-serif' }}>{q.body}</p>
<div className="flex items-center justify-between flex-wrap gap-2">
<div className="flex flex-wrap gap-1.5">
{q.tags.map((tag) => (
<span
key={tag}
className="px-2 py-0.5 rounded-full text-[10px] font-medium bg-[#F8FAFC] text-[#94A3B8] border border-[#E5E7EB]"
style={{ fontFamily: 'Inter, sans-serif' }}
>
#{tag}
</span>
))}
</div>
<div className="flex items-center gap-3 text-xs text-[#94A3B8]">
<span className="flex items-center gap-1">
<MessageCircle className="w-3.5 h-3.5" />
{q.answers} answers
</span>
{q.isAnswered && (
<span className="px-2 py-0.5 rounded-full bg-emerald-50 text-emerald-600 text-[10px] font-semibold"> Answered</span>
)}
</div>
</div>
</div>
</div>
</motion.div>
</StaggerItem>
))}
</StaggerContainer>
</div>
{/* Sidebar */}
<div className="flex flex-col gap-5">
{/* Trending */}
<AnimatedContainer direction="right" delay={0.1}>
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-5" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
<div className="flex items-center gap-2 mb-4">
<TrendingUp className="w-4.5 h-4.5 text-[#F97316]" />
<h3 className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Trending Topics</h3>
</div>
<div className="flex flex-wrap gap-2">
{TRENDING_TAGS.map((tag) => (
<button
key={tag}
onClick={() => setSearchQuery(tag)}
className="px-3 py-1.5 rounded-full text-xs font-medium bg-[#FFF7ED] text-[#F97316] hover:bg-[#F97316] hover:text-white transition-all border border-[#FED7AA]"
style={{ fontFamily: 'Inter, sans-serif' }}
>
#{tag}
</button>
))}
</div>
</div>
</AnimatedContainer>
{/* Stats */}
<AnimatedContainer direction="right" delay={0.2}>
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-5" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
{[
{ label: 'Total Questions', value: '124' },
{ label: 'Answered', value: '98%' },
{ label: 'Active Students', value: '340+' },
].map((stat, i) => (
<div key={i} className={`flex justify-between py-2.5 ${i < 2 ? 'border-b border-[#E5E7EB]' : ''}`}>
<span className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>{stat.label}</span>
<span className="text-xs font-bold text-[#F97316]" style={{ fontFamily: 'Poppins, sans-serif' }}>{stat.value}</span>
</div>
))}
</div>
</AnimatedContainer>
</div>
</div>
</motion.div>
)}
{/* Confessions Tab */}
{activeTab === 'confession' && (
<motion.div
key="confession"
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -16 }}
transition={{ duration: 0.3 }}
>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2">
{/* Post confession */}
<div
className="rounded-3xl p-6 mb-8 border"
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)', borderColor: 'rgba(255,255,255,0.1)' }}
>
<div className="flex items-center gap-3 mb-4">
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ backgroundColor: 'rgba(249,115,22,0.2)' }}>
<Lock className="w-5 h-5 text-[#F97316]" />
</div>
<div>
<h3 className="text-white font-semibold text-sm" style={{ fontFamily: 'Poppins, sans-serif' }}>Share Anonymously</h3>
<p className="text-slate-400 text-xs">Your identity is never revealed</p>
</div>
</div>
<textarea
value={confessionText}
onChange={(e) => setConfessionText(e.target.value)}
placeholder="Share your thoughts, stories, crushes, or anything on your mind... It's completely anonymous 🤫"
rows={4}
className="w-full rounded-xl p-4 text-sm placeholder-slate-500 focus:outline-none resize-none mb-4"
style={{
fontFamily: 'Inter, sans-serif',
backgroundColor: 'rgba(255,255,255,0.06)',
border: '1px solid rgba(255,255,255,0.1)',
color: 'white',
}}
/>
<div className="flex items-center gap-3 p-3 rounded-xl mb-4" style={{ backgroundColor: 'rgba(249,115,22,0.1)', border: '1px solid rgba(249,115,22,0.2)' }}>
<Shield className="w-4 h-4 text-[#F97316] shrink-0" />
<span className="text-xs text-slate-400" style={{ fontFamily: 'Inter, sans-serif' }}>
No IP tracking. No username. 100% anonymous posting.
</span>
</div>
<div className="flex items-center gap-3">
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
onClick={handleConfess}
className="flex items-center gap-2 px-6 py-2.5 rounded-xl text-white text-sm font-semibold"
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
>
<Smile className="w-4 h-4" />
Post Anonymously
</motion.button>
<AnimatePresence>
{confessionPosted && (
<motion.span
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0 }}
className="text-xs text-emerald-400"
style={{ fontFamily: 'Inter, sans-serif' }}
>
Posted successfully!
</motion.span>
)}
</AnimatePresence>
</div>
</div>
{/* Confessions Feed */}
<StaggerContainer className="flex flex-col gap-4">
{CONFESSIONS_DATA.map((conf) => (
<StaggerItem key={conf.id}>
<motion.div
whileHover={{ y: -2 }}
className="bg-white rounded-2xl border border-[#E5E7EB] p-5"
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
>
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-2">
<div className="w-8 h-8 rounded-xl flex items-center justify-center bg-[#FFF7ED]">
<Shield className="w-4 h-4 text-[#F97316]" />
</div>
<div>
<span className="text-xs font-semibold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Anonymous</span>
<p className="text-[10px] text-[#94A3B8]">{formatDate(conf.createdAt)}</p>
</div>
</div>
{conf.category && (
<span className="px-2.5 py-0.5 rounded-full text-[10px] font-semibold bg-[#FFF7ED] text-[#F97316] border border-[#FED7AA]" style={{ fontFamily: 'Poppins, sans-serif' }}>
{conf.category}
</span>
)}
</div>
<p className="text-sm text-[#475569] leading-relaxed mb-4" style={{ fontFamily: 'Inter, sans-serif' }}>{conf.content}</p>
<div className="flex items-center justify-between">
<button
onClick={() => toggleLike(conf.id)}
className="flex items-center gap-2 px-3 py-1.5 rounded-xl transition-all"
style={{ backgroundColor: likedIds.has(conf.id) ? '#FFF7ED' : '#F8FAFC' }}
>
<Heart
className="w-3.5 h-3.5"
style={{ color: likedIds.has(conf.id) ? '#F97316' : '#94A3B8' }}
fill={likedIds.has(conf.id) ? '#F97316' : 'none'}
/>
<span className="text-xs text-[#94A3B8]">{conf.reactions + (likedIds.has(conf.id) ? 1 : 0)}</span>
</button>
</div>
</motion.div>
</StaggerItem>
))}
</StaggerContainer>
</div>
{/* Sidebar */}
<AnimatedContainer direction="right" delay={0.15}>
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-5" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
<h3 className="text-sm font-semibold text-[#1E293B] mb-4 flex items-center gap-2" style={{ fontFamily: 'Poppins, sans-serif' }}>
<Shield className="w-4 h-4 text-[#F97316]" />
Community Rules
</h3>
{[
'Be respectful and kind',
'No hate speech or bullying',
'No personal information',
'Keep it relevant to campus life',
'Confessions are 100% anonymous',
].map((rule, i) => (
<div key={i} className="flex items-start gap-2.5 py-2.5 border-b border-[#E5E7EB] last:border-0">
<ChevronRight className="w-3.5 h-3.5 text-[#F97316] mt-0.5 shrink-0" />
<span className="text-xs text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>{rule}</span>
</div>
))}
</div>
</AnimatedContainer>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
);
}

263
src/pages/Events/Events.tsx Normal file
View File

@@ -0,0 +1,263 @@
import { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Calendar, MapPin, Clock, Users, ChevronLeft, ChevronRight, Tag, ExternalLink } from 'lucide-react';
import SectionTitle from '@/components/SectionTitle/SectionTitle';
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer';
import { EVENTS_DATA, CLUBS_DATA } from '@/constants';
const CATEGORY_COLORS: Record<string, { bg: string; text: string; border: string }> = {
technical: { bg: '#EFF6FF', text: '#3B82F6', border: '#BFDBFE' },
cultural: { bg: '#FDF2F8', text: '#EC4899', border: '#FBCFE8' },
sports: { bg: '#ECFDF5', text: '#10B981', border: '#A7F3D0' },
seminar: { bg: '#FFF7ED', text: '#F97316', border: '#FED7AA' },
workshop: { bg: '#F5F3FF', text: '#8B5CF6', border: '#DDD6FE' },
};
const CLUB_CATEGORY_COLORS: Record<string, string> = {
Technical: '#3B82F6',
Cultural: '#EC4899',
Social: '#10B981',
Creative: '#F59E0B',
};
export default function Events() {
const [currentIdx, setCurrentIdx] = useState(0);
const [selectedCategory, setSelectedCategory] = useState('All');
const categories = ['All', 'Technical', 'Cultural', 'Seminar', 'Workshop'];
const filteredEvents = EVENTS_DATA.filter((e) =>
selectedCategory === 'All' || e.category === selectedCategory.toLowerCase()
);
const prev = () => setCurrentIdx((i) => (i === 0 ? EVENTS_DATA.length - 1 : i - 1));
const next = () => setCurrentIdx((i) => (i === EVENTS_DATA.length - 1 ? 0 : i + 1));
const featured = EVENTS_DATA[currentIdx];
const featCfg = CATEGORY_COLORS[featured.category] || CATEGORY_COLORS.seminar;
return (
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
{/* Header */}
<div className="bg-white border-b border-[#E5E7EB] py-10">
<div className="container-custom">
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
Clubs &{' '}
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
Events
</span>
</h1>
<p className="text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
Discover events, join clubs, and be part of the RIT community.
</p>
</div>
</div>
<div className="container-custom py-10">
{/* Featured Event Carousel */}
<SectionTitle tag="Upcoming" title="Featured" highlight="Events" />
<AnimatedContainer className="relative mb-14">
<div className="bg-white rounded-3xl border border-[#E5E7EB] overflow-hidden" style={{ boxShadow: '0 8px 40px -8px rgba(0,0,0,0.1)' }}>
<AnimatePresence mode="wait">
<motion.div
key={featured.id}
initial={{ opacity: 0, x: 30 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -30 }}
transition={{ duration: 0.3 }}
className="flex flex-col md:flex-row"
>
{/* Image placeholder */}
<div
className="w-full md:w-2/5 h-56 md:h-auto flex items-center justify-center"
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)' }}
>
<div className="text-center">
<div className="text-5xl mb-2">
{featured.category === 'technical' ? '💻' : featured.category === 'cultural' ? '🎭' : featured.category === 'seminar' ? '🎤' : '📅'}
</div>
<div className="text-white/60 text-sm">{featured.venue}</div>
</div>
</div>
{/* Content */}
<div className="flex-1 p-8">
<div className="flex items-center gap-2 mb-3">
<span
className="px-3 py-1 rounded-full text-xs font-semibold capitalize"
style={{ backgroundColor: featCfg.bg, color: featCfg.text, border: `1px solid ${featCfg.border}`, fontFamily: 'Poppins, sans-serif' }}
>
{featured.category}
</span>
<span className="px-3 py-1 rounded-full text-xs font-semibold bg-emerald-50 text-emerald-600 border border-emerald-200" style={{ fontFamily: 'Poppins, sans-serif' }}>
Upcoming
</span>
</div>
<h3 className="text-2xl font-bold text-[#1E293B] mb-3" style={{ fontFamily: 'Playfair Display, serif' }}>
{featured.title}
</h3>
<p className="text-[#475569] text-sm leading-relaxed mb-5" style={{ fontFamily: 'Inter, sans-serif' }}>
{featured.description}
</p>
<div className="grid grid-cols-2 gap-3 mb-6">
{[
{ icon: Calendar, label: new Date(featured.date).toLocaleDateString('en-IN', { day: 'numeric', month: 'long', year: 'numeric' }) },
{ icon: Clock, label: featured.time },
{ icon: MapPin, label: featured.venue },
{ icon: Users, label: featured.organizer },
].map((item, i) => (
<div key={i} className="flex items-center gap-2 text-sm text-[#475569]">
<item.icon className="w-4 h-4 text-[#F97316] shrink-0" />
<span style={{ fontFamily: 'Inter, sans-serif' }}>{item.label}</span>
</div>
))}
</div>
<div className="flex items-center gap-3">
<motion.button
whileHover={{ scale: 1.03 }}
className="flex items-center gap-2 px-5 py-2.5 rounded-xl text-white text-sm font-semibold"
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
>
Register Now
<ExternalLink className="w-4 h-4" />
</motion.button>
<motion.button
whileHover={{ scale: 1.03 }}
className="px-5 py-2.5 rounded-xl text-sm font-semibold border-2 border-[#E5E7EB] text-[#475569] hover:border-[#F97316] hover:text-[#F97316] transition-all"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
More Details
</motion.button>
</div>
</div>
</motion.div>
</AnimatePresence>
</div>
{/* Carousel Controls */}
<div className="flex items-center justify-center gap-3 mt-4">
<button onClick={prev} className="w-9 h-9 rounded-xl border border-[#E5E7EB] bg-white flex items-center justify-center text-[#475569] hover:border-[#F97316] hover:text-[#F97316] transition-all">
<ChevronLeft className="w-4 h-4" />
</button>
{EVENTS_DATA.map((_, i) => (
<button
key={i}
onClick={() => setCurrentIdx(i)}
className="transition-all rounded-full"
style={{ width: i === currentIdx ? '24px' : '8px', height: '8px', backgroundColor: i === currentIdx ? '#F97316' : '#E5E7EB' }}
/>
))}
<button onClick={next} className="w-9 h-9 rounded-xl border border-[#E5E7EB] bg-white flex items-center justify-center text-[#475569] hover:border-[#F97316] hover:text-[#F97316] transition-all">
<ChevronRight className="w-4 h-4" />
</button>
</div>
</AnimatedContainer>
{/* All Events Grid */}
<div className="mb-14">
<div className="flex items-center justify-between mb-6 flex-wrap gap-4">
<h2 className="text-xl font-bold text-[#1E293B]" style={{ fontFamily: 'Playfair Display, serif' }}>All Events</h2>
<div className="flex gap-2 flex-wrap">
{categories.map((cat) => (
<button
key={cat}
onClick={() => setSelectedCategory(cat)}
className="px-3.5 py-1.5 rounded-xl text-xs font-medium transition-all"
style={{
fontFamily: 'Poppins, sans-serif',
backgroundColor: selectedCategory === cat ? '#F97316' : '#F8FAFC',
color: selectedCategory === cat ? 'white' : '#475569',
border: `1px solid ${selectedCategory === cat ? '#F97316' : '#E5E7EB'}`,
}}
>
{cat}
</button>
))}
</div>
</div>
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 gap-4">
{filteredEvents.map((event) => {
const cfg = CATEGORY_COLORS[event.category] || CATEGORY_COLORS.seminar;
return (
<StaggerItem key={event.id}>
<motion.div
whileHover={{ y: -4 }}
className="bg-white rounded-2xl border border-[#E5E7EB] p-5 flex gap-4"
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
>
<div className="w-14 h-14 rounded-xl flex flex-col items-center justify-center shrink-0" style={{ backgroundColor: cfg.bg }}>
<span className="text-xs font-bold" style={{ color: cfg.text, fontFamily: 'Poppins, sans-serif' }}>
{new Date(event.date).toLocaleDateString('en-IN', { day: 'numeric' })}
</span>
<span className="text-[10px]" style={{ color: cfg.text }}>
{new Date(event.date).toLocaleDateString('en-IN', { month: 'short' })}
</span>
</div>
<div className="flex-1">
<span className="px-2 py-0.5 rounded-full text-[10px] font-semibold capitalize" style={{ backgroundColor: cfg.bg, color: cfg.text, fontFamily: 'Poppins, sans-serif' }}>
{event.category}
</span>
<h3 className="text-sm font-semibold text-[#1E293B] mt-1.5 mb-1" style={{ fontFamily: 'Poppins, sans-serif' }}>{event.title}</h3>
<div className="flex items-center gap-3 text-xs text-[#94A3B8]">
<span className="flex items-center gap-1"><Clock className="w-3 h-3" />{event.time}</span>
<span className="flex items-center gap-1"><MapPin className="w-3 h-3" />{event.venue}</span>
</div>
</div>
</motion.div>
</StaggerItem>
);
})}
</StaggerContainer>
</div>
{/* Clubs Section */}
<SectionTitle tag="Join a Club" title="Student" highlight="Clubs" subtitle="Explore clubs and societies at RIT. Find your passion and connect with like-minded peers." />
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{CLUBS_DATA.map((club) => (
<StaggerItem key={club.id}>
<motion.div
whileHover={{ y: -4 }}
className="bg-white rounded-2xl border border-[#E5E7EB] p-5"
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
>
<div className="flex items-start justify-between mb-3">
<div
className="w-11 h-11 rounded-xl flex items-center justify-center text-white font-bold text-lg"
style={{ background: `linear-gradient(135deg, ${CLUB_CATEGORY_COLORS[club.category] || '#F97316'}, ${CLUB_CATEGORY_COLORS[club.category] || '#F97316'}CC)`, fontFamily: 'Poppins, sans-serif' }}
>
{club.name[0]}
</div>
<div className="flex items-center gap-1 text-xs text-[#94A3B8]">
<Users className="w-3.5 h-3.5" />
<span>{club.members} members</span>
</div>
</div>
<h3 className="font-semibold text-[#1E293B] mb-1" style={{ fontFamily: 'Poppins, sans-serif' }}>{club.name}</h3>
<p className="text-xs text-[#94A3B8] mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>{club.description}</p>
<div className="flex items-center justify-between">
<span className="flex items-center gap-1 text-xs" style={{ color: CLUB_CATEGORY_COLORS[club.category] || '#F97316' }}>
<Tag className="w-3 h-3" />
{club.category}
</span>
<motion.button
whileHover={{ scale: 1.04 }}
className="px-4 py-1.5 rounded-lg text-xs font-semibold text-white"
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
>
Join Club
</motion.button>
</div>
</motion.div>
</StaggerItem>
))}
</StaggerContainer>
</div>
</div>
);
}

562
src/pages/Home/Home.tsx Normal file
View File

@@ -0,0 +1,562 @@
import { motion } from 'framer-motion';
import { Link } from 'react-router-dom';
import {
ArrowRight, Sparkles, BookOpen, Bot, GraduationCap,
CheckCircle, Zap, Shield, Brain
} from 'lucide-react';
import SectionTitle from '@/components/SectionTitle/SectionTitle';
import StatsCard from '@/components/StatsCard/StatsCard';
import FeatureCard from '@/components/FeatureCard/FeatureCard';
import FloatingCard from '@/components/FloatingCard/FloatingCard';
import ChatPreview from '@/components/ChatPreview/ChatPreview';
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer';
import { STATS, FEATURES, CAMPUS_LOCATIONS } from '@/constants';
import * as LucideIcons from 'lucide-react';
export default function Home() {
return (
<div>
{/* ─── Hero Section ─────────────────────────────────────────────────────── */}
<section className="relative overflow-hidden pt-12 pb-24" style={{ backgroundColor: '#FAFAFA' }}>
{/* Background Decoration */}
<div
className="absolute top-0 right-0 w-[500px] h-[500px] rounded-full opacity-[0.04] -translate-y-1/2 translate-x-1/3 pointer-events-none"
style={{ background: 'radial-gradient(circle, #F97316, transparent)' }}
/>
<div
className="absolute bottom-0 left-0 w-[350px] h-[350px] rounded-full opacity-[0.03] translate-y-1/2 -translate-x-1/3 pointer-events-none"
style={{ background: 'radial-gradient(circle, #F97316, transparent)' }}
/>
<div className="container-custom">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-16 items-center">
{/* Left Visual Section (RIT Events Hub Style) */}
<div className="lg:col-span-6 relative flex justify-center">
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.7 }}
className="relative w-full max-w-lg"
>
{/* Main campus image/graphic card */}
<div
className="rounded-[24px] overflow-hidden bg-white border border-[#E5E7EB] relative aspect-[4/3] w-full"
style={{ boxShadow: '0 24px 60px -12px rgba(0,0,0,0.15)' }}
>
{/* Inline Premium Vector SVG Illustration of RIT Campus Building & Canopy */}
<svg viewBox="0 0 800 600" className="w-full h-full select-none" preserveAspectRatio="xMidYMid slice">
<defs>
<linearGradient id="skyGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor="#1E3A8A" />
<stop offset="40%" stopColor="#3B82F6" />
<stop offset="100%" stopColor="#93C5FD" />
</linearGradient>
<linearGradient id="grassGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor="#059669" />
<stop offset="100%" stopColor="#065F46" />
</linearGradient>
<linearGradient id="pathGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor="#F1F5F9" />
<stop offset="100%" stopColor="#CBD5E1" />
</linearGradient>
<linearGradient id="buildGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="#E2E8F0" />
<stop offset="100%" stopColor="#F8FAFC" />
</linearGradient>
<linearGradient id="canopyGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor="#F97316" />
<stop offset="100%" stopColor="#EA580C" />
</linearGradient>
<filter id="glow">
<feGaussianBlur stdDeviation="15" result="coloredBlur"/>
<feMerge>
<feMergeNode in="coloredBlur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
{/* Sky */}
<rect width="800" height="600" fill="url(#skyGrad)" />
{/* Glowing Sun */}
<circle cx="700" cy="100" r="50" fill="#FEF08A" opacity="0.8" filter="url(#glow)" />
{/* Distant Hills / Tree Line */}
<path d="M0 450 Q150 420 300 440 T600 430 T800 450 L800 600 L0 600 Z" fill="#047857" opacity="0.7" />
<path d="M0 460 Q200 440 400 455 T800 460 L800 600 L0 600 Z" fill="url(#grassGrad)" />
{/* Academic Building Facade (Modern RIT Blocks) */}
<rect x="50" y="200" width="300" height="260" fill="url(#buildGrad)" rx="8" />
{/* Windows Grid */}
<g fill="#475569" opacity="0.3">
{/* Row 1 */}
<rect x="75" y="230" width="35" height="40" rx="3" />
<rect x="135" y="230" width="35" height="40" rx="3" />
<rect x="195" y="230" width="35" height="40" rx="3" />
<rect x="255" y="230" width="35" height="40" rx="3" />
{/* Row 2 */}
<rect x="75" y="300" width="35" height="40" rx="3" />
<rect x="135" y="300" width="35" height="40" rx="3" />
<rect x="195" y="300" width="35" height="40" rx="3" />
<rect x="255" y="300" width="35" height="40" rx="3" />
{/* Row 3 */}
<rect x="75" y="370" width="35" height="40" rx="3" />
<rect x="135" y="370" width="35" height="40" rx="3" />
<rect x="195" y="370" width="35" height="40" rx="3" />
<rect x="255" y="370" width="35" height="40" rx="3" />
</g>
{/* Main Entrance Pillars & Arch (RIT Architecture) */}
<rect x="380" y="150" width="380" height="310" fill="#F1F5F9" rx="16" opacity="0.95" />
<rect x="400" y="170" width="340" height="290" fill="#E2E8F0" rx="12" />
{/* Premium Canopy Truss Structure (RIT Event Canopy Representation) */}
<polygon points="360,150 780,120 780,180 360,210" fill="url(#canopyGrad)" opacity="0.9" />
<g stroke="white" strokeWidth="3" opacity="0.8">
<line x1="380" y1="155" x2="380" y2="200" />
<line x1="450" y1="150" x2="450" y2="195" />
<line x1="520" y1="145" x2="520" y2="190" />
<line x1="590" y1="140" x2="590" y2="185" />
<line x1="660" y1="135" x2="660" y2="180" />
<line x1="730" y1="130" x2="730" y2="175" />
{/* Cross trusses */}
<line x1="380" y1="155" x2="450" y2="195" />
<line x1="450" y1="150" x2="520" y2="190" />
<line x1="520" y1="145" x2="590" y2="185" />
<line x1="590" y1="140" x2="660" y2="180" />
<line x1="660" y1="135" x2="730" y2="175" />
</g>
{/* Columns supporting Canopy */}
<rect x="400" y="195" width="25" height="265" fill="#64748B" rx="4" />
<rect x="520" y="190" width="25" height="270" fill="#64748B" rx="4" />
<rect x="640" y="180" width="25" height="280" fill="#64748B" rx="4" />
<rect x="730" y="175" width="25" height="285" fill="#64748B" rx="4" />
{/* Campus Pathway leading to building */}
<polygon points="350,460 550,460 700,600 100,600" fill="url(#pathGrad)" />
<line x1="400" y1="460" x2="250" y2="600" stroke="#CBD5E1" strokeWidth="4" />
<line x1="500" y1="460" x2="550" y2="600" stroke="#CBD5E1" strokeWidth="4" />
{/* Trees & Foliage in Foreground */}
<g fill="#059669">
<circle cx="80" cy="480" r="45" />
<circle cx="120" cy="510" r="35" />
<circle cx="720" cy="500" r="50" />
<circle cx="760" cy="480" r="40" />
</g>
<g fill="#10B981">
<circle cx="75" cy="465" r="35" />
<circle cx="715" cy="485" r="40" />
</g>
</svg>
{/* Subtle dark gradient overlay to ensure text contrast */}
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-black/10 pointer-events-none" />
{/* Text on top of the image (RIT Events Hub Style) */}
<div className="absolute bottom-6 left-6 z-10 text-left pointer-events-none">
<h2 className="text-white font-extrabold text-2xl tracking-wide leading-tight" style={{ fontFamily: 'Playfair Display, serif' }}>
Rajalakshmi Institute
</h2>
<p className="text-orange-400 font-bold text-sm mt-1" style={{ fontFamily: 'Inter, sans-serif' }}>
of Technology, Chennai
</p>
<div className="inline-flex items-center gap-1.5 mt-3.5 px-3 py-1 rounded-full bg-white/10 backdrop-blur-sm border border-white/20">
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse" />
<span className="text-[10px] text-slate-300 font-bold uppercase tracking-wider">Est. 2008 NBA & NAAC A+</span>
</div>
</div>
</div>
{/* Overlapping Dark Card (Innovation Card - Exact Match with Reference Image) */}
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.6, delay: 0.3 }}
className="absolute -bottom-6 -right-4 md:-right-8 bg-[#1E293B] text-white p-6 rounded-2xl border border-white/10 max-w-[240px] z-20"
style={{ boxShadow: '0 12px 40px rgba(0,0,0,0.25)' }}
>
<span className="text-[#F97316] text-[11px] font-bold tracking-wider uppercase mb-1.5 block" style={{ fontFamily: 'Poppins, sans-serif' }}>
INNOVATION
</span>
<p className="text-[12px] text-slate-300 leading-relaxed font-semibold" style={{ fontFamily: 'Inter, sans-serif' }}>
Where engineering meets guidance. Fueling the academic journey of our freshers.
</p>
</motion.div>
</motion.div>
</div>
{/* Right Text Content */}
<div className="lg:col-span-6 flex flex-col justify-center">
<motion.div
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="mb-3"
>
<span className="text-[#F97316] text-xs font-bold uppercase tracking-widest" style={{ fontFamily: 'Poppins, sans-serif' }}>
OUR LEGACY & FUTURE
</span>
</motion.div>
<motion.h1
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1 }}
className="text-4xl md:text-5xl xl:text-6xl font-bold leading-[1.1] mb-6"
style={{ fontFamily: 'Playfair Display, serif', color: '#1E293B' }}
>
Welcome to <br />
<span className="font-serif italic text-[#F97316] font-medium block mt-1">
RIT Freshers Hub
</span>
</motion.h1>
<motion.p
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.2 }}
className="text-base text-[#475569] leading-relaxed mb-8"
style={{ fontFamily: 'Inter, sans-serif' }}
>
The RIT Freshers Hub is your centralized gateway to campus life, designed to simplify how you discover and participate in academic and extracurricular activities. With seamless access to notes, PYQs, AI assistant, and bus routes, we ensure you have everything needed to succeed.
</motion.p>
<motion.div
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 }}
className="flex flex-wrap gap-4 mb-8"
>
<Link to="/notes">
<motion.button
whileHover={{ scale: 1.03, boxShadow: '0 8px 25px rgba(249,115,22,0.3)' }}
whileTap={{ scale: 0.97 }}
className="flex items-center gap-2 px-6 py-3 rounded-xl text-white font-semibold text-sm cursor-pointer"
style={{
fontFamily: 'Poppins, sans-serif',
background: 'linear-gradient(135deg, #F97316, #FB923C)',
}}
>
<BookOpen className="w-4 h-4" />
Explore Hub
<ArrowRight className="w-3.5 h-3.5" />
</motion.button>
</Link>
<Link to="/ai-assistant">
<motion.button
whileHover={{ scale: 1.03, borderColor: '#F97316', color: '#F97316', backgroundColor: '#FFF7ED' }}
whileTap={{ scale: 0.97 }}
className="flex items-center gap-2 px-6 py-3 rounded-xl font-semibold text-sm border-2 border-[#E5E7EB] text-[#1E293B] bg-white transition-all cursor-pointer"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
<Bot className="w-4 h-4" />
Ask AI Assistant
</motion.button>
</Link>
</motion.div>
{/* Badges positioned cleanly below buttons with more breathing room */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.6, delay: 0.4 }}
className="flex flex-wrap gap-6 text-[#94A3B8] border-t border-[#E5E7EB] pt-6"
>
{[
{ icon: CheckCircle, text: 'Official Portal' },
{ icon: Zap, text: 'Instant AI Support' },
{ icon: Shield, text: 'Student Verified' },
].map((badge, i) => (
<div key={i} className="flex items-center gap-2 text-xs font-medium" style={{ fontFamily: 'Inter, sans-serif' }}>
<badge.icon className="w-4 h-4 text-[#F97316]/70" />
<span>{badge.text}</span>
</div>
))}
</motion.div>
</div>
</div>
</div>
</section>
{/* ─── Stats Section ────────────────────────────────────────────────────── */}
<section className="py-16 bg-white">
<div className="container-custom">
<StaggerContainer className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4">
{STATS.map((stat, i) => (
<StaggerItem key={i}>
<StatsCard stat={stat} delay={i * 0.05} />
</StaggerItem>
))}
</StaggerContainer>
</div>
</section>
{/* ─── Features Section ─────────────────────────────────────────────────── */}
<section className="section-padding" style={{ backgroundColor: '#FAFAFA' }}>
<div className="container-custom">
<SectionTitle
tag="Everything You Need"
title="Built for"
highlight="RIT Freshers"
subtitle="From notes to AI assistance — we've got everything you need to navigate campus life with confidence."
/>
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
{FEATURES.map((feature) => (
<StaggerItem key={feature.id}>
<FeatureCard feature={feature} />
</StaggerItem>
))}
</StaggerContainer>
</div>
</section>
{/* ─── AI Section ───────────────────────────────────────────────────────── */}
<section className="section-padding bg-white">
<div className="container-custom">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
{/* Left Text */}
<AnimatedContainer direction="left">
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full mb-5"
style={{ backgroundColor: '#F5F3FF', border: '1px solid #DDD6FE' }}>
<Sparkles className="w-4 h-4 text-[#8B5CF6]" />
<span className="text-xs font-semibold text-[#8B5CF6] uppercase tracking-wider" style={{ fontFamily: 'Poppins, sans-serif' }}>
AI-Powered
</span>
</div>
<h2 className="text-3xl md:text-4xl font-bold mb-4" style={{ fontFamily: 'Playfair Display, serif', color: '#1E293B' }}>
Your 24/7{' '}
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
Campus Guide
</span>
</h2>
<p className="text-[#475569] leading-relaxed mb-8" style={{ fontFamily: 'Inter, sans-serif' }}>
Ask anything about RIT from admission documents to canteen timings. Our AI assistant, powered by Google Gemini with campus-specific knowledge, gives you instant, accurate answers.
</p>
<div className="grid grid-cols-2 gap-4 mb-8">
{[
{ icon: 'Brain', label: 'Gemini AI', desc: 'State-of-the-art AI' },
{ icon: 'Database', label: 'RAG System', desc: 'Campus knowledge base' },
{ icon: 'Map', label: 'Campus Context', desc: 'RIT-specific answers' },
{ icon: 'Zap', label: 'Instant Answers', desc: 'No wait time' },
].map((item, i) => {
const Icon = (LucideIcons as Record<string, React.ComponentType<{ className?: string }>>)[item.icon];
return (
<div key={i} className="flex items-start gap-3 p-4 rounded-xl border border-[#E5E7EB] bg-[#FAFAFA]">
<div className="w-9 h-9 rounded-lg flex items-center justify-center bg-[#FFF7ED] shrink-0">
{Icon && <Icon className="w-4.5 h-4.5" style={{ color: '#F97316' } as React.CSSProperties} />}
</div>
<div>
<div className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>{item.label}</div>
<div className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>{item.desc}</div>
</div>
</div>
);
})}
</div>
<Link to="/ai-assistant">
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
className="flex items-center gap-2 px-7 py-3.5 rounded-xl text-white font-semibold"
style={{
fontFamily: 'Poppins, sans-serif',
background: 'linear-gradient(135deg, #F97316, #FB923C)',
boxShadow: '0 6px 20px rgba(249,115,22,0.35)',
}}
>
<Bot className="w-4.5 h-4.5" />
Try AI Assistant
<ArrowRight className="w-4 h-4" />
</motion.button>
</Link>
</AnimatedContainer>
{/* Right Chat Preview */}
<AnimatedContainer direction="right" delay={0.2}>
<ChatPreview />
</AnimatedContainer>
</div>
</div>
</section>
{/* ─── Campus Quick Nav ─────────────────────────────────────────────────── */}
<section className="section-padding" style={{ backgroundColor: '#FAFAFA' }}>
<div className="container-custom">
<SectionTitle
tag="Navigate Campus"
title="Explore"
highlight="RIT Campus"
subtitle="Find your way around campus — departments, labs, library, and all key locations."
/>
<AnimatedContainer>
<div className="bg-white rounded-3xl border border-[#E5E7EB] overflow-hidden" style={{ boxShadow: '0 8px 40px -8px rgba(0,0,0,0.1)' }}>
{/* Map placeholder */}
<div
className="h-56 flex items-center justify-center relative overflow-hidden"
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)' }}
>
<div
className="absolute inset-0 opacity-10"
style={{
backgroundImage: 'linear-gradient(rgba(249,115,22,0.5) 1px, transparent 1px), linear-gradient(90deg, rgba(249,115,22,0.5) 1px, transparent 1px)',
backgroundSize: '30px 30px',
}}
/>
{/* Location pins */}
{[
{ label: 'CSE Block', x: '25%', y: '30%' },
{ label: 'Library', x: '55%', y: '45%' },
{ label: 'Canteen', x: '70%', y: '65%' },
{ label: 'Hostel', x: '20%', y: '65%' },
].map((pin, i) => (
<motion.div
key={i}
className="absolute flex flex-col items-center gap-1 cursor-pointer group"
style={{ left: pin.x, top: pin.y, transform: 'translate(-50%, -50%)' }}
whileHover={{ scale: 1.1 }}
>
<div className="w-6 h-6 rounded-full flex items-center justify-center shadow-lg group-hover:scale-110 transition-all"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
<div className="w-2 h-2 rounded-full bg-white" />
</div>
<span className="text-[10px] text-white font-medium bg-black/50 px-1.5 py-0.5 rounded-full opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap">
{pin.label}
</span>
</motion.div>
))}
<div className="relative z-10 text-center">
<div className="text-white/40 text-sm" style={{ fontFamily: 'Inter, sans-serif' }}>Interactive Campus Map</div>
<Link to="/campus">
<motion.button
whileHover={{ scale: 1.05 }}
className="mt-2 px-5 py-2 rounded-xl text-white text-xs font-semibold"
style={{ background: 'rgba(249,115,22,0.8)', fontFamily: 'Poppins, sans-serif' }}
>
Open Full Map
</motion.button>
</Link>
</div>
</div>
{/* Quick nav buttons */}
<div className="p-5 grid grid-cols-4 sm:grid-cols-8 gap-3">
{CAMPUS_LOCATIONS.map((loc) => {
const Icon = (LucideIcons as Record<string, React.ComponentType<{ className?: string }>>)[loc.icon];
return (
<Link to="/campus" key={loc.id}>
<motion.div
whileHover={{ y: -3, backgroundColor: '#FFF7ED' }}
className="flex flex-col items-center gap-2 p-3 rounded-xl cursor-pointer transition-all border border-transparent hover:border-[#FED7AA]"
>
<div className="w-9 h-9 rounded-xl bg-[#F8FAFC] flex items-center justify-center">
{Icon && <Icon className="w-4.5 h-4.5 text-[#F97316]" />}
</div>
<span className="text-[10px] text-[#475569] text-center leading-tight" style={{ fontFamily: 'Inter, sans-serif' }}>
{loc.name}
</span>
</motion.div>
</Link>
);
})}
</div>
</div>
</AnimatedContainer>
</div>
</section>
{/* ─── Community Teaser ─────────────────────────────────────────────────── */}
<section className="section-padding bg-white">
<div className="container-custom">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Q&A teaser */}
<AnimatedContainer direction="left">
<div className="rounded-3xl p-7 h-full border border-[#E5E7EB]"
style={{ background: 'linear-gradient(135deg, #FAFAFA, #FFF7ED)', boxShadow: '0 4px 20px -4px rgba(0,0,0,0.07)' }}>
<div className="flex items-center gap-3 mb-4">
<div className="w-11 h-11 rounded-xl flex items-center justify-center bg-[#FFF7ED]">
<LucideIcons.MessageCircle className="w-5 h-5 text-[#F97316]" />
</div>
<div>
<h3 className="font-semibold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Freshers Q&A</h3>
<p className="text-xs text-[#94A3B8]">Get answers from seniors</p>
</div>
</div>
<div className="space-y-3 mb-5">
{['How is hostel life at RIT?', 'Best clubs to join as a fresher?', 'Tips for first semester survival'].map((q, i) => (
<div key={i} className="flex items-start gap-2.5 p-3 bg-white rounded-xl border border-[#E5E7EB]">
<span className="text-[#F97316] text-xs font-bold mt-0.5">Q</span>
<span className="text-xs text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>{q}</span>
</div>
))}
</div>
<Link to="/community">
<motion.button
whileHover={{ scale: 1.02 }}
className="w-full py-2.5 rounded-xl text-sm font-semibold text-[#F97316] border-2 border-[#F97316] hover:bg-[#FFF7ED] transition-all"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
Join the Discussion
</motion.button>
</Link>
</div>
</AnimatedContainer>
{/* Anonymous Confession Teaser */}
<AnimatedContainer direction="right" delay={0.1}>
<div className="rounded-3xl p-7 h-full border border-[#E5E7EB]"
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)', boxShadow: '0 4px 20px -4px rgba(0,0,0,0.15)' }}>
<div className="flex items-center gap-3 mb-4">
<div className="w-11 h-11 rounded-xl flex items-center justify-center" style={{ backgroundColor: 'rgba(249,115,22,0.2)' }}>
<LucideIcons.Heart className="w-5 h-5 text-[#F97316]" />
</div>
<div>
<h3 className="font-semibold text-white" style={{ fontFamily: 'Poppins, sans-serif' }}>Anonymous Confessions</h3>
<p className="text-xs text-slate-400">Share without revealing identity</p>
</div>
</div>
<div className="space-y-3 mb-5">
{[
{ text: "I went to the wrong department on my first day 😅", reactions: 87 },
{ text: "The library WiFi is faster than my home connection 😂", reactions: 134 },
].map((c, i) => (
<div key={i} className="p-3 rounded-xl" style={{ backgroundColor: 'rgba(255,255,255,0.07)', border: '1px solid rgba(255,255,255,0.1)' }}>
<p className="text-xs text-slate-300 mb-2" style={{ fontFamily: 'Inter, sans-serif' }}>{c.text}</p>
<div className="flex items-center gap-1 text-xs text-slate-500">
<LucideIcons.Heart className="w-3 h-3 text-[#F97316]" />
<span>{c.reactions}</span>
</div>
</div>
))}
</div>
<div className="flex items-center gap-2 p-3 rounded-xl mb-4" style={{ backgroundColor: 'rgba(249,115,22,0.1)', border: '1px solid rgba(249,115,22,0.2)' }}>
<LucideIcons.Shield className="w-4 h-4 text-[#F97316] shrink-0" />
<span className="text-xs text-slate-400" style={{ fontFamily: 'Inter, sans-serif' }}>
100% Anonymous. No tracking. No identity revealed.
</span>
</div>
<Link to="/community">
<motion.button
whileHover={{ scale: 1.02 }}
className="w-full py-2.5 rounded-xl text-sm font-semibold text-white transition-all"
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
>
Confess Anonymously
</motion.button>
</Link>
</div>
</AnimatedContainer>
</div>
</div>
</section>
</div>
);
}

212
src/pages/Notes/Notes.tsx Normal file
View File

@@ -0,0 +1,212 @@
import { useState } from 'react';
import { motion } from 'framer-motion';
import { Search, Download, Filter, BookOpen, FileText, ScrollText, BookMarked, ChevronRight } from 'lucide-react';
import SectionTitle from '@/components/SectionTitle/SectionTitle';
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
import { TOOLKIT_ITEMS, DEPARTMENTS } from '@/constants';
const SEMESTERS = ['All', '1st Sem', '2nd Sem', '3rd Sem', '4th Sem', '5th Sem', '6th Sem', '7th Sem', '8th Sem'];
const TYPE_COLORS: Record<string, { bg: string; text: string; icon: React.ComponentType<{ className?: string }> }> = {
notes: { bg: '#EFF6FF', text: '#3B82F6', icon: BookOpen },
pyq: { bg: '#FFF7ED', text: '#F97316', icon: FileText },
syllabus: { bg: '#ECFDF5', text: '#10B981', icon: ScrollText },
assignment: { bg: '#FDF2F8', text: '#EC4899', icon: BookMarked },
};
const NOTES_DATA = [
{ id: '1', title: 'Engineering Mathematics Unit 1-5', subject: 'Mathematics', department: 'Computer Science & Engineering', semester: 1, type: 'notes', downloads: 348, fileSize: '4.2 MB', uploadedAt: '2025-06-01' },
{ id: '2', title: 'Physics PYQ 2020-2024', subject: 'Physics', department: 'Computer Science & Engineering', semester: 1, type: 'pyq', downloads: 512, fileSize: '8.1 MB', uploadedAt: '2025-06-10' },
{ id: '3', title: 'C Programming Complete Notes', subject: 'Programming', department: 'Computer Science & Engineering', semester: 1, type: 'notes', downloads: 734, fileSize: '6.3 MB', uploadedAt: '2025-06-15' },
{ id: '4', title: 'Data Structures PYQ 2019-2024', subject: 'Data Structures', department: 'Computer Science & Engineering', semester: 3, type: 'pyq', downloads: 621, fileSize: '10.2 MB', uploadedAt: '2025-07-01' },
{ id: '5', title: 'Circuit Theory Full Notes', subject: 'Circuit Theory', department: 'Electronics & Communication', semester: 2, type: 'notes', downloads: 289, fileSize: '5.7 MB', uploadedAt: '2025-06-20' },
{ id: '6', title: 'Anna University Syllabus 2021', subject: 'Syllabus', department: 'All Departments', semester: 1, type: 'syllabus', downloads: 890, fileSize: '2.1 MB', uploadedAt: '2025-05-01' },
{ id: '7', title: 'Thermodynamics Notes', subject: 'Thermodynamics', department: 'Mechanical Engineering', semester: 3, type: 'notes', downloads: 201, fileSize: '3.9 MB', uploadedAt: '2025-06-25' },
{ id: '8', title: 'Digital Electronics PYQ', subject: 'Digital Electronics', department: 'Electronics & Communication', semester: 4, type: 'pyq', downloads: 445, fileSize: '7.3 MB', uploadedAt: '2025-07-05' },
];
export default function Notes() {
const [searchQuery, setSearchQuery] = useState('');
const [selectedSem, setSelectedSem] = useState('All');
const [selectedDept, setSelectedDept] = useState('All Departments');
const [selectedType, setSelectedType] = useState('all');
const filtered = NOTES_DATA.filter((note) => {
const matchSearch = note.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
note.subject.toLowerCase().includes(searchQuery.toLowerCase());
const matchSem = selectedSem === 'All' || note.semester === parseInt(selectedSem);
const matchDept = selectedDept === 'All Departments' || note.department === selectedDept || note.department === 'All Departments';
const matchType = selectedType === 'all' || note.type === selectedType;
return matchSearch && matchSem && matchDept && matchType;
});
return (
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
{/* Header */}
<div className="bg-white border-b border-[#E5E7EB] py-10">
<div className="container-custom">
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }}>
<div className="flex items-center gap-2 text-xs text-[#94A3B8] mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>
<span>Home</span>
<ChevronRight className="w-3 h-3" />
<span className="text-[#F97316]">Notes & PYQs</span>
</div>
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
Notes &{' '}
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
PYQs
</span>
</h1>
<p className="text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
Access semester-wise notes, previous year question papers, and study materials.
</p>
</motion.div>
</div>
</div>
<div className="container-custom py-10">
{/* Search + Filters */}
<motion.div
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.1 }}
className="bg-white rounded-2xl border border-[#E5E7EB] p-5 mb-8"
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
>
{/* Search */}
<div className="relative mb-4">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-[#94A3B8]" />
<input
type="text"
placeholder="Search notes, subjects..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-11 pr-4 py-3 rounded-xl border border-[#E5E7EB] text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] transition-colors"
style={{ fontFamily: 'Inter, sans-serif' }}
/>
</div>
<div className="flex flex-wrap gap-3">
{/* Type Filter */}
<div className="flex items-center gap-2 flex-wrap">
<Filter className="w-4 h-4 text-[#94A3B8]" />
{['all', 'notes', 'pyq', 'syllabus'].map((type) => (
<button
key={type}
onClick={() => setSelectedType(type)}
className="px-3 py-1.5 rounded-lg text-xs font-medium capitalize transition-all"
style={{
fontFamily: 'Poppins, sans-serif',
backgroundColor: selectedType === type ? '#F97316' : '#F8FAFC',
color: selectedType === type ? 'white' : '#475569',
border: `1px solid ${selectedType === type ? '#F97316' : '#E5E7EB'}`,
}}
>
{type === 'all' ? 'All Types' : type.toUpperCase()}
</button>
))}
</div>
{/* Semester Filter */}
<div className="flex gap-2 flex-wrap">
{SEMESTERS.slice(0, 5).map((sem) => (
<button
key={sem}
onClick={() => setSelectedSem(sem)}
className="px-3 py-1.5 rounded-lg text-xs font-medium transition-all"
style={{
fontFamily: 'Poppins, sans-serif',
backgroundColor: selectedSem === sem ? '#1E293B' : '#F8FAFC',
color: selectedSem === sem ? 'white' : '#475569',
border: `1px solid ${selectedSem === sem ? '#1E293B' : '#E5E7EB'}`,
}}
>
{sem}
</button>
))}
</div>
</div>
</motion.div>
{/* Results */}
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 mb-12">
{filtered.map((note) => {
const typeConfig = TYPE_COLORS[note.type];
const TypeIcon = typeConfig.icon;
return (
<StaggerItem key={note.id}>
<motion.div
whileHover={{ y: -4 }}
className="bg-white rounded-2xl border border-[#E5E7EB] p-5 flex flex-col gap-3"
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
>
<div className="flex items-start justify-between gap-2">
<div className="w-10 h-10 rounded-xl flex items-center justify-center shrink-0" style={{ backgroundColor: typeConfig.bg }}>
<TypeIcon className="w-5 h-5" style={{ color: typeConfig.text } as React.CSSProperties} />
</div>
<span
className="px-2 py-0.5 rounded-full text-[10px] font-bold uppercase"
style={{ backgroundColor: typeConfig.bg, color: typeConfig.text, fontFamily: 'Poppins, sans-serif' }}
>
{note.type}
</span>
</div>
<div>
<h3 className="text-sm font-semibold text-[#1E293B] leading-snug mb-1" style={{ fontFamily: 'Poppins, sans-serif' }}>
{note.title}
</h3>
<p className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>
{note.subject} · Sem {note.semester}
</p>
</div>
<div className="flex items-center justify-between text-xs text-[#94A3B8] pt-2 border-t border-[#E5E7EB]">
<span style={{ fontFamily: 'Inter, sans-serif' }}>{note.downloads} downloads · {note.fileSize}</span>
</div>
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.97 }}
className="w-full flex items-center justify-center gap-2 py-2.5 rounded-xl text-sm font-semibold text-white"
style={{
fontFamily: 'Poppins, sans-serif',
background: 'linear-gradient(135deg, #F97316, #FB923C)',
}}
>
<Download className="w-4 h-4" />
Download
</motion.button>
</motion.div>
</StaggerItem>
);
})}
</StaggerContainer>
{/* Toolkit Section */}
<SectionTitle tag="Useful Downloads" title="Student" highlight="Toolkit" align="left" />
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
{TOOLKIT_ITEMS.map((item) => (
<StaggerItem key={item.id}>
<motion.a
href={item.url}
whileHover={{ y: -3 }}
className="flex items-center gap-4 p-4 bg-white rounded-2xl border border-[#E5E7EB] cursor-pointer group"
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
>
<div className="w-10 h-10 rounded-xl flex items-center justify-center bg-[#FFF7ED] shrink-0">
<Download className="w-4.5 h-4.5 text-[#F97316]" />
</div>
<div>
<div className="text-sm font-semibold text-[#1E293B] group-hover:text-[#F97316] transition-colors" style={{ fontFamily: 'Poppins, sans-serif' }}>
{item.title}
</div>
<div className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>
{item.description}
</div>
</div>
</motion.a>
</StaggerItem>
))}
</StaggerContainer>
</div>
</div>
);
}

View File

@@ -0,0 +1,219 @@
import { useState } from 'react';
import { motion } from 'framer-motion';
import {
User, Mail, Phone, GraduationCap, BookOpen,
Settings, LogOut, Edit3, Camera, Bell,
Shield, Star, Calendar, ChevronRight
} from 'lucide-react';
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer';
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
const MOCK_USER = {
name: 'Arjun Kumar',
rollNo: 'CB.EN.U4CSE25001',
department: 'Computer Science & Engineering',
semester: '1st Semester',
email: 'arjun.kumar@rit.ac.in',
phone: '+91 98765 43210',
joinedDate: 'August 2025',
batch: '2025-2029',
};
const ACTIVITY = [
{ label: 'Notes Downloaded', value: '12', icon: BookOpen, color: '#F97316' },
{ label: 'Questions Asked', value: '3', icon: Star, color: '#8B5CF6' },
{ label: 'Events Registered', value: '2', icon: Calendar, color: '#10B981' },
{ label: 'AI Queries', value: '48', icon: Shield, color: '#3B82F6' },
];
const MENU_ITEMS = [
{ icon: Bell, label: 'Notifications', badge: '3' },
{ icon: BookOpen, label: 'My Downloads', badge: null },
{ icon: Star, label: 'Bookmarks', badge: null },
{ icon: Settings, label: 'Settings', badge: null },
{ icon: Shield, label: 'Privacy & Security', badge: null },
];
export default function Profile() {
const [isEditing, setIsEditing] = useState(false);
return (
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
{/* Header */}
<div
className="py-16 relative overflow-hidden"
style={{ background: 'linear-gradient(135deg, #1E293B 0%, #334155 100%)' }}
>
<div
className="absolute inset-0 opacity-5"
style={{
backgroundImage: 'radial-gradient(circle, #F97316 1px, transparent 1px)',
backgroundSize: '32px 32px',
}}
/>
<div className="container-custom relative z-10">
<div className="flex flex-col md:flex-row items-start md:items-center gap-6">
{/* Avatar */}
<div className="relative">
<motion.div
whileHover={{ scale: 1.05 }}
className="w-24 h-24 rounded-2xl flex items-center justify-center text-3xl font-bold text-white cursor-pointer"
style={{
background: 'linear-gradient(135deg, #F97316, #FB923C)',
fontFamily: 'Poppins, sans-serif',
boxShadow: '0 8px 30px rgba(249,115,22,0.4)',
}}
>
AK
</motion.div>
<button
className="absolute -bottom-2 -right-2 w-8 h-8 rounded-xl flex items-center justify-center text-white"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
aria-label="Change photo"
>
<Camera className="w-3.5 h-3.5" />
</button>
</div>
{/* Info */}
<div className="flex-1">
<h1 className="text-2xl font-bold text-white mb-1" style={{ fontFamily: 'Playfair Display, serif' }}>
{MOCK_USER.name}
</h1>
<p className="text-orange-300 text-sm mb-1" style={{ fontFamily: 'Inter, sans-serif' }}>
{MOCK_USER.rollNo}
</p>
<p className="text-slate-400 text-sm mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>
{MOCK_USER.department} · {MOCK_USER.batch}
</p>
<div className="flex flex-wrap gap-2">
<span className="px-3 py-1 rounded-full text-xs font-semibold bg-orange-500/20 text-orange-300 border border-orange-500/30" style={{ fontFamily: 'Poppins, sans-serif' }}>
{MOCK_USER.semester}
</span>
<span className="px-3 py-1 rounded-full text-xs font-semibold bg-emerald-500/20 text-emerald-400 border border-emerald-500/30" style={{ fontFamily: 'Poppins, sans-serif' }}>
Active Student
</span>
</div>
</div>
{/* Edit button */}
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
onClick={() => setIsEditing(!isEditing)}
className="flex items-center gap-2 px-5 py-2.5 rounded-xl text-white text-sm font-semibold border border-white/20 hover:bg-white/10 transition-all"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
<Edit3 className="w-4 h-4" />
Edit Profile
</motion.button>
</div>
</div>
</div>
<div className="container-custom py-8">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Left */}
<div className="lg:col-span-2 flex flex-col gap-6">
{/* Activity Stats */}
<AnimatedContainer>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
{ACTIVITY.map((item, i) => (
<motion.div
key={i}
whileHover={{ y: -3 }}
className="bg-white rounded-2xl border border-[#E5E7EB] p-4 text-center"
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
>
<div
className="w-10 h-10 rounded-xl flex items-center justify-center mx-auto mb-2"
style={{ backgroundColor: `${item.color}15` }}
>
<item.icon className="w-5 h-5" style={{ color: item.color } as React.CSSProperties} />
</div>
<div className="text-xl font-bold mb-0.5" style={{ color: item.color, fontFamily: 'Poppins, sans-serif' }}>
{item.value}
</div>
<div className="text-[10px] text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>{item.label}</div>
</motion.div>
))}
</div>
</AnimatedContainer>
{/* Personal Details */}
<AnimatedContainer delay={0.1}>
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-6" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
<h3 className="text-sm font-semibold text-[#1E293B] mb-5" style={{ fontFamily: 'Poppins, sans-serif' }}>
Personal Information
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
{[
{ icon: User, label: 'Full Name', value: MOCK_USER.name },
{ icon: GraduationCap, label: 'Roll Number', value: MOCK_USER.rollNo },
{ icon: Mail, label: 'Email', value: MOCK_USER.email },
{ icon: Phone, label: 'Phone', value: MOCK_USER.phone },
{ icon: BookOpen, label: 'Department', value: MOCK_USER.department },
{ icon: Calendar, label: 'Joined', value: MOCK_USER.joinedDate },
].map((item, i) => (
<div key={i} className="flex items-center gap-3 p-3 rounded-xl bg-[#F8FAFC] border border-[#E5E7EB]">
<div className="w-9 h-9 rounded-lg flex items-center justify-center bg-[#FFF7ED] shrink-0">
<item.icon className="w-4.5 h-4.5 text-[#F97316]" />
</div>
<div>
<div className="text-[10px] text-[#94A3B8] uppercase tracking-wider" style={{ fontFamily: 'Inter, sans-serif' }}>{item.label}</div>
<div className="text-sm font-medium text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{item.value}</div>
</div>
</div>
))}
</div>
</div>
</AnimatedContainer>
</div>
{/* Right Sidebar */}
<div className="flex flex-col gap-5">
{/* Menu */}
<AnimatedContainer direction="right" delay={0.1}>
<div className="bg-white rounded-2xl border border-[#E5E7EB] overflow-hidden" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
{MENU_ITEMS.map((item, i) => (
<motion.button
key={i}
whileHover={{ backgroundColor: '#FFF7ED', x: 2 }}
className="w-full flex items-center justify-between px-5 py-3.5 border-b border-[#E5E7EB] last:border-0 transition-all text-left"
>
<div className="flex items-center gap-3">
<item.icon className="w-4 h-4 text-[#94A3B8]" />
<span className="text-sm text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>{item.label}</span>
</div>
<div className="flex items-center gap-2">
{item.badge && (
<span className="w-5 h-5 rounded-full flex items-center justify-center text-[10px] text-white font-bold"
style={{ backgroundColor: '#F97316', fontFamily: 'Poppins, sans-serif' }}>
{item.badge}
</span>
)}
<ChevronRight className="w-3.5 h-3.5 text-[#94A3B8]" />
</div>
</motion.button>
))}
</div>
</AnimatedContainer>
{/* Logout */}
<AnimatedContainer direction="right" delay={0.2}>
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className="w-full flex items-center justify-center gap-2 py-3 rounded-xl border-2 border-red-100 text-red-400 hover:bg-red-50 text-sm font-semibold transition-all"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
<LogOut className="w-4 h-4" />
Sign Out
</motion.button>
</AnimatedContainer>
</div>
</div>
</div>
</div>
);
}