API_portfolio / static /script.js
NGOC1712's picture
Upload 21 files
b8d618b verified
// 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! ๐Ÿš€');