File manager - Edit - /home/webapp69.cm.in.th/u69319090020/Portfolio/020 สิปปกร วงค์พรหม/script.js
Back
/** * Script for Sippakorn Wongprom's Portfolio Web Application * Includes Dark/Light Mode, Mobile Navigation transitions, Form submission, and Smooth Scrolling. */ if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initAll); } else { initAll(); } function initAll() { initTheme(); initMobileMenu(); initSmoothScroll(); initContactForm(); initProfileHover(); initActiveSectionTracker(); initSidebar(); initShopeeDrawer(); } /** * 1. Dark/Light Theme Handler (with manual toggle and LocalStorage caching) */ function initTheme() { const themeToggleBtns = document.querySelectorAll('.theme-toggle-btn'); const sunIcons = document.querySelectorAll('.sun-icon'); const moonIcons = document.querySelectorAll('.moon-icon'); // Check saved theme or user preference const savedTheme = localStorage.getItem('portfolio-theme'); const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; if (savedTheme === 'dark' || (!savedTheme && prefersDark)) { document.documentElement.classList.add('dark'); updateThemeUI(true); } else { document.documentElement.classList.remove('dark'); updateThemeUI(false); } // Bind click handlers to all theme buttons themeToggleBtns.forEach(btn => { btn.addEventListener('click', () => { const isDark = document.documentElement.classList.contains('dark'); if (isDark) { document.documentElement.classList.remove('dark'); localStorage.setItem('portfolio-theme', 'light'); updateThemeUI(false); } else { document.documentElement.classList.add('dark'); localStorage.setItem('portfolio-theme', 'dark'); updateThemeUI(true); } }); }); // Helper function to swap icon visibility with smooth transition function updateThemeUI(isDark) { sunIcons.forEach(sun => { if (isDark) { sun.classList.remove('hidden'); sun.classList.add('animate-spin-slow'); } else { sun.classList.add('hidden'); } }); moonIcons.forEach(moon => { if (isDark) { moon.classList.add('hidden'); } else { moon.classList.remove('hidden'); } }); } } /** * 2. Mobile Menu Transitions and Trigger State */ function initMobileMenu() { const mobileMenuBtn = document.getElementById('mobile-menu-btn'); const mobileMenu = document.getElementById('mobile-menu'); const mobileMenuLinks = document.querySelectorAll('.mobile-menu-link'); if (!mobileMenuBtn || !mobileMenu) return; // Toggle state mobileMenuBtn.addEventListener('click', () => { const isExpanded = mobileMenuBtn.getAttribute('aria-expanded') === 'true'; toggleMenu(!isExpanded); }); // Close menu when clicking on links mobileMenuLinks.forEach(link => { link.addEventListener('click', () => { toggleMenu(false); }); }); // Toggle helper function toggleMenu(isOpen) { mobileMenuBtn.setAttribute('aria-expanded', isOpen); // Change Hamburger Icon to Close (X) Icon dynamically const iconOpen = mobileMenuBtn.querySelector('.icon-open'); const iconClose = mobileMenuBtn.querySelector('.icon-close'); if (isOpen) { mobileMenu.classList.remove('hidden'); // Subtle entry transition delay setTimeout(() => { mobileMenu.classList.remove('opacity-0', '-translate-y-4'); }, 20); iconOpen.classList.add('hidden'); iconClose.classList.remove('hidden'); } else { mobileMenu.classList.add('opacity-0', '-translate-y-4'); // Delay hidden class to allow transition to finish setTimeout(() => { mobileMenu.classList.add('hidden'); }, 300); iconOpen.classList.remove('hidden'); iconClose.classList.add('hidden'); } } } /** * 3. Smooth scroll functionality */ function initSmoothScroll() { const navLinks = document.querySelectorAll('a[href^="#"]'); navLinks.forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); const targetId = this.getAttribute('href'); if (targetId === '#') return; const targetElement = document.querySelector(targetId); if (targetElement) { // Offset for sticky navigation bar const headerOffset = 80; const elementPosition = targetElement.getBoundingClientRect().top; const offsetPosition = elementPosition + window.pageYOffset - headerOffset; window.scrollTo({ top: offsetPosition, behavior: 'smooth' }); } }); }); } /** * 4. Custom Contact Form Handling and Validation */ function initContactForm() { const contactForm = document.getElementById('contact-form'); const alertContainer = document.getElementById('contact-alert'); if (!contactForm) return; contactForm.addEventListener('submit', (e) => { e.preventDefault(); const name = document.getElementById('cf-name').value.trim(); const email = document.getElementById('cf-email').value.trim(); const message = document.getElementById('cf-message').value.trim(); if (!name || !email || !message) { showAlert('กรุณากรอกข้อมูลให้ครบถ้วนในช่องที่จำเป็น', 'error'); return; } if (!validateEmail(email)) { showAlert('รูปแบบอีเมลไม่ถูกต้อง กรุณาตรวจสอบอีกครั้ง', 'error'); return; } // Simulate sending progress const submitBtn = contactForm.querySelector('button[type="submit"]'); const originalBtnText = submitBtn.innerHTML; submitBtn.disabled = true; submitBtn.innerHTML = ` <svg class="animate-spin h-5 w-5 text-white inline-block mr-2" fill="none" viewBox="0 0 24 24"> <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path> </svg> กำลังส่งข้อความ... `; // Send form data via AJAX to FormSubmit fetch('https://formsubmit.co/ajax/wsebkorn@gmail.com', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ name: name, email: email, subject: document.getElementById('cf-subject').value.trim() || 'ติดต่อจากพอร์ตโฟลิโอ', message: message }) }) .then(response => { if (response.ok) { showAlert('ส่งข้อความสำเร็จ! ขอบคุณสำหรับการติดต่อ สิปปกร จะรีบติดต่อกลับโดยเร็วที่สุด', 'success'); contactForm.reset(); } else { showAlert('เกิดข้อผิดพลาดในการส่งข้อความ กรุณาลองใหม่อีกครั้ง', 'error'); } }) .catch(error => { console.error('Error submitting form:', error); showAlert('ไม่สามารถส่งข้อความได้ในขณะนี้ กรุณาตรวจสอบการเชื่อมต่ออินเทอร์เน็ต', 'error'); }) .finally(() => { submitBtn.disabled = false; submitBtn.innerHTML = originalBtnText; }); }); function validateEmail(email) { const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return re.test(email); } function showAlert(msg, type) { if (!alertContainer) return; alertContainer.innerHTML = msg; alertContainer.className = 'p-4 rounded-xl text-sm font-medium transition-all duration-300 '; if (type === 'success') { alertContainer.className += 'bg-emerald-100 text-emerald-800 dark:bg-emerald-950/60 dark:text-emerald-300 dark:border dark:border-emerald-500/30'; } else { alertContainer.className += 'bg-rose-100 text-rose-800 dark:bg-rose-950/60 dark:text-rose-300 dark:border dark:border-rose-500/30'; } alertContainer.classList.remove('hidden'); // Auto dismiss after 6 seconds setTimeout(() => { alertContainer.classList.add('hidden'); }, 6000); } } /** * 5. Hover profile image to play MP4 video and turn theme yellow. * Left-click to lock/toggle this state permanently. */ function initProfileHover() { const container = document.querySelector('.profile-container'); if (!container) return; const img = container.querySelector('.profile-img'); const video = container.querySelector('.profile-video'); if (!img || !video) return; let isLocked = false; let isHovered = false; function updateState() { const isActive = isHovered || isLocked; const isDark = document.documentElement.classList.contains('dark'); if (isActive) { if (isDark) { document.body.classList.remove('theme-red'); document.body.classList.add('theme-yellow'); } else { document.body.classList.remove('theme-yellow'); document.body.classList.add('theme-red'); } img.classList.add('hidden'); video.classList.remove('hidden'); video.play().catch(e => { console.log('Video play interrupted or blocked:', e); }); if (isLocked) { container.classList.add('is-locked'); } else { container.classList.remove('is-locked'); } } else { document.body.classList.remove('theme-yellow', 'theme-red'); video.pause(); video.classList.add('hidden'); img.classList.remove('hidden'); container.classList.remove('is-locked'); } } container.addEventListener('mouseenter', () => { isHovered = true; updateState(); }); container.addEventListener('mouseleave', () => { isHovered = false; updateState(); }); container.addEventListener('click', (e) => { e.stopPropagation(); isLocked = !isLocked; updateState(); }); // Listen to theme changes to dynamically switch colors if locked/hovered document.querySelectorAll('.theme-toggle-btn').forEach(btn => { btn.addEventListener('click', () => { setTimeout(updateState, 50); }); }); } /** * 6. Track active section and update mobile bottom navigation & sidebar link highlighting */ function initActiveSectionTracker() { const sections = document.querySelectorAll('section[id]'); const bottomLinks = document.querySelectorAll('.mobile-bottom-link'); const sidebarLinks = document.querySelectorAll('.sidebar-link'); if (sections.length === 0) return; const options = { root: null, rootMargin: '-30% 0px -60% 0px', threshold: 0 }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const id = entry.target.getAttribute('id'); // Update Mobile Bottom Links bottomLinks.forEach(link => { if (link.getAttribute('href') === `#${id}`) { link.classList.add('active'); } else { link.classList.remove('active'); } }); // Update Desktop Sidebar Links sidebarLinks.forEach(link => { if (link.getAttribute('href') === `#${id}`) { link.classList.add('active'); } else { link.classList.remove('active'); } }); } }); }, options); sections.forEach(section => observer.observe(section)); } /** * 7. Desktop Sidebar Collapse/Expand functionality */ function initSidebar() { const sidebar = document.getElementById('desktop-sidebar'); const toggleBtn = document.getElementById('sidebar-toggle-btn'); const toggleIcon = document.getElementById('sidebar-toggle-icon'); const sidebarTexts = document.querySelectorAll('.sidebar-text'); if (!sidebar || !toggleBtn) return; let isExpanded = false; toggleBtn.addEventListener('click', () => { isExpanded = !isExpanded; if (isExpanded) { // Expand sidebar width smoothly sidebar.classList.remove('w-16'); sidebar.classList.add('w-64'); toggleIcon.classList.add('rotate-180'); // Reveal text labels sidebarTexts.forEach(txt => { txt.classList.remove('hidden'); setTimeout(() => { txt.classList.remove('opacity-0'); txt.classList.add('opacity-100'); }, 50); }); } else { // Collapse sidebar width smoothly sidebar.classList.remove('w-64'); sidebar.classList.add('w-16'); toggleIcon.classList.remove('rotate-180'); // Hide text labels sidebarTexts.forEach(txt => { txt.classList.remove('opacity-100'); txt.classList.add('opacity-0'); setTimeout(() => { txt.classList.add('hidden'); }, 300); }); } }); } /** * 8. Shopee Drawer Controller (Slide down from top) */ function initShopeeDrawer() { const btnTop = document.getElementById('shopee-btn-top'); const btnBottom = document.getElementById('shopee-btn-bottom'); const drawer = document.getElementById('shopee-drawer'); const backdrop = document.getElementById('shopee-drawer-backdrop'); const closeBtn = document.getElementById('close-shopee-drawer'); if (!drawer || !backdrop) return; function openDrawer() { drawer.classList.remove('invisible', 'pointer-events-none', '-translate-y-full'); drawer.classList.add('translate-y-0'); backdrop.classList.remove('hidden'); // Allow browser layout engine to register backdrop display change setTimeout(() => { backdrop.classList.remove('opacity-0'); backdrop.classList.add('opacity-100'); }, 20); } function closeDrawer() { drawer.classList.remove('translate-y-0'); drawer.classList.add('-translate-y-full'); backdrop.classList.remove('opacity-100'); backdrop.classList.add('opacity-0'); setTimeout(() => { backdrop.classList.add('hidden'); drawer.classList.add('invisible', 'pointer-events-none'); }, 500); // 500ms matches transform transition-all duration-500 } if (btnTop) btnTop.addEventListener('click', openDrawer); if (btnBottom) btnBottom.addEventListener('click', openDrawer); if (closeBtn) closeBtn.addEventListener('click', closeDrawer); if (backdrop) backdrop.addEventListener('click', closeDrawer); }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.31 |
proxy
|
phpinfo
|
Settings