PageSourceSearch

https://rcsobe.com/wire-lead-capture.js

js rcsobe.com collected 2026-09-29 07:09:52 UTC 29,418 bytes, 616 lines download raw bytes

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.