From c44150afc41a0d7fa1eebcfaf60f348a3b643517 Mon Sep 17 00:00:00 2001 From: AyushGupta011 Date: Sun, 1 Feb 2026 00:57:20 +0530 Subject: [PATCH 1/9] changed data base from sql to postgres prism orm and added toast , fix the team page height --- backend/.gitignore | 5 + backend/config/db.js | 73 +------- backend/controllers/adminController.js | 176 ++++++++++-------- backend/controllers/authController.js | 45 +++-- backend/controllers/eventController.js | 86 +++++---- backend/controllers/registrationController.js | 104 ++++++----- backend/middleware/rateLimiter.js | 0 backend/package-lock.json | 24 +-- backend/package.json | 4 +- backend/prisma.config.ts | 14 ++ .../20260131175617_init/migration.sql | 61 ++++++ .../20260131183656_init/migration.sql | 4 + .../20260131184207_init/migration.sql | 2 + backend/prisma/migrations/migration_lock.toml | 3 + backend/prisma/schema.prisma | 69 +++++++ backend/server.js | 26 +-- package-lock.json | 35 +++- package.json | 3 +- src/components/Landing.jsx | 3 +- src/components/TeamGrid.jsx | 2 +- src/main.jsx | 2 + src/pages/AdminDashboard.jsx | 11 +- src/pages/EventRegistration.jsx | 3 +- src/pages/TeamPage.jsx | 4 +- 24 files changed, 463 insertions(+), 296 deletions(-) create mode 100644 backend/.gitignore create mode 100644 backend/middleware/rateLimiter.js create mode 100644 backend/prisma.config.ts create mode 100644 backend/prisma/migrations/20260131175617_init/migration.sql create mode 100644 backend/prisma/migrations/20260131183656_init/migration.sql create mode 100644 backend/prisma/migrations/20260131184207_init/migration.sql create mode 100644 backend/prisma/migrations/migration_lock.toml create mode 100644 backend/prisma/schema.prisma diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..9f62ec0 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,5 @@ +node_modules +# Keep environment variables out of version control +.env + +/generated/prisma diff --git a/backend/config/db.js b/backend/config/db.js index dfd50b9..811bb3d 100644 --- a/backend/config/db.js +++ b/backend/config/db.js @@ -1,69 +1,10 @@ -// const { Pool } = require('pg'); -import { Pool } from 'pg'; -import dotenv from 'dotenv'; -dotenv.config(); +import "dotenv/config"; +import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from '../generated/prisma/client.js'; -const pool = new Pool({ - user: process.env.DB_USER, - host: process.env.DB_HOST, - database: process.env.DB_NAME, - password: process.env.DB_PASSWORD, - port: process.env.DB_PORT, -}); +const connectionString = `${process.env.DATABASE_URL}` -const initDB = async () => { - const client = await pool.connect(); - - try { - await client.query(` - CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - email VARCHAR(255) UNIQUE NOT NULL, - password VARCHAR(255) NOT NULL, - role VARCHAR(50) DEFAULT 'user', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - `); - await client.query(` - CREATE TABLE IF NOT EXISTS events ( - id SERIAL PRIMARY KEY, - title VARCHAR(255) NOT NULL, - description TEXT, - date DATE, - max_team_size INTEGER DEFAULT 1, - registration_fee DECIMAL(10, 2), - qr_code_image VARCHAR(255), - status VARCHAR(50) DEFAULT 'active', - created_by INTEGER REFERENCES users(id), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - `); +const adapter = new PrismaPg({ connectionString }) +const prisma = new PrismaClient({ adapter }) - await client.query(` - CREATE TABLE IF NOT EXISTS registrations ( - id SERIAL PRIMARY KEY, - event_id INTEGER REFERENCES events(id) ON DELETE CASCADE, - user_id INTEGER REFERENCES users(id), - team_size INTEGER NOT NULL, - team_leader_name VARCHAR(255) NOT NULL, - team_leader_email VARCHAR(255) NOT NULL, - team_leader_contact VARCHAR(20) NOT NULL, - team_members JSONB, - payment_screenshot VARCHAR(255), - transaction_id VARCHAR(255), - status VARCHAR(50) DEFAULT 'pending', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - `); - - console.log('Database tables created successfully'); - } catch (error) { - console.error('Database initialization error:', error); - throw error; - } finally { - client.release(); - } -}; - -// module.exports = { pool, initDB }; -export { pool, initDB }; \ No newline at end of file +export { prisma } \ No newline at end of file diff --git a/backend/controllers/adminController.js b/backend/controllers/adminController.js index dd98ad9..11a9a36 100644 --- a/backend/controllers/adminController.js +++ b/backend/controllers/adminController.js @@ -1,24 +1,27 @@ -import { pool } from '../config/db.js'; +import { prisma } from '../config/db.js'; import bcrypt from 'bcryptjs'; const getDashboardStats = async (req, res) => { try { - const usersCount = await pool.query('SELECT COUNT(*) FROM users WHERE role = $1', ['user']); - const eventsCount = await pool.query('SELECT COUNT(*) FROM events'); - const registrationsCount = await pool.query('SELECT COUNT(*) FROM registrations'); - const pendingCount = await pool.query( - 'SELECT COUNT(*) FROM registrations WHERE status = $1', - ['pending'] - ); - - res.json({ - stats: { - totalUsers: parseInt(usersCount.rows[0].count), - totalEvents: parseInt(eventsCount.rows[0].count), - totalRegistrations: parseInt(registrationsCount.rows[0].count), - pendingRegistrations: parseInt(pendingCount.rows[0].count) - } - }); + const [usersCount, eventsCount, registrationsCount, pendingCount] = await Promise.all([ + prisma.user.count({ + where: { role: 'user' } + }), + prisma.event.count(), + prisma.registration.count(), + prisma.registration.count({ + where: { status: 'pending' } + }) +]); + +res.json({ + stats: { + totalUsers: usersCount, + totalEvents: eventsCount, + totalRegistrations: registrationsCount, + pendingRegistrations: pendingCount + } +}); } catch (error) { console.error('Get dashboard stats error:', error); res.status(500).json({ message: 'Server error' }); @@ -26,11 +29,19 @@ const getDashboardStats = async (req, res) => { }; const getAllUsers = async (req, res) => { try { - const result = await pool.query( - 'SELECT id, email, role, created_at FROM users ORDER BY created_at DESC' - ); + const result = await prisma.user.findMany({ + select: { + id: true, + email: true, + role: true, + createdAt: true + }, + orderBy: { + createdAt: 'desc' + } + }); - res.json({ users: result.rows }); + res.json({ users: result }); } catch (error) { console.error('Get all users error:', error); res.status(500).json({ message: 'Server error' }); @@ -42,27 +53,26 @@ const updateUser = async (req, res) => { const { id } = req.params; const { email, role, password } = req.body; - let query = 'UPDATE users SET email = $1, role = $2'; - let params = [email, role]; - + const updateData = { + email, + role, +}; if (password) { - const hashedPassword = await bcrypt.hash(password, 10); - query += ', password = $3'; - params.push(hashedPassword); - } - - query += ` WHERE id = $${params.length + 1} RETURNING id, email, role, created_at`; - params.push(id); - - const result = await pool.query(query, params); - - if (result.rows.length === 0) { - return res.status(404).json({ message: 'User not found' }); - } - + updateData.password = await bcrypt.hash(password, 10); +} +const updatedUser = await prisma.user.update({ + where: { id: parseInt(id) }, + data: updateData, + select: { + id: true, + email: true, + role: true, + createdAt: true, + }, + }); res.json({ message: 'User updated successfully', - user: result.rows[0] + user: updatedUser }); } catch (error) { console.error('Update user error:', error); @@ -74,14 +84,18 @@ const deleteUser = async (req, res) => { try { const { id } = req.params; - const user = await pool.query('SELECT role FROM users WHERE id = $1', [id]); - if (user.rows.length > 0 && user.rows[0].role === 'admin') { + const user = await prisma.user.findUnique({ + where: { id: parseInt(id) }, + select: { role: true } + }); + if (user && user.role === 'admin') { return res.status(403).json({ message: 'Cannot delete admin account' }); } - const result = await pool.query('DELETE FROM users WHERE id = $1 RETURNING *', [id]); - - if (result.rows.length === 0) { + const result = await prisma.user.delete({ + where: { id: parseInt(id) } + }); + if (!result) { return res.status(404).json({ message: 'User not found' }); } @@ -96,25 +110,20 @@ const getAllRegistrations = async (req, res) => { try { const { eventId } = req.query; - let query = ` - SELECT r.*, e.title as event_title, e.date as event_date, - u.email as user_email - FROM registrations r - JOIN events e ON r.event_id = e.id - JOIN users u ON r.user_id = u.id - `; - - const params = []; - if (eventId) { - query += ' WHERE r.event_id = $1'; - params.push(eventId); - } - - query += ' ORDER BY r.created_at DESC'; - - const result = await pool.query(query, params); + const registrations = await prisma.registration.findMany({ + where: eventId ? { eventId: Number(eventId) } : {}, + orderBy: { createdAt: 'desc' }, + include: { + event: { + select: { title: true, date: true } + }, + user: { + select: { email: true } + } + } + }); - res.json({ registrations: result.rows }); + res.json({ registrations: registrations }); } catch (error) { console.error('Get all registrations error:', error); res.status(500).json({ message: 'Server error' }); @@ -130,18 +139,18 @@ const updateRegistrationStatus = async (req, res) => { return res.status(400).json({ message: 'Invalid status' }); } - const result = await pool.query( - 'UPDATE registrations SET status = $1 WHERE id = $2 RETURNING *', - [status, id] - ); + const updatedRegistration = await prisma.registration.update({ + where: { id: parseInt(id) }, + data: { status: status } + }); - if (result.rows.length === 0) { + if (!updatedRegistration) { return res.status(404).json({ message: 'Registration not found' }); } res.json({ message: 'Registration status updated successfully', - registration: result.rows[0] + registration: updatedRegistration }); } catch (error) { console.error('Update registration status error:', error); @@ -162,22 +171,26 @@ const updateRegistration = async (req, res) => { status } = req.body; - const result = await pool.query( - `UPDATE registrations - SET team_size = $1, team_leader_name = $2, team_leader_email = $3, - team_leader_contact = $4, team_members = $5, transaction_id = $6, status = $7 - WHERE id = $8 RETURNING *`, - [teamSize, teamLeaderName, teamLeaderEmail, teamLeaderContact, - teamMembers, transactionId, status, id] - ); + const updatedRegistration = await prisma.registration.update({ + where: { id: parseInt(id) }, + data: { + teamSize, + teamLeaderName, + teamLeaderEmail, + teamLeaderContact, + teamMembers, + transactionId, + status + } + }); - if (result.rows.length === 0) { + if (!updatedRegistration) { return res.status(404).json({ message: 'Registration not found' }); } res.json({ message: 'Registration updated successfully', - registration: result.rows[0] + registration: updatedRegistration }); } catch (error) { console.error('Update registration error:', error); @@ -189,9 +202,10 @@ const deleteRegistration = async (req, res) => { try { const { id } = req.params; - const result = await pool.query('DELETE FROM registrations WHERE id = $1 RETURNING *', [id]); - - if (result.rows.length === 0) { + const deletedRegistration = await prisma.registration.delete({ + where: { id: parseInt(id) } + }); + if (!deletedRegistration) { return res.status(404).json({ message: 'Registration not found' }); } diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js index e208e3e..4a62d6c 100644 --- a/backend/controllers/authController.js +++ b/backend/controllers/authController.js @@ -1,6 +1,6 @@ import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; -import { pool } from '../config/db.js'; +import { prisma } from '../config/db.js'; const register = async (req, res) => { try { @@ -18,23 +18,23 @@ const register = async (req, res) => { return res.status(400).json({ message: 'Password must be at least 6 characters long' }); } - const userExists = await pool.query( - 'SELECT * FROM users WHERE email = $1', - [email] - ); + const UserExist=await prisma.user.findUnique({ + where:{email:email} + }); - if (userExists.rows.length > 0) { + if (UserExist) { return res.status(400).json({ message: 'User already exists with this email' }); } const hashedPassword = await bcrypt.hash(password, 10); - const result = await pool.query( - 'INSERT INTO users (email, password, role) VALUES ($1, $2, $3) RETURNING id, email, role, created_at', - [email, hashedPassword, 'user'] - ); - - const user = result.rows[0]; + const user = await prisma.user.create({ + data: { + email, + password: hashedPassword, + role: 'user' + } + }); const token = jwt.sign( { id: user.id, email: user.email, role: user.role }, @@ -66,15 +66,13 @@ const login = async (req, res) => { return res.status(400).json({ message: 'Email and password are required' }); } - const result = await pool.query( - 'SELECT * FROM users WHERE email = $1', - [email] - ); + const user = await prisma.user.findUnique({ + where: { email: email } + }); - if (result.rows.length === 0) { + if (!user) { return res.status(401).json({ message: 'Invalid email or password' }); } - const user = result.rows[0]; const isValidPassword = await bcrypt.compare(password, user.password); if (!isValidPassword) { return res.status(401).json({ message: 'Invalid email or password' }); @@ -102,16 +100,15 @@ const login = async (req, res) => { const getCurrentUser = async (req, res) => { try { - const result = await pool.query( - 'SELECT id, email, role, created_at FROM users WHERE id = $1', - [req.user.id] - ); + const user = await prisma.user.findUnique({ + where: { id: req.user.id } + }); - if (result.rows.length === 0) { + if (!user) { return res.status(404).json({ message: 'User not found' }); } - res.json({ user: result.rows[0] }); + res.json({ user: user }); } catch (error) { console.error('Get current user error:', error); res.status(500).json({ message: 'Server error' }); diff --git a/backend/controllers/eventController.js b/backend/controllers/eventController.js index fe297a0..2d02641 100644 --- a/backend/controllers/eventController.js +++ b/backend/controllers/eventController.js @@ -1,15 +1,17 @@ -import { pool } from '../config/db.js'; +import { prisma } from "../config/db.js"; const getAllEvents = async (req, res) => { try { - const result = await pool.query(` - SELECT e.*, u.email as created_by_email - FROM events e - LEFT JOIN users u ON e.created_by = u.id - WHERE e.status = 'active' - ORDER BY e.created_at DESC - `); - - res.json({ events: result.rows }); + const events = await prisma.event.findMany({ + where: { status: 'active' }, + orderBy: { createdAt: 'desc' }, + include: { + createdBy: { + select: { email: true } + } + } + }); + + res.json({ events: events }); } catch (error) { console.error('Get events error:', error); res.status(500).json({ message: 'Failed to fetch events' }); @@ -19,18 +21,20 @@ const getEventById = async (req, res) => { try { const { id } = req.params; - const result = await pool.query(` - SELECT e.*, u.email as created_by_email - FROM events e - LEFT JOIN users u ON e.created_by = u.id - WHERE e.id = $1 - `, [id]); + const event = await prisma.event.findUnique({ + where: { id: Number(id) }, + include: { + createdBy: { + select: { email: true } + } + } + }); - if (result.rows.length === 0) { + if (!event) { return res.status(404).json({ message: 'Event not found' }); } - res.json({ event: result.rows[0] }); + res.json({ event: event }); } catch (error) { console.error('Get event error:', error); res.status(500).json({ message: 'Failed to fetch event' }); @@ -47,15 +51,22 @@ const createEvent = async (req, res) => { return res.status(400).json({ message: 'Title, description, and date are required' }); } - const result = await pool.query( - `INSERT INTO events (title, description, date, max_team_size, registration_fee, qr_code_image, created_by) - VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`, - [title, description, date, maxTeamSize || 1, registrationFee || 0, qrCodeImage, req.user.id] + const event = await prisma.event.create({ + data: { + title, + description, + date:date ? new Date(date) : null, + max_team_size : maxTeamSize || 1, + registration_fee: registrationFee || 0, + qr_code_image: qrCodeImage, + createdBy: { connect: { id: req.user.id } } + } + } ); res.status(201).json({ message: 'Event created successfully', - event: result.rows[0] + event: event }); } catch (error) { console.error('Create event error:', error); @@ -68,21 +79,26 @@ const updateEvent = async (req, res) => { const { title, description, date, maxTeamSize, registrationFee, status } = req.body; const qrCodeImage = req.file ? req.file.path : req.body.existingQrCode; - const result = await pool.query( - `UPDATE events - SET title = $1, description = $2, date = $3, max_team_size = $4, - registration_fee = $5, qr_code_image = $6, status = $7 - WHERE id = $8 RETURNING *`, - [title, description, date, maxTeamSize, registrationFee, qrCodeImage, status || 'active', id] - ); + const result = await prisma.event.update({ + where: { id: Number(id) }, + data: { + title, + description, + date, + maxTeamSize, + registrationFee, + qrCodeImage, + status: status || 'active' + } + }) - if (result.rows.length === 0) { + if (!result) { return res.status(404).json({ message: 'Event not found' }); } res.json({ message: 'Event updated successfully', - event: result.rows[0] + event: result }); } catch (error) { console.error('Update event error:', error); @@ -93,9 +109,11 @@ const deleteEvent = async (req, res) => { try { const { id } = req.params; - const result = await pool.query('DELETE FROM events WHERE id = $1 RETURNING *', [id]); + const result = await prisma.event.delete({ + where: { id: Number(id) } + }); - if (result.rows.length === 0) { + if (!result) { return res.status(404).json({ message: 'Event not found' }); } diff --git a/backend/controllers/registrationController.js b/backend/controllers/registrationController.js index 778dd8f..c196517 100644 --- a/backend/controllers/registrationController.js +++ b/backend/controllers/registrationController.js @@ -1,4 +1,4 @@ -import { pool } from '../config/db.js'; +import { prisma } from "../config/db.js"; const createRegistration = async (req, res) => { try { const { @@ -34,13 +34,15 @@ const createRegistration = async (req, res) => { } } } - const eventCheck = await pool.query('SELECT * FROM events WHERE id = $1', [eventId]); - if (eventCheck.rows.length === 0) { + const eventCheck = await prisma.event.findUnique({ + where: { id: Number(eventId) } + }); + if (!eventCheck) { return res.status(404).json({ message: 'Event not found' }); } - const event = eventCheck.rows[0]; - if (event.registration_fee > 0) { + const event = eventCheck; + if (event.registrationFee > 0) { if (!paymentScreenshot) { return res.status(400).json({ message: 'Payment screenshot is required for this event' }); } @@ -48,36 +50,35 @@ const createRegistration = async (req, res) => { return res.status(400).json({ message: 'Transaction ID is required' }); } } - const existingReg = await pool.query( - 'SELECT * FROM registrations WHERE event_id = $1 AND user_id = $2', - [eventId, req.user.id] - ); + const existingReg = await prisma.registration.findFirst({ + where: { + event_id: Number(eventId), + user_id: req.user.id + } + }); - if (existingReg.rows.length > 0) { + if (existingReg) { return res.status(400).json({ message: 'You have already registered for this event' }); } - const result = await pool.query( - `INSERT INTO registrations - (event_id, user_id, team_size, team_leader_name, team_leader_email, - team_leader_contact, team_members, payment_screenshot, transaction_id, status) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *`, - [ - eventId, - req.user.id, - teamSize, - teamLeaderName, - teamLeaderEmail, - teamLeaderContact, - teamMembers || null, - paymentScreenshot, - transactionId || null, - 'pending' - ] - ); + const result = await prisma.registration.create({ + data: { + event_id: Number(eventId), + user_id: req.user.id, + team_size: teamSize, + team_leader_name: teamLeaderName, + team_leader_email: teamLeaderEmail, + team_leader_contact: teamLeaderContact, + team_members: teamMembers || null, + payment_screenshot: paymentScreenshot, + transaction_id: transactionId || null, + status: 'pending' + } + }); + res.status(201).json({ message: 'Registration submitted successfully! Await admin approval.', - registration: result.rows[0] + registration: result }); } catch (error) { console.error('Create registration error:', error); @@ -86,16 +87,17 @@ const createRegistration = async (req, res) => { }; const getUserRegistrations = async (req, res) => { try { - const result = await pool.query( - `SELECT r.*, e.title as event_title, e.date as event_date - FROM registrations r - JOIN events e ON r.event_id = e.id - WHERE r.user_id = $1 - ORDER BY r.created_at DESC`, - [req.user.id] - ); + const result = await prisma.registration.findMany({ + where: { user_id: req.user.id }, + orderBy: { createdAt: 'desc' }, + include: { + event: { + select: { title: true, date: true } + } + } + }); - res.json({ registrations: result.rows }); + res.json({ registrations: result }); } catch (error) { console.error('Get user registrations error:', error); res.status(500).json({ message: 'Failed to fetch registrations' }); @@ -104,24 +106,26 @@ const getUserRegistrations = async (req, res) => { const getRegistrationById = async (req, res) => { try { const { id } = req.params; - - const result = await pool.query( - `SELECT r.*, e.title as event_title, e.date as event_date, u.email as user_email - FROM registrations r - JOIN events e ON r.event_id = e.id - JOIN users u ON r.user_id = u.id - WHERE r.id = $1`, - [id] - ); + const result = await prisma.registration.findUnique({ + where: { id: Number(id) }, + include: { + event: { + select: { title: true, date: true } + }, + user: { + select: { email: true } + } + } + }); - if (result.rows.length === 0) { + if (!result) { return res.status(404).json({ message: 'Registration not found' }); } - if (result.rows[0].user_id !== req.user.id && req.user.role !== 'admin') { + if (result.user_id !== req.user.id && req.user.role !== 'admin') { return res.status(403).json({ message: 'Access denied' }); } - res.json({ registration: result.rows[0] }); + res.json({ registration: result }); } catch (error) { console.error('Get registration error:', error); res.status(500).json({ message: 'Failed to fetch registration' }); diff --git a/backend/middleware/rateLimiter.js b/backend/middleware/rateLimiter.js new file mode 100644 index 0000000..e69de29 diff --git a/backend/package-lock.json b/backend/package-lock.json index 932e1cf..76a11ab 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -18,10 +18,10 @@ "jsonwebtoken": "^9.0.3", "multer": "^1.4.5-lts.1", "multer-storage-cloudinary": "^4.0.0", - "pg": "^8.17.2" + "pg": "^8.18.0" }, "devDependencies": { - "@types/node": "^25.0.10", + "@types/node": "^25.1.0", "@types/pg": "^8.16.0", "nodemon": "^3.0.1", "prisma": "^7.3.0" @@ -336,9 +336,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", - "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", + "integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", "dev": true, "license": "MIT", "dependencies": { @@ -1969,12 +1969,12 @@ "license": "MIT" }, "node_modules/pg": { - "version": "8.17.2", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.17.2.tgz", - "integrity": "sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", + "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", "dependencies": { - "pg-connection-string": "^2.10.1", + "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", @@ -2003,9 +2003,9 @@ "optional": true }, "node_modules/pg-connection-string": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.10.1.tgz", - "integrity": "sha512-iNzslsoeSH2/gmDDKiyMqF64DATUCWj3YJ0wP14kqcsf2TUklwimd+66yYojKwZCA7h2yRNLGug71hCBA2a4sw==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz", + "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==", "license": "MIT" }, "node_modules/pg-int8": { diff --git a/backend/package.json b/backend/package.json index fe876e4..c38d3fa 100644 --- a/backend/package.json +++ b/backend/package.json @@ -19,10 +19,10 @@ "jsonwebtoken": "^9.0.3", "multer": "^1.4.5-lts.1", "multer-storage-cloudinary": "^4.0.0", - "pg": "^8.17.2" + "pg": "^8.18.0" }, "devDependencies": { - "@types/node": "^25.0.10", + "@types/node": "^25.1.0", "@types/pg": "^8.16.0", "nodemon": "^3.0.1", "prisma": "^7.3.0" diff --git a/backend/prisma.config.ts b/backend/prisma.config.ts new file mode 100644 index 0000000..831a20f --- /dev/null +++ b/backend/prisma.config.ts @@ -0,0 +1,14 @@ +// This file was generated by Prisma, and assumes you have installed the following: +// npm install --save-dev prisma dotenv +import "dotenv/config"; +import { defineConfig } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + migrations: { + path: "prisma/migrations", + }, + datasource: { + url: process.env["DATABASE_URL"], + }, +}); diff --git a/backend/prisma/migrations/20260131175617_init/migration.sql b/backend/prisma/migrations/20260131175617_init/migration.sql new file mode 100644 index 0000000..40257ff --- /dev/null +++ b/backend/prisma/migrations/20260131175617_init/migration.sql @@ -0,0 +1,61 @@ +-- CreateEnum +CREATE TYPE "Role" AS ENUM ('user', 'admin'); + +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "email" TEXT NOT NULL, + "password" TEXT NOT NULL, + "role" "Role" NOT NULL DEFAULT 'user', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Event" ( + "id" SERIAL NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT, + "date" DATE, + "max_team_size" INTEGER NOT NULL DEFAULT 1, + "registration_fee" DECIMAL(65,30), + "qr_code_image" TEXT, + "status" TEXT NOT NULL DEFAULT 'active', + "createdAt" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(6) NOT NULL, + "createdById" INTEGER, + + CONSTRAINT "Event_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Registration" ( + "id" SERIAL NOT NULL, + "event_id" INTEGER NOT NULL, + "user_id" INTEGER, + "team_size" INTEGER NOT NULL, + "team_leader_name" VARCHAR(255) NOT NULL, + "team_leader_email" VARCHAR(255) NOT NULL, + "team_leader_contact" VARCHAR(20) NOT NULL, + "team_members" JSONB, + "payment_screenshot" VARCHAR(255), + "transaction_id" VARCHAR(255), + "status" VARCHAR(50) NOT NULL DEFAULT 'pending', + "createdAt" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Registration_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- AddForeignKey +ALTER TABLE "Event" ADD CONSTRAINT "Event_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Registration" ADD CONSTRAINT "Registration_event_id_fkey" FOREIGN KEY ("event_id") REFERENCES "Event"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Registration" ADD CONSTRAINT "Registration_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/backend/prisma/migrations/20260131183656_init/migration.sql b/backend/prisma/migrations/20260131183656_init/migration.sql new file mode 100644 index 0000000..5d44858 --- /dev/null +++ b/backend/prisma/migrations/20260131183656_init/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "Event" ALTER COLUMN "max_team_size" SET DEFAULT '1', +ALTER COLUMN "max_team_size" SET DATA TYPE TEXT, +ALTER COLUMN "registration_fee" SET DATA TYPE TEXT; diff --git a/backend/prisma/migrations/20260131184207_init/migration.sql b/backend/prisma/migrations/20260131184207_init/migration.sql new file mode 100644 index 0000000..ae31b9c --- /dev/null +++ b/backend/prisma/migrations/20260131184207_init/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Registration" ALTER COLUMN "team_size" SET DATA TYPE TEXT; diff --git a/backend/prisma/migrations/migration_lock.toml b/backend/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/backend/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma new file mode 100644 index 0000000..b0c0e98 --- /dev/null +++ b/backend/prisma/schema.prisma @@ -0,0 +1,69 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client-js" + output = "../generated/prisma" +} + +datasource db { + provider = "postgresql" +} + + + + +model User{ + id Int @id @default(autoincrement()) + email String @unique + password String + role Role @default(user) + events Event[] + registrations Registration[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + + +enum Role { + user + admin +} + +model Event{ + id Int @id @default(autoincrement()) + title String + description String? + date DateTime? @db.Date + max_team_size String @default("1") + registration_fee String? + qr_code_image String? + status String @default("active") + registrations Registration[] + createdAt DateTime @default(now()) @db.Timestamp(6) + updatedAt DateTime @updatedAt @db.Timestamp(6) + createdById Int? + createdBy User? @relation(fields: [createdById], references: [id]) +} + + +model Registration { + id Int @id @default(autoincrement()) + event_id Int + user_id Int? + team_size String + team_leader_name String @db.VarChar(255) + team_leader_email String @db.VarChar(255) + team_leader_contact String @db.VarChar(20) + team_members Json? @db.JsonB + payment_screenshot String? @db.VarChar(255) + transaction_id String? @db.VarChar(255) + status String @default("pending") @db.VarChar(50) + createdAt DateTime @default(now()) @db.Timestamp(6) + event Event @relation(fields: [event_id], references: [id], onDelete: Cascade) + user User? @relation(fields: [user_id], references: [id]) + +} \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 92356f5..bfc083b 100644 --- a/backend/server.js +++ b/backend/server.js @@ -7,11 +7,12 @@ import dotenv from 'dotenv'; dotenv.config(); -import { pool, initDB } from './config/db.js'; + import authRoutes from './routes/authRoutes.js'; import eventRoutes from './routes/eventRoutes.js'; import registrationRoutes from './routes/registrationRoutes.js'; import adminRoutes from './routes/adminRoutes.js'; +import { prisma } from './config/db.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -44,21 +45,22 @@ app.use((err, req, res, next) => { const initializeApp = async () => { try { - await initDB(); - const adminExists = await pool.query( - 'SELECT * FROM users WHERE email = $1', - [process.env.ADMIN_EMAIL || 'abesec.codechef@gmail.com'] - ); + const adminExists = await prisma.user.findUnique({ + where: { email: process.env.ADMIN_EMAIL || 'abesec.codechef@gmail.com' } + }); - if (adminExists.rows.length === 0) { + if (!adminExists) { const hashedPassword = await bcrypt.hash(process.env.ADMIN_PASSWORD, 10); - await pool.query( - 'INSERT INTO users (email, password, role) VALUES ($1, $2, $3)', - [process.env.ADMIN_EMAIL || 'abesec.codechef@gmail.com', hashedPassword, 'admin'] - ); + await prisma.user.create({ + data: { + email: process.env.ADMIN_EMAIL || 'abesec.codechef@gmail.com', + password: hashedPassword, + role: 'admin' + } + }); console.log(' Default admin user created'); - console.log(' Email:', process.env.ADMIN_EMAIL); + console.log(' Email:', process.env.ADMIN_EMAIL || 'abesec.codechef@gmail.com'); console.log(' Password:', process.env.ADMIN_PASSWORD); } app.listen(PORT, () => { diff --git a/package-lock.json b/package-lock.json index 78455e5..4a51caf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@studio-freight/lenis": "^1.0.42", "@tabler/icons-react": "^3.35.0", "@tailwindcss/vite": "^4.1.17", - "axios": "^1.13.3", + "axios": "^1.13.4", "clsx": "^2.1.1", "face-api.js": "^0.22.2", "framer-motion": "^12.23.24", @@ -26,6 +26,7 @@ "postprocessing": "^6.38.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-hot-toast": "^2.6.0", "react-icons": "^5.5.0", "react-router-dom": "^7.9.5", "react-scroll": "^1.9.3", @@ -2030,9 +2031,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.3.tgz", - "integrity": "sha512-ERT8kdX7DZjtUm7IitEyV7InTHAF42iJuMArIiDIV5YtPanJkgw4hw5Dyg9fh0mihdWNn1GKaeIWErfe56UQ1g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", + "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -2991,6 +2992,15 @@ "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==", "license": "MIT" }, + "node_modules/goober": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz", + "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -4043,6 +4053,23 @@ "react": "^19.2.0" } }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", diff --git a/package.json b/package.json index 58ff3c6..ea31b63 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@studio-freight/lenis": "^1.0.42", "@tabler/icons-react": "^3.35.0", "@tailwindcss/vite": "^4.1.17", - "axios": "^1.13.3", + "axios": "^1.13.4", "clsx": "^2.1.1", "face-api.js": "^0.22.2", "framer-motion": "^12.23.24", @@ -28,6 +28,7 @@ "postprocessing": "^6.38.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-hot-toast": "^2.6.0", "react-icons": "^5.5.0", "react-router-dom": "^7.9.5", "react-scroll": "^1.9.3", diff --git a/src/components/Landing.jsx b/src/components/Landing.jsx index 2663a22..c2eb17c 100644 --- a/src/components/Landing.jsx +++ b/src/components/Landing.jsx @@ -12,6 +12,7 @@ import { IconLogout, IconDashboard, } from "@tabler/icons-react"; +import { toast } from 'react-hot-toast'; import "../styles/Landing.css"; import Squares from "./Squares"; import { FloatingNav } from "./FloatingNavbar"; @@ -60,7 +61,7 @@ export default function Landing() { } if (!latestEvent) { - alert('No events available for registration at the moment'); + toast.error('No events available for registration at the moment'); return; } diff --git a/src/components/TeamGrid.jsx b/src/components/TeamGrid.jsx index be9008e..a35dc67 100644 --- a/src/components/TeamGrid.jsx +++ b/src/components/TeamGrid.jsx @@ -163,7 +163,7 @@ export default function TeamGrid() {

