Compare commits

...

6 Commits

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

View File

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

View File

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

View File

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

View File

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

33
package-lock.json generated
View File

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

BIN
public/clubs/center_ai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
public/clubs/center_iot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 KiB

After

Width:  |  Height:  |  Size: 394 KiB

View File

@@ -36,20 +36,65 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
{ name: 'Yahoo Mail', url: `https://compose.mail.yahoo.com/?to=${faculty.email}`, icon: Mail, color: 'text-purple-600', bg: 'bg-purple-50' }, { 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 ( return (
<> <>
<motion.div <motion.div
whileHover={{ y: -6, boxShadow: '0 20px 40px -10px rgba(0,0,0,0.1)' }} whileHover={{ y: -4 }}
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
className="bg-white border border-[#E8ECF4] rounded-[20px] p-5 flex flex-col gap-4 relative" 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"
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
> >
{/* Avatar + Name */} {/* Avatar + Name */}
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="relative"> <div className="relative">
<div <div
className="w-14 h-14 rounded-2xl flex items-center justify-center text-white font-bold text-lg shrink-0 shadow-sm" 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} {initials}
</div> </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 className="absolute -bottom-1 -right-1 w-4 h-4 bg-[#22C55E] border-2 border-white rounded-full z-10 shadow-sm" />
</div> </div>
<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} {faculty.name}
</h3> </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} {faculty.designation}
</p> </p>
</div> </div>
@@ -68,27 +113,27 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
{/* Details */} {/* Details */}
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex items-center gap-2 text-xs text-[#475569]"> <div className="flex items-center gap-2 text-xs text-[#4A3E5E]">
<Building2 className="w-3.5 h-3.5 text-[#94A3B8] shrink-0" /> <Building2 className="w-3.5 h-3.5 text-[#9E91B6] shrink-0" />
<span style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.department}</span> <span style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.department}</span>
</div> </div>
</div> </div>
{/* Contact & Actions */} {/* 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 <button
onClick={() => setShowEmailOptions(true)} 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" 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: 'Poppins, sans-serif' }} style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
aria-label={`Email ${faculty.name}`} aria-label={`Email ${faculty.name}`}
> >
<Mail className="w-3.5 h-3.5" /> <Mail className="w-3.5 h-3.5 text-[#FF6B00]" />
Email Email
</button> </button>
<button <button
onClick={() => setShowModal(true)} 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]" 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: '#F97316', fontFamily: 'Poppins, sans-serif' }} style={{ color: '#FF6B00', fontFamily: 'Plus Jakarta Sans, sans-serif' }}
> >
View Profile View Profile
<ChevronRight className="w-3.5 h-3.5" /> <ChevronRight className="w-3.5 h-3.5" />
@@ -135,11 +180,11 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
> >
<div <div
className="w-32 h-32 rounded-full flex items-center justify-center text-white font-bold text-5xl mb-5 shadow-lg" 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} {initials}
</div> </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} {faculty.name}
</h2> </h2>
<p className="text-base font-semibold text-[#FF7A00] text-center" style={{ fontFamily: 'Inter, sans-serif' }}> <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"> <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]" /> <User className="w-5 h-5 text-[#FF7A00]" />
</div> </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>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <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"> <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-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> <p className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.department}</p>
</div> </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"> <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-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> <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> <p className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.experience || 'Not specified'}</p>
</div> </div>
{faculty.joinedInstitution && ( {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-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> <p className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.joinedInstitution}</p>
</div> </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>
</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"> <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" /> <BookOpen className="w-5 h-5 text-indigo-500" />
</div> </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>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="p-5 rounded-xl bg-indigo-50/30 border border-indigo-100/50"> <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-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>
<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>
</div> </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}</p>
</div>
)}
{faculty.studentsGuided && ( {faculty.studentsGuided && (
<div className="p-5 rounded-xl bg-blue-50/30 border border-blue-100/50"> <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> <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"> <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" /> <GraduationCap className="w-5 h-5 text-pink-500" />
</div> </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>
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
{faculty.achievements && faculty.achievements.length > 0 && ( {faculty.achievements && faculty.achievements.length > 0 && (
@@ -295,30 +342,36 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
)} )}
{/* Section 4: Office Info */} {/* Section 4: Office Info */}
<div className="bg-white rounded-[20px] shadow-sm border border-[#E8ECF4] p-6 sm:p-8"> {(faculty.office || faculty.officeHours) && (
<div className="flex items-center gap-3 mb-6"> <div className="bg-white rounded-[20px] shadow-sm border border-[#E8ECF4] p-6 sm:p-8">
<div className="w-10 h-10 rounded-xl bg-emerald-50 flex items-center justify-center shrink-0"> <div className="flex items-center gap-3 mb-6">
<Clock className="w-5 h-5 text-emerald-500" /> <div className="w-10 h-10 rounded-xl bg-emerald-50 flex items-center justify-center shrink-0">
</div> <Clock className="w-5 h-5 text-emerald-500" />
<h3 className="text-lg font-bold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Office Information</h3>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<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>
</div> </div>
<h3 className="text-lg font-bold text-[#1E293B]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>Office Information</h3>
</div> </div>
<div className="p-5 rounded-xl bg-blue-50/30 border border-blue-100/50 flex items-start gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Building2 className="w-6 h-6 text-blue-500 shrink-0 mt-0.5" /> {faculty.officeHours && (
<div> <div className="p-5 rounded-xl bg-emerald-50/50 border border-emerald-100 flex items-start gap-4">
<p className="text-xs font-bold text-blue-500 uppercase tracking-wider mb-1" style={{ fontFamily: 'Inter, sans-serif' }}>Office Location</p> <Clock className="w-6 h-6 text-emerald-500 shrink-0 mt-0.5" />
<p className="text-sm font-medium text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.office || 'Not specified'}</p> <div>
</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}</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}</p>
</div>
</div>
)}
</div> </div>
</div> </div>
</div> )}
</div> </div>
</div> </div>
</motion.div> </motion.div>
@@ -349,7 +402,7 @@ export default function FacultyCard({ faculty }: FacultyCardProps) {
> >
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div> <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> <p className="text-xs text-[#64748B] mt-0.5" style={{ fontFamily: 'Inter, sans-serif' }}>Compose email to {faculty.name}</p>
</div> </div>
<button onClick={() => setShowEmailOptions(false)} className="p-2 rounded-full hover:bg-gray-100 transition-colors"> <button onClick={() => setShowEmailOptions(false)} className="p-2 rounded-full hover:bg-gray-100 transition-colors">

View File

@@ -13,14 +13,13 @@ export default function FeatureCard({ feature }: FeatureCardProps) {
return ( return (
<motion.div <motion.div
whileHover={{ y: -6, boxShadow: '0 20px 50px -12px rgba(0,0,0,0.15)' }} whileHover={{ y: -4 }}
transition={{ duration: 0.25 }} transition={{ duration: 0.25 }}
className="bg-white border border-[#E5E7EB] rounded-2xl p-6 flex flex-col gap-4 group cursor-pointer" 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"
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
> >
{/* Icon */} {/* Icon */}
<div <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 }} style={{ backgroundColor: feature.bgColor, color: feature.color }}
> >
{IconComponent && <IconComponent className="w-6 h-6" />} {IconComponent && <IconComponent className="w-6 h-6" />}
@@ -29,12 +28,12 @@ export default function FeatureCard({ feature }: FeatureCardProps) {
{/* Content */} {/* Content */}
<div className="flex-1"> <div className="flex-1">
<h3 <h3
className="text-base font-semibold text-[#1E293B] mb-1.5" className="text-base font-bold text-[#1A0B2E] mb-1.5"
style={{ fontFamily: 'Poppins, sans-serif' }} style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
> >
{feature.title} {feature.title}
</h3> </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} {feature.description}
</p> </p>
</div> </div>
@@ -42,9 +41,9 @@ export default function FeatureCard({ feature }: FeatureCardProps) {
{/* Arrow */} {/* Arrow */}
<Link <Link
to={feature.path} 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={{ style={{
fontFamily: 'Poppins, sans-serif', fontFamily: 'Plus Jakarta Sans, sans-serif',
color: feature.color, color: feature.color,
}} }}
aria-label={`Explore ${feature.title}`} aria-label={`Explore ${feature.title}`}

View File

@@ -1,123 +1,239 @@
import { Link } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { GraduationCap, MapPin, Phone, Mail, ArrowRight } from 'lucide-react'; 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 = [ const exploreLinks = [
{ label: 'Home', path: '/' }, { label: 'Notes & PYQs', path: '/notes', icon: BookOpen },
{ label: 'Notes & PYQs', path: '/notes' }, { label: 'Student Toolkit', path: '/toolkit', icon: Wrench },
{ label: 'RIT Chatbot', path: '/ai-assistant' }, { label: 'RIT Chatbot', path: '/ai-assistant', icon: Bot },
{ label: '3D Campus Map', path: '/campus' }, { label: 'Faculty Directory', path: '/faculty', icon: UserCheck },
{ label: 'Bus Routes', path: '/bus-routes' }, { label: 'LeetCode Arena', path: '/leetcode', icon: Trophy },
{ label: 'Faculty Directory', path: '/faculty' }, ];
{ label: 'Events', path: '/events' },
{ label: 'Community', path: '/community' }, const campusLinks = [
{ label: 'Dev Hub', path: '/collab' }, { 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() { export default function Footer() {
const location = useLocation();
const isHome = location.pathname === '/';
return ( return (
<footer className="bg-[#1E293B] text-white"> <div className="w-full relative z-20">
{/* CTA Banner */} {/* ─── 1. CTA Banner (Floating Overlay Box - Only on Home Page) ────────── */}
<div style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }} className="py-12"> {isHome && (
<div className="container-custom flex flex-col md:flex-row items-center justify-between gap-6"> <div className="container-custom relative z-20 -mb-20 lg:-mb-24 px-4">
<div> <motion.div
<h3 className="text-2xl font-bold text-white" style={{ fontFamily: 'Playfair Display, serif' }}> whileHover={{ y: -3 }}
Ready to explore RIT? transition={{ duration: 0.3 }}
</h3> className="relative overflow-hidden rounded-3xl p-8 lg:p-12 shadow-2xl border border-orange-400/30"
<p className="text-orange-100 mt-1" style={{ fontFamily: 'Inter, sans-serif' }}> style={{
Everything a fresher needs — all in one place. background: 'linear-gradient(135deg, #EA580C 0%, #F97316 50%, #FB923C 100%)',
</p> }}
</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"
style={{ fontFamily: 'Poppins, sans-serif' }}
> >
Ask Chatbot {/* Ambient Background Lighting */}
<ArrowRight className="w-4 h-4" /> <div className="absolute -right-16 -top-16 w-64 h-64 bg-white/10 rounded-full blur-3xl pointer-events-none" />
</Link> <div className="absolute -left-16 -bottom-16 w-64 h-64 bg-black/10 rounded-full blur-3xl pointer-events-none" />
</div>
</div>
{/* Main Footer */} <div className="relative z-10 flex flex-col md:flex-row items-center justify-between gap-6">
<div className="container-custom py-14"> <div className="text-center md:text-left">
<div className="grid grid-cols-1 md:grid-cols-3 gap-10"> <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">
{/* Brand */} <Sparkles className="w-3.5 h-3.5 text-amber-200" />
<div> <span>RIT Student Portal</span>
<div className="flex items-center gap-2.5 mb-4"> </div>
<div <h3
className="w-10 h-10 rounded-xl flex items-center justify-center" className="text-2xl lg:text-3xl xl:text-4xl font-extrabold text-white tracking-tight"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }} style={{ fontFamily: 'Poppins, sans-serif' }}
> >
<GraduationCap className="w-5 h-5 text-white" /> Ready to explore RIT?
</div> </h3>
<div> <p
<div className="font-bold text-white text-sm" style={{ fontFamily: 'Poppins, sans-serif' }}>RIT Freshers Hub</div> className="text-orange-100 text-sm lg:text-base mt-2 max-w-xl font-medium"
<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.
</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' }} style={{ fontFamily: 'Inter, sans-serif' }}
> >
<span className="w-1 h-1 rounded-full bg-[#F97316] opacity-0 group-hover:opacity-100 transition-opacity" /> Everything a fresher needs — semester notes, campus map, chatbot, bus routes, and developer tools all in one place.
{link.label} </p>
</Link> </div>
))}
<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>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>
{/* 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-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>
);
})}
</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>
);
})}
</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> </div>
</div>
{/* Contact */} {/* Bottom Copyright Bar */}
<div> <div className="border-t border-[#2A104E] bg-[#090314] py-9 lg:py-10 px-4 mt-20">
<h4 className="text-white font-semibold mb-4 text-sm uppercase tracking-wider" style={{ fontFamily: 'Poppins, sans-serif' }}> <div className="container-custom flex flex-col sm:flex-row items-center justify-between gap-5">
Campus Location & Contact <p className="text-purple-300/70 text-sm lg:text-base font-medium" style={{ fontFamily: 'Inter, sans-serif' }}>
</h4> © {new Date().getFullYear()} RIT Freshers Hub. Built for Rajalakshmi Institute of Technology Students.
<ul className="space-y-3"> </p>
<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]" /> <div className="flex items-center gap-6 text-sm lg:text-base font-medium text-purple-300/70">
<span>Rajalakshmi Institute of Technology,<br />Kuthambakkam, Chennai - 600 124</span> <a href="#" className="hover:text-orange-400 transition-colors">
</li> Privacy Policy
<li className="flex items-center gap-3 text-slate-400 text-sm"> </a>
<Phone className="w-4 h-4 shrink-0 text-[#F97316]" /> <span className="text-purple-900">·</span>
<a href="tel:+914423422890" className="hover:text-[#F97316] transition-colors">+91 44 2342 2890</a> <a href="#" className="hover:text-orange-400 transition-colors">
</li> Terms of Use
<li className="flex items-center gap-3 text-slate-400 text-sm"> </a>
<Mail className="w-4 h-4 shrink-0 text-[#F97316]" /> <span className="text-purple-900">·</span>
<a href="mailto:info@rit.ac.in" className="hover:text-[#F97316] transition-colors">info@rit.ac.in</a> <a href="#" className="hover:text-orange-400 transition-colors">
</li> Accessibility
</ul> </a>
</div>
</div> </div>
</div> </div>
</div> </footer>
</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.
</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>
</div>
</div>
</footer>
); );
} }

