Full Code of metafizzy/flickity for AI

master a64cc3305215 cached
81 files
284.3 KB
81.3k tokens
64 symbols
1 requests
Download .txt
Showing preview only (304K chars total). Download the full file or copy to clipboard to get everything.
Repository: metafizzy/flickity
Branch: master
Commit: a64cc3305215
Files: 81
Total size: 284.3 KB

Directory structure:
gitextract_uopm_3sr/

├── .eslintrc.js
├── .github/
│   ├── contributing.md
│   ├── issue_template.md
│   └── workflows/
│       └── nodejs.yml
├── .gitignore
├── .nvmrc
├── README.md
├── bin/
│   ├── .eslintrc.js
│   ├── bundle-css.js
│   ├── bundle-js.js
│   ├── lint-json.js
│   └── version.js
├── bower.json
├── css/
│   └── flickity.css
├── dist/
│   ├── flickity.css
│   └── flickity.pkgd.js
├── js/
│   ├── add-remove-cell.js
│   ├── animate.js
│   ├── cell.js
│   ├── core.js
│   ├── drag.js
│   ├── imagesloaded.js
│   ├── index.js
│   ├── lazyload.js
│   ├── page-dots.js
│   ├── player.js
│   ├── prev-next-button.js
│   └── slide.js
├── package.json
├── sandbox/
│   ├── adaptive-height.html
│   ├── add-remove.html
│   ├── ajax.html
│   ├── basic.html
│   ├── freescroll.html
│   ├── group-cells.html
│   ├── jquery.html
│   ├── js/
│   │   ├── add-remove.js
│   │   ├── basic.js
│   │   ├── jquery.js
│   │   ├── scroll-event.js
│   │   ├── tricky-drag.js
│   │   ├── v2-sizzle.js
│   │   └── wrap-around.js
│   ├── lazyload.html
│   ├── media.html
│   ├── right-to-left.html
│   ├── sandbox.css
│   ├── scroll-event.html
│   ├── single.html
│   ├── styles.html
│   ├── tricky-drag.html
│   ├── v2-sizzle.html
│   └── wrap-around.html
├── stylelint.config.js
└── test/
    ├── drag.html
    ├── index.html
    ├── test.css
    └── unit/
        ├── adaptive-height.js
        ├── add-remove-cells.js
        ├── auto-play.js
        ├── cell-selector.js
        ├── change.js
        ├── contain.js
        ├── destroy.js
        ├── drag.js
        ├── empty.js
        ├── get-parent-cell.js
        ├── get-wrap-cells.js
        ├── group-cells.js
        ├── imagesloaded.js
        ├── init.js
        ├── initial-index.js
        ├── lazyload-srcset.js
        ├── lazyload.js
        ├── page-dots.js
        ├── position-cells.js
        ├── prev-next-buttons.js
        ├── resize.js
        ├── select-cell.js
        ├── watch.js
        └── wrap-around-fill.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: {
    Flickity: 'readonly',
    QUnit: 'readonly',
  },
  rules: {
    'prefer-object-spread': 'error',
  },
  ignorePatterns: [ 'bower_components' ],
};


