File manager - Edit - /home/webapp69.cm.in.th/u69319090014/69319090014/app.js
Back
document.addEventListener('DOMContentLoaded', () => { // Initialize Lucide Icons if (typeof lucide !== 'undefined') { lucide.createIcons(); } /* ========================================================================== HEADER SCROLL EFFECT ========================================================================== */ const header = document.querySelector('.header'); window.addEventListener('scroll', () => { if (window.scrollY > 50) { header.classList.add('scrolled'); } else { header.classList.remove('scrolled'); } }); /* ========================================================================== MOBILE BOTTOM BAR & BOTTOM SHEET DRAWER ========================================================================== */ const hamburger = document.getElementById('hamburger'); // top header hamburger const bottomHamburger = document.getElementById('bottom-hamburger'); // center bottom bar btn const navMenu = document.getElementById('nav-menu'); // slide-in desktop drawer const bottomSheet = document.getElementById('bottom-sheet'); const sheetOverlay = document.getElementById('bottom-sheet-overlay'); // Create dim overlay for the desktop side-drawer (top hamburger) const navOverlay = document.createElement('div'); navOverlay.classList.add('nav-overlay'); document.body.appendChild(navOverlay); /* ---- Top header hamburger toggles the side drawer (mobile drawer) ---- */ const closeNavMenu = () => { hamburger.classList.remove('active'); navMenu.classList.remove('active'); navOverlay.classList.remove('active'); hamburger.setAttribute('aria-expanded', 'false'); }; const toggleNavMenu = () => { const isOpen = navMenu.classList.contains('active'); if (isOpen) { closeNavMenu(); } else { hamburger.classList.add('active'); navMenu.classList.add('active'); navOverlay.classList.add('active'); hamburger.setAttribute('aria-expanded', 'true'); } }; hamburger.addEventListener('click', toggleNavMenu); navOverlay.addEventListener('click', closeNavMenu); // Close side-drawer when a desktop nav link is clicked const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => link.addEventListener('click', closeNavMenu)); /* ---- Bottom hamburger toggles the bottom sheet ---- */ const openSheet = () => { bottomSheet.classList.add('active'); sheetOverlay.classList.add('active'); bottomSheet.setAttribute('aria-hidden', 'false'); bottomHamburger.classList.add('active'); bottomHamburger.setAttribute('aria-expanded', 'true'); }; const closeSheet = () => { bottomSheet.classList.remove('active'); sheetOverlay.classList.remove('active'); bottomSheet.setAttribute('aria-hidden', 'true'); bottomHamburger.classList.remove('active'); bottomHamburger.setAttribute('aria-expanded', 'false'); }; const toggleSheet = () => { bottomSheet.classList.contains('active') ? closeSheet() : openSheet(); }; if (bottomHamburger) bottomHamburger.addEventListener('click', toggleSheet); if (sheetOverlay) sheetOverlay.addEventListener('click', closeSheet); // Close sheet when a bottom-sheet nav link is clicked const sheetLinks = document.querySelectorAll('.bsn-link'); sheetLinks.forEach(link => link.addEventListener('click', closeSheet)); // Close sheet when the mbb-item shortcuts (Home / Contact) are clicked const mbbItems = document.querySelectorAll('.mbb-item'); mbbItems.forEach(item => item.addEventListener('click', closeSheet)); /* ========================================================================== NAVBAR LINK ACTIVE HIGHLIGHT (INTERSECTION OBSERVER) ========================================================================== */ const sections = document.querySelectorAll('section[id]'); const observerOptions = { root: null, rootMargin: '-20% 0px -60% 0px', threshold: 0 }; const observerCallback = (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const id = entry.target.getAttribute('id'); // Update top desktop nav navLinks.forEach(link => { link.classList.toggle('active', link.getAttribute('href') === `#${id}`); }); // Update bottom sheet links sheetLinks.forEach(link => { link.classList.toggle('active', link.getAttribute('href') === `#${id}`); }); // Update mbb shortcut items mbbItems.forEach(item => { item.classList.toggle('active', item.getAttribute('href') === `#${id}`); }); } }); }; const observer = new IntersectionObserver(observerCallback, observerOptions); sections.forEach(section => observer.observe(section)); /* ========================================================================== HERO TEXT TYPEWRITER EFFECT ========================================================================== */ const typeTarget = document.querySelector('.typewriter-text'); if (typeTarget) { const words = ["Full-Stack Developer", "Samurai Coder", "UI/UX Designer", "Problem Solver"]; let wordIndex = 0; let charIndex = 0; let isDeleting = false; let typeSpeed = 100; const type = () => { const currentWord = words[wordIndex]; if (isDeleting) { // Deleting text typeTarget.textContent = currentWord.substring(0, charIndex - 1); charIndex--; typeSpeed = 50; // Faster when deleting } else { // Writing text typeTarget.textContent = currentWord.substring(0, charIndex + 1); charIndex++; typeSpeed = 150; // Steady writing } // Word finished writing if (!isDeleting && charIndex === currentWord.length) { typeSpeed = 2000; // Pause at end of word isDeleting = true; } // Word finished deleting else if (isDeleting && charIndex === 0) { isDeleting = false; wordIndex = (wordIndex + 1) % words.length; typeSpeed = 500; // Small pause before writing next word } setTimeout(type, typeSpeed); }; // Start typewriter loop setTimeout(type, 1000); } /* ========================================================================== CONTACT FORM VALIDATION & SUBMISSION HANDLING ========================================================================== */ const contactForm = document.getElementById('contact-form'); const submitBtn = document.getElementById('submit-btn'); const feedbackModal = document.getElementById('feedback-modal'); const modalClose = document.getElementById('modal-close'); // Email regex validation helper const isValidEmail = (email) => { const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return regex.test(email); }; // Show error message const showError = (inputElement) => { const formGroup = inputElement.parentElement; formGroup.classList.add('error'); }; // Remove error message const clearError = (inputElement) => { const formGroup = inputElement.parentElement; formGroup.classList.remove('error'); }; // Input event listener to clear errors dynamically as user types const inputsToValidate = contactForm.querySelectorAll('input[required], textarea[required]'); inputsToValidate.forEach(input => { input.addEventListener('input', () => { if (input.value.trim() !== "") { if (input.type === 'email') { if (isValidEmail(input.value.trim())) { clearError(input); } } else { clearError(input); } } }); }); // Form submit listener contactForm.addEventListener('submit', (e) => { e.preventDefault(); let hasErrors = false; // Validate each required field inputsToValidate.forEach(input => { const val = input.value.trim(); if (val === "") { showError(input); hasErrors = true; } else if (input.type === 'email' && !isValidEmail(val)) { showError(input); hasErrors = true; } else { clearError(input); } }); // If validation succeeds, trigger beautiful loading state and modal if (!hasErrors) { // Set loading state const originalBtnContent = submitBtn.innerHTML; submitBtn.disabled = true; submitBtn.innerHTML = ` <span>กำลังส่งข้อความ...</span> <div class="spinner"></div> `; // Add simple inline style for CSS spinner const spinnerStyle = document.createElement('style'); spinnerStyle.id = 'temp-spinner-style'; spinnerStyle.innerHTML = ` .spinner { width: 18px; height: 18px; border: 2px solid rgba(255, 255, 255, 0.2); border-top: 2px solid var(--text-primary); border-radius: 50%; animation: spin 0.8s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(spinnerStyle); // Mock an API POST delay (e.g. 1.5 seconds) setTimeout(() => { // Restore submit button submitBtn.disabled = false; submitBtn.innerHTML = originalBtnContent; const styleToRemove = document.getElementById('temp-spinner-style'); if (styleToRemove) styleToRemove.remove(); // Open dynamic success modal popup feedbackModal.classList.add('active'); // Clear the form contactForm.reset(); }, 1500); } }); // Close feedback modal const closeModal = () => { feedbackModal.classList.remove('active'); }; modalClose.addEventListener('click', closeModal); feedbackModal.addEventListener('click', (e) => { if (e.target === feedbackModal) { closeModal(); } }); });
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.35 |
proxy
|
phpinfo
|
Settings