175 lines
		
	
	
		
			No EOL
		
	
	
		
			5.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			175 lines
		
	
	
		
			No EOL
		
	
	
		
			5.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
(() => {
 | 
						|
    'use strict';
 | 
						|
  
 | 
						|
    const navbar = document.querySelector('.navbar');
 | 
						|
    const featureCards = document.querySelectorAll('.feature-card');
 | 
						|
    const newsletterCards = document.querySelectorAll('.newsletter-card');
 | 
						|
    const progressBar = document.querySelector('.reading-progress');
 | 
						|
    const emailInput = document.getElementById('email-input');
 | 
						|
    const notifyBtn = document.getElementById('notify-button');
 | 
						|
    const emailRE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
 | 
						|
  
 | 
						|
    document.addEventListener(
 | 
						|
      'click',
 | 
						|
      (e) => {
 | 
						|
        const a = e.target.closest('a[href^="#"]');
 | 
						|
        if (!a) return;
 | 
						|
  
 | 
						|
        const href = a.getAttribute('href');
 | 
						|
        if (!href || href === '#') return;
 | 
						|
  
 | 
						|
        const target = document.querySelector(href);
 | 
						|
        if (!target) return;
 | 
						|
  
 | 
						|
        e.preventDefault();
 | 
						|
        target.scrollIntoView({ behavior: 'smooth', block: 'start' });
 | 
						|
      },
 | 
						|
      { passive: false }
 | 
						|
    );
 | 
						|
  
 | 
						|
    if ('IntersectionObserver' in window) {
 | 
						|
      const observer = new IntersectionObserver(
 | 
						|
        (entries, obs) => {
 | 
						|
          for (const entry of entries) {
 | 
						|
            if (entry.isIntersecting) {
 | 
						|
              entry.target.classList.add('is-visible');
 | 
						|
              obs.unobserve(entry.target);
 | 
						|
            }
 | 
						|
          }
 | 
						|
        },
 | 
						|
        { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }
 | 
						|
      );
 | 
						|
  
 | 
						|
      featureCards.forEach((card) => {
 | 
						|
        card.classList.add('will-animate');
 | 
						|
        observer.observe(card);
 | 
						|
      });
 | 
						|
    } else {
 | 
						|
      featureCards.forEach((card) => card.classList.add('is-visible'));
 | 
						|
    }
 | 
						|
  
 | 
						|
    window.addEventListener('load', () => {
 | 
						|
      document
 | 
						|
        .querySelectorAll('.newsletter-header, .newsletter-content')
 | 
						|
        .forEach((el, i) => {
 | 
						|
          el.style.transitionDelay = `${i * 0.2}s`;
 | 
						|
          el.classList.add('is-visible');
 | 
						|
        });
 | 
						|
  
 | 
						|
      newsletterCards.forEach((card, i) => {
 | 
						|
        card.style.transitionDelay = `${i * 0.1}s`;
 | 
						|
        card.classList.add('is-visible');
 | 
						|
      });
 | 
						|
    });
 | 
						|
  
 | 
						|
    let lastY = 0;
 | 
						|
    let ticking = false;
 | 
						|
  
 | 
						|
    function onScroll() {
 | 
						|
      lastY = window.scrollY || window.pageYOffset;
 | 
						|
      if (!ticking) {
 | 
						|
        requestAnimationFrame(updateOnScroll);
 | 
						|
        ticking = true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  
 | 
						|
    function updateOnScroll() {
 | 
						|
      if (navbar) {
 | 
						|
        navbar.classList.toggle('navbar--scrolled', lastY > 50);
 | 
						|
        navbar.classList.toggle('navbar--deeper', lastY > 100);
 | 
						|
      }
 | 
						|
  
 | 
						|
      if (progressBar) {
 | 
						|
        const max = document.body.scrollHeight - window.innerHeight;
 | 
						|
        const progress = max > 0 ? Math.min(Math.max(lastY / max, 0), 1) : 0;
 | 
						|
        progressBar.style.width = `${progress * 100}%`;
 | 
						|
      }
 | 
						|
  
 | 
						|
      ticking = false;
 | 
						|
    }
 | 
						|
  
 | 
						|
    window.addEventListener('scroll', onScroll, { passive: true });
 | 
						|
    updateOnScroll(); // initial state
 | 
						|
  
 | 
						|
    if (notifyBtn && emailInput) {
 | 
						|
      let inFlight = false;
 | 
						|
      const controller = new AbortController();
 | 
						|
  
 | 
						|
      notifyBtn.addEventListener('click', async () => {
 | 
						|
        const email = emailInput.value.trim();
 | 
						|
        if (!emailRE.test(email)) {
 | 
						|
          alert('Please enter a valid email address.');
 | 
						|
          emailInput.focus();
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        if (inFlight) return;
 | 
						|
  
 | 
						|
        inFlight = true;
 | 
						|
        notifyBtn.disabled = true;
 | 
						|
  
 | 
						|
        try {
 | 
						|
          const res = await fetch('/subscribe', {
 | 
						|
            method: 'POST',
 | 
						|
            headers: { 'Content-Type': 'application/json' },
 | 
						|
            body: JSON.stringify({ email }),
 | 
						|
            signal: controller.signal,
 | 
						|
          });
 | 
						|
  
 | 
						|
          let message = 'Thank you for subscribing!';
 | 
						|
          if (res.ok) {
 | 
						|
            const data = await res.json().catch(() => ({}));
 | 
						|
            message = data.message || message;
 | 
						|
          } else {
 | 
						|
            message = "Thanks! We'll notify you when we launch.";
 | 
						|
          }
 | 
						|
  
 | 
						|
          alert(message);
 | 
						|
          emailInput.value = '';
 | 
						|
        } catch (err) {
 | 
						|
          console.error('Subscribe error:', err);
 | 
						|
          alert("Thanks! We'll notify you when we launch.");
 | 
						|
          emailInput.value = '';
 | 
						|
        } finally {
 | 
						|
          notifyBtn.disabled = false;
 | 
						|
          inFlight = false;
 | 
						|
        }
 | 
						|
      });
 | 
						|
  
 | 
						|
      window.addEventListener('beforeunload', () => controller.abort(), {
 | 
						|
        passive: true,
 | 
						|
      });
 | 
						|
    }
 | 
						|
  
 | 
						|
    window.shareNewsletter = async function shareNewsletter() {
 | 
						|
      try {
 | 
						|
        if (navigator.share) {
 | 
						|
          await navigator.share({
 | 
						|
            title: document.title,
 | 
						|
            text: 'Check out this newsletter from RideAware',
 | 
						|
            url: location.href,
 | 
						|
          });
 | 
						|
          return;
 | 
						|
        }
 | 
						|
      } catch (err) {
 | 
						|
        console.warn('navigator.share error/cancel:', err);
 | 
						|
      }
 | 
						|
  
 | 
						|
      if (navigator.clipboard && window.isSecureContext) {
 | 
						|
        try {
 | 
						|
          await navigator.clipboard.writeText(location.href);
 | 
						|
          alert('Newsletter URL copied to clipboard!');
 | 
						|
          return;
 | 
						|
        } catch {
 | 
						|
          /* fall through */
 | 
						|
        }
 | 
						|
      }
 | 
						|
  
 | 
						|
      const tmp = document.createElement('input');
 | 
						|
      tmp.value = location.href;
 | 
						|
      document.body.appendChild(tmp);
 | 
						|
      tmp.select();
 | 
						|
      document.execCommand('copy');
 | 
						|
      document.body.removeChild(tmp);
 | 
						|
      alert('Newsletter URL copied to clipboard!');
 | 
						|
    };
 | 
						|
  })();  
 |