1/** 2 * WIRE Miami: Lead Capture & Content Gating System 3 * ================================================== 4 * Requires: wire-config.js loaded BEFORE this file. 5 * 6 * HOW IT WORKS: 7 * - Floor plans, detailed pricing, and full gallery are NOT in the HTML. 8 * - They are loaded dynamically ONLY after registration. 9 * - The registration modal has NO X button, only Submit or "No thanks" link. 10 * - "No thanks" scrolls to contact form; content stays locked. 11 * - localStorage tracks registration so returning visitors see everything. 12 * 13 * USAGE IN HTML: 14 * 1. Add data-gate="floorplans" to any button/link that should trigger gate 15 * 2. Add class="wire-gated" to any section that should be blurred until registration 16 * 3. Add data-gated-src="real-image.jpg" on imgs to lazy-load after registration 17 * 4. The system auto-creates the modal, slide-in CTA, and exit intent 18 */ 19 20(function() { 21 'use strict'; 22 23 var W = window.WIRE; 24 if (!W) { console.error('wire-config.js must load before wire-lead-capture.js'); return; } 25 26 var PROP = W.property.name; 27 var ACCENT = (W.colors && W.colors.accent) || '#424242'; 28 var GOLD = (W.colors && W.colors.gold) || '#424242'; 29 30 /* Exit-popup copy: overridable per site via window.WIRE.exitPopup. 31 Default is the safe "private showing" CTA. Only override when this 32 property has pre-construction pricing authorized by Adrian. */ 33 var EXIT_POPUP = { 34 title: (W.exitPopup && W.exitPopup.title) || 'Schedule a Private Showing', 35 sub: (W.exitPopup && W.exitPopup.sub) || 'Experience ' + PROP + ' in person. Weâll arrange a private tour at your convenience.', 36 btn: (W.exitPopup && W.exitPopup.btn) || 'Schedule a Showing' 37 }; 38 39 /* ââââââââââââââââââââââââââââââ 40 REGISTRATION STATE 41 ââââââââââââââââââââââââââââââ */ 42 43 function isRegistered() { 44 try { return localStorage.getItem('wireRegistered') === 'true'; } catch(e) { return false; } 45 } 46 47 function setRegistered(name, email, phone) { 48 try { 49 localStorage.setItem('wireRegistered', 'true'); 50 localStorage.setItem('wireName', name); 51 localStorage.setItem('wireEmail', email); 52 localStorage.setItem('wirePhone', phone); 53 } catch(e) {} 54 } 55 56 /* ââââââââââââââââââââââââââââââ 57 REGISTRATION MODAL (no X button) 58 ââââââââââââââââââââââââââââââ */ 59 60 var modalHTML = '' + 61 '<div id="wire-gate" role="dialog" aria-modal="true" aria-labelledby="wire-gate-title" style="' + 62 'display:none;position:fixed;inset:0;z-index:10000;background:rgba(0,0,0,0.88);' + 63 'backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);' + 64 'align-items:center;justify-content:center;padding:20px;">' + 65 '<div style="background:#1a1a1a;color:#fff;padding:44px 36px;border-radius:0;' + 66 'max-width:460px;width:100%;position:relative;box-shadow:0 24px 80px rgba(0,0,0,0.5);">' + 67 '<h2 id="wire-gate-title" style="font-family:Georgia,serif;font-size:1.6rem;font-weight:400;' + 68 'margin:0 0 8px;line-height:1.3;text-align:center;"></h2>' + 69 '<p id="wire-gate-sub" style="color:rgba(255,255,255,0.6);font-size:0.9rem;text-align:center;' + 70 'margin:0 0 28px;line-height:1.5;"></p>' + 71 '<form id="wire-gate-form" novalidate>' + 72 '<div style="margin-bottom:16px;">' + 73 '<label for="wire-g-name" style="display:block;font-size:0.78rem;color:rgba(255,255,255,0.5);' + 74 'margin-bottom:6px;text-transform:uppercase;letter-spacing:0.08em;">Full Name *</label>' + 75 '<input type="text" id="wire-g-name" name="name" required autocomplete="name" ' + 76 'style="width:100%;padding:14px 16px;background:#2a2a2a;border:1px solid #444;' + 77 'border-radius:0;color:#fff;font-size:1rem;outline:none;" ' + 78 'onfocus="this.style.borderColor=\'' + ACCENT + '\'" onblur="this.style.borderColor=\'#444\'">' + 79 '</div>' + 80 '<div style="margin-bottom:16px;">' + 81 '<label for="wire-g-email" style="display:block;font-size:0.78rem;color:rgba(255,255,255,0.5);' + 82 'margin-bottom:6px;text-transform:uppercase;letter-spacing:0.08em;">Email *</label>' + 83 '<input type="email" id="wire-g-email" name="email" required autocomplete="email" ' + 84 'style="width:100%;padding:14px 16px;background:#2a2a2a;border:1px solid #444;' + 85 'border-radius:0;color:#fff;font-size:1rem;outline:none;" ' + 86 'onfocus="this.style.borderColor=\'' + ACCENT + '\'" onblur
86="this.style.borderColor=\'#444\'">' + 87 '</div>' + 88 '<div style="margin-bottom:20px;">' + 89 '<label for="wire-g-phone" style="display:block;font-size:0.78rem;color:rgba(255,255,255,0.5);' + 90 'margin-bottom:6px;text-transform:uppercase;letter-spacing:0.08em;">Phone *</label>' + 91 '<input type="tel" id="wire-g-phone" name="phone" required autocomplete="tel" ' + 92 'style="width:100%;padding:14px 16px;background:#2a2a2a;border:1px solid #444;' + 93 'border-radius:0;color:#fff;font-size:1rem;outline:none;" ' + 94 'onfocus="this.style.borderColor=\'' + ACCENT + '\'" onblur="this.style.borderColor=\'#444\'">' + 95 '</div>' + 96 '<div style="margin-bottom:20px;">' + 97 '<span style="display:block;font-size:0.78rem;color:rgba(255,255,255,0.5);' + 98 'margin-bottom:6px;text-transform:uppercase;letter-spacing:0.08em;">Are you a buyer or an agent? *</span>' + 99 '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;">' + 100 '<label class="wire-ct-opt"><input type="radio" name="wire-g-contact-type" value="buyer">' + 101 '<span>I\'m a buyer</span></label>' + 102 '<label class="wire-ct-opt"><input type="radio" name="wire-g-contact-type" value="agent">' + 103 '<span>I\'m a real estate agent</span></label>' + 104 '</div>' + 105 '</div>' + 106 '<div id="wire-g-error" style="display:none;color:#FFFFFF;font-size:0.85rem;margin-bottom:12px;text-align:center;"></div>' + 107 '<button type="submit" id="wire-g-btn" style="width:100%;padding:16px;background:' + ACCENT + ';' + 108 'color:#fff;border:none;border-radius:0;font-size:1rem;font-weight:600;cursor:pointer;' + 109 'letter-spacing:0.03em;transition:background 0.2s;"></button>' + 110 '</form>' + 111 '<p style="text-align:center;margin:16px 0 0;">' + 112 '<a href="#" id="wire-g-dismiss" style="color:#666;font-size:0.82rem;text-decoration:none;">' + 113 'No thanks, I\u2019ll request info later</a></p>' + 114 '<p style="text-align:center;margin:12px 0 0;font-size:0.68rem;color:#555;line-height:1.5;max-width:380px;margin-left:auto;margin-right:auto;">' + 115 'By registering you agree to receive calls, emails and text messages (SMS/MMS) from WIRE Miami (Waterfront Investment Real Estate, LLC) about this and similar pre-construction opportunities, including automated messages. Message frequency varies. Msg and data rates may apply. Reply STOP to opt out, HELP for help. Consent is not a condition of any purchase. Communications may be recorded for quality assurance. ' + 116 'See our <a href="/privacy-policy.html" target="_blank" style="color:#777;text-decoration:underline;">Privacy Policy</a> and <a href="/terms-of-service.html" target="_blank" style="color:#777;text-decoration:underline;">Terms of Service</a>.</p>' + 117 '</div>' + 118 '</div>'; 119 120 /* ââââââââââââââââââââââââââââââ 121 SLIDE-IN CTA (scroll/timer triggered) 122 ââââââââââââââââââââââââââââââ */ 123 124 var slideInHTML = '' + 125 '<div id="wire-slidein" role="dialog" aria-label="Get pricing guide" style="' + 126 'display:none;position:fixed;bottom:140px;right:20px;z-index:9990;' + 127 'background:#1a1a1a;color:#fff;padding:28px 24px;border-radius:0;' + 128 'max-width:340px;width:calc(100% - 40px);box-shadow:0 12px 40px rgba(0,0,0,0.4);' + 129 'transform:translateY(20px);opacity:0;transition:transform 0.4s ease,opacity 0.4s ease;">' + 130 '<p style="font-family:Georgia,serif;font-size:1.15rem;margin:0 0 6px;line-height:1.3;">' + 131 'Get the ' + PROP + ' Pricing Guide</p>' + 132 '<p style="font-size:0.82rem;color:rgba(255,255,255,0.6);margin:0 0 16px;line-height:1.4;">' + 133 'Floor plans, pricing breakdown, deposit structure, delivered to your inbox.</p>' + 134 '<button id="wire-slidein-btn" style="width:100%;padding:14px;background:' + ACCENT + ';' + 135 'color:#fff;border:none;border-radius:0;font-size:0.9rem;font-weight:600;cursor:pointer;">' + 136 'Send Me the Guide</button>' + 137 '<p style="text-align:center;margin:10px 0 0;">' + 138 '<a href="#" id="wire-slidein-dismiss" style="color:#666;font-size:0.78rem;text-decoration:none;">' + 139 'Maybe later</a></p>' + 140 '<p style="font-size:0.62rem;color:#555;line-height:1.4;margin:12px 0 0;text-align:center;">' +
141 'By registering you agree to receive calls, emails and text messages (SMS/MMS) from WIRE Miami (Waterfront Investment Real Estate, LLC) about this and similar pre-construction opportunities, including automated messages. Message frequency varies. Msg and data rates may apply. Reply STOP to opt out, HELP for help. Consent is not a condition of any purchase. Communications may be recorded for quality assurance. ' + 142 'See our <a href="/privacy-policy.html" target="_blank" style="color:#777;text-decoration:underline;">Privacy Policy</a> and <a href="/terms-of-service.html" target="_blank" style="color:#777;text-decoration:underline;">Terms of Service</a>.</p>' + 143 '</div>'; 144 145 /* ââââââââââââââââââââââââââââââ 146 EXIT INTENT 147 ââââââââââââââââââââââââââââââ */ 148 149 var exitHTML = '' + 150 '<div id="wire-exit" role="dialog" aria-modal="true" aria-label="' + EXIT_POPUP.title + '" style="' + 151 'display:none;position:fixed;inset:0;z-index:10001;background:rgba(0,0,0,0.9);' + 152 'backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);' + 153 'align-items:center;justify-content:center;padding:20px;">' + 154 '<div style="background:#1a1a1a;color:#fff;padding:44px 36px;border-radius:0;' + 155 'max-width:440px;width:100%;text-align:center;box-shadow:0 24px 80px rgba(0,0,0,0.5);">' + 156 157 '<h3 style="font-family:Georgia,serif;font-size:1.4rem;font-weight:400;margin:0 0 8px;line-height:1.3;">' + 158 'Before You Go, ' + EXIT_POPUP.title + '</h3>' + 159 '<p style="color:rgba(255,255,255,0.6);font-size:0.88rem;margin:0 0 24px;line-height:1.5;">' + 160 EXIT_POPUP.sub + '</p>' + 161 '<button id="wire-exit-btn" style="width:100%;padding:16px;background:' + ACCENT + ';' + 162 'color:#fff;border:none;border-radius:0;font-size:1rem;font-weight:600;cursor:pointer;' + 163 'margin-bottom:12px;">' + EXIT_POPUP.btn + '</button>' + 164 '<p><a href="#" id="wire-exit-dismiss" style="color:#666;font-size:0.82rem;text-decoration:none;">' + 165 'No thanks</a></p>' + 166 '<p style="font-size:0.62rem;color:#555;line-height:1.4;margin:14px 0 0;">' + 167 'By registering you agree to receive calls, emails and text messages (SMS/MMS) from WIRE Miami (Waterfront Investment Real Estate, LLC) about this and similar pre-construction opportunities, including automated messages. Message frequency varies. Msg and data rates may apply. Reply STOP to opt out, HELP for help. Consent is not a condition of any purchase. Communications may be recorded for quality assurance. ' + 168 'See our <a href="/privacy-policy.html" target="_blank" style="color:#777;text-decoration:underline;">Privacy Policy</a> and <a href="/terms-of-service.html" target="_blank" style="color:#777;text-decoration:underline;">Terms of Service</a>.</p>' + 169 '</div>' + 170 '</div>'; 171 172 /* ââââââââââââââââââââââââââââââ 173 INJECT ALL ELEMENTS 174 ââââââââââââââââââââââââââââââ */ 175 176 document.addEventListener('DOMContentLoaded', function() { 177 178 // Insert modal 179 var modalWrap = document.createElement('div'); 180 modalWrap.innerHTML = modalHTML; 181 document.body.appendChild(modalWrap.firstChild); 182 183 // Insert slide-in (only if not registered) 184 if (!isRegistered()) { 185 var slideWrap = document.createElement('div'); 186 slideWrap.innerHTML = slideInHTML; 187 document.body.appendChild(slideWrap.firstChild); 188 189 var exitWrap = document.createElement('div'); 190 exitWrap.innerHTML = exitHTML; 191 document.body.appendChild(exitWrap.firstChild); 192 } 193 194 // If already registered, unlock everything immediately 195 if (isRegistered()) { 196 unlockAllContent(); 197 return; // No gates needed for returning visitors 198 } 199 200 setupGates(); 201 setupSlideIn(); 202 setupExitIntent(); 203 }); 204 205 /* ââââââââââââââââââââââââââââââ 206 OPEN GATE MODAL 207 ââââââââââââââââââââââââââââââ */ 208 209 var triggerContext = 'general'; 210 211 var gateMessages = { 212 floorplans: { 213 title: 'Unlock Floor Plans for ' + PROP, 214 sub: 'Get instant access to all floor plans, pricing, and availability.', 215 btn: 'Unlock Floor Plans' 216 }, 217 pricing: { 218 title: 'Get Detailed Pricing for ' + PROP, 219 sub: 'View the complete price list, deposit structure, and developer incentives.', 220 btn: 'View Pricing' 221 }, 222 gallery: { 223 title: 'View All Photos of ' + PROP, 224 sub: 'Browse the complete gallery including interiors, views, and amenities.', 225 btn: 'View Full Gallery' 226 }, 227 guide: { 228 title: 'Get the ' + PROP + ' Pricing Guide', 229 sub: 'Floor plans, pricing breakdown, and deposit structure, delivered to your inbox.', 230 btn: 'Send Me the Guide' 231 }, 232 exit: { 233 title: EXIT_POPUP.title, 234 sub: EXIT_POPUP.sub, 235 btn: EXIT_POPUP.btn 236 }, 237 general: { 238 title: 'Connect With a ' + PROP + ' Specialist', 239 sub: 'Get expert guidance on pricing, floor plans, and availability.', 240 btn: 'Get Started' 241 } 242 }; 243 244 function openGate(context) { 245 triggerContext = context || 'general'; 246 var msg = gateMessages[triggerContext] || gateMessages.general; 247 248 document.getElementById('wire-gate-title').textContent = msg.title; 249 document.getElementById('wire-gate-sub').textContent = msg.sub; 250 document.getElementById('wire-g-btn').textContent = msg.btn; 251 document.getElementById('wire-g-error').style.display = 'none'; 252 253 // Pre-fill if we have partial data 254 try { 255 var savedName = localStorage.getItem('wireName'); 256 if (savedName) document.getElementById('wire-g-name').value = savedName; 257 } catch(e) {} 258 259 var gate = document.getElementById('wire-gate'); 260 gate.style.display = 'flex'; 261 document.body.style.overflow = 'hidden'; 262 263 // Focus first empty field 264 setTimeout(function() { 265 var nameField = document.getElementById('wire-g-name'); 266 if (!nameField.value) { nameField.focus(); } 267 else { document.getElementById('wire-g-email').focus(); } 268 }, 100); 269 } 270 271 function closeGate() { 272 document.getElementById('wire-gate').style.display = 'none'; 273 document.body.style.overflow = ''; 274 } 275 276 /* ââââââââââââââââââââââââââââââ 277 SETUP GATE TRIGGERS 278 ââââââââââââââââââââââââââââââ */ 279 280 function setupGates() { 281 282 // Any element with data-gate="xxx" opens the registration modal 283 document.addEventListener('click', function(e) { 284 var trigger = e.target.closest('[data-gate]'); 285 if (!trigger) return; 286 287 e.preventDefault(); 288 289 if (isRegistered()) { 290 // Already registered: perform the natural action 291 var href = trigger.getAttribute('href'); 292 if (href && href.startsWith('#')) { 293 // Anchor link: smooth scroll to section 294 var target = document.querySelector(href); 295 if (target) target.scrollIntoView({ behavior: 'smooth' }); 296 } else if (href && href !== '#') { 297 // Regular link: follow it 298 if (trigger.getAttribute('target') === '_blank') { 299 window.open(href, '_blank', 'noopener,noreferrer'); 300 } else { 301 window.location.href = href; 302 } 303 } 304 // If no href, just do nothing (button was purely a gate trigger) 305 return; 306 } 307 308 openGate(trigger.dataset.gate); 309 }); 310 311 // Form submit 312 document.getElementById('wire-gate-form').addEventListener('submit', function(e) { 313 e.preventDefault(); 314 handleRegistration(); 315 }); 316 317 // "No thanks": scrolls to contact, content stays locked 318 document.getElementById('wire-g-dismiss').addEventListener('click', function(e) { 319 e.preventDefault(); 320 closeGate(); 321 var contact = document.getElementById('contact'); 322 if (contact) contact.scrollIntoView({ behavior: 'smooth' }); 323 }); 324 325 // Prevent clicking outside to close (intentionally NO backdrop close) 326 document.getElementById('wire-gate').addEventListener('click', function(e) { 327 // Only close if clicking the dismiss link, not the backdrop 328 // This is intentional: we want no easy dismiss 329 }); 330 331 // ESC key closes gate but scrolls to contact (same as "no thanks") 332 document.addEventListener('keydown', function(e) { 333 if (e.key === 'Escape' && document.getElementById('wire-gate').style.display === 'flex') { 334 closeGate(); 335 var contact = document.getElementById('contact'); 336 if (contact) contact.scrollIntoView({ behavior: 'smooth' }); 337 } 338 }); 339 } 340 341 /* ââââââââââââââââââââââââââââââ 342 HANDLE REGISTRATION (FUB + Google Ads) 343 ââââââââââââââââââââââââââââââ */ 344 345 // Map data-gate values to FUB registration types 346 var gateToRegType = { 347 floorplans: 'Floor Plan Request', 348 pricing: 'Pricing Guide Request', 349 brochure: 'Brochure Download', 350 factsheet: 'Fact Sheet Request', 351 guide: 'Pricing Guide Request', 352 gallery: 'General Registration', 353 exit: 'General Registration', 354 general: 'General Registration' 355 }; 356 357 function handleRegistration() { 358 var name = document.getElementById('wire-g-name').value.trim(); 359 var email = document.getElementById('wire-g-email').value.trim(); 360 var phone = document.getElementById('wire-g-phone').value.trim(); 361 var ctChecked = document.querySelector('input[name="wire-g-contact-type"]:checked'); 362 var contactType = ctChecked ? ctChecked.value : ''; 363 var errEl = document.getElementById('wire-g-error'); 364 365 // Validate 366 if (!name || !email || !phone || !contactType) { 367 errEl.textContent = 'Please fill in all fields.'; 368 errEl.style.display = 'block'; 369 return; 370 } 371 if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { 372 errEl.textContent = 'Please enter a valid email address.'; 373 errEl.style.display = 'block'; 374 return; 375 } 376 377 var btn = document.getElementById('wire-g-btn'); 378 btn.disabled = true; 379 btn.textContent = 'Submitting...'; 380 btn.style.opacity = '0.7'; 381
382 var nameParts = name.split(' '); 383 var firstName = nameParts[0]; 384 var lastName = nameParts.slice(1).join(' ') || ''; 385 386 // Determine registration type from gate context 387 var regType = gateToRegType[triggerContext] || 'General Registration'; 388 389 // Submit to FUB via centralized function (wire-tracking.js) 390 if (typeof wireSubmitToFUB === 'function') { 391 wireSubmitToFUB({ 392 firstName: firstName, 393 lastName: lastName, 394 email: email, 395 phone: phone, 396 contactType: contactType 397 }, regType); 398 } 399 // Option B (2026-06-09): wire-crm dual-write moved server-side into the 400 // /api/form-lead handler. The browser no longer calls wireSubmitToWIRE 401 // (it would double-write the CRM once the handler owns it). 402 403 // Proceed with success (don't block UX on FUB response) 404 onRegistrationSuccess(name, email, phone); 405 } 406 407 function onRegistrationSuccess(name, email, phone) { 408 // Save registration state 409 setRegistered(name, email, phone); 410 411 // Fire Google Ads conversion 412 if (typeof gtag === 'function') { 413 gtag('event', 'conversion', { 414 'send_to': ((triggerContext === 'floorplans' && W.tracking.googleAdsConversionFloorplan) ? W.tracking.googleAdsConversionFloorplan : W.tracking.googleAdsConversion), 415 'value': 1.0, 416 'currency': 'USD' 417 }); 418 } 419 420 // Close all modals 421 closeGate(); 422 var exit = document.getElementById('wire-exit'); 423 if (exit) exit.style.display = 'none'; 424 var slideIn = document.getElementById('wire-slidein'); 425 if (slideIn) slideIn.style.display = 'none'; 426 427 // Unlock all gated content 428 unlockAllContent(); 429 430 // Show brief thank you 431 showThankYou(name.split(' ')[0]); 432 } 433 434 /* ââââââââââââââââââââââââââââââ 435 UNLOCK GATED CONTENT 436 ââââââââââââââââââââââââââââââ */ 437 438 function unlockAllContent() { 439 // Remove blur from gated sections 440 document.querySelectorAll('.wire-gated').forEach(function(el) { 441 el.classList.add('wire-unlocked'); 442 el.classList.remove('wire-gated'); 443 }); 444 445 // Load gated images (floor plans, etc.) 446 document.querySelectorAll('[data-gated-src]').forEach(function(img) { 447 img.src = img.dataset.gatedSrc; 448 img.removeAttribute('data-gated-src'); 449 }); 450 451 // Enable gated download links 452 document.querySelectorAll('[data-gated-href]').forEach(function(a) { 453 a.href = a.dataset.gatedHref; 454 a.removeAttribute('data-gated-href'); 455 a.classList.remove('wire-locked-link'); 456 }); 457 458 // Show elements hidden until registration 459 document.querySelectorAll('.wire-show-registered').forEach(function(el) { 460 el.style.setProperty('display', 'block', 'important'); 461 }); 462 463 // Hide lock overlays 464 document.querySelectorAll('.wire-lock-overlay').forEach(function(el) { 465 el.style.display = 'none'; 466 }); 467 468 // Change gate buttons to direct actions 469 document.querySelectorAll('[data-gate]').forEach(function(el) { 470 el.removeAttribute('data-gate'); 471 }); 472 } 473 474 // Expose globally so wire-contact-form.js can trigger unlock after form submission 475 window.wireUnlockAll = unlockAllContent; 476 477 /* ââââââââââââââââââââââââââââââ 478 THANK YOU TOAST 479 ââââââââââââââââââââââââââââââ */ 480 481 function showThankYou(firstName) { 482 var toast = document.createElement('div'); 483 toast.setAttribute('role', 'status'); 484 toast.setAttribute('aria-live', 'polite'); 485 toast.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);' + 486 'background:#1a1a1a;color:#fff;padding:16px 28px;border-radius:0;z-index:10002;' + 487 'font-size:0.95rem;box-shadow:0 8px 30px rgba(0,0,0,0.3);' + 488 'animation:wireToastIn 0.3s ease;max-width:90%;text-align:center;'; 489 toast.innerHTML = 'Welcome, ' + firstName + '! Full access unlocked.'; 490 document.body.appendChild(toast); 491 492 setTimeout(function() { 493 toast.style.opacity = '0'; 494 toast.style.transition = 'opacity 0.3s'; 495 setTimeout(function() { toast.remove(); }, 300); 496 }, 3000); 497 } 498 499 /* ââââââââââââââââââââââââââââââ 500 SLIDE-IN CTA (40% scroll OR 60s timer) 501 ââââââââââââââââââââââââââââââ */ 502 503 function setupSlideIn() { 504 var slideIn = document.getElementById('wire-slidein'); 505 if (!slideIn) return; 506 507 var shown = false;
508 var dismissed = false; 509 510 function showSlideIn() { 511 if (shown || dismissed || isRegistered()) return; 512 shown = true; 513 slideIn.style.display = 'block'; 514 setTimeout(function() { 515 slideIn.style.transform = 'translateY(0)'; 516 slideIn.style.opacity = '1'; 517 }, 10); 518 } 519 520 // Trigger on 40% scroll 521 window.addEventListener('scroll', function() { 522 var scrollPct = (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100; 523 if (scrollPct >= 40) showSlideIn(); 524 }, { passive: true }); 525 526 // Trigger on 45s timer 527 setTimeout(showSlideIn, 45000); 528 529 // "Send Me the Guide" button opens registration gate 530 document.getElementById('wire-slidein-btn').addEventListener('click', function() { 531 slideIn.style.display = 'none'; 532 dismissed = true; 533 openGate('guide'); 534 }); 535 536 // "Maybe later" dismiss 537 document.getElementById('wire-slidein-dismiss').addEventListener('click', function(e) { 538 e.preventDefault(); 539 slideIn.style.transform = 'translateY(20px)'; 540 slideIn.style.opacity = '0'; 541 setTimeout(function() { slideIn.style.display = 'none'; }, 400);
542 dismissed = true; 543 }); 544 } 545 546 /* ââââââââââââââââââââââââââââââ 547 EXIT INTENT 548 ââââââââââââââââââââââââââââââ */ 549 550 function setupExitIntent() { 551 var exitEl = document.getElementById('wire-exit'); 552 if (!exitEl) return; 553 554 var fired = false; 555 556 // Desktop: mouse leaves viewport at top 557 document.addEventListener('mouseout', function(e) { 558 if (fired || isRegistered()) return; 559 if (e.clientY <= 0 && e.relatedTarget === null) { 560 fired = true; 561 exitEl.style.display = 'flex'; 562 document.body.style.overflow = 'hidden'; 563 } 564 }); 565 566 // Mobile: back button / history (best effort) 567 // We detect via visibilitychange, fires when user switches tabs or hits back 568 document.addEventListener('visibilitychange', function() { 569 if (document.visibilityState === 'hidden' && !fired && !isRegistered()) { 570 // Can't show modal when page is hidden, but mark as triggered 571 // so if they come back within session, we show it 572 fired = true; 573 } 574 }); 575 576 // "Get Exclusive Pricing" opens registration gate 577 document.getElementById('wire-exit-btn').addEventListener('click', function() { 578 exitEl.style.display = 'none'; 579 document.body.style.overflow = ''; 580 openGate('exit'); 581 }); 582 583 // "No thanks" dismiss 584 document.getElementById('wire-exit-dismiss').addEventListener('click', function(e) { 585 e.preventDefault(); 586 exitEl.style.display = 'none'; 587 document.body.style.overflow = ''; 588 }); 589 } 590 591 /* ââââââââââââââââââââââââââââââ 592 INJECT GATING CSS 593 ââââââââââââââââââââââââââââââ */ 594 595 var gateCSS = document.createElement('style'); 596 gateCSS.textContent = '' + 597 '.wire-gated{position:relative;filter:blur(12px);pointer-events:none;user-select:none;-webkit-user-select:none;}' + 598 '.wire-gated::after{content:"";position:absolute;inset:0;background:rgba(0,0,0,0.3);pointer-events:auto;cursor:pointer;filter:none;}' + 599 '.wire-unlocked{filter:none!important;pointer-events:auto!important;user-select:auto!important;}' + 600 '.wire-unlocked::after{display:none!important;}' + 601 '.wire-lock-overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;' + 602 'background:rgba(26,26,26,0.7);color:#fff;z-index:2;cursor:pointer;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);}' + 603 '.wire-lock-overlay .lock-icon{font-size:2rem;margin-bottom:8px;}' + 604 '.wire-lock-overlay .lock-text{font-size:0.95rem;font-weight:600;}' + 605 '.wire-lock-overlay .lock-sub{font-size:0.8rem;color:rgba(255,255,255,0.6);margin-top:4px;}' + 606 '.wire-locked-link{opacity:0.5;pointer-events:none;}' + 607 '.wire-show-registered{display:none!important;}' + 608 '#wire-gate .wire-ct-opt{display:block;cursor:pointer;position:relative;}' + 609 '#wire-gate .wire-ct-opt input{position:absolute;opacity:0;width:1px;height:1px;}' + 610 '#wire-gate .wire-ct-opt span{display:block;padding:13px 8px;background:#2a2a2a;border:1px solid #444;border-radius:0;color:rgba(255,255,255,0.75);font-size:0.88rem;text-align:center;transition:border-color 0.2s,background 0.2s;}' + 611 '#wire-gate .wire-ct-opt input:checked+span{border-color:#fff;background:#3a3a3a;color:#fff;}' + 612 '#wire-gate .wire-ct-opt input:focus-visible+span{outline:2px solid #fff;outline-offset:2px;}' + 613 '@keyframes wireToastIn{from{opacity:0;transform:translate(-50%,-10px)}to{opacity:1;transform:translate(-50%,0)}}'; 614 document.head.appendChild(gateCSS); 615 616})();
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.