================================================
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/). Create one by forking any one of the [CodePen demos](https://codepen.io/desandro/pens/tags/?grid_type=list&selected_tag=flickity-docs&sort_order=asc) from [the docs](https://flickity.metafizzy.co).

**CodePens**

+ [Basic](https://codepen.io/desandro/pen/azqbop)
+ [imagesLoaded](https://codepen.io/desandro/pen/MYQWEe)
+ [lazyLoad](https://codepen.io/desandro/pen/vOeGzL)
+ [autoPlay](https://codepen.io/desandro/pen/RNQwaB)

**Test cases**

+ 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.

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, 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 Flickity's vision.
+ **Follow the code style.** Spaces in brackets, semicolons, trailing commas.
+ **Do not edit `dist/` files.** Make your edits to source files in `js/` and `css/`.
+ **Do not run `gulp` 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 Flickity 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**. Create one by forking any one of the CodePen examples from the docs. See guidelines link above. -->

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


================================================
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: [16.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 lint
      env:
        CI: true


================================================
FILE: .gitignore
================================================
bower_components/
node_modules/
/**/browserify/bundle.js
notes.md
sandbox/yoshi-parallax


================================================
FILE: .nvmrc
================================================
16

================================================
FILE: README.md
================================================
# Flickity

_Touch, responsive, flickable carousels_

See [flickity.metafizzy.co](https://flickity.metafizzy.co) for complete docs and demos.

## Install

### Download

+ CSS:
  - [flickity.min.css](https://unpkg.com/flickity@2/dist/flickity.min.css) minified, or
  - [flickity.css](https://unpkg.com/flickity@2/dist/flickity.css) un-minified
+ JavaScript:
  - [flickity.pkgd.min.js](https://unpkg.com/flickity@2/dist/flickity.pkgd.min.js) minified, or
  - [flickity.pkgd.js](https://unpkg.com/flickity@2/dist/flickity.pkgd.js) un-minified

### CDN

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

``` html
<link rel="stylesheet" href="https://unpkg.com/flickity@2/dist/flickity.min.css">
```

``` html
<script src="https://unpkg.com/flickity@2/dist/flickity.pkgd.min.js"></script>
```

### Package managers

Bower: `bower install flickity --save`

npm: `npm install flickity --save`

## License

### Commercial license

If you want to use Flickity to develop commercial sites, themes, projects, and applications, the Commercial license is the appropriate license. With this option, your source code is kept proprietary. Purchase a Flickity Commercial License at [flickity.metafizzy.co](https://flickity.metafizzy.co/#commercial-license)

### Open source license

If you are creating an open source application under a license compatible with the [GNU GPL license v3](https://www.gnu.org/licenses/gpl-3.0.html), you may use Flickity under the terms of the GPLv3.

[Read more about Flickity's license](https://flickity.metafizzy.co/license.html).

## Usage

Flickity works with a container element and a set of child cell elements

``` html
<div class="carousel">
  <div class="carousel-cell">...</div>
  <div class="carousel-cell">...</div>
  <div class="carousel-cell">...</div>
  ...
</div>
```

### Options

``` js
var flky = new Flickity( '.gallery', {
  // options, defaults listed

  accessibility: true,
  // enable keyboard navigation, pressing left & right keys

  adaptiveHeight: false,
  // set carousel height to the selected slide

  autoPlay: false,
  // advances to the next cell
  // if true, default is 3 seconds
  // or set time between advances in milliseconds
  // i.e. `autoPlay: 1000` will advance every 1 second

  cellAlign: 'center',
  // alignment of cells, 'center', 'left', or 'right'
  // or a decimal 0-1, 0 is beginning (left) of container, 1 is end (right)

  cellSelector: undefined,
  // specify selector for cell elements

  contain: false,
  // will contain cells to container
  // so no excess scroll at beginning or end
  // has no effect if wrapAround is enabled

  draggable: '>1',
  // enables dragging & flicking
  // if at least 2 cells

  dragThreshold: 3,
  // number of pixels a user must scroll horizontally to start dragging
  // increase to allow more room for vertical scroll for touch devices

  freeScroll: false,
  // enables content to be freely scrolled and flicked
  // without aligning cells

  friction: 0.2,
  // smaller number = easier to flick farther

  groupCells: false,
  // group cells together in slides

  initialIndex: 0,
  // zero-based index of the initial selected cell

  lazyLoad: true,
  // enable lazy-loading images
  // set img data-flickity-lazyload="src.jpg"
  // set to number to load images adjacent cells

  percentPosition: true,
  // sets positioning in percent values, rather than pixels
  // Enable if items have percent widths
  // Disable if items have pixel widths, like images

  prevNextButtons: true,
  // creates and enables buttons to click to previous & next cells

  pageDots: true,
  // create and enable page dots

  resize: true,
  // listens to window resize events to adjust size & positions

  rightToLeft: false,
  // enables right-to-left layout

  setGallerySize: true,
  // sets the height of gallery
  // disable if gallery already has height set with CSS

  watchCSS: false,
  // watches the content of :after of the element
  // activates if #element:after { content: 'flickity' }

  wrapAround: false
  // at end of cells, wraps-around to first for infinite scrolling

});
```

---

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


================================================
FILE: bin/.eslintrc.js
================================================
module.exports = {
  plugins: [ 'metafizzy' ],
  extends: 'plugin:metafizzy/node',
};


================================================
FILE: bin/bundle-css.js
================================================
const CleanCss = require('clean-css');
const fs = require('fs');

let srcCss = fs.readFileSync( 'css/flickity.css', 'utf8' );
let minifiedCss = new CleanCss().minify( srcCss ).styles.replace( '*/', '*/\n' );
fs.writeFileSync( 'dist/flickity.min.css', minifiedCss );


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

const indexPath = 'js/index.js';
const distPath = 'dist/flickity.pkgd.js';
const distMinPath = 'dist/flickity.pkgd.min.js';

let indexContent = fs.readFileSync( `./${indexPath}`, 'utf8' );
// get file paths from index.js
let jsPaths = indexContent.match( /require\('([.\-/\w]+)'\)/gi )
  .map( ( path ) => path.replace( "require('.", 'js' ).replace( "')", '.js' ) );

let paths = [
  'node_modules/jquery-bridget/jquery-bridget.js',
  'node_modules/ev-emitter/ev-emitter.js',
  'node_modules/get-size/get-size.js',
  'node_modules/fizzy-ui-utils/utils.js',
  'node_modules/unidragger/unidragger.js',
  'node_modules/imagesloaded/imagesloaded.js',
  'js/cell.js',
  'js/slide.js',
  'js/animate.js',
  ...jsPaths,
];

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

// add banner
let banner = indexContent.split(' */')[0] + ' */\n\n';
banner = banner.replace( 'Flickity', 'Flickity 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/lint-json.js
================================================
require('../package.json');
require('../bower.json');


================================================
FILE: bin/version.js
================================================
const fs = require('fs');
const version = require('../package.json').version;

[ 'css/flickity.css', 'js/index.js' ].forEach( ( file ) => {
  let src = fs.readFileSync( file, 'utf8' );
  src = src.replace( /Flickity v\d+\.\d+\.\d+/, `Flickity v${version}` );
  fs.writeFileSync( file, src, 'utf8' );
} );


================================================
FILE: bower.json
================================================
{
  "name": "flickity",
  "description": "Touch, responsive, flickable carousels",
  "main": [
    "js/index.js",
    "css/flickity.css"
  ],
  "dependencies": {
    "ev-emitter": "^2.1.2",
    "fizzy-ui-utils": "^3.0.0",
    "get-size": "^3.0.0",
    "unidragger": "^3.0.1"
  },
  "devDependencies": {
  },
  "keywords": [
    "gallery",
    "carousel",
    "touch"
  ],
  "homepage": "https://flickity.metafizzy.co",
  "authors": [
    "Metafizzy"
  ],
  "license": "GPL-3.0",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests",
    "sandbox",
    "package.json",
    "gulpfile.js",
    "notes.md"
  ]
}


================================================
FILE: css/flickity.css
================================================
/*! Flickity v3.0.0
https://flickity.metafizzy.co
---------------------------------------------- */

.flickity-enabled {
  position: relative;
}

.flickity-enabled:focus { outline: none; }

.flickity-viewport {
  overflow: hidden;
  position: relative;
  height: 100%;
  touch-action: pan-y;
}

.flickity-slider {
  position: absolute;
  width: 100%;
  height: 100%;
  left: 0;
}

.flickity-rtl .flickity-slider {
  left: unset;
  right: 0;
}

/* draggable */

.flickity-enabled.is-draggable {
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

.flickity-enabled.is-draggable .flickity-viewport {
  cursor: move;
  cursor: grab;
}

.flickity-enabled.is-draggable .flickity-viewport.is-pointer-down {
  cursor: grabbing;
}

/* ---- flickity-cell ---- */

.flickity-cell {
  position: absolute;
  left: 0;
}

.flickity-rtl .flickity-cell {
  left: unset;
  right: 0;
}

/* ---- flickity-button ---- */

.flickity-button {
  position: absolute;
  background: hsla(0, 0%, 100%, 75%);
  border: none;
  color: hsl(0, 0%, 20%);
}

.flickity-button:hover {
  background: white;
  cursor: pointer;
}

.flickity-button:focus {
  outline: none;
  box-shadow: 0 0 0 5px #19F;
}

.flickity-button:active {
  color: #19F;
}

.flickity-button:disabled {
  opacity: 0.3;
  cursor: auto;
  /* prevent disabled button from capturing pointer up event. #716 */
  pointer-events: none;
}

.flickity-button-icon {
  fill: currentcolor;
}

/* ---- previous/next buttons ---- */

.flickity-prev-next-button {
  top: 50%;
  width: 44px;
  height: 44px;
  z-index: 1; /* above viewport */
  border-radius: 50%;
  /* vertically center */
  transform: translateY(-50%);
}

.flickity-prev-next-button.previous { left: 10px; }
.flickity-prev-next-button.next { right: 10px; }
/* right to left */
.flickity-rtl .flickity-prev-next-button.previous {
  left: auto;
  right: 10px;
}

.flickity-rtl .flickity-prev-next-button.next {
  right: auto;
  left: 10px;
}

.flickity-prev-next-button .flickity-button-icon {
  position: absolute;
  left: 20%;
  top: 20%;
  width: 60%;
  height: 60%;
}

/* ---- page dots ---- */

.flickity-page-dots {
  position: absolute;
  width: 100%;
  bottom: -25px;
  z-index: 1; /* above viewport */
  text-align: center;
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
}

.flickity-rtl .flickity-page-dots { direction: rtl; }

.flickity-page-dot {
  position: relative;
  display: block;
  width: 10px;
  height: 10px;
  padding: 0;
  margin: 0 8px;
  background: hsl(0, 0%, 20%, 25%);
  border-radius: 50%;
  cursor: pointer;
  appearance: none;
  border: none;
  text-indent: -9999px;
  overflow: hidden;
}

.flickity-rtl .flickity-page-dot {
  text-indent: 9999px;
}

.flickity-page-dot:hover {
  background: hsla(0, 0%, 20%, 75%);
}

.flickity-page-dot:active {
  background: #19F;
}

.flickity-page-dot.is-selected {
  background: hsl(0, 0%, 20%);
}


================================================
FILE: dist/flickity.css
================================================
/*! Flickity v3.0.0
https://flickity.metafizzy.co
---------------------------------------------- */

.flickity-enabled {
  position: relative;
}

.flickity-enabled:focus { outline: none; }

.flickity-viewport {
  overflow: hidden;
  position: relative;
  height: 100%;
  touch-action: pan-y;
}

.flickity-slider {
  position: absolute;
  width: 100%;
  height: 100%;
  left: 0;
}

.flickity-rtl .flickity-slider {
  left: unset;
  right: 0;
}

/* draggable */

.flickity-enabled.is-draggable {
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}

.flickity-enabled.is-draggable .flickity-viewport {
  cursor: move;
  cursor: grab;
}

.flickity-enabled.is-draggable .flickity-viewport.is-pointer-down {
  cursor: grabbing;
}

/* ---- flickity-cell ---- */

.flickity-cell {
  position: absolute;
  left: 0;
}

.flickity-rtl .flickity-cell {
  left: unset;
  right: 0;
}

/* ---- flickity-button ---- */

.flickity-button {
  position: absolute;
  background: hsl(0 0% 100% / 75%);
  border: none;
  color: #333;
}

.flickity-button:hover {
  background: white;
  cursor: pointer;
}

.flickity-button:focus {
  outline: none;
  box-shadow: 0 0 0 5px #19F;
}

.flickity-button:active {
  opacity: 0.6;
}

.flickity-button:disabled {
  opacity: 0.3;
  cursor: auto;
  /* prevent disabled button from capturing pointer up event. #716 */
  pointer-events: none;
}

.flickity-button-icon {
  fill: currentColor;
}

/* ---- previous/next buttons ---- */

.flickity-prev-next-button {
  top: 50%;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  /* vertically center */
  transform: translateY(-50%);
}

.flickity-prev-next-button.previous { left: 10px; }
.flickity-prev-next-button.next { right: 10px; }
/* right to left */
.flickity-rtl .flickity-prev-next-button.previous {
  left: auto;
  right: 10px;
}

.flickity-rtl .flickity-prev-next-button.next {
  right: auto;
  left: 10px;
}

.flickity-prev-next-button .flickity-button-icon {
  position: absolute;
  left: 20%;
  top: 20%;
  width: 60%;
  height: 60%;
}

/* ---- page dots ---- */

.flickity-page-dots {
  position: absolute;
  width: 100%;
  bottom: -25px;
  text-align: center;
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
}

.flickity-rtl .flickity-page-dots { direction: rtl; }

.flickity-page-dot {
  display: block;
  width: 10px;
  height: 10px;
  padding: 0;
  margin: 0 8px;
  background: hsl(0 0% 20% / 25%);
  border-radius: 50%;
  cursor: pointer;
  appearance: none;
  border: none;
  text-indent: -9999px;
  overflow: hidden;
}

.flickity-rtl .flickity-page-dot {
  text-indent: 9999px;
}

.flickity-page-dot:focus {
  outline: none;
  box-shadow: 0 0 0 5px #19F;
}

.flickity-page-dot.is-selected {
  background: hsl(0 0% 20% / 100%);
}


================================================
FILE: dist/flickity.pkgd.js
================================================
/*!
 * Flickity PACKAGED v3.0.0
 * Touch, responsive, flickable carousels
 *
 * Licensed GPLv3 for open source use
 * or Flickity Commercial License for commercial use
 *
 * https://flickity.metafizzy.co
 * Copyright 2015-2022 Metafizzy
 */

/**
 * Bridget makes jQuery widgets
 * v3.0.1
 * 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.1.1
 * 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;

} ) );
/*!
 * Infinite Scroll v2.0.4
 * measure size of elements
 * MIT license
 */

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

} )( window, function factory() {

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

// get a number from a string, not a percentage
function getStyleSize( value ) {
  let num = parseFloat( value );
  // not a percent like '100%', and a number
  let isValid = value.indexOf('%') == -1 && !isNaN( num );
  return isValid && num;
}

// -------------------------- measurements -------------------------- //

let measurements = [
  'paddingLeft',
  'paddingRight',
  'paddingTop',
  'paddingBottom',
  'marginLeft',
  'marginRight',
  'marginTop',
  'marginBottom',
  'borderLeftWidth',
  'borderRightWidth',
  'borderTopWidth',
  'borderBottomWidth',
];

let measurementsLength = measurements.length;

function getZeroSize() {
  let size = {
    width: 0,
    height: 0,
    innerWidth: 0,
    innerHeight: 0,
    outerWidth: 0,
    outerHeight: 0,
  };
  measurements.forEach( ( measurement ) => {
    size[ measurement ] = 0;
  } );
  return size;
}

// -------------------------- getSize -------------------------- //

function getSize( elem ) {
  // use querySeletor if elem is string
  if ( typeof elem == 'string' ) elem = document.querySelector( elem );

  // do not proceed on non-objects
  let isElement = elem && typeof elem == 'object' && elem.nodeType;
  if ( !isElement ) return;

  let style = getComputedStyle( elem );

  // if hidden, everything is 0
  if ( style.display == 'none' ) return getZeroSize();

  let size = {};
  size.width = elem.offsetWidth;
  size.height = elem.offsetHeight;

  let isBorderBox = size.isBorderBox = style.boxSizing == 'border-box';

  // get all measurements
  measurements.forEach( ( measurement ) => {
    let value = style[ measurement ];
    let num = parseFloat( value );
    // any 'auto', 'medium' value will be 0
    size[ measurement ] = !isNaN( num ) ? num : 0;
  } );

  let paddingWidth = size.paddingLeft + size.paddingRight;
  let paddingHeight = size.paddingTop + size.paddingBottom;
  let marginWidth = size.marginLeft + size.marginRight;
  let marginHeight = size.marginTop + size.marginBottom;
  let borderWidth = size.borderLeftWidth + size.borderRightWidth;
  let borderHeight = size.borderTopWidth + size.borderBottomWidth;

  // overwrite width and height if we can get it from style
  let styleWidth = getStyleSize( style.width );
  if ( styleWidth !== false ) {
    size.width = styleWidth +
      // add padding and border unless it's already including it
      ( isBorderBox ? 0 : paddingWidth + borderWidth );
  }

  let styleHeight = getStyleSize( style.height );
  if ( styleHeight !== false ) {
    size.height = styleHeight +
      // add padding and border unless it's already including it
      ( isBorderBox ? 0 : paddingHeight + borderHeight );
  }

  size.innerWidth = size.width - ( paddingWidth + borderWidth );
  size.innerHeight = size.height - ( paddingHeight + borderHeight );

  size.outerWidth = size.width + marginWidth;
  size.outerHeight = size.height + marginHeight;

  return size;
}

return getSize;

} );
/**
 * 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;

} ) );
/*!
 * Unidragger v3.0.0
 * Draggable base class
 * MIT license
 */

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

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

function Unidragger() {}

// inherit EvEmitter
let proto = Unidragger.prototype = Object.create( EvEmitter.prototype );

// ----- bind start ----- //

// trigger handler methods for events
proto.handleEvent = function( event ) {
  let method = 'on' + event.type;
  if ( this[ method ] ) {
    this[ method ]( event );
  }
};

let startEvent, activeEvents;
if ( 'ontouchstart' in window ) {
  // HACK prefer Touch Events as you can preventDefault on touchstart to
  // disable scroll in iOS & mobile Chrome metafizzy/flickity#1177
  startEvent = 'touchstart';
  activeEvents = [ 'touchmove', 'touchend', 'touchcancel' ];
} else if ( window.PointerEvent ) {
  // Pointer Events
  startEvent = 'pointerdown';
  activeEvents = [ 'pointermove', 'pointerup', 'pointercancel' ];
} else {
  // mouse events
  startEvent = 'mousedown';
  activeEvents = [ 'mousemove', 'mouseup' ];
}

// prototype so it can be overwriteable by Flickity
proto.touchActionValue = 'none';

proto.bindHandles = function() {
  this._bindHandles( 'addEventListener', this.touchActionValue );
};

proto.unbindHandles = function() {
  this._bindHandles( 'removeEventListener', '' );
};

/**
 * Add or remove start event
 * @param {String} bindMethod - addEventListener or removeEventListener
 * @param {String} touchAction - value for touch-action CSS property
 */
proto._bindHandles = function( bindMethod, touchAction ) {
  this.handles.forEach( ( handle ) => {
    handle[ bindMethod ]( startEvent, this );
    handle[ bindMethod ]( 'click', this );
    // touch-action: none to override browser touch gestures. metafizzy/flickity#540
    if ( window.PointerEvent ) handle.style.touchAction = touchAction;
  } );
};

proto.bindActivePointerEvents = function() {
  activeEvents.forEach( ( eventName ) => {
    window.addEventListener( eventName, this );
  } );
};

proto.unbindActivePointerEvents = function() {
  activeEvents.forEach( ( eventName ) => {
    window.removeEventListener( eventName, this );
  } );
};

// ----- event handler helpers ----- //

// trigger method with matching pointer
proto.withPointer = function( methodName, event ) {
  if ( event.pointerId == this.pointerIdentifier ) {
    this[ methodName ]( event, event );
  }
};

// trigger method with matching touch
proto.withTouch = function( methodName, event ) {
  let touch;
  for ( let changedTouch of event.changedTouches ) {
    if ( changedTouch.identifier == this.pointerIdentifier ) {
      touch = changedTouch;
    }
  }
  if ( touch ) this[ methodName ]( event, touch );
};

// ----- start event ----- //

proto.onmousedown = function( event ) {
  this.pointerDown( event, event );
};

proto.ontouchstart = function( event ) {
  this.pointerDown( event, event.changedTouches[0] );
};

proto.onpointerdown = function( event ) {
  this.pointerDown( event, event );
};

// nodes that have text fields
const cursorNodes = [ 'TEXTAREA', 'INPUT', 'SELECT', 'OPTION' ];
// input types that do not have text fields
const clickTypes = [ 'radio', 'checkbox', 'button', 'submit', 'image', 'file' ];

/**
 * any time you set `event, pointer` it refers to:
 * @param {Event} event
 * @param {Event | Touch} pointer
 */
proto.pointerDown = function( event, pointer ) {
  // dismiss multi-touch taps, right clicks, and clicks on text fields
  let isCursorNode = cursorNodes.includes( event.target.nodeName );
  let isClickType = clickTypes.includes( event.target.type );
  let isOkayElement = !isCursorNode || isClickType;
  let isOkay = !this.isPointerDown && !event.button && isOkayElement;
  if ( !isOkay ) return;

  this.isPointerDown = true;
  // save pointer identifier to match up touch events
  this.pointerIdentifier = pointer.pointerId !== undefined ?
    // pointerId for pointer events, touch.indentifier for touch events
    pointer.pointerId : pointer.identifier;
  // track position for move
  this.pointerDownPointer = {
    pageX: pointer.pageX,
    pageY: pointer.pageY,
  };

  this.bindActivePointerEvents();
  this.emitEvent( 'pointerDown', [ event, pointer ] );
};

// ----- move ----- //

proto.onmousemove = function( event ) {
  this.pointerMove( event, event );
};

proto.onpointermove = function( event ) {
  this.withPointer( 'pointerMove', event );
};

proto.ontouchmove = function( event ) {
  this.withTouch( 'pointerMove', event );
};

proto.pointerMove = function( event, pointer ) {
  let moveVector = {
    x: pointer.pageX - this.pointerDownPointer.pageX,
    y: pointer.pageY - this.pointerDownPointer.pageY,
  };
  this.emitEvent( 'pointerMove', [ event, pointer, moveVector ] );
  // start drag if pointer has moved far enough to start drag
  let isDragStarting = !this.isDragging && this.hasDragStarted( moveVector );
  if ( isDragStarting ) this.dragStart( event, pointer );
  if ( this.isDragging ) this.dragMove( event, pointer, moveVector );
};

// condition if pointer has moved far enough to start drag
proto.hasDragStarted = function( moveVector ) {
  return Math.abs( moveVector.x ) > 3 || Math.abs( moveVector.y ) > 3;
};

// ----- drag ----- //

proto.dragStart = function( event, pointer ) {
  this.isDragging = true;
  this.isPreventingClicks = true; // set flag to prevent clicks
  this.emitEvent( 'dragStart', [ event, pointer ] );
};

proto.dragMove = function( event, pointer, moveVector ) {
  this.emitEvent( 'dragMove', [ event, pointer, moveVector ] );
};

// ----- end ----- //

proto.onmouseup = function( event ) {
  this.pointerUp( event, event );
};

proto.onpointerup = function( event ) {
  this.withPointer( 'pointerUp', event );
};

proto.ontouchend = function( event ) {
  this.withTouch( 'pointerUp', event );
};

proto.pointerUp = function( event, pointer ) {
  this.pointerDone();
  this.emitEvent( 'pointerUp', [ event, pointer ] );

  if ( this.isDragging ) {
    this.dragEnd( event, pointer );
  } else {
    // pointer didn't move enough for drag to start
    this.staticClick( event, pointer );
  }
};

proto.dragEnd = function( event, pointer ) {
  this.isDragging = false; // reset flag
  // re-enable clicking async
  setTimeout( () => delete this.isPreventingClicks );

  this.emitEvent( 'dragEnd', [ event, pointer ] );
};

// triggered on pointer up & pointer cancel
proto.pointerDone = function() {
  this.isPointerDown = false;
  delete this.pointerIdentifier;
  this.unbindActivePointerEvents();
  this.emitEvent('pointerDone');
};

// ----- cancel ----- //

proto.onpointercancel = function( event ) {
  this.withPointer( 'pointerCancel', event );
};

proto.ontouchcancel = function( event ) {
  this.withTouch( 'pointerCancel', event );
};

proto.pointerCancel = function( event, pointer ) {
  this.pointerDone();
  this.emitEvent( 'pointerCancel', [ event, pointer ] );
};

// ----- click ----- //

// handle all clicks and prevent clicks when dragging
proto.onclick = function( event ) {
  if ( this.isPreventingClicks ) event.preventDefault();
};

// triggered after pointer down & up with no/tiny movement
proto.staticClick = function( event, pointer ) {
  // ignore emulated mouse up clicks
  let isMouseup = event.type == 'mouseup';
  if ( isMouseup && this.isIgnoringMouseUp ) return;

  this.emitEvent( 'staticClick', [ event, pointer ] );

  // set flag for emulated clicks 300ms after touchend
  if ( isMouseup ) {
    this.isIgnoringMouseUp = true;
    // reset flag after 400ms
    setTimeout( () => {
      delete this.isIgnoringMouseUp;
    }, 400 );
  }
};

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

return Unidragger;

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

( function( window, factory ) {
  // universal module definition
  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,
    function factory( window, EvEmitter ) {

let $ = window.jQuery;
let console = window.console;

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

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

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

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

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

/**
 * @param {[Array, Element, NodeList, String]} elem
 * @param {[Object, Function]} options - if function, use as callback
 * @param {Function} onAlways - callback function
 * @returns {ImagesLoaded}
 */
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
  let 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 = {};
  // shift arguments if no options set
  if ( typeof options == 'function' ) {
    onAlways = options;
  } else {
    Object.assign( this.options, options );
  }

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

  this.getImages();
  // add jQuery Deferred object
  if ( $ ) 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.getImages = function() {
  this.images = [];

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

const elementNodeTypes = [ 1, 9, 11 ];

/**
 * @param {Node} elem
 */
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
  let { nodeType } = elem;
  if ( !nodeType || !elementNodeTypes.includes( nodeType ) ) return;

  let childImgs = elem.querySelectorAll('img');
  // concat childElems to filterFound array
  for ( let img of childImgs ) {
    this.addImage( img );
  }

  // get child background images
  if ( typeof this.options.background == 'string' ) {
    let children = elem.querySelectorAll( this.options.background );
    for ( let child of children ) {
      this.addElementBackgroundImages( child );
    }
  }
};

const reURL = /url\((['"])?(.*?)\1\)/gi;

ImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
  let style = getComputedStyle( elem );
  // Firefox returns null if in a hidden iframe https://bugzil.la/548397
  if ( !style ) return;

  // get url inside url("...")
  let matches = reURL.exec( style.backgroundImage );
  while ( matches !== null ) {
    let url = matches && matches[2];
    if ( url ) {
      this.addBackground( url, elem );
    }
    matches = reURL.exec( style.backgroundImage );
  }
};

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

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

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

  /* eslint-disable-next-line func-style */
  let onProgress = ( image, elem, message ) => {
    // HACK - Chrome triggers event before object properties have changed. #83
    setTimeout( () => {
      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() {
  let eventName = this.hasAnyBroken ? 'fail' : 'done';
  this.isComplete = true;
  this.emitEvent( eventName, [ this ] );
  this.emitEvent( 'always', [ this ] );
  if ( this.jqDeferred ) {
    let 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.
  let 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();
  // add crossOrigin attribute. #204
  if ( this.img.crossOrigin ) {
    this.proxyImage.crossOrigin = this.img.crossOrigin;
  }
  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.currentSrc || 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;
  let { parentNode } = this.img;
  // emit progress with parent <picture> or self <img>
  let elem = parentNode.nodeName === 'PICTURE' ? parentNode : this.img;
  this.emitEvent( 'progress', [ this, elem, message ] );
};

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

// trigger specified handler for event type
LoadingImage.prototype.handleEvent = function( event ) {
  let 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
  let 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, onAlways ) {
    let instance = new ImagesLoaded( this, options, onAlways );
    return instance.jqDeferred.promise( $( this ) );
  };
};
// try making plugin
ImagesLoaded.makeJQueryPlugin();

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

return ImagesLoaded;

} );
// Flickity.Cell
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory( require('get-size') );
  } else {
    // browser global
    window.Flickity = window.Flickity || {};
    window.Flickity.Cell = factory( window.getSize );
  }

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

const cellClassName = 'flickity-cell';

function Cell( elem ) {
  this.element = elem;
  this.element.classList.add( cellClassName );

  this.x = 0;
  this.unselect();
}

let proto = Cell.prototype;

proto.destroy = function() {
  // reset style
  this.unselect();
  this.element.classList.remove( cellClassName );
  this.element.style.transform = '';
  this.element.removeAttribute('aria-hidden');
};

proto.getSize = function() {
  this.size = getSize( this.element );
};

proto.select = function() {
  this.element.classList.add('is-selected');
  this.element.removeAttribute('aria-hidden');
};

proto.unselect = function() {
  this.element.classList.remove('is-selected');
  this.element.setAttribute( 'aria-hidden', 'true' );
};

proto.remove = function() {
  this.element.remove();
};

return Cell;

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

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

function Slide( beginMargin, endMargin, cellAlign ) {
  this.beginMargin = beginMargin;
  this.endMargin = endMargin;
  this.cellAlign = cellAlign;
  this.cells = [];
  this.outerWidth = 0;
  this.height = 0;
}

let proto = Slide.prototype;

proto.addCell = function( cell ) {
  this.cells.push( cell );
  this.outerWidth += cell.size.outerWidth;
  this.height = Math.max( cell.size.outerHeight, this.height );
  // first cell stuff
  if ( this.cells.length === 1 ) {
    this.x = cell.x; // x comes from first cell
    this.firstMargin = cell.size[ this.beginMargin ];
  }
};

proto.updateTarget = function() {
  let lastCell = this.getLastCell();
  let lastMargin = lastCell ? lastCell.size[ this.endMargin ] : 0;
  let slideWidth = this.outerWidth - ( this.firstMargin + lastMargin );
  this.target = this.x + this.firstMargin + slideWidth * this.cellAlign;
};

proto.getLastCell = function() {
  return this.cells[ this.cells.length - 1 ];
};

proto.select = function() {
  this.cells.forEach( ( cell ) => cell.select() );
};

proto.unselect = function() {
  this.cells.forEach( ( cell ) => cell.unselect() );
};

proto.getCellElements = function() {
  return this.cells.map( ( cell ) => cell.element );
};

return Slide;

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

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

// -------------------------- animate -------------------------- //

let proto = {};

proto.startAnimation = function() {
  if ( this.isAnimating ) return;

  this.isAnimating = true;
  this.restingFrames = 0;
  this.animate();
};

proto.animate = function() {
  this.applyDragForce();
  this.applySelectedAttraction();

  let previousX = this.x;

  this.integratePhysics();
  this.positionSlider();
  this.settle( previousX );
  // animate next frame
  if ( this.isAnimating ) requestAnimationFrame( () => this.animate() );
};

proto.positionSlider = function() {
  let x = this.x;
  // wrap position around
  if ( this.isWrapping ) {
    x = utils.modulo( x, this.slideableWidth ) - this.slideableWidth;
    this.shiftWrapCells( x );
  }

  this.setTranslateX( x, this.isAnimating );
  this.dispatchScrollEvent();
};

proto.setTranslateX = function( x, is3d ) {
  x += this.cursorPosition;
  // reverse if right-to-left and using transform
  if ( this.options.rightToLeft ) x = -x;
  let translateX = this.getPositionValue( x );
  // use 3D transforms for hardware acceleration on iOS
  // but use 2D when settled, for better font-rendering
  this.slider.style.transform = is3d ?
    `translate3d(${translateX},0,0)` : `translateX(${translateX})`;
};

proto.dispatchScrollEvent = function() {
  let firstSlide = this.slides[0];
  if ( !firstSlide ) return;

  let positionX = -this.x - firstSlide.target;
  let progress = positionX / this.slidesWidth;
  this.dispatchEvent( 'scroll', null, [ progress, positionX ] );
};

proto.positionSliderAtSelected = function() {
  if ( !this.cells.length ) return;

  this.x = -this.selectedSlide.target;
  this.velocity = 0; // stop wobble
  this.positionSlider();
};

proto.getPositionValue = function( position ) {
  if ( this.options.percentPosition ) {
    // percent position, round to 2 digits, like 12.34%
    return ( Math.round( ( position / this.size.innerWidth ) * 10000 ) * 0.01 ) + '%';
  } else {
    // pixel positioning
    return Math.round( position ) + 'px';
  }
};

proto.settle = function( previousX ) {
  // keep track of frames where x hasn't moved
  let isResting = !this.isPointerDown &&
      Math.round( this.x * 100 ) === Math.round( previousX * 100 );
  if ( isResting ) this.restingFrames++;
  // stop animating if resting for 3 or more frames
  if ( this.restingFrames > 2 ) {
    this.isAnimating = false;
    delete this.isFreeScrolling;
    // render position with translateX when settled
    this.positionSlider();
    this.dispatchEvent( 'settle', null, [ this.selectedIndex ] );
  }
};

proto.shiftWrapCells = function( x ) {
  // shift before cells
  let beforeGap = this.cursorPosition + x;
  this._shiftCells( this.beforeShiftCells, beforeGap, -1 );
  // shift after cells
  let afterGap = this.size.innerWidth - ( x + this.slideableWidth + this.cursorPosition );
  this._shiftCells( this.afterShiftCells, afterGap, 1 );
};

proto._shiftCells = function( cells, gap, shift ) {
  cells.forEach( ( cell ) => {
    let cellShift = gap > 0 ? shift : 0;
    this._wrapShiftCell( cell, cellShift );
    gap -= cell.size.outerWidth;
  } );
};

proto._unshiftCells = function( cells ) {
  if ( !cells || !cells.length ) return;

  cells.forEach( ( cell ) => this._wrapShiftCell( cell, 0 ) );
};

// @param {Integer} shift - 0, 1, or -1
proto._wrapShiftCell = function( cell, shift ) {
  this._renderCellPosition( cell, cell.x + this.slideableWidth * shift );
};

// -------------------------- physics -------------------------- //

proto.integratePhysics = function() {
  this.x += this.velocity;
  this.velocity *= this.getFrictionFactor();
};

proto.applyForce = function( force ) {
  this.velocity += force;
};

proto.getFrictionFactor = function() {
  return 1 - this.options[ this.isFreeScrolling ? 'freeScrollFriction' : 'friction' ];
};

proto.getRestingPosition = function() {
  // my thanks to Steven Wittens, who simplified this math greatly
  return this.x + this.velocity / ( 1 - this.getFrictionFactor() );
};

proto.applyDragForce = function() {
  if ( !this.isDraggable || !this.isPointerDown ) return;

  // change the position to drag position by applying force
  let dragVelocity = this.dragX - this.x;
  let dragForce = dragVelocity - this.velocity;
  this.applyForce( dragForce );
};

proto.applySelectedAttraction = function() {
  // do not attract if pointer down or no slides
  let dragDown = this.isDraggable && this.isPointerDown;
  if ( dragDown || this.isFreeScrolling || !this.slides.length ) return;

  let distance = this.selectedSlide.target * -1 - this.x;
  let force = distance * this.options.selectedAttraction;
  this.applyForce( force );
};

return proto;

} ) );
// Flickity main
/* eslint-disable max-params */
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('ev-emitter'),
        require('get-size'),
        require('fizzy-ui-utils'),
        require('./cell'),
        require('./slide'),
        require('./animate'),
    );
  } else {
    // browser global
    let _Flickity = window.Flickity;

    window.Flickity = factory(
        window,
        window.EvEmitter,
        window.getSize,
        window.fizzyUIUtils,
        _Flickity.Cell,
        _Flickity.Slide,
        _Flickity.animatePrototype,
    );
  }

}( typeof window != 'undefined' ? window : this,
    function factory( window, EvEmitter, getSize, utils, Cell, Slide, animatePrototype ) {
/* eslint-enable max-params */

// vars
const { getComputedStyle, console } = window;
let { jQuery } = window;

// -------------------------- Flickity -------------------------- //

// globally unique identifiers
let GUID = 0;
// internal store of all Flickity intances
let instances = {};

function Flickity( element, options ) {
  let queryElement = utils.getQueryElement( element );
  if ( !queryElement ) {
    if ( console ) console.error(`Bad element for Flickity: ${queryElement || element}`);
    return;
  }
  this.element = queryElement;
  // do not initialize twice on same element
  if ( this.element.flickityGUID ) {
    let instance = instances[ this.element.flickityGUID ];
    if ( instance ) instance.option( options );
    return instance;
  }

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

  // kick things off
  this._create();
}

Flickity.defaults = {
  accessibility: true,
  // adaptiveHeight: false,
  cellAlign: 'center',
  // cellSelector: undefined,
  // contain: false,
  freeScrollFriction: 0.075, // friction when free-scrolling
  friction: 0.28, // friction when selecting
  namespaceJQueryEvents: true,
  // initialIndex: 0,
  percentPosition: true,
  resize: true,
  selectedAttraction: 0.025,
  setGallerySize: true,
  // watchCSS: false,
  // wrapAround: false
};

// hash of methods triggered on _create()
Flickity.create = {};

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

proto._create = function() {
  let { resize, watchCSS, rightToLeft } = this.options;
  // add id for Flickity.data
  let id = this.guid = ++GUID;
  this.element.flickityGUID = id; // expando
  instances[ id ] = this; // associate via id
  // initial properties
  this.selectedIndex = 0;
  // how many frames slider has been in same position
  this.restingFrames = 0;
  // initial physics properties
  this.x = 0;
  this.velocity = 0;
  this.beginMargin = rightToLeft ? 'marginRight' : 'marginLeft';
  this.endMargin = rightToLeft ? 'marginLeft' : 'marginRight';
  // create viewport & slider
  this.viewport = document.createElement('div');
  this.viewport.className = 'flickity-viewport';
  this._createSlider();
  // used for keyboard navigation
  this.focusableElems = [ this.element ];

  if ( resize || watchCSS ) {
    window.addEventListener( 'resize', this );
  }

  // add listeners from on option
  for ( let eventName in this.options.on ) {
    let listener = this.options.on[ eventName ];
    this.on( eventName, listener );
  }

  for ( let method in Flickity.create ) {
    Flickity.create[ method ].call( this );
  }

  if ( watchCSS ) {
    this.watchCSS();
  } else {
    this.activate();
  }
};

/**
 * set options
 * @param {Object} opts - options to extend
 */
proto.option = function( opts ) {
  Object.assign( this.options, opts );
};

proto.activate = function() {
  if ( this.isActive ) return;

  this.isActive = true;
  this.element.classList.add('flickity-enabled');
  if ( this.options.rightToLeft ) {
    this.element.classList.add('flickity-rtl');
  }

  this.getSize();
  // move initial cell elements so they can be loaded as cells
  let cellElems = this._filterFindCellElements( this.element.children );
  this.slider.append( ...cellElems );
  this.viewport.append( this.slider );
  this.element.append( this.viewport );
  // get cells from children
  this.reloadCells();

  if ( this.options.accessibility ) {
    // allow element to focusable
    this.element.tabIndex = 0;
    // listen for key presses
    this.element.addEventListener( 'keydown', this );
  }

  this.emitEvent('activate');
  this.selectInitialIndex();
  // flag for initial activation, for using initialIndex
  this.isInitActivated = true;
  // ready event. #493
  this.dispatchEvent('ready');
};

// slider positions the cells
proto._createSlider = function() {
  // slider element does all the positioning
  let slider = document.createElement('div');
  slider.className = 'flickity-slider';
  this.slider = slider;
};

proto._filterFindCellElements = function( elems ) {
  return utils.filterFindElements( elems, this.options.cellSelector );
};

// goes through all children
proto.reloadCells = function() {
  // collection of item elements
  this.cells = this._makeCells( this.slider.children );
  this.positionCells();
  this._updateWrapShiftCells();
  this.setGallerySize();
};

/**
 * turn elements into Flickity.Cells
 * @param {[Array, NodeList, HTMLElement]} elems - elements to make into cells
 * @returns {Array} items - collection of new Flickity Cells
 */
proto._makeCells = function( elems ) {
  let cellElems = this._filterFindCellElements( elems );

  // create new Cells for collection
  return cellElems.map( ( cellElem ) => new Cell( cellElem ) );
};

proto.getLastCell = function() {
  return this.cells[ this.cells.length - 1 ];
};

proto.getLastSlide = function() {
  return this.slides[ this.slides.length - 1 ];
};

// positions all cells
proto.positionCells = function() {
  // size all cells
  this._sizeCells( this.cells );
  // position all cells
  this._positionCells( 0 );
};

/**
 * position certain cells
 * @param {Integer} index - which cell to start with
 */
proto._positionCells = function( index ) {
  index = index || 0;
  // also measure maxCellHeight
  // start 0 if positioning all cells
  this.maxCellHeight = index ? this.maxCellHeight || 0 : 0;
  let cellX = 0;
  // get cellX
  if ( index > 0 ) {
    let startCell = this.cells[ index - 1 ];
    cellX = startCell.x + startCell.size.outerWidth;
  }

  this.cells.slice( index ).forEach( ( cell ) => {
    cell.x = cellX;
    this._renderCellPosition( cell, cellX );
    cellX += cell.size.outerWidth;
    this.maxCellHeight = Math.max( cell.size.outerHeight, this.maxCellHeight );
  } );
  // keep track of cellX for wrap-around
  this.slideableWidth = cellX;
  // slides
  this.updateSlides();
  // contain slides target
  this._containSlides();
  // update slidesWidth
  this.slidesWidth = this.cells.length ?
    this.getLastSlide().target - this.slides[0].target : 0;
};

proto._renderCellPosition = function( cell, x ) {
  // render position of cell with in slider
  let sideOffset = this.options.rightToLeft ? -1 : 1;
  let renderX = x * sideOffset;
  if ( this.options.percentPosition ) renderX *= this.size.innerWidth / cell.size.width;
  let positionValue = this.getPositionValue( renderX );
  cell.element.style.transform = `translateX( ${positionValue} )`;
};

/**
 * cell.getSize() on multiple cells
 * @param {Array} cells - cells to size
 */
proto._sizeCells = function( cells ) {
  cells.forEach( ( cell ) => cell.getSize() );
};

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

proto.updateSlides = function() {
  this.slides = [];
  if ( !this.cells.length ) return;

  let { beginMargin, endMargin } = this;
  let slide = new Slide( beginMargin, endMargin, this.cellAlign );
  this.slides.push( slide );

  let canCellFit = this._getCanCellFit();

  this.cells.forEach( ( cell, i ) => {
    // just add cell if first cell in slide
    if ( !slide.cells.length ) {
      slide.addCell( cell );
      return;
    }

    let slideWidth = ( slide.outerWidth - slide.firstMargin ) +
      ( cell.size.outerWidth - cell.size[ endMargin ] );

    if ( canCellFit( i, slideWidth ) ) {
      slide.addCell( cell );
    } else {
      // doesn't fit, new slide
      slide.updateTarget();

      slide = new Slide( beginMargin, endMargin, this.cellAlign );
      this.slides.push( slide );
      slide.addCell( cell );
    }
  } );
  // last slide
  slide.updateTarget();
  // update .selectedSlide
  this.updateSelectedSlide();
};

proto._getCanCellFit = function() {
  let { groupCells } = this.options;
  if ( !groupCells ) return () => false;

  if ( typeof groupCells == 'number' ) {
    // group by number. 3 -> [0,1,2], [3,4,5], ...
    let number = parseInt( groupCells, 10 );
    return ( i ) => ( i % number ) !== 0;
  }
  // default, group by width of slide
  let percent = 1;
  // parse '75%
  let percentMatch = typeof groupCells == 'string' && groupCells.match( /^(\d+)%$/ );
  if ( percentMatch ) percent = parseInt( percentMatch[1], 10 ) / 100;
  let groupWidth = ( this.size.innerWidth + 1 ) * percent;
  return ( i, slideWidth ) => slideWidth <= groupWidth;
};

// alias _init for jQuery plugin .flickity()
proto._init =
proto.reposition = function() {
  this.positionCells();
  this.positionSliderAtSelected();
};

proto.getSize = function() {
  this.size = getSize( this.element );
  this.setCellAlign();
  this.cursorPosition = this.size.innerWidth * this.cellAlign;
};

let cellAlignShorthands = {
  left: 0,
  center: 0.5,
  right: 1,
};

proto.setCellAlign = function() {
  let { cellAlign, rightToLeft } = this.options;
  let shorthand = cellAlignShorthands[ cellAlign ];
  this.cellAlign = shorthand !== undefined ? shorthand : cellAlign;
  if ( rightToLeft ) this.cellAlign = 1 - this.cellAlign;
};

proto.setGallerySize = function() {
  if ( !this.options.setGallerySize ) return;

  let height = this.options.adaptiveHeight && this.selectedSlide ?
    this.selectedSlide.height : this.maxCellHeight;
  this.viewport.style.height = `${height}px`;
};

proto._updateWrapShiftCells = function() {
  // update isWrapping
  this.isWrapping = this.getIsWrapping();
  // only for wrap-around
  if ( !this.isWrapping ) return;

  // unshift previous cells
  this._unshiftCells( this.beforeShiftCells );
  this._unshiftCells( this.afterShiftCells );
  // get before cells
  // initial gap
  let beforeGapX = this.cursorPosition;
  let lastIndex = this.cells.length - 1;
  this.beforeShiftCells = this._getGapCells( beforeGapX, lastIndex, -1 );
  // get after cells
  // ending gap between last cell and end of gallery viewport
  let afterGapX = this.size.innerWidth - this.cursorPosition;
  // start cloning at first cell, working forwards
  this.afterShiftCells = this._getGapCells( afterGapX, 0, 1 );
};

proto.getIsWrapping = function() {
  let { wrapAround } = this.options;
  if ( !wrapAround || this.slides.length < 2 ) return false;

  if ( wrapAround !== 'fill' ) return true;
  // check that slides can fit

  let gapWidth = this.slideableWidth - this.size.innerWidth;
  if ( gapWidth > this.size.innerWidth ) return true; // gap * 2x big, all good
  // check that content width - shifting cell is bigger than viewport width
  for ( let cell of this.cells ) {
    if ( cell.size.outerWidth > gapWidth ) return false;
  }
  return true;
};

proto._getGapCells = function( gapX, cellIndex, increment ) {
  // keep adding cells until the cover the initial gap
  let cells = [];
  while ( gapX > 0 ) {
    let cell = this.cells[ cellIndex ];
    if ( !cell ) break;

    cells.push( cell );
    cellIndex += increment;
    gapX -= cell.size.outerWidth;
  }
  return cells;
};

// ----- contain & wrap ----- //

// contain cell targets so no excess sliding
proto._containSlides = function() {
  let isContaining = this.options.contain && !this.isWrapping &&
      this.cells.length;
  if ( !isContaining ) return;

  let contentWidth = this.slideableWidth - this.getLastCell().size[ this.endMargin ];
  // content is less than gallery size
  let isContentSmaller = contentWidth < this.size.innerWidth;
  if ( isContentSmaller ) {
    // all cells fit inside gallery
    this.slides.forEach( ( slide ) => {
      slide.target = contentWidth * this.cellAlign;
    } );
  } else {
    // contain to bounds
    let beginBound = this.cursorPosition + this.cells[0].size[ this.beginMargin ];
    let endBound = contentWidth - this.size.innerWidth * ( 1 - this.cellAlign );
    this.slides.forEach( ( slide ) => {
      slide.target = Math.max( slide.target, beginBound );
      slide.target = Math.min( slide.target, endBound );
    } );
  }
};

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

/**
 * emits events via eventEmitter and jQuery events
 * @param {String} type - name of event
 * @param {Event} event - original event
 * @param {Array} args - extra arguments
 */
proto.dispatchEvent = function( type, event, args ) {
  let emitArgs = event ? [ event ].concat( args ) : args;
  this.emitEvent( type, emitArgs );

  if ( jQuery && this.$element ) {
    // default trigger with type if no event
    type += this.options.namespaceJQueryEvents ? '.flickity' : '';
    let $event = type;
    if ( event ) {
      // create jQuery event
      let jQEvent = new jQuery.Event( event );
      jQEvent.type = type;
      $event = jQEvent;
    }
    this.$element.trigger( $event, args );
  }
};

const unidraggerEvents = [
  'dragStart',
  'dragMove',
  'dragEnd',
  'pointerDown',
  'pointerMove',
  'pointerEnd',
  'staticClick',
];

let _emitEvent = proto.emitEvent;
proto.emitEvent = function( eventName, args ) {
  if ( eventName === 'staticClick' ) {
    // add cellElem and cellIndex args to staticClick
    let clickedCell = this.getParentCell( args[0].target );
    let cellElem = clickedCell && clickedCell.element;
    let cellIndex = clickedCell && this.cells.indexOf( clickedCell );
    args = args.concat( cellElem, cellIndex );
  }
  // do regular thing
  _emitEvent.call( this, eventName, args );
  // duck-punch in jQuery events for Unidragger events
  let isUnidraggerEvent = unidraggerEvents.includes( eventName );
  if ( !isUnidraggerEvent || !jQuery || !this.$element ) return;

  eventName += this.options.namespaceJQueryEvents ? '.flickity' : '';
  let event = args.shift( 0 );
  let jQEvent = new jQuery.Event( event );
  jQEvent.type = eventName;
  this.$element.trigger( jQEvent, args );
};

// -------------------------- select -------------------------- //

/**
 * @param {Integer} index - index of the slide
 * @param {Boolean} isWrap - will wrap-around to last/first if at the end
 * @param {Boolean} isInstant - will immediately set position at selected cell
 */
proto.select = function( index, isWrap, isInstant ) {
  if ( !this.isActive ) return;

  index = parseInt( index, 10 );
  this._wrapSelect( index );

  if ( this.isWrapping || isWrap ) {
    index = utils.modulo( index, this.slides.length );
  }
  // bail if invalid index
  if ( !this.slides[ index ] ) return;

  let prevIndex = this.selectedIndex;
  this.selectedIndex = index;
  this.updateSelectedSlide();
  if ( isInstant ) {
    this.positionSliderAtSelected();
  } else {
    this.startAnimation();
  }
  if ( this.options.adaptiveHeight ) {
    this.setGallerySize();
  }
  // events
  this.dispatchEvent( 'select', null, [ index ] );
  // change event if new index
  if ( index !== prevIndex ) {
    this.dispatchEvent( 'change', null, [ index ] );
  }
};

// wraps position for wrapAround, to move to closest slide. #113
proto._wrapSelect = function( index ) {
  if ( !this.isWrapping ) return;

  const { selectedIndex, slideableWidth, slides: { length } } = this;
  // shift index for wrap, do not wrap dragSelect
  if ( !this.isDragSelect ) {
    let wrapIndex = utils.modulo( index, length );
    // go to shortest
    let delta = Math.abs( wrapIndex - selectedIndex );
    let backWrapDelta = Math.abs( ( wrapIndex + length ) - selectedIndex );
    let forewardWrapDelta = Math.abs( ( wrapIndex - length ) - selectedIndex );
    if ( backWrapDelta < delta ) {
      index += length;
    } else if ( forewardWrapDelta < delta ) {
      index -= length;
    }
  }

  // wrap position so slider is within normal area
  if ( index < 0 ) {
    this.x -= slideableWidth;
  } else if ( index >= length ) {
    this.x += slideableWidth;
  }
};

proto.previous = function( isWrap, isInstant ) {
  this.select( this.selectedIndex - 1, isWrap, isInstant );
};

proto.next = function( isWrap, isInstant ) {
  this.select( this.selectedIndex + 1, isWrap, isInstant );
};

proto.updateSelectedSlide = function() {
  let slide = this.slides[ this.selectedIndex ];
  // selectedIndex could be outside of slides, if triggered before resize()
  if ( !slide ) return;

  // unselect previous selected slide
  this.unselectSelectedSlide();
  // update new selected slide
  this.selectedSlide = slide;
  slide.select();
  this.selectedCells = slide.cells;
  this.selectedElements = slide.getCellElements();
  // HACK: selectedCell & selectedElement is first cell in slide, backwards compatibility
  this.selectedCell = slide.cells[0];
  this.selectedElement = this.selectedElements[0];
};

proto.unselectSelectedSlide = function() {
  if ( this.selectedSlide ) this.selectedSlide.unselect();
};

proto.selectInitialIndex = function() {
  let initialIndex = this.options.initialIndex;
  // already activated, select previous selectedIndex
  if ( this.isInitActivated ) {
    this.select( this.selectedIndex, false, true );
    return;
  }
  // select with selector string
  if ( initialIndex && typeof initialIndex == 'string' ) {
    let cell = this.queryCell( initialIndex );
    if ( cell ) {
      this.selectCell( initialIndex, false, true );
      return;
    }
  }

  let index = 0;
  // select with number
  if ( initialIndex && this.slides[ initialIndex ] ) {
    index = initialIndex;
  }
  // select instantly
  this.select( index, false, true );
};

/**
 * select slide from number or cell element
 * @param {[Element, Number]} value - zero-based index or element to select
 * @param {Boolean} isWrap - enables wrapping around for extra index
 * @param {Boolean} isInstant - disables slide animation
 */
proto.selectCell = function( value, isWrap, isInstant ) {
  // get cell
  let cell = this.queryCell( value );
  if ( !cell ) return;

  let index = this.getCellSlideIndex( cell );
  this.select( index, isWrap, isInstant );
};

proto.getCellSlideIndex = function( cell ) {
  // get index of slide that has cell
  let cellSlide = this.slides.find( ( slide ) => slide.cells.includes( cell ) );
  return this.slides.indexOf( cellSlide );
};

// -------------------------- get cells -------------------------- //

/**
 * get Flickity.Cell, given an Element
 * @param {Element} elem - matching cell element
 * @returns {Flickity.Cell} cell - matching cell
 */
proto.getCell = function( elem ) {
  // loop through cells to get the one that matches
  for ( let cell of this.cells ) {
    if ( cell.element === elem ) return cell;
  }
};

/**
 * get collection of Flickity.Cells, given Elements
 * @param {[Element, Array, NodeList]} elems - multiple elements
 * @returns {Array} cells - Flickity.Cells
 */
proto.getCells = function( elems ) {
  elems = utils.makeArray( elems );
  return elems.map( ( elem ) => this.getCell( elem ) ).filter( Boolean );
};

/**
 * get cell elements
 * @returns {Array} cellElems
 */
proto.getCellElements = function() {
  return this.cells.map( ( cell ) => cell.element );
};

/**
 * get parent cell from an element
 * @param {Element} elem - child element
 * @returns {Flickit.Cell} cell - parent cell
 */
proto.getParentCell = function( elem ) {
  // first check if elem is cell
  let cell = this.getCell( elem );
  if ( cell ) return cell;

  // try to get parent cell elem
  let closest = elem.closest('.flickity-slider > *');
  return this.getCell( closest );
};

/**
 * get cells adjacent to a slide
 * @param {Integer} adjCount - number of adjacent slides
 * @param {Integer} index - index of slide to start
 * @returns {Array} cells - array of Flickity.Cells
 */
proto.getAdjacentCellElements = function( adjCount, index ) {
  if ( !adjCount ) return this.selectedSlide.getCellElements();

  index = index === undefined ? this.selectedIndex : index;

  let len = this.slides.length;
  if ( 1 + ( adjCount * 2 ) >= len ) {
    return this.getCellElements(); // get all
  }

  let cellElems = [];
  for ( let i = index - adjCount; i <= index + adjCount; i++ ) {
    let slideIndex = this.isWrapping ? utils.modulo( i, len ) : i;
    let slide = this.slides[ slideIndex ];
    if ( slide ) {
      cellElems = cellElems.concat( slide.getCellElements() );
    }
  }
  return cellElems;
};

/**
 * select slide from number or cell element
 * @param {[Element, String, Number]} selector - element, selector string, or index
 * @returns {Flickity.Cell} - matching cell
 */
proto.queryCell = function( selector ) {
  if ( typeof selector == 'number' ) {
    // use number as index
    return this.cells[ selector ];
  }
  // do not select invalid selectors from hash: #123, #/. #791
  let isSelectorString = typeof selector == 'string' && !selector.match( /^[#.]?[\d/]/ );
  if ( isSelectorString ) {
    // use string as selector, get element
    selector = this.element.querySelector( selector );
  }
  // get cell from element
  return this.getCell( selector );
};

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

proto.uiChange = function() {
  this.emitEvent('uiChange');
};

// ----- resize ----- //

proto.onresize = function() {
  this.watchCSS();
  this.resize();
};

utils.debounceMethod( Flickity, 'onresize', 150 );

proto.resize = function() {
  // #1177 disable resize behavior when animating or dragging for iOS 15
  if ( !this.isActive || this.isAnimating || this.isDragging ) return;
  this.getSize();
  // wrap values
  if ( this.isWrapping ) {
    this.x = utils.modulo( this.x, this.slideableWidth );
  }
  this.positionCells();
  this._updateWrapShiftCells();
  this.setGallerySize();
  this.emitEvent('resize');
  // update selected index for group slides, instant
  // TODO: position can be lost between groups of various numbers
  let selectedElement = this.selectedElements && this.selectedElements[0];
  this.selectCell( selectedElement, false, true );
};

// watches the :after property, activates/deactivates
proto.watchCSS = function() {
  if ( !this.options.watchCSS ) return;

  let afterContent = getComputedStyle( this.element, ':after' ).content;
  // activate if :after { content: 'flickity' }
  if ( afterContent.includes('flickity') ) {
    this.activate();
  } else {
    this.deactivate();
  }
};

// ----- keydown ----- //

// go previous/next if left/right keys pressed
proto.onkeydown = function( event ) {
  let { activeElement } = document;
  let handler = Flickity.keyboardHandlers[ event.key ];
  // only work if element is in focus
  if ( !this.options.accessibility || !activeElement || !handler ) return;

  let isFocused = this.focusableElems.some( ( elem ) => activeElement === elem );
  if ( isFocused ) handler.call( this );
};

Flickity.keyboardHandlers = {
  ArrowLeft: function() {
    this.uiChange();
    let leftMethod = this.options.rightToLeft ? 'next' : 'previous';
    this[ leftMethod ]();
  },
  ArrowRight: function() {
    this.uiChange();
    let rightMethod = this.options.rightToLeft ? 'previous' : 'next';
    this[ rightMethod ]();
  },
};

// ----- focus ----- //

proto.focus = function() {
  this.element.focus({ preventScroll: true });
};

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

// deactivate all Flickity functionality, but keep stuff available
proto.deactivate = function() {
  if ( !this.isActive ) return;

  this.element.classList.remove('flickity-enabled');
  this.element.classList.remove('flickity-rtl');
  this.unselectSelectedSlide();
  // destroy cells
  this.cells.forEach( ( cell ) => cell.destroy() );
  this.viewport.remove();
  // move child elements back into element
  this.element.append( ...this.slider.children );
  if ( this.options.accessibility ) {
    this.element.removeAttribute('tabIndex');
    this.element.removeEventListener( 'keydown', this );
  }
  // set flags
  this.isActive = false;
  this.emitEvent('deactivate');
};

proto.destroy = function() {
  this.deactivate();
  window.removeEventListener( 'resize', this );
  this.allOff();
  this.emitEvent('destroy');
  if ( jQuery && this.$element ) {
    jQuery.removeData( this.element, 'flickity' );
  }
  delete this.element.flickityGUID;
  delete instances[ this.guid ];
};

// -------------------------- prototype -------------------------- //

Object.assign( proto, animatePrototype );

// -------------------------- extras -------------------------- //

/**
 * get Flickity instance from element
 * @param {[Element, String]} elem - element or selector string
 * @returns {Flickity} - Flickity instance
 */
Flickity.data = function( elem ) {
  elem = utils.getQueryElement( elem );
  if ( elem ) return instances[ elem.flickityGUID ];
};

utils.htmlInit( Flickity, 'flickity' );

let { jQueryBridget } = window;
if ( jQuery && jQueryBridget ) {
  jQueryBridget( 'flickity', Flickity, jQuery );
}

// set internal jQuery, for Webpack + jQuery v3, #478
Flickity.setJQuery = function( jq ) {
  jQuery = jq;
};

Flickity.Cell = Cell;
Flickity.Slide = Slide;

return Flickity;

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

}( typeof window != 'undefined' ? window : this,
    function factory( window, Flickity, Unidragger, utils ) {

// ----- defaults ----- //

Object.assign( Flickity.defaults, {
  draggable: '>1',
  dragThreshold: 3,
} );

// -------------------------- drag prototype -------------------------- //

let proto = Flickity.prototype;
Object.assign( proto, Unidragger.prototype ); // inherit Unidragger
proto.touchActionValue = '';

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

Flickity.create.drag = function() {
  this.on( 'activate', this.onActivateDrag );
  this.on( 'uiChange', this._uiChangeDrag );
  this.on( 'deactivate', this.onDeactivateDrag );
  this.on( 'cellChange', this.updateDraggable );
  this.on( 'pointerDown', this.handlePointerDown );
  this.on( 'pointerUp', this.handlePointerUp );
  this.on( 'pointerDown', this.handlePointerDone );
  this.on( 'dragStart', this.handleDragStart );
  this.on( 'dragMove', this.handleDragMove );
  this.on( 'dragEnd', this.handleDragEnd );
  this.on( 'staticClick', this.handleStaticClick );
  // TODO updateDraggable on resize? if groupCells & slides change
};

proto.onActivateDrag = function() {
  this.handles = [ this.viewport ];
  this.bindHandles();
  this.updateDraggable();
};

proto.onDeactivateDrag = function() {
  this.unbindHandles();
  this.element.classList.remove('is-draggable');
};

proto.updateDraggable = function() {
  // disable dragging if less than 2 slides. #278
  if ( this.options.draggable === '>1' ) {
    this.isDraggable = this.slides.length > 1;
  } else {
    this.isDraggable = this.options.draggable;
  }
  this.element.classList.toggle( 'is-draggable', this.isDraggable );
};

proto._uiChangeDrag = function() {
  delete this.isFreeScrolling;
};

// -------------------------- pointer events -------------------------- //

proto.handlePointerDown = function( event ) {
  if ( !this.isDraggable ) {
    // proceed for staticClick
    this.bindActivePointerEvents( event );
    return;
  }

  let isTouchStart = event.type === 'touchstart';
  let isTouchPointer = event.pointerType === 'touch';
  let isFocusNode = event.target.matches('input, textarea, select');
  if ( !isTouchStart && !isTouchPointer && !isFocusNode ) event.preventDefault();
  if ( !isFocusNode ) this.focus();
  // blur
  if ( document.activeElement !== this.element ) document.activeElement.blur();
  // stop if it was moving
  this.dragX = this.x;
  this.viewport.classList.add('is-pointer-down');
  // track scrolling
  this.pointerDownScroll = getScrollPosition();
  window.addEventListener( 'scroll', this );
  this.bindActivePointerEvents( event );
};

// ----- move ----- //

proto.hasDragStarted = function( moveVector ) {
  return Math.abs( moveVector.x ) > this.options.dragThreshold;
};

// ----- up ----- //

proto.handlePointerUp = function() {
  delete this.isTouchScrolling;
  this.viewport.classList.remove('is-pointer-down');
};

proto.handlePointerDone = function() {
  window.removeEventListener( 'scroll', this );
  delete this.pointerDownScroll;
};

// -------------------------- dragging -------------------------- //

proto.handleDragStart = function() {
  if ( !this.isDraggable ) return;

  this.dragStartPosition = this.x;
  this.startAnimation();
  window.removeEventListener( 'scroll', this );
};

proto.handleDragMove = function( event, pointer, moveVector ) {
  if ( !this.isDraggable ) return;

  event.preventDefault();

  this.previousDragX = this.dragX;
  // reverse if right-to-left
  let direction = this.options.rightToLeft ? -1 : 1;
  // wrap around move. #589
  if ( this.isWrapping ) moveVector.x %= this.slideableWidth;
  let dragX = this.dragStartPosition + moveVector.x * direction;

  if ( !this.isWrapping ) {
    // slow drag
    let originBound = Math.max( -this.slides[0].target, this.dragStartPosition );
    dragX = dragX > originBound ? ( dragX + originBound ) * 0.5 : dragX;
    let endBound = Math.min( -this.getLastSlide().target, this.dragStartPosition );
    dragX = dragX < endBound ? ( dragX + endBound ) * 0.5 : dragX;
  }

  this.dragX = dragX;
  this.dragMoveTime = new Date();
};

proto.handleDragEnd = function() {
  if ( !this.isDraggable ) return;

  let { freeScroll } = this.options;
  if ( freeScroll ) this.isFreeScrolling = true;
  // set selectedIndex based on where flick will end up
  let index = this.dragEndRestingSelect();

  if ( freeScroll && !this.isWrapping ) {
    // if free-scroll & not wrap around
    // do not free-scroll if going outside of bounding slides
    // so bounding slides can attract slider, and keep it in bounds
    let restingX = this.getRestingPosition();
    this.isFreeScrolling = -restingX > this.slides[0].target &&
      -restingX < this.getLastSlide().target;
  } else if ( !freeScroll && index === this.selectedIndex ) {
    // boost selection if selected index has not changed
    index += this.dragEndBoostSelect();
  }
  delete this.previousDragX;
  // apply selection
  // HACK, set flag so dragging stays in correct direction
  this.isDragSelect = this.isWrapping;
  this.select( index );
  delete this.isDragSelect;
};

proto.dragEndRestingSelect = function() {
  let restingX = this.getRestingPosition();
  // how far away from selected slide
  let distance = Math.abs( this.getSlideDistance( -restingX, this.selectedIndex ) );
  // get closet resting going up and going down
  let positiveResting = this._getClosestResting( restingX, distance, 1 );
  let negativeResting = this._getClosestResting( restingX, distance, -1 );
  // use closer resting for wrap-around
  return positiveResting.distance < negativeResting.distance ?
    positiveResting.index : negativeResting.index;
};

/**
 * given resting X and distance to selected cell
 * get the distance and index of the closest cell
 * @param {Number} restingX - estimated post-flick resting position
 * @param {Number} distance - distance to selected cell
 * @param {Integer} increment - +1 or -1, going up or down
 * @returns {Object} - { distance: {Number}, index: {Integer} }
 */
proto._getClosestResting = function( restingX, distance, increment ) {
  let index = this.selectedIndex;
  let minDistance = Infinity;
  let condition = this.options.contain && !this.isWrapping ?
    // if containing, keep going if distance is equal to minDistance
    ( dist, minDist ) => dist <= minDist :
    ( dist, minDist ) => dist < minDist;

  while ( condition( distance, minDistance ) ) {
    // measure distance to next cell
    index += increment;
    minDistance = distance;
    distance = this.getSlideDistance( -restingX, index );
    if ( distance === null ) break;

    distance = Math.abs( distance );
  }
  return {
    distance: minDistance,
    // selected was previous index
    index: index - increment,
  };
};

/**
 * measure distance between x and a slide target
 * @param {Number} x - horizontal position
 * @param {Integer} index - slide index
 * @returns {Number} - slide distance
 */
proto.getSlideDistance = function( x, index ) {
  let len = this.slides.length;
  // wrap around if at least 2 slides
  let isWrapAround = this.options.wrapAround && len > 1;
  let slideIndex = isWrapAround ? utils.modulo( index, len ) : index;
  let slide = this.slides[ slideIndex ];
  if ( !slide ) return null;

  // add distance for wrap-around slides
  let wrap = isWrapAround ? this.slideableWidth * Math.floor( index/len ) : 0;
  return x - ( slide.target + wrap );
};

proto.dragEndBoostSelect = function() {
  // do not boost if no previousDragX or dragMoveTime
  if ( this.previousDragX === undefined || !this.dragMoveTime ||
    // or if drag was held for 100 ms
    new Date() - this.dragMoveTime > 100 ) {
    return 0;
  }

  let distance = this.getSlideDistance( -this.dragX, this.selectedIndex );
  let delta = this.previousDragX - this.dragX;
  if ( distance > 0 && delta > 0 ) {
    // boost to next if moving towards the right, and positive velocity
    return 1;
  } else if ( distance < 0 && delta < 0 ) {
    // boost to previous if moving towards the left, and negative velocity
    return -1;
  }
  return 0;
};

// ----- scroll ----- //

proto.onscroll = function() {
  let scroll = getScrollPosition();
  let scrollMoveX = this.pointerDownScroll.x - scroll.x;
  let scrollMoveY = this.pointerDownScroll.y - scroll.y;
  // cancel click/tap if scroll is too much
  if ( Math.abs( scrollMoveX ) > 3 || Math.abs( scrollMoveY ) > 3 ) {
    this.pointerDone();
  }
};

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

function getScrollPosition() {
  return {
    x: window.pageXOffset,
    y: window.pageYOffset,
  };
}

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

return Flickity;

} ) );
// prev/next buttons
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory( require('./core') );
  } else {
    // browser global
    factory( window.Flickity );
  }

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

const svgURI = 'http://www.w3.org/2000/svg';

// -------------------------- PrevNextButton -------------------------- //

function PrevNextButton( increment, direction, arrowShape ) {
  this.increment = increment;
  this.direction = direction;
  this.isPrevious = increment === 'previous';
  this.isLeft = direction === 'left';
  this._create( arrowShape );
}

PrevNextButton.prototype._create = function( arrowShape ) {
  // properties
  let element = this.element = document.createElement('button');
  element.className = `flickity-button flickity-prev-next-button ${this.increment}`;
  let label = this.isPrevious ? 'Previous' : 'Next';
  // prevent button from submitting form https://stackoverflow.com/a/10836076/182183
  element.setAttribute( 'type', 'button' );
  element.setAttribute( 'aria-label', label );
  // init as disabled
  this.disable();
  // create arrow
  let svg = this.createSVG( label, arrowShape );
  element.append( svg );
};

PrevNextButton.prototype.createSVG = function( label, arrowShape ) {
  let svg = document.createElementNS( svgURI, 'svg' );
  svg.setAttribute( 'class', 'flickity-button-icon' );
  svg.setAttribute( 'viewBox', '0 0 100 100' );
  // add title #1189
  let title = document.createElementNS( svgURI, 'title' );
  title.append( label );
  // add path
  let path = document.createElementNS( svgURI, 'path' );
  let pathMovements = getArrowMovements( arrowShape );
  path.setAttribute( 'd', pathMovements );
  path.setAttribute( 'class', 'arrow' );
  // rotate arrow
  if ( !this.isLeft ) {
    path.setAttribute( 'transform', 'translate(100, 100) rotate(180)' );
  }
  svg.append( title, path );
  return svg;
};

// get SVG path movmement
function getArrowMovements( shape ) {
  // use shape as movement if string
  if ( typeof shape == 'string' ) return shape;

  let { x0, x1, x2, x3, y1, y2 } = shape;

  // create movement string
  return `M ${x0}, 50
    L ${x1}, ${y1 + 50}
    L ${x2}, ${y2 + 50}
    L ${x3}, 50
    L ${x2}, ${50 - y2}
    L ${x1}, ${50 - y1}
    Z`;
}

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

PrevNextButton.prototype.enable = function() {
  this.element.removeAttribute('disabled');
};

PrevNextButton.prototype.disable = function() {
  this.element.setAttribute( 'disabled', true );
};

// -------------------------- Flickity prototype -------------------------- //

Object.assign( Flickity.defaults, {
  prevNextButtons: true,
  arrowShape: {
    x0: 10,
    x1: 60, y1: 50,
    x2: 70, y2: 40,
    x3: 30,
  },
} );

Flickity.create.prevNextButtons = function() {
  if ( !this.options.prevNextButtons ) return;

  let { rightToLeft, arrowShape } = this.options;
  let prevDirection = rightToLeft ? 'right' : 'left';
  let nextDirection = rightToLeft ? 'left' : 'right';
  this.prevButton = new PrevNextButton( 'previous', prevDirection, arrowShape );
  this.nextButton = new PrevNextButton( 'next', nextDirection, arrowShape );
  this.focusableElems.push( this.prevButton.element );
  this.focusableElems.push( this.nextButton.element );

  this.handlePrevButtonClick = () => {
    this.uiChange();
    this.previous();
  };

  this.handleNextButtonClick = () => {
    this.uiChange();
    this.next();
  };

  this.on( 'activate', this.activatePrevNextButtons );
  this.on( 'select', this.updatePrevNextButtons );
};

let proto = Flickity.prototype;

proto.updatePrevNextButtons = function() {
  let lastIndex = this.slides.length ? this.slides.length - 1 : 0;
  this.updatePrevNextButton( this.prevButton, 0 );
  this.updatePrevNextButton( this.nextButton, lastIndex );
};

proto.updatePrevNextButton = function( button, disabledIndex ) {
  // enable is wrapAround and at least 2 slides
  if ( this.isWrapping && this.slides.length > 1 ) {
    button.enable();
    return;
  }

  let isEnabled = this.selectedIndex !== disabledIndex;
  button[ isEnabled ? 'enable' : 'disable' ]();
  // if disabling button that is focused,
  // shift focus to element to maintain keyboard accessibility
  let isDisabledFocused = !isEnabled && document.activeElement === button.element;
  if ( isDisabledFocused ) this.focus();
};

proto.activatePrevNextButtons = function() {
  this.prevButton.element.addEventListener( 'click', this.handlePrevButtonClick );
  this.nextButton.element.addEventListener( 'click', this.handleNextButtonClick );
  this.element.append( this.prevButton.element, this.nextButton.element );
  this.on( 'deactivate', this.deactivatePrevNextButtons );
};

proto.deactivatePrevNextButtons = function() {
  this.prevButton.element.remove();
  this.nextButton.element.remove();
  this.prevButton.element.removeEventListener( 'click', this.handlePrevButtonClick );
  this.nextButton.element.removeEventListener( 'click', this.handleNextButtonClick );
  this.off( 'deactivate', this.deactivatePrevNextButtons );
};

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

Flickity.PrevNextButton = PrevNextButton;

return Flickity;

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

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

// -------------------------- PageDots -------------------------- //

function PageDots() {
  // create holder element
  this.holder = document.createElement('div');
  this.holder.className = 'flickity-page-dots';
  // create dots, array of elements
  this.dots = [];
}

PageDots.prototype.setDots = function( slidesLength ) {
  // get difference between number of slides and number of dots
  let delta = slidesLength - this.dots.length;
  if ( delta > 0 ) {
    this.addDots( delta );
  } else if ( delta < 0 ) {
    this.removeDots( -delta );
  }
};

PageDots.prototype.addDots = function( count ) {
  let newDots = new Array( count ).fill()
    .map( ( item, i ) => {
      let dot = document.createElement('button');
      dot.setAttribute( 'type', 'button' );
      let num = i + 1 + this.dots.length;
      dot.className = 'flickity-page-dot';
      dot.textContent = `View slide ${num}`;
      return dot;
    } );

  this.holder.append( ...newDots );
  this.dots = this.dots.concat( newDots );
};

PageDots.prototype.removeDots = function( count ) {
  // remove from this.dots collection
  let removeDots = this.dots.splice( this.dots.length - count, count );
  // remove from DOM
  removeDots.forEach( ( dot ) => dot.remove() );
};

PageDots.prototype.updateSelected = function( index ) {
  // remove selected class on previous
  if ( this.selectedDot ) {
    this.selectedDot.classList.remove('is-selected');
    this.selectedDot.removeAttribute('aria-current');
  }
  // don't proceed if no dots
  if ( !this.dots.length ) return;

  this.selectedDot = this.dots[ index ];
  this.selectedDot.classList.add('is-selected');
  this.selectedDot.setAttribute( 'aria-current', 'step' );
};

Flickity.PageDots = PageDots;

// -------------------------- Flickity -------------------------- //

Object.assign( Flickity.defaults, {
  pageDots: true,
} );

Flickity.create.pageDots = function() {
  if ( !this.options.pageDots ) return;

  this.pageDots = new PageDots();
  this.handlePageDotsClick = this.onPageDotsClick.bind( this );
  // events
  this.on( 'activate', this.activatePageDots );
  this.on( 'select', this.updateSelectedPageDots );
  this.on( 'cellChange', this.updatePageDots );
  this.on( 'resize', this.updatePageDots );
  this.on( 'deactivate', this.deactivatePageDots );
};

let proto = Flickity.prototype;

proto.activatePageDots = function() {
  this.pageDots.setDots( this.slides.length );
  this.focusableElems.push( ...this.pageDots.dots );
  this.pageDots.holder.addEventListener( 'click', this.handlePageDotsClick );
  this.element.append( this.pageDots.holder );
};

proto.onPageDotsClick = function( event ) {
  let index = this.pageDots.dots.indexOf( event.target );
  if ( index === -1 ) return; // only dot clicks

  this.uiChange();
  this.select( index );
};

proto.updateSelectedPageDots = function() {
  this.pageDots.updateSelected( this.selectedIndex );
};

proto.updatePageDots = function() {
  this.pageDots.dots.forEach( ( dot ) => {
    utils.removeFrom( this.focusableElems, dot );
  } );
  this.pageDots.setDots( this.slides.length );
  this.focusableElems.push( ...this.pageDots.dots );
};

proto.deactivatePageDots = function() {
  this.pageDots.holder.remove();
  this.pageDots.holder.removeEventListener( 'click', this.handlePageDotsClick );
};

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

Flickity.PageDots = PageDots;

return Flickity;

} ) );
// player & autoPlay
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory( require('./core') );
  } else {
    // browser global
    factory( window.Flickity );
  }

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

// -------------------------- Player -------------------------- //

function Player( autoPlay, onTick ) {
  this.autoPlay = autoPlay;
  this.onTick = onTick;
  this.state = 'stopped';
  // visibility change event handler
  this.onVisibilityChange = this.visibilityChange.bind( this );
  this.onVisibilityPlay = this.visibilityPlay.bind( this );
}

// start play
Player.prototype.play = function() {
  if ( this.state === 'playing' ) return;

  // do not play if page is hidden, start playing when page is visible
  let isPageHidden = document.hidden;
  if ( isPageHidden ) {
    document.addEventListener( 'visibilitychange', this.onVisibilityPlay );
    return;
  }

  this.state = 'playing';
  // listen to visibility change
  document.addEventListener( 'visibilitychange', this.onVisibilityChange );
  // start ticking
  this.tick();
};

Player.prototype.tick = function() {
  // do not tick if not playing
  if ( this.state !== 'playing' ) return;

  // default to 3 seconds
  let time = typeof this.autoPlay == 'number' ? this.autoPlay : 3000;
  // HACK: reset ticks if stopped and started within interval
  this.clear();
  this.timeout = setTimeout( () => {
    this.onTick();
    this.tick();
  }, time );
};

Player.prototype.stop = function() {
  this.state = 'stopped';
  this.clear();
  // remove visibility change event
  document.removeEventListener( 'visibilitychange', this.onVisibilityChange );
};

Player.prototype.clear = function() {
  clearTimeout( this.timeout );
};

Player.prototype.pause = function() {
  if ( this.state === 'playing' ) {
    this.state = 'paused';
    this.clear();
  }
};

Player.prototype.unpause = function() {
  // re-start play if paused
  if ( this.state === 'paused' ) this.play();
};

// pause if page visibility is hidden, unpause if visible
Player.prototype.visibilityChange = function() {
  let isPageHidden = document.hidden;
  this[ isPageHidden ? 'pause' : 'unpause' ]();
};

Player.prototype.visibilityPlay = function() {
  this.play();
  document.removeEventListener( 'visibilitychange', this.onVisibilityPlay );
};

// -------------------------- Flickity -------------------------- //

Object.assign( Flickity.defaults, {
  pauseAutoPlayOnHover: true,
} );

Flickity.create.player = function() {
  this.player = new Player( this.options.autoPlay, () => {
    this.next( true );
  } );

  this.on( 'activate', this.activatePlayer );
  this.on( 'uiChange', this.stopPlayer );
  this.on( 'pointerDown', this.stopPlayer );
  this.on( 'deactivate', this.deactivatePlayer );
};

let proto = Flickity.prototype;

proto.activatePlayer = function() {
  if ( !this.options.autoPlay ) return;

  this.player.play();
  this.element.addEventListener( 'mouseenter', this );
};

// Player API, don't hate the ... thanks I know where the door is

proto.playPlayer = function() {
  this.player.play();
};

proto.stopPlayer = function() {
  this.player.stop();
};

proto.pausePlayer = function() {
  this.player.pause();
};

proto.unpausePlayer = function() {
  this.player.unpause();
};

proto.deactivatePlayer = function() {
  this.player.stop();
  this.element.removeEventListener( 'mouseenter', this );
};

// ----- mouseenter/leave ----- //

// pause auto-play on hover
proto.onmouseenter = function() {
  if ( !this.options.pauseAutoPlayOnHover ) return;

  this.player.pause();
  this.element.addEventListener( 'mouseleave', this );
};

// resume auto-play on hover off
proto.onmouseleave = function() {
  this.player.unpause();
  this.element.removeEventListener( 'mouseleave', this );
};

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

Flickity.Player = Player;

return Flickity;

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

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

// append cells to a document fragment
function getCellsFragment( cells ) {
  let fragment = document.createDocumentFragment();
  cells.forEach( ( cell ) => fragment.appendChild( cell.element ) );
  return fragment;
}

// -------------------------- add/remove cell prototype -------------------------- //

let proto = Flickity.prototype;

/**
 * Insert, prepend, or append cells
 * @param {[Element, Array, NodeList]} elems - Elements to insert
 * @param {Integer} index - Zero-based number to insert
 */
proto.insert = function( elems, index ) {
  let cells = this._makeCells( elems );
  if ( !cells || !cells.length ) return;

  let len = this.cells.length;
  // default to append
  index = index === undefined ? len : index;
  // add cells with document fragment
  let fragment = getCellsFragment( cells );
  // append to slider
  let isAppend = index === len;
  if ( isAppend ) {
    this.slider.appendChild( fragment );
  } else {
    let insertCellElement = this.cells[ index ].element;
    this.slider.insertBefore( fragment, insertCellElement );
  }
  // add to this.cells
  if ( index === 0 ) {
    // prepend, add to start
    this.cells = cells.concat( this.cells );
  } else if ( isAppend ) {
    // append, add to end
    this.cells = this.cells.concat( cells );
  } else {
    // insert in this.cells
    let endCells = this.cells.splice( index, len - index );
    this.cells = this.cells.concat( cells ).concat( endCells );
  }

  this._sizeCells( cells );
  this.cellChange( index );
  this.positionSliderAtSelected();
};

proto.append = function( elems ) {
  this.insert( elems, this.cells.length );
};

proto.prepend = function( elems ) {
  this.insert( elems, 0 );
};

/**
 * Remove cells
 * @param {[Element, Array, NodeList]} elems - ELements to remove
 */
proto.remove = function( elems ) {
  let cells = this.getCells( elems );
  if ( !cells || !cells.length ) return;

  let minCellIndex = this.cells.length - 1;
  // remove cells from collection & DOM
  cells.forEach( ( cell ) => {
    cell.remove();
    let index = this.cells.indexOf( cell );
    minCellIndex = Math.min( index, minCellIndex );
    utils.removeFrom( this.cells, cell );
  } );

  this.cellChange( minCellIndex );
  this.positionSliderAtSelected();
};

/**
 * logic to be run after a cell's size changes
 * @param {Element} elem - cell's element
 */
proto.cellSizeChange = function( elem ) {
  let cell = this.getCell( elem );
  if ( !cell ) return;

  cell.getSize();

  let index = this.cells.indexOf( cell );
  this.cellChange( index );
  // do not position slider after lazy load
};

/**
 * logic any time a cell is changed: added, removed, or size changed
 * @param {Integer} changedCellIndex - index of the changed cell, optional
 */
proto.cellChange = function( changedCellIndex ) {
  let prevSelectedElem = this.selectedElement;
  this._positionCells( changedCellIndex );
  this._updateWrapShiftCells();
  this.setGallerySize();
  // update selectedIndex, try to maintain position & select previous selected element
  let cell = this.getCell( prevSelectedElem );
  if ( cell ) this.selectedIndex = this.getCellSlideIndex( cell );
  this.selectedIndex = Math.min( this.slides.length - 1, this.selectedIndex );

  this.emitEvent( 'cellChange', [ changedCellIndex ] );
  // position slider
  this.select( this.selectedIndex );
};

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

return Flickity;

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

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

const lazyAttr = 'data-flickity-lazyload';
const lazySrcAttr = `${lazyAttr}-src`;
const lazySrcsetAttr = `${lazyAttr}-srcset`;
const imgSelector = `img[${lazyAttr}], img[${lazySrcAttr}], ` +
  `img[${lazySrcsetAttr}], source[${lazySrcsetAttr}]`;

Flickity.create.lazyLoad = function() {
  this.on( 'select', this.lazyLoad );

  this.handleLazyLoadComplete = this.onLazyLoadComplete.bind( this );
};

let proto = Flickity.prototype;

proto.lazyLoad = function() {
  let { lazyLoad } = this.options;
  if ( !lazyLoad ) return;

  // get adjacent cells, use lazyLoad option for adjacent count
  let adjCount = typeof lazyLoad == 'number' ? lazyLoad : 0;
  // lazy load images
  this.getAdjacentCellElements( adjCount )
    .map( getCellLazyImages )
    .flat()
    .forEach( ( img ) => new LazyLoader( img, this.handleLazyLoadComplete ) );
};

function getCellLazyImages( cellElem ) {
  // check if cell element is lazy image
  if ( cellElem.matches('img') ) {
    let cellAttr = cellElem.getAttribute( lazyAttr );
    let cellSrcAttr = cellElem.getAttribute( lazySrcAttr );
    let cellSrcsetAttr = cellElem.getAttribute( lazySrcsetAttr );
    if ( cellAttr || cellSrcAttr || cellSrcsetAttr ) {
      return cellElem;
    }
  }
  // select lazy images in cell
  return [ ...cellElem.querySelectorAll( imgSelector ) ];
}

proto.onLazyLoadComplete = function( img, event ) {
  let cell = this.getParentCell( img );
  let cellElem = cell && cell.element;
  this.cellSizeChange( cellElem );

  this.dispatchEvent( 'lazyLoad', event, cellElem );
};

// -------------------------- LazyLoader -------------------------- //

/**
 * class to handle loading images
 * @param {Image} img - Image element
 * @param {Function} onComplete - callback function
 */
function LazyLoader( img, onComplete ) {
  this.img = img;
  this.onComplete = onComplete;
  this.load();
}

LazyLoader.prototype.handleEvent = utils.handleEvent;

LazyLoader.prototype.load = function() {
  this.img.addEventListener( 'load', this );
  this.img.addEventListener( 'error', this );
  // get src & srcset
  let src = this.img.getAttribute( lazyAttr ) ||
    this.img.getAttribute( lazySrcAttr );
  let srcset = this.img.getAttribute( lazySrcsetAttr );
  // set src & serset
  this.img.src = src;
  if ( srcset ) this.img.setAttribute( 'srcset', srcset );
  // remove attr
  this.img.removeAttribute( lazyAttr );
  this.img.removeAttribute( lazySrcAttr );
  this.img.removeAttribute( lazySrcsetAttr );
};

LazyLoader.prototype.onload = function( event ) {
  this.complete( event, 'flickity-lazyloaded' );
};

LazyLoader.prototype.onerror = function( event ) {
  this.complete( event, 'flickity-lazyerror' );
};

LazyLoader.prototype.complete = function( event, className ) {
  // unbind events
  this.img.removeEventListener( 'load', this );
  this.img.removeEventListener( 'error', this );
  let mediaElem = this.img.parentNode.matches('picture') ? this.img.parentNode : this.img;
  mediaElem.classList.add( className );

  this.onComplete( this.img, event );
};

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

Flickity.LazyLoader = LazyLoader;

return Flickity;

} ) );
// imagesloaded
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        require('./core'),
        require('imagesloaded'),
    );
  } else {
    // browser global
    factory(
        window.Flickity,
        window.imagesLoaded,
    );
  }

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

Flickity.create.imagesLoaded = function() {
  this.on( 'activate', this.imagesLoaded );
};

Flickity.prototype.imagesLoaded = function() {
  if ( !this.options.imagesLoaded ) return;

  let onImagesLoadedProgress = ( instance, image ) => {
    let cell = this.getParentCell( image.img );
    this.cellSizeChange( cell && cell.element );
    if ( !this.options.freeScroll ) this.positionSliderAtSelected();
  };
  imagesLoaded( this.slider ).on( 'progress', onImagesLoadedProgress );
};

return Flickity;

} ) );


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

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

// append cells to a document fragment
function getCellsFragment( cells ) {
  let fragment = document.createDocumentFragment();
  cells.forEach( ( cell ) => fragment.appendChild( cell.element ) );
  return fragment;
}

// -------------------------- add/remove cell prototype -------------------------- //

let proto = Flickity.prototype;

/**
 * Insert, prepend, or append cells
 * @param {[Element, Array, NodeList]} elems - Elements to insert
 * @param {Integer} index - Zero-based number to insert
 */
proto.insert = function( elems, index ) {
  let cells = this._makeCells( elems );
  if ( !cells || !cells.length ) return;

  let len = this.cells.length;
  // default to append
  index = index === undefined ? len : index;
  // add cells with document fragment
  let fragment = getCellsFragment( cells );
  // append to slider
  let isAppend = index === len;
  if ( isAppend ) {
    this.slider.appendChild( fragment );
  } else {
    let insertCellElement = this.cells[ index ].element;
    this.slider.insertBefore( fragment, insertCellElement );
  }
  // add to this.cells
  if ( index === 0 ) {
    // prepend, add to start
    this.cells = cells.concat( this.cells );
  } else if ( isAppend ) {
    // append, add to end
    this.cells = this.cells.concat( cells );
  } else {
    // insert in this.cells
    let endCells = this.cells.splice( index, len - index );
    this.cells = this.cells.concat( cells ).concat( endCells );
  }

  this._sizeCells( cells );
  this.cellChange( index );
  this.positionSliderAtSelected();
};

proto.append = function( elems ) {
  this.insert( elems, this.cells.length );
};

proto.prepend = function( elems ) {
  this.insert( elems, 0 );
};

/**
 * Remove cells
 * @param {[Element, Array, NodeList]} elems - ELements to remove
 */
proto.remove = function( elems ) {
  let cells = this.getCells( elems );
  if ( !cells || !cells.length ) return;

  let minCellIndex = this.cells.length - 1;
  // remove cells from collection & DOM
  cells.forEach( ( cell ) => {
    cell.remove();
    let index = this.cells.indexOf( cell );
    minCellIndex = Math.min( index, minCellIndex );
    utils.removeFrom( this.cells, cell );
  } );

  this.cellChange( minCellIndex );
  this.positionSliderAtSelected();
};

/**
 * logic to be run after a cell's size changes
 * @param {Element} elem - cell's element
 */
proto.cellSizeChange = function( elem ) {
  let cell = this.getCell( elem );
  if ( !cell ) return;

  cell.getSize();

  let index = this.cells.indexOf( cell );
  this.cellChange( index );
  // do not position slider after lazy load
};

/**
 * logic any time a cell is changed: added, removed, or size changed
 * @param {Integer} changedCellIndex - index of the changed cell, optional
 */
proto.cellChange = function( changedCellIndex ) {
  let prevSelectedElem = this.selectedElement;
  this._positionCells( changedCellIndex );
  this._updateWrapShiftCells();
  this.setGallerySize();
  // update selectedIndex, try to maintain position & select previous selected element
  let cell = this.getCell( prevSelectedElem );
  if ( cell ) this.selectedIndex = this.getCellSlideIndex( cell );
  this.selectedIndex = Math.min( this.slides.length - 1, this.selectedIndex );

  this.emitEvent( 'cellChange', [ changedCellIndex ] );
  // position slider
  this.select( this.selectedIndex );
};

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

return Flickity;

} ) );


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

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

