feat: initial frontend build for RIT Freshers Hub
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
8
.oxlintrc.json
Normal file
8
.oxlintrc.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
|
"plugins": ["react", "typescript", "oxc"],
|
||||||
|
"rules": {
|
||||||
|
"react/rules-of-hooks": "error",
|
||||||
|
"react/only-export-components": ["warn", { "allowConstantExport": true }]
|
||||||
|
}
|
||||||
|
}
|
||||||
32
README.md
Normal file
32
README.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# React + TypeScript + Vite
|
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules.
|
||||||
|
|
||||||
|
Currently, two official plugins are available:
|
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
||||||
|
|
||||||
|
## React Compiler
|
||||||
|
|
||||||
|
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||||
|
|
||||||
|
## Expanding the Oxlint configuration
|
||||||
|
|
||||||
|
If you are developing a production application, we recommend enabling type-aware lint rules by installing `oxlint-tsgolint` and editing `.oxlintrc.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
|
"plugins": ["react", "typescript", "oxc"],
|
||||||
|
"options": {
|
||||||
|
"typeAware": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"react/rules-of-hooks": "error",
|
||||||
|
"react/only-export-components": ["warn", { "allowConstantExport": true }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [Oxlint rules documentation](https://oxc.rs/docs/guide/usage/linter/rules) for the full list of rules and categories.
|
||||||
26
index.html
Normal file
26
index.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="description" content="RIT Freshers Hub – Your all-in-one AI-powered student portal for Rajalakshmi Institute of Technology. Notes, PYQs, AI Assistant, Campus Map, Bus Routes, Faculty Directory, Events, and Community." />
|
||||||
|
<meta name="keywords" content="RIT, Rajalakshmi Institute of Technology, Freshers, Student Portal, Notes, PYQs, AI Assistant, Campus Map, Bus Routes" />
|
||||||
|
<meta name="theme-color" content="#F97316" />
|
||||||
|
|
||||||
|
<!-- Open Graph -->
|
||||||
|
<meta property="og:title" content="RIT Freshers Hub" />
|
||||||
|
<meta property="og:description" content="Everything a Fresher Needs — All in One Place." />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
|
||||||
|
<!-- Google Fonts preconnect -->
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
|
||||||
|
<title>RIT Freshers Hub – Student Portal</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3994
package-lock.json
generated
Normal file
3994
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
54
package.json
Normal file
54
package.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"name": "freshers-hub",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"lint": "oxlint",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hookform/resolvers": "^5.4.0",
|
||||||
|
"@radix-ui/react-accordion": "^1.2.17",
|
||||||
|
"@radix-ui/react-avatar": "^1.2.3",
|
||||||
|
"@radix-ui/react-collapsible": "^1.1.17",
|
||||||
|
"@radix-ui/react-dialog": "^1.1.20",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.1.21",
|
||||||
|
"@radix-ui/react-label": "^2.1.12",
|
||||||
|
"@radix-ui/react-navigation-menu": "^1.2.19",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.15",
|
||||||
|
"@radix-ui/react-select": "^2.3.4",
|
||||||
|
"@radix-ui/react-separator": "^1.1.12",
|
||||||
|
"@radix-ui/react-slot": "^1.3.0",
|
||||||
|
"@radix-ui/react-switch": "^1.3.4",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.18",
|
||||||
|
"@radix-ui/react-tooltip": "^1.2.13",
|
||||||
|
"@tanstack/react-query": "^5.101.3",
|
||||||
|
"@tanstack/react-query-devtools": "^5.101.3",
|
||||||
|
"axios": "^1.18.1",
|
||||||
|
"class-variance-authority": "^0.7.1",
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"framer-motion": "^12.42.2",
|
||||||
|
"lucide-react": "^1.25.0",
|
||||||
|
"react": "^19.2.7",
|
||||||
|
"react-dom": "^19.2.7",
|
||||||
|
"react-hook-form": "^7.82.0",
|
||||||
|
"react-router-dom": "^7.18.1",
|
||||||
|
"tailwind-merge": "^3.6.0",
|
||||||
|
"zod": "^4.4.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tailwindcss/vite": "^4.3.3",
|
||||||
|
"@types/node": "^24.13.3",
|
||||||
|
"@types/react": "^19.2.17",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@vitejs/plugin-react": "^6.0.3",
|
||||||
|
"autoprefixer": "^10.5.4",
|
||||||
|
"oxlint": "^1.71.0",
|
||||||
|
"tailwindcss": "^4.3.3",
|
||||||
|
"typescript": "~6.0.2",
|
||||||
|
"vite": "^8.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
public/favicon.svg
Normal file
1
public/favicon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.3 KiB |
24
public/icons.svg
Normal file
24
public/icons.svg
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<symbol id="bluesky-icon" viewBox="0 0 16 17">
|
||||||
|
<g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
|
||||||
|
<defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="discord-icon" viewBox="0 0 20 19">
|
||||||
|
<path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="documentation-icon" viewBox="0 0 21 20">
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="github-icon" viewBox="0 0 19 19">
|
||||||
|
<path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="social-icon" viewBox="0 0 20 20">
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
|
||||||
|
<path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
|
||||||
|
</symbol>
|
||||||
|
<symbol id="x-icon" viewBox="0 0 19 19">
|
||||||
|
<path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
|
||||||
|
</symbol>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.9 KiB |
184
src/App.css
Normal file
184
src/App.css
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
.counter {
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: var(--accent);
|
||||||
|
background: var(--accent-bg);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--accent-border);
|
||||||
|
}
|
||||||
|
&:focus-visible {
|
||||||
|
outline: 2px solid var(--accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.base,
|
||||||
|
.framework,
|
||||||
|
.vite {
|
||||||
|
inset-inline: 0;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.base {
|
||||||
|
width: 170px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.framework,
|
||||||
|
.vite {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.framework {
|
||||||
|
z-index: 1;
|
||||||
|
top: 34px;
|
||||||
|
height: 28px;
|
||||||
|
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
|
||||||
|
scale(1.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vite {
|
||||||
|
z-index: 0;
|
||||||
|
top: 107px;
|
||||||
|
height: 26px;
|
||||||
|
width: auto;
|
||||||
|
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
|
||||||
|
scale(0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#center {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 25px;
|
||||||
|
place-content: center;
|
||||||
|
place-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
padding: 32px 20px 24px;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#next-steps {
|
||||||
|
display: flex;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
flex: 1 1 0;
|
||||||
|
padding: 32px;
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
padding: 24px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#docs {
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#next-steps ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 32px 0 0;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--text-h);
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--social-bg);
|
||||||
|
display: flex;
|
||||||
|
padding: 6px 12px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: box-shadow 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
.button-icon {
|
||||||
|
height: 18px;
|
||||||
|
width: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
margin-top: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
li {
|
||||||
|
flex: 1 1 calc(50% - 8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#spacer {
|
||||||
|
height: 88px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ticks {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: -4.5px;
|
||||||
|
border: 5px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
left: 0;
|
||||||
|
border-left-color: var(--border);
|
||||||
|
}
|
||||||
|
&::after {
|
||||||
|
right: 0;
|
||||||
|
border-right-color: var(--border);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/App.tsx
Normal file
41
src/App.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { BrowserRouter, Routes, Route } from 'react-router-dom';
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
import MainLayout from '@/layouts/MainLayout';
|
||||||
|
import Home from '@/pages/Home/Home';
|
||||||
|
import Notes from '@/pages/Notes/Notes';
|
||||||
|
import AIAssistant from '@/pages/AIAssistant/AIAssistant';
|
||||||
|
import Campus from '@/pages/Campus/Campus';
|
||||||
|
import Events from '@/pages/Events/Events';
|
||||||
|
import Community from '@/pages/Community/Community';
|
||||||
|
import Profile from '@/pages/Profile/Profile';
|
||||||
|
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
staleTime: 5 * 60 * 1000,
|
||||||
|
gcTime: 10 * 60 * 1000,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<BrowserRouter>
|
||||||
|
<Routes>
|
||||||
|
<Route element={<MainLayout />}>
|
||||||
|
<Route path="/" element={<Home />} />
|
||||||
|
<Route path="/notes" element={<Notes />} />
|
||||||
|
<Route path="/ai-assistant" element={<AIAssistant />} />
|
||||||
|
<Route path="/campus" element={<Campus />} />
|
||||||
|
<Route path="/events" element={<Events />} />
|
||||||
|
<Route path="/community" element={<Community />} />
|
||||||
|
<Route path="/profile" element={<Profile />} />
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
</BrowserRouter>
|
||||||
|
</QueryClientProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
BIN
src/assets/hero.png
Normal file
BIN
src/assets/hero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
1
src/assets/react.svg
Normal file
1
src/assets/react.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
1
src/assets/vite.svg
Normal file
1
src/assets/vite.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.5 KiB |
97
src/components/AnimatedContainer/AnimatedContainer.tsx
Normal file
97
src/components/AnimatedContainer/AnimatedContainer.tsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface AnimatedContainerProps {
|
||||||
|
children: ReactNode;
|
||||||
|
delay?: number;
|
||||||
|
className?: string;
|
||||||
|
direction?: 'up' | 'left' | 'right' | 'fade';
|
||||||
|
}
|
||||||
|
|
||||||
|
const variants = {
|
||||||
|
up: {
|
||||||
|
hidden: { opacity: 0, y: 30 },
|
||||||
|
visible: { opacity: 1, y: 0 },
|
||||||
|
},
|
||||||
|
left: {
|
||||||
|
hidden: { opacity: 0, x: -30 },
|
||||||
|
visible: { opacity: 1, x: 0 },
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
hidden: { opacity: 0, x: 30 },
|
||||||
|
visible: { opacity: 1, x: 0 },
|
||||||
|
},
|
||||||
|
fade: {
|
||||||
|
hidden: { opacity: 0 },
|
||||||
|
visible: { opacity: 1 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AnimatedContainer({
|
||||||
|
children,
|
||||||
|
delay = 0,
|
||||||
|
className = '',
|
||||||
|
direction = 'up',
|
||||||
|
}: AnimatedContainerProps) {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial="hidden"
|
||||||
|
whileInView="visible"
|
||||||
|
viewport={{ once: true, margin: '-50px' }}
|
||||||
|
transition={{ duration: 0.55, delay, ease: 'easeOut' }}
|
||||||
|
variants={variants[direction]}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stagger container for grid cards
|
||||||
|
export function StaggerContainer({
|
||||||
|
children,
|
||||||
|
className = '',
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial="hidden"
|
||||||
|
whileInView="visible"
|
||||||
|
viewport={{ once: true, margin: '-50px' }}
|
||||||
|
variants={{
|
||||||
|
hidden: {},
|
||||||
|
visible: {
|
||||||
|
transition: {
|
||||||
|
staggerChildren: 0.08,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Individual stagger item
|
||||||
|
export function StaggerItem({
|
||||||
|
children,
|
||||||
|
className = '',
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
variants={{
|
||||||
|
hidden: { opacity: 0, y: 24 },
|
||||||
|
visible: { opacity: 1, y: 0, transition: { duration: 0.45, ease: 'easeOut' } },
|
||||||
|
}}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
114
src/components/BusCard/BusCard.tsx
Normal file
114
src/components/BusCard/BusCard.tsx
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Clock, MapPin, ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import type { BusRoute } from '@/types';
|
||||||
|
|
||||||
|
interface BusCardProps {
|
||||||
|
route: BusRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BusCard({ route }: BusCardProps) {
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
layout
|
||||||
|
whileHover={{ y: -3 }}
|
||||||
|
transition={{ duration: 0.2 }}
|
||||||
|
className="bg-white border border-[#E5E7EB] rounded-2xl overflow-hidden"
|
||||||
|
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||||
|
>
|
||||||
|
{/* Color Strip */}
|
||||||
|
<div className="h-1.5" style={{ backgroundColor: route.color }} />
|
||||||
|
|
||||||
|
<div className="p-5">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-start justify-between gap-3 mb-3">
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<span
|
||||||
|
className="px-2.5 py-0.5 rounded-full text-xs font-bold text-white"
|
||||||
|
style={{ backgroundColor: route.color, fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
>
|
||||||
|
{route.number}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-semibold text-[#1E293B] text-sm" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
{route.name}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5 text-xs text-[#94A3B8] shrink-0">
|
||||||
|
<Clock className="w-3.5 h-3.5" />
|
||||||
|
<span style={{ fontFamily: 'Inter, sans-serif' }}>{route.departureTime}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Route Summary */}
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<div className="flex flex-col items-center gap-1">
|
||||||
|
<div className="w-2 h-2 rounded-full border-2" style={{ borderColor: route.color }} />
|
||||||
|
<div className="w-0.5 h-8 bg-[#E5E7EB]" />
|
||||||
|
<div className="w-2 h-2 rounded-full" style={{ backgroundColor: route.color }} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-between h-12">
|
||||||
|
<span className="text-xs font-medium text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
{route.from}
|
||||||
|
</span>
|
||||||
|
<span className="text-xs font-medium text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
{route.to}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Arrival */}
|
||||||
|
<div className="flex items-center justify-between text-xs text-[#94A3B8] mb-3">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<MapPin className="w-3.5 h-3.5" />
|
||||||
|
<span style={{ fontFamily: 'Inter, sans-serif' }}>{route.stops.length} stops</span>
|
||||||
|
</div>
|
||||||
|
<span style={{ fontFamily: 'Inter, sans-serif' }}>Arrives: {route.arrivalTime}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Toggle Stops */}
|
||||||
|
<button
|
||||||
|
onClick={() => setExpanded(!expanded)}
|
||||||
|
className="w-full flex items-center justify-center gap-1.5 py-2 rounded-xl text-xs font-medium transition-all"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Poppins, sans-serif',
|
||||||
|
color: route.color,
|
||||||
|
backgroundColor: `${route.color}12`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{expanded ? 'Hide Stops' : 'View Stops'}
|
||||||
|
{expanded ? <ChevronUp className="w-3.5 h-3.5" /> : <ChevronDown className="w-3.5 h-3.5" />}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Stops Expanded */}
|
||||||
|
<motion.div
|
||||||
|
initial={false}
|
||||||
|
animate={{ height: expanded ? 'auto' : 0, opacity: expanded ? 1 : 0 }}
|
||||||
|
transition={{ duration: 0.25 }}
|
||||||
|
className="overflow-hidden"
|
||||||
|
>
|
||||||
|
<div className="mt-3 flex flex-col gap-2 pl-1">
|
||||||
|
{route.stops.map((stop, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-3">
|
||||||
|
<div className="flex flex-col items-center gap-0.5">
|
||||||
|
<div
|
||||||
|
className="w-2 h-2 rounded-full"
|
||||||
|
style={{ backgroundColor: i === 0 || i === route.stops.length - 1 ? route.color : '#E5E7EB' }}
|
||||||
|
/>
|
||||||
|
{i < route.stops.length - 1 && <div className="w-0.5 h-4 bg-[#E5E7EB]" />}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between flex-1">
|
||||||
|
<span className="text-xs text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>{stop.name}</span>
|
||||||
|
<span className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>{stop.time}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
127
src/components/ChatPreview/ChatPreview.tsx
Normal file
127
src/components/ChatPreview/ChatPreview.tsx
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Bot, Sparkles, ArrowRight } from 'lucide-react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { AI_SUGGESTED_PROMPTS } from '@/constants';
|
||||||
|
|
||||||
|
const PREVIEW_MESSAGES = [
|
||||||
|
{ role: 'user', text: 'What documents do I need for the first day?' },
|
||||||
|
{ role: 'ai', text: 'For your first day at RIT, you\'ll need: Allotment order, 10th & 12th mark sheets, Transfer Certificate, Conduct Certificate, and 10 passport-size photos. Originals and 2 sets of photocopies are required. Would you like a complete checklist?' },
|
||||||
|
{ role: 'user', text: 'Yes, and what about hostel admission?' },
|
||||||
|
{ role: 'ai', text: 'For hostel admission, additionally bring your Aadhaar card, medical fitness certificate, and filled hostel application form. The hostel fee can be paid at the accounts office.' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function ChatPreview() {
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-3xl border border-[#E5E7EB] overflow-hidden" style={{ boxShadow: '0 8px 40px -8px rgba(0,0,0,0.12)' }}>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="px-5 py-4 border-b border-[#E5E7EB] flex items-center gap-3">
|
||||||
|
<div
|
||||||
|
className="w-9 h-9 rounded-xl flex items-center justify-center"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||||
|
>
|
||||||
|
<Bot className="w-5 h-5 text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
RIT AI Assistant
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1.5 text-xs text-[#94A3B8]">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400 inline-block" />
|
||||||
|
Powered by Gemini AI
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-auto">
|
||||||
|
<span className="px-2.5 py-1 rounded-full text-xs font-medium bg-[#FFF7ED] text-[#F97316]" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
Live
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Messages */}
|
||||||
|
<div className="p-5 flex flex-col gap-3 min-h-[260px]">
|
||||||
|
{PREVIEW_MESSAGES.map((msg, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={i}
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.4, delay: i * 0.15 }}
|
||||||
|
className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}
|
||||||
|
>
|
||||||
|
{msg.role === 'ai' && (
|
||||||
|
<div className="w-7 h-7 rounded-full flex items-center justify-center mr-2 shrink-0 mt-0.5"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
|
||||||
|
<Bot className="w-3.5 h-3.5 text-white" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className="text-xs leading-relaxed max-w-[78%] px-3.5 py-2.5"
|
||||||
|
style={{
|
||||||
|
background: msg.role === 'user' ? 'linear-gradient(135deg, #F97316, #FB923C)' : '#F8FAFC',
|
||||||
|
color: msg.role === 'user' ? 'white' : '#1E293B',
|
||||||
|
borderRadius: msg.role === 'user' ? '16px 16px 4px 16px' : '16px 16px 16px 4px',
|
||||||
|
border: msg.role === 'ai' ? '1px solid #E5E7EB' : 'none',
|
||||||
|
fontFamily: 'Inter, sans-serif',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{msg.text}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Typing indicator */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.8 }}
|
||||||
|
className="flex justify-start"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-1 px-4 py-2.5 bg-[#F8FAFC] rounded-2xl border border-[#E5E7EB]">
|
||||||
|
{[0, 1, 2].map((i) => (
|
||||||
|
<motion.span
|
||||||
|
key={i}
|
||||||
|
className="w-1.5 h-1.5 bg-[#94A3B8] rounded-full"
|
||||||
|
animate={{ y: [0, -4, 0] }}
|
||||||
|
transition={{ duration: 0.8, repeat: Infinity, delay: i * 0.15 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Suggested Prompts */}
|
||||||
|
<div className="px-5 pb-2 flex flex-wrap gap-2">
|
||||||
|
{AI_SUGGESTED_PROMPTS.slice(0, 3).map((prompt, i) => (
|
||||||
|
<motion.span
|
||||||
|
key={i}
|
||||||
|
whileHover={{ scale: 1.03 }}
|
||||||
|
className="text-xs px-3 py-1.5 rounded-full border border-[#E5E7EB] text-[#475569] cursor-pointer hover:border-[#F97316] hover:text-[#F97316] transition-all"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
>
|
||||||
|
{prompt}
|
||||||
|
</motion.span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CTA */}
|
||||||
|
<div className="p-5 pt-3">
|
||||||
|
<Link to="/ai-assistant">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.02 }}
|
||||||
|
whileTap={{ scale: 0.98 }}
|
||||||
|
className="w-full flex items-center justify-center gap-2 py-3 rounded-xl text-sm font-semibold text-white transition-all"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Poppins, sans-serif',
|
||||||
|
background: 'linear-gradient(135deg, #F97316, #FB923C)',
|
||||||
|
boxShadow: '0 4px 15px rgba(249,115,22,0.35)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Sparkles className="w-4 h-4" />
|
||||||
|
Ask Anything
|
||||||
|
<ArrowRight className="w-4 h-4" />
|
||||||
|
</motion.button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
96
src/components/FacultyCard/FacultyCard.tsx
Normal file
96
src/components/FacultyCard/FacultyCard.tsx
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Mail, Phone, Building2, User } from 'lucide-react';
|
||||||
|
import type { Faculty } from '@/types';
|
||||||
|
|
||||||
|
interface FacultyCardProps {
|
||||||
|
faculty: Faculty;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVATAR_COLORS = [
|
||||||
|
['#F97316', '#FB923C'],
|
||||||
|
['#8B5CF6', '#A78BFA'],
|
||||||
|
['#10B981', '#34D399'],
|
||||||
|
['#3B82F6', '#60A5FA'],
|
||||||
|
['#EC4899', '#F472B6'],
|
||||||
|
['#F59E0B', '#FCD34D'],
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function FacultyCard({ faculty }: FacultyCardProps) {
|
||||||
|
const colorIndex = parseInt(faculty.id) % AVATAR_COLORS.length;
|
||||||
|
const [from, to] = AVATAR_COLORS[colorIndex];
|
||||||
|
const initials = faculty.name
|
||||||
|
.split(' ')
|
||||||
|
.filter((_, i) => i === 0 || i === faculty.name.split(' ').length - 1)
|
||||||
|
.map((n) => n[0])
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ y: -5, boxShadow: '0 16px 40px -8px rgba(0,0,0,0.12)' }}
|
||||||
|
transition={{ duration: 0.25 }}
|
||||||
|
className="bg-white border border-[#E5E7EB] rounded-2xl p-5 flex flex-col gap-4"
|
||||||
|
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||||
|
>
|
||||||
|
{/* Avatar + Name */}
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div
|
||||||
|
className="w-14 h-14 rounded-2xl flex items-center justify-center text-white font-bold text-lg shrink-0"
|
||||||
|
style={{ background: `linear-gradient(135deg, ${from}, ${to})`, fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
>
|
||||||
|
{initials}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-[#1E293B] text-sm leading-tight" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
{faculty.name}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-[#F97316] font-medium mt-0.5" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
{faculty.designation}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Details */}
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex items-center gap-2 text-xs text-[#475569]">
|
||||||
|
<Building2 className="w-3.5 h-3.5 text-[#94A3B8] shrink-0" />
|
||||||
|
<span style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.department}</span>
|
||||||
|
</div>
|
||||||
|
{faculty.office && (
|
||||||
|
<div className="flex items-center gap-2 text-xs text-[#475569]">
|
||||||
|
<User className="w-3.5 h-3.5 text-[#94A3B8] shrink-0" />
|
||||||
|
<span style={{ fontFamily: 'Inter, sans-serif' }}>{faculty.office}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{faculty.specialization && (
|
||||||
|
<div className="text-xs text-[#94A3B8] pl-5" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
{faculty.specialization}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact */}
|
||||||
|
<div className="flex items-center gap-2 pt-2 border-t border-[#E5E7EB]">
|
||||||
|
<a
|
||||||
|
href={`mailto:${faculty.email}`}
|
||||||
|
className="flex-1 flex items-center justify-center gap-1.5 py-2 rounded-xl text-xs font-medium transition-all hover:bg-[#FFF7ED]"
|
||||||
|
style={{ color: '#F97316', fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
aria-label={`Email ${faculty.name}`}
|
||||||
|
>
|
||||||
|
<Mail className="w-3.5 h-3.5" />
|
||||||
|
Email
|
||||||
|
</a>
|
||||||
|
{faculty.phone && (
|
||||||
|
<a
|
||||||
|
href={`tel:${faculty.phone}`}
|
||||||
|
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"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
aria-label={`Call ${faculty.name}`}
|
||||||
|
>
|
||||||
|
<Phone className="w-3.5 h-3.5" />
|
||||||
|
Call
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
63
src/components/FeatureCard/FeatureCard.tsx
Normal file
63
src/components/FeatureCard/FeatureCard.tsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { ArrowRight } from 'lucide-react';
|
||||||
|
import * as LucideIcons from 'lucide-react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import type { Feature } from '@/types';
|
||||||
|
|
||||||
|
interface FeatureCardProps {
|
||||||
|
feature: Feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FeatureCard({ feature }: FeatureCardProps) {
|
||||||
|
const IconComponent = (LucideIcons as Record<string, React.ComponentType<{ className?: string }>>)[feature.icon];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ y: -6, boxShadow: '0 20px 50px -12px rgba(0,0,0,0.15)' }}
|
||||||
|
transition={{ duration: 0.25 }}
|
||||||
|
className="bg-white border border-[#E5E7EB] rounded-2xl p-6 flex flex-col gap-4 group cursor-pointer"
|
||||||
|
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||||
|
>
|
||||||
|
{/* Icon */}
|
||||||
|
<div
|
||||||
|
className="w-12 h-12 rounded-xl flex items-center justify-center transition-all duration-300 group-hover:scale-110"
|
||||||
|
style={{ backgroundColor: feature.bgColor }}
|
||||||
|
>
|
||||||
|
{IconComponent && <IconComponent className="w-6 h-6" style={{ color: feature.color } as React.CSSProperties} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3
|
||||||
|
className="text-base font-semibold text-[#1E293B] mb-1.5"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
>
|
||||||
|
{feature.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-[#94A3B8] leading-relaxed" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
{feature.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Arrow */}
|
||||||
|
<Link
|
||||||
|
to={feature.path}
|
||||||
|
className="flex items-center gap-1.5 text-sm font-semibold transition-all duration-200 w-fit"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Poppins, sans-serif',
|
||||||
|
color: feature.color,
|
||||||
|
}}
|
||||||
|
aria-label={`Explore ${feature.title}`}
|
||||||
|
>
|
||||||
|
Explore
|
||||||
|
<motion.span
|
||||||
|
className="inline-flex"
|
||||||
|
animate={{ x: [0, 3, 0] }}
|
||||||
|
transition={{ duration: 1.5, repeat: Infinity, ease: 'easeInOut' }}
|
||||||
|
>
|
||||||
|
<ArrowRight className="w-4 h-4" />
|
||||||
|
</motion.span>
|
||||||
|
</Link>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
28
src/components/FloatingCard/FloatingCard.tsx
Normal file
28
src/components/FloatingCard/FloatingCard.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface FloatingCardProps {
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
delay?: number;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FloatingCard({ children, className = '', delay = 0, style }: FloatingCardProps) {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.85 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ duration: 0.6, delay }}
|
||||||
|
className={`bg-white rounded-2xl border border-[#E5E7EB] shadow-[0_8px_32px_rgba(0,0,0,0.12)] ${className}`}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
animate={{ y: [0, -8, 0] }}
|
||||||
|
transition={{ duration: 3.5, repeat: Infinity, ease: 'easeInOut', delay }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
163
src/components/Footer/Footer.tsx
Normal file
163
src/components/Footer/Footer.tsx
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { GraduationCap, MapPin, Phone, Mail, Camera, Send, Globe, Play, ArrowRight } from 'lucide-react';
|
||||||
|
|
||||||
|
const quickLinks = [
|
||||||
|
{ label: 'Home', path: '/' },
|
||||||
|
{ label: 'Notes & PYQs', path: '/notes' },
|
||||||
|
{ label: 'AI Assistant', path: '/ai-assistant' },
|
||||||
|
{ label: 'Campus Map', path: '/campus' },
|
||||||
|
{ label: 'Events', path: '/events' },
|
||||||
|
{ label: 'Community', path: '/community' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const resources = [
|
||||||
|
{ label: 'Student Handbook', path: '#' },
|
||||||
|
{ label: 'Academic Calendar', path: '#' },
|
||||||
|
{ label: 'ERP Portal', path: '#' },
|
||||||
|
{ label: 'Library Portal', path: '#' },
|
||||||
|
{ label: 'Exam Results', path: '#' },
|
||||||
|
{ label: 'Fee Payment', path: '#' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<footer className="bg-[#1E293B] text-white">
|
||||||
|
{/* CTA Banner */}
|
||||||
|
<div style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }} className="py-12">
|
||||||
|
<div className="container-custom flex flex-col md:flex-row items-center justify-between gap-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-2xl font-bold text-white" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||||
|
Ready to explore RIT?
|
||||||
|
</h3>
|
||||||
|
<p className="text-orange-100 mt-1" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
Everything a fresher needs — all in one place.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<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 AI Assistant
|
||||||
|
<ArrowRight className="w-4 h-4" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Footer */}
|
||||||
|
<div className="container-custom py-14">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-10">
|
||||||
|
{/* Brand */}
|
||||||
|
<div>
|
||||||
|
<div className="flex items-center gap-2.5 mb-4">
|
||||||
|
<div
|
||||||
|
className="w-10 h-10 rounded-xl flex items-center justify-center"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||||
|
>
|
||||||
|
<GraduationCap className="w-5 h-5 text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="font-bold text-white text-sm" style={{ fontFamily: 'Poppins, sans-serif' }}>RIT Freshers Hub</div>
|
||||||
|
<div className="text-[10px] text-slate-400">Rajalakshmi Institute of Technology</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-slate-400 text-sm leading-relaxed mb-5" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
Your all-in-one guide to campus life at RIT. Notes, AI assistant, events, faculty, and more — made for freshers.
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
{[Camera, Send, Globe, Play].map((Icon, i) => (
|
||||||
|
<a
|
||||||
|
key={i}
|
||||||
|
href="#"
|
||||||
|
className="w-9 h-9 rounded-lg bg-slate-700 flex items-center justify-center text-slate-400 hover:bg-[#F97316] hover:text-white transition-all duration-200"
|
||||||
|
aria-label="Social link"
|
||||||
|
>
|
||||||
|
<Icon className="w-4 h-4" />
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Links */}
|
||||||
|
<div>
|
||||||
|
<h4 className="text-white font-semibold mb-4 text-sm uppercase tracking-wider" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
Quick Links
|
||||||
|
</h4>
|
||||||
|
<ul className="space-y-2.5">
|
||||||
|
{quickLinks.map((link) => (
|
||||||
|
<li key={link.path}>
|
||||||
|
<Link
|
||||||
|
to={link.path}
|
||||||
|
className="text-slate-400 hover:text-[#F97316] text-sm transition-colors flex items-center gap-1.5 group"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
>
|
||||||
|
<span className="w-1 h-1 rounded-full bg-[#F97316] opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Resources */}
|
||||||
|
<div>
|
||||||
|
<h4 className="text-white font-semibold mb-4 text-sm uppercase tracking-wider" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
Resources
|
||||||
|
</h4>
|
||||||
|
<ul className="space-y-2.5">
|
||||||
|
{resources.map((res) => (
|
||||||
|
<li key={res.label}>
|
||||||
|
<a
|
||||||
|
href={res.path}
|
||||||
|
className="text-slate-400 hover:text-[#F97316] text-sm transition-colors flex items-center gap-1.5 group"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
>
|
||||||
|
<span className="w-1 h-1 rounded-full bg-[#F97316] opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||||
|
{res.label}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact */}
|
||||||
|
<div>
|
||||||
|
<h4 className="text-white font-semibold mb-4 text-sm uppercase tracking-wider" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
Contact
|
||||||
|
</h4>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
<li className="flex items-start gap-3 text-slate-400 text-sm" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
<MapPin className="w-4 h-4 mt-0.5 shrink-0 text-[#F97316]" />
|
||||||
|
<span>Rajalakshmi Institute of Technology,<br />Kuthambakkam, Chennai - 600 124</span>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-center gap-3 text-slate-400 text-sm">
|
||||||
|
<Phone className="w-4 h-4 shrink-0 text-[#F97316]" />
|
||||||
|
<a href="tel:+914423422890" className="hover:text-[#F97316] transition-colors">+91 44 2342 2890</a>
|
||||||
|
</li>
|
||||||
|
<li className="flex items-center gap-3 text-slate-400 text-sm">
|
||||||
|
<Mail className="w-4 h-4 shrink-0 text-[#F97316]" />
|
||||||
|
<a href="mailto:info@rit.ac.in" className="hover:text-[#F97316] transition-colors">info@rit.ac.in</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom Bar */}
|
||||||
|
<div className="border-t border-slate-700">
|
||||||
|
<div className="container-custom py-5 flex flex-col sm:flex-row items-center justify-between gap-3">
|
||||||
|
<p className="text-slate-500 text-xs" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
© 2025 RIT Freshers Hub. All rights reserved.
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
181
src/components/Navbar/Navbar.tsx
Normal file
181
src/components/Navbar/Navbar.tsx
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { Menu, X, GraduationCap, Bell } from 'lucide-react';
|
||||||
|
import { NAV_LINKS } from '@/constants';
|
||||||
|
|
||||||
|
export default function Navbar() {
|
||||||
|
const [isScrolled, setIsScrolled] = useState(false);
|
||||||
|
const [isMobileOpen, setIsMobileOpen] = useState(false);
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleScroll = () => setIsScrolled(window.scrollY > 20);
|
||||||
|
window.addEventListener('scroll', handleScroll);
|
||||||
|
return () => window.removeEventListener('scroll', handleScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMobileOpen(false);
|
||||||
|
}, [location.pathname]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<motion.nav
|
||||||
|
initial={{ y: -100, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
transition={{ duration: 0.6, ease: 'easeOut' }}
|
||||||
|
className={`fixed top-4 left-1/2 -translate-x-1/2 z-50 w-[95%] max-w-6xl transition-all duration-300 ${
|
||||||
|
isScrolled
|
||||||
|
? 'shadow-[0_8px_32px_rgba(0,0,0,0.12)] bg-white/90'
|
||||||
|
: 'bg-white/80'
|
||||||
|
}`}
|
||||||
|
style={{
|
||||||
|
backdropFilter: 'blur(16px)',
|
||||||
|
WebkitBackdropFilter: 'blur(16px)',
|
||||||
|
borderRadius: '20px',
|
||||||
|
border: '1px solid rgba(229,231,235,0.8)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between px-5 py-2.5">
|
||||||
|
{/* Logo - RIT Event Hub Style */}
|
||||||
|
<Link to="/" className="flex items-center gap-2 group">
|
||||||
|
{/* Blue Circular Tree Icon */}
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
className="w-10 h-10 rounded-full flex items-center justify-center shrink-0"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #1E40AF, #3B82F6)' }}
|
||||||
|
>
|
||||||
|
<svg viewBox="0 0 100 100" className="w-6.5 h-6.5 text-white" fill="none" stroke="currentColor" strokeWidth="6" strokeLinecap="round">
|
||||||
|
{/* Minimalist Tree Branch Representation */}
|
||||||
|
<path d="M50 85 V40" />
|
||||||
|
<path d="M50 65 Q65 55 75 60" />
|
||||||
|
<path d="M50 55 Q35 45 25 50" />
|
||||||
|
<path d="M50 45 Q70 30 65 15" />
|
||||||
|
<path d="M50 45 Q30 30 35 15" />
|
||||||
|
<circle cx="50" cy="38" r="8" fill="white" stroke="none" />
|
||||||
|
<circle cx="65" cy="15" r="5" fill="white" stroke="none" />
|
||||||
|
<circle cx="35" cy="15" r="5" fill="white" stroke="none" />
|
||||||
|
<circle cx="75" cy="60" r="5" fill="white" stroke="none" />
|
||||||
|
<circle cx="25" cy="50" r="5" fill="white" stroke="none" />
|
||||||
|
</svg>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span className="font-extrabold text-[#1E293B] text-2xl tracking-tighter" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
rit
|
||||||
|
</span>
|
||||||
|
<div className="h-7 w-[1px] bg-gray-300 mx-2" />
|
||||||
|
<div className="flex flex-col text-[8.5px] font-bold text-[#475569] leading-tight tracking-wider" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
<span>RAJALAKSHMI</span>
|
||||||
|
<span>INSTITUTE OF</span>
|
||||||
|
<span>TECHNOLOGY</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Desktop Nav */}
|
||||||
|
<div className="hidden md:flex items-center gap-1">
|
||||||
|
{NAV_LINKS.map((link) => {
|
||||||
|
const isActive = location.pathname === link.path;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={link.path}
|
||||||
|
to={link.path}
|
||||||
|
className="relative px-4 py-2 rounded-xl text-xs font-semibold uppercase tracking-wider transition-all duration-200"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Poppins, sans-serif',
|
||||||
|
color: isActive ? '#F97316' : '#475569',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isActive && (
|
||||||
|
<motion.div
|
||||||
|
layoutId="nav-active"
|
||||||
|
className="absolute inset-0 rounded-xl"
|
||||||
|
style={{ backgroundColor: '#FFF7ED' }}
|
||||||
|
transition={{ type: 'spring', bounce: 0.2, duration: 0.4 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span className="relative z-10">{link.label}</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Side */}
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
className="hidden md:flex w-9 h-9 rounded-xl items-center justify-center text-[#94A3B8] hover:text-[#F97316] hover:bg-[#FFF7ED] transition-all"
|
||||||
|
aria-label="Notifications"
|
||||||
|
>
|
||||||
|
<Bell className="w-4.5 h-4.5" />
|
||||||
|
</motion.button>
|
||||||
|
|
||||||
|
{/* Profile styled as an orange LOG OUT pill button from Reference Image */}
|
||||||
|
<Link to="/profile" className="hidden md:block">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.03, boxShadow: '0 4px 15px rgba(249,115,22,0.3)' }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
className="px-5 py-2 rounded-full text-white font-bold text-[10.5px] tracking-wider uppercase transition-all"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Poppins, sans-serif',
|
||||||
|
background: 'linear-gradient(135deg, #F97316, #FB923C)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Profile
|
||||||
|
</motion.button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Mobile Toggle */}
|
||||||
|
<motion.button
|
||||||
|
whileTap={{ scale: 0.9 }}
|
||||||
|
onClick={() => setIsMobileOpen(!isMobileOpen)}
|
||||||
|
className="md:hidden w-9 h-9 rounded-xl flex items-center justify-center text-[#475569] hover:bg-gray-100 transition-all"
|
||||||
|
aria-label="Toggle menu"
|
||||||
|
>
|
||||||
|
{isMobileOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Menu */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{isMobileOpen && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ height: 0, opacity: 0 }}
|
||||||
|
animate={{ height: 'auto', opacity: 1 }}
|
||||||
|
exit={{ height: 0, opacity: 0 }}
|
||||||
|
transition={{ duration: 0.25 }}
|
||||||
|
className="overflow-hidden md:hidden"
|
||||||
|
>
|
||||||
|
<div className="px-4 pb-4 pt-2 flex flex-col gap-1 border-t border-gray-100">
|
||||||
|
{NAV_LINKS.map((link) => {
|
||||||
|
const isActive = location.pathname === link.path;
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
key={link.path}
|
||||||
|
to={link.path}
|
||||||
|
className="px-4 py-3 rounded-xl text-sm font-medium transition-all"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Inter, sans-serif',
|
||||||
|
color: isActive ? '#F97316' : '#475569',
|
||||||
|
backgroundColor: isActive ? '#FFF7ED' : 'transparent',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{link.label}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</motion.nav>
|
||||||
|
|
||||||
|
{/* Spacer */}
|
||||||
|
<div className="h-20" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
82
src/components/SectionTitle/SectionTitle.tsx
Normal file
82
src/components/SectionTitle/SectionTitle.tsx
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
|
|
||||||
|
interface SectionTitleProps {
|
||||||
|
tag?: string;
|
||||||
|
title: string;
|
||||||
|
highlight?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
align?: 'left' | 'center';
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SectionTitle({
|
||||||
|
tag,
|
||||||
|
title,
|
||||||
|
highlight,
|
||||||
|
subtitle,
|
||||||
|
align = 'center',
|
||||||
|
}: SectionTitleProps) {
|
||||||
|
const isCenter = align === 'center';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`mb-12 ${isCenter ? 'text-center' : 'text-left'}`}>
|
||||||
|
{tag && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.4 }}
|
||||||
|
className={`inline-flex items-center gap-2 mb-3 ${isCenter ? 'mx-auto' : ''}`}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="px-3 py-1 rounded-full text-xs font-semibold tracking-wider uppercase"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Poppins, sans-serif',
|
||||||
|
backgroundColor: '#FFF7ED',
|
||||||
|
color: '#F97316',
|
||||||
|
border: '1px solid #FED7AA',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<motion.h2
|
||||||
|
initial={{ opacity: 0, y: 15 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.1 }}
|
||||||
|
className="text-3xl md:text-4xl font-bold"
|
||||||
|
style={{ fontFamily: 'Playfair Display, serif', color: '#1E293B' }}
|
||||||
|
>
|
||||||
|
{title}{' '}
|
||||||
|
{highlight && (
|
||||||
|
<span
|
||||||
|
className="relative inline-block"
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(135deg, #F97316, #FB923C)',
|
||||||
|
WebkitBackgroundClip: 'text',
|
||||||
|
WebkitTextFillColor: 'transparent',
|
||||||
|
backgroundClip: 'text',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{highlight}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</motion.h2>
|
||||||
|
|
||||||
|
{subtitle && (
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.2 }}
|
||||||
|
className={`mt-3 text-base text-[#475569] max-w-xl ${isCenter ? 'mx-auto' : ''}`}
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
>
|
||||||
|
{subtitle}
|
||||||
|
</motion.p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
80
src/components/StatsCard/StatsCard.tsx
Normal file
80
src/components/StatsCard/StatsCard.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import type { Stat } from '@/types';
|
||||||
|
|
||||||
|
interface StatsCardProps {
|
||||||
|
stat: Stat;
|
||||||
|
delay?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useCountUp(target: number, duration: number = 1800, start: boolean = false) {
|
||||||
|
const [count, setCount] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!start) return;
|
||||||
|
let startTime: number | null = null;
|
||||||
|
const step = (timestamp: number) => {
|
||||||
|
if (!startTime) startTime = timestamp;
|
||||||
|
const progress = Math.min((timestamp - startTime) / duration, 1);
|
||||||
|
const ease = 1 - Math.pow(1 - progress, 3); // ease-out cubic
|
||||||
|
setCount(Math.floor(ease * target));
|
||||||
|
if (progress < 1) requestAnimationFrame(step);
|
||||||
|
};
|
||||||
|
requestAnimationFrame(step);
|
||||||
|
}, [target, duration, start]);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function StatsCard({ stat, delay = 0 }: StatsCardProps) {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const count = useCountUp(stat.value, 1800, isVisible);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setIsVisible(true);
|
||||||
|
observer.disconnect();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0.2 }
|
||||||
|
);
|
||||||
|
if (ref.current) observer.observe(ref.current);
|
||||||
|
return () => observer.disconnect();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
ref={ref}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
|
viewport={{ once: true }}
|
||||||
|
transition={{ duration: 0.5, delay }}
|
||||||
|
whileHover={{ y: -3, boxShadow: '0 8px 30px rgba(0,0,0,0.06)' }}
|
||||||
|
className="relative bg-white border border-[#E5E7EB] rounded-2xl p-6 py-8 flex flex-col items-center justify-center text-center group overflow-hidden"
|
||||||
|
style={{ boxShadow: '0 2px 10px rgba(0,0,0,0.03)' }}
|
||||||
|
>
|
||||||
|
{/* Corner Bracket Decorations from Reference Image */}
|
||||||
|
<div className="absolute top-4 right-4 w-3.5 h-3.5 border-t-2 border-r-2 border-[#F97316]/50 rounded-tr-[4px] transition-all duration-300 group-hover:scale-110 group-hover:border-[#F97316]" />
|
||||||
|
<div className="absolute bottom-4 left-4 w-3.5 h-3.5 border-b-2 border-l-2 border-[#F97316]/50 rounded-bl-[4px] transition-all duration-300 group-hover:scale-110 group-hover:border-[#F97316]" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className="text-3xl md:text-4xl font-extrabold text-[#1E293B] tracking-tight"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
>
|
||||||
|
{count}
|
||||||
|
{stat.suffix}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="text-[11px] font-bold uppercase tracking-wider text-[#94A3B8] mt-2"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
>
|
||||||
|
{stat.label}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
399
src/constants/index.ts
Normal file
399
src/constants/index.ts
Normal file
@@ -0,0 +1,399 @@
|
|||||||
|
import type { Feature, Stat, BusRoute, Faculty, Event, Club, Question, Confession, ToolkitItem, CampusLocation } from '@/types';
|
||||||
|
|
||||||
|
// ─── Navigation ──────────────────────────────────────────────────────────────
|
||||||
|
export const NAV_LINKS = [
|
||||||
|
{ label: 'Home', path: '/' },
|
||||||
|
{ label: 'Notes', path: '/notes' },
|
||||||
|
{ label: 'AI Assistant', path: '/ai-assistant' },
|
||||||
|
{ label: 'Campus', path: '/campus' },
|
||||||
|
{ label: 'Events', path: '/events' },
|
||||||
|
{ label: 'Community', path: '/community' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Stats ────────────────────────────────────────────────────────────────────
|
||||||
|
export const STATS: Stat[] = [
|
||||||
|
{ value: 150, suffix: '+', label: 'PYQs', icon: 'FileText' },
|
||||||
|
{ value: 40, suffix: '+', label: 'Faculty', icon: 'Users' },
|
||||||
|
{ value: 25, suffix: '+', label: 'Clubs', icon: 'Star' },
|
||||||
|
{ value: 24, suffix: '/7', label: 'AI Assistant', icon: 'Bot' },
|
||||||
|
{ value: 1000, suffix: '+', label: 'Students', icon: 'GraduationCap' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Features ────────────────────────────────────────────────────────────────
|
||||||
|
export const FEATURES: Feature[] = [
|
||||||
|
{
|
||||||
|
id: 'notes',
|
||||||
|
icon: 'BookOpen',
|
||||||
|
title: 'Notes & PYQs',
|
||||||
|
description: 'Access semester-wise notes, previous year question papers, and study materials instantly.',
|
||||||
|
path: '/notes',
|
||||||
|
color: '#F97316',
|
||||||
|
bgColor: '#FFF7ED',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'ai',
|
||||||
|
icon: 'Bot',
|
||||||
|
title: 'AI Fresher Assistant',
|
||||||
|
description: 'Get instant answers about campus life, academics, rules, and everything RIT – powered by Gemini AI.',
|
||||||
|
path: '/ai-assistant',
|
||||||
|
color: '#8B5CF6',
|
||||||
|
bgColor: '#F5F3FF',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'map',
|
||||||
|
icon: 'Map',
|
||||||
|
title: 'Campus Map',
|
||||||
|
description: 'Explore RIT campus interactively – find departments, labs, library, canteen, and facilities.',
|
||||||
|
path: '/campus',
|
||||||
|
color: '#10B981',
|
||||||
|
bgColor: '#ECFDF5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'bus',
|
||||||
|
icon: 'Bus',
|
||||||
|
title: 'Bus Routes',
|
||||||
|
description: 'View all campus bus routes, timings, and pickup locations at a glance.',
|
||||||
|
path: '/campus',
|
||||||
|
color: '#3B82F6',
|
||||||
|
bgColor: '#EFF6FF',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'faculty',
|
||||||
|
icon: 'UserCheck',
|
||||||
|
title: 'Faculty Directory',
|
||||||
|
description: 'Find faculty contact details, department, designation, and office hours.',
|
||||||
|
path: '/campus',
|
||||||
|
color: '#EC4899',
|
||||||
|
bgColor: '#FDF2F8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'qa',
|
||||||
|
icon: 'MessageCircle',
|
||||||
|
title: 'Freshers Q&A',
|
||||||
|
description: 'Ask questions, get answers from seniors, and join the freshers community discussion.',
|
||||||
|
path: '/community',
|
||||||
|
color: '#F59E0B',
|
||||||
|
bgColor: '#FFFBEB',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'events',
|
||||||
|
icon: 'Calendar',
|
||||||
|
title: 'Clubs & Events',
|
||||||
|
description: 'Discover clubs, upcoming events, workshops, and cultural programs at RIT.',
|
||||||
|
path: '/events',
|
||||||
|
color: '#EF4444',
|
||||||
|
bgColor: '#FEF2F2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'toolkit',
|
||||||
|
icon: 'Wrench',
|
||||||
|
title: 'Student Toolkit',
|
||||||
|
description: 'Download timetables, academic calendar, syllabus, forms, and ERP guides.',
|
||||||
|
path: '/notes',
|
||||||
|
color: '#06B6D4',
|
||||||
|
bgColor: '#ECFEFF',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'confession',
|
||||||
|
icon: 'Heart',
|
||||||
|
title: 'Anonymous Confession',
|
||||||
|
description: 'Share your thoughts, feelings, and confessions anonymously with the RIT community.',
|
||||||
|
path: '/community',
|
||||||
|
color: '#F97316',
|
||||||
|
bgColor: '#FFF7ED',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Bus Routes ──────────────────────────────────────────────────────────────
|
||||||
|
export const BUS_ROUTES: BusRoute[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
number: 'RIT-01',
|
||||||
|
name: 'Chennai Central Route',
|
||||||
|
from: 'Chennai Central',
|
||||||
|
to: 'RIT Campus',
|
||||||
|
departureTime: '07:00 AM',
|
||||||
|
arrivalTime: '08:30 AM',
|
||||||
|
color: '#F97316',
|
||||||
|
stops: [
|
||||||
|
{ name: 'Chennai Central', time: '07:00 AM' },
|
||||||
|
{ name: 'Koyambedu', time: '07:30 AM' },
|
||||||
|
{ name: 'Porur', time: '08:00 AM' },
|
||||||
|
{ name: 'RIT Campus', time: '08:30 AM' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
number: 'RIT-02',
|
||||||
|
name: 'Tambaram Express',
|
||||||
|
from: 'Tambaram',
|
||||||
|
to: 'RIT Campus',
|
||||||
|
departureTime: '07:15 AM',
|
||||||
|
arrivalTime: '08:45 AM',
|
||||||
|
color: '#3B82F6',
|
||||||
|
stops: [
|
||||||
|
{ name: 'Tambaram', time: '07:15 AM' },
|
||||||
|
{ name: 'Chrompet', time: '07:35 AM' },
|
||||||
|
{ name: 'Pallavaram', time: '07:55 AM' },
|
||||||
|
{ name: 'RIT Campus', time: '08:45 AM' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
number: 'RIT-03',
|
||||||
|
name: 'Anna Nagar Route',
|
||||||
|
from: 'Anna Nagar',
|
||||||
|
to: 'RIT Campus',
|
||||||
|
departureTime: '07:20 AM',
|
||||||
|
arrivalTime: '08:50 AM',
|
||||||
|
color: '#10B981',
|
||||||
|
stops: [
|
||||||
|
{ name: 'Anna Nagar', time: '07:20 AM' },
|
||||||
|
{ name: 'Vadapalani', time: '07:45 AM' },
|
||||||
|
{ name: 'Valasaravakkam', time: '08:10 AM' },
|
||||||
|
{ name: 'RIT Campus', time: '08:50 AM' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
number: 'RIT-04',
|
||||||
|
name: 'OMR Tech Route',
|
||||||
|
from: 'Sholinganallur',
|
||||||
|
to: 'RIT Campus',
|
||||||
|
departureTime: '07:00 AM',
|
||||||
|
arrivalTime: '08:30 AM',
|
||||||
|
color: '#8B5CF6',
|
||||||
|
stops: [
|
||||||
|
{ name: 'Sholinganallur', time: '07:00 AM' },
|
||||||
|
{ name: 'Perungudi', time: '07:20 AM' },
|
||||||
|
{ name: 'Medavakkam', time: '07:45 AM' },
|
||||||
|
{ name: 'RIT Campus', time: '08:30 AM' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Faculty ─────────────────────────────────────────────────────────────────
|
||||||
|
export const FACULTY_DATA: Faculty[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'Dr. S. Ramesh Kumar',
|
||||||
|
designation: 'Professor & HOD',
|
||||||
|
department: 'Computer Science & Engineering',
|
||||||
|
email: 'ramesh@rit.edu',
|
||||||
|
office: 'CSE Block, Room 301',
|
||||||
|
specialization: 'Machine Learning, AI',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: 'Dr. P. Mahalakshmi',
|
||||||
|
designation: 'Associate Professor',
|
||||||
|
department: 'Electronics & Communication',
|
||||||
|
email: 'mahalakshmi@rit.edu',
|
||||||
|
office: 'ECE Block, Room 205',
|
||||||
|
specialization: 'VLSI Design, Embedded Systems',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
name: 'Prof. R. Vijayakumar',
|
||||||
|
designation: 'Assistant Professor',
|
||||||
|
department: 'Mechanical Engineering',
|
||||||
|
email: 'vijay@rit.edu',
|
||||||
|
office: 'MECH Block, Room 102',
|
||||||
|
specialization: 'CAD/CAM, Robotics',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
name: 'Dr. K. Anitha',
|
||||||
|
designation: 'Professor',
|
||||||
|
department: 'Information Technology',
|
||||||
|
email: 'anitha@rit.edu',
|
||||||
|
office: 'IT Block, Room 401',
|
||||||
|
specialization: 'Data Science, Cloud Computing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
name: 'Prof. M. Selvakumar',
|
||||||
|
designation: 'Associate Professor',
|
||||||
|
department: 'Civil Engineering',
|
||||||
|
email: 'selva@rit.edu',
|
||||||
|
office: 'CIVIL Block, Room 110',
|
||||||
|
specialization: 'Structural Engineering',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '6',
|
||||||
|
name: 'Dr. S. Priya',
|
||||||
|
designation: 'Assistant Professor',
|
||||||
|
department: 'Computer Science & Engineering',
|
||||||
|
email: 'priya@rit.edu',
|
||||||
|
office: 'CSE Block, Room 205',
|
||||||
|
specialization: 'Cyber Security, Networks',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Events ──────────────────────────────────────────────────────────────────
|
||||||
|
export const EVENTS_DATA: Event[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'Fresher\'s Orientation Day 2025',
|
||||||
|
description: 'Welcome ceremony for the new batch of students. Meet faculty, seniors, and explore campus.',
|
||||||
|
date: '2025-08-05',
|
||||||
|
time: '09:00 AM',
|
||||||
|
venue: 'Main Auditorium',
|
||||||
|
organizer: 'Student Council',
|
||||||
|
category: 'seminar',
|
||||||
|
isUpcoming: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Hackathon 2025 – RIT Code Fest',
|
||||||
|
description: 'A 24-hour hackathon open to all departments. Build innovative solutions for real-world problems.',
|
||||||
|
date: '2025-08-20',
|
||||||
|
time: '08:00 AM',
|
||||||
|
venue: 'Computer Lab Complex',
|
||||||
|
organizer: 'CSE Department',
|
||||||
|
category: 'technical',
|
||||||
|
isUpcoming: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
title: 'Techno Cultural Fest – RITMO',
|
||||||
|
description: 'Annual cultural and technical fest with competitions, performances, and prize money up to ₹2 lakhs.',
|
||||||
|
date: '2025-09-15',
|
||||||
|
time: '10:00 AM',
|
||||||
|
venue: 'College Ground',
|
||||||
|
organizer: 'Arts & Cultural Committee',
|
||||||
|
category: 'cultural',
|
||||||
|
isUpcoming: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
title: 'Industry Expert Talk – AI & Future',
|
||||||
|
description: 'Guest lecture by industry experts from Google and Microsoft on AI trends and career opportunities.',
|
||||||
|
date: '2025-08-10',
|
||||||
|
time: '02:00 PM',
|
||||||
|
venue: 'Seminar Hall',
|
||||||
|
organizer: 'ECE Department',
|
||||||
|
category: 'seminar',
|
||||||
|
isUpcoming: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Clubs ───────────────────────────────────────────────────────────────────
|
||||||
|
export const CLUBS_DATA: Club[] = [
|
||||||
|
{ id: '1', name: 'Coding Club', description: 'For programming enthusiasts', category: 'Technical', members: 120 },
|
||||||
|
{ id: '2', name: 'Robotics Club', description: 'Build and code robots', category: 'Technical', members: 85 },
|
||||||
|
{ id: '3', name: 'Photography Club', description: 'Capture campus moments', category: 'Creative', members: 60 },
|
||||||
|
{ id: '4', name: 'Music Club', description: 'Classical and contemporary music', category: 'Cultural', members: 95 },
|
||||||
|
{ id: '5', name: 'Drama Club', description: 'Theatre and performing arts', category: 'Cultural', members: 45 },
|
||||||
|
{ id: '6', name: 'NSS', description: 'National Service Scheme', category: 'Social', members: 200 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Community Q&A ───────────────────────────────────────────────────────────
|
||||||
|
export const QUESTIONS_DATA: Question[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
title: 'How is the hostel facility at RIT?',
|
||||||
|
body: 'I\'m a fresher joining CSE. Can someone give me honest reviews about the hostel rooms, food, and wifi?',
|
||||||
|
author: 'Priya S.',
|
||||||
|
createdAt: '2025-07-15T10:00:00Z',
|
||||||
|
answers: 12,
|
||||||
|
votes: 45,
|
||||||
|
tags: ['hostel', 'accommodation', 'fresher'],
|
||||||
|
isAnswered: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
title: 'Which subjects are most important in 1st semester CSE?',
|
||||||
|
body: 'I want to prepare in advance. What are the toughest subjects in the first semester?',
|
||||||
|
author: 'Ravi K.',
|
||||||
|
createdAt: '2025-07-18T14:30:00Z',
|
||||||
|
answers: 8,
|
||||||
|
votes: 38,
|
||||||
|
tags: ['academics', 'cse', 'semester-1'],
|
||||||
|
isAnswered: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
title: 'Is there a gym on campus?',
|
||||||
|
body: 'Does RIT have a gym or fitness center for students? What are the timings?',
|
||||||
|
author: 'Arun M.',
|
||||||
|
createdAt: '2025-07-20T09:15:00Z',
|
||||||
|
answers: 4,
|
||||||
|
votes: 22,
|
||||||
|
tags: ['campus', 'sports', 'facilities'],
|
||||||
|
isAnswered: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Confessions ─────────────────────────────────────────────────────────────
|
||||||
|
export const CONFESSIONS_DATA: Confession[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
content: 'I was so nervous on my first day that I went to the wrong department. A senior helped me out and became my best friend. RIT has such a warm community! 🧡',
|
||||||
|
createdAt: '2025-07-19T11:00:00Z',
|
||||||
|
reactions: 87,
|
||||||
|
category: 'Wholesome',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
content: 'The library WiFi is actually faster than my home connection. No complaints from me 😂',
|
||||||
|
createdAt: '2025-07-18T15:30:00Z',
|
||||||
|
reactions: 134,
|
||||||
|
category: 'Funny',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
content: 'I have a crush on someone in my class but I\'m too shy to talk. We have been in the same group project for 2 months now 😅',
|
||||||
|
createdAt: '2025-07-17T09:00:00Z',
|
||||||
|
reactions: 212,
|
||||||
|
category: 'Crush',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Toolkit Items ───────────────────────────────────────────────────────────
|
||||||
|
export const TOOLKIT_ITEMS: ToolkitItem[] = [
|
||||||
|
{ id: '1', title: 'Timetable 2025-26', description: 'Semester timetable for all departments', type: 'pdf', url: '#', icon: 'Clock', category: 'Academic' },
|
||||||
|
{ id: '2', title: 'Academic Calendar', description: 'Important dates, holidays, and exams', type: 'pdf', url: '#', icon: 'CalendarDays', category: 'Academic' },
|
||||||
|
{ id: '3', title: 'Fee Payment Form', description: 'Semester fee payment form', type: 'form', url: '#', icon: 'CreditCard', category: 'Administrative' },
|
||||||
|
{ id: '4', title: 'Syllabus 2025', description: 'Updated syllabus for all semesters', type: 'pdf', url: '#', icon: 'BookMarked', category: 'Academic' },
|
||||||
|
{ id: '5', title: 'Regulations 2021', description: 'Anna University regulations document', type: 'pdf', url: '#', icon: 'ScrollText', category: 'Regulatory' },
|
||||||
|
{ id: '6', title: 'ERP Portal Guide', description: 'Step-by-step guide to use ERP system', type: 'pdf', url: '#', icon: 'Monitor', category: 'Guide' },
|
||||||
|
{ id: '7', title: 'Leave Application', description: 'Student leave application form', type: 'form', url: '#', icon: 'FileCheck', category: 'Administrative' },
|
||||||
|
{ id: '8', title: 'Bus Pass Form', description: 'Apply for monthly bus pass', type: 'form', url: '#', icon: 'Bus', category: 'Administrative' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Campus Locations ────────────────────────────────────────────────────────
|
||||||
|
export const CAMPUS_LOCATIONS: CampusLocation[] = [
|
||||||
|
{ id: 'dept', name: 'Departments', icon: 'Building2' },
|
||||||
|
{ id: 'library', name: 'Library', icon: 'Library' },
|
||||||
|
{ id: 'labs', name: 'Labs', icon: 'FlaskConical' },
|
||||||
|
{ id: 'hostel', name: 'Hostels', icon: 'Home' },
|
||||||
|
{ id: 'canteen', name: 'Canteen', icon: 'UtensilsCrossed' },
|
||||||
|
{ id: 'busstop', name: 'Bus Stop', icon: 'Bus' },
|
||||||
|
{ id: 'auditorium', name: 'Auditorium', icon: 'Theater' },
|
||||||
|
{ id: 'sports', name: 'Sports Ground', icon: 'Trophy' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── AI Suggested Prompts ────────────────────────────────────────────────────
|
||||||
|
export const AI_SUGGESTED_PROMPTS = [
|
||||||
|
'What documents do I need for admission?',
|
||||||
|
'How do I access the ERP portal?',
|
||||||
|
'Tell me about hostel facilities',
|
||||||
|
'What are the top clubs at RIT?',
|
||||||
|
'How does the bus route system work?',
|
||||||
|
'What are the library timings?',
|
||||||
|
'Who is the HOD of CSE department?',
|
||||||
|
'What is the dress code at RIT?',
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Departments ─────────────────────────────────────────────────────────────
|
||||||
|
export const DEPARTMENTS = [
|
||||||
|
'All Departments',
|
||||||
|
'Computer Science & Engineering',
|
||||||
|
'Electronics & Communication',
|
||||||
|
'Mechanical Engineering',
|
||||||
|
'Civil Engineering',
|
||||||
|
'Information Technology',
|
||||||
|
'Electrical & Electronics',
|
||||||
|
'Artificial Intelligence & Data Science',
|
||||||
|
];
|
||||||
296
src/index.css
Normal file
296
src/index.css
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
@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&display=swap');
|
||||||
|
|
||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-primary: #F97316;
|
||||||
|
--color-primary-light: #FB923C;
|
||||||
|
--color-primary-dark: #EA580C;
|
||||||
|
--color-primary-50: #FFF7ED;
|
||||||
|
--color-primary-100: #FFEDD5;
|
||||||
|
--color-primary-200: #FED7AA;
|
||||||
|
--color-secondary: #FB923C;
|
||||||
|
--color-background: #FAFAFA;
|
||||||
|
--color-surface: #FFFFFF;
|
||||||
|
--color-border: #E5E7EB;
|
||||||
|
--color-heading: #1E293B;
|
||||||
|
--color-body: #475569;
|
||||||
|
--color-muted: #94A3B8;
|
||||||
|
--color-accent: #FFF7ED;
|
||||||
|
|
||||||
|
--font-heading: 'Playfair Display', Georgia, serif;
|
||||||
|
--font-body: 'Inter', system-ui, sans-serif;
|
||||||
|
--font-button: 'Poppins', system-ui, sans-serif;
|
||||||
|
|
||||||
|
--radius-sm: 8px;
|
||||||
|
--radius-md: 12px;
|
||||||
|
--radius-lg: 16px;
|
||||||
|
--radius-xl: 20px;
|
||||||
|
--radius-2xl: 24px;
|
||||||
|
--radius-3xl: 32px;
|
||||||
|
--radius-full: 9999px;
|
||||||
|
|
||||||
|
--shadow-soft: 0 2px 15px -3px rgba(0,0,0,0.07), 0 10px 20px -2px rgba(0,0,0,0.04);
|
||||||
|
--shadow-medium: 0 4px 25px -5px rgba(0,0,0,0.1), 0 10px 30px -5px rgba(0,0,0,0.07);
|
||||||
|
--shadow-strong: 0 8px 40px -8px rgba(0,0,0,0.15), 0 20px 45px -8px rgba(0,0,0,0.1);
|
||||||
|
--shadow-orange: 0 8px 30px -4px rgba(249,115,22,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tailwind Preflight handles resets natively. Custom modifications go here: */
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
background-color: #FAFAFA;
|
||||||
|
color: #475569;
|
||||||
|
line-height: 1.6;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar styling */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #F97316;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #EA580C;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Selection */
|
||||||
|
::selection {
|
||||||
|
background-color: rgba(249, 115, 22, 0.2);
|
||||||
|
color: #1E293B;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typography base */
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: var(--font-heading);
|
||||||
|
color: #1E293B;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus ring */
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid #F97316;
|
||||||
|
outline-offset: 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% { transform: translateY(0px); }
|
||||||
|
50% { transform: translateY(-10px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-orange {
|
||||||
|
0%, 100% { box-shadow: 0 0 0 0 rgba(249, 115, 22, 0.4); }
|
||||||
|
70% { box-shadow: 0 0 0 10px rgba(249, 115, 22, 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% { 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 {
|
||||||
|
animation: float 4s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-float-delayed {
|
||||||
|
animation: float 4s ease-in-out infinite 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-pulse-orange {
|
||||||
|
animation: pulse-orange 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-shimmer {
|
||||||
|
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: shimmer 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glass effect */
|
||||||
|
.glass {
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass-dark {
|
||||||
|
background: rgba(30, 41, 59, 0.8);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card base */
|
||||||
|
.card-base {
|
||||||
|
background: #FFFFFF;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px solid #E5E7EB;
|
||||||
|
box-shadow: 0 2px 15px -3px rgba(0,0,0,0.07), 0 10px 20px -2px rgba(0,0,0,0.04);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-base:hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: 0 8px 40px -8px rgba(0,0,0,0.15), 0 20px 45px -8px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Gradient text */
|
||||||
|
.gradient-text {
|
||||||
|
background: linear-gradient(135deg, #F97316, #FB923C, #FBBF24);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Orange button */
|
||||||
|
.btn-primary {
|
||||||
|
font-family: var(--font-button);
|
||||||
|
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px 28px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px rgba(249, 115, 22, 0.3);
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px rgba(249, 115, 22, 0.4);
|
||||||
|
background: linear-gradient(135deg, #EA580C, #F97316);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ghost button */
|
||||||
|
.btn-ghost {
|
||||||
|
font-family: var(--font-button);
|
||||||
|
background: transparent;
|
||||||
|
color: #1E293B;
|
||||||
|
border: 2px solid #E5E7EB;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 10px 26px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ghost:hover {
|
||||||
|
border-color: #F97316;
|
||||||
|
color: #F97316;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section spacing */
|
||||||
|
.section-padding {
|
||||||
|
padding: 80px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.section-padding {
|
||||||
|
padding: 56px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container */
|
||||||
|
.container-custom {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container-custom {
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Orange dot pattern background */
|
||||||
|
.dot-pattern {
|
||||||
|
background-image: radial-gradient(circle, #F9731615 1px, transparent 1px);
|
||||||
|
background-size: 24px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Noise texture overlay */
|
||||||
|
.noise-overlay::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0.03;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chat bubble */
|
||||||
|
.chat-bubble-user {
|
||||||
|
background: linear-gradient(135deg, #F97316, #FB923C);
|
||||||
|
color: white;
|
||||||
|
border-radius: 18px 18px 4px 18px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
max-width: 75%;
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-bubble-ai {
|
||||||
|
background: #F8FAFC;
|
||||||
|
color: #1E293B;
|
||||||
|
border-radius: 18px 18px 18px 4px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
max-width: 80%;
|
||||||
|
align-self: flex-start;
|
||||||
|
border: 1px solid #E5E7EB;
|
||||||
|
}
|
||||||
34
src/layouts/MainLayout.tsx
Normal file
34
src/layouts/MainLayout.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import Navbar from '@/components/Navbar/Navbar';
|
||||||
|
import Footer from '@/components/Footer/Footer';
|
||||||
|
|
||||||
|
const pageVariants = {
|
||||||
|
initial: { opacity: 0, y: 10 },
|
||||||
|
enter: { opacity: 1, y: 0 },
|
||||||
|
exit: { opacity: 0, y: -10 },
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function MainLayout() {
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
|
||||||
|
<Navbar />
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.main
|
||||||
|
key={location.pathname}
|
||||||
|
variants={pageVariants}
|
||||||
|
initial="initial"
|
||||||
|
animate="enter"
|
||||||
|
exit="exit"
|
||||||
|
transition={{ duration: 0.3, ease: 'easeInOut' }}
|
||||||
|
>
|
||||||
|
<Outlet />
|
||||||
|
</motion.main>
|
||||||
|
</AnimatePresence>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
src/lib/utils.ts
Normal file
22
src/lib/utils.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { type ClassValue, clsx } from 'clsx';
|
||||||
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatDate(dateStr: string): string {
|
||||||
|
return new Date(dateStr).toLocaleDateString('en-IN', {
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTime(timeStr: string): string {
|
||||||
|
return timeStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function truncate(str: string, n: number): string {
|
||||||
|
return str.length > n ? str.substring(0, n - 1) + '…' : str;
|
||||||
|
}
|
||||||
10
src/main.tsx
Normal file
10
src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { StrictMode } from 'react'
|
||||||
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import './index.css'
|
||||||
|
import App from './App.tsx'
|
||||||
|
|
||||||
|
createRoot(document.getElementById('root')!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>,
|
||||||
|
)
|
||||||
252
src/pages/AIAssistant/AIAssistant.tsx
Normal file
252
src/pages/AIAssistant/AIAssistant.tsx
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
import { useState, useRef, useEffect } from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { Bot, Send, Sparkles, RefreshCw, Mic, X, ChevronRight } from 'lucide-react';
|
||||||
|
import { AI_SUGGESTED_PROMPTS } from '@/constants';
|
||||||
|
import type { ChatMessage } from '@/types';
|
||||||
|
|
||||||
|
const INITIAL_MESSAGES: ChatMessage[] = [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
role: 'assistant',
|
||||||
|
content: "👋 Hi there! I'm your **RIT AI Assistant**, powered by Google Gemini. I have comprehensive knowledge about Rajalakshmi Institute of Technology — from admission procedures to campus facilities.\n\nHow can I help you today?",
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function formatContent(text: string) {
|
||||||
|
return text
|
||||||
|
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||||
|
.replace(/\n/g, '<br/>');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AIAssistant() {
|
||||||
|
const [messages, setMessages] = useState<ChatMessage[]>(INITIAL_MESSAGES);
|
||||||
|
const [input, setInput] = useState('');
|
||||||
|
const [isTyping, setIsTyping] = useState(false);
|
||||||
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||||
|
}, [messages, isTyping]);
|
||||||
|
|
||||||
|
const sendMessage = async (text: string) => {
|
||||||
|
if (!text.trim()) return;
|
||||||
|
|
||||||
|
const userMsg: ChatMessage = {
|
||||||
|
id: Date.now().toString(),
|
||||||
|
role: 'user',
|
||||||
|
content: text,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
setMessages((prev) => [...prev, userMsg]);
|
||||||
|
setInput('');
|
||||||
|
setIsTyping(true);
|
||||||
|
|
||||||
|
// Simulate AI response
|
||||||
|
await new Promise((r) => setTimeout(r, 1500));
|
||||||
|
setIsTyping(false);
|
||||||
|
|
||||||
|
const responses: Record<string, string> = {
|
||||||
|
hostel: "**RIT Hostel Facilities:**\n\n• Separate hostels for boys and girls\n• Wi-Fi connectivity in all rooms\n• 24/7 security and CCTV\n• Hygienic canteen with vegetarian & non-vegetarian options\n• Common rooms with TV and recreation\n• In-house medical facility\n\nFor hostel admission, contact the hostel office with your Aadhaar card, medical certificate, and filled hostel application form.",
|
||||||
|
bus: "**RIT Bus Routes:**\n\nRIT operates **15+ bus routes** covering major areas of Chennai:\n\n• Route 01: Chennai Central → Koyambedu → Porur → RIT (7:00 AM)\n• Route 02: Tambaram → Chrompet → Pallavaram → RIT (7:15 AM)\n• Route 03: Anna Nagar → Vadapalani → RIT (7:20 AM)\n\nAll buses depart from respective stops by 7:30 AM. Return buses leave RIT at 4:30 PM and 6:00 PM.",
|
||||||
|
library: "**RIT Library Information:**\n\n• **Timings:** Mon-Sat 8:00 AM – 8:00 PM, Sunday 10:00 AM – 5:00 PM\n• **Collection:** Over 50,000 books, 200+ journals\n• **Digital Access:** IEEE Xplore, ACM Digital Library, Scopus\n• **Services:** Book borrowing (4 books, 14 days), Reference, Photocopying\n• **Wi-Fi:** Available throughout the library\n\nYou'll need your student ID card to access the library.",
|
||||||
|
};
|
||||||
|
|
||||||
|
const lower = text.toLowerCase();
|
||||||
|
let responseText = '';
|
||||||
|
|
||||||
|
if (lower.includes('hostel')) responseText = responses.hostel;
|
||||||
|
else if (lower.includes('bus') || lower.includes('route')) responseText = responses.bus;
|
||||||
|
else if (lower.includes('library')) responseText = responses.library;
|
||||||
|
else {
|
||||||
|
responseText = `Thank you for your question about **"${text}"**!\n\nI'm connected to RIT's knowledge base and can help you with:\n\n• 📚 Academic information & syllabus\n• 🏠 Hostel & accommodation\n• 🚌 Bus routes & timings\n• 📋 Admission procedures\n• 🏛️ Campus facilities\n• 👩🏫 Faculty information\n• 🎉 Events & clubs\n\nCould you be more specific about what you'd like to know? I'll give you the most accurate information!`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const aiMsg: ChatMessage = {
|
||||||
|
id: (Date.now() + 1).toString(),
|
||||||
|
role: 'assistant',
|
||||||
|
content: responseText,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
setMessages((prev) => [...prev, aiMsg]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
|
e.preventDefault();
|
||||||
|
sendMessage(input);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearChat = () => {
|
||||||
|
setMessages(INITIAL_MESSAGES);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex flex-col" style={{ backgroundColor: '#FAFAFA' }}>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="bg-white border-b border-[#E5E7EB] py-4 sticky top-16 z-40">
|
||||||
|
<div className="container-custom flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
|
||||||
|
<Bot className="w-5 h-5 text-white" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 className="font-semibold text-[#1E293B] text-sm" style={{ fontFamily: 'Poppins, sans-serif' }}>RIT AI Assistant</h1>
|
||||||
|
<div className="flex items-center gap-1.5 text-xs text-[#94A3B8]">
|
||||||
|
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" />
|
||||||
|
Powered by Gemini AI · Campus Knowledge Enabled
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
onClick={clearChat}
|
||||||
|
className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs text-[#94A3B8] hover:text-[#475569] hover:bg-gray-100 transition-all"
|
||||||
|
>
|
||||||
|
<RefreshCw className="w-3.5 h-3.5" />
|
||||||
|
New Chat
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 container-custom py-6 flex flex-col gap-4 max-w-4xl mx-auto w-full">
|
||||||
|
{/* Features strip */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
className="flex flex-wrap gap-2"
|
||||||
|
>
|
||||||
|
{[
|
||||||
|
{ icon: Sparkles, label: 'Gemini AI' },
|
||||||
|
{ icon: Bot, label: 'RAG Enabled' },
|
||||||
|
{ icon: ChevronRight, label: 'Campus Knowledge' },
|
||||||
|
].map((item, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-white border border-[#E5E7EB] text-xs text-[#475569]">
|
||||||
|
<item.icon className="w-3.5 h-3.5 text-[#F97316]" />
|
||||||
|
{item.label}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Messages */}
|
||||||
|
<div
|
||||||
|
className="flex-1 bg-white rounded-3xl border border-[#E5E7EB] p-6 flex flex-col gap-4 overflow-y-auto"
|
||||||
|
style={{ minHeight: '400px', maxHeight: '65vh', boxShadow: '0 4px 25px -5px rgba(0,0,0,0.08)' }}
|
||||||
|
>
|
||||||
|
{messages.map((msg) => (
|
||||||
|
<motion.div
|
||||||
|
key={msg.id}
|
||||||
|
initial={{ opacity: 0, y: 12 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
className={`flex gap-3 ${msg.role === 'user' ? 'flex-row-reverse' : ''}`}
|
||||||
|
>
|
||||||
|
{msg.role === 'assistant' && (
|
||||||
|
<div className="w-8 h-8 rounded-xl flex items-center justify-center shrink-0 mt-1" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
|
||||||
|
<Bot className="w-4 h-4 text-white" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className="max-w-[80%] px-4 py-3 text-sm leading-relaxed"
|
||||||
|
style={{
|
||||||
|
background: msg.role === 'user' ? 'linear-gradient(135deg, #F97316, #FB923C)' : '#F8FAFC',
|
||||||
|
color: msg.role === 'user' ? 'white' : '#1E293B',
|
||||||
|
borderRadius: msg.role === 'user' ? '20px 20px 4px 20px' : '20px 20px 20px 4px',
|
||||||
|
border: msg.role === 'ai' ? '1px solid #E5E7EB' : 'none',
|
||||||
|
fontFamily: 'Inter, sans-serif',
|
||||||
|
}}
|
||||||
|
dangerouslySetInnerHTML={{ __html: formatContent(msg.content) }}
|
||||||
|
/>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Typing */}
|
||||||
|
<AnimatePresence>
|
||||||
|
{isTyping && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
className="flex gap-3"
|
||||||
|
>
|
||||||
|
<div className="w-8 h-8 rounded-xl flex items-center justify-center shrink-0" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
|
||||||
|
<Bot className="w-4 h-4 text-white" />
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1 px-4 py-3 bg-[#F8FAFC] rounded-2xl border border-[#E5E7EB]">
|
||||||
|
{[0, 1, 2].map((i) => (
|
||||||
|
<motion.span
|
||||||
|
key={i}
|
||||||
|
className="w-2 h-2 bg-[#94A3B8] rounded-full"
|
||||||
|
animate={{ y: [0, -5, 0] }}
|
||||||
|
transition={{ duration: 0.8, repeat: Infinity, delay: i * 0.15 }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
<div ref={messagesEndRef} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Suggested Prompts */}
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{AI_SUGGESTED_PROMPTS.slice(0, 4).map((prompt, i) => (
|
||||||
|
<motion.button
|
||||||
|
key={i}
|
||||||
|
whileHover={{ scale: 1.02, backgroundColor: '#FFF7ED' }}
|
||||||
|
whileTap={{ scale: 0.98 }}
|
||||||
|
onClick={() => sendMessage(prompt)}
|
||||||
|
className="px-3.5 py-2 rounded-xl border border-[#E5E7EB] text-xs text-[#475569] bg-white transition-all hover:border-[#F97316] hover:text-[#F97316]"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
>
|
||||||
|
{prompt}
|
||||||
|
</motion.button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Input */}
|
||||||
|
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-3 flex items-center gap-3" style={{ boxShadow: '0 4px 20px -4px rgba(0,0,0,0.08)' }}>
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
value={input}
|
||||||
|
onChange={(e) => setInput(e.target.value)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
placeholder="Ask anything about RIT..."
|
||||||
|
className="flex-1 text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none bg-transparent"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
/>
|
||||||
|
{input && (
|
||||||
|
<motion.button
|
||||||
|
initial={{ scale: 0 }}
|
||||||
|
animate={{ scale: 1 }}
|
||||||
|
whileTap={{ scale: 0.9 }}
|
||||||
|
onClick={() => setInput('')}
|
||||||
|
className="w-8 h-8 rounded-xl flex items-center justify-center text-[#94A3B8] hover:bg-gray-100 transition-all"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</motion.button>
|
||||||
|
)}
|
||||||
|
<button className="w-9 h-9 rounded-xl flex items-center justify-center text-[#94A3B8] hover:bg-gray-100 transition-all">
|
||||||
|
<Mic className="w-4.5 h-4.5" />
|
||||||
|
</button>
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
whileTap={{ scale: 0.95 }}
|
||||||
|
onClick={() => sendMessage(input)}
|
||||||
|
disabled={!input.trim()}
|
||||||
|
className="w-10 h-10 rounded-xl flex items-center justify-center text-white transition-all disabled:opacity-50"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||||
|
>
|
||||||
|
<Send className="w-4 h-4" />
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
230
src/pages/Campus/Campus.tsx
Normal file
230
src/pages/Campus/Campus.tsx
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Search, ChevronRight, MapPin } from 'lucide-react';
|
||||||
|
import SectionTitle from '@/components/SectionTitle/SectionTitle';
|
||||||
|
import BusCard from '@/components/BusCard/BusCard';
|
||||||
|
import FacultyCard from '@/components/FacultyCard/FacultyCard';
|
||||||
|
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
|
||||||
|
import { BUS_ROUTES, FACULTY_DATA, CAMPUS_LOCATIONS, DEPARTMENTS } from '@/constants';
|
||||||
|
import * as LucideIcons from 'lucide-react';
|
||||||
|
|
||||||
|
type Tab = 'map' | 'bus' | 'faculty';
|
||||||
|
|
||||||
|
export default function Campus() {
|
||||||
|
const [activeTab, setActiveTab] = useState<Tab>('map');
|
||||||
|
const [searchFaculty, setSearchFaculty] = useState('');
|
||||||
|
const [selectedDept, setSelectedDept] = useState('All Departments');
|
||||||
|
const [busSearch, setBusSearch] = useState('');
|
||||||
|
|
||||||
|
const filteredFaculty = FACULTY_DATA.filter((f) => {
|
||||||
|
const matchSearch = f.name.toLowerCase().includes(searchFaculty.toLowerCase()) ||
|
||||||
|
f.department.toLowerCase().includes(searchFaculty.toLowerCase());
|
||||||
|
const matchDept = selectedDept === 'All Departments' || f.department === selectedDept;
|
||||||
|
return matchSearch && matchDept;
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredRoutes = BUS_ROUTES.filter((r) =>
|
||||||
|
r.name.toLowerCase().includes(busSearch.toLowerCase()) ||
|
||||||
|
r.from.toLowerCase().includes(busSearch.toLowerCase()) ||
|
||||||
|
r.number.toLowerCase().includes(busSearch.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
const TABS: { id: Tab; label: string; icon: string }[] = [
|
||||||
|
{ id: 'map', label: 'Campus Map', icon: 'Map' },
|
||||||
|
{ id: 'bus', label: 'Bus Routes', icon: 'Bus' },
|
||||||
|
{ id: 'faculty', label: 'Faculty Directory', icon: 'Users' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="bg-white border-b border-[#E5E7EB] py-10">
|
||||||
|
<div className="container-custom">
|
||||||
|
<div className="flex items-center gap-2 text-xs text-[#94A3B8] mb-3">
|
||||||
|
<span>Home</span>
|
||||||
|
<ChevronRight className="w-3 h-3" />
|
||||||
|
<span className="text-[#F97316]">Campus</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||||
|
Explore{' '}
|
||||||
|
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||||
|
Campus
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
Navigate campus, find bus routes, and connect with faculty.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="container-custom py-8">
|
||||||
|
{/* Tabs */}
|
||||||
|
<div className="flex gap-2 bg-white rounded-2xl p-2 border border-[#E5E7EB] mb-8 w-fit" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||||
|
{TABS.map((tab) => {
|
||||||
|
const Icon = (LucideIcons as Record<string, React.ComponentType<{ className?: string }>>)[tab.icon];
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={tab.id}
|
||||||
|
onClick={() => setActiveTab(tab.id)}
|
||||||
|
className="relative flex items-center gap-2 px-5 py-2.5 rounded-xl text-sm font-medium transition-all"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Poppins, sans-serif',
|
||||||
|
color: activeTab === tab.id ? 'white' : '#475569',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{activeTab === tab.id && (
|
||||||
|
<motion.div
|
||||||
|
layoutId="campus-tab"
|
||||||
|
className="absolute inset-0 rounded-xl"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||||
|
transition={{ type: 'spring', bounce: 0.2, duration: 0.4 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span className="relative z-10 flex items-center gap-2">
|
||||||
|
{Icon && <Icon className="w-4 h-4" />}
|
||||||
|
{tab.label}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Map Tab */}
|
||||||
|
{activeTab === 'map' && (
|
||||||
|
<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
||||||
|
<div className="bg-white rounded-3xl border border-[#E5E7EB] overflow-hidden mb-8" style={{ boxShadow: '0 4px 25px -5px rgba(0,0,0,0.1)' }}>
|
||||||
|
<div
|
||||||
|
className="h-96 flex items-center justify-center relative overflow-hidden"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)' }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 opacity-10"
|
||||||
|
style={{
|
||||||
|
backgroundImage: 'linear-gradient(rgba(249,115,22,0.5) 1px, transparent 1px), linear-gradient(90deg, rgba(249,115,22,0.5) 1px, transparent 1px)',
|
||||||
|
backgroundSize: '40px 40px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Mock campus blocks */}
|
||||||
|
{[
|
||||||
|
{ label: 'CSE Block', x: '20%', y: '25%', w: '14%', h: '18%' },
|
||||||
|
{ label: 'ECE Block', x: '38%', y: '25%', w: '12%', h: '18%' },
|
||||||
|
{ label: 'Library', x: '58%', y: '30%', w: '16%', h: '14%' },
|
||||||
|
{ label: 'Canteen', x: '65%', y: '58%', w: '12%', h: '12%' },
|
||||||
|
{ label: 'Hostel', x: '18%', y: '58%', w: '14%', h: '20%' },
|
||||||
|
{ label: 'Auditorium', x: '40%', y: '55%', w: '18%', h: '14%' },
|
||||||
|
].map((block, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={i}
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
className="absolute flex items-center justify-center rounded-lg cursor-pointer group"
|
||||||
|
style={{
|
||||||
|
left: block.x,
|
||||||
|
top: block.y,
|
||||||
|
width: block.w,
|
||||||
|
height: block.h,
|
||||||
|
backgroundColor: 'rgba(249,115,22,0.15)',
|
||||||
|
border: '1px solid rgba(249,115,22,0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="text-[10px] text-orange-300 font-medium text-center leading-tight px-1" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
{block.label}
|
||||||
|
</span>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* Pin */}
|
||||||
|
<div className="relative z-10 text-center">
|
||||||
|
<div className="flex items-center gap-2 text-white/40 text-sm">
|
||||||
|
<MapPin className="w-4 h-4" />
|
||||||
|
<span>RIT Campus, Kuthambakkam, Chennai</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Nav */}
|
||||||
|
<div className="p-6 grid grid-cols-4 sm:grid-cols-8 gap-4">
|
||||||
|
{CAMPUS_LOCATIONS.map((loc) => {
|
||||||
|
const Icon = (LucideIcons as Record<string, React.ComponentType<{ className?: string }>>)[loc.icon];
|
||||||
|
return (
|
||||||
|
<motion.button
|
||||||
|
key={loc.id}
|
||||||
|
whileHover={{ y: -3, backgroundColor: '#FFF7ED' }}
|
||||||
|
className="flex flex-col items-center gap-2 p-3 rounded-xl transition-all hover:border-[#FED7AA] border border-transparent"
|
||||||
|
>
|
||||||
|
<div className="w-10 h-10 rounded-xl bg-[#F8FAFC] flex items-center justify-center">
|
||||||
|
{Icon && <Icon className="w-5 h-5 text-[#F97316]" />}
|
||||||
|
</div>
|
||||||
|
<span className="text-[11px] text-[#475569] text-center leading-tight" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
{loc.name}
|
||||||
|
</span>
|
||||||
|
</motion.button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Bus Routes Tab */}
|
||||||
|
{activeTab === 'bus' && (
|
||||||
|
<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
||||||
|
<div className="relative mb-6">
|
||||||
|
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-[#94A3B8]" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search bus routes..."
|
||||||
|
value={busSearch}
|
||||||
|
onChange={(e) => setBusSearch(e.target.value)}
|
||||||
|
className="w-full pl-11 pr-4 py-3 rounded-xl border border-[#E5E7EB] bg-white text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] transition-colors"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
{filteredRoutes.map((route) => (
|
||||||
|
<StaggerItem key={route.id}>
|
||||||
|
<BusCard route={route} />
|
||||||
|
</StaggerItem>
|
||||||
|
))}
|
||||||
|
</StaggerContainer>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Faculty Tab */}
|
||||||
|
{activeTab === 'faculty' && (
|
||||||
|
<motion.div initial={{ opacity: 0, y: 16 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 mb-6">
|
||||||
|
<div className="relative flex-1">
|
||||||
|
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-[#94A3B8]" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search faculty by name or department..."
|
||||||
|
value={searchFaculty}
|
||||||
|
onChange={(e) => setSearchFaculty(e.target.value)}
|
||||||
|
className="w-full pl-11 pr-4 py-3 rounded-xl border border-[#E5E7EB] bg-white text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] transition-colors"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
value={selectedDept}
|
||||||
|
onChange={(e) => setSelectedDept(e.target.value)}
|
||||||
|
className="px-4 py-3 rounded-xl border border-[#E5E7EB] bg-white text-sm text-[#475569] focus:outline-none focus:border-[#F97316] transition-colors"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
>
|
||||||
|
{DEPARTMENTS.map((dept) => (
|
||||||
|
<option key={dept} value={dept}>{dept}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{filteredFaculty.map((faculty) => (
|
||||||
|
<StaggerItem key={faculty.id}>
|
||||||
|
<FacultyCard faculty={faculty} />
|
||||||
|
</StaggerItem>
|
||||||
|
))}
|
||||||
|
</StaggerContainer>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
394
src/pages/Community/Community.tsx
Normal file
394
src/pages/Community/Community.tsx
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import {
|
||||||
|
MessageCircle, Heart, TrendingUp, ThumbsUp, Send,
|
||||||
|
Shield, Lock, Smile, ChevronRight, Search, Plus
|
||||||
|
} from 'lucide-react';
|
||||||
|
import SectionTitle from '@/components/SectionTitle/SectionTitle';
|
||||||
|
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
|
||||||
|
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer';
|
||||||
|
import { QUESTIONS_DATA, CONFESSIONS_DATA } from '@/constants';
|
||||||
|
import { formatDate } from '@/lib/utils';
|
||||||
|
|
||||||
|
type Tab = 'qa' | 'confession';
|
||||||
|
|
||||||
|
const TRENDING_TAGS = ['hostel', 'academics', 'clubs', 'campus', 'canteen', 'sports', 'placement', 'library'];
|
||||||
|
|
||||||
|
export default function Community() {
|
||||||
|
const [activeTab, setActiveTab] = useState<Tab>('qa');
|
||||||
|
const [confessionText, setConfessionText] = useState('');
|
||||||
|
const [questionText, setQuestionText] = useState('');
|
||||||
|
const [confessionPosted, setConfessionPosted] = useState(false);
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [likedIds, setLikedIds] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
|
const filteredQuestions = QUESTIONS_DATA.filter((q) =>
|
||||||
|
q.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
q.tags.some((t) => t.includes(searchQuery.toLowerCase()))
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleConfess = () => {
|
||||||
|
if (!confessionText.trim()) return;
|
||||||
|
setConfessionPosted(true);
|
||||||
|
setConfessionText('');
|
||||||
|
setTimeout(() => setConfessionPosted(false), 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleLike = (id: string) => {
|
||||||
|
setLikedIds((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
if (next.has(id)) next.delete(id); else next.add(id);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="bg-white border-b border-[#E5E7EB] py-10">
|
||||||
|
<div className="container-custom">
|
||||||
|
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||||
|
RIT{' '}
|
||||||
|
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||||
|
Community
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
Ask questions, share confessions, and connect with fellow RIT students.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="container-custom py-8">
|
||||||
|
{/* Tabs */}
|
||||||
|
<div className="flex gap-2 bg-white rounded-2xl p-2 border border-[#E5E7EB] mb-8 w-fit" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||||
|
{[
|
||||||
|
{ id: 'qa' as Tab, label: 'Freshers Q&A', icon: MessageCircle },
|
||||||
|
{ id: 'confession' as Tab, label: 'Confessions', icon: Heart },
|
||||||
|
].map((tab) => (
|
||||||
|
<button
|
||||||
|
key={tab.id}
|
||||||
|
onClick={() => setActiveTab(tab.id)}
|
||||||
|
className="relative flex items-center gap-2 px-5 py-2.5 rounded-xl text-sm font-medium transition-all"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif', color: activeTab === tab.id ? 'white' : '#475569' }}
|
||||||
|
>
|
||||||
|
{activeTab === tab.id && (
|
||||||
|
<motion.div
|
||||||
|
layoutId="community-tab"
|
||||||
|
className="absolute inset-0 rounded-xl"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||||
|
transition={{ type: 'spring', bounce: 0.2, duration: 0.4 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span className="relative z-10 flex items-center gap-2">
|
||||||
|
<tab.icon className="w-4 h-4" />
|
||||||
|
{tab.label}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
{/* Q&A Tab */}
|
||||||
|
{activeTab === 'qa' && (
|
||||||
|
<motion.div
|
||||||
|
key="qa"
|
||||||
|
initial={{ opacity: 0, y: 16 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -16 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
{/* Ask Question Box */}
|
||||||
|
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-5 mb-6" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||||
|
<h3 className="text-sm font-semibold text-[#1E293B] mb-3" style={{ fontFamily: 'Poppins, sans-serif' }}>Ask a Question</h3>
|
||||||
|
<textarea
|
||||||
|
value={questionText}
|
||||||
|
onChange={(e) => setQuestionText(e.target.value)}
|
||||||
|
placeholder="What's on your mind? Ask your seniors anything about RIT..."
|
||||||
|
rows={3}
|
||||||
|
className="w-full border border-[#E5E7EB] rounded-xl p-3 text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] resize-none transition-colors mb-3"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
/>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
Your question will be visible to all students
|
||||||
|
</span>
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.03 }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
className="flex items-center gap-2 px-4 py-2 rounded-xl text-white text-sm font-semibold"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||||
|
>
|
||||||
|
<Plus className="w-4 h-4" />
|
||||||
|
Post Question
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Search */}
|
||||||
|
<div className="relative mb-5">
|
||||||
|
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-[#94A3B8]" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search questions..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="w-full pl-10 pr-4 py-2.5 rounded-xl border border-[#E5E7EB] bg-white text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] transition-colors"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Questions */}
|
||||||
|
<StaggerContainer className="flex flex-col gap-4">
|
||||||
|
{filteredQuestions.map((q) => (
|
||||||
|
<StaggerItem key={q.id}>
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ y: -2 }}
|
||||||
|
className="bg-white rounded-2xl border border-[#E5E7EB] p-5"
|
||||||
|
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
{/* Votes */}
|
||||||
|
<div className="flex flex-col items-center gap-1 shrink-0">
|
||||||
|
<button
|
||||||
|
onClick={() => toggleLike(q.id)}
|
||||||
|
className="w-8 h-8 rounded-lg flex items-center justify-center transition-all"
|
||||||
|
style={{ backgroundColor: likedIds.has(q.id) ? '#FFF7ED' : '#F8FAFC' }}
|
||||||
|
>
|
||||||
|
<ThumbsUp className="w-4 h-4" style={{ color: likedIds.has(q.id) ? '#F97316' : '#94A3B8' }} />
|
||||||
|
</button>
|
||||||
|
<span className="text-xs font-semibold text-[#475569]">{q.votes + (likedIds.has(q.id) ? 1 : 0)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1">
|
||||||
|
<h3 className="text-sm font-semibold text-[#1E293B] mb-1.5 hover:text-[#F97316] cursor-pointer transition-colors"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
{q.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-[#94A3B8] mb-3 line-clamp-2" style={{ fontFamily: 'Inter, sans-serif' }}>{q.body}</p>
|
||||||
|
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{q.tags.map((tag) => (
|
||||||
|
<span
|
||||||
|
key={tag}
|
||||||
|
className="px-2 py-0.5 rounded-full text-[10px] font-medium bg-[#F8FAFC] text-[#94A3B8] border border-[#E5E7EB]"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
>
|
||||||
|
#{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3 text-xs text-[#94A3B8]">
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<MessageCircle className="w-3.5 h-3.5" />
|
||||||
|
{q.answers} answers
|
||||||
|
</span>
|
||||||
|
{q.isAnswered && (
|
||||||
|
<span className="px-2 py-0.5 rounded-full bg-emerald-50 text-emerald-600 text-[10px] font-semibold">✓ Answered</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</StaggerItem>
|
||||||
|
))}
|
||||||
|
</StaggerContainer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sidebar */}
|
||||||
|
<div className="flex flex-col gap-5">
|
||||||
|
{/* Trending */}
|
||||||
|
<AnimatedContainer direction="right" delay={0.1}>
|
||||||
|
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-5" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||||
|
<div className="flex items-center gap-2 mb-4">
|
||||||
|
<TrendingUp className="w-4.5 h-4.5 text-[#F97316]" />
|
||||||
|
<h3 className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Trending Topics</h3>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{TRENDING_TAGS.map((tag) => (
|
||||||
|
<button
|
||||||
|
key={tag}
|
||||||
|
onClick={() => setSearchQuery(tag)}
|
||||||
|
className="px-3 py-1.5 rounded-full text-xs font-medium bg-[#FFF7ED] text-[#F97316] hover:bg-[#F97316] hover:text-white transition-all border border-[#FED7AA]"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
>
|
||||||
|
#{tag}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AnimatedContainer>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<AnimatedContainer direction="right" delay={0.2}>
|
||||||
|
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-5" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||||
|
{[
|
||||||
|
{ label: 'Total Questions', value: '124' },
|
||||||
|
{ label: 'Answered', value: '98%' },
|
||||||
|
{ label: 'Active Students', value: '340+' },
|
||||||
|
].map((stat, i) => (
|
||||||
|
<div key={i} className={`flex justify-between py-2.5 ${i < 2 ? 'border-b border-[#E5E7EB]' : ''}`}>
|
||||||
|
<span className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>{stat.label}</span>
|
||||||
|
<span className="text-xs font-bold text-[#F97316]" style={{ fontFamily: 'Poppins, sans-serif' }}>{stat.value}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</AnimatedContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Confessions Tab */}
|
||||||
|
{activeTab === 'confession' && (
|
||||||
|
<motion.div
|
||||||
|
key="confession"
|
||||||
|
initial={{ opacity: 0, y: 16 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: -16 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
{/* Post confession */}
|
||||||
|
<div
|
||||||
|
className="rounded-3xl p-6 mb-8 border"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)', borderColor: 'rgba(255,255,255,0.1)' }}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ backgroundColor: 'rgba(249,115,22,0.2)' }}>
|
||||||
|
<Lock className="w-5 h-5 text-[#F97316]" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-white font-semibold text-sm" style={{ fontFamily: 'Poppins, sans-serif' }}>Share Anonymously</h3>
|
||||||
|
<p className="text-slate-400 text-xs">Your identity is never revealed</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<textarea
|
||||||
|
value={confessionText}
|
||||||
|
onChange={(e) => setConfessionText(e.target.value)}
|
||||||
|
placeholder="Share your thoughts, stories, crushes, or anything on your mind... It's completely anonymous 🤫"
|
||||||
|
rows={4}
|
||||||
|
className="w-full rounded-xl p-4 text-sm placeholder-slate-500 focus:outline-none resize-none mb-4"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Inter, sans-serif',
|
||||||
|
backgroundColor: 'rgba(255,255,255,0.06)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.1)',
|
||||||
|
color: 'white',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3 p-3 rounded-xl mb-4" style={{ backgroundColor: 'rgba(249,115,22,0.1)', border: '1px solid rgba(249,115,22,0.2)' }}>
|
||||||
|
<Shield className="w-4 h-4 text-[#F97316] shrink-0" />
|
||||||
|
<span className="text-xs text-slate-400" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
No IP tracking. No username. 100% anonymous posting.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.03 }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
onClick={handleConfess}
|
||||||
|
className="flex items-center gap-2 px-6 py-2.5 rounded-xl text-white text-sm font-semibold"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||||
|
>
|
||||||
|
<Smile className="w-4 h-4" />
|
||||||
|
Post Anonymously
|
||||||
|
</motion.button>
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{confessionPosted && (
|
||||||
|
<motion.span
|
||||||
|
initial={{ opacity: 0, x: -10 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
exit={{ opacity: 0 }}
|
||||||
|
className="text-xs text-emerald-400"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
>
|
||||||
|
✓ Posted successfully!
|
||||||
|
</motion.span>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Confessions Feed */}
|
||||||
|
<StaggerContainer className="flex flex-col gap-4">
|
||||||
|
{CONFESSIONS_DATA.map((conf) => (
|
||||||
|
<StaggerItem key={conf.id}>
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ y: -2 }}
|
||||||
|
className="bg-white rounded-2xl border border-[#E5E7EB] p-5"
|
||||||
|
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between mb-3">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-8 h-8 rounded-xl flex items-center justify-center bg-[#FFF7ED]">
|
||||||
|
<Shield className="w-4 h-4 text-[#F97316]" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span className="text-xs font-semibold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Anonymous</span>
|
||||||
|
<p className="text-[10px] text-[#94A3B8]">{formatDate(conf.createdAt)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{conf.category && (
|
||||||
|
<span className="px-2.5 py-0.5 rounded-full text-[10px] font-semibold bg-[#FFF7ED] text-[#F97316] border border-[#FED7AA]" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
{conf.category}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-[#475569] leading-relaxed mb-4" style={{ fontFamily: 'Inter, sans-serif' }}>{conf.content}</p>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<button
|
||||||
|
onClick={() => toggleLike(conf.id)}
|
||||||
|
className="flex items-center gap-2 px-3 py-1.5 rounded-xl transition-all"
|
||||||
|
style={{ backgroundColor: likedIds.has(conf.id) ? '#FFF7ED' : '#F8FAFC' }}
|
||||||
|
>
|
||||||
|
<Heart
|
||||||
|
className="w-3.5 h-3.5"
|
||||||
|
style={{ color: likedIds.has(conf.id) ? '#F97316' : '#94A3B8' }}
|
||||||
|
fill={likedIds.has(conf.id) ? '#F97316' : 'none'}
|
||||||
|
/>
|
||||||
|
<span className="text-xs text-[#94A3B8]">{conf.reactions + (likedIds.has(conf.id) ? 1 : 0)}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</StaggerItem>
|
||||||
|
))}
|
||||||
|
</StaggerContainer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sidebar */}
|
||||||
|
<AnimatedContainer direction="right" delay={0.15}>
|
||||||
|
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-5" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||||
|
<h3 className="text-sm font-semibold text-[#1E293B] mb-4 flex items-center gap-2" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
<Shield className="w-4 h-4 text-[#F97316]" />
|
||||||
|
Community Rules
|
||||||
|
</h3>
|
||||||
|
{[
|
||||||
|
'Be respectful and kind',
|
||||||
|
'No hate speech or bullying',
|
||||||
|
'No personal information',
|
||||||
|
'Keep it relevant to campus life',
|
||||||
|
'Confessions are 100% anonymous',
|
||||||
|
].map((rule, i) => (
|
||||||
|
<div key={i} className="flex items-start gap-2.5 py-2.5 border-b border-[#E5E7EB] last:border-0">
|
||||||
|
<ChevronRight className="w-3.5 h-3.5 text-[#F97316] mt-0.5 shrink-0" />
|
||||||
|
<span className="text-xs text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>{rule}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</AnimatedContainer>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
263
src/pages/Events/Events.tsx
Normal file
263
src/pages/Events/Events.tsx
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
|
import { Calendar, MapPin, Clock, Users, ChevronLeft, ChevronRight, Tag, ExternalLink } from 'lucide-react';
|
||||||
|
import SectionTitle from '@/components/SectionTitle/SectionTitle';
|
||||||
|
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
|
||||||
|
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer';
|
||||||
|
import { EVENTS_DATA, CLUBS_DATA } from '@/constants';
|
||||||
|
|
||||||
|
const CATEGORY_COLORS: Record<string, { bg: string; text: string; border: string }> = {
|
||||||
|
technical: { bg: '#EFF6FF', text: '#3B82F6', border: '#BFDBFE' },
|
||||||
|
cultural: { bg: '#FDF2F8', text: '#EC4899', border: '#FBCFE8' },
|
||||||
|
sports: { bg: '#ECFDF5', text: '#10B981', border: '#A7F3D0' },
|
||||||
|
seminar: { bg: '#FFF7ED', text: '#F97316', border: '#FED7AA' },
|
||||||
|
workshop: { bg: '#F5F3FF', text: '#8B5CF6', border: '#DDD6FE' },
|
||||||
|
};
|
||||||
|
|
||||||
|
const CLUB_CATEGORY_COLORS: Record<string, string> = {
|
||||||
|
Technical: '#3B82F6',
|
||||||
|
Cultural: '#EC4899',
|
||||||
|
Social: '#10B981',
|
||||||
|
Creative: '#F59E0B',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Events() {
|
||||||
|
const [currentIdx, setCurrentIdx] = useState(0);
|
||||||
|
const [selectedCategory, setSelectedCategory] = useState('All');
|
||||||
|
|
||||||
|
const categories = ['All', 'Technical', 'Cultural', 'Seminar', 'Workshop'];
|
||||||
|
|
||||||
|
const filteredEvents = EVENTS_DATA.filter((e) =>
|
||||||
|
selectedCategory === 'All' || e.category === selectedCategory.toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
|
const prev = () => setCurrentIdx((i) => (i === 0 ? EVENTS_DATA.length - 1 : i - 1));
|
||||||
|
const next = () => setCurrentIdx((i) => (i === EVENTS_DATA.length - 1 ? 0 : i + 1));
|
||||||
|
|
||||||
|
const featured = EVENTS_DATA[currentIdx];
|
||||||
|
const featCfg = CATEGORY_COLORS[featured.category] || CATEGORY_COLORS.seminar;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="bg-white border-b border-[#E5E7EB] py-10">
|
||||||
|
<div className="container-custom">
|
||||||
|
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||||
|
Clubs &{' '}
|
||||||
|
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||||
|
Events
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
Discover events, join clubs, and be part of the RIT community.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="container-custom py-10">
|
||||||
|
{/* Featured Event Carousel */}
|
||||||
|
<SectionTitle tag="Upcoming" title="Featured" highlight="Events" />
|
||||||
|
|
||||||
|
<AnimatedContainer className="relative mb-14">
|
||||||
|
<div className="bg-white rounded-3xl border border-[#E5E7EB] overflow-hidden" style={{ boxShadow: '0 8px 40px -8px rgba(0,0,0,0.1)' }}>
|
||||||
|
<AnimatePresence mode="wait">
|
||||||
|
<motion.div
|
||||||
|
key={featured.id}
|
||||||
|
initial={{ opacity: 0, x: 30 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
exit={{ opacity: 0, x: -30 }}
|
||||||
|
transition={{ duration: 0.3 }}
|
||||||
|
className="flex flex-col md:flex-row"
|
||||||
|
>
|
||||||
|
{/* Image placeholder */}
|
||||||
|
<div
|
||||||
|
className="w-full md:w-2/5 h-56 md:h-auto flex items-center justify-center"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)' }}
|
||||||
|
>
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="text-5xl mb-2">
|
||||||
|
{featured.category === 'technical' ? '💻' : featured.category === 'cultural' ? '🎭' : featured.category === 'seminar' ? '🎤' : '📅'}
|
||||||
|
</div>
|
||||||
|
<div className="text-white/60 text-sm">{featured.venue}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="flex-1 p-8">
|
||||||
|
<div className="flex items-center gap-2 mb-3">
|
||||||
|
<span
|
||||||
|
className="px-3 py-1 rounded-full text-xs font-semibold capitalize"
|
||||||
|
style={{ backgroundColor: featCfg.bg, color: featCfg.text, border: `1px solid ${featCfg.border}`, fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
>
|
||||||
|
{featured.category}
|
||||||
|
</span>
|
||||||
|
<span className="px-3 py-1 rounded-full text-xs font-semibold bg-emerald-50 text-emerald-600 border border-emerald-200" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
Upcoming
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="text-2xl font-bold text-[#1E293B] mb-3" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||||
|
{featured.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-[#475569] text-sm leading-relaxed mb-5" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
{featured.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3 mb-6">
|
||||||
|
{[
|
||||||
|
{ icon: Calendar, label: new Date(featured.date).toLocaleDateString('en-IN', { day: 'numeric', month: 'long', year: 'numeric' }) },
|
||||||
|
{ icon: Clock, label: featured.time },
|
||||||
|
{ icon: MapPin, label: featured.venue },
|
||||||
|
{ icon: Users, label: featured.organizer },
|
||||||
|
].map((item, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-2 text-sm text-[#475569]">
|
||||||
|
<item.icon className="w-4 h-4 text-[#F97316] shrink-0" />
|
||||||
|
<span style={{ fontFamily: 'Inter, sans-serif' }}>{item.label}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.03 }}
|
||||||
|
className="flex items-center gap-2 px-5 py-2.5 rounded-xl text-white text-sm font-semibold"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||||
|
>
|
||||||
|
Register Now
|
||||||
|
<ExternalLink className="w-4 h-4" />
|
||||||
|
</motion.button>
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.03 }}
|
||||||
|
className="px-5 py-2.5 rounded-xl text-sm font-semibold border-2 border-[#E5E7EB] text-[#475569] hover:border-[#F97316] hover:text-[#F97316] transition-all"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
>
|
||||||
|
More Details
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Carousel Controls */}
|
||||||
|
<div className="flex items-center justify-center gap-3 mt-4">
|
||||||
|
<button onClick={prev} className="w-9 h-9 rounded-xl border border-[#E5E7EB] bg-white flex items-center justify-center text-[#475569] hover:border-[#F97316] hover:text-[#F97316] transition-all">
|
||||||
|
<ChevronLeft className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
{EVENTS_DATA.map((_, i) => (
|
||||||
|
<button
|
||||||
|
key={i}
|
||||||
|
onClick={() => setCurrentIdx(i)}
|
||||||
|
className="transition-all rounded-full"
|
||||||
|
style={{ width: i === currentIdx ? '24px' : '8px', height: '8px', backgroundColor: i === currentIdx ? '#F97316' : '#E5E7EB' }}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<button onClick={next} className="w-9 h-9 rounded-xl border border-[#E5E7EB] bg-white flex items-center justify-center text-[#475569] hover:border-[#F97316] hover:text-[#F97316] transition-all">
|
||||||
|
<ChevronRight className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</AnimatedContainer>
|
||||||
|
|
||||||
|
{/* All Events Grid */}
|
||||||
|
<div className="mb-14">
|
||||||
|
<div className="flex items-center justify-between mb-6 flex-wrap gap-4">
|
||||||
|
<h2 className="text-xl font-bold text-[#1E293B]" style={{ fontFamily: 'Playfair Display, serif' }}>All Events</h2>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
{categories.map((cat) => (
|
||||||
|
<button
|
||||||
|
key={cat}
|
||||||
|
onClick={() => setSelectedCategory(cat)}
|
||||||
|
className="px-3.5 py-1.5 rounded-xl text-xs font-medium transition-all"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Poppins, sans-serif',
|
||||||
|
backgroundColor: selectedCategory === cat ? '#F97316' : '#F8FAFC',
|
||||||
|
color: selectedCategory === cat ? 'white' : '#475569',
|
||||||
|
border: `1px solid ${selectedCategory === cat ? '#F97316' : '#E5E7EB'}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cat}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 gap-4">
|
||||||
|
{filteredEvents.map((event) => {
|
||||||
|
const cfg = CATEGORY_COLORS[event.category] || CATEGORY_COLORS.seminar;
|
||||||
|
return (
|
||||||
|
<StaggerItem key={event.id}>
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ y: -4 }}
|
||||||
|
className="bg-white rounded-2xl border border-[#E5E7EB] p-5 flex gap-4"
|
||||||
|
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||||
|
>
|
||||||
|
<div className="w-14 h-14 rounded-xl flex flex-col items-center justify-center shrink-0" style={{ backgroundColor: cfg.bg }}>
|
||||||
|
<span className="text-xs font-bold" style={{ color: cfg.text, fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
{new Date(event.date).toLocaleDateString('en-IN', { day: 'numeric' })}
|
||||||
|
</span>
|
||||||
|
<span className="text-[10px]" style={{ color: cfg.text }}>
|
||||||
|
{new Date(event.date).toLocaleDateString('en-IN', { month: 'short' })}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<span className="px-2 py-0.5 rounded-full text-[10px] font-semibold capitalize" style={{ backgroundColor: cfg.bg, color: cfg.text, fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
{event.category}
|
||||||
|
</span>
|
||||||
|
<h3 className="text-sm font-semibold text-[#1E293B] mt-1.5 mb-1" style={{ fontFamily: 'Poppins, sans-serif' }}>{event.title}</h3>
|
||||||
|
<div className="flex items-center gap-3 text-xs text-[#94A3B8]">
|
||||||
|
<span className="flex items-center gap-1"><Clock className="w-3 h-3" />{event.time}</span>
|
||||||
|
<span className="flex items-center gap-1"><MapPin className="w-3 h-3" />{event.venue}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</StaggerItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</StaggerContainer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Clubs Section */}
|
||||||
|
<SectionTitle tag="Join a Club" title="Student" highlight="Clubs" subtitle="Explore clubs and societies at RIT. Find your passion and connect with like-minded peers." />
|
||||||
|
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{CLUBS_DATA.map((club) => (
|
||||||
|
<StaggerItem key={club.id}>
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ y: -4 }}
|
||||||
|
className="bg-white rounded-2xl border border-[#E5E7EB] p-5"
|
||||||
|
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between mb-3">
|
||||||
|
<div
|
||||||
|
className="w-11 h-11 rounded-xl flex items-center justify-center text-white font-bold text-lg"
|
||||||
|
style={{ background: `linear-gradient(135deg, ${CLUB_CATEGORY_COLORS[club.category] || '#F97316'}, ${CLUB_CATEGORY_COLORS[club.category] || '#F97316'}CC)`, fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
>
|
||||||
|
{club.name[0]}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1 text-xs text-[#94A3B8]">
|
||||||
|
<Users className="w-3.5 h-3.5" />
|
||||||
|
<span>{club.members} members</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3 className="font-semibold text-[#1E293B] mb-1" style={{ fontFamily: 'Poppins, sans-serif' }}>{club.name}</h3>
|
||||||
|
<p className="text-xs text-[#94A3B8] mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>{club.description}</p>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="flex items-center gap-1 text-xs" style={{ color: CLUB_CATEGORY_COLORS[club.category] || '#F97316' }}>
|
||||||
|
<Tag className="w-3 h-3" />
|
||||||
|
{club.category}
|
||||||
|
</span>
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.04 }}
|
||||||
|
className="px-4 py-1.5 rounded-lg text-xs font-semibold text-white"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||||
|
>
|
||||||
|
Join Club
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</StaggerItem>
|
||||||
|
))}
|
||||||
|
</StaggerContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
562
src/pages/Home/Home.tsx
Normal file
562
src/pages/Home/Home.tsx
Normal file
@@ -0,0 +1,562 @@
|
|||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
ArrowRight, Sparkles, BookOpen, Bot, GraduationCap,
|
||||||
|
CheckCircle, Zap, Shield, Brain
|
||||||
|
} from 'lucide-react';
|
||||||
|
import SectionTitle from '@/components/SectionTitle/SectionTitle';
|
||||||
|
import StatsCard from '@/components/StatsCard/StatsCard';
|
||||||
|
import FeatureCard from '@/components/FeatureCard/FeatureCard';
|
||||||
|
import FloatingCard from '@/components/FloatingCard/FloatingCard';
|
||||||
|
import ChatPreview from '@/components/ChatPreview/ChatPreview';
|
||||||
|
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
|
||||||
|
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer';
|
||||||
|
import { STATS, FEATURES, CAMPUS_LOCATIONS } from '@/constants';
|
||||||
|
import * as LucideIcons from 'lucide-react';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* ─── Hero Section ─────────────────────────────────────────────────────── */}
|
||||||
|
<section className="relative overflow-hidden pt-12 pb-24" style={{ backgroundColor: '#FAFAFA' }}>
|
||||||
|
{/* Background Decoration */}
|
||||||
|
<div
|
||||||
|
className="absolute top-0 right-0 w-[500px] h-[500px] rounded-full opacity-[0.04] -translate-y-1/2 translate-x-1/3 pointer-events-none"
|
||||||
|
style={{ background: 'radial-gradient(circle, #F97316, transparent)' }}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="absolute bottom-0 left-0 w-[350px] h-[350px] rounded-full opacity-[0.03] translate-y-1/2 -translate-x-1/3 pointer-events-none"
|
||||||
|
style={{ background: 'radial-gradient(circle, #F97316, transparent)' }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="container-custom">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-12 gap-16 items-center">
|
||||||
|
{/* Left – Visual Section (RIT Events Hub Style) */}
|
||||||
|
<div className="lg:col-span-6 relative flex justify-center">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, scale: 0.95 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{ duration: 0.7 }}
|
||||||
|
className="relative w-full max-w-lg"
|
||||||
|
>
|
||||||
|
{/* Main campus image/graphic card */}
|
||||||
|
<div
|
||||||
|
className="rounded-[24px] overflow-hidden bg-white border border-[#E5E7EB] relative aspect-[4/3] w-full"
|
||||||
|
style={{ boxShadow: '0 24px 60px -12px rgba(0,0,0,0.15)' }}
|
||||||
|
>
|
||||||
|
{/* Inline Premium Vector SVG Illustration of RIT Campus Building & Canopy */}
|
||||||
|
<svg viewBox="0 0 800 600" className="w-full h-full select-none" preserveAspectRatio="xMidYMid slice">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="skyGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stopColor="#1E3A8A" />
|
||||||
|
<stop offset="40%" stopColor="#3B82F6" />
|
||||||
|
<stop offset="100%" stopColor="#93C5FD" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="grassGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stopColor="#059669" />
|
||||||
|
<stop offset="100%" stopColor="#065F46" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="pathGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stopColor="#F1F5F9" />
|
||||||
|
<stop offset="100%" stopColor="#CBD5E1" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="buildGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
|
<stop offset="0%" stopColor="#E2E8F0" />
|
||||||
|
<stop offset="100%" stopColor="#F8FAFC" />
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="canopyGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||||
|
<stop offset="0%" stopColor="#F97316" />
|
||||||
|
<stop offset="100%" stopColor="#EA580C" />
|
||||||
|
</linearGradient>
|
||||||
|
<filter id="glow">
|
||||||
|
<feGaussianBlur stdDeviation="15" result="coloredBlur"/>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="coloredBlur"/>
|
||||||
|
<feMergeNode in="SourceGraphic"/>
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
{/* Sky */}
|
||||||
|
<rect width="800" height="600" fill="url(#skyGrad)" />
|
||||||
|
|
||||||
|
{/* Glowing Sun */}
|
||||||
|
<circle cx="700" cy="100" r="50" fill="#FEF08A" opacity="0.8" filter="url(#glow)" />
|
||||||
|
|
||||||
|
{/* Distant Hills / Tree Line */}
|
||||||
|
<path d="M0 450 Q150 420 300 440 T600 430 T800 450 L800 600 L0 600 Z" fill="#047857" opacity="0.7" />
|
||||||
|
<path d="M0 460 Q200 440 400 455 T800 460 L800 600 L0 600 Z" fill="url(#grassGrad)" />
|
||||||
|
|
||||||
|
{/* Academic Building Facade (Modern RIT Blocks) */}
|
||||||
|
<rect x="50" y="200" width="300" height="260" fill="url(#buildGrad)" rx="8" />
|
||||||
|
{/* Windows Grid */}
|
||||||
|
<g fill="#475569" opacity="0.3">
|
||||||
|
{/* Row 1 */}
|
||||||
|
<rect x="75" y="230" width="35" height="40" rx="3" />
|
||||||
|
<rect x="135" y="230" width="35" height="40" rx="3" />
|
||||||
|
<rect x="195" y="230" width="35" height="40" rx="3" />
|
||||||
|
<rect x="255" y="230" width="35" height="40" rx="3" />
|
||||||
|
{/* Row 2 */}
|
||||||
|
<rect x="75" y="300" width="35" height="40" rx="3" />
|
||||||
|
<rect x="135" y="300" width="35" height="40" rx="3" />
|
||||||
|
<rect x="195" y="300" width="35" height="40" rx="3" />
|
||||||
|
<rect x="255" y="300" width="35" height="40" rx="3" />
|
||||||
|
{/* Row 3 */}
|
||||||
|
<rect x="75" y="370" width="35" height="40" rx="3" />
|
||||||
|
<rect x="135" y="370" width="35" height="40" rx="3" />
|
||||||
|
<rect x="195" y="370" width="35" height="40" rx="3" />
|
||||||
|
<rect x="255" y="370" width="35" height="40" rx="3" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* Main Entrance Pillars & Arch (RIT Architecture) */}
|
||||||
|
<rect x="380" y="150" width="380" height="310" fill="#F1F5F9" rx="16" opacity="0.95" />
|
||||||
|
<rect x="400" y="170" width="340" height="290" fill="#E2E8F0" rx="12" />
|
||||||
|
|
||||||
|
{/* Premium Canopy Truss Structure (RIT Event Canopy Representation) */}
|
||||||
|
<polygon points="360,150 780,120 780,180 360,210" fill="url(#canopyGrad)" opacity="0.9" />
|
||||||
|
<g stroke="white" strokeWidth="3" opacity="0.8">
|
||||||
|
<line x1="380" y1="155" x2="380" y2="200" />
|
||||||
|
<line x1="450" y1="150" x2="450" y2="195" />
|
||||||
|
<line x1="520" y1="145" x2="520" y2="190" />
|
||||||
|
<line x1="590" y1="140" x2="590" y2="185" />
|
||||||
|
<line x1="660" y1="135" x2="660" y2="180" />
|
||||||
|
<line x1="730" y1="130" x2="730" y2="175" />
|
||||||
|
|
||||||
|
{/* Cross trusses */}
|
||||||
|
<line x1="380" y1="155" x2="450" y2="195" />
|
||||||
|
<line x1="450" y1="150" x2="520" y2="190" />
|
||||||
|
<line x1="520" y1="145" x2="590" y2="185" />
|
||||||
|
<line x1="590" y1="140" x2="660" y2="180" />
|
||||||
|
<line x1="660" y1="135" x2="730" y2="175" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* Columns supporting Canopy */}
|
||||||
|
<rect x="400" y="195" width="25" height="265" fill="#64748B" rx="4" />
|
||||||
|
<rect x="520" y="190" width="25" height="270" fill="#64748B" rx="4" />
|
||||||
|
<rect x="640" y="180" width="25" height="280" fill="#64748B" rx="4" />
|
||||||
|
<rect x="730" y="175" width="25" height="285" fill="#64748B" rx="4" />
|
||||||
|
|
||||||
|
{/* Campus Pathway leading to building */}
|
||||||
|
<polygon points="350,460 550,460 700,600 100,600" fill="url(#pathGrad)" />
|
||||||
|
<line x1="400" y1="460" x2="250" y2="600" stroke="#CBD5E1" strokeWidth="4" />
|
||||||
|
<line x1="500" y1="460" x2="550" y2="600" stroke="#CBD5E1" strokeWidth="4" />
|
||||||
|
|
||||||
|
{/* Trees & Foliage in Foreground */}
|
||||||
|
<g fill="#059669">
|
||||||
|
<circle cx="80" cy="480" r="45" />
|
||||||
|
<circle cx="120" cy="510" r="35" />
|
||||||
|
<circle cx="720" cy="500" r="50" />
|
||||||
|
<circle cx="760" cy="480" r="40" />
|
||||||
|
</g>
|
||||||
|
<g fill="#10B981">
|
||||||
|
<circle cx="75" cy="465" r="35" />
|
||||||
|
<circle cx="715" cy="485" r="40" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
{/* Subtle dark gradient overlay to ensure text contrast */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-black/10 pointer-events-none" />
|
||||||
|
|
||||||
|
{/* Text on top of the image (RIT Events Hub Style) */}
|
||||||
|
<div className="absolute bottom-6 left-6 z-10 text-left pointer-events-none">
|
||||||
|
<h2 className="text-white font-extrabold text-2xl tracking-wide leading-tight" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||||
|
Rajalakshmi Institute
|
||||||
|
</h2>
|
||||||
|
<p className="text-orange-400 font-bold text-sm mt-1" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
of Technology, Chennai
|
||||||
|
</p>
|
||||||
|
<div className="inline-flex items-center gap-1.5 mt-3.5 px-3 py-1 rounded-full bg-white/10 backdrop-blur-sm border border-white/20">
|
||||||
|
<span className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse" />
|
||||||
|
<span className="text-[10px] text-slate-300 font-bold uppercase tracking-wider">Est. 2008 • NBA & NAAC A+</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Overlapping Dark Card (Innovation Card - Exact Match with Reference Image) */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, x: 20 }}
|
||||||
|
animate={{ opacity: 1, x: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.3 }}
|
||||||
|
className="absolute -bottom-6 -right-4 md:-right-8 bg-[#1E293B] text-white p-6 rounded-2xl border border-white/10 max-w-[240px] z-20"
|
||||||
|
style={{ boxShadow: '0 12px 40px rgba(0,0,0,0.25)' }}
|
||||||
|
>
|
||||||
|
<span className="text-[#F97316] text-[11px] font-bold tracking-wider uppercase mb-1.5 block" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
INNOVATION
|
||||||
|
</span>
|
||||||
|
<p className="text-[12px] text-slate-300 leading-relaxed font-semibold" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
Where engineering meets guidance. Fueling the academic journey of our freshers.
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right – Text Content */}
|
||||||
|
<div className="lg:col-span-6 flex flex-col justify-center">
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 15 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
className="mb-3"
|
||||||
|
>
|
||||||
|
<span className="text-[#F97316] text-xs font-bold uppercase tracking-widest" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
OUR LEGACY & FUTURE
|
||||||
|
</span>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.h1
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.1 }}
|
||||||
|
className="text-4xl md:text-5xl xl:text-6xl font-bold leading-[1.1] mb-6"
|
||||||
|
style={{ fontFamily: 'Playfair Display, serif', color: '#1E293B' }}
|
||||||
|
>
|
||||||
|
Welcome to <br />
|
||||||
|
<span className="font-serif italic text-[#F97316] font-medium block mt-1">
|
||||||
|
RIT Freshers Hub
|
||||||
|
</span>
|
||||||
|
</motion.h1>
|
||||||
|
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0, y: 15 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.2 }}
|
||||||
|
className="text-base text-[#475569] leading-relaxed mb-8"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
>
|
||||||
|
The RIT Freshers Hub is your centralized gateway to campus life, designed to simplify how you discover and participate in academic and extracurricular activities. With seamless access to notes, PYQs, AI assistant, and bus routes, we ensure you have everything needed to succeed.
|
||||||
|
</motion.p>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 15 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.3 }}
|
||||||
|
className="flex flex-wrap gap-4 mb-8"
|
||||||
|
>
|
||||||
|
<Link to="/notes">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.03, boxShadow: '0 8px 25px rgba(249,115,22,0.3)' }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
className="flex items-center gap-2 px-6 py-3 rounded-xl text-white font-semibold text-sm cursor-pointer"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Poppins, sans-serif',
|
||||||
|
background: 'linear-gradient(135deg, #F97316, #FB923C)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BookOpen className="w-4 h-4" />
|
||||||
|
Explore Hub
|
||||||
|
<ArrowRight className="w-3.5 h-3.5" />
|
||||||
|
</motion.button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link to="/ai-assistant">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.03, borderColor: '#F97316', color: '#F97316', backgroundColor: '#FFF7ED' }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
className="flex items-center gap-2 px-6 py-3 rounded-xl font-semibold text-sm border-2 border-[#E5E7EB] text-[#1E293B] bg-white transition-all cursor-pointer"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
>
|
||||||
|
<Bot className="w-4 h-4" />
|
||||||
|
Ask AI Assistant
|
||||||
|
</motion.button>
|
||||||
|
</Link>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Badges positioned cleanly below buttons with more breathing room */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0 }}
|
||||||
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ duration: 0.6, delay: 0.4 }}
|
||||||
|
className="flex flex-wrap gap-6 text-[#94A3B8] border-t border-[#E5E7EB] pt-6"
|
||||||
|
>
|
||||||
|
{[
|
||||||
|
{ icon: CheckCircle, text: 'Official Portal' },
|
||||||
|
{ icon: Zap, text: 'Instant AI Support' },
|
||||||
|
{ icon: Shield, text: 'Student Verified' },
|
||||||
|
].map((badge, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-2 text-xs font-medium" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
<badge.icon className="w-4 h-4 text-[#F97316]/70" />
|
||||||
|
<span>{badge.text}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ─── Stats Section ────────────────────────────────────────────────────── */}
|
||||||
|
<section className="py-16 bg-white">
|
||||||
|
<div className="container-custom">
|
||||||
|
<StaggerContainer className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4">
|
||||||
|
{STATS.map((stat, i) => (
|
||||||
|
<StaggerItem key={i}>
|
||||||
|
<StatsCard stat={stat} delay={i * 0.05} />
|
||||||
|
</StaggerItem>
|
||||||
|
))}
|
||||||
|
</StaggerContainer>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ─── Features Section ─────────────────────────────────────────────────── */}
|
||||||
|
<section className="section-padding" style={{ backgroundColor: '#FAFAFA' }}>
|
||||||
|
<div className="container-custom">
|
||||||
|
<SectionTitle
|
||||||
|
tag="Everything You Need"
|
||||||
|
title="Built for"
|
||||||
|
highlight="RIT Freshers"
|
||||||
|
subtitle="From notes to AI assistance — we've got everything you need to navigate campus life with confidence."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5">
|
||||||
|
{FEATURES.map((feature) => (
|
||||||
|
<StaggerItem key={feature.id}>
|
||||||
|
<FeatureCard feature={feature} />
|
||||||
|
</StaggerItem>
|
||||||
|
))}
|
||||||
|
</StaggerContainer>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ─── AI Section ───────────────────────────────────────────────────────── */}
|
||||||
|
<section className="section-padding bg-white">
|
||||||
|
<div className="container-custom">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
|
||||||
|
{/* Left – Text */}
|
||||||
|
<AnimatedContainer direction="left">
|
||||||
|
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full mb-5"
|
||||||
|
style={{ backgroundColor: '#F5F3FF', border: '1px solid #DDD6FE' }}>
|
||||||
|
<Sparkles className="w-4 h-4 text-[#8B5CF6]" />
|
||||||
|
<span className="text-xs font-semibold text-[#8B5CF6] uppercase tracking-wider" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
AI-Powered
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold mb-4" style={{ fontFamily: 'Playfair Display, serif', color: '#1E293B' }}>
|
||||||
|
Your 24/7{' '}
|
||||||
|
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||||
|
Campus Guide
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<p className="text-[#475569] leading-relaxed mb-8" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
Ask anything about RIT — from admission documents to canteen timings. Our AI assistant, powered by Google Gemini with campus-specific knowledge, gives you instant, accurate answers.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4 mb-8">
|
||||||
|
{[
|
||||||
|
{ icon: 'Brain', label: 'Gemini AI', desc: 'State-of-the-art AI' },
|
||||||
|
{ icon: 'Database', label: 'RAG System', desc: 'Campus knowledge base' },
|
||||||
|
{ icon: 'Map', label: 'Campus Context', desc: 'RIT-specific answers' },
|
||||||
|
{ icon: 'Zap', label: 'Instant Answers', desc: 'No wait time' },
|
||||||
|
].map((item, i) => {
|
||||||
|
const Icon = (LucideIcons as Record<string, React.ComponentType<{ className?: string }>>)[item.icon];
|
||||||
|
return (
|
||||||
|
<div key={i} className="flex items-start gap-3 p-4 rounded-xl border border-[#E5E7EB] bg-[#FAFAFA]">
|
||||||
|
<div className="w-9 h-9 rounded-lg flex items-center justify-center bg-[#FFF7ED] shrink-0">
|
||||||
|
{Icon && <Icon className="w-4.5 h-4.5" style={{ color: '#F97316' } as React.CSSProperties} />}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-semibold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>{item.label}</div>
|
||||||
|
<div className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>{item.desc}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Link to="/ai-assistant">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.03 }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
className="flex items-center gap-2 px-7 py-3.5 rounded-xl text-white font-semibold"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Poppins, sans-serif',
|
||||||
|
background: 'linear-gradient(135deg, #F97316, #FB923C)',
|
||||||
|
boxShadow: '0 6px 20px rgba(249,115,22,0.35)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Bot className="w-4.5 h-4.5" />
|
||||||
|
Try AI Assistant
|
||||||
|
<ArrowRight className="w-4 h-4" />
|
||||||
|
</motion.button>
|
||||||
|
</Link>
|
||||||
|
</AnimatedContainer>
|
||||||
|
|
||||||
|
{/* Right – Chat Preview */}
|
||||||
|
<AnimatedContainer direction="right" delay={0.2}>
|
||||||
|
<ChatPreview />
|
||||||
|
</AnimatedContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ─── Campus Quick Nav ─────────────────────────────────────────────────── */}
|
||||||
|
<section className="section-padding" style={{ backgroundColor: '#FAFAFA' }}>
|
||||||
|
<div className="container-custom">
|
||||||
|
<SectionTitle
|
||||||
|
tag="Navigate Campus"
|
||||||
|
title="Explore"
|
||||||
|
highlight="RIT Campus"
|
||||||
|
subtitle="Find your way around campus — departments, labs, library, and all key locations."
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AnimatedContainer>
|
||||||
|
<div className="bg-white rounded-3xl border border-[#E5E7EB] overflow-hidden" style={{ boxShadow: '0 8px 40px -8px rgba(0,0,0,0.1)' }}>
|
||||||
|
{/* Map placeholder */}
|
||||||
|
<div
|
||||||
|
className="h-56 flex items-center justify-center relative overflow-hidden"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)' }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 opacity-10"
|
||||||
|
style={{
|
||||||
|
backgroundImage: 'linear-gradient(rgba(249,115,22,0.5) 1px, transparent 1px), linear-gradient(90deg, rgba(249,115,22,0.5) 1px, transparent 1px)',
|
||||||
|
backgroundSize: '30px 30px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Location pins */}
|
||||||
|
{[
|
||||||
|
{ label: 'CSE Block', x: '25%', y: '30%' },
|
||||||
|
{ label: 'Library', x: '55%', y: '45%' },
|
||||||
|
{ label: 'Canteen', x: '70%', y: '65%' },
|
||||||
|
{ label: 'Hostel', x: '20%', y: '65%' },
|
||||||
|
].map((pin, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={i}
|
||||||
|
className="absolute flex flex-col items-center gap-1 cursor-pointer group"
|
||||||
|
style={{ left: pin.x, top: pin.y, transform: 'translate(-50%, -50%)' }}
|
||||||
|
whileHover={{ scale: 1.1 }}
|
||||||
|
>
|
||||||
|
<div className="w-6 h-6 rounded-full flex items-center justify-center shadow-lg group-hover:scale-110 transition-all"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
|
||||||
|
<div className="w-2 h-2 rounded-full bg-white" />
|
||||||
|
</div>
|
||||||
|
<span className="text-[10px] text-white font-medium bg-black/50 px-1.5 py-0.5 rounded-full opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap">
|
||||||
|
{pin.label}
|
||||||
|
</span>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
<div className="relative z-10 text-center">
|
||||||
|
<div className="text-white/40 text-sm" style={{ fontFamily: 'Inter, sans-serif' }}>Interactive Campus Map</div>
|
||||||
|
<Link to="/campus">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
className="mt-2 px-5 py-2 rounded-xl text-white text-xs font-semibold"
|
||||||
|
style={{ background: 'rgba(249,115,22,0.8)', fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
>
|
||||||
|
Open Full Map
|
||||||
|
</motion.button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick nav buttons */}
|
||||||
|
<div className="p-5 grid grid-cols-4 sm:grid-cols-8 gap-3">
|
||||||
|
{CAMPUS_LOCATIONS.map((loc) => {
|
||||||
|
const Icon = (LucideIcons as Record<string, React.ComponentType<{ className?: string }>>)[loc.icon];
|
||||||
|
return (
|
||||||
|
<Link to="/campus" key={loc.id}>
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ y: -3, backgroundColor: '#FFF7ED' }}
|
||||||
|
className="flex flex-col items-center gap-2 p-3 rounded-xl cursor-pointer transition-all border border-transparent hover:border-[#FED7AA]"
|
||||||
|
>
|
||||||
|
<div className="w-9 h-9 rounded-xl bg-[#F8FAFC] flex items-center justify-center">
|
||||||
|
{Icon && <Icon className="w-4.5 h-4.5 text-[#F97316]" />}
|
||||||
|
</div>
|
||||||
|
<span className="text-[10px] text-[#475569] text-center leading-tight" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
{loc.name}
|
||||||
|
</span>
|
||||||
|
</motion.div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AnimatedContainer>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ─── Community Teaser ─────────────────────────────────────────────────── */}
|
||||||
|
<section className="section-padding bg-white">
|
||||||
|
<div className="container-custom">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
{/* Q&A teaser */}
|
||||||
|
<AnimatedContainer direction="left">
|
||||||
|
<div className="rounded-3xl p-7 h-full border border-[#E5E7EB]"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #FAFAFA, #FFF7ED)', boxShadow: '0 4px 20px -4px rgba(0,0,0,0.07)' }}>
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<div className="w-11 h-11 rounded-xl flex items-center justify-center bg-[#FFF7ED]">
|
||||||
|
<LucideIcons.MessageCircle className="w-5 h-5 text-[#F97316]" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Freshers Q&A</h3>
|
||||||
|
<p className="text-xs text-[#94A3B8]">Get answers from seniors</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3 mb-5">
|
||||||
|
{['How is hostel life at RIT?', 'Best clubs to join as a fresher?', 'Tips for first semester survival'].map((q, i) => (
|
||||||
|
<div key={i} className="flex items-start gap-2.5 p-3 bg-white rounded-xl border border-[#E5E7EB]">
|
||||||
|
<span className="text-[#F97316] text-xs font-bold mt-0.5">Q</span>
|
||||||
|
<span className="text-xs text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>{q}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Link to="/community">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.02 }}
|
||||||
|
className="w-full py-2.5 rounded-xl text-sm font-semibold text-[#F97316] border-2 border-[#F97316] hover:bg-[#FFF7ED] transition-all"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
>
|
||||||
|
Join the Discussion
|
||||||
|
</motion.button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</AnimatedContainer>
|
||||||
|
|
||||||
|
{/* Anonymous Confession Teaser */}
|
||||||
|
<AnimatedContainer direction="right" delay={0.1}>
|
||||||
|
<div className="rounded-3xl p-7 h-full border border-[#E5E7EB]"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)', boxShadow: '0 4px 20px -4px rgba(0,0,0,0.15)' }}>
|
||||||
|
<div className="flex items-center gap-3 mb-4">
|
||||||
|
<div className="w-11 h-11 rounded-xl flex items-center justify-center" style={{ backgroundColor: 'rgba(249,115,22,0.2)' }}>
|
||||||
|
<LucideIcons.Heart className="w-5 h-5 text-[#F97316]" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-semibold text-white" style={{ fontFamily: 'Poppins, sans-serif' }}>Anonymous Confessions</h3>
|
||||||
|
<p className="text-xs text-slate-400">Share without revealing identity</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-3 mb-5">
|
||||||
|
{[
|
||||||
|
{ text: "I went to the wrong department on my first day 😅", reactions: 87 },
|
||||||
|
{ text: "The library WiFi is faster than my home connection 😂", reactions: 134 },
|
||||||
|
].map((c, i) => (
|
||||||
|
<div key={i} className="p-3 rounded-xl" style={{ backgroundColor: 'rgba(255,255,255,0.07)', border: '1px solid rgba(255,255,255,0.1)' }}>
|
||||||
|
<p className="text-xs text-slate-300 mb-2" style={{ fontFamily: 'Inter, sans-serif' }}>{c.text}</p>
|
||||||
|
<div className="flex items-center gap-1 text-xs text-slate-500">
|
||||||
|
<LucideIcons.Heart className="w-3 h-3 text-[#F97316]" />
|
||||||
|
<span>{c.reactions}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 p-3 rounded-xl mb-4" style={{ backgroundColor: 'rgba(249,115,22,0.1)', border: '1px solid rgba(249,115,22,0.2)' }}>
|
||||||
|
<LucideIcons.Shield className="w-4 h-4 text-[#F97316] shrink-0" />
|
||||||
|
<span className="text-xs text-slate-400" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
100% Anonymous. No tracking. No identity revealed.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Link to="/community">
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.02 }}
|
||||||
|
className="w-full py-2.5 rounded-xl text-sm font-semibold text-white transition-all"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif', background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||||
|
>
|
||||||
|
Confess Anonymously
|
||||||
|
</motion.button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</AnimatedContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
212
src/pages/Notes/Notes.tsx
Normal file
212
src/pages/Notes/Notes.tsx
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import { Search, Download, Filter, BookOpen, FileText, ScrollText, BookMarked, ChevronRight } from 'lucide-react';
|
||||||
|
import SectionTitle from '@/components/SectionTitle/SectionTitle';
|
||||||
|
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
|
||||||
|
import { TOOLKIT_ITEMS, DEPARTMENTS } from '@/constants';
|
||||||
|
|
||||||
|
const SEMESTERS = ['All', '1st Sem', '2nd Sem', '3rd Sem', '4th Sem', '5th Sem', '6th Sem', '7th Sem', '8th Sem'];
|
||||||
|
|
||||||
|
const TYPE_COLORS: Record<string, { bg: string; text: string; icon: React.ComponentType<{ className?: string }> }> = {
|
||||||
|
notes: { bg: '#EFF6FF', text: '#3B82F6', icon: BookOpen },
|
||||||
|
pyq: { bg: '#FFF7ED', text: '#F97316', icon: FileText },
|
||||||
|
syllabus: { bg: '#ECFDF5', text: '#10B981', icon: ScrollText },
|
||||||
|
assignment: { bg: '#FDF2F8', text: '#EC4899', icon: BookMarked },
|
||||||
|
};
|
||||||
|
|
||||||
|
const NOTES_DATA = [
|
||||||
|
{ id: '1', title: 'Engineering Mathematics – Unit 1-5', subject: 'Mathematics', department: 'Computer Science & Engineering', semester: 1, type: 'notes', downloads: 348, fileSize: '4.2 MB', uploadedAt: '2025-06-01' },
|
||||||
|
{ id: '2', title: 'Physics PYQ 2020-2024', subject: 'Physics', department: 'Computer Science & Engineering', semester: 1, type: 'pyq', downloads: 512, fileSize: '8.1 MB', uploadedAt: '2025-06-10' },
|
||||||
|
{ id: '3', title: 'C Programming Complete Notes', subject: 'Programming', department: 'Computer Science & Engineering', semester: 1, type: 'notes', downloads: 734, fileSize: '6.3 MB', uploadedAt: '2025-06-15' },
|
||||||
|
{ id: '4', title: 'Data Structures PYQ 2019-2024', subject: 'Data Structures', department: 'Computer Science & Engineering', semester: 3, type: 'pyq', downloads: 621, fileSize: '10.2 MB', uploadedAt: '2025-07-01' },
|
||||||
|
{ id: '5', title: 'Circuit Theory Full Notes', subject: 'Circuit Theory', department: 'Electronics & Communication', semester: 2, type: 'notes', downloads: 289, fileSize: '5.7 MB', uploadedAt: '2025-06-20' },
|
||||||
|
{ id: '6', title: 'Anna University Syllabus 2021', subject: 'Syllabus', department: 'All Departments', semester: 1, type: 'syllabus', downloads: 890, fileSize: '2.1 MB', uploadedAt: '2025-05-01' },
|
||||||
|
{ id: '7', title: 'Thermodynamics Notes', subject: 'Thermodynamics', department: 'Mechanical Engineering', semester: 3, type: 'notes', downloads: 201, fileSize: '3.9 MB', uploadedAt: '2025-06-25' },
|
||||||
|
{ id: '8', title: 'Digital Electronics PYQ', subject: 'Digital Electronics', department: 'Electronics & Communication', semester: 4, type: 'pyq', downloads: 445, fileSize: '7.3 MB', uploadedAt: '2025-07-05' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Notes() {
|
||||||
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [selectedSem, setSelectedSem] = useState('All');
|
||||||
|
const [selectedDept, setSelectedDept] = useState('All Departments');
|
||||||
|
const [selectedType, setSelectedType] = useState('all');
|
||||||
|
|
||||||
|
const filtered = NOTES_DATA.filter((note) => {
|
||||||
|
const matchSearch = note.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||||
|
note.subject.toLowerCase().includes(searchQuery.toLowerCase());
|
||||||
|
const matchSem = selectedSem === 'All' || note.semester === parseInt(selectedSem);
|
||||||
|
const matchDept = selectedDept === 'All Departments' || note.department === selectedDept || note.department === 'All Departments';
|
||||||
|
const matchType = selectedType === 'all' || note.type === selectedType;
|
||||||
|
return matchSearch && matchSem && matchDept && matchType;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="bg-white border-b border-[#E5E7EB] py-10">
|
||||||
|
<div className="container-custom">
|
||||||
|
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }}>
|
||||||
|
<div className="flex items-center gap-2 text-xs text-[#94A3B8] mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
<span>Home</span>
|
||||||
|
<ChevronRight className="w-3 h-3" />
|
||||||
|
<span className="text-[#F97316]">Notes & PYQs</span>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl md:text-4xl font-bold text-[#1E293B] mb-2" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||||
|
Notes &{' '}
|
||||||
|
<span style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent', backgroundClip: 'text' }}>
|
||||||
|
PYQs
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p className="text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
Access semester-wise notes, previous year question papers, and study materials.
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="container-custom py-10">
|
||||||
|
{/* Search + Filters */}
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: 16 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.4, delay: 0.1 }}
|
||||||
|
className="bg-white rounded-2xl border border-[#E5E7EB] p-5 mb-8"
|
||||||
|
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||||
|
>
|
||||||
|
{/* Search */}
|
||||||
|
<div className="relative mb-4">
|
||||||
|
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-4.5 h-4.5 text-[#94A3B8]" />
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search notes, subjects..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="w-full pl-11 pr-4 py-3 rounded-xl border border-[#E5E7EB] text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none focus:border-[#F97316] transition-colors"
|
||||||
|
style={{ fontFamily: 'Inter, sans-serif' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{/* Type Filter */}
|
||||||
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
<Filter className="w-4 h-4 text-[#94A3B8]" />
|
||||||
|
{['all', 'notes', 'pyq', 'syllabus'].map((type) => (
|
||||||
|
<button
|
||||||
|
key={type}
|
||||||
|
onClick={() => setSelectedType(type)}
|
||||||
|
className="px-3 py-1.5 rounded-lg text-xs font-medium capitalize transition-all"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Poppins, sans-serif',
|
||||||
|
backgroundColor: selectedType === type ? '#F97316' : '#F8FAFC',
|
||||||
|
color: selectedType === type ? 'white' : '#475569',
|
||||||
|
border: `1px solid ${selectedType === type ? '#F97316' : '#E5E7EB'}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{type === 'all' ? 'All Types' : type.toUpperCase()}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Semester Filter */}
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
{SEMESTERS.slice(0, 5).map((sem) => (
|
||||||
|
<button
|
||||||
|
key={sem}
|
||||||
|
onClick={() => setSelectedSem(sem)}
|
||||||
|
className="px-3 py-1.5 rounded-lg text-xs font-medium transition-all"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Poppins, sans-serif',
|
||||||
|
backgroundColor: selectedSem === sem ? '#1E293B' : '#F8FAFC',
|
||||||
|
color: selectedSem === sem ? 'white' : '#475569',
|
||||||
|
border: `1px solid ${selectedSem === sem ? '#1E293B' : '#E5E7EB'}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{sem}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Results */}
|
||||||
|
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 mb-12">
|
||||||
|
{filtered.map((note) => {
|
||||||
|
const typeConfig = TYPE_COLORS[note.type];
|
||||||
|
const TypeIcon = typeConfig.icon;
|
||||||
|
return (
|
||||||
|
<StaggerItem key={note.id}>
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ y: -4 }}
|
||||||
|
className="bg-white rounded-2xl border border-[#E5E7EB] p-5 flex flex-col gap-3"
|
||||||
|
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between gap-2">
|
||||||
|
<div className="w-10 h-10 rounded-xl flex items-center justify-center shrink-0" style={{ backgroundColor: typeConfig.bg }}>
|
||||||
|
<TypeIcon className="w-5 h-5" style={{ color: typeConfig.text } as React.CSSProperties} />
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
className="px-2 py-0.5 rounded-full text-[10px] font-bold uppercase"
|
||||||
|
style={{ backgroundColor: typeConfig.bg, color: typeConfig.text, fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
>
|
||||||
|
{note.type}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="text-sm font-semibold text-[#1E293B] leading-snug mb-1" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
{note.title}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
{note.subject} · Sem {note.semester}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between text-xs text-[#94A3B8] pt-2 border-t border-[#E5E7EB]">
|
||||||
|
<span style={{ fontFamily: 'Inter, sans-serif' }}>{note.downloads} downloads · {note.fileSize}</span>
|
||||||
|
</div>
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.02 }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
className="w-full flex items-center justify-center gap-2 py-2.5 rounded-xl text-sm font-semibold text-white"
|
||||||
|
style={{
|
||||||
|
fontFamily: 'Poppins, sans-serif',
|
||||||
|
background: 'linear-gradient(135deg, #F97316, #FB923C)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Download className="w-4 h-4" />
|
||||||
|
Download
|
||||||
|
</motion.button>
|
||||||
|
</motion.div>
|
||||||
|
</StaggerItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</StaggerContainer>
|
||||||
|
|
||||||
|
{/* Toolkit Section */}
|
||||||
|
<SectionTitle tag="Useful Downloads" title="Student" highlight="Toolkit" align="left" />
|
||||||
|
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
{TOOLKIT_ITEMS.map((item) => (
|
||||||
|
<StaggerItem key={item.id}>
|
||||||
|
<motion.a
|
||||||
|
href={item.url}
|
||||||
|
whileHover={{ y: -3 }}
|
||||||
|
className="flex items-center gap-4 p-4 bg-white rounded-2xl border border-[#E5E7EB] cursor-pointer group"
|
||||||
|
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||||
|
>
|
||||||
|
<div className="w-10 h-10 rounded-xl flex items-center justify-center bg-[#FFF7ED] shrink-0">
|
||||||
|
<Download className="w-4.5 h-4.5 text-[#F97316]" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-semibold text-[#1E293B] group-hover:text-[#F97316] transition-colors" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
{item.title}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
{item.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.a>
|
||||||
|
</StaggerItem>
|
||||||
|
))}
|
||||||
|
</StaggerContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
219
src/pages/Profile/Profile.tsx
Normal file
219
src/pages/Profile/Profile.tsx
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import {
|
||||||
|
User, Mail, Phone, GraduationCap, BookOpen,
|
||||||
|
Settings, LogOut, Edit3, Camera, Bell,
|
||||||
|
Shield, Star, Calendar, ChevronRight
|
||||||
|
} from 'lucide-react';
|
||||||
|
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer';
|
||||||
|
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
|
||||||
|
|
||||||
|
const MOCK_USER = {
|
||||||
|
name: 'Arjun Kumar',
|
||||||
|
rollNo: 'CB.EN.U4CSE25001',
|
||||||
|
department: 'Computer Science & Engineering',
|
||||||
|
semester: '1st Semester',
|
||||||
|
email: 'arjun.kumar@rit.ac.in',
|
||||||
|
phone: '+91 98765 43210',
|
||||||
|
joinedDate: 'August 2025',
|
||||||
|
batch: '2025-2029',
|
||||||
|
};
|
||||||
|
|
||||||
|
const ACTIVITY = [
|
||||||
|
{ label: 'Notes Downloaded', value: '12', icon: BookOpen, color: '#F97316' },
|
||||||
|
{ label: 'Questions Asked', value: '3', icon: Star, color: '#8B5CF6' },
|
||||||
|
{ label: 'Events Registered', value: '2', icon: Calendar, color: '#10B981' },
|
||||||
|
{ label: 'AI Queries', value: '48', icon: Shield, color: '#3B82F6' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const MENU_ITEMS = [
|
||||||
|
{ icon: Bell, label: 'Notifications', badge: '3' },
|
||||||
|
{ icon: BookOpen, label: 'My Downloads', badge: null },
|
||||||
|
{ icon: Star, label: 'Bookmarks', badge: null },
|
||||||
|
{ icon: Settings, label: 'Settings', badge: null },
|
||||||
|
{ icon: Shield, label: 'Privacy & Security', badge: null },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Profile() {
|
||||||
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen" style={{ backgroundColor: '#FAFAFA' }}>
|
||||||
|
{/* Header */}
|
||||||
|
<div
|
||||||
|
className="py-16 relative overflow-hidden"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #1E293B 0%, #334155 100%)' }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 opacity-5"
|
||||||
|
style={{
|
||||||
|
backgroundImage: 'radial-gradient(circle, #F97316 1px, transparent 1px)',
|
||||||
|
backgroundSize: '32px 32px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="container-custom relative z-10">
|
||||||
|
<div className="flex flex-col md:flex-row items-start md:items-center gap-6">
|
||||||
|
{/* Avatar */}
|
||||||
|
<div className="relative">
|
||||||
|
<motion.div
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
className="w-24 h-24 rounded-2xl flex items-center justify-center text-3xl font-bold text-white cursor-pointer"
|
||||||
|
style={{
|
||||||
|
background: 'linear-gradient(135deg, #F97316, #FB923C)',
|
||||||
|
fontFamily: 'Poppins, sans-serif',
|
||||||
|
boxShadow: '0 8px 30px rgba(249,115,22,0.4)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
AK
|
||||||
|
</motion.div>
|
||||||
|
<button
|
||||||
|
className="absolute -bottom-2 -right-2 w-8 h-8 rounded-xl flex items-center justify-center text-white"
|
||||||
|
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
|
||||||
|
aria-label="Change photo"
|
||||||
|
>
|
||||||
|
<Camera className="w-3.5 h-3.5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Info */}
|
||||||
|
<div className="flex-1">
|
||||||
|
<h1 className="text-2xl font-bold text-white mb-1" style={{ fontFamily: 'Playfair Display, serif' }}>
|
||||||
|
{MOCK_USER.name}
|
||||||
|
</h1>
|
||||||
|
<p className="text-orange-300 text-sm mb-1" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
{MOCK_USER.rollNo}
|
||||||
|
</p>
|
||||||
|
<p className="text-slate-400 text-sm mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
|
{MOCK_USER.department} · {MOCK_USER.batch}
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
<span className="px-3 py-1 rounded-full text-xs font-semibold bg-orange-500/20 text-orange-300 border border-orange-500/30" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
{MOCK_USER.semester}
|
||||||
|
</span>
|
||||||
|
<span className="px-3 py-1 rounded-full text-xs font-semibold bg-emerald-500/20 text-emerald-400 border border-emerald-500/30" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
Active Student
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Edit button */}
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.03 }}
|
||||||
|
whileTap={{ scale: 0.97 }}
|
||||||
|
onClick={() => setIsEditing(!isEditing)}
|
||||||
|
className="flex items-center gap-2 px-5 py-2.5 rounded-xl text-white text-sm font-semibold border border-white/20 hover:bg-white/10 transition-all"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
>
|
||||||
|
<Edit3 className="w-4 h-4" />
|
||||||
|
Edit Profile
|
||||||
|
</motion.button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="container-custom py-8">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||||
|
{/* Left */}
|
||||||
|
<div className="lg:col-span-2 flex flex-col gap-6">
|
||||||
|
{/* Activity Stats */}
|
||||||
|
<AnimatedContainer>
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
||||||
|
{ACTIVITY.map((item, i) => (
|
||||||
|
<motion.div
|
||||||
|
key={i}
|
||||||
|
whileHover={{ y: -3 }}
|
||||||
|
className="bg-white rounded-2xl border border-[#E5E7EB] p-4 text-center"
|
||||||
|
style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="w-10 h-10 rounded-xl flex items-center justify-center mx-auto mb-2"
|
||||||
|
style={{ backgroundColor: `${item.color}15` }}
|
||||||
|
>
|
||||||
|
<item.icon className="w-5 h-5" style={{ color: item.color } as React.CSSProperties} />
|
||||||
|
</div>
|
||||||
|
<div className="text-xl font-bold mb-0.5" style={{ color: item.color, fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
{item.value}
|
||||||
|
</div>
|
||||||
|
<div className="text-[10px] text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>{item.label}</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</AnimatedContainer>
|
||||||
|
|
||||||
|
{/* Personal Details */}
|
||||||
|
<AnimatedContainer delay={0.1}>
|
||||||
|
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-6" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||||
|
<h3 className="text-sm font-semibold text-[#1E293B] mb-5" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
Personal Information
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-5">
|
||||||
|
{[
|
||||||
|
{ icon: User, label: 'Full Name', value: MOCK_USER.name },
|
||||||
|
{ icon: GraduationCap, label: 'Roll Number', value: MOCK_USER.rollNo },
|
||||||
|
{ icon: Mail, label: 'Email', value: MOCK_USER.email },
|
||||||
|
{ icon: Phone, label: 'Phone', value: MOCK_USER.phone },
|
||||||
|
{ icon: BookOpen, label: 'Department', value: MOCK_USER.department },
|
||||||
|
{ icon: Calendar, label: 'Joined', value: MOCK_USER.joinedDate },
|
||||||
|
].map((item, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-3 p-3 rounded-xl bg-[#F8FAFC] border border-[#E5E7EB]">
|
||||||
|
<div className="w-9 h-9 rounded-lg flex items-center justify-center bg-[#FFF7ED] shrink-0">
|
||||||
|
<item.icon className="w-4.5 h-4.5 text-[#F97316]" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-[10px] text-[#94A3B8] uppercase tracking-wider" style={{ fontFamily: 'Inter, sans-serif' }}>{item.label}</div>
|
||||||
|
<div className="text-sm font-medium text-[#1E293B]" style={{ fontFamily: 'Inter, sans-serif' }}>{item.value}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</AnimatedContainer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Right Sidebar */}
|
||||||
|
<div className="flex flex-col gap-5">
|
||||||
|
{/* Menu */}
|
||||||
|
<AnimatedContainer direction="right" delay={0.1}>
|
||||||
|
<div className="bg-white rounded-2xl border border-[#E5E7EB] overflow-hidden" style={{ boxShadow: '0 2px 15px -3px rgba(0,0,0,0.07)' }}>
|
||||||
|
{MENU_ITEMS.map((item, i) => (
|
||||||
|
<motion.button
|
||||||
|
key={i}
|
||||||
|
whileHover={{ backgroundColor: '#FFF7ED', x: 2 }}
|
||||||
|
className="w-full flex items-center justify-between px-5 py-3.5 border-b border-[#E5E7EB] last:border-0 transition-all text-left"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<item.icon className="w-4 h-4 text-[#94A3B8]" />
|
||||||
|
<span className="text-sm text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>{item.label}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{item.badge && (
|
||||||
|
<span className="w-5 h-5 rounded-full flex items-center justify-center text-[10px] text-white font-bold"
|
||||||
|
style={{ backgroundColor: '#F97316', fontFamily: 'Poppins, sans-serif' }}>
|
||||||
|
{item.badge}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<ChevronRight className="w-3.5 h-3.5 text-[#94A3B8]" />
|
||||||
|
</div>
|
||||||
|
</motion.button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</AnimatedContainer>
|
||||||
|
|
||||||
|
{/* Logout */}
|
||||||
|
<AnimatedContainer direction="right" delay={0.2}>
|
||||||
|
<motion.button
|
||||||
|
whileHover={{ scale: 1.02 }}
|
||||||
|
whileTap={{ scale: 0.98 }}
|
||||||
|
className="w-full flex items-center justify-center gap-2 py-3 rounded-xl border-2 border-red-100 text-red-400 hover:bg-red-50 text-sm font-semibold transition-all"
|
||||||
|
style={{ fontFamily: 'Poppins, sans-serif' }}
|
||||||
|
>
|
||||||
|
<LogOut className="w-4 h-4" />
|
||||||
|
Sign Out
|
||||||
|
</motion.button>
|
||||||
|
</AnimatedContainer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
151
src/types/index.ts
Normal file
151
src/types/index.ts
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
// ─── Navigation ─────────────────────────────────────────────────────────────
|
||||||
|
export interface NavLink {
|
||||||
|
label: string;
|
||||||
|
path: string;
|
||||||
|
icon?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Features ────────────────────────────────────────────────────────────────
|
||||||
|
export interface Feature {
|
||||||
|
id: string;
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
path: string;
|
||||||
|
color: string;
|
||||||
|
bgColor: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Stats ────────────────────────────────────────────────────────────────────
|
||||||
|
export interface Stat {
|
||||||
|
value: number;
|
||||||
|
suffix: string;
|
||||||
|
label: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Faculty ─────────────────────────────────────────────────────────────────
|
||||||
|
export interface Faculty {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
designation: string;
|
||||||
|
department: string;
|
||||||
|
email: string;
|
||||||
|
phone?: string;
|
||||||
|
office?: string;
|
||||||
|
avatar?: string;
|
||||||
|
specialization?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Bus Routes ──────────────────────────────────────────────────────────────
|
||||||
|
export interface BusStop {
|
||||||
|
name: string;
|
||||||
|
time: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BusRoute {
|
||||||
|
id: string;
|
||||||
|
number: string;
|
||||||
|
name: string;
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
departureTime: string;
|
||||||
|
arrivalTime: string;
|
||||||
|
stops: BusStop[];
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Notes / PYQs ────────────────────────────────────────────────────────────
|
||||||
|
export interface Note {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
subject: string;
|
||||||
|
department: string;
|
||||||
|
semester: number;
|
||||||
|
type: 'notes' | 'pyq' | 'syllabus' | 'assignment';
|
||||||
|
downloadUrl: string;
|
||||||
|
uploadedAt: string;
|
||||||
|
downloads: number;
|
||||||
|
fileSize: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Events ──────────────────────────────────────────────────────────────────
|
||||||
|
export interface Event {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
date: string;
|
||||||
|
time: string;
|
||||||
|
venue: string;
|
||||||
|
organizer: string;
|
||||||
|
category: 'technical' | 'cultural' | 'sports' | 'seminar' | 'workshop';
|
||||||
|
imageUrl?: string;
|
||||||
|
registrationUrl?: string;
|
||||||
|
isUpcoming: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Clubs ───────────────────────────────────────────────────────────────────
|
||||||
|
export interface Club {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
category: string;
|
||||||
|
members: number;
|
||||||
|
logo?: string;
|
||||||
|
contactEmail?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Community / Q&A ─────────────────────────────────────────────────────────
|
||||||
|
export interface Question {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
author: string;
|
||||||
|
createdAt: string;
|
||||||
|
answers: number;
|
||||||
|
votes: number;
|
||||||
|
tags: string[];
|
||||||
|
isAnswered: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Confession {
|
||||||
|
id: string;
|
||||||
|
content: string;
|
||||||
|
createdAt: string;
|
||||||
|
reactions: number;
|
||||||
|
category?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── AI Chat ─────────────────────────────────────────────────────────────────
|
||||||
|
export interface ChatMessage {
|
||||||
|
id: string;
|
||||||
|
role: 'user' | 'assistant';
|
||||||
|
content: string;
|
||||||
|
timestamp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Toolkit ─────────────────────────────────────────────────────────────────
|
||||||
|
export interface ToolkitItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
type: 'pdf' | 'link' | 'form';
|
||||||
|
url: string;
|
||||||
|
icon: string;
|
||||||
|
category: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Campus Location ─────────────────────────────────────────────────────────
|
||||||
|
export interface CampusLocation {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── API Response ────────────────────────────────────────────────────────────
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
data: T;
|
||||||
|
message: string;
|
||||||
|
success: boolean;
|
||||||
|
}
|
||||||
32
tsconfig.app.json
Normal file
32
tsconfig.app.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
"target": "es2023",
|
||||||
|
"lib": ["ES2023", "DOM"],
|
||||||
|
"module": "esnext",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"allowArbitraryExtensions": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Path aliases */
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
23
tsconfig.node.json
Normal file
23
tsconfig.node.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "es2023",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"types": ["node"],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"module": "nodenext",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
17
vite.config.ts
Normal file
17
vite.config.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
// https://vite.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
react(),
|
||||||
|
tailwindcss(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user