File size: 6,912 Bytes
b8d618b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
// 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! ๐Ÿš€');