// -------------------------- animate -------------------------- //

let proto = {};

proto.startAnimation = function() {
  if ( this.isAnimating ) return;

  this.isAnimating = true;
  this.restingFrames = 0;
  this.animate();
};

proto.animate = function() {
  this.applyDragForce();
  this.applySelectedAttraction();

  let previousX = this.x;

  this.integratePhysics();
  this.positionSlider();
  this.settle( previousX );
  // animate next frame
  if ( this.isAnimating ) requestAnimationFrame( () => this.animate() );
};

proto.positionSlider = function() {
  let x = this.x;
  // wrap position around
  if ( this.isWrapping ) {
    x = utils.modulo( x, this.slideableWidth ) - this.slideableWidth;
    this.shiftWrapCells( x );
  }

  this.setTranslateX( x, this.isAnimating );
  this.dispatchScrollEvent();
};

proto.setTranslateX = function( x, is3d ) {
  x += this.cursorPosition;
  // reverse if right-to-left and using transform
  if ( this.options.rightToLeft ) x = -x;
  let translateX = this.getPositionValue( x );
  // use 3D transforms for hardware acceleration on iOS
  // but use 2D when settled, for better font-rendering
  this.slider.style.transform = is3d ?
    `translate3d(${translateX},0,0)` : `translateX(${translateX})`;
};

