1 2$(function() { 3 4 function isMobile() { 5 return /Mobi|Android/i.test(navigator.userAgent); 6 } 7 8 $('.scroll').on('click', function(e) { 9 e.preventDefault(); 10 11 var navbar = $('.fixed-top').height() || 0; 12 var subnav = $('.sticky-top').height() || 0; 13 var offset = navbar + subnav + 30; 14 var target = this.hash; 15 16 if ($(this).data('offset') !== undefined) { 17 offset = $(this).data('offset'); 18 } 19 20 if ($(target).length) { 21 $('html, body').stop().animate({ 22 'scrollTop': $(target).offset().top - offset 23 }, 300, 'swing'); 24 } 25 }); 26 27 $(window).on('load', function() { 28 if (window.location.hash) { 29 var target = window.location.hash; 30 var $link = $('.scroll[href="' + target + '"]').first(); 31 32 if ($link.length) { 33 if (isMobile()) { 34 // â Mobile: Verzögerung einbauen 35 setTimeout(function() { 36 $link.trigger('click'); 37 }, 600); 38 } else { 39 // â Desktop: sofort klicken 40 $link.trigger('click'); 41 } 42 } 43 } 44 }); 45 46}); 47 48 49 50$(document).ready(function(){ 51 52 //$('#popup').fancybox().trigger('click'); 53 $('#popup').fancybox({ 54 transitionIn: 'fade', 55 transitionOut: 'fade', 56 speedIn: 200, 57 speedOut: 200, 58 overlayColor: '#000', 59 overlayOpacity: 0.7, 60 padding: 30, 61 margin: 30, 62 width: 600, 63 helpers: { 64 overlay: { 65 closeClick: false // Klick aufs Overlay schlieÃt NICHT 66 } 67 }, 68 afterShow: function() { 69 // Links klickbar machen - stopPropagation verhindert fancybox close 70 $('.fancybox-inner a').off('click touchstart touchend').on('click touchstart touchend', function(e) { 71 e.stopPropagation(); 72 // Link normal funktionieren lassen (href wird automatisch ausgeführt) 73 }); 74 } 75 }).trigger('click'); 76 77 $(window).scroll(function(){ 78 var section = $('section:first'); 79 var sheight = section.height(); 80 var scroll = $(window).scrollTop(); 81 if (scroll > sheight - 100) { 82 $("nav.navbar").addClass('scrolled'); 83 } 84 85 else{ 86 $("nav.navbar").removeClass('scrolled'); 87 } 88 }) 89 90 $('#gallery a').simpleLightbox({loadingCaption: '...'}); 91 92}); 93 94 95/* 96 $("#popup").fancybox({ 97 'transitionIn' : 'fade', 98 'transitionOut' : 'fade', 99 'speedIn' : 200, 100 'speedOut' : 200, 101 'overlayColor' : '#000', 102 'overlayOpacity': 0.7, 103 'opacity' : true, 104 'changeFade' : 'fast', 105 'overlayShow' : true, 106 'padding' : 30, 107 'margin' : 30, 108 'width' : 600 109 }); 110*/ 111 112 113 114/** 115 * A lightweight youtube embed. Still should feel the same to the user, just MUCH faster to initialize and paint. 116 * 117 * Thx to these as the inspiration 118 * https://storage.googleapis.com/amp-vs-non-amp/youtube-lazy.html 119 * https://autoplay-youtube-player.glitch.me/ 120 * 121 * Once built it, I also found these: 122 * https://github.com/ampproject/amphtml/blob/master/extensions/amp-youtube (ðð) 123 * https://github.com/Daugilas/lazyYT 124 * https://github.com/vb/lazyframe 125 */ 126class LiteYTEmbed extends HTMLElement { 127 connectedCallback() { 128 this.videoId = this.getAttribute('videoid'); 129 130 let playBtnEl = this.querySelector('.lty-playbtn'); 131 // A label for the button takes priority over a [playlabel] attribute on the custom-element 132 this.playLabel = (playBtnEl && playBtnEl.textContent.trim()) || this.getAttribute('playlabel') || 'Play'; 133 134 /** 135 * Lo, the youtube placeholder image! (aka the thumbnail, poster image, etc) 136 * 137 * See https://github.com/paulirish/lite-youtube-embed/blob/master/youtube-thumbnail-urls.md 138 * 139 * TODO: Do the sddefault->hqdefault fallback 140 * - When doing this, apply referrerpolicy (https://github.com/ampproject/amphtml/pull/3940) 141 * TODO: Consider using webp if supported, falling back to jpg 142 */ 143 if (!this.style.backgroundImage) { 144 this.style.backgroundImage = `url("https://i.ytimg.com/vi/${this.videoId}/hqdefault.jpg")`; 145 } 146 147 // Set up play button, and its visually hidden label 148 if (!playBtnEl) { 149 playBtnEl = document.createElement('button'); 150 playBtnEl.type = 'button'; 151 playBtnEl.classList.add('lty-playbtn'); 152 this.append(playBtnEl); 153 } 154 if (!playBtnEl.textContent) { 155 const playBtnLabelEl = document.createElement('span'); 156 playBtnLabelEl.className = 'lyt-visually-hidden'; 157 playBtnLabelEl.textContent = this.playLabel; 158 playBtnEl.append(playBtnLabelEl); 159 } 160 playBtnEl.removeAttribute('href'); 161 162 // On hover (or tap), warm up the TCP connections we're (likely) about to use. 163 this.addEventListener('pointerover', LiteYTEmbed.warmConnections, {once: true}); 164 165 // Once the user clicks, add the real iframe and drop our play button 166 // TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time 167 // We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003 168 this.addEventListener('click', this.addIframe); 169 170 // Chrome & Edge desktop have no problem with the basic YouTube Embed with ?autoplay=1 171 // However Safari desktop and most/all mobile browsers do not successfully track the user gesture of clicking through the creation/loading of the iframe, 172 // so they don't autoplay automatically. Instead we must load an additional 2 sequential JS f
172iles (1KB + 165KB) (un-br) for the YT Player API 173 // TODO: Try loading the the YT API in parallel with our iframe and then attaching/playing it. #82 174 this.needsYTApiForAutoplay = navigator.vendor.includes('Apple') || navigator.userAgent.includes('Mobi'); 175 } 176 177 /** 178 * Add a <link rel={preload | preconnect} ...> to the head 179 */ 180 static addPrefetch(kind, url, as) { 181 const linkEl = document.createElement('link'); 182 linkEl.rel = kind; 183 linkEl.href = url; 184 if (as) { 185 linkEl.as = as; 186 } 187 document.head.append(linkEl); 188 } 189 190 /** 191 * Begin pre-connecting to warm up the iframe load 192 * Since the embed's network requests load within its iframe, 193 * preload/prefetch'ing them outside the iframe will only cause double-downloads. 194 * So, the best we can do is warm up a few connections to origins that are in the critical path. 195 * 196 * Maybe `<link rel=preload as=document>` would work, but it's unsupported: http://crbug.com/593267 197 * But TBH, I don't think it'll happen soon with Site Isolation and split caches adding serious complexity. 198 */ 199 static warmConnections() { 200 if (LiteYTEmbed.preconnected) return; 201 202 // The iframe document and most of its subresources come right off youtube.com 203 LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com'); 204 // The botguard script is fetched off from google.com 205 LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com'); 206 207 // Not certain if these ad related domains are in the critical path. Could verify with domain-specific throttling. 208 LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net'); 209 LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net'); 210 211 LiteYTEmbed.preconnected = true; 212 } 213 214 fetchYTPlayerApi() { 215 if (window.YT || (window.YT && window.YT.Player)) return; 216 217 this.ytApiPromise = new Promise((res, rej) => { 218 var el = document.createElement('script'); 219 el.src = 'https://www.youtube.com/iframe_api'; 220 el.async = true; 221 el.onload = _ => { 222 YT.ready(res); 223 }; 224 el.onerror = rej; 225 this.append(el); 226 }); 227 } 228 229 async addYTPlayerIframe(params) { 230 this.fetchYTPlayerApi(); 231 await this.ytApiPromise; 232 233 const videoPlaceholderEl = document.createElement('div') 234 this.append(videoPlaceholderEl); 235 236 const paramsObj = Object.fromEntries(params.entries()); 237 238 new YT.Player(videoPlaceholderEl, { 239 width: '100%', 240 videoId: this.videoId, 241 playerVars: paramsObj, 242 events: { 243 'onReady': event => { 244 event.target.playVideo(); 245 } 246 } 247 }); 248 } 249 250 async addIframe(){ 251 if (this.classList.contains('lyt-activated')) return; 252 this.classList.add('lyt-activated'); 253 254 const params = new URLSearchParams(this.getAttribute('params') || []); 255 params.append('autoplay', '1'); 256 params.append('playsinline', '1'); 257 258 if (this.needsYTApiForAutoplay) { 259 return this.addYTPlayerIframe(params); 260 } 261 262 const iframeEl = document.createElement('iframe'); 263 iframeEl.width = 560; 264 iframeEl.height = 315; 265 // No encoding necessary as [title] is safe. https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#:~:text=Safe%20HTML%20Attributes%20include 266 iframeEl.title = this.playLabel; 267 iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'; 268 iframeEl.allowFullscreen = true; 269 // AFAIK, the encoding here isn't necessary for XSS, but we'll do it only because this is a URL 270 // https://stackoverflow.com/q/64959723/89484 271 iframeEl.src = `https://www.youtube-nocookie.com/embed/${encodeURIComponent(this.videoId)}?${params.toString()}`; 272 this.append(iframeEl); 273 274 // Set focus for a11y 275 iframeEl.focus(); 276 } 277} 278// Register custom element 279customElements.define('lite-youtube', LiteYTEmbed);
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.