Compare commits

..

6 Commits

Author SHA1 Message Date
Shanmuga Krishnan S M
d827f4aab9 Update campus map layout, refine navbar, remove discord integration
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-17 09:02:33 +05:30
Anbu-dev-creator
29377ce56e New commit 2026-08-17 08:28:01 +05:30
Anbu-dev-creator
97a59ee715 Some changes in toolkit section 2026-08-16 21:53:41 +05:30
Amudieshwar
3afb40d451 feat: integrate 15 official RIT Future Tech Centers of Excellence with logos, faculty leads, and AI Matcher 2026-08-16 17:28:23 +05:30
Amudieshwar-AG
bb71b725d9 Update Faculty page, FacultyCard component, types, and constants 2026-08-14 18:43:41 +05:30
Amudieshwar A G
fcaf16405d feat: upgrade UI design system with Orange, White & Dark Purple theme 2026-08-14 13:46:41 +05:30
43 changed files with 5136 additions and 1692 deletions

View File

@@ -101,7 +101,7 @@ public class CollabController {
request.setApplicationsCount(request.getApplicationsCount() + 1);
collabRequestRepository.save(request);
// Notify Telegram/Discord intermediary bot asynchronously
// Notify Telegram intermediary bot asynchronously
new Thread(() -> {
try {
Map<String, Object> payload = new HashMap<>();
@@ -112,7 +112,6 @@ public class CollabController {
payload.put("author_name", request.getAuthorName());
payload.put("contact_info", request.getContactInfo());
payload.put("telegram_chat_id", request.getTelegramChatId());
payload.put("discord_user_id", request.getDiscordUserId());
payload.put("applicant_name", savedApp.getApplicantName());
payload.put("applicant_email", savedApp.getApplicantEmail());
payload.put("applicant_dept", savedApp.getApplicantDept());
@@ -172,11 +171,6 @@ public class CollabController {
return collabRequestRepository.findByTelegramChatIdAndStatusOrderByCreatedAtDesc(chatId, "OPEN");
}
@GetMapping("/active/discord/{userId}")
public List<CollabRequest> getActiveRequestsByDiscord(@PathVariable String userId) {
return collabRequestRepository.findByDiscordUserIdAndStatusOrderByCreatedAtDesc(userId, "OPEN");
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteOrCancelCollabRequest(@PathVariable Integer id) {
return collabRequestRepository.findById(id).map(req -> {

View File

@@ -46,9 +46,6 @@ public class CollabRequest {
@Column(name = "telegram_chat_id")
private Long telegramChatId;
@Column(name = "discord_user_id")
private String discordUserId;
@Column(name = "status")
@Builder.Default
private String status = "OPEN";

View File

@@ -10,5 +10,4 @@ public interface CollabRequestRepository extends JpaRepository<CollabRequest, In
List<CollabRequest> findAllByOrderByCreatedAtDesc();
List<CollabRequest> findByAuthorEmailOrderByCreatedAtDesc(String authorEmail);
List<CollabRequest> findByTelegramChatIdAndStatusOrderByCreatedAtDesc(Long telegramChatId, String status);
List<CollabRequest> findByDiscordUserIdAndStatusOrderByCreatedAtDesc(String discordUserId, String status);
}

View File

@@ -1,7 +1,7 @@
# ─── DATABASE CONNECTION CONFIGURATION ───
spring.datasource.url=jdbc:postgresql://localhost:5432/rit_freshers_hub?sslmode=disable
spring.datasource.username=postgres
spring.datasource.password=${DB_PASSWORD:Devesh@54}
spring.datasource.password=${DB_PASSWORD:Anbukathir@2006}
# ─── JPA / HIBERNATE SETTINGS ───
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect

33
package-lock.json generated
View File

@@ -102,6 +102,7 @@
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.7",
"@babel/generator": "^7.29.7",
@@ -1614,21 +1615,21 @@
}
},
"node_modules/@emnapi/core": {
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz",
"integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==",
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz",
"integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.2",
"@emnapi/wasi-threads": "1.2.3",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.11.2",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz",
"integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==",
"node_modules/@emnapi/core/node_modules/@emnapi/wasi-threads": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz",
"integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -4421,6 +4422,7 @@
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.3.tgz",
"integrity": "sha512-ZjgcRwmQUSCythDPj6pE2NKFi9bpQegxIBxgt3hPytXI1ElFHNaa3A7aWW8vFaLCIRjPv467rbREYHbxcdgQbg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@tanstack/query-core": "5.101.3"
},
@@ -4506,6 +4508,7 @@
"integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~7.18.0"
}
@@ -4516,6 +4519,7 @@
"integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -4526,6 +4530,7 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -4601,6 +4606,7 @@
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -4859,6 +4865,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.42",
"caniuse-lite": "^1.0.30001803",
@@ -6534,7 +6541,8 @@
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==",
"license": "BSD-2-Clause"
"license": "BSD-2-Clause",
"peer": true
},
"node_modules/leven": {
"version": "3.1.0",
@@ -7162,6 +7170,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.16",
"picocolors": "^1.1.1",
@@ -7205,6 +7214,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -7214,6 +7224,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -7226,6 +7237,7 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.82.0.tgz",
"integrity": "sha512-Zw/uFZ2dO+02GHlBn7JFGn8kZJ7LdM33B/0BXOovzFay+CMhf94JMw5BVu+F1tVkUKjNvBuaE3fz5BJhga10Tg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18.0.0"
},
@@ -7532,6 +7544,7 @@
"integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/estree": "1.0.9"
},
@@ -8077,6 +8090,7 @@
"integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==",
"dev": true,
"license": "BSD-2-Clause",
"peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
@@ -8402,6 +8416,7 @@
"integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.5",

BIN
public/clubs/center_ai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
public/clubs/center_iot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 KiB

After

Width:  |  Height:  |  Size: 394 KiB

View File

@@ -36,20 +36,65 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
{ name: 'Yahoo Mail', url: `https://compose.mail.yahoo.com/?to=${faculty.email}`, icon: Mail, color: 'text-purple-600', bg: 'bg-purple-50' },
];
const parseToListItems = (text: string | undefined): string[] => {
if (!text) return [];
const cleaned = text.replace(/<br\s*\/?>/gi, '\n');
const lines = cleaned.split(/\n+/);
const items: string[] = [];
lines.forEach((line) => {
let subParts: string[];
if (line.includes(';')) {
subParts = line.split(';');
} else if (line.includes(',') && !line.includes('(')) {
subParts = line.split(',');
} else {
subParts = [line];
}
subParts.forEach((part) => {
let trimmed = part.trim();
trimmed = trimmed.replace(/^[\d+.)\-•\s*]+/, '').trim();
if (trimmed && trimmed !== '—' && trimmed !== 'Nil' && trimmed !== 'nil') {
items.push(trimmed);
}
});
});
return items;
};
const renderPointList = (value: string | undefined, placeholder: string = 'Not specified') => {
const items = parseToListItems(value);
if (items.length <= 1) {
return (
<p className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>
{items[0] || placeholder}
</p>
);
}
return (
<ul className="space-y-1.5 mt-1 text-left">
{items.map((item, idx) => (
<li key={idx} className="flex items-start gap-2 text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>
<span className="w-1.5 h-1.5 rounded-full bg-[#FF6B00] mt-2 shrink-0" />
<span className="leading-tight">{item}</span>
</li>
))}
</ul>
);
};
return (
<>
<motion.div
whileHover={{ y: -6, boxShadow: '0 20px 40px -10px rgba(0,0,0,0.1)' }}
whileHover={{ y: -4 }}
transition={{ duration: 0.3 }}
className="bg-white border border-[#E8ECF4] rounded-[20px] p-5 flex flex-col gap-4 relative"
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
className="bg-white border border-[#E9E5EE] rounded-2xl p-5 flex flex-col gap-4 relative shadow-[0_4px_20px_-4px_rgba(19,9,36,0.04)] hover:shadow-[0_12px_35px_-6px_rgba(19,9,36,0.08)] hover:border-[#FF6B00]/30"
>
{/* Avatar + Name */}
<div className="flex items-center gap-4">
<div className="relative">
<div
className="w-14 h-14 rounded-2xl flex items-center justify-center text-white font-bold text-lg shrink-0 shadow-sm"
style={{ background: `linear-gradient(135deg, ${from}, ${to})`, fontFamily: 'Poppins, sans-serif' }}
style={{ background: `linear-gradient(135deg, ${from}, ${to})`, fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{initials}
</div>
@@ -57,10 +102,10 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
<div className="absolute -bottom-1 -right-1 w-4 h-4 bg-[#22C55E] border-2 border-white rounded-full z-10 shadow-sm" />
</div>
<div>
<h3 className="font-semibold text-[#1E293B] text-sm leading-tight" style={{ fontFamily: 'Poppins, sans-serif' }}>
<h3 className="font-bold text-[#1A0B2E] text-sm leading-tight" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
{faculty.name}
</h3>
<p className="text-xs text-[#FF7A00] font-medium mt-0.5" style={{ fontFamily: 'Inter, sans-serif' }}>
<p className="text-xs text-[#FF6B00] font-semibold mt-0.5" style={{ fontFamily: 'Inter, sans-serif' }}>
{faculty.designation}
</p>
</div>
@@ -68,27 +113,27 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
{/* Details */}
<div className="flex flex-col gap-2">
<div className="flex items-center gap-2 text-xs text-[#475569]">
<Building2 className="w-3.5 h-3.5 text-[#94A3B8] shrink-0" />
<div className="flex items-center gap-2 text-xs text-[#4A3E5E]">
<Building2 className="w-3.5 h-3.5 text-[#9E91B6] shrink-0" />
<span style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.department}</span>
</div>
</div>
{/* Contact & Actions */}
<div className="flex items-center gap-2 pt-2 border-t border-[#E5E7EB] mt-auto">
<div className="flex items-center gap-2 pt-2 border-t border-[#E9E5EE] mt-auto">
<button
onClick={() => setShowEmailOptions(true)}
className="flex-1 flex items-center justify-center gap-1.5 py-2 rounded-xl text-xs font-medium text-[#475569] transition-all hover:bg-gray-50 border border-transparent hover:border-gray-200"
style={{ fontFamily: 'Poppins, sans-serif' }}
className="flex-1 flex items-center justify-center gap-1.5 py-2 rounded-xl text-xs font-semibold text-[#4A3E5E] transition-all hover:bg-purple-50/50 border border-transparent hover:border-[#E9E5EE]"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
aria-label={`Email ${faculty.name}`}
>
<Mail className="w-3.5 h-3.5" />
<Mail className="w-3.5 h-3.5 text-[#FF6B00]" />
Email
</button>
<button
onClick={() => setShowModal(true)}
className="flex-1 flex items-center justify-center gap-1.5 py-2 rounded-xl text-xs font-medium transition-all hover:bg-[#FFF7ED]"
style={{ color: '#F97316', fontFamily: 'Poppins, sans-serif' }}
className="flex-1 flex items-center justify-center gap-1.5 py-2 rounded-xl text-xs font-bold transition-all hover:bg-[#FFF7ED]"
style={{ color: '#FF6B00', fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
View Profile
<ChevronRight className="w-3.5 h-3.5" />
@@ -135,11 +180,11 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
>
<div
className="w-32 h-32 rounded-full flex items-center justify-center text-white font-bold text-5xl mb-5 shadow-lg"
style={{ background: `linear-gradient(135deg, ${from}, ${to})`, fontFamily: 'Poppins, sans-serif' }}
style={{ background: `linear-gradient(135deg, ${from}, ${to})`, fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{initials}
</div>
<h2 className="text-2xl font-bold text-center text-[#1E293B] mb-1" style={{ fontFamily: 'Poppins, sans-serif' }}>
<h2 className="text-2xl font-bold text-center text-[#1E293B] mb-1" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
{faculty.name}
</h2>
<p className="text-base font-semibold text-[#FF7A00] text-center" style={{ fontFamily: 'Inter, sans-serif' }}>
@@ -193,17 +238,13 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
<div className="w-10 h-10 rounded-xl bg-orange-50 flex items-center justify-center shrink-0">
<User className="w-5 h-5 text-[#FF7A00]" />
</div>
<h3 className="text-lg font-bold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Professional Details</h3>
<h3 className="text-lg font-bold text-[#1E293B]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>Professional Details</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="p-4 rounded-xl bg-gray-50/80 border border-gray-100 transition-colors hover:bg-gray-50">
<p className="text-xs font-semibold text-[#94A3B8] uppercase tracking-wider mb-1" style={{ fontFamily: 'Inter, sans-serif' }}>Department</p>
<p className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.department}</p>
</div>
<div className="p-4 rounded-xl bg-gray-50/80 border border-gray-100 transition-colors hover:bg-gray-50">
<p className="text-xs font-semibold text-[#94A3B8] uppercase tracking-wider mb-1" style={{ fontFamily: 'Inter, sans-serif' }}>Specialization</p>
<p className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.specialization || 'Not specified'}</p>
</div>
<div className="p-4 rounded-xl bg-gray-50/80 border border-gray-100 transition-colors hover:bg-gray-50">
<p className="text-xs font-semibold text-[#94A3B8] uppercase tracking-wider mb-1" style={{ fontFamily: 'Inter, sans-serif' }}>Qualification</p>
<p className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.qualification || 'Not specified'}</p>
@@ -213,11 +254,15 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
<p className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.experience || 'Not specified'}</p>
</div>
{faculty.joinedInstitution && (
<div className="p-4 rounded-xl bg-gray-50/80 border border-gray-100 transition-colors hover:bg-gray-50 md:col-span-2">
<div className="p-4 rounded-xl bg-gray-50/80 border border-gray-100 transition-colors hover:bg-gray-50">
<p className="text-xs font-semibold text-[#94A3B8] uppercase tracking-wider mb-1" style={{ fontFamily: 'Inter, sans-serif' }}>Joined Institution</p>
<p className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.joinedInstitution}</p>
</div>
)}
<div className="p-4 rounded-xl bg-gray-50/80 border border-gray-100 transition-colors hover:bg-gray-50 md:col-span-2">
<p className="text-xs font-semibold text-[#94A3B8] uppercase tracking-wider mb-1" style={{ fontFamily: 'Inter, sans-serif' }}>Interest / Domain / Specialization</p>
{renderPointList(faculty.interest || faculty.specialization)}
</div>
</div>
</div>
@@ -227,17 +272,19 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
<div className="w-10 h-10 rounded-xl bg-indigo-50 flex items-center justify-center shrink-0">
<BookOpen className="w-5 h-5 text-indigo-500" />
</div>
<h3 className="text-lg font-bold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Academic Information</h3>
<h3 className="text-lg font-bold text-[#1E293B]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>Academic Information</h3>
</div>
<div className="flex flex-col gap-4">
<div className="p-5 rounded-xl bg-indigo-50/30 border border-indigo-100/50">
<p className="text-xs font-bold text-indigo-400 uppercase tracking-wider mb-2" style={{ fontFamily: 'Inter, sans-serif' }}>Subjects Handling</p>
<p className="text-sm font-medium text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif', lineHeight: '1.6' }}>{faculty.subjectsHandling || 'Not specified'}</p>
{renderPointList(faculty.subjectsHandling)}
</div>
{faculty.researchAreas && (
<div className="p-5 rounded-xl bg-teal-50/30 border border-teal-100/50">
<p className="text-xs font-bold text-teal-500 uppercase tracking-wider mb-2" style={{ fontFamily: 'Inter, sans-serif' }}>Research Areas</p>
<p className="text-sm font-medium text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif', lineHeight: '1.6' }}>{faculty.researchAreas || 'Not specified'}</p>
<p className="text-sm font-medium text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif', lineHeight: '1.6' }}>{faculty.researchAreas}</p>
</div>
)}
{faculty.studentsGuided && (
<div className="p-5 rounded-xl bg-blue-50/30 border border-blue-100/50">
<p className="text-xs font-bold text-blue-500 uppercase tracking-wider mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>Students Guided</p>
@@ -267,7 +314,7 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
<div className="w-10 h-10 rounded-xl bg-pink-50 flex items-center justify-center shrink-0">
<GraduationCap className="w-5 h-5 text-pink-500" />
</div>
<h3 className="text-lg font-bold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Achievements</h3>
<h3 className="text-lg font-bold text-[#1E293B]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>Achievements</h3>
</div>
<div className="flex flex-col gap-6">
{faculty.achievements && faculty.achievements.length > 0 && (
@@ -295,30 +342,36 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
)}
{/* Section 4: Office Info */}
{(faculty.office || faculty.officeHours) && (
<div className="bg-white rounded-[20px] shadow-sm border border-[#E8ECF4] p-6 sm:p-8">
<div className="flex items-center gap-3 mb-6">
<div className="w-10 h-10 rounded-xl bg-emerald-50 flex items-center justify-center shrink-0">
<Clock className="w-5 h-5 text-emerald-500" />
</div>
<h3 className="text-lg font-bold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Office Information</h3>
<h3 className="text-lg font-bold text-[#1E293B]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>Office Information</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{faculty.officeHours && (
<div className="p-5 rounded-xl bg-emerald-50/50 border border-emerald-100 flex items-start gap-4">
<Clock className="w-6 h-6 text-emerald-500 shrink-0 mt-0.5" />
<div>
<p className="text-xs font-bold text-emerald-600 uppercase tracking-wider mb-1" style={{ fontFamily: 'Inter, sans-serif' }}>Office Hours</p>
<p className="text-sm font-medium text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.officeHours || 'Not specified'}</p>
<p className="text-sm font-medium text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.officeHours}</p>
</div>
</div>
)}
{faculty.office && (
<div className="p-5 rounded-xl bg-blue-50/30 border border-blue-100/50 flex items-start gap-4">
<Building2 className="w-6 h-6 text-blue-500 shrink-0 mt-0.5" />
<div>
<p className="text-xs font-bold text-blue-500 uppercase tracking-wider mb-1" style={{ fontFamily: 'Inter, sans-serif' }}>Office Location</p>
<p className="text-sm font-medium text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.office || 'Not specified'}</p>
<p className="text-sm font-medium text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.office}</p>
</div>
</div>
)}
</div>
</div>
)}
</div>
</div>
</motion.div>
@@ -349,7 +402,7 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
>
<div className="flex justify-between items-center">
<div>
<h3 className="text-lg font-bold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Choose Mail App</h3>
<h3 className="text-lg font-bold text-[#1E293B]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>Choose Mail App</h3>
<p className="text-xs text-[#64748B] mt-0.5" style={{ fontFamily: 'Inter, sans-serif' }}>Compose email to {faculty.name}</p>
</div>
<button onClick={() => setShowEmailOptions(false)} className="p-2 rounded-full hover:bg-gray-100 transition-colors">

View File

@@ -13,14 +13,13 @@ export default function FeatureCard({ feature }: FeatureCardProps) {
return (
<motion.div
whileHover={{ y: -6, boxShadow: '0 20px 50px -12px rgba(0,0,0,0.15)' }}
whileHover={{ y: -4 }}
transition={{ duration: 0.25 }}
className="bg-white border border-[#E5E7EB] rounded-2xl p-6 flex flex-col gap-4 group cursor-pointer"
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
className="bg-white border border-[#E9E5EE] rounded-2xl p-6 flex flex-col gap-4 group cursor-pointer shadow-[0_4px_20px_-4px_rgba(19,9,36,0.04)] hover:shadow-[0_12px_35px_-6px_rgba(19,9,36,0.08)] hover:border-[#FF6B00]/30"
>
{/* Icon */}
<div
className="w-12 h-12 rounded-xl flex items-center justify-center transition-all duration-300 group-hover:scale-110"
className="w-12 h-12 rounded-xl flex items-center justify-center transition-all duration-300 group-hover:scale-105"
style={{ backgroundColor: feature.bgColor, color: feature.color }}
>
{IconComponent && <IconComponent className="w-6 h-6" />}
@@ -29,12 +28,12 @@ export default function FeatureCard({ feature }: FeatureCardProps) {
{/* Content */}
<div className="flex-1">
<h3
className="text-base font-semibold text-[#1E293B] mb-1.5"
style={{ fontFamily: 'Poppins, sans-serif' }}
className="text-base font-bold text-[#1A0B2E] mb-1.5"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{feature.title}
</h3>
<p className="text-sm text-[#94A3B8] leading-relaxed" style={{ fontFamily: 'Inter, sans-serif' }}>
<p className="text-sm text-[#4A3E5E] leading-relaxed" style={{ fontFamily: 'Inter, sans-serif' }}>
{feature.description}
</p>
</div>
@@ -42,9 +41,9 @@ export default function FeatureCard({ feature }: FeatureCardProps) {
{/* Arrow */}
<Link
to={feature.path}
className="flex items-center gap-1.5 text-sm font-semibold transition-all duration-200 w-fit"
className="flex items-center gap-1.5 text-xs font-bold uppercase tracking-wider transition-all duration-200 w-fit"
style={{
fontFamily: 'Poppins, sans-serif',
fontFamily: 'Plus Jakarta Sans, sans-serif',
color: feature.color,
}}
aria-label={`Explore ${feature.title}`}

View File

@@ -1,123 +1,239 @@
import { Link } from 'react-router-dom';
import { GraduationCap, MapPin, Phone, Mail, ArrowRight } from 'lucide-react';
import { Link, useLocation } from 'react-router-dom';
import { motion } from 'framer-motion';
import {
GraduationCap, MapPin, Phone, Mail, ArrowRight, Sparkles,
BookOpen, Bot, Map, Bus, UserCheck, Users, MessageCircle, Wrench, Code2, Trophy
} from 'lucide-react';
const quickLinks = [
{ label: 'Home', path: '/' },
{ label: 'Notes & PYQs', path: '/notes' },
{ label: 'RIT Chatbot', path: '/ai-assistant' },
{ label: '3D Campus Map', path: '/campus' },
{ label: 'Bus Routes', path: '/bus-routes' },
{ label: 'Faculty Directory', path: '/faculty' },
{ label: 'Events', path: '/events' },
{ label: 'Community', path: '/community' },
{ label: 'Dev Hub', path: '/collab' },
const exploreLinks = [
{ label: 'Notes & PYQs', path: '/notes', icon: BookOpen },
{ label: 'Student Toolkit', path: '/toolkit', icon: Wrench },
{ label: 'RIT Chatbot', path: '/ai-assistant', icon: Bot },
{ label: 'Faculty Directory', path: '/faculty', icon: UserCheck },
{ label: 'LeetCode Arena', path: '/leetcode', icon: Trophy },
];
const campusLinks = [
{ label: 'Campus Map', path: '/campus', icon: Map },
{ label: 'Bus Routes', path: '/bus-routes', icon: Bus },
{ label: 'Clubs & Centers', path: '/events', icon: Users },
{ label: 'Freshers Q&A', path: '/community', icon: MessageCircle },
{ label: 'Dev Collab Hub', path: '/collab', icon: Code2 },
];
export default function Footer() {
const location = useLocation();
const isHome = location.pathname === '/';
return (
<footer className="bg-[#1E293B] text-white">
{/* CTA Banner */}
<div style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }} className="py-12">
<div className="container-custom flex flex-col md:flex-row items-center justify-between gap-6">
<div>
<h3 className="text-2xl font-bold text-white" style={{ fontFamily: 'Playfair Display, serif' }}>
Ready to explore RIT?
</h3>
<p className="text-orange-100 mt-1" style={{ fontFamily: 'Inter, sans-serif' }}>
Everything a fresher needs — all in one place.
</p>
<div className="w-full relative z-20">
{/* ─── 1. CTA Banner (Floating Overlay Box - Only on Home Page) ────────── */}
{isHome && (
<div className="container-custom relative z-20 -mb-20 lg:-mb-24 px-4">
<motion.div
whileHover={{ y: -3 }}
transition={{ duration: 0.3 }}
className="relative overflow-hidden rounded-3xl p-8 lg:p-12 shadow-2xl border border-orange-400/30"
style={{
background: 'linear-gradient(135deg, #EA580C 0%, #F97316 50%, #FB923C 100%)',
}}
>
{/* Ambient Background Lighting */}
<div className="absolute -right-16 -top-16 w-64 h-64 bg-white/10 rounded-full blur-3xl pointer-events-none" />
<div className="absolute -left-16 -bottom-16 w-64 h-64 bg-black/10 rounded-full blur-3xl pointer-events-none" />
<div className="relative z-10 flex flex-col md:flex-row items-center justify-between gap-6">
<div className="text-center md:text-left">
<div className="inline-flex items-center gap-1.5 px-3.5 py-1 rounded-full bg-white/15 backdrop-blur-md text-white text-xs font-bold uppercase tracking-wider mb-3 border border-white/20">
<Sparkles className="w-3.5 h-3.5 text-amber-200" />
<span>RIT Student Portal</span>
</div>
<Link
to="/ai-assistant"
className="flex items-center gap-2 bg-white text-[#F97316] px-6 py-3 rounded-xl font-semibold transition-all hover:shadow-lg hover:-translate-y-0.5"
<h3
className="text-2xl lg:text-3xl xl:text-4xl font-extrabold text-white tracking-tight"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
Ask Chatbot
<ArrowRight className="w-4 h-4" />
</Link>
</div>
</div>
{/* Main Footer */}
<div className="container-custom py-14">
<div className="grid grid-cols-1 md:grid-cols-3 gap-10">
{/* Brand */}
<div>
<div className="flex items-center gap-2.5 mb-4">
<div
className="w-10 h-10 rounded-xl flex items-center justify-center"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
Ready to explore RIT?
</h3>
<p
className="text-orange-100 text-sm lg:text-base mt-2 max-w-xl font-medium"
style={{ fontFamily: 'Inter, sans-serif' }}
>
<GraduationCap className="w-5 h-5 text-white" />
</div>
<div>
<div className="font-bold text-white text-sm" style={{ fontFamily: 'Poppins, sans-serif' }}>RIT Freshers Hub</div>
<div className="text-[10px] text-slate-400">Rajalakshmi Institute of Technology</div>
</div>
</div>
<p className="text-slate-400 text-sm leading-relaxed mb-5" style={{ fontFamily: 'Inter, sans-serif' }}>
Your all-in-one guide to campus life at RIT. Notes, chatbot, bus routes, 3D campus map, and developer hub — built for students.
Everything a fresher needs — semester notes, campus map, chatbot, bus routes, and developer tools all in one place.
</p>
</div>
{/* Quick Links */}
<div>
<h4 className="text-white font-semibold mb-4 text-sm uppercase tracking-wider" style={{ fontFamily: 'Poppins, sans-serif' }}>
Quick Navigation
</h4>
<div className="grid grid-cols-2 gap-2">
{quickLinks.map((link) => (
<Link
key={link.path}
to={link.path}
className="text-slate-400 hover:text-[#F97316] text-sm transition-colors flex items-center gap-1.5 group py-1"
style={{ fontFamily: 'Inter, sans-serif' }}
<Link to="/ai-assistant" className="shrink-0">
<motion.button
whileHover={{ scale: 1.05, boxShadow: '0 20px 30px -10px rgba(0,0,0,0.3)' }}
whileTap={{ scale: 0.96 }}
className="flex items-center gap-2.5 bg-white text-[#EA580C] px-7 py-3.5 rounded-2xl font-bold text-sm lg:text-base shadow-xl transition-all cursor-pointer group"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
<span className="w-1 h-1 rounded-full bg-[#F97316] opacity-0 group-hover:opacity-100 transition-opacity" />
{link.label}
<span>Ask Chatbot</span>
<ArrowRight className="w-4 h-4 transition-transform group-hover:translate-x-1" />
</motion.button>
</Link>
))}
</div>
</motion.div>
</div>
)}
{/* ─── 2. Main Footer Section (Deep Royal Dark Purple) ──────────── */}
<footer className={`bg-[#130924] text-slate-300 relative z-10 pb-24 lg:pb-28 ${isHome ? 'pt-32 lg:pt-36' : 'pt-28 lg:pt-36'}`}>
<div className="container-custom">
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-12 gap-12 xl:gap-14 items-start">
{/* Column 1: Brand Info (xl:col-span-4) */}
<div className="xl:col-span-4 flex flex-col gap-5">
<Link to="/" className="flex items-center gap-3.5 group w-fit">
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-orange-500 to-amber-500 flex items-center justify-center shadow-lg shadow-orange-500/25 group-hover:scale-105 transition-transform shrink-0">
<GraduationCap className="w-6.5 h-6.5 text-white" />
</div>
<div className="flex flex-col">
<span className="font-extrabold text-white text-xl tracking-tight" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
RIT Freshers Hub
</span>
<span className="text-xs font-semibold text-purple-300/80 uppercase tracking-wider mt-0.5">
Rajalakshmi Institute of Technology
</span>
</div>
</Link>
<p className="text-slate-300/80 text-sm lg:text-base leading-relaxed max-w-md" style={{ fontFamily: 'Inter, sans-serif' }}>
Your official student companion for RIT campus life. Access study resources, competitive coding ranks, project collaboration, and campus navigation.
</p>
<div className="flex items-center gap-2.5 mt-2 flex-wrap">
<span className="px-3.5 py-1.5 rounded-xl bg-[#1E0C36] border border-[#3A1968] text-xs font-semibold text-orange-400">
Official Student Portal
</span>
<span className="px-3.5 py-1.5 rounded-xl bg-emerald-500/15 border border-emerald-500/25 text-xs font-semibold text-emerald-400">
Active 24/7
</span>
</div>
</div>
{/* Contact */}
<div>
<h4 className="text-white font-semibold mb-4 text-sm uppercase tracking-wider" style={{ fontFamily: 'Poppins, sans-serif' }}>
Campus Location & Contact
{/* Column 2: Academics & Tools (xl:col-span-2) */}
<div className="xl:col-span-2">
<h4
className="!text-white font-extrabold text-xs lg:text-sm uppercase tracking-widest mb-6 lg:mb-7 flex items-center gap-2.5"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<span className="w-2.5 h-2.5 rounded-full bg-orange-500 shadow-xs shadow-orange-500/50" />
Academics
</h4>
<ul className="space-y-3">
<li className="flex items-start gap-3 text-slate-400 text-sm" style={{ fontFamily: 'Inter, sans-serif' }}>
<MapPin className="w-4 h-4 mt-0.5 shrink-0 text-[#F97316]" />
<span>Rajalakshmi Institute of Technology,<br />Kuthambakkam, Chennai - 600 124</span>
<ul className="space-y-4 lg:space-y-4.5">
{exploreLinks.map((link) => {
const Icon = link.icon;
return (
<li key={link.path}>
<Link
to={link.path}
className="text-slate-300/80 hover:text-orange-400 text-sm lg:text-base font-medium transition-colors inline-flex items-center gap-3 group py-1"
style={{ fontFamily: 'Inter, sans-serif' }}
>
<Icon className="w-4.5 h-4.5 text-purple-400/60 group-hover:text-orange-400 transition-colors shrink-0" />
<span>{link.label}</span>
</Link>
</li>
<li className="flex items-center gap-3 text-slate-400 text-sm">
<Phone className="w-4 h-4 shrink-0 text-[#F97316]" />
<a href="tel:+914423422890" className="hover:text-[#F97316] transition-colors">+91 44 2342 2890</a>
);
})}
</ul>
</div>
{/* Column 3: Campus Life (xl:col-span-3) */}
<div className="xl:col-span-3">
<h4
className="!text-white font-extrabold text-xs lg:text-sm uppercase tracking-widest mb-6 lg:mb-7 flex items-center gap-2.5"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<span className="w-2.5 h-2.5 rounded-full bg-orange-500 shadow-xs shadow-orange-500/50" />
Campus Life
</h4>
<ul className="space-y-4 lg:space-y-4.5">
{campusLinks.map((link) => {
const Icon = link.icon;
return (
<li key={link.path}>
<Link
to={link.path}
className="text-slate-300/80 hover:text-orange-400 text-sm lg:text-base font-medium transition-colors inline-flex items-center gap-3 group py-1"
style={{ fontFamily: 'Inter, sans-serif' }}
>
<Icon className="w-4.5 h-4.5 text-purple-400/60 group-hover:text-orange-400 transition-colors shrink-0" />
<span>{link.label}</span>
</Link>
</li>
<li className="flex items-center gap-3 text-slate-400 text-sm">
<Mail className="w-4 h-4 shrink-0 text-[#F97316]" />
<a href="mailto:info@rit.ac.in" className="hover:text-[#F97316] transition-colors">info@rit.ac.in</a>
);
})}
</ul>
</div>
{/* Column 4: Reach Us (xl:col-span-3) */}
<div className="xl:col-span-3">
<h4
className="!text-white font-extrabold text-xs lg:text-sm uppercase tracking-widest mb-6 lg:mb-7 flex items-center gap-2.5"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<span className="w-2.5 h-2.5 rounded-full bg-orange-500 shadow-xs shadow-orange-500/50" />
Reach Us
</h4>
<ul className="space-y-5 lg:space-y-6">
<li className="flex items-start gap-4 text-slate-300 text-sm lg:text-base leading-relaxed">
<div className="w-9 h-9 rounded-xl bg-orange-500/15 border border-orange-500/30 flex items-center justify-center shrink-0 mt-0.5">
<MapPin className="w-4.5 h-4.5 text-orange-400" />
</div>
<div className="flex flex-col">
<span className="font-semibold text-white">Rajalakshmi Institute of Technology</span>
<span className="text-slate-400 text-xs lg:text-sm mt-1">Kuthambakkam, Chennai - 600 124</span>
</div>
</li>
<li className="flex items-center gap-4 text-slate-300 text-sm lg:text-base">
<div className="w-9 h-9 rounded-xl bg-orange-500/15 border border-orange-500/30 flex items-center justify-center shrink-0">
<Phone className="w-4.5 h-4.5 text-orange-400" />
</div>
<a href="tel:+914423422890" className="hover:text-orange-400 transition-colors font-medium">
+91 44 2342 2890
</a>
</li>
<li className="flex items-center gap-4 text-slate-300 text-sm lg:text-base">
<div className="w-9 h-9 rounded-xl bg-orange-500/15 border border-orange-500/30 flex items-center justify-center shrink-0">
<Mail className="w-4.5 h-4.5 text-orange-400" />
</div>
<a href="mailto:info@rit.ac.in" className="hover:text-orange-400 transition-colors font-medium">
info@rit.ac.in
</a>
</li>
</ul>
</div>
</div>
</div>
{/* Bottom Bar */}
<div className="border-t border-slate-700">
<div className="container-custom py-5 flex flex-col sm:flex-row items-center justify-between gap-3">
<p className="text-slate-500 text-xs" style={{ fontFamily: 'Inter, sans-serif' }}>
© 2025 RIT Freshers Hub. All rights reserved.
{/* Bottom Copyright Bar */}
<div className="border-t border-[#2A104E] bg-[#090314] py-9 lg:py-10 px-4 mt-20">
<div className="container-custom flex flex-col sm:flex-row items-center justify-between gap-5">
<p className="text-purple-300/70 text-sm lg:text-base font-medium" style={{ fontFamily: 'Inter, sans-serif' }}>
© {new Date().getFullYear()} RIT Freshers Hub. Built for Rajalakshmi Institute of Technology Students.
</p>
<div className="flex items-center gap-4 text-xs text-slate-500">
<a href="#" className="hover:text-[#F97316] transition-colors">Privacy Policy</a>
<span>·</span>
<a href="#" className="hover:text-[#F97316] transition-colors">Terms of Use</a>
<span>·</span>
<a href="#" className="hover:text-[#F97316] transition-colors">Accessibility</a>
<div className="flex items-center gap-6 text-sm lg:text-base font-medium text-purple-300/70">
<a href="#" className="hover:text-orange-400 transition-colors">
Privacy Policy
</a>
<span className="text-purple-900">·</span>
<a href="#" className="hover:text-orange-400 transition-colors">
Terms of Use
</a>
<span className="text-purple-900">·</span>
<a href="#" className="hover:text-orange-400 transition-colors">
Accessibility
</a>
</div>
</div>
</div>
</footer>
</div>
);
}

View File

@@ -1,166 +1,238 @@
import { useState, useEffect, useRef } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion';
import { Menu, X, LogIn, LogOut, ShieldCheck, ChevronDown, Rocket, Sparkles } from 'lucide-react';
import { NAV_LINKS } from '@/constants';
import {
Menu,
X,
LogIn,
LogOut,
ShieldCheck,
ChevronDown,
Map,
Bus,
UserCheck,
MessageCircle,
Rocket,
BookOpen,
Wrench,
Bot,
Users,
Code2,
Trophy,
Home as HomeIcon,
} from 'lucide-react';
import { useAuth } from '@/context/AuthContext';
interface NavItem {
label: string;
path: string;
icon?: any;
}
const PRIMARY_LINKS: NavItem[] = [
{ label: 'Home', path: '/', icon: HomeIcon },
{ label: 'Notes', path: '/notes', icon: BookOpen },
{ label: 'Toolkit', path: '/toolkit', icon: Wrench },
{ label: 'Chatbot', path: '/ai-assistant', icon: Bot },
{ label: 'Clubs & Centers', path: '/events', icon: Users },
{ label: 'Dev Collab', path: '/collab', icon: Code2 },
{ label: 'LeetCode', path: '/leetcode', icon: Trophy },
];
const MORE_LINKS: NavItem[] = [
{ label: 'Campus Map', path: '/campus', icon: Map },
{ label: 'Bus Routes', path: '/bus-routes', icon: Bus },
{ label: 'Faculty Directory', path: '/faculty', icon: UserCheck },
{ label: 'Community', path: '/community', icon: MessageCircle },
{ label: 'RAISE Incubator', path: '/raise', icon: Rocket },
];
const ALL_MOBILE_LINKS: NavItem[] = [...PRIMARY_LINKS, ...MORE_LINKS];
export default function Navbar() {
const [isScrolled, setIsScrolled] = useState(false);
const [isMobileOpen, setIsMobileOpen] = useState(false);
const [isProfileOpen, setIsProfileOpen] = useState(false);
const [isMoreOpen, setIsMoreOpen] = useState(false);
const location = useLocation();
const { user, isAuthenticated, isVerifiedStudent, loginWithGoogle, logout } = useAuth();
const profileRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleScroll = () => setIsScrolled(window.scrollY > 20);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
const moreRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setIsMobileOpen(false);
setIsMoreOpen(false);
}, [location.pathname]);
// Close profile dropdown when clicking outside
// Close dropdowns when clicking outside
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (profileRef.current && !profileRef.current.contains(e.target as Node)) {
setIsProfileOpen(false);
}
if (moreRef.current && !moreRef.current.contains(e.target as Node)) {
setIsMoreOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const checkIsActive = (linkPath: string) => {
const [pathName, searchString] = linkPath.split('?');
if (location.pathname !== pathName) return false;
if (pathName === '/notes') {
const params = new URLSearchParams(location.search);
const hasToolkit = params.has('toolkit') || params.get('section') === 'toolkit';
if (searchString) {
return hasToolkit;
} else {
return !hasToolkit;
}
}
return true;
const [pathName] = linkPath.split('?');
if (pathName === '/') return location.pathname === '/';
return location.pathname.startsWith(pathName);
};
const isMoreActive = MORE_LINKS.some((link) => checkIsActive(link.path));
return (
<>
{/* Unified Floating Navbar Container */}
<header className="fixed top-3.5 left-1/2 -translate-x-1/2 z-50 w-[95%] max-w-[1440px] pointer-events-none">
<motion.nav
initial={{ y: -100, opacity: 0 }}
initial={{ y: -60, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.6, ease: 'easeOut' }}
className="fixed top-4 left-1/2 -translate-x-1/2 z-50 w-[95%] max-w-6xl transition-all duration-300 shadow-[0_8px_32px_rgba(0,0,0,0.25)]"
style={{
background: 'linear-gradient(to right, rgba(15, 23, 42, 0.45) 0%, rgba(30, 41, 59, 0.35) 50%, rgba(15, 23, 42, 0.45) 100%)',
backdropFilter: 'blur(16px)',
WebkitBackdropFilter: 'blur(16px)',
borderRadius: '20px',
border: '1px solid rgba(255, 255, 255, 0.1)',
}}
transition={{ duration: 0.45, ease: 'easeOut' }}
className="pointer-events-auto flex items-center justify-between gap-2 lg:gap-4 px-3 sm:px-4 py-2 rounded-2xl bg-slate-950/90 backdrop-blur-xl border border-white/10 shadow-[0_8px_30px_rgba(0,0,0,0.35)] transition-all"
>
<div className="flex items-center justify-between px-5 py-2.5">
{/* Logo - RIT Event Hub Style */}
<a href="/" className="flex items-center gap-2 group">
{/* Logo Image */}
{/* Left: Logo & College Brand */}
<Link to="/" className="flex items-center gap-2.5 group shrink-0">
<motion.img
whileHover={{ scale: 1.05 }}
whileHover={{ scale: 1.06 }}
src="/logo.png"
alt="RIT Logo"
className="w-10 h-10 object-contain rounded-full shrink-0"
className="w-8 h-8 lg:w-9 lg:h-9 object-contain rounded-full shrink-0"
/>
<div className="flex items-center">
<span className="font-extrabold text-white text-2xl tracking-tighter" style={{ fontFamily: 'Poppins, sans-serif' }}>
<span
className="font-extrabold text-white text-lg lg:text-xl tracking-tight group-hover:text-[#FF6B00] transition-colors"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
rit
</span>
<div className="h-7 w-[1px] bg-white/20 mx-2" />
<div className="flex flex-col text-[8.5px] font-bold text-slate-200 leading-tight tracking-wider" style={{ fontFamily: 'Inter, sans-serif' }}>
<span>RAJALAKSHMI</span>
<span>INSTITUTE OF</span>
<span>TECHNOLOGY</span>
<div className="h-4 lg:h-5 w-[1px] bg-white/20 mx-2" />
<div
className="hidden xl:flex flex-col text-[7.5px] lg:text-[8px] font-bold text-slate-300 leading-[1.15] tracking-wider uppercase"
style={{ fontFamily: 'Inter, sans-serif' }}
>
<span>Rajalakshmi</span>
<span>Institute of</span>
<span>Technology</span>
</div>
</div>
</a>
</Link>
{/* Desktop Nav */}
<div className="hidden md:flex items-center gap-1">
{NAV_LINKS.map((link) => {
{/* Center: Desktop Navigation Links */}
<div className="hidden lg:flex items-center gap-1 xl:gap-1.5 min-w-0">
{PRIMARY_LINKS.map((link) => {
const isActive = checkIsActive(link.path);
return (
<Link
key={link.path}
to={link.path}
className="relative px-1.5 lg:px-2.5 xl:px-3.5 py-1.5 rounded-lg text-[10px] lg:text-[11px] xl:text-xs font-semibold uppercase tracking-wider transition-all duration-200"
style={{
fontFamily: 'Poppins, sans-serif',
color: isActive ? '#FFFFFF' : '#CBD5E1',
}}
className={`relative px-2.5 xl:px-3 py-1.5 rounded-xl text-xs font-semibold tracking-wide transition-all duration-200 whitespace-nowrap ${
isActive ? 'text-white' : 'text-slate-300 hover:text-white hover:bg-white/10'
}`}
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{isActive && (
<motion.div
layoutId="nav-active"
className="absolute inset-0 rounded-xl"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
transition={{ type: 'spring', bounce: 0.2, duration: 0.4 }}
layoutId="nav-pill-active"
className="absolute inset-0 rounded-xl bg-gradient-to-r from-[#FF6B00] to-[#F97316] shadow-sm"
transition={{ type: 'spring', bounce: 0.15, duration: 0.35 }}
/>
)}
<span className="relative z-10">{link.label}</span>
</Link>
);
})}
{/* "More" Dropdown Menu */}
<div className="relative" ref={moreRef}>
<button
onClick={() => setIsMoreOpen(!isMoreOpen)}
className={`relative flex items-center gap-1 px-2.5 xl:px-3 py-1.5 rounded-xl text-xs font-semibold tracking-wide transition-all cursor-pointer ${
isMoreActive ? 'text-white' : 'text-slate-300 hover:text-white hover:bg-white/10'
}`}
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{isMoreActive && (
<motion.div
layoutId="nav-pill-active"
className="absolute inset-0 rounded-xl bg-gradient-to-r from-[#FF6B00] to-[#F97316] shadow-sm"
transition={{ type: 'spring', bounce: 0.15, duration: 0.35 }}
/>
)}
<span className="relative z-10">More</span>
<ChevronDown
className={`w-3.5 h-3.5 relative z-10 transition-transform duration-200 ${
isMoreOpen ? 'rotate-180 text-white' : 'text-slate-400'
}`}
/>
</button>
<AnimatePresence>
{isMoreOpen && (
<motion.div
initial={{ opacity: 0, y: 8, scale: 0.96 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 8, scale: 0.96 }}
transition={{ duration: 0.15 }}
className="absolute right-0 top-full mt-2 w-52 bg-slate-900/95 backdrop-blur-xl border border-white/10 rounded-2xl shadow-2xl p-1.5 z-50 overflow-hidden"
>
{MORE_LINKS.map((item) => {
const IconComponent = item.icon;
const active = checkIsActive(item.path);
return (
<Link
key={item.path}
to={item.path}
onClick={() => setIsMoreOpen(false)}
className={`flex items-center gap-2.5 px-3 py-2 rounded-xl text-xs font-medium transition-all ${
active
? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] text-white font-bold'
: 'text-slate-300 hover:text-white hover:bg-white/10'
}`}
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{IconComponent && <IconComponent className="w-4 h-4 shrink-0 text-slate-300" />}
<span>{item.label}</span>
</Link>
);
})}
</motion.div>
)}
</AnimatePresence>
</div>
</div>
{/* Right Side: Auth + Mobile Toggle */}
<div className="flex items-center gap-2">
{/* Pitch your Idea - RAISE Button */}
<Link
to="/raise"
className="hidden lg:flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-gradient-to-r from-rose-500 via-pink-500 to-rose-600 hover:from-rose-600 hover:to-pink-600 text-white text-[11px] font-bold shadow-md shadow-rose-500/25 border border-white/20 transition-all duration-200 hover:scale-[1.03] active:scale-95 whitespace-nowrap"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
<Sparkles className="w-3.5 h-3.5 text-amber-200" />
<span>Pitch your Idea - RAISE</span>
</Link>
{/* Auth Section */}
{/* Right: Auth Profile / Sign In */}
<div className="hidden lg:flex items-center gap-2 shrink-0">
{isAuthenticated && user ? (
<div className="relative" ref={profileRef}>
<button
onClick={() => setIsProfileOpen(!isProfileOpen)}
className="flex items-center gap-2 px-2 py-1.5 rounded-xl hover:bg-white/10 transition-all cursor-pointer"
className="flex items-center gap-2 px-3 py-1.5 rounded-xl bg-slate-800/80 hover:bg-slate-800 border border-white/10 transition-all cursor-pointer"
>
{user.pictureUrl ? (
<img
src={user.pictureUrl}
alt={user.name}
className="w-8 h-8 rounded-full border-2 border-white/30 object-cover"
className="w-6 h-6 rounded-full border border-white/30 object-cover"
referrerPolicy="no-referrer"
/>
) : (
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-orange-400 to-orange-600 flex items-center justify-center text-white font-bold text-sm">
<div className="w-6 h-6 rounded-full bg-gradient-to-br from-[#FF6B00] to-[#F97316] flex items-center justify-center text-white font-bold text-xs">
{user.name.charAt(0).toUpperCase()}
</div>
)}
<div className="hidden lg:flex flex-col items-start">
<span className="text-white text-[11px] font-semibold leading-tight max-w-[100px] truncate">
<span
className="text-white text-xs font-semibold max-w-[100px] truncate"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{user.name.split(' ')[0]}
</span>
{isVerifiedStudent && (
<span className="flex items-center gap-0.5 text-[9px] text-emerald-400 font-bold">
<ShieldCheck className="w-2.5 h-2.5" /> Verified
</span>
)}
</div>
<ChevronDown className="w-3 h-3 text-slate-400 hidden lg:block" />
<ChevronDown className="w-3.5 h-3.5 text-slate-400" />
</button>
{/* Profile Dropdown */}
@@ -171,9 +243,9 @@ export default function Navbar() {
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 8, scale: 0.95 }}
transition={{ duration: 0.15 }}
className="absolute right-0 top-full mt-2 w-64 bg-slate-900 border border-slate-700 rounded-2xl shadow-2xl overflow-hidden z-50"
className="absolute right-0 top-full mt-2 w-64 bg-slate-900 border border-white/10 rounded-2xl shadow-2xl overflow-hidden z-50"
>
<div className="p-4 border-b border-slate-700/60">
<div className="p-4 border-b border-white/10">
<div className="flex items-center gap-3">
{user.pictureUrl ? (
<img
@@ -183,7 +255,7 @@ export default function Navbar() {
referrerPolicy="no-referrer"
/>
) : (
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-orange-400 to-orange-600 flex items-center justify-center text-white font-bold text-lg">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-[#FF6B00] to-[#F97316] flex items-center justify-center text-white font-bold text-lg">
{user.name.charAt(0).toUpperCase()}
</div>
)}
@@ -200,7 +272,7 @@ export default function Navbar() {
) : (
<div className="mt-3 flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg bg-amber-500/15 border border-amber-500/25">
<span className="text-[11px] font-semibold text-amber-400">
Sign in with @ritchennai.edu.in for verified badge
Sign in with @ritchennai.edu.in
</span>
</div>
)}
@@ -211,9 +283,9 @@ export default function Navbar() {
logout();
setIsProfileOpen(false);
}}
className="w-full flex items-center gap-2.5 px-3 py-2.5 rounded-xl text-slate-300 hover:text-white hover:bg-red-500/15 text-xs font-semibold transition-all cursor-pointer"
className="w-full flex items-center gap-2.5 px-3 py-2 rounded-xl text-slate-300 hover:text-white hover:bg-red-500/15 text-xs font-semibold transition-all cursor-pointer"
>
<LogOut className="w-4 h-4" />
<LogOut className="w-4 h-4 text-red-400" />
Sign Out
</button>
</div>
@@ -226,27 +298,28 @@ export default function Navbar() {
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
onClick={loginWithGoogle}
className="hidden md:flex items-center gap-2 px-4 py-2 rounded-xl text-white text-[11px] font-semibold border border-white/15 hover:border-orange-500/40 hover:bg-white/5 transition-all cursor-pointer"
style={{ fontFamily: 'Poppins, sans-serif' }}
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-xl text-white text-xs font-bold bg-white/10 hover:bg-white/15 border border-white/15 transition-all cursor-pointer"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<LogIn className="w-3.5 h-3.5" />
Sign In
<LogIn className="w-3.5 h-3.5 text-[#FF6B00]" />
<span>Sign In</span>
</motion.button>
)}
</div>
{/* Mobile Toggle */}
{/* Mobile Hamburger Toggle */}
<div className="lg:hidden flex items-center">
<motion.button
whileTap={{ scale: 0.9 }}
onClick={() => setIsMobileOpen(!isMobileOpen)}
className="md:hidden w-11 h-11 rounded-xl flex items-center justify-center text-slate-200 hover:text-white bg-slate-800/60 hover:bg-slate-700/60 border border-slate-700/50 transition-all active:scale-95"
className="w-9 h-9 rounded-xl flex items-center justify-center text-slate-200 hover:text-white bg-slate-800/80 border border-white/10 transition-all active:scale-95"
aria-label="Toggle menu"
>
{isMobileOpen ? <X className="w-5 h-5 stroke-[2.5]" /> : <Menu className="w-5 h-5 stroke-[2.5]" />}
</motion.button>
</div>
</div>
{/* Mobile Menu */}
{/* Mobile Menu Dropdown */}
<AnimatePresence>
{isMobileOpen && (
<motion.div
@@ -254,51 +327,44 @@ export default function Navbar() {
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25 }}
className="overflow-hidden md:hidden max-h-[80vh] overflow-y-auto"
className="absolute left-0 top-full mt-2 w-full overflow-hidden lg:hidden max-h-[82vh] overflow-y-auto z-50 rounded-2xl border border-white/10 bg-slate-950/95 backdrop-blur-2xl shadow-2xl"
>
<div className="px-4 pb-5 pt-3 grid grid-cols-2 gap-2 border-t border-white/10 bg-slate-950/90 backdrop-blur-2xl rounded-b-2xl">
{NAV_LINKS.map((link) => {
<div className="p-4 space-y-3">
<div className="grid grid-cols-2 gap-2">
{ALL_MOBILE_LINKS.map((link) => {
const isActive = checkIsActive(link.path);
const IconComp = link.icon;
return (
<Link
key={link.path}
to={link.path}
onClick={() => setIsMobileOpen(false)}
className="px-3.5 py-3 rounded-xl text-xs font-semibold uppercase tracking-wider flex items-center justify-center text-center transition-all min-h-[44px]"
style={{
fontFamily: 'Poppins, sans-serif',
color: isActive ? '#FFFFFF' : '#CBD5E1',
backgroundColor: isActive ? '#F97316' : 'rgba(255, 255, 255, 0.05)',
border: isActive ? '1px solid #FB923C' : '1px solid rgba(255, 255, 255, 0.08)',
}}
className={`px-3 py-2.5 rounded-xl text-xs font-semibold flex items-center gap-2 transition-all ${
isActive
? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] text-white shadow-sm'
: 'bg-white/5 text-slate-200 hover:bg-white/10 border border-white/5'
}`}
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{link.label}
{IconComp && <IconComp className="w-3.5 h-3.5 opacity-80" />}
<span className="truncate">{link.label}</span>
</Link>
);
})}
{/* Mobile RAISE Button */}
<Link
to="/raise"
onClick={() => setIsMobileOpen(false)}
className="col-span-2 mt-1 flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl text-white text-xs font-bold bg-gradient-to-r from-rose-500 to-pink-600 border border-rose-400/40 shadow-md shadow-rose-500/20 transition-all active:scale-95"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
<Sparkles className="w-4 h-4 text-amber-200" />
Pitch your Idea - RAISE
</Link>
</div>
{/* Mobile Auth Button */}
<div className="pt-2 border-t border-white/10">
{!isAuthenticated ? (
<button
onClick={() => {
loginWithGoogle();
setIsMobileOpen(false);
}}
className="col-span-2 mt-2 flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-white text-xs font-semibold border border-orange-500/40 bg-orange-500/10 hover:bg-orange-500/20 transition-all cursor-pointer"
style={{ fontFamily: 'Poppins, sans-serif' }}
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl text-white text-xs font-semibold border border-orange-500/40 bg-orange-500/15 hover:bg-orange-500/25 transition-all cursor-pointer"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<LogIn className="w-4 h-4" />
<LogIn className="w-4 h-4 text-orange-400" />
Sign In with Google
</button>
) : (
@@ -307,20 +373,22 @@ export default function Navbar() {
logout();
setIsMobileOpen(false);
}}
className="col-span-2 mt-2 flex items-center justify-center gap-2 px-4 py-3 rounded-xl text-red-400 text-xs font-semibold border border-red-500/30 bg-red-500/10 hover:bg-red-500/20 transition-all cursor-pointer"
style={{ fontFamily: 'Poppins, sans-serif' }}
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl text-red-400 text-xs font-semibold border border-red-500/30 bg-red-500/10 hover:bg-red-500/20 transition-all cursor-pointer"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<LogOut className="w-4 h-4" />
Sign Out ({user?.name.split(' ')[0]})
</button>
)}
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</motion.nav>
</header>
{/* Spacer */}
{/* Spacer to prevent content overlap */}
<div className="h-20" />
</>
);

View File

@@ -28,12 +28,12 @@ export default function SectionTitle({
className={`inline-flex items-center gap-2 mb-3 ${isCenter ? 'mx-auto' : ''}`}
>
<span
className="px-3 py-1 rounded-full text-xs font-semibold tracking-wider uppercase"
className="px-3 py-1 rounded-full text-xs font-bold tracking-wider uppercase"
style={{
fontFamily: 'Poppins, sans-serif',
fontFamily: 'Plus Jakarta Sans, sans-serif',
backgroundColor: '#FFF7ED',
color: '#F97316',
border: '1px solid #FED7AA',
color: '#FF6B00',
border: '1px solid #FFEDD5',
}}
>
{tag}
@@ -46,15 +46,15 @@ export default function SectionTitle({
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
className="text-3xl md:text-4xl font-bold"
style={{ fontFamily: 'Playfair Display, serif', color: '#1E293B' }}
className="text-3xl md:text-4xl lg:text-5xl font-extrabold tracking-tight"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif', color: '#1A0B2E' }}
>
{title}{' '}
{highlight && (
<span
className="relative inline-block"
style={{
background: 'linear-gradient(135deg, #F97316, #FB923C)',
background: 'linear-gradient(135deg, #FF6B00, #F97316)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
backgroundClip: 'text',
@@ -71,7 +71,7 @@ export default function SectionTitle({
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.2 }}
className={`mt-3 text-base text-[#475569] max-w-xl ${isCenter ? 'mx-auto' : ''}`}
className={`mt-3 text-base text-[#4A3E5E] max-w-xl ${isCenter ? 'mx-auto' : ''}`}
style={{ fontFamily: 'Inter, sans-serif' }}
>
{subtitle}

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +1,32 @@
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700;800&family=Inter:wght@300;400;500;600;700&family=Poppins:wght@400;500;600;700&family=Plus+Jakarta+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,400&family=Archivo+Black&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,400&family=Inter:wght@300;400;500;600;700;800&family=Poppins:wght@400;500;600;700&display=swap');
@import "tailwindcss";
@theme {
--color-primary: #F97316;
--color-primary-light: #FB923C;
--color-primary: #FF6B00;
--color-primary-light: #F97316;
--color-primary-dark: #EA580C;
--color-primary-50: #FFF7ED;
--color-primary-100: #FFEDD5;
--color-primary-200: #FED7AA;
--color-secondary: #FB923C;
--color-background: #FAFAFA;
--color-secondary: #F97316;
--color-dark-purple: #130924;
--color-dark-purple-light: #1E0C36;
--color-dark-purple-surface: #2A104E;
--color-dark-purple-border: #3A1968;
--color-background: #FAF9FC;
--color-surface: #FFFFFF;
--color-border: #E5E7EB;
--color-heading: #1E293B;
--color-body: #475569;
--color-muted: #94A3B8;
--color-border: #E9E5EE;
--color-heading: #1A0B2E;
--color-body: #4A3E5E;
--color-muted: #9E91B6;
--color-accent: #FFF7ED;
--font-heading: 'Playfair Display', Georgia, serif;
--font-heading: 'Plus Jakarta Sans', 'Inter', system-ui, sans-serif;
--font-body: 'Inter', system-ui, sans-serif;
--font-button: 'Poppins', system-ui, sans-serif;
--font-button: 'Plus Jakarta Sans', 'Poppins', system-ui, sans-serif;
--radius-sm: 8px;
--radius-md: 12px;
@@ -30,19 +36,17 @@
--radius-3xl: 32px;
--radius-full: 9999px;
--shadow-soft: 0 2px 15px -3px rgba(0,0,0,0.07), 0 10px 20px -2px rgba(0,0,0,0.04);
--shadow-medium: 0 4px 25px -5px rgba(0,0,0,0.1), 0 10px 30px -5px rgba(0,0,0,0.07);
--shadow-strong: 0 8px 40px -8px rgba(0,0,0,0.15), 0 20px 45px -8px rgba(0,0,0,0.1);
--shadow-orange: 0 8px 30px -4px rgba(249,115,22,0.3);
--shadow-soft: 0 4px 20px -4px rgba(19, 9, 36, 0.05), 0 1px 3px rgba(19, 9, 36, 0.02);
--shadow-medium: 0 8px 30px -6px rgba(19, 9, 36, 0.08), 0 2px 6px rgba(19, 9, 36, 0.03);
--shadow-strong: 0 16px 45px -8px rgba(19, 9, 36, 0.15), 0 4px 12px rgba(19, 9, 36, 0.05);
--shadow-orange: 0 8px 30px -4px rgba(255, 107, 0, 0.35);
--color-brand-orange: #ff5722;
--color-brand-pink: #e91e63;
--color-brand-purple: #9c27b0;
--color-brand-orange: #FF6B00;
--color-brand-purple: #130924;
--animate-pulse-slow: pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
/* Tailwind Preflight handles resets natively. Custom modifications go here: */
/* Base resets & typography */
html {
scroll-behavior: smooth;
font-size: 16px;
@@ -50,8 +54,8 @@ html {
body {
font-family: var(--font-body);
background-color: #FAFAFA;
color: #475569;
background-color: #FAF9FC;
color: #4A3E5E;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@@ -62,10 +66,10 @@ body {
width: 6px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
background: #FAF9FC;
}
::-webkit-scrollbar-thumb {
background: #F97316;
background: #FF6B00;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
@@ -74,20 +78,23 @@ body {
/* Selection */
::selection {
background-color: rgba(249, 115, 22, 0.2);
color: #1E293B;
background-color: rgba(255, 107, 0, 0.2);
color: #1A0B2E;
}
/* Typography base */
h1, h2, h3, h4, h5, h6 {
@layer base {
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-heading);
color: #1E293B;
line-height: 1.2;
color: #1A0B2E;
font-weight: 700;
line-height: 1.25;
}
}
/* Focus ring */
:focus-visible {
outline: 2px solid #F97316;
outline: 2px solid #FF6B00;
outline-offset: 2px;
}
@@ -110,12 +117,12 @@ h1, h2, h3, h4, h5, h6 {
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
50% { transform: translateY(-8px); }
}
@keyframes pulse-orange {
0%, 100% { box-shadow: 0 0 0 0 rgba(249, 115, 22, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(249, 115, 22, 0); }
0%, 100% { box-shadow: 0 0 0 0 rgba(255, 107, 0, 0.4); }
70% { box-shadow: 0 0 0 10px rgba(255, 107, 0, 0); }
}
@keyframes shimmer {
@@ -123,12 +130,6 @@ h1, h2, h3, h4, h5, h6 {
100% { background-position: 200% 0; }
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.animate-float {
animation: float 4s ease-in-out infinite;
}
@@ -149,36 +150,37 @@ h1, h2, h3, h4, h5, h6 {
/* Glass effect */
.glass {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.6);
}
.glass-dark {
background: rgba(30, 41, 59, 0.8);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
background: rgba(19, 9, 36, 0.85);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* Card base */
.card-base {
background: #FFFFFF;
border-radius: 20px;
border: 1px solid #E5E7EB;
box-shadow: 0 2px 15px -3px rgba(0,0,0,0.07), 0 10px 20px -2px rgba(0,0,0,0.04);
transition: transform 0.3s ease, box-shadow 0.3s ease;
border-radius: 16px;
border: 1px solid #E9E5EE;
box-shadow: 0 4px 20px -4px rgba(19, 9, 36, 0.04), 0 1px 3px rgba(19, 9, 36, 0.02);
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.card-base:hover {
transform: translateY(-4px);
box-shadow: 0 8px 40px -8px rgba(0,0,0,0.15), 0 20px 45px -8px rgba(0,0,0,0.1);
transform: translateY(-3px);
box-shadow: 0 12px 35px -6px rgba(19, 9, 36, 0.08), 0 4px 12px -2px rgba(19, 9, 36, 0.04);
border-color: rgba(255, 107, 0, 0.3);
}
/* Gradient text */
.gradient-text {
background: linear-gradient(135deg, #F97316, #FB923C, #FBBF24);
background: linear-gradient(135deg, #FF6B00 0%, #F97316 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
@@ -187,16 +189,17 @@ h1, h2, h3, h4, h5, h6 {
/* Orange button */
.btn-primary {
font-family: var(--font-button);
background: linear-gradient(135deg, #F97316, #FB923C);
background: linear-gradient(135deg, #FF6B00 0%, #F97316 100%);
color: white;
border: none;
border-radius: 12px;
padding: 12px 28px;
font-weight: 600;
font-size: 15px;
font-weight: 700;
font-size: 14px;
letter-spacing: 0.01em;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(249, 115, 22, 0.3);
box-shadow: 0 4px 16px rgba(255, 107, 0, 0.28);
display: inline-flex;
align-items: center;
gap: 8px;
@@ -205,8 +208,8 @@ h1, h2, h3, h4, h5, h6 {
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(249, 115, 22, 0.4);
background: linear-gradient(135deg, #EA580C, #F97316);
box-shadow: 0 8px 24px rgba(255, 107, 0, 0.38);
background: linear-gradient(135deg, #EA580C 0%, #FF6B00 100%);
}
.btn-primary:active {
@@ -217,12 +220,12 @@ h1, h2, h3, h4, h5, h6 {
.btn-ghost {
font-family: var(--font-button);
background: transparent;
color: #1E293B;
border: 2px solid #E5E7EB;
color: #1A0B2E;
border: 1.5px solid #E9E5EE;
border-radius: 12px;
padding: 10px 26px;
font-weight: 600;
font-size: 15px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
@@ -232,8 +235,9 @@ h1, h2, h3, h4, h5, h6 {
}
.btn-ghost:hover {
border-color: #F97316;
color: #F97316;
border-color: #FF6B00;
color: #FF6B00;
background-color: rgba(255, 107, 0, 0.04);
transform: translateY(-2px);
}
@@ -263,23 +267,13 @@ h1, h2, h3, h4, h5, h6 {
/* Orange dot pattern background */
.dot-pattern {
background-image: radial-gradient(circle, #F9731615 1px, transparent 1px);
background-image: radial-gradient(circle, rgba(255, 107, 0, 0.08) 1px, transparent 1px);
background-size: 24px 24px;
}
/* Noise texture overlay */
.noise-overlay::before {
content: '';
position: absolute;
inset: 0;
opacity: 0.03;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
pointer-events: none;
}
/* Chat bubble */
.chat-bubble-user {
background: linear-gradient(135deg, #F97316, #FB923C);
background: linear-gradient(135deg, #FF6B00, #F97316);
color: white;
border-radius: 18px 18px 4px 18px;
padding: 12px 16px;
@@ -289,91 +283,26 @@ h1, h2, h3, h4, h5, h6 {
}
.chat-bubble-ai {
background: #F8FAFC;
color: #1E293B;
background: #FFFFFF;
color: #1A0B2E;
border-radius: 18px 18px 18px 4px;
padding: 12px 16px;
font-size: 14px;
max-width: 80%;
align-self: flex-start;
border: 1px solid #E5E7EB;
border: 1px solid #E9E5EE;
}
/* Verified card style input focus state */
.verified-focus-input {
transition: border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease;
background-color: #FFFFFF;
border: 1.5px solid #E5E7EB !important;
border: 1.5px solid #E9E5EE !important;
}
.verified-focus-input:focus {
outline: none !important;
border-color: #F97316 !important;
box-shadow: none !important;
border-color: #FF6B00 !important;
box-shadow: 0 0 0 3px rgba(255, 107, 0, 0.15) !important;
}
/* RAISE Incubator Website Styles */
.raise-website {
--font-sans: 'Inter', sans-serif;
--font-heading: 'Plus Jakarta Sans', sans-serif;
--color-primary: #101010;
}
.raise-website body {
font-family: var(--font-sans);
background-color: #fafafa;
color: #171717;
}
.raise-website h1,
.raise-website h2,
.raise-website h3,
.raise-website h4,
.raise-website h5,
.raise-website h6 {
font-family: var(--font-heading) !important;
}
.text-gradient {
background: linear-gradient(135deg, #2563eb 0%, #3b82f6 50%, #06b6d4 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.logo-blue {
filter: invert(29%) sepia(92%) saturate(2377%) hue-rotate(215deg) brightness(93%) contrast(97%);
}
.logo-black {
filter: brightness(0);
}
.glassmorphism {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.6);
}
.shadow-premium {
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.02),
0 1px 3px rgba(0, 0, 0, 0.05),
0 20px 40px rgba(0, 0, 0, 0.03);
}
.shadow-premium-hover {
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.shadow-premium-hover:hover {
transform: translateY(-4px);
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.04),
0 2px 8px rgba(0, 0, 0, 0.06),
0 30px 60px rgba(0, 0, 0, 0.05);
}
.grid-bg {
background-size: 40px 40px;
background-image:
linear-gradient(to right, rgba(0, 0, 0, 0.02) 1px, transparent 1px),
linear-gradient(to bottom, rgba(0, 0, 0, 0.02) 1px, transparent 1px);
}

View File

@@ -109,17 +109,17 @@ export default function AIAssistant() {
};
return (
<div className="min-h-screen flex flex-col bg-[#FAFAFA]">
<div className="min-h-screen flex flex-col bg-[#FAF9FC]">
{/* Header */}
<div className="bg-white border-b border-[#E5E7EB] py-4 relative z-10 shadow-xs">
<div className="bg-white border-b border-[#E9E5EE] py-4 relative z-10 shadow-xs">
<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 shadow-sm" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
<div className="w-10 h-10 rounded-xl flex items-center justify-center shadow-md shadow-orange-500/20" style={{ background: 'linear-gradient(135deg, #FF6B00, #F97316)' }}>
<Bot className="w-5.5 h-5.5 text-white" />
</div>
<div>
<h1 className="font-bold text-[#1E293B] text-base" style={{ fontFamily: 'Poppins, sans-serif' }}>RIT Assistant</h1>
<div className="flex items-center gap-1.5 text-xs text-[#64748B]">
<h1 className="font-extrabold text-[#1A0B2E] text-base" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>RIT Assistant</h1>
<div className="flex items-center gap-1.5 text-xs text-[#9E91B6]">
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
Campus Knowledge Engine Online
</div>
@@ -139,19 +139,6 @@ export default function AIAssistant() {
<Send className="w-3.5 h-3.5" />
<span className="hidden sm:inline">Telegram Bot</span>
</a>
<a
href="https://discord.com/api/oauth2/authorize?client_id=1476942599167414506&permissions=8&scope=bot"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-medium text-white transition-all shadow-xs hover:brightness-105 active:scale-95"
style={{
background: 'linear-gradient(135deg, #5865F2, #7289DA)',
fontFamily: 'Inter, sans-serif'
}}
>
<Bot className="w-3.5 h-3.5" />
<span className="hidden sm:inline">Discord Bot</span>
</a>
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
@@ -187,8 +174,8 @@ export default function AIAssistant() {
{/* Chat Container */}
<div
ref={chatContainerRef}
className="flex-1 bg-white rounded-3xl border border-[#E5E7EB] p-4 sm:p-6 flex flex-col gap-4 overflow-y-auto"
style={{ minHeight: '420px', maxHeight: '65vh', boxShadow: '0 8px 30px -6px rgba(0,0,0,0.04)' }}
className="flex-1 bg-white rounded-2xl border border-[#E9E5EE] p-4 sm:p-6 flex flex-col gap-4 overflow-y-auto"
style={{ minHeight: '420px', maxHeight: '65vh', boxShadow: '0 4px 20px_-4px_rgba(19,9,36,0.04)' }}
>
{messages.map((msg) => (
<motion.div
@@ -199,17 +186,17 @@ export default function AIAssistant() {
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 shadow-xs" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
<div className="w-8 h-8 rounded-xl flex items-center justify-center shrink-0 mt-1 shadow-sm" style={{ background: 'linear-gradient(135deg, #FF6B00, #F97316)' }}>
<Bot className="w-4 h-4 text-white" />
</div>
)}
<div
className="max-w-[85%] sm:max-w-[78%] px-4 py-3 text-sm leading-relaxed"
style={{
background: msg.role === 'user' ? 'linear-gradient(135deg, #F97316, #EA580C)' : '#F8FAFC',
color: msg.role === 'user' ? 'white' : '#1E293B',
background: msg.role === 'user' ? 'linear-gradient(135deg, #FF6B00, #F97316)' : '#FFFFFF',
color: msg.role === 'user' ? 'white' : '#1A0B2E',
borderRadius: msg.role === 'user' ? '20px 20px 4px 20px' : '20px 20px 20px 4px',
border: msg.role === 'assistant' ? '1px solid #E5E7EB' : 'none',
border: msg.role === 'assistant' ? '1px solid #E9E5EE' : 'none',
fontFamily: 'Inter, sans-serif',
}}
dangerouslySetInnerHTML={{ __html: formatContent(msg.content) }}

View File

@@ -50,18 +50,18 @@ export default function BusRoutes() {
);
return (
<div className="min-h-screen relative overflow-hidden bg-slate-950 text-white">
<div className="min-h-screen relative overflow-hidden bg-[#0A0414] text-white">
{/* Sleek Dark Header */}
<div className="bg-slate-900 border-b border-slate-800/80 py-4 px-4 sm:px-8 z-30 relative">
<div className="bg-[#130924] border-b border-[#3A1968]/80 py-4 px-4 sm:px-8 z-30 relative">
<div className="max-w-7xl mx-auto flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<div className="flex items-center gap-2 text-xs text-slate-400 mb-1">
<Link to="/" className="hover:text-orange-400">Home</Link>
<div className="flex items-center gap-2 text-xs text-purple-300/80 mb-1">
<Link to="/" className="hover:text-[#FF6B00]">Home</Link>
<ChevronRight className="w-3 h-3" />
<span className="text-orange-400 font-semibold">Live Bus Tracking</span>
<span className="text-[#FF6B00] font-semibold">Live Bus Tracking</span>
</div>
<div className="flex items-center gap-3">
<h1 className="text-xl sm:text-2xl font-bold text-white tracking-tight" style={{ fontFamily: 'Poppins, sans-serif' }}>
<h1 className="text-xl sm:text-2xl font-extrabold text-white tracking-tight" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
RIT Live Transport Map
</h1>
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-emerald-500/15 border border-emerald-500/30 text-emerald-400 text-[11px] font-bold">
@@ -74,25 +74,25 @@ export default function BusRoutes() {
{/* Controls: Search + Toggle Drawer Button */}
<div className="flex items-center gap-2">
<div className="relative flex-1 sm:w-64">
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400" />
<Search className="absolute left-3.5 top-1/2 -translate-y-1/2 w-4 h-4 text-purple-300/60" />
<input
type="text"
placeholder="Search route or bus no..."
value={busSearch}
onChange={(e) => setBusSearch(e.target.value)}
className="w-full pl-9 pr-4 py-2 rounded-xl bg-slate-800 border border-slate-700 text-xs text-white placeholder-slate-400 focus:outline-none focus:border-orange-500 transition-colors"
className="w-full pl-9 pr-4 py-2 rounded-xl bg-[#1E0C36] border border-[#3A1968] text-xs text-white placeholder-purple-300/50 focus:outline-none focus:border-[#FF6B00] transition-colors"
style={{ fontFamily: 'Inter, sans-serif' }}
/>
</div>
<button
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
className={`flex items-center gap-2 px-3.5 py-2 rounded-xl text-xs font-semibold border transition-all cursor-pointer ${
className={`flex items-center gap-2 px-3.5 py-2 rounded-xl text-xs font-bold border transition-all cursor-pointer ${
isSidebarOpen
? 'bg-orange-500 text-white border-orange-400 shadow-lg shadow-orange-500/20'
: 'bg-slate-800 text-slate-200 border-slate-700 hover:bg-slate-700'
? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] text-white border-orange-400 shadow-lg shadow-orange-500/20'
: 'bg-[#1E0C36] text-purple-100 border-[#3A1968] hover:bg-[#2A104E]'
}`}
style={{ fontFamily: 'Poppins, sans-serif' }}
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{isSidebarOpen ? <X className="w-4 h-4" /> : <Menu className="w-4 h-4" />}
<span className="hidden sm:inline">

View File

@@ -32,38 +32,37 @@ const BLUEPRINT_LEGEND = [
];
const LOCATION_SPOTS: LocationSpot[] = [
{ name: 'Girls Hostel', x: 16.0, y: 8.5, category: 'Hostel / Mess', desc: 'Residential Quarter for Women', area: 'Top Left' },
{ name: 'Playground', x: 40.0, y: 11.0, category: 'Amenity', desc: 'Main Sports & Athletic Field', area: 'Top Center' },
{ name: 'Canteen', x: 67.0, y: 8.5, category: 'Amenity', desc: 'Food Court & Refreshments', area: 'Top Right' },
{ name: 'Steve Jobs Block', x: 16.0, y: 21.0, category: 'Admin / Facility', desc: 'Auditorium, Computer Labs & Staff Rooms', area: 'West Wing' },
{ name: 'Girls Mess', x: 14.5, y: 31.0, category: 'Hostel / Mess', desc: 'Dedicated Dining Hall for Women', area: 'West Wing' },
{ name: 'MEC Lab', x: 14.0, y: 39.5, category: 'Lab / Academic', desc: 'Mechanical Engineering Laboratory', area: 'West Wing' },
{ name: 'EPL Lab', x: 13.0, y: 47.5, category: 'Lab / Academic', desc: 'Engineering Practices Laboratory', area: 'West Wing' },
{ name: 'Nikola Tesla Block', x: 40.5, y: 36.5, category: 'B Block', desc: 'Main Electrical & Engineering Research Block', area: 'Central Block' },
{ name: 'Astronaut Statue', x: 51.0, y: 23.0, category: 'Amenity', desc: 'Iconic Landmark Monument', area: 'Central Park' },
{ name: 'Campus Centre', x: 67.0, y: 23.0, category: 'Admin / Facility', desc: 'Student Activity & Administrative Center', area: 'North East' },
{ name: 'Hut', x: 70.0, y: 37.0, category: 'Amenity', desc: 'Student Discussion & Rest Area', area: 'Central East' },
{ name: 'Homi Jehangir Bhabha Block', x: 87.0, y: 24.0, category: 'A Block', desc: 'Science & Physics Research Block', area: 'East Wing' },
{ name: 'Boys Mess', x: 87.0, y: 17.5, category: 'Hostel / Mess', desc: 'Ground Floor Dining Hall for Men (Homi Bhabha Block)', area: 'East Wing' },
{ name: 'Staff Mess', x: 87.0, y: 30.5, category: 'Amenity', desc: 'First Floor Dining Hall for Faculty & Staff (Homi Bhabha Block)', area: 'East Wing' },
{ name: 'C Block', x: 87.0, y: 38.0, category: 'C Block', desc: 'Labs, Classrooms & Staff Rooms', area: 'East Wing' },
{ name: 'Alan Turing Block', x: 77.0, y: 53.0, category: 'Lab / Academic', desc: 'Computer Science & AI Labs', area: 'East Wing' },
{ name: 'Arcade', x: 35.0, y: 57.0, category: 'Amenity', desc: 'Student Hub & Stores', area: 'Central South' },
{ name: 'Green Building', x: 23.0, y: 71.0, category: 'Admin / Facility', desc: 'Eco-Friendly Academic Complex & Library', area: 'South West' },
{ name: 'Security Booth', x: 49.0, y: 77.0, category: 'Amenity', desc: 'Main Gate Security Checkpoint', area: 'South Entrance' },
{ name: 'ATM', x: 49.0, y: 86.0, category: 'Amenity', desc: '24/7 Campus Banking ATM', area: 'South Entrance' },
{ name: 'Bike Parking', x: 75.0, y: 67.0, category: 'Parking Area', desc: 'Two-Wheeler Vehicle Parking', area: 'South East' },
{ name: 'Generator Room', x: 76.0, y: 81.0, category: 'Admin / Facility', desc: 'Campus Power & Utility Station', area: 'South East' },
{ name: 'Side Gate', x: 23.0, y: 93.0, category: 'Amenity', desc: 'Secondary Campus Gate', area: 'South West' },
{ name: 'Main Gate', x: 59.0, y: 93.0, category: 'Amenity', desc: 'Primary Campus Entrance', area: 'South Entrance' },
{ name: 'Girls Hostel', x: 29.4, y: 7.3, category: 'Hostel / Mess', desc: 'Residential Quarter for Women', area: 'Top Left' },
{ name: 'Playground', x: 48.5, y: 8.8, category: 'Amenity', desc: 'Main Sports & Athletic Field', area: 'Top Center' },
{ name: 'Canteen', x: 68.3, y: 8.3, category: 'Amenity', desc: 'Food Court & Refreshments', area: 'Top Right' },
{ name: 'Steve Jobs Block', x: 27.2, y: 17.1, category: 'Admin / Facility', desc: 'Auditorium, Computer Labs & Staff Rooms', area: 'West Wing' },
{ name: 'Girls Mess', x: 25.7, y: 25.9, category: 'Hostel / Mess', desc: 'Dedicated Dining Hall for Women', area: 'West Wing' },
{ name: 'MEC Lab', x: 23.5, y: 34.7, category: 'Lab / Academic', desc: 'Mechanical Engineering Laboratory', area: 'West Wing' },
{ name: 'EPL Lab', x: 21.3, y: 43.5, category: 'Lab / Academic', desc: 'Engineering Practices Laboratory', area: 'West Wing' },
{ name: 'Astronaut Statue', x: 51.4, y: 16.1, category: 'Amenity', desc: 'Iconic Landmark Monument', area: 'Central Park' },
{ name: 'Campus Centre', x: 65.3, y: 20.5, category: 'Admin / Facility', desc: 'Student Activity & Administrative Center', area: 'North East' },
{ name: 'Nikola Tesla Block', x: 43.3, y: 34.7, category: 'B Block', desc: 'Main Electrical & Engineering Research Block', area: 'Central Block' },
{ name: 'Hut', x: 66.8, y: 31.3, category: 'Amenity', desc: 'Student Discussion & Rest Area', area: 'Central East' },
{ name: 'Homi Jehangir Bhabha Block', x: 79.3, y: 22.9, category: 'A Block', desc: 'Science & Physics Research Block', area: 'East Wing' },
{ name: 'C Block', x: 78.6, y: 37.6, category: 'C Block', desc: 'Labs, Classrooms & Staff Rooms', area: 'East Wing' },
{ name: 'Alan Turing Block', x: 66.8, y: 50.8, category: 'Lab / Academic', desc: 'Computer Science & AI Labs', area: 'East Wing' },
{ name: 'Arcade', x: 36.7, y: 53.2, category: 'Amenity', desc: 'Student Hub & Stores', area: 'Central South' },
{ name: 'Green Building', x: 27.2, y: 63.5, category: 'Admin / Facility', desc: 'Eco-Friendly Academic Complex & Library', area: 'South West' },
{ name: 'Security Booth', x: 44.8, y: 65.9, category: 'Amenity', desc: 'Main Gate Security Checkpoint', area: 'South Entrance' },
{ name: 'ATM', x: 44.3, y: 73.0, category: 'Amenity', desc: '24/7 Campus Banking ATM', area: 'South Entrance' },
{ name: 'Bike Parking', x: 65.3, y: 61.5, category: 'Parking Area', desc: 'Two-Wheeler Vehicle Parking', area: 'South East' },
{ name: 'Generator Room', x: 63.1, y: 71.8, category: 'Admin / Facility', desc: 'Campus Power & Utility Station', area: 'South East' },
{ name: 'Side Gate', x: 22.8, y: 75.0, category: 'Amenity', desc: 'Secondary Campus Gate', area: 'South West' },
{ name: 'Main Gate', x: 49.2, y: 77.9, category: 'Amenity', desc: 'Primary Campus Entrance', area: 'South Entrance' },
{ name: 'Bus Stop', x: 49.6, y: 92.8, category: 'Amenity', desc: 'Public & College Bus Boarding Point', area: 'Highway' },
];
const CATEGORY_MAP_SPOTS: Record<string, string[]> = {
dept: ['Homi Jehangir Bhabha Block', 'Nikola Tesla Block', 'C Block', 'Steve Jobs Block', 'Alan Turing Block', 'Green Building'],
library: ['Green Building'],
labs: ['MEC Lab', 'EPL Lab', 'Steve Jobs Block', 'Alan Turing Block', 'Nikola Tesla Block', 'C Block'],
hostel: ['Girls Hostel', 'Boys Mess'],
canteen: ['Canteen', 'Girls Mess', 'Boys Mess', 'Staff Mess', 'Arcade'],
hostel: ['Girls Hostel', 'Girls Mess'],
canteen: ['Canteen', 'Girls Mess', 'Arcade'],
auditorium: ['Steve Jobs Block', 'Campus Centre'],
sports: ['Playground'],
};
@@ -148,27 +147,27 @@ export default function Campus() {
{/* 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">
<Link to="/" className="hover:text-[#F97316]">Home</Link>
<div className="flex items-center gap-2 text-xs text-[#9E91B6] mb-3">
<Link to="/" className="hover:text-[#FF6B00]">Home</Link>
<ChevronRight className="w-3 h-3" />
<span className="text-[#F97316]">Campus Map</span>
<span className="text-[#FF6B00] font-semibold">Campus Map</span>
</div>
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
<h1 className="text-3xl md:text-4xl font-extrabold text-[#1A0B2E] mb-2" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
Explore{' '}
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
<span style={{ background: 'linear-gradient(135deg, #FF6B00, #F97316)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
Campus Map
</span>
</h1>
<p className="text-[#475569] text-sm" style={{ fontFamily: 'Inter, sans-serif' }}>
<p className="text-[#4A3E5E] text-sm" style={{ fontFamily: 'Inter, sans-serif' }}>
Navigate campus layout, click location categories or blocks to spot exact places on the blueprint.
</p>
</div>
</div>
<div className="container-custom py-8">
<div className="container-custom pt-8 pb-20 md:pb-28">
<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
{/* Map Viewer Header Bar */}
<div ref={mapRef} className="bg-[#0F172A] rounded-t-3xl p-4 md:p-6 text-white border-b border-slate-800 flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
<div ref={mapRef} className="bg-[#130924] rounded-t-3xl p-4 md:p-6 text-white border-b border-purple-900/40 flex flex-col md:flex-row items-start md:items-center justify-between gap-4">
<div>
<div className="flex items-center gap-2 mb-1">
<span className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full bg-orange-500/20 text-orange-400 text-xs font-semibold border border-orange-500/30">
@@ -178,10 +177,10 @@ export default function Campus() {
<CheckCircle2 className="w-3 h-3" /> Interactive Location Spotter
</span>
</div>
<h2 className="text-xl md:text-2xl font-bold tracking-tight" style={{ fontFamily: 'Playfair Display, serif' }}>
<h2 className="text-xl md:text-2xl font-extrabold tracking-tight" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
Rajalakshmi Institute of Technology
</h2>
<p className="text-xs md:text-sm text-slate-400 flex items-center gap-1.5 mt-0.5">
<p className="text-xs md:text-sm text-purple-300/80 flex items-center gap-1.5 mt-0.5">
<MapPin className="w-3.5 h-3.5 text-orange-400" />
Kuthambakkam, Chennai • Interactive Map Spotting Enabled
</p>
@@ -189,23 +188,23 @@ export default function Campus() {
{/* Toolbar Controls */}
<div className="flex items-center flex-wrap gap-2">
<div className="flex items-center bg-slate-800/80 rounded-xl p-1 border border-slate-700">
<button onClick={handleZoomOut} title="Zoom Out" className="p-2 text-slate-300 hover:text-white hover:bg-slate-700/60 rounded-lg transition-colors cursor-pointer">
<div className="flex items-center bg-[#1E0C36] rounded-xl p-1 border border-[#3A1968]">
<button onClick={handleZoomOut} title="Zoom Out" className="p-2 text-purple-200 hover:text-white hover:bg-purple-900/40 rounded-lg transition-colors cursor-pointer">
<ZoomOut className="w-4 h-4" />
</button>
<button onClick={handleResetZoom} title="Reset Zoom" className="px-2 text-xs font-semibold text-orange-400 hover:text-orange-300 transition-colors cursor-pointer">
{Math.round(zoomLevel * 100)}%
</button>
<button onClick={handleZoomIn} title="Zoom In" className="p-2 text-slate-300 hover:text-white hover:bg-slate-700/60 rounded-lg transition-colors cursor-pointer">
<button onClick={handleZoomIn} title="Zoom In" className="p-2 text-purple-200 hover:text-white hover:bg-purple-900/40 rounded-lg transition-colors cursor-pointer">
<ZoomIn className="w-4 h-4" />
</button>
</div>
<button onClick={() => setIsFullscreen(true)} className="p-2 bg-slate-800/80 hover:bg-slate-700/80 text-slate-200 hover:text-white rounded-xl border border-slate-700 transition-colors cursor-pointer" title="Expand to Fullscreen">
<button onClick={() => setIsFullscreen(true)} className="p-2 bg-[#1E0C36] hover:bg-[#2A104E] text-purple-200 hover:text-white rounded-xl border border-[#3A1968] transition-colors cursor-pointer" title="Expand to Fullscreen">
<Maximize2 className="w-4 h-4" />
</button>
<button onClick={handleDownload} className="flex items-center gap-1.5 px-3 py-2 bg-gradient-to-r from-orange-500 to-amber-500 hover:from-orange-600 hover:to-amber-600 text-white text-xs font-semibold rounded-xl shadow-lg shadow-orange-500/20 transition-all cursor-pointer" title="Download Blueprint Image">
<button onClick={handleDownload} className="flex items-center gap-1.5 px-4 py-2 bg-gradient-to-r from-[#FF6B00] to-[#F97316] hover:from-[#EA580C] hover:to-[#FF6B00] text-white text-xs font-bold rounded-xl shadow-lg shadow-orange-500/20 transition-all cursor-pointer" title="Download Blueprint Image">
<Download className="w-3.5 h-3.5" />
Download
</button>
@@ -269,9 +268,9 @@ export default function Campus() {
</div>
{/* Quick Location Categories Bar */}
<div className="bg-white rounded-3xl border border-[#E5E7EB] p-6 md:p-8 mb-8 shadow-xs">
<div className="bg-white rounded-2xl border border-[#E9E5EE] p-6 md:p-8 mb-8 shadow-[0_4px_20px_-4px_rgba(19,9,36,0.04)]">
<div className="flex items-center justify-between mb-5">
<h3 className="text-xl font-bold text-[#1E293B]" style={{ fontFamily: 'Playfair Display, serif' }}>Quick Location Categories</h3>
<h3 className="text-xl font-bold text-[#1A0B2E]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>Quick Location Categories</h3>
{activeCategory && (
<button onClick={() => setActiveCategory(null)} className="text-xs font-semibold text-orange-500 hover:text-orange-600 flex items-center gap-1 cursor-pointer"><X className="w-3.5 h-3.5" /> Clear Spot Filter</button>
)}
@@ -282,10 +281,10 @@ export default function Campus() {
const isCatActive = activeCategory === loc.id;
return (
<motion.button key={loc.id} whileHover={{ y: -3 }} whileTap={{ scale: 0.95 }} onClick={() => handleCategoryClick(loc.id)} className={`flex flex-col items-center gap-2.5 p-3 rounded-2xl transition-all border cursor-pointer group ${isCatActive ? 'border-orange-500 bg-orange-500 text-white shadow-lg ring-2 ring-orange-400/30' : 'border-transparent hover:border-[#FED7AA] hover:bg-orange-50/50'}`}>
<div className={`w-12 h-12 rounded-full flex items-center justify-center border transition-colors ${isCatActive ? 'bg-white text-orange-500 border-white' : 'bg-[#F8FAFC] group-hover:bg-orange-100 text-[#F97316] border-[#E5E7EB] group-hover:border-orange-300'}`}>
<div className={`w-12 h-12 rounded-full flex items-center justify-center border transition-colors ${isCatActive ? 'bg-white text-orange-500 border-white' : 'bg-[#FAF9FC] group-hover:bg-orange-100 text-[#FF6B00] border-[#E9E5EE] group-hover:border-orange-300'}`}>
{Icon && <Icon className="w-5 h-5" />}
</div>
<span className={`text-[11px] font-semibold text-center leading-tight ${isCatActive ? 'text-white' : 'text-[#475569] group-hover:text-[#F97316]'}`} style={{ fontFamily: 'Inter, sans-serif' }}>{loc.name}</span>
<span className={`text-[11px] font-semibold text-center leading-tight ${isCatActive ? 'text-white' : 'text-[#4A3E5E] group-hover:text-[#FF6B00]'}`} style={{ fontFamily: 'Inter, sans-serif' }}>{loc.name}</span>
</motion.button>
);
})}
@@ -293,16 +292,16 @@ export default function Campus() {
</div>
{/* Blueprint Legend & Color Key */}
<div className="bg-white rounded-3xl border border-[#E5E7EB] p-6 md:p-8 mb-8 shadow-xs">
<div className="bg-white rounded-2xl border border-[#E9E5EE] p-6 md:p-8 mb-8 shadow-[0_4px_20px_-4px_rgba(19,9,36,0.04)]">
<div className="flex items-center gap-2 mb-5">
<Layers className="w-5 h-5 text-[#F97316]" />
<h3 className="text-xl font-bold text-[#1E293B]" style={{ fontFamily: 'Playfair Display, serif' }}>Blueprint Map Legend</h3>
<Layers className="w-5 h-5 text-[#FF6B00]" />
<h3 className="text-xl font-bold text-[#1A0B2E]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>Blueprint Map Legend</h3>
</div>
<div className="flex flex-wrap gap-3">
{BLUEPRINT_LEGEND.map((item) => {
const isActive = activeLegend === item.id;
return (
<button key={item.id} onClick={() => { setActiveLegend(item.id); setActiveCategory(null); setSelectedFacility(null); scrollToMap(); }} className={`flex items-center gap-2.5 px-4 py-2 rounded-full border transition-all cursor-pointer ${isActive ? 'border-[#F97316] bg-orange-500 text-white shadow-md scale-105' : `${item.border} ${item.bg} ${item.text} hover:scale-105`}`}>
<button key={item.id} onClick={() => { setActiveLegend(item.id); setActiveCategory(null); setSelectedFacility(null); scrollToMap(); }} className={`flex items-center gap-2.5 px-4 py-2 rounded-full border transition-all cursor-pointer ${isActive ? 'border-[#FF6B00] bg-orange-500 text-white shadow-md scale-105' : `${item.border} ${item.bg} ${item.text} hover:scale-105`}`}>
<span className="w-3.5 h-3.5 rounded-full shrink-0 shadow-xs border border-black/10" style={{ backgroundColor: isActive ? 'white' : item.color }} />
<span className="text-xs font-semibold" style={{ fontFamily: 'Inter, sans-serif' }}>{item.label}</span>
</button>
@@ -312,28 +311,28 @@ export default function Campus() {
</div>
{/* Blueprint Facilities Index */}
<div className="bg-white rounded-3xl border border-[#E5E7EB] p-6 md:p-8 mb-8 shadow-xs">
<div className="bg-white rounded-2xl border border-[#E9E5EE] p-6 md:p-8 mb-8 shadow-[0_4px_20px_-4px_rgba(19,9,36,0.04)]">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 mb-6">
<div>
<div className="flex items-center gap-2">
<Building2 className="w-5 h-5 text-[#F97316]" />
<h3 className="text-xl font-bold text-[#1E293B]" style={{ fontFamily: 'Playfair Display, serif' }}>Campus Blocks & Facilities Index</h3>
<Building2 className="w-5 h-5 text-[#FF6B00]" />
<h3 className="text-xl font-bold text-[#1A0B2E]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>Campus Blocks & Facilities Index</h3>
</div>
<p className="text-xs text-[#64748B] mt-1" style={{ fontFamily: 'Inter, sans-serif' }}>Click any facility card below to spot its exact location on the campus map image.</p>
<p className="text-xs text-[#9E91B6] mt-1" style={{ fontFamily: 'Inter, sans-serif' }}>Click any facility card below to spot its exact location on the campus map image.</p>
</div>
<span className="text-xs font-semibold text-[#F97316] bg-orange-50 px-3.5 py-1.5 rounded-full border border-orange-200">{filteredFacilities.length} Locations Mapped</span>
<span className="text-xs font-semibold text-[#FF6B00] bg-orange-50 px-3.5 py-1.5 rounded-full border border-orange-200">{filteredFacilities.length} Locations Mapped</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{filteredFacilities.map((facility) => {
const isSelected = selectedFacility?.name === facility.name;
const isHighlighted = highlightedSpotNames.has(facility.name);
return (
<motion.button key={facility.name} whileHover={{ scale: 1.02, y: -2 }} whileTap={{ scale: 0.98 }} onClick={() => { if (isSelected) setSelectedFacility(null); else { setSelectedFacility(facility); setActiveCategory(null); scrollToMap(); } }} className={`text-left p-4 rounded-2xl border transition-all cursor-pointer ${isSelected ? 'border-[#F97316] bg-orange-500 text-white shadow-lg ring-2 ring-orange-400/40' : isHighlighted ? 'border-[#F97316] bg-orange-50/90 shadow-md' : 'border-[#E5E7EB] bg-[#F8FAFC] hover:bg-white hover:border-orange-200 hover:shadow-xs'}`}>
<motion.button key={facility.name} whileHover={{ scale: 1.02, y: -2 }} whileTap={{ scale: 0.98 }} onClick={() => { if (isSelected) setSelectedFacility(null); else { setSelectedFacility(facility); setActiveCategory(null); scrollToMap(); } }} className={`text-left p-4 rounded-2xl border transition-all cursor-pointer ${isSelected ? 'border-[#FF6B00] bg-orange-500 text-white shadow-lg ring-2 ring-orange-400/40' : isHighlighted ? 'border-[#FF6B00] bg-orange-50/90 shadow-md' : 'border-[#E9E5EE] bg-[#FAF9FC] hover:bg-white hover:border-orange-200 hover:shadow-xs'}`}>
<div className="flex items-start justify-between gap-1 mb-1.5">
<span className={`text-sm font-bold ${isSelected ? 'text-white' : 'text-[#1E293B]'}`} style={{ fontFamily: 'Poppins, sans-serif' }}>{facility.name}</span>
<span className={`text-[10px] font-semibold px-2 py-0.5 rounded-full shrink-0 border ${isSelected ? 'bg-white/20 border-white/30 text-white' : 'bg-white border-[#E5E7EB] text-[#475569]'}`}>{facility.area}</span>
<span className={`text-sm font-bold ${isSelected ? 'text-white' : 'text-[#1A0B2E]'}`} style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>{facility.name}</span>
<span className={`text-[10px] font-semibold px-2 py-0.5 rounded-full shrink-0 border ${isSelected ? 'bg-white/20 border-white/30 text-white' : 'bg-white border-[#E9E5EE] text-[#4A3E5E]'}`}>{facility.area}</span>
</div>
<p className={`text-xs line-clamp-1 ${isSelected ? 'text-orange-100' : 'text-[#64748B]'}`} style={{ fontFamily: 'Inter, sans-serif' }}>{facility.desc}</p>
<p className={`text-xs line-clamp-1 ${isSelected ? 'text-orange-100' : 'text-[#9E91B6]'}`} style={{ fontFamily: 'Inter, sans-serif' }}>{facility.desc}</p>
</motion.button>
);
})}

View File

@@ -261,24 +261,24 @@ export default function Community() {
};
return (
<div className="min-h-screen" style={{ backgroundColor: '#FAFBFD' }}>
<div className="min-h-screen bg-[#FAF9FC]">
{/* Header */}
<div className="bg-white border-b border-slate-100 py-12">
<div className="bg-white border-b border-[#E9E5EE] py-12">
<div className="container-custom">
<h1 className="text-3xl md:text-4xl font-bold mb-2 tracking-tight" style={{ fontFamily: 'Playfair Display, serif', color: '#1E293B' }}>
<h1 className="text-3xl md:text-4xl font-extrabold mb-2 tracking-tight" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif', color: '#1A0B2E' }}>
RIT Community
</h1>
<p className="text-slate-500 text-sm" style={{ fontFamily: 'Inter, sans-serif' }}>
<p className="text-[#4A3E5E] text-sm" style={{ fontFamily: 'Inter, sans-serif' }}>
Ask questions and connect with fellow RIT students.
</p>
</div>
</div>
<div className="container-custom py-10">
<div className="container-custom pt-10 pb-20 md:pb-28">
<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 border border-[#E5E7EB] rounded-2xl p-6 mb-8" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
<h3 className="text-base font-semibold text-[#1E293B] mb-3 tracking-tight" style={{ fontFamily: 'Poppins, sans-serif' }}>Ask a Question</h3>
<div className="bg-white border border-[#E9E5EE] rounded-2xl p-6 mb-8 shadow-[0_4px_20px_-4px_rgba(19,9,36,0.04)]">
<h3 className="text-base font-bold text-[#1A0B2E] mb-3 tracking-tight" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>Ask a Question</h3>
{showSuccessBanner && (
<motion.div

View File

@@ -24,7 +24,6 @@ export interface CollabRequestItem {
acceptedCount?: number;
contactInfo?: string;
telegramChatId?: number;
discordUserId?: string;
status: string;
applicationsCount: number;
createdAt: string;
@@ -432,23 +431,23 @@ export default function DevCollab() {
{/* 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">
<Link to="/" className="hover:text-[#F97316]">Home</Link>
<div className="flex items-center gap-2 text-xs text-[#9E91B6] mb-3">
<Link to="/" className="hover:text-[#FF6B00]">Home</Link>
<ChevronRight className="w-3 h-3" />
<span className="text-[#F97316]">Developer Collab Hub</span>
<span className="text-[#FF6B00] font-semibold">Developer Collab Hub</span>
</div>
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div>
<div className="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full bg-orange-50 border border-orange-200 text-[#F97316] text-xs font-semibold mb-3">
<div className="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full bg-orange-50 border border-orange-200 text-[#FF6B00] text-xs font-bold mb-3">
<Code2 className="w-4 h-4" /> RIT Developers Environment
</div>
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
<h1 className="text-3xl md:text-4xl font-extrabold text-[#1A0B2E] mb-2" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
Find Project{' '}
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
<span style={{ background: 'linear-gradient(135deg, #FF6B00, #F97316)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
Collaborators
</span>
</h1>
<p className="text-[#475569] text-sm max-w-2xl" style={{ fontFamily: 'Inter, sans-serif' }}>
<p className="text-[#4A3E5E] text-sm max-w-2xl" style={{ fontFamily: 'Inter, sans-serif' }}>
Post open-source projects, search for co-developers, or manage candidate applications cleanly on the website or via Telegram!
</p>
</div>
@@ -457,8 +456,8 @@ export default function DevCollab() {
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
onClick={() => requireVerifiedStudent(() => setShowPostModal(true))}
className="flex items-center gap-2.5 px-6 py-3.5 rounded-2xl text-white font-semibold text-sm shadow-lg shadow-orange-500/25 shrink-0 cursor-pointer"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', fontFamily: 'Poppins, sans-serif' }}
className="flex items-center gap-2.5 px-6 py-3.5 rounded-xl text-white font-bold text-sm shadow-lg shadow-orange-500/25 shrink-0 cursor-pointer"
style={{ background: 'linear-gradient(135deg, #FF6B00, #F97316)', fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{isVerifiedStudent && <ShieldCheck className="w-4 h-4" />}
<Plus className="w-4.5 h-4.5" />
@@ -467,15 +466,15 @@ export default function DevCollab() {
</div>
{/* Dashboard Tabs Bar */}
<div className="flex items-center gap-3 mt-8 border-b border-slate-200 overflow-x-auto pb-px">
<div className="flex items-center gap-3 mt-8 border-b border-[#E9E5EE] overflow-x-auto pb-px">
<button
onClick={() => setActiveTab('explore')}
className={`px-5 py-3 text-xs font-bold transition-all border-b-2 flex items-center gap-2 cursor-pointer ${
activeTab === 'explore'
? 'border-[#F97316] text-[#F97316]'
: 'border-transparent text-slate-500 hover:text-slate-800'
? 'border-[#FF6B00] text-[#FF6B00]'
: 'border-transparent text-purple-400 hover:text-[#1A0B2E]'
}`}
style={{ fontFamily: 'Poppins, sans-serif' }}
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<Code2 className="w-4 h-4" />
Explore Projects ({requests.length})

View File

@@ -1,10 +1,11 @@
import { useState } from 'react';
import { useState, useMemo } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
Users, Tag, X, Mail, Phone, UserCheck, GraduationCap, Info,
Atom, Wifi, Printer, Zap, Languages, Calculator, HeartHandshake,
Rocket, Camera, Sparkles, Mic, Target, ArrowRight, RotateCcw,
MessageCircle, ExternalLink, CheckCircle2, Compass, Globe, BookOpen, Heart
MessageCircle, ExternalLink, CheckCircle2, Compass, Globe, BookOpen, Heart,
Building2, Search, Cpu, Smartphone, Bot, SlidersHorizontal, Layers, Activity, Brain
} from 'lucide-react';
import SectionTitle from '@/components/SectionTitle/SectionTitle';
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
@@ -17,6 +18,7 @@ const CLUB_CATEGORY_COLORS: Record<string, string> = {
Cultural: '#EC4899',
Social: '#10B981',
Creative: '#F59E0B',
'Center of Excellence': '#8B5CF6',
};
const CLUB_ICON_MAP: Record<string, React.ComponentType<{ className?: string }>> = {
@@ -34,9 +36,14 @@ const CLUB_ICON_MAP: Record<string, React.ComponentType<{ className?: string }>>
BookOpen,
Globe,
Users,
Cpu,
Smartphone,
Bot,
Brain,
Activity,
};
// ─── Inline Brand SVG Icons for Feature 5 ──────────────────────────────────────
// ─── Inline Brand SVG Icons ──────────────────────────────────────────────────────
const InstagramIcon = ({ className = 'w-4 h-4' }: { className?: string }) => (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
<rect width="20" height="20" x="2" y="2" rx="5" ry="5" />
@@ -60,62 +67,79 @@ const YoutubeIcon = ({ className = 'w-4 h-4' }: { className?: string }) => (
</svg>
);
// ─── Quiz Questions Data for Feature 1 (3-Question Matcher) ─────────────────────
const QUIZ_QUESTIONS = [
// ─── Quiz Questions Data (Supporting Clubs, Centers, and Hybrid Matching) ───────
const QUIZ_QUESTIONS_ALL = [
{
id: 'interest',
title: '1. What topics or domains are you most interested in?',
id: 'domain',
title: '1. What technology or activity domain excites you the most?',
options: [
{ label: '⚙️ AI Quests, Chip Design, RobochipX & STEM', clubIds: ['steam', 'wistem', 'techspark'] },
{ label: '🎙️ Radio, Podcasting, Storytelling & RJing', clubIds: ['podx', 'mediastic'] },
{ label: '🤝 Community Service, Village Development & NSS Drives', clubIds: ['nss', 'unnat_bharat', 'rotaract'] },
{ label: '🩸 Blood Donation, Health Camps & Humanitarian Relief', clubIds: ['yrc', 'nss', 'rotaract'] },
{ label: '🎭 Dance, Music, Band & Cultural Arts', clubIds: ['artist_league', 'podx'] },
{ label: '🧮 Fast Calculation, PiDoku, Logic & Mathematics', clubIds: ['infinitus', 'steam'] },
{ label: '✍️ Tamil Literature, Debates & Cultural Heritage', clubIds: ['vaarithi', 'fusion'] },
{ label: '🏛️ Telugu Culture, Festival Celebrations & Heritage', clubIds: ['telugu', 'fusion'] },
{ label: '🌸 Japanese Culture, Anime, Manga & Foreign Languages', clubIds: ['nippon', 'fusion'] },
{ label: '📷 Photo/Video Production & Social Media Content', clubIds: ['mediastic', 'helios', 'podx'] },
{ label: '✨ Women Empowerment, Leadership & Career Vision', clubIds: ['wec', 'wistem'] },
{ label: '🚀 Youth Leadership, Yi Networking & National Summits', clubIds: ['yuva', 'rotaract'] },
{ label: '🤖 AI, Machine Learning, Neural Networks & Data Science', clubIds: ['center_ai', 'center_data_science', 'wistem'] },
{ label: '⚡ Semiconductor VLSI, Microchip Architecture & Hardware', clubIds: ['center_semiconductor', 'steam'] },
{ label: '📱 Apple iOS, SwiftUI & Mobile Ecosystem', clubIds: ['center_apple_tech', 'techspark'] },
{ label: '🕶️ AR/VR, Metaverse, Spatial Computing & 3D Interactive Media', clubIds: ['center_ar_vr', 'mediastic'] },
{ label: '🔒 Cybersecurity, Ethical Hacking & Cryptography', clubIds: ['center_cybersecurity', 'techspark'] },
{ label: '🏥 Healthcare AI, RADAR Medical Diagnostics & Patient Care', clubIds: ['center_radar_healthcare', 'yrc'] },
{ label: '📡 IoT, Smart Automation & Embedded Sensors', clubIds: ['center_iot', 'steam'] },
{ label: '🚗 Electric Vehicles, Battery Systems & Autonomous Tech', clubIds: ['center_ev_energy', 'center_zf_transportation'] },
{ label: '☁️ Cloud Architecture, DevOps & Distributed Systems', clubIds: ['center_cloud_computing', 'techspark'] },
{ label: '🚀 Aerospace, Satellite Tech & Space Robotics', clubIds: ['center_space', 'steam'] },
{ label: '⚛️ Quantum Computing, Quantum Search & Grover Algorithms', clubIds: ['grover_center_quantum', 'infinitus'] },
{ label: '🎙️ Radio, Podcasting, Live Media & Content Creation', clubIds: ['podx', 'mediastic', 'helios'] },
{ label: '🤝 Community Service, Village Adoption & NSS Drives', clubIds: ['nss', 'unnat_bharat', 'rotaract'] },
{ label: '🎭 Cultural Arts, Dance, Music, Band & Drama', clubIds: ['artist_league', 'vaarithi', 'fusion'] },
],
},
{
id: 'skills',
title: '2. What skills do you currently have or want to develop?',
id: 'skill',
title: '2. What practical skill do you want to master at RIT?',
options: [
{ label: '💻 Web3, AI Tools, Circuit Design & Coding Hackathons', clubIds: ['wistem', 'steam', 'techspark'] },
{ label: '🎙️ Public Speaking, Interviewing & Voice Recording', clubIds: ['podx', 'mediastic'] },
{ label: '🤝 Social Work, Environmental Protection & Leadership', clubIds: ['nss', 'unnat_bharat', 'rotaract'] },
{ label: '🩸 Health Awareness, Blood Drive Logistics & Disaster Relief', clubIds: ['yrc', 'nss'] },
{ label: '🕺 Stage Performance, Singing, Dance & Rap', clubIds: ['artist_league'] },
{ label: '📐 Analytical Thinking, Logic Puzzles & Aptitude', clubIds: ['infinitus'] },
{ label: '📝 Essay Writing, Tamil/Telugu/English Oratory & Literature', clubIds: ['vaarithi', 'telugu', 'fusion'] },
{ label: '🎥 Camera Operations, Video Editing & Digital Media', clubIds: ['mediastic', 'helios'] },
{ label: '🌟 Women Leadership, Self Defense & Mentorship', clubIds: ['wec', 'wistem'] },
{ label: '👑 Youth Representation, Project Management & Event Planning', clubIds: ['yuva', 'rotaract'] },
{ label: '📊 Predictive Analytics, Python ML Pipelines & Data Modeling', clubIds: ['center_data_science', 'center_ai', 'center_zf_transportation'] },
{ label: '💻 Microchip IC Layout, Verilog/VHDL & FPGA Prototyping', clubIds: ['center_semiconductor', 'steam'] },
{ label: '🍎 Native iOS SwiftUI Coding & macOS Enterprise Apps', clubIds: ['center_apple_tech', 'techspark'] },
{ label: '🥽 Unity 3D, Spatial Audio & Metaverse Experience Design', clubIds: ['center_ar_vr', 'mediastic'] },
{ label: '🛡️ Network Security Audit, Cyber Threat Defense & Ethical Hacking', clubIds: ['center_cybersecurity'] },
{ label: '🩺 Medical Signal Processing & AI Diagnosis Tools', clubIds: ['center_radar_healthcare', 'center_image_processing'] },
{ label: '🔌 Smart Sensors, ESP32/Arduino, Edge Computing & CAN Bus', clubIds: ['center_iot', 'center_ev_energy'] },
{ label: '🎙️ Voice Recording, Public Speaking & Event Management', clubIds: ['podx', 'yuva', 'artist_league'] },
{ label: '🌱 Sustainable Rural Development & Eco-System Restoration', clubIds: ['unnat_bharat', 'nss', 'rotaract'] },
],
},
{
id: 'goal',
title: '3. What is your main goal for joining a club at RIT?',
title: '3. What is your primary career aspiration during college?',
options: [
{ label: '🤖 Build hackathon projects, chip designs & STEM innovations', clubIds: ['steam', 'wistem', 'techspark'] },
{ label: '🌟 Share inspiring stories & host campus podcasts', clubIds: ['podx', 'mediastic'] },
{ label: '👥 Drive social change & serve rural communities', clubIds: ['nss', 'unnat_bharat'] },
{ label: '🩸 Organize blood donation drives & health protection campaigns', clubIds: ['yrc', 'rotaract'] },
{ label: '🏆 Perform live at cultural fests & stage shows', clubIds: ['artist_league', 'vaarithi'] },
{ label: '📖 Master new languages & explore world/regional cultures', clubIds: ['nippon', 'telugu', 'vaarithi', 'fusion'] },
{ label: '💪 Empower women in STEM & corporate leadership', clubIds: ['wec', 'wistem'] },
{ label: '💼 Connect with Young Indians (Yi), CII & Industry Leaders', clubIds: ['yuva', 'rotaract'] },
{ label: '🔬 Lead innovative research in a specialized Future Tech Center (CoE)', clubIds: ['center_ai', 'center_semiconductor', 'grover_center_quantum', 'center_apple_tech', 'center_space'] },
{ label: '🏆 Win technical hackathons & build product prototypes', clubIds: ['steam', 'wistem', 'techspark', 'center_data_science'] },
{ label: '🌟 Build a strong personal brand, public speaking & media portfolio', clubIds: ['podx', 'mediastic', 'helios'] },
{ label: '🤝 Drive impactful social initiatives & community leadership', clubIds: ['nss', 'rotaract', 'yuva', 'wec'] },
{ label: '💼 Connect with industry leaders, CII, and top startup networks', clubIds: ['yuva', 'wec', 'center_cloud_computing'] },
],
},
];
export default function Events() {
const [selectedClub, setSelectedClub] = useState<Club | null>(null);
const [searchQuery, setSearchQuery] = useState('');
const [selectedCategory, setSelectedCategory] = useState<string>('All');
const [selectedType, setSelectedType] = useState<'All' | 'Club' | 'Center'>('All');
// ─── Interactive Like / Favorite System State ─────────────────────────────
// Compute counts dynamically
const { totalCount, centersCount, clubsCount } = useMemo(() => {
let centers = 0;
let clubs = 0;
CLUBS_DATA.forEach((item) => {
const isCenter =
item.type === 'Center' ||
item.category === 'Center of Excellence' ||
item.id.startsWith('center_') ||
item.id.includes('grover_center');
if (isCenter) centers++;
else clubs++;
});
return { totalCount: CLUBS_DATA.length, centersCount: centers, clubsCount: clubs };
}, []);
// ─── Interactive Like System ─────────────────────────────────────────────
const [likedClubs, setLikedClubs] = useState<Set<string>>(() => {
try {
localStorage.removeItem('rit_freshers_liked_clubs');
@@ -152,8 +176,9 @@ export default function Events() {
} catch {}
};
// ─── Club Matcher Quiz State (Feature 1) ─────────────────────────────────
// ─── Quiz Matcher State ──────────────────────────────────────────────────
const [isQuizOpen, setIsQuizOpen] = useState(false);
const [matcherTarget, setMatcherTarget] = useState<'All' | 'Club' | 'Center'>('All');
const [quizStep, setQuizStep] = useState(0);
const [selectedAnswers, setSelectedAnswers] = useState<number[]>([]);
const [quizResults, setQuizResults] = useState<{ club: Club; score: number }[] | null>(null);
@@ -163,7 +188,7 @@ export default function Events() {
updated[quizStep] = optionIdx;
setSelectedAnswers(updated);
if (quizStep < QUIZ_QUESTIONS.length - 1) {
if (quizStep < QUIZ_QUESTIONS_ALL.length - 1) {
setQuizStep(quizStep + 1);
} else {
calculateQuizResults(updated);
@@ -175,16 +200,31 @@ export default function Events() {
CLUBS_DATA.forEach((c) => (scoreMap[c.id] = 0));
answers.forEach((ansIdx, qIdx) => {
const option = QUIZ_QUESTIONS[qIdx].options[ansIdx];
const option = QUIZ_QUESTIONS_ALL[qIdx].options[ansIdx];
if (option && option.clubIds) {
option.clubIds.forEach((clubId, idx) => {
scoreMap[clubId] = (scoreMap[clubId] || 0) + (3 - idx);
scoreMap[clubId] = (scoreMap[clubId] || 0) + (5 - idx);
});
}
});
const ranked = CLUBS_DATA.map((club) => ({
const pool = CLUBS_DATA.filter((c) => {
const isCenter =
c.type === 'Center' ||
c.category === 'Center of Excellence' ||
c.id.startsWith('center_') ||
c.id.includes('grover_center');
if (matcherTarget === 'Club') return !isCenter;
if (matcherTarget === 'Center') return isCenter;
return true;
});
const ranked = pool
.map((club) => ({
club,
score: scoreMap[club.id] || 0,
})).sort((a, b) => b.score - a.score);
}))
.sort((a, b) => b.score - a.score);
setQuizResults(ranked.slice(0, 3));
};
@@ -195,87 +235,240 @@ export default function Events() {
setQuizResults(null);
};
// ─── Filter Selection Handlers ───────────────────────────────────────────
const handleTypeSelect = (type: 'All' | 'Club' | 'Center') => {
setSelectedType(type);
setSelectedCategory('All');
};
const handleCategorySelect = (cat: string) => {
setSelectedCategory(cat);
if (cat === 'Center of Excellence') {
setSelectedType('Center');
} else if (cat !== 'All') {
setSelectedType('Club');
} else {
setSelectedType('All');
}
};
// ─── Robust Filter Logic ──────────────────────────────────────────────────
const filteredData = useMemo(() => {
return CLUBS_DATA.filter((item) => {
const isCenter =
item.type === 'Center' ||
item.category === 'Center of Excellence' ||
item.id.startsWith('center_') ||
item.id.includes('grover_center');
// 1. Type Filter check
if (selectedType === 'Club' && isCenter) return false;
if (selectedType === 'Center' && !isCenter) return false;
// 2. Category Filter check
if (selectedCategory !== 'All') {
if (selectedCategory === 'Center of Excellence' && !isCenter) return false;
if (selectedCategory !== 'Center of Excellence' && item.category !== selectedCategory) return false;
}
// 3. Search Query check
const q = searchQuery.toLowerCase().trim();
if (q) {
const matchesSearch =
item.name.toLowerCase().includes(q) ||
item.description.toLowerCase().includes(q) ||
(item.details && item.details.toLowerCase().includes(q)) ||
(item.presidentName && item.presidentName.toLowerCase().includes(q)) ||
(item.coordinatorName && item.coordinatorName.toLowerCase().includes(q));
if (!matchesSearch) return false;
}
return true;
});
}, [searchQuery, selectedCategory, selectedType]);
const categoriesList = ['All', 'Center of Excellence', 'Technical', 'Social', 'Creative'];
return (
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
<div className="min-h-screen bg-[#FAF9FC]">
{/* Header */}
<div className="bg-white border-b border-[#E5E7EB] py-10">
<div className="bg-white border-b border-[#E9E5EE] py-10">
<div className="container-custom">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-orange-50 border border-orange-200 text-[#F97316] text-xs font-bold uppercase tracking-wider mb-3">
<Building2 className="w-3.5 h-3.5" />
<span>RIT Official Directory</span>
</div>
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
Student{' '}
Student Clubs &{' '}
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
Clubs
Centers of Excellence
</span>
</h1>
<p className="text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
Explore official RIT student clubs & societies, leadership details, and community links.
<p className="text-[#475569] max-w-3xl text-sm md:text-base" style={{ fontFamily: 'Inter, sans-serif' }}>
Explore official RIT student clubs, societies, and 15 Future Tech Centers of Excellence (CoEs), including faculty coordinators, research domains, and contact channels.
</p>
</div>
</div>
<div className="container-custom pt-10 pb-20 md:pb-28">
<div className="container-custom pt-8 pb-20 md:pb-28">
{/* ─── Feature 1: "Find My Ideal Club" Banner ────────────────────────── */}
<AnimatedContainer className="mb-14">
{/* ─── Interactive Matcher Banner ─────────────────────────────────── */}
<AnimatedContainer className="mb-10">
<div
className="rounded-3xl p-6 md:p-8 text-white relative overflow-hidden border border-orange-400/30 flex flex-col md:flex-row items-center justify-between gap-6"
style={{ background: 'linear-gradient(135deg, #1E293B, #0F172A)' }}
className="rounded-2xl p-6 md:p-8 text-white relative overflow-hidden border border-[#3A1968] flex flex-col md:flex-row items-center justify-between gap-6"
style={{ background: 'linear-gradient(135deg, #130924, #1E0C36)' }}
>
{/* Background Glow */}
<div
className="absolute top-0 right-0 w-80 h-80 rounded-full opacity-20 pointer-events-none"
style={{ background: 'radial-gradient(circle, #F97316, transparent)' }}
style={{ background: 'radial-gradient(circle, #FF6B00, transparent)' }}
/>
<div className="relative z-10 max-w-xl">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-orange-500/20 text-orange-400 text-xs font-semibold mb-3 border border-orange-500/30">
<Target className="w-3.5 h-3.5" />
<span>Interactive Club Matcher</span>
<span>AI-Powered Matcher Quiz</span>
</div>
<h2 className="text-2xl md:text-3xl font-bold text-white mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
Not sure which club to join?
Find Your Ideal Club or Future Tech Center!
</h2>
<p className="text-slate-300 text-sm leading-relaxed" style={{ fontFamily: 'Inter, sans-serif' }}>
Take our 3-step AI-powered Club Matcher quiz to get instant recommendations tailored to your interests, skills, and goals!
Take our 3-step Matcher quiz to discover which of the 18 Student Clubs or 15 Future Tech Centers best match your skills, technology interests, and career goals!
</p>
</div>
<div className="flex flex-col sm:flex-row items-center gap-3 shrink-0 relative z-10 w-full sm:w-auto">
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.98 }}
onClick={() => { resetQuiz(); setIsQuizOpen(true); }}
className="relative z-10 px-6 py-3.5 rounded-2xl text-white font-semibold text-sm flex items-center gap-2.5 shadow-lg shrink-0 cursor-pointer"
whileHover={{ scale: 1.04 }}
whileTap={{ scale: 0.97 }}
onClick={() => { setMatcherTarget('Center'); resetQuiz(); setIsQuizOpen(true); }}
className="w-full sm:w-auto px-5 py-3 rounded-2xl bg-purple-600 hover:bg-purple-700 text-white font-semibold text-xs flex items-center justify-center gap-2 shadow-md cursor-pointer border border-purple-400/40"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
<Cpu className="w-4 h-4" />
Match Centers (CoEs)
</motion.button>
<motion.button
whileHover={{ scale: 1.04 }}
whileTap={{ scale: 0.97 }}
onClick={() => { setMatcherTarget('All'); resetQuiz(); setIsQuizOpen(true); }}
className="w-full sm:w-auto px-5 py-3 rounded-2xl text-white font-semibold text-xs flex items-center justify-center gap-2 shadow-md cursor-pointer"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', fontFamily: 'Poppins, sans-serif' }}
>
<Sparkles className="w-4 h-4" />
Find My Ideal Club
Match All Directory
<ArrowRight className="w-4 h-4" />
</motion.button>
</div>
</div>
</AnimatedContainer>
{/* Clubs Directory Section */}
<SectionTitle tag="Official Directory" title="Student" highlight="Clubs" subtitle="Explore official RIT clubs & societies. Click any club to view full details, leadership, and community social links." />
{/* ─── Search & Category Filters Bar ───────────────────────────────── */}
<div className="mb-10 space-y-5">
<SectionTitle tag="Official Directory" title="Clubs &" highlight="Centers" subtitle="Browse or search 18 official RIT clubs and 15 Future Tech Centers of Excellence. Click any card for faculty lead and details." />
{(() => {
const fullGridCount = Math.floor(CLUBS_DATA.length / 3) * 3;
const mainGridClubs = CLUBS_DATA.slice(0, fullGridCount);
const remainingClubs = CLUBS_DATA.slice(fullGridCount);
{/* Search & Type Toggle Controls */}
<div className="flex flex-col md:flex-row items-center justify-between gap-4 bg-white p-4 rounded-2xl border border-slate-200 shadow-xs">
{/* Search Input */}
<div className="relative w-full md:w-80">
<Search className="w-4 h-4 text-slate-400 absolute left-3.5 top-1/2 -translate-y-1/2" />
<input
type="text"
placeholder="Search name, faculty lead, domain..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2.5 rounded-xl border border-slate-200 text-xs font-medium text-slate-800 focus:outline-none focus:border-[#F97316] transition-colors"
style={{ fontFamily: 'Inter, sans-serif' }}
/>
{searchQuery && (
<button onClick={() => setSearchQuery('')} className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600">
<X className="w-3.5 h-3.5" />
</button>
)}
</div>
const renderClubCard = (club: Club) => {
{/* Type Pills Toggle (All / Clubs / Centers) */}
<div className="flex items-center gap-1.5 bg-slate-100 p-1 rounded-xl w-full md:w-auto overflow-x-auto">
{[
{ label: `All (${totalCount})`, value: 'All' },
{ label: `Clubs (${clubsCount})`, value: 'Club' },
{ label: `Centers of Excellence (${centersCount})`, value: 'Center' },
].map((t) => (
<button
key={t.value}
onClick={() => handleTypeSelect(t.value as any)}
className={`px-4 py-2 rounded-lg text-xs font-bold transition-all whitespace-nowrap cursor-pointer ${
selectedType === t.value
? 'bg-white text-[#1E293B] shadow-2xs border border-slate-200'
: 'text-slate-600 hover:text-slate-900'
}`}
style={{ fontFamily: 'Poppins, sans-serif' }}
>
{t.label}
</button>
))}
</div>
</div>
{/* Category Filter Tabs */}
<div className="flex items-center gap-2 overflow-x-auto pb-2 scrollbar-none">
<span className="text-xs font-semibold text-slate-400 flex items-center gap-1 shrink-0 pr-1">
<SlidersHorizontal className="w-3.5 h-3.5" /> Filter:
</span>
{categoriesList.map((cat) => (
<button
key={cat}
onClick={() => handleCategorySelect(cat)}
className={`px-3.5 py-1.5 rounded-xl text-xs font-semibold whitespace-nowrap transition-all cursor-pointer border ${
selectedCategory === cat
? 'bg-[#1E293B] text-white border-[#1E293B] shadow-xs'
: 'bg-white text-slate-600 border-slate-200 hover:border-slate-300 hover:bg-slate-50'
}`}
style={{ fontFamily: 'Poppins, sans-serif' }}
>
{cat}
</button>
))}
</div>
</div>
{/* ─── Directory Grid ──────────────────────────────────────────────── */}
{filteredData.length === 0 ? (
<div className="bg-white rounded-3xl border border-slate-200 p-12 text-center my-8">
<Layers className="w-10 h-10 text-slate-300 mx-auto mb-3" />
<h3 className="text-lg font-bold text-slate-700">No matching items found</h3>
<p className="text-xs text-slate-400 mt-1 mb-4">Try adjusting your search terms or filter selections.</p>
<button
onClick={() => { setSearchQuery(''); setSelectedCategory('All'); setSelectedType('All'); }}
className="px-4 py-2 bg-orange-500 text-white rounded-xl text-xs font-bold hover:bg-orange-600 transition-colors cursor-pointer"
>
Reset Filters
</button>
</div>
) : (
<StaggerContainer className="pb-8">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-7">
{filteredData.map((club) => {
const IconComponent = (club.icon && CLUB_ICON_MAP[club.icon]) || Atom;
const categoryColor = CLUB_CATEGORY_COLORS[club.category] || '#F97316';
const isCenter =
club.type === 'Center' ||
club.category === 'Center of Excellence' ||
club.id.startsWith('center_') ||
club.id.includes('grover_center');
const categoryColor = CLUB_CATEGORY_COLORS[club.category] || (isCenter ? '#8B5CF6' : '#F97316');
const isLiked = likedClubs.has(club.id);
const likesCount = likesMap[club.id] || 0;
return (
<StaggerItem key={club.id} className="w-full h-full">
<motion.div
whileHover={{ y: -4 }}
onClick={() => setSelectedClub(club)}
className="bg-white rounded-2xl border border-[#E5E7EB] p-5 cursor-pointer hover:border-[#F97316] transition-all flex flex-col justify-between h-full group"
className="bg-white rounded-2xl border border-[#E5E7EB] p-5 cursor-pointer hover:border-[#F97316] transition-all flex flex-col justify-between h-full group relative overflow-hidden"
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
>
<div>
{/* Top Row: Tech Icon / Club Logo & Interactive Heart Like Button */}
{/* Top Row: Icon/Logo + Badge + Heart Like Button */}
<div className="flex items-center justify-between mb-3">
{club.logoUrl ? (
<div className="w-12 h-12 rounded-full border border-slate-100 p-0.5 shadow-sm bg-white overflow-hidden shrink-0 transition-transform group-hover:scale-105">
@@ -285,15 +478,28 @@ export default function Events() {
<div
className="w-11 h-11 rounded-full flex items-center justify-center text-white shadow-sm transition-transform group-hover:scale-105"
style={{
background: `linear-gradient(135deg, ${categoryColor}, ${categoryColor}DD)`,
background: isCenter
? 'linear-gradient(135deg, #8B5CF6, #6366F1)'
: `linear-gradient(135deg, ${categoryColor}, ${categoryColor}DD)`,
}}
>
<IconComponent className="w-5 h-5 text-white" />
</div>
)}
{/* Like Button */}
<div className="flex items-center gap-2">
{/* Type Badge */}
{isCenter ? (
<span className="px-2 py-0.5 rounded-full text-[10px] font-bold bg-purple-100 text-purple-700 border border-purple-200">
CENTER OF EXCELLENCE
</span>
) : (
<span className="px-2 py-0.5 rounded-full text-[10px] font-bold bg-blue-50 text-blue-600 border border-blue-100">
CLUB
</span>
)}
{/* Like Button */}
<motion.button
whileHover={{ scale: 1.08 }}
whileTap={{ scale: 0.85 }}
@@ -313,58 +519,39 @@ export default function Events() {
</div>
</div>
{/* Club Title & Description */}
<h3 className="font-semibold text-[#1E293B] mb-1 group-hover:text-[#F97316] transition-colors" style={{ fontFamily: 'Poppins, sans-serif' }}>
{/* Title & Description */}
<h3 className="font-semibold text-[#1E293B] mb-1 group-hover:text-[#F97316] transition-colors line-clamp-2 text-sm md:text-base" style={{ fontFamily: 'Poppins, sans-serif' }}>
{club.name}
</h3>
<p className="text-xs text-[#64748B] mb-3 line-clamp-2" style={{ fontFamily: 'Inter, sans-serif' }}>{club.description}</p>
<p className="text-xs text-[#64748B] mb-3 line-clamp-2" style={{ fontFamily: 'Inter, sans-serif' }}>
{club.description}
</p>
</div>
{/* Bottom Row: Category Tag & View Details Button */}
<div className="flex items-center justify-between mt-auto pt-2 border-t border-slate-100">
<span className="flex items-center gap-1 text-xs font-medium" style={{ color: categoryColor }}>
<Tag className="w-3 h-3" />
{club.category}
{/* Bottom Row: Category/Coordinator Tag & View Details Button */}
<div className="flex items-center justify-between mt-auto pt-3 border-t border-slate-100">
<span className="flex items-center gap-1 text-xs font-medium truncate max-w-[60%]" style={{ color: categoryColor }}>
{isCenter ? <GraduationCap className="w-3.5 h-3.5 shrink-0 text-purple-600" /> : <Tag className="w-3 h-3 shrink-0" />}
<span className="truncate">{isCenter && club.coordinatorName ? club.coordinatorName : club.category}</span>
</span>
<motion.button
whileHover={{ scale: 1.04 }}
className="px-3.5 py-1.5 rounded-lg text-xs font-semibold text-white flex items-center gap-1 cursor-pointer"
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
className="px-3.5 py-1.5 rounded-lg text-xs font-semibold text-white flex items-center gap-1 cursor-pointer shrink-0"
style={{ fontFamily: 'Poppins, sans-serif', background: isCenter ? 'linear-gradient(135deg, #8B5CF6, #6366F1)' : 'linear-gradient(135deg, #F97316, #FB923C)' }}
>
View Details
</motion.button>
</div>
</motion.div>
</StaggerItem>
);
};
return (
<StaggerContainer className="pb-8 space-y-6 md:space-y-7">
{/* 3-Column Grid for Full Rows */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-7">
{mainGridClubs.map((club) => (
<StaggerItem key={club.id} className="w-full h-full">
{renderClubCard(club)}
</StaggerItem>
))}
})}
</div>
{/* Centered Flex Row for Remaining Clubs (WiSTEM & STEAM) */}
{remainingClubs.length > 0 && (
<div className="flex flex-wrap justify-center gap-6 md:gap-7">
{remainingClubs.map((club) => (
<StaggerItem key={club.id} className="w-full sm:w-[calc(50%-0.875rem)] lg:w-[calc(33.333%-1.167rem)]">
{renderClubCard(club)}
</StaggerItem>
))}
</div>
)}
</StaggerContainer>
);
})()}
)}
</div>
{/* ─── Club Matcher Quiz Modal (Feature 1) ─────────────────────────────── */}
{/* ─── Matcher Quiz Modal ────────────────────────────────────────────── */}
<AnimatePresence>
{isQuizOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm">
@@ -384,37 +571,43 @@ export default function Events() {
{!quizResults ? (
<div>
{/* Quiz Target Selector Header */}
<div className="flex items-center justify-between gap-2 mb-4">
<div className="flex items-center gap-2 text-[#F97316]">
<Compass className="w-5 h-5" />
<span className="text-xs font-bold uppercase tracking-wider">
{matcherTarget === 'Center' ? 'Future Tech Center Matcher' : matcherTarget === 'Club' ? 'Student Club Matcher' : 'Interactive Directory Matcher'}
</span>
</div>
</div>
{/* Quiz Progress */}
<div className="flex items-center justify-between text-xs text-slate-500 mb-2 font-medium">
<span>Question {quizStep + 1} of {QUIZ_QUESTIONS.length}</span>
<span>Step {quizStep + 1} / {QUIZ_QUESTIONS.length}</span>
<span>Question {quizStep + 1} of {QUIZ_QUESTIONS_ALL.length}</span>
<span>Step {quizStep + 1} / {QUIZ_QUESTIONS_ALL.length}</span>
</div>
<div className="w-full h-1.5 bg-slate-100 rounded-full mb-6 overflow-hidden">
<div
className="h-full bg-[#F97316] transition-all duration-300 rounded-full"
style={{ width: `${((quizStep + 1) / QUIZ_QUESTIONS.length) * 100}%` }}
style={{ width: `${((quizStep + 1) / QUIZ_QUESTIONS_ALL.length) * 100}%` }}
/>
</div>
{/* Question Header */}
<div className="flex items-center gap-2 mb-2 text-[#F97316]">
<Compass className="w-5 h-5" />
<span className="text-xs font-bold uppercase tracking-wider">Club Matcher Quiz</span>
</div>
<h3 className="text-xl font-bold text-[#1E293B] mb-5" style={{ fontFamily: 'Playfair Display, serif' }}>
{QUIZ_QUESTIONS[quizStep].title}
{QUIZ_QUESTIONS_ALL[quizStep].title}
</h3>
{/* Options List */}
<div className="space-y-3 mb-6">
{QUIZ_QUESTIONS[quizStep].options.map((opt, idx) => (
<div className="space-y-2.5 mb-6 max-h-[50vh] overflow-y-auto pr-1">
{QUIZ_QUESTIONS_ALL[quizStep].options.map((opt, idx) => (
<button
key={idx}
onClick={() => handleSelectOption(idx)}
className="w-full text-left p-4 rounded-2xl border border-slate-200 hover:border-[#F97316] hover:bg-orange-50/50 transition-all flex items-center justify-between text-sm font-medium text-slate-700 cursor-pointer group"
className="w-full text-left p-3.5 rounded-2xl border border-slate-200 hover:border-[#F97316] hover:bg-orange-50/50 transition-all flex items-center justify-between text-xs sm:text-sm font-medium text-slate-700 cursor-pointer group"
>
<span>{opt.label}</span>
<ArrowRight className="w-4 h-4 text-slate-300 group-hover:text-[#F97316] group-hover:translate-x-1 transition-all" />
<ArrowRight className="w-4 h-4 text-slate-300 group-hover:text-[#F97316] group-hover:translate-x-1 transition-all shrink-0 ml-2" />
</button>
))}
</div>
@@ -436,17 +629,22 @@ export default function Events() {
<CheckCircle2 className="w-8 h-8" />
</div>
<h3 className="text-2xl font-bold text-[#1E293B]" style={{ fontFamily: 'Playfair Display, serif' }}>
Your Ideal Club Matches!
Your Recommended Matches!
</h3>
<p className="text-xs text-slate-500 mt-1">Based on your interests & goals, here are your top recommended RIT clubs:</p>
<p className="text-xs text-slate-500 mt-1">Based on your selections, here are your top recommended RIT entities:</p>
</div>
{/* Top 3 Matches */}
<div className="space-y-3 mb-6">
{quizResults.map((item, i) => {
const IconComponent = (item.club.icon && CLUB_ICON_MAP[item.club.icon]) || Atom;
const catColor = CLUB_CATEGORY_COLORS[item.club.category] || '#F97316';
const matchPercent = i === 0 ? '98%' : i === 1 ? '91%' : '84%';
const isCenter =
item.club.type === 'Center' ||
item.club.category === 'Center of Excellence' ||
item.club.id.startsWith('center_') ||
item.club.id.includes('grover_center');
const catColor = CLUB_CATEGORY_COLORS[item.club.category] || (isCenter ? '#8B5CF6' : '#F97316');
const matchPercent = i === 0 ? '98%' : i === 1 ? '92%' : '85%';
return (
<div
@@ -455,9 +653,9 @@ export default function Events() {
setIsQuizOpen(false);
setSelectedClub(item.club);
}}
className="p-4 rounded-2xl border border-slate-200 hover:border-[#F97316] bg-slate-50 hover:bg-white cursor-pointer transition-all flex items-center justify-between"
className="p-4 rounded-2xl border border-slate-200 hover:border-[#F97316] bg-slate-50 hover:bg-white cursor-pointer transition-all flex items-center justify-between gap-3"
>
<div className="flex items-center gap-3">
<div className="flex items-center gap-3 min-w-0">
{item.club.logoUrl ? (
<div className="w-10 h-10 rounded-full border border-slate-100 p-0.5 shadow-sm bg-white overflow-hidden shrink-0">
<img src={item.club.logoUrl} alt={item.club.name} className="w-full h-full object-cover rounded-full" />
@@ -465,21 +663,23 @@ export default function Events() {
) : (
<div
className="w-10 h-10 rounded-full flex items-center justify-center text-white shrink-0"
style={{ background: `linear-gradient(135deg, ${catColor}, ${catColor}DD)` }}
style={{ background: isCenter ? 'linear-gradient(135deg, #8B5CF6, #6366F1)' : `linear-gradient(135deg, ${catColor}, ${catColor}DD)` }}
>
<IconComponent className="w-5 h-5 text-white" />
</div>
)}
<div>
<div className="min-w-0">
<div className="flex items-center gap-2">
<span className="font-bold text-sm text-[#1E293B]">{item.club.name}</span>
{i === 0 && <span className="px-2 py-0.5 rounded-full text-[10px] font-bold bg-amber-100 text-amber-700">Top Match 🏆</span>}
<span className="font-bold text-xs sm:text-sm text-[#1E293B] truncate">{item.club.name}</span>
{i === 0 && <span className="px-2 py-0.5 rounded-full text-[9px] font-bold bg-amber-100 text-amber-700 shrink-0">Top Match 🏆</span>}
</div>
<span className="text-xs text-slate-500">{item.club.category} Club</span>
<span className="text-[11px] text-slate-500 truncate block">
{isCenter ? (item.club.coordinatorName ? `CoE Lead: ${item.club.coordinatorName}` : 'Center of Excellence') : `${item.club.category} Club`}
</span>
</div>
</div>
<span className="px-3 py-1 rounded-full text-xs font-bold bg-emerald-50 text-emerald-600 border border-emerald-200 shrink-0">
{matchPercent} Match
<span className="px-2.5 py-1 rounded-full text-xs font-bold bg-emerald-50 text-emerald-600 border border-emerald-200 shrink-0">
{matchPercent}
</span>
</div>
);
@@ -498,7 +698,7 @@ export default function Events() {
onClick={() => setIsQuizOpen(false)}
className="flex-1 py-3 rounded-xl text-white font-semibold text-xs bg-[#F97316] hover:bg-[#EA580C] transition-all cursor-pointer"
>
Explore All Clubs
Explore Directory
</button>
</div>
</div>
@@ -508,7 +708,7 @@ export default function Events() {
)}
</AnimatePresence>
{/* ─── Club Details Modal (Featuring Feature 5: Social Link Hub) ───────── */}
{/* ─── Detailed View Modal ───────────────────────────────────────────── */}
<AnimatePresence>
{selectedClub && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
@@ -530,7 +730,12 @@ export default function Events() {
{/* Modal Header */}
{(() => {
const IconComponent = (selectedClub.icon && CLUB_ICON_MAP[selectedClub.icon]) || Atom;
const categoryColor = CLUB_CATEGORY_COLORS[selectedClub.category] || '#F97316';
const isCenter =
selectedClub.type === 'Center' ||
selectedClub.category === 'Center of Excellence' ||
selectedClub.id.startsWith('center_') ||
selectedClub.id.includes('grover_center');
const categoryColor = CLUB_CATEGORY_COLORS[selectedClub.category] || (isCenter ? '#8B5CF6' : '#F97316');
const isLiked = likedClubs.has(selectedClub.id);
const likesCount = likesMap[selectedClub.id] || 0;
@@ -544,25 +749,32 @@ export default function Events() {
) : (
<div
className="w-14 h-14 rounded-full flex items-center justify-center text-white font-bold text-2xl shadow-sm shrink-0"
style={{ background: `linear-gradient(135deg, ${categoryColor}, ${categoryColor}DD)` }}
style={{ background: isCenter ? 'linear-gradient(135deg, #8B5CF6, #6366F1)' : `linear-gradient(135deg, ${categoryColor}, ${categoryColor}DD)` }}
>
<IconComponent className="w-7 h-7 text-white" />
</div>
)}
<div>
<div className="flex items-center gap-2 mb-1">
<span
className="px-2.5 py-0.5 rounded-full text-xs font-semibold"
style={{ backgroundColor: `${categoryColor}15`, color: categoryColor, fontFamily: 'Poppins, sans-serif' }}
>
{selectedClub.category}
</span>
<h2 className="text-2xl font-bold text-[#1E293B] mt-1" style={{ fontFamily: 'Playfair Display, serif' }}>
{isCenter && (
<span className="px-2 py-0.5 rounded-full text-[10px] font-bold bg-purple-100 text-purple-700 border border-purple-200">
CENTER OF EXCELLENCE
</span>
)}
</div>
<h2 className="text-2xl font-bold text-[#1E293B]" style={{ fontFamily: 'Playfair Display, serif' }}>
{selectedClub.name}
</h2>
</div>
</div>
{/* Interactive Like Button in Modal */}
{/* Interactive Like Button */}
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.92 }}
@@ -574,37 +786,56 @@ export default function Events() {
}`}
>
<Heart className={`w-4 h-4 ${isLiked ? 'fill-white' : 'text-rose-500'}`} />
<span>{isLiked ? 'Liked' : 'Like Club'} ({likesCount})</span>
<span>{isLiked ? 'Liked' : 'Like'} ({likesCount})</span>
</motion.button>
</div>
);
})()}
{/* Club Detailed Description */}
{/* Detailed Description */}
<div className="mb-5 bg-gradient-to-r from-orange-50/70 via-amber-50/40 to-slate-50 border border-orange-200/80 rounded-2xl p-4.5">
<div className="flex items-center gap-2 text-xs font-bold text-[#F97316] uppercase tracking-wider mb-2">
<Info className="w-4 h-4 text-[#F97316]" />
<span>About the Club</span>
<span>About {selectedClub.type === 'Center' || selectedClub.category === 'Center of Excellence' ? 'the Center of Excellence' : 'the Club'}</span>
</div>
<p className="text-slate-700 text-sm leading-relaxed" style={{ fontFamily: 'Inter, sans-serif' }}>
{selectedClub.details || selectedClub.description}
</p>
</div>
{/* Leadership & Contact Information - Icon Based Color Cards */}
{/* Leadership & Contact Cards */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3.5 mb-6">
{/* President Card - Indigo Theme */}
{/* Faculty Coordinator Card */}
{selectedClub.coordinatorName && (
<div className="bg-amber-50/80 border border-amber-100 hover:border-amber-300 transition-colors rounded-2xl p-3.5 flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-amber-500 to-amber-600 text-white flex items-center justify-center shadow-xs shadow-amber-200 shrink-0">
<GraduationCap className="w-5 h-5 text-white" />
</div>
<div>
<span className="text-[11px] text-amber-600 font-semibold block">
{selectedClub.type === 'Center' || selectedClub.category === 'Center of Excellence' ? 'Faculty Co-ordinator' : 'Faculty Coordinator'}
</span>
<span className="text-sm font-bold text-amber-950">{selectedClub.coordinatorName}</span>
</div>
</div>
)}
{/* President / Student Lead Card */}
{selectedClub.presidentName && (
<div className="bg-indigo-50/80 border border-indigo-100 hover:border-indigo-300 transition-colors rounded-2xl p-3.5 flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-500 to-indigo-600 text-white flex items-center justify-center shadow-xs shadow-indigo-200 shrink-0">
<UserCheck className="w-5 h-5 text-white" />
</div>
<div>
<span className="text-[11px] text-indigo-500 font-semibold block">President / Student Lead</span>
<span className="text-sm font-bold text-indigo-950">{selectedClub.presidentName || 'Student President'}</span>
<span className="text-[11px] text-indigo-500 font-semibold block">
{selectedClub.type === 'Center' || selectedClub.category === 'Center of Excellence' ? 'Research / Student Lead' : 'President / Student Lead'}
</span>
<span className="text-sm font-bold text-indigo-950">{selectedClub.presidentName}</span>
</div>
</div>
)}
{/* Vice President Card - Violet Theme */}
{/* Vice President Card */}
{selectedClub.vicePresidentName && (
<div className="bg-violet-50/80 border border-violet-100 hover:border-violet-300 transition-colors rounded-2xl p-3.5 flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-violet-500 to-violet-600 text-white flex items-center justify-center shadow-xs shadow-violet-200 shrink-0">
@@ -617,21 +848,9 @@ export default function Events() {
</div>
)}
{/* Coordinator Card - Amber Theme */}
{selectedClub.coordinatorName && (
<div className="bg-amber-50/80 border border-amber-100 hover:border-amber-300 transition-colors rounded-2xl p-3.5 flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-amber-500 to-amber-600 text-white flex items-center justify-center shadow-xs shadow-amber-200 shrink-0">
<GraduationCap className="w-5 h-5 text-white" />
</div>
<div>
<span className="text-[11px] text-amber-600 font-semibold block">Faculty Coordinator</span>
<span className="text-sm font-bold text-amber-950">{selectedClub.coordinatorName}</span>
</div>
</div>
)}
{selectedClub.contactEmail && (
<div className="bg-emerald-50/80 border border-emerald-100 hover:border-emerald-300 transition-colors rounded-2xl p-3.5 flex items-center gap-3">
{/* Contact Email */}
{selectedClub.contactEmail && (
<div className="bg-emerald-50/80 border border-emerald-100 hover:border-emerald-300 transition-colors rounded-2xl p-3.5 flex items-center gap-3 col-span-1 sm:col-span-2">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-emerald-500 to-emerald-600 text-white flex items-center justify-center shadow-xs shadow-emerald-200 shrink-0">
<Mail className="w-5 h-5 text-white" />
</div>
@@ -642,31 +861,27 @@ export default function Events() {
</a>
</div>
</div>
)}
{/* Phone Card - Sky Theme */}
<div className="bg-sky-50/80 border border-sky-100 hover:border-sky-300 transition-colors rounded-2xl p-3.5 flex items-center gap-3">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-sky-500 to-sky-600 text-white flex items-center justify-center shadow-xs shadow-sky-200 shrink-0">
<Phone className="w-5 h-5 text-white" />
</div>
<div>
<span className="text-[11px] text-sky-500 font-semibold block">Contact Phone</span>
<span className="text-sm font-bold text-sky-950">{selectedClub.contactPhone || '+91 98765 43210'}</span>
</div>
</div>
)}
</div>
{/* ─── Feature 5: Social & Community Link Hub (Dummy Links) ─────── */}
{/* Social & Community Links */}
{Boolean(
selectedClub.socialLinks?.instagram ||
selectedClub.socialLinks?.linkedin ||
selectedClub.socialLinks?.whatsapp ||
selectedClub.socialLinks?.youtube
) && (
<div className="mb-6 bg-orange-50/60 border border-orange-200/80 rounded-2xl p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2 text-xs font-bold text-[#F97316] uppercase tracking-wider">
<ExternalLink className="w-3.5 h-3.5" />
<span>Community & Social Links</span>
<span>Social & Community Links</span>
</div>
<span className="text-[10px] text-emerald-600 font-medium">✓ Verified RIT Handle</span>
<span className="text-[10px] text-emerald-600 font-medium">✓ Verified RIT Entity</span>
</div>
<div className="grid grid-cols-2 gap-2.5">
{selectedClub.socialLinks?.instagram && (
<a
href={selectedClub.socialLinks.instagram}
@@ -679,18 +894,6 @@ export default function Events() {
</a>
)}
{selectedClub.socialLinks?.website && (
<a
href={selectedClub.socialLinks.website}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2 p-2.5 rounded-xl bg-white border border-slate-200 hover:border-orange-500 hover:text-orange-600 transition-all text-xs font-medium text-slate-700"
>
<Globe className="w-4 h-4 text-orange-500 shrink-0" />
<span className="truncate">Linktree Hub</span>
</a>
)}
{selectedClub.socialLinks?.linkedin && (
<a
href={selectedClub.socialLinks.linkedin}
@@ -728,6 +931,7 @@ export default function Events() {
)}
</div>
</div>
)}
{/* Close Button */}
<div>

View File

@@ -6,6 +6,23 @@ import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/An
import { FACULTY_DATA, DEPARTMENTS } from '@/constants';
import { Link } from 'react-router-dom';
const DEPT_MAP: Record<string, { abrv: string; full: string }> = {
'Computer Science & Engineering': { abrv: 'CSE', full: 'Computer Science and Engineering' },
'Computer Science & Business Systems': { abrv: 'CSBS', full: 'Computer Science and Business Systems' },
'Artificial Intelligence & Machine Learning': { abrv: 'AIML', full: 'Artificial Intelligence and Machine Learning' },
'Electronics & Communication Engineering': { abrv: 'ECE', full: 'Electronics and Communication Engineering' },
'Mechanical Engineering': { abrv: 'MECH', full: 'Mechanical Engineering' },
'Civil Engineering': { abrv: 'CIVIL', full: 'Civil Engineering' },
'Artificial Intelligence & Data Science': { abrv: 'AI&DS', full: 'Artificial Intelligence and Data Science' },
'Electrical & Electronics Engineering': { abrv: 'EEE', full: 'Electrical and Electronics Engineering' },
'Communication and Computer Engineering': { abrv: 'CCE', full: 'Communication and Computer Engineering' },
'Electronics Engineering VLSI (Design and Technology)': { abrv: 'EE VLSI (D&T)', full: 'Electronics Engineering (VLSI Design and Technology)' },
'Humanities & Sciences': { abrv: 'H&S', full: 'Humanities and Sciences' },
'M.Tech (Data Science)': { abrv: 'M.TECH-DS', full: 'M.Tech (Data Science)' },
'Mathematics': { abrv: 'MATHS', full: 'Mathematics' },
'M.E. (VLSI Design)': { abrv: 'MEVLSI', full: 'M.E. (VLSI Design)' },
};
export default function Faculty() {
const [searchFaculty, setSearchFaculty] = useState('');
const [selectedDept, setSelectedDept] = useState('All Departments');
@@ -15,23 +32,39 @@ export default function Faculty() {
const filtered = FACULTY_DATA.filter((f) => {
const searchLower = searchFaculty.trim().toLowerCase();
const matchSearch =
f.name.toLowerCase().includes(searchLower) ||
f.department.toLowerCase().includes(searchLower) ||
f.designation.toLowerCase().includes(searchLower) ||
(f.specialization && f.specialization.toLowerCase().includes(searchLower));
(f.name && f.name.toLowerCase().includes(searchLower)) ||
(f.department && f.department.toLowerCase().includes(searchLower)) ||
(f.designation && f.designation.toLowerCase().includes(searchLower)) ||
(f.specialization && f.specialization.toLowerCase().includes(searchLower)) ||
(f.interest && f.interest.toLowerCase().includes(searchLower));
const matchDept = selectedDept === 'All Departments' || f.department === selectedDept;
return matchSearch && matchDept;
});
return filtered.sort((a, b) => {
if (sortBy === 'Name A-Z') return a.name.localeCompare(b.name);
if (sortBy === 'Department') return a.department.localeCompare(b.department);
if (sortBy === 'Designation') return a.designation.localeCompare(b.designation);
if (sortBy === 'Name A-Z') return (a.name || '').localeCompare(b.name || '');
if (sortBy === 'Department') return (a.department || '').localeCompare(b.department || '');
if (sortBy === 'Designation') return (a.designation || '').localeCompare(b.designation || '');
return 0;
});
}, [searchFaculty, selectedDept, sortBy]);
const groupedFaculty = useMemo(() => {
const groups: Record<string, typeof filteredAndSortedFaculty> = {};
filteredAndSortedFaculty.forEach((faculty) => {
if (!groups[faculty.department]) {
groups[faculty.department] = [];
}
groups[faculty.department].push(faculty);
});
return groups;
}, [filteredAndSortedFaculty]);
const departmentOrder = useMemo(() => {
return DEPARTMENTS.filter(d => d !== 'All Departments');
}, []);
return (
<div className="min-h-screen relative overflow-hidden bg-[#F8FAFC]">
{/* Background decorations */}
@@ -44,26 +77,26 @@ export default function Faculty() {
<div className="relative z-10">
{/* Header */}
<div className="bg-white border-b border-[#E5E7EB] py-10">
<div className="bg-white border-b border-[#E9E5EE] py-10">
<div className="container-custom">
<div className="flex items-center gap-2 text-xs text-[#94A3B8] mb-3">
<Link to="/" className="hover:text-[#F97316]">Home</Link>
<div className="flex items-center gap-2 text-xs text-[#9E91B6] mb-3">
<Link to="/" className="hover:text-[#FF6B00]">Home</Link>
<ChevronRight className="w-3 h-3" />
<span className="text-[#F97316]">Faculty Directory</span>
<span className="text-[#FF6B00] font-semibold">Faculty Directory</span>
</div>
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
<h1 className="text-3xl md:text-4xl font-extrabold text-[#1A0B2E] mb-2" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
Faculty{' '}
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
<span style={{ background: 'linear-gradient(135deg, #FF6B00, #F97316)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
Directory
</span>
</h1>
<p className="text-[#475569] text-sm" style={{ fontFamily: 'Inter, sans-serif' }}>
<p className="text-[#4A3E5E] text-sm" style={{ fontFamily: 'Inter, sans-serif' }}>
Browse and connect with experienced faculty members across all departments.
</p>
</div>
</div>
<div className="container-custom py-8 space-y-8">
<div className="container-custom pt-8 pb-20 md:pb-28 space-y-8">
{/* Under Development Banner */}
<motion.div
initial={{ opacity: 0, y: -10 }}
@@ -87,15 +120,15 @@ export default function Faculty() {
</motion.div>
{/* Faculty Hero Section */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 items-stretch">
<div className="lg:col-span-5 bg-white rounded-3xl p-8 border border-[#E8ECF4] shadow-sm flex flex-col justify-center relative overflow-hidden">
<div className="lg:col-span-5 bg-white rounded-2xl p-8 border border-[#E9E5EE] shadow-[0_4px_20px_-4px_rgba(19,9,36,0.04)] flex flex-col justify-center relative overflow-hidden">
<div className="absolute top-0 right-0 w-32 h-32 bg-orange-50 rounded-bl-full -z-10" />
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-[#FF7A00] to-[#FB923C] flex items-center justify-center text-white mb-6 shadow-md">
<div className="w-14 h-14 rounded-2xl bg-gradient-to-br from-[#FF6B00] to-[#F97316] flex items-center justify-center text-white mb-6 shadow-md shadow-orange-500/20">
<Users className="w-7 h-7" />
</div>
<h2 className="text-3xl font-bold text-[#1E293B] mb-3" style={{ fontFamily: 'Poppins, sans-serif' }}>
<h2 className="text-3xl font-extrabold text-[#1A0B2E] mb-3" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
Faculty Directory
</h2>
<p className="text-[#64748B] text-xs leading-relaxed" style={{ fontFamily: 'Inter, sans-serif' }}>
<p className="text-[#4A3E5E] text-xs leading-relaxed" style={{ fontFamily: 'Inter, sans-serif' }}>
Browse and connect with experienced faculty members. Search by name, department, or specialization.
</p>
</div>
@@ -112,15 +145,21 @@ export default function Faculty() {
{ abrv: 'CIVIL', full: 'Civil Engineering', color: 'emerald' },
{ abrv: 'AI & DS', full: 'Artificial Intelligence & Data Science', color: 'pink' },
{ abrv: 'EEE', full: 'Electrical & Electronics Engineering', color: 'yellow' },
].map(d => {
{ abrv: 'CCE', full: 'Communication and Computer Engineering', color: 'rose' },
{ abrv: 'EE VLSI (D&T)', full: 'Electronics Engineering VLSI (Design and Technology)', color: 'teal' },
{ abrv: 'H&S', full: 'Humanities & Sciences', color: 'indigo' },
{ abrv: 'M.TECH-DS', full: 'M.Tech (Data Science)', color: 'fuchsia' },
{ abrv: 'MATHS', full: 'Mathematics', color: 'blue' },
{ abrv: 'MEVLSI', full: 'M.E. (VLSI Design)', color: 'rose' },
].filter(d => FACULTY_DATA.filter(f => f.department === d.full).length > 0).map(d => {
const count = FACULTY_DATA.filter(f => f.department === d.full).length;
return (
<div key={d.abrv} className={`p-4 rounded-2xl bg-${d.color}-50 border border-${d.color}-100 flex flex-col gap-1 transition-transform hover:scale-105 cursor-default`}>
<div className="flex justify-between items-center">
<span className={`text-xs font-bold text-${d.color}-700`} style={{ fontFamily: 'Poppins, sans-serif' }}>{d.abrv}</span>
<span className={`text-xs font-bold text-${d.color}-700`} style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>{d.abrv}</span>
<BookOpen className={`w-3.5 h-3.5 text-${d.color}-400`} />
</div>
<span className="text-2xl font-bold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>{count}</span>
<span className="text-2xl font-bold text-[#1E293B]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>{count}</span>
</div>
);
})}
@@ -134,7 +173,7 @@ export default function Faculty() {
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-[#94A3B8]" />
<input
type="text"
placeholder="Search by faculty name, department, designation or specialization..."
placeholder="Search by faculty name, department, designation or interest..."
value={searchFaculty}
onChange={(e) => setSearchFaculty(e.target.value)}
className="w-full pl-12 pr-4 py-3.5 rounded-2xl bg-[#F8FAFC] border-none text-sm text-[#1E293B] placeholder-[#94A3B8] focus:ring-2 focus:ring-[#FF7A00]/20 focus:outline-none transition-all"
@@ -150,7 +189,7 @@ export default function Faculty() {
className="h-full pl-10 pr-10 py-3.5 rounded-2xl bg-[#F8FAFC] border-none text-sm font-medium text-[#475569] focus:ring-2 focus:ring-[#FF7A00]/20 focus:outline-none transition-all appearance-none cursor-pointer min-w-[200px]"
style={{ fontFamily: 'Inter, sans-serif', backgroundImage: 'url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%2394A3B8%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E")', backgroundRepeat: 'no-repeat', backgroundPosition: 'right 1rem top 50%', backgroundSize: '0.65rem auto' }}
>
{DEPARTMENTS.map((dept) => {
{DEPARTMENTS.filter(dept => dept === 'All Departments' || FACULTY_DATA.filter((f) => f.department === dept).length > 0).map((dept) => {
const count = dept === 'All Departments' ? FACULTY_DATA.length : FACULTY_DATA.filter((f) => f.department === dept).length;
return <option key={dept} value={dept}>{dept} ({count})</option>;
})}
@@ -172,15 +211,38 @@ export default function Faculty() {
</div>
</div>
{/* Grid */}
{/* Grouped Grid */}
{filteredAndSortedFaculty.length > 0 ? (
<StaggerContainer key={`${searchFaculty}-${selectedDept}-${sortBy}`} className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 pb-20">
{filteredAndSortedFaculty.map((faculty) => (
<div className="space-y-12 pb-20">
{departmentOrder.map((deptName) => {
const facultyInDept = groupedFaculty[deptName];
if (!facultyInDept || facultyInDept.length === 0) return null;
const deptInfo = DEPT_MAP[deptName] || { abrv: deptName, full: deptName };
return (
<div key={deptName} className="space-y-6">
{/* Department Header */}
<div className="border-l-4 border-[#FF6B00] pl-4 py-1">
<h2 className="text-xl font-bold text-[#1A0B2E]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
{deptInfo.abrv}
</h2>
<p className="text-xs text-[#9E91B6] font-semibold mt-0.5" style={{ fontFamily: 'Inter, sans-serif' }}>
{deptInfo.full}
</p>
</div>
{/* Department Grid */}
<StaggerContainer key={`${deptName}-${searchFaculty}-${selectedDept}-${sortBy}`} className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{facultyInDept.map((faculty) => (
<StaggerItem key={faculty.id}>
<FacultyCard faculty={faculty} />
</StaggerItem>
))}
</StaggerContainer>
</div>
);
})}
</div>
) : (
<div className="py-20 text-center flex flex-col items-center justify-center bg-white rounded-3xl border border-[#E8ECF4] shadow-sm">
<Users className="w-12 h-12 text-[#CBD5E1] mb-4" />

View File

@@ -2,14 +2,12 @@ import { motion, useScroll, useTransform } from 'framer-motion';
import { Link } from 'react-router-dom';
import {
ArrowRight, BookOpen, Bot, GraduationCap,
CheckCircle, Zap, Shield, Trophy, Award, Cpu, Users, MapPin, Sparkles
CheckCircle, Zap, Shield, Trophy, Award, Cpu, Users, Sparkles
} from 'lucide-react';
import SectionTitle from '@/components/SectionTitle/SectionTitle';
import FeatureCard from '@/components/FeatureCard/FeatureCard';
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer';
import { FEATURES, CAMPUS_LOCATIONS } from '@/constants';
import * as LucideIcons from 'lucide-react';
import { FEATURES } from '@/constants';
import LazyVideoHero from '@/components/LazyVideoHero/LazyVideoHero';
@@ -33,7 +31,7 @@ export default function Home() {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1 }}
className="text-3xl md:text-5xl font-medium tracking-wide mb-2"
style={{ color: '#FFFFFF', fontFamily: 'Playfair Display, serif', textShadow: '0 2px 14px rgba(0,0,0,0.95)' }}
style={{ color: '#FFFFFF', fontFamily: 'Plus Jakarta Sans, sans-serif', textShadow: '0 2px 14px rgba(0,0,0,0.95)' }}
>
Welcome to
</motion.h1>
@@ -42,8 +40,8 @@ export default function Home() {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.2 }}
className="text-5xl md:text-7xl font-black leading-tight mb-6"
style={{ color: '#FFFFFF', fontFamily: 'Playfair Display, serif', textShadow: '0 4px 24px rgba(0,0,0,0.95)' }}
className="text-5xl md:text-7xl font-extrabold tracking-tight leading-tight mb-6"
style={{ color: '#FFFFFF', fontFamily: 'Plus Jakarta Sans, sans-serif', textShadow: '0 4px 24px rgba(0,0,0,0.95)' }}
>
RIT Freshers Hub
</motion.h2>
@@ -67,12 +65,12 @@ export default function Home() {
>
<a href="#built-for-freshers">
<motion.button
whileHover={{ scale: 1.04, boxShadow: '0 10px 30px -5px rgba(249,115,22,0.4)' }}
whileHover={{ scale: 1.04, boxShadow: '0 10px 30px -5px rgba(255,107,0,0.4)' }}
whileTap={{ scale: 0.96 }}
className="flex items-center gap-2 px-8 py-3.5 rounded-xl text-white font-semibold text-sm cursor-pointer shadow-md"
className="flex items-center gap-2 px-8 py-3.5 rounded-xl text-white font-bold text-sm cursor-pointer shadow-md"
style={{
fontFamily: 'Poppins, sans-serif',
background: 'linear-gradient(135deg, #F97316, #FB923C)',
fontFamily: 'Plus Jakarta Sans, sans-serif',
background: 'linear-gradient(135deg, #FF6B00, #F97316)',
}}
>
<BookOpen className="w-4 h-4" />
@@ -83,28 +81,15 @@ export default function Home() {
<Link to="/ai-assistant">
<motion.button
whileHover={{ scale: 1.04, borderColor: '#F97316', backgroundColor: 'rgba(255,255,255,0.2)' }}
whileHover={{ scale: 1.04, borderColor: '#FF6B00', backgroundColor: 'rgba(255,255,255,0.2)' }}
whileTap={{ scale: 0.96 }}
className="flex items-center gap-2 px-8 py-3.5 rounded-xl font-semibold text-sm border-2 border-white/40 text-white bg-black/30 backdrop-blur-md transition-all cursor-pointer shadow-2xs"
style={{ fontFamily: 'Poppins, sans-serif' }}
className="flex items-center gap-2 px-8 py-3.5 rounded-xl font-bold text-sm border-2 border-white/40 text-white bg-black/30 backdrop-blur-md transition-all cursor-pointer shadow-2xs"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<Bot className="w-4 h-4 text-[#F97316]" />
<Bot className="w-4 h-4 text-[#FF6B00]" />
Ask Assistant
</motion.button>
</Link>
<Link to="/raise">
<motion.button
whileHover={{ scale: 1.04, boxShadow: '0 10px 30px -5px rgba(233,30,99,0.5)' }}
whileTap={{ scale: 0.96 }}
className="flex items-center gap-2 px-8 py-3.5 rounded-xl font-bold text-sm text-white bg-gradient-to-r from-rose-500 to-pink-600 border border-white/30 backdrop-blur-md transition-all cursor-pointer shadow-lg shadow-pink-500/20"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
<Sparkles className="w-4 h-4 text-amber-200" />
Pitch your Idea - RAISE
<ArrowRight className="w-4 h-4" />
</motion.button>
</Link>
</motion.div>
</motion.div>
</section>
@@ -117,28 +102,28 @@ export default function Home() {
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.6 }}
className="rounded-3xl border border-[#E5E7EB] p-6 md:p-8 bg-white shadow-[0_20px_50px_-15px_rgba(0,0,0,0.06)]"
className="rounded-2xl border border-[#E9E5EE] p-6 md:p-8 bg-white shadow-[0_4px_20px_-4px_rgba(19,9,36,0.05)]"
>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-6 text-center">
{[
{ value: '20+', label: 'SPORTS', icon: Trophy },
{ value: '100+', label: 'FACULTY', icon: GraduationCap },
{ value: '18', label: 'CLUBS', icon: Award },
{ value: '33+', label: 'CLUBS & CENTERS', icon: Award },
{ value: '24/7', label: 'ASSISTANT', icon: Cpu },
{ value: '5000+', label: 'STUDENTS', icon: Users },
].map((stat, i) => (
<div key={i} className="flex flex-col items-center justify-center p-3">
<div className="w-10 h-10 rounded-xl bg-[#FFF7ED] flex items-center justify-center mb-2.5">
<stat.icon className="w-5 h-5 text-[#F97316]" />
<stat.icon className="w-5 h-5 text-[#FF6B00]" />
</div>
<div
className="text-2xl md:text-3xl font-black text-[#1E293B] mb-0.5"
style={{ fontFamily: 'Poppins, sans-serif' }}
className="text-2xl md:text-3xl font-extrabold text-[#1A0B2E] mb-0.5"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{stat.value}
</div>
<div
className="text-[11px] font-bold tracking-wider text-[#94A3B8] uppercase"
className="text-[11px] font-bold tracking-wider text-[#9E91B6] uppercase"
style={{ fontFamily: 'Inter, sans-serif' }}
>
{stat.label}
@@ -169,80 +154,6 @@ export default function Home() {
</StaggerContainer>
</div>
</section>
{/* ─── Campus Quick Nav ─────────────────────────────────────────────────── */}
<section className="section-padding relative z-20 bg-white border-y border-[#E5E7EB]">
<div className="container-custom">
<SectionTitle
tag="Navigate Campus"
title="Explore"
highlight="RIT Campus"
subtitle="Find your way around campus — departments, labs, library, and key locations."
/>
<AnimatedContainer>
<div className="bg-white rounded-3xl border border-[#E5E7EB] overflow-hidden shadow-sm">
{/* Map Teaser Header */}
<div
className="h-52 flex items-center justify-center relative overflow-hidden"
style={{ background: 'linear-gradient(135deg, #1E293B, #0F172A)' }}
>
<div
className="absolute inset-0 opacity-15"
style={{
backgroundImage: 'linear-gradient(rgba(249,115,22,0.4) 1px, transparent 1px), linear-gradient(90deg, rgba(249,115,22,0.4) 1px, transparent 1px)',
backgroundSize: '28px 28px',
}}
/>
<div className="relative z-10 text-center px-4">
<div className="w-12 h-12 rounded-2xl bg-[#F97316]/20 border border-[#F97316]/30 flex items-center justify-center mx-auto mb-3">
<MapPin className="w-6 h-6 text-[#F97316]" />
</div>
<h3 className="text-white text-lg font-bold mb-1" style={{ fontFamily: 'Poppins, sans-serif' }}>
Interactive Campus Map
</h3>
<p className="text-slate-400 text-xs mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>
Locate all major campus blocks, labs, library & amenities.
</p>
<Link to="/campus">
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="px-5 py-2 rounded-xl text-white text-xs font-semibold shadow-sm cursor-pointer"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', fontFamily: 'Poppins, sans-serif' }}
>
Open Full Map
</motion.button>
</Link>
</div>
</div>
{/* Quick Nav Grid */}
<div className="p-5 grid grid-cols-3 sm:grid-cols-7 gap-3 bg-[#FAFAFA]">
{CAMPUS_LOCATIONS.map((loc) => {
const Icon = (LucideIcons as unknown 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] bg-white shadow-2xs"
>
<div className="w-9 h-9 rounded-xl bg-[#FFF7ED] flex items-center justify-center">
{Icon && <Icon className="w-4.5 h-4.5 text-[#F97316]" />}
</div>
<span className="text-[11px] text-[#475569] text-center font-medium leading-tight" style={{ fontFamily: 'Inter, sans-serif' }}>
{loc.name}
</span>
</motion.div>
</Link>
);
})}
</div>
</div>
</AnimatedContainer>
</div>
</section>
</div>
);
}

View File

@@ -125,7 +125,7 @@ export default function LeetcodeLeaderboard() {
const topProfile = profiles.length > 0 ? profiles[0] : null;
return (
<div className="min-h-screen pt-24 pb-16 bg-[#FAFAFA] text-[#1E293B] px-4 sm:px-6 lg:px-8">
<div className="min-h-screen pt-24 pb-16 bg-[#FAF9FC] text-[#1A0B2E] px-4 sm:px-6 lg:px-8">
{/* Toast Notification */}
<AnimatePresence>
{toastMessage && (
@@ -133,7 +133,7 @@ export default function LeetcodeLeaderboard() {
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="fixed top-6 right-6 z-50 px-5 py-3 rounded-xl bg-white border border-[#E5E7EB] backdrop-blur-md text-[#1E293B] font-semibold shadow-2xl flex items-center gap-2"
className="fixed top-6 right-6 z-50 px-5 py-3 rounded-xl bg-white border border-[#E9E5EE] backdrop-blur-md text-[#1A0B2E] font-bold shadow-2xl flex items-center gap-2"
>
{toastMessage}
</motion.div>
@@ -142,20 +142,19 @@ export default function LeetcodeLeaderboard() {
<div className="max-w-7xl mx-auto space-y-8">
{/* Banner Header */}
<div className="relative overflow-hidden rounded-[24px] bg-white border border-[#E5E7EB] p-8 shadow-[0_8px_30px_rgb(0,0,0,0.04)]">
<div className="absolute top-0 right-0 -mt-12 -mr-12 w-96 h-96 bg-amber-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="absolute bottom-0 left-1/3 -mb-12 w-80 h-80 bg-teal-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="relative overflow-hidden rounded-2xl bg-white border border-[#E9E5EE] p-8 shadow-[0_4px_20px_-4px_rgba(19,9,36,0.04)]">
<div className="absolute top-0 right-0 -mt-12 -mr-12 w-96 h-96 bg-orange-500/5 rounded-full blur-3xl pointer-events-none" />
<div className="relative z-10 flex flex-col lg:flex-row items-start lg:items-center justify-between gap-6">
<div>
<div className="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full bg-amber-500/10 border border-amber-500/20 text-amber-500 text-xs font-semibold uppercase tracking-wider mb-4">
<Flame className="w-4 h-4 text-amber-500 animate-pulse" />
<div className="inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full bg-orange-500/10 border border-orange-500/20 text-[#FF6B00] text-xs font-bold uppercase tracking-wider mb-4">
<Flame className="w-4 h-4 text-[#FF6B00] animate-pulse" />
RIT LeetCode Arena
</div>
<h1 className="text-3xl sm:text-5xl font-extrabold tracking-tight text-[#1E293B] font-poppins">
Campus <span className="bg-clip-text text-transparent bg-gradient-to-r from-amber-400 via-orange-400 to-[#F97316]">LeetCode Leaderboard</span>
<h1 className="text-3xl sm:text-5xl font-extrabold tracking-tight text-[#1A0B2E]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
Campus <span className="bg-clip-text text-transparent bg-gradient-to-r from-[#FF6B00] to-[#F97316]">LeetCode Leaderboard</span>
</h1>
<p className="mt-2 text-slate-500 max-w-2xl text-sm sm:text-base">
<p className="mt-2 text-[#4A3E5E] max-w-2xl text-sm sm:text-base font-medium">
Track top problem solvers across departments. Automatically updated every 24 hours with rate-limited, spaced background sync.
</p>
</div>
@@ -163,7 +162,8 @@ export default function LeetcodeLeaderboard() {
<div className="flex flex-wrap items-center gap-3 shrink-0">
<button
onClick={() => setShowModal(true)}
className="flex items-center gap-2 px-5 py-3 rounded-2xl bg-gradient-to-r from-[#F97316] to-[#FB923C] hover:from-[#EA580C] hover:to-[#F97316] text-white font-bold text-sm shadow-lg shadow-[#F97316]/25 transition-all transform hover:-translate-y-0.5 active:translate-y-0 cursor-pointer"
className="flex items-center gap-2 px-5 py-3 rounded-xl bg-gradient-to-r from-[#FF6B00] to-[#F97316] hover:from-[#EA580C] hover:to-[#FF6B00] text-white font-bold text-sm shadow-lg shadow-orange-500/25 transition-all transform hover:-translate-y-0.5 active:translate-y-0 cursor-pointer"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<Plus className="w-5 h-5 stroke-[2.5]" />
Register Handle
@@ -171,9 +171,10 @@ export default function LeetcodeLeaderboard() {
<button
onClick={handleTriggerSync}
disabled={isSyncing}
className="flex items-center gap-2 px-4 py-3 rounded-2xl bg-[#F8FAFC] hover:bg-[#F1F5F9] text-slate-700 border border-[#E2E8F0] font-semibold text-sm transition-all cursor-pointer"
className="flex items-center gap-2 px-4 py-3 rounded-xl bg-[#FAF9FC] hover:bg-purple-50/50 text-[#1A0B2E] border border-[#E9E5EE] font-semibold text-sm transition-all cursor-pointer"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<RefreshCw className={`w-4 h-4 ${isSyncing ? 'animate-spin text-amber-500' : ''}`} />
<RefreshCw className={`w-4 h-4 ${isSyncing ? 'animate-spin text-[#FF6B00]' : ''}`} />
24h Spaced Sync
</button>
</div>
@@ -369,10 +370,10 @@ export default function LeetcodeLeaderboard() {
</div>
{/* Desktop Table View (Medium Screens & Up) */}
<div className="hidden md:block overflow-x-auto">
<table className="w-full text-left text-slate-600 border-collapse">
<div className="hidden md:block overflow-x-auto rounded-2xl border border-[#E9E5EE] shadow-[0_4px_20px_-4px_rgba(19,9,36,0.04)] bg-white">
<table className="w-full text-left text-[#4A3E5E] border-collapse">
<thead>
<tr className="border-b border-[#E5E7EB] bg-[#F8FAFC] text-[11px] font-bold uppercase tracking-wider text-slate-500">
<tr className="border-b border-[#3A1968] bg-[#130924] text-[11px] font-extrabold uppercase tracking-wider text-white">
<th className="py-4 px-6 text-center w-16">Rank</th>
<th className="py-4 px-6">Student</th>
<th className="py-4 px-6">Dept & Year</th>

View File

@@ -309,7 +309,7 @@ const CS_TOOLS_CATEGORIES = [
bg: '#FEFCE8',
tools: [
{ name: 'Documentation', desc: 'Confluence, Notion, MkDocs, and Docusaurus.', url: 'https://docusaurus.io/' },
{ name: 'Team Communication', desc: 'Slack, Microsoft Teams, Discord, and Mattermost.', url: 'https://slack.com/' },
{ name: 'Team Communication', desc: 'Slack, Microsoft Teams, and Mattermost.', url: 'https://slack.com/' },
{ name: 'Project Management', desc: 'Jira, Trello, Asana, and Linear ticket tracking.', url: 'https://linear.app/' },
{ name: 'Whiteboarding', desc: 'Miro, FigJam, Excalidraw, and Lucidchart.', url: 'https://excalidraw.com/' },
{ name: 'Knowledge Management', desc: 'Obsidian, Notion, and Slite workspace wiki.', url: 'https://obsidian.md/' }
@@ -1433,7 +1433,7 @@ export default function Notes() {
</div>
)}
<div className="container-custom py-10">
<div className="container-custom pt-10 pb-32 lg:pb-44">
{selectedToolkit ? (

View File

@@ -7,11 +7,12 @@ import {
TrendingUp, UserCheck, ClipboardList, ShoppingCart, Cpu, Activity, LayoutGrid,
Layers, Zap, Settings, Gauge, Radio, Wifi, Bot, Sun, Wrench, Binary, BatteryCharging,
CircuitBoard, ShieldCheck, BrainCircuit, FlaskConical, Lock, Terminal, Dna, Microscope,
Sparkles, Smartphone, Monitor, Server, CheckCircle2, Database, Briefcase, AlertTriangle, RefreshCw
Sparkles, Smartphone, Monitor, Server, CheckCircle2, Database, Briefcase, AlertTriangle, RefreshCw, Calculator,
Building2, GraduationCap, Plus, Trash2
} from 'lucide-react';
import SectionTitle from '@/components/SectionTitle/SectionTitle';
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
import { TOOLKIT_ITEMS } from '@/constants';
import { TOOLKIT_ITEMS, DEPARTMENTS } from '@/constants';
const TOOLKIT_ICONS: Record<string, { icon: React.ComponentType<{ className?: string }>; bg: string; color: string }> = {
'CS tools': { icon: Code, bg: '#EFF6FF', color: '#3B82F6' },
@@ -20,6 +21,7 @@ const TOOLKIT_ICONS: Record<string, { icon: React.ComponentType<{ className?: st
'AI tools': { icon: BrainCircuit, bg: '#FDF2F8', color: '#EC4899' },
'Electrical and Electronic tools': { icon: Zap, bg: '#FEF3C7', color: '#D97706' },
'Biotech tools': { icon: Dna, bg: '#ECFEFF', color: '#06B6D4' },
'GPA and internal calculator': { icon: Calculator, bg: '#FEF2F2', color: '#EF4444' },
};
const PROGRAMMING_LANGUAGES_GUIDES = [
@@ -276,7 +278,7 @@ const CS_TOOLS_CATEGORIES = [
bg: '#FEFCE8',
tools: [
{ name: 'Documentation', desc: 'Confluence, Notion, MkDocs, and Docusaurus.', url: 'https://docusaurus.io/' },
{ name: 'Team Communication', desc: 'Slack, Microsoft Teams, Discord, and Mattermost.', url: 'https://slack.com/' },
{ name: 'Team Communication', desc: 'Slack, Microsoft Teams, and Mattermost.', url: 'https://slack.com/' },
{ name: 'Project Management', desc: 'Jira, Trello, Asana, and Linear ticket tracking.', url: 'https://linear.app/' },
{ name: 'Whiteboarding', desc: 'Miro, FigJam, Excalidraw, and Lucidchart.', url: 'https://excalidraw.com/' },
{ name: 'Knowledge Management', desc: 'Obsidian, Notion, and Slite workspace wiki.', url: 'https://obsidian.md/' }
@@ -653,12 +655,190 @@ const BIOTECH_TOOLS_CATEGORIES = [
}
];
const PROFESSIONAL_ELECTIVES_LIST: Array<{ code: string; name: string; credits: number }> = [
{ code: 'AD23V11', name: 'Agriculture Data Analytics', credits: 3 },
{ code: 'AD23V12', name: 'Big Data Analytics', credits: 3 },
{ code: 'AD23V13', name: 'Data warehousing and Data Mining', credits: 3 },
{ code: 'AD23V14', name: 'Health Care Analytics', credits: 3 },
{ code: 'AD23V15', name: 'Image and Video Analytics', credits: 3 },
{ code: 'AD23V16', name: 'Web and Social Media Analytics', credits: 3 },
{ code: 'AD23V17', name: 'Predictive Analytics', credits: 3 },
{ code: 'AD23V18', name: 'Sentiment Analysis', credits: 3 },
{ code: 'AD23V21', name: 'AI in IoT', credits: 3 },
{ code: 'AD23V22', name: 'Applied Machine Learning', credits: 3 },
{ code: 'AD23V23', name: 'Cognitive Computing', credits: 3 },
{ code: 'AD23V24', name: 'Computer Vision', credits: 3 },
{ code: 'AD23V25', name: 'Ethics For AI', credits: 3 },
{ code: 'AD23V26', name: 'Game Theory', credits: 3 },
{ code: 'AD23V27', name: 'Quantum Computing Techniques', credits: 3 },
{ code: 'AD23V28', name: 'Responsive AI', credits: 3 },
];
const AIDS_CURRICULUM: Record<number, Array<{ code: string; name: string; credits: number; isElective?: boolean; electiveSlot?: number }>> = {
1: [
{ code: 'HS23111', name: 'Communicative English', credits: 3 },
{ code: 'MA23111', name: 'Matrices and Calculus', credits: 4 },
{ code: 'PH23111', name: 'Physics for Information Science', credits: 3 },
{ code: 'GE23111', name: 'Problem Solving and C Programming', credits: 3 },
{ code: 'GE23113', name: 'Basic Electrical and Electronics Engineering', credits: 3 },
{ code: 'GE23112', name: 'Heritage of Tamils', credits: 0 },
{ code: 'PH23121', name: 'Physics Laboratory', credits: 1 },
{ code: 'GE23121', name: 'Problem Solving and C Programming Laboratory', credits: 1 },
{ code: 'GE23122', name: 'Engineering Practices Laboratory', credits: 1 },
],
2: [
{ code: 'HS23211', name: 'Professional English', credits: 2 },
{ code: 'CY23211', name: 'Engineering Chemistry', credits: 3 },
{ code: 'MA23211', name: 'Statistics and Numerical Methods', credits: 4 },
{ code: 'AD23211', name: 'Python for Data Science', credits: 3 },
{ code: 'GE23213', name: 'Tamils and Technology', credits: 0 },
{ code: 'GE23231', name: 'Engineering Graphics', credits: 4 },
{ code: 'AD23231', name: 'Data Structures Design', credits: 4 },
{ code: 'CY23221', name: 'Chemistry Laboratory', credits: 1 },
{ code: 'AD23221', name: 'Python for Data Science Laboratory', credits: 1 },
{ code: 'GE23221', name: 'Communication Laboratory / Foreign Language', credits: 1 },
],
3: [
{ code: 'MA23311', name: 'Discrete Mathematics', credits: 4 },
{ code: 'AL23311', name: 'Artificial Intelligence', credits: 3 },
{ code: 'CS23312', name: 'Object Oriented Programming', credits: 3 },
{ code: 'CS23411', name: 'Database Management Systems', credits: 3 },
{ code: 'EC23331', name: 'Digital Principles and Computer Organization', credits: 4 },
{ code: 'AL23321', name: 'Artificial Intelligence Laboratory', credits: 1 },
{ code: 'CS23421', name: 'Database Management Systems Laboratory', credits: 1 },
{ code: 'CS23322', name: 'Object Oriented Programming Laboratory', credits: 1 },
{ code: 'AD23IC1', name: 'Data Wrangling', credits: 1 },
],
4: [
{ code: 'GE23411', name: 'Environmental Science and Sustainability', credits: 2 },
{ code: 'MA23411', name: 'Probability and Statistics', credits: 4 },
{ code: 'AD23411', name: 'Data Analytics', credits: 3 },
{ code: 'AL23411', name: 'Machine Learning', credits: 3 },
{ code: 'CS23412', name: 'Operating Systems', credits: 3 },
{ code: 'CS23431', name: 'Design and Analysis of Algorithms', credits: 4 },
{ code: 'AD23421', name: 'Data Analytics Laboratory', credits: 1 },
{ code: 'AL23421', name: 'Machine Learning Laboratory', credits: 1 },
{ code: 'CS23422', name: 'Operating Systems Laboratory', credits: 1 },
{ code: 'AD23IC2', name: 'Introduction to AZURE Machine Learning', credits: 1 },
],
5: [
{ code: 'AD23511', name: 'Deep Learning', credits: 3 },
{ code: 'CS23511', name: 'Computer Networks', credits: 3 },
{ code: 'PEC01', name: 'Professional Elective I', credits: 3, isElective: true, electiveSlot: 1 },
{ code: 'PEC02', name: 'Professional Elective II', credits: 3, isElective: true, electiveSlot: 2 },
{ code: 'CB23531', name: 'Business Analytics', credits: 4 },
{ code: 'AD23532', name: 'Data Exploration and Visualization', credits: 4 },
{ code: 'AD23521', name: 'Deep Learning Laboratory', credits: 1 },
{ code: 'CS23521', name: 'Computer Networks Laboratory', credits: 1 },
{ code: 'AD23IC3', name: 'Tableau - Data Visualization', credits: 1 },
],
6: [
{ code: 'PEC03', name: 'Professional Elective III', credits: 3, isElective: true, electiveSlot: 3 },
{ code: 'PEC04', name: 'Professional Elective IV', credits: 3, isElective: true, electiveSlot: 4 },
{ code: 'OEC01', name: 'Open Elective I', credits: 3 },
{ code: 'OEC02', name: 'Open Elective II', credits: 3 },
{ code: 'EC23631', name: 'Embedded Systems and IoT', credits: 4 },
{ code: 'CS23631', name: 'Object Oriented Software Engineering', credits: 4 },
{ code: 'AD23621', name: 'Mini Project', credits: 2 },
],
7: [
{ code: 'GE23711', name: 'Human Values and Ethics', credits: 2 },
{ code: 'MGT01', name: 'Elective - Management', credits: 3 },
{ code: 'CB23511', name: 'Data and Information Security', credits: 3 },
{ code: 'PEC05', name: 'Professional Elective V', credits: 3, isElective: true, electiveSlot: 5 },
{ code: 'PEC06', name: 'Professional Elective VI', credits: 3, isElective: true, electiveSlot: 6 },
{ code: 'AD23721', name: 'Internship / Certification Course', credits: 2 },
],
8: [
{ code: 'OEC03', name: 'Open Elective III', credits: 3 },
{ code: 'AD23821', name: 'Project Work', credits: 10 },
],
};
export default function Toolkit() {
const [searchParams, setSearchParams] = useSearchParams();
const selectedToolkit = searchParams.get('toolkit');
const [selectedCategory, setSelectedCategory] = useState<number | null>(null);
const [selectedLanguage, setSelectedLanguage] = useState<number | null>(null);
const [selectedTool, setSelectedTool] = useState<{ name: string; desc: string; url: string; category: string; bg: string; color: string } | null>(null);
const [gpaDepartment, setGpaDepartment] = useState('');
const [gpaSemester, setGpaSemester] = useState('');
const [calculationType, setCalculationType] = useState<'gpa' | 'internal' | null>(null);
const [gpaStep, setGpaStep] = useState<1 | 2>(1);
const [calculatedScore, setCalculatedScore] = useState<string | null>(null);
interface SubjectItem {
name: string;
credits: number;
grade: string;
cat1: string;
cat2: string;
assignment: string;
attendance: string;
isElective?: boolean;
electiveSlot?: number;
electiveCode?: string;
}
const [subjects, setSubjects] = useState<SubjectItem[]>([
{ name: 'Subject 1', credits: 4, grade: 'O', cat1: '45', cat2: '48', assignment: '10', attendance: '95' },
{ name: 'Subject 2', credits: 3, grade: 'A+', cat1: '42', cat2: '44', assignment: '9', attendance: '92' },
{ name: 'Subject 3', credits: 3, grade: 'A', cat1: '38', cat2: '40', assignment: '8', attendance: '88' },
{ name: 'Subject 4', credits: 4, grade: 'O', cat1: '46', cat2: '47', assignment: '10', attendance: '96' },
{ name: 'Subject 5', credits: 2, grade: 'A+', cat1: '44', cat2: '45', assignment: '9', attendance: '94' },
]);
const handleProceedToStep2 = (type: 'gpa' | 'internal') => {
if (!gpaDepartment || !gpaSemester) return;
setCalculationType(type);
const semNum = parseInt(gpaSemester.replace(/\D/g, ''), 10) || 1;
const isAIDS = gpaDepartment.toLowerCase().includes('artificial intelligence') || gpaDepartment.toLowerCase().includes('data science');
if (isAIDS && AIDS_CURRICULUM[semNum]) {
const loaded = AIDS_CURRICULUM[semNum]
.filter((s) => type === 'internal' || s.credits > 0)
.map((s) => {
if (s.isElective) {
const defaultCode = s.code === 'PEC01' ? 'AD23V11' : s.code === 'PEC02' ? 'AD23V12' : s.code === 'PEC03' ? 'AD23V13' : s.code === 'PEC04' ? 'AD23V14' : s.code === 'PEC05' ? 'AD23V15' : 'AD23V16';
const defaultObj = PROFESSIONAL_ELECTIVES_LIST.find((e) => e.code === defaultCode);
return {
name: defaultObj ? `${defaultObj.code} - ${defaultObj.name}` : `${s.code} - ${s.name}`,
credits: s.credits,
grade: 'O',
cat1: '45',
cat2: '45',
assignment: '9',
attendance: '95',
isElective: true,
electiveSlot: s.electiveSlot,
electiveCode: defaultCode,
};
}
return {
name: `${s.code} - ${s.name}`,
credits: s.credits,
grade: 'O',
cat1: '45',
cat2: '45',
assignment: '9',
attendance: '95',
isElective: false,
};
});
setSubjects(loaded);
} else {
setSubjects([
{ name: 'Subject 1', credits: 4, grade: 'O', cat1: '45', cat2: '48', assignment: '10', attendance: '95' },
{ name: 'Subject 2', credits: 3, grade: 'A+', cat1: '42', cat2: '44', assignment: '9', attendance: '92' },
{ name: 'Subject 3', credits: 3, grade: 'A', cat1: '38', cat2: '40', assignment: '8', attendance: '88' },
{ name: 'Subject 4', credits: 4, grade: 'O', cat1: '46', cat2: '47', assignment: '10', attendance: '96' },
{ name: 'Subject 5', credits: 2, grade: 'A+', cat1: '44', cat2: '45', assignment: '9', attendance: '94' },
]);
}
setGpaStep(2);
setCalculatedScore(null);
};
const setSelectedToolkit = (value: string | null) => {
if (value) {
@@ -666,35 +846,45 @@ export default function Toolkit() {
setSelectedCategory(null);
setSelectedLanguage(null);
setSelectedTool(null);
setGpaDepartment('');
setGpaSemester('');
setCalculationType(null);
setGpaStep(1);
setCalculatedScore(null);
} else {
setSearchParams({});
setSelectedCategory(null);
setSelectedLanguage(null);
setSelectedTool(null);
setGpaDepartment('');
setGpaSemester('');
setCalculationType(null);
setGpaStep(1);
setCalculatedScore(null);
}
};
const toolkitRef = useRef<HTMLDivElement>(null);
return (
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
<div className="min-h-screen bg-[#FAF9FC]">
{/* Header */}
{!selectedToolkit && (
<div className="bg-white border-b border-[#E5E7EB] py-10">
<div className="bg-white border-b border-[#E9E5EE] 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' }}>
<Link to="/" className="hover:text-[#F97316]">Home</Link>
<div className="flex items-center gap-2 text-xs text-[#9E91B6] mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>
<Link to="/" className="hover:text-[#FF6B00]">Home</Link>
<ChevronRight className="w-3 h-3" />
<span className="text-[#F97316]">Student Toolkit</span>
<span className="text-[#FF6B00] font-semibold">Student Toolkit</span>
</div>
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
<h1 className="text-3xl md:text-4xl font-extrabold text-[#1A0B2E] mb-2" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
Student{' '}
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
<span style={{ background: 'linear-gradient(135deg, #FF6B00, #F97316)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
Toolkit
</span>
</h1>
<p className="text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
<p className="text-[#4A3E5E]" style={{ fontFamily: 'Inter, sans-serif' }}>
Access specialized development environments, IDE configurations, compiler guides, and department toolkits.
</p>
</motion.div>
@@ -702,7 +892,7 @@ export default function Toolkit() {
</div>
)}
<div className="container-custom py-10">
<div className="container-custom pt-10 pb-32 lg:pb-44">
{selectedToolkit ? (
(() => {
const TOOLKIT_MAP: Record<string, { categories: typeof CS_TOOLS_CATEGORIES; label: string }> = {
@@ -712,6 +902,7 @@ export default function Toolkit() {
cybersecurity: { categories: CYBERSECURITY_TOOLS_CATEGORIES, label: 'Cybersecurity' },
ai: { categories: AI_TOOLS_CATEGORIES, label: 'Artificial Intelligence' },
biotech: { categories: BIOTECH_TOOLS_CATEGORIES, label: 'Biotechnology' },
gpa: { categories: [], label: 'GPA and Internal Calculator' },
};
const current = TOOLKIT_MAP[selectedToolkit];
if (!current) return null;
@@ -737,8 +928,8 @@ export default function Toolkit() {
setSelectedToolkit(null);
}
}}
className="inline-flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-semibold text-[#475569] bg-white border border-[#E5E7EB] hover:border-[#F97316] hover:text-[#F97316] transition-all cursor-pointer shadow-2xs mb-4"
style={{ fontFamily: 'Poppins, sans-serif' }}
className="inline-flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-bold text-[#4A3E5E] bg-white border border-[#E9E5EE] hover:border-[#FF6B00] hover:text-[#FF6B00] transition-all cursor-pointer shadow-2xs mb-4"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<ArrowLeft className="w-4 h-4" />
{selectedTool
@@ -750,8 +941,8 @@ export default function Toolkit() {
: 'Back to Toolkit'}
</button>
<div className="flex items-center gap-3">
<h2 className="text-2xl md:text-3xl font-bold text-[#1E293B]" style={{ fontFamily: 'Playfair Display, serif' }}>
{label} <span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>Toolkit</span>
<h2 className="text-2xl md:text-3xl font-extrabold text-[#1A0B2E]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
{label} <span style={{ background: 'linear-gradient(135deg, #FF6B00, #F97316)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>Toolkit</span>
</h2>
</div>
<p className="text-[#64748B] text-sm mt-1" style={{ fontFamily: 'Inter, sans-serif' }}>
@@ -759,12 +950,395 @@ export default function Toolkit() {
? `Complete beginner guide, core concepts, use cases, and resources for ${PROGRAMMING_LANGUAGES_GUIDES[selectedLanguage].name}.`
: selectedCategory !== null
? `Browse essential ${categories[selectedCategory].title} software, guides, and developer resources.`
: selectedToolkit === 'gpa'
? 'Select your department and semester for GPA and internal score calculations.'
: `Explore all categories and tools curated for ${label} students.`}
</p>
</motion.div>
{/* Main Content Area */}
{selectedLanguage !== null ? (
{selectedToolkit === 'gpa' ? (
<motion.div initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.4 }} className="max-w-3xl mx-auto">
{gpaStep === 1 ? (
/* ── STEP 1: Department and Semester Selection ── */
<div className="bg-white p-8 md:p-10 rounded-3xl border border-[#E5E7EB] shadow-xl relative overflow-hidden">
<div className="absolute top-0 left-0 right-0 h-2 bg-gradient-to-r from-[#FF6B00] via-[#EF4444] to-[#F97316]" />
<div className="flex items-center gap-4 mb-6">
<div className="w-14 h-14 rounded-2xl bg-[#FEF2F2] text-[#EF4444] flex items-center justify-center font-bold shadow-xs">
<Calculator className="w-7 h-7" />
</div>
<div>
<h3 className="text-2xl font-extrabold text-[#1E293B]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
GPA & Internal Calculator
</h3>
<p className="text-sm text-[#64748B]" style={{ fontFamily: 'Inter, sans-serif' }}>
Step 1 of 2: Select your department and semester to begin.
</p>
</div>
</div>
<div className="space-y-6">
{/* Department Input */}
<div>
<label className="block text-sm font-bold text-[#1E293B] mb-2 flex items-center gap-2" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
<Building2 className="w-4 h-4 text-[#F97316]" /> Department
</label>
<select
value={gpaDepartment}
onChange={(e) => setGpaDepartment(e.target.value)}
className="w-full px-4 py-3.5 rounded-2xl bg-[#F8FAFC] border border-[#E2E8F0] focus:border-[#F97316] focus:ring-2 focus:ring-[#F97316]/20 text-[#1E293B] text-sm font-medium transition-all outline-none cursor-pointer"
style={{ fontFamily: 'Inter, sans-serif' }}
>
<option value="" disabled>Select Department</option>
{DEPARTMENTS.filter(d => d !== 'All Departments').map((dept, idx) => (
<option key={idx} value={dept}>{dept}</option>
))}
</select>
</div>
{/* Semester Input */}
<div>
<label className="block text-sm font-bold text-[#1E293B] mb-2 flex items-center gap-2" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
<GraduationCap className="w-4 h-4 text-[#F97316]" /> Semester
</label>
<select
value={gpaSemester}
onChange={(e) => setGpaSemester(e.target.value)}
className="w-full px-4 py-3.5 rounded-2xl bg-[#F8FAFC] border border-[#E2E8F0] focus:border-[#F97316] focus:ring-2 focus:ring-[#F97316]/20 text-[#1E293B] text-sm font-medium transition-all outline-none cursor-pointer"
style={{ fontFamily: 'Inter, sans-serif' }}
>
<option value="" disabled>Select Semester</option>
{['1st Semester', '2nd Semester', '3rd Semester', '4th Semester', '5th Semester', '6th Semester', '7th Semester', '8th Semester'].map((sem, idx) => (
<option key={idx} value={sem}>{sem}</option>
))}
</select>
</div>
{/* Two Action Buttons to proceed to Step 2 */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 pt-2">
<button
onClick={() => handleProceedToStep2('internal')}
disabled={!gpaDepartment || !gpaSemester}
className={`py-3.5 px-5 rounded-2xl text-sm font-extrabold flex items-center justify-center gap-2 transition-all cursor-pointer shadow-md ${
gpaDepartment && gpaSemester
? 'bg-gradient-to-r from-[#8B5CF6] to-[#7C3AED] hover:from-[#7C3AED] hover:to-[#6D28D9] text-white shadow-purple-500/20 hover:scale-[1.01]'
: 'bg-[#E2E8F0] text-[#94A3B8] cursor-not-allowed shadow-none'
}`}
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<BarChart3 className="w-4 h-4" />
Calculate Internal
</button>
<button
onClick={() => handleProceedToStep2('gpa')}
disabled={!gpaDepartment || !gpaSemester}
className={`py-3.5 px-5 rounded-2xl text-sm font-extrabold flex items-center justify-center gap-2 transition-all cursor-pointer shadow-md ${
gpaDepartment && gpaSemester
? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] hover:from-[#EA580C] hover:to-[#DD6B20] text-white shadow-orange-500/20 hover:scale-[1.01]'
: 'bg-[#E2E8F0] text-[#94A3B8] cursor-not-allowed shadow-none'
}`}
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<Calculator className="w-4 h-4" />
Calculate GPA
</button>
</div>
</div>
</div>
) : (
/* ── STEP 2: Subject Input and Marks Section ── */
<motion.div initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} transition={{ duration: 0.3 }} className="space-y-6">
<div className="bg-white p-6 md:p-8 rounded-3xl border border-[#E5E7EB] shadow-xl relative overflow-hidden">
<div className="flex items-center justify-between gap-4 mb-6 pb-4 border-b border-[#F1F5F9]">
<div>
<button
onClick={() => setGpaStep(1)}
className="inline-flex items-center gap-1.5 text-xs font-bold text-[#FF6B00] hover:text-[#EA580C] transition-colors mb-2 cursor-pointer"
>
<ArrowLeft className="w-3.5 h-3.5" /> Back to Selection
</button>
<h3 className="text-xl md:text-2xl font-extrabold text-[#1E293B]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
{calculationType === 'gpa' ? 'GPA Calculation' : 'Internal Marks Calculation'}
</h3>
<p className="text-xs font-semibold text-[#64748B] mt-0.5">
{gpaDepartment} — <span className="text-[#FF6B00]">{gpaSemester}</span>
</p>
</div>
<div className="w-12 h-12 rounded-2xl bg-[#FFF7ED] text-[#FF6B00] flex items-center justify-center font-bold shrink-0">
{calculationType === 'gpa' ? <Calculator className="w-6 h-6" /> : <BarChart3 className="w-6 h-6" />}
</div>
</div>
{calculationType === 'gpa' ? (
/* ── Clean 2-Column GPA Layout (Fixed Credits, Grade Input Only) ── */
<div className="space-y-3 mb-6">
<div className="flex items-center justify-between text-xs font-bold uppercase tracking-wider text-[#94A3B8] px-1 mb-2">
<span>Subject & Credits</span>
<span>Grade Achieved</span>
</div>
{(() => {
const selectedElectiveCodes = new Set(subjects.filter((s) => s.isElective && s.electiveCode).map((s) => s.electiveCode));
return subjects.filter(s => s.credits > 0).map((sub, idx) => (
<div key={idx} className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 p-4 rounded-2xl bg-[#F8FAFC] border border-[#E2E8F0] hover:border-[#CBD5E1] transition-all">
<div className="flex-1 min-w-0 pr-2">
{sub.isElective ? (
<div className="space-y-1">
<div className="text-[11px] font-bold text-[#FF6B00] uppercase tracking-wider">
Professional Elective {sub.electiveSlot}
</div>
<select
value={sub.electiveCode}
onChange={(e) => {
const newCode = e.target.value;
const found = PROFESSIONAL_ELECTIVES_LIST.find((el) => el.code === newCode);
const updated = [...subjects];
updated[idx].electiveCode = newCode;
updated[idx].name = found ? `${found.code} - ${found.name}` : newCode;
setSubjects(updated);
setCalculatedScore(null);
}}
className="w-full px-3 py-2 rounded-xl bg-white border border-[#CBD5E1] focus:border-[#FF6B00] text-xs font-bold text-[#1E293B] outline-none cursor-pointer"
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
{PROFESSIONAL_ELECTIVES_LIST.filter((opt) => opt.code === sub.electiveCode || !selectedElectiveCodes.has(opt.code)).map((opt) => (
<option key={opt.code} value={opt.code}>
{opt.code} - {opt.name}
</option>
))}
</select>
</div>
) : (
<p className="text-sm font-bold text-[#1E293B]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
{sub.name}
</p>
)}
<span className="inline-block mt-1 px-2.5 py-0.5 rounded-md bg-[#FFF7ED] text-[#FF6B00] text-[11px] font-bold border border-[#FFEDD5]">
{sub.credits} {sub.credits === 1 ? 'Credit' : 'Credits'}
</span>
</div>
<div className="w-full sm:w-60 shrink-0">
<select
value={sub.grade}
onChange={(e) => {
const updated = [...subjects];
updated[idx].grade = e.target.value;
setSubjects(updated);
setCalculatedScore(null);
}}
className="w-full px-3.5 py-2.5 rounded-xl bg-white border border-[#CBD5E1] focus:border-[#FF6B00] focus:ring-2 focus:ring-[#FF6B00]/20 text-xs font-bold text-[#1E293B] outline-none cursor-pointer shadow-2xs"
style={{ fontFamily: 'Inter, sans-serif' }}
>
<option value="O">O</option>
<option value="A+">A+</option>
<option value="A">A</option>
<option value="B+">B+</option>
<option value="B">B</option>
<option value="C">C</option>
<option value="UA">UA</option>
</select>
</div>
</div>
));
})()}
</div>
) : (
/* ── Internal Marks Layout ── */
<div className="space-y-4 mb-6">
<div className="flex items-center justify-between text-xs font-bold uppercase tracking-wider text-[#94A3B8]">
<span>Subject Details</span>
<span>{subjects.length} Subjects</span>
</div>
{subjects.map((sub, idx) => (
<div key={idx} className="p-4 rounded-2xl bg-[#F8FAFC] border border-[#E2E8F0] space-y-3">
<div className="flex items-center justify-between gap-2">
<input
type="text"
value={sub.name}
onChange={(e) => {
const updated = [...subjects];
updated[idx].name = e.target.value;
setSubjects(updated);
setCalculatedScore(null);
}}
placeholder={`Subject ${idx + 1} Name`}
className="flex-1 px-3 py-2 rounded-xl bg-white border border-[#CBD5E1] text-xs font-bold text-[#1E293B] focus:border-[#FF6B00] outline-none"
/>
{subjects.length > 1 && (
<button
onClick={() => {
setSubjects(subjects.filter((_, i) => i !== idx));
setCalculatedScore(null);
}}
className="p-2 rounded-xl text-[#EF4444] hover:bg-[#FEF2F2] transition-colors cursor-pointer"
title="Remove subject"
>
<Trash2 className="w-4 h-4" />
</button>
)}
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
<div>
<label className="block text-[10px] font-semibold text-[#64748B] mb-1">CAT 1 (/50)</label>
<input
type="number"
min="0"
max="50"
value={sub.cat1}
onChange={(e) => {
const updated = [...subjects];
updated[idx].cat1 = e.target.value;
setSubjects(updated);
setCalculatedScore(null);
}}
className="w-full px-2.5 py-1.5 rounded-xl bg-white border border-[#CBD5E1] text-xs font-medium text-[#1E293B] outline-none"
/>
</div>
<div>
<label className="block text-[10px] font-semibold text-[#64748B] mb-1">CAT 2 (/50)</label>
<input
type="number"
min="0"
max="50"
value={sub.cat2}
onChange={(e) => {
const updated = [...subjects];
updated[idx].cat2 = e.target.value;
setSubjects(updated);
setCalculatedScore(null);
}}
className="w-full px-2.5 py-1.5 rounded-xl bg-white border border-[#CBD5E1] text-xs font-medium text-[#1E293B] outline-none"
/>
</div>
<div>
<label className="block text-[10px] font-semibold text-[#64748B] mb-1">Assignment (/10)</label>
<input
type="number"
min="0"
max="10"
value={sub.assignment}
onChange={(e) => {
const updated = [...subjects];
updated[idx].assignment = e.target.value;
setSubjects(updated);
setCalculatedScore(null);
}}
className="w-full px-2.5 py-1.5 rounded-xl bg-white border border-[#CBD5E1] text-xs font-medium text-[#1E293B] outline-none"
/>
</div>
<div>
<label className="block text-[10px] font-semibold text-[#64748B] mb-1">Attendance %</label>
<input
type="number"
min="0"
max="100"
value={sub.attendance}
onChange={(e) => {
const updated = [...subjects];
updated[idx].attendance = e.target.value;
setSubjects(updated);
setCalculatedScore(null);
}}
className="w-full px-2.5 py-1.5 rounded-xl bg-white border border-[#CBD5E1] text-xs font-medium text-[#1E293B] outline-none"
/>
</div>
</div>
</div>
))}
<button
onClick={() => {
setSubjects([
...subjects,
{ name: `Subject ${subjects.length + 1}`, credits: 3, grade: 'A', cat1: '40', cat2: '40', assignment: '8', attendance: '90' }
]);
setCalculatedScore(null);
}}
className="w-full py-2.5 rounded-2xl border-2 border-dashed border-[#CBD5E1] hover:border-[#FF6B00] text-xs font-bold text-[#64748B] hover:text-[#FF6B00] transition-colors flex items-center justify-center gap-1.5 cursor-pointer"
>
<Plus className="w-3.5 h-3.5" /> Add Subject
</button>
</div>
)}
{/* Submit Button for Step 2 */}
<button
onClick={() => {
if (calculationType === 'gpa') {
const GRADE_MAP: Record<string, number> = { 'O': 10, 'A+': 9, 'A': 8, 'B+': 7, 'B': 6, 'C': 5, 'UA': 0, 'RA': 0 };
let totalPts = 0;
let totalCreds = 0;
subjects.forEach(s => {
const pts = GRADE_MAP[s.grade] ?? 0;
const cred = Number(s.credits) || 0;
totalPts += pts * cred;
totalCreds += cred;
});
setCalculatedScore(totalCreds > 0 ? (totalPts / totalCreds).toFixed(2) : '0.00');
} else {
let total = 0;
subjects.forEach(s => {
const c1 = (Number(s.cat1) || 0) / 50 * 15;
const c2 = (Number(s.cat2) || 0) / 50 * 15;
const ass = (Number(s.assignment) || 0) / 10 * 5;
const att = (Number(s.attendance) || 0) >= 80 ? 5 : 3;
total += (c1 + c2 + ass + att);
});
setCalculatedScore(subjects.length > 0 ? (total / subjects.length).toFixed(1) : '0.0');
}
}}
className={`w-full py-4 rounded-2xl text-sm font-extrabold flex items-center justify-center gap-2 transition-all cursor-pointer shadow-md ${
calculationType === 'gpa'
? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] hover:from-[#EA580C] hover:to-[#DD6B20] text-white shadow-orange-500/20 hover:scale-[1.01]'
: 'bg-gradient-to-r from-[#8B5CF6] to-[#7C3AED] hover:from-[#7C3AED] hover:to-[#6D28D9] text-white shadow-purple-500/20 hover:scale-[1.01]'
}`}
style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<CheckCircle2 className="w-4 h-4" />
{calculationType === 'gpa' ? 'Calculate Final GPA' : 'Calculate Total Internal Marks'}
</button>
{/* Calculated Result Card */}
{calculatedScore !== null && (
<motion.div
initial={{ opacity: 0, scale: 0.96, y: 10 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
className="mt-6 p-6 rounded-3xl bg-gradient-to-br from-[#FFF7ED] to-[#FFFBEB] border border-[#FED7AA] shadow-sm space-y-3"
>
<div className="flex items-center justify-between">
<span className="text-xs font-bold uppercase tracking-wider text-[#EA580C]">
{calculationType === 'gpa' ? 'Calculated SGPA' : 'Average Internal Score'}
</span>
<span className="px-3 py-1 rounded-full bg-[#FF6B00]/10 text-[#FF6B00] text-xs font-bold">
{gpaSemester}
</span>
</div>
<div className="p-4 rounded-2xl bg-white border border-[#FFEDD5] flex items-baseline justify-between">
<div>
<p className="text-xs font-semibold text-[#64748B] mb-1">{gpaDepartment}</p>
<span className="text-4xl font-extrabold text-[#1E293B]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>
{calculatedScore} {calculationType === 'gpa' ? '/ 10.00' : '/ 40.0'}
</span>
</div>
<span className="px-3 py-1.5 rounded-xl bg-[#ECFDF5] text-[#10B981] text-xs font-bold">
{calculationType === 'gpa'
? (Number(calculatedScore) >= 9.0 ? 'First Class with Distinction (O)' : Number(calculatedScore) >= 8.5 ? 'First Class with Distinction' : Number(calculatedScore) >= 7.0 ? 'First Class' : Number(calculatedScore) >= 5.0 ? 'Second Class' : 'UA / Reappear')
: (Number(calculatedScore) >= 35 ? 'Excellent' : 'Good')}
</span>
</div>
</motion.div>
)}
</div>
</motion.div>
)}
</motion.div>
) : selectedLanguage !== null ? (
/* ── Detailed Programming Language Guide View ── */
(() => {
const guide = PROGRAMMING_LANGUAGES_GUIDES[selectedLanguage];
@@ -942,6 +1516,8 @@ export default function Toolkit() {
setSelectedToolkit('ai');
} else if (item.title === 'Biotech tools') {
setSelectedToolkit('biotech');
} else if (item.title === 'GPA and internal calculator') {
setSelectedToolkit('gpa');
} else if (item.url && item.url !== '#') {
window.open(item.url, '_blank');
}

View File

@@ -28,13 +28,14 @@ export interface Stat {
export interface Faculty {
id: string;
name: string;
designation: string;
designation?: string;
department: string;
email: string;
phone?: string;
office?: string;
avatar?: string;
specialization?: string;
interest?: string;
qualification?: string;
experience?: string;
subjectsHandling?: string;
@@ -101,7 +102,7 @@ export interface Event {
isUpcoming: boolean;
}
// ─── Clubs ───────────────────────────────────────────────────────────────────
// ─── Clubs & Centers ──────────────────────────────────────────────────────────
export interface ClubSocialLinks {
instagram?: string;
linkedin?: string;
@@ -117,6 +118,7 @@ export interface Club {
description: string;
details?: string;
category: string;
type?: 'Club' | 'Center';
members: number;
presidentName?: string;
vicePresidentName?: string;

View File

@@ -1,8 +1,6 @@
{
"community_bot_token": "",
"telegram_bot_token": "",
"helper_chat_ids": [971749136, 5567776672,1873240361],
"discord_bot_token": "",
"discord_helper_user_ids": [789393727641878568],
"helper_chat_ids": [971749136, 5567776672, 1873240361],
"spring_backend_url": "http://localhost:8085"
}

View File

@@ -2,4 +2,3 @@ requests>=2.28.0
fastapi>=0.95.0
uvicorn>=0.20.0
pydantic>=2.0
discord.py>=2.3.0

View File

@@ -6,7 +6,6 @@ import time
import logging
import requests
import asyncio
import discord
from typing import Optional
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
@@ -53,7 +52,6 @@ def load_config():
config = load_config()
COMMUNITY_BOT_TOKEN = os.environ.get("COMMUNITY_BOT_TOKEN") or config.get("community_bot_token", "8859374355:AAH0dhwstkTBhRerRTjzmb2RG2fjPbigzvo")
BOT_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN") or config.get("telegram_bot_token")
DISCORD_TOKEN = os.environ.get("DISCORD_BOT_TOKEN") or config.get("discord_bot_token")
BACKEND_URL = os.environ.get("SPRING_BACKEND_URL") or config.get("spring_backend_url", "http://localhost:8085")
# Database Setup
@@ -611,292 +609,8 @@ def telegram_polling_thread():
logging.error(f"Error in 24/7 chatbot polling loop: {e}")
time.sleep(5)
# Discord Action View for Collaboration Applications
class DiscordCollabActionView(discord.ui.View):
def __init__(self, application_id: int, applicant_contact: str):
super().__init__(timeout=86400)
self.application_id = application_id
self.applicant_contact = applicant_contact
@discord.ui.button(label="Accept Collaboration", style=discord.ButtonStyle.success, emoji="✅")
async def accept_button(self, interaction: discord.Interaction, button: discord.ui.Button):
try:
requests.put(f"{BACKEND_URL}/api/collab/applications/{self.application_id}/status?status=ACCEPTED", timeout=5)
await interaction.response.send_message(
f"✅ **Collaboration Request Accepted!**\nDirect contact details: **{self.applicant_contact}**",
ephemeral=False
)
for item in self.children:
item.disabled = True
await interaction.message.edit(view=self)
except Exception as e:
await interaction.response.send_message(f"❌ Error updating status: {e}", ephemeral=True)
@discord.ui.button(label="Decline Request", style=discord.ButtonStyle.danger, emoji="❌")
async def decline_button(self, interaction: discord.Interaction, button: discord.ui.Button):
try:
requests.put(f"{BACKEND_URL}/api/collab/applications/{self.application_id}/status?status=REJECTED", timeout=5)
await interaction.response.send_message("❌ **Collaboration Request Declined.**", ephemeral=False)
for item in self.children:
item.disabled = True
await interaction.message.edit(view=self)
except Exception as e:
await interaction.response.send_message(f"❌ Error updating status: {e}", ephemeral=True)
async def broadcast_discord_collab_application(application_id: int, project_idea: str, tag: str, applicant_name: str, applicant_dept: str, applicant_year: str, applicant_contact: str, message: str, user_ids: list):
formatted_msg = (
f"🤝 **New Collaboration Request for your Project!**\n\n"
f"📌 **Project Idea:** {project_idea}\n"
f"🏷️ **Tag:** `{tag}`\n\n"
f"👤 **Applicant:** {applicant_name} ({applicant_dept}, {applicant_year})\n"
f"💬 **Message:** {message or 'No message provided'}\n"
f"📱 **Contact Info:** {applicant_contact}\n\n"
f"Click a button below to respond:"
)
view = DiscordCollabActionView(application_id, applicant_contact)
for user_id_val in user_ids:
try:
user_id = int(user_id_val)
user = await discord_client.fetch_user(user_id)
if user:
await user.send(content=formatted_msg, view=view)
logging.info(f"Sent Discord collab notification DM to user {user_id}")
except Exception as e:
logging.error(f"Failed to send Discord collab DM to {user_id_val}: {e}")
# Discord Remove Select View
class DiscordCollabRemoveSelect(discord.ui.Select):
def __init__(self, active_requests: list):
options = []
for req in active_requests:
req_id = str(req["id"])
snippet = req.get("projectIdea", "Project")[:45]
options.append(discord.SelectOption(
label=f"Cancel: {snippet}",
value=req_id,
description=f"Tag: {req.get('tag')} | Dept: {req.get('department')}",
emoji="❌"
))
super().__init__(placeholder="Select a request to remove...", min_values=1, max_values=1, options=options)
async def callback(self, interaction: discord.Interaction):
req_id = self.values[0]
try:
res = requests.delete(f"{BACKEND_URL}/api/collab/{req_id}", timeout=5)
if res.status_code in [200, 204]:
await interaction.response.send_message("✅ **Collaboration Request cancelled and removed from RIT Dev Hub.**", ephemeral=False)
else:
await interaction.response.send_message("❌ Failed to cancel request.", ephemeral=True)
except Exception as e:
await interaction.response.send_message(f"❌ Error: {e}", ephemeral=True)
class DiscordCollabRemoveView(discord.ui.View):
def __init__(self, active_requests: list):
super().__init__(timeout=180)
self.add_item(DiscordCollabRemoveSelect(active_requests))
# Discord Native Interactive UI Components (Modal & Dropdown View)
class CollabModal(discord.ui.Modal, title="Post Collaboration Request"):
author_name = discord.ui.TextInput(label="1) Your Name", placeholder="e.g. Priyan Sharma", required=True)
dept_and_year = discord.ui.TextInput(label="2) Dept & Year", placeholder="e.g. CSE, 1st Year", required=True, default="CSE, 1st Year")
collaborators_needed = discord.ui.TextInput(label="3) Collaborators Needed", placeholder="e.g. 2", required=True, default="1")
project_idea = discord.ui.TextInput(label="4) Project Idea & Details", style=discord.TextStyle.paragraph, placeholder="Describe your project idea...", required=True)
github_link = discord.ui.TextInput(label="5) GitHub Link (Optional)", placeholder="https://github.com/...", required=False)
def __init__(self, tag: str):
super().__init__()
self.selected_tag = tag
async def on_submit(self, interaction: discord.Interaction):
try:
num_needed = int(self.collaborators_needed.value)
except ValueError:
num_needed = 1
dept_val = "CSE"
year_val = "1st Year"
if "," in self.dept_and_year.value:
parts = [p.strip() for p in self.dept_and_year.value.split(",", 1)]
dept_val = parts[0]
year_val = parts[1]
else:
dept_val = self.dept_and_year.value.strip()
payload = {
"authorName": self.author_name.value,
"department": dept_val,
"year": year_val,
"collaboratorsNeeded": num_needed,
"tag": self.selected_tag,
"projectIdea": self.project_idea.value,
"githubLink": self.github_link.value or None,
"contactInfo": f"Discord: {interaction.user.name}",
"discordUserId": str(interaction.user.id)
}
try:
res = requests.post(f"{BACKEND_URL}/api/collab", json=payload, timeout=10)
if res.status_code in [200, 201]:
await interaction.response.send_message(
f"🎉 **Collaboration Request posted live to RIT Dev Hub!**\n"
f"👤 **Author:** {self.author_name.value} ({dept_val}, {year_val})\n"
f"📌 **Project:** {self.project_idea.value}\n"
f"🏷️ **Tag:** `{self.selected_tag}`\n"
f"👥 **Collaborators Needed:** `{num_needed}`",
ephemeral=False
)
else:
await interaction.response.send_message(f"❌ Failed to save request (Status: {res.status_code})", ephemeral=True)
except Exception as e:
await interaction.response.send_message(f"❌ Error connecting to backend: {e}", ephemeral=True)
class CollabTagSelect(discord.ui.Select):
def __init__(self):
options = [
discord.SelectOption(
label="Co-developing from scratch",
value="looking for co-developing a project from scratch",
description="Build a brand new project from scratch together",
emoji="🚀"
),
discord.SelectOption(
label="Beta Testers needed",
value="looking for beta testers",
description="Test early builds and provide user feedback",
emoji="🧪"
),
discord.SelectOption(
label="Open-Source Contributors",
value="looking for Open-source Collaborators/Contributers",
description="Open repository seeking PRs and contributors",
emoji="🌐"
),
]
super().__init__(placeholder="Scroll down to select a tag for your request...", min_values=1, max_values=1, options=options)
async def callback(self, interaction: discord.Interaction):
selected_tag = self.values[0]
modal = CollabModal(tag=selected_tag)
await interaction.response.send_modal(modal)
class CollabView(discord.ui.View):
def __init__(self):
super().__init__(timeout=180)
self.add_item(CollabTagSelect())
# Discord Bot Client Setup
intents = discord.Intents.default()
intents.messages = True
intents.message_content = True
discord_client = discord.Client(intents=intents)
discord_loop = None
@discord_client.event
async def on_ready():
logging.info(f"Discord Bot logged in as {discord_client.user}!")
@discord_client.event
async def on_message(message):
if message.author == discord_client.user:
return
is_dm = isinstance(message.channel, discord.DMChannel)
is_mention = discord_client.user in message.mentions
if not is_dm and not is_mention:
return
content = message.content
if is_mention:
mention_str = f"<@{discord_client.user.id}>"
mention_nick_str = f"<@!{discord_client.user.id}>"
content = content.replace(mention_str, "").replace(mention_nick_str, "").strip()
# Discord /collab & /remove commands disabled as requested
if content.lower().startswith("/collab") or content.lower().startswith("/remove") or content.lower().startswith("/cancel"):
await message.reply("ℹ️ **Dev Hub collaboration requests via Discord are currently disabled.** Please use the RIT Dev Hub website (https://rit-services.in/dev-collab) or the Telegram bot (@Ritchatbot_bot).")
return
# Check if this message is a reply to a question DM sent to a helper
if message.reference and message.reference.message_id:
question_id = get_question_id(message.author.id, message.reference.message_id)
if question_id:
author_name = message.author.display_name or message.author.name or "Senior Helper"
logging.info(f"Submitting answer for question {question_id} by Discord helper '{author_name}'")
backend_endpoint = f"{BACKEND_URL}/api/questions/{question_id}/answers"
answer_payload = {
"body": content,
"author": author_name
}
try:
def call_backend():
return requests.post(backend_endpoint, json=answer_payload, timeout=10)
loop = asyncio.get_event_loop()
res = await loop.run_in_executor(None, call_backend)
if res.status_code in [200, 201]:
await message.reply("✅ **Answer posted successfully to the Q&A board!**")
else:
await message.reply(f"❌ **Failed to post answer to backend.** (Status: {res.status_code})")
except Exception as e:
logging.error(f"Error calling backend endpoint {backend_endpoint}: {e}")
await message.reply(f"❌ **Connection error to backend.** ({e})")
return
# Direct query fallback to Go chatbot service
if not content.strip():
return
logging.info(f"Querying Go chatbot service for Discord user {message.author.id}: '{content}'")
chatbot_service_url = "http://localhost:8081/api/chat"
try:
def call_chatbot():
return requests.post(chatbot_service_url, json={"message": content}, timeout=10)
loop = asyncio.get_event_loop()
res = await loop.run_in_executor(None, call_chatbot)
if res.status_code == 200:
ans_data = res.json()
bot_response = ans_data.get("answer", "I am having trouble processing that question.")
await message.reply(bot_response)
else:
logging.error(f"Go chatbot API returned status code {res.status_code}")
await message.reply("⚠️ The RIT Chatbot service is currently experiencing issues. Please try again later.")
except Exception as e:
logging.error(f"Failed to connect to Go chatbot service: {e}")
await message.reply("⚠️ I cannot connect to the RIT Chatbot database right now. Please make sure the service is online.")
async def broadcast_discord_question(question_id: int, title: str, body: str, author: str, user_ids: list):
formatted_msg = (
f"❓ **New Student Question!**\n\n"
f"👤 **Author:** {author}\n"
f"📌 **Topic:** {title}\n"
f"📝 **Details:** {body}\n\n"
f"💬 **Reply directly to this message to submit your answer.**"
)
for user_id_val in user_ids:
try:
user_id = int(user_id_val)
user = await discord_client.fetch_user(user_id)
if user:
msg = await user.send(formatted_msg)
save_mapping(user_id, msg.id, question_id)
logging.info(f"Sent Discord DM to helper {user_id}")
except Exception as e:
logging.error(f"Failed to send Discord DM to helper {user_id_val}: {e}")
async def run_discord_bot():
global discord_loop
logging.info("Starting Discord bot...")
discord_loop = asyncio.get_running_loop()
try:
await discord_client.start(DISCORD_TOKEN)
except Exception as e:
logging.error(f"Discord Bot failed to run: {e}")
# FastAPI Web Server Setup
app = FastAPI(title="RIT Telegram & Discord Intermediary Bot HTTP Server")
app = FastAPI(title="RIT Telegram Intermediary Bot HTTP Server")
app.add_middleware(
CORSMiddleware,
@@ -920,7 +634,6 @@ class CollabApplicationPayload(BaseModel):
author_name: str
contact_info: Optional[str] = None
telegram_chat_id: Optional[int] = None
discord_user_id: Optional[str] = None
applicant_name: str
applicant_dept: str
applicant_year: str
@@ -932,7 +645,7 @@ def send_question(payload: QuestionPayload):
current_config = load_config()
comm_token = os.environ.get("COMMUNITY_BOT_TOKEN") or current_config.get("community_bot_token", COMMUNITY_BOT_TOKEN)
# 1. Telegram Broadcast via Senior Helper Community Bot
# Telegram Broadcast via Senior Helper Community Bot
telegram_helpers = current_config.get("helper_chat_ids", [])
telegram_sent = 0
if telegram_helpers and comm_token:
@@ -951,27 +664,9 @@ def send_question(payload: QuestionPayload):
save_mapping(chat_id, message_id, payload.question_id)
telegram_sent += 1
# 2. Discord Broadcast
discord_helpers = current_config.get("discord_helper_user_ids", [])
discord_sent = 0
if discord_helpers and DISCORD_TOKEN:
logging.info(f"Broadcasting question {payload.question_id} to {len(discord_helpers)} Discord helpers.")
if discord_loop:
try:
asyncio.run_coroutine_threadsafe(
broadcast_discord_question(payload.question_id, payload.title, payload.body, payload.author, discord_helpers),
discord_loop
)
discord_sent = len(discord_helpers)
except Exception as e:
logging.error(f"Error scheduling Discord broadcast: {e}")
else:
logging.warning("Discord loop not running. Skipping Discord broadcast.")
return {
"status": "success",
"telegram_delivered_to": telegram_sent,
"discord_queued_for": discord_sent
"telegram_delivered_to": telegram_sent
}
@app.post("/send_collab_application")
@@ -980,9 +675,8 @@ def send_collab_application(payload: CollabApplicationPayload):
bot_token = os.environ.get("TELEGRAM_BOT_TOKEN") or current_config.get("telegram_bot_token")
telegram_sent = False
discord_sent = False
# 1. Telegram Notification strictly to Person A (the project author)
# Telegram Notification strictly to Person A (the project author)
chat_id = payload.telegram_chat_id
if chat_id:
@@ -1009,13 +703,9 @@ def send_collab_application(payload: CollabApplicationPayload):
else:
logging.info(f"Skipping Telegram notification for collab application {payload.application_id}: No telegram_chat_id provided for author.")
# 2. Discord Broadcast - Disabled as requested
discord_sent = False
return {
"status": "success",
"telegram_sent": telegram_sent,
"discord_sent": discord_sent
"telegram_sent": telegram_sent
}
async def run_uvicorn():
@@ -1030,14 +720,7 @@ async def main():
chat_thread = threading.Thread(target=telegram_polling_thread, daemon=True)
chat_thread.start()
tasks = []
if DISCORD_TOKEN:
tasks.append(run_discord_bot())
else:
logging.warning("Discord Bot Token is empty. Skipping Discord bot startup.")
tasks.append(run_uvicorn())
await asyncio.gather(*tasks)
await run_uvicorn()
if __name__ == "__main__":
try: