Full Code of metafizzy/infinite-scroll for AI

master 38a464fbbbe4 cached
85 files
228.8 KB
63.7k tokens
60 symbols
1 requests
Download .txt
Showing preview only (248K chars total). Download the full file or copy to clipboard to get everything.
Repository: metafizzy/infinite-scroll
Branch: master
Commit: 38a464fbbbe4
Files: 85
Total size: 228.8 KB

Directory structure:
gitextract_pwag9y0f/

├── .eslintrc.js
├── .github/
│   ├── contributing.md
│   ├── issue_template.md
│   └── workflows/
│       └── nodejs.yml
├── .gitignore
├── .nvmrc
├── LICENSE.txt
├── README.md
├── bin/
│   ├── build-dist.js
│   └── version.js
├── bower.json
├── dist/
│   └── infinite-scroll.pkgd.js
├── js/
│   ├── button.js
│   ├── core.js
│   ├── history.js
│   ├── index.js
│   ├── page-load.js
│   ├── scroll-watch.js
│   └── status.js
├── package.json
├── sandbox/
│   ├── button-class.html
│   ├── button-first.html
│   ├── button-load.html
│   ├── container-scroll.html
│   ├── css/
│   │   ├── blog.css
│   │   ├── loader-ellips.css
│   │   └── masonry-images.css
│   ├── element-scroll.html
│   ├── html-init.html
│   ├── jquery-plugin.html
│   ├── js/
│   │   ├── masonry-images.js
│   │   ├── scroll-loader.js
│   │   ├── unsplash-masonry.js
│   │   └── unsplash.js
│   ├── masonry-images/
│   │   ├── index.html
│   │   ├── page2.html
│   │   ├── page3.html
│   │   ├── page4.html
│   │   └── page5.html
│   ├── page/
│   │   ├── 2.html
│   │   ├── 3.html
│   │   ├── 4.html
│   │   ├── 5.html
│   │   └── 6.html
│   ├── prefill.html
│   ├── scroll-3.html
│   ├── scroll-loader.html
│   ├── unsplash-masonry.html
│   └── unsplash.html
└── test/
    ├── _get-server.js
    ├── _with-page.js
    ├── check-last-page.js
    ├── dist-jquery.js
    ├── dist.js
    ├── history.js
    ├── html/
    │   ├── _serial-t.js
    │   ├── dist-jquery.html
    │   ├── dist.html
    │   ├── history.html
    │   ├── outlayer.html
    │   ├── page/
    │   │   ├── 2.html
    │   │   ├── 2.json
    │   │   ├── 3.html
    │   │   ├── 3.json
    │   │   ├── empty.html
    │   │   ├── fill.html
    │   │   ├── no-access.html
    │   │   ├── outlayer2.html
    │   │   └── outlayer3.html
    │   ├── page-index.html
    │   ├── page-load.html
    │   ├── path.html
    │   ├── prefill.html
    │   ├── scroll-watch-element.html
    │   ├── scroll-watch-window.html
    │   └── test.css
    ├── load-next-page-promise.js
    ├── outlayer.js
    ├── page-index.js
    ├── page-load-error.js
    ├── page-load-json.js
    ├── page-load.js
    ├── path.js
    ├── prefill.js
    └── scroll-watch.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc.js
================================================
/* eslint-env node */

module.exports = {
  plugins: [ 'metafizzy' ],
  extends: 'plugin:metafizzy/browser',
  env: {
    browser: true,
    commonjs: true,
  },
  parserOptions: {
    ecmaVersion: 2018,
  },
  globals: {
    InfiniteScroll: 'readonly',
    Promise: 'readonly',
    QUnit: 'readonly',
    serialT: 'readonly',
  },
  rules: {
  },
};


================================================
FILE: .github/contributing.md
================================================
## Submitting issues

### Reduced test case required

