Spaces:
Running
Running
| // Initialize Lucide Icons | |
| lucide.createIcons(); | |
| // ===== MOBILE MENU ===== | |
| const menuBtn = document.getElementById('menuBtn'); | |
| const closeMenuBtn = document.getElementById('closeMenu'); | |
| const mobileMenu = document.getElementById('mobileMenu'); | |
| const mobileLinks = document.querySelectorAll('.mobile-nav-link'); | |
| function toggleMenu() { | |
| mobileMenu.classList.toggle('open'); | |
| } | |
| menuBtn.addEventListener('click', toggleMenu); | |
| closeMenuBtn.addEventListener('click', toggleMenu); | |
| mobileLinks.forEach(link => { | |
| link.addEventListener('click', () => { | |
| mobileMenu.classList.remove('open'); | |
| }); | |
| }); | |
| // ===== NAVBAR SCROLL EFFECT ===== | |
| const navbar = document.getElementById('navbar'); | |
| window.addEventListener('scroll', () => { | |
| if (window.scrollY > 50) { | |
| navbar.classList.add('scrolled'); | |
| } else { | |
| navbar.classList.remove('scrolled'); | |
| } | |
| }); | |
| // ===== ACTIVE NAV LINK ===== | |
| const sections = document.querySelectorAll('section[id]'); | |
| const navLinks = document.querySelectorAll('.nav-link'); | |
| function updateActiveNav() { | |
| const scrollY = window.scrollY + 100; | |
| sections.forEach(section => { | |
| const sectionTop = section.offsetTop; | |
| const sectionHeight = section.offsetHeight; | |
| const sectionId = section.getAttribute('id'); | |
| if (scrollY >= sectionTop && scrollY < sectionTop + sectionHeight) { | |
| navLinks.forEach(link => { | |
| link.classList.remove('active'); | |
| if (link.getAttribute('href') === '#' + sectionId) { | |
| link.classList.add('active'); | |
| } | |
| }); | |
| // Also update mobile nav | |
| mobileLinks.forEach(link => { | |
| link.classList.remove('active'); | |
| if (link.getAttribute('href') === '#' + sectionId) { | |
| link.classList.add('active'); | |
| } | |
| }); | |
| } | |
| }); | |
| } | |
| window.addEventListener('scroll', updateActiveNav); | |
| // ===== SCROLL ANIMATIONS ===== | |
| const scrollElements = document.querySelectorAll('.scroll-animate'); | |
| const observer = new IntersectionObserver( | |
| (entries) => { | |
| entries.forEach((entry, index) => { | |
| if (entry.isIntersecting) { | |
| // Add staggered delay based on element position | |
| setTimeout(() => { | |
| entry.target.classList.add('visible'); | |
| }, 100); | |
| observer.unobserve(entry.target); | |
| } | |
| }); | |
| }, | |
| { | |
| threshold: 0.1, | |
| rootMargin: '0px 0px -50px 0px', | |
| } | |
| ); | |
| scrollElements.forEach(el => observer.observe(el)); | |
| // ===== SKILL BARS ANIMATION ===== | |
| const skillObserver = new IntersectionObserver( | |
| (entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| const bar = entry.target.querySelector('.skill-bar-fill'); | |
| if (bar) { | |
| setTimeout(() => { | |
| bar.classList.add('animate'); | |
| }, 200); | |
| } | |
| skillObserver.unobserve(entry.target); | |
| } | |
| }); | |
| }, | |
| { | |
| threshold: 0.3, | |
| } | |
| ); | |
| document.querySelectorAll('.skill-item').forEach(item => { | |
| skillObserver.observe(item); | |
| }); | |
| // ===== BACK TO TOP ===== | |
| const backToTop = document.getElementById('backToTop'); | |
| window.addEventListener('scroll', () => { | |
| if (window.scrollY > 500) { | |
| backToTop.classList.add('visible'); | |
| } else { | |
| backToTop.classList.remove('visible'); | |
| } | |
| }); | |
| backToTop.addEventListener('click', () => { | |
| window.scrollTo({ top: 0, behavior: 'smooth' }); | |
| }); | |
| // ===== CONTACT FORM ===== | |
| const contactForm = document.getElementById('contactForm'); | |
| const formSuccess = document.getElementById('formSuccess'); | |
| contactForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| // Simple visual feedback | |
| const btn = contactForm.querySelector('button[type="submit"]'); | |
| btn.innerHTML = '<span>Sending...</span>'; | |
| btn.disabled = true; | |
| setTimeout(() => { | |
| contactForm.reset(); | |
| formSuccess.classList.remove('hidden'); | |
| formSuccess.style.display = 'block'; | |
| btn.innerHTML = '<i data-lucide="send" class="w-4 h-4"></i><span>Send Message</span>'; | |
| btn.disabled = false; | |
| lucide.createIcons(); | |
| setTimeout(() => { | |
| formSuccess.classList.add('hidden'); | |
| formSuccess.style.display = 'none'; | |
| }, 4000); | |
| }, 1200); | |
| }); | |
| // ===== FILE UPLOAD ===== | |
| const fileInput = document.getElementById('fileInput'); | |
| const fileDropZone = document.getElementById('fileDropZone'); | |
| const fileList = document.getElementById('fileList'); | |
| let uploadedFiles = []; | |
| function formatFileSize(bytes) { | |
| if (bytes === 0) return '0 Bytes'; | |
| const k = 1024; | |
| const sizes = ['Bytes', 'KB', 'MB']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; | |
| } | |
| function getFileIcon(fileName) { | |
| const ext = fileName.split('.').pop().toLowerCase(); | |
| const iconMap = { | |
| pdf: 'file-text', doc: 'file-text', docx: 'file-text', txt: 'file-text', | |
| png: 'image', jpg: 'image', jpeg: 'image', gif: 'image', svg: 'image', webp: 'image', | |
| zip: 'archive', rar: 'archive', | |
| }; | |
| return iconMap[ext] || 'file'; | |
| } | |
| function renderFileList() { | |
| fileList.innerHTML = ''; | |
| uploadedFiles.forEach((file, index) => { | |
| const fileItem = document.createElement('div'); | |
| fileItem.className = 'flex items-center justify-between gap-3 p-3 rounded-lg bg-white/[0.03] border border-white/[0.08] group/file transition-all'; | |
| fileItem.innerHTML = ` | |
| <div class="flex items-center gap-3 min-w-0"> | |
| <div class="w-9 h-9 rounded-lg bg-gradient-to-br from-violet-600/20 to-cyan-600/20 flex items-center justify-center flex-shrink-0 border border-violet-500/20"> | |
| <i data-lucide="${getFileIcon(file.name)}" class="w-4 h-4 text-violet-400"></i> | |
| </div> | |
| <div class="min-w-0"> | |
| <p class="text-sm font-medium text-white truncate">${file.name}</p> | |
| <p class="text-xs text-gray-500">${formatFileSize(file.size)}</p> | |
| </div> | |
| </div> | |
| <button type="button" class="remove-file flex-shrink-0 w-8 h-8 rounded-lg flex items-center justify-center text-gray-500 hover:text-red-400 hover:bg-red-500/10 transition-all" data-index="${index}"> | |
| <i data-lucide="x" class="w-4 h-4"></i> | |
| </button> | |
| `; | |
| fileList.appendChild(fileItem); | |
| }); | |
| lucide.createIcons(); | |
| // Attach remove handlers | |
| fileList.querySelectorAll('.remove-file').forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| const idx = parseInt(btn.getAttribute('data-index')); | |
| uploadedFiles.splice(idx, 1); | |
| renderFileList(); | |
| }); | |
| }); | |
| } | |
| function handleFiles(files) { | |
| for (const file of files) { | |
| if (file.size > 10 * 1024 * 1024) { | |
| alert(`File "${file.name}" exceeds 10MB limit.`); | |
| continue; | |
| } | |
| if (uploadedFiles.length >= 5) { | |
| alert('Maximum 5 files allowed.'); | |
| break; | |
| } | |
| uploadedFiles.push(file); | |
| } | |
| fileInput.value = ''; | |
| renderFileList(); | |
| } | |
| fileInput.addEventListener('change', (e) => { | |
| handleFiles(e.target.files); | |
| }); | |
| fileDropZone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| fileDropZone.classList.add('border-violet-500/50', 'bg-violet-500/5'); | |
| }); | |
| fileDropZone.addEventListener('dragleave', (e) => { | |
| e.preventDefault(); | |
| fileDropZone.classList.remove('border-violet-500/50', 'bg-violet-500/5'); | |
| }); | |
| fileDropZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| fileDropZone.classList.remove('border-violet-500/50', 'bg-violet-500/5'); | |
| handleFiles(e.dataTransfer.files); | |
| }); | |
| // ===== TYPING EFFECT (simulated with CSS already) ===== | |
| // Re-initialize in case dynamic content was added | |
| setTimeout(() => { | |
| lucide.createIcons(); | |
| }, 100); | |
| // ===== SMOOTH SCROLL FOR ANCHOR LINKS ===== | |
| document.querySelectorAll('a[href^="#"]').forEach(anchor => { | |
| anchor.addEventListener('click', function (e) { | |
| e.preventDefault(); | |
| const target = document.querySelector(this.getAttribute('href')); | |
| if (target) { | |
| const offset = 80; | |
| const targetPosition = target.getBoundingClientRect().top + window.pageYOffset - offset; | |
| window.scrollTo({ | |
| top: targetPosition, | |
| behavior: 'smooth' | |
| }); | |
| } | |
| }); | |
| }); | |
| // ===== PARALLAX ORBs ===== | |
| window.addEventListener('scroll', () => { | |
| const scrolled = window.pageYOffset; | |
| const orbs = document.querySelectorAll('.orb'); | |
| orbs.forEach((orb, i) => { | |
| const speed = 0.05 * (i + 1); | |
| orb.style.transform = `translateY(${scrolled * speed}px)`; | |
| }); | |
| }); |