CollegeCompassAI / script.js
eva1267890's picture
Update script.js
e597fae verified
Raw
History Blame Contribute Delete
22.7 kB
const AppState = {
currentTheme: 'dark',
currentSection: 'home',
isMenuOpen: false,
isLoaded: false
};
document.addEventListener('DOMContentLoaded', () => {
initializeApp();
});
function initializeApp() {
loadPreferences();
initTheme();
initNavigation();
initScrollEffects();
initFormHandlers();
initMobileMenu();
updateThemeUI();
AppState.isLoaded = true;
}
function loadPreferences() {
const savedTheme = localStorage.getItem('portfolio-theme');
if (savedTheme) {
AppState.currentTheme = savedTheme;
}
}
function initTheme() {
const themeToggle = document.getElementById('themeToggle');
if (themeToggle) {
themeToggle.addEventListener('click', toggleTheme);
}
setTheme(AppState.currentTheme);
}
function toggleTheme() {
const newTheme = AppState.currentTheme === 'dark' ? 'light' : 'dark';
setTheme(newTheme);
localStorage.setItem('portfolio-theme', newTheme);
}
function setTheme(theme) {
AppState.currentTheme = theme;
document.body.setAttribute('data-theme', theme);
updateThemeUI();
}
function updateThemeUI() {
const themeToggle = document.getElementById('themeToggle');
if (themeToggle) {
const icon = themeToggle.querySelector('i');
if (icon) {
icon.className = AppState.currentTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
}
}
}
function initNavigation() {
const navLinks = document.querySelectorAll('.nav-link[href^="#"]');
navLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = link.getAttribute('href');
const targetSection = document.querySelector(targetId);
if (targetSection) {
const headerHeight = document.querySelector('.main-header').offsetHeight;
const targetPosition = targetSection.offsetTop - headerHeight;
window.scrollTo({
top: targetPosition,
behavior: 'smooth'
});
updateActiveNavLink(link);
if (AppState.isMenuOpen) {
toggleMobileMenu();
}
}
});
});
window.addEventListener('scroll', handleScroll);
window.addEventListener('scroll', updateHeaderOnScroll);
}
function handleScroll() {
const sections = document.querySelectorAll('section[id]');
const scrollPosition = window.scrollY + 100;
sections.forEach(section => {
const sectionTop = section.offsetTop;
const sectionHeight = section.offsetHeight;
const sectionId = section.getAttribute('id');
if (scrollPosition >= sectionTop && scrollPosition < sectionTop + sectionHeight) {
AppState.currentSection = sectionId;
updateActiveNavLink(null, sectionId);
}
});
}
function updateActiveNavLink(clickedLink, sectionId = null) {
const navLinks = document.querySelectorAll('.nav-link');
navLinks.forEach(link => {
link.classList.remove('active');
if (clickedLink && link === clickedLink) {
link.classList.add('active');
} else if (sectionId) {
const linkSection = link.getAttribute('data-section');
if (linkSection === sectionId) {
link.classList.add('active');
}
}
});
}
function updateHeaderOnScroll() {
const header = document.querySelector('.main-header');
if (window.scrollY > 50) {
header.classList.add('scrolled');
} else {
header.classList.remove('scrolled');
}
}
function initScrollEffects() {
const observerOptions = {
threshold: 0.1,
rootMargin: '0px 0px -100px 0px'
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target);
}
});
}, observerOptions);
const fadeElements = document.querySelectorAll('.fade-in');
fadeElements.forEach(element => observer.observe(element));
const sections = document.querySelectorAll('.section');
sections.forEach(section => observer.observe(section));
}
function initFormHandlers() {
const contactForm = document.getElementById('contactForm');
if (contactForm) {
contactForm.addEventListener('submit', handleFormSubmit);
}
}
function handleFormSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
console.log('Form submitted:', data);
const message = 'Message sent successfully!';
alert(message);
e.target.reset();
}
function initMobileMenu() {
const menuToggle = document.getElementById('menuToggle');
if (menuToggle) {
menuToggle.addEventListener('click', toggleMobileMenu);
}
document.addEventListener('click', (e) => {
const navMenu = document.getElementById('navMenu');
const menuToggle = document.getElementById('menuToggle');
if (AppState.isMenuOpen &&
!navMenu.contains(e.target) &&
!menuToggle.contains(e.target)) {
toggleMobileMenu();
}
});
}
function toggleMobileMenu() {
AppState.isMenuOpen = !AppState.isMenuOpen;
const navMenu = document.getElementById('navMenu');
const menuToggle = document.getElementById('menuToggle');
if (navMenu) {
navMenu.classList.toggle('active', AppState.isMenuOpen);
}
if (menuToggle) {
menuToggle.classList.toggle('active', AppState.isMenuOpen);
}
}
function generateParticles() {
const particlesContainer = document.getElementById('particles');
if (!particlesContainer) return;
const codeSymbols = ['{', '}', '[', ']', '(', ')', '<', '>', '/', '*', '=', '+', '-', ';', ':', '&', '|', '%', '$', '#', '@'];
const particleCount = 20;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particle.textContent = codeSymbols[Math.floor(Math.random() * codeSymbols.length)];
particle.style.left = Math.random() * 100 + '%';
particle.style.animationDelay = Math.random() * 15 + 's';
particle.style.animationDuration = (10 + Math.random() * 10) + 's';
particlesContainer.appendChild(particle);
}
}
document.addEventListener('DOMContentLoaded', () => {
generateParticles();
});
//--------------animations.js-----------------
function inView(element, callback, options = {}) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
callback(entry);
if (options.once !== false) {
observer.unobserve(entry.target);
}
}
});
}, {
threshold: options.amount || 0.1,
rootMargin: options.rootMargin || '0px'
});
observer.observe(element);
return () => observer.unobserve(element);
}
function animateElement(element, props, options = {}) {
if (typeof anime === 'undefined') return;
const animeProps = {};
if (props.opacity) animeProps.opacity = props.opacity;
if (props.x !== undefined) animeProps.translateX = props.x;
if (props.y !== undefined) animeProps.translateY = props.y;
if (props.scale) animeProps.scale = props.scale;
return anime({
targets: element,
...animeProps,
duration: (options.duration || 0.8) * 1000,
delay: (options.delay || 0) * 1000,
easing: options.easing || 'easeOutExpo'
});
}
window.addEventListener('load', () => {
setTimeout(() => {
initLoaderAnimation();
}, 100);
});
function initLoaderAnimation() {
const loader = document.getElementById('loader');
const loaderPercent = document.getElementById('loaderPercent');
if (!loader || !loaderPercent) return;
let progress = 0;
const progressInterval = setInterval(() => {
progress += Math.random() * 15;
if (progress >= 100) {
progress = 100;
clearInterval(progressInterval);
setTimeout(() => {
if (typeof anime !== 'undefined') {
anime({
targets: loader,
opacity: [1, 0],
duration: 500,
easing: 'easeInOutQuad',
complete: () => {
loader.classList.add('hidden');
initPageAnimations();
}
});
} else {
loader.classList.add('hidden');
initPageAnimations();
}
}, 300);
}
if (loaderPercent) {
loaderPercent.textContent = Math.floor(progress) + '%';
}
}, 100);
}
function initPageAnimations() {
setTimeout(() => {
initHeroAnimations();
initSkillAnimations();
initTimelineAnimations();
initProjectAnimations();
initScrollAnimations();
initContactAnimations();
animateStats();
initParallax();
initSmoothScroll();
}, 300);
}
function initHeroAnimations() {
if (typeof anime === 'undefined') return;
const heroName = document.getElementById('heroName');
if (heroName) {
const nameValue = heroName.querySelector('.name-value');
if (nameValue) {
const originalText = nameValue.textContent;
nameValue.textContent = '';
anime({
targets: { value: 0 },
value: originalText.length,
duration: 1500,
delay: 500,
easing: 'easeInOutQuad',
update: function(anim) {
const length = Math.floor(anim.animatables[0].target.value);
nameValue.textContent = originalText.substring(0, length);
},
complete: () => {
const cursor = document.createElement('span');
cursor.className = 'name-cursor';
cursor.textContent = '|';
cursor.style.animation = 'blink 1s infinite';
nameValue.appendChild(cursor);
setTimeout(() => cursor.remove(), 2000);
}
});
}
}
const heroTitle = document.querySelector('.hero-title');
if (heroTitle) {
anime({
targets: heroTitle,
opacity: [0, 1],
translateX: [-30, 0],
delay: 800,
duration: 1000,
easing: 'easeOutExpo'
});
}
const heroDescription = document.querySelector('.hero-description');
if (heroDescription) {
anime({
targets: heroDescription,
opacity: [0, 1],
translateY: [20, 0],
delay: 1200,
duration: 1000,
easing: 'easeOutExpo'
});
}
const heroButtons = document.querySelectorAll('.hero-buttons .btn');
if (heroButtons.length > 0) {
anime({
targets: heroButtons,
opacity: [0, 1],
scale: [0.8, 1],
delay: anime.stagger(100, {start: 1500}),
duration: 800,
easing: 'easeOutBack'
});
}
const socialIcons = document.querySelectorAll('.hero-social .social-icon');
if (socialIcons.length > 0) {
anime({
targets: socialIcons,
opacity: [0, 1],
scale: [0, 1],
rotate: [180, 0],
delay: anime.stagger(100, {start: 2000}),
duration: 800,
easing: 'easeOutBack'
});
}
const profileImage = document.getElementById('profileImage');
if (profileImage) {
anime({
targets: profileImage,
opacity: [0, 1],
scale: [0.8, 1],
rotate: [180, 0],
delay: 1000,
duration: 1500,
easing: 'easeOutElastic(1, .8)'
});
profileImage.addEventListener('mouseenter', () => {
anime({
targets: profileImage,
scale: [1, 1.1],
rotate: [0, 5],
duration: 500,
easing: 'easeOutElastic(1, .8)'
});
});
profileImage.addEventListener('mouseleave', () => {
anime({
targets: profileImage,
scale: [1.1, 1],
rotate: [5, 0],
duration: 500,
easing: 'easeOutElastic(1, .8)'
});
});
}
const badges = document.querySelectorAll('.floating-badge');
if (badges.length > 0) {
badges.forEach((badge, index) => {
anime({
targets: badge,
opacity: [0, 1],
scale: [0, 1],
delay: 1500 + (index * 200),
duration: 800,
easing: 'easeOutBack'
});
});
}
}
function initSkillAnimations() {
const skillsSection = document.getElementById('skills');
if (!skillsSection) return;
const skillItems = skillsSection.querySelectorAll('.skill-item');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const skillItem = entry.target;
const progressBar = skillItem.querySelector('.skill-progress');
const percentElement = skillItem.querySelector('.skill-percent');
const percent = parseInt(skillItem.getAttribute('data-percent') || 0);
if (progressBar && typeof anime !== 'undefined') {
anime({
targets: progressBar,
width: ['0%', percent + '%'],
duration: 2000,
easing: 'easeOutExpo',
delay: 300
});
anime({
targets: { value: 0 },
value: percent,
duration: 2000,
easing: 'easeOutExpo',
delay: 300,
update: function(anim) {
if (percentElement) {
percentElement.textContent = Math.floor(anim.animatables[0].target.value) + '%';
}
}
});
}
observer.unobserve(skillItem);
}
});
}, { threshold: 0.5 });
skillItems.forEach(item => observer.observe(item));
}
function initTimelineAnimations() {
const timelineItems = document.querySelectorAll('.timeline-item');
timelineItems.forEach((item, index) => {
inView(item, () => {
if (typeof anime !== 'undefined') {
anime({
targets: item,
opacity: [0, 1],
translateX: [-50, 0],
delay: index * 150,
duration: 1000,
easing: 'easeOutExpo'
});
} else {
animateElement(item, { opacity: [0, 1], x: [-50, 0] }, { duration: 0.8, delay: index * 0.1 });
}
}, { amount: 0.3 });
});
}
function initProjectAnimations() {
const projectCards = document.querySelectorAll('.project-card');
projectCards.forEach((card, index) => {
inView(card, () => {
if (typeof anime !== 'undefined') {
anime({
targets: card,
opacity: [0, 1],
translateY: [50, 0],
scale: [0.9, 1],
delay: index * 100,
duration: 1000,
easing: 'easeOutExpo'
});
} else {
animateElement(card, { opacity: [0, 1], y: [50, 0], scale: [0.9, 1] }, { duration: 0.8, delay: index * 0.1 });
}
}, { amount: 0.2 });
card.addEventListener('mouseenter', () => {
if (typeof anime !== 'undefined') {
anime({ targets: card, scale: [1, 1.02], duration: 300, easing: 'easeOutQuad' });
}
});
card.addEventListener('mouseleave', () => {
if (typeof anime !== 'undefined') {
anime({ targets: card, scale: [1.02, 1], duration: 300, easing: 'easeOutQuad' });
}
});
});
}
function initScrollAnimations() {
const sections = document.querySelectorAll('.section');
sections.forEach(section => {
inView(section, () => {
const sectionHeader = section.querySelector('.section-header');
if (sectionHeader && typeof anime !== 'undefined') {
anime({
targets: sectionHeader,
opacity: [0, 1],
translateY: [-20, 0],
duration: 600,
easing: 'easeOutExpo'
});
}
}, { amount: 0.2 });
});
const cards = document.querySelectorAll('.card, .project-card, .contact-item');
cards.forEach((card, index) => {
inView(card, () => {
if (typeof anime !== 'undefined') {
anime({
targets: card,
opacity: [0, 1],
translateY: [30, 0],
delay: index * 30,
duration: 500,
easing: 'easeOutExpo'
});
} else {
animateElement(card, { opacity: [0, 1], y: [50, 0] }, { duration: 0.6, delay: index * 0.05 });
}
}, { amount: 0.2 });
});
}
function animateStats() {
const statNumbers = document.querySelectorAll('.stat-number');
statNumbers.forEach(stat => {
const target = parseInt(stat.getAttribute('data-count') || 0);
inView(stat, () => {
if (typeof anime !== 'undefined') {
anime({
targets: { value: 0 },
value: target,
duration: 2000,
easing: 'easeOutExpo',
update: function(anim) {
stat.textContent = Math.floor(anim.animatables[0].target.value);
}
});
}
}, { amount: 0.5 });
});
}
function initContactAnimations() {
const contactItems = document.querySelectorAll('.contact-item');
contactItems.forEach(item => {
item.addEventListener('mouseenter', () => {
if (typeof anime !== 'undefined') {
anime({ targets: item, scale: [1, 1.02], duration: 200, easing: 'easeOutQuad' });
}
});
item.addEventListener('mouseleave', () => {
if (typeof anime !== 'undefined') {
anime({ targets: item, scale: [1.02, 1], duration: 200, easing: 'easeOutQuad' });
}
});
});
}
function initParallax() {
const profileImage = document.getElementById('profileImage');
if (!profileImage) return;
let ticking = false;
window.addEventListener('scroll', () => {
if (!ticking) {
window.requestAnimationFrame(() => {
const scrolled = window.pageYOffset;
const parallaxSpeed = 0.3;
const maxOffset = 100;
const offset = Math.min(scrolled * parallaxSpeed, maxOffset);
if (profileImage) {
profileImage.style.transform = `translateY(${offset}px)`;
}
const gridBg = document.querySelector('.code-grid-bg');
if (gridBg) {
gridBg.style.transform = `translateY(${scrolled * 0.2}px)`;
}
ticking = false;
});
ticking = true;
}
});
}
function initSmoothScroll() {
const sections = document.querySelectorAll('section[id]');
const navLinks = document.querySelectorAll('.nav-link[href^="#"]');
navLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = link.getAttribute('href');
const targetSection = document.querySelector(targetId);
if (targetSection) {
const headerHeight = document.querySelector('.main-header').offsetHeight;
const targetPosition = targetSection.offsetTop - headerHeight;
if (typeof anime !== 'undefined') {
anime({
targets: window,
scrollTop: targetPosition,
duration: 800,
easing: 'easeInOutQuad'
});
} else {
window.scrollTo({
top: targetPosition,
behavior: 'smooth'
});
}
}
});
});
let currentSection = '';
window.addEventListener('scroll', () => {
const scrollPos = window.scrollY + 150;
sections.forEach(section => {
const sectionTop = section.offsetTop;
const sectionHeight = section.offsetHeight;
const sectionId = section.getAttribute('id');
if (scrollPos >= sectionTop && scrollPos < sectionTop + sectionHeight) {
if (currentSection !== sectionId) {
currentSection = sectionId;
navLinks.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('href') === `#${sectionId}`) {
link.classList.add('active');
}
});
}
}
});
});
}
window.Animations = {
initParallax,
initSmoothScroll
};