proto.dispatchScrollEvent = function() {
  let firstSlide = this.slides[0];
  if ( !firstSlide ) return;

  let positionX = -this.x - firstSlide.target;
  let progress = positionX / this.slidesWidth;
  this.dispatchEvent( 'scroll', null, [ progress, positionX ] );
};

proto.positionSliderAtSelected = function() {
  if ( !this.cells.length ) return;

  this.x = -this.selectedSlide.target;
  this.velocity = 0; // stop wobble
  this.positionSlider();
};

proto.getPositionValue = function( position ) {
  if ( this.options.percentPosition ) {
    // percent position, round to 2 digits, like 12.34%
    return ( Math.round( ( position / this.size.innerWidth ) * 10000 ) * 0.01 ) + '%';
  } else {
    // pixel positioning
    return Math.round( position ) + 'px';
  }
};

proto.settle = function( previousX ) {
  // keep track of frames where x hasn't moved
  let isResting = !this.isPointerDown &&
      Math.round( this.x * 100 ) === Math.round( previousX * 100 );
  if ( isResting ) this.restingFrames++;
  // stop animating if resting for 3 or more frames
  if ( this.restingFrames > 2 ) {
    this.isAnimating = false;
    delete this.isFreeScrolling;
    // render position with translateX when settled
    this.positionSlider();
    this.dispatchEvent( 'settle', null, [ this.selectedIndex ] );
  }
};

