175 lines
		
	
	
	
		
			4.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			175 lines
		
	
	
	
		
			4.8 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!');
 | 
						|
  };
 | 
						|
})();
 |