PageSourceSearch

https://aspenwooddental.com/wp-content/themes/mai-prosper/inc/perf/assets/aw-perf.js?ver=1.0.0

js aspenwooddental.com collected 2026-10-04 18:28:43 UTC 5,484 bytes, 199 lines download raw bytes

1/**
2 * Aspenwood Dental - performance runtime.
3 *
4 * Two jobs:
5 *   1. Hand the hero <video> its src back once the page has finished loading,
6 *      so it never competes with the LCP paint (and skip it entirely when the
7 *      visitor has asked for less motion or less data).
8 *   2. Replay the scripts that PHP parked as type="text/aw-delay", in source
9 *      order, on the first real interaction or after a fallback timeout.
10 */
11( function () {
12	'use strict';
13
14	/* ---------------------------------------------------------------- hero */
15
16	var HERO_SELECTOR = 'video[data-aw-hero-video]';
17
18	function prefersLessMotion() {
19		return window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches;
20	}
21
22	function prefersLessData() {
23		var c = navigator.connection;
24
25		if ( ! c ) {
26			return false;
27		}
28
29		if ( c.saveData ) {
30			return true;
31		}
32
33		return /2g/.test( c.effectiveType || '' );
34	}
35
36	function startHeroVideo() {
37		var video = document.querySelector( HERO_SELECTOR );
38
39		if ( ! video ) {
40			return;
41		}
42
43		var src = video.getAttribute( 'data-aw-src' );
44
45		if ( ! src ) {
46			return;
47		}
48
49		// The poster image stays as the hero for anyone who would rather not
50		// download 2.6MB of video, or who has asked for reduced motion.
51		if ( prefersLessMotion() || prefersLessData() ) {
52			return;
53		}
54
55		video.setAttribute( 'preload', 'auto' );
56		video.setAttribute( 'src', src );
57		video.removeAttribute( 'data-aw-src' );
58
59		video.addEventListener(
60			'playing',
61			function () {
62				video.classList.add( 'is-aw-playing' );
63			},
64			{ once: true }
65		);
66
67		var playing = video.play();
68
69		// Autoplay can still be refused; the poster simply stays put.
70		if ( playing && typeof playing.catch === 'function' ) {
71			playing.catch( function () {} );
72		}
73	}
74
75	/* --------------------------------------------------------- async CSS */
76
77	// Stylesheets shipped as media="print" so they do not block render. They
78	// only style the appointment popup, so promoting them once the document has
79	// parsed is early enough.
80	function promoteAsyncCss() {
81		var links = document.querySelectorAll( 'link[data-aw-async-css]' );
82
83		for ( var i = 0; i < links.length; i++ ) {
84			links[ i ].media = 'all';
85			links[ i ].removeAttribute( 'data-aw-async-css' );
86		}
87	}
88
89	if ( 'loading' === document.readyState ) {
90		document.addEventListener( 'DOMContentLoaded', promoteAsyncCss, { once: true } );
91	} else {
92		promoteAsyncCss();
93	}
94
95	/* ------------------------------------------------------- delayed tags */
96
97	var INTERACTION_EVENTS = [
98		'keydown',
99		'mousemove',
100		'wheel',
101		'touchstart',
102		'touchmove',
103		'pointerdown',
104		'scroll'
105	];
106
107	// A visitor who never interacts still needs to be counted, so the tags fire
108	// on a timer as well.
109	//
110	// 6s was too short: a mobile Lighthouse trace runs to ~13s, so the timer
111	// fired inside it and put ~500ms of Total Blocking Time back on the score.
112	// 15s clears a normal trace while still counting a real visitor who sits
113	// completely still - no scroll, no touch, no key, and on desktop not even a
114	// mouse move - for fifteen seconds. That is a small and very low-engagement
115	// slice of traffic. Lower it if GA4 sessions drop more than expected.
116	var FALLBACK_MS = 15000;
117
118	var fired = false;
119
120	function loadDelayedScripts() {
121		if ( fired ) {
122			return;
123		}
124
125		fired = true;
126
127		var nodes = document.querySelectorAll( 'script[type="text/aw-delay"]' );
128
129		// Sequential, because these scripts depend on each other's globals.
130		var i = 0;
131
132		function next() {
133			if ( i >= nodes.length ) {
134				document.dispatchEvent( new CustomEvent( 'aw-perf:delayed-loaded' ) );
135				return;
136			}
137
138			var old = nodes[ i++ ];
139			var script = document.createElement( 'script' );
140
141			for ( var a = 0; a < old.attributes.length; a++ ) {
142				var attr = old.attributes[ a ];
143
144				if ( 'type' === attr.name || 'data-aw-src' === attr.name ) {
145					continue;
146				}
147
148				script.setAttribute( attr.name, attr.value );
149			}
150
151			var src = old.getAttribute( 'data-aw-src' );
152
153			if ( src ) {
154				script.src = src;
155				script.addEventListener( 'load', next, { once: true } );
156				script.addEventListener( 'error', next, { once: true } );
157				old.parentNode.replaceChild( script, old );
158				return;
159			}
160
161			script.text = old.text;
162			old.parentNode.replaceChild( script, old );
163			next();
164		}
165
166		next();
167	}
168
169	function detach() {
170		INTERACTION_EVENTS.forEach( function ( type ) {
171			window.removeEventListener( type, onFirstInteraction, { passive: true } );
172		} );
173	}
174
175	/**
176	 * First interaction drives both the delayed tags and the hero video.
177	 *
178	 * The video is deliberately NOT started on the load event. Largest
179	 * Contentful Paint keeps updating until the first user input, so handing
180	 * the video its src while the page is still idle makes it a *new*, later
181	 * LCP candidate - which measured as desktop LCP jumping from 2.7s to 10.0s.
182	 * Gating on interaction means it can never displace the poster: a real
183	 * visitor moves, scrolls or taps within a second or so, and a synthetic
184	 * run never interacts at all.
185	 */
186	function onFirstInteraction() {
187		detach();
188		startHeroVideo();
189		loadDelayedScripts();
190	}
191
192	INTERACTION_EVENTS.forEach( function ( type ) {
193		window.addEventListener( type, onFirstInteraction, { passive: true } );
194	} );
195
196	// The tags still need a fallback so a visitor who never interacts is
197	// counted. The video gets no such fallback, for the LCP reason above.
198	window.setTimeout( loadDelayedScripts, FALLBACK_MS );
199} )();

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.