proto.shiftWrapCells = function( x ) {
  // shift before cells
  let beforeGap = this.cursorPosition + x;
  this._shiftCells( this.beforeShiftCells, beforeGap, -1 );
  // shift after cells
  let afterGap = this.size.innerWidth - ( x + this.slideableWidth + this.cursorPosition );
  this._shiftCells( this.afterShiftCells, afterGap, 1 );
};

proto._shiftCells = function( cells, gap, shift ) {
  cells.forEach( ( cell ) => {
    let cellShift = gap > 0 ? shift : 0;
    this._wrapShiftCell( cell, cellShift );
    gap -= cell.size.outerWidth;
  } );
};

proto._unshiftCells = function( cells ) {
  if ( !cells || !cells.length ) return;

  cells.forEach( ( cell ) => this._wrapShiftCell( cell, 0 ) );
};

// @param {Integer} shift - 0, 1, or -1
proto._wrapShiftCell = function( cell, shift ) {
  this._renderCellPosition( cell, cell.x + this.slideableWidth * shift );
};

// -------------------------- physics -------------------------- //

proto.integratePhysics = function() {
  this.x += this.velocity;
  this.velocity *= this.getFrictionFactor();
};

proto.applyForce = function( force ) {
  this.velocity += force;
};

proto.getFrictionFactor = function() {
  return 1 - this.options[ this.isFreeScrolling ? 'freeScrollFriction' : 'friction' ];
};

proto.getRestingPosition = function() {
  // my thanks to Steven Wittens, who simplified this math greatly
  return this.x + this.velocity / ( 1 - this.getFrictionFactor() );
};

proto.applyDragForce = function() {
  if ( !this.isDraggable || !this.isPointerDown ) return;

  // change the position to drag position by applying force
  let dragVelocity = this.dragX - this.x;
  let dragForce = dragVelocity - this.velocity;
  this.applyForce( dragForce );
};

proto.applySelectedAttraction = function() {
  // do not attract if pointer down or no slides
  let dragDown = this.isDraggable && this.isPointerDown;
  if ( dragDown || this.isFreeScrolling || !this.slides.length ) return;

  let distance = this.selectedSlide.target * -1 - this.x;
  let force = distance * this.options.selectedAttraction;
  this.applyForce( force );
};

return proto;

} ) );


================================================
FILE: js/cell.js
================================================
// Flickity.Cell
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory( require('get-size') );
  } else {
    // browser global
    window.Flickity = window.Flickity || {};
    window.Flickity.Cell = factory( window.getSize );
  }

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

const cellClassName = 'flickity-cell';

function Cell( elem ) {
  this.element = elem;
  this.element.classList.add( cellClassName );

  this.x = 0;
  this.unselect();
}

let proto = Cell.prototype;

proto.destroy = function() {
  // reset style
  this.unselect();
  this.element.classList.remove( cellClassName );
  this.element.style.transform = '';
  this.element.removeAttribute('aria-hidden');
};

proto.getSize = function() {
  this.size = getSize( this.element );
};

proto.select = function() {
  this.element.classList.add('is-selected');
  this.element.removeAttribute('aria-hidden');
};

proto.unselect = function() {
  this.element.classList.remove('is-selected');
  this.element.setAttribute( 'aria-hidden', 'true' );
};

proto.remove = function() {
  this.element.remove();
};

return Cell;

} ) );


================================================
FILE: js/core.js
================================================
// Flickity main
/* eslint-disable max-params */
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory(
        window,
        require('ev-emitter'),
        require('get-size'),
        require('fizzy-ui-utils'),
        require('./cell'),
        require('./slide'),
        require('./animate'),
    );
  } else {
    // browser global
    let _Flickity = window.Flickity;

    window.Flickity = factory(
        window,
        window.EvEmitter,
        window.getSize,
        window.fizzyUIUtils,
        _Flickity.Cell,
        _Flickity.Slide,
        _Flickity.animatePrototype,
    );
  }

}( typeof window != 'undefined' ? window : this,
    function factory( window, EvEmitter, getSize, utils, Cell, Slide, animatePrototype ) {
/* eslint-enable max-params */

// vars
const { getComputedStyle, console } = window;
let { jQuery } = window;

// -------------------------- Flickity -------------------------- //

// globally unique identifiers
let GUID = 0;
// internal store of all Flickity intances
let instances = {};

function Flickity( element, options ) {
  let queryElement = utils.getQueryElement( element );
  if ( !queryElement ) {
    if ( console ) console.error(`Bad element for Flickity: ${queryElement || element}`);
    return;
  }
  this.element = queryElement;
  // do not initialize twice on same element
  if ( this.element.flickityGUID ) {
    let instance = instances[ this.element.flickityGUID ];
    if ( instance ) instance.option( options );
    return instance;
  }

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

  // kick things off
  this._create();
}

Flickity.defaults = {
  accessibility: true,
  // adaptiveHeight: false,
  cellAlign: 'center',
  // cellSelector: undefined,
  // contain: false,
  freeScrollFriction: 0.075, // friction when free-scrolling
  friction: 0.28, // friction when selecting
  namespaceJQueryEvents: true,
  // initialIndex: 0,
  percentPosition: true,
  resize: true,
  selectedAttraction: 0.025,
  setGallerySize: true,
  // watchCSS: false,
  // wrapAround: false
};

// hash of methods triggered on _create()
Flickity.create = {};

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

proto._create = function() {
  let { resize, watchCSS, rightToLeft } = this.options;
  // add id for Flickity.data
  let id = this.guid = ++GUID;
  this.element.flickityGUID = id; // expando
  instances[ id ] = this; // associate via id
  // initial properties
  this.selectedIndex = 0;
  // how many frames slider has been in same position
  this.restingFrames = 0;
  // initial physics properties
  this.x = 0;
  this.velocity = 0;
  this.beginMargin = rightToLeft ? 'marginRight' : 'marginLeft';
  this.endMargin = rightToLeft ? 'marginLeft' : 'marginRight';
  // create viewport & slider
  this.viewport = document.createElement('div');
  this.viewport.className = 'flickity-viewport';
  this._createSlider();
  // used for keyboard navigation
  this.focusableElems = [ this.element ];

  if ( resize || watchCSS ) {
    window.addEventListener( 'resize', this );
  }

  // add listeners from on option
  for ( let eventName in this.options.on ) {
    let listener = this.options.on[ eventName ];
    this.on( eventName, listener );
  }

  for ( let method in Flickity.create ) {
    Flickity.create[ method ].call( this );
  }

  if ( watchCSS ) {
    this.watchCSS();
  } else {
    this.activate();
  }
};

/**
 * set options
 * @param {Object} opts - options to extend
 */
proto.option = function( opts ) {
  Object.assign( this.options, opts );
};

proto.activate = function() {
  if ( this.isActive ) return;

  this.isActive = true;
  this.element.classList.add('flickity-enabled');
  if ( this.options.rightToLeft ) {
    this.element.classList.add('flickity-rtl');
  }

  this.getSize();
  // move initial cell elements so they can be loaded as cells
  let cellElems = this._filterFindCellElements( this.element.children );
  this.slider.append( ...cellElems );
  this.viewport.append( this.slider );
  this.element.append( this.viewport );
  // get cells from children
  this.reloadCells();

  if ( this.options.accessibility ) {
    // allow element to focusable
    this.element.tabIndex = 0;
    // listen for key presses
    this.element.addEventListener( 'keydown', this );
  }

  this.emitEvent('activate');
  this.selectInitialIndex();
  // flag for initial activation, for using initialIndex
  this.isInitActivated = true;
  // ready event. #493
  this.dispatchEvent('ready');
};

// slider positions the cells
proto._createSlider = function() {
  // slider element does all the positioning
  let slider = document.createElement('div');
  slider.className = 'flickity-slider';
  this.slider = slider;
};

proto._filterFindCellElements = function( elems ) {
  return utils.filterFindElements( elems, this.options.cellSelector );
};

// goes through all children
proto.reloadCells = function() {
  // collection of item elements
  this.cells = this._makeCells( this.slider.children );
  this.positionCells();
  this._updateWrapShiftCells();
  this.setGallerySize();
};

/**
 * turn elements into Flickity.Cells
 * @param {[Array, NodeList, HTMLElement]} elems - elements to make into cells
 * @returns {Array} items - collection of new Flickity Cells
 */
proto._makeCells = function( elems ) {
  let cellElems = this._filterFindCellElements( elems );

  // create new Cells for collection
  return cellElems.map( ( cellElem ) => new Cell( cellElem ) );
};

proto.getLastCell = function() {
  return this.cells[ this.cells.length - 1 ];
};

proto.getLastSlide = function() {
  return this.slides[ this.slides.length - 1 ];
};

// positions all cells
proto.positionCells = function() {
  // size all cells
  this._sizeCells( this.cells );
  // position all cells
  this._positionCells( 0 );
};

/**
 * position certain cells
 * @param {Integer} index - which cell to start with
 */
proto._positionCells = function( index ) {
  index = index || 0;
  // also measure maxCellHeight
  // start 0 if positioning all cells
  this.maxCellHeight = index ? this.maxCellHeight || 0 : 0;
  let cellX = 0;
  // get cellX
  if ( index > 0 ) {
    let startCell = this.cells[ index - 1 ];
    cellX = startCell.x + startCell.size.outerWidth;
  }

  this.cells.slice( index ).forEach( ( cell ) => {
    cell.x = cellX;
    this._renderCellPosition( cell, cellX );
    cellX += cell.size.outerWidth;
    this.maxCellHeight = Math.max( cell.size.outerHeight, this.maxCellHeight );
  } );
  // keep track of cellX for wrap-around
  this.slideableWidth = cellX;
  // slides
  this.updateSlides();
  // contain slides target
  this._containSlides();
  // update slidesWidth
  this.slidesWidth = this.cells.length ?
    this.getLastSlide().target - this.slides[0].target : 0;
};

proto._renderCellPosition = function( cell, x ) {
  // render position of cell with in slider
  let sideOffset = this.options.rightToLeft ? -1 : 1;
  let renderX = x * sideOffset;
  if ( this.options.percentPosition ) renderX *= this.size.innerWidth / cell.size.width;
  let positionValue = this.getPositionValue( renderX );
  cell.element.style.transform = `translateX( ${positionValue} )`;
};

/**
 * cell.getSize() on multiple cells
 * @param {Array} cells - cells to size
 */
proto._sizeCells = function( cells ) {
  cells.forEach( ( cell ) => cell.getSize() );
};

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

proto.updateSlides = function() {
  this.slides = [];
  if ( !this.cells.length ) return;

  let { beginMargin, endMargin } = this;
  let slide = new Slide( beginMargin, endMargin, this.cellAlign );
  this.slides.push( slide );

  let canCellFit = this._getCanCellFit();

  this.cells.forEach( ( cell, i ) => {
    // just add cell if first cell in slide
    if ( !slide.cells.length ) {
      slide.addCell( cell );
      return;
    }

    let slideWidth = ( slide.outerWidth - slide.firstMargin ) +
      ( cell.size.outerWidth - cell.size[ endMargin ] );

    if ( canCellFit( i, slideWidth ) ) {
      slide.addCell( cell );
    } else {
      // doesn't fit, new slide
      slide.updateTarget();

      slide = new Slide( beginMargin, endMargin, this.cellAlign );
      this.slides.push( slide );
      slide.addCell( cell );
    }
  } );
  // last slide
  slide.updateTarget();
  // update .selectedSlide
  this.updateSelectedSlide();
};

proto._getCanCellFit = function() {
  let { groupCells } = this.options;
  if ( !groupCells ) return () => false;

  if ( typeof groupCells == 'number' ) {
    // group by number. 3 -> [0,1,2], [3,4,5], ...
    let number = parseInt( groupCells, 10 );
    return ( i ) => ( i % number ) !== 0;
  }
  // default, group by width of slide
  let percent = 1;
  // parse '75%
  let percentMatch = typeof groupCells == 'string' && groupCells.match( /^(\d+)%$/ );
  if ( percentMatch ) percent = parseInt( percentMatch[1], 10 ) / 100;
  let groupWidth = ( this.size.innerWidth + 1 ) * percent;
  return ( i, slideWidth ) => slideWidth <= groupWidth;
};

// alias _init for jQuery plugin .flickity()
proto._init =
proto.reposition = function() {
  this.positionCells();
  this.positionSliderAtSelected();
};

proto.getSize = function() {
  this.size = getSize( this.element );
  this.setCellAlign();
  this.cursorPosition = this.size.innerWidth * this.cellAlign;
};

let cellAlignShorthands = {
  left: 0,
  center: 0.5,
  right: 1,
};

proto.setCellAlign = function() {
  let { cellAlign, rightToLeft } = this.options;
  let shorthand = cellAlignShorthands[ cellAlign ];
  this.cellAlign = shorthand !== undefined ? shorthand : cellAlign;
  if ( rightToLeft ) this.cellAlign = 1 - this.cellAlign;
};

proto.setGallerySize = function() {
  if ( !this.options.setGallerySize ) return;

  let height = this.options.adaptiveHeight && this.selectedSlide ?
    this.selectedSlide.height : this.maxCellHeight;
  this.viewport.style.height = `${height}px`;
};

proto._updateWrapShiftCells = function() {
  // update isWrapping
  this.isWrapping = this.getIsWrapping();
  // only for wrap-around
  if ( !this.isWrapping ) return;

  // unshift previous cells
  this._unshiftCells( this.beforeShiftCells );
  this._unshiftCells( this.afterShiftCells );
  // get before cells
  // initial gap
  let beforeGapX = this.cursorPosition;
  let lastIndex = this.cells.length - 1;
  this.beforeShiftCells = this._getGapCells( beforeGapX, lastIndex, -1 );
  // get after cells
  // ending gap between last cell and end of gallery viewport
  let afterGapX = this.size.innerWidth - this.cursorPosition;
  // start cloning at first cell, working forwards
  this.afterShiftCells = this._getGapCells( afterGapX, 0, 1 );
};

proto.getIsWrapping = function() {
  let { wrapAround } = this.options;
  if ( !wrapAround || this.slides.length < 2 ) return false;

  if ( wrapAround !== 'fill' ) return true;
  // check that slides can fit

  let gapWidth = this.slideableWidth - this.size.innerWidth;
  if ( gapWidth > this.size.innerWidth ) return true; // gap * 2x big, all good
  // check that content width - shifting cell is bigger than viewport width
  for ( let cell of this.cells ) {
    if ( cell.size.outerWidth > gapWidth ) return false;
  }
  return true;
};

proto._getGapCells = function( gapX, cellIndex, increment ) {
  // keep adding cells until the cover the initial gap
  let cells = [];
  while ( gapX > 0 ) {
    let cell = this.cells[ cellIndex ];
    if ( !cell ) break;

    cells.push( cell );
    cellIndex += increment;
    gapX -= cell.size.outerWidth;
  }
  return cells;
};

// ----- contain & wrap ----- //

// contain cell targets so no excess sliding
proto._containSlides = function() {
  let isContaining = this.options.contain && !this.isWrapping &&
      this.cells.length;
  if ( !isContaining ) return;

  let contentWidth = this.slideableWidth - this.getLastCell().size[ this.endMargin ];
  // content is less than gallery size
  let isContentSmaller = contentWidth < this.size.innerWidth;
  if ( isContentSmaller ) {
    // all cells fit inside gallery
    this.slides.forEach( ( slide ) => {
      slide.target = contentWidth * this.cellAlign;
    } );
  } else {
    // contain to bounds
    let beginBound = this.cursorPosition + this.cells[0].size[ this.beginMargin ];
    let endBound = contentWidth - this.size.innerWidth * ( 1 - this.cellAlign );
    this.slides.forEach( ( slide ) => {
      slide.target = Math.max( slide.target, beginBound );
      slide.target = Math.min( slide.target, endBound );
    } );
  }
};

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

/**
 * emits events via eventEmitter and jQuery events
 * @param {String} type - name of event
 * @param {Event} event - original event
 * @param {Array} args - extra arguments
 */
proto.dispatchEvent = function( type, event, args ) {
  let emitArgs = event ? [ event ].concat( args ) : args;
  this.emitEvent( type, emitArgs );

  if ( jQuery && this.$element ) {
    // default trigger with type if no event
    type += this.options.namespaceJQueryEvents ? '.flickity' : '';
    let $event = type;
    if ( event ) {
      // create jQuery event
      let jQEvent = new jQuery.Event( event );
      jQEvent.type = type;
      $event = jQEvent;
    }
    this.$element.trigger( $event, args );
  }
};

const unidraggerEvents = [
  'dragStart',
  'dragMove',
  'dragEnd',
  'pointerDown',
  'pointerMove',
  'pointerEnd',
  'staticClick',
];

let _emitEvent = proto.emitEvent;
proto.emitEvent = function( eventName, args ) {
  if ( eventName === 'staticClick' ) {
    // add cellElem and cellIndex args to staticClick
    let clickedCell = this.getParentCell( args[0].target );
    let cellElem = clickedCell && clickedCell.element;
    let cellIndex = clickedCell && this.cells.indexOf( clickedCell );
    args = args.concat( cellElem, cellIndex );
  }
  // do regular thing
  _emitEvent.call( this, eventName, args );
  // duck-punch in jQuery events for Unidragger events
  let isUnidraggerEvent = unidraggerEvents.includes( eventName );
  if ( !isUnidraggerEvent || !jQuery || !this.$element ) return;

  eventName += this.options.namespaceJQueryEvents ? '.flickity' : '';
  let event = args.shift( 0 );
  let jQEvent = new jQuery.Event( event );
  jQEvent.type = eventName;
  this.$element.trigger( jQEvent, args );
};

// -------------------------- select -------------------------- //

/**
 * @param {Integer} index - index of the slide
 * @param {Boolean} isWrap - will wrap-around to last/first if at the end
 * @param {Boolean} isInstant - will immediately set position at selected cell
 */
proto.select = function( index, isWrap, isInstant ) {
  if ( !this.isActive ) return;

  index = parseInt( index, 10 );
  this._wrapSelect( index );

  if ( this.isWrapping || isWrap ) {
    index = utils.modulo( index, this.slides.length );
  }
  // bail if invalid index
  if ( !this.slides[ index ] ) return;

  let prevIndex = this.selectedIndex;
  this.selectedIndex = index;
  this.updateSelectedSlide();
  if ( isInstant ) {
    this.positionSliderAtSelected();
  } else {
    this.startAnimation();
  }
  if ( this.options.adaptiveHeight ) {
    this.setGallerySize();
  }
  // events
  this.dispatchEvent( 'select', null, [ index ] );
  // change event if new index
  if ( index !== prevIndex ) {
    this.dispatchEvent( 'change', null, [ index ] );
  }
};

// wraps position for wrapAround, to move to closest slide. #113
proto._wrapSelect = function( index ) {
  if ( !this.isWrapping ) return;

  const { selectedIndex, slideableWidth, slides: { length } } = this;
  // shift index for wrap, do not wrap dragSelect
  if ( !this.isDragSelect ) {
    let wrapIndex = utils.modulo( index, length );
    // go to shortest
    let delta = Math.abs( wrapIndex - selectedIndex );
    let backWrapDelta = Math.abs( ( wrapIndex + length ) - selectedIndex );
    let forewardWrapDelta = Math.abs( ( wrapIndex - length ) - selectedIndex );
    if ( backWrapDelta < delta ) {
      index += length;
    } else if ( forewardWrapDelta < delta ) {
      index -= length;
    }
  }

  // wrap position so slider is within normal area
  if ( index < 0 ) {
    this.x -= slideableWidth;
  } else if ( index >= length ) {
    this.x += slideableWidth;
  }
};

proto.previous = function( isWrap, isInstant ) {
  this.select( this.selectedIndex - 1, isWrap, isInstant );
};

proto.next = function( isWrap, isInstant ) {
  this.select( this.selectedIndex + 1, isWrap, isInstant );
};

proto.updateSelectedSlide = function() {
  let slide = this.slides[ this.selectedIndex ];
  // selectedIndex could be outside of slides, if triggered before resize()
  if ( !slide ) return;

  // unselect previous selected slide
  this.unselectSelectedSlide();
  // update new selected slide
  this.selectedSlide = slide;
  slide.select();
  this.selectedCells = slide.cells;
  this.selectedElements = slide.getCellElements();
  // HACK: selectedCell & selectedElement is first cell in slide, backwards compatibility
  this.selectedCell = slide.cells[0];
  this.selectedElement = this.selectedElements[0];
};

proto.unselectSelectedSlide = function() {
  if ( this.selectedSlide ) this.selectedSlide.unselect();
};

proto.selectInitialIndex = function() {
  let initialIndex = this.options.initialIndex;
  // already activated, select previous selectedIndex
  if ( this.isInitActivated ) {
    this.select( this.selectedIndex, false, true );
    return;
  }
  // select with selector string
  if ( initialIndex && typeof initialIndex == 'string' ) {
    let cell = this.queryCell( initialIndex );
    if ( cell ) {
      this.selectCell( initialIndex, false, true );
      return;
    }
  }

  let index = 0;
  // select with number
  if ( initialIndex && this.slides[ initialIndex ] ) {
    index = initialIndex;
  }
  // select instantly
  this.select( index, false, true );
};

/**
 * select slide from number or cell element
 * @param {[Element, Number]} value - zero-based index or element to select
 * @param {Boolean} isWrap - enables wrapping around for extra index
 * @param {Boolean} isInstant - disables slide animation
 */
proto.selectCell = function( value, isWrap, isInstant ) {
  // get cell
  let cell = this.queryCell( value );
  if ( !cell ) return;

  let index = this.getCellSlideIndex( cell );
  this.select( index, isWrap, isInstant );
};

proto.getCellSlideIndex = function( cell ) {
  // get index of slide that has cell
  let cellSlide = this.slides.find( ( slide ) => slide.cells.includes( cell ) );
  return this.slides.indexOf( cellSlide );
};

// -------------------------- get cells -------------------------- //

/**
 * get Flickity.Cell, given an Element
 * @param {Element} elem - matching cell element
 * @returns {Flickity.Cell} cell - matching cell
 */
proto.getCell = function( elem ) {
  // loop through cells to get the one that matches
  for ( let cell of this.cells ) {
    if ( cell.element === elem ) return cell;
  }
};

/**
 * get collection of Flickity.Cells, given Elements
 * @param {[Element, Array, NodeList]} elems - multiple elements
 * @returns {Array} cells - Flickity.Cells
 */
proto.getCells = function( elems ) {
  elems = utils.makeArray( elems );
  return elems.map( ( elem ) => this.getCell( elem ) ).filter( Boolean );
};

/**
 * get cell elements
 * @returns {Array} cellElems
 */
proto.getCellElements = function() {
  return this.cells.map( ( cell ) => cell.element );
};

/**
 * get parent cell from an element
 * @param {Element} elem - child element
 * @returns {Flickit.Cell} cell - parent cell
 */
proto.getParentCell = function( elem ) {
  // first check if elem is cell
  let cell = this.getCell( elem );
  if ( cell ) return cell;

  // try to get parent cell elem
  let closest = elem.closest('.flickity-slider > *');
  return this.getCell( closest );
};

/**
 * get cells adjacent to a slide
 * @param {Integer} adjCount - number of adjacent slides
 * @param {Integer} index - index of slide to start
 * @returns {Array} cells - array of Flickity.Cells
 */
proto.getAdjacentCellElements = function( adjCount, index ) {
  if ( !adjCount ) return this.selectedSlide.getCellElements();

  index = index === undefined ? this.selectedIndex : index;
  let len = this.slides.length;
  let cellElems = [];
  for ( let i = index - adjCount; i <= index + adjCount; i++ ) {
    let slideIndex = this.isWrapping ? utils.modulo( i, len ) : i;
    let slide = this.slides[ slideIndex ];
    if ( slide ) {
      cellElems = cellElems.concat( slide.getCellElements() );
    }
  }
  return cellElems;
};

/**
 * select slide from number or cell element
 * @param {[Element, String, Number]} selector - element, selector string, or index
 * @returns {Flickity.Cell} - matching cell
 */
proto.queryCell = function( selector ) {
  if ( typeof selector == 'number' ) {
    // use number as index
    return this.cells[ selector ];
  }
  // do not select invalid selectors from hash: #123, #/. #791
  let isSelectorString = typeof selector == 'string' && !selector.match( /^[#.]?[\d/]/ );
  if ( isSelectorString ) {
    // use string as selector, get element
    selector = this.element.querySelector( selector );
  }
  // get cell from element
  return this.getCell( selector );
};

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

proto.uiChange = function() {
  this.emitEvent('uiChange');
};

// ----- resize ----- //

proto.onresize = function() {
  this.watchCSS();
  this.resize();
};

utils.debounceMethod( Flickity, 'onresize', 150 );

proto.resize = function() {
  // #1177 disable resize behavior when animating or dragging for iOS 15
  if ( !this.isActive || this.isAnimating || this.isDragging ) return;
  this.getSize();
  // wrap values
  if ( this.isWrapping ) {
    this.x = utils.modulo( this.x, this.slideableWidth );
  }
  this.positionCells();
  this._updateWrapShiftCells();
  this.setGallerySize();
  this.emitEvent('resize');
  // update selected index for group slides, instant
  // TODO: position can be lost between groups of various numbers
  let selectedElement = this.selectedElements && this.selectedElements[0];
  this.selectCell( selectedElement, false, true );
};

// watches the :after property, activates/deactivates
proto.watchCSS = function() {
  if ( !this.options.watchCSS ) return;

  let afterContent = getComputedStyle( this.element, ':after' ).content;
  // activate if :after { content: 'flickity' }
  if ( afterContent.includes('flickity') ) {
    this.activate();
  } else {
    this.deactivate();
  }
};

// ----- keydown ----- //

// go previous/next if left/right keys pressed
proto.onkeydown = function( event ) {
  let { activeElement } = document;
  let handler = Flickity.keyboardHandlers[ event.key ];
  // only work if element is in focus
  if ( !this.options.accessibility || !activeElement || !handler ) return;

  let isFocused = this.focusableElems.some( ( elem ) => activeElement === elem );
  if ( isFocused ) handler.call( this );
};

Flickity.keyboardHandlers = {
  ArrowLeft: function() {
    this.uiChange();
    let leftMethod = this.options.rightToLeft ? 'next' : 'previous';
    this[ leftMethod ]();
  },
  ArrowRight: function() {
    this.uiChange();
    let rightMethod = this.options.rightToLeft ? 'previous' : 'next';
    this[ rightMethod ]();
  },
};

// ----- focus ----- //

proto.focus = function() {
  this.element.focus({ preventScroll: true });
};

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

// deactivate all Flickity functionality, but keep stuff available
proto.deactivate = function() {
  if ( !this.isActive ) return;

  this.element.classList.remove('flickity-enabled');
  this.element.classList.remove('flickity-rtl');
  this.unselectSelectedSlide();
  // destroy cells
  this.cells.forEach( ( cell ) => cell.destroy() );
  this.viewport.remove();
  // move child elements back into element
  this.element.append( ...this.slider.children );
  if ( this.options.accessibility ) {
    this.element.removeAttribute('tabIndex');
    this.element.removeEventListener( 'keydown', this );
  }
  // set flags
  this.isActive = false;
  this.emitEvent('deactivate');
};

proto.destroy = function() {
  this.deactivate();
  window.removeEventListener( 'resize', this );
  this.allOff();
  this.emitEvent('destroy');
  if ( jQuery && this.$element ) {
    jQuery.removeData( this.element, 'flickity' );
  }
  delete this.element.flickityGUID;
  delete instances[ this.guid ];
};

// -------------------------- prototype -------------------------- //

Object.assign( proto, animatePrototype );

// -------------------------- extras -------------------------- //

/**
 * get Flickity instance from element
 * @param {[Element, String]} elem - element or selector string
 * @returns {Flickity} - Flickity instance
 */
Flickity.data = function( elem ) {
  elem = utils.getQueryElement( elem );
  if ( elem ) return instances[ elem.flickityGUID ];
};

utils.htmlInit( Flickity, 'flickity' );

let { jQueryBridget } = window;
if ( jQuery && jQueryBridget ) {
  jQueryBridget( 'flickity', Flickity, jQuery );
}

// set internal jQuery, for Webpack + jQuery v3, #478
Flickity.setJQuery = function( jq ) {
  jQuery = jq;
};

Flickity.Cell = Cell;
Flickity.Slide = Slide;

return Flickity;

} ) );


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

}( typeof window != 'undefined' ? window : this,
    function factory( window, Flickity, Unidragger, utils ) {

// ----- defaults ----- //

Object.assign( Flickity.defaults, {
  draggable: '>1',
  dragThreshold: 3,
} );

// -------------------------- drag prototype -------------------------- //

let proto = Flickity.prototype;
Object.assign( proto, Unidragger.prototype ); // inherit Unidragger
proto.touchActionValue = '';

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

Flickity.create.drag = function() {
  this.on( 'activate', this.onActivateDrag );
  this.on( 'uiChange', this._uiChangeDrag );
  this.on( 'deactivate', this.onDeactivateDrag );
  this.on( 'cellChange', this.updateDraggable );
  this.on( 'pointerDown', this.handlePointerDown );
  this.on( 'pointerUp', this.handlePointerUp );
  this.on( 'pointerDown', this.handlePointerDone );
  this.on( 'dragStart', this.handleDragStart );
  this.on( 'dragMove', this.handleDragMove );
  this.on( 'dragEnd', this.handleDragEnd );
  this.on( 'staticClick', this.handleStaticClick );
  // TODO updateDraggable on resize? if groupCells & slides change
};

proto.onActivateDrag = function() {
  this.handles = [ this.viewport ];
  this.bindHandles();
  this.updateDraggable();
};

proto.onDeactivateDrag = function() {
  this.unbindHandles();
  this.element.classList.remove('is-draggable');
};

proto.updateDraggable = function() {
  // disable dragging if less than 2 slides. #278
  if ( this.options.draggable === '>1' ) {
    this.isDraggable = this.slides.length > 1;
  } else {
    this.isDraggable = this.options.draggable;
  }
  this.element.classList.toggle( 'is-draggable', this.isDraggable );
};

proto._uiChangeDrag = function() {
  delete this.isFreeScrolling;
};

// -------------------------- pointer events -------------------------- //

proto.handlePointerDown = function( event ) {
  if ( !this.isDraggable ) {
    // proceed for staticClick
    this.bindActivePointerEvents( event );
    return;
  }

  let isTouchStart = event.type === 'touchstart';
  let isTouchPointer = event.pointerType === 'touch';
  let isFocusNode = event.target.matches('input, textarea, select');
  if ( !isTouchStart && !isTouchPointer && !isFocusNode ) event.preventDefault();
  if ( !isFocusNode ) this.focus();
  // blur
  if ( document.activeElement !== this.element ) document.activeElement.blur();
  // stop if it was moving
  this.dragX = this.x;
  this.viewport.classList.add('is-pointer-down');
  // track scrolling
  this.pointerDownScroll = getScrollPosition();
  window.addEventListener( 'scroll', this );
  this.bindActivePointerEvents( event );
};

// ----- move ----- //

proto.hasDragStarted = function( moveVector ) {
  return Math.abs( moveVector.x ) > this.options.dragThreshold;
};

// ----- up ----- //

proto.handlePointerUp = function() {
  delete this.isTouchScrolling;
  this.viewport.classList.remove('is-pointer-down');
};

proto.handlePointerDone = function() {
  window.removeEventListener( 'scroll', this );
  delete this.pointerDownScroll;
};

// -------------------------- dragging -------------------------- //

proto.handleDragStart = function() {
  if ( !this.isDraggable ) return;

  this.dragStartPosition = this.x;
  this.startAnimation();
  window.removeEventListener( 'scroll', this );
};

proto.handleDragMove = function( event, pointer, moveVector ) {
  if ( !this.isDraggable ) return;

  event.preventDefault();

  this.previousDragX = this.dragX;
  // reverse if right-to-left
  let direction = this.options.rightToLeft ? -1 : 1;
  // wrap around move. #589
  if ( this.isWrapping ) moveVector.x %= this.slideableWidth;
  let dragX = this.dragStartPosition + moveVector.x * direction;

  if ( !this.isWrapping ) {
    // slow drag
    let originBound = Math.max( -this.slides[0].target, this.dragStartPosition );
    dragX = dragX > originBound ? ( dragX + originBound ) * 0.5 : dragX;
    let endBound = Math.min( -this.getLastSlide().target, this.dragStartPosition );
    dragX = dragX < endBound ? ( dragX + endBound ) * 0.5 : dragX;
  }

  this.dragX = dragX;
  this.dragMoveTime = new Date();
};

proto.handleDragEnd = function() {
  if ( !this.isDraggable ) return;

  let { freeScroll } = this.options;
  if ( freeScroll ) this.isFreeScrolling = true;
  // set selectedIndex based on where flick will end up
  let index = this.dragEndRestingSelect();

  if ( freeScroll && !this.isWrapping ) {
    // if free-scroll & not wrap around
    // do not free-scroll if going outside of bounding slides
    // so bounding slides can attract slider, and keep it in bounds
    let restingX = this.getRestingPosition();
    this.isFreeScrolling = -restingX > this.slides[0].target &&
      -restingX < this.getLastSlide().target;
  } else if ( !freeScroll && index === this.selectedIndex ) {
    // boost selection if selected index has not changed
    index += this.dragEndBoostSelect();
  }
  delete this.previousDragX;
  // apply selection
  // HACK, set flag so dragging stays in correct direction
  this.isDragSelect = this.isWrapping;
  this.select( index );
  delete this.isDragSelect;
};

proto.dragEndRestingSelect = function() {
  let restingX = this.getRestingPosition();
  // how far away from selected slide
  let distance = Math.abs( this.getSlideDistance( -restingX, this.selectedIndex ) );
  // get closet resting going up and going down
  let positiveResting = this._getClosestResting( restingX, distance, 1 );
  let negativeResting = this._getClosestResting( restingX, distance, -1 );
  // use closer resting for wrap-around
  return positiveResting.distance < negativeResting.distance ?
    positiveResting.index : negativeResting.index;
};

/**
 * given resting X and distance to selected cell
 * get the distance and index of the closest cell
 * @param {Number} restingX - estimated post-flick resting position
 * @param {Number} distance - distance to selected cell
 * @param {Integer} increment - +1 or -1, going up or down
 * @returns {Object} - { distance: {Number}, index: {Integer} }
 */
proto._getClosestResting = function( restingX, distance, increment ) {
  let index = this.selectedIndex;
  let minDistance = Infinity;
  let condition = this.options.contain && !this.isWrapping ?
    // if containing, keep going if distance is equal to minDistance
    ( dist, minDist ) => dist <= minDist :
    ( dist, minDist ) => dist < minDist;

  while ( condition( distance, minDistance ) ) {
    // measure distance to next cell
    index += increment;
    minDistance = distance;
    distance = this.getSlideDistance( -restingX, index );
    if ( distance === null ) break;

    distance = Math.abs( distance );
  }
  return {
    distance: minDistance,
    // selected was previous index
    index: index - increment,
  };
};

/**
 * measure distance between x and a slide target
 * @param {Number} x - horizontal position
 * @param {Integer} index - slide index
 * @returns {Number} - slide distance
 */
proto.getSlideDistance = function( x, index ) {
  let len = this.slides.length;
  // wrap around if at least 2 slides
  let isWrapAround = this.options.wrapAround && len > 1;
  let slideIndex = isWrapAround ? utils.modulo( index, len ) : index;
  let slide = this.slides[ slideIndex ];
  if ( !slide ) return null;

  // add distance for wrap-around slides
  let wrap = isWrapAround ? this.slideableWidth * Math.floor( index/len ) : 0;
  return x - ( slide.target + wrap );
};

proto.dragEndBoostSelect = function() {
  // do not boost if no previousDragX or dragMoveTime
  if ( this.previousDragX === undefined || !this.dragMoveTime ||
    // or if drag was held for 100 ms
    new Date() - this.dragMoveTime > 100 ) {
    return 0;
  }

  let distance = this.getSlideDistance( -this.dragX, this.selectedIndex );
  let delta = this.previousDragX - this.dragX;
  if ( distance > 0 && delta > 0 ) {
    // boost to next if moving towards the right, and positive velocity
    return 1;
  } else if ( distance < 0 && delta < 0 ) {
    // boost to previous if moving towards the left, and negative velocity
    return -1;
  }
  return 0;
};

// ----- scroll ----- //

proto.onscroll = function() {
  let scroll = getScrollPosition();
  let scrollMoveX = this.pointerDownScroll.x - scroll.x;
  let scrollMoveY = this.pointerDownScroll.y - scroll.y;
  // cancel click/tap if scroll is too much
  if ( Math.abs( scrollMoveX ) > 3 || Math.abs( scrollMoveY ) > 3 ) {
    this.pointerDone();
  }
};

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

function getScrollPosition() {
  return {
    x: window.pageXOffset,
    y: window.pageYOffset,
  };
}

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

return Flickity;

} ) );


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

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

