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.