// Smooth scrolling for navigation links document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); // Navbar scroll effect window.addEventListener('scroll', function() { const navbar = document.querySelector('.navbar'); if (window.scrollY > 50) { navbar.style.background = 'rgba(255, 255, 255, 0.98)'; navbar.style.boxShadow = '0 5px 20px rgba(15, 23, 42, 0.1)'; } else { navbar.style.background = 'rgba(255, 255, 255, 0.95)'; navbar.style.boxShadow = 'none'; } }); // Mobile menu toggle const hamburger = document.querySelector('.hamburger'); const navMenu = document.querySelector('.nav-menu'); hamburger?.addEventListener('click', function() { navMenu.classList.toggle('active'); this.classList.toggle('active'); }); // Animate circular progress rings on scroll const observerOptions = { threshold: 0.3, rootMargin: '0px' }; const observer = new IntersectionObserver(function(entries) { entries.forEach(entry => { if (entry.isIntersecting) { const techIcons = entry.target.querySelectorAll('.tech-icon'); techIcons.forEach((icon, index) => { const skill = icon.getAttribute('data-skill'); const circle = icon.querySelector('.progress-ring-circle'); const circumference = 2 * Math.PI * 40; // radius = 40 const offset = circumference - (skill / 100) * circumference; setTimeout(() => { circle.style.strokeDashoffset = offset; }, index * 100); }); } }); }, observerOptions); const skillsSection = document.querySelector('.skills'); if (skillsSection) { observer.observe(skillsSection); } // Interactive hover effects for tech icons document.addEventListener('DOMContentLoaded', function() { const techIcons = document.querySelectorAll('.tech-icon'); techIcons.forEach(icon => { const skill = icon.getAttribute('data-skill'); const circle = icon.querySelector('.progress-ring-circle'); const percentage = icon.querySelector('.percentage'); const circumference = 2 * Math.PI * 40; // radius = 40 // Set initial state circle.style.strokeDasharray = circumference; circle.style.strokeDashoffset = circumference; icon.addEventListener('mouseenter', function() { const offset = circumference - (skill / 100) * circumference; circle.style.strokeDashoffset = offset; percentage.textContent = skill + '%'; }); icon.addEventListener('mouseleave', function() { circle.style.strokeDashoffset = circumference; }); }); }); // Animate elements on scroll const animateOnScroll = () => { const elements = document.querySelectorAll('.timeline-item, .project-card, .stat-card'); elements.forEach(element => { const elementTop = element.getBoundingClientRect().top; const elementBottom = element.getBoundingClientRect().bottom; if (elementTop < window.innerHeight && elementBottom > 0) { element.style.opacity = '1'; element.style.transform = 'translateY(0)'; } }); }; window.addEventListener('scroll', animateOnScroll); window.addEventListener('load', animateOnScroll); // Character-by-character streaming effect for specific lines const streamingElement = document.getElementById('streaming-text'); if (streamingElement) { const textLines = [ 'Hi, nice to meet you!', "I'm Tran Bao Ngoc", 'A passionate AI developer', 'I love Python 🐍', 'I love researching AI 🤖', 'I love playing games 🎮' ]; let currentLineIndex = 0; let currentCharIndex = 0; let isDeleting = false; let typingSpeed = 45; let deleteSpeed = 25; function typewriterEffect() { const currentLine = textLines[currentLineIndex]; if (!isDeleting) { // Typing characters if (currentCharIndex <= currentLine.length) { streamingElement.innerHTML = currentLine.substring(0, currentCharIndex); currentCharIndex++; if (currentCharIndex > currentLine.length) { // Finished typing line, pause then start deleting setTimeout(() => { isDeleting = true; typewriterEffect(); }, 1800); // Pause for 1.8 seconds } else { setTimeout(typewriterEffect, typingSpeed + Math.random() * 20); } } } else { // Deleting characters if (currentCharIndex > 0) { currentCharIndex--; streamingElement.innerHTML = currentLine.substring(0, currentCharIndex); setTimeout(typewriterEffect, deleteSpeed); } else { // Finished deleting, move to next line isDeleting = false; currentLineIndex = (currentLineIndex + 1) % textLines.length; // Pause before starting next line setTimeout(typewriterEffect, 500); } } } // Start the typewriter effect after a delay setTimeout(typewriterEffect, 2000); } // Parallax effect for shapes window.addEventListener('scroll', () => { const shapes = document.querySelectorAll('.shape'); const scrollY = window.pageYOffset; shapes.forEach((shape, index) => { const speed = 0.5 + (index * 0.1); shape.style.transform = `translateY(${scrollY * speed}px)`; }); }); // Add active class to current navigation item window.addEventListener('scroll', () => { const sections = document.querySelectorAll('section'); const navLinks = document.querySelectorAll('.nav-link'); let current = ''; sections.forEach(section => { const sectionTop = section.offsetTop - 100; if (window.pageYOffset >= sectionTop) { current = section.getAttribute('id'); } }); navLinks.forEach(link => { link.classList.remove('active'); if (link.getAttribute('href').substring(1) === current) { link.classList.add('active'); } }); }); console.log('Portfolio loaded successfully! 🚀');