const root = document.documentElement; const body = document.body; const nav = document.querySelector('[data-nav]'); const navToggle = document.querySelector('[data-nav-toggle]'); const navMenu = document.querySelector('[data-nav-menu]'); const serviceToggle = document.querySelector('[data-service-toggle]'); const serviceWrap = serviceToggle?.closest('.nav-service'); const panel = document.querySelector('[data-lead-panel]'); const backdrop = document.querySelector('[data-panel-backdrop]'); const form = document.querySelector('[data-lead-form]'); const statusEl = document.querySelector('[data-form-status]'); const floatingCta = document.querySelector('[data-floating-cta]'); const openButtons = document.querySelectorAll('[data-open-lead]'); const closeButtons = document.querySelectorAll('[data-close-lead]'); const diagnosisByProblem = { ads_not_worth_it: 'จากปัญหาที่เลือก เราน่าจะเริ่มจากการดูข้อมูลแอด กลุ่มเป้าหมาย และคุณภาพลูกค้าที่ทักเข้ามาก่อน', wrong_leads: 'จากปัญหาที่เลือก เราน่าจะเริ่มจากการดูข้อมูลแอด กลุ่มเป้าหมาย และคุณภาพลูกค้าที่ทักเข้ามาก่อน', website_no_leads: 'จากปัญหาที่เลือก เราน่าจะเริ่มจากการดูเว็บ เส้นทางลูกค้า และจุดที่ควรชวนให้ติดต่อก่อน', slow_or_error_work: 'จากปัญหาที่เลือก เราน่าจะเริ่มจากขั้นตอนทำงานซ้ำ จุดที่ช้า และจุดที่ผิดพลาดบ่อยก่อน', ai_not_sure: 'จากปัญหาที่เลือก เราน่าจะเริ่มจากงานจริงของทีม แล้วค่อยเลือกจุดที่ AI ช่วยได้อย่างเหมาะสม', not_sure: 'ได้รับโจทย์แล้ว เราจะเริ่มจากการทำความเข้าใจธุรกิจและข้อมูลที่มีอยู่ก่อน', }; function setStatus(message, tone = '') { if (!statusEl) return; statusEl.textContent = message; statusEl.dataset.tone = tone; } function openPanel() { body.classList.add('panel-open'); panel?.setAttribute('aria-hidden', 'false'); window.setTimeout(() => { panel?.querySelector('input, textarea, button')?.focus(); }, 80); } function closePanel() { body.classList.remove('panel-open'); panel?.setAttribute('aria-hidden', 'true'); } openButtons.forEach((button) => button.addEventListener('click', openPanel)); closeButtons.forEach((button) => button.addEventListener('click', closePanel)); backdrop?.addEventListener('click', closePanel); window.addEventListener('keydown', (event) => { if (event.key === 'Escape') { closePanel(); serviceWrap?.classList.remove('is-open'); } }); navToggle?.addEventListener('click', () => { const isOpen = nav?.classList.toggle('is-open'); navToggle.setAttribute('aria-expanded', String(Boolean(isOpen))); }); serviceToggle?.addEventListener('click', () => { const isOpen = serviceWrap?.classList.toggle('is-open'); serviceToggle.setAttribute('aria-expanded', String(Boolean(isOpen))); }); navMenu?.addEventListener('click', (event) => { const target = event.target; if (target instanceof HTMLAnchorElement) { nav?.classList.remove('is-open'); navToggle?.setAttribute('aria-expanded', 'false'); } }); let ticking = false; function updateScrollState() { const scrolled = window.scrollY > 28; const pastHero = window.scrollY > window.innerHeight * 0.62; const heroProgress = Math.min(Math.max(window.scrollY / Math.max(window.innerHeight, 1), 0), 1); const maxScroll = Math.max(document.documentElement.scrollHeight - window.innerHeight, 1); const pageProgress = Math.min(Math.max(window.scrollY / maxScroll, 0), 1); const navProbeY = (nav?.getBoundingClientRect().bottom || 80) + 24; const sceneAtNav = document.elementFromPoint(window.innerWidth / 2, navProbeY)?.closest('[data-scene]'); const isOverDark = sceneAtNav?.dataset.scene === 'dark'; nav?.classList.toggle('is-scrolled', scrolled); nav?.classList.toggle('is-over-dark', isOverDark); floatingCta?.classList.toggle('is-visible', pastHero); root.style.setProperty('--scroll', heroProgress.toFixed(4)); root.style.setProperty('--page-scroll', pageProgress.toFixed(4)); root.style.setProperty('--scroll-y', `${Math.round(window.scrollY)}px`); ticking = false; } function requestScrollUpdate() { if (!ticking) { ticking = true; window.requestAnimationFrame(updateScrollState); } } window.addEventListener('scroll', requestScrollUpdate, { passive: true }); window.addEventListener('resize', requestScrollUpdate, { passive: true }); updateScrollState(); if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) { let pointerFrame = 0; window.addEventListener('pointermove', (event) => { if (pointerFrame) return; const { clientX, clientY } = event; pointerFrame = window.requestAnimationFrame(() => { const mx = (clientX / window.innerWidth - 0.5) * 2; const my = (clientY / window.innerHeight - 0.5) * 2; root.style.setProperty('--mx', mx.toFixed(4)); root.style.setProperty('--my', my.toFixed(4)); pointerFrame = 0; }); }, { passive: true }); } form?.addEventListener('submit', async (event) => { event.preventDefault(); const data = new FormData(form); const name = String(data.get('name') || '').trim(); const phone = String(data.get('phone') || '').trim(); const email = String(data.get('email') || '').trim(); const problems = data.getAll('problems').map(String); const message = String(data.get('message') || '').trim(); const endpoint = panel?.dataset.endpoint || ''; if (!name) { setStatus('กรุณาใส่ชื่อก่อนส่ง', 'error'); return; } if (!phone && !email) { setStatus('ใส่เบอร์โทรหรืออีเมลอย่างใดอย่างหนึ่งก็ได้', 'error'); return; } if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { setStatus('รูปแบบอีเมลไม่ถูกต้อง', 'error'); return; } const payload = { name, phone, email, problems, message, pageUrl: window.location.href, userAgent: navigator.userAgent, website: String(data.get('website') || ''), }; const diagnosis = problems.map((key) => diagnosisByProblem[key]).find(Boolean) || 'ได้รับโจทย์แล้ว เราจะเริ่มจากการทำความเข้าใจธุรกิจและข้อมูลที่มีอยู่ก่อน'; if (!endpoint) { setStatus('ฟอร์มพร้อมแล้ว เหลือใส่ Google Apps Script Web App URL ก่อนใช้งานจริง', 'error'); return; } setStatus('กำลังส่งโจทย์...', ''); try { await fetch(endpoint, { method: 'POST', mode: 'no-cors', headers: { 'Content-Type': 'text/plain;charset=utf-8', }, body: JSON.stringify(payload), }); form.reset(); setStatus(`ได้รับโจทย์แล้ว ${diagnosis} เราจะติดต่อกลับทางเบอร์หรืออีเมลที่ให้ไว้`, 'success'); } catch (error) { setStatus('ส่งไม่สำเร็จ กรุณาลองใหม่อีกครั้ง', 'error'); } }); // Neural Network Hero - True 3D with Dynamic Lines const heroNeural = document.querySelector('.hero-neural'); const neuralScene = document.querySelector('.neural-scene'); const canvas = document.querySelector('.neural-canvas'); const ctx = canvas?.getContext('2d'); const nodes = document.querySelectorAll('.neural-node'); if (heroNeural && neuralScene && canvas && ctx && nodes.length > 0) { // 3D rotation state let targetRotateX = 0; let targetRotateY = 0; let currentRotateX = 0; let currentRotateY = 0; // Canvas setup function resizeCanvas() { const rect = heroNeural.getBoundingClientRect(); canvas.width = rect.width * window.devicePixelRatio; canvas.height = rect.height * window.devicePixelRatio; canvas.style.width = rect.width + 'px'; canvas.style.height = rect.height + 'px'; ctx.scale(window.devicePixelRatio, window.devicePixelRatio); } resizeCanvas(); window.addEventListener('resize', resizeCanvas); // Find intersection point on node border function findBorderPoint(nodeRect, targetX, targetY) { const cx = nodeRect.left + nodeRect.width / 2; const cy = nodeRect.top + nodeRect.height / 2; const hw = nodeRect.width / 2; const hh = nodeRect.height / 2; const dx = targetX - cx; const dy = targetY - cy; if (dx === 0 && dy === 0) return { x: cx, y: cy }; const absDx = Math.abs(dx); const absDy = Math.abs(dy); let scale; if (absDx * hh > absDy * hw) { scale = hw / absDx; } else { scale = hh / absDy; } return { x: cx + dx * scale, y: cy + dy * scale }; } // Draw connections function drawConnections() { const rect = heroNeural.getBoundingClientRect(); ctx.clearRect(0, 0, rect.width, rect.height); const centerNode = document.querySelector('[data-node="center"]'); const outerNodes = document.querySelectorAll('.neural-card'); if (!centerNode) return; const centerRect = centerNode.getBoundingClientRect(); const centerX = centerRect.left + centerRect.width / 2 - rect.left; const centerY = centerRect.top + centerRect.height / 2 - rect.top; outerNodes.forEach(node => { const nodeRect = node.getBoundingClientRect(); const nodeX = nodeRect.left + nodeRect.width / 2 - rect.left; const nodeY = nodeRect.top + nodeRect.height / 2 - rect.top; const startPt = findBorderPoint( { left: centerRect.left - rect.left, top: centerRect.top - rect.top, width: centerRect.width, height: centerRect.height }, nodeX, nodeY ); const endPt = findBorderPoint( { left: nodeRect.left - rect.left, top: nodeRect.top - rect.top, width: nodeRect.width, height: nodeRect.height }, centerX, centerY ); ctx.beginPath(); ctx.moveTo(startPt.x, startPt.y); ctx.lineTo(endPt.x, endPt.y); ctx.strokeStyle = 'rgba(254, 212, 0, 0.5)'; ctx.lineWidth = 3; ctx.lineCap = 'round'; ctx.stroke(); }); } // Mouse move handler document.addEventListener('mousemove', (e) => { const rect = heroNeural.getBoundingClientRect(); const x = (e.clientX - rect.left) / rect.width; const y = (e.clientY - rect.top) / rect.height; targetRotateY = (x - 0.5) * 30; targetRotateX = (y - 0.5) * -30; }); document.addEventListener('mouseleave', () => { targetRotateX = 0; targetRotateY = 0; }); // Animation loop function animate() { currentRotateX += (targetRotateX - currentRotateX) * 0.08; currentRotateY += (targetRotateY - currentRotateY) * 0.08; neuralScene.style.transform = `rotateX(${currentRotateX}deg) rotateY(${currentRotateY}deg)`; drawConnections(); requestAnimationFrame(animate); } animate(); // Mobile: Device orientation if (window.DeviceOrientationEvent && 'ontouchstart' in window) { window.addEventListener('deviceorientation', (e) => { if (e.gamma !== null && e.beta !== null) { targetRotateY = e.gamma * 0.3; targetRotateX = (e.beta - 45) * 0.3; } }); } }