Teams

-
+
{grid.map((row, rowIndex) => (
{row.map((member, colIndex) => ( diff --git a/src/main.jsx b/src/main.jsx index e7ec7be..744fbfe 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -2,6 +2,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; import App from "./App"; +import { Toaster } from 'react-hot-toast'; import './index.css' import 'lenis/dist/lenis.css'; import ScrollToTop from "./components/ScrollToTop"; @@ -11,6 +12,7 @@ ReactDOM.createRoot(document.getElementById("root")).render( + diff --git a/src/pages/AdminDashboard.jsx b/src/pages/AdminDashboard.jsx index ffab87c..4f97a9d 100644 --- a/src/pages/AdminDashboard.jsx +++ b/src/pages/AdminDashboard.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; +import { toast } from 'react-hot-toast'; import { FloatingNav } from '../components/FloatingNavbar'; import { IconHome, @@ -116,7 +117,7 @@ export default function AdminDashboard() { await axios.delete(`${API_URL}/admin/users/${id}`, getAuthHeader()); fetchData(); } catch (error) { - alert(error.response?.data?.message || 'Failed to delete user'); + toast.error(error.response?.data?.message || 'Failed to delete user'); } }; @@ -127,7 +128,7 @@ export default function AdminDashboard() { await axios.delete(`${API_URL}/events/${id}`, getAuthHeader()); fetchData(); } catch (error) { - alert('Failed to delete event'); + toast.error('Failed to delete event'); } }; @@ -136,7 +137,7 @@ export default function AdminDashboard() { await axios.put(`${API_URL}/admin/registrations/${id}/status`, { status }, getAuthHeader()); fetchData(); } catch (error) { - alert('Failed to update status'); + toast.error('Failed to update status'); } }; @@ -147,7 +148,7 @@ export default function AdminDashboard() { await axios.delete(`${API_URL}/admin/registrations/${id}`, getAuthHeader()); fetchData(); } catch (error) { - alert('Failed to delete registration'); + toast.error('Failed to delete registration'); } }; @@ -421,7 +422,7 @@ function CreateEventModal({ onClose }) { 'Content-Type': 'multipart/form-data' } }); - alert('Event created successfully!'); + toast.success('Event created successfully!'); onClose(); } catch (error) { setError(error.response?.data?.message || 'Failed to create event'); diff --git a/src/pages/EventRegistration.jsx b/src/pages/EventRegistration.jsx index 1b8d4ca..6a2eec7 100644 --- a/src/pages/EventRegistration.jsx +++ b/src/pages/EventRegistration.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import axios from 'axios'; +import { toast } from 'react-hot-toast'; const API_URL = 'http://localhost:5000/api'; @@ -164,7 +165,7 @@ export default function EventRegistration() { } }); - alert('Registration submitted successfully! Await admin approval.'); + toast.success('Registration submitted successfully! Await admin approval.'); navigate('/'); } catch (err) { setError(err.response?.data?.message || 'Registration failed. Please try again.'); diff --git a/src/pages/TeamPage.jsx b/src/pages/TeamPage.jsx index 64d39da..12c926d 100644 --- a/src/pages/TeamPage.jsx +++ b/src/pages/TeamPage.jsx @@ -5,14 +5,14 @@ const TeamPage = () => { return (
-
+
From 52e64206447c0e7d172e68ab92783efa20910dcf Mon Sep 17 00:00:00 2001 From: "ayushmanitiwari931@gmail.com" Date: Mon, 2 Feb 2026 15:57:52 +0530 Subject: [PATCH 2/9] Add CircularGallery and related components for enhanced visual display --- src/components/CircularGallery.jsx | 475 ++++++++++++++++++++ src/components/eventsGalary.jsx | 15 + src/components/testimonial.jsx | 48 ++ src/components/ui/infinite-moving-cards.jsx | 57 +++ src/pages/HomePage.jsx | 5 + 5 files changed, 600 insertions(+) create mode 100644 src/components/CircularGallery.jsx create mode 100644 src/components/eventsGalary.jsx create mode 100644 src/components/testimonial.jsx create mode 100644 src/components/ui/infinite-moving-cards.jsx diff --git a/src/components/CircularGallery.jsx b/src/components/CircularGallery.jsx new file mode 100644 index 0000000..6a4b849 --- /dev/null +++ b/src/components/CircularGallery.jsx @@ -0,0 +1,475 @@ +import { Camera, Mesh, Plane, Program, Renderer, Texture, Transform } from 'ogl'; +import { useEffect, useRef } from 'react'; + +function debounce(func, wait) { + let timeout; + return function (...args) { + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, args), wait); + }; +} + +function lerp(p1, p2, t) { + return p1 + (p2 - p1) * t; +} + +function autoBind(instance) { + const proto = Object.getPrototypeOf(instance); + Object.getOwnPropertyNames(proto).forEach(key => { + if (key !== 'constructor' && typeof instance[key] === 'function') { + instance[key] = instance[key].bind(instance); + } + }); +} + +function createTextTexture(gl, text, font = 'bold 30px monospace', color = 'black') { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + context.font = font; + const metrics = context.measureText(text); + const textWidth = Math.ceil(metrics.width); + const textHeight = Math.ceil(parseInt(font, 10) * 1.2); + canvas.width = textWidth + 20; + canvas.height = textHeight + 20; + context.font = font; + context.fillStyle = color; + context.textBaseline = 'middle'; + context.textAlign = 'center'; + context.clearRect(0, 0, canvas.width, canvas.height); + context.fillText(text, canvas.width / 2, canvas.height / 2); + const texture = new Texture(gl, { generateMipmaps: false }); + texture.image = canvas; + return { texture, width: canvas.width, height: canvas.height }; +} + +class Title { + constructor({ gl, plane, renderer, text, textColor = '#545050', font = '30px sans-serif' }) { + autoBind(this); + this.gl = gl; + this.plane = plane; + this.renderer = renderer; + this.text = text; + this.textColor = textColor; + this.font = font; + this.createMesh(); + } + createMesh() { + const { texture, width, height } = createTextTexture(this.gl, this.text, this.font, this.textColor); + const geometry = new Plane(this.gl); + const program = new Program(this.gl, { + vertex: ` + attribute vec3 position; + attribute vec2 uv; + uniform mat4 modelViewMatrix; + uniform mat4 projectionMatrix; + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragment: ` + precision highp float; + uniform sampler2D tMap; + varying vec2 vUv; + void main() { + vec4 color = texture2D(tMap, vUv); + if (color.a < 0.1) discard; + gl_FragColor = color; + } + `, + uniforms: { tMap: { value: texture } }, + transparent: true + }); + this.mesh = new Mesh(this.gl, { geometry, program }); + const aspect = width / height; + const textHeight = this.plane.scale.y * 0.15; + const textWidth = textHeight * aspect; + this.mesh.scale.set(textWidth, textHeight, 1); + this.mesh.position.y = -this.plane.scale.y * 0.5 - textHeight * 0.5 - 0.05; + this.mesh.setParent(this.plane); + } +} + +class Media { + constructor({ + geometry, + gl, + image, + index, + length, + renderer, + scene, + screen, + text, + viewport, + bend, + textColor, + borderRadius = 0, + font + }) { + this.extra = 0; + this.geometry = geometry; + this.gl = gl; + this.image = image; + this.index = index; + this.length = length; + this.renderer = renderer; + this.scene = scene; + this.screen = screen; + this.text = text; + this.viewport = viewport; + this.bend = bend; + this.textColor = textColor; + this.borderRadius = borderRadius; + this.font = font; + this.createShader(); + this.createMesh(); + this.createTitle(); + this.onResize(); + } + createShader() { + const texture = new Texture(this.gl, { + generateMipmaps: true + }); + this.program = new Program(this.gl, { + depthTest: false, + depthWrite: false, + vertex: ` + precision highp float; + attribute vec3 position; + attribute vec2 uv; + uniform mat4 modelViewMatrix; + uniform mat4 projectionMatrix; + uniform float uTime; + uniform float uSpeed; + varying vec2 vUv; + void main() { + vUv = uv; + vec3 p = position; + p.z = (sin(p.x * 4.0 + uTime) * 1.5 + cos(p.y * 2.0 + uTime) * 1.5) * (0.1 + uSpeed * 0.5); + gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0); + } + `, + fragment: ` + precision highp float; + uniform vec2 uImageSizes; + uniform vec2 uPlaneSizes; + uniform sampler2D tMap; + uniform float uBorderRadius; + varying vec2 vUv; + + float roundedBoxSDF(vec2 p, vec2 b, float r) { + vec2 d = abs(p) - b; + return length(max(d, vec2(0.0))) + min(max(d.x, d.y), 0.0) - r; + } + + void main() { + vec2 ratio = vec2( + min((uPlaneSizes.x / uPlaneSizes.y) / (uImageSizes.x / uImageSizes.y), 1.0), + min((uPlaneSizes.y / uPlaneSizes.x) / (uImageSizes.y / uImageSizes.x), 1.0) + ); + vec2 uv = vec2( + vUv.x * ratio.x + (1.0 - ratio.x) * 0.5, + vUv.y * ratio.y + (1.0 - ratio.y) * 0.5 + ); + vec4 color = texture2D(tMap, uv); + + float d = roundedBoxSDF(vUv - 0.5, vec2(0.5 - uBorderRadius), uBorderRadius); + + // Smooth antialiasing for edges + float edgeSmooth = 0.002; + float alpha = 1.0 - smoothstep(-edgeSmooth, edgeSmooth, d); + + gl_FragColor = vec4(color.rgb, alpha); + } + `, + uniforms: { + tMap: { value: texture }, + uPlaneSizes: { value: [0, 0] }, + uImageSizes: { value: [0, 0] }, + uSpeed: { value: 0 }, + uTime: { value: 100 * Math.random() }, + uBorderRadius: { value: this.borderRadius } + }, + transparent: true + }); + const img = new Image(); + img.crossOrigin = 'anonymous'; + img.src = this.image; + img.onload = () => { + texture.image = img; + this.program.uniforms.uImageSizes.value = [img.naturalWidth, img.naturalHeight]; + }; + } + createMesh() { + this.plane = new Mesh(this.gl, { + geometry: this.geometry, + program: this.program + }); + this.plane.setParent(this.scene); + } + createTitle() { + this.title = new Title({ + gl: this.gl, + plane: this.plane, + renderer: this.renderer, + text: this.text, + textColor: this.textColor, + fontFamily: this.font + }); + } + update(scroll, direction) { + this.plane.position.x = this.x - scroll.current - this.extra; + + const x = this.plane.position.x; + const H = this.viewport.width / 2; + + if (this.bend === 0) { + this.plane.position.y = 0; + this.plane.rotation.z = 0; + } else { + const B_abs = Math.abs(this.bend); + const R = (H * H + B_abs * B_abs) / (2 * B_abs); + const effectiveX = Math.min(Math.abs(x), H); + + const arc = R - Math.sqrt(R * R - effectiveX * effectiveX); + if (this.bend > 0) { + this.plane.position.y = -arc; + this.plane.rotation.z = -Math.sign(x) * Math.asin(effectiveX / R); + } else { + this.plane.position.y = arc; + this.plane.rotation.z = Math.sign(x) * Math.asin(effectiveX / R); + } + } + + this.speed = scroll.current - scroll.last; + this.program.uniforms.uTime.value += 0.04; + this.program.uniforms.uSpeed.value = this.speed; + + const planeOffset = this.plane.scale.x / 2; + const viewportOffset = this.viewport.width / 2; + this.isBefore = this.plane.position.x + planeOffset < -viewportOffset; + this.isAfter = this.plane.position.x - planeOffset > viewportOffset; + if (direction === 'right' && this.isBefore) { + this.extra -= this.widthTotal; + this.isBefore = this.isAfter = false; + } + if (direction === 'left' && this.isAfter) { + this.extra += this.widthTotal; + this.isBefore = this.isAfter = false; + } + } + onResize({ screen, viewport } = {}) { + if (screen) this.screen = screen; + if (viewport) { + this.viewport = viewport; + if (this.plane.program.uniforms.uViewportSizes) { + this.plane.program.uniforms.uViewportSizes.value = [this.viewport.width, this.viewport.height]; + } + } + this.scale = this.screen.height / 1500; + this.plane.scale.y = (this.viewport.height * (900 * this.scale)) / this.screen.height; + this.plane.scale.x = (this.viewport.width * (700 * this.scale)) / this.screen.width; + this.plane.program.uniforms.uPlaneSizes.value = [this.plane.scale.x, this.plane.scale.y]; + this.padding = 2; + this.width = this.plane.scale.x + this.padding; + this.widthTotal = this.width * this.length; + this.x = this.width * this.index; + } +} + +class App { + constructor( + container, + { + items, + bend, + textColor = '#ffffff', + borderRadius = 0, + font = 'bold 30px Figtree', + scrollSpeed = 2, + scrollEase = 0.05 + } = {} + ) { + document.documentElement.classList.remove('no-js'); + this.container = container; + this.scrollSpeed = scrollSpeed; + this.scroll = { ease: scrollEase, current: 0, target: 0, last: 0 }; + this.onCheckDebounce = debounce(this.onCheck, 200); + this.createRenderer(); + this.createCamera(); + this.createScene(); + this.onResize(); + this.createGeometry(); + this.createMedias(items, bend, textColor, borderRadius, font); + this.update(); + this.addEventListeners(); + } + createRenderer() { + this.renderer = new Renderer({ + alpha: true, + antialias: true, + dpr: Math.min(window.devicePixelRatio || 1, 2) + }); + this.gl = this.renderer.gl; + this.gl.clearColor(0, 0, 0, 0); + this.container.appendChild(this.gl.canvas); + } + createCamera() { + this.camera = new Camera(this.gl); + this.camera.fov = 45; + this.camera.position.z = 20; + } + createScene() { + this.scene = new Transform(); + } + createGeometry() { + this.planeGeometry = new Plane(this.gl, { + heightSegments: 50, + widthSegments: 100 + }); + } + createMedias(items, bend = 1, textColor, borderRadius, font) { + const defaultItems = [ + { image: `/bc2.webp?grayscale`, text: '' }, + { image: `/rust4.webp?grayscale`, text: '' }, + { image: `/rust6.webp?grayscale`, text: '' }, + { image: `/hn2.webp?grayscale`, text: '' }, + { image: `/COC1.JPG?grayscale`, text: '' }, + { image: `/TERROR1.webp?grayscale`, text: '' }, + { image: `/OUAC1.webp?grayscale`, text: '' }, + { image: `/OUAC2.webp?grayscale`, text: '' }, + { image: `/COC4.jpeg`, text: '' }, + { image: `/rust3.webp`, text: '' }, + { image: `hn1.webp`, text: '' }, + + ]; + const galleryItems = items && items.length ? items : defaultItems; + this.mediasImages = galleryItems.concat(galleryItems); + this.medias = this.mediasImages.map((data, index) => { + return new Media({ + geometry: this.planeGeometry, + gl: this.gl, + image: data.image, + index, + length: this.mediasImages.length, + renderer: this.renderer, + scene: this.scene, + screen: this.screen, + text: data.text, + viewport: this.viewport, + bend, + textColor, + borderRadius, + font + }); + }); + } + onTouchDown(e) { + this.isDown = true; + this.scroll.position = this.scroll.current; + this.start = e.touches ? e.touches[0].clientX : e.clientX; + } + onTouchMove(e) { + if (!this.isDown) return; + const x = e.touches ? e.touches[0].clientX : e.clientX; + const distance = (this.start - x) * (this.scrollSpeed * 0.025); + this.scroll.target = this.scroll.position + distance; + } + onTouchUp() { + this.isDown = false; + this.onCheck(); + } + onWheel(e) { + const delta = e.deltaY || e.wheelDelta || e.detail; + this.scroll.target += (delta > 0 ? this.scrollSpeed : -this.scrollSpeed) * 0.2; + this.onCheckDebounce(); + } + onCheck() { + if (!this.medias || !this.medias[0]) return; + const width = this.medias[0].width; + const itemIndex = Math.round(Math.abs(this.scroll.target) / width); + const item = width * itemIndex; + this.scroll.target = this.scroll.target < 0 ? -item : item; + } + onResize() { + this.screen = { + width: this.container.clientWidth, + height: this.container.clientHeight + }; + this.renderer.setSize(this.screen.width, this.screen.height); + this.camera.perspective({ + aspect: this.screen.width / this.screen.height + }); + const fov = (this.camera.fov * Math.PI) / 180; + const height = 2 * Math.tan(fov / 2) * this.camera.position.z; + const width = height * this.camera.aspect; + this.viewport = { width, height }; + if (this.medias) { + this.medias.forEach(media => media.onResize({ screen: this.screen, viewport: this.viewport })); + } + } + update() { + this.scroll.current = lerp(this.scroll.current, this.scroll.target, this.scroll.ease); + const direction = this.scroll.current > this.scroll.last ? 'right' : 'left'; + if (this.medias) { + this.medias.forEach(media => media.update(this.scroll, direction)); + } + this.renderer.render({ scene: this.scene, camera: this.camera }); + this.scroll.last = this.scroll.current; + this.raf = window.requestAnimationFrame(this.update.bind(this)); + } + addEventListeners() { + this.boundOnResize = this.onResize.bind(this); + this.boundOnWheel = this.onWheel.bind(this); + this.boundOnTouchDown = this.onTouchDown.bind(this); + this.boundOnTouchMove = this.onTouchMove.bind(this); + this.boundOnTouchUp = this.onTouchUp.bind(this); + window.addEventListener('resize', this.boundOnResize); + window.addEventListener('mousewheel', this.boundOnWheel); + window.addEventListener('wheel', this.boundOnWheel); + window.addEventListener('mousedown', this.boundOnTouchDown); + window.addEventListener('mousemove', this.boundOnTouchMove); + window.addEventListener('mouseup', this.boundOnTouchUp); + window.addEventListener('touchstart', this.boundOnTouchDown); + window.addEventListener('touchmove', this.boundOnTouchMove); + window.addEventListener('touchend', this.boundOnTouchUp); + } + destroy() { + window.cancelAnimationFrame(this.raf); + window.removeEventListener('resize', this.boundOnResize); + window.removeEventListener('mousewheel', this.boundOnWheel); + window.removeEventListener('wheel', this.boundOnWheel); + window.removeEventListener('mousedown', this.boundOnTouchDown); + window.removeEventListener('mousemove', this.boundOnTouchMove); + window.removeEventListener('mouseup', this.boundOnTouchUp); + window.removeEventListener('touchstart', this.boundOnTouchDown); + window.removeEventListener('touchmove', this.boundOnTouchMove); + window.removeEventListener('touchend', this.boundOnTouchUp); + if (this.renderer && this.renderer.gl && this.renderer.gl.canvas.parentNode) { + this.renderer.gl.canvas.parentNode.removeChild(this.renderer.gl.canvas); + } + } +} + +// Ensure CircularGallery is properly configured and functional + +export default function CircularGallery({ + items, + bend = 3, + textColor = '#ffffff', + borderRadius = 0.05, + font = 'bold 30px Figtree', + scrollSpeed = 2, + scrollEase = 0.05 +}) { + const containerRef = useRef(null); + useEffect(() => { + const app = new App(containerRef.current, { items, bend, textColor, borderRadius, font, scrollSpeed, scrollEase }); + return () => { + app.destroy(); + }; + }, [items, bend, textColor, borderRadius, font, scrollSpeed, scrollEase]); + return
; +} diff --git a/src/components/eventsGalary.jsx b/src/components/eventsGalary.jsx new file mode 100644 index 0000000..16c1d9f --- /dev/null +++ b/src/components/eventsGalary.jsx @@ -0,0 +1,15 @@ +import CircularGallery from './CircularGallery'; + +export default function EventsGalary() { + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/src/components/testimonial.jsx b/src/components/testimonial.jsx new file mode 100644 index 0000000..6a20e0a --- /dev/null +++ b/src/components/testimonial.jsx @@ -0,0 +1,48 @@ +"use client"; + +import React from "react"; +import { InfiniteMovingCards } from "./ui/infinite-moving-cards"; + +export function InfiniteMovingCardsDemo() { + return ( +
+

Served Hot: Words from Our Bawarchis

+ +
+ ); +} + +const testimonials = [ + { + quote: + "It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair.", + name: "Charles Dickens", + title: "A Tale of Two Cities", + }, + { + quote: + "To be, or not to be, that is the question: Whether 'tis nobler in the mind to suffer The slings and arrows of outrageous fortune, Or to take Arms against a Sea of troubles, And by opposing end them: to die, to sleep.", + name: "William Shakespeare", + title: "Hamlet", + }, + { + quote: "All that we see or seem is but a dream within a dream.", + name: "Edgar Allan Poe", + title: "A Dream Within a Dream", + }, + { + quote: + "It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife.", + name: "Jane Austen", + title: "Pride and Prejudice", + }, + { + quote: + "Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world.", + name: "Herman Melville", + title: "Moby-Dick", + }, +]; diff --git a/src/components/ui/infinite-moving-cards.jsx b/src/components/ui/infinite-moving-cards.jsx new file mode 100644 index 0000000..6136ddc --- /dev/null +++ b/src/components/ui/infinite-moving-cards.jsx @@ -0,0 +1,57 @@ +import React from "react"; + +export function InfiniteMovingCards({ items = [], direction = "right", speed = "slow" }) { + const duration = speed === "slow" ? "40s" : speed === "fast" ? "12s" : "24s"; + const dir = direction === "right" ? "normal" : "reverse"; + + // Duplicate items so the marquee appears continuous + const doubled = [...items, ...items]; + + return ( +
+ + +
+ {doubled.map((t, i) => ( +
+
{t.quote}
+
{t.name} — {t.title}
+
+ ))} +
+
+ ); +} + +export default InfiniteMovingCards; diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx index 44feb28..8e43038 100644 --- a/src/pages/HomePage.jsx +++ b/src/pages/HomePage.jsx @@ -4,6 +4,8 @@ import ExPresident from "../components/ExPresident"; import Landing from "../components/Landing"; import Offering from "../components/Offering"; import About from "../components/about"; +import EventsGalary from "../components/eventsGalary"; +import { InfiniteMovingCardsDemo } from "../components/testimonial"; import useLenis from "../hooks/useLenis"; @@ -16,6 +18,9 @@ export default function HomePage() { + + + {/* */} {/*
*/}
From dd168f49e86e34a3b0051bef18ec14849881d489 Mon Sep 17 00:00:00 2001 From: "ayushmanitiwari931@gmail.com" Date: Wed, 4 Feb 2026 23:26:59 +0530 Subject: [PATCH 3/9] Update UI components and dependencies: replace InfiniteMovingCaCC-websiteV2.0/package.jsonrds with AnimatedTestimonialsDemo, update framer-motion version, and add InfiniteMenu component --- package-lock.json | 26 +- package.json | 2 +- src/components/eventsGalary.jsx | 21 +- src/components/testimonial.jsx | 48 --- src/components/testimonial.tsx | 35 ++ src/components/ui/infinite-menu.tsx | 377 ++++++++++++++++++++ src/components/ui/infinite-moving-cards.jsx | 57 --- src/components/ui/testimonials-card.tsx | 153 ++++++++ src/pages/HomePage.jsx | 4 +- 9 files changed, 594 insertions(+), 129 deletions(-) delete mode 100644 src/components/testimonial.jsx create mode 100644 src/components/testimonial.tsx create mode 100644 src/components/ui/infinite-menu.tsx delete mode 100644 src/components/ui/infinite-moving-cards.jsx create mode 100644 src/components/ui/testimonials-card.tsx diff --git a/package-lock.json b/package-lock.json index 78455e5..aa22bd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "axios": "^1.13.3", "clsx": "^2.1.1", "face-api.js": "^0.22.2", - "framer-motion": "^12.23.24", + "framer-motion": "^12.31.0", "gsap": "^3.13.0", "lenis": "^1.3.14", "lucide-react": "^0.553.0", @@ -2863,13 +2863,13 @@ } }, "node_modules/framer-motion": { - "version": "12.23.24", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz", - "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==", + "version": "12.31.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.31.0.tgz", + "integrity": "sha512-Tnd0FU05zGRFI3JJmBegXonF1rfuzYeuXd1QSdQ99Ysnppk0yWBWSW2wUsqzRpS5nv0zPNx+y0wtDj4kf0q5RQ==", "license": "MIT", "dependencies": { - "motion-dom": "^12.23.23", - "motion-utils": "^12.23.6", + "motion-dom": "^12.30.1", + "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { @@ -3742,18 +3742,18 @@ } }, "node_modules/motion-dom": { - "version": "12.23.23", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", - "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "version": "12.30.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.30.1.tgz", + "integrity": "sha512-QXB+iFJRzZTqL+Am4a1CRoHdH+0Nq12wLdqQQZZsfHlp9AMt6PA098L/61oVZsDA+Ep3QSGudzpViyRrhYhGcQ==", "license": "MIT", "dependencies": { - "motion-utils": "^12.23.6" + "motion-utils": "^12.29.2" } }, "node_modules/motion-utils": { - "version": "12.23.6", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", - "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", + "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", "license": "MIT" }, "node_modules/ms": { diff --git a/package.json b/package.json index 58ff3c6..68e36ab 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "axios": "^1.13.3", "clsx": "^2.1.1", "face-api.js": "^0.22.2", - "framer-motion": "^12.23.24", + "framer-motion": "^12.31.0", "gsap": "^3.13.0", "lenis": "^1.3.14", "lucide-react": "^0.553.0", diff --git a/src/components/eventsGalary.jsx b/src/components/eventsGalary.jsx index 16c1d9f..454a46c 100644 --- a/src/components/eventsGalary.jsx +++ b/src/components/eventsGalary.jsx @@ -2,14 +2,19 @@ import CircularGallery from './CircularGallery'; export default function EventsGalary() { return ( -
- +
+

+ Memories We Cooked Together +

+
+ +
); } \ No newline at end of file diff --git a/src/components/testimonial.jsx b/src/components/testimonial.jsx deleted file mode 100644 index 6a20e0a..0000000 --- a/src/components/testimonial.jsx +++ /dev/null @@ -1,48 +0,0 @@ -"use client"; - -import React from "react"; -import { InfiniteMovingCards } from "./ui/infinite-moving-cards"; - -export function InfiniteMovingCardsDemo() { - return ( -
-

Served Hot: Words from Our Bawarchis

- -
- ); -} - -const testimonials = [ - { - quote: - "It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair.", - name: "Charles Dickens", - title: "A Tale of Two Cities", - }, - { - quote: - "To be, or not to be, that is the question: Whether 'tis nobler in the mind to suffer The slings and arrows of outrageous fortune, Or to take Arms against a Sea of troubles, And by opposing end them: to die, to sleep.", - name: "William Shakespeare", - title: "Hamlet", - }, - { - quote: "All that we see or seem is but a dream within a dream.", - name: "Edgar Allan Poe", - title: "A Dream Within a Dream", - }, - { - quote: - "It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife.", - name: "Jane Austen", - title: "Pride and Prejudice", - }, - { - quote: - "Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world.", - name: "Herman Melville", - title: "Moby-Dick", - }, -]; diff --git a/src/components/testimonial.tsx b/src/components/testimonial.tsx new file mode 100644 index 0000000..bf5bb1b --- /dev/null +++ b/src/components/testimonial.tsx @@ -0,0 +1,35 @@ +import { TestimonialsCard } from "./ui/testimonials-card" + +const testimonials = [ +{ + id: 1, + title: "Sarah Wilson", + description: "Incredible product! It exceeded all my expectations.", + image: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=400&h=400&fit=crop", +}, +{ + id: 2, + title: "David Chen", + description: "The best investment I've made this year.", + image: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=400&fit=crop", +}, +{ + id: 3, + title: "Emma Rodriguez", + description: "Outstanding service and amazing results!", + image: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400&h=400&fit=crop", +}, +]; + +export default function AnimatedTestimonialsDemo() { +return ( +
+

+ Served Hot: Words from Our Bawarchis +

+
+ +
+
+); +} \ No newline at end of file diff --git a/src/components/ui/infinite-menu.tsx b/src/components/ui/infinite-menu.tsx new file mode 100644 index 0000000..a238932 --- /dev/null +++ b/src/components/ui/infinite-menu.tsx @@ -0,0 +1,377 @@ +// @ts-check +import React, { useCallback, useMemo, useState, memo, useEffect } from "react"; +import { + View, + StyleSheet, + Dimensions, + Animated as RNAnimated, +} from "react-native"; +import { Canvas, Circle, Group, Image, Skia } from "@shopify/react-native-skia"; +import { useSharedValue, useFrameCallback } from "react-native-reanimated"; +import { + Gesture, + GestureDetector, + GestureHandlerRootView, +} from "react-native-gesture-handler"; +import { useLoadImages } from "./hooks"; +import { Quat, Vec3 } from "./maths-type"; +import type { IDisc, IDiscComponent, IInfiniteMenu, IMenuItem } from "./types"; +import { generateIcosahedronVertices } from "./helpers"; +import { + projectToSphere, + quatConjugate, + quatFromVectors, + quatMultiply, + quatNormalize, + quatRotateVec3, + quatSlerp, + vec3Normalize, +} from "./maths"; +import { scheduleOnRN } from "react-native-worklets"; + +const DiscComponent: React.FC = memo( + ({ + x, + y, + radius, + alpha, + image, + }: IDiscComponent): React.ReactElement | null => { + const clipPath = useMemo(() => { + const path = Skia.Path.Make(); + path.addCircle(x, y, radius); + return path; + }, [x, y, radius]); + + if (radius < 1) return null; + + if (!image) { + return ( + + ); + } + + return ( + + + + ); + }, +); + +export const InfiniteMenu: React.FC & + React.FunctionComponent = memo( + ({ items, scale = 1, backgroundColor = "#000000" }: IInfiniteMenu) => { + const { width: screenWidth, height: screenHeight } = + Dimensions.get("window"); + const centerX = screenWidth / 2; + const centerY = screenHeight / 2; + + const imageUrls = useMemo( + () => items.map((item) => item.image), + [items], + ); + const loadedImages = useLoadImages(imageUrls); + + const [activeItem, setActiveItem] = useState( + items[0] || null, + ); + const [isMoving, setIsMoving] = useState(false); + const [discData, setDiscData] = useState([]); + const SPHERE_RADIUS = 2 * scale; + const DISC_BASE_SCALE = 0.25; + const CAMERA_Z = 3 * scale; + const PROJECTION_SCALE = 150; + const sphereVertices = useMemo( + () => generateIcosahedronVertices(1, SPHERE_RADIUS), + [SPHERE_RADIUS], + ); + + const verticesRef = useMemo(() => [...sphereVertices], [sphereVertices]); + + const qx = useSharedValue(0); + const qy = useSharedValue(0); + const qz = useSharedValue(0); + const qw = useSharedValue(1); + + const prx = useSharedValue(0); + const pry = useSharedValue(0); + const prz = useSharedValue(0); + const prw = useSharedValue(1); + + const rotVelocity = useSharedValue(0); + const isDown = useSharedValue(false); + const prevX = useSharedValue(0); + const prevY = useSharedValue(0); + const camZ = useSharedValue(CAMERA_Z); + const activeIdx = useSharedValue(0); + + const updateActiveItem = useCallback( + (index: number) => { + if (items.length === 0) return; + const itemIndex = index % items.length; + setActiveItem(items[itemIndex]); + }, + [items], + ); + + const updateIsMoving = useCallback((moving: boolean) => { + setIsMoving(moving); + }, []); + + const updateDiscData = useCallback((data: IDisc[]) => { + setDiscData(data); + }, []); + + const lastMoving = useSharedValue(false); + const frameSkip = useSharedValue(0); + + useFrameCallback((info) => { + "worklet"; + // Clamp dt to prevent physics jumps after idle + const rawDt = info.timeSincePreviousFrame || 16; + const dt = Math.min(rawDt, 50); + const ts = dt / 16 + 0.0001; + const IDENTITY: Quat = { x: 0, y: 0, z: 0, w: 1 }; + + const orientation: Quat = { + x: qx.value, + y: qy.value, + z: qz.value, + w: qw.value, + }; + const pointerRot: Quat = { + x: prx.value, + y: pry.value, + z: prz.value, + w: prw.value, + }; + + const dampIntensity = 0.1 * ts; + const dampenedPR = quatSlerp(pointerRot, IDENTITY, dampIntensity); + prx.value = dampenedPR.x; + pry.value = dampenedPR.y; + prz.value = dampenedPR.z; + prw.value = dampenedPR.w; + + let snapRot: Quat = IDENTITY; + + if (!isDown.value) { + const snapDir: Vec3 = { x: 0, y: 0, z: -1 }; + const invOrientation = quatConjugate(orientation); + const transformedSnapDir = quatRotateVec3(invOrientation, snapDir); + + let maxDot = -Infinity; + let nearestIdx = 0; + + for (let i = 0; i < verticesRef.length; i++) { + const v = verticesRef[i]; + const dot = + transformedSnapDir.x * v.x + + transformedSnapDir.y * v.y + + transformedSnapDir.z * v.z; + if (dot > maxDot) { + maxDot = dot; + nearestIdx = i; + } + } + + const nearestV = verticesRef[nearestIdx]; + const worldV = quatRotateVec3(orientation, nearestV); + const targetDir = vec3Normalize(worldV); + + const sqrDist = + (targetDir.x - snapDir.x) ** 2 + + (targetDir.y - snapDir.y) ** 2 + + (targetDir.z - snapDir.z) ** 2; + const distFactor = Math.max(0.1, 1 - sqrDist * 10); + const snapIntensity = 0.2 * ts * distFactor; + snapRot = quatFromVectors(targetDir, snapDir, snapIntensity); + + const itemLen = Math.max(1, items.length); + const itemIdx = nearestIdx % itemLen; + if (activeIdx.value !== itemIdx) { + activeIdx.value = itemIdx; + scheduleOnRN(updateActiveItem, itemIdx); + } + } + + const combined = quatMultiply(snapRot, dampenedPR); + const newOrientation = quatNormalize(quatMultiply(combined, orientation)); + qx.value = newOrientation.x; + qy.value = newOrientation.y; + qz.value = newOrientation.z; + qw.value = newOrientation.w; + + const rad = Math.acos(Math.min(1, Math.max(-1, combined.w))) * 2; + const rv = rad / (2 * Math.PI); + rotVelocity.value += (rv - rotVelocity.value) * 0.5 * ts; + + const targetZ = isDown.value + ? CAMERA_Z + rotVelocity.value * 80 + 2.5 + : CAMERA_Z; + const damping = isDown.value ? 7 / ts : 5 / ts; + camZ.value += (targetZ - camZ.value) / damping; + + const moving = isDown.value || Math.abs(rotVelocity.value) > 0.005; + if (moving !== lastMoving.value) { + lastMoving.value = moving; + scheduleOnRN(updateIsMoving, moving); + } + + if (!moving && !isDown.value && Math.abs(rotVelocity.value) < 0.001) { + frameSkip.value++; + if (frameSkip.value > 5) { + return; + } + } else { + frameSkip.value = 0; + } + + const discs: IDisc[] = []; + const currentCamZ = camZ.value; + const itemLen = Math.max(1, items.length); + + for (let i = 0; i < verticesRef.length; i++) { + const v = verticesRef[i]; + const worldPos = quatRotateVec3(newOrientation, v); + + const perspective = currentCamZ / (currentCamZ - worldPos.z); + const sx = centerX + worldPos.x * perspective * PROJECTION_SCALE; + const sy = centerY - worldPos.y * perspective * PROJECTION_SCALE; + + const zFactor = (Math.abs(worldPos.z) / SPHERE_RADIUS) * 0.6 + 0.4; + const baseRadius = + zFactor * DISC_BASE_SCALE * perspective * PROJECTION_SCALE; + + const alpha = Math.max(0.1, (worldPos.z / SPHERE_RADIUS) * 0.45 + 0.55); + + discs.push({ + screenX: sx, + screenY: sy, + radius: baseRadius, + alpha: alpha, + z: worldPos.z, + itemIndex: i % itemLen, + }); + } + discs.sort((a, b) => a.z - b.z); + scheduleOnRN(updateDiscData, discs); + }); + + const panGesture = Gesture.Pan() + .onBegin((e) => { + "worklet"; + prevX.value = e.x; + prevY.value = e.y; + isDown.value = true; + }) + .onUpdate((e) => { + "worklet"; + const intensity = 0.3; + const amplification = 5; + + const midX = prevX.value + (e.x - prevX.value) * intensity; + const midY = prevY.value + (e.y - prevY.value) * intensity; + + const dx = midX - prevX.value; + const dy = midY - prevY.value; + + if (dx * dx + dy * dy > 0.1) { + const p = projectToSphere(midX, midY); + const q = projectToSphere(prevX.value, prevY.value); + const newRot = quatFromVectors(p, q, amplification); + + prx.value = newRot.x; + pry.value = newRot.y; + prz.value = newRot.z; + prw.value = newRot.w; + + prevX.value = midX; + prevY.value = midY; + } + }) + .onEnd(() => { + "worklet"; + isDown.value = false; + }) + .onFinalize(() => { + "worklet"; + isDown.value = false; + }); + + const fadeAnim = useMemo( + () => new RNAnimated.Value(1), + [], + ); + const scaleAnim = useMemo( + () => new RNAnimated.Value(1), + [], + ); + + useEffect(() => { + RNAnimated.parallel([ + RNAnimated.timing(fadeAnim, { + toValue: isMoving ? 0 : 1, + duration: isMoving ? 100 : 500, + useNativeDriver: true, + }), + RNAnimated.timing(scaleAnim, { + toValue: isMoving ? 0 : 1, + duration: isMoving ? 100 : 500, + useNativeDriver: true, + }), + ]).start(); + }, [isMoving, fadeAnim, scaleAnim]); + + return ( + + + + + {discData.map((disc, idx) => ( + + ))} + + + + + ); + }, +); + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + canvas: { + flex: 1, + }, +}); + +export default memo< + React.FC & React.FunctionComponent +>(InfiniteMenu); + +export type { IMenuItem, IInfiniteMenu, IDisc, IDiscComponent }; \ No newline at end of file diff --git a/src/components/ui/infinite-moving-cards.jsx b/src/components/ui/infinite-moving-cards.jsx deleted file mode 100644 index 6136ddc..0000000 --- a/src/components/ui/infinite-moving-cards.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from "react"; - -export function InfiniteMovingCards({ items = [], direction = "right", speed = "slow" }) { - const duration = speed === "slow" ? "40s" : speed === "fast" ? "12s" : "24s"; - const dir = direction === "right" ? "normal" : "reverse"; - - // Duplicate items so the marquee appears continuous - const doubled = [...items, ...items]; - - return ( -
- - -
- {doubled.map((t, i) => ( -
-
{t.quote}
-
{t.name} — {t.title}
-
- ))} -
-
- ); -} - -export default InfiniteMovingCards; diff --git a/src/components/ui/testimonials-card.tsx b/src/components/ui/testimonials-card.tsx new file mode 100644 index 0000000..68692bb --- /dev/null +++ b/src/components/ui/testimonials-card.tsx @@ -0,0 +1,153 @@ +import React, { useState, useMemo } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { cn } from "../../lib/utils"; +import { ArrowLeft, ArrowRight } from "lucide-react"; + + +/** + * @typedef {Object} TestimonialItem + * @property {string|number} id + * @property {string} title + * @property {string} description + * @property {string} image + */ + +/** + * @typedef {Object} TestimonialsCardProps + * @property {TestimonialItem[]} items + * @property {string} [className] + * @property {number} [width=400] + * @property {boolean} [showNavigation=true] + * @property {boolean} [showCounter=true] + * @property {boolean} [autoPlay=false] + * @property {number} [autoPlayInterval=3000] + */ + +export function TestimonialsCard({ +items, +className, +width = 400, +showNavigation = true, +showCounter = true, +autoPlay = false, +autoPlayInterval = 3000, +}) { +const [activeIndex, setActiveIndex] = useState(0); +const [direction, setDirection] = useState(1); +const activeItem = items[activeIndex]; + +React.useEffect(() => { + if (!autoPlay || items.length <= 1) return; + const interval = setInterval(() => { + setDirection(1); + setActiveIndex((prev) => (prev + 1) % items.length); + }, autoPlayInterval); + return () => clearInterval(interval); +}, [autoPlay, autoPlayInterval, items.length]); + +const handleNext = () => { + if (activeIndex < items.length - 1) { + setDirection(1); + setActiveIndex(activeIndex + 1); + } +}; + +const handlePrev = () => { + if (activeIndex > 0) { + setDirection(-1); + setActiveIndex(activeIndex - 1); + } +}; + +const rotations = useMemo(() => [4, -2, -9, 7], []); + +if (!items || items.length === 0) return null; + +return ( +
+
+ {showCounter && ( +
+ {activeIndex + 1} / {items.length} +
+ )} + +
+ + {items.map((item, index) => { + const isActive = index === activeIndex; + const offset = index - activeIndex; + return ( + + {item.title} + + ); + })} + +
+ +
+ + +

{activeItem.title}

+

{activeItem.description}

+
+
+
+ + {showNavigation && items.length > 1 && ( +
+ + +
+ )} +
+
+); +} + +export default TestimonialsCard; \ No newline at end of file diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx index 8e43038..bc2e181 100644 --- a/src/pages/HomePage.jsx +++ b/src/pages/HomePage.jsx @@ -5,7 +5,7 @@ import Landing from "../components/Landing"; import Offering from "../components/Offering"; import About from "../components/about"; import EventsGalary from "../components/eventsGalary"; -import { InfiniteMovingCardsDemo } from "../components/testimonial"; +import AnimatedTestimonialsDemo from "../components/testimonial"; import useLenis from "../hooks/useLenis"; @@ -18,7 +18,7 @@ export default function HomePage() { - + {/* */} From 2bbc5fef638dc05944faa455da30825b5f1da156 Mon Sep 17 00:00:00 2001 From: "ayushmanitiwari931@gmail.com" Date: Thu, 12 Feb 2026 16:01:17 +0530 Subject: [PATCH 4/9] added some UI improvements --- src/components/BackgroundOverlayCard.jsx | 2 +- src/components/CompilationBar.jsx | 89 ++++++++++++++++++++++++ src/pages/EventPage.jsx | 12 ++-- src/pages/HomePage.jsx | 2 + 4 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 src/components/CompilationBar.jsx diff --git a/src/components/BackgroundOverlayCard.jsx b/src/components/BackgroundOverlayCard.jsx index a7f7106..9dfa8bc 100644 --- a/src/components/BackgroundOverlayCard.jsx +++ b/src/components/BackgroundOverlayCard.jsx @@ -12,7 +12,7 @@ export const BackgroundOverlayCard = ({
diff --git a/src/components/CompilationBar.jsx b/src/components/CompilationBar.jsx new file mode 100644 index 0000000..839e485 --- /dev/null +++ b/src/components/CompilationBar.jsx @@ -0,0 +1,89 @@ +import React, { useState, useEffect } from 'react'; + + +const messages = [ + { threshold: 98, text: "BUILD SUCCESSFUL. Ready to deploy." }, + { threshold: 88, text: "Rendering visual assets..." }, + { threshold: 75, text: "Parsing user_feedback.log..." }, + { threshold: 55, text: "Fetching legacy_pointers (Ex-Heads)..." }, + { threshold: 35, text: "Compiling active_initiatives..." }, + { threshold: 15, text: "Importing libraries..." }, + { threshold: 0, text: "Initializing CodeChef_ABESEC.exe..." }, +]; + +const CompilationBar = () => { + const [scrollPercent, setScrollPercent] = useState(0); + const [statusMessage, setStatusMessage] = useState(messages[messages.length - 1].text); + + useEffect(() => { + let ticking = false; + + const handleScroll = () => { + + if (!ticking) { + window.requestAnimationFrame(() => { + + const totalHeight = document.documentElement.scrollHeight - window.innerHeight; + const currentScroll = window.scrollY; + + const percent = Math.min((currentScroll / totalHeight) * 100, 100); + + setScrollPercent(percent); + + const currentMsg = messages.find(msg => percent >= msg.threshold); + if (currentMsg) { + setStatusMessage(currentMsg.text); + } + + ticking = false; + }); + + ticking = true; + } + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + return ( +
+ + {/* 1. Terminal Prompt */} +
+ + ~/codechef-abesec/site + | + {/* Added a min-width to prevent jitter when text length changes */} + + {statusMessage} + +
+ + {/* 2. Percentage */} +
+ [ {Math.round(scrollPercent)}% ] +
+ + {/* 3. Progress Bar Overlay */} +
+ +
+
+ ); +}; + +export default CompilationBar; \ No newline at end of file diff --git a/src/pages/EventPage.jsx b/src/pages/EventPage.jsx index 625d95e..cb0f9ed 100644 --- a/src/pages/EventPage.jsx +++ b/src/pages/EventPage.jsx @@ -18,37 +18,37 @@ const events = [ title: "Clash Of Coders 4.0", href: "/events/codeclash-2024", imageUrl: "https://codechefabesec.netlify.app/img/coc/1.webp", - hoverImageUrl: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWJjb3NqY21sYjY2aG1zZ3c3aGZ6Z3Z1c3NldWJrbjZ2eW54c3JqZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + hoverImageUrl: "/COC1.JPG", }, { title: "Rust - Ed", href: "/events/rust-ed", imageUrl: "https://codechefabesec.netlify.app/img/works/4/Rusted.webp", - hoverImageUrl: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWJjb3NqY21sYjY2aG1zZ3c3aGZ6Z3Z1c3NldWJrbjZ2eW54c3JqZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + hoverImageUrl: "/rust1.webp", }, { title: "Once Upon A Crime", href: "/events/once-upon-a-crime", imageUrl: "https://codechefabesec.netlify.app/img/converted/crime.webp", - hoverImageUrl: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWJjb3NqY21sYjY2aG1zZ3c3aGZ6Z3Z1c3NldWJrbjZ2eW54c3JqZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + hoverImageUrl: "/OUAC1.webp", }, { title: "T-Error 3.0", href: "/events/t-error-3", imageUrl: "https://codechefabesec.netlify.app/img/t_error/1.webp", - hoverImageUrl: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWJjb3NqY21sYjY2aG1zZ3c3aGZ6Z3Z1c3NldWJrbjZ2eW54c3JqZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + hoverImageUrl: "/TERROR1.webp", }, { title: "Beyond Code", href: "/events/byond-code", imageUrl: "https://codechefabesec.netlify.app/img/beyond_code/1.webp", - hoverImageUrl: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWJjb3NqY21sYjY2aG1zZ3c3aGZ6Z3Z1c3NldWJrbjZ2eW54c3JqZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + hoverImageUrl: "/bc1.webp", }, { title: "Head Node", href: "/events/head-node", imageUrl: "https://codechefabesec.netlify.app/img/converted/headnode.webp", - hoverImageUrl: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWJjb3NqY21sYjY2aG1zZ3c3aGZ6Z3Z1c3NldWJrbjZ2eW54c3JqZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + hoverImageUrl: "/hn4.webp", }, ]; diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx index bc2e181..32c6846 100644 --- a/src/pages/HomePage.jsx +++ b/src/pages/HomePage.jsx @@ -7,6 +7,7 @@ import About from "../components/about"; import EventsGalary from "../components/eventsGalary"; import AnimatedTestimonialsDemo from "../components/testimonial"; import useLenis from "../hooks/useLenis"; +import CompilationBar from "../components/CompilationBar"; @@ -23,6 +24,7 @@ export default function HomePage() { {/* */} {/*
*/} +
); } \ No newline at end of file From 70e6ae33a3e4774b2f7b0c226c6d6fb2beeb52df Mon Sep 17 00:00:00 2001 From: PriyatoshKumarShahi Date: Fri, 13 Feb 2026 18:45:46 +0530 Subject: [PATCH 5/9] Updated Login and Contact page --- src/components/ExPresident.jsx | 6 ++- src/pages/ContactPage.jsx | 70 +++++++++++++--------------------- src/pages/Login.jsx | 69 ++++++--------------------------- 3 files changed, 41 insertions(+), 104 deletions(-) diff --git a/src/components/ExPresident.jsx b/src/components/ExPresident.jsx index bad6868..1e306d7 100644 --- a/src/components/ExPresident.jsx +++ b/src/components/ExPresident.jsx @@ -200,14 +200,16 @@ const ExPresident = () => { transformStyle: "preserve-3d", }} > -
+
{president.name} -
+
+ +

{president.name}

diff --git a/src/pages/ContactPage.jsx b/src/pages/ContactPage.jsx index 931307d..32c7bf9 100644 --- a/src/pages/ContactPage.jsx +++ b/src/pages/ContactPage.jsx @@ -175,44 +175,26 @@ export default function ContactPage() { } }; - const scrollToContact = () => { - document.getElementById("contact-section").scrollIntoView({ - behavior: "smooth", - }); - }; - return ( -
- +
+ -
-

+ {/* Single Scrollable Contact Section */} +
+ + {/* Heading */} +

Get In Touch

+ + {/* Subheading */} +

+ Have a question or want to work together? We'd love to hear from you. +

- -
- -
-

- Get In Touch -

- -

- Let's{" "} - Talk -

- -
+ {/* Contact Form */} +
@@ -242,8 +224,8 @@ export default function ContactPage() { value={formData.subject} onChange={handleChange} placeholder="Subject" - className="w-full px-4 sm:px-6 py-3 sm:py-4 bg-white border-2 border-gray-300 rounded-xl - focus:outline-none focus:border-black transition-all duration-300 text-black font-medium text-sm sm:text-base" + className="w-full px-4 sm:px-6 py-3 sm:py-4 bg-[#1a1a1a] border-2 border-gray-700 rounded-xl + focus:outline-none focus:border-gray-500 transition-all duration-300 text-white placeholder-gray-500 font-medium text-sm sm:text-base" />
{formData.message.length} / 5000 characters (minimum 10) @@ -266,12 +248,12 @@ export default function ContactPage() { className="w-full sm:w-52 group relative px-6 sm:px-8 py-3 sm:py-4 rounded-xl font-bold text-base sm:text-lg overflow-hidden transition-all duration-500 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed" > - - + + - + {loading ? ( -
+
) : ( "Send Message" )} diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 4842ce2..b54a0a4 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -6,7 +6,6 @@ const API_URL = 'http://localhost:5000/api'; export default function Login() { const navigate = useNavigate(); - const [loginType, setLoginType] = useState('user'); const [formData, setFormData] = useState({ email: '', password: '' @@ -30,19 +29,6 @@ export default function Login() { try { const response = await axios.post(`${API_URL}/auth/login`, formData); - // Check if role matches selected login type - if (loginType === 'admin' && response.data.user.role !== 'admin') { - setError('You are not authorized to login as admin'); - setLoading(false); - return; - } - - if (loginType === 'user' && response.data.user.role === 'admin') { - setError('Admin accounts must use admin login'); - setLoading(false); - return; - } - localStorage.setItem('token', response.data.token); localStorage.setItem('user', JSON.stringify(response.data.user)); @@ -77,37 +63,6 @@ export default function Login() {

Login to CodeChef ABESEC

- {/* Login Type Selection */} -
- -
- - -
-
- {error && (

{error}

@@ -126,10 +81,10 @@ export default function Login() { onChange={handleChange} required className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-gray-900" - placeholder={loginType === 'admin' ? 'admin@abes.ac.in' : 'your.email@abes.ac.in'} + placeholder="your.email@abes.ac.in" />

- {loginType === 'admin' ? 'Admin email required' : 'Only @abes.ac.in emails are allowed'} + Use your @abes.ac.in email address

@@ -153,20 +108,18 @@ export default function Login() { disabled={loading} className="w-full bg-blue-600 text-white py-3 rounded-lg font-semibold hover:bg-blue-700 focus:ring-4 focus:ring-blue-200 transition-all disabled:opacity-50 disabled:cursor-not-allowed" > - {loading ? 'Logging in...' : `Login as ${loginType === 'admin' ? 'Admin' : 'User'}`} + {loading ? 'Logging in...' : 'Login'} - {loginType === 'user' && ( -
-

- Don't have an account?{' '} - - Register here - -

-
- )} +
+

+ Don't have an account?{' '} + + Register here + +

+
From d46d518a516de3b49c2494d9f6d93dc4428fa2a0 Mon Sep 17 00:00:00 2001 From: PriyatoshKumarShahi Date: Sat, 14 Feb 2026 08:25:56 +0530 Subject: [PATCH 6/9] Updated achievement landing section --- src/components/Achievements.jsx | 118 +++++++++++++++++++++++++------- src/components/Landing.jsx | 2 +- 2 files changed, 93 insertions(+), 27 deletions(-) diff --git a/src/components/Achievements.jsx b/src/components/Achievements.jsx index 7005e0c..6cb5907 100644 --- a/src/components/Achievements.jsx +++ b/src/components/Achievements.jsx @@ -153,26 +153,92 @@ const AchievementsSection = () => { ]; return ( -
+
- {/* Responsive Hero Section */} -
-
-

- - Celebrating{' '} - Our - - - Remarkable{' '} - Achievements - -

+ {/* Hero Section */} +
+ {/* Subtle background pattern */} +
+
+
+ +
+ {/* Heading */} +
+

+ + Remarkable{' '} + Achievements + + + Celebrating{' '} + Excellence + +

+
+ + {/* Achievement Cards */} +
+ {/* Card 1 - Trophy */} +
+
+
+ +
+
+

+ Excellence in Innovation +

+

+ Recognizing outstanding contributions to technology and creative problem-solving +

+
+
+
+ + {/* Card 2 - Medal (Hidden on mobile) */} +
+
+
+ + + +
+
+

+ Team Excellence +

+

+ Celebrating collaborative success and collective achievements +

+
+
+
+
-
+
{achievements.map((achievement, index) => (
{ }; return ( -
-
-
-
+
+
+
+
-
+
{achievement.name}
-
- {achievement.badge} +
+ {achievement.badge}
-

+

{achievement.name}

-

+

{achievement.description}

@@ -263,7 +329,7 @@ const AchievementCard = ({ achievement }) => { href={achievement.github} target="_blank" rel="noopener noreferrer" - className="group flex items-center justify-center w-10 h-10 sm:w-12 sm:h-12 bg-gray-900 text-white rounded-lg hover:bg-gray-800 transition-all duration-300 hover:scale-110" + className="group flex items-center justify-center w-10 h-10 sm:w-12 sm:h-12 bg-gray-700 text-white rounded-lg hover:bg-gray-600 transition-all duration-300 hover:scale-110" title="GitHub" > diff --git a/src/components/Landing.jsx b/src/components/Landing.jsx index c2eb17c..2420c5e 100644 --- a/src/components/Landing.jsx +++ b/src/components/Landing.jsx @@ -121,7 +121,7 @@ export default function Landing() { } return ( -
+
Date: Sat, 14 Feb 2026 13:44:47 +0530 Subject: [PATCH 7/9] removed testinomial section --- src/components/Landing.jsx | 95 ++---- src/components/LightRays.jsx | 399 ++++++++++++++++++++++++ src/components/RippleGridBackground.jsx | 148 +++++++++ src/components/ShuffleText2.jsx | 3 +- src/components/eventsGalary.jsx | 21 +- src/pages/HomePage.jsx | 2 +- src/styles/Landing.css | 10 +- 7 files changed, 600 insertions(+), 78 deletions(-) create mode 100644 src/components/LightRays.jsx create mode 100644 src/components/RippleGridBackground.jsx diff --git a/src/components/Landing.jsx b/src/components/Landing.jsx index 2663a22..849cccd 100644 --- a/src/components/Landing.jsx +++ b/src/components/Landing.jsx @@ -13,8 +13,8 @@ import { IconDashboard, } from "@tabler/icons-react"; import "../styles/Landing.css"; -import Squares from "./Squares"; import { FloatingNav } from "./FloatingNavbar"; +import LightRays from "./LightRays"; const API_URL = 'http://localhost:5000/api'; @@ -121,41 +121,50 @@ export default function Landing() { return (
+
+ +
- - - -
-
- -
- -
- +
+ + + + + + +
@@ -165,7 +174,7 @@ export default function Landing() { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.5, duration: 0.6 }} - className="flex flex-wrap justify-center lg:justify-start gap-4 -mt-10" + className="flex flex-wrap justify-center gap-4 -mt-10" >
); diff --git a/src/components/LightRays.jsx b/src/components/LightRays.jsx new file mode 100644 index 0000000..59386b5 --- /dev/null +++ b/src/components/LightRays.jsx @@ -0,0 +1,399 @@ +import { useRef, useEffect, useState } from 'react'; +import { Renderer, Program, Triangle, Mesh } from 'ogl'; + +const DEFAULT_COLOR = '#ffffff'; + +const hexToRgb = hex => { + const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return m ? [parseInt(m[1], 16) / 255, parseInt(m[2], 16) / 255, parseInt(m[3], 16) / 255] : [1, 1, 1]; +}; + +const getAnchorAndDir = (origin, w, h) => { + const outside = 0.2; + switch (origin) { + case 'top-left': + return { anchor: [0, -outside * h], dir: [0, 1] }; + case 'top-right': + return { anchor: [w, -outside * h], dir: [0, 1] }; + case 'left': + return { anchor: [-outside * w, 0.5 * h], dir: [1, 0] }; + case 'right': + return { anchor: [(1 + outside) * w, 0.5 * h], dir: [-1, 0] }; + case 'bottom-left': + return { anchor: [0, (1 + outside) * h], dir: [0, -1] }; + case 'bottom-center': + return { anchor: [0.5 * w, (1 + outside) * h], dir: [0, -1] }; + case 'bottom-right': + return { anchor: [w, (1 + outside) * h], dir: [0, -1] }; + default: // "top-center" + return { anchor: [0.5 * w, -outside * h], dir: [0, 1] }; + } +}; + +const LightRays = ({ + raysOrigin = 'top-center', + raysColor = DEFAULT_COLOR, + raysSpeed = 1, + lightSpread = 1, + rayLength = 2, + pulsating = false, + fadeDistance = 1.0, + saturation = 1.0, + followMouse = true, + mouseInfluence = 0.1, + noiseAmount = 0.0, + distortion = 0.0, + className = '' +}) => { + const containerRef = useRef(null); + const uniformsRef = useRef(null); + const rendererRef = useRef(null); + const mouseRef = useRef({ x: 0.5, y: 0.5 }); + const smoothMouseRef = useRef({ x: 0.5, y: 0.5 }); + const animationIdRef = useRef(null); + const meshRef = useRef(null); + const cleanupFunctionRef = useRef(null); + const [isVisible, setIsVisible] = useState(false); + const observerRef = useRef(null); + + useEffect(() => { + if (!containerRef.current) return; + + observerRef.current = new IntersectionObserver( + entries => { + const entry = entries[0]; + setIsVisible(entry.isIntersecting); + }, + { threshold: 0.1 } + ); + + observerRef.current.observe(containerRef.current); + + return () => { + if (observerRef.current) { + observerRef.current.disconnect(); + observerRef.current = null; + } + }; + }, []); + + useEffect(() => { + if (!isVisible || !containerRef.current) return; + + if (cleanupFunctionRef.current) { + cleanupFunctionRef.current(); + cleanupFunctionRef.current = null; + } + + const initializeWebGL = async () => { + if (!containerRef.current) return; + + await new Promise(resolve => setTimeout(resolve, 10)); + + if (!containerRef.current) return; + + const renderer = new Renderer({ + dpr: Math.min(window.devicePixelRatio, 2), + alpha: true + }); + rendererRef.current = renderer; + + const gl = renderer.gl; + gl.canvas.style.width = '100%'; + gl.canvas.style.height = '100%'; + + while (containerRef.current.firstChild) { + containerRef.current.removeChild(containerRef.current.firstChild); + } + containerRef.current.appendChild(gl.canvas); + + const vert = ` +attribute vec2 position; +varying vec2 vUv; +void main() { + vUv = position * 0.5 + 0.5; + gl_Position = vec4(position, 0.0, 1.0); +}`; + + const frag = `precision highp float; + +uniform float iTime; +uniform vec2 iResolution; + +uniform vec2 rayPos; +uniform vec2 rayDir; +uniform vec3 raysColor; +uniform float raysSpeed; +uniform float lightSpread; +uniform float rayLength; +uniform float pulsating; +uniform float fadeDistance; +uniform float saturation; +uniform vec2 mousePos; +uniform float mouseInfluence; +uniform float noiseAmount; +uniform float distortion; + +varying vec2 vUv; + +float noise(vec2 st) { + return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123); +} + +float rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord, + float seedA, float seedB, float speed) { + vec2 sourceToCoord = coord - raySource; + vec2 dirNorm = normalize(sourceToCoord); + float cosAngle = dot(dirNorm, rayRefDirection); + + float distortedAngle = cosAngle + distortion * sin(iTime * 2.0 + length(sourceToCoord) * 0.01) * 0.2; + + float spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / max(lightSpread, 0.001)); + + float distance = length(sourceToCoord); + float maxDistance = iResolution.x * rayLength; + float lengthFalloff = clamp((maxDistance - distance) / maxDistance, 0.0, 1.0); + + float fadeFalloff = clamp((iResolution.x * fadeDistance - distance) / (iResolution.x * fadeDistance), 0.5, 1.0); + float pulse = pulsating > 0.5 ? (0.8 + 0.2 * sin(iTime * speed * 3.0)) : 1.0; + + float baseStrength = clamp( + (0.45 + 0.15 * sin(distortedAngle * seedA + iTime * speed)) + + (0.3 + 0.2 * cos(-distortedAngle * seedB + iTime * speed)), + 0.0, 1.0 + ); + + return baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse; +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 coord = vec2(fragCoord.x, iResolution.y - fragCoord.y); + + vec2 finalRayDir = rayDir; + if (mouseInfluence > 0.0) { + vec2 mouseScreenPos = mousePos * iResolution.xy; + vec2 mouseDirection = normalize(mouseScreenPos - rayPos); + finalRayDir = normalize(mix(rayDir, mouseDirection, mouseInfluence)); + } + + vec4 rays1 = vec4(1.0) * + rayStrength(rayPos, finalRayDir, coord, 36.2214, 21.11349, + 1.5 * raysSpeed); + vec4 rays2 = vec4(1.0) * + rayStrength(rayPos, finalRayDir, coord, 22.3991, 18.0234, + 1.1 * raysSpeed); + + fragColor = rays1 * 0.5 + rays2 * 0.4; + + if (noiseAmount > 0.0) { + float n = noise(coord * 0.01 + iTime * 0.1); + fragColor.rgb *= (1.0 - noiseAmount + noiseAmount * n); + } + + float brightness = 1.0 - (coord.y / iResolution.y); + fragColor.x *= 0.1 + brightness * 0.8; + fragColor.y *= 0.3 + brightness * 0.6; + fragColor.z *= 0.5 + brightness * 0.5; + + if (saturation != 1.0) { + float gray = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114)); + fragColor.rgb = mix(vec3(gray), fragColor.rgb, saturation); + } + + fragColor.rgb *= raysColor; +} + +void main() { + vec4 color; + mainImage(color, gl_FragCoord.xy); + gl_FragColor = color; +}`; + + const uniforms = { + iTime: { value: 0 }, + iResolution: { value: [1, 1] }, + + rayPos: { value: [0, 0] }, + rayDir: { value: [0, 1] }, + + raysColor: { value: hexToRgb(raysColor) }, + raysSpeed: { value: raysSpeed }, + lightSpread: { value: lightSpread }, + rayLength: { value: rayLength }, + pulsating: { value: pulsating ? 1.0 : 0.0 }, + fadeDistance: { value: fadeDistance }, + saturation: { value: saturation }, + mousePos: { value: [0.5, 0.5] }, + mouseInfluence: { value: mouseInfluence }, + noiseAmount: { value: noiseAmount }, + distortion: { value: distortion } + }; + uniformsRef.current = uniforms; + + const geometry = new Triangle(gl); + const program = new Program(gl, { vertex: vert, fragment: frag, uniforms }); + const mesh = new Mesh(gl, { geometry, program }); + meshRef.current = mesh; + + const updatePlacement = () => { + if (!containerRef.current || !renderer) return; + + renderer.dpr = Math.min(window.devicePixelRatio, 2); + + const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current; + renderer.setSize(wCSS, hCSS); + + const dpr = renderer.dpr; + const w = wCSS * dpr; + const h = hCSS * dpr; + + uniforms.iResolution.value = [w, h]; + + const { anchor, dir } = getAnchorAndDir(raysOrigin, w, h); + uniforms.rayPos.value = anchor; + uniforms.rayDir.value = dir; + }; + + const loop = t => { + if (!rendererRef.current || !uniformsRef.current || !meshRef.current) { + return; + } + + uniforms.iTime.value = t * 0.001; + + if (followMouse && mouseInfluence > 0.0) { + const smoothing = 0.92; + + smoothMouseRef.current.x = smoothMouseRef.current.x * smoothing + mouseRef.current.x * (1 - smoothing); + smoothMouseRef.current.y = smoothMouseRef.current.y * smoothing + mouseRef.current.y * (1 - smoothing); + + uniforms.mousePos.value = [smoothMouseRef.current.x, smoothMouseRef.current.y]; + } + + try { + renderer.render({ scene: mesh }); + animationIdRef.current = requestAnimationFrame(loop); + } catch (error) { + console.warn('WebGL rendering error:', error); + return; + } + }; + + window.addEventListener('resize', updatePlacement); + updatePlacement(); + animationIdRef.current = requestAnimationFrame(loop); + + cleanupFunctionRef.current = () => { + if (animationIdRef.current) { + cancelAnimationFrame(animationIdRef.current); + animationIdRef.current = null; + } + + window.removeEventListener('resize', updatePlacement); + + if (renderer) { + try { + const canvas = renderer.gl.canvas; + const loseContextExt = renderer.gl.getExtension('WEBGL_lose_context'); + if (loseContextExt) { + loseContextExt.loseContext(); + } + + if (canvas && canvas.parentNode) { + canvas.parentNode.removeChild(canvas); + } + } catch (error) { + console.warn('Error during WebGL cleanup:', error); + } + } + + rendererRef.current = null; + uniformsRef.current = null; + meshRef.current = null; + }; + }; + + initializeWebGL(); + + return () => { + if (cleanupFunctionRef.current) { + cleanupFunctionRef.current(); + cleanupFunctionRef.current = null; + } + }; + }, [ + isVisible, + raysOrigin, + raysColor, + raysSpeed, + lightSpread, + rayLength, + pulsating, + fadeDistance, + saturation, + followMouse, + mouseInfluence, + noiseAmount, + distortion + ]); + + useEffect(() => { + if (!uniformsRef.current || !containerRef.current || !rendererRef.current) return; + + const u = uniformsRef.current; + const renderer = rendererRef.current; + + u.raysColor.value = hexToRgb(raysColor); + u.raysSpeed.value = raysSpeed; + u.lightSpread.value = lightSpread; + u.rayLength.value = rayLength; + u.pulsating.value = pulsating ? 1.0 : 0.0; + u.fadeDistance.value = fadeDistance; + u.saturation.value = saturation; + u.mouseInfluence.value = mouseInfluence; + u.noiseAmount.value = noiseAmount; + u.distortion.value = distortion; + + const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current; + const dpr = renderer.dpr; + const { anchor, dir } = getAnchorAndDir(raysOrigin, wCSS * dpr, hCSS * dpr); + u.rayPos.value = anchor; + u.rayDir.value = dir; + }, [ + raysColor, + raysSpeed, + lightSpread, + raysOrigin, + rayLength, + pulsating, + fadeDistance, + saturation, + mouseInfluence, + noiseAmount, + distortion + ]); + + useEffect(() => { + const handleMouseMove = e => { + if (!containerRef.current || !rendererRef.current) return; + const rect = containerRef.current.getBoundingClientRect(); + const x = (e.clientX - rect.left) / rect.width; + const y = (e.clientY - rect.top) / rect.height; + mouseRef.current = { x, y }; + }; + + if (followMouse) { + window.addEventListener('mousemove', handleMouseMove); + return () => window.removeEventListener('mousemove', handleMouseMove); + } + }, [followMouse]); + + return ( +
+ ); +}; + +export default LightRays; diff --git a/src/components/RippleGridBackground.jsx b/src/components/RippleGridBackground.jsx new file mode 100644 index 0000000..2140129 --- /dev/null +++ b/src/components/RippleGridBackground.jsx @@ -0,0 +1,148 @@ +import React, { useEffect, useRef } from 'react'; + +// Helper for smooth animations +const lerp = (start, end, amt) => (1 - amt) * start + amt * end; + +const RippleGridBackground = () => { + const canvasRef = useRef(null); + + const config = { + // VISUAL SETTINGS + gridSize: 40, // Slightly larger squares for a more modern look + gap: 0, // No gap = seamless grid (looks more like a surface) + + // COLOR GRADIENT (HSL) + // Base: The resting state (Almost invisible) + baseHue: 120, // CodeChef Greenish + baseSat: 15, // Very desaturated + baseLight: 2, // 2% lightness (Very, very dark) + + // Active: The ripple state (Subtle glow) + activeHue: 150, // Shifts to Teal/Cyan when hovered + activeSat: 30, // Slightly more color + activeLight: 10, // Only goes up to 10% brightness (Subtle!) + + // ANIMATION PHYSICS + mouseRadius: 400, // Larger radius for a smoother gradient spread + lerpFactor: 0.05, // 0.05 is very slow and smooth (liquid feel) + }; + + useEffect(() => { + const canvas = canvasRef.current; + const ctx = canvas.getContext('2d'); + let animationFrameId; + let width, height, rows, cols; + + let grid = []; + let mouse = { x: -1000, y: -1000 }; + + const resize = () => { + width = window.innerWidth; + height = window.innerHeight; + canvas.width = width; + canvas.height = height; + + const cellSize = config.gridSize + config.gap; + cols = Math.ceil(width / cellSize); + rows = Math.ceil(height / cellSize); + + grid = []; + for (let i = 0; i < cols; i++) { + grid[i] = []; + for (let j = 0; j < rows; j++) { + grid[i][j] = { + intensity: 0, // 0 = Base State, 1 = Active State + }; + } + } + }; + + const handleMouseMove = (e) => { + mouse.x = e.clientX; + mouse.y = e.clientY; + }; + + const handleMouseLeave = () => { + mouse.x = -1000; + mouse.y = -1000; + }; + + window.addEventListener('resize', resize); + window.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseleave', handleMouseLeave); + + resize(); + + const render = () => { + // Clear background with the base color + ctx.fillStyle = `hsl(${config.baseHue}, ${config.baseSat}%, ${config.baseLight}%)`; + ctx.fillRect(0, 0, width, height); + + const cellSize = config.gridSize + config.gap; + + for (let i = 0; i < cols; i++) { + for (let j = 0; j < rows; j++) { + const cell = grid[i][j]; + + const cellX = i * cellSize; + const cellY = j * cellSize; + const centerX = cellX + config.gridSize / 2; + const centerY = cellY + config.gridSize / 2; + + const dist = Math.hypot(mouse.x - centerX, mouse.y - centerY); + + // Calculate Target Intensity + let target = 0; + if (dist < config.mouseRadius) { + const normDist = dist / config.mouseRadius; + // Cubic easing (1 - d)^3 for a much softer, gradient-like falloff + target = Math.pow(1 - normDist, 3); + } + + // Smooth interpolation + cell.intensity = lerp(cell.intensity, target, config.lerpFactor); + + // Optimization: Only draw cells that aren't purely base state + if (cell.intensity > 0.001) { + // Interpolate Colors (Create the gradient transition) + const h = lerp(config.baseHue, config.activeHue, cell.intensity); + const s = lerp(config.baseSat, config.activeSat, cell.intensity); + const l = lerp(config.baseLight, config.activeLight, cell.intensity); + + ctx.fillStyle = `hsl(${h}, ${s}%, ${l}%)`; + + // No scale effect - simpler, cleaner, less distracting + ctx.fillRect(cellX, cellY, config.gridSize, config.gridSize); + } + } + } + animationFrameId = requestAnimationFrame(render); + }; + + render(); + + return () => { + window.removeEventListener('resize', resize); + window.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseleave', handleMouseLeave); + cancelAnimationFrame(animationFrameId); + }; + }, []); + + return ( + + ); +}; + +export default RippleGridBackground; \ No newline at end of file diff --git a/src/components/ShuffleText2.jsx b/src/components/ShuffleText2.jsx index 531c099..7ff9291 100644 --- a/src/components/ShuffleText2.jsx +++ b/src/components/ShuffleText2.jsx @@ -91,7 +91,8 @@ const ShuffleText2 = ({ text, className }) => { opacity: 1, y: 0, transition: { - duration: 0.3 + duration: 0.5, + ease: "easeOut" } }} style={{ display: 'inline-block', whiteSpace: 'pre' }} diff --git a/src/components/eventsGalary.jsx b/src/components/eventsGalary.jsx index 16c1d9f..546b775 100644 --- a/src/components/eventsGalary.jsx +++ b/src/components/eventsGalary.jsx @@ -2,14 +2,19 @@ import CircularGallery from './CircularGallery'; export default function EventsGalary() { return ( -
- +
+

+ Memories that we cooked together +

+
+ +
); } \ No newline at end of file diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx index 8e43038..b0f99d1 100644 --- a/src/pages/HomePage.jsx +++ b/src/pages/HomePage.jsx @@ -18,7 +18,7 @@ export default function HomePage() { - + {/* */} diff --git a/src/styles/Landing.css b/src/styles/Landing.css index 4d533d2..b9a3b2b 100644 --- a/src/styles/Landing.css +++ b/src/styles/Landing.css @@ -5,7 +5,7 @@ } .landing-heading { - font-size: 2rem; + font-size: 3rem; line-height: 1.1; text-align: center; word-break: keep-all; @@ -55,7 +55,7 @@ @media (min-width: 768px) { .landing-heading { - font-size: 3.2rem; + font-size: 4.5rem; text-align: left; } .cube { @@ -66,7 +66,7 @@ @media (min-width: 1024px) { .landing-heading { - font-size: 4.4rem; + font-size: 6rem; } .cube { --size: 320px; @@ -77,7 +77,7 @@ @media (min-width: 1440px) { .landing-heading { - font-size: 5.6rem; + font-size: 7.5rem; } .cube { --size: 380px; @@ -103,6 +103,6 @@ } @media (min-width: 480px) { .landing-heading { - font-size: 2.6rem; + font-size: 3.5rem; } } From 8b8778bc6e831456bf56c4dab33ec23faeab8da6 Mon Sep 17 00:00:00 2001 From: AyushGupta011 Date: Sun, 15 Feb 2026 03:05:42 +0530 Subject: [PATCH 8/9] changed the initiative and team style --- package-lock.json | 7 +- package.json | 1 + public/wheel/DialWheel.svg | 28 ++ public/wheel/hand.svg | 3 + public/wheel/hand_left.svg | 3 + public/wheel/hands_line.svg | 16 ++ public/wheel/rays.svg | 24 ++ src/App.css | 72 +----- src/components/FloatingNavbar.jsx | 10 +- src/components/Offering.jsx | 415 +++++++++++++++++++++++------- src/components/PinContainer.jsx | 186 ------------- src/components/TeamGrid.jsx | 92 ++++--- 12 files changed, 474 insertions(+), 383 deletions(-) create mode 100644 public/wheel/DialWheel.svg create mode 100644 public/wheel/hand.svg create mode 100644 public/wheel/hand_left.svg create mode 100644 public/wheel/hands_line.svg create mode 100644 public/wheel/rays.svg delete mode 100644 src/components/PinContainer.jsx diff --git a/package-lock.json b/package-lock.json index 4a51caf..24004c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.4", + "baseline-browser-mapping": "^2.9.19", "eslint": "^9.36.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.22", @@ -2069,9 +2070,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.23", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.23.tgz", - "integrity": "sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ==", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index ea31b63..a8ea4bb 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.4", + "baseline-browser-mapping": "^2.9.19", "eslint": "^9.36.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.22", diff --git a/public/wheel/DialWheel.svg b/public/wheel/DialWheel.svg new file mode 100644 index 0000000..2b78927 --- /dev/null +++ b/public/wheel/DialWheel.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/wheel/hand.svg b/public/wheel/hand.svg new file mode 100644 index 0000000..d5cb125 --- /dev/null +++ b/public/wheel/hand.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/wheel/hand_left.svg b/public/wheel/hand_left.svg new file mode 100644 index 0000000..418c6ed --- /dev/null +++ b/public/wheel/hand_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/wheel/hands_line.svg b/public/wheel/hands_line.svg new file mode 100644 index 0000000..a83c03a --- /dev/null +++ b/public/wheel/hands_line.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/public/wheel/rays.svg b/public/wheel/rays.svg new file mode 100644 index 0000000..09e91fe --- /dev/null +++ b/public/wheel/rays.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App.css b/src/App.css index 032f33d..509afa5 100644 --- a/src/App.css +++ b/src/App.css @@ -4,76 +4,24 @@ font-weight: 100 900; font-display: swap; } + @font-face { font-family: "Technor"; src: url("./assets/fonts/Technor-Variable.ttf") format("truetype"); font-weight: 100 900; font-display: swap; } -.offering-container{ - - background: #000000; - overflow-x: hidden; -} -.offering-main{ - padding-top: 1rem; -} -.offering-main h2{ - font-size: 4rem; - text-align: center; -font-family: 'Panchang', sans-serif; - -} -.offering-main p{ - font-size: 1.2rem; - text-align: center; - margin-top: 1rem; -} - -.offering-main span{ -text-transform: uppercase; - font-weight: 800; +@keyframes spin-slow { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } } -.cards-container{ - width: 20rem; - padding-bottom: 2rem; - padding: 1rem; -} -.cards{ - background: transparent !important; - padding: 3rem ; -border: 1px solid rgba(255, 255, 255, 0.2); - border-radius: 1rem; - backdrop-filter: blur(10px); -} -.cards h3{ - font-family: 'Technor',sans-serif; - font-size: 1.2rem; -} -.cards span{ - font-size: 0.8rem; -} -@media (max-width:480px) { - .cards-container{ - padding: 5rem; - gap: 3rem; - } - .cards{ - padding: 2rem; - gap: 1rem; - } - .offering-main{ - padding-top: 5rem; - padding: 1.8rem; - } - .offering-main h2{ - font-size: 2.3rem; - } - .offering-main p{ - font-size: 1rem; - margin-top: 1rem; - } +.animate-spin-slow { + animation: spin-slow 20s linear infinite; } \ No newline at end of file diff --git a/src/components/FloatingNavbar.jsx b/src/components/FloatingNavbar.jsx index d2729a6..3a7b77a 100644 --- a/src/components/FloatingNavbar.jsx +++ b/src/components/FloatingNavbar.jsx @@ -13,17 +13,17 @@ export const FloatingNav = ({ navItems, className }) => { const getActiveIndex = () => { const currentPath = location.pathname; - + // Check for exact match first const exactIndex = navItems.findIndex(item => item.href === currentPath); if (exactIndex !== -1) return exactIndex; - + // Check for sub-routes (e.g., /events/sub-event should highlight Events) const parentIndex = navItems.findIndex(item => { if (item.href === '/') return false; // Don't match home for sub-routes return currentPath.startsWith(item.href); }); - + return parentIndex !== -1 ? parentIndex : 0; }; @@ -50,7 +50,7 @@ export const FloatingNav = ({ navItems, className }) => { { initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - className="fixed inset-0 z-40 bg-black/95 md:hidden" + className="fixed inset-0 z-40000 bg-black/95 md:hidden" onClick={() => setIsOpen(false)} > { + const [activeIndex, setActiveIndex] = useState(0); + const totalItems = 4; + const baseRotation = -5; + const itemSpacing = 12; + const rotateVal = -(activeIndex * itemSpacing); + + const contentData = [ + { + title: "Coding\nWorkSpaces", + desc: "Immersive coding environments designed for focus and productivity. We host competitive programming workshops, hackathons, and hands-on bootcamps to sharpen your skills.", + img: "/OUAC2.webp" + }, + { + title: "Connect With\nCool Mentors", + desc: "Bridge the gap between learning and industry. Connect with experienced developers, alumni, and tech leaders who provide guidance, code reviews, and career advice.", + img: "/rust7.webp" + }, + { + title: "Innovation\nHub", + desc: "A collaborative space where wild ideas turn into reality. We provide the resources, peer support, and brainstorming sessions needed to launch your next big project.", + img: "/COC3.JPG" + }, + { + title: "Community\nEvents", + desc: "Join a thriving network of tech enthusiasts. From casual meetups to tech talks and networking nights, we foster a culture of sharing knowledge and growing together.", + img: "/COC1.JPG" + }, + ]; + + // useEffect(() => { + // const interval = setInterval(() => { + // setActiveIndex((prev) => (prev + 1) % totalItems); + // }, 4000); + // return () => clearInterval(interval); + // }, []); + + const [slideAmount, setSlideAmount] = useState(300); + const [hoveredIndex, setHoveredIndex] = useState(null); + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); + + const [isMobile, setIsMobile] = useState(false); + const [wheelConfig, setWheelConfig] = useState({ + radius: 510, + rotateOffset: -250, + scale: 0.9 + }); + + const handleMouseMove = (e) => { + setMousePosition({ x: e.clientX, y: e.clientY }); + }; + + useEffect(() => { + const handleResize = () => { + if (window.innerWidth >= 1024) { + setSlideAmount(632); + setIsMobile(false); + setWheelConfig({ radius: 510, rotateOffset: -250, scale: 0.9 }); + } else if (window.innerWidth >= 768) { + setSlideAmount(375); + setIsMobile(false); + setWheelConfig({ radius: 450, rotateOffset: -200, scale: 0.8 }); + } else { + setSlideAmount(200); + setIsMobile(true); + setWheelConfig({ radius: 250, rotateOffset: -50, scale: 0.55 }); + } + }; + + + handleResize(); + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + const handleItemClick = (index) => { + setActiveIndex(index); + }; + + const handleNext = () => { + setActiveIndex((prev) => (prev + 1) % totalItems); + }; + + const handlePrev = () => { + setActiveIndex((prev) => (prev - 1 + totalItems) % totalItems); + }; + return ( -
-
- - -
-

- Our Initiatives -

- -

- Empowering students to code, collaborate, and create impactful tech solutions. -
- We are building a community where curiosity meets execution. Through hands on programs, mentorship, and innovation driven events, we help students transform ideas into real world solutions. -

-
- - -
- - - -
-

- Coding WorkSpaces & Upskill -

- -
- - We host coding workshops for hands on practice for competitive programming. - -
- - - Coding Workspaces -
-
- - - - -
- -

- Connect With Cool Mentors -

- -
- - Connect with Experienced mentors, seniors and explore new perspectives. - -
- - Mentors -
-
- - - - -
- -

- Innovation Hub -

- -
- - Unlocking the potential of innovations and collaborating ideas and minds. - +
+
+
+ +
- - Innovation Hub -
- -
+
+
+
+ +
+ +
+
+ + +
+ + + {["Upskill", "Mentors", "Innovation", "Community"].map((item, idx) => { + return ( +
handleItemClick(idx)} + onMouseEnter={() => setHoveredIndex(idx)} + onMouseLeave={() => setHoveredIndex(null)} + className={`absolute left-[calc(50%+50px)] top-1/2 text-[12px] sm:text-[24px] md:text-[30px] lg:text-[38px] font-montserrat font-medium transition-all duration-1000 z-[100] group flex items-center gap-1 sm:gap-2 ${activeIndex === idx + ? "text-white" + : "text-white/50 hover:text-white/80" + }`} + style={{ + cursor: 'url("/wheel/hand.svg"), pointer', + transform: `rotate(${-8 + idx * 12}deg) translateX(${wheelConfig.radius}px) translateY(-50%)`, + transformOrigin: "left center", + }} + > + + {item} + +
+ ); + })} +
+
+ + + +
+

+ Initiatives +

+
+ +
+
+ {contentData.map((content, idx) => ( +
+

+ {content.title} +

+

+ {content.desc} +

+
+ ))} +
+
+ + + + + + + + + +
+
+
+ Hands Line +
+
+ Hands Line +
+
+
-
+ +
+
+
+ + + + { + isMobile ? ( +
+ Active Event +
+ ) : ( + hoveredIndex !== null && contentData[hoveredIndex] && ( +
+ Hover Preview +
+ ) + ) + } +
); }; diff --git a/src/components/PinContainer.jsx b/src/components/PinContainer.jsx deleted file mode 100644 index 6c93a86..0000000 --- a/src/components/PinContainer.jsx +++ /dev/null @@ -1,186 +0,0 @@ -"use client"; -import React, { useState, useEffect } from "react"; -import { motion } from "motion/react"; -import { cn } from "../lib/utils"; - -export const PinContainer = ({ - children, - title, - href, - className, - containerClassName, -}) => { - // 1. Add state to track mobile view - const [isMobile, setIsMobile] = useState(false); - const [transform, setTransform] = useState( - "translate(-50%,-50%) rotateX(25deg) scale(0.72)" - ); - - // 2. Check screen size on mount and resize - useEffect(() => { - const checkMobile = () => { - const isTouch = window.innerWidth < 768; // Tailwind 'md' breakpoint - setIsMobile(isTouch); - - // Force the correct initial transform based on screen size - if (isTouch) { - setTransform("translate(-50%,-50%) rotateX(0deg) scale(1)"); - } else { - setTransform("translate(-50%,-50%) rotateX(25deg) scale(0.72)"); - } - }; - - checkMobile(); - window.addEventListener("resize", checkMobile); - return () => window.removeEventListener("resize", checkMobile); - }, []); - - const onMouseEnter = () => { - // Only apply the 3D hover effect on Desktop - if (!isMobile) { - setTransform("translate(-50%,-50%) rotateX(0deg) scale(0.9)"); - } - }; - - const onMouseLeave = () => { - // Reset to appropriate resting state based on device - if (!isMobile) { - setTransform("translate(-50%,-50%) rotateX(25deg) scale(0.72)"); - } else { - setTransform("translate(-50%,-50%) rotateX(0deg) scale(1)"); - } - }; - - return ( - -
-
-
{children}
-
-
- -
- ); -}; - -export const PinPerspective = ({ title, href }) => { - return ( - -
- - -
- <> - - - - -
- - <> - - - - - -
-
- ); -}; \ No newline at end of file diff --git a/src/components/TeamGrid.jsx b/src/components/TeamGrid.jsx index a35dc67..3b1dd34 100644 --- a/src/components/TeamGrid.jsx +++ b/src/components/TeamGrid.jsx @@ -1,5 +1,6 @@ import { useEffect, useState, useRef } from "react"; import { useNavigate } from "react-router-dom"; +import { motion } from "framer-motion"; import { IconHome, IconCalendar, @@ -18,23 +19,23 @@ export default function TeamGrid() { const navigate = useNavigate(); const teamMembers = [ - { name: 'Vishesh Dudeja', role: 'Advisory', row: 0, col: 0, transformOrigin: 'right bottom',img:'1.webp', linkedin: 'https://linkedin.com/in/vishesh-dudeja-b62a79242', tech: ['Python', 'Java', 'SQL'] }, - { name: 'Sai Aryan Goswami', role: 'Core Team', row: 0, col: 2, transformOrigin: 'left bottom',img:'2.webp', linkedin: 'https://linkedin.com/in/saiaryangoswami', tech: ['UI/UX', 'Next.js', 'Full Stack'] }, - { name: 'Vidhi Gandhi', role: 'President', row: 1, col: 1, transformOrigin: 'left bottom',img:'vidhi-didi.jpg', linkedin: 'https://linkedin.com/in/vidhi-gandhi-640806296', tech: ['React.js', 'HTML', 'CSS'] }, - { name: 'Rohit Bhardwaj', role: 'Vice-president', row: 2, col: 0, transformOrigin: 'right bottom',img:'rohit-bhaiya.jpg', linkedin: 'https://linkedin.com/in/dev-rohitbhardwaj', tech: ['Solidity', 'Web3', 'Open Source'] }, - { name: 'Lavish Aggarwal', role: 'Vice-president', row: 2, col: 3, transformOrigin: 'left bottom', img:'lavish-bhaiya.jpg', linkedin: 'https://linkedin.com/in/lavishagrwl', tech: ['JavaScript', 'Python', 'React.js'] }, - { name: 'Abhinav Vishwakarma', role: 'Development Lead', row: 3, col: 1, transformOrigin: 'left bottom',img:'abhinav-bhaiya.jpg', linkedin: 'https://linkedin.com/in/abhinav-vishwakarma-fsd', tech: ['Canva', 'Python', 'Video Editing'] }, - { name: 'Ramyak Jain', role: 'Event Lead', row: 3, col: 2, transformOrigin: 'right bottom', img:'ramayk bhaiya.jpg', linkedin: 'https://linkedin.com/in/ramyak-jain', tech: ['Graphic Designer', 'Content Writing', 'Wed Designer'] }, - { name: 'Utkarsh Saxena', role: 'Cp Lead',row: 4, col: 0, transformOrigin: 'left bottom',img:'utkarsh-bhaiya.jpg', linkedin: 'https://linkedin.com/in/utkarsh-saxena-91005a290', tech: ['React.js', 'Javascript', 'DSA'] }, - { name: 'Deepanshu', role: 'Graphics Lead', row: 4, col: 3, transformOrigin: 'left bottom',img:'deepanshu bhaiya.jpg', linkedin: 'https://linkedin.com/in/deepanshu-kaushik-174059297', tech: ['UI/UX Designer', 'DSA', 'Javascript'] }, - { name: 'Swati Mittal', role: 'Pr Lead', row: 5, col: 1, transformOrigin: 'left bottom',img:'swati-didi.jpg', linkedin: 'https://linkedin.com/in/swati-mittal24', tech: ['MongoDB', 'Node.js', 'express.js'] }, - { name: 'Ananya', role: 'Content Lead', row: 6, col: 1, transformOrigin: 'left bottom',img:'ananya-didi.jpg', linkedin: 'https://linkedin.com/in/', tech: ['Copywriting', 'SEO', 'Content Strategy'] }, - { name: 'Sakhi Vishnoi', role: 'Graphics Lead', row: 6, col: 3, transformOrigin: 'left bottom', img:'sakshi didi.jpg', linkedin: 'https://linkedin.com/in/sakshi-vishnoi-7770b2315', tech: ['Flutter', 'DSA', 'C++'] }, - { name: 'Anvesh Srivastava ', role: 'Backend Developer', row: 7, col: 0, transformOrigin: 'right bottom',img:'anvesh-bhaiya.jpg', linkedin: 'https://linkedin.com/in/anvesh-srivastava', tech: ['Node.js', 'RestAPI', 'MongoDB'] }, - { name: 'kaif azmi', role: 'Frontend Developer', row: 7, col: 2, transformOrigin: 'left bottom',img:'kaif-bhaiya.jpg', linkedin: 'https://linkedin.com/in/kaifazmi', tech: ['React', 'CSS', 'JavaScript'] }, - { name: "Bhaskar Dwivedi", role: 'Mobile Developer', row: 8, col: 1, transformOrigin: 'left bottom',img:'bhaskar-bhaiya.jpg', linkedin: 'https://linkedin.com/in/bhaskar-dwi', tech: ['React Native', 'Flutter', 'TailwidCSS'] }, - { name: 'Dhruv Khare', role: 'Design Systems', row: 9, col: 0, transformOrigin: 'right bottom',img:'dhruv-bhaiya.jpg', linkedin: 'https://linkedin.com/in/dhruvkhare-softwaredev', tech: ['RestAPI', 'MonogoDB', 'Express.js'] }, - { name: 'Amit Gupta', role: 'Product Analyst', row: 9, col: 3, transformOrigin: 'left bottom', img:'amit bhaiya.png', linkedin: 'linkedin.com/in/amitguptadev', tech: ['Node.js', 'AI/ML', 'Typescript'] } + { name: 'Vishesh Dudeja', role: 'Advisory', row: 0, col: 0, transformOrigin: 'right bottom', img: '1.webp', linkedin: 'https://linkedin.com/in/vishesh-dudeja-b62a79242', tech: ['Python', 'Java', 'SQL'] }, + { name: 'Sai Aryan Goswami', role: 'Core Team', row: 0, col: 2, transformOrigin: 'left bottom', img: '2.webp', linkedin: 'https://linkedin.com/in/saiaryangoswami', tech: ['UI/UX', 'Next.js', 'Full Stack'] }, + { name: 'Vidhi Gandhi', role: 'President', row: 1, col: 1, transformOrigin: 'left bottom', img: 'vidhi-didi.jpg', linkedin: 'https://linkedin.com/in/vidhi-gandhi-640806296', tech: ['React.js', 'HTML', 'CSS'] }, + { name: 'Rohit Bhardwaj', role: 'Vice-president', row: 2, col: 0, transformOrigin: 'right bottom', img: 'rohit-bhaiya.jpg', linkedin: 'https://linkedin.com/in/dev-rohitbhardwaj', tech: ['Solidity', 'Web3', 'Open Source'] }, + { name: 'Lavish Aggarwal', role: 'Vice-president', row: 2, col: 3, transformOrigin: 'left bottom', img: 'lavish-bhaiya.jpg', linkedin: 'https://linkedin.com/in/lavishagrwl', tech: ['JavaScript', 'Python', 'React.js'] }, + { name: 'Abhinav Vishwakarma', role: 'Development Lead', row: 3, col: 1, transformOrigin: 'left bottom', img: 'abhinav-bhaiya.jpg', linkedin: 'https://linkedin.com/in/abhinav-vishwakarma-fsd', tech: ['Canva', 'Python', 'Video Editing'] }, + { name: 'Ramyak Jain', role: 'Event Lead', row: 3, col: 2, transformOrigin: 'right bottom', img: 'ramayk bhaiya.jpg', linkedin: 'https://linkedin.com/in/ramyak-jain', tech: ['Graphic Designer', 'Content Writing', 'Wed Designer'] }, + { name: 'Utkarsh Saxena', role: 'Cp Lead', row: 4, col: 0, transformOrigin: 'left bottom', img: 'utkarsh-bhaiya.jpg', linkedin: 'https://linkedin.com/in/utkarsh-saxena-91005a290', tech: ['React.js', 'Javascript', 'DSA'] }, + { name: 'Deepanshu', role: 'Graphics Lead', row: 4, col: 3, transformOrigin: 'left bottom', img: 'deepanshu bhaiya.jpg', linkedin: 'https://linkedin.com/in/deepanshu-kaushik-174059297', tech: ['UI/UX Designer', 'DSA', 'Javascript'] }, + { name: 'Swati Mittal', role: 'Pr Lead', row: 5, col: 1, transformOrigin: 'left bottom', img: 'swati-didi.jpg', linkedin: 'https://linkedin.com/in/swati-mittal24', tech: ['MongoDB', 'Node.js', 'express.js'] }, + { name: 'Ananya', role: 'Content Lead', row: 6, col: 1, transformOrigin: 'left bottom', img: 'ananya-didi.jpg', linkedin: 'https://linkedin.com/in/', tech: ['Copywriting', 'SEO', 'Content Strategy'] }, + { name: 'Sakhi Vishnoi', role: 'Graphics Lead', row: 6, col: 3, transformOrigin: 'left bottom', img: 'sakshi didi.jpg', linkedin: 'https://linkedin.com/in/sakshi-vishnoi-7770b2315', tech: ['Flutter', 'DSA', 'C++'] }, + { name: 'Anvesh Srivastava ', role: 'Backend Developer', row: 7, col: 0, transformOrigin: 'right bottom', img: 'anvesh-bhaiya.jpg', linkedin: 'https://linkedin.com/in/anvesh-srivastava', tech: ['Node.js', 'RestAPI', 'MongoDB'] }, + { name: 'kaif azmi', role: 'Frontend Developer', row: 7, col: 2, transformOrigin: 'left bottom', img: 'kaif-bhaiya.jpg', linkedin: 'https://linkedin.com/in/kaifazmi', tech: ['React', 'CSS', 'JavaScript'] }, + { name: "Bhaskar Dwivedi", role: 'Mobile Developer', row: 8, col: 1, transformOrigin: 'left bottom', img: 'bhaskar-bhaiya.jpg', linkedin: 'https://linkedin.com/in/bhaskar-dwi', tech: ['React Native', 'Flutter', 'TailwidCSS'] }, + { name: 'Dhruv Khare', role: 'Design Systems', row: 9, col: 0, transformOrigin: 'right bottom', img: 'dhruv-bhaiya.jpg', linkedin: 'https://linkedin.com/in/dhruvkhare-softwaredev', tech: ['RestAPI', 'MonogoDB', 'Express.js'] }, + { name: 'Amit Gupta', role: 'Product Analyst', row: 9, col: 3, transformOrigin: 'left bottom', img: 'amit bhaiya.png', linkedin: 'linkedin.com/in/amitguptadev', tech: ['Node.js', 'AI/ML', 'Typescript'] } ]; useEffect(() => { @@ -139,7 +140,7 @@ export default function TeamGrid() { onClick: () => navigate("/admin"), }); } - + navLinks.push({ title: "Logout", icon: , @@ -153,17 +154,36 @@ export default function TeamGrid() {
-
- - scroll down to meet the teams - -
- -
-

Teams

-
+ +

+ {"Meet our Bawarchis".split("").map((char, index) => ( + + {char} + + ))} +

+
-
+
{grid.map((row, rowIndex) => (
{row.map((member, colIndex) => ( @@ -183,9 +203,8 @@ export default function TeamGrid() { > {member.name} @@ -194,11 +213,10 @@ export default function TeamGrid() { flex flex-col items-center justify-center gap-2 sm:gap-3 transition-opacity duration-300 - ${ - hoveredIndex === member.index - ? "opacity-100" - : "opacity-0 pointer-events-none" - } + ${hoveredIndex === member.index + ? "opacity-100" + : "opacity-0 pointer-events-none" + } `} >

@@ -239,8 +257,6 @@ export default function TeamGrid() {

))}
- -
); From 6ad2e62dafe589d5cb9a7f9db9993789ccbdbc26 Mon Sep 17 00:00:00 2001 From: "ayushmanitiwari931@gmail.com" Date: Sat, 21 Feb 2026 21:07:15 +0530 Subject: [PATCH 9/9] added the galaxy background to landing page --- src/components/Landing.jsx | 33 +++++---- src/components/requestAnimationFrame.jsx | 93 ++++++++++++++++++++++++ src/pages/HomePage.jsx | 2 + 3 files changed, 113 insertions(+), 15 deletions(-) create mode 100644 src/components/requestAnimationFrame.jsx diff --git a/src/components/Landing.jsx b/src/components/Landing.jsx index 849cccd..c824e9a 100644 --- a/src/components/Landing.jsx +++ b/src/components/Landing.jsx @@ -14,7 +14,7 @@ import { } from "@tabler/icons-react"; import "../styles/Landing.css"; import { FloatingNav } from "./FloatingNavbar"; -import LightRays from "./LightRays"; +import Galaxy from "./Galaxy"; const API_URL = 'http://localhost:5000/api'; @@ -122,20 +122,23 @@ export default function Landing() { return (
-
diff --git a/src/components/requestAnimationFrame.jsx b/src/components/requestAnimationFrame.jsx new file mode 100644 index 0000000..98b6d21 --- /dev/null +++ b/src/components/requestAnimationFrame.jsx @@ -0,0 +1,93 @@ +import React, { useState, useEffect } from 'react'; + +// 1. Move static data OUTSIDE the component. +// Defined in reverse order (highest threshold first) to make the logic faster/simpler. +const messages = [ + { threshold: 98, text: "BUILD SUCCESSFUL. Ready to deploy." }, + { threshold: 88, text: "Rendering visual assets..." }, + { threshold: 75, text: "Parsing user_feedback.log..." }, + { threshold: 55, text: "Fetching legacy_pointers (Ex-Heads)..." }, + { threshold: 35, text: "Compiling active_initiatives..." }, + { threshold: 15, text: "Importing libraries..." }, + { threshold: 0, text: "Initializing CodeChef_ABESEC.exe..." }, +]; + +const CompilationBar = () => { + const [scrollPercent, setScrollPercent] = useState(0); + const [statusMessage, setStatusMessage] = useState(messages[messages.length - 1].text); + + useEffect(() => { + let ticking = false; + + const handleScroll = () => { + // 2. Use 'ticking' to prevent calculations if the browser is busy + if (!ticking) { + window.requestAnimationFrame(() => { + + const totalHeight = document.documentElement.scrollHeight - window.innerHeight; + const currentScroll = window.scrollY; + // Calculate percent + const percent = Math.min((currentScroll / totalHeight) * 100, 100); + + setScrollPercent(percent); + + // 3. Optimized search: Find the first message where percent > threshold + // Since array is reversed, this finds the highest applicable threshold immediately + const currentMsg = messages.find(msg => percent >= msg.threshold); + if (currentMsg) { + setStatusMessage(currentMsg.text); + } + + ticking = false; + }); + + ticking = true; + } + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + return ( +
+ + {/* 1. Terminal Prompt */} +
+ + ~/codechef-abesec/site + | + {/* Added a min-width to prevent jitter when text length changes */} + + {statusMessage} + +
+ + {/* 2. Percentage */} +
+ [ {Math.round(scrollPercent)}% ] +
+ + {/* 3. Progress Bar Overlay */} +
+ + {/* Optional: A thin bright line at the very top of the bar for extra "tech" feel */} +
+
+ ); +}; + +export default CompilationBar; \ No newline at end of file diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx index b0f99d1..bae25c8 100644 --- a/src/pages/HomePage.jsx +++ b/src/pages/HomePage.jsx @@ -7,6 +7,7 @@ import About from "../components/about"; import EventsGalary from "../components/eventsGalary"; import { InfiniteMovingCardsDemo } from "../components/testimonial"; import useLenis from "../hooks/useLenis"; +import CompilationBar from "../components/requestAnimationFrame"; @@ -23,6 +24,7 @@ export default function HomePage() { {/* */} {/*
); } \ No newline at end of file