Flickity.create.imagesLoaded = function() {
  this.on( 'activate', this.imagesLoaded );
};

Flickity.prototype.imagesLoaded = function() {
  if ( !this.options.imagesLoaded ) return;

  let onImagesLoadedProgress = ( instance, image ) => {
    let cell = this.getParentCell( image.img );
    this.cellSizeChange( cell && cell.element );
    if ( !this.options.freeScroll ) this.positionSliderAtSelected();
  };
  imagesLoaded( this.slider ).on( 'progress', onImagesLoadedProgress );
};

return Flickity;

} ) );


================================================
FILE: js/index.js
================================================
/*!
 * Flickity v3.0.0
 * Touch, responsive, flickable carousels
 *
 * Licensed GPLv3 for open source use
 * or Flickity Commercial License for commercial use
 *
 * https://flickity.metafizzy.co
 * Copyright 2015-2022 Metafizzy
 */

if ( typeof module == 'object' && module.exports ) {
  const Flickity = require('./core');
  require('./drag');
  require('./prev-next-button');
  require('./page-dots');
  require('./player');
  require('./add-remove-cell');
  require('./lazyload');
  require('./imagesloaded');

  module.exports = Flickity;
}


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

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

const lazyAttr = 'data-flickity-lazyload';
const lazySrcAttr = `${lazyAttr}-src`;
const lazySrcsetAttr = `${lazyAttr}-srcset`;
const imgSelector = `img[${lazyAttr}], img[${lazySrcAttr}], ` +
  `img[${lazySrcsetAttr}], source[${lazySrcsetAttr}]`;

Flickity.create.lazyLoad = function() {
  this.on( 'select', this.lazyLoad );

  this.handleLazyLoadComplete = this.onLazyLoadComplete.bind( this );
};

let proto = Flickity.prototype;

proto.lazyLoad = function() {
  let { lazyLoad } = this.options;
  if ( !lazyLoad ) return;

  // get adjacent cells, use lazyLoad option for adjacent count
  let adjCount = typeof lazyLoad == 'number' ? lazyLoad : 0;
  // lazy load images
  this.getAdjacentCellElements( adjCount )
    .map( getCellLazyImages )
    .flat()
    .forEach( ( img ) => new LazyLoader( img, this.handleLazyLoadComplete ) );
};

function getCellLazyImages( cellElem ) {
  // check if cell element is lazy image
  if ( cellElem.matches('img') ) {
    let cellAttr = cellElem.getAttribute( lazyAttr );
    let cellSrcAttr = cellElem.getAttribute( lazySrcAttr );
    let cellSrcsetAttr = cellElem.getAttribute( lazySrcsetAttr );
    if ( cellAttr || cellSrcAttr || cellSrcsetAttr ) {
      return cellElem;
    }
  }
  // select lazy images in cell
  return [ ...cellElem.querySelectorAll( imgSelector ) ];
}

proto.onLazyLoadComplete = function( img, event ) {
  let cell = this.getParentCell( img );
  let cellElem = cell && cell.element;
  this.cellSizeChange( cellElem );

  this.dispatchEvent( 'lazyLoad', event, cellElem );
};

// -------------------------- LazyLoader -------------------------- //

/**
 * class to handle loading images
 * @param {Image} img - Image element
 * @param {Function} onComplete - callback function
 */
function LazyLoader( img, onComplete ) {
  this.img = img;
  this.onComplete = onComplete;
  this.load();
}

LazyLoader.prototype.handleEvent = utils.handleEvent;

LazyLoader.prototype.load = function() {
  this.img.addEventListener( 'load', this );
  this.img.addEventListener( 'error', this );
  // get src & srcset
  let src = this.img.getAttribute( lazyAttr ) ||
    this.img.getAttribute( lazySrcAttr );
  let srcset = this.img.getAttribute( lazySrcsetAttr );
  // set src & serset
  this.img.src = src;
  if ( srcset ) this.img.setAttribute( 'srcset', srcset );
  // remove attr
  this.img.removeAttribute( lazyAttr );
  this.img.removeAttribute( lazySrcAttr );
  this.img.removeAttribute( lazySrcsetAttr );
};

LazyLoader.prototype.onload = function( event ) {
  this.complete( event, 'flickity-lazyloaded' );
};

LazyLoader.prototype.onerror = function( event ) {
  this.complete( event, 'flickity-lazyerror' );
};

LazyLoader.prototype.complete = function( event, className ) {
  // unbind events
  this.img.removeEventListener( 'load', this );
  this.img.removeEventListener( 'error', this );
  let mediaElem = this.img.parentNode.matches('picture') ? this.img.parentNode : this.img;
  mediaElem.classList.add( className );

  this.onComplete( this.img, event );
};

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

Flickity.LazyLoader = LazyLoader;

return Flickity;

} ) );


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

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

// -------------------------- PageDots -------------------------- //

function PageDots() {
  // create holder element
  this.holder = document.createElement('div');
  this.holder.className = 'flickity-page-dots';
  // create dots, array of elements
  this.dots = [];
}

PageDots.prototype.setDots = function( slidesLength ) {
  // get difference between number of slides and number of dots
  let delta = slidesLength - this.dots.length;
  if ( delta > 0 ) {
    this.addDots( delta );
  } else if ( delta < 0 ) {
    this.removeDots( -delta );
  }
};

PageDots.prototype.addDots = function( count ) {
  let newDots = new Array( count ).fill()
    .map( ( item, i ) => {
      let dot = document.createElement('button');
      dot.setAttribute( 'type', 'button' );
      let num = i + 1 + this.dots.length;
      dot.className = 'flickity-button flickity-page-dot';
      dot.textContent = `View slide ${num}`;
      return dot;
    } );

  this.holder.append( ...newDots );
  this.dots = this.dots.concat( newDots );
};

PageDots.prototype.removeDots = function( count ) {
  // remove from this.dots collection
  let removeDots = this.dots.splice( this.dots.length - count, count );
  // remove from DOM
  removeDots.forEach( ( dot ) => dot.remove() );
};

PageDots.prototype.updateSelected = function( index ) {
  // remove selected class on previous
  if ( this.selectedDot ) {
    this.selectedDot.classList.remove('is-selected');
    this.selectedDot.removeAttribute('aria-current');
  }
  // don't proceed if no dots
  if ( !this.dots.length ) return;

  this.selectedDot = this.dots[ index ];
  this.selectedDot.classList.add('is-selected');
  this.selectedDot.setAttribute( 'aria-current', 'step' );
};

Flickity.PageDots = PageDots;

// -------------------------- Flickity -------------------------- //

Object.assign( Flickity.defaults, {
  pageDots: true,
} );

Flickity.create.pageDots = function() {
  if ( !this.options.pageDots ) return;

  this.pageDots = new PageDots();
  this.handlePageDotsClick = this.onPageDotsClick.bind( this );
  // events
  this.on( 'activate', this.activatePageDots );
  this.on( 'select', this.updateSelectedPageDots );
  this.on( 'cellChange', this.updatePageDots );
  this.on( 'resize', this.updatePageDots );
  this.on( 'deactivate', this.deactivatePageDots );
};

let proto = Flickity.prototype;

proto.activatePageDots = function() {
  this.pageDots.setDots( this.slides.length );
  this.focusableElems.push( ...this.pageDots.dots );
  this.pageDots.holder.addEventListener( 'click', this.handlePageDotsClick );
  this.element.insertBefore( this.pageDots.holder, this.viewport );
};

proto.onPageDotsClick = function( event ) {
  let index = this.pageDots.dots.indexOf( event.target );
  if ( index === -1 ) return; // only dot clicks

  this.uiChange();
  this.select( index );
};

proto.updateSelectedPageDots = function() {
  this.pageDots.updateSelected( this.selectedIndex );
};

proto.updatePageDots = function() {
  this.pageDots.dots.forEach( ( dot ) => {
    utils.removeFrom( this.focusableElems, dot );
  } );
  this.pageDots.setDots( this.slides.length );
  this.focusableElems.push( ...this.pageDots.dots );
};

proto.deactivatePageDots = function() {
  this.pageDots.holder.remove();
  this.pageDots.holder.removeEventListener( 'click', this.handlePageDotsClick );
};

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

Flickity.PageDots = PageDots;

return Flickity;

} ) );


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

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

// -------------------------- Player -------------------------- //

function Player( autoPlay, onTick ) {
  this.autoPlay = autoPlay;
  this.onTick = onTick;
  this.state = 'stopped';
  // visibility change event handler
  this.onVisibilityChange = this.visibilityChange.bind( this );
  this.onVisibilityPlay = this.visibilityPlay.bind( this );
}

// start play
Player.prototype.play = function() {
  if ( this.state === 'playing' ) return;

  // do not play if page is hidden, start playing when page is visible
  let isPageHidden = document.hidden;
  if ( isPageHidden ) {
    document.addEventListener( 'visibilitychange', this.onVisibilityPlay );
    return;
  }

  this.state = 'playing';
  // listen to visibility change
  document.addEventListener( 'visibilitychange', this.onVisibilityChange );
  // start ticking
  this.tick();
};

Player.prototype.tick = function() {
  // do not tick if not playing
  if ( this.state !== 'playing' ) return;

  // default to 3 seconds
  let time = typeof this.autoPlay == 'number' ? this.autoPlay : 3000;
  // HACK: reset ticks if stopped and started within interval
  this.clear();
  this.timeout = setTimeout( () => {
    this.onTick();
    this.tick();
  }, time );
};

Player.prototype.stop = function() {
  this.state = 'stopped';
  this.clear();
  // remove visibility change event
  document.removeEventListener( 'visibilitychange', this.onVisibilityChange );
};

Player.prototype.clear = function() {
  clearTimeout( this.timeout );
};

Player.prototype.pause = function() {
  if ( this.state === 'playing' ) {
    this.state = 'paused';
    this.clear();
  }
};

Player.prototype.unpause = function() {
  // re-start play if paused
  if ( this.state === 'paused' ) this.play();
};

// pause if page visibility is hidden, unpause if visible
Player.prototype.visibilityChange = function() {
  let isPageHidden = document.hidden;
  this[ isPageHidden ? 'pause' : 'unpause' ]();
};

Player.prototype.visibilityPlay = function() {
  this.play();
  document.removeEventListener( 'visibilitychange', this.onVisibilityPlay );
};

// -------------------------- Flickity -------------------------- //

Object.assign( Flickity.defaults, {
  pauseAutoPlayOnHover: true,
} );

Flickity.create.player = function() {
  this.player = new Player( this.options.autoPlay, () => {
    this.next( true );
  } );

  this.on( 'activate', this.activatePlayer );
  this.on( 'uiChange', this.stopPlayer );
  this.on( 'pointerDown', this.stopPlayer );
  this.on( 'deactivate', this.deactivatePlayer );
};

let proto = Flickity.prototype;

proto.activatePlayer = function() {
  if ( !this.options.autoPlay ) return;

  this.player.play();
  this.element.addEventListener( 'mouseenter', this );
};

// Player API, don't hate the ... thanks I know where the door is

proto.playPlayer = function() {
  this.player.play();
};

proto.stopPlayer = function() {
  this.player.stop();
};

proto.pausePlayer = function() {
  this.player.pause();
};

proto.unpausePlayer = function() {
  this.player.unpause();
};