All bug reports and problem issues require a [**reduced test case**](https://css-tricks.com/reduced-test-cases/) or **live URL**.

+ A reduced test case clearly demonstrates the bug or issue.
+ It contains the bare minimum HTML, CSS, and JavaScript required to demonstrate the bug.
+ A link to your production site is **not** a reduced test case.

Create one by forking any one of the [CodePen demos](https://codepen.io/collection/DZejqa?grid_type=list&sort_by=item_created_at) from [the docs](https://infinite-scroll.com).

+ [Scroll & append](https://codepen.io/desandro/pen/yLaKLop)
+ [Scroll & append, vanilla JS](https://codepen.io/desandro/pen/vYXRYeY)

Providing a reduced test case is the best way to get your issue addressed. They help you point out the problem. They help me verify and debug the problem. They help others understand the problem. Without a reduced test case or live URL, your issue may be closed.

## Pull requests

Contributions are welcome! 

+ **For typos and one-line fixes,** send those right in.
+ **For larger features,** open an issue before starting any significant work. Let's discuss to see how your feature fits within Infinite Scroll's vision.
+ **Check code style.** Run `npm run lint`. Spaces in brackets, semicolons, trailing commas.
+ **Do not edit `infinite-scroll.pkgd.js`.** Make your edits to source files in `js/`.
+ **Do not run the `dist` task to update `dist/` files.** I'll take care of this when I create a new release.

Your code will be used as part of a commercial product if merged. By submitting a Pull Request, you are giving your consent for your code to be integrated into Infinite Scroll as part of a commercial product. 


================================================
FILE: .github/issue_template.md
================================================
<!-- Thanks for submitting an issue! All bug reports and problem issues require a **reduced test case** or **live URL**. Create one by forking any one of the CodePen examples from the docs. See guidelines link above. -->

**Test case:** https://codepen.io/desandro/pen/yLaKLop


================================================
FILE: .github/workflows/nodejs.yml
================================================
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [14.x]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    # - run: npm run dist
    - run: npm test
      env:
        CI: true


================================================
FILE: .gitignore
================================================
bower_components/
node_modules/


================================================
FILE: .nvmrc
================================================
14


================================================
FILE: LICENSE.txt
================================================
The MIT License (MIT)

Copyright (c) Metafizzy

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Infinite Scroll

_Automatically add next page_

See [infinite-scroll.com](https://infinite-scroll.com) for complete docs and demos.

## Install

### Download

- [infinite-scroll.pkgd.min.js](https://unpkg.com/infinite-scroll@5/dist/infinite-scroll.pkgd.min.js) minified, or
- [infinite-scroll.pkgd.js](https://unpkg.com/infinite-scroll@5/dist/infinite-scroll.pkgd.js) un-minified

### CDN

Link directly to Infinite Scroll files on [unpkg](https://unpkg.com).

``` html
<script src="https://unpkg.com/infinite-scroll@5/dist/infinite-scroll.pkgd.min.js"></script>
<!-- or -->
<script src="https://unpkg.com/infinite-scroll@5/dist/infinite-scroll.pkgd.js"></script>
```

### Package managers

npm: `npm install infinite-scroll`

Yarn: `yarn add infinite-scroll`

## License

Infinite Scroll v5 is licensed under the MIT License.

Whereas earlier versions of Infinite Scroll were previously dual licensed for commercial and closed-source usage, Infinite Scroll v5 licensing no longer has this distinction. You are free to use Infinite Scroll v5 in commercial and closed-source applications. 

## Usage

Infinite Scroll works on a container element with its child item elements

``` html
<div class="container">
  <article class="post">...</article>
  <article class="post">...</article>
  <article class="post">...</article>
  ...
</div>
```

### Options

``` js
let infScroll = new InfiniteScroll( '.container', {
  // defaults listed

  path: undefined,
  // REQUIRED. Determines the URL for the next page
  // Set to selector string to use the href of the next page's link
  // path: '.pagination__next'
  // Or set with {{#}} in place of the page number in the url
  // path: '/blog/page/{{#}}'
  // or set with function
  // path: function() {
  //   return return '/articles/P' + ( ( this.loadCount + 1 ) * 10 );
  // }

  append: undefined,
  // REQUIRED for appending content
  // Appends selected elements from loaded page to the container

  checkLastPage: true,
  // Checks if page has path selector element
  // Set to string if path is not set as selector string:
  //   checkLastPage: '.pagination__next'

  prefill: false,
  // Loads and appends pages on intialization until scroll requirement is met.

  responseBody: 'text',
  // Sets the method used on the response.
  // Set to 'json' to load JSON.

  domParseResponse: true,
  // enables parsing response body into a DOM
  // disable to load flat text

  fetchOptions: undefined,
  // sets custom settings for the fetch() request
  // for setting headers, cors, or POST method
  // can be set to an object, or a function that returns an object

  outlayer: false,
  // Integrates Masonry, Isotope or Packery
  // Appended items will be added to the layout

  scrollThreshold: 400,
  // Sets the distance between the viewport to scroll area
  // for scrollThreshold event to be triggered.

  elementScroll: false,
  // Sets scroller to an element for overflow element scrolling

  loadOnScroll: true,
  // Loads next page when scroll crosses over scrollThreshold

  history: 'replace',
  // Changes the browser history and URL.
  // Set to 'push' to use history.pushState()
  //    to create new history entries for each page change.

  historyTitle: true,
  // Updates the window title. Requires history enabled.

  hideNav: undefined,
  // Hides navigation element

  status: undefined,
  // Displays status elements indicating state of page loading:
  // .infinite-scroll-request, .infinite-scroll-load, .infinite-scroll-error
  // status: '.page-load-status'

  button: undefined,
  // Enables a button to load pages on click
  // button: '.load-next-button'

  onInit: undefined,
  // called on initialization
  // useful for binding events on init
  // onInit: function() {
  //   this.on( 'append', function() {...})
  // }

  debug: false,
  // Logs events and state changes to the console.
})
```

## Browser support

Infinite Scroll v4 supports Chrome 60+, Edge 79+, Firefox 55+, Safari 11+.

For IE10 and Android 4 support, try [Infinite Scroll v3](https://v3.infinite-scroll.com/).

## Development

This package is developed with Node.js v14 and npm v6. Manage Node and npm version with [nvm](https://github.com/nvm-sh/nvm).

``` sh
nvm use
```

Install dependencies

``` sh
npm install
```

Lint

``` sh
npm run lint
```

Run tests

``` sh
npm test
```

---

By [Metafizzy 🌈🐻](https://metafizzy.co)


================================================
FILE: bin/build-dist.js
================================================
const fs = require('fs');
const { execSync } = require('child_process');
const { minify } = require('terser');

const indexPath = 'js/index.js';
const distPath = 'dist/infinite-scroll.pkgd.js';
const distMinPath = 'dist/infinite-scroll.pkgd.min.js';

let indexContent = fs.readFileSync( `./${indexPath}`, 'utf8' );
// get file paths from index.js
let cjsBlockRegex = /module\.exports = factory\([\w ,'.\-()/\n]+;/i;
let cjsBlockMatch = indexContent.match( cjsBlockRegex );
let jsPaths = cjsBlockMatch[0].match( /require\('([.\-/\w]+)'\)/gi );

jsPaths = jsPaths.map( function( path ) {
  return path.replace( "require('.", 'js' ).replace( "')", '.js' );
} );

let paths = [
  'node_modules/jquery-bridget/jquery-bridget.js',
  'node_modules/ev-emitter/ev-emitter.js',
  'node_modules/fizzy-ui-utils/utils.js',
  ...jsPaths,
  'node_modules/imagesloaded/imagesloaded.js',
];

// concatenate files
execSync(`cat ${paths.join(' ')} > ${distPath}`);

// add banner
let banner = indexContent.split(' */')[0] + ' */\n\n';
banner = banner.replace( 'Infinite Scroll', 'Infinite Scroll PACKAGED' );
let distJsContent = fs.readFileSync( distPath, 'utf8' );
distJsContent = banner + distJsContent;
fs.writeFileSync( distPath, distJsContent );

// minify
( async function() {
  let { code } = await minify( distJsContent, { mangle: true } );
  fs.writeFileSync( distMinPath, code );
} )();


================================================
FILE: bin/version.js
================================================
/* eslint-env node */

const fs = require('fs');
const path = require('path');
const { version } = require('../package.json');

function dir( file ) {
  return path.resolve( __dirname, file );
}

let content = fs.readFileSync( dir('../js/index.js'), 'utf8' );
content = content.replace( /Infinite Scroll v[\w.-]+/,
    `Infinite Scroll v${version}` );
fs.writeFileSync( dir('../js/index.js'), content, 'utf8' );


================================================
FILE: bower.json
================================================
{
  "name": "infinite-scroll",
  "authors": [
    "David DeSandro <desandrocodes@gmail.com>"
  ],
  "description": "infinite scroll",
  "main": "js/index.js",
  "dependencies": {
    "ev-emitter": "^2.1.0",
    "fizzy-ui-utils": "^3.0.0"
  },
  "devDependencies": {},
  "keywords": [
    "infinite scroll",
    "infinite",
    "scroll",
    "plugin"
  ],
  "license": "MIT",
  "homepage": "https://infinite-scroll.com",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests",
    "sandbox",
    "package.json",
    "gulpfile.js"
  ]
}


================================================
FILE: dist/infinite-scroll.pkgd.js
================================================
/*!
 * Infinite Scroll PACKAGED v5.0.0
 * Automatically add next page
 * MIT License
 * https://infinite-scroll.com
 * Copyright 2018-2025 Metafizzy
 */

/**
 * Bridget makes jQuery widgets
 * v3.0.0
 * MIT license
 */

( function( window, factory ) {
  // module definition
 if ( typeof module == 'object' && module.exports ) {
   // CommonJS
   module.exports = factory(
       window,
       require('jquery'),
   );
 } else {
   // browser global
   window.jQueryBridget = factory(
       window,
       window.jQuery,
   );
 }

}( window, function factory( window, jQuery ) {

// ----- utils ----- //

// helper function for logging errors
// $.error breaks jQuery chaining
let console = window.console;
let logError = typeof console == 'undefined' ? function() {} :
  function( message ) {
    console.error( message );
  };

// ----- jQueryBridget ----- //

function jQueryBridget( namespace, PluginClass, $ ) {
  $ = $ || jQuery || window.jQuery;
  if ( !$ ) {
    return;
  }

  // add option method -> $().plugin('option', {...})
  if ( !PluginClass.prototype.option ) {
    // option setter
    PluginClass.prototype.option = function( opts ) {
      if ( !opts ) return;

      this.options = Object.assign( this.options || {}, opts );
    };
  }

  // make jQuery plugin
  $.fn[ namespace ] = function( arg0, ...args ) {
    if ( typeof arg0 == 'string' ) {
      // method call $().plugin( 'methodName', { options } )
      return methodCall( this, arg0, args );
    }
    // just $().plugin({ options })
    plainCall( this, arg0 );
    return this;
  };

  // $().plugin('methodName')
  function methodCall( $elems, methodName, args ) {
    let returnValue;
    let pluginMethodStr = `$().${namespace}("${methodName}")`;

    $elems.each( function( i, elem ) {
      // get instance
      let instance = $.data( elem, namespace );
      if ( !instance ) {
        logError( `${namespace} not initialized.` +
          ` Cannot call method ${pluginMethodStr}` );
        return;
      }

      let method = instance[ methodName ];
      if ( !method || methodName.charAt( 0 ) == '_' ) {
        logError(`${pluginMethodStr} is not a valid method`);
        return;
      }

      // apply method, get return value
      let value = method.apply( instance, args );
      // set return value if value is returned, use only first value
      returnValue = returnValue === undefined ? value : returnValue;
    } );

    return returnValue !== undefined ? returnValue : $elems;
  }

  function plainCall( $elems, options ) {
    $elems.each( function( i, elem ) {
      let instance = $.data( elem, namespace );
      if ( instance ) {
        // set options & init
        instance.option( options );
        instance._init();
      } else {
        // initialize new instance
        instance = new PluginClass( elem, options );
        $.data( elem, namespace, instance );
      }
    } );
  }

}

// -----  ----- //

return jQueryBridget;

} ) );
/**
 * EvEmitter v2.0.0
 * Lil' event emitter
 * MIT License
 */

( function( global, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS - Browserify, Webpack
    module.exports = factory();
  } else {
    // Browser globals
    global.EvEmitter = factory();
  }

}( typeof window != 'undefined' ? window : this, function() {

function EvEmitter() {}

let proto = EvEmitter.prototype;

proto.on = function( eventName, listener ) {
  if ( !eventName || !listener ) return this;

  // set events hash
  let events = this._events = this._events || {};
  // set listeners array
  let listeners = events[ eventName ] = events[ eventName ] || [];
  // only add once
  if ( !listeners.includes( listener ) ) {
    listeners.push( listener );
  }

  return this;
};

proto.once = function( eventName, listener ) {
  if ( !eventName || !listener ) return this;

  // add event
  this.on( eventName, listener );
  // set once flag
  // set onceEvents hash
  let onceEvents = this._onceEvents = this._onceEvents || {};
  // set onceListeners object
  let onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
  // set flag
  onceListeners[ listener ] = true;

  return this;
};

proto.off = function( eventName, listener ) {
  let listeners = this._events && this._events[ eventName ];
  if ( !listeners || !listeners.length ) return this;

  let index = listeners.indexOf( listener );
  if ( index != -1 ) {
    listeners.splice( index, 1 );
  }

  return this;
};

proto.emitEvent = function( eventName, args ) {
  let listeners = this._events && this._events[ eventName ];
  if ( !listeners || !listeners.length ) return this;

  // copy over to avoid interference if .off() in listener
  listeners = listeners.slice( 0 );
  args = args || [];
  // once stuff
  let onceListeners = this._onceEvents && this._onceEvents[ eventName ];

  for ( let listener of listeners ) {
    let isOnce = onceListeners && onceListeners[ listener ];
    if ( isOnce ) {
      // remove listener
      // remove before trigger to prevent recursion
      this.off( eventName, listener );
      // unset once flag
      delete onceListeners[ listener ];
    }
    // trigger listener
    listener.apply( this, args );
  }

  return this;
};

proto.allOff = function() {
  delete this._events;
  delete this._onceEvents;
  return this;
};

return EvEmitter;

} ) );
/**
 * Fizzy UI utils v3.0.0
 * MIT license
 */

( function( global, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory( global );
  } else {
    // browser global
    global.fizzyUIUtils = factory( global );
  }

}( this, function factory( global ) {

let utils = {};

// ----- extend ----- //

// extends objects
utils.extend = function( a, b ) {
  return Object.assign( a, b );
};

// ----- modulo ----- //

utils.modulo = function( num, div ) {
  return ( ( num % div ) + div ) % div;
};

// ----- makeArray ----- //

// turn element or nodeList into an array
utils.makeArray = function( obj ) {
  // use object if already an array
  if ( Array.isArray( obj ) ) return obj;

  // return empty array if undefined or null. #6
  if ( obj === null || obj === undefined ) return [];

  let isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
  // convert nodeList to array
  if ( isArrayLike ) return [ ...obj ];

  // array of single index
  return [ obj ];
};

// ----- removeFrom ----- //

utils.removeFrom = function( ary, obj ) {
  let index = ary.indexOf( obj );
  if ( index != -1 ) {
    ary.splice( index, 1 );
  }
};

// ----- getParent ----- //

utils.getParent = function( elem, selector ) {
  while ( elem.parentNode && elem != document.body ) {
    elem = elem.parentNode;
    if ( elem.matches( selector ) ) return elem;
  }
};

// ----- getQueryElement ----- //

// use element as selector string
utils.getQueryElement = function( elem ) {
  if ( typeof elem == 'string' ) {
    return document.querySelector( elem );
  }
  return elem;
};

// ----- handleEvent ----- //

// enable .ontype to trigger from .addEventListener( elem, 'type' )
utils.handleEvent = function( event ) {
  let method = 'on' + event.type;
  if ( this[ method ] ) {
    this[ method ]( event );
  }
};

// ----- filterFindElements ----- //

utils.filterFindElements = function( elems, selector ) {
  // make array of elems
  elems = utils.makeArray( elems );

  return elems
    // check that elem is an actual element
    .filter( ( elem ) => elem instanceof HTMLElement )
    .reduce( ( ffElems, elem ) => {
      // add elem if no selector
      if ( !selector ) {
        ffElems.push( elem );
        return ffElems;
      }
      // filter & find items if we have a selector
      // filter
      if ( elem.matches( selector ) ) {
        ffElems.push( elem );
      }
      // find children
      let childElems = elem.querySelectorAll( selector );
      // concat childElems to filterFound array
      ffElems = ffElems.concat( ...childElems );
      return ffElems;
    }, [] );
};

// ----- debounceMethod ----- //

utils.debounceMethod = function( _class, methodName, threshold ) {
  threshold = threshold || 100;
  // original method
  let method = _class.prototype[ methodName ];
  let timeoutName = methodName + 'Timeout';

  _class.prototype[ methodName ] = function() {
    clearTimeout( this[ timeoutName ] );

    let args = arguments;
    this[ timeoutName ] = setTimeout( () => {
      method.apply( this, args );
      delete this[ timeoutName ];
    }, threshold );
  };
};

// ----- docReady ----- //

utils.docReady = function( onDocReady ) {
  let readyState = document.readyState;
  if ( readyState == 'complete' || readyState == 'interactive' ) {
    // do async to allow for other scripts to run. metafizzy/flickity#441
    setTimeout( onDocReady );
  } else {
    document.addEventListener( 'DOMContentLoaded', onDocReady );
  }
};

// ----- htmlInit ----- //

// http://bit.ly/3oYLusc
utils.toDashed = function( str ) {
  return str.replace( /(.)([A-Z])/g, function( match, $1, $2 ) {
    return $1 + '-' + $2;
  } ).toLowerCase();
};

let console = global.console;

// allow user to initialize classes via [data-namespace] or .js-namespace class
// htmlInit( Widget, 'widgetName' )
// options are parsed from data-namespace-options
utils.htmlInit = function( WidgetClass, namespace ) {
  utils.docReady( function() {
    let dashedNamespace = utils.toDashed( namespace );
    let dataAttr = 'data-' + dashedNamespace;
    let dataAttrElems = document.querySelectorAll( `[${dataAttr}]` );
    let jQuery = global.jQuery;

    [ ...dataAttrElems ].forEach( ( elem ) => {
      let attr = elem.getAttribute( dataAttr );
      let options;
      try {
        options = attr && JSON.parse( attr );
      } catch ( error ) {
        // log error, do not initialize
        if ( console ) {
          console.error( `Error parsing ${dataAttr} on ${elem.className}: ${error}` );
        }
        return;
      }
      // initialize
      let instance = new WidgetClass( elem, options );
      // make available via $().data('namespace')
      if ( jQuery ) {
        jQuery.data( elem, namespace, instance );
      }
    } );

  } );
};

// -----  ----- //

return utils;

} ) );
// core
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('ev-emitter'),
        require('fizzy-ui-utils'),
    );
  } else {
    // browser global
    window.InfiniteScroll = factory(
        window,
        window.EvEmitter,
        window.fizzyUIUtils,
    );
  }

}( window, function factory( window, EvEmitter, utils ) {

let jQuery = window.jQuery;
// internal store of all InfiniteScroll intances
let instances = {};

function InfiniteScroll( element, options ) {
  let queryElem = utils.getQueryElement( element );

  if ( !queryElem ) {
    console.error( 'Bad element for InfiniteScroll: ' + ( queryElem || element ) );
    return;
  }
  element = queryElem;
  // do not initialize twice on same element
  if ( element.infiniteScrollGUID ) {
    let instance = instances[ element.infiniteScrollGUID ];
    instance.option( options );
    return instance;
  }

  this.element = element;
  // options
  this.options = { ...InfiniteScroll.defaults };
  this.option( options );
  // add jQuery
  if ( jQuery ) {
    this.$element = jQuery( this.element );
  }

  this.create();
}

// defaults
InfiniteScroll.defaults = {
  // path: null,
  // hideNav: null,
  // debug: false,
};

// create & destroy methods
InfiniteScroll.create = {};
InfiniteScroll.destroy = {};

let proto = InfiniteScroll.prototype;
// inherit EvEmitter
Object.assign( proto, EvEmitter.prototype );

// --------------------------  -------------------------- //

// globally unique identifiers
let GUID = 0;

proto.create = function() {
  // create core
  // add id for InfiniteScroll.data
  let id = this.guid = ++GUID;
  this.element.infiniteScrollGUID = id; // expando
  instances[ id ] = this; // associate via id
  // properties
  this.pageIndex = 1; // default to first page
  this.loadCount = 0;
  this.updateGetPath();
  // bail if getPath not set, or returns falsey #776
  let hasPath = this.getPath && this.getPath();
  if ( !hasPath ) {
    console.error('Disabling InfiniteScroll');
    return;
  }
  this.updateGetAbsolutePath();
  this.log( 'initialized', [ this.element.className ] );
  this.callOnInit();
  // create features
  for ( let method in InfiniteScroll.create ) {
    InfiniteScroll.create[ method ].call( this );
  }
};

proto.option = function( opts ) {
  Object.assign( this.options, opts );
};

// call onInit option, used for binding events on init
proto.callOnInit = function() {
  let onInit = this.options.onInit;
  if ( onInit ) {
    onInit.call( this, this );
  }
};

// ----- events ----- //

proto.dispatchEvent = function( type, event, args ) {
  this.log( type, args );
  let emitArgs = event ? [ event ].concat( args ) : args;
  this.emitEvent( type, emitArgs );
  // trigger jQuery event
  if ( !jQuery || !this.$element ) {
    return;
  }
  // namespace jQuery event
  type += '.infiniteScroll';
  let $event = type;
  if ( event ) {
    // create jQuery event
    /* eslint-disable-next-line new-cap */
    let jQEvent = jQuery.Event( event );
    jQEvent.type = type;
    $event = jQEvent;
  }
  this.$element.trigger( $event, args );
};

let loggers = {
  initialized: ( className ) => `on ${className}`,
  request: ( path ) => `URL: ${path}`,
  load: ( response, path ) => `${response.title || ''}. URL: ${path}`,
  error: ( error, path ) => `${error}. URL: ${path}`,
  append: ( response, path, items ) => `${items.length} items. URL: ${path}`,
  last: ( response, path ) => `URL: ${path}`,
  history: ( title, path ) => `URL: ${path}`,
  pageIndex: function( index, origin ) {
    return `current page determined to be: ${index} from ${origin}`;
  },
};

// log events
proto.log = function( type, args ) {
  if ( !this.options.debug ) return;

  let message = `[InfiniteScroll] ${type}`;
  let logger = loggers[ type ];
  if ( logger ) message += '. ' + logger.apply( this, args );
  console.log( message );
};

// -------------------------- methods used amoung features -------------------------- //

proto.updateMeasurements = function() {
  this.windowHeight = window.innerHeight;
  let rect = this.element.getBoundingClientRect();
  this.top = rect.top + window.scrollY;
};

proto.updateScroller = function() {
  let elementScroll = this.options.elementScroll;
  if ( !elementScroll ) {
    // default, use window
    this.scroller = window;
    return;
  }
  // if true, set to element, otherwise use option
  this.scroller = elementScroll === true ? this.element :
    utils.getQueryElement( elementScroll );
  if ( !this.scroller ) {
    throw new Error(`Unable to find elementScroll: ${elementScroll}`);
  }
};

// -------------------------- page path -------------------------- //

proto.updateGetPath = function() {
  let optPath = this.options.path;
  if ( !optPath ) {
    console.error(`InfiniteScroll path option required. Set as: ${optPath}`);
    return;
  }
  // function
  let type = typeof optPath;
  if ( type == 'function' ) {
    this.getPath = optPath;
    return;
  }
  // template string: '/pages/{{#}}.html'
  let templateMatch = type == 'string' && optPath.match('{{#}}');
  if ( templateMatch ) {
    this.updateGetPathTemplate( optPath );
    return;
  }
  // selector: '.next-page-selector'
  this.updateGetPathSelector( optPath );
};

proto.updateGetPathTemplate = function( optPath ) {
  // set getPath with template string
  this.getPath = () => {
    let nextIndex = this.pageIndex + 1;
    return optPath.replace( '{{#}}', nextIndex );
  };
  // get pageIndex from location
  // convert path option into regex to look for pattern in location
  // escape query (?) in url, allows for parsing GET parameters
  let regexString = optPath
    .replace( /(\\\?|\?)/, '\\?' )
    .replace( '{{#}}', '(\\d\\d?\\d?)' );
  let templateRe = new RegExp( regexString );
  let match = location.href.match( templateRe );

  if ( match ) {
    this.pageIndex = parseInt( match[1], 10 );
    this.log( 'pageIndex', [ this.pageIndex, 'template string' ] );
  }
};

let pathRegexes = [
  // WordPress & Tumblr - example.com/page/2
  // Jekyll - example.com/page2
  /^(.*?\/?page\/?)(\d\d?\d?)(.*?$)/,
  // Drupal - example.com/?page=1
  /^(.*?\/?\?page=)(\d\d?\d?)(.*?$)/,
  // catch all, last occurence of a number
  /(.*?)(\d\d?\d?)(?!.*\d)(.*?$)/,
];

// try matching href to pathRegexes patterns
let getPathParts = InfiniteScroll.getPathParts = function( href ) {
  if ( !href ) return;
  for ( let regex of pathRegexes ) {
    let match = href.match( regex );
    if ( match ) {
      let [ , begin, index, end ] = match;
      return { begin, index, end };
    }
  }
};

proto.updateGetPathSelector = function( optPath ) {
  // parse href of link: '.next-page-link'
  let hrefElem = document.querySelector( optPath );
  if ( !hrefElem ) {
    console.error(`Bad InfiniteScroll path option. Next link not found: ${optPath}`);
    return;
  }

  let href = hrefElem.getAttribute('href');
  let pathParts = getPathParts( href );
  if ( !pathParts ) {
    console.error(`InfiniteScroll unable to parse next link href: ${href}`);
    return;
  }

  let { begin, index, end } = pathParts;
  this.isPathSelector = true; // flag for checkLastPage()
  this.getPath = () => begin + ( this.pageIndex + 1 ) + end;
  // get pageIndex from href
  this.pageIndex = parseInt( index, 10 ) - 1;
  this.log( 'pageIndex', [ this.pageIndex, 'next link' ] );
};

proto.updateGetAbsolutePath = function() {
  let path = this.getPath();
  // path doesn't start with http or /
  let isAbsolute = path.match( /^http/ ) || path.match( /^\// );
  if ( isAbsolute ) {
    this.getAbsolutePath = this.getPath;
    return;
  }

  let { pathname } = location;
  // query parameter #829. example.com/?pg=2
  let isQuery = path.match( /^\?/ );
  // /foo/bar/index.html => /foo/bar
  let directory = pathname.substring( 0, pathname.lastIndexOf('/') );
  let pathStart = isQuery ? pathname : directory + '/';

  this.getAbsolutePath = () => pathStart + this.getPath();
};

// -------------------------- nav -------------------------- //

// hide navigation
InfiniteScroll.create.hideNav = function() {
  let nav = utils.getQueryElement( this.options.hideNav );
  if ( !nav ) return;

  nav.style.display = 'none';
  this.nav = nav;
};

InfiniteScroll.destroy.hideNav = function() {
  if ( this.nav ) this.nav.style.display = '';
};

// -------------------------- destroy -------------------------- //

proto.destroy = function() {
  this.allOff(); // remove all event listeners
  // call destroy methods
  for ( let method in InfiniteScroll.destroy ) {
    InfiniteScroll.destroy[ method ].call( this );
  }

  delete this.element.infiniteScrollGUID;
  delete instances[ this.guid ];
  // remove jQuery data. #807
  if ( jQuery && this.$element ) {
    jQuery.removeData( this.element, 'infiniteScroll' );
  }
};

// -------------------------- utilities -------------------------- //

// https://remysharp.com/2010/07/21/throttling-function-calls
InfiniteScroll.throttle = function( fn, threshold ) {
  threshold = threshold || 200;
  let last, timeout;

  return function() {
    let now = +new Date();
    let args = arguments;
    let trigger = () => {
      last = now;
      fn.apply( this, args );
    };
    if ( last && now < last + threshold ) {
      // hold on to it
      clearTimeout( timeout );
      timeout = setTimeout( trigger, threshold );
    } else {
      trigger();
    }
  };
};

InfiniteScroll.data = function( elem ) {
  elem = utils.getQueryElement( elem );
  let id = elem && elem.infiniteScrollGUID;
  return id && instances[ id ];
};

// set internal jQuery, for Webpack + jQuery v3
InfiniteScroll.setJQuery = function( jqry ) {
  jQuery = jqry;
};

// -------------------------- setup -------------------------- //

utils.htmlInit( InfiniteScroll, 'infinite-scroll' );

// add noop _init method for jQuery Bridget. #768
proto._init = function() {};

let { jQueryBridget } = window;
if ( jQuery && jQueryBridget ) {
  jQueryBridget( 'infiniteScroll', InfiniteScroll, jQuery );
}

// --------------------------  -------------------------- //

return InfiniteScroll;

} ) );
// page-load
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('./core'),
    );
  } else {
    // browser global
    factory(
        window,
        window.InfiniteScroll,
    );
  }

}( window, function factory( window, InfiniteScroll ) {

let proto = InfiniteScroll.prototype;

Object.assign( InfiniteScroll.defaults, {
  // append: false,
  loadOnScroll: true,
  checkLastPage: true,
  responseBody: 'text',
  domParseResponse: true,
  // prefill: false,
  // outlayer: null,
} );

InfiniteScroll.create.pageLoad = function() {
  this.canLoad = true;
  this.on( 'scrollThreshold', this.onScrollThresholdLoad );
  this.on( 'load', this.checkLastPage );
  if ( this.options.outlayer ) {
    this.on( 'append', this.onAppendOutlayer );
  }
};

proto.onScrollThresholdLoad = function() {
  if ( this.options.loadOnScroll ) this.loadNextPage();
};

let domParser = new DOMParser();

proto.loadNextPage = function() {
  if ( this.isLoading || !this.canLoad ) return;

  let { responseBody, domParseResponse, fetchOptions } = this.options;
  let path = this.getAbsolutePath();
  this.isLoading = true;
  if ( typeof fetchOptions == 'function' ) fetchOptions = fetchOptions();

  let fetchPromise = fetch( path, fetchOptions )
    .then( ( response ) => {
      if ( !response.ok ) {
        let error = new Error( response.statusText );
        this.onPageError( error, path, response );
        return { response };
      }

      return response[ responseBody ]().then( ( body ) => {
        let canDomParse = responseBody == 'text' && domParseResponse;
        if ( canDomParse ) {
          body = domParser.parseFromString( body, 'text/html' );
        }
        if ( response.status == 204 ) {
          this.lastPageReached( body, path );
          return { body, response };
        } else {
          return this.onPageLoad( body, path, response );
        }
      } );
    } )
    .catch( ( error ) => {
      this.onPageError( error, path );
    } );

  this.dispatchEvent( 'request', null, [ path, fetchPromise ] );

  return fetchPromise;
};

proto.onPageLoad = function( body, path, response ) {
  // done loading if not appending
  if ( !this.options.append ) {
    this.isLoading = false;
  }
  this.pageIndex++;
  this.loadCount++;
  this.dispatchEvent( 'load', null, [ body, path, response ] );
  return this.appendNextPage( body, path, response );
};

proto.appendNextPage = function( body, path, response ) {
  let { append, responseBody, domParseResponse } = this.options;
  // do not append json
  let isDocument = responseBody == 'text' && domParseResponse;
  if ( !isDocument || !append ) return { body, response };

  let items = body.querySelectorAll( append );
  let promiseValue = { body, response, items };
  // last page hit if no items. #840
  if ( !items || !items.length ) {
    this.lastPageReached( body, path );
    return promiseValue;
  }

  let fragment = getItemsFragment( items );
  let appendReady = () => {
    this.appendItems( items, fragment );
    this.isLoading = false;
    this.dispatchEvent( 'append', null, [ body, path, items, response ] );
    return promiseValue;
  };

  // TODO add hook for option to trigger appendReady
  if ( this.options.outlayer ) {
    return this.appendOutlayerItems( fragment, appendReady );
  } else {
    return appendReady();
  }
};

proto.appendItems = function( items, fragment ) {
  if ( !items || !items.length ) return;

  // get fragment if not provided
  fragment = fragment || getItemsFragment( items );
  refreshScripts( fragment );
  this.element.appendChild( fragment );
};

function getItemsFragment( items ) {
  // add items to fragment
  let fragment = document.createDocumentFragment();
  if ( items ) fragment.append( ...items );
  return fragment;
}

// replace <script>s with copies so they load
// <script>s added by InfiniteScroll will not load
// similar to https://stackoverflow.com/questions/610995
function refreshScripts( fragment ) {
  let scripts = fragment.querySelectorAll('script');
  for ( let script of scripts ) {
    let freshScript = document.createElement('script');
    // copy attributes
    let attrs = script.attributes;
    for ( let attr of attrs ) {
      freshScript.setAttribute( attr.name, attr.value );
    }
    // copy inner script code. #718, #782
    freshScript.innerHTML = script.innerHTML;
    script.parentNode.replaceChild( freshScript, script );
  }
}

// ----- outlayer ----- //

proto.appendOutlayerItems = function( fragment, appendReady ) {
  let imagesLoaded = InfiniteScroll.imagesLoaded || window.imagesLoaded;
  if ( !imagesLoaded ) {
    console.error('[InfiniteScroll] imagesLoaded required for outlayer option');
    this.isLoading = false;
    return;
  }
  // append once images loaded
  return new Promise( function( resolve ) {
    imagesLoaded( fragment, function() {
      let bodyResponse = appendReady();
      resolve( bodyResponse );
    } );
  } );
};

proto.onAppendOutlayer = function( response, path, items ) {
  this.options.outlayer.appended( items );
};

// ----- checkLastPage ----- //

// check response for next element
proto.checkLastPage = function( body, path ) {
  let { checkLastPage, path: pathOpt } = this.options;
  if ( !checkLastPage ) return;

  // if path is function, check if next path is truthy
  if ( typeof pathOpt == 'function' ) {
    let nextPath = this.getPath();
    if ( !nextPath ) {
      this.lastPageReached( body, path );
      return;
    }
  }
  // get selector from checkLastPage or path option
  let selector;
  if ( typeof checkLastPage == 'string' ) {
    selector = checkLastPage;
  } else if ( this.isPathSelector ) {
    // path option is selector string
    selector = pathOpt;
  }
  // check last page for selector
  // bail if no selector or not document response
  if ( !selector || !body.querySelector ) return;

  // check if response has selector
  let nextElem = body.querySelector( selector );
  if ( !nextElem ) this.lastPageReached( body, path );
};

proto.lastPageReached = function( body, path ) {
  this.canLoad = false;
  this.dispatchEvent( 'last', null, [ body, path ] );
};

// ----- error ----- //

proto.onPageError = function( error, path, response ) {
  this.isLoading = false;
  this.canLoad = false;
  this.dispatchEvent( 'error', null, [ error, path, response ] );
  return error;
};

// -------------------------- prefill -------------------------- //

InfiniteScroll.create.prefill = function() {
  if ( !this.options.prefill ) return;

  let append = this.options.append;
  if ( !append ) {
    console.error(`append option required for prefill. Set as :${append}`);
    return;
  }
  this.updateMeasurements();
  this.updateScroller();
  this.isPrefilling = true;
  this.on( 'append', this.prefill );
  this.once( 'error', this.stopPrefill );
  this.once( 'last', this.stopPrefill );
  this.prefill();
};

proto.prefill = function() {
  let distance = this.getPrefillDistance();
  this.isPrefilling = distance >= 0;
  if ( this.isPrefilling ) {
    this.log('prefill');
    this.loadNextPage();
  } else {
    this.stopPrefill();
  }
};

proto.getPrefillDistance = function() {
  // element scroll
  if ( this.options.elementScroll ) {
    return this.scroller.clientHeight - this.scroller.scrollHeight;
  }
  // window
  return this.windowHeight - this.element.clientHeight;
};

proto.stopPrefill = function() {
  this.log('stopPrefill');
  this.off( 'append', this.prefill );
};

// --------------------------  -------------------------- //

return InfiniteScroll;

} ) );
// scroll-watch
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('./core'),
        require('fizzy-ui-utils'),
    );
  } else {
    // browser global
    factory(
        window,
        window.InfiniteScroll,
        window.fizzyUIUtils,
    );
  }

}( window, function factory( window, InfiniteScroll, utils ) {

let proto = InfiniteScroll.prototype;

// default options
Object.assign( InfiniteScroll.defaults, {
  scrollThreshold: 400,
  // elementScroll: null,
} );

InfiniteScroll.create.scrollWatch = function() {
  // events
  this.pageScrollHandler = this.onPageScroll.bind( this );
  this.resizeHandler = this.onResize.bind( this );

  let scrollThreshold = this.options.scrollThreshold;
  let isEnable = scrollThreshold || scrollThreshold === 0;
  if ( isEnable ) this.enableScrollWatch();
};

InfiniteScroll.destroy.scrollWatch = function() {
  this.disableScrollWatch();
};

proto.enableScrollWatch = function() {
  if ( this.isScrollWatching ) return;

  this.isScrollWatching = true;
  this.updateMeasurements();
  this.updateScroller();
  // TODO disable after error?
  this.on( 'last', this.disableScrollWatch );
  this.bindScrollWatchEvents( true );
};

proto.disableScrollWatch = function() {
  if ( !this.isScrollWatching ) return;

  this.bindScrollWatchEvents( false );
  delete this.isScrollWatching;
};

proto.bindScrollWatchEvents = function( isBind ) {
  let addRemove = isBind ? 'addEventListener' : 'removeEventListener';
  this.scroller[ addRemove ]( 'scroll', this.pageScrollHandler );
  window[ addRemove ]( 'resize', this.resizeHandler );
};

proto.onPageScroll = InfiniteScroll.throttle( function() {
  let distance = this.getBottomDistance();
  if ( distance <= this.options.scrollThreshold ) {
    this.dispatchEvent('scrollThreshold');
  }
} );

proto.getBottomDistance = function() {
  let bottom, scrollY;
  if ( this.options.elementScroll ) {
    bottom = this.scroller.scrollHeight;
    scrollY = this.scroller.scrollTop + this.scroller.clientHeight;
  } else {
    bottom = this.top + this.element.clientHeight;
    scrollY = window.scrollY + this.windowHeight;
  }
  return bottom - scrollY;
};

proto.onResize = function() {
  this.updateMeasurements();
};

utils.debounceMethod( InfiniteScroll, 'onResize', 150 );

// --------------------------  -------------------------- //

return InfiniteScroll;

} ) );
// history
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('./core'),
        require('fizzy-ui-utils'),
    );
  } else {
    // browser global
    factory(
        window,
        window.InfiniteScroll,
        window.fizzyUIUtils,
    );
  }

}( window, function factory( window, InfiniteScroll, utils ) {

let proto = InfiniteScroll.prototype;

Object.assign( InfiniteScroll.defaults, {
  history: 'replace',
  // historyTitle: false,
} );

let link = document.createElement('a');

// ----- create/destroy ----- //

InfiniteScroll.create.history = function() {
  if ( !this.options.history ) return;

  // check for same origin
  link.href = this.getAbsolutePath();
  // MS Edge does not have origin on link
  // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12236493/
  let linkOrigin = link.origin || link.protocol + '//' + link.host;
  let isSameOrigin = linkOrigin == location.origin;
  if ( !isSameOrigin ) {
    console.error( '[InfiniteScroll] cannot set history with different origin: ' +
      `${link.origin} on ${location.origin} . History behavior disabled.` );
    return;
  }

  // two ways to handle changing history
  if ( this.options.append ) {
    this.createHistoryAppend();
  } else {
    this.createHistoryPageLoad();
  }
};

proto.createHistoryAppend = function() {
  this.updateMeasurements();
  this.updateScroller();
  // array of scroll positions of appended pages
  this.scrollPages = [
    // first page
    {
      top: 0,
      path: location.href,
      title: document.title,
    },
  ];
  this.scrollPage = this.scrollPages[0];
  // events
  this.scrollHistoryHandler = this.onScrollHistory.bind( this );
  this.unloadHandler = this.onUnload.bind( this );
  this.scroller.addEventListener( 'scroll', this.scrollHistoryHandler );
  this.on( 'append', this.onAppendHistory );
  this.bindHistoryAppendEvents( true );
};

proto.bindHistoryAppendEvents = function( isBind ) {
  let addRemove = isBind ? 'addEventListener' : 'removeEventListener';
  this.scroller[ addRemove ]( 'scroll', this.scrollHistoryHandler );
  window[ addRemove ]( 'unload', this.unloadHandler );
};

proto.createHistoryPageLoad = function() {
  this.on( 'load', this.onPageLoadHistory );
};

InfiniteScroll.destroy.history =
proto.destroyHistory = function() {
  let isHistoryAppend = this.options.history && this.options.append;
  if ( isHistoryAppend ) {
    this.bindHistoryAppendEvents( false );
  }
};

// ----- append history ----- //

proto.onAppendHistory = function( response, path, items ) {
  // do not proceed if no items. #779
  if ( !items || !items.length ) return;

  let firstItem = items[0];
  let elemScrollY = this.getElementScrollY( firstItem );
  // resolve path
  link.href = path;
  // add page data to hash
  this.scrollPages.push({
    top: elemScrollY,
    path: link.href,
    title: response.title,
  });
};

proto.getElementScrollY = function( elem ) {
  if ( this.options.elementScroll ) {
    return elem.offsetTop - this.top;
  } else {
    let rect = elem.getBoundingClientRect();
    return rect.top + window.scrollY;
  }
};

proto.onScrollHistory = function() {
  // cycle through positions, find biggest without going over
  let scrollPage = this.getClosestScrollPage();
  // set history if changed
  if ( scrollPage != this.scrollPage ) {
    this.scrollPage = scrollPage;
    this.setHistory( scrollPage.title, scrollPage.path );
  }
};

utils.debounceMethod( InfiniteScroll, 'onScrollHistory', 150 );

proto.getClosestScrollPage = function() {
  let scrollViewY;
  if ( this.options.elementScroll ) {
    scrollViewY = this.scroller.scrollTop + this.scroller.clientHeight / 2;
  } else {
    scrollViewY = window.scrollY + this.windowHeight / 2;
  }

  let scrollPage;
  for ( let page of this.scrollPages ) {
    if ( page.top >= scrollViewY ) break;

    scrollPage = page;
  }
  return scrollPage;
};

proto.setHistory = function( title, path ) {
  let optHistory = this.options.history;
  let historyMethod = optHistory && history[ optHistory + 'State' ];
  if ( !historyMethod ) return;

  history[ optHistory + 'State' ]( null, title, path );
  if ( this.options.historyTitle ) document.title = title;
  this.dispatchEvent( 'history', null, [ title, path ] );
};

// scroll to top to prevent initial scroll-reset after page refresh
// https://stackoverflow.com/a/18633915/182183
proto.onUnload = function() {
  if ( this.scrollPage.top === 0 ) return;

  // calculate where scroll position would be on refresh
  let scrollY = window.scrollY - this.scrollPage.top + this.top;
  // disable scroll event before setting scroll #679
  this.destroyHistory();
  scrollTo( 0, scrollY );
};

// ----- load history ----- //

// update URL
proto.onPageLoadHistory = function( response, path ) {
  this.setHistory( response.title, path );
};

// --------------------------  -------------------------- //

return InfiniteScroll;

} ) );
// button
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('./core'),
        require('fizzy-ui-utils'),
    );
  } else {
    // browser global
    factory(
        window,
        window.InfiniteScroll,
        window.fizzyUIUtils,
    );
  }

}( window, function factory( window, InfiniteScroll, utils ) {

// -------------------------- InfiniteScrollButton -------------------------- //

class InfiniteScrollButton {
  constructor( element, infScroll ) {
    this.element = element;
    this.infScroll = infScroll;
    // events
    this.clickHandler = this.onClick.bind( this );
    this.element.addEventListener( 'click', this.clickHandler );
    infScroll.on( 'request', this.disable.bind( this ) );
    infScroll.on( 'load', this.enable.bind( this ) );
    infScroll.on( 'error', this.hide.bind( this ) );
    infScroll.on( 'last', this.hide.bind( this ) );
  }

  onClick( event ) {
    event.preventDefault();
    this.infScroll.loadNextPage();
  }

  enable() {
    this.element.removeAttribute('disabled');
  }

  disable() {
    this.element.disabled = 'disabled';
  }

  hide() {
    this.element.style.display = 'none';
  }

  destroy() {
    this.element.removeEventListener( 'click', this.clickHandler );
  }

}

// -------------------------- InfiniteScroll methods -------------------------- //

// InfiniteScroll.defaults.button = null;

InfiniteScroll.create.button = function() {
  let buttonElem = utils.getQueryElement( this.options.button );
  if ( buttonElem ) {
    this.button = new InfiniteScrollButton( buttonElem, this );
  }
};

InfiniteScroll.destroy.button = function() {
  if ( this.button ) this.button.destroy();
};

// --------------------------  -------------------------- //

InfiniteScroll.Button = InfiniteScrollButton;

return InfiniteScroll;

} ) );
// status
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('./core'),
        require('fizzy-ui-utils'),
    );
  } else {
    // browser global
    factory(
        window,
        window.InfiniteScroll,
        window.fizzyUIUtils,
    );
  }

}( window, function factory( window, InfiniteScroll, utils ) {

let proto = InfiniteScroll.prototype;

// InfiniteScroll.defaults.status = null;

InfiniteScroll.create.status = function() {
  let statusElem = utils.getQueryElement( this.options.status );
  if ( !statusElem ) return;

  // elements
  this.statusElement = statusElem;
  this.statusEventElements = {
    request: statusElem.querySelector('.infinite-scroll-request'),
    error: statusElem.querySelector('.infinite-scroll-error'),
    last: statusElem.querySelector('.infinite-scroll-last'),
  };
  // events
  this.on( 'request', this.showRequestStatus );
  this.on( 'error', this.showErrorStatus );
  this.on( 'last', this.showLastStatus );
  this.bindHideStatus('on');
};

proto.bindHideStatus = function( bindMethod ) {
  let hideEvent = this.options.append ? 'append' : 'load';
  this[ bindMethod ]( hideEvent, this.hideAllStatus );
};

proto.showRequestStatus = function() {
  this.showStatus('request');
};

proto.showErrorStatus = function() {
  this.showStatus('error');
};

proto.showLastStatus = function() {
  this.showStatus('last');
  // prevent last then append event race condition from showing last status #706
  this.bindHideStatus('off');
};

proto.showStatus = function( eventName ) {
  show( this.statusElement );
  this.hideStatusEventElements();
  let eventElem = this.statusEventElements[ eventName ];
  show( eventElem );
};

proto.hideAllStatus = function() {
  hide( this.statusElement );
  this.hideStatusEventElements();
};

proto.hideStatusEventElements = function() {
  for ( let type in this.statusEventElements ) {
    let eventElem = this.statusEventElements[ type ];
    hide( eventElem );
  }
};

// --------------------------  -------------------------- //

function hide( elem ) {
  setDisplay( elem, 'none' );
}

function show( elem ) {
  setDisplay( elem, 'block' );
}

function setDisplay( elem, value ) {
  if ( elem ) {
    elem.style.display = value;
  }
}

// --------------------------  -------------------------- //

return InfiniteScroll;

} ) );
/*!
 * imagesLoaded v4.1.4
 * JavaScript is all like "You images are done yet or what?"
 * MIT License
 */

