feat: added main.min.js
This commit is contained in:
		
							parent
							
								
									4af0fc7cfe
								
							
						
					
					
						commit
						06d19988c7
					
				
					 1 changed files with 175 additions and 0 deletions
				
			
		
							
								
								
									
										175
									
								
								static/js/main.min.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								static/js/main.min.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,175 @@ | |||
| (() => { | ||||
|     '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!'); | ||||
|     }; | ||||
|   })();   | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Cipher
						Cipher