1/** 2 * CNC tool tags email magnet. Slides up after scroll, POSTs to AirShop. 3 */ 4(function() { 5 var card = document.getElementById('cnc-magnet'); 6 var form = document.getElementById('cnc-magnet-form'); 7 var messageEl = document.getElementById('cnc-magnet-message'); 8 var emailInput = document.getElementById('cnc-magnet-email'); 9 var closeButtons = document.querySelectorAll('[data-cnc-magnet-close]'); 10 11 if (!card || !form || !messageEl || !emailInput) return; 12 13 var STORAGE_DISMISS = 'airshop-cnc-magnet-dismissed'; 14 var STORAGE_SUBMIT = 'airshop-cnc-magnet-submitted'; 15 var MIN_MS = 8000; 16 var MIN_SCROLL = 0.4; 17 var formLoadTime = Date.now(); 18 var shown = false; 19 var readyAt = Date.now() + MIN_MS; 20 21 function apiUrl() { 22 if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') { 23 return 'http://localhost:3000/api/leads/cnc-tool-tags-signup'; 24 } 25 return 'https://airshop.work/api/leads/cnc-tool-tags-signup'; 26 } 27 28 function alreadyDone() { 29 try { 30 return Boolean( 31 window.localStorage.getItem(STORAGE_DISMISS) || 32 window.localStorage.getItem(STORAGE_SUBMIT) 33 ); 34 } catch (err) { 35 return false; 36 } 37 } 38 39 function persist(key) { 40 try { 41 window.localStorage.setItem(key, '1'); 42 } catch (err) { 43 /* ignore quota / private mode */ 44 } 45 } 46 47 function hide() { 48 card.classList.remove('is-visible'); 49 card.setAttribute('hidden', ''); 50 } 51 52 function show() { 53 if (shown || alreadyDone()) return; 54 shown = true; 55 card.removeAttribute('hidden'); 56 window.setTimeout(function() { 57 card.classList.add('is-visible'); 58 }, 20); 59 } 60 61 function dismiss() { 62 persist(STORAGE_DISMISS); 63 hide(); 64 } 65 66 function scrollProgress() { 67 var doc = document.documentElement; 68 var max = Math.max(doc.scrollHeight - window.innerHeight, 1); 69 return window.scrollY / max; 70 } 71 72 function maybeShow() { 73 if (shown || alreadyDone()) return; 74 if (Date.now() < readyAt) return; 75 if (scrollProgress() < MIN_SCROLL) return; 76 show(); 77 } 78 79 if (alreadyDone()) { 80 hide(); 81 return; 82 } 83 84 window.addEventListener('scroll', maybeShow, { passive: true }); 85 window.setTimeout(maybeShow, MIN_MS); 86 87 closeButtons.forEach(function(button) { 88 button.addEventListener('click', dismiss); 89 }); 90 91 document.addEventListener('keydown', function(event) { 92 if (event.key === 'Escape' && card.classList.contains('is-visible')) { 93 dismiss(); 94 } 95 }); 96 97 form.addEventListener('submit', async function(event) { 98 event.preventDefault(); 99 100 var submitBtn = form.querySelector('button[type="submit"]'); 101 var honeypot = form.querySelector('input[name="website"]'); 102 var email = emailInput.value.trim(); 103 104 if (honeypot && honeypot.value.trim()) { 105 return; 106 } 107 108 if (!email) { 109 messageEl.textContent = 'Please enter your email.'; 110 messageEl.className = 'cnc-magnet-message error'; 111 return; 112 } 113 114 submitBtn.disabled = true; 115 messageEl.textContent = ''; 116 messageEl.className = 'cnc-magnet-message'; 117 118 try { 119 var response = await fetch(apiUrl(), { 120 method: 'POST', 121 headers: { 'Content-Type': 'application/json' }, 122 body: JSON.stringify({ 123 email: email, 124 website: honeypot ? honeypot.value : '', 125 form_load_time: formLoadTime, 126 leadType: 'cnc_tool_tags' 127 }) 128 }); 129 130 var data = await response.json().catch(function() { 131 return {}; 132 }); 133 134 if (!response.ok) { 135 throw new Error(data.error || 'Something went wrong'); 136 } 137 138 messageEl.textContent = 'Check your inbox.';
139 messageEl.className = 'cnc-magnet-message success'; 140 form.reset(); 141 persist(STORAGE_SUBMIT); 142 window.setTimeout(hide, 1800); 143 } catch (err) { 144 messageEl.textContent = err.message || 'Failed to sign up. Please try again.'; 145 messageEl.className = 'cnc-magnet-message error'; 146 } finally { 147 submitBtn.disabled = false; 148 } 149 }); 150})();
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.