PageSourceSearch

https://mikemichalowicz.com/wp-content/themes/mike/js/g3-video-player.js?ver=1790902711

js mikemichalowicz.com collected 2026-10-02 02:26:38 UTC 10,789 bytes, 430 lines download raw bytes

1document.addEventListener('DOMContentLoaded', function () {
2	let $videoPlayer = null;
3	let $youtubePlayer = null;
4	let playerType = null; // 'vimeo' or 'youtube'
5	const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
6
7	const $videoOverlay = document.querySelector('.g3-video-overlay');
8	const $poster = document.querySelector('.g3-video-overlay__poster');
9	const $endcard = document.querySelector('.g3-video-overlay__endcard');
10	const $endcardPrimary = document.querySelector('.g3-video-overlay__endcard-primary');
11	const $endcardSecondary = document.querySelector('.g3-video-overlay__endcard-secondary');
12	const $endScreenLayers = [$poster, $endcard].filter(Boolean);
13
14	// Load YouTube IFrame API
15	const youtubeApiLoaded = new Promise((resolve) => {
16		if (typeof YT !== 'undefined' && YT.Player) {
17			resolve();
18			return;
19		}
20
21		const tag = document.createElement('script');
22		tag.src = 'https://www.youtube.com/iframe_api';
23		const firstScriptTag = document.getElementsByTagName('script')[0];
24		firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
25
26		window.onYouTubeIframeAPIReady = function () {
27			resolve();
28		};
29	});
30
31	function getVideoType(trigger) {
32		if (trigger.dataset.g3VimeoId) return 'vimeo';
33		if (trigger.dataset.g3YoutubeId) return 'youtube';
34		return null;
35	}
36
37	function getVideoId(trigger) {
38		const type = getVideoType(trigger);
39		if (type === 'vimeo') return trigger.dataset.g3VimeoId;
40		if (type === 'youtube') return trigger.dataset.g3YoutubeId;
41		return null;
42	}
43
44	function getVimeoHash(trigger) {
45		return trigger.dataset.g3VimeoHash || '';
46	}
47
48	function getVimeoUrl(videoId, hash) {
49		if (hash) {
50			return 'https://vimeo.com/' + encodeURIComponent(videoId) + '/' + encodeURIComponent(hash);
51		}
52		return 'https://vimeo.com/' + encodeURIComponent(videoId);
53	}
54
55	function hasEndcard(trigger) {
56		return trigger.classList.contains('g3-video-trigger--endcard');
57	}
58
59	function hideEndScreen() {
60		gsap.killTweensOf($endScreenLayers);
61
62		if ($poster) {
63			gsap.set($poster, { autoAlpha: 0 });
64			$poster.style.backgroundImage = '';
65			$poster.setAttribute('aria-hidden', 'true');
66		}
67
68		if ($endcard) {
69			gsap.set($endcard, { autoAlpha: 0 });
70			$endcard.setAttribute('aria-hidden', 'true');
71		}
72	}
73
74	function getPosterUrl(trigger) {
75		return trigger.dataset.g3PosterUrl || '';
76	}
77
78	function fetchPosterUrl(videoId, type, hash) {
79		if (type === 'youtube') {
80			// YouTube thumbnail API with fallback from maxresdefault to hqdefault
81			const maxresUrl = `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
82			const fallbackUrl = `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`;
83
84			return new Promise((resolve) => {
85				const img = new Image();
86				img.onload = () => resolve(maxresUrl);
87				img.onerror = () => resolve(fallbackUrl);
88				img.src = maxresUrl;
89			});
90		}
91
92		// Vimeo oEmbed API
93		const vimeoUrl = getVimeoUrl(videoId, hash);
94		return fetch('https://vimeo.com/api/oembed.json?url=' + encodeURIComponent(vimeoUrl))
95			.then((response) => response.json())
96			.then((data) => {
97				if (!data.thumbnail_url) {
98					return '';
99				}
100				return data.thumbnail_url.replace(/_\d+x\d+/, '_1280x720');
101			})
102			.catch(() => '');
103	}
104
105	function storePosterUrl(trigger, posterUrl) {
106		if (!posterUrl) {
107			return;
108		}
109		trigger.dataset.g3PosterUrl = posterUrl;
110		if (trigger.classList.contains('g3-video-trigger--poster')) {
111			trigger.style.backgroundImage = 'url("' + posterUrl + '")';
112		}
113	}
114
115	function populateEndcard(trigger) {
116		if (!$endcard || !trigger) {
117			return;
118		}
119
120		if ($endcardPrimary) {
121			$endcardPrimary.href = trigger.dataset.g3EndcardPrimaryHref || '#';
122			$endcardPrimary.textContent = trigger.dataset.g3EndcardPrimaryLabel || 'Watch Full-length Speech';
123		}
124
125		if ($endcardSecondary) {
126			$endcardSecondary.href = trigger.dataset.g3EndcardSecondaryHref || '/video-gallery/';
127			$endcardSecondary.textContent = trigger.dataset.g3EndcardSecondaryLabel || 'Keynote Gallery';
128		}
129	}
130
131	function revealEndScreen(trigger) {
132		if (!trigger) {
133			return;
134		}
135
136		populateEndcard(trigger);
137
138		const reveal = (posterUrl) => {
139			if ($poster && posterUrl) {
140				$poster.style.backgroundImage = 'url("' + posterUrl + '")';
141				$poster.setAttribute('aria-hidden', 'false');
142			}
143
144			if ($endcard) {
145				$endcard.setAttribute('aria-hidden', 'false');
146			}
147
148			gsap.killTweensOf($endScreenLayers);
149
150			if (prefersReducedMotion) {
151				gsap.set($endScreenLayers, { autoAlpha: 1 });
152				return;
153			}
154
155			gsap.set($endScreenLayers, { autoAlpha: 0 });
156
157			gsap.timeline().to($poster, { autoAlpha: 1, duration: 0.7, ease: 'power2.out' }).to($endcard, { autoAlpha: 1, duration: 0.7, ease: 'power2.out' }, '-=0.45');
158		};
159
160		const posterUrl = getPosterUrl(trigger);
161		if (posterUrl) {
162			reveal(posterUrl);
163			return;
164		}
165
166		const videoId = getVideoId(trigger);
167		const videoType = getVideoType(trigger);
168		const vimeoHash = getVimeoHash(trigger);
169		if (!videoId) {
170			reveal('');
171			return;
172		}
173
174		fetchPosterUrl(videoId, videoType, vimeoHash).then((url) => {
175			storePosterUrl(trigger, url);
176			reveal(url);
177		});
178	}
179
180	function resetVideoMount() {
181		const stage = document.querySelector('.g3-video-overlay__stage');
182		const mount = document.getElementById('g3-video');
183
184		if (!stage || !mount) {
185			return;
186		}
187
188		const freshMount = document.createElement('div');
189		freshMount.id = 'g3-video';
190		stage.replaceChild(freshMount, mount);
191	}
192
193	function destroyPlayers() {
194		if ($videoPlayer) {
195			$videoPlayer.destroy().catch(() => {});
196			$videoPlayer = null;
197		}
198
199		if ($youtubePlayer) {
200			try {
201				$youtubePlayer.destroy();
202			} catch (error) {
203				// Player may already be destroyed if the mount was replaced.
204			}
205			$youtubePlayer = null;
206		}
207
208		resetVideoMount();
209		playerType = null;
210	}
211
212	function closeVideoPlayer() {
213		hideEndScreen();
214		destroyPlayers();
215
216		gsap.to('.g3-video-overlay', {
217			duration: 0.5,
218			autoAlpha: 0,
219		});
220	}
221
222	function openVideo(trigger) {
223		const videoId = getVideoId(trigger);
224		const videoType = getVideoType(trigger);
225		const vimeoHash = getVimeoHash(trigger);
226
227		if (!videoId) {
228			return;
229		}
230
231		hideEndScreen();
232		destroyPlayers();
233		playerType = videoType;
234
235		if (videoType === 'vimeo') {
236			if (typeof Vimeo === 'undefined') {
237				return;
238			}
239
240			const vimeoOptions = {
241				responsive: true,
242				background: false,
243				controls: true,
244				pip: false,
245				vimeo_logo: false,
246			};
247
248			if (vimeoHash) {
249				vimeoOptions.url = getVimeoUrl(videoId, vimeoHash);
250			} else {
251				vimeoOptions.id = videoId;
252			}
253
254			$videoPlayer = new Vimeo.Player('g3-video', vimeoOptions);
255
256			$videoPlayer.on('ended', () => {
257				$videoPlayer.pause().catch(() => {});
258
259				if (hasEndcard(trigger)) {
260					$videoPlayer.setCurrentTime(0).catch(() => {});
261					revealEndScreen(trigger);
262					return;
263				}
264
265				gsap.to('.g3-video-overlay', {
266					duration: 0.5,
267					autoAlpha: 0,
268				});
269			});
270
271			$videoPlayer.on('play', () => {
272				if (hasEndcard(trigger)) {
273					hideEndScreen();
274				}
275			});
276
277			gsap.to('.g3-video-overlay', {
278				duration: 0.5,
279				autoAlpha: 1,
280				onComplete: function () {
281					$videoPlayer.play().catch(() => {});
282				},
283			});
284		} else if (videoType === 'youtube') {
285			youtubeApiLoaded.then(() => {
286				const playYoutubeVideo = () => {
287					if ($youtubePlayer && typeof $youtubePlayer.playVideo === 'function') {
288						$youtubePlayer.playVideo();
289					}
290				};
291
292				$youtubePlayer = new YT.Player('g3-video', {
293					videoId: videoId,
294					playerVars: {
295						autoplay: 1,
296						controls: 1,
297						rel: 0,
298						modestbranding: 1,
299					},
300					events: {
301						onReady: function () {
302							playYoutubeVideo();
303						},
304						onStateChange: function (event) {
305							if (event.data === YT.PlayerState.ENDED) {
306								if (hasEndcard(trigger)) {
307									event.target.seekTo(0);
308									event.target.stopVideo();
309									revealEndScreen(trigger);
310									return;
311								}
312
313								gsap.to('.g3-video-overlay', {
314									duration: 0.5,
315									autoAlpha: 0,
316								});
317							} else if (event.data === YT.PlayerState.PLAYING) {
318								if (hasEndcard(trigger)) {
319									hideEndScreen();
320								}
321							}
322						},
323					},
324				});
325
326				gsap.to('.g3-video-overlay', {
327					duration: 0.5,
328					autoAlpha: 1,
329					onComplete: playYoutubeVideo,
330				});
331			});
332		}
333	}
334
335	hideEndScreen();
336
337	document.querySelectorAll('.g3-video-trigger').forEach((trigger) => {
338		trigger.addEventListener('click', () => {
339			openVideo(trigger);
340		});
341	});
342
343	document.querySelectorAll('.g3-video-trigger--poster').forEach((trigger) => {
344		const videoId = getVideoId(trigger);
345		const videoType = getVideoType(trigger);
346		const vimeoHash = getVimeoHash(trigger);
347		if (!videoId) {
348			return;
349		}
350
351		fetchPosterUrl(videoId, videoType, vimeoHash).then((posterUrl) => {
352			storePosterUrl(trigger, posterUrl);
353		});
354	});
355
356	const $closeButton = document.querySelector('.g3-video__close');
357	if ($closeButton) {
358		$closeButton.addEventListener('click', (e) => {
359			e.preventDefault();
360			e.stopPropagation();
361			closeVideoPlayer();
362		});
363	}
364
365	if ($videoOverlay) {
366		$videoOverlay.addEventListener('click', (e) => {
367			if (e.target === $videoOverlay) {
368				closeVideoPlayer();
369			}
370		});
371	}
372
373	// Inline in-place Vimeo embeds: <div data-g3-vimeo-inline="VIDEO_ID"></div>
374	if (typeof Vimeo !== 'undefined') {
375		const inlinePlayers = [];
376		document.querySelectorAll('[data-g3-vimeo-inline]').forEach((el) => {
377			const vimeoId = el.getAttribute('data-g3-vimeo-inline');
378			if (!vimeoId || !/^\d+$/.test(vimeoId)) {
379				return;
380			}
381			const vimeoHash = getVimeoHash(el);
382			const vimeoOptions = {
383				responsive: false,
384				controls: true,
385				byline: false,
386				title: false,
387				portrait: false,
388				pip: false,
389				vimeo_logo: false,
390			};
391
392			if (vimeoHash) {
393				vimeoOptions.url = getVimeoUrl(vimeoId, vimeoHash);
394			} else {
395				vimeoOptions.id = vimeoId;
396			}
397
398			const player = new Vimeo.Player(el, vimeoOptions);
399			inlinePlayers.push(player);
400			player.on('play', () => {
401				inlinePlayers.forEach((other) => {
402					if (other !== player) {
403						other.pause().catch(() => {});
404					}
405				});
406			});
407		});
408	}
409
410	// Book Mike hero: attach to the existing background iframe (do not create a new player)
411	if (!prefersReducedMotion && typeof Vimeo !== 'undefined' && 'IntersectionObserver' in window) {
412		document.querySelectorAll('.book-mike-hero__player iframe').forEach((iframe) => {
413			const player = new Vimeo.Player(iframe);
414			const hero = iframe.closest('.book-mike-hero') || iframe;
415
416			new IntersectionObserver(
417				(entries) => {
418					entries.forEach((entry) => {
419						if (entry.isIntersecting) {
420							player.play().catch(() => {});
421						} else {
422							player.pause().catch(() => {});
423						}
424					});
425				},
426				{ threshold: 0.1 },
427			).observe(hero);
428		});
429	}
430});

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.