vendor: 11,447 bytes, lines 1-371
1// 2// SmoothScroll for websites v1.4.9 (Balazs Galambosi) 3// http://www.smoothscroll.net/ 4// 5// Licensed under the terms of the MIT license. 6// 7// You may use it in your theme if you credit me. 8// It is also free to use on any individual website. 9// 10// Exception: 11// The only restriction is to not publish any 12// extension for browsers or native application 13// without getting a written permission first. 14// 15 16(function () { 17 18// Scroll Variables (tweakable) 19 var defaultOptions = { 20 21 // Scrolling Core 22 frameRate : 150, // [Hz] 23 animationTime : 400, // [ms] 24 stepSize : 100, // [px] 25 26 // Pulse (less tweakable) 27 // ratio of "tail" to "acceleration" 28 pulseAlgorithm : true, 29 pulseScale : 4, 30 pulseNormalize : 1, 31 32 // Acceleration 33 accelerationDelta : 50, // 50 34 accelerationMax : 3, // 3 35 36 // Keyboard Settings 37 keyboardSupport : true, // option 38 arrowScroll : 50, // [px] 39 40 // Other 41 fixedBackground : true, 42 excluded : '' 43 }; 44 45 var options = defaultOptions; 46 47 48// Other Variables 49 var isExcluded = false; 50 var isFrame = false; 51 var direction = { x: 0, y: 0 }; 52 var initDone = false; 53 var root = document.documentElement; 54 var activeElement; 55 var observer; 56 var refreshSize; 57 var deltaBuffer = []; 58 var deltaBufferTimer; 59 var isMac = /^Mac/.test(navigator.platform); 60 61 var key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32, 62 pageup: 33, pagedown: 34, end: 35, home: 36 }; 63 var arrowKeys = { 37: 1, 38: 1, 39: 1, 40: 1 }; 64 65 /*********************************************** 66 * INITIALIZE 67 ***********************************************/ 68 69 /** 70 * Tests if smooth scrolling is allowed. Shuts down everything if not. 71 */ 72 function initTest() { 73 if (options.keyboardSupport) { 74 addEvent('keydown', keydown); 75 } 76 } 77 78 /** 79 * Sets up scrolls array, determines if frames are involved. 80 */ 81 function init() { 82 83 if (initDone || !document.body) return; 84 85 initDone = true; 86 87 var body = document.body; 88 var html = document.documentElement; 89 var windowHeight = window.innerHeight; 90 var scrollHeight = body.scrollHeight; 91 92 // check compat mode for root element 93 root = (document.compatMode.indexOf('CSS') >= 0) ? html : body; 94 activeElement = body; 95 96 initTest(); 97 98 // Checks if this script is running in a frame 99 if (top != self) { 100 isFrame = true; 101 } 102 103 /** 104 * Safari 10 fixed it, Chrome fixed it in v45: 105 * This fixes a bug where the areas left and right to 106 * the content does not trigger the onmousewheel event 107 * on some pages. e.g.: html, body { height: 100% } 108 */ 109 else if (isOldSafari && 110 scrollHeight > windowHeight && 111 (body.offsetHeight <= windowHeight || 112 html.offsetHeight <= windowHeight)) { 113 114 var fullPageElem = document.createElement('div'); 115 fullPageElem.style.cssText = 'position:absolute; z-index:-10000; ' + 116 'top:0; left:0; right:0; height:' + 117 root.scrollHeight + 'px'; 118 document.body.appendChild(fullPageElem); 119 120 // DOM changed (throttled) to fix height 121 var pendingRefresh; 122 refreshSize = function () { 123 if (pendingRefresh) return; // could also be: clearTimeout(pendingRefresh); 124 pendingRefresh = setTimeout(function () { 125 if (isExcluded) return; // could be running after cleanup 126 fullPageElem.style.height = '0'; 127 fullPageElem.style.height = root.scrollHeight + 'px'; 128 pendingRefresh = null; 129 }, 500); // act rarely to stay fast 130 }; 131 132 setTimeout(refreshSize, 10); 133 134 addEvent('resize', refreshSize); 135 136 // TODO: attributeFilter? 137 var config = { 138 attributes: true, 139 childList: true, 140 characterData: false 141 // subtree: true 142 }; 143 144 observer = new MutationObserver(refreshSize); 145 observer.observe(body, config); 146 147 if (root.offsetHeight <= windowHeight) { 148 var clearfix = document.createElement('div'); 149 clearfix.style.clear = 'both'; 150 body.appendChild(clearfix); 151 } 152 } 153 154 // disable fixed background 155 if (!options.fixedBackground && !isExcluded) { 156 body.style.backgroundAttachment = 'scroll'; 157 html.style.backgroundAttachment = 'scroll'; 158 } 159 } 160 161 /** 162 * Removes event listeners and other traces left on the page. 163 */ 164 function cleanup() { 165 observer && observer.disconnect(); 166 removeEvent(wheelEvent, wheel); 167 removeEvent('mousedown', mousedown); 168 removeEvent('keydown', keydown); 169 removeEvent('resize', refreshSize); 170 removeEvent('load', init); 171 } 172 173 174 /************************************************ 175 * SCROLLING 176 ************************************************/ 177 178 var que = []; 179 var pending = false; 180 var lastScroll = Date.now(); 181 182 /** 183 * Pushes scroll actions to the scrolling queue. 184 */ 185 function scrollArray(elem, left, top) { 186 187 directionCheck(left, top); 188 189 if (options.accelerationMax != 1) { 190 var now = Date.now(); 191 var elapsed = now - lastScroll; 192 if (elapsed < options.accelerationDelta) { 193 var factor = (1 + (50 / elapsed)) / 2; 194 if (factor > 1) { 195 factor = Math.min(factor, options.accelerationMax); 196 left *= factor; 197 top *= factor; 198 } 199 } 200 lastScroll = Date.now(); 201 } 202 203 // push a scroll command 204 que.push({ 205 x: left, 206 y: top, 207 lastX: (left < 0) ? 0.99 : -0.99, 208 lastY: (top < 0) ? 0.99 : -0.99, 209 start: Date.now() 210 }); 211 212 // don't act if there's a pending queue 213 if (pending) { 214 return; 215 } 216 217 var scrollRoot = getScrollRoot(); 218 var isWindowScroll = (elem === scrollRoot || elem === document.body); 219 220 // if we haven't already fixed the behavior, 221 // and it needs fixing for this sesh 222 if (elem.$scrollBehavior == null && isScrollBehaviorSmooth(elem)) { 223 elem.$scrollBehavior = elem.style.scrollBehavior; 224 elem.style.scrollBehavior = 'auto'; 225 } 226 227 var step = function (time) { 228 229 var now = Date.now(); 230 var scrollX = 0; 231 var scrollY = 0; 232 233 for (var i = 0; i < que.length; i++) { 234 235 var item = que[i]; 236 var elapsed = now - item.start; 237 var finished = (elapsed >= options.animationTime); 238 239 // scroll position: [0, 1] 240 var position = (finished) ? 1 : elapsed / options.animationTime; 241 242 // easing [optional] 243 if (options.pulseAlgorithm) { 244 position = pulse(position); 245 } 246 247 // only need the difference 248 var x = (item.x * position - item.lastX) >> 0; 249 var y = (item.y * position - item.lastY) >> 0; 250 251 // add this to the total scrolling 252 scrollX += x; 253 scrollY += y; 254 255 // update last values 256 item.lastX += x; 257 item.lastY += y; 258 259 // delete and step back if it's over 260 if (finished) { 261 que.splice(i, 1); i--; 262 } 263 } 264 265 // scroll left and top 266 if (isWindowScroll) { 267 window.scrollBy(scrollX, scrollY); 268 } 269 else { 270 if (scrollX) elem.scrollLeft += scrollX; 271 if (scrollY) elem.scrollTop += scrollY; 272 } 273 274 // clean up if there's nothing left to do 275 if (!left && !top) { 276 que = []; 277 } 278 279 if (que.length) { 280 requestFrame(step, elem, (1000 / options.frameRate + 1)); 281 } else { 282 pending = false; 283 // restore default behavior at the end of scrolling sesh 284 if (elem.$scrollBehavior != null) { 285 elem.style.scrollBehavior = elem.$scrollBehavior; 286 elem.$scrollBehavior = null; 287 } 288 } 289 }; 290 291 // start a new queue of actions 292 requestFrame(step, elem, 0); 293 pending = true; 294 } 295 296 297 /*********************************************** 298 * EVENTS 299 ***********************************************/ 300 301 /** 302 * Mouse wheel handler. 303 * @param {Object} event 304 */ 305 function wheel(event) { 306 307 if (!initDone) { 308 init(); 309 } 310 311 var target = event.target; 312 313 // leave early if default action is prevented 314 // or it's a zooming event with CTRL 315 if (event.defaultPrevented || event.ctrlKey) { 316 return true; 317 } 318 319 // leave embedded content alone (flash & pdf) 320 if (isNodeName(activeElement, 'embed') || 321 (isNodeName(target, 'embed') && /\.pdf/i.test(target.src)) || 322 isNodeName(activeElement, 'object') || 323 target.shadowRoot) { 324 return true; 325 } 326 327 var deltaX = -event.wheelDeltaX || event.deltaX || 0; 328 var deltaY = -event.wheelDeltaY || event.deltaY || 0; 329 330 if (isMac) { 331 if (event.wheelDeltaX && isDivisible(event.wheelDeltaX, 120)) { 332 deltaX = -120 * (event.wheelDeltaX / Math.abs(event.wheelDeltaX)); 333 } 334 if (event.wheelDeltaY && isDivisible(event.wheelDeltaY, 120)) { 335 deltaY = -120 * (event.wheelDeltaY / Math.abs(event.wheelDeltaY)); 336 } 337 } 338 339 // use wheelDelta if deltaX/Y is not available 340 if (!deltaX && !deltaY) { 341 deltaY = -event.wheelDelta || 0; 342 } 343 344 // line based scrolling (Firefox mostly) 345 if (event.deltaMode === 1) { 346 deltaX *= 40; 347 deltaY *= 40; 348 } 349 350 var overflowing = overflowingAncestor(target); 351 352 // nothing to do if there's no element that's scrollable 353 if (!overflowing) { 354 // except Chrome iframes seem to eat wheel events, which we need to 355 // propagate up, if the iframe has nothing overflowing to scroll 356 if (isFrame && isChrome) { 357 // change target to iframe element itself for the parent frame 358 Object.defineProperty(event, "target", {value: window.frameElement}); 359 event = new event.constructor(event.type, event); // redefine event because already dispatched 360 return parent.dispatchEvent(event); 361 } 362 return true; 363 } 364 365 // check if it's a touchpad scroll that should be ignored 366 if (isTouchpad(deltaY)) { 367 return true; 368 } 369 370 // scale by step size 371 // delta is 120 most of the time
372 // synaptics seems to send 1 sometimes 373 if (Math.abs(deltaX) > 1.2) { 374 deltaX *= options.stepSize / 120; 375 } 376 if (Math.abs(deltaY) > 1.2) { 377 deltaY *= options.stepSize / 120; 378 } 379 380 scrollArray(overflowing, deltaX, deltaY); 381 event.preventDefault(); 382 scheduleClearCache(); 383 } 384 385 /** 386 * Keydown event handler. 387 * @param {Object} event 388 */ 389 function keydown(event) { 390 391 var target = event.target; 392 var modifier = event.ctrlKey || event.altKey || event.metaKey || 393 (event.shiftKey && event.keyCode !== key.spacebar); 394 395 // our own tracked active element could've been removed from the DOM 396 if (!document.body.contains(activeElement)) { 397 activeElement = document.activeElement; 398 } 399 400 // do nothing if user is editing text 401 // or using a modifier key (except shift) 402 // or in a dropdown 403 // or inside interactive elements 404 var inputNodeNames = /^(textarea|select|embed|object)$/i; 405 var buttonTypes = /^(button|submit|radio|checkbox|file|color|image)$/i; 406 if ( event.defaultPrevented || 407 inputNodeNames.test(target.nodeName) || 408 isNodeName(target, 'input') && !buttonTypes.test(target.type) || 409 isNodeName(activeElement, 'video') || 410 isInsideYoutubeVideo(event) || 411 target.isContentEditable || 412 modifier ) { 413 return true; 414 } 415 416 // [spacebar] should trigger button press, leave it alone 417 if ((isNodeName(target, 'button') || 418 isNodeName(target, 'input') && buttonTypes.test(target.type)) && 419 event.keyCode === key.spacebar) { 420 return true; 421 } 422 423 // [arrwow keys] on radio buttons should be left alone 424 if (isNodeName(target, 'input') && target.type == 'radio' && 425 arrowKeys[event.keyCode]) { 426 return true; 427 } 428 429 var shift, x = 0, y = 0; 430 var overflowing = overflowingAncestor(activeElement); 431 432 if (!overflowing) { 433 // Chrome iframes seem to eat key events, which we need to 434 // propagate up, if the iframe has nothing overflowing to scroll 435 return (isFrame && isChrome) ? parent.keydown(event) : true; 436 } 437 438 var clientHeight = overflowing.clientHeight; 439 440 if (overflowing == document.body) { 441 clientHeight = window.innerHeight; 442 } 443 444 switch (event.keyCode) { 445 case key.up: 446 y = -options.arrowScroll; 447 break; 448 case key.down: 449 y = options.arrowScroll; 450 break; 451 case key.spacebar: // (+ shift) 452 shift = event.shiftKey ? 1 : -1; 453 y = -shift * clientHeight * 0.9; 454 break; 455 case key.pageup: 456 y = -clientHeight * 0.9; 457 break; 458 case key.pagedown: 459 y = clientHeight * 0.9; 460 break; 461 case key.home: 462 if (overflowing == document.body && document.scrollingElement) 463 overflowing = document.scrollingElement; 464 y = -overflowing.scrollTop; 465 break; 466 case key.end: 467 var scroll = overflowing.scrollHeight - overflowing.scrollTop; 468 var scrollRemaining = scroll - clientHeight; 469 y = (scrollRemaining > 0) ? scrollRemaining + 10 : 0; 470 break; 471 case key.left: 472 x = -options.arrowScroll; 473 break; 474 case key.right: 475 x = options.arrowScroll; 476 break; 477 default: 478 return true; // a key we don't care about 479 } 480 481 scrollArray(overflowing, x, y); 482 event.preventDefault(); 483 scheduleClearCache(); 484 } 485 486 /** 487 * Mousedown event only for updating activeElement 488 */ 489 function mousedown(event) { 490 activeElement = event.target; 491 } 492 493 494 /*********************************************** 495 * OVERFLOW 496 ***********************************************/ 497 498 var uniqueID = (function () { 499 var i = 0; 500 return function (el) { 501 return el.uniqueID || (el.uniqueID = i++); 502 }; 503 })(); 504 505 var cacheX = {}; // cleared out after a scrolling session 506 var cacheY = {}; // cleared out after a scrolling session 507 var clearCacheTimer; 508 var smoothBehaviorForElement = {}; 509 510//setInterval(function () { cache = {}; }, 10 * 1000); 511 512 function scheduleClearCache() { 513 clearTimeout(clearCacheTimer); 514 clearCacheTimer = setInterval(function () { 515 cacheX = cacheY = smoothBehaviorForElement = {}; 516 }, 1*1000); 517 } 518
vendor: 9,291 bytes, lines 519-789
519 function setCache(elems, overflowing, x) { 520 var cache = x ? cacheX : cacheY; 521 for (var i = elems.length; i--;) 522 cache[uniqueID(elems[i])] = overflowing; 523 return overflowing; 524 } 525 526 function getCache(el, x) { 527 return (x ? cacheX : cacheY)[uniqueID(el)]; 528 } 529 530// (body) (root) 531// | hidden | visible | scroll | auto | 532// hidden | no | no | YES | YES | 533// visible | no | YES | YES | YES | 534// scroll | no | YES | YES | YES | 535// auto | no | YES | YES | YES | 536 537 function overflowingAncestor(el) { 538 var elems = []; 539 var body = document.body; 540 var rootScrollHeight = root.scrollHeight; 541 do { 542 var cached = getCache(el, false); 543 if (cached) { 544 return setCache(elems, cached); 545 } 546 elems.push(el); 547 if (rootScrollHeight === el.scrollHeight) { 548 var topOverflowsNotHidden = overflowNotHidden(root) && overflowNotHidden(body); 549 var isOverflowCSS = topOverflowsNotHidden || overflowAutoOrScroll(root); 550 if (isFrame && isContentOverflowing(root) || 551 !isFrame && isOverflowCSS) { 552 return setCache(elems, getScrollRoot()); 553 } 554 } else if (isContentOverflowing(el) && overflowAutoOrScroll(el)) { 555 return setCache(elems, el); 556 } 557 } while ((el = el.parentElement)); 558 } 559 560 function isContentOverflowing(el) { 561 return (el.clientHeight + 10 < el.scrollHeight); 562 } 563 564// typically for <body> and <html> 565 function overflowNotHidden(el) { 566 var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y'); 567 return (overflow !== 'hidden'); 568 } 569 570// for all other elements 571 function overflowAutoOrScroll(el) { 572 var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y'); 573 return (overflow === 'scroll' || overflow === 'auto'); 574 } 575 576// for all other elements 577 function isScrollBehaviorSmooth(el) { 578 var id = uniqueID(el); 579 if (smoothBehaviorForElement[id] == null) { 580 var scrollBehavior = getComputedStyle(el, '')['scroll-behavior']; 581 smoothBehaviorForElement[id] = ('smooth' == scrollBehavior); 582 } 583 return smoothBehaviorForElement[id]; 584 } 585 586 587 /*********************************************** 588 * HELPERS 589 ***********************************************/ 590 591 function addEvent(type, fn, arg) { 592 window.addEventListener(type, fn, arg || false); 593 } 594 595 function removeEvent(type, fn, arg) { 596 window.removeEventListener(type, fn, arg || false); 597 } 598 599 function isNodeName(el, tag) { 600 return el && (el.nodeName||'').toLowerCase() === tag.toLowerCase(); 601 } 602 603 function directionCheck(x, y) { 604 x = (x > 0) ? 1 : -1; 605 y = (y > 0) ? 1 : -1; 606 if (direction.x !== x || direction.y !== y) { 607 direction.x = x; 608 direction.y = y; 609 que = []; 610 lastScroll = 0; 611 } 612 } 613 614 if (window.localStorage && localStorage.SS_deltaBuffer) { 615 try { // #46 Safari throws in private browsing for localStorage 616 deltaBuffer = localStorage.SS_deltaBuffer.split(','); 617 } catch (e) { } 618 } 619 620 function isTouchpad(deltaY) { 621 if (!deltaY) return; 622 if (!deltaBuffer.length) { 623 deltaBuffer = [deltaY, deltaY, deltaY]; 624 } 625 deltaY = Math.abs(deltaY); 626 deltaBuffer.push(deltaY); 627 deltaBuffer.shift(); 628 clearTimeout(deltaBufferTimer); 629 deltaBufferTimer = setTimeout(function () { 630 try { // #46 Safari throws in private browsing for localStorage 631 localStorage.SS_deltaBuffer = deltaBuffer.join(','); 632 } catch (e) { } 633 }, 1000); 634 var dpiScaledWheelDelta = deltaY > 120 && allDeltasDivisableBy(deltaY); // win64 635 return !allDeltasDivisableBy(120) && !allDeltasDivisableBy(100) && !dpiScaledWheelDelta; 636 } 637 638 function isDivisible(n, divisor) { 639 return (Math.floor(n / divisor) == n / divisor); 640 } 641 642 function allDeltasDivisableBy(divisor) { 643 return (isDivisible(deltaBuffer[0], divisor) && 644 isDivisible(deltaBuffer[1], divisor) && 645 isDivisible(deltaBuffer[2], divisor)); 646 } 647 648 function isInsideYoutubeVideo(event) { 649 var elem = event.target; 650 var isControl = false; 651 if (document.URL.indexOf ('www.youtube.com/watch') != -1) { 652 do { 653 isControl = (elem.classList && 654 elem.classList.contains('html5-video-controls')); 655 if (isControl) break; 656 } while ((elem = elem.parentNode)); 657 } 658 return isControl; 659 } 660 661 var requestFrame = (function () { 662 return (window.requestAnimationFrame || 663 window.webkitRequestAnimationFrame || 664 window.mozRequestAnimationFrame || 665 function (callback, element, delay) { 666 window.setTimeout(callback, delay || (1000/60)); 667 }); 668 })(); 669 670 var MutationObserver = (window.MutationObserver || 671 window.WebKitMutationObserver || 672 window.MozMutationObserver); 673 674 var getScrollRoot = (function() { 675 var SCROLL_ROOT = document.scrollingElement; 676 return function() { 677 if (!SCROLL_ROOT) { 678 var dummy = document.createElement('div'); 679 dummy.style.cssText = 'height:10000px;width:1px;'; 680 document.body.appendChild(dummy); 681 var bodyScrollTop = document.body.scrollTop; 682 var docElScrollTop = document.documentElement.scrollTop; 683 window.scrollBy(0, 3); 684 if (document.body.scrollTop != bodyScrollTop) 685 (SCROLL_ROOT = document.body); 686 else 687 (SCROLL_ROOT = document.documentElement); 688 window.scrollBy(0, -3); 689 document.body.removeChild(dummy); 690 } 691 return SCROLL_ROOT; 692 }; 693 })(); 694 695 696 /*********************************************** 697 * PULSE (by Michael Herf) 698 ***********************************************/ 699 700 /** 701 * Viscous fluid with a pulse for part and decay for the rest. 702 * - Applies a fixed force over an interval (a damped acceleration), and 703 * - Lets the exponential bleed away the velocity over a longer interval 704 * - Michael Herf, http://stereopsis.com/stopping/ 705 */ 706 function pulse_(x) { 707 var val, start, expx; 708 // test 709 x = x * options.pulseScale; 710 if (x < 1) { // acceleartion 711 val = x - (1 - Math.exp(-x)); 712 } else { // tail 713 // the previous animation ended here: 714 start = Math.exp(-1); 715 // simple viscous drag 716 x -= 1; 717 expx = 1 - Math.exp(-x); 718 val = start + (expx * (1 - start)); 719 } 720 return val * options.pulseNormalize; 721 } 722 723 function pulse(x) { 724 if (x >= 1) return 1; 725 if (x <= 0) return 0; 726 727 if (options.pulseNormalize == 1) { 728 options.pulseNormalize /= pulse_(1); 729 } 730 return pulse_(x); 731 } 732 733 734 /*********************************************** 735 * FIRST RUN 736 ***********************************************/ 737 738 var userAgent = window.navigator.userAgent; 739 var isEdge = /Edge/.test(userAgent); // thank you MS 740 var isChrome = /chrome/i.test(userAgent) && !isEdge; 741 var isSafari = /safari/i.test(userAgent) && !isEdge; 742 var isMobile = /mobile/i.test(userAgent); 743 var isIEWin7 = /Windows NT 6.1/i.test(userAgent) && /rv:11/i.test(userAgent); 744 var isOldSafari = isSafari && (/Version\/8/i.test(userAgent) || /Version\/9/i.test(userAgent)); 745 var isEnabledForBrowser = (isChrome || isSafari || isIEWin7) && !isMobile; 746 747 var supportsPassive = false; 748 try { 749 window.addEventListener("test", null, Object.defineProperty({}, 'passive', { 750 get: function () { 751 supportsPassive = true; 752 } 753 })); 754 } catch(e) {} 755 756 var wheelOpt = supportsPassive ? { passive: false } : false; 757 var wheelEvent = 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel'; 758 759 if (wheelEvent && isEnabledForBrowser) { 760 addEvent(wheelEvent, wheel, wheelOpt); 761 addEvent('mousedown', mousedown); 762 addEvent('load', init); 763 } 764 765 766 /*********************************************** 767 * PUBLIC INTERFACE 768 ***********************************************/ 769 770 function SmoothScroll(optionsToSet) { 771 for (var key in optionsToSet) 772 if (defaultOptions.hasOwnProperty(key)) 773 options[key] = optionsToSet[key]; 774 } 775 SmoothScroll.destroy = cleanup; 776 777 if (window.SmoothScrollOptions) // async API 778 SmoothScroll(window.SmoothScrollOptions); 779 780 if (typeof define === 'function' && define.amd) 781 define(function() { 782 return SmoothScroll; 783 }); 784 else if ('object' == typeof exports) 785 module.exports = SmoothScroll; 786 else 787 window.SmoothScroll = SmoothScroll; 788 789})();
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.