1/* ========================================================================== 2 Trigger the brand animations when they scroll into view. 3 4 Each fires ONCE. An animation that replays every time you scroll past turns 5 into wallpaper, and the whole point is that it reads as an event. 6 ========================================================================== */ 7 8(function () { 9 'use strict'; 10 11 var reduce = window.matchMedia 12 && window.matchMedia('(prefers-reduced-motion: reduce)').matches; 13 14 var targets = [ 15 // The rocket ([data-rocket], is-launching) was here until Owen asked for it 16 // to come off the homepage, which was the only page that had one. 17 { sel: '[data-flightpath]', cls: 'is-flying' }, 18 { sel: '[data-roadtrip]', cls: 'is-driving' }, 19 // Unlike the three above, this one loops rather than playing once. Gating it 20 // on the heading scrolling into view means it does not run for a visitor who 21 // never reaches that band at all. 22 { sel: '[data-plane-float]', cls: 'is-floating' } 23 ]; 24 25 if (reduce || !('IntersectionObserver' in window)) { 26 // Nothing animates, and nothing is hidden either: the plane and the truck 27 // are decorative, so leaving them in their resting state is correct. 28 return; 29 } 30 31 targets.forEach(function (t) { 32 var els = Array.prototype.slice.call(document.querySelectorAll(t.sel)); 33 if (!els.length) return; 34 35 var obs = new IntersectionObserver(function (entries) { 36 entries.forEach(function (entry) { 37 if (!entry.isIntersecting) return; 38 entry.target.classList.add(t.cls); 39 obs.unobserve(entry.target); 40 }); 41 }, { threshold: 0.45 }); 42 43 els.forEach(function (el) { obs.observe(el); }); 44 }); 45 46 /* ---- Statistic count-up ------------------------------------------------- 47 Every .stat__value on the site is a figure followed by words: "1 Billion +", 48 "40+ Years", "30+ Tested". Ramp the figure and leave the words alone, so 49 the same code works on the homepage, About and all seven industry pages 50 without any per-page markup. 51 52 The ticking digits are decorative, so they are aria-hidden and a static 53 .sr-only copy of the finished figure carries the real value. Otherwise a 54 screen reader can read whatever partial number happens to be on screen. 55 ---------------------------------------------------------------------- */ 56 57 var COUNT_MS = 1100; 58 59 function splitStat(text) { 60 var m = /^(\D*)(\d[\d,]*(?:\.\d+)?)([\s\S]*)$/.exec(text); 61 if (!m) return null; 62 return { 63 pre: m[1], 64 digits: m[2], 65 target: parseFloat(m[2].replace(/,/g, '')), 66 post: m[3] 67 }; 68 } 69 70 /* Small targets (the "1" in "1 Billion") have nothing to count through as 71 integers, so they run to one decimal place and land on the whole number. */ 72 function fmt(value, target) { 73 if (target < 10) return (Math.round(value * 10) / 10).toFixed(1); 74 return String(Math.round(value)); 75 } 76 77 function countUp(el) { 78 var stat = el.__stat; 79 var start = 0; 80 81 function frame(now) { 82 if (!start) start = now; 83 var p = Math.min((now - start) / COUNT_MS, 1); 84 var eased = 1 - Math.pow(1 - p, 3); 85 el.__run.textContent = p === 1 86 ? stat.pre + stat.digits + stat.post 87 : stat.pre + fmt(stat.target * eased, stat.target) + stat.post; 88 if (p < 1) requestAnimationFrame(frame); 89 } 90 91 requestAnimationFrame(frame); 92 } 93 94 var stats = Array.prototype.slice.call(document.querySelectorAll('.stat__value')); 95 96 stats = stats.filter(function (el) { 97 var stat = splitStat(el.textContent); 98 if (!stat) return false; 99 100 var run = document.createElement('span'); 101 run.setAttribute('aria-hidden', 'true'); 102 run.textContent = stat.pre + fmt(0, stat.target) + stat.post; 103 104 var label = document.createElement('span'); 105 label.className = 'sr-only'; 106 label.textContent = el.textContent; 107 108 el.textContent = ''; 109 el.appendChild(run); 110 el.appendChild(label); 111 112 el.__stat = stat; 113 el.__run = run; 114 return true; 115 }); 116 117 if (stats.length) { 118 var statObs = new IntersectionObserver(function (entries) {
119 entries.forEach(function (entry) { 120 if (!entry.isIntersecting) return; 121 statObs.unobserve(entry.target); 122 countUp(entry.target); 123 }); 124 }, { threshold: 0.6 }); 125 126 stats.forEach(function (el) { statObs.observe(el); }); 127 } 128})();
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.