// 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;
}