console.log("Script.js started executing at:", new Date().toISOString());
// Initialize Particles.js
particlesJS('particles-js', {
particles: {
number: { value: 80, density: { enable: true, value_area: 800 } },
color: { value: ['#1E90FF'] },
shape: { type: 'circle' },
opacity: { value: 0.5, random: true },
size: { value: 3, random: true },
line_linked: { enable: true, distance: 150, color: '#1E90FF', opacity: 0.4, width: 1 },
move: { enable: true, speed: 2, direction: 'none', random: true, straight: false, out_mode: 'out', bounce: false }
},
interactivity: {
detect_on: 'canvas',
events: { onhover: { enable: true, mode: 'repulse' }, onclick: { enable: true, mode: 'push' }, resize: true },
modes: { repulse: { distance: 100, duration: 0.4 }, push: { particles_nb: 4 } }
},
retina_detect: true
});
// Glowing Cursor Trail
const canvas = document.getElementById('cursor-trail');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const trails = [];
window.addEventListener('mousemove', (e) => {
trails.push({ x: e.clientX, y: e.clientY, life: 1 });
});
function animateTrail() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
trails.forEach((trail, index) => {
ctx.beginPath();
ctx.arc(trail.x, trail.y, 5 * trail.life, 0, Math.PI * 2);
ctx.fillStyle = `rgba(30, 144, 255, ${trail.life})`;
ctx.fill();
trail.life -= 0.02;
if (trail.life <= 0) trails.splice(index, 1);
});
requestAnimationFrame(animateTrail);
}
animateTrail();
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
// GSAP Animations
gsap.registerPlugin(ScrollTrigger);
// Animate the static text and buttons in hero section
gsap.fromTo('.animate-slide-in',
{ y: 100, opacity: 0 },
{ y: 0, opacity: 1, duration: 1.2, ease: 'power3.out', onComplete: () => {
document.querySelector('.animate-slide-in').style.opacity = '1';
}}
);
// Animate the image in the hero section
gsap.fromTo('.animate-fade-in',
{ opacity: 0 },
{ opacity: 1, duration: 1.5, ease: 'power3.out', onComplete: () => {
document.querySelector('.animate-fade-in').style.opacity = '1';
}}
);
// Animate stat cards on scroll
gsap.from('.stat-card', {
scrollTrigger: { trigger: '#about', start: 'top 80%' },
y: 50,
opacity: 0,
duration: 0.8,
stagger: 0.2,
ease: 'power3.out'
});
// Navigation Bar
const menuToggle = document.getElementById('menu-toggle');
const mobileMenu = document.getElementById('mobile-menu');
menuToggle.addEventListener('click', () => {
mobileMenu.classList.toggle('hidden');
menuToggle.querySelector('i').classList.toggle('fa-bars');
menuToggle.querySelector('i').classList.toggle('fa-times');
});
// Smooth Scrolling
document.querySelectorAll('.nav-link').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
mobileMenu.classList.add('hidden');
menuToggle.querySelector('i').classList.add('fa-bars');
menuToggle.querySelector('i').classList.remove('fa-times');
document.querySelector(link.getAttribute('href')).scrollIntoView({ behavior: 'smooth' });
});
});
// Theme Toggle (Disabled since theme switching isn't implemented)
const themeToggle = document.getElementById('theme-toggle');
themeToggle.addEventListener('click', () => {
// Theme switching not implemented
});
// Back to Top Button
const backToTop = document.getElementById('back-to-top');
window.addEventListener('scroll', () => {
backToTop.classList.toggle('hidden', window.scrollY < 500);
});
backToTop.addEventListener('click', () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
// Ripple Effect for Buttons
document.querySelectorAll('.ripple').forEach(btn => {
btn.addEventListener('click', (e) => {
const rect = btn.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const ripple = document.createElement('span');
ripple.style.left = `${x}px`;
ripple.style.top = `${y}px`;
ripple.classList.add('ripple-effect');
btn.appendChild(ripple);
setTimeout(() => ripple.remove(), 600);
});
});
// Fetch Data Helper
const fetchData = async (url, elementId, renderCallback) => {
try {
console.log(`Fetching data from ${url}`);
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
console.log(`Fetched data from ${url}:`, data);
const container = document.getElementById(elementId);
if (!container) throw new Error(`Container with ID ${elementId} not found`);
renderCallback(data, container);
} catch (error) {
console.error(`Error fetching ${url}:`, error);
document.getElementById(elementId).innerHTML = '
Error loading data. Please try again later.
';
}
};
// Render Skills with Circular Progress
fetchData('/api/skills', 'skills-grid', (data, container) => {
console.log('Rendering skills data:', data);
container.innerHTML = '';
data.forEach(category => {
const card = document.createElement('div');
card.className = 'skill-card rounded-xl hover:scale-105 transition-transform';
card.style.opacity = '1';
card.style.display = 'block';
card.innerHTML = `
${category.title}
${category.skills.map(skill => `
`).join('')}
`;
container.appendChild(card);
console.log(`Rendered skill card for ${category.title}`);
card.querySelectorAll('.progress-fill').forEach(circle => {
const proficiency = circle.parentElement.parentElement.querySelector('.progress-percentage').textContent.replace('%', '');
const circumference = 2 * Math.PI * 18;
const offset = circumference * (proficiency / 100);
gsap.to(circle, {
scrollTrigger: {
trigger: card,
start: 'left 80%',
end: 'right 20%',
scrub: true
},
strokeDasharray: `${offset} 113`,
duration: 1.5,
ease: 'power3.out'
});
});
});
});
// Render Achievements
fetchData('/api/achievements', 'achievements-grid', (data, container) => {
data.forEach(achievement => {
const card = document.createElement('div');
card.className = 'achievement-card glassmorphic p-6 rounded-xl hover:scale-105 transition-transform';
card.setAttribute('data-tilt', '');
card.setAttribute('data-tilt-max', '10');
card.innerHTML = `
${achievement.title}
${achievement.organization}
${achievement.description}
`;
container.appendChild(card);
VanillaTilt.init(card);
gsap.from(card, { scrollTrigger: { trigger: card, start: 'top 80%' }, y: 50, opacity: 0, duration: 1, ease: 'power3.out' });
});
});
// Render Hobbies with Review Form for "Educating Others"
fetchData('/api/hobbies', 'hobbies-grid', (data, container) => {
data.forEach(hobby => {
const card = document.createElement('div');
card.className = 'hobby-card glassmorphic p-6 rounded-xl hover:scale-105 transition-transform';
card.setAttribute('data-tilt', '');
card.setAttribute('data-tilt-max', '10');
card.innerHTML = `
${hobby.title}
${hobby.description}
`;
if (hobby.title === "Educating Others") {
card.innerHTML += `
`;
}
container.appendChild(card);
VanillaTilt.init(card);
gsap.from(card, { scrollTrigger: { trigger: card, start: 'top 80%' }, y: 50, opacity: 0, duration: 1, ease: 'power3.out' });
if (hobby.title === "Educating Others") {
const stars = card.querySelectorAll('.star');
const ratingInput = card.querySelector('#review-rating');
stars.forEach(star => {
star.addEventListener('click', () => {
const rating = star.getAttribute('data-value');
ratingInput.value = rating;
stars.forEach(s => {
const value = s.getAttribute('data-value');
if (value <= rating) {
s.innerHTML = '';
} else {
s.innerHTML = '';
}
});
});
});
card.querySelector('#submit-review').addEventListener('click', () => {
const name = card.querySelector('#review-name').value.trim();
const rating = parseInt(card.querySelector('#review-rating').value);
const description = card.querySelector('#review-description').value.trim();
if (!name || rating === 0 || !description) {
alert('Please fill out all fields and select a rating.');
return;
}
fetch('/api/reviews', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, rating, description })
})
.then(response => response.json())
.then(data => {
if (data.message) {
alert('Thank you for your feedback!');
card.querySelector('#review-name').value = '';
card.querySelector('#review-description').value = '';
card.querySelector('#review-rating').value = '0';
stars.forEach(star => star.innerHTML = '');
fetchReviews(card.querySelector('#reviews-container'));
} else {
alert('Error submitting feedback: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Error submitting review:', error);
alert('Error submitting feedback. Please try again later.');
});
});
function fetchReviews(reviewsContainer) {
fetch('/api/reviews')
.then(response => response.json())
.then(reviews => {
reviewsContainer.innerHTML = '';
if (reviews.length === 0) {
reviewsContainer.innerHTML = 'No testimonials yet. Be the first to share your experience!
';
return;
}
reviews.forEach(review => {
const reviewDiv = document.createElement('div');
reviewDiv.className = 'glassmorphic p-4 rounded-xl flex justify-between items-start';
reviewDiv.innerHTML = `
${review.name}
${''.repeat(review.rating)}
${''.repeat(5 - review.rating)}
${review.description}
`;
reviewsContainer.appendChild(reviewDiv);
gsap.from(reviewDiv, { opacity: 0, y: 20, duration: 0.8, ease: 'power3.out' });
reviewDiv.querySelector('.delete-review').addEventListener('click', () => {
const confirmDelete = confirm(`Are you sure you want to delete the review by ${review.name}?`);
if (confirmDelete) {
const password = prompt('Please enter the admin password to delete this review:');
if (!password) {
alert('Password is required to delete a review.');
return;
}
fetch(`/api/reviews/delete/${review.id}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password })
})
.then(response => response.json())
.then(data => {
if (data.message) {
alert(data.message);
fetchReviews(reviewsContainer);
} else {
alert('Error deleting review: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Error deleting review:', error);
alert('Error deleting review. Please try again later.');
});
}
});
});
})
.catch(error => {
console.error('Error fetching reviews:', error);
reviewsContainer.innerHTML = 'Error loading testimonials. Please try again later.
';
});
}
fetchReviews(card.querySelector('#reviews-container'));
}
});
});
// Render Projects with Modal
const modal = document.getElementById('project-modal');
const modalClose = document.getElementById('modal-close');
fetchData('/api/projects', 'projects-grid', (data, container) => {
data.forEach(project => {
const card = document.createElement('div');
card.className = 'project-card glassmorphic rounded-xl overflow-hidden cursor-pointer hover:scale-105 transition-transform';
card.setAttribute('data-tilt', '');
card.setAttribute('data-tilt-max', '10');
card.innerHTML = `
${project.title}
${project.description.substring(0, 100)}...
${project.technologies.map(tech => `${tech}`).join('')}
`;
card.addEventListener('click', () => {
const mediaContainer = document.getElementById('modal-media');
mediaContainer.innerHTML = project.video ? `
` : `
`;
document.getElementById('modal-title').textContent = project.title;
document.getElementById('modal-description').textContent = project.description;
document.getElementById('modal-role').textContent = project.role;
const contributionsContainer = document.getElementById('modal-contributions');
contributionsContainer.innerHTML = project.contributions.map(contribution => `${contribution}`).join('');
const techContainer = document.getElementById('modal-tech');
techContainer.innerHTML = project.technologies.map(tech => `${tech}`).join('');
document.getElementById('modal-github').href = project.githubLink;
document.getElementById('modal-live').href = project.liveLink;
modal.classList.remove('hidden');
gsap.from(modal.querySelector('div'), { y: 100, opacity: 0, duration: 0.8, ease: 'power3.out' });
});
container.appendChild(card);
VanillaTilt.init(card);
gsap.from(card, { scrollTrigger: { trigger: card, start: 'top 80%' }, y: 50, opacity: 0, duration: 1, ease: 'power3.out' });
});
});
modalClose.addEventListener('click', () => {
modal.classList.add('hidden');
});
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.classList.add('hidden');
});
// Render Education
fetchData('/api/education', 'education-grid', (data, container) => {
data.forEach(edu => {
const card = document.createElement('div');
card.className = 'education-card glassmorphic p-6 rounded-xl hover:scale-105 transition-transform';
card.setAttribute('data-tilt', '');
card.setAttribute('data-tilt-max', '10');
card.innerHTML = `
${edu.degree}
${edu.period}
${edu.institution}
${edu.description}
Key Achievements:
${edu.achievements.map(achievement => `- ${achievement}
`).join('')}
`;
container.appendChild(card);
VanillaTilt.init(card);
gsap.from(card, { scrollTrigger: { trigger: card, start: 'top 80%' }, y: 50, opacity: 0, duration: 1, ease: 'power3.out' });
});
});
// Render Certifications
fetchData('/api/certifications', 'certifications-grid', (data, container) => {
data.forEach(cert => {
const card = document.createElement('div');
card.className = 'certification-card glassmorphic p-6 rounded-xl hover:scale-105 transition-transform';
card.setAttribute('data-tilt', '');
card.setAttribute('data-tilt-max', '10');
card.innerHTML = `
`;
container.appendChild(card);
VanillaTilt.init(card);
gsap.from(card, { scrollTrigger: { trigger: card, start: 'top 80%' }, y: 50, opacity: 0, duration: 1, ease: 'power3.out' });
});
});
// Render Volunteer Experience
fetchData('/api/volunteer', 'volunteer-grid', (data, container) => {
data.forEach(vol => {
const card = document.createElement('div');
card.className = 'volunteer-card glassmorphic p-6 rounded-xl hover:scale-105 transition-transform';
card.setAttribute('data-tilt', '');
card.setAttribute('data-tilt-max', '10');
card.innerHTML = `
${vol.role}
${vol.period}
${vol.organization}
${vol.description}
Contributions:
${vol.contributions.map(contribution => `- ${contribution}
`).join('')}
`;
container.appendChild(card);
VanillaTilt.init(card);
gsap.from(card, { scrollTrigger: { trigger: card, start: 'top 80%' }, y: 50, opacity: 0, duration: 1, ease: 'power3.out' });
});
});
// Render Talks
fetchData('/api/talks', 'talks-grid', (data, container) => {
data.forEach(talk => {
const card = document.createElement('div');
card.className = 'talk-card glassmorphic p-6 rounded-xl hover:scale-105 transition-transform';
card.setAttribute('data-tilt', '');
card.setAttribute('data-tilt-max', '10');
card.innerHTML = `
${talk.title}
${talk.date}
${talk.event}
${talk.description}
${talk.image ? `
` : ''}
Watch Full Talk
`;
container.appendChild(card);
VanillaTilt.init(card);
gsap.from(card, { scrollTrigger: { trigger: card, start: 'top 80%' }, y: 50, opacity: 0, duration: 1, ease: 'power3.out' });
});
});
// Handle Contact Form Submission
const contactSubmitButton = document.getElementById('contact-submit');
const emailInput = document.getElementById('email');
const messageInput = document.getElementById('message');
contactSubmitButton.addEventListener('click', async () => {
const email = emailInput.value.trim();
const message = messageInput.value.trim();
// Validate inputs
if (!email || !message) {
alert('Please fill in both email and message fields.');
return;
}
if (!/\S+@\S+\.\S+/.test(email)) {
alert('Please enter a valid email address.');
return;
}
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, message })
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
}
const data = await response.json();
alert(data.message);
emailInput.value = '';
messageInput.value = '';
} catch (error) {
console.error('Error submitting contact form:', error);
alert('Error submitting form. If you\'re on Hugging Face Spaces, please use LinkedIn or GitHub to contact me.');
}
});