File manager - Edit - /home/webapp69.cm.in.th/u69319090015/porfolio/69319090015/assets/js/app.js
Back
/** * Portfolio Core JavaScript logic * Handles navigation, typing effect, project details modals, scroll indicators, and form submit. */ document.addEventListener('DOMContentLoaded', () => { // Initialize Lucide Icons if available if (typeof lucide !== 'undefined') { lucide.createIcons(); } // --- Theme Toggle Control --- const themeToggleBtn = document.getElementById('theme-toggle'); const themeToggleMobileBtn = document.getElementById('theme-toggle-mobile'); function toggleTheme() { const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; if (isDark) { document.documentElement.removeAttribute('data-theme'); localStorage.setItem('theme', 'light'); } else { document.documentElement.setAttribute('data-theme', 'dark'); localStorage.setItem('theme', 'dark'); } } if (themeToggleBtn) { themeToggleBtn.addEventListener('click', toggleTheme); } if (themeToggleMobileBtn) { themeToggleMobileBtn.addEventListener('click', toggleTheme); } // --- 1. Typing Animation --- const typingElement = document.querySelector('.typing-text'); const roles = [ 'Computer Technology Student', 'IT Support Specialist', 'IT Technician', 'Arduino Developer', 'Technology Enthusiast' ]; let roleIndex = 0; let charIndex = 0; let isDeleting = false; let typingSpeed = 100; function typeRole() { if (!typingElement) return; const currentRole = roles[roleIndex]; if (isDeleting) { typingElement.textContent = currentRole.substring(0, charIndex - 1); charIndex--; typingSpeed = 50; // Deleting is faster } else { typingElement.textContent = currentRole.substring(0, charIndex + 1); charIndex++; typingSpeed = 100; // Normal typing speed } if (!isDeleting && charIndex === currentRole.length) { // Pause at full text isDeleting = true; typingSpeed = 2000; } else if (isDeleting && charIndex === 0) { isDeleting = false; roleIndex = (roleIndex + 1) % roles.length; typingSpeed = 500; // Pause before typing next role } setTimeout(typeRole, typingSpeed); } // Start the typing loop setTimeout(typeRole, 1000); // --- 2. Scroll Spy / Active Nav Indicator --- const sections = document.querySelectorAll('section'); const navLinks = document.querySelectorAll('.nav-link'); function scrollSpy() { let scrollPos = window.scrollY || document.documentElement.scrollTop || 0; // Offset for header height scrollPos += 150; sections.forEach(section => { const top = section.offsetTop; const height = section.offsetHeight; const id = section.getAttribute('id'); if (scrollPos >= top && scrollPos < top + height) { navLinks.forEach(link => { if (link.getAttribute('href') === `#${id}`) { link.classList.add('active'); } else { link.classList.remove('active'); } }); } }); } window.addEventListener('scroll', scrollSpy); // --- 4. Portfolio Projects Data & Modal System --- const projectsData = { 1: { title: "Smart Agriculture via Arduino IoT", subtitle: "โปรเจกต์ Arduino ระบบควบคุมฟาร์มอัจฉริยะ", image: "https://images.unsplash.com/photo-1553406830-ef2513450d76?auto=format&fit=crop&w=800&q=80", tags: ["Arduino", "IoT", "ESP8266", "Hardware", "C++"], description: "ระบบควบคุมฟาร์มอัจฉริยะตรวจวัดความชื้นในดิน อุณหภูมิ และระดับน้ำ เชื่อมต่อสั่งการผ่าน Application และ Cloud Service แบบ Real-time ตัวเครื่องจะรดน้ำอัตโนมัติเมื่อค่าความชื้นต่ำกว่าเกณฑ์ และส่งการแจ้งเตือนไปยังสมาร์ทโฟนของผู้ใช้งาน", features: [ "ตรวจจับระดับความชื้นในดินและอุณหภูมิอากาศแบบ Real-time", "ระบบสั่งเปิด-ปิดปั๊มน้ำอัตโนมัติ (Relay Switch Control)", "เชื่อมต่อ WiFi ด้วยบอร์ด NodeMCU ESP8266", "แจ้งเตือนผ่าน Line Notify ทันทีเมื่อเกิดความผิดปกติในระบบ", "เก็บข้อมูลสถิติประวัติย้อนหลังด้วยฐานข้อมูล Firebase" ], link: "https://github.com", demo: "#" }, 2: { title: "PC Assembly Guide & Diagnostic Manual", subtitle: "คู่มือประกอบคอมพิวเตอร์และการตรวจเช็กอาการเสีย", image: "https://images.unsplash.com/photo-1591799264318-7e6ef8ddb7ea?auto=format&fit=crop&w=800&q=80", tags: ["Hardware", "Maintenance", "Windows", "Manual"], description: "คู่มือแบบบูรณาการที่รวบรวมเทคนิค วิธีการจัดสเปก และขั้นตอนการประกอบเครื่องคอมพิวเตอร์ทีละขั้นตอนอย่างละเอียด รวมถึงเทคนิคการแก้ไขปัญหาพื้นฐาน (Troubleshooting) ที่เขียนขึ้นเพื่อใช้งานเป็นสื่อการเรียนการสอนในสาขาวิชาชีพ", features: [ "อธิบายขั้นตอนการเลือกชิ้นส่วนหลัก CPU, RAM, Mainboard, GPU ให้สอดคล้องกัน", "เทคนิคการจัดสายไฟ (Cable Management) ภายในเคสอย่างถูกต้องเพื่อการไหลเวียนของลม", "ตารางวิเคราะห์อาการเสียทั่วไป (เช่น ไฟไม่เข้าเครื่อง, บอร์ดร้องเตือน, จอฟ้า)", "คู่มือการติดตั้งและตั้งค่า BIOS / UEFI เบื้องต้นอย่างละเอียด" ], link: "#", demo: "#" }, 3: { title: "Network Infrastructure Design", subtitle: "การออกแบบและวางระบบเครือข่ายภายในองค์กรจำลอง", image: "https://images.unsplash.com/photo-1544197150-b99a580bb7a8?auto=format&fit=crop&w=800&q=80", tags: ["Networking", "Cisco Packet Tracer", "Subnetting", "Routing"], description: "จำลองการวางโครงสร้างระบบเครือข่าย (Network Design) สำหรับองค์กรขนาดเล็กถึงกลาง มีการแบ่งโซนความปลอดภัย VLANs, จัดการ IP Address Subnetting และการกำหนดสิทธิ์การเข้าถึงข้อมูลผ่าน ACL", features: [ "แบ่งแผนกเพื่อความปลอดภัยด้วย VLAN (HR, IT, Guest, Management)", "กำหนดนโยบาย IP address ด้วย Class C Variable Length Subnet Masking (VLSM)", "ตั้งค่า Dynamic Routing OSPF ระหว่างสาขา", "ทดสอบและจำลองประสิทธิภาพเครือข่ายใน Cisco Packet Tracer" ], link: "https://github.com", demo: "#" } }; const modal = document.getElementById('project-modal'); const modalBody = document.getElementById('modal-content'); const modalClose = document.getElementById('modal-close'); // Open modal const viewDetailsButtons = document.querySelectorAll('.view-project-btn'); viewDetailsButtons.forEach(button => { button.addEventListener('click', (e) => { e.preventDefault(); const projectId = button.getAttribute('data-project-id'); const data = projectsData[projectId]; if (data) { // Construct Modal Content let tagsHtml = data.tags.map(tag => `<span class="px-2.5 py-1 text-xs font-medium bg-indigo-500/10 text-indigo-600 dark:text-indigo-300 border border-indigo-200 dark:border-indigo-500/20 rounded-full">${tag}</span>` ).join(''); let featuresHtml = data.features.map(feat => `<li class="flex items-start gap-2 text-sm text-gray-650 dark:text-gray-300"> <i data-lucide="check-circle-2" class="w-4 h-4 text-emerald-500 dark:text-emerald-400 shrink-0 mt-0.5"></i> <span>${feat}</span> </li>` ).join(''); modalBody.innerHTML = ` <div class="relative w-full h-48 md:h-64 rounded-xl overflow-hidden mb-6"> <img src="${data.image}" alt="${data.title}" class="w-full h-full object-cover"> <div class="absolute inset-0 bg-gradient-to-t from-white dark:from-gray-900 via-transparent to-transparent"></div> </div> <span class="text-xs uppercase tracking-widest text-indigo-600 dark:text-indigo-400 font-semibold mb-1 block">${data.subtitle}</span> <h3 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-white mb-3">${data.title}</h3> <div class="flex flex-wrap gap-2 mb-4"> ${tagsHtml} </div> <p class="text-gray-650 dark:text-gray-300 text-sm md:text-base leading-relaxed mb-6"> ${data.description} </p> <h4 class="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center gap-2"> <i data-lucide="cpu" class="w-5 h-5 text-indigo-600 dark:text-indigo-400"></i> คุณลักษณะเด่นและฮาร์ดแวร์ที่ใช้ </h4> <ul class="space-y-2 mb-6"> ${featuresHtml} </ul> <div class="flex flex-col sm:flex-row gap-3 pt-4 border-t border-gray-250 dark:border-gray-800"> <a href="${data.link}" target="_blank" class="flex items-center justify-center gap-2 px-5 py-2.5 bg-indigo-600 hover:bg-indigo-700 active:scale-95 transition-all text-white font-medium rounded-lg text-sm"> <i data-lucide="github" class="w-4 h-4"></i> ดูโค้ดบน GitHub </a> <a href="${data.demo}" class="flex items-center justify-center gap-2 px-5 py-2.5 bg-gray-100 dark:bg-gray-800 hover:bg-gray-250 dark:hover:bg-gray-700 active:scale-95 transition-all text-gray-700 dark:text-gray-300 hover:text-gray-950 dark:hover:text-white font-medium rounded-lg text-sm border border-gray-250 dark:border-gray-750"> <i data-lucide="external-link" class="w-4 h-4"></i> เปิดหน้าสาธิตโปรเจกต์ </a> </div> `; // Trigger Lucide on dynamic HTML if (typeof lucide !== 'undefined') { lucide.createIcons(); } // Show Modal with animation classes modal.classList.remove('hidden'); modal.classList.add('flex'); // Block main page scroll document.body.style.overflow = 'hidden'; } }); }); // Close modal function function closeModal() { modal.classList.add('hidden'); modal.classList.remove('flex'); document.body.style.overflow = ''; } if (modalClose) { modalClose.addEventListener('click', closeModal); } // Click outside modal container to close if (modal) { modal.addEventListener('click', (e) => { if (e.target === modal) { closeModal(); } }); } // Close with Escape key document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !modal.classList.contains('hidden')) { closeModal(); } }); // --- 5. Contact Form Validation & Submission --- const contactForm = document.getElementById('contact-form'); const toast = document.getElementById('toast'); if (contactForm) { contactForm.addEventListener('submit', (e) => { e.preventDefault(); const name = document.getElementById('name').value.trim(); const email = document.getElementById('email').value.trim(); const message = document.getElementById('message').value.trim(); if (!name || !email || !message) { showToast("กรุณากรอกข้อมูลให้ครบทุกช่อง", "error"); return; } // Basic Email Regex const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { showToast("กรุณากรอกอีเมลที่ถูกต้อง", "error"); return; } // Send real email via FormSubmit AJAX API const submitBtn = contactForm.querySelector('button[type="submit"]'); const originalText = submitBtn.innerHTML; 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> กำลังส่งข้อความ... `; fetch("https://formsubmit.co/ajax/maibuauty@gmail.com", { method: "POST", headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, body: JSON.stringify({ Name: name, Email: email, Message: message }) }) .then(response => { if (response.ok) { return response.json(); } else { throw new Error("ระบบมีข้อผิดพลาด กรุณาลองใหม่อีกครั้ง"); } }) .then(data => { showToast("ส่งข้อความสำเร็จ! ขอบคุณสำหรับการติดต่อ", "success"); contactForm.reset(); }) .catch(error => { showToast("เกิดข้อผิดพลาด: ไม่สามารถส่งข้อความได้", "error"); }) .finally(() => { submitBtn.disabled = false; submitBtn.innerHTML = originalText; }); }); } function showToast(message, type = "success") { if (!toast) return; const toastText = toast.querySelector('.toast-text'); const toastIcon = toast.querySelector('.toast-icon-container'); toastText.textContent = message; if (type === "success") { toastIcon.innerHTML = '<i data-lucide="check-circle-2" class="w-5 h-5 text-emerald-500 dark:text-emerald-400"></i>'; toast.className = "fixed bottom-5 right-5 z-50 flex items-center gap-3 px-4 py-3 bg-white dark:bg-gray-900 border border-emerald-500/30 rounded-xl shadow-2xl animate-fade-in transition-colors duration-300"; } else { toastIcon.innerHTML = '<i data-lucide="alert-triangle" class="w-5 h-5 text-red-500 dark:text-red-400"></i>'; toast.className = "fixed bottom-5 right-5 z-50 flex items-center gap-3 px-4 py-3 bg-white dark:bg-gray-900 border border-red-500/30 rounded-xl shadow-2xl animate-fade-in transition-colors duration-300"; } if (typeof lucide !== 'undefined') { lucide.createIcons(); } toast.classList.remove('hidden'); setTimeout(() => { toast.classList.add('hidden'); }, 4000); } });
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.35 |
proxy
|
phpinfo
|
Settings