1// Reddit Comment Scraper Landing Page 2console.log('Reddit Comment Scraper Landing Page Loaded'); 3 4// Pricing billing toggle: swaps price + note copy via data-monthly/data-yearly 5document.addEventListener('DOMContentLoaded', function () { 6 const buttons = document.querySelectorAll('.pricing-toggle-btn'); 7 if (!buttons.length) return; 8 buttons.forEach(function (btn) { 9 btn.addEventListener('click', function () { 10 const mode = btn.dataset.billing; 11 buttons.forEach(function (b) { 12 b.classList.toggle('is-active', b === btn); 13 }); 14 document.querySelectorAll('.pricing-card [data-monthly]').forEach(function (el) { 15 el.textContent = el.dataset[mode]; 16 }); 17 }); 18 }); 19}); 20 21// Mobile hamburger menu: close on outside click and nav link click 22document.addEventListener('DOMContentLoaded', function () { 23 const navToggle = document.getElementById('nav-toggle'); 24 if (navToggle) { 25 // Close menu when clicking outside (on the body::after overlay) 26 document.addEventListener('click', function (e) { 27 if (!navToggle.checked) return; 28 const navLinks = document.querySelector('.nav-links'); 29 const toggleLabel = document.querySelector('.nav-toggle-label'); 30 if (navLinks && !navLinks.contains(e.target) && !toggleLabel.contains(e.target)) { 31 navToggle.checked = false; 32 } 33 }); 34 35 // Close menu when a nav link is clicked 36 document.querySelectorAll('.nav-links .nav-link, .nav-links .btn').forEach(function (link) { 37 link.addEventListener('click', function () { 38 navToggle.checked = false; 39 }); 40 }); 41 } 42}); 43 44// TinyAdz Conversion Tracking for CTA Clicks 45document.addEventListener('DOMContentLoaded', function () { 46 // Track all "Add to Chrome" button clicks 47 const chromeButtons = document.querySelectorAll('a[href*="chromewebstore.google.com"]'); 48 49 chromeButtons.forEach(button => { 50 button.addEventListener('click', function () { 51 // TinyAdz conversion tracking 52 if (typeof window.$tac === 'function') { 53 window.$tac('conversion', { type: 'CTA_CLICK' }); 54 } 55 // Meta Pixel events are handled by /meta-pixel-events.js 56 }); 57 }); 58 59 // ======================================== 60 // SCROLL-TRIGGERED ANIMATIONS 61 // ======================================== 62 63 // Create intersection observer for scroll animations 64 const observerOptions = { 65 root: null, 66 rootMargin: '0px 0px -100px 0px', 67 threshold: 0.1 68 }; 69 70 const animationObserver = new IntersectionObserver((entries) => { 71 entries.forEach(entry => { 72 if (entry.isIntersecting) { 73 entry.target.classList.add('visible'); 74 // Stop observing once animated 75 animationObserver.unobserve(entry.target); 76 } 77 }); 78 }, observerOptions); 79 80 // Observe section titles 81 document.querySelectorAll('.section-title').forEach(el => { 82 animationObserver.observe(el); 83 }); 84 85 // Observe feature grids for staggered animation 86 document.querySelectorAll('.features-grid').forEach(el => { 87 animationObserver.observe(el); 88 }); 89 90 // Observe CLI section elements 91 document.querySelectorAll('.cli-terminal, .cli-features, .mcp-chat').forEach(el => { 92 animationObserver.observe(el); 93 }); 94 95 // Observe elements with animate-on-scroll class 96 document.querySelectorAll('.animate-on-scroll').forEach(el => { 97 animationObserver.observe(el); 98 }); 99 100 // Observe stagger-children containers 101 document.querySelectorAll('.stagger-children').forEach(el => { 102 animationObserver.observe(el); 103 }); 104 105 // Observe use case list items with stagger 106 const useCaseList = document.querySelector('.use-case-list'); 107 if (useCaseList) { 108 const useCaseItems = useCaseList.querySelectorAll('li'); 109 useCaseItems.forEach((item, index) => { 110 item.style.opacity = '0'; 111 item.style.transform = 'translateY(20px)'; 112 item.style.transition = `all 0.5s ease-out ${index * 0.1}s`; 113 }); 114 115 const useCaseObserver = new IntersectionObserver((entries) => {
116 entries.forEach(entry => { 117 if (entry.isIntersecting) { 118 useCaseItems.forEach(item => { 119 item.style.opacity = '1'; 120 item.style.transform = 'translateY(0)'; 121 }); 122 useCaseObserver.unobserve(entry.target); 123 } 124 }); 125 }, observerOptions); 126 127 useCaseObserver.observe(useCaseList); 128 } 129 130 // Lazy YouTube facade: only load the player iframe on click (keeps the 131 // page fast â no YouTube JS until the user actually plays a video). 132 document.querySelectorAll('.video-card').forEach(card => { 133 const thumb = card.querySelector('.video-thumb'); 134 if (!thumb) return; 135 thumb.addEventListener('click', () => { 136 const id = card.dataset.videoId; 137 if (!id) return; 138 const iframe = document.createElement('iframe'); 139 iframe.className = 'video-iframe'; 140 iframe.src = `https://www.youtube-nocookie.com/embed/${id}?autoplay=1&rel=0`; 141 iframe.title = card.querySelector('.video-title')?.textContent || 'Video'; 142 iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'; 143 iframe.allowFullscreen = true; 144 thumb.replaceWith(iframe); 145 }); 146 }); 147}); 148 149// ======================================== 150// PLATFORM LOGO MARQUEE â fill any viewport for an endless loop 151// ======================================== 152// The CSS loop is seamless, but the static track only holds a couple of 153// screen-widths of logos, so on wide displays it runs out and shows empty 154// space. Duplicate the strip until it comfortably exceeds the viewport, and 155// scale the duration to the new width so the scroll speed stays constant. 156document.addEventListener('DOMContentLoaded', function () { 157 const track = document.querySelector('.platform-track'); 158 const marquee = track && track.closest('.platform-marquee'); 159 if (!track || !marquee) return; 160 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; 161 162 // Clone the full original set (an even number of logos, so the -50% loop 163 // stays seamless). Mark clones hidden from assistive tech. 164 const originals = Array.from(track.children); 165 const target = Math.max(marquee.offsetWidth, (window.screen && screen.width) || 0) * 2 + 400; 166 let guard = 0; 167 while (track.scrollWidth < target && guard < 12) { 168 originals.forEach(function (el) { 169 const clone = el.cloneNode(true); 170 clone.setAttribute('aria-hidden', 'true'); 171 clone.setAttribute('alt', ''); 172 track.appendChild(clone); 173 }); 174 guard++; 175 } 176 177 // translateX(-50%) travels half the track width; ~60px/s reads as calm. 178 const duration = (track.scrollWidth / 2) / 60; 179 track.style.animationDuration = duration.toFixed(1) + 's'; 180});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.