// Now.js Website JavaScript class NowJSWebsite { constructor() { this.init(); this.bindEvents(); this.startAnimations(); } init() { // Initialize components this.navbar = document.querySelector('.navbar'); this.backToTopBtn = document.getElementById('backToTop'); this.heroCode = document.getElementById('heroCode'); this.newsletterForm = document.getElementById('newsletterForm'); // State this.isScrolling = false; this.currentTab = 'component'; // Initialize features this.initSmoothScroll(); this.initTypingAnimation(); this.initCounterAnimation(); this.initTabSystem(); this.initCopyButtons(); } bindEvents() { // Scroll events window.addEventListener('scroll', this.throttle(this.handleScroll.bind(this), 16)); // Navigation events document.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', this.handleNavClick.bind(this)); }); // Button events document.getElementById('getStartedBtn')?.addEventListener('click', this.handleGetStarted.bind(this)); document.getElementById('learnMoreBtn')?.addEventListener('click', this.handleLearnMore.bind(this)); // Back to top button this.backToTopBtn?.addEventListener('click', this.scrollToTop.bind(this)); // Newsletter form this.newsletterForm?.addEventListener('submit', this.handleNewsletterSubmit.bind(this)); // Mobile menu toggle const navToggle = document.querySelector('.nav-toggle'); navToggle?.addEventListener('click', this.toggleMobileMenu.bind(this)); // Resize events window.addEventListener('resize', this.throttle(this.handleResize.bind(this), 100)); // Tab system document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', this.handleTabClick.bind(this)); }); // Intersection Observer for animations this.initIntersectionObserver(); } // Smooth scrolling for navigation links initSmoothScroll() { document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', (e) => { e.preventDefault(); const target = document.querySelector(anchor.getAttribute('href')); if (target) { const offsetTop = target.offsetTop - 80; // Account for fixed navbar window.scrollTo({ top: offsetTop, behavior: 'smooth' }); } }); }); } // Typing animation for hero code initTypingAnimation() { if (!this.heroCode) return; const code = `import { App } from 'now.js'; const app = new App({ root: '#app', router: true, store: true }); // สร้าง Component class Welcome extends Component { render() { return \`

สวัสดี Now.js! 🚀

Framework ที่ใช้งานง่าย

