File manager - Edit - /home/webapp69.cm.in.th/u69319090029/Portfolio/js/app.js
Back
/** * ========================================================================== * PORTFOLIO APPLICATION CONTROLLER * ========================================================================== */ document.addEventListener("DOMContentLoaded", () => { // 1. Initialize Lucide Icons if (typeof lucide !== "undefined") { lucide.createIcons(); } // 2. Dark / Light Theme Toggle Setup const themeToggleBtn = document.getElementById("theme-toggle"); const htmlElement = document.documentElement; // Load saved theme or default to 'dark' const savedTheme = localStorage.getItem("portfolio-theme") || "dark"; htmlElement.setAttribute("data-theme", savedTheme); themeToggleBtn.addEventListener("click", () => { const currentTheme = htmlElement.getAttribute("data-theme"); const newTheme = currentTheme === "dark" ? "light" : "dark"; htmlElement.setAttribute("data-theme", newTheme); localStorage.setItem("portfolio-theme", newTheme); // Re-render Lucide icons if state changes if (typeof lucide !== "undefined") { lucide.createIcons(); } showToast(`สลับการใช้งานใน โหมด${newTheme === 'dark' ? 'มืด' : 'สว่าง'} เรียบร้อย!`, "success"); }); // 3. Sticky Navigation Bar on Scroll const header = document.getElementById("header"); const backToTopBtn = document.getElementById("back-to-top"); window.addEventListener("scroll", () => { if (window.scrollY > 50) { header.classList.add("scrolled"); } else { header.classList.remove("scrolled"); } // Show/Hide Back to Top Button if (window.scrollY > 600) { backToTopBtn.classList.add("visible"); } else { backToTopBtn.classList.remove("visible"); } }); backToTopBtn.addEventListener("click", () => { window.scrollTo({ top: 0, behavior: "smooth" }); }); // 4. Hamburger Mobile Navigation Menu Toggle const menuToggle = document.getElementById("menu-toggle"); const mobileMenu = document.getElementById("nav-menu-mobile"); const mobileLinks = document.querySelectorAll(".mobile-nav-link"); menuToggle.addEventListener("click", () => { const isOpen = menuToggle.classList.toggle("active"); if (isOpen) { mobileMenu.style.display = "block"; // Simple animation trigger delay setTimeout(() => { mobileMenu.style.opacity = "1"; }, 10); } else { mobileMenu.style.display = "none"; } }); // Close mobile menu when clicking links mobileLinks.forEach(link => { link.addEventListener("click", () => { menuToggle.classList.remove("active"); mobileMenu.style.display = "none"; }); }); // 5. Active Link Highlight on Scroll Tracking const sections = document.querySelectorAll("section[id]"); const navLinks = document.querySelectorAll(".nav-link"); const mobileNavLinks = document.querySelectorAll(".mobile-nav-link"); const bottomNavLinks = document.querySelectorAll(".bottom-nav-item"); function highlightActiveLink() { const scrollY = window.scrollY; sections.forEach(section => { const sectionHeight = section.offsetHeight; const sectionTop = section.offsetTop - 100; // Offset for sticky navbar const sectionId = section.getAttribute("id"); if (scrollY > sectionTop && scrollY <= sectionTop + sectionHeight) { // Update Desktop Links navLinks.forEach(link => { link.classList.remove("active"); if (link.getAttribute("href") === `#${sectionId}`) { link.classList.add("active"); } }); // Update Mobile Links mobileNavLinks.forEach(link => { link.classList.remove("active"); if (link.getAttribute("href") === `#${sectionId}`) { link.classList.add("active"); } }); // Update Bottom Nav Links bottomNavLinks.forEach(link => { link.classList.remove("active"); let targetId = sectionId; if (targetId === "skills") { targetId = "about"; } if (link.getAttribute("href") === `#${targetId}`) { link.classList.add("active"); } }); } }); } window.addEventListener("scroll", highlightActiveLink); // 6. Portfolio Projects Filtering System const filterButtons = document.querySelectorAll(".filter-btn"); const projectCards = document.querySelectorAll(".project-card"); filterButtons.forEach(button => { button.addEventListener("click", () => { // Remove active class from all buttons and add to clicked filterButtons.forEach(btn => btn.classList.remove("active")); button.classList.add("active"); const filterValue = button.getAttribute("data-filter"); projectCards.forEach(card => { const category = card.getAttribute("data-category"); if (filterValue === "all" || category === filterValue) { card.style.display = "flex"; // Apply fade scale effect setTimeout(() => { card.style.opacity = "1"; card.style.transform = "scale(1)"; }, 50); } else { card.style.opacity = "0"; card.style.transform = "scale(0.95)"; setTimeout(() => { card.style.display = "none"; }, 300); } }); }); }); // 7. Contact Form Handling & Validation System const contactForm = document.getElementById("portfolio-contact-form"); const submitBtn = document.getElementById("form-submit-btn"); const sendIcon = document.getElementById("send-icon"); const spinner = document.getElementById("send-spinner"); contactForm.addEventListener("submit", (e) => { e.preventDefault(); // Inputs const nameInput = document.getElementById("form-name"); const emailInput = document.getElementById("form-email"); const subjectInput = document.getElementById("form-subject"); const messageInput = document.getElementById("form-message"); // Validate state let isValid = true; // Reset previous validation classes document.querySelectorAll(".form-group").forEach(group => { group.classList.remove("has-error"); }); document.querySelectorAll(".form-input").forEach(input => { input.classList.remove("error"); }); // 1. Name Check if (!nameInput.value.trim()) { showInputError(nameInput, "name-error"); isValid = false; } // 2. Email Check const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailInput.value.trim() || !emailRegex.test(emailInput.value)) { showInputError(emailInput, "email-error"); isValid = false; } // 3. Subject Check if (!subjectInput.value.trim()) { showInputError(subjectInput, "subject-error"); isValid = false; } // 4. Message Check if (!messageInput.value.trim()) { showInputError(messageInput, "message-error"); isValid = false; } if (!isValid) return; // If form is valid, trigger simulated submission with loading state setLoadingState(true); setTimeout(() => { setLoadingState(false); showToast("ส่งข้อความของคุณเรียบร้อยแล้ว! ผมจะติดต่อกลับทันที", "success"); contactForm.reset(); }, 1800); }); function showInputError(inputElement, errorId) { inputElement.classList.add("error"); const groupElement = inputElement.closest(".form-group"); if (groupElement) { groupElement.classList.add("has-error"); } } function setLoadingState(isLoading) { if (isLoading) { submitBtn.disabled = true; sendIcon.classList.add("hidden"); spinner.classList.remove("hidden"); } else { submitBtn.disabled = false; sendIcon.classList.remove("hidden"); spinner.classList.add("hidden"); } } // 8. Custom Floating Toast Notification System function showToast(message, type = "success") { const toastContainer = document.getElementById("toast-container"); const toast = document.createElement("div"); toast.className = `toast toast-${type}`; const iconName = type === "success" ? "check-circle" : "alert-circle"; toast.innerHTML = ` <i data-lucide="${iconName}"></i> <span>${message}</span> `; toastContainer.appendChild(toast); // Initialize lucide for new toast element if (typeof lucide !== "undefined") { lucide.createIcons(); } // Trigger slide in setTimeout(() => { toast.classList.add("show"); }, 100); // Slide out and remove setTimeout(() => { toast.classList.remove("show"); setTimeout(() => { toast.remove(); }, 400); }, 4000); } });
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.33 |
proxy
|
phpinfo
|
Settings