BinKhoaLe1812's picture
Upload 6 files
3a4efe4 verified
// Enhanced theme toggle with smooth transitions
const toggle = document.getElementById('themeToggle');
const root = document.documentElement;
const saved = localStorage.getItem('theme') || 'dark';
// Apply saved theme
if (saved === 'light') {
document.documentElement.classList.remove('dark');
} else {
document.documentElement.classList.add('dark');
}
// Update theme toggle icon
const updateThemeIcon = () => {
const icon = toggle?.querySelector('i');
if (icon) {
icon.setAttribute('data-lucide', document.documentElement.classList.contains('dark') ? 'sun-medium' : 'moon');
if (window.lucide) {
lucide.createIcons();
}
}
};
// Initialize theme icon
updateThemeIcon();
toggle?.addEventListener('click', () => {
const isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
updateThemeIcon();
// Add smooth transition effect
document.body.style.transition = 'background-color 0.3s ease, color 0.3s ease';
setTimeout(() => {
document.body.style.transition = '';
}, 300);
});
// Enhanced tabs logic with smooth transitions and ARIA support
document.querySelectorAll('.tab').forEach(btn => {
btn.addEventListener('click', () => {
// Remove active class and ARIA attributes from all tabs
document.querySelectorAll('.tab').forEach(b => {
b.classList.remove('active');
b.setAttribute('aria-selected', 'false');
});
// Add active class and ARIA attributes to clicked tab
btn.classList.add('active');
btn.setAttribute('aria-selected', 'true');
// Get target diagram
const target = btn.getAttribute('data-target');
const targetDiagram = document.querySelector(target);
if (targetDiagram) {
// Hide all diagrams completely and update ARIA
document.querySelectorAll('.diagram').forEach(d => {
d.style.opacity = '0';
d.style.transform = 'translateY(20px)';
d.style.display = 'none';
d.setAttribute('aria-hidden', 'true');
d.classList.remove('visible');
});
// Show target diagram with fade in and update ARIA
setTimeout(() => {
targetDiagram.style.display = 'block';
targetDiagram.classList.add('visible');
targetDiagram.style.opacity = '1';
targetDiagram.style.transform = 'translateY(0)';
targetDiagram.setAttribute('aria-hidden', 'false');
}, 150);
}
});
});
// Initialize first tab as active and show first diagram
document.addEventListener('DOMContentLoaded', () => {
// Hide all diagrams except the first one
document.querySelectorAll('.diagram').forEach((diagram, index) => {
if (index === 0) {
// First diagram should be visible
diagram.style.display = 'block';
diagram.style.opacity = '1';
diagram.style.transform = 'translateY(0)';
diagram.classList.add('visible');
diagram.setAttribute('aria-hidden', 'false');
} else {
// Other diagrams should be hidden
diagram.style.display = 'none';
diagram.style.opacity = '0';
diagram.style.transform = 'translateY(20px)';
diagram.classList.remove('visible');
diagram.setAttribute('aria-hidden', 'true');
}
});
// Ensure Mermaid diagrams are properly initialized
if (window.mermaid) {
mermaid.init(undefined, document.querySelectorAll('.mermaid'));
}
});
// Enhanced metric counters with better performance
const counters = document.querySelectorAll('.metric-value');
const easeOutCubic = t => 1 - Math.pow(1 - t, 3);
const easeOutQuart = t => 1 - Math.pow(1 - t, 4);
const animateCount = (el, to, duration = 1200) => {
const start = performance.now();
const from = 0;
const isLargeNumber = to > 1000;
const easing = isLargeNumber ? easeOutQuart : easeOutCubic;
// Ensure the element has proper positioning
el.style.position = 'relative';
el.style.zIndex = '2';
el.style.width = '100%';
el.style.textAlign = 'center';
el.style.display = 'block';
const step = (now) => {
const p = Math.min(1, (now - start) / duration);
const v = Math.floor(from + (to - from) * easing(p));
// Format number with appropriate separators
if (to >= 1000000) {
el.textContent = (v / 1000000).toFixed(1) + 'M';
} else if (to >= 1000) {
el.textContent = (v / 1000).toFixed(1) + 'K';
} else {
el.textContent = v.toLocaleString();
}
if (p < 1) {
requestAnimationFrame(step);
} else {
// Ensure final value is exact
if (to >= 1000000) {
el.textContent = (to / 1000000).toFixed(1) + 'M';
} else if (to >= 1000) {
el.textContent = (to / 1000).toFixed(1) + 'K';
} else {
el.textContent = to.toLocaleString();
}
}
};
requestAnimationFrame(step);
};
// Enhanced intersection observer with better performance
const observer = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) {
const el = e.target;
const count = parseInt(el.dataset.count, 10) || 0;
// Add loading state
el.classList.add('loading');
// Animate with slight delay for better visual effect
setTimeout(() => {
animateCount(el, count);
el.classList.remove('loading');
}, 200);
observer.unobserve(el);
}
});
}, {
threshold: 0.3,
rootMargin: '0px 0px -50px 0px'
});
// Observe counters
counters.forEach(c => observer.observe(c));
// Enhanced VanillaTilt initialization with error handling
const initTilt = () => {
if (window.VanillaTilt) {
try {
document.querySelectorAll('[data-tilt]').forEach(el => {
VanillaTilt.init(el, {
max: 6,
speed: 400,
glare: true,
'max-glare': 0.1,
scale: 1.02,
gyroscope: false
});
});
} catch (error) {
console.warn('VanillaTilt initialization failed:', error);
}
}
};
// Initialize tilt effects
initTilt();
// Smooth scrolling for navigation links
document.querySelectorAll('a[href^="#"]').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = link.getAttribute('href');
const targetElement = document.querySelector(targetId);
if (targetElement) {
const headerHeight = document.querySelector('header')?.offsetHeight || 0;
const targetPosition = targetElement.offsetTop - headerHeight - 20;
window.scrollTo({
top: targetPosition,
behavior: 'smooth'
});
}
});
});
// Enhanced error handling for external resources
const handleResourceError = (resource, fallback) => {
resource.addEventListener('error', () => {
console.warn(`Failed to load resource, using fallback`);
if (fallback) {
resource.src = fallback;
}
});
};
// Handle external script loading errors
window.addEventListener('error', (e) => {
if (e.target.tagName === 'SCRIPT' && e.target.src) {
console.warn(`Script failed to load: ${e.target.src}`);
}
});
// Performance monitoring
const logPerformance = () => {
if ('performance' in window) {
window.addEventListener('load', () => {
setTimeout(() => {
const perfData = performance.getEntriesByType('navigation')[0];
console.log(`Page load time: ${perfData.loadEventEnd - perfData.loadEventStart}ms`);
}, 0);
});
}
};
// Initialize performance monitoring
logPerformance();
// Add keyboard navigation support
document.addEventListener('keydown', (e) => {
// Tab navigation for architecture tabs
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
const activeTab = document.querySelector('.tab.active');
if (activeTab) {
const tabs = Array.from(document.querySelectorAll('.tab'));
const currentIndex = tabs.indexOf(activeTab);
let nextIndex;
if (e.key === 'ArrowLeft') {
nextIndex = currentIndex > 0 ? currentIndex - 1 : tabs.length - 1;
} else {
nextIndex = currentIndex < tabs.length - 1 ? currentIndex + 1 : 0;
}
tabs[nextIndex].click();
tabs[nextIndex].focus();
}
}
});
// Add loading states for dynamic content
const addLoadingState = (element, duration = 1000) => {
element.classList.add('loading');
setTimeout(() => {
element.classList.remove('loading');
}, duration);
};
// Initialize all interactive elements
document.addEventListener('DOMContentLoaded', () => {
// Add loading states to cards that might load content dynamically
document.querySelectorAll('.card').forEach(card => {
card.addEventListener('mouseenter', () => {
addLoadingState(card, 500);
});
});
// Initialize tooltips for chips
document.querySelectorAll('.chip').forEach(chip => {
chip.setAttribute('title', chip.textContent);
});
});