PageSourceSearch

https://laurenslatest.com/wp-content/plugins/wp-emailtru-plugin/js/spotlight.js?ver=1.40.0

js laurenslatest.com collected 2026-10-03 23:38:49 UTC 20,299 bytes, 595 lines download raw bytes

1(function() {
2  'use strict';
3  
4  // Track which forms have been spotlighted
5  const spotlightedForms = new WeakSet();
6  let isSpotlightActive = false;
7  let activeTimeout = null;
8  let overlay = null;
9  let isCheckingForms = false;
10  let lastCheckTime = 0;
11  let spotlightInitialized = false;
12  
13  // Popup modal state
14  let popupInitialized = false;
15  let popupModal = null;
16  let popupTimer = null;
17  let isPopupOpen = false;
18  let popupDelayMs = 10000;
19  
20  // AJAX form submission state
21  let ajaxInitialized = false;
22  
23  // Initialize spotlight functionality
24  function init() {
25    if (spotlightInitialized) return;
26    spotlightInitialized = true;
27
28    // Get or create overlay
29    overlay = document.getElementById('form-spotlight-overlay');
30    if (!overlay) {
31      overlay = document.createElement('div');
32      overlay.id = 'form-spotlight-overlay';
33      overlay.className = 'spotlight-overlay';
34      document.body.appendChild(overlay);
35    }
36    
37    // Check immediately
38    setTimeout(checkForms, 100);
39    
40    // Check on scroll - directly call checkForms without debounce
41    window.addEventListener('scroll', checkForms, { passive: true });
42    
43    // Also use requestAnimationFrame for continuous checking
44    startContinuousCheck();
45  }
46
47  // Initialize popup modals
48  function initPopups() {
49    if (popupInitialized) return;
50    
51    const popups = document.querySelectorAll('.emailtru-popup-modal');
52    if (!popups || popups.length === 0) {
53      return;
54    }
55    
56    popupInitialized = true;
57    popupModal = popups[0];
58    
59    // Close when clicking on the backdrop (outside the inner card)
60    popupModal.addEventListener('click', function(event) {
61      if (!isPopupOpen) return;
62      if (event.target === popupModal) {
63        hidePopup();
64      }
65    });
66    
67    const ctaButton = popupModal.querySelector('.emailtru-popup-cta');
68    const formContainer = popupModal.querySelector('.emailtru-popup-form-container');
69    if (ctaButton && formContainer) {
70      ctaButton.addEventListener('click', function() {
71        // Reveal the email form and hide the initial CTA
72        ctaButton.style.display = 'none';
73        formContainer.style.display = 'block';
74      });
75    }
76    
77    const declineButton = popupModal.querySelector('.emailtru-popup-decline');
78    if (declineButton) {
79      declineButton.addEventListener('click', function() {
80        hidePopup();
81      });
82    }
83
84    const closeButton = popupModal.querySelector('.emailtru-popup-close');
85    if (closeButton) {
86      closeButton.addEventListener('click', function() {
87        hidePopup();
88      });
89    }
90    
91    document.addEventListener('keydown', function(event) {
92      if (!isPopupOpen) return;
93      if (event.key === 'Escape' || event.key === 'Esc') {
94        hidePopup();
95      }
96    });
97    
98    // Automatically open time-based popups after the configured delay (seconds).
99    // JavaScript-triggered popups remain hidden until emailtruOpenPopup() is called.
100    const triggerType = popupModal.getAttribute('data-emailtru-popup-trigger-type') || 'time_based';
101    popupDelayMs = 10000;
102    const delayAttr = popupModal.getAttribute('data-emailtru-popup-delay');
103    if (delayAttr !== null && delayAttr !== '') {
104      const parsed = parseInt(delayAttr, 10);
105      if (!isNaN(parsed) && parsed >= 0) {
106        popupDelayMs = parsed * 1000;
107      }
108    }
109
110    if (triggerType !== 'javascript') {
111      schedulePopupTimer();
112    }
113  }
114  
115  function initAjaxForms() {
116    if (ajaxInitialized) return;
117    ajaxInitialized = true;
118
119    document.addEventListener('submit', function(event) {
120      const form = event.target;
121      if (!form || form.nodeName !== 'FORM') return;
122      if (!form.hasAttribute('data-emailtru-ck-form-id')) return;
123
124      const ckFormId = form.getAttribute('data-emailtru-ck-form-id');
125      if (!ckFormId || ckFormId === '0') return;
126
127      const action = (form.getAttribute('action') || '').trim();
128      const isKitDirectSubmit = action.indexOf('https://app.kit.com/forms/') === 0 && action.indexOf('/subscriptions') !== -1;
129
130      if (isKitDirectSubmit) {
131        event.preventDefault();
132
133        const submitButton = form.querySelector('button[type="submit"], input[type="submit"]');
134        const previousButtonText = submitButton && submitButton.tagName === 'BUTTON' ? submitButton.textContent : null;
135        if (submitButton) {
136          submitButton.disabled = true;
137          if (submitButton.tagName === 'BUTTON') {
138            submitButton.textContent = 'Submitting...';
139          }
140        }
141
142        function showMessage(message, kind) {
143          let messageEl = form.nextElementSibling;
144          if (!messageEl || !messageEl.classList || !messageEl.classList.contains('emailtru-form-message')) {
145            messageEl = document.createElement('div');
146            messageEl.className = 'emailtru-form-message';
147            messageEl.setAttribute('role', kind === 'error' ? 'alert' : 'status');
148            messageEl.setAttribute('aria-live', kind === 'error' ? 'assertive' : 'polite');
149            form.insertAdjacentElement('afterend', messageEl);
150          }
151          messageEl.classList.toggle('emailtru-form-message--error', kind === 'error');
152          messageEl.textContent = message;
153        }
154
155        function setButtonEnabled(enabled) {
156          if (!submitButton) return;
157          submitButton.disabled = !enabled;
158          if (submitButton.tagName === 'BUTTON' && previousButtonText !== null) {
159            submitButton.textContent = enabled ? previousButtonText : 'Submitting...';
160          }
161        }
162
163        function showSuccessAndHide() {
164          showMessage('Success! Watch your inbox for great recipes.', 'success');
165          form.reset();
166          form.style.display = 'none';
167          if (form.classList.contains('emailtru-popup-form')) {
168            setTimeout(function() {
169              hidePopup();
170            }, 1500);
171          }
172          setButtonEnabled(true);
173        }
174
175        function showErrorAndEnable(message) {
176          showMessage(message || 'Something went wrong. Please try again.', 'error');
177          setButtonEnabled(true);
178        }
179
180        function submitViaHiddenIframe() {
181          const iframeName = 'emailtru_kit_subscribe_' + ckFormId;
182          let iframe = document.querySelector('iframe[name="' + iframeName + '"]');
183          if (!iframe) {
184            iframe = document.createElement('iframe');
185            iframe.name = iframeName;
186            iframe.setAttribute('aria-hidden', 'true');
187            iframe.tabIndex = -1;
188            iframe.style.width = '0';
189            iframe.style.height = '0';
190            iframe.style.border = '0';
191            iframe.style.position = 'absolute';
192            iframe.style.left = '-9999px';
193            document.body.appendChild(iframe);
194          }
195
196          let handled = false;
197          const onLoad = function() {
198            if (handled) return;
199            handled = true;
200            iframe.removeEventListener('load', onLoad);
201            showSuccessAndHide();
202          };
203
204          iframe.addEventListener('load', onLoad);
205          form.setAttribute('target', iframeName);
206          form.submit();
207        }
208
209        // Preferred: POST via fetch with Accept: application/json (no page redirect).
210        // If Kit blocks CORS, fall back to hidden-iframe submit (still no redirect).
211        try {
212          const directFormData = new FormData(form);
213          const params = new URLSearchParams();
214          directFormData.forEach(function(value, key) {
215            params.append(key, value);
216          });
217
218          fetch(action, {
219            method: 'POST',
220            headers: {
221              'Accept': 'application/json',
222              'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
223            },
224            body: params.toString(),
225            credentials: 'omit'
226          })
227            .then(function(response) {
228              const contentType = (response.headers && response.headers.get) ? (response.headers.get('content-type') || '') : '';
229              if (contentType.indexOf('application/json') !== -1) {
230                return response.json()
231                  .catch(function() { return null; })
232                  .then(function(payload) {
233                    return { ok: response.ok, payload: payload };
234                  });
235              }
236
237              return response.text()
238                .catch(function() { return ''; })
239                .then(function(text) {
240                  return { ok: response.ok, text: text };
241                });
242            })
243            .then(function(result) {
244              if (result && result.ok) {
245                showSuccessAndHide();
246                return;
247              }
248
249              if (result && result.payload && (result.payload.message || result.payload.error)) {
250                showErrorAndEnable(result.payload.message || result.payload.error);
251                return;
252              }
253
254              submitViaHiddenIframe();
255            })
256            .catch(function() {
257              submitViaHiddenIframe();
258            });
259        } catch (e) {
260          submitViaHiddenIframe();
261        }
262
263        return;
264      }
265
266      event.preventDefault();
267
268      if (!window.emailtruSpotlight || !emailtruSpotlight.ajaxUrl) return;
269
270      const formData = new FormData(form);
271      formData.append('action', 'emailtru_spotlight_subscribe');
272      formData.append('nonce', emailtruSpotlight.nonce || '');
273      formData.append('ck_form_id', ckFormId || '');
274
275      const submitButton = form.querySelector('button[type="submit"], input[type="submit"]');
276      const previousButtonText = submitButton && submitButton.tagName === 'BUTTON' ? submitButton.textContent : null;
277      if (submitButton) {
278        submitButton.disabled = true;
279        if (submitButton.tagName === 'BUTTON') {
280          submitButton.textContent = 'Submitting...';
281        }
282      }
283
284      function showMessage(message, kind) {
285        let messageEl = form.nextElementSibling;
286        if (!messageEl || !messageEl.classList || !messageEl.classList.contains('emailtru-form-message')) {
287          messageEl = document.createElement('div');
288          messageEl.className = 'emailtru-form-message';
289          messageEl.setAttribute('role', kind === 'error' ? 'alert' : 'status');
290          messageEl.setAttribute('aria-live', kind === 'error' ? 'assertive' : 'polite');
291          form.insertAdjacentElement('afterend', messageEl);
292        }
293        messageEl.classList.toggle('emailtru-form-message--error', kind === 'error');
294        messageEl.textContent = message;
295      }
296
297      fetch(emailtruSpotlight.ajaxUrl, {
298        method: 'POST',
299        credentials: 'same-origin',
300        body: formData
301      })
302        .then(function(response) {
303          return response.json()
304            .catch(function() { return null; })
305            .then(function(payload) {
306              return { ok: response.ok, payload: payload };
307            });
308        })
309        .then(function(result) {
310          if (result.payload && result.payload.success) {
311            const message = (result.payload.data && result.payload.data.message) ? result.payload.data.message : 'Success! Watch your inbox for great recipes.';
312            showMessage(message, 'success');
313            form.reset();
314            form.style.display = 'none';
315            if (form.classList.contains('emailtru-popup-form')) {
316              setTimeout(function() {
317                hidePopup();
318              }, 1500);
319            }
320            return;
321          }
322
323          const errorMessage = (result.payload && result.payload.data && result.payload.data.message)
324            ? result.payload.data.message
325            : 'Something went wrong. Please try again.';
326          showMessage(errorMessage, 'error');
327        })
328        .catch(function() {
329          showMessage('Something went wrong. Please try again.', 'error');
330        })
331        .finally(function() {
332          if (submitButton) {
333            submitButton.disabled = false;
334            if (submitButton.tagName === 'BUTTON' && previousButtonText !== null) {
335              submitButton.textContent = previousButtonText;
336            }
337          }
338        });
339    }, true);
340  }
341
342  function showPopup() {
343    if (!popupModal || isPopupOpen) return;
344
345    // Avoid stacking with ConvertKit popups: only show when
346    // there is no .formkit-modal with data-active="true"
347    if (!canShowPopup()) {
348      schedulePopupTimer();
349      return;
350    }
351
352    popupModal.classList.add('emailtru-popup-visible');
353    popupModal.setAttribute('aria-hidden', 'false');
354    isPopupOpen = true;
355  }
356
357  function hidePopup() {
358    if (!popupModal || !isPopupOpen) return;
359    popupModal.classList.remove('emailtru-popup-visible');
360    popupModal.setAttribute('aria-hidden', 'true');
361    isPopupOpen = false;
362    
363    if (popupTimer) {
364      clearTimeout(popupTimer);
365      popupTimer = null;
366    }
367  }
368
369  // Determine if it's safe to show the popup (no active ConvertKit modal)
370  function canShowPopup() {
371    const activeFormkitModal = document.querySelector('.formkit-modal[data-active="true"]');
372    if (activeFormkitModal) {
373      return false;
374    }
375    return true;
376  }
377
378  // Schedule or reschedule the popup timer respecting the configured delay
379  function schedulePopupTimer() {
380    if (!popupModal) return;
381
382    if ((popupModal.getAttribute('data-emailtru-popup-trigger-type') || 'time_based') === 'javascript') {
383      return;
384    }
385
386    if (popupTimer) {
387      clearTimeout(popupTimer);
388      popupTimer = null;
389    }
390
391    popupTimer = setTimeout(function() {
392      showPopup();
393    }, popupDelayMs);
394  }
395
396  // Public API for JavaScript-triggered popups.
397  window.emailtruOpenPopup = function(formId) {
398    const selector = '[data-emailtru-popup-form-id="' +
399      String(formId).replace(/([\\"\\\\])/g, '\\$1') + '"]';
400    const requestedModal = document.querySelector(selector);
401    if (!requestedModal) return false;
402
403    popupModal = requestedModal;
404    showPopup();
405    return true;
406  };
407  
408  // Continuously check forms with requestAnimationFrame
409  function startContinuousCheck() {
410    function check() {
411      checkForms();
412      requestAnimationFrame(check);
413    }
414    requestAnimationFrame(check);
415  }
416  
417  // Check all forms for center position
418  function checkForms() {
419    // Throttle checks to every 50ms
420    const now = Date.now();
421    if (now - lastCheckTime < 50) return;
422    lastCheckTime = now;
423    
424    // Don't check if already active or checking
425    if (isSpotlightActive || isCheckingForms) return;
426    
427    isCheckingForms = true;
428    
429    try {
430      // Target any embedded EmailTru form container
431      const forms = document.querySelectorAll('.emailtru-forms');
432      
433      // Find the form closest to center that hasn't been spotlighted
434      let closestForm = null;
435      let closestDistance = Infinity;
436      
437      for (let i = 0; i < forms.length; i++) {
438        const form = forms[i];
439        
440        // Skip if already spotlighted
441        if (spotlightedForms.has(form)) continue;
442        
443        // Skip if this element or its parent has the no-spotlight attribute
444        if (form.hasAttribute('data-emailtru-no-spotlight') && form.getAttribute('data-emailtru-no-spotlight') === 'true') {
445          continue;
446        }
447        
448        // Check parent elements for the no-spotlight flag
449        let parent = form.parentElement;
450        let shouldSkip = false;
451        while (parent && parent !== document.body) {
452          if (parent.hasAttribute('data-emailtru-no-spotlight') && parent.getAttribute('data-emailtru-no-spotlight') === 'true') {
453            shouldSkip = true;
454            break;
455          }
456          parent = parent.parentElement;
457        }
458        if (shouldSkip) continue;
459        
460        const rect = form.getBoundingClientRect();
461        const viewportHeight = window.innerHeight;
462        const viewportCenterY = viewportHeight / 2;
463        const formCenterY = rect.top + (rect.height / 2);
464        const distance = Math.abs(formCenterY - viewportCenterY);
465        
466        if (distance < closestDistance) {
467          closestDistance = distance;
468          closestForm = form;
469        }
470      }
471      
472      // Only spotlight if the closest form is actually centered
473      if (closestForm && isInCenter(closestForm)) {
474        showSpotlight(closestForm);
475      }
476    } finally {
477      isCheckingForms = false;
478    }
479  }
480  
481  // Check if element is in center of viewport
482  function isInCenter(form) {
483    if (!form) return false;
484    
485    try {
486      const rect = form.getBoundingClientRect();
487      const viewportHeight = window.innerHeight;
488      const viewportCenterY = viewportHeight / 2;
489      
490      // Check if form has dimensions
491      if (!rect || rect.height <= 0 || rect.width <= 0) return false;
492      
493      // Check if form is visible in viewport
494      if (rect.bottom < 0 || rect.top > viewportHeight) return false;
495      
496      // NEW APPROACH: Check if the viewport center line passes through the form
497      // This is simple: is the center Y position between form's top and bottom?
498      const formContainsCenter = rect.top <= viewportCenterY && rect.bottom >= viewportCenterY;
499      
500      if (formContainsCenter) return true;
501      
502      // ALTERNATIVE: Check if form center is reasonably close to viewport center
503      const formCenterY = rect.top + (rect.height / 2);
504      const distanceFromCenter = Math.abs(formCenterY - viewportCenterY);
505      const maxDistance = viewportHeight * 0.25; // Within 25% of viewport height
506      
507      return distanceFromCenter <= maxDistance;
508    } catch (e) {
509      return false;
510    }
511  }
512  
513  function getSpotlightDurationMs(form) {
514    var seconds = 3;
515    var node = form;
516    while (node && node !== document.body) {
517      if (node.hasAttribute && node.hasAttribute('data-emailtru-spotlight-seconds')) {
518        var parsed = parseInt(node.getAttribute('data-emailtru-spotlight-seconds'), 10);
519        if (!isNaN(parsed) && parsed >= 0) {
520          seconds = parsed;
521        }
522        break;
523      }
524      node = node.parentElement;
525    }
526    return seconds * 1000;
527  }
528
529  // Show spotlight on a form
530  function showSpotlight(form) {
531    if (!overlay || spotlightedForms.has(form) || isSpotlightActive) return;
532    
533    const rect = form.getBoundingClientRect();
534    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
535    const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
536    
537    // Position and size the spotlight
538    overlay.style.setProperty('--spotlight-top', (rect.top + scrollTop) + 'px');
539    overlay.style.setProperty('--spotlight-left', (rect.left + scrollLeft) + 'px');
540    overlay.style.setProperty('--spotlight-width', rect.width + 'px');
541    overlay.style.setProperty('--spotlight-height', rect.height + 'px');
542    
543    // Show the overlay with a slight delay to ensure CSS vars are set
544    setTimeout(function() {
545      overlay.classList.add('active');
546    }, 10);
547    
548    isSpotlightActive = true;
549    spotlightedForms.add(form);
550    
551    // Clear any existing timeout
552    if (activeTimeout) {
553      clearTimeout(activeTimeout);
554      activeTimeout = null;
555    }
556
557    var durationMs = getSpotlightDurationMs(form);
558    
559    // Remove after spotlight_seconds (default 3s)
560    activeTimeout = setTimeout(function() {
561      overlay.classList.remove('active');
562      // Wait for CSS transition to complete before allowing next spotlight
563      setTimeout(function() {
564        isSpotlightActive = false;
565      }, 600); // 0.5s transition + 0.1s buffer
566      activeTimeout = null;
567    }, durationMs);
568  }
569  
570  // Wait for forms and/or popups to load
571  function waitForFormsAndPopups() {
572    initAjaxForms();
573    const forms = document.querySelectorAll('.emailtru-forms');
574    const popups = document.querySelectorAll('.emailtru-popup-modal');
575    
576    if (forms.length > 0) {
577      init();
578    }
579    
580    if (popups.length > 0) {
581      initPopups();
582    }
583    
584    if (forms.length === 0 && popups.length === 0) {
585      setTimeout(waitForFormsAndPopups, 500);
586    }
587  }
588  
589  // Start when DOM is ready
590  if (document.readyState === 'loading') {
591    document.addEventListener('DOMContentLoaded', waitForFormsAndPopups);
592  } else {
593    waitForFormsAndPopups();
594  }
595})();

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.