PageSourceSearch

https://gobravo.pt/wp-content/plugins/bt-bb-ab/js/bt_conversion.js?ver=1.9.1

js gobravo.pt collected 2026-10-06 07:18:52 UTC 68,679 bytes, 2,070 lines download raw bytes

1// is user active global vars
2window.abst = window.abst || {};
3// activity timer
4window.abst.eventQueue = [];
5window.abst.abconvertpartner = {};
6// Check localStorage for existing approval status, default to waiting if not found
7window.abst.waitForApproval = false;//localStorage.getItem('abstApprovalStatus') !== 'approved';
8
9// Helper function to set approval status
10function setAbstApprovalStatus(approved) {
11  window.abst.waitForApproval = !approved;
12  if (approved) {
13    localStorage.setItem('abstApprovalStatus', 'approved');
14  } else {
15    localStorage.removeItem('abstApprovalStatus');
16  }
17}
18
19// activity timer - initialize once as an object
20window.abst.timer = localStorage.getItem('absttimer') === null ? {} : JSON.parse(localStorage.getItem('absttimer') || '{}');
21
22window.abst.currscroll = window.scrollY;
23window.abst.currentMousePos = -1;
24window.abst.oldMousePos = -2;
25window.abst.abactive = true;
26window.abst.timeoutTime = 3000; // how much inactivity before we stop loggin in milliseconds
27window.abst.intervals = {};
28// Initialize service partners
29const services = ['abawp', 'clarity', 'gai', 'abmix', 'abumav', 'umami', 'cabin', 'plausible', 'fathom', 'ga4', 'posthog'];
30window.abst.abconvertpartner = Object.fromEntries(services.map(service => [service, false]));
31
32//what size, mobile, tablet or desktop 
33if(window.innerWidth < 768) {
34  window.abst.size = 'mobile';
35} 
36else if(window.innerWidth < 1024) {
37  window.abst.size = 'tablet';
38}
39else {
40  window.abst.size = 'desktop';
41}
42
43  if(window.btab_vars && window.btab_vars.advanced_tracking == '1') {
44    setAbCrypto();
45  }
46
47  // add server events to cookie to be evented on next page load
48  function addServerEvents(data) {
49    const serverEvents = abstGetCookie('abst_server_events');
50
51  if (serverEvents) {
52    const events = JSON.parse(decodeURIComponent(serverEvents));
53    if (!events.some(event => event.eid === data.eid && event.variation === data.variation && event.type === data.type)) {
54      events.push(data);
55      abstSetCookie('abst_server_events', JSON.stringify(events), 2);
56      console.log('server event added to existing', data);
57    }
58  } else {
59    abstSetCookie('abst_server_events', JSON.stringify([data]), 2);
60    console.log('server event added none existing creating', data);
61  }
62}
63
64
65  jQuery(document).ready(function(){
66
67    const serverEvents = abstGetCookie('abst_server_events');
68    if (serverEvents) {
69      const events = JSON.parse(decodeURIComponent(serverEvents));
70      events.forEach(event => {
71        console.log('server event', event);
72        btab_track_event(event);
73      });
74      abstSetCookie('abst_server_events', false, 2);
75    }
76
77
78
79
80    if( window.btab_vars && !window.btab_vars.is_preview && jQuery('.ab-test-page-redirect').length > 0 ) {
81      jQuery('.ab-test-page-redirect').remove();
82    }
83
84    if( jQuery('[bt_hidden="true"]').length > 0 ) {
85      jQuery('[bt_hidden="true"]').remove();
86    }
87
88  
89    // update scroll status
90    jQuery(document).mousemove(function(event) {
91      window.abst.currentMousePos = event.pageX;
92    });
93    // catch mouse / keyboard action
94    jQuery('body').on('mousedown keydown touchstart', function(event) {
95      userActiveNow();
96    });
97    
98    if(!('abConversionValue' in window.abst))
99      window.abst.abConversionValue = 1;
100
101    window.abst.timerInterval = setInterval(function(){
102      //check if scroll's changed
103
104      if((window.abst.currscroll != window.scrollY) || (window.abst.currentMousePos != window.abst.oldMousePos) )
105      {
106        window.abst.currscroll = window.scrollY;
107        window.abst.oldMousePos = window.abst.currentMousePos;
108        userActiveNow();
109      }
110
111      // check for active class and decrement all active timers
112      if(window.abst.abactive) //for each active timer, increment
113        abstOneSecond();
114
115    },1000); // every second
116
117
118    //conversion things
119    if (typeof conversion_details !== 'undefined' &&  conversion_details)
120    {
121      var eid = null;
122      var variation = null;
123
124      // current p info
125      page_url = normalizeUrl(window.location.href);
126      //loop through each conversion d
127      jQuery.each( conversion_details, function( key, detail ) {
128
129        //regex url matching for url's with *   *about*
130        if (detail.conversion_page_url && page_url) {
131          // Escape special characters in the user input
132          var escapedUrl = detail.conversion_page_url.replace(/([.+?^${}()|[\]\\])/g, '\\$1');
133          // Replace * with a regex pattern (.*)
134          var regexPattern = escapedUrl.replace(/\*/g, '.*');
135          // Create a regular expression object
136          var regex = new RegExp(`^${regexPattern}$`);
137        }
138
139        if(typeof regex !== 'undefined'){
140          if (regex.test(page_url)) {
141              eid = key;
142          }
143        }
144        else if((detail.conversion_page_url === page_url) && (detail.conversion_page_url !== undefined) && (detail.conversion_page_url != ''))  
145        {
146          eid = key;
147        }
148        else if(current_page.includes(detail.conversion_page_id) || current_page.includes(parseInt(detail.conversion_page_id))) 
149        {
150          eid = key;
151        }
152        else
153        {
154          return true; // skip to the next one
155        }
156
157        variation = abstGetCookie('btab_'+eid);
158
159        if(!variation)
160          return true; // skip to the next one
161        
162        variation = JSON.parse(variation);
163
164        //skip if its already converted
165        if( variation.conversion == 1 )
166          return true; // skip to the next one
167        conversionValue = 1;
168        if(window.abst.abConversionValue && detail.use_order_value == true)// woo etc
169          conversionValue = window.abst.abConversionValue;
170
171        bt_experiment_w(eid,variation.variation,'conversion',false,conversionValue);
172    });
173
174    }
175
176    //foreach experiment
177    if (typeof bt_experiments !== 'undefined')
178    {
179      // check for css classes, then add attributes
180      jQuery("[class^='ab-'],[class*=' ab-']").each(function(e){
181        if(jQuery(this).attr('class').includes('ab-var-') || jQuery(this).attr('class').includes('ab-convert'))
182        {
183          allClasses = jQuery(this).attr('class');
184          allClasses = allClasses.split(" "); // into an array
185          thisTestVar = false;
186          thisTestGoal = false;
187          thisTestId = false;
188          thisTestConversion = false;
189          allClasses.forEach(function(element) { 
190            
191            if(element.startsWith('ab-var-'))
192              thisTestVar = element;
193            else if (element.startsWith('ab-goal-'))
194              thisTestGoal = element;
195            else if(element.startsWith('ab-') && !element.includes('ab-convert') )
196              thisTestId = element;          
197            if(element == 'ab-convert')
198              thisTestConversion = true;
199
200          });
201
202          if(thisTestVar !== false && thisTestId !== false)
203          {
204            //we've got variations, do ya thing!
205            jQuery(this).attr('bt-eid',thisTestId.replace("ab-",""));
206            jQuery(this).attr('bt-variation',thisTestVar.replace("ab-var-",""));
207          }
208
209          if(thisTestConversion == true && thisTestId !== false)
210          {
211            // it's a conversion, convert!
212            abstConvert(thisTestId.replace("ab-",""));
213          }
214
215          if(thisTestGoal == true && thisTestId !== false)
216          {
217            //its a goal, record it!
218            abstGoal(thisTestId.replace("ab-",""),thisTestGoal.replace("ab-goal-",""));
219          }
220        }
221
222        if(jQuery(this).attr('class').includes('ab-click-convert-'))
223        {
224          jQuery(this).attr('class').split(" ").forEach(function(element) { // loop through all classes
225            if(element.trim().startsWith('ab-click-convert-'))
226              abClickListener(element.replace("ab-click-convert-",""),"."+element);
227          });
228        }
229
230      });
231
232      // legacy probably can be removed, check bricks 
233      jQuery("[data-bt-variation]").each(function(){ 
234        jQuery(this).attr('bt-variation',jQuery(this).attr('data-bt-variation')); 
235        jQuery(this).attr('bt-eid',jQuery(this).attr('data-bt-eid')); 
236      }); 
237
238      //fix bricks child attributes
239      //fix bricks, move attr's up one level       
240      document.querySelectorAll(".bricks-element [bt-eid]").forEach(el => {
241        let parent = el.closest('.bricks-element');
242        parent.setAttribute('bt-eid', el.getAttribute('bt-eid'));
243        parent.setAttribute('bt-variation', el.getAttribute('bt-variation'));
244        el.removeAttribute('bt-eid');
245        el.removeAttribute('bt-variation');
246      });
247
248      let searchParams = new URLSearchParams(window.location.search)
249      const abtv = searchParams.get("abtv");
250      const abtid = searchParams.get("abtid");
251
252      //sort experiments by bt_experiments.test_type = full_page, then the rest
253      bt_experiments = Object.entries(bt_experiments).sort((a, b) => {
254        if (a[1].test_type == "full_page") {
255          return -1;
256        }
257        if (b[1].test_type == "full_page") {
258          return 1;
259        }
260        return 0;
261      }).reduce((r, a) => Object.assign(r, { [a[0]]: a[1] }), {});
262
263      if (abtv && abtid && bt_experiments[abtid]) {
264        console.log('AB Split Test: URL variables detected. Skipping user.');
265        showSkippedVisitorDefault(abtid,true,abtv);
266        return true; 
267      }
268      jQuery.each(bt_experiments, function(experimentId, experiment) {
269        //check for URL variables and then ignore the user
270    
271        // if there is a winner, AND the current page is the default page and AUTOCOMPLETE then just go straight there do not pass go
272        if(experiment.test_winner) 
273        {
274          if(experiment.test_type == 'full_page' && (experiment.full_page_default_page == btab_vars.post_id)) // if full p and the p is this page
275          {
276            if(experiment.test_winner !== btab_vars.post_id) // if its not the current page
277            {
278              if(experiment.page_variations[experiment.test_winner] == undefined) // if its not defined
279              {
280                console.log('split test winner is not found, not redirecting');
281                return false; // skip
282              }
283
284              console.log('Split Test winner is page redirect. Redirecting to '+ experiment.page_variations[experiment.test_winner]);
285              window.location.replace( abRedirectUrl(experiment.page_variations[experiment.test_winner]));
286
287              return false;
288            }
289            else // it is the current page so show it
290            {
291              console.log('Test winner is current page. Showing '+ experiment.test_winner);
292              document.body.classList.add('abst-show-page');
293              return false; // skip 2 next experiment
294            }
295
296          }
297          else if(experiment.test_type == 'magic')
298          {
299            console.log('Magic winner is '+ experiment.test_winner + ' WHAT DO I DO NOW TODO');
300          }
301          else if(experiment.test_type == 'css_test')
302          {
303            console.log('Split Test CSS winner. Showing '+ experiment.test_winner);
304            jQuery('body').addClass(experiment.test_winner);
305          } // text test winner todo
306          else // on page test
307          {
308
309            //show variation
310            console.log('Split Test winner is on this page. Showing '+ experiment.test_winner);
311            jQuery('*[bt-eid=\"'+experimentId+'\"][bt-variation=\"'+experiment.test_winner+'\"]').addClass('bt-show-variation');
312          }
313          return true;
314          //skip this experiment
315        }
316        
317        if(experiment.test_type == "css_test")
318        {
319          for (var i = 0; i < experiment.css_test_variations; i++) {
320            // Code to be executed for each element
321            jQuery('body').append('<script class="bt-css-scripts" bt-variation="test-css-'+experimentId+'-'+ (i+1)+ '" bt-eid="'+experimentId+'"/>');
322          }
323        }
324
325        //full page test handler
326        if(experiment.test_type == 'full_page' && experiment.full_page_default_page == btab_vars.post_id)
327        {
328          //add original do nothing variation
329          jQuery('body').append('<div class="bt-redirect-handle" style="display: none !important" bt-variation="'+experiment.full_page_default_page+'" bt-eid="'+experimentId+'"></div>');
330          //foreach variation
331          jQuery.each(experiment.page_variations,function(varId,variation){
332            jQuery('body').append('<div class="bt-redirect-handle" style="display: none !important" bt-variation="'+varId+'" bt-eid="'+experimentId+'" bt-url="'+variation+'"></div>');
333          });
334        }
335
336        
337        //add conversion click handlers
338        if(experiment.conversion_page == 'selector')
339        {
340          if(experiment.conversion_selector != '')
341          {
342            var conversionSelector = experiment.conversion_selector;
343            abClickListener(experimentId,conversionSelector, 0);
344          }
345        }
346
347
348        // if experiment goals exists
349          //loop through
350        if(experiment['goals'])
351          for (var i = 1; i < Object.keys(experiment['goals']).length; i++) {
352            // if experiment.goals[i] key is click
353            //get key name for goal
354            const firstKey = Object.keys(experiment['goals'][i])[0];
355          
356
357            // click selector
358            if(firstKey === 'selector')
359            {
360              abClickListener(experimentId,experiment['goals'][i]['selector'], i);
361            }
362
363            // text subgoall
364            startInverval = false;
365            if(firstKey === 'text')
366            {
367              startInverval = true;
368    
369              convstatus = abstGetCookie('btab_'+experimentId); 
370              if(startInverval && convstatus) // if test exists and not false
371              {
372                convstatus = JSON.parse(convstatus);
373                if (convstatus && convstatus['goals'] && convstatus['goals'][i] == 1)
374                  startInverval = false;
375              }
376    
377              if(experiment['goals'][i]['text'] == '')
378                startInverval = false;
379    
380              //if text exists and not complete
381              if(startInverval)
382              {
383                startTextWatcher(experimentId,experiment['goals'][i]['text'],i);
384              }
385            }
386
387            if(firstKey === 'page')
388            {
389              var goalPage = experiment['goals'][i]['page'];
390
391              if(goalPage == btab_vars.post_id){
392                abstGoal(experimentId,i);
393              }
394            }
395
396            if(firstKey === 'url')
397            {
398              page_url = normalizeUrl(window.location.href);
399
400              goal_url = normalizeUrl(experiment['goals'][i]['url']);
401
402              if(page_url == goal_url){
403                abstGoal(experimentId,i);
404                }
405
406            }
407            
408          }
409        
410        
411
412        //text conversion
413        if(experiment.conversion_page == 'text')
414        {
415          startInverval = true;
416          if(!bt_experiments[experimentId])
417            startInverval = false; // not if not defined
418
419          convstatus = abstGetCookie('btab_'+experimentId); 
420          if(startInverval && convstatus) // if test exists and not false
421          {
422            convstatus = JSON.parse(convstatus);
423            if(convstatus.conversion == 1) // if its converted
424              startInverval = false;
425          }
426
427          if(experiment.conversion_text == '')
428            startInverval = false;
429
430          //if text exists and not complete
431          if(startInverval)
432          {
433            startTextWatcher(experimentId,experiment.conversion_text);
434          }
435        }
436
437        //text subgoals
438
439
440
441
442        if(experiment.conversion_page == 'surecart-order-paid' && window.scData  ) // if surecart is slected and surecart js is detected
443        {
444          document.addEventListener('scOrderPaid', function(e) { // add listener
445            console.log('surecart OrderPaid');
446            const checkout = e.detail;
447            if( checkout && checkout.amount_due ) {
448              if( experiment.use_order_value == true)
449                  window.abst.abConversionValue = (checkout.amount_due/100).toFixed(2); // set value
450              abstConvert(experimentId, window.abst.abConversionValue);
451            }
452          });   
453      }
454
455
456        if( experiment.conversion_page == 'fingerprint' && !localStorage.getItem("ab-uuid") ) { 
457          console.log("ab-uuid: set fingerprint");
458          setAbFingerprint();
459        } 
460      
461      });
462
463
464      var experiments_el = jQuery('[bt-eid]:not([bt-eid=""])[bt-variation]:not([bt-variation=""])');
465      var current_exp = {};
466      var exp_redirect = {};
467
468      experiments_el.each(function(e) {
469        var me = jQuery(this),
470            experimentId = me.attr('bt-eid'),
471            variation = me.attr('bt-variation'),
472            redirect_url = me.attr('bt-url');
473
474        if( current_exp[experimentId] === undefined ) {
475           current_exp[experimentId] = [];
476          exp_redirect[experimentId] = [];
477        }        
478        if (!current_exp[experimentId].includes(variation)) {
479          current_exp[experimentId].push(variation);
480          exp_redirect[experimentId][variation] = redirect_url;
481        }
482      
483      });
484
485      // add css tests to current exp
486      jQuery.each(bt_experiments, function(experimentId, value) {
487        // Code to be executed for each element
488        if(bt_experiments[experimentId]['test_type'] == 'css_test')
489        {
490          current_exp[experimentId] = []; // create
491          exp_redirect[experimentId] = [];
492
493          for (var i = 1; i <= parseInt(bt_experiments[experimentId]['css_test_variations']); i++) {
494            current_exp[experimentId].push('test-css-'+experimentId+'-'+i);
495            exp_redirect[experimentId]['test-css-'+experimentId+'-'+i] = '';
496          }
497        }
498        else if(bt_experiments[experimentId]['test_type'] == 'magic')
499        {
500          if(bt_experiments[experimentId]['magic_definition'] && bt_experiments[experimentId]['magic_definition'].length > 0){
501            magic_definition = JSON.parse(bt_experiments[experimentId]['magic_definition']);
502            current_exp[experimentId] = []; // create
503            exp_redirect[experimentId] = [];
504            var randVar = getRandomInt(0, magic_definition[0].variations.length - 1);
505            current_exp[experimentId].push('test-magic-'+experimentId+'-'+randVar); 
506            exp_redirect[experimentId]['test-magic-'+experimentId+'-'+randVar] = '';
507          }
508        }
509      });
510     
511      jQuery.each(current_exp, function(experimentId, variations) {
512        //check it exists
513        if( bt_experiments[experimentId] === undefined ) {
514          console.log("ABST: " +'Test ID '+ experimentId +' does not exist.');
515          showSkippedVisitorDefault(experimentId); 
516          return true; // continue to next exp
517        }
518
519    
520
521        // if there is a winner for the test, then do no more - its already done above
522        if( bt_experiments[experimentId].test_winner) 
523          return true; // continue to next exp
524
525        /*
526        //if cookie consent isnt done, then show default
527        if ('BorlabsCookie' in window)
528        {
529          // borlabs v2
530          if (typeof window.BorlabsCookie.checkCookieConsent === 'function' &&
531              !window.BorlabsCookie.checkCookieConsent('split-test') &&
532              !window.BorlabsCookie.checkCookieConsent('ab-split-test'))
533            {
534              console.log('Borlabs cookie consent prevented a split test from running');
535              showSkippedVisitorDefault(experimentId);
536              return true; // continue to next exp
537            }
538          //borlabs v3
539          if(typeof window.BorlabsCookie.Consents.hasConsent === 'function')
540          {
541            if(!window.BorlabsCookie.Consents.hasConsent('split-test') && !window.BorlabsCookie.Consents.hasConsent('ab-split-test') )
542            {
543              console.log('Borlabs v3 cookie consent prevented a split test from running');
544              showSkippedVisitorDefault(experimentId);
545              return true; // continue to next exp
546            }
547          }
548        }
549        */
550
551          
552        if( bt_experiments[experimentId]['is_current_user_track'] == false ) {
553          showSkippedVisitorDefault(experimentId);
554          return true; // continue to next exp
555        }
556
557        // if the test is not published
558        if( bt_experiments[experimentId]['test_status'] !== 'publish' ) {
559          showSkippedVisitorDefault(experimentId);
560          return true; // continue to next exp
561        }
562
563        var targetVisitor = true;
564
565        var btab = abstGetCookie('btab_'+ experimentId);
566        
567        var experimentVariation = '';
568
569        if( !btab ) // no existing data, create
570        {
571          if(bt_experiments[experimentId]['test_type'] == 'css_test'){
572            var randVar = getRandomInt(1, parseInt(bt_experiments[experimentId]['css_test_variations'])) - 1;    
573          }
574          else if(bt_experiments[experimentId]['test_type'] == 'magic' && bt_experiments[experimentId]['magic_definition'] && bt_experiments[experimentId]['magic_definition'].length > 0){
575            magic_definition = JSON.parse(bt_experiments[experimentId]['magic_definition']);
576            //console.log(magic_definition[0].variations);
577            var randVar = getRandomInt(0, magic_definition[0].variations.length - 1);
578            variations = magic_definition[0].variations
579          }
580          else
581            var randVar = getRandomInt(0, variations.length - 1);
582
583          
584          if(btab_vars.is_free == '1' && variations.length > 2){
585            var randVar = getRandomInt(0, 1);  //limit to 2
586            console.log('Free version of AB Split Test is limited to 1 variation. Your others will not be shown. Upgrade: https://absplittest.com/pricing?ref=ug');
587          }
588
589          experimentVariation = variations[randVar];
590
591
592        } 
593        else //parse existing data
594        { 
595          
596          try {
597            var btab_cookie = JSON.parse(btab);
598            experimentVariation = btab_cookie.variation;
599          } catch (err) {
600            console.log('Error parsing cookie data:', err);
601          }
602        }
603 
604        var variation_element = false;
605        if(bt_experiments[experimentId]['test_type'] == 'css_test') 
606        {
607          jQuery('body').addClass(experimentVariation); 
608        }
609        else if(bt_experiments[experimentId]['test_type'] == 'magic')
610        {
611        }
612        else // on page tests
613        {   
614          variation_element = jQuery('*[bt-eid=\"'+experimentId+'\"][bt-variation=\"'+experimentVariation+'\"]'); 
615        } 
616 
617        if( btab ){ // if we have a cookie
618            btab = JSON.parse( btab );
619            var redirect_url = exp_redirect[experimentId];
620            redirect_url = redirect_url[experimentVariation];
621            if( redirect_url && !btab_vars.is_preview ) {
622              window.location.replace( abRedirectUrl( redirect_url) );
623              return true; // finished
624            }
625            else
626            {
627              abstShowPage(); // full page
628              if(variation_element)
629                variation_element.addClass('bt-show-variation');  // on page
630
631              if(bt_experiments[experimentId]['test_type'] == 'magic')
632              {
633                magic_definition = JSON.parse(bt_experiments[experimentId]['magic_definition']);
634
635                magic_definition.forEach((element, index) => {
636                  magicSelector = jQuery(element.selector);
637                  var vartn = btab.variation.replace('magic-', '');
638                  if(element.type == 'text')
639                  {
640                    magicSelector.text(element.variations[vartn]);
641                    console.log('swapped text  ');
642                  }
643                  else if(element.type == 'image')
644                  {
645                    magicSelector.attr('src', element.variations[vartn]).attr('srcset', element.variations[vartn]);
646                    console.log('swapped img');
647                  }
648                });
649              }
650            }
651            return true; // continue to next exp
652        }
653
654        if( !btab ) { // new user, check targeting
655          var targetPercentage = bt_experiments[experimentId].target_percentage;
656          
657          if(targetPercentage == '')
658            targetPercentage = 100;
659          
660          var url_query = bt_experiments[experimentId].url_query;
661          urlQueryResult = null;
662          
663          //we've got a url query
664          if(url_query !== '')
665          {
666            if(url_query.includes('*'))
667            {
668              console.log('wildcard search the entire URL ' + url_query);
669              //              wildcard search the entire URL
670              //remove the *
671              url_query = url_query.replace(/\*/g,'');
672              console.log('url_query',url_query);
673              console.log('window.location.href',window.location.href);
674              targetVisitor = window.location.href.includes(url_query);
675              // if href includes string 
676              console.log('targetVisitor',targetVisitor);
677            }
678            else
679            {
680              var exploded_query = url_query.trim().split("=");
681              if(exploded_query.length == 1) // just the query key
682              {
683                targetVisitor = bt_getQueryVariable(exploded_query[0]);
684              }
685              else if(exploded_query.length == 2) //query key and value
686              {
687                urlQueryResult = bt_getQueryVariable(exploded_query[0]);
688                targetVisitor = exploded_query[1] == urlQueryResult;
689              } // else if the string contains an * then we are going to wildcard search the entire URL
690            }
691          }
692          
693          var target_option_device_size = bt_experiments[experimentId].target_option_device_size;
694
695          if(targetVisitor && target_option_device_size != 'all')
696          {
697            var device_size = getDeviceSize(jQuery(window).width());
698
699            targetVisitor = target_option_device_size.includes(device_size);
700
701          }
702
703
704          if(!targetVisitor)
705          {
706            showSkippedVisitorDefault(experimentId);
707            return true;  // continue to next exp
708          }
709          // randomly target users according to percentage
710          var percentage = getRandomInt(1,100);
711          if(targetPercentage < percentage)
712          {        
713            showSkippedVisitorDefault(experimentId,true);
714            console.log('ABST ' + experimentId+' skipped not in percentage target');
715            return true;  // continue to next exp
716          } 
717
718          // no experiment cookie set, calculate and create        
719          bt_experiments[experimentId].variations = bt_get_variations(experimentId);
720        } 
721
722        if(variation_element && !variation_element.length) 
723        {
724          showSkippedVisitorDefault(experimentId);
725          console.log('ABST variation doesnt exist, or doesnt match');
726          return true;  // continue to next exp
727        }
728
729        if (Object.keys(exp_redirect).length > 0)
730        {
731          redirect_url = exp_redirect[experimentId];
732          redirect_url = redirect_url[experimentVariation];
733        }
734        else
735          redirect_url = '';
736
737        // if its css, add it to body   
738
739        if(bt_experiments[experimentId]['test_type'] == 'magic')
740          {
741            magic_definition.forEach((element, index) => {
742              magicSelector = jQuery(element.selector);
743              if(element.type == 'text')
744              {
745                magicSelector.text(magic_definition[index].variations[parseInt(randVar)]);
746              }
747              else if(element.type == 'image')
748              {
749                magicSelector.attr('src', magic_definition[index].variations[parseInt(randVar)]).attr('srcset', magic_definition[index].variations[parseInt(randVar)]);
750              }
751              experimentVariation = 'magic-' + randVar;
752            });
753          }
754
755        if(variation_element)     
756          variation_element.addClass('bt-show-variation'); 
757
758
759        /// if its magic or on page, then set up visible listeners
760        if(bt_experiments[experimentId]['test_type'] == 'ab_test')
761        {
762          watch_for_tag_event(experimentId, 'experiment', experimentVariation); // watch 4 visible instead of insta visit
763        }
764        else if (bt_experiments[experimentId]['test_type'] == 'magic')
765        {
766          //loop through the magic elements and watch for them
767          magic_definition.forEach((element, index) => {
768            watch_for_tag_event(experimentId,element.selector, experimentVariation); // watch 4 visible instead of insta visit
769          });
770        }
771        else
772        {
773          bt_experiment_w(experimentId,experimentVariation,'visit', redirect_url);
774        }
775        
776
777      });
778    }
779
780    // warn users on localhost
781    if(btIsLocalhost())
782      console.info("AB Split Test: It looks like you're on a localhost, using local storage instead of cookies. External Conversion Pixels will not work on Local web servers.");
783    
784    abst_find_analytics();
785    window.dispatchEvent(new Event('resize')); // trigger a window resize event. Useful for sliders etc. that dynamically resize
786    jQuery('body').trigger('ab-test-setup-complete'); // the end of the ab testing. do other things if you like with   jQuery('body').bind('ab-test-setup-complete', function() {    //do what you want! });
787    //add class ab-test-setup-complete to body
788    document.body.classList.add('ab-test-setup-complete');
789});
790
791function startTextWatcher(experimentId,word,goalId = null){
792 
793  if(!goalId)
794    goalId = 0;
795
796  if (typeof abst.intervals[experimentId] === 'undefined') {
797    abst.intervals[experimentId] = {};
798  }
799
800  window.abst.intervals[experimentId][goalId] = setInterval(function () {
801    let found = false;
802    var escapedWord = word.replace(/'/g, "\\'"); // Escape single quotes
803    
804    
805    // Search within the main page more efficiently
806    jQuery("body").find(":contains('" + escapedWord + "')").each(function () {
807      if (jQuery(this).text().indexOf(word) >= 0 && jQuery(this).is(":visible")) {
808        found = true;
809        return false; // Break the loop
810      }
811    });
812  
813    if (!found) {
814      jQuery("iframe").each(function () {
815        try {
816          var iframeBody = jQuery(this).contents().find("body");
817          found = iframeBody.find(":contains('" + escapedWord + "')").filter(function () {
818            return jQuery(this).text().indexOf(word) >= 0 && jQuery(this).is(":visible");
819          }).length > 0;
820  
821          if (found){
822            console.log('ABST: ' + experimentId + ' ' + word);
823            return false; // Break the loop
824          } 
825        } catch (error) {
826          //console.error("Error accessing iframe contents:", error);
827        }
828      });
829    }
830  
831      if(found){
832        if(goalId == 0)
833        {
834          abstConvert(experimentId);
835        }
836        else
837        {  
838          abstGoal(experimentId,goalId);
839        }
840        clearInterval(window.abst.intervals[experimentId][goalId]); // Stop the interval after finding the word
841      }
842    }, 1000);
843  }
844  
845
846  function abstConvert(testId = '',orderValue = 1){
847    console.log('abstConvert',testId,orderValue);
848    if(testId !== '')
849    {
850      var btab = abstGetCookie('btab_'+ testId);
851       if(btab)
852       {
853          btab = JSON.parse(btab);
854          if(btab.conversion == 0)
855          {
856            bt_experiment_w(testId,btab.variation,'conversion',false,orderValue);
857            btab.conversion = 1;
858            experiment_vars = JSON.stringify(btab);
859            abstSetCookie('btab_'+ testId, experiment_vars, 1000);   
860            if(abst.intervals[testId]){
861              clearInterval(abst.intervals[testId]);
862            }
863          }
864          else
865          {
866            console.log("ABST: " +bt_experiments[testId].name + ': Visitor has already converted');
867          }
868       }
869       else
870       {
871        if(!bt_experiments[testId])
872          console.log("ABST: " +'Test ID not found or test not active');
873       }
874    }
875  }
876
877  function abstGoal(testId = '',goal = ''){
878    //    console.log('start abstGoal',testId,goal);
879    if(testId !== '' && goal !== '')
880    {
881      var btab = abstGetCookie('btab_'+ testId);
882       if(btab)
883       {
884          btab = JSON.parse(btab);
885          //console.log('btab',btab);
886          if(btab.conversion !== 1) // no goals after conversion
887          {
888            if(Array.isArray(btab.goals))
889            {
890              //if its not in the goal
891              if(btab.goals[goal] !== 0)
892              {
893                btab.goals[goal] = 1; 
894                bt_experiment_w(testId,btab.variation,goal,false,1);
895              }
896              
897              experiment_vars = JSON.stringify(btab);
898              abstSetCookie('btab_'+ testId, experiment_vars, 1000);
899              if(abst.intervals[testId+""+goal])
900              {
901                clearInterval(abst.intervals[testId+""+goal]);
902              }
903            }
904            else
905            {
906              console.log("ABST: " +bt_experiments[testId].name + ': Visitor has already goaled');
907            }
908          } 
909          //else
910            //console.log('no goals after conversion');
911       }
912       else
913       {
914        if(!bt_experiments[testId])
915          console.log("ABST: " +'Test ID not found or test not active');
916       }
917    }
918  }
919    
920function showSkippedVisitorDefault(eid,createCookie = false,variation = false){
921  if(!window.bt_experiments[eid]){ // if no experiment, show first variations
922    
923      btv = jQuery('*[bt-eid=\"'+eid+'\"]').first().attr('bt-variation');
924      jQuery('*[bt-eid=\"'+eid+'\"][bt-variation=\"'+btv+'\"]').addClass('bt-show-variation');
925      return true;
926  }
927
928  if(variation && eid) // if we have a variation passed, just do it
929  {
930    if(bt_experiments[eid].test_type == "css_test") // css version 1
931    {
932      jQuery("body").addClass(variation); // add 1 class
933    }
934    else if (bt_experiments[eid].test_type == "full_page") // full page
935    {
936      url = bt_experiments[eid].page_variations[variation];
937      if(url !== undefined)
938      {
939        window.location.replace( abRedirectUrl(url) ); // follow the link w search params
940        return true;
941      }
942      else
943      {
944        console.log('No matching page found, must be default page no redirect.');
945        //add show class to page
946        document.body.classList.add('abst-show-page');
947        return false;
948      }
949    }
950    else if (bt_experiments[eid].test_type == "magic")
951    {
952      console.log('magic test, showing ver ' + variation);
953      // Clean up the variation string and get the letter
954      const letter = variation.replace('Variation ','').replace('(Original)','').trim().toLowerCase();
955      
956      // Safety check - make sure we have a single letter
957      if (letter.length !== 1 || !/[a-z]/.test(letter)) {
958        console.error('Invalid variation format:', variation);
959        return false;
960      }
961      
962      // Convert letter to index (a=0, b=1, etc)
963      const magVar = letter.charCodeAt(0) - 'a'.charCodeAt(0);
964      
965      // Verify the index is in valid range
966      if (magVar < 0 || magVar > 25) {
967        console.error('Invalid variation letter:', letter);
968        return false;
969      }
970      
971      showMagicTest(eid, magVar);
972    }
973    else // on page
974    {
975      jQuery('*[bt-eid=\"'+eid+'\"][bt-variation=\"'+variation+'\"]').addClass('bt-show-variation');
976    }
977    abstShowPage();
978    if(createCookie)
979    {
980      skippedCookie(eid,variation);
981    }
982    return true;
983  }
984
985  if(bt_experiments[eid].test_winner !== ''){ // if we have a winner
986
987    if(bt_experiments[eid].test_type =="full_page") // full page winner
988    {
989      url = bt_experiments[eid].page_variations[bt_experiments[eid].test_winner];
990      if(url !== undefined)
991      {
992        window.location.replace( abRedirectUrl(url) ); // follow the link w search params
993
994        return true;
995      }
996      else
997      {
998        console.log("ABST: " +'Full page test complete without matching page winner. Showing current page.');
999      }
1000      abstShowPage();
1001    }
1002
1003    if(bt_experiments[eid].test_type == "css_test") // css winner
1004    {
1005      console.log('css test winner, showing ver ' + bt_experiments[eid]['test_winner']);
1006      jQuery("body").addClass('test-css-'+eid+'-'+bt_experiments[eid]['test_winner']); // add winning class
1007      return true; // next
1008    }
1009
1010    if(bt_experiments[eid].test_type == "magic") // magic winner
1011    {
1012      console.log('magic test winner, showing ver ' + bt_experiments[eid]['test_winner']);
1013      showMagicTest(eid, bt_experiments[eid]['test_winner']);
1014      return true; // next
1015    }
1016  }
1017
1018  if(bt_experiments[eid].test_type == "full_page")
1019  {
1020    abstShowPage();
1021    if(createCookie)
1022      skippedCookie(eid,bt_experiments[eid].full_page_default_page);
1023
1024    return true; // next
1025  }
1026
1027  if(bt_experiments[eid].test_type == "css_test") // css version 1
1028  {
1029    jQuery("body").addClass('test-css-'+eid+'-1'); // add 1 class
1030    if(createCookie)
1031      skippedCookie(eid,'test-css-'+eid+'-1');
1032    return true; // next
1033  }
1034  
1035  //on page tests only from here
1036  
1037  if(!eid)
1038    return;
1039  var foundSpecial = false;
1040  jQuery('*[bt-eid=\"'+eid+'\"]').each(function(index, element){
1041    var variationName = jQuery(element).attr('bt-variation').toLowerCase();
1042    var defaultNames = ["original","one","1","default","standard","a","control"];
1043    if(defaultNames.includes(variationName))
1044    {
1045      btv = variationName;
1046      jQuery(element).addClass('bt-show-variation'); // show the matched element
1047      foundSpecial = true;
1048    }
1049  });
1050    if(!foundSpecial){
1051      btv = jQuery('*[bt-eid=\"'+eid+'\"]').first().attr('bt-variation');
1052      jQuery('*[bt-eid=\"'+eid+'\"][bt-variation=\"'+btv+'\"]').addClass('bt-show-variation');
1053  }
1054
1055  if(createCookie)
1056    skippedCookie(eid,btv);
1057 
1058}
1059function skippedCookie(eid,btv){
1060    var experiment_vars = {
1061      eid: eid,
1062      variation: btv,
1063      conversion: 1,
1064      skipped:1
1065    };
1066    experiment_vars = JSON.stringify(experiment_vars);
1067    abstSetCookie('btab_'+ eid, experiment_vars, 1000);
1068    return true;
1069}
1070
1071//takes input slug or url and ends url suitable for window/replace
1072function abRedirectUrl(url){
1073
1074  url = url + window.location.search + window.location.hash;
1075  // if it starts with http/s dop nothing
1076  if(url.startsWith('http') || url.startsWith('/'))
1077    return url;
1078  else
1079    return '/' + url;
1080  
1081}
1082
1083function abstOneSecond(){
1084  if(Object.keys(window.abst.timer).length > 0)
1085  {
1086    Object.entries(window.abst.timer).forEach(([ index,item]) => {
1087      if(window.abst.timer[index] > -1) // dont decrease below -1
1088        window.abst.timer[index] = window.abst.timer[index] - 1;
1089
1090      if(window.abst.timer[index] == 0){  // convert if its counted down, only fired once
1091
1092        //console.log('time active converting ' + index);
1093        //index could be goal-eid-goalid
1094        if(index.includes('goal-'))
1095        {
1096          var parts = index.split('-');
1097          abstGoal(parts[1],parts[2]);
1098        }
1099        else
1100          abstConvert(index);
1101      
1102      }
1103    });
1104
1105    // update localstorage
1106    localStorage.setItem('absttimer', JSON.stringify(window.abst.timer)); // localstorage need strings
1107  }
1108}
1109
1110
1111
1112function userActiveNow(){
1113  window.abst.currscroll = window.scrollY; // update last known scroll n mouse
1114  window.abst.abactive = true; // we active
1115  clearTimeout(window.abst.timeoutTimer); // delete the old timout
1116  window.abst.timeoutTimer = setTimeout(abstActiveTimeout, window.abst.timeoutTime); // create new timeout
1117}
1118
1119function abstActiveTimeout(){
1120  window.abst.abactive = false;
1121}
1122
1123
1124function getRandomInt(min, max) {
1125    min = Math.ceil(min);
1126    max = Math.floor(max);
1127    return Math.floor(Math.random() * (max - min + 1)) + min;
1128}
1129
1130function abstSetCookie(c_name, value, exdays) {
1131  if(btIsLocalhost())
1132    return btSetLocal(c_name, value);
1133
1134    var hostname = window.location.hostname;
1135    var domain = hostname.replace(/^www\./, '').split('.').slice(-2).join('.');
1136    domain = '.' + domain;
1137
1138    var exdate = new Date();
1139    exdate.setDate(exdate.getDate() + exdays);
1140    var c_value = escape(value) + ((exdays == null) ? '' : ';path=/; expires=' + exdate.toUTCString()) + "; SameSite=None; Secure; domain=" + domain;
1141
1142    document.cookie = c_name + '=' + c_value;
1143}
1144function deleteCookie(c_name) {
1145      if(btIsLocalhost())
1146        return btDeleteLocal(c_name);
1147
1148    document.cookie = c_name+"= ; expires = Thu, 01 Jan 1970 00:00:00 GMT";
1149}
1150
1151function abstGetCookie(c_name) {
1152    if(btIsLocalhost())
1153      return btGetLocal(c_name);
1154
1155    var i, x, y, ARRcookies = document.cookie.split(';');
1156    for (i = 0; i < ARRcookies.length; i++) {
1157        x = ARRcookies[i].substr(0, ARRcookies[i].indexOf('='));
1158        y = ARRcookies[i].substr(ARRcookies[i].indexOf('=') + 1);
1159        x = x.replace(/^\s+|\s+$/g, '');
1160        if (x == c_name) {
1161            return unescape(y);
1162        }
1163    }
1164}
1165
1166function abstShowPage(){
1167  jQuery("body").addClass('abst-show-page');
1168  jQuery("body",parent.window.document).addClass('abst-show-page'); // bb iframe UI
1169}
1170
1171function btSetLocal(c_name,value){
1172    localStorage.setItem(c_name, value);
1173}
1174
1175function btGetLocal(c_name){
1176  return localStorage.getItem(c_name);
1177}
1178
1179function btDeleteLocal(c_name){
1180  localStorage.removeItem(c_name);
1181}
1182
1183function btIsLocalhost(){
1184  return (location.hostname === "localhost" || location.hostname === "127.0.0.1"|| location.hostname.endsWith(".local")|| location.hostname.endsWith(".test"));
1185}
1186
1187function bt_get_variations(eid){
1188  
1189  variation = [];
1190  jQuery('[bt-eid=' + eid + ']').each(function (index, val) {
1191    var newVariation = jQuery(this).attr('bt-variation');
1192    
1193    // Check if the variation already exists in the array
1194    if (variation.indexOf(newVariation) === -1) {
1195      variation.push(newVariation);
1196    }
1197  });
1198
1199  if(btab_vars.is_free == '1')
1200  {
1201    //only return first 2 variations
1202    variation = variation.slice(0,2);
1203  }
1204
1205  return variation;
1206}
1207
1208function bt_experiment_w(eid,variation,type, url,orderValue=1){
1209
1210  // dont log it if its a skipper or malformed
1211  if(variation == '_bt_skip_' || btab_vars.is_preview || !eid || !variation)
1212  {
1213    return true;
1214  }
1215
1216  // if its a fingerprinter and we dont have a uuid, then wait for it
1217  if(bt_experiments[eid].conversion_page == 'fingerprint' && !localStorage.getItem('ab-uuid'))
1218  {
1219    console.log("bt_exp_w: waiting for fingerprint");
1220    setTimeout(bt_experiment_w, 500, eid, variation, type, url,orderValue);
1221    return true; //back in 500ms
1222  }
1223  
1224  var data = {
1225    'action': 'bt_experiment_w',
1226    'eid'  : eid,   
1227    'variation'  : variation,   
1228    'type'  : type,
1229    'size'  : abst.size,
1230    'location' : btab_vars.post_id,
1231    'orderValue': orderValue
1232  };
1233
1234  var experiment_vars = {
1235    eid: eid,
1236    variation: variation,
1237    conversion: 0,
1238    goals:[],
1239  };
1240
1241//todo mote to on seen watcher
1242  //visit time watcher
1243  if( type == 'visit' && bt_experiments[eid]['conversion_page'] == 'time')
1244  {
1245    window.abst.timer[eid] = bt_experiments[eid]['conversion_time'];
1246  }
1247  //goal time watcher
1248  if( type == 'visit')
1249  {
1250    // console.log(bt_experiments[eid]['goals']);
1251    //foreach goals 
1252    jQuery.each(bt_experiments[eid]['goals'], function(key, value) {
1253      // if valuee.time is set
1254      if(value['time'] > 0)
1255      {
1256        window.abst.timer["goal-"+eid + "-"+key] = value['time'];
1257      }
1258    });
1259  }
1260
1261  // set up conversion
1262  if( type == 'conversion' ) 
1263    experiment_vars.conversion = 1;
1264  else if ( type == 'visit' )
1265  {
1266    // not conversion or goal
1267  }
1268  else // goal
1269  {
1270    experiment_vars.goals[type] = 1; // add to goals list
1271  }
1272
1273
1274  //add uuid if necessary
1275  if( bt_experiments[eid]['conversion_page'] == 'fingerprint')
1276    data.uuid = localStorage.getItem('ab-uuid');
1277
1278
1279  //add advanced id if necessary
1280  if( btab_vars.advanced_tracking == '1' )
1281    data.ab_advanced_id = localStorage.getItem('ab-advanced-id');
1282
1283  experiment_vars = JSON.stringify(experiment_vars);
1284    // For non-redirect events, check if we're waiting for approval
1285  if (window.abst.waitForApproval) {
1286    queueEventData(data, url);
1287  } else {
1288    // Process normally if not waiting for approval
1289    // Send beacon
1290    console.log('sent beacon', data);
1291    navigator.sendBeacon(bt_ajaxurl+"?action=bt_experiment_w&method=beacon", JSON.stringify(data));
1292  }
1293  abstSetCookie('btab_'+ eid, experiment_vars, 1000);      
1294  //show the page if its not a redirect or external
1295  if(url && url !== "ex")
1296    {
1297      addServerEvents({
1298        eid: eid,
1299        variation: variation,
1300        type: 'visit',
1301      });
1302      window.location.replace(abRedirectUrl(url));
1303    }
1304    else
1305    {
1306    btab_track_event(data); // analytics tagger    
1307    abstShowPage(); // show the page
1308  }
1309
1310  return true;
1311}
1312
1313/**
1314 * Helper function to queue event data in localStorage
1315 */
1316function queueEventData(data, url) {
1317  // Add to queue in localStorage
1318  const queueData = {
1319    data: data,
1320    timestamp: new Date().getTime(),
1321    url: url
1322  };
1323  
1324  // Get existing queue or initialize empty array
1325  let queue = [];
1326  try {
1327    const queueString = localStorage.getItem('abstTestDataQueue');
1328    if (queueString) {
1329      queue = JSON.parse(queueString);
1330    }
1331  } catch (e) {
1332    console.error('Error parsing abstTestDataQueue', e);
1333  }
1334  
1335  // Add new item to queue
1336  queue.push(queueData);
1337  
1338  // Save updated queue
1339  localStorage.setItem('abstTestDataQueue', JSON.stringify(queue));
1340  
1341  console.log('Event queued for approval', queueData);
1342}
1343
1344/**
1345 * Process events when approval is given
1346 * This function should be called when cookie consent or other approval is given
1347 */
1348function abst_process_approved_events() {
1349  // Set approval flag to true and save to localStorage
1350  setAbstApprovalStatus(true);
1351  
1352  // Get queued events
1353  let queue = [];
1354  try {
1355    const queueString = localStorage.getItem('abstTestDataQueue');
1356    if (queueString) {
1357      queue = JSON.parse(queueString);
1358    }
1359  } catch (e) {
1360    console.error('Error parsing abstTestDataQueue', e);
1361    return false;
1362  }
1363  
1364  if (!queue.length) {
1365    console.log('No queued events to process');
1366    return true;
1367  }
1368  
1369  console.log('Processing ' + queue.length + ' queued events');
1370  
1371  // Process each queued event
1372  queue.forEach(queueItem => {
1373    // Track the event
1374    btab_track_event(queueItem.data);
1375    
1376    // Send beacon for the event
1377    console.log('Sending queued beacon', queueItem.data);
1378    navigator.sendBeacon(bt_ajaxurl+"?action=bt_experiment_w&method=beacon", JSON.stringify(queueItem.data));
1379  });
1380  
1381  // Clear the queue
1382  localStorage.removeItem('abstTestDataQueue');
1383  
1384  return true;
1385}
1386
1387/**
1388 * Track an event
1389 * This function is used to track events in the analytics partners
1390 * @param {Object} data - The event data
1391 * @param {string} data.eid - The experiment ID
1392 * @param {string} data.variation - The variation
1393 * @param {string} data.type - The event type
1394 */
1395async function btab_track_event(data){
1396
1397  //create javascript event with data 
1398  jQuery('body').trigger('abst_event', [data]);
1399  
1400  if(btab_vars.is_free == '1')
1401    return false;
1402
1403  if(btab_vars.tagging == '0'){
1404    //console.log('event tagging turned off');
1405    return false;
1406  }
1407
1408  window.abst.eventQueue.push(data);
1409
1410  trackName = bt_experiments[data.eid].name || data.eid;
1411  //gtag always
1412  gtm_data = {
1413    'event': 'ab_split_test',
1414    'test_name': trackName,
1415    'test_variation': data.variation,
1416    'test_event': data.type,
1417    'test_id': data.eid,
1418  };
1419  if( bt_experiments[data.eid]['conversion_page'] == 'fingerprint')
1420    gtm_data.abuuid = localStorage.getItem('ab-uuid');
1421  else if(localStorage.getItem('ab-advanced-id'))
1422    gtm_data.abuuid = localStorage.getItem('ab-advanced-id');
1423  
1424  window.dataLayer = window.dataLayer || [];
1425  window.dataLayer.push(gtm_data); // add to gtm data layer
1426
1427  if(window.abst.abconvertpartner.ga4) //ga4 add
1428    gtag('event',  'ab_split_test', {
1429      'test_name': data.eid,
1430      'test_variation': data.variation,
1431      'test_event': data.type,
1432    });
1433  
1434
1435  if(window.abst.abconvertpartner.abawp){ //analyticswp
1436    AnalyticsWP.event('Test: ' + bt_experiments[data.eid].name, {
1437      test_id: data.eid,
1438      test_name: bt_experiments[data.eid].name,
1439      test_variation: data.variation,
1440      test_visit_type: data.type
1441    });
1442  }
1443
1444  if(window.abst.abconvertpartner.clarity){ //clarity
1445    clarity("set", bt_experiments[data.eid].name + "-" + data.type, data.variation);
1446  }  
1447
1448  if(window.abst.abconvertpartner.gai){ //google analytics
1449    tracker = ga.getAll()[0];
1450    if (tracker)
1451    {
1452        tracker.send("event", bt_experiments[data.eid].name, data.type, data.variation,{nonInteraction: true}); // send non interactive event to GA
1453        window.abst.abconvertpartner.gai = true;
1454    }
1455  }
1456
1457  if(window.abst.abconvertpartner.abmix){ //abmix
1458    mixpanel.track(bt_experiments[data.eid].name, {'type': data.type,'variation':data.variation}, {send_immediately: true});
1459  }
1460
1461  if(window.abst.abconvertpartner.abumav){ //umami
1462    usermaven("track", bt_experiments[data.eid].name, {
1463      type: data.type,
1464      variation: data.variation
1465    });
1466    
1467  }
1468
1469  if(window.abst.abconvertpartner.umami){ //umami
1470    umami.track(bt_experiments[data.eid].name, {
1471      type: data.type,
1472      variation: data.variation
1473    });
1474  }
1475
1476  if(window.abst.abconvertpartner.cabin){ //cabin
1477    cabin.event(bt_experiments[data.eid].name + ' | ' + data.type + ' | ' + data.variation);
1478  }
1479
1480  if(window.abst.abconvertpartner.plausible){ //plausible
1481    plausible(bt_experiments[data.eid].name, {
1482      props: {
1483        type: data.type,
1484        variation: data.variation
1485      }
1486    });
1487  }
1488
1489  if(window.abst.abconvertpartner.fathom){ //fathom
1490    fathom.trackGoal(bt_experiments[data.eid].name, {
1491      type: data.type,
1492      variation: data.variation
1493    });
1494  }
1495  if(window.abst.abconvertpartner.posthog){
1496    posthog.capture(bt_experiments[data.eid].name, {
1497      type: data.type,
1498      variation: data.variation
1499    });
1500  }
1501}
1502
1503function abst_find_analytics(){
1504  if(btab_vars.is_free == '1')
1505    return false;
1506
1507  window.abeventstarted = new Date().getTime();
1508
1509  window.dataLayer || (window.dataLayer = []); //gtag
1510  
1511  // Define all analytics providers we want to check for
1512  const analyticsProviders = [
1513    {
1514      name: 'ga4',
1515      check: () => typeof gtag === "function",
1516      process: () => {
1517        window.abst.eventQueue.forEach(element => {
1518          gtag('event',  'ab_split_test', {
1519            'test_name': bt_experiments[element.eid].name,
1520            'test_variation': element.variation,
1521            'test_event': element.type,
1522            'ab_uuid': element.uuid,
1523          });
1524        });
1525      }
1526    },
1527    {
1528      name: 'clarity',
1529      check: () => typeof clarity === "function",
1530      process: () => {
1531        window.abst.eventQueue.forEach(element => {
1532          clarity("set", element.eid + "-" + element.type, element.variation);
1533        });
1534      }
1535    },
1536    {
1537      name: 'gai',
1538      check: () => {
1539        if (typeof ga === "function") {
1540          const tracker = ga.getAll()[0];
1541          return !!tracker;
1542        }
1543        return false;
1544      },
1545      process: () => {
1546        const tracker = ga.getAll()[0];
1547        window.abst.eventQueue.forEach(element => {
1548          tracker.send("event", element.eid, element.type, element.variation, {nonInteraction: true});
1549        });
1550      }
1551    },
1552    {
1553      name: 'fathom',
1554      check: () => !!window.fathom,
1555      process: () => {
1556        window.abst.eventQueue.forEach(element => {
1557          window.fathom.trackEvent(element.eid + ", " + element.type + ": " + element.variation);
1558        });
1559      }
1560    },
1561    {
1562      name: 'posthog',
1563      check: () => !!window.posthog,
1564      process: () => {
1565        window.abst.eventQueue.forEach(element => {
1566          posthog.capture(bt_experiments[element.eid].name, {
1567            type: element.type,
1568            variation: element.variation
1569          });
1570        });
1571      }
1572    },
1573    {
1574      name: 'abmix',
1575      check: () => typeof mixpanel === "object",
1576      process: () => {
1577        window.abst.eventQueue.forEach(element => {
1578          mixpanel.track(bt_experiments[element.eid].name, {'type': element.type,'variation':element.variation}, {send_immediately: true});
1579        });
1580      }
1581    },
1582    {
1583      name: 'abumav',
1584      check: () => typeof usermaven === "function",
1585      process: () => {
1586        window.abst.eventQueue.forEach(element => {
1587          usermaven("track", bt_experiments[element.eid].name, {
1588            type: element.type,
1589            variation: element.variation
1590          });
1591        });
1592      }
1593    },
1594    {
1595      name: 'umami',
1596      check: () => !!window.umami,
1597      process: () => {
1598        window.abst.eventQueue.forEach(element => {
1599          umami.track(bt_experiments[element.eid].name, {
1600            type: element.type,
1601            variation: element.variation
1602          });
1603        });
1604      }
1605    },
1606    {
1607      name: 'cabin',
1608      check: () => !!window.cabin,
1609      process: () => {
1610        window.abst.eventQueue.forEach(element => {
1611          cabin.event(bt_experiments[element.eid].name + ' | ' + element.type + ' | ' + element.variation);
1612        });
1613      }
1614    },
1615    {
1616      name: 'plausible',
1617      check: () => !!window.plausible,
1618      process: () => {
1619        window.abst.eventQueue.forEach(element => {
1620          plausible(bt_experiments[element.eid].name, {
1621            props: {
1622              type: element.type, 
1623              variation: element.variation
1624            }
1625          });
1626        });
1627      }
1628    },
1629    {
1630      name: 'abawp',
1631      check: () => typeof AnalyticsWP === "object",
1632      process: () => {
1633        window.abst.eventQueue.forEach(element => {
1634          AnalyticsWP.event('Test: ' + bt_experiments[element.eid].name, {
1635            test_id: element.eid,
1636            test_name: bt_experiments[element.eid].name,
1637            test_variation: element.variation,
1638            test_visit_type: element.type
1639          });
1640        });
1641      }
1642    }
1643  ];
1644
1645  // Initialize partner object if not exists
1646  window.abst.abconvertpartner = window.abst.abconvertpartner || {};
1647  
1648  // Maximum time to wait for analytics providers (in milliseconds)
1649  const MAX_WAIT_TIME = 10000; // 10 seconds
1650  const CHECK_INTERVAL = 500;
1650 // Check every 500ms
1651  
1652  // Function to check a single analytics provider
1653  const checkProvider = (provider) => {
1654    // Skip if already processed
1655    if (window.abst.abconvertpartner[provider.name]) {
1656      return true;
1657    }
1658    
1659    // Check if provider is available
1660    if (provider.check()) {
1661      window.abst.abconvertpartner[provider.name] = true;
1662      provider.process();
1663      return true;
1664    }
1665    
1666    return false;
1667  };
1668  
1669  // Function to check all providers
1670  const checkAllProviders = () => {
1671    return analyticsProviders.map(provider => checkProvider(provider));
1672  };
1673  
1674  // Start time to calculate timeout
1675  const startTime = new Date().getTime();
1676  
1677  // Create a promise that resolves when all providers are found or timeout
1678  const findAnalyticsPromise = new Promise((resolve) => {
1679    const checkAnalytics = () => {
1680      // Check all providers
1681      const results = checkAllProviders();
1682      
1683      // If all providers are found or we've exceeded the max wait time, resolve
1684      const allFound = results.every(result => result === true);
1685      const timeElapsed = new Date().getTime() - startTime;
1686      
1687      if (allFound || timeElapsed > MAX_WAIT_TIME) {
1688        resolve();
1689      } else {
1690        // Check again after interval
1691        setTimeout(checkAnalytics, CHECK_INTERVAL);
1692      }
1693    };
1694    
1695    // Start checking
1696    checkAnalytics();
1697  });
1698  
1699  // Return the promise for potential chaining
1700  return findAnalyticsPromise;
1701}
1702
1703// check for a full page test visit cookie
1704function bt_getQueryVariable(variable) {
1705  var query = window.location.search.substring(1);
1706  var vars = query.split("&");
1707  for (var i=0;i<vars.length;i++) {
1708    var pair = vars[i].split("=");
1709    if(pair[0] == variable){
1710      if(pair[1] == null)
1711        return true;
1712      return pair[1];
1713    }
1714  }
1715  return(false);
1716}
1717
1718if (!String.prototype.endsWith) {
1719  String.prototype.endsWith = function(search, this_len) {
1720    if (this_len === undefined || this_len > this.length) {
1721      this_len = this.length;
1722    }
1723    return this.substring(this_len - search.length, this_len) === search;
1724  };
1725}
1726
1727
1728
1729//function to find and replace strings uin document, including strings with formatting like strong em, etc
1730function bt_replace_all(find, replace, location = 'body') {
1731  // Get the element by the provided location
1732  const element = document.querySelector(location);
1733  
1734  // Function to recursively replace text
1735  function replaceText(node) {
1736      if (node.nodeType === Node.TEXT_NODE) { // Check if it's a text node
1737          // Replace text, preserving HTML entities by decoding and encoding
1738          let text = node.textContent;
1739          let div = document.createElement('div');
1740          div.innerHTML = text.replace(new RegExp(find, 'gi'), replace);
1741          node.textContent = div.textContent || div.innerText || "";
1742      } else {
1743          // Otherwise, handle all its children nodes
1744          node.childNodes.forEach(replaceText);
1745      }
1746  }
1747
1748  // Start the text replacement process from the chosen element
1749  replaceText(element);
1750}
1751
1752// Usage example:
1753//bt_replace_all('find text', 'replace text');
1754
1755function bt_replace_all_html(find, replace, location = 'body') {
1756  // Get the element by the provided location
1757  const element = document.querySelector(location);
1758  
1759  // Function to recursively replace HTML
1760  function replaceHTML(node) {
1761      if (node.nodeType === Node.ELEMENT_NODE) { // Check if it's an element node
1762          // Replace HTML, preserving the node structure
1763          node.innerHTML = node.innerHTML.split(find).join(replace);
1764      } else {
1765          // Otherwise, handle all its children nodes
1766          node.childNodes.forEach(replaceHTML);
1767      }
1768  }
1769
1770  // Start the HTML replacement process from the chosen element
1771  replaceHTML(element);
1772}
1773
1774function abClickListener(experimentId,conversionSelector, goalId = 0) {
1775  var testCookie = abstGetCookie('btab_'+experimentId);
1776  if(testCookie)
1777  {
1778    testCookie = JSON.parse(testCookie);
1779    if(goalId == 0)
1780    {
1781      if(testCookie.conversion == 1)
1782      {
1783        //console.log('abClickListener: already converted');
1784        return;
1785      }
1786    }
1787    else // issa goal
1788    {
1789      if(testCookie.goals[goalId] == 1)
1790      {
1791        //console.log('abClickListener: goal already converted');
1792        return;
1793      }      
1794    }
1795  }
1796  var eventType = 'click'; // Default event type
1797  // If a pipe symbol exists, split it into the conversion selector and the event type
1798  if(conversionSelector.indexOf('|') !== -1) {
1799      var conversionParts = conversionSelector.split('|');  
1800      // Check if there are at least two conversionParts
1801      if(conversionParts.length >= 2) {
1802          conversionSelector = conversionParts[0]; // First part is the conversion selector
1803          eventType = conversionParts[1]; // Second part is the event type
1804      }
1805  }
1806
1807  try {
1808    // Listen for clicks on elements matching the class selector
1809    document.addEventListener(eventType, function(event) {
1810      var target = event.target;
1811      while (target && target !== document) {
1812        if (target.matches(conversionSelector)) {
1813          //console.log(eventType + ' conversion on ' + conversionSelector + ' type ' + goalId);
1814          if(goalId > 0)
1815            abstGoal(experimentId,goalId);
1816          else
1817            abstConvert(experimentId);
1818          break;
1819        }
1820        target = target.parentNode;
1821      }
1822    }, true);
1823
1824    // Listen for clicks on any links with ab-click-convert- query parameter
1825    document.addEventListener('click', function(event) {
1826      var target = event.target;
1827      while (target && target !== document) {
1828        if (target.tagName === 'A' && target.href) {
1829          try {
1830            var url = new URL(target.href);
1831            var params = new URLSearchParams(url.search);
1832            
1833            // Check for the ab-click-convert-ID parameter
1834            for (const [key, value] of params.entries()) {
1835              if (key.startsWith('ab-click-convert-')) {
1836                var linkExperimentId = key.replace('ab-click-convert-', '');
1837                if (linkExperimentId === experimentId) {
1838                  event.preventDefault(); // Prevent default link behavior
1839                  console.log('Query string conversion for experiment ID: ' + experimentId);
1840                  if(goalId > 0)
1841                    abstGoal(experimentId, goalId);
1842                  else
1843                    abstConvert(experimentId);
1844                  
1845                  // Continue to the link after a short delay to allow conversion to be processed
1846                  setTimeout(function() {
1847                    window.location.href = target.href;
1848                  }, 300);
1849                  break;
1850                }
1851              }
1852            }
1853          } catch (e) {
1854            // Invalid URL, just continue
1855          }
1856        }
1857        target = target.parentNode;
1858      }
1859    }, true);
1860  } catch(error) {
1861    console.info('ABST: Invalid conversion selector:' +  conversionSelector + ' ' + error + ' ' + goalId); 
1862  }
1863  var iframes = document.querySelectorAll('iframe');
1864  iframes.forEach(function(iframe) {
1865  try {
1866    var iframeDoc = iframe.contentWindow.document;
1867    iframeDoc.addEventListener(eventType, function(event) {
1868      if (event.target.matches(conversionSelector)) {
1869        console.log(eventType + ' IFRAME conversion on ' + conversionSelector);
1870        abstConvert(experimentId);
1871      }
1872    }, true);
1873  } catch (error) {
1874    //console.error("Error accessing cross-origin iframe:", error); // CORS issue, the iframe is not in the same origin and does not allow cross origin access.
1875  }
1876  });
1877}
1878async function setAbFingerprint() {
1879  if(!localStorage.getItem("ab-uuid")){
1880    try {
1881      const module = await import(window.bt_pluginurl + "/js/ab-fingerprint.js");
1882      const fp = await ThumbmarkJS.getFingerprint();
1883      localStorage.setItem("ab-uuid", fp);
1884      console.log("set Fingerprint: " + fp);
1885    } catch (error) {
1886      console.error("Error setting fingerprint:", error);
1887    }
1888  }
1889  else{
1890    console.log("ab-uuid: already set: " + localStorage.getItem("ab-uuid"));
1891  }
1892}
1893async function setAbCrypto() {
1894  if(!localStorage.getItem("ab-advanced-id")){
1895      let fp;
1896    try {
1897      fp = crypto.randomUUID();
1898      console.log("Set advanced id: " + fp);
1899    } catch (e) { // localhost, http
1900      fp = "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c => (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16));
1901      console.log("Set advanced ID: " + fp);
1902    }
1903    localStorage.setItem("ab-advanced-id", fp);
1904    //set cookie same
1905    document.cookie = "ab-advanced-id=" + fp + "; SameSite=None; Secure;";
1906  }
1907  else{
1908    console.log("ab-advanced-id: already set: " + localStorage.getItem("ab-advanced-id"));
1909  }
1910}
1911
1912/**
1913 * Revoke approval for tracking (for testing or when user revokes consent)
1914 */
1915function abst_revoke_approval() {
1916  setAbstApprovalStatus(false);
1917  console.log('Approval revoked, events will be queued until approval is given again');
1918  return true;
1919}
1920
1921function showMagicTest(eid, index) { // called from magic bar so we arent logging anything
1922  var magic_definition = JSON.parse(bt_experiments[eid].magic_definition);
1923  //foreach 
1924  jQuery.each(magic_definition, function(i, swapElement) {
1925      if(swapElement.type == 'text')
1926          jQuery(swapElement.selector).text(swapElement.variations[index]);
1927      if(swapElement.type == 'image')
1928          jQuery(swapElement.selector).attr('src', swapElement.variations[index]).attr('srcset', swapElement.variations[index]);
1929  }); 
1930  //scroll into view first element
1931  jQuery('html, body').animate({
1932    //scroll element to 1/3 down view
1933    scrollTop: jQuery(magic_definition[0].selector).offset().top - window.innerHeight / 3
1934  }, 1000);
1935  // flash a box around the swAPPED ELEMENT FOR 2 SECONDS
1936  jQuery(magic_definition[0].selector).addClass('ab-highlight');
1937  setTimeout(function(){
1938    jQuery(magic_definition[0].selector).removeClass('ab-highlight');
1939  }, 3000);
1940}
1941
1942
1943
1944
1945// Initialize global visibility tracking system
1946if (!window.btab_vars.visibility_tracker) {
1947  window.btab_vars.visibility_tracker = {
1948    elements: {},
1949    active: false,
1950    interval: null
1951  };
1952  
1953  // Master visibility checking function
1954  window.btab_vars.checkVisibility = function() {
1955    const tracker = window.btab_vars.visibility_tracker;
1956    let hasActiveElements = false;
1957    
1958    Object.entries(tracker.elements).forEach(([key, data]) => {
1959      const elements = jQuery(data.selector);
1960      
1961      if (elements.length && elements.is(":visible")) {
1962        // Check if any element and all its parents are truly visible
1963        let trulyVisible = false;
1964        
1965        elements.each(function() {
1966          let el = jQuery(this);
1967          let allParentsVisible = true;
1968          
1969          // Check this element and all parents
1970          while (el.length) {
1971            try {
1972              // Check if hidden or transparent
1973              if (el.is(":hidden")) {
1974                allParentsVisible = false;
1975                break;
1976              }
1977              
1978              // Safely get CSS properties with null checks
1979              try {
1980                const visibility = el.css("visibility");
1981                const opacity = el.css("opacity");
1982                
1983                if (visibility === "hidden" || (opacity !== undefined && parseFloat(opacity) === 0)) {
1984                  allParentsVisible = false;
1985                  break;
1986                }
1987              } catch (cssError) {
1988                // If we can't get CSS properties, assume element is visible
1989              }
1990              
1991              // Move up to parent
1992              el = el.parent();
1993            } catch (error) {
1994              //console.log('Error checking visibility:', error);
1995              break;
1996            }
1997          }
1998          
1999          if (allParentsVisible) {
2000            trulyVisible = true;
2001            return false; // Break the each loop
2002          }
2003        });
2004        
2005        if (trulyVisible) {
2006          //console.log('absplit: visible viewed event fired:', data.eid, data.variation);
2007          bt_experiment_w(data.eid, data.variation, 'visit', false);
2008          delete tracker.elements[key]; // Remove from tracking
2009        } else {
2010          hasActiveElements = true;
2011        }
2012      } else {
2013        hasActiveElements = true;
2014      }
2015    });
2016    
2017    // Stop interval if nothing left to track
2018    if (!hasActiveElements) {
2019      clearInterval(tracker.interval);
2020      tracker.active = false;
2021    }
2022  };
2023}
2024// Add this at the top of the file or before any usage
2025function ensureVisibilityTrackerInitialized() {
2026    if (!window.btab_vars.visibility_tracker) {
2027        window.btab_vars.visibility_tracker = {
2028            elements: {},
2029            active: false,
2030            interval: null
2031        };
2032    }
2033}
2034
2035// Use this function in watch_for_tag_event and anywhere else you initialize the tracker
2036function watch_for_tag_event(eid, selector = 'experiment', variation = null) {
2037    if (variation == null) return; // no variation, no event
2038
2039    // If it's an experiment, construct the selector
2040    if (selector == 'experiment') // get the testID, on page element tests
2041        selector = `[bt-eid="${eid}"]` + (variation ? `[bt-variation="${variation}"]` : '');
2042
2043    // Ensure tracker is initialized only once
2044    ensureVisibilityTrackerInitialized();
2045
2046    const tracker = window.btab_vars.visibility_tracker;
2047    const key = `${eid}_${selector}`;
2048
2049    // Add to tracking list
2050    tracker.elements[key] = { eid, selector, variation };
2051
2052    // Start interval if not already running
2053    if (!tracker.active) {
2054        tracker.interval = setInterval(window.btab_vars.checkVisibility, 500);
2055        tracker.active = true;
2056    }
2057
2058    // For backward compatibility
2059    if (!window.btab_vars.waiting_for_view) {
2060        window.btab_vars.waiting_for_view = {};
2061    }
2062    window.btab_vars.waiting_for_view[selector] = tracker.interval;
2063}
2064function normalizeUrl(url) {
2065  page_url = url.replace(window.location.origin,'');
2066  if (page_url.charAt(0) == "/") page_url = page_url.substr(1);
2067  if (page_url.charAt(page_url.length - 1) == "/") page_url = page_url.substr(0, page_url.length - 1);
2068
2069  return page_url;
2070}

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.