View File

@@ -1,166 +1,238 @@
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
import { Link, useLocation } from 'react-router-dom'; import { Link, useLocation } from 'react-router-dom';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { Menu, X, LogIn, LogOut, ShieldCheck, ChevronDown, Rocket, Sparkles } from 'lucide-react'; import {
import { NAV_LINKS } from '@/constants'; 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'; 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() { export default function Navbar() {
const [isScrolled, setIsScrolled] = useState(false);
const [isMobileOpen, setIsMobileOpen] = useState(false); const [isMobileOpen, setIsMobileOpen] = useState(false);
const [isProfileOpen, setIsProfileOpen] = useState(false); const [isProfileOpen, setIsProfileOpen] = useState(false);
const [isMoreOpen, setIsMoreOpen] = useState(false);
const location = useLocation(); const location = useLocation();
const { user, isAuthenticated, isVerifiedStudent, loginWithGoogle, logout } = useAuth(); const { user, isAuthenticated, isVerifiedStudent, loginWithGoogle, logout } = useAuth();
const profileRef = useRef<HTMLDivElement>(null); const profileRef = useRef<HTMLDivElement>(null);
const moreRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleScroll = () => setIsScrolled(window.scrollY > 20);
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
useEffect(() => { useEffect(() => {
setIsMobileOpen(false); setIsMobileOpen(false);
setIsMoreOpen(false);
}, [location.pathname]); }, [location.pathname]);
// Close profile dropdown when clicking outside // Close dropdowns when clicking outside
useEffect(() => { useEffect(() => {
const handleClickOutside = (e: MouseEvent) => { const handleClickOutside = (e: MouseEvent) => {
if (profileRef.current && !profileRef.current.contains(e.target as Node)) { if (profileRef.current && !profileRef.current.contains(e.target as Node)) {
setIsProfileOpen(false); setIsProfileOpen(false);
} }
if (moreRef.current && !moreRef.current.contains(e.target as Node)) {
setIsMoreOpen(false);
}
}; };
document.addEventListener('mousedown', handleClickOutside); document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside);
}, []); }, []);
const checkIsActive = (linkPath: string) => { const checkIsActive = (linkPath: string) => {
const [pathName, searchString] = linkPath.split('?'); const [pathName] = linkPath.split('?');
if (location.pathname !== pathName) return false; if (pathName === '/') return location.pathname === '/';
return location.pathname.startsWith(pathName);
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 isMoreActive = MORE_LINKS.some((link) => checkIsActive(link.path));
return ( return (
<> <>
<motion.nav {/* Unified Floating Navbar Container */}
initial={{ y: -100, opacity: 0 }} <header className="fixed top-3.5 left-1/2 -translate-x-1/2 z-50 w-[95%] max-w-[1440px] pointer-events-none">
animate={{ y: 0, opacity: 1 }} <motion.nav
transition={{ duration: 0.6, ease: 'easeOut' }} initial={{ y: -60, opacity: 0 }}
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)]" animate={{ y: 0, opacity: 1 }}
style={{ transition={{ duration: 0.45, ease: 'easeOut' }}
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%)', 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"
backdropFilter: 'blur(16px)', >
WebkitBackdropFilter: 'blur(16px)', {/* Left: Logo & College Brand */}
borderRadius: '20px', <Link to="/" className="flex items-center gap-2.5 group shrink-0">
border: '1px solid rgba(255, 255, 255, 0.1)',
}}
>
<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 */}
<motion.img <motion.img
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.06 }}
src="/logo.png" src="/logo.png"
alt="RIT Logo" 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"> <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 rit
</span> </span>
<div className="h-7 w-[1px] bg-white/20 mx-2" /> <div className="h-4 lg:h-5 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' }}> <div
<span>RAJALAKSHMI</span> className="hidden xl:flex flex-col text-[7.5px] lg:text-[8px] font-bold text-slate-300 leading-[1.15] tracking-wider uppercase"
<span>INSTITUTE OF</span> style={{ fontFamily: 'Inter, sans-serif' }}
<span>TECHNOLOGY</span> >
<span>Rajalakshmi</span>
<span>Institute of</span>
<span>Technology</span>
</div> </div>
</div> </div>
</a> </Link>
{/* Desktop Nav */} {/* Center: Desktop Navigation Links */}
<div className="hidden md:flex items-center gap-1"> <div className="hidden lg:flex items-center gap-1 xl:gap-1.5 min-w-0">
{NAV_LINKS.map((link) => { {PRIMARY_LINKS.map((link) => {
const isActive = checkIsActive(link.path); const isActive = checkIsActive(link.path);
return ( return (
<Link <Link
key={link.path} key={link.path}
to={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" 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 ${
style={{ isActive ? 'text-white' : 'text-slate-300 hover:text-white hover:bg-white/10'
fontFamily: 'Poppins, sans-serif', }`}
color: isActive ? '#FFFFFF' : '#CBD5E1', style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
}}
> >
{isActive && ( {isActive && (
<motion.div <motion.div
layoutId="nav-active" layoutId="nav-pill-active"
className="absolute inset-0 rounded-xl" className="absolute inset-0 rounded-xl bg-gradient-to-r from-[#FF6B00] to-[#F97316] shadow-sm"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }} transition={{ type: 'spring', bounce: 0.15, duration: 0.35 }}
transition={{ type: 'spring', bounce: 0.2, duration: 0.4 }}
/> />
)} )}
<span className="relative z-10">{link.label}</span> <span className="relative z-10">{link.label}</span>
</Link> </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> </div>
{/* Right Side: Auth + Mobile Toggle */} {/* Right: Auth Profile / Sign In */}
<div className="flex items-center gap-2"> <div className="hidden lg:flex items-center gap-2 shrink-0">
{/* 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 */}
{isAuthenticated && user ? ( {isAuthenticated && user ? (
<div className="relative" ref={profileRef}> <div className="relative" ref={profileRef}>
<button <button
onClick={() => setIsProfileOpen(!isProfileOpen)} 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 ? ( {user.pictureUrl ? (
<img <img
src={user.pictureUrl} src={user.pictureUrl}
alt={user.name} 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" 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()} {user.name.charAt(0).toUpperCase()}
</div> </div>
)} )}
<div className="hidden lg:flex flex-col items-start"> <span
<span className="text-white text-[11px] font-semibold leading-tight max-w-[100px] truncate"> className="text-white text-xs font-semibold max-w-[100px] truncate"
{user.name.split(' ')[0]} style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
</span> >
{isVerifiedStudent && ( {user.name.split(' ')[0]}
<span className="flex items-center gap-0.5 text-[9px] text-emerald-400 font-bold"> </span>
<ShieldCheck className="w-2.5 h-2.5" /> Verified <ChevronDown className="w-3.5 h-3.5 text-slate-400" />
</span>
)}
</div>
<ChevronDown className="w-3 h-3 text-slate-400 hidden lg:block" />
</button> </button>
{/* Profile Dropdown */} {/* Profile Dropdown */}
@@ -171,9 +243,9 @@ export default function Navbar() {
animate={{ opacity: 1, y: 0, scale: 1 }} animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, y: 8, scale: 0.95 }} exit={{ opacity: 0, y: 8, scale: 0.95 }}
transition={{ duration: 0.15 }} 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"> <div className="flex items-center gap-3">
{user.pictureUrl ? ( {user.pictureUrl ? (
<img <img
@@ -183,7 +255,7 @@ export default function Navbar() {
referrerPolicy="no-referrer" 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()} {user.name.charAt(0).toUpperCase()}
</div> </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"> <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"> <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> </span>
</div> </div>
)} )}
@@ -211,9 +283,9 @@ export default function Navbar() {
logout(); logout();
setIsProfileOpen(false); 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 Sign Out
</button> </button>
</div> </div>
@@ -226,101 +298,97 @@ export default function Navbar() {
whileHover={{ scale: 1.03 }} whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }} whileTap={{ scale: 0.97 }}
onClick={loginWithGoogle} 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" 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: 'Poppins, sans-serif' }} style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
> >
<LogIn className="w-3.5 h-3.5" /> <LogIn className="w-3.5 h-3.5 text-[#FF6B00]" />
Sign In <span>Sign In</span>
</motion.button> </motion.button>
)} )}
</div>
{/* Mobile Toggle */} {/* Mobile Hamburger Toggle */}
<div className="lg:hidden flex items-center">
<motion.button <motion.button
whileTap={{ scale: 0.9 }} whileTap={{ scale: 0.9 }}
onClick={() => setIsMobileOpen(!isMobileOpen)} 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" aria-label="Toggle menu"
> >
{isMobileOpen ? <X className="w-5 h-5 stroke-[2.5]" /> : <Menu className="w-5 h-5 stroke-[2.5]" />} {isMobileOpen ? <X className="w-5 h-5 stroke-[2.5]" /> : <Menu className="w-5 h-5 stroke-[2.5]" />}
</motion.button> </motion.button>
</div> </div>
</div>
{/* Mobile Menu */} {/* Mobile Menu Dropdown */}
<AnimatePresence> <AnimatePresence>
{isMobileOpen && ( {isMobileOpen && (
<motion.div <motion.div
initial={{ height: 0, opacity: 0 }} initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }} animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }} exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.25 }} 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"> <div className="p-4 space-y-3">
{NAV_LINKS.map((link) => { <div className="grid grid-cols-2 gap-2">
const isActive = checkIsActive(link.path); {ALL_MOBILE_LINKS.map((link) => {
return ( const isActive = checkIsActive(link.path);
<Link const IconComp = link.icon;
key={link.path} return (
to={link.path} <Link
onClick={() => setIsMobileOpen(false)} key={link.path}
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]" to={link.path}
style={{ onClick={() => setIsMobileOpen(false)}
fontFamily: 'Poppins, sans-serif', className={`px-3 py-2.5 rounded-xl text-xs font-semibold flex items-center gap-2 transition-all ${
color: isActive ? '#FFFFFF' : '#CBD5E1', isActive
backgroundColor: isActive ? '#F97316' : 'rgba(255, 255, 255, 0.05)', ? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] text-white shadow-sm'
border: isActive ? '1px solid #FB923C' : '1px solid rgba(255, 255, 255, 0.08)', : 'bg-white/5 text-slate-200 hover:bg-white/10 border border-white/5'
}} }`}
> style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
{link.label} >
</Link> {IconComp && <IconComp className="w-3.5 h-3.5 opacity-80" />}
); <span className="truncate">{link.label}</span>
})} </Link>
);
})}
</div>
{/* Mobile RAISE Button */} {/* Mobile Auth Button */}
<Link <div className="pt-2 border-t border-white/10">
to="/raise" {!isAuthenticated ? (
onClick={() => setIsMobileOpen(false)} <button
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" onClick={() => {
style={{ fontFamily: 'Poppins, sans-serif' }} loginWithGoogle();
> setIsMobileOpen(false);
<Sparkles className="w-4 h-4 text-amber-200" /> }}
Pitch your Idea - RAISE 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"
</Link> style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
>
<LogIn className="w-4 h-4 text-orange-400" />
Sign In with Google
</button>
) : (
<button
onClick={() => {
logout();
setIsMobileOpen(false);
}}
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>
{/* Mobile Auth Button */} {/* Spacer to prevent content overlap */}
{!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' }}
>
<LogIn className="w-4 h-4" />
Sign In with Google
</button>
) : (
<button
onClick={() => {
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' }}
>
<LogOut className="w-4 h-4" />
Sign Out ({user?.name.split(' ')[0]})
</button>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</motion.nav>
{/* Spacer */}
<div className="h-20" /> <div className="h-20" />
</> </>
); );

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -109,17 +109,17 @@ export default function AIAssistant() {
}; };
return ( return (
<div className="min-h-screen flex flex-col bg-[#FAFAFA]"> <div className="min-h-screen flex flex-col bg-[#FAF9FC]">
{/* Header */} {/* 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="container-custom flex items-center justify-between">
<div className="flex items-center gap-3"> <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" /> <Bot className="w-5.5 h-5.5 text-white" />
</div> </div>
<div> <div>
<h1 className="font-bold text-[#1E293B] text-base" style={{ fontFamily: 'Poppins, sans-serif' }}>RIT Assistant</h1> <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-[#64748B]"> <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" /> <span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
Campus Knowledge Engine Online Campus Knowledge Engine Online
</div> </div>
@@ -139,19 +139,6 @@ export default function AIAssistant() {
<Send className="w-3.5 h-3.5" /> <Send className="w-3.5 h-3.5" />
<span className="hidden sm:inline">Telegram Bot</span> <span className="hidden sm:inline">Telegram Bot</span>
</a> </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 <motion.button
whileHover={{ scale: 1.03 }} whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }} whileTap={{ scale: 0.97 }}
@@ -187,8 +174,8 @@ export default function AIAssistant() {
{/* Chat Container */} {/* Chat Container */}
<div <div
ref={chatContainerRef} 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" 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 8px 30px -6px rgba(0,0,0,0.04)' }} style={{ minHeight: '420px', maxHeight: '65vh', boxShadow: '0 4px 20px_-4px_rgba(19,9,36,0.04)' }}
> >
{messages.map((msg) => ( {messages.map((msg) => (
<motion.div <motion.div
@@ -199,17 +186,17 @@ export default function AIAssistant() {
className={`flex gap-3 ${msg.role === 'user' ? 'flex-row-reverse' : ''}`} className={`flex gap-3 ${msg.role === 'user' ? 'flex-row-reverse' : ''}`}
> >
{msg.role === 'assistant' && ( {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" /> <Bot className="w-4 h-4 text-white" />
</div> </div>
)} )}
<div <div
className="max-w-[85%] sm:max-w-[78%] px-4 py-3 text-sm leading-relaxed" className="max-w-[85%] sm:max-w-[78%] px-4 py-3 text-sm leading-relaxed"
style={{ style={{
background: msg.role === 'user' ? 'linear-gradient(135deg, #F97316, #EA580C)' : '#F8FAFC', background: msg.role === 'user' ? 'linear-gradient(135deg, #FF6B00, #F97316)' : '#FFFFFF',
color: msg.role === 'user' ? 'white' : '#1E293B', color: msg.role === 'user' ? 'white' : '#1A0B2E',
borderRadius: msg.role === 'user' ? '20px 20px 4px 20px' : '20px 20px 20px 4px', 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', fontFamily: 'Inter, sans-serif',
}} }}
dangerouslySetInnerHTML={{ __html: formatContent(msg.content) }} dangerouslySetInnerHTML={{ __html: formatContent(msg.content) }}

View File

@@ -50,18 +50,18 @@ export default function BusRoutes() {
); );
return ( 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 */} {/* 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 className="max-w-7xl mx-auto flex flex-col md:flex-row md:items-center justify-between gap-4">
<div> <div>
<div className="flex items-center gap-2 text-xs text-slate-400 mb-1"> <div className="flex items-center gap-2 text-xs text-purple-300/80 mb-1">
<Link to="/" className="hover:text-orange-400">Home</Link> <Link to="/" className="hover:text-[#FF6B00]">Home</Link>
<ChevronRight className="w-3 h-3" /> <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>
<div className="flex items-center gap-3"> <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 RIT Live Transport Map
</h1> </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"> <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 */} {/* Controls: Search + Toggle Drawer Button */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="relative flex-1 sm:w-64"> <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 <input
type="text" type="text"
placeholder="Search route or bus no..." placeholder="Search route or bus no..."
value={busSearch} value={busSearch}
onChange={(e) => setBusSearch(e.target.value)} 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' }} style={{ fontFamily: 'Inter, sans-serif' }}
/> />
</div> </div>
<button <button
onClick={() => setIsSidebarOpen(!isSidebarOpen)} 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 isSidebarOpen
? 'bg-orange-500 text-white border-orange-400 shadow-lg shadow-orange-500/20' ? 'bg-gradient-to-r from-[#FF6B00] to-[#F97316] 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-[#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" />} {isSidebarOpen ? <X className="w-4 h-4" /> : <Menu className="w-4 h-4" />}
<span className="hidden sm:inline"> <span className="hidden sm:inline">

View File

@@ -32,38 +32,37 @@ const BLUEPRINT_LEGEND = [
]; ];
const LOCATION_SPOTS: LocationSpot[] = [ 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: 'Girls Hostel', x: 29.4, y: 7.3, 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: 'Playground', x: 48.5, y: 8.8, 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: 'Canteen', x: 68.3, y: 8.3, 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: '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: 14.5, y: 31.0, category: 'Hostel / Mess', desc: 'Dedicated Dining Hall for Women', 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: 14.0, y: 39.5, category: 'Lab / Academic', desc: 'Mechanical Engineering Laboratory', 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: 13.0, y: 47.5, category: 'Lab / Academic', desc: 'Engineering Practices Laboratory', area: 'West Wing' }, { name: 'EPL Lab', x: 21.3, y: 43.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.4, y: 16.1, category: 'Amenity', desc: 'Iconic Landmark Monument', area: 'Central Park' },
{ name: 'Astronaut Statue', x: 51.0, y: 23.0, 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: 'Campus Centre', x: 67.0, y: 23.0, 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: 70.0, y: 37.0, category: 'Amenity', desc: 'Student Discussion & Rest Area', area: 'Central East' }, { name: 'Hut', x: 66.8, y: 31.3, 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: 'Homi Jehangir Bhabha Block', x: 79.3, y: 22.9, 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: 'C Block', x: 78.6, y: 37.6, category: 'C Block', desc: 'Labs, Classrooms & Staff Rooms', 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: 'Alan Turing Block', x: 66.8, y: 50.8, category: 'Lab / Academic', desc: 'Computer Science & AI Labs', area: 'East Wing' },
{ name: 'C Block', x: 87.0, y: 38.0, category: 'C Block', desc: 'Labs, Classrooms & Staff Rooms', area: 'East Wing' }, { name: 'Arcade', x: 36.7, y: 53.2, category: 'Amenity', desc: 'Student Hub & Stores', area: 'Central South' },
{ name: 'Alan Turing Block', x: 77.0, y: 53.0, category: 'Lab / Academic', desc: 'Computer Science & AI Labs', area: 'East Wing' }, { name: 'Green Building', x: 27.2, y: 63.5, category: 'Admin / Facility', desc: 'Eco-Friendly Academic Complex & Library', area: 'South West' },
{ name: 'Arcade', x: 35.0, y: 57.0, category: 'Amenity', desc: 'Student Hub & Stores', area: 'Central South' }, { name: 'Security Booth', x: 44.8, y: 65.9, category: 'Amenity', desc: 'Main Gate Security Checkpoint', area: 'South Entrance' },
{ name: 'Green Building', x: 23.0, y: 71.0, category: 'Admin / Facility', desc: 'Eco-Friendly Academic Complex & Library', area: 'South West' }, { name: 'ATM', x: 44.3, y: 73.0, category: 'Amenity', desc: '24/7 Campus Banking ATM', area: 'South Entrance' },
{ name: 'Security Booth', x: 49.0, y: 77.0, category: 'Amenity', desc: 'Main Gate Security Checkpoint', area: 'South Entrance' }, { name: 'Bike Parking', x: 65.3, y: 61.5, category: 'Parking Area', desc: 'Two-Wheeler Vehicle Parking', area: 'South East' },
{ name: 'ATM', x: 49.0, y: 86.0, category: 'Amenity', desc: '24/7 Campus Banking ATM', area: 'South Entrance' }, { name: 'Generator Room', x: 63.1, y: 71.8, category: 'Admin / Facility', desc: 'Campus Power & Utility Station', area: 'South East' },
{ name: 'Bike Parking', x: 75.0, y: 67.0, category: 'Parking Area', desc: 'Two-Wheeler Vehicle Parking', area: 'South East' }, { name: 'Side Gate', x: 22.8, y: 75.0, category: 'Amenity', desc: 'Secondary Campus Gate', area: 'South West' },
{ name: 'Generator Room', x: 76.0, y: 81.0, category: 'Admin / Facility', desc: 'Campus Power & Utility Station', area: 'South East' }, { name: 'Main Gate', x: 49.2, y: 77.9, category: 'Amenity', desc: 'Primary Campus Entrance', area: 'South Entrance' },
{ name: 'Side Gate', x: 23.0, y: 93.0, category: 'Amenity', desc: 'Secondary Campus Gate', area: 'South West' }, { name: 'Bus Stop', x: 49.6, y: 92.8, category: 'Amenity', desc: 'Public & College Bus Boarding Point', area: 'Highway' },
{ name: 'Main Gate', x: 59.0, y: 93.0, category: 'Amenity', desc: 'Primary Campus Entrance', area: 'South Entrance' },
]; ];
const CATEGORY_MAP_SPOTS: Record<string, string[]> = { 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'], dept: ['Homi Jehangir Bhabha Block', 'Nikola Tesla Block', 'C Block', 'Steve Jobs Block', 'Alan Turing Block', 'Green Building'],
library: ['Green Building'], library: ['Green Building'],
labs: ['MEC Lab', 'EPL Lab', 'Steve Jobs Block', 'Alan Turing Block', 'Nikola Tesla Block', 'C Block'], labs: ['MEC Lab', 'EPL Lab', 'Steve Jobs Block', 'Alan Turing Block', 'Nikola Tesla Block', 'C Block'],
hostel: ['Girls Hostel', 'Boys Mess'], hostel: ['Girls Hostel', 'Girls Mess'],
canteen: ['Canteen', 'Girls Mess', 'Boys Mess', 'Staff Mess', 'Arcade'], canteen: ['Canteen', 'Girls Mess', 'Arcade'],
auditorium: ['Steve Jobs Block', 'Campus Centre'], auditorium: ['Steve Jobs Block', 'Campus Centre'],
sports: ['Playground'], sports: ['Playground'],
}; };
@@ -148,27 +147,27 @@ export default function Campus() {
{/* Header */} {/* Header */}
<div className="bg-white border-b border-[#E5E7EB] py-10"> <div className="bg-white border-b border-[#E5E7EB] py-10">
<div className="container-custom"> <div className="container-custom">
<div className="flex items-center gap-2 text-xs text-[#94A3B8] mb-3"> <div className="flex items-center gap-2 text-xs text-[#9E91B6] mb-3">
<Link to="/" className="hover:text-[#F97316]">Home</Link> <Link to="/" className="hover:text-[#FF6B00]">Home</Link>
<ChevronRight className="w-3 h-3" /> <ChevronRight className="w-3 h-3" />
<span className="text-[#F97316]">Campus Map</span> <span className="text-[#FF6B00] font-semibold">Campus Map</span>
</div> </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{' '} 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 Campus Map
</span> </span>
</h1> </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. Navigate campus layout, click location categories or blocks to spot exact places on the blueprint.
</p> </p>
</div> </div>
</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 }}> <motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
{/* Map Viewer Header Bar */} {/* 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>
<div className="flex items-center gap-2 mb-1"> <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"> <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 <CheckCircle2 className="w-3 h-3" /> Interactive Location Spotter
</span> </span>
</div> </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 Rajalakshmi Institute of Technology
</h2> </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" /> <MapPin className="w-3.5 h-3.5 text-orange-400" />
Kuthambakkam, Chennai • Interactive Map Spotting Enabled Kuthambakkam, Chennai • Interactive Map Spotting Enabled
</p> </p>
@@ -189,23 +188,23 @@ export default function Campus() {
{/* Toolbar Controls */} {/* Toolbar Controls */}
<div className="flex items-center flex-wrap gap-2"> <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"> <div className="flex items-center bg-[#1E0C36] rounded-xl p-1 border border-[#3A1968]">
<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"> <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" /> <ZoomOut className="w-4 h-4" />
</button> </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"> <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)}% {Math.round(zoomLevel * 100)}%
</button> </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" /> <ZoomIn className="w-4 h-4" />
</button> </button>
</div> </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" /> <Maximize2 className="w-4 h-4" />
</button> </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 className="w-3.5 h-3.5" />
Download Download
</button> </button>
@@ -269,9 +268,9 @@ export default function Campus() {
</div> </div>
{/* Quick Location Categories Bar */} {/* 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"> <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 && ( {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> <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; const isCatActive = activeCategory === loc.id;
return ( 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'}`}> <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" />} {Icon && <Icon className="w-5 h-5" />}
</div> </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> </motion.button>
); );
})} })}
@@ -293,16 +292,16 @@ export default function Campus() {
</div> </div>
{/* Blueprint Legend & Color Key */} {/* 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"> <div className="flex items-center gap-2 mb-5">
<Layers className="w-5 h-5 text-[#F97316]" /> <Layers className="w-5 h-5 text-[#FF6B00]" />
<h3 className="text-xl font-bold text-[#1E293B]" style={{ fontFamily: 'Playfair Display, serif' }}>Blueprint Map Legend</h3> <h3 className="text-xl font-bold text-[#1A0B2E]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>Blueprint Map Legend</h3>
</div> </div>
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
{BLUEPRINT_LEGEND.map((item) => { {BLUEPRINT_LEGEND.map((item) => {
const isActive = activeLegend === item.id; const isActive = activeLegend === item.id;
return ( 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="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> <span className="text-xs font-semibold" style={{ fontFamily: 'Inter, sans-serif' }}>{item.label}</span>
</button> </button>
@@ -312,28 +311,28 @@ export default function Campus() {
</div> </div>
{/* Blueprint Facilities Index */} {/* 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 className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 mb-6">
<div> <div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Building2 className="w-5 h-5 text-[#F97316]" /> <Building2 className="w-5 h-5 text-[#FF6B00]" />
<h3 className="text-xl font-bold text-[#1E293B]" style={{ fontFamily: 'Playfair Display, serif' }}>Campus Blocks & Facilities Index</h3> <h3 className="text-xl font-bold text-[#1A0B2E]" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>Campus Blocks & Facilities Index</h3>
</div> </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> </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>
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{filteredFacilities.map((facility) => { {filteredFacilities.map((facility) => {
const isSelected = selectedFacility?.name === facility.name; const isSelected = selectedFacility?.name === facility.name;
const isHighlighted = highlightedSpotNames.has(facility.name); const isHighlighted = highlightedSpotNames.has(facility.name);
return ( 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"> <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-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-[#E5E7EB] text-[#475569]'}`}>{facility.area}</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> </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> </motion.button>
); );
})} })}

View File

@@ -261,24 +261,24 @@ export default function Community() {
}; };
return ( return (
<div className="min-h-screen" style={{ backgroundColor: '#FAFBFD' }}> <div className="min-h-screen bg-[#FAF9FC]">
{/* Header */} {/* 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"> <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 RIT Community
</h1> </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. Ask questions and connect with fellow RIT students.
</p> </p>
</div> </div>
</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="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div className="lg:col-span-2"> {/* Ask Question Box */} <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)' }}> <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-semibold text-[#1E293B] mb-3 tracking-tight" style={{ fontFamily: 'Poppins, sans-serif' }}>Ask a Question</h3> <h3 className="text-base font-bold text-[#1A0B2E] mb-3 tracking-tight" style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}>Ask a Question</h3>
{showSuccessBanner && ( {showSuccessBanner && (
<motion.div <motion.div

View File

@@ -24,7 +24,6 @@ export interface CollabRequestItem {
acceptedCount?: number; acceptedCount?: number;
contactInfo?: string; contactInfo?: string;
telegramChatId?: number; telegramChatId?: number;
discordUserId?: string;
status: string; status: string;
applicationsCount: number; applicationsCount: number;
createdAt: string; createdAt: string;
@@ -432,23 +431,23 @@ export default function DevCollab() {
{/* Header */} {/* Header */}
<div className="bg-white border-b border-[#E5E7EB] py-10"> <div className="bg-white border-b border-[#E5E7EB] py-10">
<div className="container-custom"> <div className="container-custom">
<div className="flex items-center gap-2 text-xs text-[#94A3B8] mb-3"> <div className="flex items-center gap-2 text-xs text-[#9E91B6] mb-3">
<Link to="/" className="hover:text-[#F97316]">Home</Link> <Link to="/" className="hover:text-[#FF6B00]">Home</Link>
<ChevronRight className="w-3 h-3" /> <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>
<div className="flex flex-col md:flex-row md:items-center justify-between gap-6"> <div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div> <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 <Code2 className="w-4 h-4" /> RIT Developers Environment
</div> </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{' '} 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 Collaborators
</span> </span>
</h1> </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! Post open-source projects, search for co-developers, or manage candidate applications cleanly on the website or via Telegram!
</p> </p>
</div> </div>
@@ -457,8 +456,8 @@ export default function DevCollab() {
whileHover={{ scale: 1.03 }} whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }} whileTap={{ scale: 0.97 }}
onClick={() => requireVerifiedStudent(() => setShowPostModal(true))} 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" 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, #F97316, #FB923C)', fontFamily: 'Poppins, sans-serif' }} style={{ background: 'linear-gradient(135deg, #FF6B00, #F97316)', fontFamily: 'Plus Jakarta Sans, sans-serif' }}
> >
{isVerifiedStudent && <ShieldCheck className="w-4 h-4" />} {isVerifiedStudent && <ShieldCheck className="w-4 h-4" />}
<Plus className="w-4.5 h-4.5" /> <Plus className="w-4.5 h-4.5" />
@@ -467,15 +466,15 @@ export default function DevCollab() {
</div> </div>
{/* Dashboard Tabs Bar */} {/* 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 <button
onClick={() => setActiveTab('explore')} onClick={() => setActiveTab('explore')}
className={`px-5 py-3 text-xs font-bold transition-all border-b-2 flex items-center gap-2 cursor-pointer ${ className={`px-5 py-3 text-xs font-bold transition-all border-b-2 flex items-center gap-2 cursor-pointer ${
activeTab === 'explore' activeTab === 'explore'
? 'border-[#F97316] text-[#F97316]' ? 'border-[#FF6B00] text-[#FF6B00]'
: 'border-transparent text-slate-500 hover:text-slate-800' : '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" /> <Code2 className="w-4 h-4" />
Explore Projects ({requests.length}) Explore Projects ({requests.length})

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,23 @@ import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/An
import { FACULTY_DATA, DEPARTMENTS } from '@/constants'; import { FACULTY_DATA, DEPARTMENTS } from '@/constants';
import { Link } from 'react-router-dom'; 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() { export default function Faculty() {
const [searchFaculty, setSearchFaculty] = useState(''); const [searchFaculty, setSearchFaculty] = useState('');
const [selectedDept, setSelectedDept] = useState('All Departments'); const [selectedDept, setSelectedDept] = useState('All Departments');
@@ -15,23 +32,39 @@ export default function Faculty() {
const filtered = FACULTY_DATA.filter((f) => { const filtered = FACULTY_DATA.filter((f) => {
const searchLower = searchFaculty.trim().toLowerCase(); const searchLower = searchFaculty.trim().toLowerCase();
const matchSearch = const matchSearch =
f.name.toLowerCase().includes(searchLower) || (f.name && f.name.toLowerCase().includes(searchLower)) ||
f.department.toLowerCase().includes(searchLower) || (f.department && f.department.toLowerCase().includes(searchLower)) ||
f.designation.toLowerCase().includes(searchLower) || (f.designation && f.designation.toLowerCase().includes(searchLower)) ||
(f.specialization && f.specialization.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; const matchDept = selectedDept === 'All Departments' || f.department === selectedDept;
return matchSearch && matchDept; return matchSearch && matchDept;
}); });
return filtered.sort((a, b) => { return filtered.sort((a, b) => {
if (sortBy === 'Name A-Z') return a.name.localeCompare(b.name); if (sortBy === 'Name A-Z') return (a.name || '').localeCompare(b.name || '');
if (sortBy === 'Department') return a.department.localeCompare(b.department); if (sortBy === 'Department') return (a.department || '').localeCompare(b.department || '');
if (sortBy === 'Designation') return a.designation.localeCompare(b.designation); if (sortBy === 'Designation') return (a.designation || '').localeCompare(b.designation || '');
return 0; return 0;
}); });
}, [searchFaculty, selectedDept, sortBy]); }, [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 ( return (
<div className="min-h-screen relative overflow-hidden bg-[#F8FAFC]"> <div className="min-h-screen relative overflow-hidden bg-[#F8FAFC]">
{/* Background decorations */} {/* Background decorations */}
@@ -44,26 +77,26 @@ export default function Faculty() {
<div className="relative z-10"> <div className="relative z-10">
{/* Header */} {/* 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="container-custom">
<div className="flex items-center gap-2 text-xs text-[#94A3B8] mb-3"> <div className="flex items-center gap-2 text-xs text-[#9E91B6] mb-3">
<Link to="/" className="hover:text-[#F97316]">Home</Link> <Link to="/" className="hover:text-[#FF6B00]">Home</Link>
<ChevronRight className="w-3 h-3" /> <ChevronRight className="w-3 h-3" />
<span className="text-[#F97316]">Faculty Directory</span> <span className="text-[#FF6B00] font-semibold">Faculty Directory</span>
</div> </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{' '} 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 Directory
</span> </span>
</h1> </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. Browse and connect with experienced faculty members across all departments.
</p> </p>
</div> </div>
</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 */} {/* Under Development Banner */}
<motion.div <motion.div
initial={{ opacity: 0, y: -10 }} initial={{ opacity: 0, y: -10 }}
@@ -87,15 +120,15 @@ export default function Faculty() {
</motion.div> </motion.div>
{/* Faculty Hero Section */} {/* Faculty Hero Section */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 items-stretch"> <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="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" /> <Users className="w-7 h-7" />
</div> </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 Faculty Directory
</h2> </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. Browse and connect with experienced faculty members. Search by name, department, or specialization.
</p> </p>
</div> </div>
@@ -112,15 +145,21 @@ export default function Faculty() {
{ abrv: 'CIVIL', full: 'Civil Engineering', color: 'emerald' }, { abrv: 'CIVIL', full: 'Civil Engineering', color: 'emerald' },
{ abrv: 'AI & DS', full: 'Artificial Intelligence & Data Science', color: 'pink' }, { abrv: 'AI & DS', full: 'Artificial Intelligence & Data Science', color: 'pink' },
{ abrv: 'EEE', full: 'Electrical & Electronics Engineering', color: 'yellow' }, { 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; const count = FACULTY_DATA.filter(f => f.department === d.full).length;
return ( 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 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"> <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`} /> <BookOpen className={`w-3.5 h-3.5 text-${d.color}-400`} />
</div> </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> </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]" /> <Search className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-[#94A3B8]" />
<input <input
type="text" type="text"
placeholder="Search by faculty name, department, designation or specialization..." placeholder="Search by faculty name, department, designation or interest..."
value={searchFaculty} value={searchFaculty}
onChange={(e) => setSearchFaculty(e.target.value)} 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" 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]" 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' }} 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; 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>; return <option key={dept} value={dept}>{dept} ({count})</option>;
})} })}
@@ -172,15 +211,38 @@ export default function Faculty() {
</div> </div>
</div> </div>
{/* Grid */} {/* Grouped Grid */}
{filteredAndSortedFaculty.length > 0 ? ( {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"> <div className="space-y-12 pb-20">
{filteredAndSortedFaculty.map((faculty) => ( {departmentOrder.map((deptName) => {
<StaggerItem key={faculty.id}> const facultyInDept = groupedFaculty[deptName];
<FacultyCard faculty={faculty} /> if (!facultyInDept || facultyInDept.length === 0) return null;
</StaggerItem> const deptInfo = DEPT_MAP[deptName] || { abrv: deptName, full: deptName };
))}
</StaggerContainer> 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"> <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" /> <Users className="w-12 h-12 text-[#CBD5E1] mb-4" />

View File

@@ -2,14 +2,12 @@ import { motion, useScroll, useTransform } from 'framer-motion';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { import {
ArrowRight, BookOpen, Bot, GraduationCap, ArrowRight, BookOpen, Bot, GraduationCap,
CheckCircle, Zap, Shield, Trophy, Award, Cpu, Users, MapPin, Sparkles CheckCircle, Zap, Shield, Trophy, Award, Cpu, Users, Sparkles
} from 'lucide-react'; } from 'lucide-react';
import SectionTitle from '@/components/SectionTitle/SectionTitle'; import SectionTitle from '@/components/SectionTitle/SectionTitle';
import FeatureCard from '@/components/FeatureCard/FeatureCard'; import FeatureCard from '@/components/FeatureCard/FeatureCard';
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer'; import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer'; import { FEATURES } from '@/constants';
import { FEATURES, CAMPUS_LOCATIONS } from '@/constants';
import * as LucideIcons from 'lucide-react';
import LazyVideoHero from '@/components/LazyVideoHero/LazyVideoHero'; import LazyVideoHero from '@/components/LazyVideoHero/LazyVideoHero';
@@ -33,7 +31,7 @@ export default function Home() {
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1 }} transition={{ duration: 0.6, delay: 0.1 }}
className="text-3xl md:text-5xl font-medium tracking-wide mb-2" 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 Welcome to
</motion.h1> </motion.h1>
@@ -42,8 +40,8 @@ export default function Home() {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.2 }} transition={{ duration: 0.7, delay: 0.2 }}
className="text-5xl md:text-7xl font-black leading-tight mb-6" className="text-5xl md:text-7xl font-extrabold tracking-tight leading-tight mb-6"
style={{ color: '#FFFFFF', fontFamily: 'Playfair Display, serif', textShadow: '0 4px 24px rgba(0,0,0,0.95)' }} style={{ color: '#FFFFFF', fontFamily: 'Plus Jakarta Sans, sans-serif', textShadow: '0 4px 24px rgba(0,0,0,0.95)' }}
> >
RIT Freshers Hub RIT Freshers Hub
</motion.h2> </motion.h2>
@@ -67,12 +65,12 @@ export default function Home() {
> >
<a href="#built-for-freshers"> <a href="#built-for-freshers">
<motion.button <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 }} 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={{ style={{
fontFamily: 'Poppins, sans-serif', fontFamily: 'Plus Jakarta Sans, sans-serif',
background: 'linear-gradient(135deg, #F97316, #FB923C)', background: 'linear-gradient(135deg, #FF6B00, #F97316)',
}} }}
> >
<BookOpen className="w-4 h-4" /> <BookOpen className="w-4 h-4" />
@@ -83,28 +81,15 @@ export default function Home() {
<Link to="/ai-assistant"> <Link to="/ai-assistant">
<motion.button <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 }} 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" 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: 'Poppins, sans-serif' }} 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 Ask Assistant
</motion.button> </motion.button>
</Link> </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>
</motion.div> </motion.div>
</section> </section>
@@ -117,28 +102,28 @@ export default function Home() {
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.6 }} 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"> <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: '20+', label: 'SPORTS', icon: Trophy },
{ value: '100+', label: 'FACULTY', icon: GraduationCap }, { 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: '24/7', label: 'ASSISTANT', icon: Cpu },
{ value: '5000+', label: 'STUDENTS', icon: Users }, { value: '5000+', label: 'STUDENTS', icon: Users },
].map((stat, i) => ( ].map((stat, i) => (
<div key={i} className="flex flex-col items-center justify-center p-3"> <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"> <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>
<div <div
className="text-2xl md:text-3xl font-black text-[#1E293B] mb-0.5" className="text-2xl md:text-3xl font-extrabold text-[#1A0B2E] mb-0.5"
style={{ fontFamily: 'Poppins, sans-serif' }} style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
> >
{stat.value} {stat.value}
</div> </div>
<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' }} style={{ fontFamily: 'Inter, sans-serif' }}
> >
{stat.label} {stat.label}
@@ -169,80 +154,6 @@ export default function Home() {
</StaggerContainer> </StaggerContainer>
</div> </div>
</section> </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> </div>
); );
} }

View File

@@ -125,7 +125,7 @@ export default function LeetcodeLeaderboard() {
const topProfile = profiles.length > 0 ? profiles[0] : null; const topProfile = profiles.length > 0 ? profiles[0] : null;
return ( 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 */} {/* Toast Notification */}
<AnimatePresence> <AnimatePresence>
{toastMessage && ( {toastMessage && (
@@ -133,7 +133,7 @@ export default function LeetcodeLeaderboard() {
initial={{ opacity: 0, y: -20 }} initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }} 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} {toastMessage}
</motion.div> </motion.div>
@@ -142,20 +142,19 @@ export default function LeetcodeLeaderboard() {
<div className="max-w-7xl mx-auto space-y-8"> <div className="max-w-7xl mx-auto space-y-8">
{/* Banner Header */} {/* 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="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-amber-500/5 rounded-full blur-3xl pointer-events-none" /> <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="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 z-10 flex flex-col lg:flex-row items-start lg:items-center justify-between gap-6"> <div className="relative z-10 flex flex-col lg:flex-row items-start lg:items-center justify-between gap-6">
<div> <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"> <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-amber-500 animate-pulse" /> <Flame className="w-4 h-4 text-[#FF6B00] animate-pulse" />
RIT LeetCode Arena RIT LeetCode Arena
</div> </div>
<h1 className="text-3xl sm:text-5xl font-extrabold tracking-tight text-[#1E293B] font-poppins"> <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-amber-400 via-orange-400 to-[#F97316]">LeetCode Leaderboard</span> Campus <span className="bg-clip-text text-transparent bg-gradient-to-r from-[#FF6B00] to-[#F97316]">LeetCode Leaderboard</span>
</h1> </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. Track top problem solvers across departments. Automatically updated every 24 hours with rate-limited, spaced background sync.
</p> </p>
</div> </div>
@@ -163,7 +162,8 @@ export default function LeetcodeLeaderboard() {
<div className="flex flex-wrap items-center gap-3 shrink-0"> <div className="flex flex-wrap items-center gap-3 shrink-0">
<button <button
onClick={() => setShowModal(true)} 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]" /> <Plus className="w-5 h-5 stroke-[2.5]" />
Register Handle Register Handle
@@ -171,9 +171,10 @@ export default function LeetcodeLeaderboard() {
<button <button
onClick={handleTriggerSync} onClick={handleTriggerSync}
disabled={isSyncing} 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 24h Spaced Sync
</button> </button>
</div> </div>
@@ -369,10 +370,10 @@ export default function LeetcodeLeaderboard() {
</div> </div>
{/* Desktop Table View (Medium Screens & Up) */} {/* Desktop Table View (Medium Screens & Up) */}
<div className="hidden md:block overflow-x-auto"> <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-slate-600 border-collapse"> <table className="w-full text-left text-[#4A3E5E] border-collapse">
<thead> <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 text-center w-16">Rank</th>
<th className="py-4 px-6">Student</th> <th className="py-4 px-6">Student</th>
<th className="py-4 px-6">Dept & Year</th> <th className="py-4 px-6">Dept & Year</th>

View File

@@ -309,7 +309,7 @@ const CS_TOOLS_CATEGORIES = [
bg: '#FEFCE8', bg: '#FEFCE8',
tools: [ tools: [
{ name: 'Documentation', desc: 'Confluence, Notion, MkDocs, and Docusaurus.', url: 'https://docusaurus.io/' }, { 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: '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: '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/' } { name: 'Knowledge Management', desc: 'Obsidian, Notion, and Slite workspace wiki.', url: 'https://obsidian.md/' }
@@ -1433,7 +1433,7 @@ export default function Notes() {
</div> </div>
)} )}
<div className="container-custom py-10"> <div className="container-custom pt-10 pb-32 lg:pb-44">
{selectedToolkit ? ( {selectedToolkit ? (

View File

@@ -7,11 +7,12 @@ import {
TrendingUp, UserCheck, ClipboardList, ShoppingCart, Cpu, Activity, LayoutGrid, TrendingUp, UserCheck, ClipboardList, ShoppingCart, Cpu, Activity, LayoutGrid,
Layers, Zap, Settings, Gauge, Radio, Wifi, Bot, Sun, Wrench, Binary, BatteryCharging, Layers, Zap, Settings, Gauge, Radio, Wifi, Bot, Sun, Wrench, Binary, BatteryCharging,
CircuitBoard, ShieldCheck, BrainCircuit, FlaskConical, Lock, Terminal, Dna, Microscope, 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'; } from 'lucide-react';
import SectionTitle from '@/components/SectionTitle/SectionTitle'; import SectionTitle from '@/components/SectionTitle/SectionTitle';
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer'; 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 }> = { const TOOLKIT_ICONS: Record<string, { icon: React.ComponentType<{ className?: string }>; bg: string; color: string }> = {
'CS tools': { icon: Code, bg: '#EFF6FF', color: '#3B82F6' }, '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' }, 'AI tools': { icon: BrainCircuit, bg: '#FDF2F8', color: '#EC4899' },
'Electrical and Electronic tools': { icon: Zap, bg: '#FEF3C7', color: '#D97706' }, 'Electrical and Electronic tools': { icon: Zap, bg: '#FEF3C7', color: '#D97706' },
'Biotech tools': { icon: Dna, bg: '#ECFEFF', color: '#06B6D4' }, 'Biotech tools': { icon: Dna, bg: '#ECFEFF', color: '#06B6D4' },
'GPA and internal calculator': { icon: Calculator, bg: '#FEF2F2', color: '#EF4444' },
}; };
const PROGRAMMING_LANGUAGES_GUIDES = [ const PROGRAMMING_LANGUAGES_GUIDES = [
@@ -276,7 +278,7 @@ const CS_TOOLS_CATEGORIES = [
bg: '#FEFCE8', bg: '#FEFCE8',
tools: [ tools: [
{ name: 'Documentation', desc: 'Confluence, Notion, MkDocs, and Docusaurus.', url: 'https://docusaurus.io/' }, { 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: '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: '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/' } { 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() { export default function Toolkit() {
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const selectedToolkit = searchParams.get('toolkit'); const selectedToolkit = searchParams.get('toolkit');
const [selectedCategory, setSelectedCategory] = useState<number | null>(null); const [selectedCategory, setSelectedCategory] = useState<number | null>(null);
const [selectedLanguage, setSelectedLanguage] = 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 [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) => { const setSelectedToolkit = (value: string | null) => {
if (value) { if (value) {
@@ -666,35 +846,45 @@ export default function Toolkit() {
setSelectedCategory(null); setSelectedCategory(null);
setSelectedLanguage(null); setSelectedLanguage(null);
setSelectedTool(null); setSelectedTool(null);
setGpaDepartment('');
setGpaSemester('');
setCalculationType(null);
setGpaStep(1);
setCalculatedScore(null);
} else { } else {
setSearchParams({}); setSearchParams({});
setSelectedCategory(null); setSelectedCategory(null);
setSelectedLanguage(null); setSelectedLanguage(null);
setSelectedTool(null); setSelectedTool(null);
setGpaDepartment('');
setGpaSemester('');
setCalculationType(null);
setGpaStep(1);
setCalculatedScore(null);
} }
}; };
const toolkitRef = useRef<HTMLDivElement>(null); const toolkitRef = useRef<HTMLDivElement>(null);
return ( return (
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}> <div className="min-h-screen bg-[#FAF9FC]">
{/* Header */} {/* Header */}
{!selectedToolkit && ( {!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"> <div className="container-custom">
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }}> <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' }}> <div className="flex items-center gap-2 text-xs text-[#9E91B6] mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>
<Link to="/" className="hover:text-[#F97316]">Home</Link> <Link to="/" className="hover:text-[#FF6B00]">Home</Link>
<ChevronRight className="w-3 h-3" /> <ChevronRight className="w-3 h-3" />
<span className="text-[#F97316]">Student Toolkit</span> <span className="text-[#FF6B00] font-semibold">Student Toolkit</span>
</div> </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{' '} 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 Toolkit
</span> </span>
</h1> </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. Access specialized development environments, IDE configurations, compiler guides, and department toolkits.
</p> </p>
</motion.div> </motion.div>
@@ -702,7 +892,7 @@ export default function Toolkit() {
</div> </div>
)} )}
<div className="container-custom py-10"> <div className="container-custom pt-10 pb-32 lg:pb-44">
{selectedToolkit ? ( {selectedToolkit ? (
(() => { (() => {
const TOOLKIT_MAP: Record<string, { categories: typeof CS_TOOLS_CATEGORIES; label: string }> = { 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' }, cybersecurity: { categories: CYBERSECURITY_TOOLS_CATEGORIES, label: 'Cybersecurity' },
ai: { categories: AI_TOOLS_CATEGORIES, label: 'Artificial Intelligence' }, ai: { categories: AI_TOOLS_CATEGORIES, label: 'Artificial Intelligence' },
biotech: { categories: BIOTECH_TOOLS_CATEGORIES, label: 'Biotechnology' }, biotech: { categories: BIOTECH_TOOLS_CATEGORIES, label: 'Biotechnology' },
gpa: { categories: [], label: 'GPA and Internal Calculator' },
}; };
const current = TOOLKIT_MAP[selectedToolkit]; const current = TOOLKIT_MAP[selectedToolkit];
if (!current) return null; if (!current) return null;
@@ -737,8 +928,8 @@ export default function Toolkit() {
setSelectedToolkit(null); 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" 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: 'Poppins, sans-serif' }} style={{ fontFamily: 'Plus Jakarta Sans, sans-serif' }}
> >
<ArrowLeft className="w-4 h-4" /> <ArrowLeft className="w-4 h-4" />
{selectedTool {selectedTool
@@ -750,8 +941,8 @@ export default function Toolkit() {
: 'Back to Toolkit'} : 'Back to Toolkit'}
</button> </button>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<h2 className="text-2xl md:text-3xl font-bold text-[#1E293B]" style={{ fontFamily: 'Playfair Display, serif' }}> <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, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>Toolkit</span> {label} <span style={{ background: 'linear-gradient(135deg, #FF6B00, #F97316)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>Toolkit</span>
</h2> </h2>
</div> </div>
<p className="text-[#64748B] text-sm mt-1" style={{ fontFamily: 'Inter, sans-serif' }}> <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}.` ? `Complete beginner guide, core concepts, use cases, and resources for ${PROGRAMMING_LANGUAGES_GUIDES[selectedLanguage].name}.`
: selectedCategory !== null : selectedCategory !== null
? `Browse essential ${categories[selectedCategory].title} software, guides, and developer resources.` ? `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.`} : `Explore all categories and tools curated for ${label} students.`}
</p> </p>
</motion.div> </motion.div>
{/* Main Content Area */} {/* 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 ── */ /* ── Detailed Programming Language Guide View ── */
(() => { (() => {
const guide = PROGRAMMING_LANGUAGES_GUIDES[selectedLanguage]; const guide = PROGRAMMING_LANGUAGES_GUIDES[selectedLanguage];
@@ -942,6 +1516,8 @@ export default function Toolkit() {
setSelectedToolkit('ai'); setSelectedToolkit('ai');
} else if (item.title === 'Biotech tools') { } else if (item.title === 'Biotech tools') {
setSelectedToolkit('biotech'); setSelectedToolkit('biotech');
} else if (item.title === 'GPA and internal calculator') {
setSelectedToolkit('gpa');
} else if (item.url && item.url !== '#') { } else if (item.url && item.url !== '#') {
window.open(item.url, '_blank'); window.open(item.url, '_blank');
} }

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,6 @@ import time
import logging import logging
import requests import requests
import asyncio import asyncio
import discord
from typing import Optional from typing import Optional
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
@@ -53,7 +52,6 @@ def load_config():
config = load_config() config = load_config()
COMMUNITY_BOT_TOKEN = os.environ.get("COMMUNITY_BOT_TOKEN") or config.get("community_bot_token", "8859374355:AAH0dhwstkTBhRerRTjzmb2RG2fjPbigzvo") 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") 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") BACKEND_URL = os.environ.get("SPRING_BACKEND_URL") or config.get("spring_backend_url", "http://localhost:8085")
# Database Setup # Database Setup
@@ -611,292 +609,8 @@ def telegram_polling_thread():
logging.error(f"Error in 24/7 chatbot polling loop: {e}") logging.error(f"Error in 24/7 chatbot polling loop: {e}")
time.sleep(5) 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 # 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( app.add_middleware(
CORSMiddleware, CORSMiddleware,
@@ -920,7 +634,6 @@ class CollabApplicationPayload(BaseModel):
author_name: str author_name: str
contact_info: Optional[str] = None contact_info: Optional[str] = None
telegram_chat_id: Optional[int] = None telegram_chat_id: Optional[int] = None
discord_user_id: Optional[str] = None
applicant_name: str applicant_name: str
applicant_dept: str applicant_dept: str
applicant_year: str applicant_year: str
@@ -932,7 +645,7 @@ def send_question(payload: QuestionPayload):
current_config = load_config() current_config = load_config()
comm_token = os.environ.get("COMMUNITY_BOT_TOKEN") or current_config.get("community_bot_token", COMMUNITY_BOT_TOKEN) 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_helpers = current_config.get("helper_chat_ids", [])
telegram_sent = 0 telegram_sent = 0
if telegram_helpers and comm_token: if telegram_helpers and comm_token:
@@ -951,27 +664,9 @@ def send_question(payload: QuestionPayload):
save_mapping(chat_id, message_id, payload.question_id) save_mapping(chat_id, message_id, payload.question_id)
telegram_sent += 1 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 { return {
"status": "success", "status": "success",
"telegram_delivered_to": telegram_sent, "telegram_delivered_to": telegram_sent
"discord_queued_for": discord_sent
} }
@app.post("/send_collab_application") @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") bot_token = os.environ.get("TELEGRAM_BOT_TOKEN") or current_config.get("telegram_bot_token")
telegram_sent = False 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 chat_id = payload.telegram_chat_id
if chat_id: if chat_id:
@@ -1009,13 +703,9 @@ def send_collab_application(payload: CollabApplicationPayload):
else: else:
logging.info(f"Skipping Telegram notification for collab application {payload.application_id}: No telegram_chat_id provided for author.") 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 { return {
"status": "success", "status": "success",
"telegram_sent": telegram_sent, "telegram_sent": telegram_sent
"discord_sent": discord_sent
} }
async def run_uvicorn(): async def run_uvicorn():
@@ -1030,14 +720,7 @@ async def main():
chat_thread = threading.Thread(target=telegram_polling_thread, daemon=True) chat_thread = threading.Thread(target=telegram_polling_thread, daemon=True)
chat_thread.start() chat_thread.start()
tasks = [] await run_uvicorn()
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)
if __name__ == "__main__": if __name__ == "__main__":
try: try: