PageSourceSearch

https://www.hpsmechanical.com/wp-content/plugins/full-picture-anal…/public/common/fupi-helpers.js?ver=10.1.2.1

js hpsmechanical.com collected 2026-09-30 22:00:42 UTC 43,474 bytes, 1,117 lines download raw bytes

1function fupi_top_helpers(){
2
3    'use strict';
4
5    var d = document;
6
7    // VARS
8
9    FP.v = {};
10    fp.track_queue = [];
11    fpdata.activity = { total : 0, last : 0, current : 0 };
12    fpdata.refreshed = false;
13    
14    if ( document.referrer == document.location.origin + document.location.pathname ) {
15        fpdata.refreshed = true;
16    } else {
17        if ( performance.navigation ? performance.navigation.type === performance.navigation.TYPE_RELOAD : performance.getEntriesByType('navigation')[0].type === 'reload' ) fpdata.refreshed = true; // deprecated method first, the new one later
18    }
19
20    // HELPERS
21
22    function getFormattedDateTime(){
23        const d = new Date();
24        
25        const year = d.getFullYear();
26        const month = d.getMonth() + 1;
27        const day = d.getDate();
28        const hours = d.getHours() * 100;
29        const minutes = d.getMinutes();
30        
31        return {
32            year: year,
33            month: month,
34            day: day,
35            time: hours + minutes
36        };
37    }
38
39    fpdata.date = getFormattedDateTime();
40
41    function check_if_can_save_wpfp_cookies( first_check = false ) {
42
43        // save previous state
44        let prev_check = !! fp.vars.can_save_wpfp_cookies; // "!!" changes undefined into false
45        
46        // get current state
47        fp.vars.can_save_wpfp_cookies = ! fp.notice.enabled || fp.notice.mode == 'notify' || fp.notice.mode == 'hide' || ( fp.notice.mode == 'optout' && ! fpdata.cookies ) || ( fp.notice.mode == 'optin' && fpdata.cookies && fpdata.cookies.stats );
48
49        // check if the state has changed
50        fp.vars.consents_changed = first_check ? false : prev_check != fp.vars.can_save_wpfp_cookies;        
51    }
52
53    function shouldCustomScriptLoadHere( geo ){
54
55        let geo_method = geo[0],
56            geo_countries = geo[1];
57
58        if ( fp.main.geo && geo_method && geo_countries ) { // we check fp.geo to prevent situations when user disabled geo but didn't save new tools settings
59        
60            if ( fp.ready ) {
61                return geo_method == 'incl' ? geo_countries.includes( fpdata.country ) : ! geo_countries.includes( fpdata.country );
62            }
63            return false;
64        }
65
66        return true;
67    }
68
69    function shouldScriptLoadHere( id ){
70
71        if ( fp.main.geo && fp[id].limit_country ) { // we check fp.main.geo to prevent situations when user disabled geo but didn't save new tools settings
72            
73            if ( fp.ready ) {
74
75                if ( fp[id].limit_country.method == 'excl' ) {
76                    return ! fp[id].limit_country.countries.includes( fpdata.country );
77                } else {
78                    return fp[id].limit_country.countries.includes( fpdata.country );
79                }
80            }
81
82            return false;
83        }
84
85        return true;
86    };
87
88    function get_session_data_from_cookies(){
89
90        let session_cookie = FP.readCookie('fp_current_session');
91        if ( session_cookie ) fpdata.current_session = JSON.parse(session_cookie);
92
93        
94    }
95
96    //
97    // FP - start
98    //
99
100    FP.setCookie = (name, value, days = 0, mins = 0, path = "/") => {
101		var expires = "";
102		if ( days > 0 || mins > 0 ) {
103			var date = new Date();
104			if (days > 0) {
105				date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
106			} else {
107				date.setTime(date.getTime() + mins * 60 * 1000);
108			}
109			expires = "; expires=" + date.toGMTString();
110		};
111		d.cookie = name + "=" + value + expires + "; path=" + path + "; sameSite=strict";
112	};
113
114    FP.deleteCookie = name => { FP.setCookie(name, "", -1); };
115
116    FP.getUrlParamByName = ( name, url = false ) => {
117		if ( ! url ) {
118            url = window.location.search;
119        } else {
120            url = url.split('?')[1];
121			if ( ! url ) return null;
122        };
123		
124		const queryString = url,
125			urlParams = new URLSearchParams(queryString);
126		return urlParams.get(name);
127	};
128
129    FP.nl2Arr = nl => nl ? [].slice.call(nl) : false;
130
131    FP.findID = (e, c) => {
132        if ( c === null ) return null;
133        if ( !e ) return false;
134        c = c || document;
135        return c.getElementById(e);
136    };
137
138    FP.findFirst = (e, c) => {
139        if ( c === null ) return null;
140        if ( !e ) return false;
141        c = c || document;
142        return c.querySelector(e);
143    };
144
145    FP.findAll = (e, c) => {
146		if ( c === null ) return [];
147		if ( ! e ) return false;
148		c = c || document;
149		return FP.nl2Arr(c.querySelectorAll(e));
150	};
151
152    FP.getScript = ( url, cb, attrs, tag = 'script', fallback = false ) => {
153
154		attrs = attrs || false;
155		var new_tag = "";
156
157		if ( tag == "img" ) {
158			new_tag = d.createElement('img');
159			new_tag.src = url;
160		} else if ( tag == "link" ) {
161			new_tag = d.createElement('link');
162			new_tag.href = url;
163		} else {
164			new_tag = d.createElement('script')
165			new_tag.src = url;
166			new_tag.type = 'application/javascript';
167		}
168
169        if ( cb ) new_tag.onload = cb;
170
171		if ( fallback ) new_tag.onerror = ()=>{ FP.getScript( fallback, cb, attrs, tag ); };
172
173		if ( attrs ) {
174            // check if attrs holds a reference to and HTML tag
175            if ( attrs instanceof Element ) {
176                let old_tag = attrs;
177                // copy from the original tag (with exceptions)
178                Array.from( old_tag.attributes ).forEach( attr => {
179                    if ( attr.name !== 'type' && attr.name !== 'data-src' && ! attr.name.includes('fupi') ) {
180                        new_tag.setAttribute( attr.name, attr.value );
181                    }
182                });
183            // or add provided values
184            } else {
185                for (var key in attrs) {
186                    if ( key !== "/" ) new_tag.setAttribute(key, attrs[key]);
187                }
188            }
189		}
190
191		d.getElementsByTagName("head")[0].appendChild(new_tag);
192	};
193
194    FP.createTag = FP.getScript;
195
196    function loadInlineScript(temp_script) {
197		
198		let scr_id = temp_script.dataset?.fupi_id,
199            orig_id = scr_id.replace('fp_',''),
200            is_cscr = temp_script.dataset?.module == 'cscr';
201        
202        if ( ! scr_id ) return;
203
204		let new_script = document.createElement('script');
205
206		new_script.innerHTML = temp_script.innerHTML;
207		temp_script.parentNode.replaceChild(new_script, temp_script);
208
209		if ( fp.main.debug ) {
210            if ( is_cscr && fp.cscr[orig_id] ) {
211                console.log( "[FP] Custom script loaded: " + fp.cscr[orig_id] );
212            } else {
213                console.log( "[FP] Inline script loaded");
214            }
215        };
216
217		fp.loaded.push( orig_id );
218	};
219
220    FP.sendEvt = (evt_name, details_a) => {
221        var details = details_a ? { 'detail' : details_a } : {},
222            fp_event = new CustomEvent( evt_name, details );
223        document.dispatchEvent(fp_event);
224    };
225
226    FP.getRandomStr = ()=>{
227		return ( Math.random() + 1 ).toString(36).substring(2);
228	};
229
230    FP.updateConsents = () => {
231
232		if ( fp.vars.use_other_cmp ) return;
233		
234		if ( ! fp.main.track_current_user ) {
235
236			if ( fp.main.debug ) console.log("[FP] Current user cannot be tracked");
237
238		// if the user made a choice in the past
239		} else if ( fpdata.cookies ){
240
241			set_consents_in_fpdata( fpdata?.cookies?.stats, fpdata?.cookies?.personalisation, fpdata?.cookies?.marketing );
242
243		// if no choice was made in the past
244		} else {
245			
246			// deny all if consent banner is in optin mode
247			if ( fp.notice.enabled && fp.notice.mode == "optin" ) {
248				
249				set_consents_in_fpdata();
250			
251			// agree to all if consent banner is disabled or we are in optout or notification mode
252			} else {
253				set_consents_in_fpdata(true, true, true);
254			}
255		}
256
257		if ( ! fp.main.is_customizer ) {
258
259			// set MS Ads consent
260			
261			if ( fp.main.track_current_user && fpdata.cookies ){
262				if ( fpdata.cookies.stats && fpdata.cookies.marketing ) {
263					window.uetq.push( "consent", "update", {
264						"ad_storage": "granted"
265					});
266				}
267			} else {
268				if ( ! ( fp.notice.enabled && fp.notice.mode == "optin" ) ) {
269					window.uetq.push( "consent", "update", {
270						"ad_storage": "granted"
271					});
272				}
273			}
274			
275			// Set GTAG consents
276
277			["gtag", "fupi_gtm_gtag"].forEach( tag_name => {
278
279				if ( tag_name == "fupi_gtm_gtag" && ! window.fupi_gtm_gtag ) return;
280
281				// update if the user made a choice in the past
282				if ( fp.main.track_current_user && fpdata.cookies ){
283
284					FP.set_gtag_consents( tag_name, "update", fpdata?.cookies?.stats, fpdata?.cookies?.personalisation, fpdata?.cookies?.marketing );
285					if ( fp.main.debug ) console.log("[FP] Google consents set to user choices");
286				
287				// if no choice was made in the past
288				} else {
289					
290					// agree to all if consent banner is disabled or is in optout or notification mode
291					if ( ! ( fp.notice.enabled && fp.notice.mode == "optin" ) ) {
292
293						FP.set_gtag_consents( tag_name, "update", true, true, true );
294						if ( fp.main.debug ) console.log("[FP] All Google consents granted");
295
296					};
297				}
298			} );
299		}
300	}
301
302	FP.postToServer = ( event_data_a, cb = false ) => {
303
304		if ( fpdata.is_robot ) return;
305		if ( fp.main.debug ) console.log( "[FP] Posting to server", event_data_a );
306
307		let fetch_url = fp.main.server_method == "rest" ? "/index.php?rest_route=/fupi/v1/sender" : "/wp-admin/admin-ajax.php?action=fupi_ajax";
308
309		if ( fp.main.debug || event_data_a[0][0] == 'cdb') {
310		
311			fetch( fetch_url, {
312				method: "POST",
313				body: JSON.stringify( event_data_a ),
314				credentials: 'same-origin',
315				headers: {
316					"Content-type": "application/json; charset=UTF-8",
317					// "X-WP-Nonce": fp_nonce
318				}
319			})
320			.then((response) => response.json())
321			.then((json) => {
322				if ( cb ) { 
323					cb(json);
324				} else {
325					console.log( "[FP] Server response", json);
326				}
327			});
328
329		} else {
330
331			fetch( fetch_url, {
332				method: "POST",
333				credentials: 'same-origin',
334				body: JSON.stringify( event_data_a ),
335				headers: {
336					"Content-type": "application/json; charset=UTF-8",
337					// "X-WP-Nonce": fp_nonce
338				}
339			});
340		}
341	};
342
343    // fires function every X ms with an option to fire them ADDITIONALLY on start ("leading") and after events ("trailing")
344    FP.throttle = function (func, wait, options) {
345        var context, args, result;
346        var timeout = null;
347        var previous = 0;
348        if (!options) options = {};
349
350        var later = function later() {
351            previous = options.leading === false ? 0 : Date.now();
352            timeout = null;
353            result = func.apply(context, args);
354            if (!timeout) context = args = null;
355        };
356
357        return function () {
358            var now = Date.now();
359            if (!previous && options.leading === false) previous = now;
360            var remaining = wait - (now - previous);
361            context = this;
362            args = arguments;
363
364            if (remaining <= 0 || remaining > wait) {
365                if (timeout) {
366                    clearTimeout(timeout);
367                    timeout = null;
368                }
369
370                previous = now;
371                result = func.apply(context, args);
372                if (!timeout) context = args = null;
373            } else if (!timeout && options.trailing !== false) {
374                timeout = setTimeout(later, remaining);
375            }
376
377            return result;
378        };
379    };
380
381    FP.remove = el => { if (el) el.parentNode.removeChild(el); };
382
383    FP.addAction = function( action_name, fn ){
384
385        let arr = Array.isArray(action_name) ? action_name : [action_name]; // make sure that trigger is in an array
386
387        arr.forEach(function ( name , i) { 
388            if ( ! fp.actions[name] ) fp.actions[name] = [];
389            fp.actions[name].push( fn );
390        });
391    }
392
393    FP.doActions = ( action_name, args_o = null, cb ) => {
394        if ( fp.actions[action_name] ) fp.actions[action_name].forEach( fn => {
395            let return_val = fn( args_o );
396            if ( typeof return_val !== 'undefined' ) args_o = return_val;
397        } );
398        if ( cb ) cb( args_o );
399    }
400
401    FP.hasActions = action_name => {
402        return fp.actions[action_name] && fp.actions[action_name].length > 0;
403    };
404
405    FP.isScriptAllowedToLoad = function( id, force_load, permissions_a, geo_a ){
406    
407        // STOP if we don't have all the basic data yet
408        if ( ! fp.ready ) return false;
409
410        // STOP if the script has already loaded
411        if ( fp.loaded.includes(id) ) return false;
412
413        // START if the load is forced
414        if ( force_load ) return true;
415
416        // STOP if the tab is not in focus AND we are NOT on WooCommerce checkout page
417        if ( fpdata.page_type !== 'Woo Order Received' ) {
418            if ( document.hidden ) return false;
419        }
420
421        // STOP tracking an excluded visitor (MUST be after checking FORCE LOAD)
422        if ( ! fp.main.track_current_user ) return false;
423
424        // STOP if the script is set NOT to load in the current location
425        if ( geo_a && ! shouldCustomScriptLoadHere( geo_a ) ) return false;
426
427        // STOP if any permissions need to be given in this location...
428        if ( fp.notice.enabled && fp.notice.mode == 'optin' && ! fpdata.cookies && permissions_a.length > 0 ) return false;
429
430        // STOP if required permissions are not given
431        if ( fpdata.cookies && permissions_a.length > 0 ) {
432            if ( ! permissions_a.every( permission => fpdata.cookies[permission] ) ) return false;
433        }
434
435        return true;
436    };
437
438    FP.isAllowedToLoad = function( module_id, permissions_a, required_a, nr, tool_does_not_use_cookies = false ) {
439
440        var id = nr ? module_id + nr : module_id;
441
442        // STOP if we don't have all the basic data yet
443        if ( ! fp.ready ) return false;
444
445        // STOP if we have no settings for this tool
446        if ( ! fp[id] ) return false;
447
448        // STOP if the script has already loaded
449        if ( fp.loaded.includes(id) ) return false;
450
451        // STOP if the required data is missing
452        if ( required_a.some( req => ! fp[id][req] ) ) return false;
453
454        // START if the load is forced
455        if ( fp[id].force_load ) return true;
456
457        // STOP if the tab is not in focus AND we are NOT on WooCommerce checkout page
458        if ( fpdata.page_type !== 'Woo Order Received' ) {
459            if ( document.hidden ) return false;
460        }
461
462        // STOP tracking an excluded visitor (MUST be after checking FORCE LOAD)
463        if ( ! fp.main.track_current_user ) return false;
464
465        // STOP if the script is set NOT to load in the current location
466        if ( ! shouldScriptLoadHere( id ) ) return false;
467
468        // START 
469        // if the tool doesn't use cookies
470        if ( tool_does_not_use_cookies ) return true;
471
472        // STOP if we wait for consent but some permissions need to be given
473        if ( fp.notice.enabled && fp.notice.mode == 'optin' && ! fpdata.cookies && permissions_a.length > 0) return false;
474
475        // STOP if user chose their cookie preferences but some required permissions are missing
476        if ( fp.notice.enabled && fpdata.cookies && permissions_a.length > 0 ) {
477            if ( ! permissions_a.every( permission => fpdata.cookies[permission] ) ) return false;
478        }
479
480        return true;
481    };
482
483    FP.startActivityTimer = function () {
484
485        fpdata.activity.last = 0;
486
487        if (!FP.v.activityTimerRunning) {
488            FP.v.activityTimerRunning = true;
489            FP.v.activityTimer = setInterval(function () {
490
491                fpdata.activity.total++;
492                fpdata.activity.current++;
493                fpdata.activity.last++;
494
495                FP.doActions( 'active_time_tick' );
496
497                if ( fpdata.activity.last >= 15 ) FP.stopActivityTimer(); // stop timer after 15 secs from last activity
498            }, 1000);
499        }
500    };
501
502    FP.stopActivityTimer = function () {
503        if (FP.v.activityTimerRunning) {
504            FP.v.activityTimerRunning = false;
505            clearInterval(FP.v.activityTimer);
506        }
507    };
508
509    FP.encode = function (string) {
510        return window.btoa(string).split('=').join('')
511    }
512
513    FP.detectAddedElement = callback => {
514
515		const observer = new MutationObserver( mutations => {
516		  	mutations.forEach( mutation => {
517				if ( mutation.type === 'childList' ) {
518			  		callback( mutation.addedNodes );
519				}
520		  	} );
521		} );
522	  
523		observer.observe( document.body, {
524			childList: true,
525			subtree: true
526		} );
527	}
528
529    FP.initCondTracking = ( events_settings, cb ) => {
530		
531		if ( ! ( events_settings && fp.atrig ) ) return;
532		if ( ! fp.atrig.actions ) fp.atrig.actions = {};
533
534		events_settings.forEach( e => {
535			
536			if ( ! fp.atrig.actions[e.atrig_id] ) fp.atrig.actions[e.atrig_id] = [];
537			
538			fp.atrig.actions[e.atrig_id].push( {
539				'func' : cb.bind( this, e ),
540				'repeat' : e.repeat
541			} );
542		});
543	}
544
545    FP.updateSessionData = () => {
546        
547        check_if_can_save_wpfp_cookies();
548
549        if ( fp.vars.consents_changed ) {
550            
551            // if we can no longer use session cookies
552            if ( ! fp.vars.can_save_wpfp_cookies ) {
553                
554                // delete everything
555                FP.deleteCookie( 'fp_current_session' );
556                delete fpdata.current_session;
557                
558            
559            // if we can now use session cookies
560            } else {
561
562                // get latest sesion data (in case user agreed to cookies on a different tab)
563                get_session_data_from_cookies();
564
565                // set new data if it wasn't set before (when a user has just chosen sth in the banner)
566                ! fpdata.current_session ? create_new_single_session_data() : update_single_session_data();
567                
568            };
569
570        } else {
571            // get latest sesion data (in case user visited a different tab before opening this one)
572            get_session_data_from_cookies();
573        }
574    }
575    
576    FP.isClickTarget = function( selector ){
577		return ( ! fpdata.clicked.link && ! fpdata.clicked.middle && fpdata.clicked.element.matches( selector ) ) || ( fpdata.clicked.link && fpdata.clicked.link.element.matches( selector ) );
578	}
579
580    
581
582    //
583    //
584    // FP - END
585    //
586    //
587
588    // BROKEN LINK TRACKING + REDIRECT TO A CUSTOM 404 PAGE
589	if ( fp.track.track404 && fpdata.page_type == "404" && ! FP.getUrlParamByName("broken_link_location") ){
590		const location = fp.track.redirect404_url ? new URL( fp.track.redirect404_url ) : window.location;
591		window.location = location + ( location.search ? "&" : "?" ) + "broken_link_location=" + ( document.referrer || "direct_traffic_or_unknown" ) + "&broken_link=" + window.location;
592	}
593	
594    // Random number (sometimes gets handy)
595	fp.random = FP.getRandomStr(7);
596
597    // CHECK FOR BOT TRAFFIC
598	// -- modified version of https://stackoverflow.com/a/65563155/7702522
599
600	fpdata.is_robot = (() => {
601		
602		// SMALL list
603		if ( fp.main.bot_list == "basic" ) {
604			
605			const robots = new RegExp([/bot/,/spider/,/crawl/,/APIs-Google/,/AdsBot/,/Googlebot/,/mediapartners/,/Google Favicon/,/FeedFetcher/,/Google-Read-Aloud/,/googleweblight/,/bingbot/,/yandex/,/baidu/,/duckduck/,/Yahoo Link Preview/,/ia_archiver/,/facebookexternalhit/,/pinterest\.combot/,/redditbot/,/slackbot/,/Twitterbot/,/WhatsApp/,/S[eE][mM]rushBot/].map((r) => r.source).join("|"),"i");
606
607			return robots.test(navigator.userAgent);
608
609		// BIG list
610		} else if ( fp.main.bot_list == "big" ) {
611
612			const robots = new RegExp([
613				/Googlebot/, /AdsBot/, /Feedfetcher-Google/, /Mediapartners-Google/, /Mediapartners/, /APIs-Google/, 
614				/Google-InspectionTool/, /Storebot-Google/, /GoogleOther/, /bingbot/, /Slurp/, /wget/, /LinkedInBot/, 
615				/Python-urllib/, /python-requests/, /aiohttp/, /httpx/, /libwww-perl/, /httpunit/, /Nutch/, 
616				/Go-http-client/, /phpcrawl/, /msnbot/, /jyxobot/, /FAST-WebCrawler/, /FAST Enterprise Crawler/, 
617				/BIGLOTRON/, /Teoma/, /convera/, /seekbot/, /Gigabot/, /Gigablast/, /exabot/, /ia_archiver/, 
618				/GingerCrawler/, /webmon/, /HTTrack/, /grub\.org/, /UsineNouvelleCrawler/, /antibot/, 
619				/netresearchserver/, /speedy/, /fluffy/, /findlink/, /msrbot/, /panscient/, /yacybot/, /AISearchBot/, 
620				/ips-agent/, /tagoobot/, /MJ12bot/, /woriobot/, /yanga/, /buzzbot/, /mlbot/, /yandex\.combots/, 
621				/purebot/, /Linguee Bot/, /CyberPatrol/, /voilabot/, /Baiduspider/, /citeseerxbot/, /spbot/, 
622				/twengabot/, /postrank/, /Turnitin/, /scribdbot/, /page2rss/, /sitebot/, /linkdex/, /Adidxbot/, 
623				/ezooms/, /dotbot/, /Mail\.RU_Bot/, /discobot/, /heritrix/, /findthatfile/, /europarchive\.org/, 
624				/NerdByNature\.Bot/, /sistrix crawler/, /Ahrefs/, /fuelbot/, /CrunchBot/, /IndeedBot/, 
625				/mappydata/, /woobot/, /ZoominfoBot/, /PrivacyAwareBot/, /Multiviewbot/, /SWIMGBot/, /Grobbot/, 
626				/eright/, /Apercite/, /semanticbot/, /Aboundex/, /domaincrawler/, /wbsearchbot/, /summify/, /CCBot/, 
627				/edisterbot/, /SeznamBot/, /ec2linkfinder/, /gslfbot/, /aiHitBot/, /intelium_bot/, 
628				/facebookexternalhit/, /Yeti/, /RetrevoPageAnalyzer/, /lb-spider/, /Sogou/, /lssbot/, /careerbot/, 
629				/wotbox/, /wocbot/, /ichiro/, /DuckDuckBot/, /lssrocketcrawler/, /drupact/, /webcompanycrawler/, 
630				/acoonbot/, /openindexspider/, /gnam gnam spider/, /web-archive-net\.com\.bot/, /backlinkcrawler/, 
631				/coccoc/, /integromedb/, /content crawler spider/, /toplistbot/, /it2media-domain-crawler/, 
632				/ip-web-crawler\.com/, /siteexplorer\.info/, /elisabot/, /proximic/, /changedetection/, /arabot/, 
633				/WeSEE:Search/, /niki-bot/, /CrystalSemanticsBot/, /rogerbot/, /360Spider/, /psbot/, 
634				/InterfaxScanBot/, /CC Metadata Scaper/, /g00g1e\.net/, /GrapeshotCrawler/, /urlappendbot/, 
635				/brainobot/, /fr-crawler/, /binlar/, /SimpleCrawler/, /Twitterbot/, /cXensebot/, /smtbot/, 
636				/bnf\.fr_bot/, /A6-Indexer/, /ADmantX/, /Facebot/, /OrangeBot/, /memorybot/, /AdvBot/, 
637				/MegaIndex/, /SemanticScholarBot/, /ltx71/, /nerdybot/, /xovibot/, /BUbiNG/, /Qwantify/, 
638				/archive\.org_bot/, /Applebot/, /TweetmemeBot/, /crawler4j/, /findxbot/, /SemrushBot/, 
639				/yoozBot/, /lipperhey/, /Y!J/, /Domain Re-Animator Bot/, /AddThis/, /Screaming Frog SEO Spider/, 
640				/MetaURI/, /Scrapy/, /Livelapbot/, /OpenHoseBot/, /CapsuleChecker/, /collection@infegy\.com/, 
641				/IstellaBot/, /DeuSu/, /betaBot/, /Cliqzbot/, /MojeekBot/, /netEstate NE Crawler/, 
642				/SafeSearch microdata crawler/, /Gluten Free Crawler/, /Sonic/, /Sysomos/, /Trove/, /deadlinkchecker/, 
643				/Slack-ImgProxy/, /Embedly/, /RankActiveLinkBot/, /iskanie/, /SafeDNSBot/, /SkypeUriPreview/, 
644				/Veoozbot/, /Slackbot/, /redditbot/, /datagnionbot/, /Google-Adwords-Instant/, /adbeat_bot/, 
645				/WhatsApp/, /contxbot/, /pinterest\.combot/, /electricmonk/, /GarlikCrawler/, /BingPreview/, 
646				/vebidoobot/, /FemtosearchBot/, /Yahoo Link Preview/, /MetaJobBot/, /DomainStatsBot/, /mindUpBot/, 
647				/Daum/, /Jugendschutzprogramm-Crawler/, /Xenu Link Sleuth/, /Pcore-HTTP/, /moatbot/, /KosmioBot/, 
648				/pingdom/, /AppInsights/, /PhantomJS/, /Gowikibot/, /PiplBot/, /Discordbot/, /TelegramBot/, 
649				/Jetslide/, /newsharecounts/, /James BOT/, /Barkrowler/, /TinEye/, /SocialRankIOBot/, 
650				/trendictionbot/, /Ocarinabot/, /epicbot/, /Primalbot/, /DuckDuckGo-Favicons-Bot/, /GnowitNewsbot/, 
651				/Leikibot/, /LinkArchiver/, /YaK/, /PaperLiBot/, /Digg Deeper/, /dcrawl/, /Snacktory/, /AndersPinkBot/, 
652				/Fyrebot/, /EveryoneSocialBot/, /Mediatoolkitbot/, /Luminator-robots/, /ExtLinksBot/, /SurveyBot/, 
653				/NING/, /okhttp/, /Nuzzel/, /omgili/, /PocketParser/, /YisouSpider/, /um-LN/, /ToutiaoSpider/, 
654				/MuckRack/, /Jamie's Spider/, /AHC/, /NetcraftSurveyAgent/, /Laserlikebot/, /^Apache-HttpClient/, 
655				/AppEngine-Google/, /Jetty/, /Upflow/, /Thinklab/, /Traackr\.com/, /Twurly/, /Mastodon/, /http_get/, 
656				/DnyzBot/, /botify/, /007ac9 Crawler/, /BehloolBot/, /BrandVerity/, /check_http/, /BDCbot/, 
657				/ZumBot/, /EZID/, /ICC-Crawler/, /ArchiveBot/, /^LCC /, /filterdb\.iss\.netcrawler/, /BLP_bbot/, 
658				/BomboraBot/, /Buck/, /Companybook-Crawler/, /Genieo/, /magpie-crawler/, /MeltwaterNews/,
659				/Moreover/,/newspaper/,/ScoutJet/,/sentry/,/StorygizeBot/,/UptimeRobot/,/OutclicksBot/,
660				/seoscanners/,/Hatena/,/Google Web Preview/,/MauiBot/,/AlphaBot/,/SBL-BOT/,/IAS crawler/,
661				/adscanner/,/Netvibes/,/acapbot/,/Baidu-YunGuanCe/,/bitlybot/,/blogmuraBot/,/Bot\.AraTurka\.com/,
662				/bot-pge\.chlooe\.com/,/BoxcarBot/,/BTWebClient/,/ContextAd Bot/,/Digincore bot/,/Disqus/,/Feedly/,
663				/Fetch/,/Fever/,/Flamingo_SearchEngine/,/FlipboardProxy/,/g2reader-bot/,/G2 Web Services/,/imrbot/,
664				/K7MLWCBot/,/Kemvibot/,/Landau-Media-Spider/,/linkapediabot/,/vkShare/,/Siteimprove\.com/,/BLEXBot/,
665				/DareBoost/,/ZuperlistBot/,/Miniflux/,/Feedspot/,/Diffbot/,/SEOkicks/,/tracemyfile/,/Nimbostratus-Bot/,
666				/zgrab/,/PR-CY\.RU/,/AdsTxtCrawler/,/Datafeedwatch/,/Zabbix/,/TangibleeBot/,/google-xrawler/,/axios/,
667				/Amazon CloudFront/,/Pulsepoint/,/CloudFlare-AlwaysOnline/,/Google-Structured-Data-Testing-Tool/,
668				/WordupInfoSearch/,/WebDataStats/,/HttpUrlConnection/,/Seekport Crawler/,/ZoomBot/,/VelenPublicWebCrawler/,
669				/MoodleBot/,/jpg-newsbot/,/outbrain/,/W3C_Validator/,/Validator\.nu/,/W3C-checklink/,/W3C-mobileOK/,
670				/W3C_I18n-Checker/,/FeedValidator/,/W3C_CSS_Validator/,/W3C_Unicorn/,/Google-PhysicalWeb/,/Blackboard/,
671				/ICBot/,/BazQux/,/Twingly/,/Rivva/,/Experibot/,/awesomecrawler/,/Dataprovider\.com/,/GroupHigh/,
672				/theoldreader\.com/,/AnyEvent/,/Uptimebot\.org/,/Nmap Scripting Engine/,/2ip\.ru/,/Clickagy/,
673				/Caliperbot/,/MBCrawler/,/online-webceo-bot/,/B2B Bot/,/AddSearchBot/,/Google Favicon/,/HubSpot/,
674				/Chrome-Lighthouse/,/HeadlessChrome/,/CheckMarkNetwork/,/www\.uptime\.com/,/Streamline3Bot/,/serpstatbot/,
675				/MixnodeCache/,/^curl/,/SimpleScraper/,/RSSingBot/,/Jooblebot/,/fedoraplanet/,/Friendica/,/NextCloud/,
676				/Tiny Tiny RSS/,/RegionStuttgartBot/,/Bytespider/,/Datanyze/,/Google-Site-Verification/,/TrendsmapResolver/,
677				/tweetedtimes/,/NTENTbot/,/Gwene/,/SimplePie/,/SearchAtlas/,/Superfeedr/,/feedbot/,/UT-Dorkbot/,/Amazonbot/,
678				/SerendeputyBot/,/Eyeotabot/,/officestorebot/,/Neticle Crawler/,/SurdotlyBot/,/LinkisBot/,/AwarioSmartBot/,
679				/AwarioRssBot/,/RyteBot/,/FreeWebMonitoring SiteChecker/,/AspiegelBot/,/NAVER Blog Rssbot/,/zenback bot/,
680				/SentiBot/,/Domains Project/,/Pandalytics/,/VKRobot/,/bidswitchbot/,/tigerbot/,/NIXStatsbot/,/Atom Feed Robot/,
681				/curebot/,/PagePeeker/,/Vigil/,/rssbot/,/startmebot/,/JobboerseBot/,/seewithkids/,/NINJA bot/,/Cutbot/,
682				/BublupBot/,/BrandONbot/,/RidderBot/,/Taboolabot/,/Dubbotbot/,/FindITAnswersbot/,/infoobot/,/Refindbot/,
683				/BlogTraffic\d\.\d+ Feed-Fetcher/,/SeobilityBot/,/Cincraw/,/Dragonbot/,/VoluumDSP-content-bot/,/FreshRSS/,
684				/BitBot/,/^PHP-Curl-Class/,/Google-Certificates-Bridge/,/centurybot/,/Viber/,/e\.ventures Investment Crawler/,
685				/evc-batch/,/PetalBot/,/virustotal/,/(^| )PTST/,/minicrawler/,/Cookiebot/,/trovitBot/,/seostar\.co/,/IonCrawl/,
686				/Uptime-Kuma/,/SeekportBot/,/FreshpingBot/,/Feedbin/,/CriteoBot/,/Snap URL Preview Service/,/Better Uptime Bot/,
687				/RuxitSynthetic/,/Google-Read-Aloud/,/ValveSteam/,/OdklBot/,/GPTBot/,/ChatGPT-User/,/YandexRenderResourcesBot/,
688				/LightspeedSystemsCrawler/,/ev-crawler/,/BitSightBot/,/woorankreview/,/Google-Safety/,/AwarioB
688ot/,/DataForSeoBot/,
689				/Linespider/,/WellKnownBot/,/A Patent Crawler/,/StractBot/,/search\.marginalia\.nu/,/YouBot/,/Nicecrawler/,/Neevabot/,
690				/BrightEdge Crawler/,/SiteCheckerBotCrawler/,/TombaPublicWebCrawler/,/CrawlyProjectCrawler/,/KomodiaBot/,/KStandBot/,
691				/CISPA Webcrawler/,/MTRobot/,/hyscore\.io/,/AlexandriaOrgBot/,/2ip bot/,/Yellowbrandprotectionbot/,/SEOlizer/,
692				/vuhuvBot/,/INETDEX-BOT/,/Synapse/,/t3versionsBot/,/deepnoc/,/Cocolyzebot/,/hypestat/,/ReverseEngineeringBot/,
693				/sempi\.tech/,/Iframely/,/MetaInspector/,/node-fetch/,/lkxscan/,/python-opengraph/,/OpenGraphCheck/,
694				/developers\.google\.com\+websnippet/,/SenutoBot/,/MaCoCu/,/NewsBlur/,/inoreader/,/NetSystemsResearch/,/PageThing/,
695				/WordPress/,/PhxBot/,/ImagesiftBot/,/Expanse/,/InternetMeasurement/,/^BW/,/GeedoBot/,/Audisto Crawler/,
696				/PerplexityBot/,/claudebot/,/Monsidobot/,/GroupMeBot/].map((r) => r.source).join("|"),"i");
697
698				return robots.test(navigator.userAgent);
699
700		} else {
701			return false;
702		};
703	})();
704
705    //
706    // CONSENTS
707    //
708
709	function set_consents_in_fpdata( stats = false, pers = false, market = false ){
710		fpdata.consents = {
711			'can_track_stats' : stats,
712			'can_track_pers' : pers,
713			'can_track_market' : market,
714		};
715	}
716
717    // GET VARS
718
719	let magic_keyw = FP.getUrlParamByName( fp.main.magic_keyword ),
720		ga4_debug = FP.getUrlParamByName("ga4_debug");
721
722	// UPDATE COOKIE DATA - fupi_cookies and fpdata.cookies
723
724	if ( magic_keyw && magic_keyw == "off" ){
725
726		fp.main.track_current_user = false;
727		
728		fpdata.cookies = { "stats" : false, "personalisation" : false, "marketing" : false, "disabled" : true };
729		FP.setCookie( "fp_cookie", JSON.stringify(fpdata.cookies), 7300 );
730
731	} else if ( ga4_debug && typeof fpdata.cookies == "object" ){
732			
733		fpdata.cookies.ga4_debug = ga4_debug == "on" ? "on" : "off";
734		FP.setCookie( "fp_ga4_debug", JSON.stringify(fpdata.cookies), 7300 );
735
736	} else if ( FP.getUrlParamByName("reset_cookies") || ( magic_keyw && ( magic_keyw == "reset" || magic_keyw == "on" ) ) ){
737
738		FP.deleteCookie("fp_cookie");
739		fpdata.cookies = false;
740
741	// check if should ask for consent again
742	} else if ( fpdata.cookies && ! fpdata.cookies.disabled && fp.notice && ! fp.notice.dont_ask_again) {
743
744        let pp_pub_changed = fp.notice.priv_policy_update && ( ! fpdata.cookies.pp_pub || fpdata.cookies.pp_pub != fp.notice.priv_policy_update ),
745            tools_changed = fp.tools && ( ! fpdata.cookies.tools || ! fp.tools.every( id => fpdata.cookies.tools.includes(id) ) );
746
747        if ( pp_pub_changed || tools_changed ) {
748            FP.deleteCookie("fp_cookie");
749            fpdata.cookies = false;
750        } else {
751            if ( fpdata.cookies.disabled ) fp.main.track_current_user = false;
752        }
753	}
754
755    // CONSENT BANNER - apply page blur and lock scroll
756
757	if ( fp.main.track_current_user && ! fp.main.is_customizer && fp.notice.enabled && ! fpdata.cookies && fp.notice.display_notice ) {
758		
759		// BLUR BACKGROUND
760		if ( fp.notice.blur_page ) document.getElementsByTagName( "html" )[0].classList.add("fupi_blur");
761		
762		// LOCK PAGESCROLL
763		if ( fp.notice.scroll_lock ) document.getElementsByTagName( "html" )[0].classList.add("fupi_scroll_lock");
764	}
765
766    let uses_geo = false;
767
768    
769
770    if ( ! uses_geo ) {
771		fpdata.country = 'unknown';
772		FP.updateConsents();
773		fp.ready = true;
774	}
775
776    //
777    // CONSENTS - END
778    //
779
780    // Check if WPFP can use cookies
781
782    check_if_can_save_wpfp_cookies( true );
783
784    // Get session data from cookies
785
786    get_session_data_from_cookies();
787
788    
789
790    function create_new_single_session_data( updated_from_free = false ){
791        
792        fpdata.current_session = {};
793
794        
795
796        FP.setCookie( 'fp_current_session', JSON.stringify(fpdata.current_session), 0, 30 ); // 30 mins
797    }
798
799    function update_single_session_data(){
800        if ( ! fpdata.current_session ) return;
801        
802        FP.setCookie( 'fp_current_session', JSON.stringify(fpdata.current_session), 0, 30 ); // 30 mins
803    }
804
805    
806
807    // Set or update session data when the page loads
808    function set_session_data(){
809        
810        if ( fp.vars.can_save_wpfp_cookies ) {
811            
812            fpdata.new_session = ! fpdata.current_session;
813            
814            ! fpdata.current_session ? create_new_single_session_data() : update_single_session_data();
815            
816
817        } else {
818            fpdata.new_session = ! fpdata.refreshed && ! window.sessionStorage['fp_old_session'] && ( window.history.length == 1 || ( window.history.length == 2 && ! document.referrer ) );
819        }
820
821        fpdata.new_tab = ! fpdata.new_session && ! window.sessionStorage['fp_old_session'];
822        window.sessionStorage['fp_old_session'] = true; // removed on a new tab
823    };
824
825    set_session_data();
826
827    // CREATE, UPDATE OR REMOVE SESSION DATA
828    // triggered after users change cookie preferences or when a page gains focus
829    
830    // the difference between this Fn and isAnchorLinkToCurrentPage is that this one doesn't check if the link actually works
831    function isLinkToCurrentPage (link) {
832        let target_url_obj = new URL( link.href );
833        return target_url_obj.origin + target_url_obj.pathname == location.origin + location.pathname;
834    }
835	
836	// checks if the link is an anchor to an EXISTING element on the same page
837	function isAnchorLinkToCurrentPage (link){
838        
839        let url = new URL( link.href ),
840            el = false;
841        
842        try {
843            el = FP.findFirst( url.hash );
844        } catch (e) {
845            el = false;
846        }
847        
848        return !! el;
849    };
850
851	function isOutboundLink(el){
852        return el.href.indexOf(location.host) == -1 && el.href.indexOf('mailto:') != 0 && el.href.indexOf('tel:') != 0 && el.href.indexOf('http') == 0;
853    };
854
855	function trackClicks(e, is_middle_click){
856
857		let redirect_stopped = false;
858
859		if ( e.target.classList.contains('fupi_click_stopped') ) return;
860		
861		// PREVENT MULTI CLICKS
862	    // Do not run this function if a visitor clicks the element multiple times in a short time-span
863	    var now = Date.now();
864	    if ( ! ( ! FP.v.lastClick || now - FP.v.lastClick > fp.track.dblclck_time ) ) return;
865	    FP.v.lastClick = now;
866		
867		// PREVENT REDIRECTS
868
869		// prevent default if a visitor clicked a link that is to be opened on the current tab
870		var link_el = e.target.closest('a[href]');
871
872		if ( link_el && ! is_middle_click && link_el.target != '_blank' && fp.track.link_click_delay  ){
873			e.preventDefault(); // prevents redirects
874			e.stopPropagation(); // prevents other event listeners from firing
875			e.target.classList.add('fupi_click_stopped');
876			e.target.style.pointerEvents = 'none';
877			redirect_stopped = true;
878			if ( fp.main.debug ) console.log('[FP] link-click stopped');
879		};
880
881		// GET CLICK DATA
882
883		fpdata.clicked = {
884			'element' : e.target,
885			'middle' : is_middle_click,
886		};
887
888		if ( link_el && link_el.href.length > 1 ) {
889
890			fpdata.clicked.link 				= {};
891			fpdata.clicked.link.element			= link_el;
892			fpdata.clicked.link.href			= link_el.href;
893			fpdata.clicked.link.is_to_current_page = isLinkToCurrentPage(link_el);
894			fpdata.clicked.link.target_blank 	= link_el.target == '_blank';
895
896			fpdata.clicked.link.is_outbound 	= isOutboundLink(link_el);
897			fpdata.clicked.link.is_anchor 		= isAnchorLinkToCurrentPage(link_el);
898
899            if ( link_el.href.includes('mailto:') ){
900                fpdata.clicked.link.is_email = true;
901                fpdata.clicked.link.safe_email = link_el.href.replace('mailto:', '').split('@')[0];
902            }
903
904            if ( link_el.href.includes('tel:') ){
905                fpdata.clicked.link.is_tel = true;
906                fpdata.clicked.link.safe_tel = link_el.href.replace('tel:','').slice(-5);
907            }
908		}
909
910		// RUN QUEUED FNs
911
912		FP.doActions( 
913			'click', 
914			false, // no slug
915			() => { // cb after all functions are run
916				if ( fpdata.clicked.element.classList.contains('fupi_click_stopped') && typeof fpdata.clicked.element.click == 'function' ) {
917					fpdata.clicked.element.click();
918					fpdata.clicked.element.classList.remove('fupi_click_stopped');
919					e.target.style.pointerEvents = '';
920					if ( fp.main.debug ) console.log('[FP] Initiated click event after all FP functions');
921				}
922			}
923		);
924    };
925
926    // Add blocked scripts to HTML
927    function addScriptsToDOM() {
928
929        // find scripts which are not loaded
930        let plain_txt_scripts = FP.findAll('script[data-fupi_class*="fupi_blocked_script"]');
931
932        plain_txt_scripts.forEach( plain_el => {
933            
934            let dta = plain_el.dataset,
935                tag_type = dta.fupi_type,
936                scr_id = dta.fupi_id,
937                url = dta.fupi_url ?? false,
938                force_load = dta.fupi_force == '1',
939                permissions_a = dta.fupi_permiss ? dta.fupi_permiss.split(' ') : [],
940                geo_a = dta.fupi_geo ? dta.fupi_geo.split(',') : [];
941            
942            if ( FP.isScriptAllowedToLoad( scr_id, force_load, permissions_a, geo_a ) ) {
943                if ( tag_type == 'inline' ){
944                    loadInlineScript( plain_el ); // replaces old tag
945                } else {
946                    if ( url ){
947                        FP.createTag(
948                            url, 
949                            ()=>{ FP.loaded( scr_id, scr_id ) }, 
950                            plain_el,
951                            tag_type
952                        );
953                        FP.remove(plain_el);
954                    } else {
955                        if (fp.main.debug) console.error('[FP] Couldn\'t load script with ID ' + scr_id +  '. Missing src or href values.')
956                    }
957                }
958            }
959                
960        });
961	}
962
963    // FIRE TAGS WHEN THE DOCUMENT GETS AND LOSES FOCUS
964
965    function doPageShowActions(){
966
967        if ( ! fp.vars.page_show_tracked ){
968
969            fp.vars.page_show_tracked = true;
970            fp.vars.page_hide_tracked = false;
971
972            fpdata.doc_in_focus = true;
973            
974            FP.startActivityTimer();
975            
976            var cookies = FP.readCookie('fp_cookie');
977
978            if ( cookies ) {
979                if ( fpdata.cookies && cookies == JSON.stringify( fpdata.cookies ) ) return;
980                cookies = JSON.parse(cookies);
981                fpdata.cookies = cookies;
982                FP.updateConsents();
983            } else {
984                fpdata.cookies = false;
985            }
986            
987            FP.updateSessionData();
988
989            FP.sendEvt( 'fp_load_scripts' );
990
991            if ( fp.actions.page_in_focus ) FP.doActions('page_in_focus');
992        }
993    }
994
995    function doPageHideActions(){
996        if ( ! fp.vars.page_hide_tracked ) {
997            fp.vars.page_hide_tracked = true;
998            fp.vars.page_show_tracked = false;
999
1000            fpdata.doc_in_focus = false;
1001            FP.stopActivityTimer();
1002            if ( fp.actions.page_in_blur ) FP.doActions('page_in_blur');
1003            fpdata.activity.current = 0;
1004        }
1005    }
1006
1007    // ON DYNAMIC URL CHANGES
1008    // URL is set every second with setInterval() and not "active_time_tick", to make the check even when the page is not in focus
1009
1010	let currentUrl = location.href;
1011	  
1012	setInterval( () => { 
1013
1014        fpdata.date = getFormattedDateTime();
1015
1016		if ( location.href !== currentUrl ) {
1017			currentUrl = location.href;
1018			FP.doActions( 'url_change' );
1019		}
1020	}, 1000);
1021
1022    // pagehide/show fires on some events but is cross-browser
1023    // !! it fires BEFORE visibilitychanges
1024    // !! when the pageshow event fires the document obj doesn't have updated cookies or localStarage yet, thus we don't use it for "show"
1025    window.addEventListener( "pagehide", e => { doPageHideActions() }, false );
1026    // window.addEventListener( "pageshow", e => { doPageShowActions('pageshow') }, false ); visibilitychange is universal
1027
1028    // beforeunload doesn't work with prev/next page buttons and has problems on mobiles
1029    window.addEventListener( "beforeunload", e => { doPageHideActions() }, false );
1030
1031    // visibilitychanges fires on all events but has problems on mobiles and in safari
1032    // !! fires AFTER pagehide/show
1033    // !! when the visibilitychange event fires for "show" the document obj already has updated cookies and localStarage
1034    document.addEventListener( 'visibilitychange', function () {
1035        document.hidden ? doPageHideActions() :  doPageShowActions('visibilitychange');
1036        //if ( fp.page_visibility ) FP.doActions( 'page_visibility' );
1037    }, false);
1038
1039    document.documentElement.addEventListener('mouseleave', (e) => {
1040        // Don't fire if the page is losing focus (not a real mouse leave)
1041        if (document.hidden) return;
1042        
1043        // Check if mouse actually left viewport boundaries
1044        if (e.clientY <= 0 || e.clientX <= 0 || 
1045            e.clientX >= window.innerWidth || e.clientY >= window.innerHeight) {
1046            FP.doActions('mouse_leave_window');
1047        }
1048    }, false );
1049
1050
1051    // load custom/extra scripts to DOM
1052    document.addEventListener( 'fp_load_scripts', addScriptsToDOM );
1053
1054    function formSubmitEvents( {target} ){
1055        fpdata.submitted_form = { 'element' : target };
1056		FP.doActions( 'form_submit' );
1057    }
1058
1059    // ON FIELD BLUR
1060    document.addEventListener('blur', function(e) {
1061        // Check if the blurred element is a form field
1062        if ( e.target.nodeType === Node.ELEMENT_NODE && e.target.matches('input, textarea, select') ) {
1063            fpdata.last_form_field = e.target;
1064            FP.doActions( 'form_field_blur' );
1065        }
1066    }, true);
1067
1068    // ON DOM LOADED
1069
1070    function do_when_DOM_loaded(){
1071        
1072        addScriptsToDOM();
1073
1074        // init actions waiting for DOM loaded
1075        setTimeout ( ()=>{ FP.doActions( ['dom_loaded'] ) }, 250 ); // makes sure that footer JS files loaded and hooked to dom_loaded event
1076
1077        // start listening to form submits
1078        setTimeout( ()=>{ document.addEventListener('submit', formSubmitEvents ) }, fp.track.formsubm_trackdelay ? fp.track.formsubm_trackdelay * 1000 : 1000 );
1079        
1080        // start listening to DOM modifications
1081		if ( fp.track.use_mutation_observer ) FP.detectAddedElement( addedNodes => {
1082            let els = FP.nl2Arr( addedNodes );
1083            if ( els.length > 0 ) FP.doActions( ['dom_modified'], els );
1084        } );
1085    }
1086
1087    if ( document.readyState === "complete" ) {
1088        do_when_DOM_loaded();
1089    } else {
1090        document.addEventListener('DOMContentLoaded', do_when_DOM_loaded );
1091    }
1092
1093    // ON MOUSE MOVE
1094    document.addEventListener( 'mousemove', FP.throttle( FP.startActivityTimer, 500 ) ); // This starts the user's activity timer
1095
1096    if ( ! document.hidden) {
1097        FP.startActivityTimer();
1098        fpdata.doc_in_focus = true;
1099        fp.vars.page_show_tracked = true;
1100        fp.vars.page_hide_tracked = false;
1101    };
1102
1103    // ON LEFT CLICK
1104	window.addEventListener( 'click', function (e) {
1105		if ( fp.actions.click ) trackClicks(e, false);
1106	}, true);
1107
1108	// ON MIDDLE CLICK
1109	window.addEventListener( 'mouseup', function(e) {
1110		if ( fp.actions.click && e.button == 1) trackClicks(e, true);
1111	});
1112
1113    FP.loaded('head_helpers');
1114
1115};
1116
1117FP.load('head_helpers', 'fupi_top_helpers', ['head_js'] );

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.