proto.deactivatePlayer = function() {
  this.player.stop();
  this.element.removeEventListener( 'mouseenter', this );
};

// ----- mouseenter/leave ----- //

// pause auto-play on hover
proto.onmouseenter = function() {
  if ( !this.options.pauseAutoPlayOnHover ) return;

  this.player.pause();
  this.element.addEventListener( 'mouseleave', this );
};

// resume auto-play on hover off
proto.onmouseleave = function() {
  this.player.unpause();
  this.element.removeEventListener( 'mouseleave', this );
};

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

Flickity.Player = Player;

return Flickity;

} ) );


================================================
FILE: js/prev-next-button.js
================================================
// prev/next buttons
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory( require('./core') );
  } else {
    // browser global
    factory( window.Flickity );
  }

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

const svgURI = 'http://www.w3.org/2000/svg';

// -------------------------- PrevNextButton -------------------------- //

function PrevNextButton( increment, direction, arrowShape ) {
  this.increment = increment;
  this.direction = direction;
  this.isPrevious = increment === 'previous';
  this.isLeft = direction === 'left';
  this._create( arrowShape );
}

PrevNextButton.prototype._create = function( arrowShape ) {
  // properties
  let element = this.element = document.createElement('button');
  element.className = `flickity-button flickity-prev-next-button ${this.increment}`;
  let label = this.isPrevious ? 'Previous' : 'Next';
  // prevent button from submitting form https://stackoverflow.com/a/10836076/182183
  element.setAttribute( 'type', 'button' );
  element.setAttribute( 'aria-label', label );
  // init as disabled
  this.disable();
  // create arrow
  let svg = this.createSVG( label, arrowShape );
  element.append( svg );
};

PrevNextButton.prototype.createSVG = function( label, arrowShape ) {
  let svg = document.createElementNS( svgURI, 'svg' );
  svg.setAttribute( 'class', 'flickity-button-icon' );
  svg.setAttribute( 'viewBox', '0 0 100 100' );
  // add title #1189
  let title = document.createElementNS( svgURI, 'title' );
  title.append( label );
  // add path
  let path = document.createElementNS( svgURI, 'path' );
  let pathMovements = getArrowMovements( arrowShape );
  path.setAttribute( 'd', pathMovements );
  path.setAttribute( 'class', 'arrow' );
  // rotate arrow
  if ( !this.isLeft ) {
    path.setAttribute( 'transform', 'translate(100, 100) rotate(180)' );
  }
  svg.append( title, path );
  return svg;
};

// get SVG path movmement
function getArrowMovements( shape ) {
  // use shape as movement if string
  if ( typeof shape == 'string' ) return shape;

  let { x0, x1, x2, x3, y1, y2 } = shape;

  // create movement string
  return `M ${x0}, 50
    L ${x1}, ${y1 + 50}
    L ${x2}, ${y2 + 50}
    L ${x3}, 50
    L ${x2}, ${50 - y2}
    L ${x1}, ${50 - y1}
    Z`;
}

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

PrevNextButton.prototype.enable = function() {
  this.element.removeAttribute('disabled');
};

PrevNextButton.prototype.disable = function() {
  this.element.setAttribute( 'disabled', true );
};

// -------------------------- Flickity prototype -------------------------- //

Object.assign( Flickity.defaults, {
  prevNextButtons: true,
  arrowShape: {
    x0: 10,
    x1: 60, y1: 50,
    x2: 70, y2: 40,
    x3: 30,
  },
} );

Flickity.create.prevNextButtons = function() {
  if ( !this.options.prevNextButtons ) return;

  let { rightToLeft, arrowShape } = this.options;
  let prevDirection = rightToLeft ? 'right' : 'left';
  let nextDirection = rightToLeft ? 'left' : 'right';
  this.prevButton = new PrevNextButton( 'previous', prevDirection, arrowShape );
  this.nextButton = new PrevNextButton( 'next', nextDirection, arrowShape );
  this.focusableElems.push( this.prevButton.element );
  this.focusableElems.push( this.nextButton.element );

  this.handlePrevButtonClick = () => {
    this.uiChange();
    this.previous();
  };

  this.handleNextButtonClick = () => {
    this.uiChange();
    this.next();
  };

  this.on( 'activate', this.activatePrevNextButtons );
  this.on( 'select', this.updatePrevNextButtons );
};

let proto = Flickity.prototype;

proto.updatePrevNextButtons = function() {
  let lastIndex = this.slides.length ? this.slides.length - 1 : 0;
  this.updatePrevNextButton( this.prevButton, 0 );
  this.updatePrevNextButton( this.nextButton, lastIndex );
};

proto.updatePrevNextButton = function( button, disabledIndex ) {
  // enable is wrapAround and at least 2 slides
  if ( this.isWrapping && this.slides.length > 1 ) {
    button.enable();
    return;
  }

  let isEnabled = this.selectedIndex !== disabledIndex;
  button[ isEnabled ? 'enable' : 'disable' ]();
  // if disabling button that is focused,
  // shift focus to element to maintain keyboard accessibility
  let isDisabledFocused = !isEnabled && document.activeElement === button.element;
  if ( isDisabledFocused ) this.focus();
};

proto.activatePrevNextButtons = function() {
  this.prevButton.element.addEventListener( 'click', this.handlePrevButtonClick );
  this.nextButton.element.addEventListener( 'click', this.handleNextButtonClick );
  this.element.prepend( this.prevButton.element, this.nextButton.element );
  this.on( 'deactivate', this.deactivatePrevNextButtons );
};

proto.deactivatePrevNextButtons = function() {
  this.prevButton.element.remove();
  this.nextButton.element.remove();
  this.prevButton.element.removeEventListener( 'click', this.handlePrevButtonClick );
  this.nextButton.element.removeEventListener( 'click', this.handleNextButtonClick );
  this.off( 'deactivate', this.deactivatePrevNextButtons );
};

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

Flickity.PrevNextButton = PrevNextButton;

return Flickity;

} ) );


================================================
FILE: js/slide.js
================================================
// slide
( function( window, factory ) {
  // universal module definition
  if ( typeof module == 'object' && module.exports ) {
    // CommonJS
    module.exports = factory();
  } else {
    // browser global
    window.Flickity = window.Flickity || {};
    window.Flickity.Slide = factory();
  }

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

function Slide( beginMargin, endMargin, cellAlign ) {
  this.beginMargin = beginMargin;
  this.endMargin = endMargin;
  this.cellAlign = cellAlign;
  this.cells = [];
  this.outerWidth = 0;
  this.height = 0;
}

let proto = Slide.prototype;

proto.addCell = function( cell ) {
  this.cells.push( cell );
  this.outerWidth += cell.size.outerWidth;
  this.height = Math.max( cell.size.outerHeight, this.height );
  // first cell stuff
  if ( this.cells.length === 1 ) {
    this.x = cell.x; // x comes from first cell
    this.firstMargin = cell.size[ this.beginMargin ];
  }
};

proto.updateTarget = function() {
  let lastCell = this.getLastCell();
  let lastMargin = lastCell ? lastCell.size[ this.endMargin ] : 0;
  let slideWidth = this.outerWidth - ( this.firstMargin + lastMargin );
  this.target = this.x + this.firstMargin + slideWidth * this.cellAlign;
};

proto.getLastCell = function() {
  return this.cells[ this.cells.length - 1 ];
};

proto.select = function() {
  this.cells.forEach( ( cell ) => cell.select() );
};

proto.unselect = function() {
  this.cells.forEach( ( cell ) => cell.unselect() );
};

proto.getCellElements = function() {
  return this.cells.map( ( cell ) => cell.element );
};

return Slide;

} ) );


================================================
FILE: package.json
================================================
{
  "name": "flickity",
  "version": "3.0.0",
  "description": "Touch, responsive, flickable carousels",
  "main": "js/index.js",
  "style": "css/flickity.css",
  "scripts": {
    "test": "npm run lint && echo \"View test/ in browser\" && exit 1",
    "lintJs": "npx eslint .",
    "lintJson": "node bin/lint-json.js",
    "lintCss": "npx stylelint '**/*.css'",
    "lint": "npm run lintJson && npm run lintJs && npm run lintCss",
    "dist": "npm run bundleCss && npm run bundleJs",
    "bundleCss": "cp css/flickity.css dist/flickity.css && node bin/bundle-css.js",
    "bundleJs": "node bin/bundle-js.js",
    "version": "node bin/version.js && npm run dist && git add -A css js dist"
  },
  "dependencies": {
    "ev-emitter": "^2.1.2",
    "fizzy-ui-utils": "^3.0.0",
    "get-size": "^3.0.0",
    "imagesloaded": "^5.0.0",
    "unidragger": "^3.0.1"
  },
  "devDependencies": {
    "clean-css": "^5.2.2",
    "eslint": "^8.10.0",
    "eslint-plugin-metafizzy": "^2.0.1",
    "jquery-bridget": "^3.0.1",
    "qunit": "^2.17.2",
    "stylelint": "^14.2.0",
    "stylelint-config-standard": "^24.0.0",
    "terser": "^5.10.0"
  },
  "repository": {
    "type": "git",
    "url": "git://github.com/metafizzy/flickity.git"
  },
  "keywords": [
    "touch",
    "responsive",
    "flick",
    "slider",
    "carousel",
    "gallery",
    "DOM",
    "browser"
  ],
  "author": "Metafizzy",
  "license": "GPL-3.0",
  "bugs": {
    "url": "https://github.com/metafizzy/flickity/issues"
  },
  "homepage": "https://flickity.metafizzy.co",
  "directories": {
    "test": "test"
  }
}


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

  <title>slides</title>

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

<style>
* { box-sizing: border-box; }

html { overflow-y: scroll; }

.carousel {
  border: 1px solid;
  margin-bottom: 40px;
}

.flickity-viewport {
  overflow: visible;
  transition: height 0.2s;
}

.carousel__cell {
  width: 32%;
  height: 100px;
  background: #BDF;
  margin-left: 1%;
  margin-right: 1%;
  font-size: 40px;
}

.carousel__cell.is-selected {
  background: #68F;
}

.carousel__cell--height2 { height: 200px; }
.carousel__cell--height3 { height: 300px; }
.carousel__cell--height4 { height: 400px; }

/* ----  ---- */

.image-carousel img {
  display: block;
  width: 50%;
}

</style>

</head>
<body>

  <h1>slides</h1>

<div class="carousel carousel1">
  <div class="carousel__cell carousel__cell--height2">0</div>
  <div class="carousel__cell">1</div>
  <div class="carousel__cell">2</div>
  <div class="carousel__cell carousel__cell--height2">3</div>
  <div class="carousel__cell carousel__cell--height3">4</div>
  <div class="carousel__cell">5</div>
  <div class="carousel__cell carousel__cell--height4">6</div>
  <div class="carousel__cell">7</div>
  <div class="carousel__cell carousel__cell--height3">8</div>
  <div class="carousel__cell">9</div>
  <div class="carousel__cell">10</div>
</div>

<div class="carousel image-carousel">
  <img src="https://i.imgur.com/r8p3Xgq.jpg" />
  <img src="https://i.imgur.com/q9zO6tw.jpg" />
  <img src="https://i.imgur.com/bwy74ok.jpg" />
  <img src="https://i.imgur.com/bAZWoqx.jpg" />
  <img src="https://i.imgur.com/PgmEBSB.jpg" />
  <img src="https://i.imgur.com/aboaFoB.jpg" />
  <img src="https://i.imgur.com/LkmcILl.jpg" />
  <img src="https://i.imgur.com/hODreXI.jpg" />
  <img src="https://i.imgur.com/UORFJ3w.jpg" />
</div>

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

<script src="../js/cell.js"></script>
<script src="../js/slide.js"></script>
<script src="../js/animate.js"></script>
<script src="../js/core.js"></script>
<script src="../js/drag.js"></script>
<script src="../js/prev-next-button.js"></script>
<script src="../js/page-dots.js"></script>
<script src="../js/player.js"></script>
<script src="../js/add-remove-cell.js"></script>
<script src="../js/lazyload.js"></script>

<script>
var flkty = new Flickity( '.carousel1', {
  adaptiveHeight: true,
  // groupCells: true,
  // wrapAround: true,
});

var imgFlkty = new Flickity( '.image-carousel', {
  adaptiveHeight: true
});

window.onload = function() {
  imgFlkty.reposition();
}
</script>

</body>
</html>


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

  <title>add/remove cells</title>

  <link rel="stylesheet" href="../css/flickity.css" />
  <link rel="stylesheet" href="sandbox.css" />

  <style>
    .remove-button {
      font-size: 20px;
      position: absolute;
      right: 10px;
      top: 10px;
    }
  </style>

</head>
<body>

  <h1>add/remove cells</h1>

  <div id="demo1" class="demo">
    <div class="container variable-width" data-flickity>
      <div class="cell n1 w3"><b>1</b><button class="remove-button">&times;</button></div>
      <div class="cell n2"><b>2</b><button class="remove-button">&times;</button></div>
      <div class="cell n3 w3"><b>3</b><button class="remove-button">&times;</button></div>
      <div class="cell n4"><b>4</b><button class="remove-button">&times;</button></div>
      <div class="cell n5 w2"><b>5</b><button class="remove-button">&times;</button></div>
      <div class="cell n6 w2"><b>6</b><button class="remove-button">&times;</button></div>
    </div>
    <p>
      <button class="prepend-button">Prepend</button>
      <button class="insert-button">Insert</button>
      <button class="append-button">Append</button>
    </p>
  </div>

  <h2>freeScroll</h2>

  <div id="demo2" class="demo">
    <div class="container variable-width"
      data-flickity='{ "freeScroll": true }'>
      <div class="cell n6"><b>1</b><button class="remove-button">&times;</button></div>
      <div class="cell n5 w2"><b>2</b><button class="remove-button">&times;</button></div>
      <div class="cell n4"><b>3</b><button class="remove-button">&times;</button></div>
      <div class="cell n3 w3"><b>4</b><button class="remove-button">&times;</button></div>
      <div class="cell n2"><b>5</b><button class="remove-button">&times;</button></div>
    </div>
    <p>
      <button class="prepend-button">Prepend</button>
      <button class="insert-button">Insert</button>
      <button class="append-button">Append</button>
    </p>
  </div>

  <h2>wrapAround</h2>

  <div id="demo3" class="demo">
    <div class="container variable-width"
      data-flickity='{ "wrapAround": true }'>
      <div class="cell n6"><b>1</b><button class="remove-button">&times;</button></div>
      <div class="cell n5 w2"><b>2</b><button class="remove-button">&times;</button></div>
      <div class="cell n4"><b>3</b><button class="remove-button">&times;</button></div>
      <div class="cell n3 w3"><b>4</b><button class="remove-button">&times;</button></div>
      <div class="cell n2"><b>5</b><button class="remove-button">&times;</button></div>
      <div class="cell n6 w2"><b>6</b><button class="remove-button">&times;</button></div>
    </div>
    <p>
      <button class="prepend-button">Prepend</button>
      <button class="insert-button">Insert</button>
      <button class="append-button">Append</button>
    </p>
  </div>

  <h2>wrapAround, freeScroll</h2>

  <div id="demo4" class="demo">
    <div class="container variable-width"
      data-flickity='{ "wrapAround": true, "freeScroll": true }'>
      <div class="cell n6"><b>1</b><button class="remove-button">&times;</button></div>
      <div class="cell n5 w2"><b>2</b><button class="remove-button">&times;</button></div>
      <div class="cell n4"><b>3</b><button class="remove-button">&times;</button></div>
      <div class="cell n3 w3"><b>4</b><button class="remove-button">&times;</button></div>
      <div class="cell n2"><b>5</b><button class="remove-button">&times;</button></div>
      <div class="cell n6 w2"><b>6</b><button class="remove-button">&times;</button></div>
    </div>
    <p>
      <button class="prepend-button">Prepend</button>
      <button class="insert-button">Insert</button>
      <button class="append-button">Append</button>
    </p>
  </div>

  <h2>contain</h2>

  <div id="demo5" class="demo">
    <div class="container variable-width"
      data-flickity='{ "contain": true }'>
      <div class="cell n1 w3"><b>1</b><button class="remove-button">&times;</button></div>
      <div class="cell n2"><b>2</b><button class="remove-button">&times;</button></div>
      <div class="cell n3 w3"><b>3</b><button class="remove-button">&times;</button></div>
      <div class="cell n4"><b>4</b><button class="remove-button">&times;</button></div>
      <div class="cell n5 w2"><b>5</b><button class="remove-button">&times;</button></div>
      <div class="cell n6 w2"><b>6</b><button class="remove-button">&times;</button></div>
    </div>
    <p>
      <button class="prepend-button">Prepend</button>
      <button class="insert-button">Insert</button>
      <button class="append-button">Append</button>
    </p>
  </div>


  <h2>reposition</h2>

  <div id="reposition">
    <div class="container variable-width">
      <div class="cell w2"><b>1</b></div>
      <div class="cell"><b>2</b></div>
      <div class="cell w3"><b>3</b></div>
      <div class="cell"><b>4</b></div>
      <div class="cell"><b>5</b></div>
      <div class="cell w2"><b>6</b></div>
      <div class="cell"><b>7</b></div>
      <div class="cell w2"><b>8</b></div>
      <div class="cell"><b>9</b></div>
    </div>
  </div>

  <h2>prepend with single #492</h2>
  <div id="prepend-single">
    <div class="container variable-width">
      <div class="cell n1"><b>1</b><button class="remove-button">&times;</button></div>
    </div>
    <p>
      <button class="prepend-button">Prepend</button>
    </p>
  </div>

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

<script src="../js/cell.js"></script>
<script src="../js/slide.js"></script>
<script src="../js/animate.js"></script>
<script src="../js/core.js"></script>
<script src="../js/drag.js"></script>
<script src="../js/prev-next-button.js"></script>
<script src="../js/page-dots.js"></script>
<script src="../js/player.js"></script>
<script src="../js/add-remove-cell.js"></script>

<script src="js/add-remove.js"></script>

</body>
</html>


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

  <title>ajax</title>

  <link rel="stylesheet" href="../css/flickity.css" />
  <style>
  .gallery-cell {
      width: auto;
      height: 320px;
  }
  .gallery-cell img {
      height:100%;
  }
  </style>

</head>
<body>

  <h1>ajax</h1>

  <div class="gallery"></div>

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

<script src="../js/cell.js"></script>
<script src="../js/slide.js"></script>
<script src="../js/animate.js"></script>
<script src="../js/core.js"></script>
<script src="../js/drag.js"></script>
<script src="../js/prev-next-button.js"></script>
<script src="../js/page-dots.js"></script>
<script src="../js/player.js"></script>
<script src="../js/add-remove-cell.js"></script>
<script src="../js/imagesloaded.js"></script>

<script>
window.onajaxload = function({ items }) {
  let gallery = document.querySelector('.gallery');
  gallery.innerHTML = items.map( ( item ) => {
    return `<div class="gallery-cell"><img src="${item.media.m}" /></div>`;
  } ).join(' ');
  
  new Flickity( gallery, {
    freeScroll: true,
    friction: 0.2,
    imagesLoaded: true,
    prevNextButtons: false,
    pageDots: false,
    wrapAround: true
  });
};
</script>

<script src="https://api.flickr.com/services/feeds/photos_public.gne?&format=json&jsoncallback=onajaxload&extras=url_n&per_page=10"></script>

</body>
</html>


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

  <title>basic1</title>

  <link rel="stylesheet" href="../css/flickity.css" />
  <link rel="stylesheet" href="sandbox.css" />

  <style>
    #gallery3,
    #gallery3 .cell,
    #gallery3 input,
    #gallery3 button {
      font-size: 20px;
    }

    #gallery3 .cell {
      padding-top: 20px;
      padding-left: 100px;
    }

    #gallery3 p { margin: 0 0 10px; }

    /*#gallery6 .cell { margin: 0 40px; }*/

    @media screen and ( min-width: 900px ) {
      #gallery5 {
        background: #DDF;
      }
      #gallery5:after {
        content: 'flickity';
        display: none;
      }
    }
  </style>

</head>
<body>

  <h1>basic1</h1>

  <div id="full-width" class="container">
    <div class="cell" n1></div>
    <div class="cell" n2></div>
    <div class="cell" n3></div>
    <div class="cell" n4></div>
    <div class="cell" n5></div>
    <div class="cell" n6></div>
    <div class="cell" n7></div>
  </div>

  <div id="half-width" class="container">
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
  </div>

  <div id="gallery3" class="container">
    <div class="cell">
      <select>
        <option>Michaelangelo</option>
        <option>Donatello</option>
        <option>Leonardo</option>
        <option>Raphael</option>
      </select>
    </div>
    <div class="cell">
      <p><button>Button</button> <a href="https://example.com">go to example.com</a></p>
      <p><input /></p>
      <p>
        <input type="checkbox" />
        <input type="radio" name="foo" />
        <input type="radio" name="foo" />
        <input type="range" />
      </p>
    </div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
  </div>

  <div id="gallery4" class="container" data-flickity>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
    <div class="cell"></div>
  </div>

  <h2>contain</h2>

  <div id="gallery6" class="container variable-width"
    data-flickity='{ "contain": true }'>
    <div class="cell n1">1</div>
    <div class="cell n2 w2">2</div>
    <div class="cell n3 w3">3</div>
    <div class="cell n4">4</div>
    <div class="cell n5 w2">5</div>
    <div class="cell n6">6</div>
  </div>

  <h2>contain, freeScroll</h2>

  <div id="gallery6" class="container variable-width"
    data-flickity='{ "contain": true, "freeScroll": true }'>
    <div class="cell n1">1</div>
    <div class="cell n2 w2">2</div>
    <div class="cell n3 w3">3</div>
    <div class="cell n4">4</div>
    <div class="cell n5 w2">5</div>
    <div class="cell n6">6</div>
    <div class="cell n1">7</div>
    <div class="cell n2 w2">8</div>
    <div class="cell n3 w3">9</div>
  </div>

  <h2>contain, few</h2>

  <div class="container variable-width"
    data-flickity='{ "contain": true, "cellAlign": "center" }'>
    <div class="cell n1">1</div>
    <div class="cell n2">2</div>
    <!-- <div class="cell n3">3</div> -->
  </div>

  <h2>watch, activate >900px</h2>

  <div id="gallery5" class="container variable-width"
    data-flickity='{ "wrapAround": true, "watchCSS": true }'>
    <div class="cell n1 w3"></div>
    <div class="cell n2 w2"></div>
    <div class="cell n3"></div>
    <div class="cell n4 w3"></div>
    <div class="cell n5"></div>
    <div class="cell n6 w2"></div>
    <div class="cell n4"></div>
    <div class="cell n2"></div>
  </div>

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

<script src="../js/cell.js"></script>
<script src="../js/slide.js"></script>
<script src="../js/animate.js"></script>
<script src="../js/core.js"></script>
<script src="../js/drag.js"></script>
<script src="../js/prev-next-button.js"></script>
<script src="../js/page-dots.js"></script>
<script src="../js/player.js"></script>
<script src="../js/add-remove-cell.js"></script>
<script src="../js/lazyload.js"></script>

<script src="js/basic.js"></script>

</body>
</html>


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

  <title>freescroll</title>

  <link rel="stylesheet" href="../css/flickity.css" />
  <style>

  * { box-sizing: border-box; }

  body { font-family: sans-serif; }

  .gallery {
    background: #FAFAFA;
    margin-bottom: 40px;
  }

  .gallery-cell {
    width: 180px;
    height: 200px;
    margin-right: 10px;
    background: #8C8;
    counter-increment: gallery-cell;
  }

  .gallery-cell.is-selected {
    background: #F90;
  }

  /* cell number */
  .gallery-cell:before {
    display: block;
    text-align: center;
    content: counter(gallery-cell);
    line-height: 200px;
    font-size: 80px;
    color: white;
  }

  </style>

</head>
<body>

  <h1>freescroll</h1>

<div class="gallery"
  data-flickity='{ "contain": true, "freeScroll": true }'>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
</div>

<div class="gallery"
  data-flickity='{ "contain": true }'>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
  <div class="gallery-cell"></div>
</div>

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

<script src="../js/cell.js"></script>
<script src="../js/slide.js"></script>
<script src="../js/animate.js"></script>
<script src="../js/core.js"></script>
<script src="../js/drag.js"></script>
<script src="../js/prev-next-button.js"></script>
<script src="../js/page-dots.js"></script>
<script src="../js/player.js"></script>
<script src="../js/add-remove-cell.js"></script>
<script src="../js/lazyload.js"></script>