( function( window, factory ) { 'use strict';
  // universal module definition

  /*global define: false, module: false, require: false */

  if ( typeof define == 'function' && define.amd ) {
    // AMD
    define( [
      'ev-emitter/ev-emitter'
    ], function( EvEmitter ) {
      return factory( window, EvEmitter );
    });
  } else if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
      window,
      require('ev-emitter')
    );
  } else {
    // browser global
    window.imagesLoaded = factory(
      window,
      window.EvEmitter
    );
  }

})( typeof window !== 'undefined' ? window : this,

// --------------------------  factory -------------------------- //

function factory( window, EvEmitter ) {

'use strict';

var $ = window.jQuery;
var console = window.console;

// -------------------------- helpers -------------------------- //

// extend objects
function extend( a, b ) {
  for ( var prop in b ) {
    a[ prop ] = b[ prop ];
  }
  return a;
}

var arraySlice = Array.prototype.slice;

// turn element or nodeList into an array
function makeArray( obj ) {
  if ( Array.isArray( obj ) ) {
    // use object if already an array
    return obj;
  }

  var isArrayLike = typeof obj == 'object' && typeof obj.length == 'number';
  if ( isArrayLike ) {
    // convert nodeList to array
    return arraySlice.call( obj );
  }

  // array of single index
  return [ obj ];
}

// -------------------------- imagesLoaded -------------------------- //

/**
 * @param {Array, Element, NodeList, String} elem
 * @param {Object or Function} options - if function, use as callback
 * @param {Function} onAlways - callback function
 */
function ImagesLoaded( elem, options, onAlways ) {
  // coerce ImagesLoaded() without new, to be new ImagesLoaded()
  if ( !( this instanceof ImagesLoaded ) ) {
    return new ImagesLoaded( elem, options, onAlways );
  }
  // use elem as selector string
  var queryElem = elem;
  if ( typeof elem == 'string' ) {
    queryElem = document.querySelectorAll( elem );
  }
  // bail if bad element
  if ( !queryElem ) {
    console.error( 'Bad element for imagesLoaded ' + ( queryElem || elem ) );
    return;
  }

  this.elements = makeArray( queryElem );
  this.options = extend( {}, this.options );
  // shift arguments if no options set
  if ( typeof options == 'function' ) {
    onAlways = options;
  } else {
    extend( this.options, options );
  }

  if ( onAlways ) {
    this.on( 'always', onAlways );
  }

  this.getImages();

  if ( $ ) {
    // add jQuery Deferred object
    this.jqDeferred = new $.Deferred();
  }

  // HACK check async to allow time to bind listeners
  setTimeout( this.check.bind( this ) );
}

ImagesLoaded.prototype = Object.create( EvEmitter.prototype );

ImagesLoaded.prototype.options = {};

ImagesLoaded.prototype.getImages = function() {
  this.images = [];

  // filter & find items if we have an item selector
  this.elements.forEach( this.addElementImages, this );
};

/**
 * @param {Node} element
 */
ImagesLoaded.prototype.addElementImages = function( elem ) {
  // filter siblings
  if ( elem.nodeName == 'IMG' ) {
    this.addImage( elem );
  }
  // get background image on element
  if ( this.options.background === true ) {
    this.addElementBackgroundImages( elem );
  }

  // find children
  // no non-element nodes, #143
  var nodeType = elem.nodeType;
  if ( !nodeType || !elementNodeTypes[ nodeType ] ) {
    return;
  }
  var childImgs = elem.querySelectorAll('img');
  // concat childElems to filterFound array
  for ( var i=0; i < childImgs.length; i++ ) {
    var img = childImgs[i];
    this.addImage( img );
  }

  // get child background images
  if ( typeof this.options.background == 'string' ) {
    var children = elem.querySelectorAll( this.options.background );
    for ( i=0; i < children.length; i++ ) {
      var child = children[i];
      this.addElementBackgroundImages( child );
    }
  }
};

var elementNodeTypes = {
  1: true,
  9: true,
  11: true
};

ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
  var style = getComputedStyle( elem );
  if ( !style ) {
    // Firefox returns null if in a hidden iframe https://bugzil.la/548397
    return;
  }
  // get url inside url("...")
  var reURL = /url\((['"])?(.*?)\1\)/gi;
  var matches = reURL.exec( style.backgroundImage );
  while ( matches !== null ) {
    var url = matches && matches[2];
    if ( url ) {
      this.addBackground( url, elem );
    }
    matches = reURL.exec( style.backgroundImage );
  }
};

/**
 * @param {Image} img
 */
ImagesLoaded.prototype.addImage = function( img ) {
  var loadingImage = new LoadingImage( img );
  this.images.push( loadingImage );
};

ImagesLoaded.prototype.addBackground = function( url, elem ) {
  var background = new Background( url, elem );
  this.images.push( background );
};

ImagesLoaded.prototype.check = function() {
  var _this = this;
  this.progressedCount = 0;
  this.hasAnyBroken = false;
  // complete if no images
  if ( !this.images.length ) {
    this.complete();
    return;
  }

  function onProgress( image, elem, message ) {
    // HACK - Chrome triggers event before object properties have changed. #83
    setTimeout( function() {
      _this.progress( image, elem, message );
    });
  }

  this.images.forEach( function( loadingImage ) {
    loadingImage.once( 'progress', onProgress );
    loadingImage.check();
  });
};

ImagesLoaded.prototype.progress = function( image, elem, message ) {
  this.progressedCount++;
  this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
  // progress event
  this.emitEvent( 'progress', [ this, image, elem ] );
  if ( this.jqDeferred && this.jqDeferred.notify ) {
    this.jqDeferred.notify( this, image );
  }
  // check if completed
  if ( this.progressedCount == this.images.length ) {
    this.complete();
  }

  if ( this.options.debug && console ) {
    console.log( 'progress: ' + message, image, elem );
  }
};

ImagesLoaded.prototype.complete = function() {
  var eventName = this.hasAnyBroken ? 'fail' : 'done';
  this.isComplete = true;
  this.emitEvent( eventName, [ this ] );
  this.emitEvent( 'always', [ this ] );
  if ( this.jqDeferred ) {
    var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
    this.jqDeferred[ jqMethod ]( this );
  }
};

// --------------------------  -------------------------- //

function LoadingImage( img ) {
  this.img = img;
}

LoadingImage.prototype = Object.create( EvEmitter.prototype );

LoadingImage.prototype.check = function() {
  // If complete is true and browser supports natural sizes,
  // try to check for image status manually.
  var isComplete = this.getIsImageComplete();
  if ( isComplete ) {
    // report based on naturalWidth
    this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
    return;
  }

  // If none of the checks above matched, simulate loading on detached element.
  this.proxyImage = new Image();
  this.proxyImage.addEventListener( 'load', this );
  this.proxyImage.addEventListener( 'error', this );
  // bind to image as well for Firefox. #191
  this.img.addEventListener( 'load', this );
  this.img.addEventListener( 'error', this );
  this.proxyImage.src = this.img.src;
};

LoadingImage.prototype.getIsImageComplete = function() {
  // check for non-zero, non-undefined naturalWidth
  // fixes Safari+InfiniteScroll+Masonry bug infinite-scroll#671
  return this.img.complete && this.img.naturalWidth;
};

LoadingImage.prototype.confirm = function( isLoaded, message ) {
  this.isLoaded = isLoaded;
  this.emitEvent( 'progress', [ this, this.img, message ] );
};

// ----- events ----- //

// trigger specified handler for event type
LoadingImage.prototype.handleEvent = function( event ) {
  var method = 'on' + event.type;
  if ( this[ method ] ) {
    this[ method ]( event );
  }
};

LoadingImage.prototype.onload = function() {
  this.confirm( true, 'onload' );
  this.unbindEvents();
};

LoadingImage.prototype.onerror = function() {
  this.confirm( false, 'onerror' );
  this.unbindEvents();
};

LoadingImage.prototype.unbindEvents = function() {
  this.proxyImage.removeEventListener( 'load', this );
  this.proxyImage.removeEventListener( 'error', this );
  this.img.removeEventListener( 'load', this );
  this.img.removeEventListener( 'error', this );
};

// -------------------------- Background -------------------------- //

function Background( url, element ) {
  this.url = url;
  this.element = element;
  this.img = new Image();
}

// inherit LoadingImage prototype
Background.prototype = Object.create( LoadingImage.prototype );

Background.prototype.check = function() {
  this.img.addEventListener( 'load', this );
  this.img.addEventListener( 'error', this );
  this.img.src = this.url;
  // check if image is already complete
  var isComplete = this.getIsImageComplete();
  if ( isComplete ) {
    this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
    this.unbindEvents();
  }
};

Background.prototype.unbindEvents = function() {
  this.img.removeEventListener( 'load', this );
  this.img.removeEventListener( 'error', this );
};

Background.prototype.confirm = function( isLoaded, message ) {
  this.isLoaded = isLoaded;
  this.emitEvent( 'progress', [ this, this.element, message ] );
};

// -------------------------- jQuery -------------------------- //

ImagesLoaded.makeJQueryPlugin = function( jQuery ) {
  jQuery = jQuery || window.jQuery;
  if ( !jQuery ) {
    return;
  }
  // set local variable
  $ = jQuery;
  // $().imagesLoaded()
  $.fn.imagesLoaded = function( options, callback ) {
    var instance = new ImagesLoaded( this, options, callback );
    return instance.jqDeferred.promise( $(this) );
  };
};
// try making plugin
ImagesLoaded.makeJQueryPlugin();

// --------------------------  -------------------------- //

return ImagesLoaded;

});


================================================
FILE: js/button.js
================================================
// button
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('./core'),
        require('fizzy-ui-utils'),
    );
  } else {
    // browser global
    factory(
        window,
        window.InfiniteScroll,
        window.fizzyUIUtils,
    );
  }

}( window, function factory( window, InfiniteScroll, utils ) {

// -------------------------- InfiniteScrollButton -------------------------- //

class InfiniteScrollButton {
  constructor( element, infScroll ) {
    this.element = element;
    this.infScroll = infScroll;
    // events
    this.clickHandler = this.onClick.bind( this );
    this.element.addEventListener( 'click', this.clickHandler );
    infScroll.on( 'request', this.disable.bind( this ) );
    infScroll.on( 'load', this.enable.bind( this ) );
    infScroll.on( 'error', this.hide.bind( this ) );
    infScroll.on( 'last', this.hide.bind( this ) );
  }

  onClick( event ) {
    event.preventDefault();
    this.infScroll.loadNextPage();
  }

  enable() {
    this.element.removeAttribute('disabled');
  }

  disable() {
    this.element.disabled = 'disabled';
  }

  hide() {
    this.element.style.display = 'none';
  }

  destroy() {
    this.element.removeEventListener( 'click', this.clickHandler );
  }

}

// -------------------------- InfiniteScroll methods -------------------------- //

// InfiniteScroll.defaults.button = null;

InfiniteScroll.create.button = function() {
  let buttonElem = utils.getQueryElement( this.options.button );
  if ( buttonElem ) {
    this.button = new InfiniteScrollButton( buttonElem, this );
  }
};

InfiniteScroll.destroy.button = function() {
  if ( this.button ) this.button.destroy();
};

// --------------------------  -------------------------- //

InfiniteScroll.Button = InfiniteScrollButton;

return InfiniteScroll;

} ) );


================================================
FILE: js/core.js
================================================
// core
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('ev-emitter'),
        require('fizzy-ui-utils'),
    );
  } else {
    // browser global
    window.InfiniteScroll = factory(
        window,
        window.EvEmitter,
        window.fizzyUIUtils,
    );
  }

}( window, function factory( window, EvEmitter, utils ) {

let jQuery = window.jQuery;
// internal store of all InfiniteScroll intances
let instances = {};

function InfiniteScroll( element, options ) {
  let queryElem = utils.getQueryElement( element );

  if ( !queryElem ) {
    console.error( 'Bad element for InfiniteScroll: ' + ( queryElem || element ) );
    return;
  }
  element = queryElem;
  // do not initialize twice on same element
  if ( element.infiniteScrollGUID ) {
    let instance = instances[ element.infiniteScrollGUID ];
    instance.option( options );
    return instance;
  }

  this.element = element;
  // options
  this.options = { ...InfiniteScroll.defaults };
  this.option( options );
  // add jQuery
  if ( jQuery ) {
    this.$element = jQuery( this.element );
  }

  this.create();
}

// defaults
InfiniteScroll.defaults = {
  // path: null,
  // hideNav: null,
  // debug: false,
};

// create & destroy methods
InfiniteScroll.create = {};
InfiniteScroll.destroy = {};

let proto = InfiniteScroll.prototype;
// inherit EvEmitter
Object.assign( proto, EvEmitter.prototype );

// --------------------------  -------------------------- //

// globally unique identifiers
let GUID = 0;

proto.create = function() {
  // create core
  // add id for InfiniteScroll.data
  let id = this.guid = ++GUID;
  this.element.infiniteScrollGUID = id; // expando
  instances[ id ] = this; // associate via id
  // properties
  this.pageIndex = 1; // default to first page
  this.loadCount = 0;
  this.updateGetPath();
  // bail if getPath not set, or returns falsey #776
  let hasPath = this.getPath && this.getPath();
  if ( !hasPath ) {
    console.error('Disabling InfiniteScroll');
    return;
  }
  this.updateGetAbsolutePath();
  this.log( 'initialized', [ this.element.className ] );
  this.callOnInit();
  // create features
  for ( let method in InfiniteScroll.create ) {
    InfiniteScroll.create[ method ].call( this );
  }
};

proto.option = function( opts ) {
  Object.assign( this.options, opts );
};

// call onInit option, used for binding events on init
proto.callOnInit = function() {
  let onInit = this.options.onInit;
  if ( onInit ) {
    onInit.call( this, this );
  }
};

// ----- events ----- //

proto.dispatchEvent = function( type, event, args ) {
  this.log( type, args );
  let emitArgs = event ? [ event ].concat( args ) : args;
  this.emitEvent( type, emitArgs );
  // trigger jQuery event
  if ( !jQuery || !this.$element ) {
    return;
  }
  // namespace jQuery event
  type += '.infiniteScroll';
  let $event = type;
  if ( event ) {
    // create jQuery event
    /* eslint-disable-next-line new-cap */
    let jQEvent = jQuery.Event( event );
    jQEvent.type = type;
    $event = jQEvent;
  }
  this.$element.trigger( $event, args );
};

let loggers = {
  initialized: ( className ) => `on ${className}`,
  request: ( path ) => `URL: ${path}`,
  load: ( response, path ) => `${response.title || ''}. URL: ${path}`,
  error: ( error, path ) => `${error}. URL: ${path}`,
  append: ( response, path, items ) => `${items.length} items. URL: ${path}`,
  last: ( response, path ) => `URL: ${path}`,
  history: ( title, path ) => `URL: ${path}`,
  pageIndex: function( index, origin ) {
    return `current page determined to be: ${index} from ${origin}`;
  },
};

// log events
proto.log = function( type, args ) {
  if ( !this.options.debug ) return;

  let message = `[InfiniteScroll] ${type}`;
  let logger = loggers[ type ];
  if ( logger ) message += '. ' + logger.apply( this, args );
  console.log( message );
};

// -------------------------- methods used amoung features -------------------------- //

proto.updateMeasurements = function() {
  this.windowHeight = window.innerHeight;
  let rect = this.element.getBoundingClientRect();
  this.top = rect.top + window.scrollY;
};

proto.updateScroller = function() {
  let elementScroll = this.options.elementScroll;
  if ( !elementScroll ) {
    // default, use window
    this.scroller = window;
    return;
  }
  // if true, set to element, otherwise use option
  this.scroller = elementScroll === true ? this.element :
    utils.getQueryElement( elementScroll );
  if ( !this.scroller ) {
    throw new Error(`Unable to find elementScroll: ${elementScroll}`);
  }
};

// -------------------------- page path -------------------------- //

proto.updateGetPath = function() {
  let optPath = this.options.path;
  if ( !optPath ) {
    console.error(`InfiniteScroll path option required. Set as: ${optPath}`);
    return;
  }
  // function
  let type = typeof optPath;
  if ( type == 'function' ) {
    this.getPath = optPath;
    return;
  }
  // template string: '/pages/{{#}}.html'
  let templateMatch = type == 'string' && optPath.match('{{#}}');
  if ( templateMatch ) {
    this.updateGetPathTemplate( optPath );
    return;
  }
  // selector: '.next-page-selector'
  this.updateGetPathSelector( optPath );
};

proto.updateGetPathTemplate = function( optPath ) {
  // set getPath with template string
  this.getPath = () => {
    let nextIndex = this.pageIndex + 1;
    return optPath.replace( '{{#}}', nextIndex );
  };
  // get pageIndex from location
  // convert path option into regex to look for pattern in location
  // escape query (?) in url, allows for parsing GET parameters
  let regexString = optPath
    .replace( /(\\\?|\?)/, '\\?' )
    .replace( '{{#}}', '(\\d\\d?\\d?)' );
  let templateRe = new RegExp( regexString );
  let match = location.href.match( templateRe );

  if ( match ) {
    this.pageIndex = parseInt( match[1], 10 );
    this.log( 'pageIndex', [ this.pageIndex, 'template string' ] );
  }
};

let pathRegexes = [
  // WordPress & Tumblr - example.com/page/2
  // Jekyll - example.com/page2
  /^(.*?\/?page\/?)(\d\d?\d?)(.*?$)/,
  // Drupal - example.com/?page=1
  /^(.*?\/?\?page=)(\d\d?\d?)(.*?$)/,
  // catch all, last occurence of a number
  /(.*?)(\d\d?\d?)(?!.*\d)(.*?$)/,
];

// try matching href to pathRegexes patterns
let getPathParts = InfiniteScroll.getPathParts = function( href ) {
  if ( !href ) return;
  for ( let regex of pathRegexes ) {
    let match = href.match( regex );
    if ( match ) {
      let [ , begin, index, end ] = match;
      return { begin, index, end };
    }
  }
};

proto.updateGetPathSelector = function( optPath ) {
  // parse href of link: '.next-page-link'
  let hrefElem = document.querySelector( optPath );
  if ( !hrefElem ) {
    console.error(`Bad InfiniteScroll path option. Next link not found: ${optPath}`);
    return;
  }

  let href = hrefElem.getAttribute('href');
  let pathParts = getPathParts( href );
  if ( !pathParts ) {
    console.error(`InfiniteScroll unable to parse next link href: ${href}`);
    return;
  }

  let { begin, index, end } = pathParts;
  this.isPathSelector = true; // flag for checkLastPage()
  this.getPath = () => begin + ( this.pageIndex + 1 ) + end;
  // get pageIndex from href
  this.pageIndex = parseInt( index, 10 ) - 1;
  this.log( 'pageIndex', [ this.pageIndex, 'next link' ] );
};

proto.updateGetAbsolutePath = function() {
  let path = this.getPath();
  // path doesn't start with http or /
  let isAbsolute = path.match( /^http/ ) || path.match( /^\// );
  if ( isAbsolute ) {
    this.getAbsolutePath = this.getPath;
    return;
  }

  let { pathname } = location;
  // query parameter #829. example.com/?pg=2
  let isQuery = path.match( /^\?/ );
  // /foo/bar/index.html => /foo/bar
  let directory = pathname.substring( 0, pathname.lastIndexOf('/') );
  let pathStart = isQuery ? pathname : directory + '/';

  this.getAbsolutePath = () => pathStart + this.getPath();
};

// -------------------------- nav -------------------------- //

// hide navigation
InfiniteScroll.create.hideNav = function() {
  let nav = utils.getQueryElement( this.options.hideNav );
  if ( !nav ) return;

  nav.style.display = 'none';
  this.nav = nav;
};

InfiniteScroll.destroy.hideNav = function() {
  if ( this.nav ) this.nav.style.display = '';
};

// -------------------------- destroy -------------------------- //

proto.destroy = function() {
  this.allOff(); // remove all event listeners
  // call destroy methods
  for ( let method in InfiniteScroll.destroy ) {
    InfiniteScroll.destroy[ method ].call( this );
  }

  delete this.element.infiniteScrollGUID;
  delete instances[ this.guid ];
  // remove jQuery data. #807
  if ( jQuery && this.$element ) {
    jQuery.removeData( this.element, 'infiniteScroll' );
  }
};

// -------------------------- utilities -------------------------- //

// https://remysharp.com/2010/07/21/throttling-function-calls
InfiniteScroll.throttle = function( fn, threshold ) {
  threshold = threshold || 200;
  let last, timeout;

  return function() {
    let now = +new Date();
    let args = arguments;
    let trigger = () => {
      last = now;
      fn.apply( this, args );
    };
    if ( last && now < last + threshold ) {
      // hold on to it
      clearTimeout( timeout );
      timeout = setTimeout( trigger, threshold );
    } else {
      trigger();
    }
  };
};

InfiniteScroll.data = function( elem ) {
  elem = utils.getQueryElement( elem );
  let id = elem && elem.infiniteScrollGUID;
  return id && instances[ id ];
};

// set internal jQuery, for Webpack + jQuery v3
InfiniteScroll.setJQuery = function( jqry ) {
  jQuery = jqry;
};

// -------------------------- setup -------------------------- //

utils.htmlInit( InfiniteScroll, 'infinite-scroll' );

// add noop _init method for jQuery Bridget. #768
proto._init = function() {};

let { jQueryBridget } = window;
if ( jQuery && jQueryBridget ) {
  jQueryBridget( 'infiniteScroll', InfiniteScroll, jQuery );
}

// --------------------------  -------------------------- //

return InfiniteScroll;

} ) );