\`; } } app.mount(Welcome);`; this.typeText(this.heroCode, code, 50); } typeText(element, text, speed = 100) { element.innerHTML = ''; let i = 0; const timer = setInterval(() => { if (i < text.length) { element.innerHTML += text.charAt(i); i++; } else { clearInterval(timer); this.addSyntaxHighlighting(element); } }, speed); } addSyntaxHighlighting(element) { let html = element.innerHTML; // Keywords html = html.replace(/\b(import|from|class|extends|return|const|new)\b/g, '$1'); // Strings html = html.replace(/(["'`])((?:(?!\1)[^\\]|\\.)*)(\1)/g, '$1$2$3'); // Comments html = html.replace(/(\/\/.*$)/gm, '$1'); // Functions and methods html = html.replace(/\b(\w+)(\()/g, '$1$2'); element.innerHTML = html; } // Counter animation initCounterAnimation() { const counters = document.querySelectorAll('.stat-number'); counters.forEach(counter => { const target = parseInt(counter.dataset.count); const increment = target / 100; let current = 0; const updateCounter = () => { if (current < target) { current += increment; counter.textContent = Math.floor(current).toLocaleString('th-TH'); requestAnimationFrame(updateCounter); } else { counter.textContent = target.toLocaleString('th-TH'); } }; // Start animation when element is in viewport const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { updateCounter(); observer.unobserve(counter); } }); }); observer.observe(counter); }); } // Tab system for code examples initTabSystem() { const tabBtns = document.querySelectorAll('.tab-btn'); const tabContents = document.querySelectorAll('.tab-content'); tabBtns.forEach(btn => { btn.addEventListener('click', () => { const tabId = btn.dataset.tab; // Remove active class from all buttons and contents tabBtns.forEach(b => b.classList.remove('active')); tabContents.forEach(c => c.classList.remove('active')); // Add active class to clicked button and corresponding content btn.classList.add('active'); document.getElementById(tabId)?.classList.add('active'); this.currentTab = tabId; }); }); } // Copy button functionality initCopyButtons() { document.querySelectorAll('.copy-btn').forEach(btn => { btn.addEventListener('click', () => { const text = btn.dataset.copy; navigator.clipboard.writeText(text).then(() => { const originalText = btn.textContent; btn.textContent = '✅'; setTimeout(() => { btn.textContent = originalText; }, 2000); }); }); }); } // Intersection Observer for scroll animations initIntersectionObserver() { const options = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('animate'); // Add stagger delay for grid items const gridItems = entry.target.querySelectorAll('.feature-card, .community-card'); gridItems.forEach((item, index) => { setTimeout(() => { item.style.animation = `fadeInUp 0.6s ease-out forwards`; }, index * 100); }); } }); }, options); // Observe sections document.querySelectorAll('.features, .examples, .getting-started, .community').forEach(section => { observer.observe(section); }); } // Event handlers handleScroll() { const scrollTop = window.pageYOffset; // Navbar background if (scrollTop > 50) { this.navbar.classList.add('scrolled'); } else { this.navbar.classList.remove('scrolled'); } // Back to top button if (scrollTop > 300) { this.backToTopBtn?.classList.add('visible'); } else { this.backToTopBtn?.classList.remove('visible'); } // Update active nav link this.updateActiveNavLink(); // Parallax effect for hero background const hero = document.querySelector('.hero'); if (hero && scrollTop < hero.offsetHeight) { const parallaxSpeed = scrollTop * 0.5; hero.style.transform = `translateY(${parallaxSpeed}px)`; } } updateActiveNavLink() { const sections = document.querySelectorAll('section[id]'); const navLinks = document.querySelectorAll('.nav-link'); let currentSection = ''; sections.forEach(section => { const rect = section.getBoundingClientRect(); if (rect.top <= 100 && rect.bottom >= 100) { currentSection = section.id; } }); navLinks.forEach(link => { link.classList.remove('active'); if (link.getAttribute('href') === `#${currentSection}`) { link.classList.add('active'); } }); } handleNavClick(e) { e.preventDefault(); const targetId = e.target.getAttribute('href'); if (targetId.startsWith('#')) { const target = document.querySelector(targetId); if (target) { const offsetTop = target.offsetTop - 80; window.scrollTo({ top: offsetTop, behavior: 'smooth' }); } } } handleGetStarted() { const docsSection = document.getElementById('docs'); if (docsSection) { docsSection.scrollIntoView({behavior: 'smooth'}); } // Track event (analytics) this.trackEvent('get_started_clicked', {source: 'hero'}); } handleLearnMore() { const featuresSection = document.getElementById('features'); if (featuresSection) { featuresSection.scrollIntoView({behavior: 'smooth'}); } // Track event (analytics) this.trackEvent('learn_more_clicked', {source: 'hero'}); } handleTabClick(e) { const tabId = e.target.dataset.tab; if (tabId) { this.switchTab(tabId); } } switchTab(tabId) { const tabBtns = document.querySelectorAll('.tab-btn'); const tabContents = document.querySelectorAll('.tab-content'); tabBtns.forEach(btn => btn.classList.remove('active')); tabContents.forEach(content => content.classList.remove('active')); document.querySelector(`[data-tab="${tabId}"]`)?.classList.add('active'); document.getElementById(tabId)?.classList.add('active'); this.currentTab = tabId; // Track tab change this.trackEvent('tab_changed', {tab: tabId}); } handleNewsletterSubmit(e) { e.preventDefault(); const emailInput = e.target.querySelector('input[type="email"]'); const submitBtn = e.target.querySelector('button[type="submit"]'); const email = emailInput.value; if (!this.isValidEmail(email)) { this.showNotification('กรุณาใส่อีเมลที่ถูกต้อง', 'error'); return; } // Show loading state const originalText = submitBtn.textContent; submitBtn.innerHTML = ' กำลังสมัคร...'; submitBtn.disabled = true; // Simulate API call setTimeout(() => { // Reset form emailInput.value = ''; submitBtn.textContent = originalText; submitBtn.disabled = false; this.showNotification('สมัครสมาชิกเรียบร้อยแล้ว! 🎉', 'success'); // Track successful subscription this.trackEvent('newsletter_subscribed', {email}); }, 2000); } toggleMobileMenu() { const navMenu = document.querySelector('.nav-menu'); const navToggle = document.querySelector('.nav-toggle'); navMenu?.classList.toggle('active'); navToggle?.classList.toggle('active'); } handleResize() { // Close mobile menu on desktop if (window.innerWidth > 768) { document.querySelector('.nav-menu')?.classList.remove('active'); document.querySelector('.nav-toggle')?.classList.remove('active'); } } scrollToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); this.trackEvent('scroll_to_top_clicked'); } // Utility functions startAnimations() { // Start counter animations after page load setTimeout(() => { this.initCounterAnimation(); }, 1000); // Start code typing animation setTimeout(() => { this.initTypingAnimation(); }, 1500); } throttle(func, limit) { let inThrottle; return function() { const args = arguments; const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } } } debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } showNotification(message, type = 'info') { // Create notification element const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.innerHTML = `
${message}
`; // Add styles Object.assign(notification.style, { position: 'fixed', top: '100px', right: '20px', background: type === 'success' ? '#48bb78' : type === 'error' ? '#f56565' : '#667eea', color: 'white', padding: '1rem', borderRadius: '0.5rem', boxShadow: '0 4px 20px rgba(0,0,0,0.15)', zIndex: '10000', animation: 'slideInRight 0.3s ease-out', maxWidth: '300px' }); document.body.appendChild(notification); // Auto remove after 5 seconds setTimeout(() => { notification.style.animation = 'slideOutRight 0.3s ease-in'; setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 300); }, 5000); // Close button notification.querySelector('.notification-close').addEventListener('click', () => { notification.style.animation = 'slideOutRight 0.3s ease-in'; setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 300); }); } trackEvent(eventName, data = {}) { // Analytics tracking (replace with your analytics service) console.log('Event tracked:', eventName, data); // Example: Google Analytics 4 if (typeof gtag !== 'undefined') { gtag('event', eventName, data); } // Example: Facebook Pixel if (typeof fbq !== 'undefined') { fbq('trackCustom', eventName, data); } } // Performance monitoring measurePerformance() { if ('performance' in window) { const navigation = performance.getEntriesByType('navigation')[0]; const loadTime = navigation.loadEventEnd - navigation.loadEventStart; console.log('Page load time:', loadTime, 'ms'); // Track performance this.trackEvent('page_performance', { loadTime: Math.round(loadTime), domContentLoaded: Math.round(navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart) }); } } // Lazy loading for images initLazyLoading() { if ('IntersectionObserver' in window) { const imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.remove('lazy'); imageObserver.unobserve(img); } }); }); document.querySelectorAll('img[data-src]').forEach(img => { imageObserver.observe(img); }); } } // Service Worker registration registerServiceWorker() { if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js') .then(registration => { console.log('SW registered:', registration); }) .catch(error => { console.log('SW registration failed:', error); }); } } // Dark mode support initDarkMode() { const darkModeToggle = document.querySelector('.dark-mode-toggle'); const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)'); const currentTheme = localStorage.getItem('theme'); if (currentTheme === 'dark' || (!currentTheme && prefersDarkScheme.matches)) { document.body.classList.add('dark-mode'); } darkModeToggle?.addEventListener('click', () => { document.body.classList.toggle('dark-mode'); const theme = document.body.classList.contains('dark-mode') ? 'dark' : 'light'; localStorage.setItem('theme', theme); this.trackEvent('theme_changed', {theme}); }); } } // Additional CSS for animations const additionalStyles = ` @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } } .navbar.scrolled { background: rgba(255, 255, 255, 0.98); box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1); } .nav-menu.active { display: flex; position: absolute; top: 100%; left: 0; right: 0; background: white; flex-direction: column; padding: 1rem; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); } .nav-toggle.active span:nth-child(1) { transform: rotate(45deg) translate(5px, 5px); } .nav-toggle.active span:nth-child(2) { opacity: 0; } .nav-toggle.active span:nth-child(3) { transform: rotate(-45deg) translate(7px, -6px); } `; // Inject additional styles const styleSheet = document.createElement('style'); styleSheet.textContent = additionalStyles; document.head.appendChild(styleSheet); // Initialize the website when DOM is loaded document.addEventListener('DOMContentLoaded', () => { const website = new NowJSWebsite(); // Performance monitoring window.addEventListener('load', () => { website.measurePerformance(); website.initLazyLoading(); website.registerServiceWorker(); website.initDarkMode(); }); // Global error handling window.addEventListener('error', (e) => { console.error('JavaScript error:', e.error); website.trackEvent('javascript_error', { message: e.message, filename: e.filename, lineno: e.lineno }); }); }); // Export for use in other scripts if (typeof module !== 'undefined' && module.exports) { module.exports = NowJSWebsite; }