Deploy from Lumerel
This commit is contained in:
231
script.js
Normal file
231
script.js
Normal file
@@ -0,0 +1,231 @@
|
||||
// Mobile Menu Toggle
|
||||
const mobileToggle = document.getElementById('mobileToggle');
|
||||
const navMenu = document.getElementById('navMenu');
|
||||
|
||||
mobileToggle.addEventListener('click', () => {
|
||||
navMenu.classList.toggle('active');
|
||||
|
||||
// Animate hamburger to X
|
||||
const spans = mobileToggle.querySelectorAll('span');
|
||||
if (navMenu.classList.contains('active')) {
|
||||
spans[0].style.transform = 'rotate(45deg) translate(5px, 5px)';
|
||||
spans[1].style.opacity = '0';
|
||||
spans[2].style.transform = 'rotate(-45deg) translate(7px, -6px)';
|
||||
} else {
|
||||
spans[0].style.transform = 'none';
|
||||
spans[1].style.opacity = '1';
|
||||
spans[2].style.transform = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Close mobile menu when clicking on a link
|
||||
const navLinks = document.querySelectorAll('.nav-link');
|
||||
navLinks.forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
navMenu.classList.remove('active');
|
||||
const spans = mobileToggle.querySelectorAll('span');
|
||||
spans[0].style.transform = 'none';
|
||||
spans[1].style.opacity = '1';
|
||||
spans[2].style.transform = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// Navbar scroll effect
|
||||
const nav = document.getElementById('nav');
|
||||
let lastScroll = 0;
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const currentScroll = window.pageYOffset;
|
||||
|
||||
if (currentScroll > 50) {
|
||||
nav.classList.add('scrolled');
|
||||
} else {
|
||||
nav.classList.remove('scrolled');
|
||||
}
|
||||
|
||||
lastScroll = currentScroll;
|
||||
});
|
||||
|
||||
// Smooth scroll for anchor links
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
const offsetTop = target.offsetTop - 80;
|
||||
window.scrollTo({
|
||||
top: offsetTop,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Animate on Scroll (AOS) implementation
|
||||
function initAOS() {
|
||||
const observerOptions = {
|
||||
threshold: 0.1,
|
||||
rootMargin: '0px 0px -50px 0px'
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('aos-animate');
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
// Observe all elements with data-aos attribute
|
||||
document.querySelectorAll('[data-aos]').forEach(element => {
|
||||
observer.observe(element);
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize AOS when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', initAOS);
|
||||
|
||||
// Animate skill bars when they come into view
|
||||
const skillObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const progressBars = entry.target.querySelectorAll('.skill-progress');
|
||||
progressBars.forEach(bar => {
|
||||
const width = bar.style.width;
|
||||
bar.style.width = '0';
|
||||
setTimeout(() => {
|
||||
bar.style.width = width;
|
||||
}, 100);
|
||||
});
|
||||
skillObserver.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, { threshold: 0.5 });
|
||||
|
||||
const skillsSection = document.querySelector('.skills');
|
||||
if (skillsSection) {
|
||||
skillObserver.observe(skillsSection);
|
||||
}
|
||||
|
||||
// Contact Form Handling
|
||||
const contactForm = document.getElementById('contactForm');
|
||||
|
||||
contactForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Get form data
|
||||
const formData = {
|
||||
name: document.getElementById('name').value,
|
||||
email: document.getElementById('email').value,
|
||||
subject: document.getElementById('subject').value,
|
||||
message: document.getElementById('message').value
|
||||
};
|
||||
|
||||
// In a real application, you would send this data to a server
|
||||
console.log('Form submitted:', formData);
|
||||
|
||||
// Show success message
|
||||
showNotification('Thank you for your message! I\'ll get back to you soon.', 'success');
|
||||
|
||||
// Reset form
|
||||
contactForm.reset();
|
||||
});
|
||||
|
||||
// Notification function
|
||||
function showNotification(message, type = 'info') {
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification notification-${type}`;
|
||||
notification.textContent = message;
|
||||
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 100px;
|
||||
right: 20px;
|
||||
background: ${type === 'success' ? '#10b981' : '#3b82f6'};
|
||||
color: white;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
|
||||
z-index: 10000;
|
||||
animation: slideIn 0.3s ease;
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.style.animation = 'slideOut 0.3s ease';
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(notification);
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Add animation keyframes
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(400px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideOut {
|
||||
from {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
transform: translateX(400px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Parallax effect for hero section
|
||||
window.addEventListener('scroll', () => {
|
||||
const scrolled = window.pageYOffset;
|
||||
const hero = document.querySelector('.hero');
|
||||
|
||||
if (hero && scrolled < window.innerHeight) {
|
||||
const parallaxElements = hero.querySelectorAll('.floating-card');
|
||||
parallaxElements.forEach((element, index) => {
|
||||
const speed = 0.5 + (index * 0.1);
|
||||
element.style.transform = `translateY(${scrolled * speed}px)`;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add active state to navigation based on scroll position
|
||||
window.addEventListener('scroll', () => {
|
||||
const sections = document.querySelectorAll('section[id]');
|
||||
const scrollY = window.pageYOffset;
|
||||
|
||||
sections.forEach(section => {
|
||||
const sectionHeight = section.offsetHeight;
|
||||
const sectionTop = section.offsetTop - 100;
|
||||
const sectionId = section.getAttribute('id');
|
||||
const navLink = document.querySelector(`.nav-link[href="#${sectionId}"]`);
|
||||
|
||||
if (navLink && scrollY > sectionTop && scrollY <= sectionTop + sectionHeight) {
|
||||
document.querySelectorAll('.nav-link').forEach(link => {
|
||||
link.style.color = '';
|
||||
});
|
||||
navLink.style.color = 'var(--primary-color)';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Preload animation for page load
|
||||
window.addEventListener('load', () => {
|
||||
document.body.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
document.body.style.transition = 'opacity 0.5s ease';
|
||||
document.body.style.opacity = '1';
|
||||
}, 100);
|
||||
});
|
||||
Reference in New Issue
Block a user