================================================
FILE: js/history.js
================================================
// history
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('./core'),
        require('fizzy-ui-utils'),
    );
  } else {
    // browser global
    factory(
        window,
        window.InfiniteScroll,
        window.fizzyUIUtils,
    );
  }

}( window, function factory( window, InfiniteScroll, utils ) {

let proto = InfiniteScroll.prototype;

Object.assign( InfiniteScroll.defaults, {
  history: 'replace',
  // historyTitle: false,
} );

let link = document.createElement('a');

// ----- create/destroy ----- //

InfiniteScroll.create.history = function() {
  if ( !this.options.history ) return;

  // check for same origin
  link.href = this.getAbsolutePath();
  // MS Edge does not have origin on link
  // https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12236493/
  let linkOrigin = link.origin || link.protocol + '//' + link.host;
  let isSameOrigin = linkOrigin == location.origin;
  if ( !isSameOrigin ) {
    console.error( '[InfiniteScroll] cannot set history with different origin: ' +
      `${link.origin} on ${location.origin} . History behavior disabled.` );
    return;
  }

  // two ways to handle changing history
  if ( this.options.append ) {
    this.createHistoryAppend();
  } else {
    this.createHistoryPageLoad();
  }
};

proto.createHistoryAppend = function() {
  this.updateMeasurements();
  this.updateScroller();
  // array of scroll positions of appended pages
  this.scrollPages = [
    // first page
    {
      top: 0,
      path: location.href,
      title: document.title,
    },
  ];
  this.scrollPage = this.scrollPages[0];
  // events
  this.scrollHistoryHandler = this.onScrollHistory.bind( this );
  this.unloadHandler = this.onUnload.bind( this );
  this.scroller.addEventListener( 'scroll', this.scrollHistoryHandler );
  this.on( 'append', this.onAppendHistory );
  this.bindHistoryAppendEvents( true );
};

proto.bindHistoryAppendEvents = function( isBind ) {
  let addRemove = isBind ? 'addEventListener' : 'removeEventListener';
  this.scroller[ addRemove ]( 'scroll', this.scrollHistoryHandler );
  window[ addRemove ]( 'unload', this.unloadHandler );
};

proto.createHistoryPageLoad = function() {
  this.on( 'load', this.onPageLoadHistory );
};

InfiniteScroll.destroy.history =
proto.destroyHistory = function() {
  let isHistoryAppend = this.options.history && this.options.append;
  if ( isHistoryAppend ) {
    this.bindHistoryAppendEvents( false );
  }
};

// ----- append history ----- //

proto.onAppendHistory = function( response, path, items ) {
  // do not proceed if no items. #779
  if ( !items || !items.length ) return;

  let firstItem = items[0];
  let elemScrollY = this.getElementScrollY( firstItem );
  // resolve path
  link.href = path;
  // add page data to hash
  this.scrollPages.push({
    top: elemScrollY,
    path: link.href,
    title: response.title,
  });
};

proto.getElementScrollY = function( elem ) {
  if ( this.options.elementScroll ) {
    return elem.offsetTop - this.top;
  } else {
    let rect = elem.getBoundingClientRect();
    return rect.top + window.scrollY;
  }
};

proto.onScrollHistory = function() {
  // cycle through positions, find biggest without going over
  let scrollPage = this.getClosestScrollPage();
  // set history if changed
  if ( scrollPage != this.scrollPage ) {
    this.scrollPage = scrollPage;
    this.setHistory( scrollPage.title, scrollPage.path );
  }
};

utils.debounceMethod( InfiniteScroll, 'onScrollHistory', 150 );

proto.getClosestScrollPage = function() {
  let scrollViewY;
  if ( this.options.elementScroll ) {
    scrollViewY = this.scroller.scrollTop + this.scroller.clientHeight / 2;
  } else {
    scrollViewY = window.scrollY + this.windowHeight / 2;
  }

  let scrollPage;
  for ( let page of this.scrollPages ) {
    if ( page.top >= scrollViewY ) break;

    scrollPage = page;
  }
  return scrollPage;
};

proto.setHistory = function( title, path ) {
  let optHistory = this.options.history;
  let historyMethod = optHistory && history[ optHistory + 'State' ];
  if ( !historyMethod ) return;

  history[ optHistory + 'State' ]( null, title, path );
  if ( this.options.historyTitle ) document.title = title;
  this.dispatchEvent( 'history', null, [ title, path ] );
};

// scroll to top to prevent initial scroll-reset after page refresh
// https://stackoverflow.com/a/18633915/182183
proto.onUnload = function() {
  if ( this.scrollPage.top === 0 ) return;

  // calculate where scroll position would be on refresh
  let scrollY = window.scrollY - this.scrollPage.top + this.top;
  // disable scroll event before setting scroll #679
  this.destroyHistory();
  scrollTo( 0, scrollY );
};

// ----- load history ----- //

// update URL
proto.onPageLoadHistory = function( response, path ) {
  this.setHistory( response.title, path );
};

// --------------------------  -------------------------- //

return InfiniteScroll;

} ) );


================================================
FILE: js/index.js
================================================
/*!
 * Infinite Scroll v5.0.0
 * Automatically add next page
 * MIT License
 * https://infinite-scroll.com
 * Copyright 2018-2025 Metafizzy
 */

( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        require('./core'),
        require('./page-load'),
        require('./scroll-watch'),
        require('./history'),
        require('./button'),
        require('./status'),
    );
  }

} )( window, function factory( InfiniteScroll ) {
  return InfiniteScroll;
} );


================================================
FILE: js/page-load.js
================================================
// page-load
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('./core'),
    );
  } else {
    // browser global
    factory(
        window,
        window.InfiniteScroll,
    );
  }

}( window, function factory( window, InfiniteScroll ) {

let proto = InfiniteScroll.prototype;

Object.assign( InfiniteScroll.defaults, {
  // append: false,
  loadOnScroll: true,
  checkLastPage: true,
  responseBody: 'text',
  domParseResponse: true,
  // prefill: false,
  // outlayer: null,
} );

InfiniteScroll.create.pageLoad = function() {
  this.canLoad = true;
  this.on( 'scrollThreshold', this.onScrollThresholdLoad );
  this.on( 'load', this.checkLastPage );
  if ( this.options.outlayer ) {
    this.on( 'append', this.onAppendOutlayer );
  }
};

proto.onScrollThresholdLoad = function() {
  if ( this.options.loadOnScroll ) this.loadNextPage();
};

let domParser = new DOMParser();

proto.loadNextPage = function() {
  if ( this.isLoading || !this.canLoad ) return;

  let { responseBody, domParseResponse, fetchOptions } = this.options;
  let path = this.getAbsolutePath();
  this.isLoading = true;
  if ( typeof fetchOptions == 'function' ) fetchOptions = fetchOptions();

  let fetchPromise = fetch( path, fetchOptions )
    .then( ( response ) => {
      if ( !response.ok ) {
        let error = new Error( response.statusText );
        this.onPageError( error, path, response );
        return { response };
      }

      return response[ responseBody ]().then( ( body ) => {
        let canDomParse = responseBody == 'text' && domParseResponse;
        if ( canDomParse ) {
          body = domParser.parseFromString( body, 'text/html' );
        }
        if ( response.status == 204 ) {
          this.lastPageReached( body, path );
          return { body, response };
        } else {
          return this.onPageLoad( body, path, response );
        }
      } );
    } )
    .catch( ( error ) => {
      this.onPageError( error, path );
    } );

  this.dispatchEvent( 'request', null, [ path, fetchPromise ] );

  return fetchPromise;
};

proto.onPageLoad = function( body, path, response ) {
  // done loading if not appending
  if ( !this.options.append ) {
    this.isLoading = false;
  }
  this.pageIndex++;
  this.loadCount++;
  this.dispatchEvent( 'load', null, [ body, path, response ] );
  return this.appendNextPage( body, path, response );
};

proto.appendNextPage = function( body, path, response ) {
  let { append, responseBody, domParseResponse } = this.options;
  // do not append json
  let isDocument = responseBody == 'text' && domParseResponse;
  if ( !isDocument || !append ) return { body, response };

  let items = body.querySelectorAll( append );
  let promiseValue = { body, response, items };
  // last page hit if no items. #840
  if ( !items || !items.length ) {
    this.lastPageReached( body, path );
    return promiseValue;
  }

  let fragment = getItemsFragment( items );
  let appendReady = () => {
    this.appendItems( items, fragment );
    this.isLoading = false;
    this.dispatchEvent( 'append', null, [ body, path, items, response ] );
    return promiseValue;
  };

  // TODO add hook for option to trigger appendReady
  if ( this.options.outlayer ) {
    return this.appendOutlayerItems( fragment, appendReady );
  } else {
    return appendReady();
  }
};

proto.appendItems = function( items, fragment ) {
  if ( !items || !items.length ) return;

  // get fragment if not provided
  fragment = fragment || getItemsFragment( items );
  refreshScripts( fragment );
  this.element.appendChild( fragment );
};

function getItemsFragment( items ) {
  // add items to fragment
  let fragment = document.createDocumentFragment();
  if ( items ) fragment.append( ...items );
  return fragment;
}

// replace <script>s with copies so they load
// <script>s added by InfiniteScroll will not load
// similar to https://stackoverflow.com/questions/610995
function refreshScripts( fragment ) {
  let scripts = fragment.querySelectorAll('script');
  for ( let script of scripts ) {
    let freshScript = document.createElement('script');
    // copy attributes
    let attrs = script.attributes;
    for ( let attr of attrs ) {
      freshScript.setAttribute( attr.name, attr.value );
    }
    // copy inner script code. #718, #782
    freshScript.innerHTML = script.innerHTML;
    script.parentNode.replaceChild( freshScript, script );
  }
}

// ----- outlayer ----- //

proto.appendOutlayerItems = function( fragment, appendReady ) {
  let imagesLoaded = InfiniteScroll.imagesLoaded || window.imagesLoaded;
  if ( !imagesLoaded ) {
    console.error('[InfiniteScroll] imagesLoaded required for outlayer option');
    this.isLoading = false;
    return;
  }
  // append once images loaded
  return new Promise( function( resolve ) {
    imagesLoaded( fragment, function() {
      let bodyResponse = appendReady();
      resolve( bodyResponse );
    } );
  } );
};

proto.onAppendOutlayer = function( response, path, items ) {
  this.options.outlayer.appended( items );
};

// ----- checkLastPage ----- //

// check response for next element
proto.checkLastPage = function( body, path ) {
  let { checkLastPage, path: pathOpt } = this.options;
  if ( !checkLastPage ) return;

  // if path is function, check if next path is truthy
  if ( typeof pathOpt == 'function' ) {
    let nextPath = this.getPath();
    if ( !nextPath ) {
      this.lastPageReached( body, path );
      return;
    }
  }
  // get selector from checkLastPage or path option
  let selector;
  if ( typeof checkLastPage == 'string' ) {
    selector = checkLastPage;
  } else if ( this.isPathSelector ) {
    // path option is selector string
    selector = pathOpt;
  }
  // check last page for selector
  // bail if no selector or not document response
  if ( !selector || !body.querySelector ) return;

  // check if response has selector
  let nextElem = body.querySelector( selector );
  if ( !nextElem ) this.lastPageReached( body, path );
};

proto.lastPageReached = function( body, path ) {
  this.canLoad = false;
  this.dispatchEvent( 'last', null, [ body, path ] );
};

// ----- error ----- //

proto.onPageError = function( error, path, response ) {
  this.isLoading = false;
  this.canLoad = false;
  this.dispatchEvent( 'error', null, [ error, path, response ] );
  return error;
};

// -------------------------- prefill -------------------------- //

InfiniteScroll.create.prefill = function() {
  if ( !this.options.prefill ) return;

  let append = this.options.append;
  if ( !append ) {
    console.error(`append option required for prefill. Set as :${append}`);
    return;
  }
  this.updateMeasurements();
  this.updateScroller();
  this.isPrefilling = true;
  this.on( 'append', this.prefill );
  this.once( 'error', this.stopPrefill );
  this.once( 'last', this.stopPrefill );
  this.prefill();
};

proto.prefill = function() {
  let distance = this.getPrefillDistance();
  this.isPrefilling = distance >= 0;
  if ( this.isPrefilling ) {
    this.log('prefill');
    this.loadNextPage();
  } else {
    this.stopPrefill();
  }
};

proto.getPrefillDistance = function() {
  // element scroll
  if ( this.options.elementScroll ) {
    return this.scroller.clientHeight - this.scroller.scrollHeight;
  }
  // window
  return this.windowHeight - this.element.clientHeight;
};

proto.stopPrefill = function() {
  this.log('stopPrefill');
  this.off( 'append', this.prefill );
};

// --------------------------  -------------------------- //

return InfiniteScroll;

} ) );


================================================
FILE: js/scroll-watch.js
================================================
// scroll-watch
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('./core'),
        require('fizzy-ui-utils'),
    );
  } else {
    // browser global
    factory(
        window,
        window.InfiniteScroll,
        window.fizzyUIUtils,
    );
  }

}( window, function factory( window, InfiniteScroll, utils ) {

let proto = InfiniteScroll.prototype;

// default options
Object.assign( InfiniteScroll.defaults, {
  scrollThreshold: 400,
  // elementScroll: null,
} );

InfiniteScroll.create.scrollWatch = function() {
  // events
  this.pageScrollHandler = this.onPageScroll.bind( this );
  this.resizeHandler = this.onResize.bind( this );

  let scrollThreshold = this.options.scrollThreshold;
  let isEnable = scrollThreshold || scrollThreshold === 0;
  if ( isEnable ) this.enableScrollWatch();
};

InfiniteScroll.destroy.scrollWatch = function() {
  this.disableScrollWatch();
};

proto.enableScrollWatch = function() {
  if ( this.isScrollWatching ) return;

  this.isScrollWatching = true;
  this.updateMeasurements();
  this.updateScroller();
  // TODO disable after error?
  this.on( 'last', this.disableScrollWatch );
  this.bindScrollWatchEvents( true );
};

proto.disableScrollWatch = function() {
  if ( !this.isScrollWatching ) return;

  this.bindScrollWatchEvents( false );
  delete this.isScrollWatching;
};

proto.bindScrollWatchEvents = function( isBind ) {
  let addRemove = isBind ? 'addEventListener' : 'removeEventListener';
  this.scroller[ addRemove ]( 'scroll', this.pageScrollHandler );
  window[ addRemove ]( 'resize', this.resizeHandler );
};

proto.onPageScroll = InfiniteScroll.throttle( function() {
  let distance = this.getBottomDistance();
  if ( distance <= this.options.scrollThreshold ) {
    this.dispatchEvent('scrollThreshold');
  }
} );

proto.getBottomDistance = function() {
  let bottom, scrollY;
  if ( this.options.elementScroll ) {
    bottom = this.scroller.scrollHeight;
    scrollY = this.scroller.scrollTop + this.scroller.clientHeight;
  } else {
    bottom = this.top + this.element.clientHeight;
    scrollY = window.scrollY + this.windowHeight;
  }
  return bottom - scrollY;
};

proto.onResize = function() {
  this.updateMeasurements();
};

utils.debounceMethod( InfiniteScroll, 'onResize', 150 );

// --------------------------  -------------------------- //

return InfiniteScroll;

} ) );


================================================
FILE: js/status.js
================================================
// status
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('./core'),
        require('fizzy-ui-utils'),
    );
  } else {
    // browser global
    factory(
        window,
        window.InfiniteScroll,
        window.fizzyUIUtils,
    );
  }

}( window, function factory( window, InfiniteScroll, utils ) {

let proto = InfiniteScroll.prototype;

// InfiniteScroll.defaults.status = null;

InfiniteScroll.create.status = function() {
  let statusElem = utils.getQueryElement( this.options.status );
  if ( !statusElem ) return;

  // elements
  this.statusElement = statusElem;
  this.statusEventElements = {
    request: statusElem.querySelector('.infinite-scroll-request'),
    error: statusElem.querySelector('.infinite-scroll-error'),
    last: statusElem.querySelector('.infinite-scroll-last'),
  };
  // events
  this.on( 'request', this.showRequestStatus );
  this.on( 'error', this.showErrorStatus );
  this.on( 'last', this.showLastStatus );
  this.bindHideStatus('on');
};

proto.bindHideStatus = function( bindMethod ) {
  let hideEvent = this.options.append ? 'append' : 'load';
  this[ bindMethod ]( hideEvent, this.hideAllStatus );
};

proto.showRequestStatus = function() {
  this.showStatus('request');
};

proto.showErrorStatus = function() {
  this.showStatus('error');
};

proto.showLastStatus = function() {
  this.showStatus('last');
  // prevent last then append event race condition from showing last status #706
  this.bindHideStatus('off');
};

proto.showStatus = function( eventName ) {
  show( this.statusElement );
  this.hideStatusEventElements();
  let eventElem = this.statusEventElements[ eventName ];
  show( eventElem );
};

proto.hideAllStatus = function() {
  hide( this.statusElement );
  this.hideStatusEventElements();
};

proto.hideStatusEventElements = function() {
  for ( let type in this.statusEventElements ) {
    let eventElem = this.statusEventElements[ type ];
    hide( eventElem );
  }
};

// --------------------------  -------------------------- //

function hide( elem ) {
  setDisplay( elem, 'none' );
}

function show( elem ) {
  setDisplay( elem, 'block' );
}

function setDisplay( elem, value ) {
  if ( elem ) {
    elem.style.display = value;
  }
}

// --------------------------  -------------------------- //

return InfiniteScroll;

} ) );


================================================
FILE: package.json
================================================
{
  "name": "infinite-scroll",
  "version": "5.0.0",
  "description": "Automatically add next page",
  "main": "js/index.js",
  "files": [
    "dist",
    "js"
  ],
  "dependencies": {
    "ev-emitter": "^2.1.0",
    "fizzy-ui-utils": "^3.0.0"
  },
  "devDependencies": {
    "ava": "^3.13.0",
    "eslint": "^7.15.0",
    "eslint-plugin-metafizzy": "^1.2.1",
    "imagesloaded": "^4.1.4",
    "jquery": "^3.5.1",
    "jquery-bridget": "^3.0.0",
    "masonry-layout": "^4.2.2",
    "puppeteer": "^5.5.0",
    "terser": "^5.5.1"
  },
  "scripts": {
    "dist": "node bin/build-dist.js",
    "lint": "npx eslint .",
    "test": "npm run lint && ava",
    "version": "node bin/version.js && npm run dist && git add -A . dist"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/metafizzy/infinite-scroll.git"
  },
  "keywords": [
    "plugin",
    "ui",
    "DOM",
    "browser"
  ],
  "author": "Metafizzy",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/metafizzy/infinite-scroll/issues"
  },
  "homepage": "https://infinite-scroll.com",
  "directories": {
    "test": "test"
  }
}


================================================
FILE: sandbox/button-class.html
================================================

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>button class</title>

  <link rel="stylesheet" href="css/blog.css" />

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>button class</h1>

  </div>
</div>

<div class="container">
  <div class="posts-container">
<article class="post">
  <a class="post-header" href="/blog/wyday-logos">
    <h1 class="post-header__title">wyDay logos</h1>
    <p class="post-header__date">22 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes &quot;premium software development tools for high-tech companies.&quot; A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before &amp; after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>

  </div>
</article>
<article class="post">
  <a class="post-header" href="/blog/mealio-logo">
    <h1 class="post-header__title">Mealio logo</h1>
    <p class="post-header__date">8 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user&#39;s preferences. It&#39;s like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly &amp; clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>

  </div>
