script.js

18.00 KB
29/10/2025 16:36
JS
script.js
// 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 \`
            <div class="welcome">
                <h1>สวัสดี Now.js! 🚀</h1>
                <p>Framework ที่ใช้งานง่าย</p>
            </div>
        \`;
    }
}

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, '<span style="color: #c792ea">$1</span>');

    // Strings
    html = html.replace(/(["'`])((?:(?!\1)[^\\]|\\.)*)(\1)/g, '<span style="color: #c3e88d">$1$2$3</span>');

    // Comments
    html = html.replace(/(\/\/.*$)/gm, '<span style="color: #546e7a">$1</span>');

    // Functions and methods
    html = html.replace(/\b(\w+)(\()/g, '<span style="color: #82aaff">$1</span>$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 = '<span class="loading"></span> กำลังสมัคร...';
    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 = `
            <div class="notification-content">
                <span>${message}</span>
                <button class="notification-close">&times;</button>
            </div>
        `;

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