1const reducedMotion = window.matchMedia(`(prefers-reduced-motion: reduce)`) === true || window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true; 2let motion = 1; 3 4//if reduced motion set, replace videos with img poster 5if( reducedMotion ) { 6 motion = 0; 7 $('.bg-video').each(function() { 8 var poster = $(this).attr('poster'); 9 var title = $(this).attr('title'); 10 var classes = $(this).attr('class'); 11 var image = $('<img>').attr({'src': poster, 'alt': title, 'class': classes}); 12 $(this).replaceWith(image); 13 }); 14} 15 16//focus on search bar on offcanvas search open 17$('#nav-search').on('shown.bs.offcanvas', function(){ 18 $('#search-bar').focus(); 19}); 20 21//load delayed blog images 22$('#nav-search').on('show.bs.offcanvas', function(){ 23 $( $('img[data-src]', this) ).each(function() { 24 $(this).attr('src', $(this).attr('data-src')).removeAttr('data-src').on('load', function() { 25 $(this).removeClass('placeholder'); 26 }); 27 }); 28}); 29 30//toggle menus open/close aria + hide other on open 31$(document).on('hide.bs.offcanvas', function(e) { 32 $("[data-bs-target='#" + e.target.id + "']").attr('aria-pressed', false); 33 $('body').attr('offcanvas',''); 34}).on('show.bs.offcanvas', function(e) { 35 $('body').attr('offcanvas',e.target.id); 36 $('.offcanvas-top.show, .offcanvas-end.show, .offcanvas-bottom.show, .offcanvas-start.show').offcanvas('hide'); 37 $(e.relatedTarget).attr('aria-pressed', true); 38 var footerPosition = $('footer').offset().top; 39 var viewportTop = $(window).scrollTop(); 40 var viewportHeight = $(window).height(); 41 var viewportBottom = viewportTop + viewportHeight; 42 //if primary nav, scroll to top 43 if( e.target.id == 'nav-primary' ) { 44 setTimeout(function() { 45 $(window).scrollTop(0); 46 $('#nav-primary').css('top', ($('#alert').height() ?? 0) + ($('#header').height() ?? 0)); 47 }, 0); 48 } 49 //if section nav past footer, scroll to top of footer 50 else if( e.target.id == 'section-menu' && footerPosition < viewportBottom ) { 51 setTimeout(function() { 52 $(window).scrollTop(footerPosition - viewportHeight); 53 }, 0); 54 } 55}); 56 57//initialize carousels 58let initializeCarousels = [...$('.carousel')].map(el => new bootstrap.Carousel(el)); 59 60/* ===================================================== 61SCROLLING 62===================================================== */ 63 64//delay scrolling to prevent triggering too many events 65var scrollTimer = 0; 66$(document).scroll(function() { 67 var minScrollTime = 250; 68 if(!scrollTimer) { 69 scrollTimer = setTimeout(function() { 70 var scrollPosition = $(document).scrollTop(); 71 sectionMenuUpdate(scrollPosition); 72 mobileMenuUpdate(scrollPosition); 73 scrollTimer = null; 74 }, minScrollTime); 75 } 76}); 77 78//update mobile menu position when it hits footer (fix for sticky issues) 79var mobileNav = $('#nav-mobile'); 80function mobileMenuUpdate(position) { 81 if( mobileNav.is(":visible") ) { 82 var footerTop = $('footer').offset().top; 83 var viewportHeight = $(window).height(); 84 var viewportBottom = position + viewportHeight; 85 var mobileNavHeight = mobileNav.height(); 86 $('#nav-mobile-spacer').height(mobileNavHeight) 87 if( footerTop < viewportBottom ) { 88 if( !mobileNav.hasClass('position-relative') ) { 89 mobileNav.addClass('position-relative'); 90 $('#nav-mobile-spacer').hide(); 91 } 92 } else { 93 if( mobileNav.hasClass('position-relative') ) { 94 mobileNav.removeClass('position-relative'); 95 $('#nav-mobile-spacer').show(); 96 } 97 } 98 } 99} 100 101//scroll spy that actually works (thanks Bootstrap) 102var sectionMenuIds = $('#section-menu-nav li a'); 103function sectionMenuUpdate(position) { 104 sectionMenuIds.each(function() { 105 var container = $(this).attr('href'); 106 var containerTop = $(container).offset().top; 107 var containerHeight = $(container).outerHeight(); 108 var containerBottom = containerTop + containerHeight; 109 if( position < (containerBottom - 200) && position >= (containerTop - 200) ) { 110 $(this).addClass('active'); 111 } else { 112 $(this).removeClass('active'); 113 } 114 });
115} 116 117//hide offcanvas section menu on anchor click 118$('#section-menu a').on('click', function() { 119 $('#section-menu').offcanvas('hide'); 120}); 121 122/* ===================================================== 123UNWRAPPER 124===================================================== */ 125 126function numberConverter(txt) { 127 if ($.isNumeric(txt)) { 128 return Number(txt); 129 } else { 130 return txt; 131 } 132} 133 134function unwrapper(selector) { 135 var unwrapped = new Object(); 136 var wrapped = selector.split(';'); 137 $(wrapped).each(function(x, y) { 138 var package = y.split(':'); 139 if(package[1].indexOf(",") !== -1) { 140 var parcelGroup = []; 141 parcel = package[1].split(','); 142 $(parcel).each(function(x, z){ 143 parcelGroup.push( numberConverter(z) ); 144 }); 145 unwrapped[package[0]] = parcelGroup; 146 } else { 147 unwrapped[package[0]] = numberConverter(package[1]); 148 } 149 }); 150 return unwrapped; 151} 152 153/* ===================================================== 154ANNOTATION ANIMATION TRIGGER 155===================================================== */ 156 157var annotationObserver = new IntersectionObserver(annotationIntersection, { 158 threshold: 0.5 159}); 160function annotationIntersection(entries, opts) { 161 entries.forEach(entry => { 162 if(entry.isIntersecting && !$(entry.target).hasClass('annotated')) { 163 var itemGroup = []; 164 if( $(entry.target).attr('data-annotate') == 'group' ) { 165 var selector = { 166 list: $(entry.target).find('[data-annotate-item]'), 167 attr: 'data-annotate-item' 168 }; 169 } else { 170 var selector = { 171 list: $(entry.target), 172 attr: 'data-annotate' 173 }; 174 } 175 $(selector.list).each(function() { 176 var options = unwrapper( $(this).attr(selector.attr) ); 177 options['animate'] = motion; 178 var item = RoughNotation.annotate(this, options); 179 itemGroup.push(item); 180 }); 181 RoughNotation.annotationGroup(itemGroup).show(); 182 $(entry.target).addClass('annotated'); 183 } else if(motion) { 184 setTimeout(function() { 185 $(entry.target).removeClass('annotated').parent().find('.rough-annotation').remove(); 186 }, 300); 187 } 188 }); 189} 190$('[data-annotate]').each(function() { 191 annotationObserver.observe(this); 192}); 193 194/* ===================================================== 195LAZY VIDEO TRIGGER 196===================================================== */ 197 198var videoObserver = new IntersectionObserver(videoIntersection, { 199 threshold: 0, 200 rootMargin: '100%' 201}); 202 203function videoIntersection(entries, opts) { 204 entries.forEach(entry => { 205 var videoSources = $('source', entry.target); 206 if( entry.isIntersecting && !videoSources.first().attr('src') ) { 207 $(videoSources).each(function() { 208 $(this).attr('src', $(this).attr('data-src')).removeAttr('data-src'); 209 }); 210 $(entry.target).trigger('load'); 211 videoObserver.unobserve(entry.target); 212 } 213 }); 214} 215 216$('video[loading=lazy]').each(function() { 217 videoObserver.observe(this); 218}); 219 220/* ===================================================== 221LAZY SCRIPT TRIGGER 222===================================================== */ 223 224var scriptObserver = new IntersectionObserver(lazyIntersection, { 225 threshold: 0, 226 rootMargin: '100%' 227}); 228 229function lazyIntersection(entries, opts) { 230 entries.forEach(entry => { 231 var scriptSource = $(entry.target).attr('data-lazy-script'); 232 if( entry.isIntersecting && scriptSource ) { 233 $(entry.target).removeAttr('data-lazy-script'); 234 $.getScript(scriptSource); 235 scriptObserver.unobserve(entry.target); 236 } 237 }); 238} 239 240$('[data-lazy-script]').each(function() { 241 scriptObserver.observe(this); 242}); 243 244/* ===================================================== 245PREEMPTIVE MODAL 246===================================================== */ 247const url = window.location.search; 248const params = new URLSearchParams(url); 249 250if( params.has('modal') ) { 251 var modal = params.get('modal'); 252 if( $('#' + modal).length ) { 253 new bootstrap.Modal($('#' + modal)).show(); 254 } 255} 256 257/* =====================================================
258VIDEO MODAL 259===================================================== */ 260$('.modal-video').on('show.bs.modal', function(e){ 261 var btn = $(e.relatedTarget); 262 var videoID = btn.data('video-id'); 263 var videoType = btn.data('video-type'); 264 var videoTitle = btn.data('video-title'); 265 266 switch (videoType) { 267 case 'wistia': 268 var videoSrc = "https://fast.wistia.net/embed/iframe/"+videoID+"?videoFoam=false&autoPlay=true&playerColor=005596"; 269 break; 270 case 'youtube': 271 var videoSrc = "https://www.youtube.com/embed/"+videoID+"?rel=0&controls=1&showinfo=0&autoplay=1&color=white"; 272 break; 273 case 'panopto': 274 var videoSrc = "https://cuwaa.hosted.panopto.com/Panopto/Pages/Embed.aspx?id="+videoID+"&autoplay=true&offerviewer=false&showtitle=false&showbrand=false&captions=false&interactivity=all&hideoverlay=false"; 275 break; 276 default: 277 var videoSrc = ""; 278 } 279 //if none of these data attributes are blank 280 if(videoTitle && videoID && videoSrc) { 281 $('.modal-title', this).text(videoTitle); 282 $('.modal-player', this).attr({'src': videoSrc, 'title': videoTitle}).addClass('loading'); 283 $('.modal-player.loading').one('load', function(){ 284 $('.modal-player').removeClass('placeholder loading'); 285 }); 286 } else { 287 $('.modal-player', this).removeClass('placeholder loading').attr({'src': '', 'title': 'Error'}); 288 $('.alert', this).show(); 289 console.log("ERROR: required video data attribute missing."); 290 } 291}); 292//reset modal on close 293$('.modal-video').on('hidden.bs.modal', function() { 294 $(this).find('.modal-player').addClass('placeholder').attr({'src': '', 'title': 'Loa
294ding...'}); 295 $(this).find('.modal-title').text('Video'); 296 $(this).find('.alert').hide(); 297});
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.