</article>

</div>

  <div class="scroll-status">
    <div class="infinite-scroll-request">
      <div class="loader-ellips">
        <span class="loader-ellips__dot loader-ellips__dot--1"></span>
        <span class="loader-ellips__dot loader-ellips__dot--2"></span>
        <span class="loader-ellips__dot loader-ellips__dot--3"></span>
        <span class="loader-ellips__dot loader-ellips__dot--4"></span>
      </div>
    </div>
    <p class="infinite-scroll-error">Last page reached</p>
  </div>

  <p>
    <button class="load-more-button">Load more</button>
  </p>

  <nav class="pagination">
    <span class="pagination__current">Page 1 of 17</span>
      <a class="pagination__next" href="page/2.html">Next &middot; 2</a>
  </nav>

</div>
  

<footer class="site-footer">
  <div class="container">

    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>


  </div>
</footer>

<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/button.js"></script>
<script src="../js/status.js"></script>
<script>
var container = document.querySelector('.posts-container');
var infScroll = new InfiniteScroll( container, {
  path: '.pagination__next',
  append: '.post',
  historyTitle: true,
  scrollThreshold: false,
  history: false,
  button: '.load-more-button',
  status: '.scroll-status',
  debug: true,
});

</script>

</body>
</html>


================================================
FILE: sandbox/button-first.html
================================================

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>button first</title>

  <link rel="stylesheet" href="css/blog.css" />

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>button first</h1>

  </div>
</div>

<div class="container">
  <div class="posts-container">
<article class="post">
  <a class="post-header" href="/blog/wyday-logos">
    <h1 class="post-header__title">wyDay logos</h1>
    <p class="post-header__date">22 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes &quot;premium software development tools for high-tech companies.&quot; A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before &amp; after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>

  </div>
</article>
<article class="post">
  <a class="post-header" href="/blog/mealio-logo">
    <h1 class="post-header__title">Mealio logo</h1>
    <p class="post-header__date">8 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user&#39;s preferences. It&#39;s like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly &amp; clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>

  </div>
</article>

</div>

  <p>
    <button class="load-more-button">Load more</button>
  </p>

  <nav class="pagination">
    <span class="pagination__current">Page 1 of 17</span>
      <a class="pagination__next" href="page/2.html">Next &middot; 2</a>
  </nav>

  </div>
  

<footer class="site-footer">
  <div class="container">

    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>


  </div>
</footer>

<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script>
var container = document.querySelector('.posts-container');
var infScroll = new InfiniteScroll( container, {
  path: '.pagination__next',
  append: '.post',
  historyTitle: true,
  loadOnScroll: false,
  history: false,
});

var loadMoreButton = document.querySelector('.load-more-button');

loadMoreButton.onclick = function() {
  infScroll.options.loadOnScroll = true;
  infScroll.loadNextPage();
  loadMoreButton.style.display = 'none';
};
</script>

</body>
</html>


================================================
FILE: sandbox/button-load.html
================================================

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>button load</title>

  <link rel="stylesheet" href="css/blog.css" />

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>button load</h1>

  </div>
</div>

<div class="container">
  <div class="posts-container">
<article class="post">
  <a class="post-header" href="/blog/wyday-logos">
    <h1 class="post-header__title">wyDay logos</h1>
    <p class="post-header__date">22 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes &quot;premium software development tools for high-tech companies.&quot; A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before &amp; after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>

  </div>
</article>
<article class="post">
  <a class="post-header" href="/blog/mealio-logo">
    <h1 class="post-header__title">Mealio logo</h1>
    <p class="post-header__date">8 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user&#39;s preferences. It&#39;s like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly &amp; clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>

  </div>
</article>

</div>

  <p>
    <button class="load-more-button">Load more</button>
  </p>

  <nav class="pagination">
    <span class="pagination__current">Page 1 of 17</span>
      <a class="pagination__next" href="page/2.html">Next &middot; 2</a>
  </nav>

  </div>
  

<footer class="site-footer">
  <div class="container">

    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>


  </div>
</footer>

<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/button.js"></script>
<script>
var container = document.querySelector('.posts-container');
var infScroll = new InfiniteScroll( container, {
  path: '.pagination__next',
  append: '.post',
  button: '.load-more-button',
  loadOnScroll: false,
  history: false,
});

</script>

</body>
</html>


================================================
FILE: sandbox/container-scroll.html
================================================

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>container scroll</title>

  <link rel="stylesheet" href="css/blog.css" />

<style>
.sidebar {
  width: 300px;
  height: 400px;
  overflow-y: scroll;
  overflow-x: hidden;
  border: 1px solid #CCC;
}

.post {
  margin-top: 0;
  font-size: 12px;
}
</style>

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>container scroll</h1>

  </div>
</div>


<div class="container">
  <div class="sidebar">
  <div class="posts-container">
<article class="post">
  <a class="post-header" href="/blog/wyday-logos">
    <h1 class="post-header__title">wyDay logos</h1>
    <p class="post-header__date">22 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes &quot;premium software development tools for high-tech companies.&quot; A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before &amp; after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>

  </div>
</article>
<article class="post">
  <a class="post-header" href="/blog/mealio-logo">
    <h1 class="post-header__title">Mealio logo</h1>
    <p class="post-header__date">8 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user&#39;s preferences. It&#39;s like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly &amp; clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>

  </div>
</article>



</div> <!-- posts-container -->
</div> <!-- sidebar -->
</div> <!-- container -->

  <nav class="pagination">
    <span class="pagination__current">Page 1 of 17</span>
      <a class="pagination__next" href="page/2.html">Next &middot; 2</a>
  </nav>

<footer class="site-footer">
  <div class="container">

    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>


  </div>
</footer>

<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>

<script>

var postsContainer = document.querySelector('.posts-container');

var infScroll = new InfiniteScroll( postsContainer, {
  elementScroll: '.sidebar',
  append: '.post',
  path: '.pagination__next',
  scrollThreshold: 200,
  // history: false,
});

</script>

</body>
</html>


================================================
FILE: sandbox/css/blog.css
================================================
body {
  line-height: 1.4;
  background: #EEE;
  color: #444;
}

.container {
  padding: 0 20px;
  max-width: 700px;
  margin: 0 auto;
}

.site-header {
  height: 150px;
  background: #FFF;
}

.post {
  margin: 60px 0;
  background: white;
  padding: 10px;
}

.post img {
  display: block;
  max-width: 100%;
}

.load-more-button {
  font-size: 20px;
  padding: 20px;
}

.site-footer {
  padding: 200px 0;
  background: #333;
  color: white;
}


.scroll-status {
  margin: 60px 0;
  display: none;
}


================================================
FILE: sandbox/css/loader-ellips.css
================================================
/* loader-ellips
------------------------- */

.loader-ellips {
  font-size: 20px;
  position: relative;
  width: 4em;
  height: 1em;
  margin: 10px auto;
}

.loader-ellips__dot {
  display: block;
  width: 1em;
  height: 1em;
  border-radius: 0.5em;
  background: #333;
  position: absolute;
  animation-duration: 0.5s;
  animation-timing-function: ease;
  animation-iteration-count: infinite;
}

.loader-ellips__dot--1,
.loader-ellips__dot--2 {
  left: 0;
}
.loader-ellips__dot--3 { left: 1.5em; }
.loader-ellips__dot--4 { left: 3em; }

@keyframes reveal {
  from { transform: scale(0.001); }
  to { transform: scale(1); }
}

@keyframes slide {
  to { transform: translateX(1.5em) }
}

.loader-ellips__dot--1 {
  animation-name: reveal;
}

.loader-ellips__dot--2,
.loader-ellips__dot--3 {
  animation-name: slide;
}

.loader-ellips__dot--4 {
  animation-name: reveal;
  animation-direction: reverse;
}


================================================
FILE: sandbox/css/masonry-images.css
================================================
.container {
  max-width: 1200px;
  margin: 0 auto;
  padding-bottom: 400px;
}

.grid {
  min-height: 600px;
}

.grid__col-sizer,
.grid__item {
  width: 30%;
}

.grid__gutter-sizer { width: 3.33%; }

.grid__item {
  float: left;
  margin-bottom: 30px;
  visibility: hidden;; /*hidden by default*/
}

.grid.are-images-ready .grid__item { visibility: visible; }

.grid__item img {
  display: block;
  max-width: 100%;
}

.scroll-status {
  display: none;
}

================================================
FILE: sandbox/element-scroll.html
================================================

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>element scroll</title>

  <link rel="stylesheet" href="css/blog.css" />

<style>
.posts-container {
  width: 300px;
  height: 400px;
  overflow-y: scroll;
  overflow-x: hidden;
  border: 1px solid #CCC;
}

.post {
  margin-top: 0;
  font-size: 12px;
}
</style>

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>element scroll</h1>

  </div>
</div>


<div class="container">
  <div class="posts-container">
<article class="post">
  <a class="post-header" href="/blog/wyday-logos">
    <h1 class="post-header__title">wyDay logos</h1>
    <p class="post-header__date">22 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes &quot;premium software development tools for high-tech companies.&quot; A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before &amp; after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>

  </div>
</article>
<article class="post">
  <a class="post-header" href="/blog/mealio-logo">
    <h1 class="post-header__title">Mealio logo</h1>
    <p class="post-header__date">8 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user&#39;s preferences. It&#39;s like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly &amp; clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>

  </div>
</article>



</div> <!-- posts-container -->
</div> <!-- container -->

  <nav class="pagination">
    <span class="pagination__current">Page 1 of 17</span>
      <a class="pagination__next" href="page/2.html">Next &middot; 2</a>
  </nav>

<footer class="site-footer">
  <div class="container">

    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>


  </div>
</footer>

<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>

<script>

var postsContainer = document.querySelector('.posts-container');

var infScroll = new InfiniteScroll( postsContainer, {
  elementScroll: true,
  append: '.post',
  path: '.pagination__next',
  // history: false,
});

</script>

</body>
</html>


================================================
FILE: sandbox/html-init.html
================================================

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>html init</title>

  <link rel="stylesheet" href="css/blog.css" />

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>html init</h1>

  </div>
</div>

<div class="container">
  <div class="posts-container"
    data-infinite-scroll='{
      "path": ".pagination__next",
      "append": ".post",
      "historyTitle": true,
      "status": ".scroll-status"
    }'>
<article class="post">
  <a class="post-header" href="/blog/wyday-logos">
    <h1 class="post-header__title">wyDay logos</h1>
    <p class="post-header__date">22 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes &quot;premium software development tools for high-tech companies.&quot; A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before &amp; after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>

  </div>
</article>
<article class="post">
  <a class="post-header" href="/blog/mealio-logo">
    <h1 class="post-header__title">Mealio logo</h1>
    <p class="post-header__date">8 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user&#39;s preferences. It&#39;s like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly &amp; clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>

  </div>
</article>

</div> <!-- posts-container -->

  <div class="scroll-status">
    <div class="infinite-scroll-request">
      <div class="loader-ellips">
        <span class="loader-ellips__dot loader-ellips__dot--1"></span>
        <span class="loader-ellips__dot loader-ellips__dot--2"></span>
        <span class="loader-ellips__dot loader-ellips__dot--3"></span>
        <span class="loader-ellips__dot loader-ellips__dot--4"></span>
      </div>
    </div>
    <p class="infinite-scroll-error">Last page reached</p>
  </div>

  <nav class="pagination">
    <span class="pagination__current">Page 1 of 17</span>
      <a class="pagination__next" href="page/2.html">Next &middot; 2</a>
  </nav>
</div> <!-- container -->

<footer class="site-footer">
  <div class="container">

    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>


  </div>
</footer>

<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/status.js"></script>
<script src="../js/button.js"></script>

</body>
</html>


================================================
FILE: sandbox/jquery-plugin.html
================================================

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>jquery plugin</title>

  <link rel="stylesheet" href="css/blog.css" />

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>jquery plugin</h1>

  </div>
</div>

<div class="container">
  <div class="posts-container">
<article class="post">
  <a class="post-header" href="/blog/wyday-logos">
    <h1 class="post-header__title">wyDay logos</h1>
    <p class="post-header__date">22 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes &quot;premium software development tools for high-tech companies.&quot; A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before &amp; after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>

  </div>
</article>
<article class="post">
  <a class="post-header" href="/blog/mealio-logo">
    <h1 class="post-header__title">Mealio logo</h1>
    <p class="post-header__date">8 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user&#39;s preferences. It&#39;s like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly &amp; clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>

  </div>
</article>

</div> <!-- posts-container -->

  <div class="scroll-status">
    <div class="infinite-scroll-request">
      <div class="loader-ellips">
        <span class="loader-ellips__dot loader-ellips__dot--1"></span>
        <span class="loader-ellips__dot loader-ellips__dot--2"></span>
        <span class="loader-ellips__dot loader-ellips__dot--3"></span>
        <span class="loader-ellips__dot loader-ellips__dot--4"></span>
      </div>
    </div>
    <p class="infinite-scroll-error">Last page reached</p>
  </div>

  <nav class="pagination">
    <span class="pagination__current">Page 1 of 17</span>
      <a class="pagination__next" href="page/2.html">Next &middot; 2</a>
  </nav>
</div> <!-- container -->

<footer class="site-footer">
  <div class="container">

    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>


  </div>
</footer>

<script src="../node_modules/jquery/dist/jquery.js"></script>
<script src="../node_modules/jquery-bridget/jquery-bridget.js"></script>

<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/button.js"></script>
<script src="../js/status.js"></script>

<script>
var $postsContainer = $('.posts-container').infiniteScroll({
  path: '.pagination__next',
  append: '.post',
  status: '.scroll-status',
});
</script>

</body>
</html>


================================================
FILE: sandbox/js/masonry-images.js
================================================
/* globals Masonry, imagesLoaded */
let msnry = new Masonry( '.grid', {
  itemSelector: 'none', // select no images on init
  columnWidth: '.grid__col-sizer',
  gutter: '.grid__gutter-sizer',
  percentPosition: true,
  stagger: 30,
  visibleStyle: {
    transform: 'translateY(0)',
    opacity: 1,
  },
  hiddenStyle: {
    transform: 'translateY(100px)',
    opacity: 0,
  },
} );

imagesLoaded( '.grid', function() {
  msnry.options.itemSelector = '.grid__item'; // select proper items
  document.querySelector('.grid').classList.add('are-images-ready');
  let items = document.querySelectorAll('.grid__item');
  msnry.appended( items );
} );

window.infScroll = new InfiniteScroll( '.grid', {
  path: '.pagination__next',
  append: '.grid__item',
  debug: true,
  outlayer: msnry,
  status: '.scroll-status',
  scrollThreshold: 1,
} );


================================================
FILE: sandbox/js/scroll-loader.js
================================================
let container = document.querySelector('.posts-container');
window.infScroll = new InfiniteScroll( container, {
  path: '.pagination__next',
  append: '.post',
  nav: '.pagination',
  status: '.scroll-status',
  debug: true,
  // history: false,
} );


================================================
FILE: sandbox/js/unsplash-masonry.js
================================================
/* globals Masonry, imagesLoaded */

let msnry = new Masonry( '.grid', {
  itemSelector: '.grid-item',
  columnWidth: '.grid-sizer',
  percentPosition: true,
  stagger: 30,
  visibleStyle: { transform: 'translateY(0)', opacity: 1 },
  hiddenStyle: { transform: 'translateY(100px)', opacity: 0 },
} );

const clientId = '9ad80b14098bcead9c7de952435e937cc3723ae61084ba8e729adb642daf0251';

let infScroll = new InfiniteScroll( '.grid', {
  path: `https://api.unsplash.com/photos?client_id=${clientId}&page={{#}}`,
  responseBody: 'json',
  status: '.scroll-status',
  history: false,
} );

let proxyDiv = document.createElement('div');

infScroll.on( 'load', function( data ) {
  // convert data into HTML
  let itemsHTML = data.map( getItem ).join('');
  // get elements from HTML string
  proxyDiv.innerHTML = itemsHTML;
  let items = proxyDiv.querySelectorAll('.grid-item');
  // append items after imagesLoaded
  imagesLoaded( items, function() {
    infScroll.appendItems( items );
    msnry.appended( items );
  } );
} );

// load first page
infScroll.loadNextPage();

// ----- template ----- //

let itemTemplateSrc = document.querySelector('#item-template').innerHTML;

function getItem( photo ) {
  return microTemplate( itemTemplateSrc, photo );
}

// micro templating, sort-of
function microTemplate( src, data ) {
  // replace {{tags}} in source
  return src.replace( /\{\{([\w\-_.]+)\}\}/gi, function( match, key ) {
    // walk through objects to get value
    let value = data;
    key.split('.').forEach( ( part ) => {
      value = value[ part ];
    } );
    return value;
  } );
}


================================================
FILE: sandbox/js/unsplash.js
================================================
let container = document.querySelector('.container');

let unsplashID = '9ad80b14098bcead9c7de952435e937cc3723ae61084ba8e729adb642daf0251';

let infScroll = new InfiniteScroll( '.container', {
  path: `https://api.unsplash.com/photos?page={{#}}&client_id=${unsplashID}`,
  responseBody: 'json',
  history: false,
} );

infScroll.on( 'load', function( data ) {
  let itemsHTML = data.map( getItem ).join('');
  container.innerHTML += itemsHTML;
} );

let itemTemplateSrc = document.querySelector('#item-template').innerHTML;

function getItem( photo ) {
  return microTemplate( itemTemplateSrc, photo );
}

// micro templating, sort-of
function microTemplate( src, data ) {
  // replace {{tags}} in source
  return src.replace( /\{\{([\w\-_.]+)\}\}/gi, function( match, key ) {
    // walk through objects to get value
    let value = data;
    key.split('.').forEach( function( part ) {
      value = value[ part ];
    } );
    return value;
  } );
}

// load first page
infScroll.loadNextPage();


================================================
FILE: sandbox/masonry-images/index.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>Masonry images</title>

  <link rel="stylesheet" href="../css/masonry-images.css" />
  <link rel="stylesheet" href="../css/loader-ellips.css" />

</head>
<body>

<div class="container">

<h1>Masonry images</h1>

<div class="grid">
  <div class="grid__col-sizer"></div>
  <div class="grid__gutter-sizer"></div>
  <div class="grid__item"><img src="https://i.imgur.com/8AYv5Ik.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/2jpy7yO.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/qW7eNlI.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/n3M06QP.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/BNhGcqd.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/TNmDTdL.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/PRc06Uy.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/hHYirXc.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/2Qi40rJ.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/hztiigA.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/3AbaiOH.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/plQzrOk.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/uxYU1h3.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/g4F6d2R.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/s2Tqsfq.jpg" /></div>
</div>

<p><a class="pagination__next" href="page2.html">Next</a></p>

<div class="scroll-status">
  <div class="infinite-scroll-request">
    <div class="loader-ellips">
      <span class="loader-ellips__dot loader-ellips__dot--1"></span>
      <span class="loader-ellips__dot loader-ellips__dot--2"></span>
      <span class="loader-ellips__dot loader-ellips__dot--3"></span>
      <span class="loader-ellips__dot loader-ellips__dot--4"></span>
    </div>
  </div>
  <p class="infinite-scroll-error">No more pages to load</p>
  <p class="infinite-scroll-last">Last page loaded</p>
</div>

</div>

<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../node_modules/get-size/get-size.js"></script>
<script src="../../node_modules/outlayer/item.js"></script>
<script src="../../node_modules/outlayer/outlayer.js"></script>
<script src="../../node_modules/masonry-layout/masonry.js"></script>
<script src="../../node_modules/imagesloaded/imagesloaded.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../../js/button.js"></script>
<script src="../../js/status.js"></script>
<script src="../js/masonry-images.js"></script>

</body>
</html>


================================================
FILE: sandbox/masonry-images/page2.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>Masonry images - page 2</title>

  <link rel="stylesheet" href="../css/masonry-images.css" />
  <link rel="stylesheet" href="../css/loader-ellips.css" />

</head>
<body>

<div class="container">

<h1>Masonry images - page 2</h1>

<div class="grid">
  <div class="grid__col-sizer"></div>
  <div class="grid__gutter-sizer"></div>
  <div class="grid__item"><img src="https://i.imgur.com/9eHhuCK.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/kwxMKGr.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/fp6SDtQ.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/INAj06W.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/oLQ1tBD.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/2izuAhd.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/qoWEPJe.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/6Wr89AO.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/PQh9aqW.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/uhORS25.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/QeN4jBt.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/ahtrWkN.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/fd1Mmhy.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/AOgABvd.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/ypd73RX.jpg" /></div>
</div>

<p><a class="pagination__next" href="page3.html">Next</a></p>

<div class="scroll-status">
  <div class="infinite-scroll-request">
    <div class="loader-ellips">
      <span class="loader-ellips__dot loader-ellips__dot--1"></span>
      <span class="loader-ellips__dot loader-ellips__dot--2"></span>
      <span class="loader-ellips__dot loader-ellips__dot--3"></span>
      <span class="loader-ellips__dot loader-ellips__dot--4"></span>
    </div>
  </div>
  <p class="infinite-scroll-error">No more pages to load</p>
  <p class="infinite-scroll-last">Last page loaded</p>
</div>

</div>

<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../node_modules/get-size/get-size.js"></script>
<script src="../../node_modules/outlayer/item.js"></script>
<script src="../../node_modules/outlayer/outlayer.js"></script>
<script src="../../node_modules/masonry-layout/masonry.js"></script>
<script src="../../node_modules/imagesloaded/imagesloaded.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../../js/button.js"></script>
<script src="../../js/status.js"></script>
<script src="../js/masonry-images.js"></script>

</body>
</html>


================================================
FILE: sandbox/masonry-images/page3.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>Masonry images - page 3</title>

  <link rel="stylesheet" href="../css/masonry-images.css" />
  <link rel="stylesheet" href="../css/loader-ellips.css" />

</head>
<body>

<div class="container">

<h1>Masonry images - page 3</h1>

<div class="grid">
  <div class="grid__col-sizer"></div>
  <div class="grid__gutter-sizer"></div>
  <div class="grid__item"><img src="https://i.imgur.com/ZPPFND3.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/EpYbuG7.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/Qmz61wo.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/laIuV0D.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/aPia86B.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/777dcVU.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/iQRKg2a.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/kXUHDn5.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/MV9SvaP.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/qjQ9XWl.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/ZJ088Tk.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/XREWwIc.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/SuZLV2U.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/71H2B0k.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/vxOA4hg.jpg" /></div>
</div>

<p><a class="pagination__next" href="page4.html">Next</a></p>

<div class="scroll-status">
  <div class="infinite-scroll-request">
    <div class="loader-ellips">
      <span class="loader-ellips__dot loader-ellips__dot--1"></span>
      <span class="loader-ellips__dot loader-ellips__dot--2"></span>
      <span class="loader-ellips__dot loader-ellips__dot--3"></span>
      <span class="loader-ellips__dot loader-ellips__dot--4"></span>
    </div>
  </div>
  <p class="infinite-scroll-error">No more pages to load</p>
  <p class="infinite-scroll-last">Last page loaded</p>
