File manager - Edit - /home/webapp69.cm.in.th/u69319090024/Portfolio/js/app.js
Back
// Portfolio App Logic document.addEventListener('DOMContentLoaded', () => { // Initialize Lucide Icons if (typeof lucide !== 'undefined') { lucide.createIcons(); } initDarkMode(); initMobileMenu(); initScrollNavbar(); initProjectFilters(); initScrollReveal(); initContactForm(); }); /** * 1. Dark/Light Theme Switching */ function initDarkMode() { const themeToggleBtn = document.getElementById('theme-toggle'); const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon'); const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon'); // Check initial state const isDarkMode = localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches); if (isDarkMode) { document.documentElement.classList.add('dark'); themeToggleLightIcon.classList.remove('hidden'); themeToggleDarkIcon.classList.add('hidden'); } else { document.documentElement.classList.remove('dark'); themeToggleDarkIcon.classList.remove('hidden'); themeToggleLightIcon.classList.add('hidden'); } // Toggle button event handler themeToggleBtn.addEventListener('click', () => { // Toggle icons themeToggleDarkIcon.classList.toggle('hidden'); themeToggleLightIcon.classList.toggle('hidden'); if (document.documentElement.classList.contains('dark')) { document.documentElement.classList.remove('dark'); localStorage.setItem('color-theme', 'light'); } else { document.documentElement.classList.add('dark'); localStorage.setItem('color-theme', 'dark'); } }); } /** * 2. Mobile Menu Toggling */ function initMobileMenu() { const menuBtn = document.getElementById('mobile-menu-btn'); const mobileMenu = document.getElementById('mobile-menu'); const mobileMenuLinks = mobileMenu.querySelectorAll('a'); // Toggle Menu menuBtn.addEventListener('click', () => { const isExpanded = menuBtn.getAttribute('aria-expanded') === 'true'; menuBtn.setAttribute('aria-expanded', !isExpanded); mobileMenu.classList.toggle('hidden'); // Toggle hamburger icon to X const icon = menuBtn.querySelector('i'); if (icon) { if (mobileMenu.classList.contains('hidden')) { icon.setAttribute('data-lucide', 'menu'); } else { icon.setAttribute('data-lucide', 'x'); } lucide.createIcons(); } }); // Close Menu on Link click mobileMenuLinks.forEach(link => { link.addEventListener('click', () => { mobileMenu.classList.add('hidden'); menuBtn.setAttribute('aria-expanded', 'false'); const icon = menuBtn.querySelector('i'); if (icon) { icon.setAttribute('data-lucide', 'menu'); lucide.createIcons(); } }); }); // Close Mobile Menu on Resize to large screen window.addEventListener('resize', () => { if (window.innerWidth >= 768) { mobileMenu.classList.add('hidden'); menuBtn.setAttribute('aria-expanded', 'false'); const icon = menuBtn.querySelector('i'); if (icon) { icon.setAttribute('data-lucide', 'menu'); lucide.createIcons(); } } }); } /** * 3. Sticky & Blurred Navbar on Scroll */ function initScrollNavbar() { const navbar = document.getElementById('navbar'); window.addEventListener('scroll', () => { if (window.scrollY > 50) { navbar.classList.add('shadow-md', 'backdrop-blur-md', 'bg-white/85', 'dark:bg-slate-900/80'); navbar.classList.remove('bg-transparent', 'bg-white/40', 'dark:bg-transparent'); } else { navbar.classList.remove('shadow-md', 'backdrop-blur-md', 'bg-white/85', 'dark:bg-slate-900/80'); navbar.classList.add('bg-transparent'); } }); } /** * 4. Portfolio Filter System */ function initProjectFilters() { const filterButtons = document.querySelectorAll('.filter-btn'); const projectCards = document.querySelectorAll('.project-card'); filterButtons.forEach(button => { button.addEventListener('click', () => { // Deactivate all buttons filterButtons.forEach(btn => { btn.classList.remove('bg-indigo-600', 'text-white'); btn.classList.add('bg-slate-100', 'text-slate-600', 'dark:bg-slate-800', 'dark:text-slate-300'); }); // Activate clicked button button.classList.add('bg-indigo-600', 'text-white'); button.classList.remove('bg-slate-100', 'text-slate-600', 'dark:bg-slate-800', 'dark:text-slate-300'); const filterValue = button.getAttribute('data-filter'); // Filter logic projectCards.forEach(card => { const category = card.getAttribute('data-category'); // Add fade out animation card.style.opacity = '0'; card.style.transform = 'scale(0.95)'; setTimeout(() => { if (filterValue === 'all' || category === filterValue) { card.classList.remove('hidden'); // Force reflow card.offsetHeight; card.style.opacity = '1'; card.style.transform = 'scale(1)'; } else { card.classList.add('hidden'); } }, 300); }); }); }); } /** * 5. Scroll Reveal with Intersection Observer */ function initScrollReveal() { const revealElements = document.querySelectorAll('.reveal, .reveal-left, .reveal-right'); const revealObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('active'); observer.unobserve(entry.target); // Unobserve once animated } }); }, { threshold: 0.1, // trigger when 10% visible rootMargin: '0px 0px -50px 0px' // offset trigger point slightly }); revealElements.forEach(el => { revealObserver.observe(el); }); } /** * 6. Contact Form Handle & Dynamic Custom Toast Notification */ function initContactForm() { const form = document.getElementById('contact-form'); if (!form) return; const submitBtn = form.querySelector('button[type="submit"]'); const originalBtnText = submitBtn.innerHTML; form.addEventListener('submit', (e) => { e.preventDefault(); // Get input elements const nameInput = document.getElementById('name'); const emailInput = document.getElementById('email'); const messageInput = document.getElementById('message'); // Simple validation checks if (!nameInput.value.trim() || !emailInput.value.trim() || !messageInput.value.trim()) { showToast('กรุณากรอกข้อมูลให้ครบถ้วนทุกช่อง', 'error'); return; } if (!isValidEmail(emailInput.value.trim())) { showToast('รูปแบบอีเมลไม่ถูกต้อง', 'error'); return; } // Set loading state submitBtn.disabled = true; submitBtn.innerHTML = ` <svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white inline-block" xmlns="http://www.w3.org/2000/svg" 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> กำลังส่งข้อความ... `; // Simulate Network Request (API call) setTimeout(() => { showToast('ส่งข้อความสำเร็จ! ผมจะติดต่อกลับโดยเร็วที่สุด', 'success'); form.reset(); submitBtn.disabled = false; submitBtn.innerHTML = originalBtnText; }, 1500); }); } function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } /** * Toast Notification Utility */ function showToast(message, type = 'success') { // Remove existing toast if present const existingToast = document.querySelector('.toast-notification'); if (existingToast) { existingToast.remove(); } // Create Toast element const toast = document.createElement('div'); toast.className = `toast-notification fixed bottom-5 right-5 z-50 flex items-center p-4 mb-4 w-full max-w-md rounded-lg shadow-xl transition-all duration-500 transform translate-y-10 opacity-0 glass`; const iconColor = type === 'success' ? 'text-emerald-500 bg-emerald-100 dark:bg-emerald-900/30' : 'text-rose-500 bg-rose-100 dark:bg-rose-900/30'; const iconName = type === 'success' ? 'check-circle' : 'alert-circle'; toast.innerHTML = ` <div class="inline-flex items-center justify-center flex-shrink-0 w-8 h-8 rounded-lg ${iconColor}"> <i data-lucide="${iconName}" class="w-5 h-5"></i> </div> <div class="ml-3 text-sm font-semibold text-slate-800 dark:text-slate-100">${message}</div> <button type="button" class="ml-auto -mx-1.5 -my-1.5 bg-white text-slate-400 hover:text-slate-900 rounded-lg focus:ring-2 focus:ring-slate-300 p-1.5 hover:bg-slate-100 inline-flex items-center justify-center h-8 w-8 dark:bg-slate-800 dark:text-slate-500 dark:hover:text-white dark:hover:bg-slate-700" aria-label="Close" onclick="this.parentElement.remove()"> <i data-lucide="x" class="w-4 h-4"></i> </button> `; document.body.appendChild(toast); // Initialize Lucide icons inside toast if (typeof lucide !== 'undefined') { lucide.createIcons(); } // Trigger animation in setTimeout(() => { toast.classList.remove('translate-y-10', 'opacity-0'); }, 10); // Auto remove after 4.5 seconds setTimeout(() => { toast.classList.add('translate-y-10', 'opacity-0'); setTimeout(() => { toast.remove(); }, 500); }, 4500); }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.31 |
proxy
|
phpinfo
|
Settings