Compare commits
6 Commits
3f277f53f4
...
d827f4aab9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d827f4aab9 | ||
|
|
29377ce56e | ||
|
|
97a59ee715 | ||
|
|
3afb40d451 | ||
|
|
bb71b725d9 | ||
|
|
fcaf16405d |
@@ -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 -> {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
After Width: | Height: | Size: 157 KiB |
BIN
public/clubs/center_apple_tech.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
public/clubs/center_ar_vr.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
public/clubs/center_cloud_computing.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
public/clubs/center_cybersecurity.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
public/clubs/center_data_science.png
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
public/clubs/center_ev_energy.png
Normal file
|
After Width: | Height: | Size: 210 KiB |
BIN
public/clubs/center_image_processing.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
public/clubs/center_iot.png
Normal file
|
After Width: | Height: | Size: 146 KiB |
BIN
public/clubs/center_radar_healthcare.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
public/clubs/center_semiconductor.png
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
public/clubs/center_smart_manufacturing.png
Normal file
|
After Width: | Height: | Size: 272 KiB |
BIN
public/clubs/center_space.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
public/clubs/center_zf_transportation.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
public/clubs/grover_center_quantum.png
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
public/map.png
|
Before Width: | Height: | Size: 282 KiB After Width: | Height: | Size: 394 KiB |
@@ -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">
|
||||
|
||||
@@ -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}`}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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" />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
|
||||
221
src/index.css
@@ -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 */
|
||||
@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);
|
||||
}
|
||||
|
||||
@@ -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) }}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
{/* 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">
|
||||
<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>
|
||||
@@ -643,30 +862,26 @@ export default function Events() {
|
||||
</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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2,7 +2,5 @@
|
||||
"community_bot_token": "",
|
||||
"telegram_bot_token": "",
|
||||
"helper_chat_ids": [971749136, 5567776672, 1873240361],
|
||||
"discord_bot_token": "",
|
||||
"discord_helper_user_ids": [789393727641878568],
|
||||
"spring_backend_url": "http://localhost:8085"
|
||||
}
|
||||
|
||||
@@ -2,4 +2,3 @@ requests>=2.28.0
|
||||
fastapi>=0.95.0
|
||||
uvicorn>=0.20.0
|
||||
pydantic>=2.0
|
||||
discord.py>=2.3.0
|
||||
|
||||
@@ -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:
|
||||
|
||||