</div>

</div>

<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../node_modules/get-size/get-size.js"></script>
<script src="../../node_modules/outlayer/item.js"></script>
<script src="../../node_modules/outlayer/outlayer.js"></script>
<script src="../../node_modules/masonry-layout/masonry.js"></script>
<script src="../../node_modules/imagesloaded/imagesloaded.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../../js/button.js"></script>
<script src="../../js/status.js"></script>
<script src="../js/masonry-images.js"></script>

</body>
</html>


================================================
FILE: sandbox/masonry-images/page4.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>Masonry images - page 4</title>

  <link rel="stylesheet" href="../css/masonry-images.css" />
  <link rel="stylesheet" href="../css/loader-ellips.css" />

</head>
<body>

<div class="container">

<h1>Masonry images - page 4</h1>

<div class="grid">
  <div class="grid__col-sizer"></div>
  <div class="grid__gutter-sizer"></div>
  <div class="grid__item"><img src="https://i.imgur.com/qEQsC3o.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/RLlbgpi.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/1t5kzGJ.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/FEFyXGY.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/N12L8j8.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/t5r6sHg.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/zKfV3Pu.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/8BR5akz.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/J3cPlfc.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/FTYmLBR.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/kyGu5xM.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/9rEbt6I.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/jK3PhZu.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/1EgrcMv.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/ZemMjK8.jpg" /></div>
</div>

<p><a class="pagination__next" href="page5.html">Next</a></p>

<div class="scroll-status">
  <div class="infinite-scroll-request">
    <div class="loader-ellips">
      <span class="loader-ellips__dot loader-ellips__dot--1"></span>
      <span class="loader-ellips__dot loader-ellips__dot--2"></span>
      <span class="loader-ellips__dot loader-ellips__dot--3"></span>
      <span class="loader-ellips__dot loader-ellips__dot--4"></span>
    </div>
  </div>
  <p class="infinite-scroll-error">No more pages to load</p>
  <p class="infinite-scroll-last">Last page loaded</p>
</div>

</div>

<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../node_modules/get-size/get-size.js"></script>
<script src="../../node_modules/outlayer/item.js"></script>
<script src="../../node_modules/outlayer/outlayer.js"></script>
<script src="../../node_modules/masonry-layout/masonry.js"></script>
<script src="../../node_modules/imagesloaded/imagesloaded.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../../js/button.js"></script>
<script src="../../js/status.js"></script>
<script src="../js/masonry-images.js"></script>

</body>
</html>


================================================
FILE: sandbox/masonry-images/page5.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>Masonry images - page 5</title>

  <link rel="stylesheet" href="../css/masonry-images.css" />

</head>
<body>

<div class="container">

<h1>Masonry images - page 5</h1>

<div class="grid">
  <div class="grid__col-sizer"></div>
  <div class="grid__gutter-sizer"></div>
  <div class="grid__item"><img src="https://i.imgur.com/DmSgSF6.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/N9IPWC1.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/EHlzx9S.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/jtLvPpy.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/0WNdAS5.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/oytir6O.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/9T3zb3D.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/utsastL.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/jUfMIMY.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/GSKGpmx.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/QDHO514.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/PuMcFeB.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/9sRo7tL.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/azKDpNP.jpg" /></div>
  <div class="grid__item"><img src="https://i.imgur.com/0zhNKir.jpg" /></div>
</div>

</div>

<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../node_modules/get-size/get-size.js"></script>
<script src="../../node_modules/outlayer/item.js"></script>
<script src="../../node_modules/outlayer/outlayer.js"></script>
<script src="../../node_modules/masonry-layout/masonry.js"></script>
<script src="../../node_modules/imagesloaded/imagesloaded.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../../js/button.js"></script>
<script src="../../js/status.js"></script>
<script src="../js/masonry-images.js"></script>

</body>
</html>


================================================
FILE: sandbox/page/2.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>Page 2</title>

  <link rel="stylesheet" href="../css/blog.css" />
  <link rel="stylesheet" href="../css/loader-ellips.css" />

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>Blog page 2</h1>

  </div>
</div>

<div class="container">
  <div class="posts-container">
  <article class="post">
    <a class="post-header" href="/blog/sunset-spark-logo">
      <h1 class="post-header__title">Sunset Spark logo</h1>
      <p class="post-header__date">6 Mar 2017</p>
    </a>
    <div class="post__content">
  <p><img src="https://i.imgur.com/c40SxNg.png" alt="Sunset Spark logo"></p>
  <p><a href="http://www.sunsetspark.org/">Sunset Spark</a> is a technology and science school in Sunset Park, Brooklyn, NY. They teach new American families all the subjects I would have devoured as a kid: robotics, creative coding, game development. <a href="https://www.instagram.com/SunsetSparkNYC/">Just take a peak at their Insta</a>. I&#39;m proud to have designed their new logo.</p>
  <p>The double-S and bolt logo is a nice balance between approachable/fun and professional/serious. The bolt is front and center. The S&#39;s take a back seat formed by the bolt and the outlines.</p>

    </div>
  </article>
  <article class="post">
    <a class="post-header" href="/blog/every-vector-2016">
      <h1 class="post-header__title">Every vector 2016</h1>
      <p class="post-header__date">9 Dec 2016</p>
    </a>
    <div class="post__content">
  <p><a href="https://i.imgur.com/mXaAcNO.png"><img src="https://i.imgur.com/mXaAcNO.png" alt="Metafizzy every vector 2016"></a></p>
  <p><a href="http://www.draplin.com/1998/01/ddc100_pretty_much_everything_up_to_oct_11_2013_poster.html">Draplin-esque</a> collage. Every vector made in 2016. Accepted, rejected, upcoming, or unused.</p>
  <script>
    console.log('hello page 2!')
  </script>
    </div>
  </article>
</div>


  <nav class="pagination">
      <a class="pagination__previous" href="../scroll-loader.js">Previous &middot; 1</a>
    <span class="pagination__current">Page 2 of 17</span>
      <a class="pagination__next" href="3.html">Next &middot; 3</a>
  </nav>

</div>

<footer class="site-footer">
  <div class="container">
    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
  </div>
</footer>

<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../js/scroll-loader.js"></script>

</body>
</html>


================================================
FILE: sandbox/page/3.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>#3 page</title>

  <link rel="stylesheet" href="../css/blog.css" />
  <link rel="stylesheet" href="../css/loader-ellips.css" />

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>Blog page 3</h1>

  </div>
</div>

<div class="container">
  <div class="posts-container">

  <article class="post">
    <a class="post-header" href="/blog/shepherd-logo">
      <h1 class="post-header__title">Shepherd logo & brand</h1>
      <p class="post-header__date">6 Dec 2016</p>
    </a>
    <div class="post__content">
  <p><img src="https://i.imgur.com/e0Y9rfd.png" alt="Shepherd brand"></p>
  <p>Shepherd is a platform for business owners to build communities and peer groups so they can better face the challenges and opportunities they share (launching soon!). I designed the brand with supervision from Shepherd&#39;s lead, Ben. The main logo depicts two crossed shepherd crooks forming an S.</p>
  <p>The alternate logo is a dog, an Australian shepherd. Shepherds still use dogs to herd and control their flocks. Dogs are a great symbol of companionship and loyalty, qualities central to Shepherd. Herding breeds like the Aussie and border collie are the smartest.</p>
  <p>Erin loves Aussies. Even as her husband, I&#39;ll always be second place in her heart, behind her family Aussie, Tess. She was thrilled to find my screen like this:</p>
  <p><img src="https://i.imgur.com/N7OuNQS.jpg" alt="Aussie desktop"></p>
  <p>Designing an Aussie logo was a fun challenge. Blue merle Aussies have striking coloring, with gray and black patchwork  and tan points. My solution was to stylize the whole image: use a striped pattern for the patches, punch up the colors to blue and gold, and stick to an angular grid. It&#39;s a dog you won&#39;t forget.</p>

    </div>
  </article>

<article class="post">
  <a class="post-header" href="/blog/logo-pizza-delivered">
    <h1 class="post-header__title">Logo Pizza Delivered</h1>
    <p class="post-header__date">2 Oct 2016</p>
  </a>
  <div class="post__content">
<p><a href="http://logo.pizza"><img src="https://i.imgur.com/u40AMfs.png" alt="Logo Pizza"></a></p>
<p>After shipping <a href="/blog/flickity-v2-released">Flickity v2</a> thus wrapping 2016&#39;s huge development project, I didn&#39;t have it in me to write another line of code. I discussed my state of mind and motivation in this 3 min podcast.</p>
<p><a class="embedly-card" href="https://bumpers.fm/e/avbd99o4m4ug00o93t30">New thing, shortsightedness on Bumpers</a></p>
<script async src="//cdn.embedly.com/widgets/platform.js"></script>

<p>Looking to change things up, I started making logos. </p>
<p>Man, I love logos. A little piece of imagery that represents the ideal you want your project to be — that&#39;s design magic right there. I&#39;ve been able to work on some great logo projects, but I&#39;ve been itching to do more. Rather than wait for projects to come my way, I gave myself a project of my own: design 50 logos. <strong>50 logos in 30 days.</strong></p>
<p>Fifty logos is a lot. At least one or two a day for an entire month. I tried pushing myself: exploring different styles, subjects, and techniques. It was like design boot camp: working all those muscles you never use.</p>

  </div>
</article>
</div>

  <nav class="pagination">
      <a class="pagination__previous" href="2.html">Previous &middot; 2</a>
    <span class="pagination__current">Page 3 of 17</span>
      <a class="pagination__next" href="4.html">Next &middot; 4</a>
  </nav>

</div>

<footer class="site-footer">
  <div class="container">
    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
  </div>
</footer>

<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../js/scroll-loader.js"></script>

</body>
</html>


================================================
FILE: sandbox/page/4.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>4th page</title>

  <link rel="stylesheet" href="../css/blog.css" />
  <link rel="stylesheet" href="../css/loader-ellips.css" />

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>Blog page 4</h1>

  </div>
</div>

<div class="container">
<div class="posts-container">
<article class="post">
  <a class="post-header" href="/blog/fetchy-shiba-logo">
    <h1 class="post-header__title">Fetchy Shiba logo comin' to get ya</h1>
    <p class="post-header__date">18 Aug 2016</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/zwxfN0H.png" alt="Fetchy logos"></p>
<p><a href="https://fetchy.io">Fetchy</a> takes YouTube videos and converts them to downloadable videos and audio files. Thank heavens. Pulling out videos to make clips and gifs has been difficult for too long. Fetchy is a great service that I consistently need. I was pumped to be tasked with designing its logo.</p>
<p>I kicked off the concepts with dogs, lots of dogs. Why over think it? I sketched out a variety of breeds, actions, and styles. I tried using the most meme-able breeds: corgis and pugs. To mix things up, I sketched a couple &quot;f bird&quot; concepts and more straight-forward monogram letters.</p>
<p>Look at that winged puppy. Too bad he didn&#39;t get used. Fly high, little guy.</p>

  </div>
</article>
<article class="post">
  <a class="post-header" href="/blog/codepen-showcase-round-1">
    <h1 class="post-header__title">CodePen showcase: Round 1</h1>
    <p class="post-header__date">9 Aug 2016</p>
  </a>
  <div class="post__content">
<p>Metafizzy&#39;s libraries have hundreds of CodePen demos. One for every feature, option, and behavior. But these demos are simplified examples. They don&#39;t do a good job of showing off what they&#39;re capable of.  So I reached out to the true code artists of CodePen to see how they could make Isotope, Flickity, and Packery shine in the spotlight. Give &#39;em the old razzle-dazzle.</p>
<p>I&#39;ll be collecting these in the <a href="https://codepen.io/collection/AVjkpG/">Metafizzy showcase CodePen collection</a>, as well as individual collections for each library.</p>
<ul>
<li><a href="https://codepen.io/collection/DgkGmy">Isotope showcase</a></li>
<li><a href="https://codepen.io/collection/AYWzeJ">Flickity showcase</a></li>
<li><a href="https://codepen.io/collection/DRRLpZ/">Packery showcase</a></li>
</ul>
<p><a href="https://codepen.io/Kseso">Kseso</a> makes the most of Flickity and CSS, using <code>is-selected</code> classes to trigger transitions for each slide. Within each slide is a great mix of imagery and typography at that!</p>
<p data-height="400" data-theme-id="dark" data-slug-hash="oLyqGg" data-default-tab="result" data-user="Kseso" data-embed-version="2" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/Kseso/pen/oLyqGg/">Playing with Flickity</a> by Kseso (<a href="https://codepen.io/Kseso">@Kseso</a>) on <a href="https://codepen.io">CodePen</a>.</p>

<p><a href="https://codepen.io/jshawl">Jesse Shawl</a> made a slide puzzle with Packery. I can&#39;t believe this actually works!</p>
<p data-height="400" data-theme-id="dark" data-slug-hash="ZORWxv" data-default-tab="result" data-user="jshawl" data-embed-version="2" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/jshawl/pen/ZORWxv/">Order the tiles</a> by Jesse Shawl (<a href="https://codepen.io/jshawl">@jshawl</a>) on <a href="https://codepen.io">CodePen</a>.</p>

<p><a href="https://codepen.io/pixelass">Gregor Adams</a> took the Packery concept composed this 3D cube ballet. I love how the cubes align even with staggered animation.</p>
<p data-height="400" data-theme-id="dark" data-slug-hash="wWxPqg" data-default-tab="result" data-user="pixelass" data-embed-version="2" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/pixelass/pen/wWxPqg/">Pack(ev)ery thing</a> by Gregor Adams (<a href="https://codepen.io/pixelass">@pixelass</a>) on <a href="https://codepen.io">CodePen</a>.</p>

<p>But he didn&#39;t stop there. Gregor made another Packery demo, this time using simpler rectangles. Check out how it works when you resize it.</p>
<p data-height="400" data-theme-id="dark" data-slug-hash="zBLrRr" data-default-tab="result" data-user="pixelass" data-embed-version="2" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/pixelass/pen/zBLrRr/">Packery hackery</a> by Gregor Adams (<a href="https://codepen.io/pixelass">@pixelass</a>) on <a href="https://codepen.io">CodePen</a>.</p>

<p>Perhaps the best use of Isotope ever, <a href="https://codepen.io/acjdesigns">Antoinette Janus</a> makes sense of many characters and fusions in Steven Universe. I have a hard time remembering the difference between Sugilite and Sardonyx ;)</p>
<p data-height="400" data-theme-id="dark" data-slug-hash="GqXgAE" data-default-tab="result" data-user="acjdesigns" data-embed-version="2" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/acjdesigns/pen/GqXgAE/">Steven Universe x Isotope [Sponsored]</a> by Antoinette Janus (<a href="https://codepen.io/acjdesigns">@acjdesigns</a>) on <a href="https://codepen.io">CodePen</a>.</p>

<p><a href="https://codepen.io/bennettfeely">Bennett Feely</a> makes a 3D, hovering Packery layout. It&#39;s melting my mind how this works.</p>
<p data-height="400" data-theme-id="dark" data-slug-hash="JKBJbx" data-default-tab="result" data-user="bennettfeely" data-embed-version="2" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/bennettfeely/pen/JKBJbx/">Packery layout with 3D blocks</a> by Bennett Feely (<a href="https://codepen.io/bennettfeely">@bennettfeely</a>) on <a href="https://codepen.io">CodePen</a>.</p>

<p><a href="https://codepen.io/katydecorah">Katy DeCorah</a> brings her ever-impressive style to make this captivating grid animation using Packery. This one is fun to resize.</p>
<p data-height="400" data-theme-id="dark" data-slug-hash="qNygjp" data-default-tab="result" data-user="katydecorah" data-embed-version="2" data-preview="true" class="codepen">See the Pen <a href="https://codepen.io/katydecorah/pen/qNygjp/">Geo scope</a> by Katy DeCorah (<a href="https://codepen.io/katydecorah">@katydecorah</a>) on <a href="https://codepen.io">CodePen</a>.</p>

<p>It&#39;s a delight to see what creative coders can come up with.</p>
<p>We have some more artists lined up so stay tuned for Round 2!</p>
<script async src="//assets.codepen.io/assets/embed/ei.js"></script>

  </div>
</article>


  <nav class="pagination">
      <a class="pagination__previous" href="/blog/page3">Previous &middot; 3</a>
    <span class="pagination__current">Page 4 of 17</span>
      <a class="pagination__next" href="5.html">Next &middot; 5</a>
  </nav>
</div>
</div>

<footer class="site-footer">
  <div class="container">
    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
  </div>
</footer>

<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../js/scroll-loader.js"></script>

</body>
</html>


================================================
FILE: sandbox/page/5.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>The fifth page</title>

  <link rel="stylesheet" href="../css/blog.css" />
  <link rel="stylesheet" href="../css/loader-ellips.css" />

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>Blog page 5</h1>

  </div>
</div>

<div class="container">
<article class="post">
  <a class="post-header" href="/blog/flickity-takes-the-field">
    <h1 class="post-header__title">Flickity takes The Field</h1>
    <p class="post-header__date">17 Dec 2015</p>
  </a>
  <div class="post__content">
<p><a href="http://www.thefieldmag.com/"><em>The Field</em></a> is a new outdoor lifestyle publication, or as founder Chris Stillitano puts it, &quot;<em>The Field</em> is a place for good design and the great outdoors.&quot; It&#39;s also a great place to see Flickity in use.</p>
<p><em>The Field</em>&#39;s beautiful photo galleries are made with <a href="https://flickity.metafizzy.co/">Flickity</a>. It&#39;s great to see how they&#39;ve taken advantage of Flickity&#39;s <a href="https://flickity.metafizzy.co/style.html#previous-next-buttons">customizable previous &amp; buttons</a> to minimally style and position them. The slide counters change <a href="https://flickity.metafizzy.co/api.html#cellselect">on <code>cellSelect</code></a>.</p>
<div class="fit-video">
  <iframe src="https://vid.me/e/opMu?muted=1" width="740" height="480" frameborder="0" allowfullscreen webkitallowfullscreen mozallowfullscreen scrolling="no"></iframe>
</div>

<p>The site has a keen design sense, utilizing different layouts for its various content types. So much care went into these photo galleries. Watch how images resize and center while resizing the browser, even with images of varying aspect ratios.</p>
<div class="fit-video">
  <iframe src="https://vid.me/e/LIHQ?muted=1" width="806" height="480" frameborder="0" allowfullscreen webkitallowfullscreen mozallowfullscreen scrolling="no"></iframe>
</div>

  </div>
</article>
<article class="post">
  <a class="post-header" href="/blog/fizzy-bear-branded">
    <h1 class="post-header__title">Fizzy bear branded</h1>
    <p class="post-header__date">1 Dec 2015</p>
  </a>
  <div class="post__content">
