Spaces:
Running
Running
| // Portfolio Website JavaScript | |
| // Interactive functionality for navigation, theme toggle, and user experience | |
| (function() { | |
| 'use strict'; | |
| // DOM Elements | |
| const themeToggle = document.getElementById('themeToggle'); | |
| const navToggle = document.querySelector('.nav-toggle'); | |
| const navLinks = document.querySelector('.nav-links'); | |
| const header = document.querySelector('.site-header'); | |
| const resumeBtn = document.getElementById('resumeBtn'); | |
| // Theme Management | |
| class ThemeManager { | |
| constructor() { | |
| this.currentTheme = localStorage.getItem('theme') || 'light'; | |
| this.init(); | |
| } | |
| init() { | |
| this.applyTheme(this.currentTheme); | |
| this.updateThemeIcon(); | |
| if (themeToggle) { | |
| themeToggle.addEventListener('click', () => this.toggleTheme()); | |
| } | |
| } | |
| toggleTheme() { | |
| this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light'; | |
| this.applyTheme(this.currentTheme); | |
| this.updateThemeIcon(); | |
| localStorage.setItem('theme', this.currentTheme); | |
| } | |
| applyTheme(theme) { | |
| document.documentElement.setAttribute('data-theme', theme); | |
| } | |
| updateThemeIcon() { | |
| if (themeToggle) { | |
| themeToggle.innerHTML = this.currentTheme === 'light' ? '🌙' : '☀️'; | |
| themeToggle.setAttribute('aria-label', | |
| `Switch to ${this.currentTheme === 'light' ? 'dark' : 'light'} mode`); | |
| } | |
| } | |
| } | |
| // Navigation Management | |
| class NavigationManager { | |
| constructor() { | |
| this.isMenuOpen = false; | |
| this.init(); | |
| } | |
| init() { | |
| // Mobile menu toggle | |
| if (navToggle && navLinks) { | |
| navToggle.addEventListener('click', () => this.toggleMobileMenu()); | |
| } | |
| // Close mobile menu when clicking on links | |
| if (navLinks) { | |
| navLinks.addEventListener('click', (e) => { | |
| if (e.target.tagName === 'A') { | |
| this.closeMobileMenu(); | |
| } | |
| }); | |
| } | |
| // Close mobile menu when clicking outside | |
| document.addEventListener('click', (e) => { | |
| if (this.isMenuOpen && !e.target.closest('.nav')) { | |
| this.closeMobileMenu(); | |
| } | |
| }); | |
| // Smooth scrolling for anchor links | |
| this.initSmoothScrolling(); | |
| // Header scroll effect | |
| this.initHeaderScrollEffect(); | |
| } | |
| toggleMobileMenu() { | |
| this.isMenuOpen = !this.isMenuOpen; | |
| navLinks.classList.toggle('active', this.isMenuOpen); | |
| navToggle.setAttribute('aria-expanded', this.isMenuOpen.toString()); | |
| navToggle.innerHTML = this.isMenuOpen ? '✕' : '☰'; | |
| } | |
| closeMobileMenu() { | |
| this.isMenuOpen = false; | |
| navLinks.classList.remove('active'); | |
| navToggle.setAttribute('aria-expanded', 'false'); | |
| navToggle.innerHTML = '☰'; | |
| } | |
| initSmoothScrolling() { | |
| document.querySelectorAll('a[href^="#"]').forEach(anchor => { | |
| anchor.addEventListener('click', (e) => { | |
| const href = anchor.getAttribute('href'); | |
| if (href === '#' || href === '#top') { | |
| e.preventDefault(); | |
| window.scrollTo({ top: 0, behavior: 'smooth' }); | |
| return; | |
| } | |
| const target = document.querySelector(href); | |
| if (target) { | |
| e.preventDefault(); | |
| const headerHeight = header ? header.offsetHeight : 0; | |
| const targetPosition = target.offsetTop - headerHeight - 20; | |
| window.scrollTo({ | |
| top: targetPosition, | |
| behavior: 'smooth' | |
| }); | |
| } | |
| }); | |
| }); | |
| } | |
| initHeaderScrollEffect() { | |
| if (!header) return; | |
| let lastScrollY = window.scrollY; | |
| let ticking = false; | |
| const updateHeader = () => { | |
| const scrollY = window.scrollY; | |
| if (scrollY > 100) { | |
| header.style.background = 'rgba(255, 255, 255, 0.95)'; | |
| header.style.backdropFilter = 'blur(10px)'; | |
| } else { | |
| header.style.background = ''; | |
| header.style.backdropFilter = ''; | |
| } | |
| lastScrollY = scrollY; | |
| ticking = false; | |
| }; | |
| const requestTick = () => { | |
| if (!ticking) { | |
| requestAnimationFrame(updateHeader); | |
| ticking = true; | |
| } | |
| }; | |
| window.addEventListener('scroll', requestTick, { passive: true }); | |
| } | |
| } | |
| // Resume Download Manager | |
| class ResumeManager { | |
| constructor() { | |
| this.init(); | |
| } | |
| init() { | |
| if (resumeBtn) { | |
| resumeBtn.addEventListener('click', (e) => this.handleResumeDownload(e)); | |
| } | |
| } | |
| handleResumeDownload(e) { | |
| e.preventDefault(); | |
| // Show loading state | |
| const originalText = resumeBtn.innerHTML; | |
| resumeBtn.innerHTML = '📄 Downloading...'; | |
| resumeBtn.classList.add('loading'); | |
| // Create download link | |
| const link = document.createElement('a'); | |
| link.href = 'Rakesh Resume.pdf'; | |
| link.download = 'Rakesh_Kumar_Resume.pdf'; | |
| link.style.display = 'none'; | |
| document.body.appendChild(link); | |
| // Trigger download | |
| try { | |
| link.click(); | |
| // Track download (if analytics is available) | |
| if (typeof gtag !== 'undefined') { | |
| gtag('event', 'download', { | |
| 'event_category': 'Resume', | |
| 'event_label': 'PDF Download' | |
| }); | |
| } | |
| // Show success message | |
| setTimeout(() => { | |
| resumeBtn.innerHTML = '✅ Downloaded!'; | |
| setTimeout(() => { | |
| resumeBtn.innerHTML = originalText; | |
| resumeBtn.classList.remove('loading'); | |
| }, 2000); | |
| }, 500); | |
| } catch (error) { | |
| console.error('Download failed:', error); | |
| resumeBtn.innerHTML = '❌ Download Failed'; | |
| setTimeout(() => { | |
| resumeBtn.innerHTML = originalText; | |
| resumeBtn.classList.remove('loading'); | |
| }, 2000); | |
| } finally { | |
| document.body.removeChild(link); | |
| } | |
| } | |
| } | |
| // Animation and Intersection Observer | |
| class AnimationManager { | |
| constructor() { | |
| this.init(); | |
| } | |
| init() { | |
| // Intersection Observer for fade-in animations | |
| if ('IntersectionObserver' in window) { | |
| this.initScrollAnimations(); | |
| } | |
| // Typing animation for hero subtitle | |
| this.initTypingAnimation(); | |
| } | |
| initScrollAnimations() { | |
| const observerOptions = { | |
| threshold: 0.1, | |
| rootMargin: '0px 0px -50px 0px' | |
| }; | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| entry.target.style.opacity = '1'; | |
| entry.target.style.transform = 'translateY(0)'; | |
| } | |
| }); | |
| }, observerOptions); | |
| // Observe elements for animation | |
| document.querySelectorAll('.card, .timeline li, .case').forEach(el => { | |
| el.style.opacity = '0'; | |
| el.style.transform = 'translateY(30px)'; | |
| el.style.transition = 'opacity 0.6s ease, transform 0.6s ease'; | |
| observer.observe(el); | |
| }); | |
| } | |
| initTypingAnimation() { | |
| const subtitle = document.querySelector('.hero-subtitle'); | |
| if (!subtitle) return; | |
| const text = subtitle.textContent; | |
| subtitle.textContent = ''; | |
| subtitle.style.borderRight = '2px solid var(--accent-primary)'; | |
| let i = 0; | |
| const typeWriter = () => { | |
| if (i < text.length) { | |
| subtitle.textContent += text.charAt(i); | |
| i++; | |
| setTimeout(typeWriter, 50); | |
| } else { | |
| // Remove cursor after typing is complete | |
| setTimeout(() => { | |
| subtitle.style.borderRight = 'none'; | |
| }, 1000); | |
| } | |
| }; | |
| // Start typing animation after a short delay | |
| setTimeout(typeWriter, 1000); | |
| } | |
| } | |
| // Utility Functions | |
| class Utils { | |
| static debounce(func, wait) { | |
| let timeout; | |
| return function executedFunction(...args) { | |
| const later = () => { | |
| clearTimeout(timeout); | |
| func(...args); | |
| }; | |
| clearTimeout(timeout); | |
| timeout = setTimeout(later, wait); | |
| }; | |
| } | |
| static throttle(func, limit) { | |
| let inThrottle; | |
| return function() { | |
| const args = arguments; | |
| const context = this; | |
| if (!inThrottle) { | |
| func.apply(context, args); | |
| inThrottle = true; | |
| setTimeout(() => inThrottle = false, limit); | |
| } | |
| }; | |
| } | |
| static isElementInViewport(el) { | |
| const rect = el.getBoundingClientRect(); | |
| return ( | |
| rect.top >= 0 && | |
| rect.left >= 0 && | |
| rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && | |
| rect.right <= (window.innerWidth || document.documentElement.clientWidth) | |
| ); | |
| } | |
| } | |
| // Performance Monitoring | |
| class PerformanceMonitor { | |
| constructor() { | |
| this.init(); | |
| } | |
| init() { | |
| // Log page load performance | |
| window.addEventListener('load', () => { | |
| if ('performance' in window) { | |
| const perfData = performance.getEntriesByType('navigation')[0]; | |
| console.log('Page Load Time:', perfData.loadEventEnd - perfData.fetchStart, 'ms'); | |
| } | |
| }); | |
| } | |
| } | |
| // Error Handling | |
| class ErrorHandler { | |
| constructor() { | |
| this.init(); | |
| } | |
| init() { | |
| window.addEventListener('error', (e) => { | |
| console.error('JavaScript Error:', e.error); | |
| // Could send to analytics or error reporting service | |
| }); | |
| window.addEventListener('unhandledrejection', (e) => { | |
| console.error('Unhandled Promise Rejection:', e.reason); | |
| // Could send to analytics or error reporting service | |
| }); | |
| } | |
| } | |
| // Initialize Application | |
| class App { | |
| constructor() { | |
| this.init(); | |
| } | |
| init() { | |
| // Wait for DOM to be ready | |
| if (document.readyState === 'loading') { | |
| document.addEventListener('DOMContentLoaded', () => this.start()); | |
| } else { | |
| this.start(); | |
| } | |
| } | |
| start() { | |
| try { | |
| // Initialize all managers | |
| new ThemeManager(); | |
| new NavigationManager(); | |
| new ResumeManager(); | |
| new AnimationManager(); | |
| new PerformanceMonitor(); | |
| new ErrorHandler(); | |
| console.log('Portfolio website initialized successfully!'); | |
| } catch (error) { | |
| console.error('Failed to initialize portfolio:', error); | |
| } | |
| } | |
| } | |
| // Start the application | |
| new App(); | |
| })(); | |
| // Export for potential module use | |
| if (typeof module !== 'undefined' && module.exports) { | |
| module.exports = { App }; | |
| } |