1/* ZeroRank pricing.js â the ribbon video gate (861px), from pricing.html, plus the billing toggle (authored). */ 2(function () { 3 "use strict"; 4 /* The ribbon is display:none under 861px â but display:none does NOT stop a 5 <video> downloading. Chrome honours preload and fetches the whole clip 6 whether or not the element is ever painted, so a phone was paying 2.4MB 7 for something it could not see. The element therefore ships with no src 8 at all; it is handed one only once the breakpoint is genuinely met. The 9 mount above has already bound `loadeddata`, so play follows on its own. */ 10 var v = document.getElementById('zr-hero-video'); 11 if (!v || !v.dataset.src) return; 12 13 /* Starting playback is this block's job too. The shared mount above ran 14 before the element had a src at all, so its own play() attempt was 15 always going to be refused; it left a `loadeddata` listener behind, and 16 relying on that alone left the ribbon sitting on its first frame. Kick 17 it here, on the two events that mean there is something to play, and 18 again if the tab comes back â a muted loop that has been suspended does 19 not always resume on its own. */ 20 var play = function () { 21 if (!v.src) return; 22 var p = v.play(); 23 if (p && p.catch) p.catch(function () { /* first frame stands in */ }); 24 }; 25 26 var wide = matchMedia('(min-width: 861px)'); 27 var load = function () { 28 if (!wide.matches || v.src) return; 29 v.src = v.dataset.src; 30 v.load(); 31 play(); 32 }; 33 load(); 34 35 v.addEventListener('loadeddata', play); 36 v.addEventListener('canplay', play); 37 document.addEventListener('visibilitychange', function () { 38 if (!document.hidden) play(); 39 }); 40 wide.addEventListener ? wide.addEventListener('change', load) 41 : wide.addListener(load); 42})(); 43 44 45/* ---------------------------------------------------------------- */ 46 47/* ââ billing toggle âââââââââââââââââââââââââââââââââââââââââââââââââââââ 48 Monthly / Yearly. The legacy page shipped the markup and the CSS (the pill 49 travels on #zr-billing[data-period], the â20% chip recolours on 50 aria-pressed) but no script, so clicking Monthly did nothing. Authored for 51 the port, not sliced: each .zr-plan__amount counts from one price to the 52 other over 420ms (ease-out, rAF, exact value written by a backstop timer), 53 keeping its .zr-plan__per child; .zr-plan__save shows its data-save text on 54 yearly and empties on monthly (pricing.css hides :empty). Reduced motion 55 skips the tween. */ 56(function () { 57 'use strict'; 58 var group = document.getElementById('zr-billing'); 59 if (!group) return; 60 var buttons = Array.prototype.slice.call(group.querySelectorAll('button[data-period]')); 61 var amounts = Array.prototype.slice.call(document.querySelectorAll('.zr-plan__amount[data-yearly][data-monthly]')); 62 var saves = Array.prototype.slice.call(document.querySelectorAll('.zr-plan__save[data-save]')); 63 var reduce = !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches); 64 var DURATION = 420; 65 var run = 0; 66 67 function parts(text) { /* "$71" â { prefix: "$", value: 71, suffix: "" } */ 68 var m = /^([^0-9]*)([0-9][0-9,]*)(.*)$/.exec(text || ''); 69 return m ? { prefix: m[1], value: parseInt(m[2].replace(/,/g, ''), 10), suffix: m[3] } : null; 70 } 71 function priceNode(el) { /* the first text node is the price; .zr-plan__per stays where it is */ 72 for (var i = 0; i < el.childNodes.length; i++) if (el.childNodes[i].nodeType === 3) return el.childNodes[i]; 73 var t = document.createTextNode(''); el.insertBefore(t, el.firstChild); return t; 74 } 75 function tween(node, from, to, target, id) { 76 var start = null; 77 var tick = function (now) { 78 if (id !== run) return; 79 if (start === null) start = now; 80 var p = Math.min(1, (now - start) / DURATION); 81 var e = 1 - Math.pow(1 - p, 3); 82 node.data = to.prefix + Math.round(from.value + (to.value - from.value) * e) + to.suffix; 83 if (p < 1) requestAnimationFrame(tick); 84 }; 85 requestAnimationFrame(tick); 86 setTimeout(function () { if (id === run) node.data = target; }, DURATION + 80); 87 } 88 function setPeriod(period) { 89 var id = ++run;
90 group.setAttribute('data-period', period); 91 buttons.forEach(function (b) { b.setAttribute('aria-pressed', String(b.getAttribute('data-period') === period)); }); 92 amounts.forEach(function (el) { 93 var target = el.getAttribute('data-' + period) || ''; 94 var node = priceNode(el); 95 var from = parts(node.data), to = parts(target); 96 if (reduce || !from || !to || from.value === to.value) { node.data = target; return; } 97 tween(node, from, to, target, id); 98 }); 99 saves.forEach(function (el) { el.textContent = period === 'yearly' ? el.getAttribute('data-save') : ''; }); 100 } 101 group.addEventListener('click', function (e) { 102 var b = e.target.closest('button[data-period]'); 103 if (!b || !group.contains(b)) return; 104 var period = b.getAttribute('data-period'); 105 if (period !== 'monthly' && period !== 'yearly') return; 106 if (group.getAttribute('data-period') === period) return; 107 setPeriod(period); 108 }); 109})();
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.