<p>Fizzy&#39;s got a brand new brand.</p>
<p><img src="https://i.imgur.com/T0sVgzD.png" alt="Metafizzy brand"></p>
<p><a href="/blog/logotype-james-edmondson">The previous logotype was a beaut</a> and served Metafizzy well. But it did have some issues (No fault of James&#39; — these issues were all self-inflicted as I requested).</p>
<ul>
<li>Legibility trouble: People would read &quot;Meta fuzzy&quot; or worse &quot;Meta furry&quot;</li>
<li>The two line treatment led to people spelling the name as two words &quot;Meta Fizzy&quot;</li>
<li>It did not reduce well. It didn&#39;t hold up at small sizes, like a Twitter avatar or favicon</li>
</ul>

  </div>
</article>


  <nav class="pagination">
      <a class="pagination__previous" href="/blog/page4">Previous &middot; 4</a>
    <span class="pagination__current">Page 5 of 17</span>
      <a class="pagination__next" href="6.html">Next &middot; 6</a>
  </nav>

</div>

<footer class="site-footer">
  <div class="container">
    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
  </div>
</footer>

<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../js/scroll-loader.js"></script>

</body>
</html>


================================================
FILE: sandbox/page/6.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>Page number 6</title>

  <link rel="stylesheet" href="../css/blog.css" />
  <link rel="stylesheet" href="../css/loader-ellips.css" />

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>Blog page 6</h1>

  </div>
</div>

<div class="container">
  <div class="posts-container">
<article class="post">
  <a class="post-header" href="/blog/triggering-jquery-vanilla-js-events">
    <h1 class="post-header__title">Triggering jQuery and vanilla JS events</h1>
    <p class="post-header__date">3 Jul 2015</p>
  </a>
  <div class="post__content">
<p>You can now bind to jQuery events in <a href="https://isotope.metafizzy.co/">Isotope</a>, <a href="https://packery.metafizzy.co/">Packery</a>, and <a href="http://desandro.masonry.com">Masonry</a>. The recent upgrades allow you to use standard jQuery event methods <code>.on()</code>, <code>.off</code>, and <code>.one()</code>, rather than using ugly plugin method syntax.</p>
<pre><code class="lang-js"><span class="hljs-comment">// previous plugin method syntax</span>
<span class="hljs-comment">// Isotope &lt;= v2.2.0</span>
$grid.isotope( <span class="hljs-string">'on'</span>, <span class="hljs-string">'layoutComplete'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{...})

<span class="hljs-comment">// standard jQuery event</span>
<span class="hljs-comment">// Isotope &gt;= v2.2.1</span>
$grid.on( <span class="hljs-string">'layoutComplete'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{...})
</code></pre>
<p><a href="https://codepen.io/desandro/pen/scajv">View Isotope layoutComplete demo on CodePen</a>.</p>
<p>This feature is already in <a href="https://flickity.metafizzy.co/">Flickity</a> and <a href="http://draggabilly.desandro.com/">Draggabilly</a>. It was prime time to port it over to the layout libraries.</p>

  </div>
</article>
<article class="post">
  <a class="post-header" href="/blog/dropping-ie8-9-support">
    <h1 class="post-header__title">Dropping IE8 and 9 support</h1>
    <p class="post-header__date">1 Jul 2015</p>
  </a>
  <div class="post__content">
<p>2015 will be the last year Metafizzy supports IE8 and 9. In 2016, we&#39;ll release new major versions of our libraries that remove support for the Internet Explorers of yore.</p>
<p>The time has come. <a href="http://caniuse.com/usage-table">Global browser usage</a> is especially low for both browsers. IE8 is at 2%. IE9 is at 1.5%.</p>
<ul>
<li><a href="https://www.microsoft.com/en-us/WindowsForBusiness/end-of-xp-support">Microsoft ended support for Windows XP in 2014</a>, <a href="https://support.microsoft.com/en-us/lifecycle/search?sort=PN&amp;alpha=internet%20explorer">support for IE8 will end in January 2016</a>. IE9 will only be supported for Windows Vista.</li>
<li>The New York Times <a href="http://www.nytimes.com/content/help/site/ie8-support.html">dropped IE8 support in 2014</a> and <a href="http://www.nytimes.com/content/help/site/ie9-support.html">IE9 support in June this year</a></li>
<li><a href="http://emberjs.com/blog/2015/04/20/ie8-support-update.html">Ember 2.0 will not support IE8</a>. <a href="https://github.com/emberjs/rfcs/pull/45">Read Ember community input on IE support</a></li>
<li><a href="http://blog.getbootstrap.com/2014/10/29/bootstrap-3-3-0-released/">Bootstrap 4 will drop support for IE8</a></li>
<li><a href="http://analytics.blogspot.com/2014/12/keeping-ga-web-experience-modern.html">Google Analytics dropped support for IE9 in January this year</a></li>
</ul>
<p>IE8 and 9 were the last browsers to have significant feature gaps. Dropping their support will allow a lot of ugly code to be removed. Polyfill libraries can be removed like  <a href="https://github.com/desandro/eventie">eventie</a>, <a href="https://github.com/desandro/classie">classie</a>, and <a href="https://github.com/desandro/doc-ready">doc-ready</a>.</p>
<p>If you still require IE8 and 9 support, previous versions will still be completely available to download and view documentation. The old versions will no longer get bug fixes or improved features, but you can continue using them as long as you like.</p>
<p>We&#39;ve opened issues to track this change for each library. Follow along:</p>
<ul>
<li><a href="https://github.com/metafizzy/isotope/issues/947">Isotope dropping IE8 and 9 support</a></li>
<li><a href="https://github.com/metafizzy/flickity/issues/178">Flickity dropping IE8 and 9 support</a></li>
<li><a href="https://github.com/metafizzy/packery/issues/281">Packery dropping IE8 and 9 support</a></li>
<li><a href="https://github.com/desandro/masonry/issues/719">Masonry dropping IE8 and 9 support</a></li>
</ul>

  </div>
</article>
</div>

  <nav class="pagination">
      <a class="pagination__previous" href="/blog/page5">Previous &middot; 5</a>
    <span class="pagination__current">Page 6 of 17</span>
  </nav>

</div>

<footer class="site-footer">
  <div class="container">
    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>
  </div>
</footer>

<script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../../js/core.js"></script>
<script src="../../js/scroll-watch.js"></script>
<script src="../../js/page-load.js"></script>
<script src="../../js/history.js"></script>
<script src="../js/scroll-loader.js"></script>

</body>
</html>


================================================
FILE: sandbox/prefill.html
================================================

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>prefill</title>

  <link rel="stylesheet" href="css/blog.css" />

  <style>
    .post {
      max-height: 100px;
      margin: 20px 0;
      overflow-y: hidden;
    }
  </style>

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>prefill</h1>

  </div>
</div>

<div class="container">
  <div class="posts-container">
<article class="post">
  <a class="post-header" href="/blog/wyday-logos">
    <h1 class="post-header__title">wyDay logos</h1>
    <p class="post-header__date">22 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes &quot;premium software development tools for high-tech companies.&quot; A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before &amp; after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>

  </div>
</article>
<article class="post">
  <a class="post-header" href="/blog/mealio-logo">
    <h1 class="post-header__title">Mealio logo</h1>
    <p class="post-header__date">8 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user&#39;s preferences. It&#39;s like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly &amp; clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>

  </div>
</article>

</div> <!-- posts-container -->

  <div class="scroll-status">
    <div class="infinite-scroll-request">
      <div class="loader-ellips">
        <span class="loader-ellips__dot loader-ellips__dot--1"></span>
        <span class="loader-ellips__dot loader-ellips__dot--2"></span>
        <span class="loader-ellips__dot loader-ellips__dot--3"></span>
        <span class="loader-ellips__dot loader-ellips__dot--4"></span>
      </div>
    </div>
    <p class="infinite-scroll-error">Last page reached</p>
  </div>

  <nav class="pagination">
    <span class="pagination__current">Page 1 of 17</span>
      <a class="pagination__next" href="page/2.html">Next &middot; 2</a>
  </nav>
</div> <!-- container -->

<footer class="site-footer">
  <div class="container">

    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>


  </div>
</footer>

<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/status.js"></script>
<script>
var infScroll = new InfiniteScroll( '.posts-container', {
  path: '.pagination__next',
  append: '.post',
  prefill: true,
  history: false,
});
</script>

</body>
</html>


================================================
FILE: sandbox/scroll-3.html
================================================

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>scroll 3, then button (Facebook)</title>

  <link rel="stylesheet" href="css/blog.css" />

<style>

  .load-more-button {
    display: none;
  }

</style>

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>scroll 3, then button</h1>

  </div>
</div>

<div class="container">
  <div class="posts-container">
<article class="post">
  <a class="post-header" href="/blog/wyday-logos">
    <h1 class="post-header__title">wyDay logos</h1>
    <p class="post-header__date">22 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes &quot;premium software development tools for high-tech companies.&quot; A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before &amp; after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>

  </div>
</article>
<article class="post">
  <a class="post-header" href="/blog/mealio-logo">
    <h1 class="post-header__title">Mealio logo</h1>
    <p class="post-header__date">8 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user&#39;s preferences. It&#39;s like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly &amp; clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>

  </div>
</article>

</div>

  <p>
    <button class="load-more-button">Load more</button>
  </p>

  <nav class="pagination">
    <span class="pagination__current">Page 1 of 17</span>
      <a class="pagination__next" href="page/2.html">Next &middot; 2</a>
  </nav>

  </div>
  

<footer class="site-footer">
  <div class="container">

    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>


  </div>
</footer>

<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script>
var container = document.querySelector('.posts-container');
var infScroll = new InfiniteScroll( container, {
  path: '.pagination__next',
  append: '.post',
  historyTitle: true,
  history: false,
});

var loadMoreButton = document.querySelector('.load-more-button');

var loadPageCount = 0;
infScroll.on( 'load', onPageLoad );

function onPageLoad() {
  if ( infScroll.loadCount == 3 ) {
    loadMoreButton.style.display = 'inline-block';
    infScroll.options.loadOnScroll = false;
    infScroll.off( 'load', onPageLoad );
  }
}

loadMoreButton.onclick = function() {
  infScroll.loadNextPage();
};
</script>

</body>
</html>


================================================
FILE: sandbox/scroll-loader.html
================================================

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>scroll loader</title>

  <link rel="stylesheet" href="css/blog.css" />
  <link rel="stylesheet" href="css/loader-ellips.css" />

</head>
<body>

<div class="site-header">
  <div class="container">

    <h1>scroll loader</h1>

  </div>
</div>

<div class="container">
  <div class="posts-container">
<article class="post">
  <a class="post-header" href="/blog/wyday-logos">
    <h1 class="post-header__title">wyDay logos</h1>
    <p class="post-header__date">22 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/pbs1Ylg.png" alt="wyDay logos"></p>
<p>After seeing my work on <a href="http://logo.pizza">Logo Pizza</a>, Wyatt at <a href="https://wyday.com/">wyDay</a> employed my services to design a new set of logos for wyDay and its products, LimeLM and wyBuild. wyDay makes &quot;premium software development tools for high-tech companies.&quot; A kindred spirit for Metafizzy!</p>
<p><img src="https://i.imgur.com/jUySqoc.png" alt="wyDay logos before &amp; after"></p>
<p>wyDay already had a strong brand to work with. That LimeLM pirate with lime eye patch is a great visual — easily memorable. The wyDay and wyBuild logos were a bit generic, so I was offered a blank slate for them. The end result is a set of straight-forward, iconic emblems.</p>

  </div>
</article>
<article class="post">
  <a class="post-header" href="/blog/mealio-logo">
    <h1 class="post-header__title">Mealio logo</h1>
    <p class="post-header__date">8 Mar 2017</p>
  </a>
  <div class="post__content">
<p><img src="https://i.imgur.com/3Ycdt9U.png" alt="Mealio logo"></p>
<p><em>If you eat and if you even remotely like food then <a href="https://www.mealio.com">Mealio</a> is the right place for you.</em> Mealio helps people solve the problem of constantly having to create a weekly meal plan by generating them based on the user&#39;s preferences. It&#39;s like Spotify for food.</p>
<p>I was tasked to design the Mealio logo. I delivered on an friendly &amp; clever M monogram that evokes the approachable and helpful qualities of Mealio. The fork and knife fitting within the counters of the M say it all. Knife, fork, M = Mealio.</p>

  </div>
</article>

</div> <!-- posts-container -->

  <div class="scroll-status">
    <div class="infinite-scroll-request">
      <div class="loader-ellips">
        <span class="loader-ellips__dot loader-ellips__dot--1"></span>
        <span class="loader-ellips__dot loader-ellips__dot--2"></span>
        <span class="loader-ellips__dot loader-ellips__dot--3"></span>
        <span class="loader-ellips__dot loader-ellips__dot--4"></span>
      </div>
    </div>
    <p class="infinite-scroll-error">No more pages to load</p>
    <p class="infinite-scroll-last">Last page loaded</p>
  </div>

  <nav class="pagination">
    <span class="pagination__current">Page 1 of 17</span>
      <a class="pagination__next" href="page/2.html">Next &middot; 2</a>
  </nav>
</div> <!-- container -->

<footer class="site-footer">
  <div class="container">

    <p class="site-footer__fizzy-makes">Metafizzy makes delightful UI libraries and logos</p>


  </div>
</footer>

<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/button.js"></script>
<script src="../js/status.js"></script>
<script src="js/scroll-loader.js"></script>

</body>
</html>


================================================
FILE: sandbox/unsplash-masonry.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>unsplash masonry</title>

  <link rel="stylesheet" href="css/loader-ellips.css" />

<style>

html { overflow-y: scroll; }

body {
  padding-bottom: 400px;
}

.grid-sizer, .grid-item {
  width: 25%;
}

.grid-item img {
  display: block;
  max-width: 100%;
}

.grid-item__caption {
  position: absolute;
  left: 0;
  bottom: 0;
  font-size: 14px;
}

.grid-item__caption a {
  color: white;
  padding: 0 10px;
  text-decoration: none;
}

</style>

</head>
<body>

  <h1>unsplash masonry</h1>

  <p>Loading photos from <a href="https://unsplash.com/developers?utm_source=infinite-scroll-demos&utm_medium=referral&utm_campaign=api-credit">Unsplash API</a></p>

  <div class="grid">
    <div class="grid-sizer"></div>
  </div>

  <div class="scroll-status">
    <div class="infinite-scroll-request">
      <div class="loader-ellips">
        <span class="loader-ellips__dot loader-ellips__dot--1"></span>
        <span class="loader-ellips__dot loader-ellips__dot--2"></span>
        <span class="loader-ellips__dot loader-ellips__dot--3"></span>
        <span class="loader-ellips__dot loader-ellips__dot--4"></span>
      </div>
    </div>
    <p class="infinite-scroll-error">No more pages to load</p>
    <p class="infinite-scroll-last">Last page loaded</p>
  </div>

<script type="text/html" id="item-template">
  <div class="grid-item">
    <img src="{{urls.small}}" alt="Photo by {{user.name}}" />
    <p class="grid-item__caption">
      <a href="{{user.links.html}}?utm_source=infinite-scroll-demos&utm_medium=referral&utm_campaign=api-credit">{{user.name}}</a>
    </p>
  </div>
</script>

<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>

<script src="../node_modules/imagesloaded/imagesloaded.js"></script>
<script src="../node_modules/get-size/get-size.js"></script>
<script src="../node_modules/outlayer/item.js"></script>
<script src="../node_modules/outlayer/outlayer.js"></script>
<script src="../node_modules/masonry-layout/masonry.js"></script>

<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/status.js"></script>
<script src="js/unsplash-masonry.js"></script>

</body>
</html>


================================================
FILE: sandbox/unsplash.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>unsplash</title>

<style>
#item-template { display: none; }
</style>

</head>
<body>

  <h1>unsplash</h1>

  <p>Loading photos from <a href="https://unsplash.com/developers?utm_source=infinite-scroll-demos&utm_medium=referral&utm_campaign=api-credit">Unsplash API</a></p>

  <div class="container"></div>

<script type="text/html" id="item-template">
  <div class="item">
    <img src="{{urls.small}}" />
    <p class="caption">
      <a href="{{user.links.html}}?utm_source=infinite-scroll-demos&utm_medium=referral&utm_campaign=api-credit">{{user.name}}</a>
    </p>
  </div>
</script>

<script src="../node_modules/ev-emitter/ev-emitter.js"></script>
<script src="../node_modules/fizzy-ui-utils/utils.js"></script>
<script src="../js/core.js"></script>
<script src="../js/scroll-watch.js"></script>
<script src="../js/page-load.js"></script>
<script src="../js/history.js"></script>
<script src="../js/status.js"></script>
<script src="js/unsplash.js"></script>

</body>
</html>


================================================
FILE: test/_get-server.js
================================================
// https://developer.mozilla.org/en-US/docs/Node_server_without_framework
// https://stackoverflow.com/a/29046869

const http = require('http');
const url = require('url');
const fs = require('fs');
const path = require('path');

module.exports = function getServer() {
  return http.createServer( listener );
};

const mimeTypes = {
  '.ico': 'image/x-icon',
  '.html': 'text/html',
  '.js': 'text/javascript',
  '.json': 'application/json',
  '.css': 'text/css',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.wav': 'audio/wav',
  '.mp3': 'audio/mpeg',
  '.svg': 'image/svg+xml',
  '.pdf': 'application/pdf',
  '.doc': 'application/msword',
};

function listener( req, res ) {
  // if ( req.url.includes('.html') ) {
  //   console.log( `${req.method} ${req.url}` );
  // }

  let parsedUrl = url.parse( req.url );
  let pathname = `.${parsedUrl.pathname}`;
  let ext = path.parse( pathname ).ext;

  fs.exists( pathname, function( exist ) {
    if ( !exist ) {
      // if the file is not found, return 404
      res.statusCode = 404;
      res.end(`File ${pathname} not found`);
      return;
    }

    // if is a directory search for index file matching the extention
    if ( fs.statSync( pathname ).isDirectory() ) {
      pathname += '/index' + ext;
    }

    // read file from file system
    fs.readFile( pathname, function( err, data ) {
      if ( err ) {
        res.statusCode = 500;
        res.end(`Error getting the file: ${err}.`);
      } else {
        // if the file is found, set Content-type and send data
        let mimeType = mimeTypes[ ext ] || 'text/plain';
        res.setHeader( 'Content-type', mimeType );
        res.end( data );
      }
    } );
  } );

}


================================================
FILE: test/_with-page.js
================================================
// https://github.com/avajs/ava/blob/v3.13.0/docs/recipes/puppeteer.md

const puppeteer = require('puppeteer');
const getServer = require('./_get-server.js');
const getPort = require('get-port');

module.exports = async( t, run ) => {
  const port = await getPort({ port: getPort.makeRange( 9100, 9200 ) });
  let server = getServer();
  server.listen( port );

  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  try {
    await run( t, page );
  } finally {
    await page.close();
    await browser.close();
    server.close();
  }
};


================================================
FILE: test/check-last-page.js
================================================
const test = require('ava');
const puppeteer = require('puppeteer');
const getServer = require('./_get-server.js');

const port = 9005;
let server, browser;

test.before( async function() {
  server = getServer();
  server.listen( port );
  browser = await puppeteer.launch();
} );

test.after( async function() {
  await browser.close();
  server.close();
} );

async function withPage( t, run ) {
  const page = await browser.newPage();
  await page.goto(`http://localhost:${port}/test/html/page-load.html`);
  try {
    await run( t, page );
  } finally {
    await page.close();
  }
}

function getPageAssertions() {
  return async function() {
    let { infScroll, serialT } = window;

    // check that last doesn't trigger when not last page
    infScroll.on( 'last', onLast );
    function onLast() {
      serialT.fail('last event should not trigger when not last page');
    }

    await infScroll.loadNextPage().then( function() {
      infScroll.off( 'last', onLast );
    } );

    let promise = new Promise( function( resolve ) {
      infScroll.once( 'last', function() {
        serialT.pass(`last event triggered on ${infScroll.pageIndex}`);
        resolve( serialT.assertions );
      } );
    } );
    // load page 3
    infScroll.loadNextPage();
    return promise;
  };
}

// ------ tests ------ //

test( 'checkLastPage: true', withPage, async function( t, page ) {
  await page.evaluate( function() {
    window.infScroll = new InfiniteScroll( '.container', {
      append: '.post',
      path: '.next-link',
      // checkLastPage: true, // true by default
    } );
  } );

  let assertions = await page.evaluate( getPageAssertions() );
  assertions.forEach( ({ method, args }) => t[ method ]( ...args ) );
} );

test( 'checkLastPage: ".selector-string"', withPage, async function( t, page ) {
  await page.evaluate( function() {
    window.infScroll = new InfiniteScroll( '.container', {
      append: '.post',
      path: 'page/{{#}}.html',
      checkLastPage: '.next-link',
    } );
  } );

  let assertions = await page.evaluate( getPageAssertions() );
  assertions.forEach( ({ method, args }) => t[ method ]( ...args ) );
} );

test( 'checkLastPage with empty page', withPage, async function( t, page ) {
  await page.evaluate( function() {
    window.infScroll = new InfiniteScroll( '.container', {
      // provide only page/2.html, then falsy
      path: function() {
        if ( this.pageIndex < 2 ) {
          return `page/${this.pageIndex + 1}.html`;
        } else {
          return 'page/empty.html';
        }
      },
      // checkLastPage: true, // true by default
      append: '.post',
    } );
  } );

  let assertions = await page.evaluate( getPageAssertions() );
  assertions.forEach( ({ method, args }) => t[ method ]( ...args ) );
} );

test( 'checkLastPage with path: function() {}', withPage, async function( t, page ) {
  let assertions = await page.evaluate( function() {
    let infScroll = new InfiniteScroll( '.container', {
      // provide only page/2.html, then falsy
      path: function() {
        if ( this.pageIndex < 2 ) {
          return `page/${this.pageIndex + 1}.html`;
        }
      },
      // checkLastPage: true, // true by default
      append: '.post',
    } );

    // function returning falsey will trigger last right after pageLoad
    let promise = new Promise( function( resolve ) {
      infScroll.once( 'last', function() {
        serialT.is( infScroll.pageIndex, 2 );
        resolve( serialT.assertions );
      } );
    } );

    // load page 2
    infScroll.loadNextPage();
    return promise;
  } );

  assertions.forEach( ({ method, args }) => t[ method ]( ...args ) );
} );


================================================
FILE: test/dist-jquery.js
================================================
const test = require('ava');
const puppeteer = require('puppeteer');
const getServer = require('./_get-server.js');

const port = 9011;
let server, browser, page;

test.before( async function() {
  server = getServer();
  server.listen( port );
  browser = await puppeteer.launch();
  page = await browser.newPage();
  await page.goto(`http://localhost:${port}/test/html/dist-jquery.html`);
} );

test.after( async function() {
  page.close();
  await browser.close();
  server.close();
} );

// ------ tests ------ //

test( 'dist jQuery', async( t ) => {

  let assertions = await page.evaluate( function() {
    const { jQuery } = window;
    let $container = document.querySelector('.container');
    $container = jQuery('.container').infiniteScroll({
      path: 'page/{{#}}.html',
      append: '.post',
    });

    let infScroll = $container.data('infinite-scroll');

    return $container.infiniteScroll('loadNextPage')
      .then( function( load ) {
        let { response, body, items } = load;
        serialT.true( response instanceof Response );
        serialT.true( response.ok );
        serialT.is( response.status, 200 );
        serialT.true( body instanceof HTMLDocument );
        serialT.true( items instanceof NodeList );
        serialT.is( items.length, 2 );
        serialT.true( items[0] == $container[0].children[1] );
        serialT.true( items[1] == $container[0].children[2] );
        serialT.is( infScroll.pageIndex, 2 );
        serialT.is( infScroll.loadCount, 1 );
      } )
      .then( () => serialT.assertions );
  } );

  assertions.forEach( ({ method, args }) => t[ method ]( ...args ) );
} );


================================================
FILE: test/dist.js
================================================
const test = require('ava');
const puppeteer = require('puppeteer');
const getServer = require('./_get-server.js');

const port = 9010;
let server, browser, page;

test.before( async function() {
  server = getServer();
  server.listen( port );
  browser = await puppeteer.launch();
  page = await browser.newPage();
  await page.goto(`http://localhost:${port}/test/html/dist.html`);
} );

test.after( async function() {
  page.close();
  await browser.close();
  server.close();
} );

// ------ tests ------ //

test( 'dist', async( t ) => {

  let assertions = await page.evaluate( function() {
    let $container = document.querySelector('.container');
    let infScroll = new InfiniteScroll( $container, {
      path: 'page/{{#}}.html',
      append: '.post',
    } );

    return infScroll.loadNextPage()
      .then( function( load ) {
        let { response, body, items } = load;
        serialT.true( response instanceof Response );
        serialT.true( response.ok );
        serialT.is( response.status, 200 );
        serialT.true( body instanceof HTMLDocument );
        serialT.true( items instanceof NodeList );
        serialT.is( items.length, 2 );
        serialT.true( items[0] == $container.children[1] );
        serialT.true( items[1] == $container.children[2] );
        serialT.is( infScroll.pageIndex, 2 );
        serialT.is( infScroll.loadCount, 1 );
      } )
      .then( () => serialT.assertions );
  } );

  assertions.forEach( ({ method, args }) => t[ method ]( ...args ) );
} );


================================================
FILE: test/history.js
================================================
const test = require('ava');
const puppeteer = require('puppeteer');
const getServer = require('./_get-server.js');

const port = 9007;
let server, browser, page;

test.before( async function() {
  server = getServer();
  server.listen( port );
  browser = await puppeteer.launch();
  page = await browser.newPage();
  await page.goto(`http://localhost:${port}/test/html/history.html`);
} );

test.after( async function() {
  await page.close();
  await browser.close();
  server.close();
} );

// ------ tests ------ //

test( 'history', async( t ) => {

  t.plan( 6 );

  let assertions = await page.evaluate( function() {
    let $container = document.querySelector('.container');
    let infScroll = new InfiniteScroll( $container, {
      path: 'page/{{#}}.html',
      append: '.post',
      scrollThreshold: false,
      // history: 'replace', // default
      historyTitle: true,
    } );

    function getTop( $elem ) {
      return $elem.getBoundingClientRect().top + window.scrollY;
    }

    let page1Top = getTop( $container ) - 100;
    let page2Top, page3Top;

    // append next page, scroll to it to trigger history
    return new Promise( function( resolve ) {
      infScroll.once( 'append', function( response, _path, items ) {
        page2Top = getTop( items[0] );
        // TODO this block should be its own promise, but can't get it to work
        infScroll.once( 'history', function( title, path ) {
          serialT.is( path, location.href, `2nd page history url changed to ${path}` );
          serialT.is( title, document.title, `document title changed to ${title}` );
          resolve();
        } );
        scrollTo( 0, page2Top - window.innerHeight / 4 );
      } );

      infScroll.loadNextPage();
    } )
      // scroll back up to top of page to trigger history on previous page
      .then( new Promise( function( resolve ) {
        infScroll.once( 'history', function( title, path ) {
          serialT.is( path, location.href, `1st page history, url changed to ${path}` );
          serialT.is( title, document.title, `document title changed to ${title}` );
          resolve();
        } );
        scrollTo( 0, page1Top );
      } ) )
      .then( new Promise( function( resolve ) {
        infScroll.once( 'append', function( response, _path, items ) {
          page3Top = getTop( items[0] );
          resolve();
        } );

        infScroll.loadNextPage();
      } ) )
      .then( new Promise( function( resolve ) {
        infScroll.once( 'history', function( title, path ) {
          serialT.is( path, location.href, `3rd page history url changed to ${path}` );
          serialT.is( title, document.title, `document title changed to ${title}` );
          resolve();
        } );
        scrollTo( 0, page3Top - window.innerHeight / 4 );
      } ) )
      .then( () => serialT.assertions );
  } );

  assertions.forEach( ({ method, args }) => t[ method ]( ...args ) );

} );


================================================
FILE: test/html/_serial-t.js
================================================
// serialT works kinda like t, but to pass serialized assertions out of .evaluate
// serialT.assertions will be passed out of .evaluate

// https://github.com/avajs/ava/blob/master/docs/03-assertions.md#built-in-assertions
const tKeys = [
  'pass',
  'fail',
  'assert',
  'truthy',
  'falsy',
  'true',
  'false',
  'is',
  'not',
  'deepEqual',
  'notDeepEqual',
];

window.serialT = tKeys.reduce( ( serializedT, method ) => {
  serializedT[ method ] = function( ...args ) {
    serializedT.assertions.push({ method, args });
  };
  return serializedT;
}, { assertions: [] } );


================================================
FILE: test/html/dist-jquery.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>Infinite Scroll test - dist jQuery</title>

  <link rel="stylesheet" href="test.css" />

  <!-- dependencies -->
  <script src="../../node_modules/jquery/dist/jquery.js"></script>
  <script src="../../dist/infinite-scroll.pkgd.min.js"></script>
  <!-- test helper -->
  <script src="_serial-t.js"></script>

</head>
<body>

<h1>Infinite Scroll test - dist jQuery</h1>

<div class="container">
  <div class="post">page 1, post 1</div>
</div>
<p><a class="next-link" href="page/2.html">Next</a></p>

</body>
</html>


================================================
FILE: test/html/dist.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>Infinite Scroll test - dist</title>

  <link rel="stylesheet" href="test.css" />

  <!-- dependencies -->
  <script src="../../dist/infinite-scroll.pkgd.min.js"></script>
  <!-- test helper -->
  <script src="_serial-t.js"></script>

</head>
<body>

<h1>Infinite Scroll test - dist</h1>

<div class="container">
  <div class="post">page 1, post 1</div>
</div>
<p><a class="next-link" href="page/2.html">Next</a></p>

</body>
</html>


================================================
FILE: test/html/history.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>Infinite Scroll test - history</title>

  <link rel="stylesheet" href="test.css" />

  <!-- dependencies -->
  <script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
  <script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
  <!-- infinite scroll -->
  <script src="../../js/core.js"></script>
  <script src="../../js/scroll-watch.js"></script>
  <script src="../../js/page-load.js"></script>
  <script src="../../js/history.js"></script>
  <script src="../../js/button.js"></script>
  <script src="../../js/status.js"></script>
  <!-- test helper -->
  <script src="_serial-t.js"></script>

</head>
<body>

<h1>Infinite Scroll test - history</h1>

<div class="container container--big-posts">
  <div class="post">page 1, post 1</div>
  <div class="post">page 1, post 2</div>
</div>

</body>
</html>


================================================
FILE: test/html/outlayer.html
================================================
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />

  <title>Infinite Scroll test - page-load</title>

  <link rel="stylesheet" href="test.css" />
  <style>
    .outlayer-item,
    .col-sizer {
      width: calc(100%/3);
    }

    .outlayer-item {
      float: left;
    }

    .outlayer-item img {
      display: block;
      max-width: 100%;
    }
  </style>

  <!-- dependencies -->
  <script src="../../node_modules/ev-emitter/ev-emitter.js"></script>
  <script src="../../node_modules/fizzy-ui-utils/utils.js"></script>
  <!-- outlayer -->
  <script src="../../node_modules/imagesloaded/imagesloaded.js"></script>
  <script src="../../node_modules/get-size/get-size.js"></script>
  <script src="../../node_modules/outlayer/item.js"></script>
  <script src="../../node_modules/outlayer/outlayer.js"></script>
  <script src="../../node_modules/masonry-layout/masonry.js"></script>
  <!-- infinite scroll -->
  <script src="../../js/core.js"></script>
  <script src="../../js/scroll-watch.js"></script>
  <script src="../../js/page-load.js"></script>
  <script src="../../js/history.js"></script>
  <script src="../../js/button.js"></script>
  <script src="../../js/status.js"></script>
  <!-- test helper -->
  <script src="_serial-t.js"></script>

</head>
<body>

<h1>Infinite Scroll test - outlayer</h1>

<div class="container container--outl
Download .txt
gitextract_pwag9y0f/

├── .eslintrc.js
├── .github/
│   ├── contributing.md
│   ├── issue_template.md
│   └── workflows/
│       └── nodejs.yml
├── .gitignore
├── .nvmrc
├── LICENSE.txt
├── README.md
├── bin/
│   ├── build-dist.js
│   └── version.js
├── bower.json
├── dist/
│   └── infinite-scroll.pkgd.js
├── js/
│   ├── button.js
│   ├── core.js
│   ├── history.js
│   ├── index.js
│   ├── page-load.js
│   ├── scroll-watch.js
│   └── status.js
├── package.json
├── sandbox/
│   ├── button-class.html
│   ├── button-first.html
│   ├── button-load.html
│   ├── container-scroll.html
│   ├── css/
│   │   ├── blog.css
│   │   ├── loader-ellips.css
│   │   └── masonry-images.css
│   ├── element-scroll.html
│   ├── html-init.html
│   ├── jquery-plugin.html
│   ├── js/
│   │   ├── masonry-images.js
│   │   ├── scroll-loader.js
│   │   ├── unsplash-masonry.js
│   │   └── unsplash.js
│   ├── masonry-images/
│   │   ├── index.html
│   │   ├── page2.html
│   │   ├── page3.html
│   │   ├── page4.html
│   │   └── page5.html
│   ├── page/
│   │   ├── 2.html
│   │   ├── 3.html
│   │   ├── 4.html
│   │   ├── 5.html
│   │   └── 6.html
│   ├── prefill.html
│   ├── scroll-3.html
│   ├── scroll-loader.html
│   ├── unsplash-masonry.html
│   └── unsplash.html
└── test/
    ├── _get-server.js
    ├── _with-page.js
    ├── check-last-page.js
    ├── dist-jquery.js
    ├── dist.js
    ├── history.js
    ├── html/
    │   ├── _serial-t.js
    │   ├── dist-jquery.html
    │   ├── dist.html
    │   ├── history.html
    │   ├── outlayer.html
    │   ├── page/
    │   │   ├── 2.html
    │   │   ├── 2.json
    │   │   ├── 3.html
    │   │   ├── 3.json
    │   │   ├── empty.html
    │   │   ├── fill.html
    │   │   ├── no-access.html
    │   │   ├── outlayer2.html
    │   │   └── outlayer3.html
    │   ├── page-index.html
    │   ├── page-load.html
    │   ├── path.html
    │   ├── prefill.html
    │   ├── scroll-watch-element.html
    │   ├── scroll-watch-window.html
    │   └── test.css
    ├── load-next-page-promise.js
    ├── outlayer.js
    ├── page-index.js
    ├── page-load-error.js
    ├── page-load-json.js
    ├── page-load.js
    ├── path.js
    ├── prefill.js
    └── scroll-watch.js
Download .txt
SYMBOL INDEX (60 symbols across 17 files)

FILE: bin/version.js
  function dir (line 7) | function dir( file ) {

FILE: dist/infinite-scroll.pkgd.js
  function jQueryBridget (line 45) | function jQueryBridget( namespace, PluginClass, $ ) {
  function EvEmitter (line 141) | function EvEmitter() {}
  function InfiniteScroll (line 444) | function InfiniteScroll( element, options ) {
  function getItemsFragment (line 924) | function getItemsFragment( items ) {
  function refreshScripts (line 934) | function refreshScripts( fragment ) {
  class InfiniteScrollButton (line 1370) | class InfiniteScrollButton {
    method constructor (line 1371) | constructor( element, infScroll ) {
    method onClick (line 1383) | onClick( event ) {
    method enable (line 1388) | enable() {
    method disable (line 1392) | disable() {
    method hide (line 1396) | hide() {
    method destroy (line 1400) | destroy() {
  function hide (line 1511) | function hide( elem ) {
  function show (line 1515) | function show( elem ) {
  function setDisplay (line 1519) | function setDisplay( elem, value ) {
  function extend (line 1576) | function extend( a, b ) {
  function makeArray (line 1586) | function makeArray( obj ) {
  function ImagesLoaded (line 1609) | function ImagesLoaded( elem, options, onAlways ) {
  function onProgress (line 1743) | function onProgress( image, elem, message ) {
  function LoadingImage (line 1787) | function LoadingImage( img ) {
  function Background (line 1853) | function Background( url, element ) {

FILE: js/button.js
  class InfiniteScrollButton (line 24) | class InfiniteScrollButton {
    method constructor (line 25) | constructor( element, infScroll ) {
    method onClick (line 37) | onClick( event ) {
    method enable (line 42) | enable() {
    method disable (line 46) | disable() {
    method hide (line 50) | hide() {
    method destroy (line 54) | destroy() {

FILE: js/core.js
  function InfiniteScroll (line 26) | function InfiniteScroll( element, options ) {

FILE: js/page-load.js
  function getItemsFragment (line 135) | function getItemsFragment( items ) {
  function refreshScripts (line 145) | function refreshScripts( fragment ) {

FILE: js/status.js
  function hide (line 84) | function hide( elem ) {
  function show (line 88) | function show( elem ) {
  function setDisplay (line 92) | function setDisplay( elem, value ) {

FILE: sandbox/js/unsplash-masonry.js
  function getItem (line 43) | function getItem( photo ) {
  function microTemplate (line 48) | function microTemplate( src, data ) {

FILE: sandbox/js/unsplash.js
  function getItem (line 18) | function getItem( photo ) {
  function microTemplate (line 23) | function microTemplate( src, data ) {

FILE: test/_get-server.js
  function listener (line 28) | function listener( req, res ) {

FILE: test/check-last-page.js
  function withPage (line 19) | async function withPage( t, run ) {
  function getPageAssertions (line 29) | function getPageAssertions() {

FILE: test/history.js
  function getTop (line 38) | function getTop( $elem ) {

FILE: test/outlayer.js
  function withPage (line 21) | async function withPage( t, run ) {
  function checkItems (line 39) | function checkItems( items ) {
  function loadNextPagePromise (line 49) | function loadNextPagePromise() {

FILE: test/page-index.js
  function withPage (line 19) | async function withPage( t, run ) {

FILE: test/page-load-error.js
  function withPage (line 19) | async function withPage( t, run ) {

FILE: test/path.js
  function withPage (line 19) | async function withPage( t, run ) {

FILE: test/prefill.js
  function withPage (line 19) | async function withPage( t, run ) {
  function onAppend (line 49) | function onAppend() {
  function onAppend (line 81) | function onAppend() {
  function onAppend (line 115) | function onAppend() {

FILE: test/scroll-watch.js
  function updateContainerBottom (line 36) | function updateContainerBottom() {
  function waitForDebounce (line 43) | function waitForDebounce() {
  function onScrollThresholdFail (line 49) | function onScrollThresholdFail() {
  function updateContainerBottom (line 122) | function updateContainerBottom() {
  function waitForDebounce (line 128) | function waitForDebounce() {
  function getPost (line 134) | function getPost() {
  function onScrollThresholdFail (line 140) | function onScrollThresholdFail() {
Condensed preview — 85 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (250K chars).
[
  {
    "path": ".eslintrc.js",
    "chars": 351,
    "preview": "/* eslint-env node */\n\nmodule.exports = {\n  plugins: [ 'metafizzy' ],\n  extends: 'plugin:metafizzy/browser',\n  env: {\n  "
  },
  {
    "path": ".github/contributing.md",
    "chars": 1738,
    "preview": "## Submitting issues\n\n### Reduced test case required\n\nAll bug reports and problem issues require a [**reduced test case*"
  },
  {
    "path": ".github/issue_template.md",
    "chars": 277,
    "preview": "<!-- Thanks for submitting an issue! All bug reports and problem issues require a **reduced test case** or **live URL**."
  },
  {
    "path": ".github/workflows/nodejs.yml",
    "chars": 736,
    "preview": "# This workflow will do a clean install of node dependencies, build the source code and run tests across different versi"
  },
  {
    "path": ".gitignore",
    "chars": 32,
    "preview": "bower_components/\nnode_modules/\n"
  },
  {
    "path": ".nvmrc",
    "chars": 3,
    "preview": "14\n"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1071,
    "preview": "The MIT License (MIT)\n\nCopyright (c) Metafizzy\n\nPermission is hereby granted, free of charge, to any person obtaining a "
  },
  {
    "path": "README.md",
    "chars": 4380,
    "preview": "# Infinite Scroll\n\n_Automatically add next page_\n\nSee [infinite-scroll.com](https://infinite-scroll.com) for complete do"
  },
  {
    "path": "bin/build-dist.js",
    "chars": 1378,
    "preview": "const fs = require('fs');\nconst { execSync } = require('child_process');\nconst { minify } = require('terser');\n\nconst in"
  },
  {
    "path": "bin/version.js",
    "chars": 412,
    "preview": "/* eslint-env node */\n\nconst fs = require('fs');\nconst path = require('path');\nconst { version } = require('../package.j"
  },
  {
    "path": "bower.json",
    "chars": 575,
    "preview": "{\n  \"name\": \"infinite-scroll\",\n  \"authors\": [\n    \"David DeSandro <desandrocodes@gmail.com>\"\n  ],\n  \"description\": \"infi"
  },
  {
    "path": "dist/infinite-scroll.pkgd.js",
    "chars": 50026,
    "preview": "/*!\n * Infinite Scroll PACKAGED v5.0.0\n * Automatically add next page\n * MIT License\n * https://infinite-scroll.com\n * C"
  },
  {
    "path": "js/button.js",
    "chars": 1942,
    "preview": "// button\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' && module.ex"
  },
  {
    "path": "js/core.js",
    "chars": 10165,
    "preview": "// core\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' && module.expo"
  },
  {
    "path": "js/history.js",
    "chars": 5051,
    "preview": "// history\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' && module.e"
  },
  {
    "path": "js/index.js",
    "chars": 585,
    "preview": "/*!\n * Infinite Scroll v5.0.0\n * Automatically add next page\n * MIT License\n * https://infinite-scroll.com\n * Copyright "
  },
  {
    "path": "js/page-load.js",
    "chars": 7659,
    "preview": "// page-load\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' && module"
  },
  {
    "path": "js/scroll-watch.js",
    "chars": 2501,
    "preview": "// scroll-watch\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' && mod"
  },
  {
    "path": "js/status.js",
    "chars": 2454,
    "preview": "// status\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' && module.ex"
  },
  {
    "path": "package.json",
    "chars": 1116,
    "preview": "{\n  \"name\": \"infinite-scroll\",\n  \"version\": \"5.0.0\",\n  \"description\": \"Automatically add next page\",\n  \"main\": \"js/index"
  },
  {
    "path": "sandbox/button-class.html",
    "chars": 3879,
    "preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\""
  },
  {
    "path": "sandbox/button-first.html",
    "chars": 3459,
    "preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\""
  },
  {
    "path": "sandbox/button-load.html",
    "chars": 3288,
    "preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\""
  },
  {
    "path": "sandbox/container-scroll.html",
    "chars": 3473,
    "preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\""
  },
  {
    "path": "sandbox/css/blog.css",
    "chars": 500,
    "preview": "body {\n  line-height: 1.4;\n  background: #EEE;\n  color: #444;\n}\n\n.container {\n  padding: 0 20px;\n  max-width: 700px;\n  m"
  },
  {
    "path": "sandbox/css/loader-ellips.css",
    "chars": 904,
    "preview": "/* loader-ellips\n------------------------- */\n\n.loader-ellips {\n  font-size: 20px;\n  position: relative;\n  width: 4em;\n "
  },
  {
    "path": "sandbox/css/masonry-images.css",
    "chars": 454,
    "preview": ".container {\n  max-width: 1200px;\n  margin: 0 auto;\n  padding-bottom: 400px;\n}\n\n.grid {\n  min-height: 600px;\n}\n\n.grid__c"
  },
  {
    "path": "sandbox/element-scroll.html",
    "chars": 3399,
    "preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\""
  },
  {
    "path": "sandbox/html-init.html",
    "chars": 3678,
    "preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\""
  },
  {
    "path": "sandbox/jquery-plugin.html",
    "chars": 3827,
    "preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\""
  },
  {
    "path": "sandbox/js/masonry-images.js",
    "chars": 839,
    "preview": "/* globals Masonry, imagesLoaded */\nlet msnry = new Masonry( '.grid', {\n  itemSelector: 'none', // select no images on i"
  },
  {
    "path": "sandbox/js/scroll-loader.js",
    "chars": 251,
    "preview": "let container = document.querySelector('.posts-container');\nwindow.infScroll = new InfiniteScroll( container, {\n  path: "
  },
  {
    "path": "sandbox/js/unsplash-masonry.js",
    "chars": 1597,
    "preview": "/* globals Masonry, imagesLoaded */\n\nlet msnry = new Masonry( '.grid', {\n  itemSelector: '.grid-item',\n  columnWidth: '."
  },
  {
    "path": "sandbox/js/unsplash.js",
    "chars": 998,
    "preview": "let container = document.querySelector('.container');\n\nlet unsplashID = '9ad80b14098bcead9c7de952435e937cc3723ae61084ba8"
  },
  {
    "path": "sandbox/masonry-images/index.html",
    "chars": 3007,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "sandbox/masonry-images/page2.html",
    "chars": 3025,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "sandbox/masonry-images/page3.html",
    "chars": 3025,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "sandbox/masonry-images/page4.html",
    "chars": 3025,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "sandbox/masonry-images/page5.html",
    "chars": 2382,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "sandbox/page/2.html",
    "chars": 2829,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "sandbox/page/3.html",
    "chars": 4186,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "sandbox/page/4.html",
    "chars": 7507,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "sandbox/page/5.html",
    "chars": 3768,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "sandbox/page/6.html",
    "chars": 5665,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "sandbox/prefill.html",
    "chars": 3755,
    "preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\""
  },
  {
    "path": "sandbox/scroll-3.html",
    "chars": 3711,
    "preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\""
  },
  {
    "path": "sandbox/scroll-loader.html",
    "chars": 3692,
    "preview": "\n<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\""
  },
  {
    "path": "sandbox/unsplash-masonry.html",
    "chars": 2444,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "sandbox/unsplash.html",
    "chars": 1115,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/_get-server.js",
    "chars": 1695,
    "preview": "// https://developer.mozilla.org/en-US/docs/Node_server_without_framework\n// https://stackoverflow.com/a/29046869\n\nconst"
  },
  {
    "path": "test/_with-page.js",
    "chars": 572,
    "preview": "// https://github.com/avajs/ava/blob/v3.13.0/docs/recipes/puppeteer.md\n\nconst puppeteer = require('puppeteer');\nconst ge"
  },
  {
    "path": "test/check-last-page.js",
    "chars": 3671,
    "preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
  },
  {
    "path": "test/dist-jquery.js",
    "chars": 1638,
    "preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
  },
  {
    "path": "test/dist.js",
    "chars": 1513,
    "preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
  },
  {
    "path": "test/history.js",
    "chars": 2934,
    "preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
  },
  {
    "path": "test/html/_serial-t.js",
    "chars": 580,
    "preview": "// serialT works kinda like t, but to pass serialized assertions out of .evaluate\n// serialT.assertions will be passed o"
  },
  {
    "path": "test/html/dist-jquery.html",
    "chars": 647,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/html/dist.html",
    "chars": 566,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/html/history.html",
    "chars": 957,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/html/outlayer.html",
    "chars": 1888,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/html/page/2.html",
    "chars": 456,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/html/page/2.json",
    "chars": 144,
    "preview": "[\n  {\n    \"id\": 2,\n    \"content\": \"venus\"\n  },\n  {\n    \"id\": 3,\n    \"content\": \"mother earth\"\n  },\n  {\n    \"id\": 4,\n    "
  },
  {
    "path": "test/html/page/3.html",
    "chars": 330,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/html/page/3.json",
    "chars": 142,
    "preview": "[\n  {\n    \"id\": 5,\n    \"content\": \"jupiter\"\n  },\n  {\n    \"id\": 6,\n    \"content\": \"saturn\"\n  },\n  {\n    \"id\": 7,\n    \"con"
  },
  {
    "path": "test/html/page/empty.html",
    "chars": 208,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/html/page/fill.html",
    "chars": 37,
    "preview": "<div class=\"post\">prefill post</div>\n"
  },
  {
    "path": "test/html/page/no-access.html",
    "chars": 342,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/html/page/outlayer2.html",
    "chars": 632,
    "preview": "<div class=\"outlayer-item\"><img src=\"https://i.imgur.com/qW7eNlI.jpg\" /></div>\n<div class=\"outlayer-item\"><img src=\"http"
  },
  {
    "path": "test/html/page/outlayer3.html",
    "chars": 790,
    "preview": "<div class=\"outlayer-item\"><img src=\"https://i.imgur.com/ZPPFND3.jpg\" /></div>\n<div class=\"outlayer-item\"><img src=\"http"
  },
  {
    "path": "test/html/page-index.html",
    "chars": 897,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/html/page-load.html",
    "chars": 955,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/html/path.html",
    "chars": 925,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/html/prefill.html",
    "chars": 1089,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/html/scroll-watch-element.html",
    "chars": 1195,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/html/scroll-watch-window.html",
    "chars": 969,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "test/html/test.css",
    "chars": 287,
    "preview": "body {\n  font-family: sans-serif;\n}\n\n.post {\n  background: #EEE;\n  margin-bottom: 10px;\n}\n\n.container {\n  border: 1px so"
  },
  {
    "path": "test/load-next-page-promise.js",
    "chars": 2655,
    "preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
  },
  {
    "path": "test/outlayer.js",
    "chars": 3629,
    "preview": "/* globals imagesLoaded, Masonry */\n\nconst test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServe"
  },
  {
    "path": "test/page-index.js",
    "chars": 2077,
    "preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
  },
  {
    "path": "test/page-load-error.js",
    "chars": 3623,
    "preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
  },
  {
    "path": "test/page-load-json.js",
    "chars": 2737,
    "preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\ncons"
  },
  {
    "path": "test/page-load.js",
    "chars": 4118,
    "preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
  },
  {
    "path": "test/path.js",
    "chars": 3491,
    "preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
  },
  {
    "path": "test/prefill.js",
    "chars": 3646,
    "preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
  },
  {
    "path": "test/scroll-watch.js",
    "chars": 6165,
    "preview": "const test = require('ava');\nconst puppeteer = require('puppeteer');\nconst getServer = require('./_get-server.js');\n\ncon"
  }
]

About this extraction

This page contains the full source code of the metafizzy/infinite-scroll GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 85 files (228.8 KB), approximately 63.7k tokens, and a symbol index with 60 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!