</body>
</html>


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

  <title>group cells</title>

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

<style>
* { box-sizing: border-box; }

html { overflow-y: scroll; }

.carousel {
  border: 1px solid;
  margin-bottom: 40px;
/*  width: 1000px;*/
}

.carousel__cell {
/*  width: 25%;*/
  width: 32%;
  height: 200px;
  background: #BDF;
  margin-left: 1%;
  margin-right: 1%;
  font-size: 40px;
}

/*.carousel__cell--width2 { width: 40%; }
.carousel__cell--width3 { width: 80%; }*/

.carousel__cell--width2 { width: 66%; }
.carousel__cell--width3 { width: 100%; }

.carousel__cell.is-selected {
  background: #68F;
}

@media (min-width: 1000px) {
  /* fit four */
  .carousel__cell { width: 23.5%; }
  .carousel__cell--width2 { width: 49%; }
  .carousel__cell--width3 { width: 74.5%; }
}


</style>

</head>
<body>

  <h1>group cells</h1>

<div class="carousel carousel1">
  <div class="carousel__cell carousel__cell--width2">0</div>
  <div class="carousel__cell">1</div>
  <div class="carousel__cell">2</div>
  <div class="carousel__cell carousel__cell--width2">3</div>
  <div class="carousel__cell carousel__cell--w
Download .txt
gitextract_uopm_3sr/

├── .eslintrc.js
├── .github/
│   ├── contributing.md
│   ├── issue_template.md
│   └── workflows/
│       └── nodejs.yml
├── .gitignore
├── .nvmrc
├── README.md
├── bin/
│   ├── .eslintrc.js
│   ├── bundle-css.js
│   ├── bundle-js.js
│   ├── lint-json.js
│   └── version.js
├── bower.json
├── css/
│   └── flickity.css
├── dist/
│   ├── flickity.css
│   └── flickity.pkgd.js
├── js/
│   ├── add-remove-cell.js
│   ├── animate.js
│   ├── cell.js
│   ├── core.js
│   ├── drag.js
│   ├── imagesloaded.js
│   ├── index.js
│   ├── lazyload.js
│   ├── page-dots.js
│   ├── player.js
│   ├── prev-next-button.js
│   └── slide.js
├── package.json
├── sandbox/
│   ├── adaptive-height.html
│   ├── add-remove.html
│   ├── ajax.html
│   ├── basic.html
│   ├── freescroll.html
│   ├── group-cells.html
│   ├── jquery.html
│   ├── js/
│   │   ├── add-remove.js
│   │   ├── basic.js
│   │   ├── jquery.js
│   │   ├── scroll-event.js
│   │   ├── tricky-drag.js
│   │   ├── v2-sizzle.js
│   │   └── wrap-around.js
│   ├── lazyload.html
│   ├── media.html
│   ├── right-to-left.html
│   ├── sandbox.css
│   ├── scroll-event.html
│   ├── single.html
│   ├── styles.html
│   ├── tricky-drag.html
│   ├── v2-sizzle.html
│   └── wrap-around.html
├── stylelint.config.js
└── test/
    ├── drag.html
    ├── index.html
    ├── test.css
    └── unit/
        ├── adaptive-height.js
        ├── add-remove-cells.js
        ├── auto-play.js
        ├── cell-selector.js
        ├── change.js
        ├── contain.js
        ├── destroy.js
        ├── drag.js
        ├── empty.js
        ├── get-parent-cell.js
        ├── get-wrap-cells.js
        ├── group-cells.js
        ├── imagesloaded.js
        ├── init.js
        ├── initial-index.js
        ├── lazyload-srcset.js
        ├── lazyload.js
        ├── page-dots.js
        ├── position-cells.js
        ├── prev-next-buttons.js
        ├── resize.js
        ├── select-cell.js
        ├── watch.js
        └── wrap-around-fill.js
Download .txt
SYMBOL INDEX (64 symbols across 22 files)

FILE: dist/flickity.pkgd.js
  function jQueryBridget (line 48) | function jQueryBridget( namespace, PluginClass, $ ) {
  function EvEmitter (line 144) | function EvEmitter() {}
  function getStyleSize (line 246) | function getStyleSize( value ) {
  function getZeroSize (line 272) | function getZeroSize() {
  function getSize (line 289) | function getSize( elem ) {
  function Unidragger (line 570) | function Unidragger() {}
  function makeArray (line 860) | function makeArray( obj ) {
  function ImagesLoaded (line 880) | function ImagesLoaded( elem, options, onAlways ) {
  function LoadingImage (line 1044) | function LoadingImage( img ) {
  function Background (line 1117) | function Background( url, element ) {
  function Cell (line 1186) | function Cell( elem ) {
  function Slide (line 1239) | function Slide( beginMargin, endMargin, cellAlign ) {
  function Flickity (line 1506) | function Flickity( element, options ) {
  function getScrollPosition (line 2628) | function getScrollPosition() {
  function PrevNextButton (line 2657) | function PrevNextButton( increment, direction, arrowShape ) {
  function getArrowMovements (line 2701) | function getArrowMovements( shape ) {
  function PageDots (line 2830) | function PageDots() {
  function Player (line 2961) | function Player( autoPlay, onTick ) {
  function getCellsFragment (line 3128) | function getCellsFragment( cells ) {
  function getCellLazyImages (line 3292) | function getCellLazyImages( cellElem ) {
  function LazyLoader (line 3321) | function LazyLoader( img, onComplete ) {

FILE: js/add-remove-cell.js
  function getCellsFragment (line 21) | function getCellsFragment( cells ) {

FILE: js/cell.js
  function Cell (line 17) | function Cell( elem ) {

FILE: js/core.js
  function Flickity (line 46) | function Flickity( element, options ) {

FILE: js/drag.js
  function getScrollPosition (line 281) | function getScrollPosition() {

FILE: js/lazyload.js
  function getCellLazyImages (line 47) | function getCellLazyImages( cellElem ) {
  function LazyLoader (line 76) | function LazyLoader( img, onComplete ) {

FILE: js/page-dots.js
  function PageDots (line 22) | function PageDots() {

FILE: js/player.js
  function Player (line 16) | function Player( autoPlay, onTick ) {

FILE: js/prev-next-button.js
  function PrevNextButton (line 18) | function PrevNextButton( increment, direction, arrowShape ) {
  function getArrowMovements (line 62) | function getArrowMovements( shape ) {

FILE: js/slide.js
  function Slide (line 15) | function Slide( beginMargin, endMargin, cellAlign ) {

FILE: sandbox/js/add-remove.js
  function getRandom (line 3) | function getRandom( ary ) {
  function makeCell (line 11) | function makeCell() {
  function makeCells (line 24) | function makeCells() {

FILE: sandbox/js/tricky-drag.js
  function onStaticClick (line 5) | function onStaticClick( event, pointer, cellElem, cellIndex ) {
  function makeGroupCell (line 23) | function makeGroupCell() {

FILE: test/unit/adaptive-height.js
  function checkSelectHeight (line 9) | function checkSelectHeight( index, height ) {

FILE: test/unit/add-remove-cells.js
  function makeCellElem (line 3) | function makeCellElem() {
  function isPositionApprox (line 10) | function isPositionApprox( value, expected ) {
  function checkCellElem (line 22) | function checkCellElem( cellElem, index, message ) {
  function checkCellPositions (line 58) | function checkCellPositions() {

FILE: test/unit/auto-play.js
  function nextTest (line 14) | function nextTest() {
  function onPauseSelect (line 41) | function onPauseSelect() {
  function onSelect (line 59) | function onSelect() {
  function onSelect (line 72) | function onSelect() {

FILE: test/unit/change.js
  function onInitChange (line 7) | function onInitChange() {
  function onChangeC (line 16) | function onChangeC( index ) {
  function onChangeB (line 21) | function onChangeB() {
  function onSelectB (line 25) | function onSelectB( index ) {
  function onChangeA (line 32) | function onChangeA( index ) {

FILE: test/unit/drag.js
  function noop (line 3) | function noop() {}
  function fakeEvent (line 7) | function fakeEvent( type, pageX ) {
  function triggerEvent (line 19) | function triggerEvent() {
  function getDoNextDragTest (line 46) | function getDoNextDragTest( done ) {
  function getFakeDragTest (line 58) | function getFakeDragTest( args ) {
  function getDragTest (line 88) | function getDragTest( args ) {
  function getDragTest (line 151) | function getDragTest( args ) {

FILE: test/unit/empty.js
  function makeCellElem (line 15) | function makeCellElem() {

FILE: test/unit/group-cells.js
  function getSlideCellsCount (line 9) | function getSlideCellsCount() {

FILE: test/unit/imagesloaded.js
  function isPositionApprox (line 6) | function isPositionApprox( value, expected ) {

FILE: test/unit/page-dots.js
  function getSelectedDotIndex (line 15) | function getSelectedDotIndex() {

FILE: test/unit/position-cells.js
  function isPositionApprox (line 4) | function isPositionApprox( value, expected ) {
  function checkCellPositions (line 12) | function checkCellPositions( flkty, expecteds ) {
Condensed preview — 81 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (308K chars).
[
  {
    "path": ".eslintrc.js",
    "chars": 374,
    "preview": "/* eslint-env node */\n\nmodule.exports = {\n  plugins: [ 'metafizzy' ],\n  extends: 'plugin:metafizzy/browser',\n  env: {\n  "
  },
  {
    "path": ".github/contributing.md",
    "chars": 1801,
    "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": 260,
    "preview": "<!-- Thanks for submitting an issue! All bug reports and problem issues require a **reduced test case**. Create one by f"
  },
  {
    "path": ".github/workflows/nodejs.yml",
    "chars": 711,
    "preview": "# This workflow will do a clean install of node dependencies, build the source code and run tests across different versi"
  },
  {
    "path": ".gitignore",
    "chars": 89,
    "preview": "bower_components/\nnode_modules/\n/**/browserify/bundle.js\nnotes.md\nsandbox/yoshi-parallax\n"
  },
  {
    "path": ".nvmrc",
    "chars": 2,
    "preview": "16"
  },
  {
    "path": "README.md",
    "chars": 4150,
    "preview": "# Flickity\n\n_Touch, responsive, flickable carousels_\n\nSee [flickity.metafizzy.co](https://flickity.metafizzy.co) for com"
  },
  {
    "path": "bin/.eslintrc.js",
    "chars": 86,
    "preview": "module.exports = {\n  plugins: [ 'metafizzy' ],\n  extends: 'plugin:metafizzy/node',\n};\n"
  },
  {
    "path": "bin/bundle-css.js",
    "chars": 266,
    "preview": "const CleanCss = require('clean-css');\nconst fs = require('fs');\n\nlet srcCss = fs.readFileSync( 'css/flickity.css', 'utf"
  },
  {
    "path": "bin/bundle-js.js",
    "chars": 1318,
    "preview": "const fs = require('fs');\nconst { execSync } = require('child_process');\nconst { minify } = require('terser');\n\nconst in"
  },
  {
    "path": "bin/lint-json.js",
    "chars": 54,
    "preview": "require('../package.json');\nrequire('../bower.json');\n"
  },
  {
    "path": "bin/version.js",
    "chars": 305,
    "preview": "const fs = require('fs');\nconst version = require('../package.json').version;\n\n[ 'css/flickity.css', 'js/index.js' ].for"
  },
  {
    "path": "bower.json",
    "chars": 650,
    "preview": "{\n  \"name\": \"flickity\",\n  \"description\": \"Touch, responsive, flickable carousels\",\n  \"main\": [\n    \"js/index.js\",\n    \"c"
  },
  {
    "path": "css/flickity.css",
    "chars": 2887,
    "preview": "/*! Flickity v3.0.0\nhttps://flickity.metafizzy.co\n---------------------------------------------- */\n\n.flickity-enabled {"
  },
  {
    "path": "dist/flickity.css",
    "chars": 2746,
    "preview": "/*! Flickity v3.0.0\nhttps://flickity.metafizzy.co\n---------------------------------------------- */\n\n.flickity-enabled {"
  },
  {
    "path": "dist/flickity.pkgd.js",
    "chars": 95083,
    "preview": "/*!\n * Flickity PACKAGED v3.0.0\n * Touch, responsive, flickable carousels\n *\n * Licensed GPLv3 for open source use\n * or"
  },
  {
    "path": "js/add-remove-cell.js",
    "chars": 3830,
    "preview": "// add, remove cell\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' &&"
  },
  {
    "path": "js/animate.js",
    "chars": 5111,
    "preview": "// animate\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' && module.e"
  },
  {
    "path": "js/cell.js",
    "chars": 1236,
    "preview": "// Flickity.Cell\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' && mo"
  },
  {
    "path": "js/core.js",
    "chars": 25400,
    "preview": "// Flickity main\n/* eslint-disable max-params */\n( function( window, factory ) {\n  // universal module definition\n  if ("
  },
  {
    "path": "js/drag.js",
    "chars": 9066,
    "preview": "// drag\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' && module.expo"
  },
  {
    "path": "js/imagesloaded.js",
    "chars": 972,
    "preview": "// imagesloaded\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' && mod"
  },
  {
    "path": "js/index.js",
    "chars": 545,
    "preview": "/*!\n * Flickity v3.0.0\n * Touch, responsive, flickable carousels\n *\n * Licensed GPLv3 for open source use\n * or Flickity"
  },
  {
    "path": "js/lazyload.js",
    "chars": 3551,
    "preview": "// lazyload\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' && module."
  },
  {
    "path": "js/page-dots.js",
    "chars": 3856,
    "preview": "// page dots\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' && module"
  },
  {
    "path": "js/player.js",
    "chars": 3970,
    "preview": "// player & autoPlay\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' &"
  },
  {
    "path": "js/prev-next-button.js",
    "chars": 5268,
    "preview": "// prev/next buttons\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' &"
  },
  {
    "path": "js/slide.js",
    "chars": 1604,
    "preview": "// slide\n( function( window, factory ) {\n  // universal module definition\n  if ( typeof module == 'object' && module.exp"
  },
  {
    "path": "package.json",
    "chars": 1579,
    "preview": "{\n  \"name\": \"flickity\",\n  \"version\": \"3.0.0\",\n  \"description\": \"Touch, responsive, flickable carousels\",\n  \"main\": \"js/i"
  },
  {
    "path": "sandbox/adaptive-height.html",
    "chars": 2851,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <tit"
  },
  {
    "path": "sandbox/add-remove.html",
    "chars": 6149,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title"
  },
  {
    "path": "sandbox/ajax.html",
    "chars": 1718,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title"
  },
  {
    "path": "sandbox/basic.html",
    "chars": 4682,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title"
  },
  {
    "path": "sandbox/freescroll.html",
    "chars": 2303,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <tit"
  },
  {
    "path": "sandbox/group-cells.html",
    "chars": 2290,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <tit"
  },
  {
    "path": "sandbox/jquery.html",
    "chars": 1591,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>jquery</title>\n\n  <link rel=\"stylesheet\" href=\"../css/f"
  },
  {
    "path": "sandbox/js/add-remove.js",
    "chars": 2068,
    "preview": "let cellCount = 6;\n\nfunction getRandom( ary ) {\n  let index = Math.floor( Math.random() * ary.length );\n  return ary[ in"
  },
  {
    "path": "sandbox/js/basic.js",
    "chars": 684,
    "preview": "let flky = window.flky = new Flickity('#full-width');\n\n// flky.on( 'dragMove', function( event, pointer ) {\n//   console"
  },
  {
    "path": "sandbox/js/jquery.js",
    "chars": 686,
    "preview": "/* globals $ */\n\nlet $gallery1 = $('#gallery1').flickity();\nlet flkty = $gallery1.data('flickity');\n\n// $gallery1.on( 'd"
  },
  {
    "path": "sandbox/js/scroll-event.js",
    "chars": 1361,
    "preview": "let flkty = new Flickity( '.carousel1', {\n  initialIndex: 2,\n  // groupCells: true,\n  // wrapAround: true,\n  // cellAlig"
  },
  {
    "path": "sandbox/js/tricky-drag.js",
    "chars": 975,
    "preview": "let nonDragFlkty = new Flickity( '.carousel--non-drag', {\n  draggable: false,\n} );\n\nfunction onStaticClick( event, point"
  },
  {
    "path": "sandbox/js/v2-sizzle.js",
    "chars": 518,
    "preview": "let flkty = new Flickity( '.carousel', {\n  groupCells: true,\n  adaptiveHeight: true,\n  wrapAround: true,\n} );\n\nlet paraB"
  },
  {
    "path": "sandbox/js/wrap-around.js",
    "chars": 373,
    "preview": "window.flkty = new Flickity( '#gallery1', {\n  wrapAround: true,\n} );\n\nwindow.flkty2 = new Flickity( '#gallery2', {\n} );\n"
  },
  {
    "path": "sandbox/lazyload.html",
    "chars": 10761,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <tit"
  },
  {
    "path": "sandbox/media.html",
    "chars": 5217,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title"
  },
  {
    "path": "sandbox/right-to-left.html",
    "chars": 1686,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n\n  <title>right to left</title>\n\n  <link rel=\"stylesheet\" href=\"."
  },
  {
    "path": "sandbox/sandbox.css",
    "chars": 1735,
    "preview": "* { box-sizing: border-box; }\n\nbody {\n  font-family: sans-serif;\n  color: #333;\n}\n\n.container {\n  border: 1px solid;\n  m"
  },
  {
    "path": "sandbox/scroll-event.html",
    "chars": 5454,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <tit"
  },
  {
    "path": "sandbox/single.html",
    "chars": 1325,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title"
  },
  {
    "path": "sandbox/styles.html",
    "chars": 1822,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title"
  },
  {
    "path": "sandbox/tricky-drag.html",
    "chars": 2002,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" "
  },
  {
    "path": "sandbox/v2-sizzle.html",
    "chars": 4296,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <tit"
  },
  {
    "path": "sandbox/wrap-around.html",
    "chars": 4576,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title"
  },
  {
    "path": "stylelint.config.js",
    "chars": 495,
    "preview": "module.exports = {\n  extends: 'stylelint-config-standard',\n  ignoreFiles: [ 'dist/*' ],\n  rules: {\n    'color-function-n"
  },
  {
    "path": "test/drag.html",
    "chars": 2163,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <tit"
  },
  {
    "path": "test/index.html",
    "chars": 11095,
    "preview": "<!doctype html>\n<html>\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <tit"
  },
  {
    "path": "test/test.css",
    "chars": 1707,
    "preview": "/* stylelint-disable no-descending-specificity */\n\nbody {\n  font-family: sans-serif;\n  color: #333;\n}\n\n/* move over quni"
  },
  {
    "path": "test/unit/adaptive-height.js",
    "chars": 677,
    "preview": "QUnit.test( 'adaptiveHeight', function( assert ) {\n\n  let flkty = new Flickity( '#adaptive-height', {\n    adaptiveHeight"
  },
  {
    "path": "test/unit/add-remove-cells.js",
    "chars": 3577,
    "preview": "QUnit.test( 'add/remove cells', function( assert ) {\n\n  function makeCellElem() {\n    let cellElem = document.createElem"
  },
  {
    "path": "test/unit/auto-play.js",
    "chars": 2225,
    "preview": "QUnit.test( 'auto play', function( assert ) {\n\n  let done = assert.async();\n\n  let flkty = new Flickity( '#auto-play', {"
  },
  {
    "path": "test/unit/cell-selector.js",
    "chars": 1031,
    "preview": "QUnit.test( 'cellSelector', function( assert ) {\n\n  let elem = document.querySelector('#cell-selector');\n  let notCell1 "
  },
  {
    "path": "test/unit/change.js",
    "chars": 1306,
    "preview": "/* eslint-disable no-invalid-this */\n\nQUnit.test( 'change', function( assert ) {\n\n  let done = assert.async();\n\n  functi"
  },
  {
    "path": "test/unit/contain.js",
    "chars": 820,
    "preview": "QUnit.test( 'contain', function( assert ) {\n\n  let flkty = new Flickity( '#contain', {\n    contain: true,\n  } );\n\n  asse"
  },
  {
    "path": "test/unit/destroy.js",
    "chars": 907,
    "preview": "QUnit.test( 'destroy', function( assert ) {\n\n  let elem = document.querySelector('#destroy');\n  let flkty = new Flickity"
  },
  {
    "path": "test/unit/drag.js",
    "chars": 4450,
    "preview": "( function() {\n\nfunction noop() {}\n\nlet fakeDrag = window.fakeDrag = function( flkty, positions ) {\n\n  function fakeEven"
  },
  {
    "path": "test/unit/empty.js",
    "chars": 1156,
    "preview": "QUnit.test( 'empty', function( assert ) {\n\n  let gallery = document.querySelector('#empty');\n\n  let flkty = new Flickity"
  },
  {
    "path": "test/unit/get-parent-cell.js",
    "chars": 1527,
    "preview": "QUnit.test( 'getParentCell', function( assert ) {\n\n  let gallery = document.querySelector('#get-parent-cell');\n  let flk"
  },
  {
    "path": "test/unit/get-wrap-cells.js",
    "chars": 883,
    "preview": "QUnit.test( 'getWrapCells', function( assert ) {\n\n  let flkty = new Flickity( '#get-wrap-cells', {\n    wrapAround: true,"
  },
  {
    "path": "test/unit/group-cells.js",
    "chars": 1355,
    "preview": "QUnit.test( 'groupCells', function( assert ) {\n\n  let done = assert.async();\n\n  let flkty = new Flickity( '#group-cells'"
  },
  {
    "path": "test/unit/imagesloaded.js",
    "chars": 1914,
    "preview": "/* globals imagesLoaded */\n\n( function() {\n\n  // position values can be off by 0.1% or 1px\n  function isPositionApprox( "
  },
  {
    "path": "test/unit/init.js",
    "chars": 1497,
    "preview": "( function() {\n\nQUnit.module('Flickity');\n\nlet utils = window.fizzyUIUtils;\n\nQUnit.test( 'init', function( assert ) {\n\n "
  },
  {
    "path": "test/unit/initial-index.js",
    "chars": 884,
    "preview": "QUnit.test( 'initialIndex', function( assert ) {\n  // initialIndex number\n  let flkty = new Flickity( '#initial-index', "
  },
  {
    "path": "test/unit/lazyload-srcset.js",
    "chars": 858,
    "preview": "QUnit.test( 'lazyload srcset', function( assert ) {\n\n  let done = assert.async();\n\n  let gallery = document.querySelecto"
  },
  {
    "path": "test/unit/lazyload.js",
    "chars": 937,
    "preview": "QUnit.test( 'lazyload', function( assert ) {\n\n  let done = assert.async();\n\n  let gallery = document.querySelector('#laz"
  },
  {
    "path": "test/unit/page-dots.js",
    "chars": 950,
    "preview": "QUnit.test( 'pageDots', function( assert ) {\n\n  let elem = document.querySelector('#page-dots');\n  let flkty = new Flick"
  },
  {
    "path": "test/unit/position-cells.js",
    "chars": 1799,
    "preview": "( function() {\n\n// position values can be off by 0.1% or 1px\nfunction isPositionApprox( value, expected ) {\n  let isPerc"
  },
  {
    "path": "test/unit/prev-next-buttons.js",
    "chars": 1180,
    "preview": "QUnit.test( 'prev-next-buttons', function( assert ) {\n\n  let elem = document.querySelector('#prev-next-buttons');\n  let "
  },
  {
    "path": "test/unit/resize.js",
    "chars": 430,
    "preview": "QUnit.test( 'resize', function( assert ) {\n\n  let elem = document.querySelector('#resize');\n  let flkty = new Flickity( "
  },
  {
    "path": "test/unit/select-cell.js",
    "chars": 648,
    "preview": "QUnit.test( 'selectCell', function( assert ) {\n\n  let gallery = document.querySelector('#select-cell');\n  let cellElems "
  },
  {
    "path": "test/unit/watch.js",
    "chars": 239,
    "preview": "QUnit.test( 'watch fallback', function( assert ) {\n\n  let elem = document.querySelector('#watch');\n  let flkty = new Fli"
  },
  {
    "path": "test/unit/wrap-around-fill.js",
    "chars": 481,
    "preview": "QUnit.test( 'wrapAround: \"fill\"', function( assert ) {\n\n  let elem = document.querySelector('#wrap-around-fill');\n  let "
  }
]

About this extraction

This page contains the full source code of the metafizzy/flickity GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 81 files (284.3 KB), approximately 81.3k tokens, and a symbol index with 64 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!