File manager - Edit - /home/webapp69.cm.in.th/u69319090007/portfolio/script.js
Back
document.addEventListener('DOMContentLoaded', () => { // ========================================================================== // 1. CUSTOM CURSOR FOLLOWER // ========================================================================== const cursor = document.getElementById('custom-cursor'); const cursorGlow = document.getElementById('custom-cursor-glow'); if (cursor && cursorGlow) { let glowAnimation = null; document.addEventListener('mousemove', (e) => { cursor.style.left = `${e.clientX}px`; cursor.style.top = `${e.clientY}px`; // Cancel the previous animation frame to prevent memory leaks and CPU spikes if (glowAnimation) { glowAnimation.cancel(); } glowAnimation = cursorGlow.animate({ left: `${e.clientX}px`, top: `${e.clientY}px` }, { duration: 120, fill: 'forwards' }); }); // Hover effect for interactive elements const interactables = 'a, button, input, textarea, .project-card, .theme-toggle'; document.querySelectorAll(interactables).forEach(el => { el.addEventListener('mouseenter', () => { cursor.classList.add('active'); cursorGlow.classList.add('active'); }); el.addEventListener('mouseleave', () => { cursor.classList.remove('active'); cursorGlow.classList.remove('active'); }); }); } // ========================================================================== // 2. MOBILE HAMBURGER MENU // ========================================================================== const hamburger = document.getElementById('hamburger-toggle'); const mobileMenu = document.getElementById('mobile-menu'); const mobileLinks = document.querySelectorAll('.mobile-link'); if (hamburger && mobileMenu) { hamburger.addEventListener('click', () => { hamburger.classList.toggle('active'); mobileMenu.classList.toggle('active'); }); // Close mobile menu when clicking links mobileLinks.forEach(link => { link.addEventListener('click', () => { hamburger.classList.remove('active'); mobileMenu.classList.remove('active'); }); }); } // ========================================================================== // 3. STICKY NAVBAR & ACTIVE SCROLL TRACKING // ========================================================================== const navbar = document.getElementById('navbar'); window.addEventListener('scroll', () => { if (window.scrollY > 50) { navbar.classList.add('scrolled'); } else { navbar.classList.remove('scrolled'); } }); // Highlight active section link on scroll const sections = document.querySelectorAll('section[id]'); const navLinks = document.querySelectorAll('.nav-link'); const mobLinks = document.querySelectorAll('.mobile-link'); function highlightNavigation() { const scrollY = window.scrollY; sections.forEach(current => { const sectionHeight = current.offsetHeight; const sectionTop = current.offsetTop - 120; // offset navbar height const sectionId = current.getAttribute('id'); if (scrollY > sectionTop && scrollY <= sectionTop + sectionHeight) { // Set active to desktop links navLinks.forEach(link => { link.classList.remove('active'); if (link.getAttribute('href') === `#${sectionId}`) { link.classList.add('active'); } }); // Set active to mobile links mobLinks.forEach(link => { link.classList.remove('active'); if (link.getAttribute('href') === `#${sectionId}`) { link.classList.add('active'); } }); } }); } window.addEventListener('scroll', highlightNavigation); // ========================================================================== // 4. DARK / LIGHT THEME TOGGLER // ========================================================================== const themeToggleBtn = document.getElementById('theme-toggle'); const htmlElement = document.documentElement; // Check system preference or localStorage const savedTheme = localStorage.getItem('theme'); const systemPrefersLight = window.matchMedia('(prefers-color-scheme: light)').matches; const initialTheme = savedTheme ? savedTheme : (systemPrefersLight ? 'light' : 'dark'); htmlElement.setAttribute('data-theme', initialTheme); themeToggleBtn.addEventListener('click', () => { const currentTheme = htmlElement.getAttribute('data-theme'); const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; htmlElement.setAttribute('data-theme', newTheme); localStorage.setItem('theme', newTheme); }); // ========================================================================== // 5. HERO TYPEWRITER EFFECT // ========================================================================== const typewriterElement = document.getElementById('typewriter'); const words = ["Software Engineer", "C Developer", "Game Developer", "Coffee & Toy Enthusiast"]; let wordIndex = 0; let charIndex = 0; let isDeleting = false; let typingSpeed = 100; function typeEffect() { if (!typewriterElement) return; const currentWord = words[wordIndex]; if (isDeleting) { typewriterElement.textContent = currentWord.substring(0, charIndex - 1); charIndex--; typingSpeed = 50; // speed up when deleting } else { typewriterElement.textContent = currentWord.substring(0, charIndex + 1); charIndex++; typingSpeed = 120; // slow down when typing } if (!isDeleting && charIndex === currentWord.length) { // Wait before starting to delete typingSpeed = 2000; isDeleting = true; } else if (isDeleting && charIndex === 0) { isDeleting = false; wordIndex = (wordIndex + 1) % words.length; typingSpeed = 500; // pause before typing next word } setTimeout(typeEffect, typingSpeed); } // Start typewriter effect typeEffect(); // ========================================================================== // 6. SCROLL REVEAL (INTERSECTION OBSERVER) // ========================================================================== const revealElements = document.querySelectorAll('.reveal-fade-up, .reveal-fade-left, .reveal-fade-right, .skills-category-card'); const revealObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('revealed'); // Keep observing or unobserve depending on effect (unobserve is cleaner) observer.unobserve(entry.target); } }); }, { threshold: 0.15, rootMargin: '0px 0px -50px 0px' // trigger slightly before entering screen completely }); revealElements.forEach(element => { revealObserver.observe(element); }); // ========================================================================== // 7. CONTACT FORM SUBMISSION MOCKUP // ========================================================================== const contactForm = document.getElementById('contact-form'); const formStatus = document.getElementById('form-status'); if (contactForm) { contactForm.addEventListener('submit', (e) => { e.preventDefault(); // Show sending state formStatus.className = 'form-status'; formStatus.textContent = 'กำลังส่งข้อมูล... (Sending message...)'; // Mock server timeout response (1.5 seconds) setTimeout(() => { // Success message formStatus.className = 'form-status success'; formStatus.textContent = 'ขอบคุณครับ! ข้อความของคุณถูกส่งแล้ว จะติดต่อกลับโดยเร็วที่สุด (Sent successfully!)'; // Clear fields contactForm.reset(); // Hide message after 5 seconds setTimeout(() => { formStatus.textContent = ''; }, 5000); }, 1500); }); } });
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.32 |
proxy
|
phpinfo
|
Settings