[
  {
    "path": ".babelrc",
    "content": "{\n  \"plugins\": [\n    \"@babel/plugin-transform-optional-chaining\",\n    \"@babel/plugin-transform-nullish-coalescing-operator\"\n  ]\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true"
  },
  {
    "path": ".eslintignore",
    "content": "node_modules\n!.*.js\n.eslintrc.js\n*.d.ts\n*.ts\n"
  },
  {
    "path": ".eslintrc.json",
    "content": "{\n    \"env\": {\n        \"browser\": true,\n        \"es6\": true\n    },\n    \"extends\": \"airbnb-base\",\n    \"parser\": \"@babel/eslint-parser\",\n    \"parserOptions\": {\n        \"sourceType\": \"module\",\n        \"requireConfigFile\": false,\n        \"babelOptions\": {\n          \"plugins\": [\"@babel/plugin-syntax-class-properties\"]\n        }\n    },\n    \"overrides\": [\n        {\n            \"files\": [\n                \"website/static/es6/**/*.js\"\n            ]\n        }\n    ],\n    \"rules\": {\n        \"max-len\": [\"error\", { \"code\": 100, \"comments\": 140 }],\n        \"comma-dangle\": \"off\",\n        \"no-param-reassign\": 0,\n        \"import/extensions\": 0,\n        \"no-underscore-dangle\": 0,\n        \"consistent-return\": 0,\n        \"no-trailing-spaces\": \"warn\",\n        \"spaced-comment\": 0,\n        \"no-plusplus\": 0,\n        \"prefer-template\": 0,\n        \"object-curly-newline\": 0,\n        \"arrow-body-style\": 0,\n        \"import/prefer-default-export\": 0,\n        \"no-else-return\": 0,\n        \"no-lonely-if\": 0,\n        \"default-case\": 0,\n        \"linebreak-style\": 0,\n        \"class-methods-use-this\": 0,\n        \"max-classes-per-file\": 0,\n        \"arrow-parens\": 0,\n        \"no-multiple-empty-lines\": 0,\n        \"no-mixed-operators\": 0,\n        \"function-paren-newline\": 0\n    }\n}\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "open_collective: photoswipe"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: PhotoSwipe Jobs\n\non: [workflow_dispatch]\n\njobs:\n  build:\n    name: Publish docs\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"checkout repository\"\n        uses: actions/checkout@v2\n\n      - name: \"setup node\"\n        uses: actions/setup-node@v2\n        with:\n          node-version: 16\n\n      - name: \"github api fetch (for stars count)\"\n        working-directory: demo-docs-website\n        run: curl -s https://api.github.com/repos/dimsemenov/photoswipe -o ./repo-data.json \n\n      - name: \"npm install & build docs\"\n        working-directory: demo-docs-website\n        run: |\n          npm install\n          npm run build\n\n      - name: \"install ssh key\"\n        uses: shimataro/ssh-key-action@v2\n        with:\n          key: ${{ secrets.SSH_PRIVATE_KEY }} \n          known_hosts: ${{ secrets.KNOWN_HOSTS }}\n\n      - name: \"deploy with rsync\"\n        run: rsync -avz ./demo-docs-website/build/ ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:${{ secrets.RSYNC_DIR }}"
  },
  {
    "path": ".gitignore",
    "content": "node_modules/\r\nrelease/\r\nGemfile.lock\r\ntest/dest\r\n*.gem\r\npkg/\r\n*.swp\r\n*~\r\n_site/\r\n.bundle/\r\nwebsite/static/photoswipe/\r\n.DS_Store\r\nbbin/\r\nsftp-config*\r\n_site\r\n.htaccess\r\nprivate-*\r\n__article/\r\n_site/*\r\nwebsite/build/\r\nnode_modules\r\n_production\r\nall.min.css\r\naws-keys.json\r\n*.sublime-project\r\n*.sublime-workspace\r\nwebsite/dist/\r\n*.idea\r\n/.idea\r\n.vscode\r\n.node-version\r\n.sass-cache/\r\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2014-2022 Dmitry Semenov, https://dimsemenov.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "**FEEDBACK NEEDED** I am developing the new version - PhotoSwipe v6, please [read about upcoming changes and leave feedback](https://github.com/dimsemenov/PhotoSwipe/discussions/2170).\n\n\n\nPhotoSwipe v5 — JavaScript image gallery and lightbox\n\n**[Demo](https://photoswipe.com)** | **[Documentation](https://photoswipe.com/getting-started/)**\n\n[![Sponsor via OpenCollective](https://img.shields.io/opencollective/all/photoswipe?label=Sponsor%20via%20OpenCollective)](https://opencollective.com/photoswipe)\n[![Follow on Twitter](https://img.shields.io/twitter/follow/photoswipe?style=social)](https://twitter.com/intent/user?screen_name=photoswipe)\n\n\n### Repo structure\n\n- `dist/` - main JS and CSS\n- `src/` - source JS and CSS.\n  - `src/js/photoswipe.js` - entry for PhotoSwipe Core.\n  - `src/js/lightbox/lightbox.js` - entry for PhotoSwipe Lightbox.\n- `docs/` - documentation markdown files.\n- `demo-docs-website/` - website with documentation, demos and manual tests.\n- `build/` - rollup build config.\n\nTo build JS and CSS in `dist/` directory, run `npm run build`.\n\nTo run the demo website and automatically rebuild files during development, run `npm install` in `demo-docs-website/` and `npm run watch` in the root directory.\n\n### Older versions\n\nDocumentation for the old version (v4) can be found [here](https://photoswipe.com/v4-docs/getting-started.html) and [the code for 4.1.3 is here](https://github.com/dimsemenov/PhotoSwipe/tree/v4.1.3).\n\n[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg)](https://savelife.in.ua/en/)\n\n---\n\nThis project is tested with [BrowserStack](https://www.browserstack.com/).\n"
  },
  {
    "path": "build/config-builder.js",
    "content": "// eslint-disable-next-line import/no-extraneous-dependencies\nimport { terser } from 'rollup-plugin-terser';\nimport { babel } from '@rollup/plugin-babel';\n\nimport pkg from '../package.json';\n\nconst year = new Date().getFullYear();\n\nfunction getBanner(name) {\n  return `/*!\n  * ${name} ${pkg.version} - https://photoswipe.com\n  * (c) ${year} Dmytro Semenov\n  */`;\n}\n\nfunction getMinifyPlugin() {\n  return terser({\n    output: {\n      comments: /^\\**!/i,\n    },\n    mangle: {\n      properties: {\n        // mangle properties and func names that start with underscore\n        regex: /^_/,\n      }\n    }\n  });\n}\n\nfunction getBabelPlugin() {\n  return babel({\n    exclude: 'node_modules/**'\n  })\n}\n\nconst baseOutputDir = 'demo-docs-website/static/photoswipe/';\nexport const lightboxJS = {\n  input: 'src/js/lightbox/lightbox.js',\n  output: {\n    banner: getBanner('PhotoSwipe Lightbox'),\n    file: baseOutputDir + 'photoswipe-lightbox.esm.js',\n    format: 'esm',\n    sourcemap: true\n  },\n  plugins: [getBabelPlugin()]\n};\n\nexport const coreJS = {\n  input: 'src/js/photoswipe.js',\n  output: {\n    banner: getBanner('PhotoSwipe'),\n    file: baseOutputDir + 'photoswipe.esm.js',\n    format: 'esm',\n    sourcemap: true\n  },\n  plugins: [getBabelPlugin()]\n};\n\nexport const minLightboxJS = {\n  input: 'src/js/lightbox/lightbox.js',\n  output: {\n    banner: getBanner('PhotoSwipe Lightbox'),\n    file: baseOutputDir + 'photoswipe-lightbox.esm.min.js',\n    format: 'esm'\n  },\n  plugins: [getBabelPlugin(), getMinifyPlugin()]\n};\n\nexport const minCoreJS = {\n  input: 'src/js/photoswipe.js',\n  output: {\n    banner: getBanner('PhotoSwipe'),\n    file: baseOutputDir + 'photoswipe.esm.min.js',\n    format: 'esm',\n  },\n  plugins: [getBabelPlugin(), getMinifyPlugin()]\n};\n\n// UMD config\nconst umdBaseOutputDir = 'demo-docs-website/static/photoswipe/umd/';\nexport const umdMinLightboxJS = {\n  input: 'src/js/lightbox/lightbox.js',\n  output: {\n    name: 'PhotoSwipeLightbox',\n    banner: getBanner('PhotoSwipe Lightbox'),\n    file: umdBaseOutputDir + 'photoswipe-lightbox.umd.min.js',\n    format: 'umd'\n  },\n  plugins: [getBabelPlugin(), getMinifyPlugin()]\n};\n\nexport const umdMinCoreJS = {\n  input: 'src/js/photoswipe.js',\n  output: {\n    name: 'PhotoSwipe',\n    banner: getBanner('PhotoSwipe'),\n    file: umdBaseOutputDir + 'photoswipe.umd.min.js',\n    format: 'umd',\n  },\n  plugins: [getBabelPlugin(), getMinifyPlugin()]\n};\n"
  },
  {
    "path": "build/rollup.config.js",
    "content": "import { \n  lightboxJS,\n  coreJS,\n  minLightboxJS,\n  minCoreJS,\n  umdMinLightboxJS,\n  umdMinCoreJS\n} from './config-builder';\n\nexport default [lightboxJS, coreJS, minLightboxJS, minCoreJS, umdMinLightboxJS, umdMinCoreJS];\n"
  },
  {
    "path": "build/rollup.config.watch.js",
    "content": "import { lightboxJS, coreJS } from './config-builder';\n\nexport default [lightboxJS, coreJS];\n"
  },
  {
    "path": "demo-docs-website/.gitignore",
    "content": "# Dependencies\n/node_modules\n\n# Production\n/build\n\n# Generated files\n.docusaurus\n.cache-loader\n\n# Misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n"
  },
  {
    "path": "demo-docs-website/babel.config.js",
    "content": "module.exports = {\n  presets: [require.resolve('@docusaurus/core/lib/babel/preset')],\n};\n"
  },
  {
    "path": "demo-docs-website/docusaurus.config.js",
    "content": "// @ts-check\n// Note: type annotations allow type checking and IDEs autocompletion\nconst path = require('path');\nconst lightCodeTheme = require('prism-react-renderer/themes/github');\n\n/** @type {import('@docusaurus/types').Config} */\nconst config = {\n  title: 'PhotoSwipe',\n  tagline: '',\n  url: 'https://photoswipe.com',\n  baseUrl: '/',\n  onBrokenLinks: 'throw',\n  onBrokenMarkdownLinks: 'warn',\n  favicon: 'img/favicon-16x16.png',\n  organizationName: 'dimsemenov', // Usually your GitHub org/user name.\n  projectName: 'photoswipe', // Usually your repo name.\n\n\n  // scripts: [\n  //   'https://docusaurus.io/slash.js',\n  //   {\n  //     src:\n  //       'https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js',\n  //     async: true,\n  //   },\n  // ],\n  stylesheets: [\n    {\n      href: '/photoswipe/photoswipe.css',\n      type: 'text/css',\n    },\n  ],\n\n  plugins: [\n    [\n      '@docusaurus/plugin-google-gtag',\n      {\n        trackingID: 'G-57MLE6HBT9',\n      },\n    ],\n  ],\n  presets: [\n    [\n      'classic',\n      /** @type {import('@docusaurus/preset-classic').Options} */\n      ({\n        docs: {\n          path: '../docs/',\n          routeBasePath: '/',\n          sidebarPath: require.resolve('./sidebars.js'),\n          // Please change this to your repo.\n          editUrl: 'https://github.com/dimsemenov/PhotoSwipe/tree/master/docs',\n          breadcrumbs: false\n        },\n        theme: {\n          customCss: require.resolve('./src/css/custom.css'),\n        },\n      }),\n    ],\n  ],\n\n  themeConfig:\n    /** @type {import('@docusaurus/preset-classic').ThemeConfig} */\n    ({\n      colorMode: {\n        defaultMode: 'light',\n        disableSwitch: true,\n        respectPrefersColorScheme: false,\n      },\n      navbar: {\n        title: 'PhotoSwipe',\n        \n        // logo: {\n        //   alt: 'My Site Logo',\n        //   src: 'img/logo.svg',\n        // },\n        items: [\n          {\n            type: 'doc',\n            docId: 'getting-started',\n            position: 'left',\n            label: 'Docs',\n          }\n        ],\n      },\n      footer: {\n        style: 'dark',\n        links: [\n          {\n\n          },\n          {\n            title: 'Community',\n            items: [\n              {\n                label: 'Twitter',\n                href: 'https://twitter.com/photoswipe',\n              },\n            ],\n          },\n          {\n            title: 'More',\n            items: [\n              {\n                label: 'Blog',\n                to: '/blog',\n              },\n              {\n                label: 'GitHub',\n                href: 'https://github.com/dimsemenov/photoswipe',\n              },\n            ],\n          },\n        ],\n        copyright: 'Made in Ukraine<span class=\"ukraine-flag\"></span> by <a href=\"https://twitter.com/dimsemenov\">Dmytro Semenov</a>',\n      },\n      prism: {\n        theme: lightCodeTheme,\n      },\n\n    }),\n};\n\nmodule.exports = config;\n"
  },
  {
    "path": "demo-docs-website/package.json",
    "content": "{\n  \"name\": \"demo-docs-website\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"docusaurus\": \"docusaurus\",\n    \"start\": \"docusaurus start\",\n    \"build\": \"docusaurus build\",\n    \"build-and-analyze\": \"docusaurus build --bundle-analyzer\",\n    \"swizzle\": \"docusaurus swizzle\",\n    \"deploy\": \"docusaurus deploy\",\n    \"clear\": \"docusaurus clear\",\n    \"serve\": \"docusaurus serve\",\n    \"write-translations\": \"docusaurus write-translations\",\n    \"write-heading-ids\": \"docusaurus write-heading-ids\"\n  },\n  \"dependencies\": {\n    \"@docsearch/react\": \"^3.0.0\",\n    \"@docusaurus/core\": \"2.0.0-beta.17\",\n    \"@docusaurus/plugin-google-gtag\": \"^2.0.0-beta.17\",\n    \"@docusaurus/preset-classic\": \"2.0.0-beta.17\",\n    \"@docusaurus/theme-live-codeblock\": \"^2.0.0-beta.17\",\n    \"@mdx-js/react\": \"^1.6.22\",\n    \"clsx\": \"^1.1.1\",\n    \"photoswipe-deep-zoom-plugin\": \"^1.1.1\",\n    \"photoswipe-dynamic-caption-plugin\": \"^1.1.1\",\n    \"prism-react-renderer\": \"^1.2.1\",\n    \"react\": \"^17.0.1\",\n    \"react-dom\": \"^17.0.1\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.5%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\",\n      \"last 1 safari version\"\n    ]\n  }\n}\n"
  },
  {
    "path": "demo-docs-website/repo-data.json",
    "content": "{\n  \"id\": 1580851,\n  \"name\": \"PhotoSwipe\",\n  \"full_name\": \"dimsemenov/PhotoSwipe\",\n  \"private\": false,\n  \"owner\": {\n    \"login\": \"dimsemenov\",\n    \"id\": 1061115,\n    \"avatar_url\": \"https://avatars.githubusercontent.com/u/1061115?v=4\",\n    \"gravatar_id\": \"\",\n    \"url\": \"https://api.github.com/users/dimsemenov\",\n    \"html_url\": \"https://github.com/dimsemenov\",\n    \"followers_url\": \"https://api.github.com/users/dimsemenov/followers\",\n    \"following_url\": \"https://api.github.com/users/dimsemenov/following{/other_user}\",\n    \"gists_url\": \"https://api.github.com/users/dimsemenov/gists{/gist_id}\",\n    \"starred_url\": \"https://api.github.com/users/dimsemenov/starred{/owner}{/repo}\",\n    \"subscriptions_url\": \"https://api.github.com/users/dimsemenov/subscriptions\",\n    \"organizations_url\": \"https://api.github.com/users/dimsemenov/orgs\",\n    \"repos_url\": \"https://api.github.com/users/dimsemenov/repos\",\n    \"events_url\": \"https://api.github.com/users/dimsemenov/events{/privacy}\",\n    \"received_events_url\": \"https://api.github.com/users/dimsemenov/received_events\",\n    \"type\": \"User\",\n    \"site_admin\": false\n  },\n  \"html_url\": \"https://github.com/dimsemenov/PhotoSwipe\",\n  \"description\": \"JavaScript image gallery for mobile and desktop, modular, framework independent\",\n  \"fork\": false,\n  \"url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe\",\n  \"forks_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/forks\",\n  \"keys_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/keys{/key_id}\",\n  \"collaborators_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/collaborators{/collaborator}\",\n  \"teams_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/teams\",\n  \"hooks_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/hooks\",\n  \"issue_events_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/issues/events{/number}\",\n  \"events_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/events\",\n  \"assignees_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/assignees{/user}\",\n  \"branches_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/branches{/branch}\",\n  \"tags_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/tags\",\n  \"blobs_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/git/blobs{/sha}\",\n  \"git_tags_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/git/tags{/sha}\",\n  \"git_refs_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/git/refs{/sha}\",\n  \"trees_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/git/trees{/sha}\",\n  \"statuses_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/statuses/{sha}\",\n  \"languages_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/languages\",\n  \"stargazers_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/stargazers\",\n  \"contributors_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/contributors\",\n  \"subscribers_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/subscribers\",\n  \"subscription_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/subscription\",\n  \"commits_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/commits{/sha}\",\n  \"git_commits_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/git/commits{/sha}\",\n  \"comments_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/comments{/number}\",\n  \"issue_comment_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/issues/comments{/number}\",\n  \"contents_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/contents/{+path}\",\n  \"compare_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/compare/{base}...{head}\",\n  \"merges_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/merges\",\n  \"archive_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/{archive_format}{/ref}\",\n  \"downloads_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/downloads\",\n  \"issues_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/issues{/number}\",\n  \"pulls_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/pulls{/number}\",\n  \"milestones_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/milestones{/number}\",\n  \"notifications_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/notifications{?since,all,participating}\",\n  \"labels_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/labels{/name}\",\n  \"releases_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/releases{/id}\",\n  \"deployments_url\": \"https://api.github.com/repos/dimsemenov/PhotoSwipe/deployments\",\n  \"created_at\": \"2011-04-07T05:46:29Z\",\n  \"updated_at\": \"2022-03-27T01:05:17Z\",\n  \"pushed_at\": \"2022-03-26T14:18:32Z\",\n  \"git_url\": \"git://github.com/dimsemenov/PhotoSwipe.git\",\n  \"ssh_url\": \"git@github.com:dimsemenov/PhotoSwipe.git\",\n  \"clone_url\": \"https://github.com/dimsemenov/PhotoSwipe.git\",\n  \"svn_url\": \"https://github.com/dimsemenov/PhotoSwipe\",\n  \"homepage\": \"http://photoswipe.com\",\n  \"size\": 31451,\n  \"stargazers_count\": 21653,\n  \"watchers_count\": 21653,\n  \"language\": \"JavaScript\",\n  \"has_issues\": true,\n  \"has_projects\": true,\n  \"has_downloads\": true,\n  \"has_wiki\": true,\n  \"has_pages\": false,\n  \"forks_count\": 3271,\n  \"mirror_url\": null,\n  \"archived\": false,\n  \"disabled\": false,\n  \"open_issues_count\": 564,\n  \"license\": {\n    \"key\": \"mit\",\n    \"name\": \"MIT License\",\n    \"spdx_id\": \"MIT\",\n    \"url\": \"https://api.github.com/licenses/mit\",\n    \"node_id\": \"MDc6TGljZW5zZTEz\"\n  },\n  \"allow_forking\": true,\n  \"is_template\": false,\n  \"topics\": [\n    \"gallery\",\n    \"image\",\n    \"javascript\",\n    \"lightbox\"\n  ],\n  \"visibility\": \"public\",\n  \"forks\": 3271,\n  \"open_issues\": 564,\n  \"watchers\": 21655,\n  \"default_branch\": \"master\",\n  \"temp_clone_token\": null,\n  \"network_count\": 3271,\n  \"subscribers_count\": 556\n}"
  },
  {
    "path": "demo-docs-website/sidebars.js",
    "content": "/**\n * Creating a sidebar enables you to:\n - create an ordered group of docs\n - render a sidebar for each doc of that group\n - provide next/previous navigation\n\n The sidebars can be generated from the filesystem, or explicitly defined here.\n\n Create as many sidebars as you want.\n */\n\n// @ts-check\n\n/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */\nconst sidebars = {\n  // By default, Docusaurus generates a sidebar from the docs folder structure\n  // tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],\n\n  mainSidebar: [\n    {\n      type: 'doc',\n      id: 'getting-started',\n    },\n    {\n      type: 'category',\n      label: 'Examples',\n      items: [\n        {\n          type: 'doc',\n          id: 'styling',\n        },\n        {\n          type: 'doc',\n          id: 'opening-or-closing-transition',\n        },\n        {\n          type: 'doc',\n          id: 'adding-ui-elements',\n        },\n        {\n          type: 'doc',\n          id: 'adjusting-zoom-level',\n        },\n        {\n          type: 'doc',\n          id: 'caption',\n        },\n        {\n          type: 'doc',\n          id: 'click-and-tap-actions',\n        },\n        {\n          type: 'doc',\n          id: 'custom-content',\n        },\n        {\n          type: 'doc',\n          id: 'data-sources',\n        },\n        {\n          type: 'doc',\n          id: 'native-fullscreen-on-open',\n        },\n      ],\n    },\n\n    {\n      type: 'category',\n      label: 'API',\n      items: [\n        {\n          type: 'doc',\n          id: 'options',\n        },\n        {\n          type: 'doc',\n          id: 'events',\n        },\n        {\n          type: 'doc',\n          id: 'filters',\n        },\n        {\n          type: 'doc',\n          id: 'methods',\n        },\n      ],\n    },\n\n    {\n      type: 'category',\n      label: 'Frameworks',\n      items: [\n        {\n          type: 'doc',\n          id: 'react-image-gallery',\n        },\n        {\n          type: 'doc',\n          id: 'vue-image-gallery',\n        },\n        {\n          type: 'doc',\n          id: 'svelte-image-gallery',\n        },\n      ],\n    },\n  ]\n\n  // But you can create a sidebar manually\n  /*\n  tutorialSidebar: [\n    {\n      type: 'category',\n      label: 'Tutorial',\n      items: ['hello'],\n    },\n  ],\n   */\n};\n\nmodule.exports = sidebars;\n"
  },
  {
    "path": "demo-docs-website/src/components/HomepageFeatures/index.js",
    "content": "import React from 'react';\nimport clsx from 'clsx';\nimport styles from './styles.module.css';\n\nconst FeatureList = [\n  {\n    title: 'Easy to Use',\n    Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,\n    description: (\n      <>\n        Docusaurus was designed from the ground up to be easily installed and\n        used to get your website up and running quickly.\n      </>\n    ),\n  },\n  {\n    title: 'Focus on What Matters',\n    Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,\n    description: (\n      <>\n        Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go\n        ahead and move your docs into the <code>docs</code> directory.\n      </>\n    ),\n  },\n  {\n    title: 'Powered by React',\n    Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,\n    description: (\n      <>\n        Extend or customize your website layout by reusing React. Docusaurus can\n        be extended while reusing the same header and footer.\n      </>\n    ),\n  },\n];\n\nfunction Feature({Svg, title, description}) {\n  return (\n    <div className={clsx('col col--4')}>\n      <div className=\"text--center\">\n        <Svg className={styles.featureSvg} role=\"img\" />\n      </div>\n      <div className=\"text--center padding-horiz--md\">\n        <h3>{title}</h3>\n        <p>{description}</p>\n      </div>\n    </div>\n  );\n}\n\nexport default function HomepageFeatures() {\n  return (\n    <section className={styles.features}>\n      <div className=\"container\">\n        <div className=\"row\">\n          {FeatureList.map((props, idx) => (\n            <Feature key={idx} {...props} />\n          ))}\n        </div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "demo-docs-website/src/components/HomepageFeatures/styles.module.css",
    "content": ".features {\n  display: flex;\n  align-items: center;\n  padding: 2rem 0;\n  width: 100%;\n}\n\n.featureSvg {\n  height: 200px;\n  width: 200px;\n}\n"
  },
  {
    "path": "demo-docs-website/src/components/PswpCodePreview/demo-images.js",
    "content": "export const pswpDemoImages = [{\"sizes\":[{\"height\":2500,\"width\":1875,\"type\":\"jpg\",\"src\":\"1/img-2500.jpg\"},{\"height\":2000,\"width\":1500,\"type\":\"jpg\",\"src\":\"1/img-2000.jpg\"},{\"height\":1500,\"width\":1125,\"type\":\"jpg\",\"src\":\"1/img-1500.jpg\"},{\"height\":1000,\"width\":750,\"type\":\"jpg\",\"src\":\"1/img-1000.jpg\"},{\"height\":700,\"width\":525,\"type\":\"jpg\",\"src\":\"1/img-700.jpg\"},{\"height\":400,\"width\":300,\"type\":\"jpg\",\"src\":\"1/img-400.jpg\"},{\"height\":200,\"width\":150,\"type\":\"jpg\",\"src\":\"1/img-200.jpg\"}]},{\"sizes\":[{\"height\":2500,\"width\":1669,\"type\":\"jpg\",\"src\":\"2/img-2500.jpg\"},{\"height\":2000,\"width\":1335,\"type\":\"jpg\",\"src\":\"2/img-2000.jpg\"},{\"height\":1500,\"width\":1001,\"type\":\"jpg\",\"src\":\"2/img-1500.jpg\"},{\"height\":1000,\"width\":668,\"type\":\"jpg\",\"src\":\"2/img-1000.jpg\"},{\"height\":700,\"width\":467,\"type\":\"jpg\",\"src\":\"2/img-700.jpg\"},{\"height\":400,\"width\":267,\"type\":\"jpg\",\"src\":\"2/img-400.jpg\"},{\"height\":200,\"width\":134,\"type\":\"jpg\",\"src\":\"2/img-200.jpg\"}]},{\"sizes\":[{\"height\":1666,\"width\":2500,\"type\":\"jpg\",\"src\":\"3/img-2500.jpg\"},{\"height\":1333,\"width\":2000,\"type\":\"jpg\",\"src\":\"3/img-2000.jpg\"},{\"height\":1000,\"width\":1500,\"type\":\"jpg\",\"src\":\"3/img-1500.jpg\"},{\"height\":667,\"width\":1000,\"type\":\"jpg\",\"src\":\"3/img-1000.jpg\"},{\"height\":467,\"width\":700,\"type\":\"jpg\",\"src\":\"3/img-700.jpg\"},{\"height\":267,\"width\":400,\"type\":\"jpg\",\"src\":\"3/img-400.jpg\"},{\"height\":133,\"width\":200,\"type\":\"jpg\",\"src\":\"3/img-200.jpg\"}]},{\"sizes\":[{\"height\":1667,\"width\":2500,\"type\":\"jpg\",\"src\":\"4/img-2500.jpg\"},{\"height\":1333,\"width\":2000,\"type\":\"jpg\",\"src\":\"4/img-2000.jpg\"},{\"height\":1000,\"width\":1500,\"type\":\"jpg\",\"src\":\"4/img-1500.jpg\"},{\"height\":667,\"width\":1000,\"type\":\"jpg\",\"src\":\"4/img-1000.jpg\"},{\"height\":467,\"width\":700,\"type\":\"jpg\",\"src\":\"4/img-700.jpg\"},{\"height\":267,\"width\":400,\"type\":\"jpg\",\"src\":\"4/img-400.jpg\"},{\"height\":133,\"width\":200,\"type\":\"jpg\",\"src\":\"4/img-200.jpg\"}]},{\"sizes\":[{\"height\":1668,\"width\":2500,\"type\":\"jpg\",\"src\":\"5/img-2500.jpg\"},{\"height\":1334,\"width\":2000,\"type\":\"jpg\",\"src\":\"5/img-2000.jpg\"},{\"height\":1001,\"width\":1500,\"type\":\"jpg\",\"src\":\"5/img-1500.jpg\"},{\"height\":667,\"width\":1000,\"type\":\"jpg\",\"src\":\"5/img-1000.jpg\"},{\"height\":467,\"width\":700,\"type\":\"jpg\",\"src\":\"5/img-700.jpg\"},{\"height\":267,\"width\":400,\"type\":\"jpg\",\"src\":\"5/img-400.jpg\"},{\"height\":133,\"width\":200,\"type\":\"jpg\",\"src\":\"5/img-200.jpg\"}]},{\"sizes\":[{\"height\":1667,\"width\":2500,\"type\":\"jpg\",\"src\":\"6/img-2500.jpg\"},{\"height\":1333,\"width\":2000,\"type\":\"jpg\",\"src\":\"6/img-2000.jpg\"},{\"height\":1000,\"width\":1500,\"type\":\"jpg\",\"src\":\"6/img-1500.jpg\"},{\"height\":667,\"width\":1000,\"type\":\"jpg\",\"src\":\"6/img-1000.jpg\"},{\"height\":467,\"width\":700,\"type\":\"jpg\",\"src\":\"6/img-700.jpg\"},{\"height\":267,\"width\":400,\"type\":\"jpg\",\"src\":\"6/img-400.jpg\"},{\"height\":133,\"width\":200,\"type\":\"jpg\",\"src\":\"6/img-200.jpg\"}]},{\"sizes\":[{\"height\":2500,\"width\":1875,\"type\":\"jpg\",\"src\":\"7/img-2500.jpg\"},{\"height\":2000,\"width\":1500,\"type\":\"jpg\",\"src\":\"7/img-2000.jpg\"},{\"height\":1500,\"width\":1125,\"type\":\"jpg\",\"src\":\"7/img-1500.jpg\"},{\"height\":1000,\"width\":750,\"type\":\"jpg\",\"src\":\"7/img-1000.jpg\"},{\"height\":700,\"width\":525,\"type\":\"jpg\",\"src\":\"7/img-700.jpg\"},{\"height\":400,\"width\":300,\"type\":\"jpg\",\"src\":\"7/img-400.jpg\"},{\"height\":200,\"width\":150,\"type\":\"jpg\",\"src\":\"7/img-200.jpg\"}]},{\"sizes\":[{\"height\":1667,\"width\":2500,\"type\":\"jpg\",\"src\":\"8/img-2500.jpg\"},{\"height\":1334,\"width\":2000,\"type\":\"jpg\",\"src\":\"8/img-2000.jpg\"},{\"height\":1000,\"width\":1500,\"type\":\"jpg\",\"src\":\"8/img-1500.jpg\"},{\"height\":667,\"width\":1000,\"type\":\"jpg\",\"src\":\"8/img-1000.jpg\"},{\"height\":467,\"width\":700,\"type\":\"jpg\",\"src\":\"8/img-700.jpg\"},{\"height\":267,\"width\":400,\"type\":\"jpg\",\"src\":\"8/img-400.jpg\"},{\"height\":133,\"width\":200,\"type\":\"jpg\",\"src\":\"8/img-200.jpg\"}]},{\"sizes\":[{\"height\":1667,\"width\":2500,\"type\":\"jpg\",\"src\":\"9/img-2500.jpg\"},{\"height\":1333,\"width\":2000,\"type\":\"jpg\",\"src\":\"9/img-2000.jpg\"},{\"height\":1000,\"width\":1500,\"type\":\"jpg\",\"src\":\"9/img-1500.jpg\"},{\"height\":667,\"width\":1000,\"type\":\"jpg\",\"src\":\"9/img-1000.jpg\"},{\"height\":467,\"width\":700,\"type\":\"jpg\",\"src\":\"9/img-700.jpg\"},{\"height\":267,\"width\":400,\"type\":\"jpg\",\"src\":\"9/img-400.jpg\"},{\"height\":133,\"width\":200,\"type\":\"jpg\",\"src\":\"9/img-200.jpg\"}]},{\"sizes\":[{\"height\":2500,\"width\":2000,\"type\":\"jpg\",\"src\":\"10/img-2500.jpg\"},{\"height\":2000,\"width\":1600,\"type\":\"jpg\",\"src\":\"10/img-2000.jpg\"},{\"height\":1500,\"width\":1200,\"type\":\"jpg\",\"src\":\"10/img-1500.jpg\"},{\"height\":1000,\"width\":800,\"type\":\"jpg\",\"src\":\"10/img-1000.jpg\"},{\"height\":700,\"width\":560,\"type\":\"jpg\",\"src\":\"10/img-700.jpg\"},{\"height\":400,\"width\":320,\"type\":\"jpg\",\"src\":\"10/img-400.jpg\"},{\"height\":200,\"width\":160,\"type\":\"jpg\",\"src\":\"10/img-200.jpg\"}]},{\"sizes\":[{\"height\":2500,\"width\":2000,\"type\":\"jpg\",\"src\":\"11/img-2500.jpg\"},{\"height\":2000,\"width\":1600,\"type\":\"jpg\",\"src\":\"11/img-2000.jpg\"},{\"height\":1500,\"width\":1200,\"type\":\"jpg\",\"src\":\"11/img-1500.jpg\"},{\"height\":1000,\"width\":800,\"type\":\"jpg\",\"src\":\"11/img-1000.jpg\"},{\"height\":700,\"width\":560,\"type\":\"jpg\",\"src\":\"11/img-700.jpg\"},{\"height\":400,\"width\":320,\"type\":\"jpg\",\"src\":\"11/img-400.jpg\"},{\"height\":200,\"width\":160,\"type\":\"jpg\",\"src\":\"11/img-200.jpg\"}]}];"
  },
  {
    "path": "demo-docs-website/src/components/PswpCodePreview/gallery-templates/basic--badges.js",
    "content": "export function basicBadgesTemplate(props) {\n  let out = `<div class=\"pswp-gallery pswp-gallery--single-column\" id=\"gallery--${props.id}\">`;\n  for (let i = 0; i < (props.numItems || 4); i++) {\n    out += `<a href=\"${props.img[i].src}\" \n    data-pswp-width=\"${props.img[i].width}\" \n    data-pswp-height=\"${props.img[i].height}\" \n    target=\"_blank\">\n    <img src=\"${props.img[i].thumbSrc}\" alt=\"\" />\n    <div class=\"badge\">Badge ${i + 1}</div>\n  </a>`;\n  }\n  out += '</div>';\n  return out;\n}\n"
  },
  {
    "path": "demo-docs-website/src/components/PswpCodePreview/gallery-templates/basic--cropped.js",
    "content": "export function basicCroppedTemplate(props) {\n  let out = `<div class=\"pswp-gallery\" id=\"gallery--${props.id}\">`;\n  for (let i = 0; i < (props.numItems || 3); i++) {\n    out += `<a href=\"${props.img[i].src}\" \n    data-pswp-width=\"${props.img[i].width}\" \n    data-pswp-height=\"${props.img[i].height}\" \n    target=\"_blank\"\n    data-cropped=\"true\">\n    <img src=\"${props.img[i].thumbSrc}\" alt=\"\" />\n  </a>`;\n  }\n  out += '</div>';\n  return out;\n}\n"
  },
  {
    "path": "demo-docs-website/src/components/PswpCodePreview/gallery-templates/basic.js",
    "content": "export function basicTemplate(props) {\n  let out = `<div class=\"pswp-gallery\" id=\"gallery--${props.id}\">\\n`;\n  for (let i = 0; i < (props.numItems || 3); i++) {\n    out += `  <a href=\"${props.img[i].src}\" \n    data-pswp-width=\"${props.img[i].width}\" \n    data-pswp-height=\"${props.img[i].height}\" \n    target=\"_blank\">\n    <img src=\"${props.img[i].thumbSrc}\" alt=\"\" />\n  </a>\\n`;\n  }\n  out += '</div>';\n  return out;\n}\n"
  },
  {
    "path": "demo-docs-website/src/components/PswpCodePreview/gallery-templates/caption.js",
    "content": "export function captionTemplate(props) {\n  return `<div class=\"pswp-gallery pswp-gallery--with-caption\" id=\"gallery--${props.id}\">\n  <div class=\"pswp-gallery__item\">\n    <a  \n      href=\"${props.img[1].src}\" \n      data-pswp-width=\"${props.img[1].width}\" \n      data-pswp-height=\"${props.img[1].height}\" \n      target=\"_blank\">\n      <img src=\"${props.img[1].thumbSrc}\" alt=\"Caption 1\" />\n    </a>\n    <div class=\"hidden-caption-content\">Lorem ipsum dolor sit amet, consectetur adipiscing elit. <a href=\"https://example.com\" target=\"_blank\" rel=\"nofollow\">Test link &rarr;</a></div>\n  </div>\n\n  <div class=\"pswp-gallery__item\">\n    <a  \n      href=\"${props.img[2].src}\" \n      data-pswp-width=\"${props.img[2].width}\" \n      data-pswp-height=\"${props.img[2].height}\" \n      target=\"_blank\">\n      <img src=\"${props.img[2].thumbSrc}\" alt=\"Caption 2\" />\n    </a>\n  </div>\n\n  <div class=\"pswp-gallery__item\">\n    <a  \n      href=\"${props.img[3].src}\" \n      data-pswp-width=\"${props.img[3].width}\" \n      data-pswp-height=\"${props.img[3].height}\" \n      target=\"_blank\">\n      <img src=\"${props.img[3].thumbSrc}\" alt=\"Caption 3\" />\n    </a>\n  </div>\n  \n  <div class=\"pswp-gallery__item\">\n    <a  \n      href=\"${props.img[4].src}\" \n      data-pswp-width=\"${props.img[4].width}\" \n      data-pswp-height=\"${props.img[4].height}\" \n      target=\"_blank\">\n      <img src=\"${props.img[4].thumbSrc}\" alt=\"Caption 4\" />\n    </a>\n  </div>\n\n  \n</div>`;\n}\n"
  },
  {
    "path": "demo-docs-website/src/components/PswpCodePreview/gallery-templates/content-types.js",
    "content": "export function contentTypesTemplate(props) {\n  return `<div class=\"pswp-gallery\" id=\"gallery--${props.id}\">\n    <a  \n      href=\"${props.img[0].src}\" \n      data-pswp-width=\"${props.img[0].width}\" \n      data-pswp-height=\"${props.img[0].height}\" \n      data-pswp-webp-src=\"https://cdn.photoswipe.com/photoswipe-demo-images/photos/1/img-2500.webp\"\n      target=\"_blank\">\n      <img src=\"${props.img[0].thumbSrc}\" alt=\"\" />\n    </a>\n\n    <a  \n      href=\"${props.img[2].src}\" \n      data-pswp-width=\"${props.img[2].width}\" \n      data-pswp-height=\"${props.img[2].height}\" \n      target=\"_blank\">\n      <img src=\"${props.img[2].thumbSrc}\" alt=\"\" />\n    </a>\n\n    <a  \n      href=\"${props.img[3].src}\" \n      data-pswp-width=\"${props.img[3].width}\" \n      data-pswp-height=\"${props.img[3].height}\" \n      target=\"_blank\">\n      <img src=\"${props.img[3].thumbSrc}\" alt=\"\" />\n    </a>\n  \n</div>`;\n}\n"
  },
  {
    "path": "demo-docs-website/src/components/PswpCodePreview/gallery-templates/custom-html-markup-data-source.js",
    "content": "export function customHTMLDataSourceTemplate(props) {\n  let out = `<div class=\"pswp-gallery pswp-gallery--single-column\" id=\"gallery--${props.id}\">\\n`;\n  for (let i = 0; i < (props.numItems || 3); i++) {\n    out += `  <a href=\"${props.img[i].src}\" \n    data-my-size=\"${props.img[i].width}x${props.img[i].height}\" \n    data-thumb-src=\"${props.img[i].thumbSrc}\"\n    style=\"background-image:url(${props.img[i].thumbSrc})\"\n    target=\"_blank\">Test ${i + 1}</a>\\n`;\n  }\n  out += '</div>';\n  return out;\n}\n"
  },
  {
    "path": "demo-docs-website/src/components/PswpCodePreview/gallery-templates/getting-started.js",
    "content": "export function gettingStartedTemplate(props) {\n  return `<div class=\"pswp-gallery pswp-gallery--single-column\" id=\"gallery--${props.id}\">\n  <a href=\"${props.img[1].src}\" \n    data-pswp-width=\"${props.img[1].width}\" \n    data-pswp-height=\"${props.img[1].height}\" \n    target=\"_blank\">\n    <img src=\"${props.img[1].thumbSrc}\" alt=\"\" />\n  </a>\n  <!-- cropped thumbnail: -->\n  <a href=\"${props.img[6].src}\" \n    data-pswp-width=\"${props.img[6].width}\" \n    data-pswp-height=\"${props.img[6].height}\" \n    data-cropped=\"true\" \n    target=\"_blank\">\n    <img src=\"${props.img[6].thumbSrc}\" alt=\"\" />\n    Cropped\n  </a>\n  <!-- data-pswp-src with custom URL in href -->\n  <a href=\"https://unsplash.com\" \n    data-pswp-src=\"${props.img[2].src}\"\n    data-pswp-width=\"${props.img[2].width}\" \n    data-pswp-height=\"${props.img[2].height}\" \n    target=\"_blank\">\n    <img src=\"${props.img[2].thumbSrc}\" alt=\"\" />\n  </a>\n  <!-- Without thumbnail: -->\n  <a href=\"http://example.com\" \n    data-pswp-src=\"${props.img[4].src}\"\n    data-pswp-width=\"${props.img[4].width}\" \n    data-pswp-height=\"${props.img[4].height}\" \n    target=\"_blank\">\n    No thumbnail\n  </a>\n  <!-- wrapped with any element: -->\n  <div>\n    <a href=\"${props.img[5].src}\"\n      data-pswp-width=\"${props.img[5].width}\" \n      data-pswp-height=\"${props.img[5].height}\" \n      target=\"_blank\">\n      <img src=\"${props.img[5].thumbSrc}\" alt=\"\" />\n    </a>\n  </div>\n</div>`;\n}\n"
  },
  {
    "path": "demo-docs-website/src/components/PswpCodePreview/gallery-templates/srcset-test.js",
    "content": "export function srcsetTemplate(props) {\n  const items = [{ thumbSrc: '1-300x200.png', largeSrc: '1-1500x1000.png', width: 1500, height: 1000, srcset: '%1-600x400.png 600w, %1-1200x800.png 1200w, %1-1500x1000.png 1500w' }, { thumbSrc: '2-300x200.png', largeSrc: '2-1500x1000.png', width: 1500, height: 1000, srcset: '%2-600x400.png 600w, %2-1200x800.png 1200w, %2-1500x1000.png 1500w' }, { thumbSrc: '3-300x300.png', largeSrc: '3-1500x1500.png', width: 1500, height: 1500, srcset: '%3-600x600.png 600w, %3-1200x1200.png 1200w, %3-1500x1500.png 1500w' }, { thumbSrc: '4-200x300.png', largeSrc: '4-1000x1500.png', width: 1000, height: 1500, srcset: '%4-400x600.png 400w, %4-800x1200.png 800w, %4-1000x1500.png 1000w' }];\n  let out = `<div class=\"pswp-gallery\" id=\"gallery--${props.id}\">\\n`;\n  const baseURL = props.cdnURL + 'srcset-test/';\n  items.forEach((item) => {\n    const srcset = item.srcset.replace(/%/g, baseURL);\n    out += `  <a href=\"${baseURL + item.largeSrc}\" \n    data-pswp-width=\"${item.width}\" \n    data-pswp-height=\"${item.height}\" \n    data-pswp-srcset=\"${srcset}\" \n    target=\"_blank\">\n    <img src=\"${baseURL + item.thumbSrc}\" alt=\"\" />\n  </a>\\n`;\n  });\n  out += '</div>';\n  return out;\n}\n"
  },
  {
    "path": "demo-docs-website/src/components/PswpCodePreview/index.js",
    "content": "import React from 'react';\nimport CodeBlock from '../../theme/CodeBlock';\nimport { pswpDemoImages } from './demo-images';\nimport { basicTemplate } from './gallery-templates/basic';\n\nlet uidCounter = 1;\n\n/**\n * Get the smallest size (but not smaller than minSize)\n *\n * @param {Array} sizes\n * @param {Integer} minSize\n */\nconst getSmallestImageSize = (sizes, minSize) => {\n  sizes = sizes.filter(size => size.width >= minSize);\n  return sizes.reduce((a, b) => (a.width < b.width ? a : b));\n};\n\n/**\n * Generates gallery for demo\n * \n * Supported params:\n *   displayHTML: false|true (whether HTML code block should be visible)\n *   numItems: Integer (number of images to display)\n *   galleryID: String (ID attribute)\n*/\nfunction generateGallery(galleryData) {\n  \n\n  const thumbnailSize = 70;\n  const cdnURL = 'https://cdn.photoswipe.com/photoswipe-demo-images/photos/';\n  const templateProps = {\n    numItems: Math.min(parseInt(galleryData.numItems, 10) || 3, 11),\n    id: galleryData.galleryID || (uidCounter++),\n    img: [],\n    cdnURL\n  };\n  const { orientation } = galleryData;\n  let demoImages = [ ...pswpDemoImages ];\n\n  if (orientation === 'landscape') {\n    demoImages = demoImages.filter((imageData) => {\n      return imageData.sizes[0].width >= imageData.sizes[0].height;\n    });\n  } else if (orientation === 'portrait') {\n    demoImages = demoImages.filter((imageData) => {\n      return imageData.sizes[0].width <= imageData.sizes[0].height;\n    });\n  }\n\n  demoImages.forEach((imageData, index) => {\n    const largest = imageData.sizes[0];\n    const thumbnail = getSmallestImageSize(imageData.sizes, thumbnailSize);\n    templateProps.img.push({\n      index,\n      width: largest.width,\n      height: largest.height,\n      src: cdnURL + largest.src,\n      thumbSrc: cdnURL + thumbnail.src\n    });\n  });\n\n  if (galleryData.templateFn) {\n    return galleryData.templateFn(templateProps);\n  }\n  \n  \n  return basicTemplate(templateProps);\n}\n\nexport default function PswpCodePreview(props) {\n  return (\n    <div className='pswp-example'>\n      { props.children }\n      { props.galleryID && <CodeBlock language='html' pswpcode displayHTML={props.displayHTML}>{generateGallery(props)}</CodeBlock> }\n    </div>\n  );\n}\n"
  },
  {
    "path": "demo-docs-website/src/css/custom.css",
    "content": "/**\n * Any CSS included here will be global. The classic template\n * bundles Infima by default. Infima is a CSS framework designed to\n * work well for content-centric websites.\n */\n\n/* You can override the default Infima variables here. */\n:root {\n  --ifm-color-primary: #2e8555;\n  --ifm-color-primary-dark: #29784c;\n  --ifm-color-primary-darker: #277148;\n  --ifm-color-primary-darkest: #205d3b;\n  --ifm-color-primary-light: #33925d;\n  --ifm-color-primary-lighter: #359962;\n  --ifm-color-primary-lightest: #3cad6e; \n  --ifm-code-font-size: 95%;\n\n  --ifm-toc-border-color: rgba(0, 0, 0, 0.065);\n  --ifm-code-background: rgb(250, 250, 250);\n\n  --ifm-link-color: #3169B3;\n  --ifm-link-hover-color: #C00;\n  --ifm-link-decoration: underline;\n  --ifm-link-hover-decoration: underline;\n\n  --ifm-navbar-link-hover-color: var(--ifm-link-hover-color);\n\n  --ifm-footer-background-color: none;\n  --ifm-footer-color: auto;\n  --ifm-footer-padding-horizontal: calc(var(--ifm-spacing-horizontal) * 2);\n  --ifm-footer-padding-vertical: calc(var(--ifm-spacing-vertical) * 2);\n\n  --pswp-docs-main-content-width: 1100px;\n\n  --ifm-heading-font-weight: var(--ifm-font-weight-semibold);\n\n\n}\n\na {\n  transition: none;\n}\n\na:hover {\n  color: var(--ifm-link-hover-color);\n  /* autoprefixer: ignore next */\n  text-decoration: var(--ifm-link-hover-decoration);\n}\n\npre code,\n.pswp-example__preview {\n  background: var(--ifm-code-background);\n}\n\ncode {\n  border: 0;\n}\n\n.docusaurus-highlight-code-line {\n  background-color: rgba(0, 0, 0, 0.1);\n  display: block;\n  margin: 0 calc(-1 * var(--ifm-pre-padding));\n  padding: 0 var(--ifm-pre-padding);\n}\n\n.ukraine-flag {\n  width: 21px;\n  height: 14px;\n  position: relative;\n  background: #ffcc00;\n  top: 2px;\n  display: inline-block;\n  margin-left: 3px;\n  margin-right: 2px;\n}\n.ukraine-flag:before {\n content:'';\n position: absolute;\n width: 21px;\n height: 7px;\n left:0;\n top:0;\n background: #0066cc;\n}\n\na.hash-link {\n  text-decoration: none;\n  transition: none;\n  padding: 0.5rem 0.7rem;\n}\n\n.theme-code-block--hidden {\n  display: none;\n}\n\n\n.navbar,\n.footer,\n.main-wrapper {\n  width: 100%;\n  max-width: var(--pswp-docs-main-content-width);\n  margin: 0 auto;\n}\n.navbar {\n  z-index: 20;\n  box-shadow: none;\n  border-bottom: 1px solid var(--ifm-toc-border-color);\n}\n.footer {\n  margin-top: 60px;\n  border-top: 1px solid var(--ifm-toc-border-color);\n}\n.docs-wrapper {\n  display: flex;\n  flex-direction: row;\n  align-items: flex-start;\n  justify-content: center;\n}\n.pswp-docs__main-column > article {\n  max-width: 700px;\n}\n.pswp-docs__sidebar-menu {\n  width: 30%;\n  width: 250px;\n}\n\n/*.pswp-docs__main-column {\n  width: 70%;\n} */\n.theme-doc-markdown {\n  /* max-width: 670px; */\n}\n\n@import './sidebar-menu.css';\n@import './docs-page.css';\n@import './example-code-block.css';\n@import './scrollbar.css';\n@import './header.css';\n@import './home.css';"
  },
  {
    "path": "demo-docs-website/src/css/docs-page.css",
    "content": "/* .theme-doc-footer,\n.pagination-nav,\n.theme-doc-markdown > * {\n  max-width: 650px;\n} */\n\n.theme-doc-markdown iframe {\n  width: 100%;\n  height: 450px;\n}\n\n.theme-doc-footer {\n  border-top: 1px solid var(--ifm-toc-border-color);\n  padding: var(--ifm-global-spacing) 0;\n}\nnav.pagination-nav {\n  border-top: 1px solid var(--ifm-toc-border-color);\n  margin-top: 0;\n}\na.pagination-nav__link {\n  border: 0;\n  text-decoration: none;\n  padding: var(--ifm-global-spacing) 0;\n}\na.pagination-nav__link .pagination-nav__sublabel {\n  text-decoration: none;\n}\n.pagination-nav__label {\n  font-size: var(--ifm-h3-font-size);\n}\n.pagination-nav__label::after,\n.pagination-nav__label::before {\n  content: none !important;\n}\n    "
  },
  {
    "path": "demo-docs-website/src/css/example-code-block.css",
    "content": ".pswp-example {\n  width: 100%;\n  position: relative;\n  margin-bottom: 1rem;\n  \n}\n\nspan.docusaurus-highlight-code-line {\n  background: rgba(255, 217, 109, .3);\n}\n\n\n.pswp-example .theme-code-block {\n  width: calc(100% - 204px);\n  margin-bottom: 4px;\n}\n.pswp-example__preview {\n  width: 200px;\n  position: absolute;\n  right: 0;\n  top: 0;\n  height: 100%;\n  overflow-y: auto;\n  margin-left: 4px;\n}\n\n.pswp-example__preview {\n  padding: var(--ifm-pre-padding);\n}\n.pswp-example .pswp-gallery {\n  display: flex;\n  flex-direction: row;\n  align-items: flex-start;\n  flex-wrap: wrap;\n  position: relative;\n  width: 155px;\n  \n}\n\n.pswp-example .pswp-gallery img {\n  display: block;\n}\n.pswp-gallery {\n  display: flex;\n}\n.pswp-gallery > * {\n  display: block;\n  position: relative;\n  margin-bottom: 4px;\n  margin-right: 4px;\n  width: 70px;\n}\n.pswp-example .pswp-gallery > *:nth-child(2n) {\n  margin-right: 0;\n}\n\n.pswp-example .pswp-gallery--single-column > * {\n  width: 120px;\n  margin-bottom: 18px;\n  font-size: 14px;\n}\n\nbutton.pswp-example__hide-html-btn {\n  position: absolute;\n  cursor: pointer;\n  z-index: 10;\n  font-size: 12px;\n  right: 0;\n  bottom: 0;\n  color: var(--ifm-link-color);\n  background: none;\n  border: 0;\n  padding: 2px 5px;\n  background: var(--ifm-pre-background);\n}\nbutton.pswp-example__hide-html-btn:hover {\n  color: var(--ifm-link-hover-color);\n}\n\n[data-cropped] img {\n  width: 100%;\n  height: 70px;\n  object-fit: cover;\n}\n\n@media only screen and (max-width: 600px) {\n  .pswp-example .theme-code-block {\n      width: 100%;\n  }\n  .pswp-example__preview {\n      width: 100%;\n      height: auto;\n      position: relative;\n  }\n  .pswp-example__preview .pswp-gallery {\n      max-width: 150px;\n  }\n}\n\n.pswp-example__code--hidden {\n  display: none;\n}\n\n\ninput[type=\"checkbox\"].hidden-cb {\n  width: 1px;\n  height: 1px;\n  opacity: 0.01;\n  overflow: hidden;\n  position: absolute;\n  right: 0;\n  bottom: 0;\n}\nlabel.pswp-example__toggle {\n  position: absolute;\n  right: 0;\n  bottom: -25px;\n  padding: 0 0;\n  border-radius: 0;\n  font-size: 14px;\n  color: #1B57A5;\n  cursor: pointer;\n  z-index: 20;\n}\nlabel.pswp-example__toggle:hover {\n  color: #c00;\n}\ninput[type=\"checkbox\"]:focus + label {\n  outline: 1px dotted #212121;\n  outline: 5px auto -webkit-focus-ring-color;\n}\ninput[type=\"checkbox\"]:checked {\n  display: none;\n}\ninput[type=\"checkbox\"]:checked + label {\n  display: none;\n}\ninput[type=\"checkbox\"]:checked + label + .pswp-example__code--hidden {\n  display: block !important;\n}"
  },
  {
    "path": "demo-docs-website/src/css/header.css",
    "content": "a.navbar__brand {\n  text-decoration: none;\n  font-size: 20px;\n  line-height: 1;\n  white-space: nowrap;\n  transform: translate(0, -2px);\n}\na.navbar__link {\n  color: var(--ifm-link-color);\n  text-decoration: none;\n}\na.navbar__link--active {\n  color: var(--ifm-font-color-base);\n}\n.navbar__link svg {\n  display: none;\n}\n\na.pswp-docs__github-link {\n  display: flex;\n  flex-direction: row;\n  align-items: flex-start;\n  text-decoration: none;\n  font-weight: var(--ifm-font-weight-semibold);\n}\na.pswp-docs__github-link:hover {\n  text-decoration: none;\n}\n.pswp-docs__github-link svg {\n  transform: translate(0, 2px);\n  margin-right: 3px;\n}\n.pswp-docs__github-link-left {\n  border-top-left-radius: 4px;\n  border-bottom-left-radius: 4px;\n  padding: 2px 8px;\n  border: 1px solid #D5D5D5;\n}\n.pswp-docs__github-link-right {\n  background: #fff;\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 4px;\n  padding: 2px 8px;\n  border: 1px solid #D5D5D5;\n  border-left: 0;\n  color: #222;\n}\n\n.navbar__brand span {\n  font-size: 12px;\n  margin-left: 4px;\n  margin-top: -12px;\n  line-height: 1;\n}\nbutton.DocSearch {\n  --docsearch-searchbox-background: #e9e9e9;\n  --docsearch-primary-color: var(--ifm-link-color);\n  --docsearch-text-color: #222;\n  --docsearch-muted-color: #222;\n  --docsearch-searchbox-shadow: inset 0 0 0 2px var(--docsearch-primary-color);\n  --docsearch-highlight-color: var(--ifm-link-color);\n  height: 35px;\n}\nbutton .DocSearch-Button-Placeholder {\n  font-size: 16px;\n  font-weight: 400;\n}\nbutton .DocSearch-Button-Keys {\n  display: none !important;\n}"
  },
  {
    "path": "demo-docs-website/src/css/home.css",
    "content": ".pswp-docs__home .navbar__brand {\n  display: none;\n}\n.pswp-docs__header-title-text {\n  margin: 60px 0px 50px;\n  font-size: 22px;\n  text-align: center;\n}\n.pswp-docs__whats-new {\n  margin-top: 50px;\n}\n.pswp-docs__whats-new h4 {\n  margin: 50px auto 12px;\n  font-size: 24px;\n}\n\n.pswp-docs__home-block-main-col > * {\n  max-width: 600px;\n  margin-left: auto;\n  margin-right: auto;\n}\n\n.pswp-docs__home-block-full-width-col {\n  max-width: 1068px;\n}\n\n\n\n.pswp-docs__header-title-text a svg {\n  display: inline-block;\n  width: 16px;\n  fill: var(--ifm-link-color);\n  transform: translate(0px, 3px);\n  margin-left: 0.2em;\n}\n.pswp-docs__header-title-text a:hover svg {\n  fill: var(--ifm-link-hover-color);\n}\n\n\n.pswp-docs__header-title-text a {\n  font-weight: bold;\n  text-decoration: none;\n}\n\n.pswp-docs__header-title-text h1 {\n  font-weight: bold;\n  font-size: 64px;\n  line-height: 1;\n  margin-bottom: 14px;\n}\n\n.pswp-docs__header-title-text h1 span {\n  position: absolute;\n  vertical-align: super;\n  font-size: 16px;\n  text-decoration: none;\n}\n\n.pswp-docs__header-title-text p {\n  margin-bottom: 0;\n}\n\n\n\n\n.pswp-docs__home-gallery {\n  position: relative;\n  display: grid;\n  width: 100%;\n  grid-template-columns: 1fr 0.5fr 0.5fr;\n  grid-gap: 10px;\n}\n.pswp-docs__home-gallery-credit {\n  position: absolute;\n  right: 0;\n  bottom: -21px;\n  font-size: 12px;\n}\n.pswp-docs__home-gallery .pswp-docs__home-gallery-item {\n  position: relative;\n}\nfigure.pswp-docs__home-gallery-item {\n  display: block;\n  margin: 0;\n  padding: 0;\n}\n.pswp-docs__home-gallery-item figcaption {\n  display: none;\n}\n\n.pswp-docs__home-gallery .pswp-docs__home-gallery-item img {\n  position: absolute;\n  left: 0;\n  top: 0;\n  width: 100%;\n  height: 100%;\n  object-fit: cover;\n}\n.pswp-docs__home-gallery .pswp-docs__home-gallery-item:first-child {\n  grid-row: span 2;\n}\n.pswp-docs__home-gallery .pswp-docs__home-gallery-item a {\n  display: block;\n  position: relative;\n  overflow: hidden;\n  width: 100%;\n  height: 100%;\n  padding-bottom: 100%;\n}\n\n\n.pswp-docs__home-gallery-example {\n  display: flex;\n  flex-direction: row;\n  align-items: flex-start;\n  flex-wrap: wrap;\n  width: 100%;\n}\n.pswp-docs__home-gallery-example a {\n  position: relative;\n  margin: 0 4px 4px 0;\n  line-height: 0;\n  display: block;\n}\n.pswp-docs__home-gallery-example img {\n  position: absolute;\n  left: 0;\n  top: 0;\n  width: 100%;\n  max-width: none;\n}\n\n.pswp-docs__home-block h2 {\n  font-weight: bold;\n  font-size: 40px;\n  line-height: 1.1;\n  margin: 70px auto 36px;\n}\n\n.pswp__dynamic-caption {\n  font-size: 14px;\n  line-height: 1.5;\n}\n\n#gallery--deep-zoom {\n  display: flex;\n  flex-direction: row;\n  flex-wrap: wrap;\n  align-items: baseline;\n}\n#gallery--deep-zoom figure {\n  display: block;\n  margin: 0;\n  padding: 0;\n  margin-right: 4px;\n  min-width: 100px;\n  max-width: 180px;\n}\n@media (max-width: 900px) {\n  .pswp__dynamic-caption.pswp__dynamic-caption--aside {\n    margin-top: 0;\n  }\n}\n@media (max-width: 700px) {\n  #gallery--deep-zoom {\n    max-width: 296px;\n  }\n}\n\n\n\n#gallery--deep-zoom figure:last-child {\n  margin-right: 0;\n}\n#gallery--deep-zoom figure > a {\n  display: block;\n  position: relative;\n}\n#gallery--deep-zoom img {\n  position: absolute;\n  left: 0;\n  top: 0;\n  width: 100%;\n}\nfigcaption.caption {\n  font-size: 12px;\n  margin-top: 6px;\n}\n\n\n\n@media (max-width: 650px) {\n  .pswp-docs__home-gallery {\n    grid-gap: 5px;\n  }\n  .pswp-docs__header-title-text h1 {\n    font-size: 36px;\n  }\n  .pswp-docs__header-title-text {\n    margin: 40px 0px 30px;\n    font-size: 18px;\n    text-align: center;\n  }\n  .pswp-docs__home-block h2 {\n    font-size: 26px;\n    margin: 40px auto 12px;\n  }\n  .pswp-docs__whats-new h4 {\n    margin: 24px auto 12px;\n    font-size: var(--ifm-font-size-base);\n  }\n  #gallery--deep-zoom {\n    max-width: 296px;\n  }\n}"
  },
  {
    "path": "demo-docs-website/src/css/scrollbar.css",
    "content": "/* style scrollbar */\n.docs-styled-scrollbar::-webkit-scrollbar {\n  width: 6px;\n  height: 6px;\n}\n.docs-styled-scrollbar::-webkit-scrollbar-button {\n  width: 0px;\n  height: 0px;\n}\n.docs-styled-scrollbar::-webkit-scrollbar-thumb {\n  background: #C2C2C2;\n  border: 0;\n  border-radius: 0;\n}\n.docs-styled-scrollbar::-webkit-scrollbar-thumb:hover {\n  background: #aaa;\n}\n.docs-styled-scrollbar::-webkit-scrollbar-thumb:active {\n  background: #999;\n}\n.docs-styled-scrollbar::-webkit-scrollbar-track {\nbackground: none;\nborder: 0;\nborder-radius: 0;\nbackground: #eee;\n}\n.docs-styled-scrollbar::-webkit-scrollbar-track:hover {\n  background: #ddd;\n}\n.docs-styled-scrollbar::-webkit-scrollbar-corner {\nbackground: none;\n}\n"
  },
  {
    "path": "demo-docs-website/src/css/sidebar-menu.css",
    "content": "aside.theme-doc-sidebar-container {\n  border: 0;\n  margin-top: 0;\n}\n\naside.theme-doc-sidebar-container {\n  position: sticky;\n  margin-top: 30px;\n  top: 30px;\n}\n.pswp-docs__sidebar-menu {\n  position: relative;\n  border-right: 1px solid var(--ifm-toc-border-color);\n  display: flex;\n  flex-direction: column;\n  align-items: flex-start;\n  padding: 0 var(--ifm-navbar-padding-horizontal)\n}\n.pswp-docs__sidebar-menu--mobile {\n  position: relative;\n  margin-top: 0;\n  top: auto;\n}\n\n.pswp-docs__sidebar-menu-item  {\n  text-decoration: none;\n  font-size: 16px;\n  line-height: 22px;\n  padding: 4px 0;\n}\n\n.pswp-docs__sidebar-menu-item--category {\n  color: #393939;\n  opacity: 0.5;\n  margin-top: 16px;\n}\n.pswp-docs__sidebar-menu-item--category:first-child {\n  margin-top: 0;;\n}\n\na.pswp-docs__sidebar-menu-item--active {\n  color: var(--ifm-font-color-base);\n}\n\n\n.docSidebarContainer {\n  display: none;\n}\n\n\n@media (max-width: 996px) {\n  .pswp-docs__sidebar-menu {\n    border-right: 0;\n  }\n  .theme-doc-sidebar-container > .pswp-docs__sidebar-menu {\n    display: none;\n  }\n}\n\n@media (min-width: 997px) {\n\n}"
  },
  {
    "path": "demo-docs-website/src/pages/_index-deep-zoom-demo.js",
    "content": "import React, { useEffect } from 'react';\nimport Lightbox from '../../static/photoswipe/photoswipe-lightbox.esm.js';\n\n//import PhotoSwipeDeepZoom from 'photoswipe-deep-zoom-plugin';\n\nconst galleryHTML = `\n  <figure style=\"flex: 132.13213213213;\">\n    <a style=\"padding-bottom:75.6818%\" href=\"https://cdn.photoswipe.com/photoswipe-deep-zoom/old-map/full.jpg\" data-pswp-width=\"1700\" data-pswp-height=\"1285\" data-pswp-tile-url=\"https://cdn.photoswipe.com/photoswipe-deep-zoom/old-map/{z}/{x}_{y}.jpeg\" data-pswp-tile-size=\"254\" data-pswp-tile-overlap=\"1\" data-pswp-max-width=\"5832\" data-pswp-max-height=\"4409\" target=\"_blank\">\n      <img src=\"https://cdn.photoswipe.com/photoswipe-deep-zoom/old-map/thumb.jpg\" alt=\"\">\n    </a>\n    <figcaption class=\"caption\">\n      <a href=\"https://en.wikipedia.org/wiki/Cambriae_Typus\"><strong>Cambriae Typus</strong></a>\n      <br>\n      Humphrey Llwyd\n      <br>\n      5,832px x 4,409px\n    </figcaption>\n  </figure>\n\n  <figure style=\"flex: 80;\">\n    <a style=\"padding-bottom:124.7%\" href=\"https://cdn.photoswipe.com/photoswipe-deep-zoom/a-sergeant-of-the-light-horse/full.jpg\" data-pswp-width=\"1635\" data-pswp-height=\"2048\" data-pswp-tile-type=\"zoomify\" data-pswp-tile-url=\"https://cdn.photoswipe.com/photoswipe-deep-zoom/a-sergeant-of-the-light-horse/TileGroup{zoomify_group}/{z}-{x}-{y}.jpg\" data-pswp-tile-size=\"256\" data-pswp-max-width=\"4578\" data-pswp-max-height=\"5736\" target=\"_blank\">\n      <img src=\"https://cdn.photoswipe.com/photoswipe-deep-zoom/a-sergeant-of-the-light-horse/thumb.jpg\" alt=\"A sergeant of the Light Horse\">\n    </a>\n    <figcaption class=\"caption\">\n      <a href=\"https://en.wikipedia.org/wiki/A_Sergeant_of_the_Light_Horse\"><strong>A sergeant of the Light Horse</strong></a>\n      <br>\n      George Lambert\n      <br>\n      4,578px x 5,736px\n    </figcaption>\n  </figure>\n\n  <figure style=\"flex: 126.34408602151;\">\n    <a style=\"padding-bottom:79.15%\" href=\"https://cdn.photoswipe.com/photoswipe-deep-zoom/starry/starry_files/full.jpg\" data-pswp-width=\"1700\" data-pswp-height=\"1346\" data-pswp-tile-url=\"https://cdn.photoswipe.com/photoswipe-deep-zoom/starry/starry_files/{z}/{x}_{y}.jpeg\" data-pswp-tile-size=\"254\" data-pswp-tile-overlap=\"1\" data-pswp-max-width=\"30000\" data-pswp-max-height=\"23756\" target=\"_blank\">\n      <img src=\"https://cdn.photoswipe.com/photoswipe-deep-zoom/starry/thumb.jpg\" alt=\"\">\n    </a>\n    <figcaption class=\"caption\">\n      <a href=\"https://en.wikipedia.org/wiki/The_Starry_Night\"><strong>The Starry Night</strong></a>\n      <br>\n      Vincent van Gogh\n      <br>\n      30,000px x 23,756px\n    </figcaption>\n  </figure>\n\n  <figure style=\"flex: 93.418259023355;\">\n    <a style=\"padding-bottom:107%\" data-pswp-group-id=\"1\" href=\"https://cdn.photoswipe.com/photoswipe-demo-images/photos/chen-hongshou/large.jpg\" data-pswp-width=\"1820\" data-pswp-height=\"1948\" target=\"_blank\">\n      <img src=\"https://cdn.photoswipe.com/photoswipe-demo-images/photos/chen-hongshou/thumb.jpg\" alt=\"\">\n    </a>\n    <figcaption class=\"caption\">\n      <a href=\"https://en.wikipedia.org/wiki/Chen_Hongshou\"><strong>Magnolia and Erect Rock</strong></a>\n      <br>\n      Chen Hongshou\n      <br>\n      1,820px x 1,948px (not tiled)\n    </figcaption>\n  </figure>\n\n`;\n\nexport default function DeepZoomGalleryDemo() {\n\n  useEffect(() => {\n    let deepZoomPlugin;\n    let lightbox = new Lightbox({\n      gallery: '#gallery--deep-zoom',\n      children: 'figure > a',\n      pswpModule: () => import('../../static/photoswipe/photoswipe.esm.js'),\n\n      // dynamically load deep zoom plugin\n      openPromise: () => {\n        // make sure it's initialized only once per lightbox\n        if (!deepZoomPlugin) {\n          return import('photoswipe-deep-zoom-plugin').then((deepZoomPluginModule) => {\n            deepZoomPlugin = new deepZoomPluginModule.default(lightbox, {\n              // deep zoom plugin options\n            });\n          })\n        }\n      },\n      \n      // Recommended PhotoSwipe options for this plugin\n      allowPanToNext: false, // prevent swiping to the next slide when image is zoomed\n      allowMouseDrag: true, // display dragging cursor at max zoom level\n      wheelToZoom: true, // enable wheel-based zoom\n      zoom: false // disable default zoom button\n    });\n    lightbox.init();\n\n    return function cleanup() {\n      if (lightbox) {\n        lightbox.destroy();\n        lightbox = null;\n      }\n      \n      if (deepZoomPlugin) {\n        deepZoomPlugin.destroy();\n        deepZoomPlugin = null;\n      }\n    };\n  }, []);\n\n  return (\n    <div id=\"gallery--deep-zoom\" dangerouslySetInnerHTML={{__html: galleryHTML}} />\n  )\n}\n"
  },
  {
    "path": "demo-docs-website/src/pages/_index-gallery-header.js",
    "content": "import React, { useEffect } from 'react';\nimport Lightbox from '../../static/photoswipe/photoswipe-lightbox.esm.js';\nimport PhotoSwipe from '../../static/photoswipe/photoswipe.esm.js'\n\nimport PhotoSwipeDynamicCaption from 'photoswipe-dynamic-caption-plugin';\nimport 'photoswipe-dynamic-caption-plugin/photoswipe-dynamic-caption-plugin.css';\n\nconst baseCdnUrl = 'https://cdn.photoswipe.com/photoswipe-demo-images/photos/home-demo/';\nconst imagesData = [\n  {\n    caption: `<strong>Test Caption</strong><br>\n    Dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.`,\n    width: 2500,\n    height: 3125,\n    src: `${baseCdnUrl}luca-bravo-ny6qxqv_m04-unsplash_snrzpf/luca-bravo-ny6qxqv_m04-unsplash_snrzpf_c_scale,w_2500.jpg`,\n    thumbSrc: `${baseCdnUrl}luca-bravo-ny6qxqv_m04-unsplash_snrzpf/luca-bravo-ny6qxqv_m04-unsplash_snrzpf_c_scale,w_664.jpg`,\n    thumbSrcset: `${baseCdnUrl}luca-bravo-ny6qxqv_m04-unsplash_snrzpf/thumb.jpg 524w,\n    ${baseCdnUrl}luca-bravo-ny6qxqv_m04-unsplash_snrzpf/luca-bravo-ny6qxqv_m04-unsplash_snrzpf_c_scale,w_664.jpg 664w,\n    ${baseCdnUrl}luca-bravo-ny6qxqv_m04-unsplash_snrzpf/luca-bravo-ny6qxqv_m04-unsplash_snrzpf_c_scale,w_932.jpg 932w`,\n    srcset: `${baseCdnUrl}luca-bravo-ny6qxqv_m04-unsplash_snrzpf/luca-bravo-ny6qxqv_m04-unsplash_snrzpf_c_scale,w_300.jpg 300w,\n    ${baseCdnUrl}luca-bravo-ny6qxqv_m04-unsplash_snrzpf/luca-bravo-ny6qxqv_m04-unsplash_snrzpf_c_scale,w_664.jpg 664w,\n    ${baseCdnUrl}luca-bravo-ny6qxqv_m04-unsplash_snrzpf/luca-bravo-ny6qxqv_m04-unsplash_snrzpf_c_scale,w_932.jpg 932w,\n    ${baseCdnUrl}luca-bravo-ny6qxqv_m04-unsplash_snrzpf/luca-bravo-ny6qxqv_m04-unsplash_snrzpf_c_scale,w_1355.jpg 1355w,\n    ${baseCdnUrl}luca-bravo-ny6qxqv_m04-unsplash_snrzpf/luca-bravo-ny6qxqv_m04-unsplash_snrzpf_c_scale,w_2500.jpg 2500w`\n  },\n\n\n  {\n    width: 2500,\n    height: 1667,\n    caption: `<strong>Another Test Caption</strong><br>\n    Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.`, \n    src: `${baseCdnUrl}luca-bravo-O453M2Liufs-unsplash_qqt53u/luca-bravo-O453M2Liufs-unsplash_qqt53u_c_scale,w_2500.jpg`,\n    thumbSrc: `${baseCdnUrl}luca-bravo-O453M2Liufs-unsplash_qqt53u/luca-bravo-O453M2Liufs-unsplash_qqt53u_c_scale,w_300.jpg`,\n    thumbSrcset: `${baseCdnUrl}luca-bravo-O453M2Liufs-unsplash_qqt53u/luca-bravo-O453M2Liufs-unsplash_qqt53u_c_scale,w_300.jpg 300w,\n    ${baseCdnUrl}luca-bravo-O453M2Liufs-unsplash_qqt53u/thumb.jpg 393w,\n    ${baseCdnUrl}luca-bravo-O453M2Liufs-unsplash_qqt53u/luca-bravo-O453M2Liufs-unsplash_qqt53u_c_scale,w_612.jpg 612w,\n    ${baseCdnUrl}luca-bravo-O453M2Liufs-unsplash_qqt53u/luca-bravo-O453M2Liufs-unsplash_qqt53u_c_scale,w_826.jpg 826w`,\n    srcset: `${baseCdnUrl}luca-bravo-O453M2Liufs-unsplash_qqt53u/luca-bravo-O453M2Liufs-unsplash_qqt53u_c_scale,w_300.jpg 300w,\n    ${baseCdnUrl}luca-bravo-O453M2Liufs-unsplash_qqt53u/luca-bravo-O453M2Liufs-unsplash_qqt53u_c_scale,w_612.jpg 612w,\n    ${baseCdnUrl}luca-bravo-O453M2Liufs-unsplash_qqt53u/luca-bravo-O453M2Liufs-unsplash_qqt53u_c_scale,w_826.jpg 826w,\n    ${baseCdnUrl}luca-bravo-O453M2Liufs-unsplash_qqt53u/luca-bravo-O453M2Liufs-unsplash_qqt53u_c_scale,w_1115.jpg 1115w,\n    ${baseCdnUrl}luca-bravo-O453M2Liufs-unsplash_qqt53u/luca-bravo-O453M2Liufs-unsplash_qqt53u_c_scale,w_1400.jpg 1400w,\n    ${baseCdnUrl}luca-bravo-O453M2Liufs-unsplash_qqt53u/luca-bravo-O453M2Liufs-unsplash_qqt53u_c_scale,w_2500.jpg 2500w`\n  },\n  {\n    width: 2500,\n    height: 1667,\n    caption: `<strong>Long caption</strong><br>\n    Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore.Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?`, \n    src: `${baseCdnUrl}luca-bravo-VowIFDxogG4-unsplash_ibrktu/luca-bravo-VowIFDxogG4-unsplash_ibrktu_c_scale,w_2500.jpg`,\n    thumbSrc: `${baseCdnUrl}luca-bravo-VowIFDxogG4-unsplash_ibrktu/luca-bravo-VowIFDxogG4-unsplash_ibrktu_c_scale,w_300.jpg`,\n    thumbSrcset: `${baseCdnUrl}luca-bravo-VowIFDxogG4-unsplash_ibrktu/luca-bravo-VowIFDxogG4-unsplash_ibrktu_c_scale,w_300.jpg 300w,\n    ${baseCdnUrl}luca-bravo-VowIFDxogG4-unsplash_ibrktu/thumb.jpg 393w,\n    ${baseCdnUrl}luca-bravo-VowIFDxogG4-unsplash_ibrktu/luca-bravo-VowIFDxogG4-unsplash_ibrktu_c_scale,w_639.jpg 639w`,\n    srcset: `${baseCdnUrl}luca-bravo-VowIFDxogG4-unsplash_ibrktu/luca-bravo-VowIFDxogG4-unsplash_ibrktu_c_scale,w_300.jpg 300w,\n    ${baseCdnUrl}luca-bravo-VowIFDxogG4-unsplash_ibrktu/luca-bravo-VowIFDxogG4-unsplash_ibrktu_c_scale,w_639.jpg 639w,\n    ${baseCdnUrl}luca-bravo-VowIFDxogG4-unsplash_ibrktu/luca-bravo-VowIFDxogG4-unsplash_ibrktu_c_scale,w_1176.jpg 1176w,\n    ${baseCdnUrl}luca-bravo-VowIFDxogG4-unsplash_ibrktu/luca-bravo-VowIFDxogG4-unsplash_ibrktu_c_scale,w_1200.jpg 1200w,\n    ${baseCdnUrl}luca-bravo-VowIFDxogG4-unsplash_ibrktu/luca-bravo-VowIFDxogG4-unsplash_ibrktu_c_scale,w_1385.jpg 1385w,\n    ${baseCdnUrl}luca-bravo-VowIFDxogG4-unsplash_ibrktu/luca-bravo-VowIFDxogG4-unsplash_ibrktu_c_scale,w_1834.jpg 1834w,\n    ${baseCdnUrl}luca-bravo-VowIFDxogG4-unsplash_ibrktu/luca-bravo-VowIFDxogG4-unsplash_ibrktu_c_scale,w_2500.jpg 2500w`\n  },\n\n  {\n    width: 2500,\n    height: 1667,\n    caption: `<strong>Lorem Ipsum</strong><br>Unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.`, \n    src: `${baseCdnUrl}luca-bravo-zAjdgNXsMeg-unsplash_q6zdih/luca-bravo-zAjdgNXsMeg-unsplash_q6zdih_c_scale,w_2500.jpg`,\n    thumbSrc: `${baseCdnUrl}luca-bravo-zAjdgNXsMeg-unsplash_q6zdih/luca-bravo-zAjdgNXsMeg-unsplash_q6zdih_c_scale,w_300.jpg`,\n    thumbSrcset: `${baseCdnUrl}luca-bravo-zAjdgNXsMeg-unsplash_q6zdih/luca-bravo-zAjdgNXsMeg-unsplash_q6zdih_c_scale,w_300.jpg 300w,\n    ${baseCdnUrl}luca-bravo-zAjdgNXsMeg-unsplash_q6zdih/thumb.jpg 393w,\n    ${baseCdnUrl}luca-bravo-zAjdgNXsMeg-unsplash_q6zdih/luca-bravo-zAjdgNXsMeg-unsplash_q6zdih_c_scale,w_627.jpg 627w,\n    ${baseCdnUrl}luca-bravo-zAjdgNXsMeg-unsplash_q6zdih/luca-bravo-zAjdgNXsMeg-unsplash_q6zdih_c_scale,w_861.jpg 861w`,\n    srcset: `${baseCdnUrl}luca-bravo-zAjdgNXsMeg-unsplash_q6zdih/luca-bravo-zAjdgNXsMeg-unsplash_q6zdih_c_scale,w_300.jpg 300w,\n    ${baseCdnUrl}luca-bravo-zAjdgNXsMeg-unsplash_q6zdih/luca-bravo-zAjdgNXsMeg-unsplash_q6zdih_c_scale,w_627.jpg 627w,\n    ${baseCdnUrl}luca-bravo-zAjdgNXsMeg-unsplash_q6zdih/luca-bravo-zAjdgNXsMeg-unsplash_q6zdih_c_scale,w_861.jpg 861w,\n    ${baseCdnUrl}luca-bravo-zAjdgNXsMeg-unsplash_q6zdih/luca-bravo-zAjdgNXsMeg-unsplash_q6zdih_c_scale,w_1057.jpg 1057w,\n    ${baseCdnUrl}luca-bravo-zAjdgNXsMeg-unsplash_q6zdih/luca-bravo-zAjdgNXsMeg-unsplash_q6zdih_c_scale,w_1441.jpg 1441w,\n    ${baseCdnUrl}luca-bravo-zAjdgNXsMeg-unsplash_q6zdih/luca-bravo-zAjdgNXsMeg-unsplash_q6zdih_c_scale,w_1881.jpg 1881w,\n    ${baseCdnUrl}luca-bravo-zAjdgNXsMeg-unsplash_q6zdih/luca-bravo-zAjdgNXsMeg-unsplash_q6zdih_c_scale,w_2500.jpg 2500w`\n  },\n\n\n  {\n    width: 2500,\n    height: 1667,\n    caption: `<strong>Test Caption</strong><br/>\n    Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?<br/>`, \n    src: `${baseCdnUrl}luca-bravo-A-fubu9QJxE-unsplash_jxy5p8/luca-bravo-A-fubu9QJxE-unsplash_jxy5p8_c_scale,w_2500.jpg`,\n    thumbSrc: `${baseCdnUrl}luca-bravo-A-fubu9QJxE-unsplash_jxy5p8/luca-bravo-A-fubu9QJxE-unsplash_jxy5p8_c_scale,w_300.jpg`,\n    thumbSrcset: `${baseCdnUrl}luca-bravo-A-fubu9QJxE-unsplash_jxy5p8/luca-bravo-A-fubu9QJxE-unsplash_jxy5p8_c_scale,w_300.jpg 300w,\n    ${baseCdnUrl}luca-bravo-A-fubu9QJxE-unsplash_jxy5p8/thumb.jpg 393w,\n    ${baseCdnUrl}luca-bravo-A-fubu9QJxE-unsplash_jxy5p8/luca-bravo-A-fubu9QJxE-unsplash_jxy5p8_c_scale,w_649.jpg 649w,\n    ${baseCdnUrl}luca-bravo-A-fubu9QJxE-unsplash_jxy5p8/luca-bravo-A-fubu9QJxE-unsplash_jxy5p8_c_scale,w_996.jpg 996w`,\n    srcset: `${baseCdnUrl}luca-bravo-A-fubu9QJxE-unsplash_jxy5p8/luca-bravo-A-fubu9QJxE-unsplash_jxy5p8_c_scale,w_300.jpg 300w,\n    ${baseCdnUrl}luca-bravo-A-fubu9QJxE-unsplash_jxy5p8/luca-bravo-A-fubu9QJxE-unsplash_jxy5p8_c_scale,w_649.jpg 649w,\n    ${baseCdnUrl}luca-bravo-A-fubu9QJxE-unsplash_jxy5p8/luca-bravo-A-fubu9QJxE-unsplash_jxy5p8_c_scale,w_996.jpg 996w,\n    ${baseCdnUrl}luca-bravo-A-fubu9QJxE-unsplash_jxy5p8/luca-bravo-A-fubu9QJxE-unsplash_jxy5p8_c_scale,w_1188.jpg 1188w,\n    ${baseCdnUrl}luca-bravo-A-fubu9QJxE-unsplash_jxy5p8/luca-bravo-A-fubu9QJxE-unsplash_jxy5p8_c_scale,w_1303.jpg 1303w,\n    ${baseCdnUrl}luca-bravo-A-fubu9QJxE-unsplash_jxy5p8/luca-bravo-A-fubu9QJxE-unsplash_jxy5p8_c_scale,w_1840.jpg 1840w,\n    ${baseCdnUrl}luca-bravo-A-fubu9QJxE-unsplash_jxy5p8/luca-bravo-A-fubu9QJxE-unsplash_jxy5p8_c_scale,w_2500.jpg 2500w`\n  },\n\n\n];\n\nfunction GalleryItem(props) {\n  const attributes = {\n    style: { \n      paddingBottom:  props.cropped ? 1 / props.aspectRatio * 100 + '%'  : props.height / props.width * 100 + '%'\n    },\n    href: props.src,\n    'data-pswp-srcset': props.srcset,\n    'data-pswp-width': props.width,\n    'data-pswp-height': props.height,\n    target: '_blank',\n    ...( props.cropped && { 'data-cropped': true } )\n  };\n\n  const figureAttributes = {\n    style: {},\n    className: 'pswp-docs__home-gallery-item'\n  };\n\n  let sizes = props.sizes;\n  if (props.justifiedRow) {\n    figureAttributes.style.flex = props.width / props.height * 100;\n    const widthRatio = ( props.width / props.height) / props.aspectRatioSumm;\n    sizes = `(min-width: 1124px) ${Math.ceil(600 * widthRatio)}px, ${Math.ceil(100 * widthRatio)}vw`;\n  }\n\n  return (\n    <figure {...figureAttributes}>\n      <a {...attributes}>\n      <img\n        sizes={sizes}\n        srcSet={props.thumbSrcset}\n        src={props.thumbSrc}\n        alt=\"\" />\n      { props.caption && <figcaption dangerouslySetInnerHTML={{__html: props.caption}}></figcaption> }\n      </a>\n    </figure>\n  );\n}\n\nexport function GalleryExampleOpenZoomed(props) {\n  useEffect(() => {\n    let lightbox = new Lightbox({\n      gallery: '#gallery--open-zooomed figure > a',\n      initialZoomLevel: 'fill',\n      secondaryZoomLevel: 'fill',\n      maxZoomLevel: 3,\n      pswpModule: PhotoSwipe\n    });\n    lightbox.init();\n\n    return function cleanup() {\n      lightbox.destroy();\n      lightbox = null;\n    };\n  }, []);\n\n  return (\n    <GalleryExample id=\"gallery--open-zooomed\" items={[3, 4]} cropped={false} justifiedRow={true} />\n  )\n}\n\n\nexport function GalleryExampleDynamicCaptionPlugin(props) {\n  useEffect(() => {\n    const smallScreenPadding = {\n      top: 0, bottom: 0, left: 0, right: 0\n    };\n    const largeScreenPadding = {\n      top: 20, bottom: 20, left: 50, right: 50\n    };\n    let lightbox = new Lightbox({\n      gallery: '#gallery--dynamic-caption',\n      children: '.pswp-docs__home-gallery-item > a',\n      paddingFn: (viewportSize) => {\n        return viewportSize.x < 700 ? smallScreenPadding : largeScreenPadding\n      },\n      pswpModule: () => import('../../static/photoswipe/photoswipe.esm.js')\n    });\n    lightbox.init();\n\n    let captionPlugin = new PhotoSwipeDynamicCaption(lightbox, {\n      type: 'auto',\n      captionContent: 'figcaption'\n    });\n\n    return function cleanup() {\n      lightbox.destroy();\n      lightbox = null;\n      captionPlugin = null;\n    };\n  }, []);\n\n  return (\n    <GalleryExample id=\"gallery--dynamic-caption\" items={[0,1,2]} cropped={false} justifiedRow={true} />\n  )\n}\n\n\nexport function GalleryExample(props) {\n\n  const aspectRatioSumm = imagesData.reduce((summ, image, index) => {\n    if (props.items && !props.items.includes(index)) {\n      return summ;\n    }\n    return summ + image.width / image.height;\n  }, 0);\n\n  const galleryItems = imagesData.map((image, index) => {\n    if (props.items && !props.items.includes(index)) {\n      return;\n    }\n\n    return <GalleryItem \n      key={index} \n      {...image} \n      cropped={props.cropped} \n      aspectRatio={1} \n      aspectRatioSumm={aspectRatioSumm}\n      justifiedRow={props.justifiedRow} />\n  });\n\n  return (\n    <div className=\"pswp-docs__home-gallery-example\" id={props.id} data-summ={aspectRatioSumm}>\n      {galleryItems}\n    </div>\n  );\n}\n\nexport default function GalleryHeader() {\n  useEffect(() => {\n    let lightbox = new Lightbox({\n      gallery: '#gallery--header-home',\n      children: '.pswp-docs__home-gallery-item > a',\n      pswpModule: () => import('../../static/photoswipe/photoswipe.esm.js')\n    });\n    lightbox.init();\n\n    return function cleanup() {\n      lightbox.destroy();\n      lightbox = null;\n    };\n  }, []);\n\n  return (\n    <div className=\"pswp-docs__home-gallery\" id=\"gallery--header-home\">\n      <GalleryItem {...imagesData[0]}\n        aspectRatio={1} \n        cropped={true}\n        sizes=\"(min-width: 1124px) 524px, 50vw\" />\n      <GalleryItem {...imagesData[1]}\n        aspectRatio={1} \n        cropped={true}\n        sizes=\"(min-width: 1124px) 393px, 35vw\" />\n      <GalleryItem {...imagesData[2]}\n        aspectRatio={1} \n        cropped={true}\n        sizes=\"(min-width: 1124px) 393px, 35vw\" />\n      <GalleryItem {...imagesData[3]}\n        aspectRatio={1}\n        cropped={true}\n        sizes=\"(min-width: 1124px) 393px, 35vw\" />\n      <GalleryItem {...imagesData[4]}\n        aspectRatio={1} \n        cropped={true}\n        sizes=\"(min-width: 1124px) 393px, 35vw\" />\n      <div className='pswp-docs__home-gallery-credit'>Photos by Luca Bravo</div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "demo-docs-website/src/pages/index.js",
    "content": "import React, { useEffect } from 'react';\nimport Layout from '@theme/Layout';\nimport useDocusaurusContext from '@docusaurus/useDocusaurusContext';\nimport GalleryHeader, { GalleryExampleDynamicCaptionPlugin, GalleryExampleOpenZoomed } from './_index-gallery-header';\nimport CodeBlock from '../theme/CodeBlock';\nimport packageInfo from '../../../package.json';\nimport DeepZoomGalleryDemo from './_index-deep-zoom-demo';\nimport Head from '@docusaurus/Head';\n\nfunction HomepageHeader() {\n  return (\n    <div className=\"pswp-docs__home-block pswp-docs__header-intro container\">\n      <Head>\n        <html className=\"pswp-docs__home\" />\n        <title>PhotoSwipe: Responsive JavaScript Image Gallery</title>\n      </Head>\n      <div className=\"row\">\n        <div className=\"col col--12 pswp-docs__header-title-text\">\n          <h1>PhotoSwipe <span>{packageInfo.version}</span></h1>\n          <p>JavaScript image gallery and lightbox</p>\n          <a href=\"/getting-started\">Documentation and examples<svg viewBox=\"0 0 448 512\"><path d=\"M438.6 278.6l-160 160C272.4 444.9 264.2 448 256 448s-16.38-3.125-22.62-9.375c-12.5-12.5-12.5-32.75 0-45.25L338.8 288H32C14.33 288 .0016 273.7 .0016 256S14.33 224 32 224h306.8l-105.4-105.4c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0l160 160C451.1 245.9 451.1 266.1 438.6 278.6z\"/></svg></a>\n        </div>\n      </div>\n      <div className=\"row\">\n        <div className=\"col col--12\">\n          <GalleryHeader />\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction WhatsNew() {\n  const initCodeExample = `<script type=\"module\">\nimport Lightbox from './photoswipe-lightbox.esm.js';\nconst lightbox = new Lightbox({\n  gallery: '#my-gallery',\n  children: 'a',\n  pswpModule: () => import('./photoswipe.esm.js')\n});\nlightbox.init();\n</script>`;\n\n  return (\n    <div className=\"pswp-docs__home-block container pswp-docs__whats-new\">\n      <div className=\"row\">\n        <div className=\"col col--12 pswp-docs__home-block-main-col\">\n          <h2>What’s new in v5</h2>\n\n          <h4>Code quality and rewrite in ES6</h4>\n          <p>The script is now distributed as an ES module and does not require a build step to use. <a href=\"/getting-started\">The documentation</a> is also updated and now includes more examples.</p>\n\n          <h4>Simpler initialization and dynamic import support</h4>\n          <p>PhotoSwipe now supports dynamic import and does not block page rendering.</p>\n          <CodeBlock language=\"html\">{initCodeExample}</CodeBlock>\n\n          <h4>Animation and gesture engine update</h4>\n          <p>Improved performance of most animations, touch gestures should feel more fluid now. \n            The initial opening or closing <a href=\"/opening-or-closing-transition#animating-from-cropped-thumbnail\">transition can be run from a CSS-cropped thumbnail</a>, as you can see on the top of this page.</p>\n\n          <h4>Single CSS file and no external assets</h4>\n          <p>Using CSS variables, default icons are dynamically generated and tiny.<br/><a href=\"/styling\">Styling guide &rarr;</a></p>\n\n          <h4>Built-in responsive images support</h4>\n          <p>PhotoSwipe also dynamically loads larger images as a user zooms via srcset.</p>\n\n          <h4>Open images in a zoomed state</h4>\n          <p>It's now much easier to control zoom level, refer to the <a href=\"/adjusting-zoom-level\">Adjusting Zoom Level</a> section of docs for more info. The example below opens images in a zoomed state and individually.</p>\n          <GalleryExampleOpenZoomed />\n\n          <h4>Removed features from the core</h4>\n          <p>Some built-in features were removed in v5, either because they are using outdated technology or just rarely used. Some of them are or will be replaced by a plugin. These include:</p>\n          <ul>\n            <li>History API (#hash-based navigation is outdated)</li>\n            <li>Social sharing (unreliable URL, lack of Opengraph support)</li>\n            <li>Fullscreen button (rarely used, double fullscreen). <a href=\"/native-fullscreen-on-open\">Related example in docs &rarr;</a></li>\n            <li>Caption (accessibility problems). Refer to the <a href=\"/caption\">caption section of docs</a>.</li>\n            <li>Inline gallery support (v5 is mainly designed to be used as a dialog).</li>\n          </ul>\n\n          <h2>Plugins</h2>\n\n          <h4><a href=\"https://github.com/dimsemenov/photoswipe-dynamic-caption-plugin\">Dynamic Caption plugin</a></h4>\n          \n          <p>\n            A plugin that dynamically positions the caption below or aside \n            depending on the available free space.<br/>\n          </p>\n          <GalleryExampleDynamicCaptionPlugin />\n\n          <h4><a href=\"https://github.com/dimsemenov/photoswipe-deep-zoom-plugin\">Tiled Deep Zoom plugin</a> (experimental)</h4>\n          <p>\n            Tile-based image viewer that allows displaying of extremely large images.\n            Unlike conventional tile-viewers (such as Leaflet or OpenSeaDragon) \n            it displays tiles only after the user zooms beyond the primary image,\n            and keeps all default PhotoSwipe navigation between slides.<br/>\n          </p>\n          <DeepZoomGalleryDemo />\n\n\n          <h2>License</h2>\n\n          <p>PhotoSwipe is free for personal or commercial projects (MIT license).<br/>Please <a href=\"https://opencollective.com/photoswipe\">support the development on Open Collective</a> if you find it useful.</p>\n\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default function Home() {\n  const {siteConfig} = useDocusaurusContext();\n  return (\n    <Layout\n      description=\"Open-source JavaScript image gallery and lightbox.\">\n      <HomepageHeader />\n      <WhatsNew />\n    </Layout>\n  );\n}\n"
  },
  {
    "path": "demo-docs-website/src/pages/index.module.css",
    "content": "/**\n * CSS files with the .module.css suffix will be treated as CSS modules\n * and scoped locally.\n */"
  },
  {
    "path": "demo-docs-website/src/theme/CodeBlock/index.js",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport React, {isValidElement, useEffect, useState} from 'react';\nimport clsx from 'clsx';\nimport Highlight, {defaultProps} from 'prism-react-renderer';\nimport {\n  useThemeConfig,\n  parseCodeBlockTitle,\n  parseLanguage,\n  parseLines,\n  ThemeClassNames,\n  usePrismTheme,\n} from '@docusaurus/theme-common';\nimport styles from './styles.module.css';\nexport default function CodeBlock({\n  children,\n  className: blockClassName = '',\n  metastring,\n  title,\n  pswpcode,\n  pswpdisplayhtml,\n  displayHTML,\n  language: languageProp,\n}) {\n  const {prism} = useThemeConfig();\n  const [mounted, setMounted] = useState(false); // The Prism theme on SSR is always the default theme but the site theme\n  // can be in a different mode. React hydration doesn't update DOM styles\n  // that come from SSR. Hence force a re-render after mounting to apply the\n  // current relevant styles. There will be a flash seen of the original\n  // styles seen using this current approach but that's probably ok. Fixing\n  // the flash will require changing the theming approach and is not worth it\n  // at this point.\n\n  let esModuleScript;\n\n  useEffect(() => {\n    setMounted(true);\n    if (pswpcode && language === 'js' && !esModuleScript) {\n      esModuleScript = document.createElement('script');\n      esModuleScript.type = 'module';\n      esModuleScript.innerHTML = children;           \n      document.body.appendChild(esModuleScript);\n    }\n\n    return () => {\n      if (esModuleScript) {\n        esModuleScript.remove();\n        esModuleScript = null;\n      }\n    };\n  }, []); // We still parse the metastring in case we want to support more syntax in the\n  // future. Note that MDX doesn't strip quotes when parsing metastring:\n  // \"title=\\\"xyz\\\"\" => title: \"\\\"xyz\\\"\"\n\n  const codeBlockTitle = parseCodeBlockTitle(metastring) || title;\n  const prismTheme = usePrismTheme(); // <pre> tags in markdown map to CodeBlocks and they may contain JSX children.\n  // When the children is not a simple string, we just return a styled block\n  // without actually highlighting.\n\n  if (React.Children.toArray(children).some((el) => isValidElement(el))) {\n    return (\n      <Highlight\n        {...defaultProps}\n        key={String(mounted)}\n        theme={prismTheme}\n        code=\"\"\n        language={'text'}>\n        {({className, style}) => (\n          <pre\n            /* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */\n            tabIndex={0}\n            className={clsx(\n              className,\n              styles.codeBlockStandalone,\n              'docs-styled-scrollbar',\n              styles.codeBlockContainer,\n              blockClassName,\n              ThemeClassNames.common.codeBlock,\n            )}\n            style={style}>\n            <code className={styles.codeBlockLines}>{children}</code>\n          </pre>\n        )}\n      </Highlight>\n    );\n  } // The children is now guaranteed to be one/more plain strings\n\n  const content = Array.isArray(children) ? children.join('') : children;\n  const language =\n    languageProp ?? parseLanguage(blockClassName) ?? prism.defaultLanguage;\n  const {highlightLines, code} = parseLines(content, metastring, language);\n\n  let hideCodeBlock = false;\n\n  if (language === 'html' && pswpcode && !displayHTML && !pswpdisplayhtml) {\n    hideCodeBlock = true;\n  }\n  \n\n  return (\n    <React.Fragment>\n      {!hideCodeBlock && <Highlight\n        {...defaultProps}\n        key={String(mounted)}\n        theme={prismTheme}\n        code={code}\n        language={language ?? 'text'}>\n        {({className, style, tokens, getLineProps, getTokenProps}) => (\n          <div\n            className={clsx(\n              styles.codeBlockContainer,\n              blockClassName,\n              hideCodeBlock ? 'theme-code-block--hidden' : '',\n              {\n                [`language-${language}`]:\n                  language && !blockClassName.includes(`language-${language}`),\n              },\n              ThemeClassNames.common.codeBlock,\n            )}>\n            {codeBlockTitle && (\n              <div style={style} className={styles.codeBlockTitle}>\n                {codeBlockTitle}\n              </div>\n            )}\n            <div className={clsx(styles.codeBlockContent, language)}>\n              <pre\n                /* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */\n                tabIndex={0}\n                className={clsx(className, styles.codeBlock, 'docs-styled-scrollbar')}\n                style={style}>\n                <code className={styles.codeBlockLines}>\n                  {tokens.map((line, i) => {\n                    if (line.length === 1 && line[0].content === '\\n') {\n                      line[0].content = '';\n                    }\n\n                    const lineProps = getLineProps({\n                      line,\n                      key: i,\n                    });\n\n                    if (highlightLines.includes(i)) {\n                      lineProps.className += ' docusaurus-highlight-code-line';\n                    }\n\n                    return (\n                      <span key={i} {...lineProps}>\n                        {line.map((token, key) => (\n                          <span\n                            key={key}\n                            {...getTokenProps({\n                              token,\n                              key,\n                            })}\n                          />\n                        ))}\n                        <br />\n                      </span>\n                    );\n                  })}\n                </code>\n              </pre>\n            </div>\n          </div>\n        )}\n      </Highlight> }\n      {language === 'css' && pswpcode &&\n        <style>{children}</style>\n      }\n      {language === 'html' && pswpcode && \n        <div className=\"pswp-example__preview docs-styled-scrollbar\" role=\"region\" aria-label=\"Code preview\" dangerouslySetInnerHTML={{__html: children}}></div>\n      }\n    </React.Fragment>\n  );\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/CodeBlock/styles.module.css",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.codeBlockContainer {\n  margin-bottom: var(--ifm-leading);\n}\n\n.codeBlockContent {\n  position: relative;\n  /* rtl:ignore */\n  direction: ltr;\n}\n\n.codeBlockContent pre {\n  border-radius: 0;\n}\n\n.codeBlockTitle {\n  border-bottom: 1px solid var(--ifm-color-emphasis-300);\n  font-size: var(--ifm-code-font-size);\n  font-weight: 500;\n  padding: 0.75rem var(--ifm-pre-padding);\n  border-top-left-radius: var(--ifm-global-radius);\n  border-top-right-radius: var(--ifm-global-radius);\n}\n\n.codeBlock {\n  margin: 0;\n  padding: 0;\n}\n\n.codeBlockTitle + .codeBlockContent .codeBlock {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n\n.codeBlockStandalone {\n  padding: 0;\n  border-radius: var(--ifm-global-radius);\n}\n\n.codeBlockLines {\n  font: inherit;\n  /* rtl:ignore */\n  float: left;\n  min-width: 100%;\n  padding: var(--ifm-pre-padding);\n}\n\n@media print {\n  .codeBlockLines {\n    white-space: pre-wrap;\n  }\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/DocItem/index.js",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport React from 'react';\nimport clsx from 'clsx';\nimport DocPaginator from '@theme/DocPaginator';\nimport DocVersionBanner from '@theme/DocVersionBanner';\nimport DocVersionBadge from '@theme/DocVersionBadge';\nimport Seo from '@theme/Seo';\nimport DocItemFooter from '@theme/DocItemFooter';\nimport TOC from '@theme/TOC';\nimport TOCCollapsible from '@theme/TOCCollapsible';\nimport Heading from '@theme/Heading';\nimport styles from './styles.module.css';\nimport {ThemeClassNames, useWindowSize} from '@docusaurus/theme-common';\nimport DocBreadcrumbs from '@theme/DocBreadcrumbs';\nexport default function DocItem(props) {\n  const {content: DocContent} = props;\n  const {metadata, frontMatter, assets} = DocContent;\n  const {\n    keywords,\n    hide_title: hideTitle,\n    hide_table_of_contents: hideTableOfContents,\n    toc_min_heading_level: tocMinHeadingLevel,\n    toc_max_heading_level: tocMaxHeadingLevel,\n  } = frontMatter;\n  const {description, title} = metadata;\n  const image = assets.image ?? frontMatter.image; // We only add a title if:\n  // - user asks to hide it with front matter\n  // - the markdown content does not already contain a top-level h1 heading\n\n  const shouldAddTitle =\n    !hideTitle && typeof DocContent.contentTitle === 'undefined';\n  const windowSize = useWindowSize();\n  const canRenderTOC =\n    !hideTableOfContents && DocContent.toc && DocContent.toc.length > 0;\n  const renderTocDesktop =\n    canRenderTOC && (windowSize === 'desktop' || windowSize === 'ssr');\n  return (\n    <>\n      <Seo\n        {...{\n          title,\n          description,\n          keywords,\n          image,\n        }}\n      />\n    \n      <DocVersionBanner />\n        <article>\n          <DocBreadcrumbs />\n          <DocVersionBadge />\n\n          <div\n            className={clsx(ThemeClassNames.docs.docMarkdown, 'markdown')}>\n            {/*\n            Title can be declared inside md content or declared through\n            front matter and added manually. To make both cases consistent,\n            the added title is added under the same div.markdown block\n            See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120\n            */}\n            {shouldAddTitle && (\n              <header>\n                <Heading as=\"h1\">{title}</Heading>\n              </header>\n            )}\n\n            <DocContent />\n          </div>\n\n          <DocItemFooter {...props} />\n        </article>\n    </>\n  );\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/DocItem/styles.module.css",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.docItemContainer header + *,\n.docItemContainer article > *:first-child {\n  margin-top: 0;\n}\n\n@media (min-width: 997px) {\n  /* Prevent hydration FOUC, as the mobile TOC needs to be server-rendered */\n  .tocMobile {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/DocItemFooter/index.js",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport React from 'react';\nimport clsx from 'clsx';\nimport LastUpdated from '@theme/LastUpdated';\nimport EditThisPage from '@theme/EditThisPage';\nimport TagsListInline from '@theme/TagsListInline';\nimport styles from './styles.module.css';\nimport {ThemeClassNames} from '@docusaurus/theme-common';\n\nfunction TagsRow(props) {\n  return (\n    <div\n      className={clsx(\n        ThemeClassNames.docs.docFooterTagsRow,\n        'row margin-bottom--sm',\n      )}>\n      <div className=\"col\">\n        <TagsListInline {...props} />\n      </div>\n    </div>\n  );\n}\n\nfunction EditMetaRow({\n  editUrl,\n  lastUpdatedAt,\n  lastUpdatedBy,\n  formattedLastUpdatedAt,\n}) {\n  return (\n    <div className={clsx(ThemeClassNames.docs.docFooterEditMetaRow, 'row')}>\n      <div className=\"col\">\n        {editUrl && <EditThisPage editUrl={editUrl} />}\n        <br/>\n        Updates on <a href=\"https://twitter.com/photoswipe\">Twitter</a>\n        <br/>  \n        Code on <a href=\"https://github.com/dimsemenov/photoswipe\">GitHub</a>\n        <br/>\n        Sponsor on <a href=\"https://opencollective.com/photoswipe\">Open Collective</a>\n      </div>\n\n      <div className={clsx('col', styles.lastUpdated)}>\n        {(lastUpdatedAt || lastUpdatedBy) && (\n          <LastUpdated\n            lastUpdatedAt={lastUpdatedAt}\n            formattedLastUpdatedAt={formattedLastUpdatedAt}\n            lastUpdatedBy={lastUpdatedBy}\n          />\n        )}\n      </div>\n    </div>\n  );\n}\n\nexport default function DocItemFooter(props) {\n  const {content: DocContent} = props;\n  const {metadata} = DocContent;\n  const {editUrl, lastUpdatedAt, formattedLastUpdatedAt, lastUpdatedBy, tags} =\n    metadata;\n  const canDisplayTagsRow = tags.length > 0;\n  const canDisplayEditMetaRow = !!(editUrl || lastUpdatedAt || lastUpdatedBy);\n  const canDisplayFooter = canDisplayTagsRow || canDisplayEditMetaRow;\n\n  if (!canDisplayFooter) {\n    return null;\n  }\n\n  return (\n    <footer\n      className={clsx(ThemeClassNames.docs.docFooter, 'docusaurus-mt-lg')}>\n      {canDisplayTagsRow && <TagsRow tags={tags} />}\n      {canDisplayEditMetaRow && (\n        <EditMetaRow\n          editUrl={editUrl}\n          lastUpdatedAt={lastUpdatedAt}\n          lastUpdatedBy={lastUpdatedBy}\n          formattedLastUpdatedAt={formattedLastUpdatedAt}\n        />\n      )}\n    </footer>\n  );\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/DocItemFooter/styles.module.css",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.lastUpdated {\n  margin-top: 0.2rem;\n  font-style: italic;\n  font-size: smaller;\n}\n\n@media (min-width: 997px) {\n  .lastUpdated {\n    text-align: right;\n  }\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/DocPage/index.js",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport React, {useState, useCallback} from 'react';\nimport {MDXProvider} from '@mdx-js/react';\nimport renderRoutes from '@docusaurus/renderRoutes';\nimport Layout from '@theme/Layout';\nimport DocSidebar from '@theme/DocSidebar';\nimport MDXComponents from '@theme/MDXComponents';\nimport NotFound from '@theme/NotFound';\nimport IconArrow from '@theme/IconArrow';\nimport {matchPath} from '@docusaurus/router';\nimport {translate} from '@docusaurus/Translate';\nimport clsx from 'clsx';\nimport styles from './styles.module.css';\nimport {\n  ThemeClassNames,\n  docVersionSearchTag,\n  DocsSidebarProvider,\n  useDocsSidebar,\n  DocsVersionProvider,\n} from '@docusaurus/theme-common';\nimport Head from '@docusaurus/Head';\n\nfunction DocPageContent({\n  currentDocRoute,\n  versionMetadata,\n  children,\n  sidebarName,\n}) {\n  const sidebar = useDocsSidebar();\n  const {pluginId, version} = versionMetadata;\n  const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false);\n  const [hiddenSidebar, setHiddenSidebar] = useState(false);\n  const toggleSidebar = useCallback(() => {\n    if (hiddenSidebar) {\n      setHiddenSidebar(false);\n    }\n\n    setHiddenSidebarContainer((value) => !value);\n  }, [hiddenSidebar]);\n  return (\n    <Layout\n      wrapperClassName={ThemeClassNames.wrapper.docsPages}\n      pageClassName={ThemeClassNames.page.docsDocPage}\n      searchMetadata={{\n        version,\n        tag: docVersionSearchTag(pluginId, version),\n      }}>\n        {sidebar && (\n          <aside\n            className={clsx(\n              ThemeClassNames.docs.docSidebarContainer,\n              styles.docSidebarContainer,\n              {\n                [styles.docSidebarContainerHidden]: hiddenSidebarContainer,\n              },\n            )}\n            onTransitionEnd={(e) => {\n              if (\n                !e.currentTarget.classList.contains(styles.docSidebarContainer)\n              ) {\n                return;\n              }\n\n              if (hiddenSidebarContainer) {\n                setHiddenSidebar(true);\n              }\n            }}>\n            <DocSidebar\n              key={\n                // Reset sidebar state on sidebar changes\n                // See https://github.com/facebook/docusaurus/issues/3414\n                sidebarName\n              }\n              sidebar={sidebar}\n              path={currentDocRoute.path}\n              onCollapse={toggleSidebar}\n              isHidden={hiddenSidebar}\n            />\n\n            {hiddenSidebar && (\n              <div\n                className={styles.collapsedDocSidebar}\n                title={translate({\n                  id: 'theme.docs.sidebar.expandButtonTitle',\n                  message: 'Expand sidebar',\n                  description:\n                    'The ARIA label and title attribute for expand button of doc sidebar',\n                })}\n                aria-label={translate({\n                  id: 'theme.docs.sidebar.expandButtonAriaLabel',\n                  message: 'Expand sidebar',\n                  description:\n                    'The ARIA label and title attribute for expand button of doc sidebar',\n                })}\n                tabIndex={0}\n                role=\"button\"\n                onKeyDown={toggleSidebar}\n                onClick={toggleSidebar}>\n                <IconArrow className={styles.expandSidebarButtonIcon} />\n              </div>\n            )}\n          </aside>\n        )}\n        <main\n          className={clsx('pswp-docs__main-column container padding-top--md padding-bottom--lg', styles.docMainContainer, {\n            [styles.docMainContainerEnhanced]:\n              hiddenSidebarContainer || !sidebar,\n          })}>\n            <MDXProvider components={MDXComponents}>{children}</MDXProvider>\n        </main>\n    </Layout>\n  );\n}\n\nexport default function DocPage(props) {\n  const {\n    route: {routes: docRoutes},\n    versionMetadata,\n    location,\n  } = props;\n  const currentDocRoute = docRoutes.find((docRoute) =>\n    matchPath(location.pathname, docRoute),\n  );\n\n  if (!currentDocRoute) {\n    return <NotFound />;\n  } // For now, the sidebarName is added as route config: not ideal!\n\n  const sidebarName = currentDocRoute.sidebar;\n  const sidebar = sidebarName\n    ? versionMetadata.docsSidebars[sidebarName]\n    : null;\n  return (\n    <>\n      <Head>\n        {/* TODO we should add a core addRoute({htmlClassName}) action */}\n        <html className={versionMetadata.className} />\n      </Head>\n      <DocsVersionProvider version={versionMetadata}>\n        <DocsSidebarProvider sidebar={sidebar}>\n          <DocPageContent\n            currentDocRoute={currentDocRoute}\n            versionMetadata={versionMetadata}\n            sidebarName={sidebarName}>\n            {renderRoutes(docRoutes, {\n              versionMetadata,\n            })}\n          </DocPageContent>\n        </DocsSidebarProvider>\n      </DocsVersionProvider>\n    </>\n  );\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/DocPage/styles.module.css",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */"
  },
  {
    "path": "demo-docs-website/src/theme/DocSidebar/index.js",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport React, {useState} from 'react';\nimport clsx from 'clsx';\nimport {useLocation} from '@docusaurus/router';\n\n\nimport {\n  useThemeConfig,\n  useAnnouncementBar,\n  MobileSecondaryMenuFiller,\n  ThemeClassNames,\n  useScrollPosition,\n  useWindowSize,\n} from '@docusaurus/theme-common';\n\nimport styles from './styles.module.css';\n\nfunction useShowAnnouncementBar() {\n  const {isActive} = useAnnouncementBar();\n  const [showAnnouncementBar, setShowAnnouncementBar] = useState(isActive);\n  useScrollPosition(\n    ({scrollY}) => {\n      if (isActive) {\n        setShowAnnouncementBar(scrollY === 0);\n      }\n    },\n    [isActive],\n  );\n  return isActive && showAnnouncementBar;\n}\n\nfunction SimpleSidebarCategory(props) {\n  return (\n    <div className=\"pswp-docs__sidebar-menu-item pswp-docs__sidebar-menu-item--category\">{props.label}</div>\n  );\n}\nfunction SimpleSidebarItem(props) {\n  const location = useLocation();\n  const isActive = (props.href.toLowerCase() === location.pathname.toLowerCase());\n    return (\n    <a href={props.href} className={clsx(\n      'pswp-docs__sidebar-menu-item',\n      (isActive ? 'pswp-docs__sidebar-menu-item--active' : '')\n    )}>{props.label}</a>\n  );\n}\nfunction SimpleSidebar(props) {\n  const sidebarItems = [];\n\n  let index = 0;\n  props.sidebar.forEach((sidebarItem) => {\n    if (sidebarItem.type === 'category') {\n      sidebarItems.push(<SimpleSidebarCategory {...sidebarItem} key={index} />);\n      index++;\n\n      sidebarItem.items.forEach((subItem) => {\n        sidebarItems.push(<SimpleSidebarItem {...subItem} key={index} />);\n        index++;\n      });\n    } else if (sidebarItem.type === 'link') {\n      sidebarItems.push(<SimpleSidebarItem {...sidebarItem} key={index} />);\n      index++;\n    }\n  });\n\n  return (\n    <>\n      {sidebarItems}\n    </>\n  );\n}\n\nfunction DocSidebarDesktop({path, sidebar, onCollapse, isHidden}) {\n  const showAnnouncementBar = useShowAnnouncementBar();\n  const {\n    navbar: {hideOnScroll},\n    hideableSidebar,\n  } = useThemeConfig();\n  \n  return (\n    <div className=\"pswp-docs__sidebar-menu\">\n      <SimpleSidebar sidebar={sidebar} />\n    </div>\n  );\n} // eslint-disable-next-line react/function-component-definition\n\nconst DocSidebarMobileSecondaryMenu = ({toggleSidebar, sidebar, path}) => (\n  <div className=\"pswp-docs__sidebar-menu pswp-docs__sidebar-menu--mobile\">\n    <SimpleSidebar sidebar={sidebar} />\n  </div>\n);\n\nfunction DocSidebarMobile(props) {\n  return (\n    <MobileSecondaryMenuFiller\n      component={DocSidebarMobileSecondaryMenu}\n      props={props}\n    />\n  );\n}\n\nconst DocSidebarDesktopMemo = React.memo(DocSidebarDesktop);\nconst DocSidebarMobileMemo = React.memo(DocSidebarMobile);\nexport default function DocSidebar(props) {\n  const windowSize = useWindowSize(); // Desktop sidebar visible on hydration: need SSR rendering\n\n  const shouldRenderSidebarDesktop =\n    windowSize === 'desktop' || windowSize === 'ssr'; // Mobile sidebar not visible on hydration: can avoid SSR rendering\n\n  const shouldRenderSidebarMobile = windowSize === 'mobile';\n  return (\n    <>\n      {shouldRenderSidebarDesktop && <DocSidebarDesktopMemo {...props} />}\n      {shouldRenderSidebarMobile && <DocSidebarMobileMemo {...props} />}\n    </>\n  );\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/DocSidebar/is-same-path.ts",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n// Compare the 2 paths, case insensitive and ignoring trailing slash\nexport const isSamePath = (\n  path1: string | undefined,\n  path2: string | undefined,\n): boolean => {\n  const normalize = (pathname: string | undefined) =>\n    (!pathname || pathname?.endsWith('/')\n      ? pathname\n      : `${pathname}/`\n    )?.toLowerCase();\n  return normalize(path1) === normalize(path2);\n};"
  },
  {
    "path": "demo-docs-website/src/theme/DocSidebar/styles.module.css",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n:root {\n  --collapse-button-bg-color-dark: #2e333a;\n}\n\n@media (min-width: 997px) {\n  .sidebar {\n    display: flex;\n    flex-direction: column;\n    max-height: 100vh;\n    height: 100%;\n    position: sticky;\n    top: 0;\n    padding-top: var(--ifm-navbar-height);\n    transition: opacity 50ms ease;\n  }\n\n  .sidebarWithHideableNavbar {\n    padding-top: 0;\n  }\n\n  .sidebarHidden {\n    opacity: 0;\n    height: 0;\n    overflow: hidden;\n    visibility: hidden;\n  }\n\n  .sidebarLogo {\n    display: flex !important;\n    align-items: center;\n    margin: 0 var(--ifm-navbar-padding-horizontal);\n    min-height: var(--ifm-navbar-height);\n    max-height: var(--ifm-navbar-height);\n    color: inherit !important;\n    text-decoration: none !important;\n  }\n\n  .sidebarLogo img {\n    margin-right: 0.5rem;\n    height: 2rem;\n  }\n\n  .menu {\n    flex-grow: 1;\n    padding: 0.5rem;\n  }\n\n  .menuWithAnnouncementBar {\n    margin-bottom: var(--docusaurus-announcement-bar-height);\n  }\n\n  .collapseSidebarButton {\n    display: block !important;\n    background-color: var(--ifm-button-background-color);\n    height: 40px;\n    position: sticky;\n    bottom: 0;\n    border-radius: 0;\n    border: 1px solid var(--ifm-toc-border-color);\n  }\n\n  .collapseSidebarButtonIcon {\n    transform: rotate(180deg);\n    margin-top: 4px;\n  }\n\n  [dir='rtl'] .collapseSidebarButtonIcon {\n    transform: rotate(0);\n  }\n\n  [data-theme='dark'] .collapseSidebarButton {\n    background-color: var(--collapse-button-bg-color-dark);\n  }\n\n  [data-theme='dark'] .collapseSidebarButton:hover,\n  [data-theme='dark'] .collapseSidebarButton:focus {\n    background-color: var(--ifm-color-emphasis-200);\n  }\n}\n\n.sidebarLogo,\n.collapseSidebarButton {\n  display: none;\n}\n\n.sidebarMenuIcon {\n  vertical-align: middle;\n}\n\n.sidebarMenuCloseIcon {\n  display: inline-flex;\n  justify-content: center;\n  align-items: center;\n  height: 24px;\n  font-size: 1.5rem;\n  font-weight: var(--ifm-font-weight-bold);\n  line-height: 0.9;\n  width: 24px;\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/EditThisPage/index.js",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport React from 'react';\nimport {ThemeClassNames} from '@docusaurus/theme-common';\nexport default function EditThisPage({editUrl}) {\n  return (\n    <>Found a typo? <a href={editUrl}\n        target=\"_blank\"\n        rel=\"noreferrer noopener\"\n        className={ThemeClassNames.common.editThisPage}>Edit this page</a></>\n  );\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/Footer/index.js",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport React from 'react';\nimport clsx from 'clsx';\nimport Link from '@docusaurus/Link';\nimport {useThemeConfig} from '@docusaurus/theme-common';\nimport useBaseUrl from '@docusaurus/useBaseUrl';\nimport styles from './styles.module.css';\nimport ThemedImage from '@theme/ThemedImage';\n\nfunction FooterLogo({sources, alt, width, height}) {\n  return (\n    <ThemedImage\n      className=\"footer__logo\"\n      alt={alt}\n      sources={sources}\n      width={width}\n      height={height}\n    />\n  );\n}\n\nfunction Footer() {\n  const {footer} = useThemeConfig();\n  const {copyright, links = [], logo = {}} = footer || {};\n  const sources = {\n    light: useBaseUrl(logo.src),\n    dark: useBaseUrl(logo.srcDark || logo.src),\n  };\n\n  if (!footer) {\n    return null;\n  }\n\n  return (\n    <footer\n      className={'footer'}>\n      <div className=\"container container-fluid\">\n        {(logo || copyright) && (\n          <div className=\"footer__bottom text--center\">\n            {copyright ? (\n              <div\n                className=\"footer__copyright\" // Developer provided the HTML, so assume it's safe.\n                // eslint-disable-next-line react/no-danger\n                dangerouslySetInnerHTML={{\n                  __html: copyright,\n                }}\n              />\n            ) : null}\n          </div>\n        )}\n      </div>\n    </footer>\n  );\n}\n\nexport default React.memo(Footer);\n"
  },
  {
    "path": "demo-docs-website/src/theme/Footer/styles.module.css",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n.footerLogoLink {\n  opacity: 0.5;\n  transition: opacity var(--ifm-transition-fast)\n    var(--ifm-transition-timing-default);\n}\n\n.footerLogoLink:hover {\n  opacity: 1;\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/Logo/index.js",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport React from 'react';\nimport Link from '@docusaurus/Link';\nimport ThemedImage from '@theme/ThemedImage';\nimport useBaseUrl from '@docusaurus/useBaseUrl';\nimport useDocusaurusContext from '@docusaurus/useDocusaurusContext';\nimport {useThemeConfig} from '@docusaurus/theme-common';\nimport packageInfo from '../../../../package.json';\n\nexport default function Logo(props) {\n  const {\n    siteConfig: {title},\n  } = useDocusaurusContext();\n  const {\n    navbar: {\n      title: navbarTitle,\n      logo = {\n        src: '',\n      },\n    },\n  } = useThemeConfig();\n  const {imageClassName, titleClassName, ...propsRest} = props;\n  const logoLink = useBaseUrl(logo.href || '/');\n  const sources = {\n    light: useBaseUrl(logo.src),\n    dark: useBaseUrl(logo.srcDark || logo.src),\n  };\n  const themedImage = (\n    <ThemedImage\n      sources={sources}\n      height={logo.height}\n      width={logo.width}\n      alt={logo.alt || navbarTitle || title}\n    />\n  );\n  return (\n    <Link\n      to={logoLink}\n      {...propsRest}\n      {...(logo.target && {\n        target: logo.target,\n      })}>\n      {logo.src &&\n        (imageClassName ? (\n          <div className={imageClassName}>{themedImage}</div>\n        ) : (\n          themedImage\n        ))}\n      {navbarTitle != null && <b className={titleClassName}>{navbarTitle}</b>}\n      <span>{packageInfo.version}</span>\n    </Link>\n  );\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/MDXComponents/index.js",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport React, {isValidElement} from 'react';\nimport Head from '@docusaurus/Head';\nimport Link from '@docusaurus/Link';\nimport CodeBlock from '@theme/CodeBlock';\nimport Heading from '@theme/Heading';\nimport Details from '@theme/Details';\nimport PswpCodePreview from '@site/src/components/PswpCodePreview';\n\nimport './styles.css'; // MDX elements are wrapped through the MDX pragma. In some cases (notably usage\n// with Head/Helmet) we need to unwrap those elements.\n\nfunction unwrapMDXElement(element) {\n  if (element?.props?.mdxType && element?.props?.originalType) {\n    const {mdxType, originalType, ...newProps} = element.props;\n    return React.createElement(element.props.originalType, newProps);\n  }\n\n  return element;\n}\n\nconst MDXComponents = {\n  head: (props) => {\n    const unwrappedChildren = React.Children.map(props.children, (child) =>\n      unwrapMDXElement(child),\n    );\n    return <Head {...props}>{unwrappedChildren}</Head>;\n  },\n  code: (props) => {\n    const inlineElements = [\n      'a',\n      'b',\n      'big',\n      'i',\n      'span',\n      'em',\n      'strong',\n      'sup',\n      'sub',\n      'small',\n    ];\n    const shouldBeInline = React.Children.toArray(props.children).every(\n      (el) =>\n        (typeof el === 'string' && !el.includes('\\n')) ||\n        (React.isValidElement(el) && inlineElements.includes(el.props.mdxType)),\n    );\n    return shouldBeInline ? <code {...props} /> : <CodeBlock {...props} />;\n  },\n  a: (props) => <Link {...props} />,\n  pre: (props) => (\n    <CodeBlock // If this pre is created by a ``` fenced codeblock, unwrap the children\n      {...(isValidElement(props.children) &&\n      props.children.props.originalType === 'code'\n        ? props.children?.props\n        : {...props})}\n    />\n  ),\n  details: (props) => {\n    const items = React.Children.toArray(props.children); // Split summary item from the rest to pass it as a separate prop to the\n    // Details theme component\n\n    const summary = items.find((item) => item?.props?.mdxType === 'summary');\n    const children = <>{items.filter((item) => item !== summary)}</>;\n    return (\n      <Details {...props} summary={summary}>\n        {children}\n      </Details>\n    );\n  },\n  h1: (props) => <Heading as=\"h1\" {...props} />,\n  h2: (props) => <Heading as=\"h2\" {...props} />,\n  h3: (props) => <Heading as=\"h3\" {...props} />,\n  h4: (props) => <Heading as=\"h4\" {...props} />,\n  h5: (props) => <Heading as=\"h5\" {...props} />,\n  h6: (props) => <Heading as=\"h6\" {...props} />,\n  PswpCodePreview: (props) => <PswpCodePreview {...props} />\n};\nexport default MDXComponents;\n"
  },
  {
    "path": "demo-docs-website/src/theme/MDXComponents/styles.css",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nul.contains-task-list {\n  padding-left: 0;\n  list-style: none;\n}\n\nimg {\n  height: auto;\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/Navbar/github-stars.js",
    "content": "import React from 'react'\n\nimport repoData from '../../../repo-data.json';\n\nexport default function ReactGithubStars() {\n  return (\n    <a href=\"https://github.com/dimsemenov/photoswipe\" className=\"pswp-docs__github-link\">\n      <span className=\"pswp-docs__github-link-left\">Github</span>\n      <span className='pswp-docs__github-link-right'>\n        <svg aria-hidden=\"true\" height=\"16\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\">\n          <path fillRule=\"evenodd\" d=\"M8 .25a.75.75 0 01.673.418l1.882 3.815 4.21.612a.75.75 0 01.416 1.279l-3.046 2.97.719 4.192a.75.75 0 01-1.088.791L8 12.347l-3.766 1.98a.75.75 0 01-1.088-.79l.72-4.194L.818 6.374a.75.75 0 01.416-1.28l4.21-.611L7.327.668A.75.75 0 018 .25zm0 2.445L6.615 5.5a.75.75 0 01-.564.41l-3.097.45 2.24 2.184a.75.75 0 01.216.664l-.528 3.084 2.769-1.456a.75.75 0 01.698 0l2.77 1.456-.53-3.084a.75.75 0 01.216-.664l2.24-2.183-3.096-.45a.75.75 0 01-.564-.41L8 2.694v.001z\"></path>\n        </svg>\n        <span className=\"pswp-docs__github-link-star-count\">{\n          (repoData \n            && repoData.stargazers_count\n            && parseInt(repoData.stargazers_count, 10) > 100) ? parseInt(repoData.stargazers_count, 10).toLocaleString() : ''\n        }</span>\n      </span>\n    </a>\n  )\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/Navbar/index.js",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\nimport React, { useCallback, useState, useEffect } from \"react\";\nimport clsx from \"clsx\";\nimport Translate from \"@docusaurus/Translate\";\nimport ColorModeToggle from \"@theme/ColorModeToggle\";\nimport {\n  useThemeConfig,\n  useMobileSecondaryMenuRenderer,\n  usePrevious,\n  useHistoryPopHandler,\n  useHideableNavbar,\n  useLockBodyScroll,\n  useWindowSize,\n  useColorMode,\n} from \"@docusaurus/theme-common\";\nimport { useActivePlugin } from \"@docusaurus/plugin-content-docs/client\";\nimport NavbarItem from \"@theme/NavbarItem\";\nimport Logo from \"@theme/Logo\";\nimport IconMenu from \"@theme/IconMenu\";\nimport IconClose from \"@theme/IconClose\";\nimport styles from \"./styles.module.css\"; // retrocompatible with v1\nimport ReactGithubStars from \"./github-stars\";\n\nimport { DocSearch } from \"@docsearch/react\";\n\nimport \"@docsearch/css\";\n\nconst DefaultNavItemPosition = \"right\";\n\nfunction useNavbarItems() {\n  // TODO temporary casting until ThemeConfig type is improved\n  return useThemeConfig().navbar.items;\n} // If split links by left/right\n// if position is unspecified, fallback to right (as v1)\n\nfunction splitNavItemsByPosition(items) {\n  const leftItems = items.filter(\n    (item) => (item.position ?? DefaultNavItemPosition) === \"left\"\n  );\n  const rightItems = items.filter(\n    (item) => (item.position ?? DefaultNavItemPosition) === \"right\"\n  );\n  return {\n    leftItems,\n    rightItems,\n  };\n}\n\nfunction useMobileSidebar() {\n  const windowSize = useWindowSize(); // Mobile sidebar not visible on hydration: can avoid SSR rendering\n\n  const shouldRender = windowSize === \"mobile\"; // || windowSize === 'ssr';\n\n  const [shown, setShown] = useState(false); // Close mobile sidebar on navigation pop\n  // Most likely firing when using the Android back button (but not only)\n\n  useHistoryPopHandler(() => {\n    if (shown) {\n      setShown(false); // Should we prevent the navigation here?\n      // See https://github.com/facebook/docusaurus/pull/5462#issuecomment-911699846\n\n      return false; // prevent pop navigation\n    }\n\n    return undefined;\n  });\n  const toggle = useCallback(() => {\n    setShown((s) => !s);\n  }, []);\n  useEffect(() => {\n    if (windowSize === \"desktop\") {\n      setShown(false);\n    }\n  }, [windowSize]);\n  return {\n    shouldRender,\n    toggle,\n    shown,\n  };\n}\n\nfunction useColorModeToggle() {\n  const {\n    colorMode: { disableSwitch },\n  } = useThemeConfig();\n  const { isDarkTheme, setLightTheme, setDarkTheme } = useColorMode();\n  const toggle = useCallback(\n    (e) => (e.target.checked ? setDarkTheme() : setLightTheme()),\n    [setLightTheme, setDarkTheme]\n  );\n  return {\n    isDarkTheme,\n    toggle,\n    disabled: disableSwitch,\n  };\n}\n\nconst TwitterLink = () => {\n  return (\n    <a\n      className=\"navbar__item navbar__link\"\n      href=\"https://twitter.com/photoswipe\"\n    >\n      Twitter\n    </a>\n  );\n};\n\nfunction useSecondaryMenu({ sidebarShown, toggleSidebar }) {\n  const content = useMobileSecondaryMenuRenderer()?.({\n    toggleSidebar,\n  });\n  const previousContent = usePrevious(content);\n  const [shown, setShown] = useState(\n    () =>\n      // /!\\ content is set with useEffect,\n      // so it's not available on mount anyway\n      // \"return !!content\" => always returns false\n      false\n  ); // When content is become available for the first time (set in useEffect)\n  // we set this content to be shown!\n\n  useEffect(() => {\n    const contentBecameAvailable = content && !previousContent;\n\n    if (contentBecameAvailable) {\n      setShown(true);\n    }\n  }, [content, previousContent]);\n  const hasContent = !!content; // On sidebar close, secondary menu is set to be shown on next re-opening\n  // (if any secondary menu content available)\n\n  useEffect(() => {\n    if (!hasContent) {\n      setShown(false);\n      return;\n    }\n\n    if (!sidebarShown) {\n      setShown(true);\n    }\n  }, [sidebarShown, hasContent]);\n  const hide = useCallback(() => {\n    setShown(false);\n  }, []);\n  return {\n    shown,\n    hide,\n    content,\n  };\n}\n\nfunction NavbarMobileSidebar({ sidebarShown, toggleSidebar }) {\n  useLockBodyScroll(sidebarShown);\n  const items = useNavbarItems();\n  const colorModeToggle = useColorModeToggle();\n  const secondaryMenu = useSecondaryMenu({\n    sidebarShown,\n    toggleSidebar,\n  });\n  return (\n    <div className=\"navbar-sidebar\">\n      <div className=\"navbar-sidebar__brand\">\n        <Logo\n          className=\"navbar__brand\"\n          imageClassName=\"navbar__logo\"\n          titleClassName=\"navbar__title\"\n        />\n        {!colorModeToggle.disabled && (\n          <ColorModeToggle\n            className={styles.navbarSidebarToggle}\n            checked={colorModeToggle.isDarkTheme}\n            onChange={colorModeToggle.toggle}\n          />\n        )}\n        <button\n          type=\"button\"\n          className=\"clean-btn navbar-sidebar__close\"\n          onClick={toggleSidebar}\n        >\n          <IconClose\n            color=\"var(--ifm-color-emphasis-600)\"\n            className={styles.navbarSidebarCloseSvg}\n          />\n        </button>\n      </div>\n\n      <div\n        className={clsx(\"navbar-sidebar__items\", {\n          \"navbar-sidebar__items--show-secondary\": secondaryMenu.shown,\n        })}\n      >\n        <div className=\"navbar-sidebar__item menu\">\n          <ul className=\"menu__list\">\n            {items.map((item, i) => (\n              <NavbarItem mobile {...item} onClick={toggleSidebar} key={i} />\n            ))}\n          </ul>\n        </div>\n\n        <div className=\"navbar-sidebar__item menu\">\n          {items.length > 0 && (\n            <button\n              type=\"button\"\n              className=\"clean-btn navbar-sidebar__back\"\n              onClick={secondaryMenu.hide}\n            >\n              <Translate\n                id=\"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel\"\n                description=\"The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)\"\n              >\n                ← Back to main menu\n              </Translate>\n            </button>\n          )}\n          {secondaryMenu.content}\n        </div>\n      </div>\n    </div>\n  );\n}\n\nexport default function Navbar() {\n  const {\n    navbar: { hideOnScroll, style },\n  } = useThemeConfig();\n  const mobileSidebar = useMobileSidebar();\n  const colorModeToggle = useColorModeToggle();\n  const activeDocPlugin = useActivePlugin();\n  const { navbarRef, isNavbarVisible } = useHideableNavbar(hideOnScroll);\n  const items = useNavbarItems();\n  const { leftItems, rightItems } = splitNavItemsByPosition(items);\n  return (\n    <nav\n      ref={navbarRef}\n      className={clsx(\"navbar\", {\n        \"navbar--dark\": style === \"dark\",\n        \"navbar--primary\": style === \"primary\",\n        \"navbar-sidebar--show\": mobileSidebar.shown,\n        [styles.navbarHideable]: hideOnScroll,\n        [styles.navbarHidden]: hideOnScroll && !isNavbarVisible,\n      })}\n    >\n      <div className=\"navbar__inner\">\n        <div className=\"navbar__items\">\n          {(items?.length > 0 || activeDocPlugin) && (\n            <button\n              aria-label=\"Navigation bar toggle\"\n              className=\"navbar__toggle clean-btn\"\n              type=\"button\"\n              tabIndex={0}\n              onClick={mobileSidebar.toggle}\n              onKeyDown={mobileSidebar.toggle}\n            >\n              <IconMenu />\n            </button>\n          )}\n          <Logo\n            className=\"navbar__brand\"\n            imageClassName=\"navbar__logo\"\n            titleClassName=\"navbar__title\"\n          />\n          {leftItems.map((item, i) => (\n            <NavbarItem {...item} key={i} />\n          ))}\n          <DocSearch\n            appId=\"M9RBCSJL4H\"\n            indexName=\"photoswipe\"\n            apiKey=\"a8151d869decf80b4e64c7103b1ea00a\"\n          />\n          <div\n            className=\"v6-feedback\"\n            style={{ fontWeight: 700, marginLeft: 4 }}\n          >\n            <a href=\"https://github.com/dimsemenov/PhotoSwipe/discussions/2170\">\n              v6 feedback\n            </a>\n          </div>\n        </div>\n        <div className=\"navbar__items navbar__items--right\">\n          <TwitterLink />\n          <ReactGithubStars />\n          {rightItems.map((item, i) => (\n            <NavbarItem {...item} key={i} />\n          ))}\n          {!colorModeToggle.disabled && (\n            <ColorModeToggle\n              className={styles.toggle}\n              checked={colorModeToggle.isDarkTheme}\n              onChange={colorModeToggle.toggle}\n            />\n          )}\n        </div>\n      </div>\n\n      <div\n        role=\"presentation\"\n        className=\"navbar-sidebar__backdrop\"\n        onClick={mobileSidebar.toggle}\n      />\n\n      {mobileSidebar.shouldRender && (\n        <NavbarMobileSidebar\n          sidebarShown={mobileSidebar.shown}\n          toggleSidebar={mobileSidebar.toggle}\n        />\n      )}\n    </nav>\n  );\n}\n"
  },
  {
    "path": "demo-docs-website/src/theme/Navbar/styles.module.css",
    "content": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n/*\nHide toggle in small viewports\n */\n@media (max-width: 996px) {\n  .toggle {\n    display: none;\n  }\n}\n\n.navbarHideable {\n  transition: transform var(--ifm-transition-fast) ease;\n}\n\n.navbarHidden {\n  transform: translate3d(0, calc(-100% - 2px), 0);\n}\n\n.navbarSidebarToggle {\n  margin-right: 1rem;\n}\n"
  },
  {
    "path": "demo-docs-website/static/.nojekyll",
    "content": ""
  },
  {
    "path": "demo-docs-website/static/photoswipe/photoswipe-lightbox.esm.js",
    "content": "/*!\n  * PhotoSwipe Lightbox 5.4.4 - https://photoswipe.com\n  * (c) 2024 Dmytro Semenov\n  */\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/**\n * @template {keyof HTMLElementTagNameMap} T\n * @param {string} className\n * @param {T} tagName\n * @param {Node} [appendToEl]\n * @returns {HTMLElementTagNameMap[T]}\n */\nfunction createElement(className, tagName, appendToEl) {\n  const el = document.createElement(tagName);\n\n  if (className) {\n    el.className = className;\n  }\n\n  if (appendToEl) {\n    appendToEl.appendChild(el);\n  }\n\n  return el;\n}\n/**\n * Get transform string\n *\n * @param {number} x\n * @param {number} [y]\n * @param {number} [scale]\n * @returns {string}\n */\n\nfunction toTransformString(x, y, scale) {\n  let propValue = `translate3d(${x}px,${y || 0}px,0)`;\n\n  if (scale !== undefined) {\n    propValue += ` scale3d(${scale},${scale},1)`;\n  }\n\n  return propValue;\n}\n/**\n * Apply width and height CSS properties to element\n *\n * @param {HTMLElement} el\n * @param {string | number} w\n * @param {string | number} h\n */\n\nfunction setWidthHeight(el, w, h) {\n  el.style.width = typeof w === 'number' ? `${w}px` : w;\n  el.style.height = typeof h === 'number' ? `${h}px` : h;\n}\n/** @typedef {LOAD_STATE[keyof LOAD_STATE]} LoadState */\n\n/** @type {{ IDLE: 'idle'; LOADING: 'loading'; LOADED: 'loaded'; ERROR: 'error' }} */\n\nconst LOAD_STATE = {\n  IDLE: 'idle',\n  LOADING: 'loading',\n  LOADED: 'loaded',\n  ERROR: 'error'\n};\n/**\n * Check if click or keydown event was dispatched\n * with a special key or via mouse wheel.\n *\n * @param {MouseEvent | KeyboardEvent} e\n * @returns {boolean}\n */\n\nfunction specialKeyUsed(e) {\n  return 'button' in e && e.button === 1 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey;\n}\n/**\n * Parse `gallery` or `children` options.\n *\n * @param {import('../photoswipe.js').ElementProvider} [option]\n * @param {string} [legacySelector]\n * @param {HTMLElement | Document} [parent]\n * @returns HTMLElement[]\n */\n\nfunction getElementsFromOption(option, legacySelector, parent = document) {\n  /** @type {HTMLElement[]} */\n  let elements = [];\n\n  if (option instanceof Element) {\n    elements = [option];\n  } else if (option instanceof NodeList || Array.isArray(option)) {\n    elements = Array.from(option);\n  } else {\n    const selector = typeof option === 'string' ? option : legacySelector;\n\n    if (selector) {\n      elements = Array.from(parent.querySelectorAll(selector));\n    }\n  }\n\n  return elements;\n}\n/**\n * Check if variable is PhotoSwipe class\n *\n * @param {any} fn\n * @returns {boolean}\n */\n\nfunction isPswpClass(fn) {\n  return typeof fn === 'function' && fn.prototype && fn.prototype.goTo;\n}\n/**\n * Check if browser is Safari\n *\n * @returns {boolean}\n */\n\nfunction isSafari() {\n  return !!(navigator.vendor && navigator.vendor.match(/apple/i));\n}\n\n/** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n\n/** @typedef {import('../photoswipe.js').DataSource} DataSource */\n\n/** @typedef {import('../ui/ui-element.js').UIElementData} UIElementData */\n\n/** @typedef {import('../slide/content.js').default} ContentDefault */\n\n/** @typedef {import('../slide/slide.js').default} Slide */\n\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n\n/** @typedef {import('../slide/zoom-level.js').default} ZoomLevel */\n\n/** @typedef {import('../slide/get-thumb-bounds.js').Bounds} Bounds */\n\n/**\n * Allow adding an arbitrary props to the Content\n * https://photoswipe.com/custom-content/#using-webp-image-format\n * @typedef {ContentDefault & Record<string, any>} Content\n */\n\n/** @typedef {{ x?: number; y?: number }} Point */\n\n/**\n * @typedef {Object} PhotoSwipeEventsMap https://photoswipe.com/events/\n *\n *\n * https://photoswipe.com/adding-ui-elements/\n *\n * @prop {undefined} uiRegister\n * @prop {{ data: UIElementData }} uiElementCreate\n *\n *\n * https://photoswipe.com/events/#initialization-events\n *\n * @prop {undefined} beforeOpen\n * @prop {undefined} firstUpdate\n * @prop {undefined} initialLayout\n * @prop {undefined} change\n * @prop {undefined} afterInit\n * @prop {undefined} bindEvents\n *\n *\n * https://photoswipe.com/events/#opening-or-closing-transition-events\n *\n * @prop {undefined} openingAnimationStart\n * @prop {undefined} openingAnimationEnd\n * @prop {undefined} closingAnimationStart\n * @prop {undefined} closingAnimationEnd\n *\n *\n * https://photoswipe.com/events/#closing-events\n *\n * @prop {undefined} close\n * @prop {undefined} destroy\n *\n *\n * https://photoswipe.com/events/#pointer-and-gesture-events\n *\n * @prop {{ originalEvent: PointerEvent }} pointerDown\n * @prop {{ originalEvent: PointerEvent }} pointerMove\n * @prop {{ originalEvent: PointerEvent }} pointerUp\n * @prop {{ bgOpacity: number }} pinchClose can be default prevented\n * @prop {{ panY: number }} verticalDrag can be default prevented\n *\n *\n * https://photoswipe.com/events/#slide-content-events\n *\n * @prop {{ content: Content }} contentInit\n * @prop {{ content: Content; isLazy: boolean }} contentLoad can be default prevented\n * @prop {{ content: Content; isLazy: boolean }} contentLoadImage can be default prevented\n * @prop {{ content: Content; slide: Slide; isError?: boolean }} loadComplete\n * @prop {{ content: Content; slide: Slide }} loadError\n * @prop {{ content: Content; width: number; height: number }} contentResize can be default prevented\n * @prop {{ content: Content; width: number; height: number; slide: Slide }} imageSizeChange\n * @prop {{ content: Content }} contentLazyLoad can be default prevented\n * @prop {{ content: Content }} contentAppend can be default prevented\n * @prop {{ content: Content }} contentActivate can be default prevented\n * @prop {{ content: Content }} contentDeactivate can be default prevented\n * @prop {{ content: Content }} contentRemove can be default prevented\n * @prop {{ content: Content }} contentDestroy can be default prevented\n *\n *\n * undocumented\n *\n * @prop {{ point: Point; originalEvent: PointerEvent }} imageClickAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} bgClickAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} tapAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} doubleTapAction can be default prevented\n *\n * @prop {{ originalEvent: KeyboardEvent }} keydown can be default prevented\n * @prop {{ x: number; dragging: boolean }} moveMainScroll\n * @prop {{ slide: Slide }} firstZoomPan\n * @prop {{ slide: Slide | undefined, data: SlideData, index: number }} gettingData\n * @prop {undefined} beforeResize\n * @prop {undefined} resize\n * @prop {undefined} viewportSize\n * @prop {undefined} updateScrollOffset\n * @prop {{ slide: Slide }} slideInit\n * @prop {{ slide: Slide }} afterSetContent\n * @prop {{ slide: Slide }} slideLoad\n * @prop {{ slide: Slide }} appendHeavy can be default prevented\n * @prop {{ slide: Slide }} appendHeavyContent\n * @prop {{ slide: Slide }} slideActivate\n * @prop {{ slide: Slide }} slideDeactivate\n * @prop {{ slide: Slide }} slideDestroy\n * @prop {{ destZoomLevel: number, centerPoint: Point | undefined, transitionDuration: number | false | undefined }} beforeZoomTo\n * @prop {{ slide: Slide }} zoomPanUpdate\n * @prop {{ slide: Slide }} initialZoomPan\n * @prop {{ slide: Slide }} calcSlideSize\n * @prop {undefined} resolutionChanged\n * @prop {{ originalEvent: WheelEvent }} wheel can be default prevented\n * @prop {{ content: Content }} contentAppendImage can be default prevented\n * @prop {{ index: number; itemData: SlideData }} lazyLoadSlide can be default prevented\n * @prop {undefined} lazyLoad\n * @prop {{ slide: Slide }} calcBounds\n * @prop {{ zoomLevels: ZoomLevel, slideData: SlideData }} zoomLevelsUpdate\n *\n *\n * legacy\n *\n * @prop {undefined} init\n * @prop {undefined} initialZoomIn\n * @prop {undefined} initialZoomOut\n * @prop {undefined} initialZoomInEnd\n * @prop {undefined} initialZoomOutEnd\n * @prop {{ dataSource: DataSource | undefined, numItems: number }} numItems\n * @prop {{ itemData: SlideData; index: number }} itemData\n * @prop {{ index: number, itemData: SlideData, instance: PhotoSwipe }} thumbBounds\n */\n\n/**\n * @typedef {Object} PhotoSwipeFiltersMap https://photoswipe.com/filters/\n *\n * @prop {(numItems: number, dataSource: DataSource | undefined) => number} numItems\n * Modify the total amount of slides. Example on Data sources page.\n * https://photoswipe.com/filters/#numitems\n *\n * @prop {(itemData: SlideData, index: number) => SlideData} itemData\n * Modify slide item data. Example on Data sources page.\n * https://photoswipe.com/filters/#itemdata\n *\n * @prop {(itemData: SlideData, element: HTMLElement, linkEl: HTMLAnchorElement) => SlideData} domItemData\n * Modify item data when it's parsed from DOM element. Example on Data sources page.\n * https://photoswipe.com/filters/#domitemdata\n *\n * @prop {(clickedIndex: number, e: MouseEvent, instance: PhotoSwipeLightbox) => number} clickedIndex\n * Modify clicked gallery item index.\n * https://photoswipe.com/filters/#clickedindex\n *\n * @prop {(placeholderSrc: string | false, content: Content) => string | false} placeholderSrc\n * Modify placeholder image source.\n * https://photoswipe.com/filters/#placeholdersrc\n *\n * @prop {(isContentLoading: boolean, content: Content) => boolean} isContentLoading\n * Modify if the content is currently loading.\n * https://photoswipe.com/filters/#iscontentloading\n *\n * @prop {(isContentZoomable: boolean, content: Content) => boolean} isContentZoomable\n * Modify if the content can be zoomed.\n * https://photoswipe.com/filters/#iscontentzoomable\n *\n * @prop {(useContentPlaceholder: boolean, content: Content) => boolean} useContentPlaceholder\n * Modify if the placeholder should be used for the content.\n * https://photoswipe.com/filters/#usecontentplaceholder\n *\n * @prop {(isKeepingPlaceholder: boolean, content: Content) => boolean} isKeepingPlaceholder\n * Modify if the placeholder should be kept after the content is loaded.\n * https://photoswipe.com/filters/#iskeepingplaceholder\n *\n *\n * @prop {(contentErrorElement: HTMLElement, content: Content) => HTMLElement} contentErrorElement\n * Modify an element when the content has error state (for example, if image cannot be loaded).\n * https://photoswipe.com/filters/#contenterrorelement\n *\n * @prop {(element: HTMLElement, data: UIElementData) => HTMLElement} uiElement\n * Modify a UI element that's being created.\n * https://photoswipe.com/filters/#uielement\n *\n * @prop {(thumbnail: HTMLElement | null | undefined, itemData: SlideData, index: number) => HTMLElement} thumbEl\n * Modify the thumbnail element from which opening zoom animation starts or ends.\n * https://photoswipe.com/filters/#thumbel\n *\n * @prop {(thumbBounds: Bounds | undefined, itemData: SlideData, index: number) => Bounds} thumbBounds\n * Modify the thumbnail bounds from which opening zoom animation starts or ends.\n * https://photoswipe.com/filters/#thumbbounds\n *\n * @prop {(srcsetSizesWidth: number, content: Content) => number} srcsetSizesWidth\n *\n * @prop {(preventPointerEvent: boolean, event: PointerEvent, pointerType: string) => boolean} preventPointerEvent\n *\n */\n\n/**\n * @template {keyof PhotoSwipeFiltersMap} T\n * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter\n */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent<T> : PhotoSwipeEvent<T> & PhotoSwipeEventsMap[T]} AugmentedEvent\n */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {(event: AugmentedEvent<T>) => void} EventCallback\n */\n\n/**\n * Base PhotoSwipe event object\n *\n * @template {keyof PhotoSwipeEventsMap} T\n */\nclass PhotoSwipeEvent {\n  /**\n   * @param {T} type\n   * @param {PhotoSwipeEventsMap[T]} [details]\n   */\n  constructor(type, details) {\n    this.type = type;\n    this.defaultPrevented = false;\n\n    if (details) {\n      Object.assign(this, details);\n    }\n  }\n\n  preventDefault() {\n    this.defaultPrevented = true;\n  }\n\n}\n/**\n * PhotoSwipe base class that can listen and dispatch for events.\n * Shared by PhotoSwipe Core and PhotoSwipe Lightbox, extended by base.js\n */\n\n\nclass Eventable {\n  constructor() {\n    /**\n     * @type {{ [T in keyof PhotoSwipeEventsMap]?: ((event: AugmentedEvent<T>) => void)[] }}\n     */\n    this._listeners = {};\n    /**\n     * @type {{ [T in keyof PhotoSwipeFiltersMap]?: Filter<T>[] }}\n     */\n\n    this._filters = {};\n    /** @type {PhotoSwipe | undefined} */\n\n    this.pswp = undefined;\n    /** @type {PhotoSwipeOptions | undefined} */\n\n    this.options = undefined;\n  }\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {PhotoSwipeFiltersMap[T]} fn\n   * @param {number} priority\n   */\n\n\n  addFilter(name, fn, priority = 100) {\n    var _this$_filters$name, _this$_filters$name2, _this$pswp;\n\n    if (!this._filters[name]) {\n      this._filters[name] = [];\n    }\n\n    (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.push({\n      fn,\n      priority\n    });\n    (_this$_filters$name2 = this._filters[name]) === null || _this$_filters$name2 === void 0 || _this$_filters$name2.sort((f1, f2) => f1.priority - f2.priority);\n    (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.addFilter(name, fn, priority);\n  }\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {PhotoSwipeFiltersMap[T]} fn\n   */\n\n\n  removeFilter(name, fn) {\n    if (this._filters[name]) {\n      // @ts-expect-error\n      this._filters[name] = this._filters[name].filter(filter => filter.fn !== fn);\n    }\n\n    if (this.pswp) {\n      this.pswp.removeFilter(name, fn);\n    }\n  }\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {Parameters<PhotoSwipeFiltersMap[T]>} args\n   * @returns {Parameters<PhotoSwipeFiltersMap[T]>[0]}\n   */\n\n\n  applyFilters(name, ...args) {\n    var _this$_filters$name3;\n\n    (_this$_filters$name3 = this._filters[name]) === null || _this$_filters$name3 === void 0 || _this$_filters$name3.forEach(filter => {\n      // @ts-expect-error\n      args[0] = filter.fn.apply(this, args);\n    });\n    return args[0];\n  }\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {EventCallback<T>} fn\n   */\n\n\n  on(name, fn) {\n    var _this$_listeners$name, _this$pswp2;\n\n    if (!this._listeners[name]) {\n      this._listeners[name] = [];\n    }\n\n    (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.push(fn); // When binding events to lightbox,\n    // also bind events to PhotoSwipe Core,\n    // if it's open.\n\n    (_this$pswp2 = this.pswp) === null || _this$pswp2 === void 0 || _this$pswp2.on(name, fn);\n  }\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {EventCallback<T>} fn\n   */\n\n\n  off(name, fn) {\n    var _this$pswp3;\n\n    if (this._listeners[name]) {\n      // @ts-expect-error\n      this._listeners[name] = this._listeners[name].filter(listener => fn !== listener);\n    }\n\n    (_this$pswp3 = this.pswp) === null || _this$pswp3 === void 0 || _this$pswp3.off(name, fn);\n  }\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {PhotoSwipeEventsMap[T]} [details]\n   * @returns {AugmentedEvent<T>}\n   */\n\n\n  dispatch(name, details) {\n    var _this$_listeners$name2;\n\n    if (this.pswp) {\n      return this.pswp.dispatch(name, details);\n    }\n\n    const event =\n    /** @type {AugmentedEvent<T>} */\n    new PhotoSwipeEvent(name, details);\n    (_this$_listeners$name2 = this._listeners[name]) === null || _this$_listeners$name2 === void 0 || _this$_listeners$name2.forEach(listener => {\n      listener.call(this, event);\n    });\n    return event;\n  }\n\n}\n\nclass Placeholder {\n  /**\n   * @param {string | false} imageSrc\n   * @param {HTMLElement} container\n   */\n  constructor(imageSrc, container) {\n    // Create placeholder\n    // (stretched thumbnail or simple div behind the main image)\n\n    /** @type {HTMLImageElement | HTMLDivElement | null} */\n    this.element = createElement('pswp__img pswp__img--placeholder', imageSrc ? 'img' : 'div', container);\n\n    if (imageSrc) {\n      const imgEl =\n      /** @type {HTMLImageElement} */\n      this.element;\n      imgEl.decoding = 'async';\n      imgEl.alt = '';\n      imgEl.src = imageSrc;\n      imgEl.setAttribute('role', 'presentation');\n    }\n\n    this.element.setAttribute('aria-hidden', 'true');\n  }\n  /**\n   * @param {number} width\n   * @param {number} height\n   */\n\n\n  setDisplayedSize(width, height) {\n    if (!this.element) {\n      return;\n    }\n\n    if (this.element.tagName === 'IMG') {\n      // Use transform scale() to modify img placeholder size\n      // (instead of changing width/height directly).\n      // This helps with performance, specifically in iOS15 Safari.\n      setWidthHeight(this.element, 250, 'auto');\n      this.element.style.transformOrigin = '0 0';\n      this.element.style.transform = toTransformString(0, 0, width / 250);\n    } else {\n      setWidthHeight(this.element, width, height);\n    }\n  }\n\n  destroy() {\n    var _this$element;\n\n    if ((_this$element = this.element) !== null && _this$element !== void 0 && _this$element.parentNode) {\n      this.element.remove();\n    }\n\n    this.element = null;\n  }\n\n}\n\n/** @typedef {import('./slide.js').default} Slide */\n\n/** @typedef {import('./slide.js').SlideData} SlideData */\n\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n\n/** @typedef {import('../util/util.js').LoadState} LoadState */\n\nclass Content {\n  /**\n   * @param {SlideData} itemData Slide data\n   * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance\n   * @param {number} index\n   */\n  constructor(itemData, instance, index) {\n    this.instance = instance;\n    this.data = itemData;\n    this.index = index;\n    /** @type {HTMLImageElement | HTMLDivElement | undefined} */\n\n    this.element = undefined;\n    /** @type {Placeholder | undefined} */\n\n    this.placeholder = undefined;\n    /** @type {Slide | undefined} */\n\n    this.slide = undefined;\n    this.displayedImageWidth = 0;\n    this.displayedImageHeight = 0;\n    this.width = Number(this.data.w) || Number(this.data.width) || 0;\n    this.height = Number(this.data.h) || Number(this.data.height) || 0;\n    this.isAttached = false;\n    this.hasSlide = false;\n    this.isDecoding = false;\n    /** @type {LoadState} */\n\n    this.state = LOAD_STATE.IDLE;\n\n    if (this.data.type) {\n      this.type = this.data.type;\n    } else if (this.data.src) {\n      this.type = 'image';\n    } else {\n      this.type = 'html';\n    }\n\n    this.instance.dispatch('contentInit', {\n      content: this\n    });\n  }\n\n  removePlaceholder() {\n    if (this.placeholder && !this.keepPlaceholder()) {\n      // With delay, as image might be loaded, but not rendered\n      setTimeout(() => {\n        if (this.placeholder) {\n          this.placeholder.destroy();\n          this.placeholder = undefined;\n        }\n      }, 1000);\n    }\n  }\n  /**\n   * Preload content\n   *\n   * @param {boolean} isLazy\n   * @param {boolean} [reload]\n   */\n\n\n  load(isLazy, reload) {\n    if (this.slide && this.usePlaceholder()) {\n      if (!this.placeholder) {\n        const placeholderSrc = this.instance.applyFilters('placeholderSrc', // use  image-based placeholder only for the first slide,\n        // as rendering (even small stretched thumbnail) is an expensive operation\n        this.data.msrc && this.slide.isFirstSlide ? this.data.msrc : false, this);\n        this.placeholder = new Placeholder(placeholderSrc, this.slide.container);\n      } else {\n        const placeholderEl = this.placeholder.element; // Add placeholder to DOM if it was already created\n\n        if (placeholderEl && !placeholderEl.parentElement) {\n          this.slide.container.prepend(placeholderEl);\n        }\n      }\n    }\n\n    if (this.element && !reload) {\n      return;\n    }\n\n    if (this.instance.dispatch('contentLoad', {\n      content: this,\n      isLazy\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (this.isImageContent()) {\n      this.element = createElement('pswp__img', 'img'); // Start loading only after width is defined, as sizes might depend on it.\n      // Due to Safari feature, we must define sizes before srcset.\n\n      if (this.displayedImageWidth) {\n        this.loadImage(isLazy);\n      }\n    } else {\n      this.element = createElement('pswp__content', 'div');\n      this.element.innerHTML = this.data.html || '';\n    }\n\n    if (reload && this.slide) {\n      this.slide.updateContentSize(true);\n    }\n  }\n  /**\n   * Preload image\n   *\n   * @param {boolean} isLazy\n   */\n\n\n  loadImage(isLazy) {\n    var _this$data$src, _this$data$alt;\n\n    if (!this.isImageContent() || !this.element || this.instance.dispatch('contentLoadImage', {\n      content: this,\n      isLazy\n    }).defaultPrevented) {\n      return;\n    }\n\n    const imageElement =\n    /** @type HTMLImageElement */\n    this.element;\n    this.updateSrcsetSizes();\n\n    if (this.data.srcset) {\n      imageElement.srcset = this.data.srcset;\n    }\n\n    imageElement.src = (_this$data$src = this.data.src) !== null && _this$data$src !== void 0 ? _this$data$src : '';\n    imageElement.alt = (_this$data$alt = this.data.alt) !== null && _this$data$alt !== void 0 ? _this$data$alt : '';\n    this.state = LOAD_STATE.LOADING;\n\n    if (imageElement.complete) {\n      this.onLoaded();\n    } else {\n      imageElement.onload = () => {\n        this.onLoaded();\n      };\n\n      imageElement.onerror = () => {\n        this.onError();\n      };\n    }\n  }\n  /**\n   * Assign slide to content\n   *\n   * @param {Slide} slide\n   */\n\n\n  setSlide(slide) {\n    this.slide = slide;\n    this.hasSlide = true;\n    this.instance = slide.pswp; // todo: do we need to unset slide?\n  }\n  /**\n   * Content load success handler\n   */\n\n\n  onLoaded() {\n    this.state = LOAD_STATE.LOADED;\n\n    if (this.slide && this.element) {\n      this.instance.dispatch('loadComplete', {\n        slide: this.slide,\n        content: this\n      }); // if content is reloaded\n\n      if (this.slide.isActive && this.slide.heavyAppended && !this.element.parentNode) {\n        this.append();\n        this.slide.updateContentSize(true);\n      }\n\n      if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {\n        this.removePlaceholder();\n      }\n    }\n  }\n  /**\n   * Content load error handler\n   */\n\n\n  onError() {\n    this.state = LOAD_STATE.ERROR;\n\n    if (this.slide) {\n      this.displayError();\n      this.instance.dispatch('loadComplete', {\n        slide: this.slide,\n        isError: true,\n        content: this\n      });\n      this.instance.dispatch('loadError', {\n        slide: this.slide,\n        content: this\n      });\n    }\n  }\n  /**\n   * @returns {Boolean} If the content is currently loading\n   */\n\n\n  isLoading() {\n    return this.instance.applyFilters('isContentLoading', this.state === LOAD_STATE.LOADING, this);\n  }\n  /**\n   * @returns {Boolean} If the content is in error state\n   */\n\n\n  isError() {\n    return this.state === LOAD_STATE.ERROR;\n  }\n  /**\n   * @returns {boolean} If the content is image\n   */\n\n\n  isImageContent() {\n    return this.type === 'image';\n  }\n  /**\n   * Update content size\n   *\n   * @param {Number} width\n   * @param {Number} height\n   */\n\n\n  setDisplayedSize(width, height) {\n    if (!this.element) {\n      return;\n    }\n\n    if (this.placeholder) {\n      this.placeholder.setDisplayedSize(width, height);\n    }\n\n    if (this.instance.dispatch('contentResize', {\n      content: this,\n      width,\n      height\n    }).defaultPrevented) {\n      return;\n    }\n\n    setWidthHeight(this.element, width, height);\n\n    if (this.isImageContent() && !this.isError()) {\n      const isInitialSizeUpdate = !this.displayedImageWidth && width;\n      this.displayedImageWidth = width;\n      this.displayedImageHeight = height;\n\n      if (isInitialSizeUpdate) {\n        this.loadImage(false);\n      } else {\n        this.updateSrcsetSizes();\n      }\n\n      if (this.slide) {\n        this.instance.dispatch('imageSizeChange', {\n          slide: this.slide,\n          width,\n          height,\n          content: this\n        });\n      }\n    }\n  }\n  /**\n   * @returns {boolean} If the content can be zoomed\n   */\n\n\n  isZoomable() {\n    return this.instance.applyFilters('isContentZoomable', this.isImageContent() && this.state !== LOAD_STATE.ERROR, this);\n  }\n  /**\n   * Update image srcset sizes attribute based on width and height\n   */\n\n\n  updateSrcsetSizes() {\n    // Handle srcset sizes attribute.\n    //\n    // Never lower quality, if it was increased previously.\n    // Chrome does this automatically, Firefox and Safari do not,\n    // so we store largest used size in dataset.\n    if (!this.isImageContent() || !this.element || !this.data.srcset) {\n      return;\n    }\n\n    const image =\n    /** @type HTMLImageElement */\n    this.element;\n    const sizesWidth = this.instance.applyFilters('srcsetSizesWidth', this.displayedImageWidth, this);\n\n    if (!image.dataset.largestUsedSize || sizesWidth > parseInt(image.dataset.largestUsedSize, 10)) {\n      image.sizes = sizesWidth + 'px';\n      image.dataset.largestUsedSize = String(sizesWidth);\n    }\n  }\n  /**\n   * @returns {boolean} If content should use a placeholder (from msrc by default)\n   */\n\n\n  usePlaceholder() {\n    return this.instance.applyFilters('useContentPlaceholder', this.isImageContent(), this);\n  }\n  /**\n   * Preload content with lazy-loading param\n   */\n\n\n  lazyLoad() {\n    if (this.instance.dispatch('contentLazyLoad', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    this.load(true);\n  }\n  /**\n   * @returns {boolean} If placeholder should be kept after content is loaded\n   */\n\n\n  keepPlaceholder() {\n    return this.instance.applyFilters('isKeepingPlaceholder', this.isLoading(), this);\n  }\n  /**\n   * Destroy the content\n   */\n\n\n  destroy() {\n    this.hasSlide = false;\n    this.slide = undefined;\n\n    if (this.instance.dispatch('contentDestroy', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    this.remove();\n\n    if (this.placeholder) {\n      this.placeholder.destroy();\n      this.placeholder = undefined;\n    }\n\n    if (this.isImageContent() && this.element) {\n      this.element.onload = null;\n      this.element.onerror = null;\n      this.element = undefined;\n    }\n  }\n  /**\n   * Display error message\n   */\n\n\n  displayError() {\n    if (this.slide) {\n      var _this$instance$option, _this$instance$option2;\n\n      let errorMsgEl = createElement('pswp__error-msg', 'div');\n      errorMsgEl.innerText = (_this$instance$option = (_this$instance$option2 = this.instance.options) === null || _this$instance$option2 === void 0 ? void 0 : _this$instance$option2.errorMsg) !== null && _this$instance$option !== void 0 ? _this$instance$option : '';\n      errorMsgEl =\n      /** @type {HTMLDivElement} */\n      this.instance.applyFilters('contentErrorElement', errorMsgEl, this);\n      this.element = createElement('pswp__content pswp__error-msg-container', 'div');\n      this.element.appendChild(errorMsgEl);\n      this.slide.container.innerText = '';\n      this.slide.container.appendChild(this.element);\n      this.slide.updateContentSize(true);\n      this.removePlaceholder();\n    }\n  }\n  /**\n   * Append the content\n   */\n\n\n  append() {\n    if (this.isAttached || !this.element) {\n      return;\n    }\n\n    this.isAttached = true;\n\n    if (this.state === LOAD_STATE.ERROR) {\n      this.displayError();\n      return;\n    }\n\n    if (this.instance.dispatch('contentAppend', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    const supportsDecode = ('decode' in this.element);\n\n    if (this.isImageContent()) {\n      // Use decode() on nearby slides\n      //\n      // Nearby slide images are in DOM and not hidden via display:none.\n      // However, they are placed offscreen (to the left and right side).\n      //\n      // Some browsers do not composite the image until it's actually visible,\n      // using decode() helps.\n      //\n      // You might ask \"why dont you just decode() and then append all images\",\n      // that's because I want to show image before it's fully loaded,\n      // as browser can render parts of image while it is loading.\n      // We do not do this in Safari due to partial loading bug.\n      if (supportsDecode && this.slide && (!this.slide.isActive || isSafari())) {\n        this.isDecoding = true; // purposefully using finally instead of then,\n        // as if srcset sizes changes dynamically - it may cause decode error\n\n        /** @type {HTMLImageElement} */\n\n        this.element.decode().catch(() => {}).finally(() => {\n          this.isDecoding = false;\n          this.appendImage();\n        });\n      } else {\n        this.appendImage();\n      }\n    } else if (this.slide && !this.element.parentNode) {\n      this.slide.container.appendChild(this.element);\n    }\n  }\n  /**\n   * Activate the slide,\n   * active slide is generally the current one,\n   * meaning the user can see it.\n   */\n\n\n  activate() {\n    if (this.instance.dispatch('contentActivate', {\n      content: this\n    }).defaultPrevented || !this.slide) {\n      return;\n    }\n\n    if (this.isImageContent() && this.isDecoding && !isSafari()) {\n      // add image to slide when it becomes active,\n      // even if it's not finished decoding\n      this.appendImage();\n    } else if (this.isError()) {\n      this.load(false, true); // try to reload\n    }\n\n    if (this.slide.holderElement) {\n      this.slide.holderElement.setAttribute('aria-hidden', 'false');\n    }\n  }\n  /**\n   * Deactivate the content\n   */\n\n\n  deactivate() {\n    this.instance.dispatch('contentDeactivate', {\n      content: this\n    });\n\n    if (this.slide && this.slide.holderElement) {\n      this.slide.holderElement.setAttribute('aria-hidden', 'true');\n    }\n  }\n  /**\n   * Remove the content from DOM\n   */\n\n\n  remove() {\n    this.isAttached = false;\n\n    if (this.instance.dispatch('contentRemove', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (this.element && this.element.parentNode) {\n      this.element.remove();\n    }\n\n    if (this.placeholder && this.placeholder.element) {\n      this.placeholder.element.remove();\n    }\n  }\n  /**\n   * Append the image content to slide container\n   */\n\n\n  appendImage() {\n    if (!this.isAttached) {\n      return;\n    }\n\n    if (this.instance.dispatch('contentAppendImage', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    } // ensure that element exists and is not already appended\n\n\n    if (this.slide && this.element && !this.element.parentNode) {\n      this.slide.container.appendChild(this.element);\n    }\n\n    if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {\n      this.removePlaceholder();\n    }\n  }\n\n}\n\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n\n/**\n * @param {PhotoSwipeOptions} options\n * @param {PhotoSwipeBase} pswp\n * @returns {Point}\n */\nfunction getViewportSize(options, pswp) {\n  if (options.getViewportSizeFn) {\n    const newViewportSize = options.getViewportSizeFn(options, pswp);\n\n    if (newViewportSize) {\n      return newViewportSize;\n    }\n  }\n\n  return {\n    x: document.documentElement.clientWidth,\n    // TODO: height on mobile is very incosistent due to toolbar\n    // find a way to improve this\n    //\n    // document.documentElement.clientHeight - doesn't seem to work well\n    y: window.innerHeight\n  };\n}\n/**\n * Parses padding option.\n * Supported formats:\n *\n * // Object\n * padding: {\n *  top: 0,\n *  bottom: 0,\n *  left: 0,\n *  right: 0\n * }\n *\n * // A function that returns the object\n * paddingFn: (viewportSize, itemData, index) => {\n *  return {\n *    top: 0,\n *    bottom: 0,\n *    left: 0,\n *    right: 0\n *  };\n * }\n *\n * // Legacy variant\n * paddingLeft: 0,\n * paddingRight: 0,\n * paddingTop: 0,\n * paddingBottom: 0,\n *\n * @param {'left' | 'top' | 'bottom' | 'right'} prop\n * @param {PhotoSwipeOptions} options PhotoSwipe options\n * @param {Point} viewportSize PhotoSwipe viewport size, for example: { x:800, y:600 }\n * @param {SlideData} itemData Data about the slide\n * @param {number} index Slide index\n * @returns {number}\n */\n\nfunction parsePaddingOption(prop, options, viewportSize, itemData, index) {\n  let paddingValue = 0;\n\n  if (options.paddingFn) {\n    paddingValue = options.paddingFn(viewportSize, itemData, index)[prop];\n  } else if (options.padding) {\n    paddingValue = options.padding[prop];\n  } else {\n    const legacyPropName = 'padding' + prop[0].toUpperCase() + prop.slice(1); // @ts-expect-error\n\n    if (options[legacyPropName]) {\n      // @ts-expect-error\n      paddingValue = options[legacyPropName];\n    }\n  }\n\n  return Number(paddingValue) || 0;\n}\n/**\n * @param {PhotoSwipeOptions} options\n * @param {Point} viewportSize\n * @param {SlideData} itemData\n * @param {number} index\n * @returns {Point}\n */\n\nfunction getPanAreaSize(options, viewportSize, itemData, index) {\n  return {\n    x: viewportSize.x - parsePaddingOption('left', options, viewportSize, itemData, index) - parsePaddingOption('right', options, viewportSize, itemData, index),\n    y: viewportSize.y - parsePaddingOption('top', options, viewportSize, itemData, index) - parsePaddingOption('bottom', options, viewportSize, itemData, index)\n  };\n}\n\nconst MAX_IMAGE_WIDTH = 4000;\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n\n/** @typedef {'fit' | 'fill' | number | ((zoomLevelObject: ZoomLevel) => number)} ZoomLevelOption */\n\n/**\n * Calculates zoom levels for specific slide.\n * Depends on viewport size and image size.\n */\n\nclass ZoomLevel {\n  /**\n   * @param {PhotoSwipeOptions} options PhotoSwipe options\n   * @param {SlideData} itemData Slide data\n   * @param {number} index Slide index\n   * @param {PhotoSwipe} [pswp] PhotoSwipe instance, can be undefined if not initialized yet\n   */\n  constructor(options, itemData, index, pswp) {\n    this.pswp = pswp;\n    this.options = options;\n    this.itemData = itemData;\n    this.index = index;\n    /** @type { Point | null } */\n\n    this.panAreaSize = null;\n    /** @type { Point | null } */\n\n    this.elementSize = null;\n    this.fit = 1;\n    this.fill = 1;\n    this.vFill = 1;\n    this.initial = 1;\n    this.secondary = 1;\n    this.max = 1;\n    this.min = 1;\n  }\n  /**\n   * Calculate initial, secondary and maximum zoom level for the specified slide.\n   *\n   * It should be called when either image or viewport size changes.\n   *\n   * @param {number} maxWidth\n   * @param {number} maxHeight\n   * @param {Point} panAreaSize\n   */\n\n\n  update(maxWidth, maxHeight, panAreaSize) {\n    /** @type {Point} */\n    const elementSize = {\n      x: maxWidth,\n      y: maxHeight\n    };\n    this.elementSize = elementSize;\n    this.panAreaSize = panAreaSize;\n    const hRatio = panAreaSize.x / elementSize.x;\n    const vRatio = panAreaSize.y / elementSize.y;\n    this.fit = Math.min(1, hRatio < vRatio ? hRatio : vRatio);\n    this.fill = Math.min(1, hRatio > vRatio ? hRatio : vRatio); // zoom.vFill defines zoom level of the image\n    // when it has 100% of viewport vertical space (height)\n\n    this.vFill = Math.min(1, vRatio);\n    this.initial = this._getInitial();\n    this.secondary = this._getSecondary();\n    this.max = Math.max(this.initial, this.secondary, this._getMax());\n    this.min = Math.min(this.fit, this.initial, this.secondary);\n\n    if (this.pswp) {\n      this.pswp.dispatch('zoomLevelsUpdate', {\n        zoomLevels: this,\n        slideData: this.itemData\n      });\n    }\n  }\n  /**\n   * Parses user-defined zoom option.\n   *\n   * @private\n   * @param {'initial' | 'secondary' | 'max'} optionPrefix Zoom level option prefix (initial, secondary, max)\n   * @returns { number | undefined }\n   */\n\n\n  _parseZoomLevelOption(optionPrefix) {\n    const optionName =\n    /** @type {'initialZoomLevel' | 'secondaryZoomLevel' | 'maxZoomLevel'} */\n    optionPrefix + 'ZoomLevel';\n    const optionValue = this.options[optionName];\n\n    if (!optionValue) {\n      return;\n    }\n\n    if (typeof optionValue === 'function') {\n      return optionValue(this);\n    }\n\n    if (optionValue === 'fill') {\n      return this.fill;\n    }\n\n    if (optionValue === 'fit') {\n      return this.fit;\n    }\n\n    return Number(optionValue);\n  }\n  /**\n   * Get zoom level to which image will be zoomed after double-tap gesture,\n   * or when user clicks on zoom icon,\n   * or mouse-click on image itself.\n   * If you return 1 image will be zoomed to its original size.\n   *\n   * @private\n   * @return {number}\n   */\n\n\n  _getSecondary() {\n    let currZoomLevel = this._parseZoomLevelOption('secondary');\n\n    if (currZoomLevel) {\n      return currZoomLevel;\n    } // 3x of \"fit\" state, but not larger than original\n\n\n    currZoomLevel = Math.min(1, this.fit * 3);\n\n    if (this.elementSize && currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) {\n      currZoomLevel = MAX_IMAGE_WIDTH / this.elementSize.x;\n    }\n\n    return currZoomLevel;\n  }\n  /**\n   * Get initial image zoom level.\n   *\n   * @private\n   * @return {number}\n   */\n\n\n  _getInitial() {\n    return this._parseZoomLevelOption('initial') || this.fit;\n  }\n  /**\n   * Maximum zoom level when user zooms\n   * via zoom/pinch gesture,\n   * via cmd/ctrl-wheel or via trackpad.\n   *\n   * @private\n   * @return {number}\n   */\n\n\n  _getMax() {\n    // max zoom level is x4 from \"fit state\",\n    // used for zoom gesture and ctrl/trackpad zoom\n    return this._parseZoomLevelOption('max') || Math.max(1, this.fit * 4);\n  }\n\n}\n\n/**\n * Lazy-load an image\n * This function is used both by Lightbox and PhotoSwipe core,\n * thus it can be called before dialog is opened.\n *\n * @param {SlideData} itemData Data about the slide\n * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance\n * @param {number} index\n * @returns {Content} Image that is being decoded or false.\n */\n\nfunction lazyLoadData(itemData, instance, index) {\n  const content = instance.createContentFromData(itemData, index);\n  /** @type {ZoomLevel | undefined} */\n\n  let zoomLevel;\n  const {\n    options\n  } = instance; // We need to know dimensions of the image to preload it,\n  // as it might use srcset, and we need to define sizes\n\n  if (options) {\n    zoomLevel = new ZoomLevel(options, itemData, -1);\n    let viewportSize;\n\n    if (instance.pswp) {\n      viewportSize = instance.pswp.viewportSize;\n    } else {\n      viewportSize = getViewportSize(options, instance);\n    }\n\n    const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index);\n    zoomLevel.update(content.width, content.height, panAreaSize);\n  }\n\n  content.lazyLoad();\n\n  if (zoomLevel) {\n    content.setDisplayedSize(Math.ceil(content.width * zoomLevel.initial), Math.ceil(content.height * zoomLevel.initial));\n  }\n\n  return content;\n}\n/**\n * Lazy-loads specific slide.\n * This function is used both by Lightbox and PhotoSwipe core,\n * thus it can be called before dialog is opened.\n *\n * By default, it loads image based on viewport size and initial zoom level.\n *\n * @param {number} index Slide index\n * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox eventable instance\n * @returns {Content | undefined}\n */\n\nfunction lazyLoadSlide(index, instance) {\n  const itemData = instance.getItemData(index);\n\n  if (instance.dispatch('lazyLoadSlide', {\n    index,\n    itemData\n  }).defaultPrevented) {\n    return;\n  }\n\n  return lazyLoadData(itemData, instance, index);\n}\n\n/** @typedef {import(\"../photoswipe.js\").default} PhotoSwipe */\n\n/** @typedef {import(\"../slide/slide.js\").SlideData} SlideData */\n\n/**\n * PhotoSwipe base class that can retrieve data about every slide.\n * Shared by PhotoSwipe Core and PhotoSwipe Lightbox\n */\n\nclass PhotoSwipeBase extends Eventable {\n  /**\n   * Get total number of slides\n   *\n   * @returns {number}\n   */\n  getNumItems() {\n    var _this$options;\n\n    let numItems = 0;\n    const dataSource = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.dataSource;\n\n    if (dataSource && 'length' in dataSource) {\n      // may be an array or just object with length property\n      numItems = dataSource.length;\n    } else if (dataSource && 'gallery' in dataSource) {\n      // query DOM elements\n      if (!dataSource.items) {\n        dataSource.items = this._getGalleryDOMElements(dataSource.gallery);\n      }\n\n      if (dataSource.items) {\n        numItems = dataSource.items.length;\n      }\n    } // legacy event, before filters were introduced\n\n\n    const event = this.dispatch('numItems', {\n      dataSource,\n      numItems\n    });\n    return this.applyFilters('numItems', event.numItems, dataSource);\n  }\n  /**\n   * @param {SlideData} slideData\n   * @param {number} index\n   * @returns {Content}\n   */\n\n\n  createContentFromData(slideData, index) {\n    return new Content(slideData, this, index);\n  }\n  /**\n   * Get item data by index.\n   *\n   * \"item data\" should contain normalized information that PhotoSwipe needs to generate a slide.\n   * For example, it may contain properties like\n   * `src`, `srcset`, `w`, `h`, which will be used to generate a slide with image.\n   *\n   * @param {number} index\n   * @returns {SlideData}\n   */\n\n\n  getItemData(index) {\n    var _this$options2;\n\n    const dataSource = (_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : _this$options2.dataSource;\n    /** @type {SlideData | HTMLElement} */\n\n    let dataSourceItem = {};\n\n    if (Array.isArray(dataSource)) {\n      // Datasource is an array of elements\n      dataSourceItem = dataSource[index];\n    } else if (dataSource && 'gallery' in dataSource) {\n      // dataSource has gallery property,\n      // thus it was created by Lightbox, based on\n      // gallery and children options\n      // query DOM elements\n      if (!dataSource.items) {\n        dataSource.items = this._getGalleryDOMElements(dataSource.gallery);\n      }\n\n      dataSourceItem = dataSource.items[index];\n    }\n\n    let itemData = dataSourceItem;\n\n    if (itemData instanceof Element) {\n      itemData = this._domElementToItemData(itemData);\n    } // Dispatching the itemData event,\n    // it's a legacy verion before filters were introduced\n\n\n    const event = this.dispatch('itemData', {\n      itemData: itemData || {},\n      index\n    });\n    return this.applyFilters('itemData', event.itemData, index);\n  }\n  /**\n   * Get array of gallery DOM elements,\n   * based on childSelector and gallery element.\n   *\n   * @param {HTMLElement} galleryElement\n   * @returns {HTMLElement[]}\n   */\n\n\n  _getGalleryDOMElements(galleryElement) {\n    var _this$options3, _this$options4;\n\n    if ((_this$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.children || (_this$options4 = this.options) !== null && _this$options4 !== void 0 && _this$options4.childSelector) {\n      return getElementsFromOption(this.options.children, this.options.childSelector, galleryElement) || [];\n    }\n\n    return [galleryElement];\n  }\n  /**\n   * Converts DOM element to item data object.\n   *\n   * @param {HTMLElement} element DOM element\n   * @returns {SlideData}\n   */\n\n\n  _domElementToItemData(element) {\n    /** @type {SlideData} */\n    const itemData = {\n      element\n    };\n    const linkEl =\n    /** @type {HTMLAnchorElement} */\n    element.tagName === 'A' ? element : element.querySelector('a');\n\n    if (linkEl) {\n      // src comes from data-pswp-src attribute,\n      // if it's empty link href is used\n      itemData.src = linkEl.dataset.pswpSrc || linkEl.href;\n\n      if (linkEl.dataset.pswpSrcset) {\n        itemData.srcset = linkEl.dataset.pswpSrcset;\n      }\n\n      itemData.width = linkEl.dataset.pswpWidth ? parseInt(linkEl.dataset.pswpWidth, 10) : 0;\n      itemData.height = linkEl.dataset.pswpHeight ? parseInt(linkEl.dataset.pswpHeight, 10) : 0; // support legacy w & h properties\n\n      itemData.w = itemData.width;\n      itemData.h = itemData.height;\n\n      if (linkEl.dataset.pswpType) {\n        itemData.type = linkEl.dataset.pswpType;\n      }\n\n      const thumbnailEl = element.querySelector('img');\n\n      if (thumbnailEl) {\n        var _thumbnailEl$getAttri;\n\n        // msrc is URL to placeholder image that's displayed before large image is loaded\n        // by default it's displayed only for the first slide\n        itemData.msrc = thumbnailEl.currentSrc || thumbnailEl.src;\n        itemData.alt = (_thumbnailEl$getAttri = thumbnailEl.getAttribute('alt')) !== null && _thumbnailEl$getAttri !== void 0 ? _thumbnailEl$getAttri : '';\n      }\n\n      if (linkEl.dataset.pswpCropped || linkEl.dataset.cropped) {\n        itemData.thumbCropped = true;\n      }\n    }\n\n    return this.applyFilters('domItemData', itemData, element, linkEl);\n  }\n  /**\n   * Lazy-load by slide data\n   *\n   * @param {SlideData} itemData Data about the slide\n   * @param {number} index\n   * @returns {Content} Image that is being decoded or false.\n   */\n\n\n  lazyLoadData(itemData, index) {\n    return lazyLoadData(itemData, this, index);\n  }\n\n}\n\n/**\n * @template T\n * @typedef {import('../types.js').Type<T>} Type<T>\n */\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n\n/** @typedef {import('../photoswipe.js').DataSource} DataSource */\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {import('../slide/content.js').default} Content */\n\n/** @typedef {import('../core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */\n\n/** @typedef {import('../core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {import('../core/eventable.js').EventCallback<T>} EventCallback<T>\n */\n\n/**\n * PhotoSwipe Lightbox\n *\n * - If user has unsupported browser it falls back to default browser action (just opens URL)\n * - Binds click event to links that should open PhotoSwipe\n * - parses DOM strcture for PhotoSwipe (retrieves large image URLs and sizes)\n * - Initializes PhotoSwipe\n *\n *\n * Loader options use the same object as PhotoSwipe, and supports such options:\n *\n * gallery - Element | Element[] | NodeList | string selector for the gallery element\n * children - Element | Element[] | NodeList | string selector for the gallery children\n *\n */\n\nclass PhotoSwipeLightbox extends PhotoSwipeBase {\n  /**\n   * @param {PhotoSwipeOptions} [options]\n   */\n  constructor(options) {\n    super();\n    /** @type {PhotoSwipeOptions} */\n\n    this.options = options || {};\n    this._uid = 0;\n    this.shouldOpen = false;\n    /**\n     * @private\n     * @type {Content | undefined}\n     */\n\n    this._preloadedContent = undefined;\n    this.onThumbnailsClick = this.onThumbnailsClick.bind(this);\n  }\n  /**\n   * Initialize lightbox, should be called only once.\n   * It's not included in the main constructor, so you may bind events before it.\n   */\n\n\n  init() {\n    // Bind click events to each gallery\n    getElementsFromOption(this.options.gallery, this.options.gallerySelector).forEach(galleryElement => {\n      galleryElement.addEventListener('click', this.onThumbnailsClick, false);\n    });\n  }\n  /**\n   * @param {MouseEvent} e\n   */\n\n\n  onThumbnailsClick(e) {\n    // Exit and allow default browser action if:\n    if (specialKeyUsed(e) // ... if clicked with a special key (ctrl/cmd...)\n    || window.pswp) {\n      // ... if PhotoSwipe is already open\n      return;\n    } // If both clientX and clientY are 0 or not defined,\n    // the event is likely triggered by keyboard,\n    // so we do not pass the initialPoint\n    //\n    // Note that some screen readers emulate the mouse position,\n    // so it's not the ideal way to detect them.\n    //\n\n    /** @type {Point | null} */\n\n\n    let initialPoint = {\n      x: e.clientX,\n      y: e.clientY\n    };\n\n    if (!initialPoint.x && !initialPoint.y) {\n      initialPoint = null;\n    }\n\n    let clickedIndex = this.getClickedIndex(e);\n    clickedIndex = this.applyFilters('clickedIndex', clickedIndex, e, this);\n    /** @type {DataSource} */\n\n    const dataSource = {\n      gallery:\n      /** @type {HTMLElement} */\n      e.currentTarget\n    };\n\n    if (clickedIndex >= 0) {\n      e.preventDefault();\n      this.loadAndOpen(clickedIndex, dataSource, initialPoint);\n    }\n  }\n  /**\n   * Get index of gallery item that was clicked.\n   *\n   * @param {MouseEvent} e click event\n   * @returns {number}\n   */\n\n\n  getClickedIndex(e) {\n    // legacy option\n    if (this.options.getClickedIndexFn) {\n      return this.options.getClickedIndexFn.call(this, e);\n    }\n\n    const clickedTarget =\n    /** @type {HTMLElement} */\n    e.target;\n    const childElements = getElementsFromOption(this.options.children, this.options.childSelector,\n    /** @type {HTMLElement} */\n    e.currentTarget);\n    const clickedChildIndex = childElements.findIndex(child => child === clickedTarget || child.contains(clickedTarget));\n\n    if (clickedChildIndex !== -1) {\n      return clickedChildIndex;\n    } else if (this.options.children || this.options.childSelector) {\n      // click wasn't on a child element\n      return -1;\n    } // There is only one item (which is the gallery)\n\n\n    return 0;\n  }\n  /**\n   * Load and open PhotoSwipe\n   *\n   * @param {number} index\n   * @param {DataSource} [dataSource]\n   * @param {Point | null} [initialPoint]\n   * @returns {boolean}\n   */\n\n\n  loadAndOpen(index, dataSource, initialPoint) {\n    // Check if the gallery is already open\n    if (window.pswp || !this.options) {\n      return false;\n    } // Use the first gallery element if dataSource is not provided\n\n\n    if (!dataSource && this.options.gallery && this.options.children) {\n      const galleryElements = getElementsFromOption(this.options.gallery);\n\n      if (galleryElements[0]) {\n        dataSource = {\n          gallery: galleryElements[0]\n        };\n      }\n    } // set initial index\n\n\n    this.options.index = index; // define options for PhotoSwipe constructor\n\n    this.options.initialPointerPos = initialPoint;\n    this.shouldOpen = true;\n    this.preload(index, dataSource);\n    return true;\n  }\n  /**\n   * Load the main module and the slide content by index\n   *\n   * @param {number} index\n   * @param {DataSource} [dataSource]\n   */\n\n\n  preload(index, dataSource) {\n    const {\n      options\n    } = this;\n\n    if (dataSource) {\n      options.dataSource = dataSource;\n    } // Add the main module\n\n    /** @type {Promise<Type<PhotoSwipe>>[]} */\n\n\n    const promiseArray = [];\n    const pswpModuleType = typeof options.pswpModule;\n\n    if (isPswpClass(options.pswpModule)) {\n      promiseArray.push(Promise.resolve(\n      /** @type {Type<PhotoSwipe>} */\n      options.pswpModule));\n    } else if (pswpModuleType === 'string') {\n      throw new Error('pswpModule as string is no longer supported');\n    } else if (pswpModuleType === 'function') {\n      promiseArray.push(\n      /** @type {() => Promise<Type<PhotoSwipe>>} */\n      options.pswpModule());\n    } else {\n      throw new Error('pswpModule is not valid');\n    } // Add custom-defined promise, if any\n\n\n    if (typeof options.openPromise === 'function') {\n      // allow developers to perform some task before opening\n      promiseArray.push(options.openPromise());\n    }\n\n    if (options.preloadFirstSlide !== false && index >= 0) {\n      this._preloadedContent = lazyLoadSlide(index, this);\n    } // Wait till all promises resolve and open PhotoSwipe\n\n\n    const uid = ++this._uid;\n    Promise.all(promiseArray).then(iterableModules => {\n      if (this.shouldOpen) {\n        const mainModule = iterableModules[0];\n\n        this._openPhotoswipe(mainModule, uid);\n      }\n    });\n  }\n  /**\n   * @private\n   * @param {Type<PhotoSwipe> | { default: Type<PhotoSwipe> }} module\n   * @param {number} uid\n   */\n\n\n  _openPhotoswipe(module, uid) {\n    // Cancel opening if UID doesn't match the current one\n    // (if user clicked on another gallery item before current was loaded).\n    //\n    // Or if shouldOpen flag is set to false\n    // (developer may modify it via public API)\n    if (uid !== this._uid && this.shouldOpen) {\n      return;\n    }\n\n    this.shouldOpen = false; // PhotoSwipe is already open\n\n    if (window.pswp) {\n      return;\n    }\n    /**\n     * Pass data to PhotoSwipe and open init\n     *\n     * @type {PhotoSwipe}\n     */\n\n\n    const pswp = typeof module === 'object' ? new module.default(this.options) // eslint-disable-line\n    : new module(this.options); // eslint-disable-line\n\n    this.pswp = pswp;\n    window.pswp = pswp; // map listeners from Lightbox to PhotoSwipe Core\n\n    /** @type {(keyof PhotoSwipeEventsMap)[]} */\n\n    Object.keys(this._listeners).forEach(name => {\n      var _this$_listeners$name;\n\n      (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.forEach(fn => {\n        pswp.on(name,\n        /** @type {EventCallback<typeof name>} */\n        fn);\n      });\n    }); // same with filters\n\n    /** @type {(keyof PhotoSwipeFiltersMap)[]} */\n\n    Object.keys(this._filters).forEach(name => {\n      var _this$_filters$name;\n\n      (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.forEach(filter => {\n        pswp.addFilter(name, filter.fn, filter.priority);\n      });\n    });\n\n    if (this._preloadedContent) {\n      pswp.contentLoader.addToCache(this._preloadedContent);\n      this._preloadedContent = undefined;\n    }\n\n    pswp.on('destroy', () => {\n      // clean up public variables\n      this.pswp = undefined;\n      delete window.pswp;\n    });\n    pswp.init();\n  }\n  /**\n   * Unbinds all events, closes PhotoSwipe if it's open.\n   */\n\n\n  destroy() {\n    var _this$pswp;\n\n    (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.destroy();\n    this.shouldOpen = false;\n    this._listeners = {};\n    getElementsFromOption(this.options.gallery, this.options.gallerySelector).forEach(galleryElement => {\n      galleryElement.removeEventListener('click', this.onThumbnailsClick, false);\n    });\n  }\n\n}\n\nexport { PhotoSwipeLightbox as default };\n//# sourceMappingURL=photoswipe-lightbox.esm.js.map\n"
  },
  {
    "path": "demo-docs-website/static/photoswipe/photoswipe.css",
    "content": "/*! PhotoSwipe main CSS by Dmytro Semenov | photoswipe.com */\n\n.pswp {\n  --pswp-bg: #000;\n  --pswp-placeholder-bg: #222;\n  \n\n  --pswp-root-z-index: 100000;\n  \n  --pswp-preloader-color: rgba(79, 79, 79, 0.4);\n  --pswp-preloader-color-secondary: rgba(255, 255, 255, 0.9);\n  \n  /* defined via js:\n  --pswp-transition-duration: 333ms; */\n  \n  --pswp-icon-color: #fff;\n  --pswp-icon-color-secondary: #4f4f4f;\n  --pswp-icon-stroke-color: #4f4f4f;\n  --pswp-icon-stroke-width: 2px;\n\n  --pswp-error-text-color: var(--pswp-icon-color);\n}\n\n\n/*\n\tStyles for basic PhotoSwipe (pswp) functionality (sliding area, open/close transitions)\n*/\n\n.pswp {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n\tz-index: var(--pswp-root-z-index);\n\tdisplay: none;\n\ttouch-action: none;\n\toutline: 0;\n\topacity: 0.003;\n\tcontain: layout style size;\n\t-webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\n/* Prevents focus outline on the root element,\n  (it may be focused initially) */\n.pswp:focus {\n  outline: 0;\n}\n\n.pswp * {\n  box-sizing: border-box;\n}\n\n.pswp img {\n  max-width: none;\n}\n\n.pswp--open {\n\tdisplay: block;\n}\n\n.pswp,\n.pswp__bg {\n\ttransform: translateZ(0);\n\twill-change: opacity;\n}\n\n.pswp__bg {\n  opacity: 0.005;\n\tbackground: var(--pswp-bg);\n}\n\n.pswp,\n.pswp__scroll-wrap {\n\toverflow: hidden;\n}\n\n.pswp__scroll-wrap,\n.pswp__bg,\n.pswp__container,\n.pswp__item,\n.pswp__content,\n.pswp__img,\n.pswp__zoom-wrap {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n}\n\n.pswp__img,\n.pswp__zoom-wrap {\n\twidth: auto;\n\theight: auto;\n}\n\n.pswp--click-to-zoom.pswp--zoom-allowed .pswp__img {\n\tcursor: -webkit-zoom-in;\n\tcursor: -moz-zoom-in;\n\tcursor: zoom-in;\n}\n\n.pswp--click-to-zoom.pswp--zoomed-in .pswp__img {\n\tcursor: move;\n\tcursor: -webkit-grab;\n\tcursor: -moz-grab;\n\tcursor: grab;\n}\n\n.pswp--click-to-zoom.pswp--zoomed-in .pswp__img:active {\n  cursor: -webkit-grabbing;\n  cursor: -moz-grabbing;\n  cursor: grabbing;\n}\n\n/* :active to override grabbing cursor */\n.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img,\n.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img:active,\n.pswp__img {\n\tcursor: -webkit-zoom-out;\n\tcursor: -moz-zoom-out;\n\tcursor: zoom-out;\n}\n\n\n/* Prevent selection and tap highlights */\n.pswp__container,\n.pswp__img,\n.pswp__button,\n.pswp__counter {\n\t-webkit-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n}\n\n.pswp__item {\n\t/* z-index for fade transition */\n\tz-index: 1;\n\toverflow: hidden;\n}\n\n.pswp__hidden {\n\tdisplay: none !important;\n}\n\n/* Allow to click through pswp__content element, but not its children */\n.pswp__content {\n  pointer-events: none;\n}\n.pswp__content > * {\n  pointer-events: auto;\n}\n\n\n/*\n\n  PhotoSwipe UI\n\n*/\n\n/*\n\tError message appears when image is not loaded\n\t(JS option errorMsg controls markup)\n*/\n.pswp__error-msg-container {\n  display: grid;\n}\n.pswp__error-msg {\n\tmargin: auto;\n\tfont-size: 1em;\n\tline-height: 1;\n\tcolor: var(--pswp-error-text-color);\n}\n\n/*\nclass pswp__hide-on-close is applied to elements that\nshould hide (for example fade out) when PhotoSwipe is closed\nand show (for example fade in) when PhotoSwipe is opened\n */\n.pswp .pswp__hide-on-close {\n\topacity: 0.005;\n\twill-change: opacity;\n\ttransition: opacity var(--pswp-transition-duration) cubic-bezier(0.4, 0, 0.22, 1);\n\tz-index: 10; /* always overlap slide content */\n\tpointer-events: none; /* hidden elements should not be clickable */\n}\n\n/* class pswp--ui-visible is added when opening or closing transition starts */\n.pswp--ui-visible .pswp__hide-on-close {\n\topacity: 1;\n\tpointer-events: auto;\n}\n\n/* <button> styles, including css reset */\n.pswp__button {\n\tposition: relative;\n\tdisplay: block;\n\twidth: 50px;\n\theight: 60px;\n\tpadding: 0;\n\tmargin: 0;\n\toverflow: hidden;\n\tcursor: pointer;\n\tbackground: none;\n\tborder: 0;\n\tbox-shadow: none;\n\topacity: 0.85;\n\t-webkit-appearance: none;\n\t-webkit-touch-callout: none;\n}\n\n.pswp__button:hover,\n.pswp__button:active,\n.pswp__button:focus {\n  transition: none;\n  padding: 0;\n  background: none;\n  border: 0;\n  box-shadow: none;\n  opacity: 1;\n}\n\n.pswp__button:disabled {\n  opacity: 0.3;\n  cursor: auto;\n}\n\n.pswp__icn {\n  fill: var(--pswp-icon-color);\n  color: var(--pswp-icon-color-secondary);\n}\n\n.pswp__icn {\n  position: absolute;\n  top: 14px;\n  left: 9px;\n  width: 32px;\n  height: 32px;\n  overflow: hidden;\n  pointer-events: none;\n}\n\n.pswp__icn-shadow {\n  stroke: var(--pswp-icon-stroke-color);\n  stroke-width: var(--pswp-icon-stroke-width);\n  fill: none;\n}\n\n.pswp__icn:focus {\n\toutline: 0;\n}\n\n/*\n\tdiv element that matches size of large image,\n\tlarge image loads on top of it,\n\tused when msrc is not provided\n*/\ndiv.pswp__img--placeholder,\n.pswp__img--with-bg {\n\tbackground: var(--pswp-placeholder-bg);\n}\n\n.pswp__top-bar {\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\twidth: 100%;\n\theight: 60px;\n\tdisplay: flex;\n  flex-direction: row;\n  justify-content: flex-end;\n\tz-index: 10;\n\n\t/* allow events to pass through top bar itself */\n\tpointer-events: none !important;\n}\n.pswp__top-bar > * {\n  pointer-events: auto;\n  /* this makes transition significantly more smooth,\n     even though inner elements are not animated */\n  will-change: opacity;\n}\n\n\n/*\n\n  Close button\n\n*/\n.pswp__button--close {\n  margin-right: 6px;\n}\n\n\n/*\n\n  Arrow buttons\n\n*/\n.pswp__button--arrow {\n  position: absolute;\n  top: 0;\n  width: 75px;\n  height: 100px;\n  top: 50%;\n  margin-top: -50px;\n}\n\n.pswp__button--arrow:disabled {\n  display: none;\n  cursor: default;\n}\n\n.pswp__button--arrow .pswp__icn {\n  top: 50%;\n  margin-top: -30px;\n  width: 60px;\n  height: 60px;\n  background: none;\n  border-radius: 0;\n}\n\n.pswp--one-slide .pswp__button--arrow {\n  display: none;\n}\n\n/* hide arrows on touch screens */\n.pswp--touch .pswp__button--arrow {\n  visibility: hidden;\n}\n\n/* show arrows only after mouse was used */\n.pswp--has_mouse .pswp__button--arrow {\n  visibility: visible;\n}\n\n.pswp__button--arrow--prev {\n  right: auto;\n  left: 0px;\n}\n\n.pswp__button--arrow--next {\n  right: 0px;\n}\n.pswp__button--arrow--next .pswp__icn {\n  left: auto;\n  right: 14px;\n  /* flip horizontally */\n  transform: scale(-1, 1);\n}\n\n/*\n\n  Zoom button\n\n*/\n.pswp__button--zoom {\n  display: none;\n}\n\n.pswp--zoom-allowed .pswp__button--zoom {\n  display: block;\n}\n\n/* \"+\" => \"-\" */\n.pswp--zoomed-in .pswp__zoom-icn-bar-v {\n  display: none;\n}\n\n\n/*\n\n  Loading indicator\n\n*/\n.pswp__preloader {\n  position: relative;\n  overflow: hidden;\n  width: 50px;\n  height: 60px;\n  margin-right: auto;\n}\n\n.pswp__preloader .pswp__icn {\n  opacity: 0;\n  transition: opacity 0.2s linear;\n  animation: pswp-clockwise 600ms linear infinite;\n}\n\n.pswp__preloader--active .pswp__icn {\n  opacity: 0.85;\n}\n\n@keyframes pswp-clockwise {\n  0% { transform: rotate(0deg); }\n  100% { transform: rotate(360deg); }\n}\n\n\n/*\n\n  \"1 of 10\" counter\n\n*/\n.pswp__counter {\n  height: 30px;\n  margin-top: 15px;\n  margin-inline-start: 20px;\n  font-size: 14px;\n  line-height: 30px;\n  color: var(--pswp-icon-color);\n  text-shadow: 1px 1px 3px var(--pswp-icon-color-secondary);\n  opacity: 0.85;\n}\n\n.pswp--one-slide .pswp__counter {\n  display: none;\n}\n"
  },
  {
    "path": "demo-docs-website/static/photoswipe/photoswipe.esm.js",
    "content": "/*!\n  * PhotoSwipe 5.4.4 - https://photoswipe.com\n  * (c) 2024 Dmytro Semenov\n  */\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/**\n * @template {keyof HTMLElementTagNameMap} T\n * @param {string} className\n * @param {T} tagName\n * @param {Node} [appendToEl]\n * @returns {HTMLElementTagNameMap[T]}\n */\nfunction createElement(className, tagName, appendToEl) {\n  const el = document.createElement(tagName);\n\n  if (className) {\n    el.className = className;\n  }\n\n  if (appendToEl) {\n    appendToEl.appendChild(el);\n  }\n\n  return el;\n}\n/**\n * @param {Point} p1\n * @param {Point} p2\n * @returns {Point}\n */\n\nfunction equalizePoints(p1, p2) {\n  p1.x = p2.x;\n  p1.y = p2.y;\n\n  if (p2.id !== undefined) {\n    p1.id = p2.id;\n  }\n\n  return p1;\n}\n/**\n * @param {Point} p\n */\n\nfunction roundPoint(p) {\n  p.x = Math.round(p.x);\n  p.y = Math.round(p.y);\n}\n/**\n * Returns distance between two points.\n *\n * @param {Point} p1\n * @param {Point} p2\n * @returns {number}\n */\n\nfunction getDistanceBetween(p1, p2) {\n  const x = Math.abs(p1.x - p2.x);\n  const y = Math.abs(p1.y - p2.y);\n  return Math.sqrt(x * x + y * y);\n}\n/**\n * Whether X and Y positions of points are equal\n *\n * @param {Point} p1\n * @param {Point} p2\n * @returns {boolean}\n */\n\nfunction pointsEqual(p1, p2) {\n  return p1.x === p2.x && p1.y === p2.y;\n}\n/**\n * The float result between the min and max values.\n *\n * @param {number} val\n * @param {number} min\n * @param {number} max\n * @returns {number}\n */\n\nfunction clamp(val, min, max) {\n  return Math.min(Math.max(val, min), max);\n}\n/**\n * Get transform string\n *\n * @param {number} x\n * @param {number} [y]\n * @param {number} [scale]\n * @returns {string}\n */\n\nfunction toTransformString(x, y, scale) {\n  let propValue = `translate3d(${x}px,${y || 0}px,0)`;\n\n  if (scale !== undefined) {\n    propValue += ` scale3d(${scale},${scale},1)`;\n  }\n\n  return propValue;\n}\n/**\n * Apply transform:translate(x, y) scale(scale) to element\n *\n * @param {HTMLElement} el\n * @param {number} x\n * @param {number} [y]\n * @param {number} [scale]\n */\n\nfunction setTransform(el, x, y, scale) {\n  el.style.transform = toTransformString(x, y, scale);\n}\nconst defaultCSSEasing = 'cubic-bezier(.4,0,.22,1)';\n/**\n * Apply CSS transition to element\n *\n * @param {HTMLElement} el\n * @param {string} [prop] CSS property to animate\n * @param {number} [duration] in ms\n * @param {string} [ease] CSS easing function\n */\n\nfunction setTransitionStyle(el, prop, duration, ease) {\n  // inOut: 'cubic-bezier(.4, 0, .22, 1)', // for \"toggle state\" transitions\n  // out: 'cubic-bezier(0, 0, .22, 1)', // for \"show\" transitions\n  // in: 'cubic-bezier(.4, 0, 1, 1)'// for \"hide\" transitions\n  el.style.transition = prop ? `${prop} ${duration}ms ${ease || defaultCSSEasing}` : 'none';\n}\n/**\n * Apply width and height CSS properties to element\n *\n * @param {HTMLElement} el\n * @param {string | number} w\n * @param {string | number} h\n */\n\nfunction setWidthHeight(el, w, h) {\n  el.style.width = typeof w === 'number' ? `${w}px` : w;\n  el.style.height = typeof h === 'number' ? `${h}px` : h;\n}\n/**\n * @param {HTMLElement} el\n */\n\nfunction removeTransitionStyle(el) {\n  setTransitionStyle(el);\n}\n/**\n * @param {HTMLImageElement} img\n * @returns {Promise<HTMLImageElement | void>}\n */\n\nfunction decodeImage(img) {\n  if ('decode' in img) {\n    return img.decode().catch(() => {});\n  }\n\n  if (img.complete) {\n    return Promise.resolve(img);\n  }\n\n  return new Promise((resolve, reject) => {\n    img.onload = () => resolve(img);\n\n    img.onerror = reject;\n  });\n}\n/** @typedef {LOAD_STATE[keyof LOAD_STATE]} LoadState */\n\n/** @type {{ IDLE: 'idle'; LOADING: 'loading'; LOADED: 'loaded'; ERROR: 'error' }} */\n\nconst LOAD_STATE = {\n  IDLE: 'idle',\n  LOADING: 'loading',\n  LOADED: 'loaded',\n  ERROR: 'error'\n};\n/**\n * Check if click or keydown event was dispatched\n * with a special key or via mouse wheel.\n *\n * @param {MouseEvent | KeyboardEvent} e\n * @returns {boolean}\n */\n\nfunction specialKeyUsed(e) {\n  return 'button' in e && e.button === 1 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey;\n}\n/**\n * Parse `gallery` or `children` options.\n *\n * @param {import('../photoswipe.js').ElementProvider} [option]\n * @param {string} [legacySelector]\n * @param {HTMLElement | Document} [parent]\n * @returns HTMLElement[]\n */\n\nfunction getElementsFromOption(option, legacySelector, parent = document) {\n  /** @type {HTMLElement[]} */\n  let elements = [];\n\n  if (option instanceof Element) {\n    elements = [option];\n  } else if (option instanceof NodeList || Array.isArray(option)) {\n    elements = Array.from(option);\n  } else {\n    const selector = typeof option === 'string' ? option : legacySelector;\n\n    if (selector) {\n      elements = Array.from(parent.querySelectorAll(selector));\n    }\n  }\n\n  return elements;\n}\n/**\n * Check if browser is Safari\n *\n * @returns {boolean}\n */\n\nfunction isSafari() {\n  return !!(navigator.vendor && navigator.vendor.match(/apple/i));\n}\n\n// Detect passive event listener support\nlet supportsPassive = false;\n/* eslint-disable */\n\ntry {\n  /* @ts-ignore */\n  window.addEventListener('test', null, Object.defineProperty({}, 'passive', {\n    get: () => {\n      supportsPassive = true;\n    }\n  }));\n} catch (e) {}\n/* eslint-enable */\n\n/**\n * @typedef {Object} PoolItem\n * @prop {HTMLElement | Window | Document | undefined | null} target\n * @prop {string} type\n * @prop {EventListenerOrEventListenerObject} listener\n * @prop {boolean} [passive]\n */\n\n\nclass DOMEvents {\n  constructor() {\n    /**\n     * @type {PoolItem[]}\n     * @private\n     */\n    this._pool = [];\n  }\n  /**\n   * Adds event listeners\n   *\n   * @param {PoolItem['target']} target\n   * @param {PoolItem['type']} type Can be multiple, separated by space.\n   * @param {PoolItem['listener']} listener\n   * @param {PoolItem['passive']} [passive]\n   */\n\n\n  add(target, type, listener, passive) {\n    this._toggleListener(target, type, listener, passive);\n  }\n  /**\n   * Removes event listeners\n   *\n   * @param {PoolItem['target']} target\n   * @param {PoolItem['type']} type\n   * @param {PoolItem['listener']} listener\n   * @param {PoolItem['passive']} [passive]\n   */\n\n\n  remove(target, type, listener, passive) {\n    this._toggleListener(target, type, listener, passive, true);\n  }\n  /**\n   * Removes all bound events\n   */\n\n\n  removeAll() {\n    this._pool.forEach(poolItem => {\n      this._toggleListener(poolItem.target, poolItem.type, poolItem.listener, poolItem.passive, true, true);\n    });\n\n    this._pool = [];\n  }\n  /**\n   * Adds or removes event\n   *\n   * @private\n   * @param {PoolItem['target']} target\n   * @param {PoolItem['type']} type\n   * @param {PoolItem['listener']} listener\n   * @param {PoolItem['passive']} [passive]\n   * @param {boolean} [unbind] Whether the event should be added or removed\n   * @param {boolean} [skipPool] Whether events pool should be skipped\n   */\n\n\n  _toggleListener(target, type, listener, passive, unbind, skipPool) {\n    if (!target) {\n      return;\n    }\n\n    const methodName = unbind ? 'removeEventListener' : 'addEventListener';\n    const types = type.split(' ');\n    types.forEach(eType => {\n      if (eType) {\n        // Events pool is used to easily unbind all events when PhotoSwipe is closed,\n        // so developer doesn't need to do this manually\n        if (!skipPool) {\n          if (unbind) {\n            // Remove from the events pool\n            this._pool = this._pool.filter(poolItem => {\n              return poolItem.type !== eType || poolItem.listener !== listener || poolItem.target !== target;\n            });\n          } else {\n            // Add to the events pool\n            this._pool.push({\n              target,\n              type: eType,\n              listener,\n              passive\n            });\n          }\n        } // most PhotoSwipe events call preventDefault,\n        // and we do not need browser to scroll the page\n\n\n        const eventOptions = supportsPassive ? {\n          passive: passive || false\n        } : false;\n        target[methodName](eType, listener, eventOptions);\n      }\n    });\n  }\n\n}\n\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n\n/**\n * @param {PhotoSwipeOptions} options\n * @param {PhotoSwipeBase} pswp\n * @returns {Point}\n */\nfunction getViewportSize(options, pswp) {\n  if (options.getViewportSizeFn) {\n    const newViewportSize = options.getViewportSizeFn(options, pswp);\n\n    if (newViewportSize) {\n      return newViewportSize;\n    }\n  }\n\n  return {\n    x: document.documentElement.clientWidth,\n    // TODO: height on mobile is very incosistent due to toolbar\n    // find a way to improve this\n    //\n    // document.documentElement.clientHeight - doesn't seem to work well\n    y: window.innerHeight\n  };\n}\n/**\n * Parses padding option.\n * Supported formats:\n *\n * // Object\n * padding: {\n *  top: 0,\n *  bottom: 0,\n *  left: 0,\n *  right: 0\n * }\n *\n * // A function that returns the object\n * paddingFn: (viewportSize, itemData, index) => {\n *  return {\n *    top: 0,\n *    bottom: 0,\n *    left: 0,\n *    right: 0\n *  };\n * }\n *\n * // Legacy variant\n * paddingLeft: 0,\n * paddingRight: 0,\n * paddingTop: 0,\n * paddingBottom: 0,\n *\n * @param {'left' | 'top' | 'bottom' | 'right'} prop\n * @param {PhotoSwipeOptions} options PhotoSwipe options\n * @param {Point} viewportSize PhotoSwipe viewport size, for example: { x:800, y:600 }\n * @param {SlideData} itemData Data about the slide\n * @param {number} index Slide index\n * @returns {number}\n */\n\nfunction parsePaddingOption(prop, options, viewportSize, itemData, index) {\n  let paddingValue = 0;\n\n  if (options.paddingFn) {\n    paddingValue = options.paddingFn(viewportSize, itemData, index)[prop];\n  } else if (options.padding) {\n    paddingValue = options.padding[prop];\n  } else {\n    const legacyPropName = 'padding' + prop[0].toUpperCase() + prop.slice(1); // @ts-expect-error\n\n    if (options[legacyPropName]) {\n      // @ts-expect-error\n      paddingValue = options[legacyPropName];\n    }\n  }\n\n  return Number(paddingValue) || 0;\n}\n/**\n * @param {PhotoSwipeOptions} options\n * @param {Point} viewportSize\n * @param {SlideData} itemData\n * @param {number} index\n * @returns {Point}\n */\n\nfunction getPanAreaSize(options, viewportSize, itemData, index) {\n  return {\n    x: viewportSize.x - parsePaddingOption('left', options, viewportSize, itemData, index) - parsePaddingOption('right', options, viewportSize, itemData, index),\n    y: viewportSize.y - parsePaddingOption('top', options, viewportSize, itemData, index) - parsePaddingOption('bottom', options, viewportSize, itemData, index)\n  };\n}\n\n/** @typedef {import('./slide.js').default} Slide */\n\n/** @typedef {Record<Axis, number>} Point */\n\n/** @typedef {'x' | 'y'} Axis */\n\n/**\n * Calculates minimum, maximum and initial (center) bounds of a slide\n */\n\nclass PanBounds {\n  /**\n   * @param {Slide} slide\n   */\n  constructor(slide) {\n    this.slide = slide;\n    this.currZoomLevel = 1;\n    this.center =\n    /** @type {Point} */\n    {\n      x: 0,\n      y: 0\n    };\n    this.max =\n    /** @type {Point} */\n    {\n      x: 0,\n      y: 0\n    };\n    this.min =\n    /** @type {Point} */\n    {\n      x: 0,\n      y: 0\n    };\n  }\n  /**\n   * _getItemBounds\n   *\n   * @param {number} currZoomLevel\n   */\n\n\n  update(currZoomLevel) {\n    this.currZoomLevel = currZoomLevel;\n\n    if (!this.slide.width) {\n      this.reset();\n    } else {\n      this._updateAxis('x');\n\n      this._updateAxis('y');\n\n      this.slide.pswp.dispatch('calcBounds', {\n        slide: this.slide\n      });\n    }\n  }\n  /**\n   * _calculateItemBoundsForAxis\n   *\n   * @param {Axis} axis\n   */\n\n\n  _updateAxis(axis) {\n    const {\n      pswp\n    } = this.slide;\n    const elSize = this.slide[axis === 'x' ? 'width' : 'height'] * this.currZoomLevel;\n    const paddingProp = axis === 'x' ? 'left' : 'top';\n    const padding = parsePaddingOption(paddingProp, pswp.options, pswp.viewportSize, this.slide.data, this.slide.index);\n    const panAreaSize = this.slide.panAreaSize[axis]; // Default position of element.\n    // By default, it is center of viewport:\n\n    this.center[axis] = Math.round((panAreaSize - elSize) / 2) + padding; // maximum pan position\n\n    this.max[axis] = elSize > panAreaSize ? Math.round(panAreaSize - elSize) + padding : this.center[axis]; // minimum pan position\n\n    this.min[axis] = elSize > panAreaSize ? padding : this.center[axis];\n  } // _getZeroBounds\n\n\n  reset() {\n    this.center.x = 0;\n    this.center.y = 0;\n    this.max.x = 0;\n    this.max.y = 0;\n    this.min.x = 0;\n    this.min.y = 0;\n  }\n  /**\n   * Correct pan position if it's beyond the bounds\n   *\n   * @param {Axis} axis x or y\n   * @param {number} panOffset\n   * @returns {number}\n   */\n\n\n  correctPan(axis, panOffset) {\n    // checkPanBounds\n    return clamp(panOffset, this.max[axis], this.min[axis]);\n  }\n\n}\n\nconst MAX_IMAGE_WIDTH = 4000;\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n\n/** @typedef {'fit' | 'fill' | number | ((zoomLevelObject: ZoomLevel) => number)} ZoomLevelOption */\n\n/**\n * Calculates zoom levels for specific slide.\n * Depends on viewport size and image size.\n */\n\nclass ZoomLevel {\n  /**\n   * @param {PhotoSwipeOptions} options PhotoSwipe options\n   * @param {SlideData} itemData Slide data\n   * @param {number} index Slide index\n   * @param {PhotoSwipe} [pswp] PhotoSwipe instance, can be undefined if not initialized yet\n   */\n  constructor(options, itemData, index, pswp) {\n    this.pswp = pswp;\n    this.options = options;\n    this.itemData = itemData;\n    this.index = index;\n    /** @type { Point | null } */\n\n    this.panAreaSize = null;\n    /** @type { Point | null } */\n\n    this.elementSize = null;\n    this.fit = 1;\n    this.fill = 1;\n    this.vFill = 1;\n    this.initial = 1;\n    this.secondary = 1;\n    this.max = 1;\n    this.min = 1;\n  }\n  /**\n   * Calculate initial, secondary and maximum zoom level for the specified slide.\n   *\n   * It should be called when either image or viewport size changes.\n   *\n   * @param {number} maxWidth\n   * @param {number} maxHeight\n   * @param {Point} panAreaSize\n   */\n\n\n  update(maxWidth, maxHeight, panAreaSize) {\n    /** @type {Point} */\n    const elementSize = {\n      x: maxWidth,\n      y: maxHeight\n    };\n    this.elementSize = elementSize;\n    this.panAreaSize = panAreaSize;\n    const hRatio = panAreaSize.x / elementSize.x;\n    const vRatio = panAreaSize.y / elementSize.y;\n    this.fit = Math.min(1, hRatio < vRatio ? hRatio : vRatio);\n    this.fill = Math.min(1, hRatio > vRatio ? hRatio : vRatio); // zoom.vFill defines zoom level of the image\n    // when it has 100% of viewport vertical space (height)\n\n    this.vFill = Math.min(1, vRatio);\n    this.initial = this._getInitial();\n    this.secondary = this._getSecondary();\n    this.max = Math.max(this.initial, this.secondary, this._getMax());\n    this.min = Math.min(this.fit, this.initial, this.secondary);\n\n    if (this.pswp) {\n      this.pswp.dispatch('zoomLevelsUpdate', {\n        zoomLevels: this,\n        slideData: this.itemData\n      });\n    }\n  }\n  /**\n   * Parses user-defined zoom option.\n   *\n   * @private\n   * @param {'initial' | 'secondary' | 'max'} optionPrefix Zoom level option prefix (initial, secondary, max)\n   * @returns { number | undefined }\n   */\n\n\n  _parseZoomLevelOption(optionPrefix) {\n    const optionName =\n    /** @type {'initialZoomLevel' | 'secondaryZoomLevel' | 'maxZoomLevel'} */\n    optionPrefix + 'ZoomLevel';\n    const optionValue = this.options[optionName];\n\n    if (!optionValue) {\n      return;\n    }\n\n    if (typeof optionValue === 'function') {\n      return optionValue(this);\n    }\n\n    if (optionValue === 'fill') {\n      return this.fill;\n    }\n\n    if (optionValue === 'fit') {\n      return this.fit;\n    }\n\n    return Number(optionValue);\n  }\n  /**\n   * Get zoom level to which image will be zoomed after double-tap gesture,\n   * or when user clicks on zoom icon,\n   * or mouse-click on image itself.\n   * If you return 1 image will be zoomed to its original size.\n   *\n   * @private\n   * @return {number}\n   */\n\n\n  _getSecondary() {\n    let currZoomLevel = this._parseZoomLevelOption('secondary');\n\n    if (currZoomLevel) {\n      return currZoomLevel;\n    } // 3x of \"fit\" state, but not larger than original\n\n\n    currZoomLevel = Math.min(1, this.fit * 3);\n\n    if (this.elementSize && currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) {\n      currZoomLevel = MAX_IMAGE_WIDTH / this.elementSize.x;\n    }\n\n    return currZoomLevel;\n  }\n  /**\n   * Get initial image zoom level.\n   *\n   * @private\n   * @return {number}\n   */\n\n\n  _getInitial() {\n    return this._parseZoomLevelOption('initial') || this.fit;\n  }\n  /**\n   * Maximum zoom level when user zooms\n   * via zoom/pinch gesture,\n   * via cmd/ctrl-wheel or via trackpad.\n   *\n   * @private\n   * @return {number}\n   */\n\n\n  _getMax() {\n    // max zoom level is x4 from \"fit state\",\n    // used for zoom gesture and ctrl/trackpad zoom\n    return this._parseZoomLevelOption('max') || Math.max(1, this.fit * 4);\n  }\n\n}\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n/**\n * Renders and allows to control a single slide\n */\n\nclass Slide {\n  /**\n   * @param {SlideData} data\n   * @param {number} index\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(data, index, pswp) {\n    this.data = data;\n    this.index = index;\n    this.pswp = pswp;\n    this.isActive = index === pswp.currIndex;\n    this.currentResolution = 0;\n    /** @type {Point} */\n\n    this.panAreaSize = {\n      x: 0,\n      y: 0\n    };\n    /** @type {Point} */\n\n    this.pan = {\n      x: 0,\n      y: 0\n    };\n    this.isFirstSlide = this.isActive && !pswp.opener.isOpen;\n    this.zoomLevels = new ZoomLevel(pswp.options, data, index, pswp);\n    this.pswp.dispatch('gettingData', {\n      slide: this,\n      data: this.data,\n      index\n    });\n    this.content = this.pswp.contentLoader.getContentBySlide(this);\n    this.container = createElement('pswp__zoom-wrap', 'div');\n    /** @type {HTMLElement | null} */\n\n    this.holderElement = null;\n    this.currZoomLevel = 1;\n    /** @type {number} */\n\n    this.width = this.content.width;\n    /** @type {number} */\n\n    this.height = this.content.height;\n    this.heavyAppended = false;\n    this.bounds = new PanBounds(this);\n    this.prevDisplayedWidth = -1;\n    this.prevDisplayedHeight = -1;\n    this.pswp.dispatch('slideInit', {\n      slide: this\n    });\n  }\n  /**\n   * If this slide is active/current/visible\n   *\n   * @param {boolean} isActive\n   */\n\n\n  setIsActive(isActive) {\n    if (isActive && !this.isActive) {\n      // slide just became active\n      this.activate();\n    } else if (!isActive && this.isActive) {\n      // slide just became non-active\n      this.deactivate();\n    }\n  }\n  /**\n   * Appends slide content to DOM\n   *\n   * @param {HTMLElement} holderElement\n   */\n\n\n  append(holderElement) {\n    this.holderElement = holderElement;\n    this.container.style.transformOrigin = '0 0'; // Slide appended to DOM\n\n    if (!this.data) {\n      return;\n    }\n\n    this.calculateSize();\n    this.load();\n    this.updateContentSize();\n    this.appendHeavy();\n    this.holderElement.appendChild(this.container);\n    this.zoomAndPanToInitial();\n    this.pswp.dispatch('firstZoomPan', {\n      slide: this\n    });\n    this.applyCurrentZoomPan();\n    this.pswp.dispatch('afterSetContent', {\n      slide: this\n    });\n\n    if (this.isActive) {\n      this.activate();\n    }\n  }\n\n  load() {\n    this.content.load(false);\n    this.pswp.dispatch('slideLoad', {\n      slide: this\n    });\n  }\n  /**\n   * Append \"heavy\" DOM elements\n   *\n   * This may depend on a type of slide,\n   * but generally these are large images.\n   */\n\n\n  appendHeavy() {\n    const {\n      pswp\n    } = this;\n    const appendHeavyNearby = true; // todo\n    // Avoid appending heavy elements during animations\n\n    if (this.heavyAppended || !pswp.opener.isOpen || pswp.mainScroll.isShifted() || !this.isActive && !appendHeavyNearby) {\n      return;\n    }\n\n    if (this.pswp.dispatch('appendHeavy', {\n      slide: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    this.heavyAppended = true;\n    this.content.append();\n    this.pswp.dispatch('appendHeavyContent', {\n      slide: this\n    });\n  }\n  /**\n   * Triggered when this slide is active (selected).\n   *\n   * If it's part of opening/closing transition -\n   * activate() will trigger after the transition is ended.\n   */\n\n\n  activate() {\n    this.isActive = true;\n    this.appendHeavy();\n    this.content.activate();\n    this.pswp.dispatch('slideActivate', {\n      slide: this\n    });\n  }\n  /**\n   * Triggered when this slide becomes inactive.\n   *\n   * Slide can become inactive only after it was active.\n   */\n\n\n  deactivate() {\n    this.isActive = false;\n    this.content.deactivate();\n\n    if (this.currZoomLevel !== this.zoomLevels.initial) {\n      // allow filtering\n      this.calculateSize();\n    } // reset zoom level\n\n\n    this.currentResolution = 0;\n    this.zoomAndPanToInitial();\n    this.applyCurrentZoomPan();\n    this.updateContentSize();\n    this.pswp.dispatch('slideDeactivate', {\n      slide: this\n    });\n  }\n  /**\n   * The slide should destroy itself, it will never be used again.\n   * (unbind all events and destroy internal components)\n   */\n\n\n  destroy() {\n    this.content.hasSlide = false;\n    this.content.remove();\n    this.container.remove();\n    this.pswp.dispatch('slideDestroy', {\n      slide: this\n    });\n  }\n\n  resize() {\n    if (this.currZoomLevel === this.zoomLevels.initial || !this.isActive) {\n      // Keep initial zoom level if it was before the resize,\n      // as well as when this slide is not active\n      // Reset position and scale to original state\n      this.calculateSize();\n      this.currentResolution = 0;\n      this.zoomAndPanToInitial();\n      this.applyCurrentZoomPan();\n      this.updateContentSize();\n    } else {\n      // readjust pan position if it's beyond the bounds\n      this.calculateSize();\n      this.bounds.update(this.currZoomLevel);\n      this.panTo(this.pan.x, this.pan.y);\n    }\n  }\n  /**\n   * Apply size to current slide content,\n   * based on the current resolution and scale.\n   *\n   * @param {boolean} [force] if size should be updated even if dimensions weren't changed\n   */\n\n\n  updateContentSize(force) {\n    // Use initial zoom level\n    // if resolution is not defined (user didn't zoom yet)\n    const scaleMultiplier = this.currentResolution || this.zoomLevels.initial;\n\n    if (!scaleMultiplier) {\n      return;\n    }\n\n    const width = Math.round(this.width * scaleMultiplier) || this.pswp.viewportSize.x;\n    const height = Math.round(this.height * scaleMultiplier) || this.pswp.viewportSize.y;\n\n    if (!this.sizeChanged(width, height) && !force) {\n      return;\n    }\n\n    this.content.setDisplayedSize(width, height);\n  }\n  /**\n   * @param {number} width\n   * @param {number} height\n   */\n\n\n  sizeChanged(width, height) {\n    if (width !== this.prevDisplayedWidth || height !== this.prevDisplayedHeight) {\n      this.prevDisplayedWidth = width;\n      this.prevDisplayedHeight = height;\n      return true;\n    }\n\n    return false;\n  }\n  /** @returns {HTMLImageElement | HTMLDivElement | null | undefined} */\n\n\n  getPlaceholderElement() {\n    var _this$content$placeho;\n\n    return (_this$content$placeho = this.content.placeholder) === null || _this$content$placeho === void 0 ? void 0 : _this$content$placeho.element;\n  }\n  /**\n   * Zoom current slide image to...\n   *\n   * @param {number} destZoomLevel Destination zoom level.\n   * @param {Point} [centerPoint]\n   * Transform origin center point, or false if viewport center should be used.\n   * @param {number | false} [transitionDuration] Transition duration, may be set to 0.\n   * @param {boolean} [ignoreBounds] Minimum and maximum zoom levels will be ignored.\n   */\n\n\n  zoomTo(destZoomLevel, centerPoint, transitionDuration, ignoreBounds) {\n    const {\n      pswp\n    } = this;\n\n    if (!this.isZoomable() || pswp.mainScroll.isShifted()) {\n      return;\n    }\n\n    pswp.dispatch('beforeZoomTo', {\n      destZoomLevel,\n      centerPoint,\n      transitionDuration\n    }); // stop all pan and zoom transitions\n\n    pswp.animations.stopAllPan(); // if (!centerPoint) {\n    //   centerPoint = pswp.getViewportCenterPoint();\n    // }\n\n    const prevZoomLevel = this.currZoomLevel;\n\n    if (!ignoreBounds) {\n      destZoomLevel = clamp(destZoomLevel, this.zoomLevels.min, this.zoomLevels.max);\n    } // if (transitionDuration === undefined) {\n    //   transitionDuration = this.pswp.options.zoomAnimationDuration;\n    // }\n\n\n    this.setZoomLevel(destZoomLevel);\n    this.pan.x = this.calculateZoomToPanOffset('x', centerPoint, prevZoomLevel);\n    this.pan.y = this.calculateZoomToPanOffset('y', centerPoint, prevZoomLevel);\n    roundPoint(this.pan);\n\n    const finishTransition = () => {\n      this._setResolution(destZoomLevel);\n\n      this.applyCurrentZoomPan();\n    };\n\n    if (!transitionDuration) {\n      finishTransition();\n    } else {\n      pswp.animations.startTransition({\n        isPan: true,\n        name: 'zoomTo',\n        target: this.container,\n        transform: this.getCurrentTransform(),\n        onComplete: finishTransition,\n        duration: transitionDuration,\n        easing: pswp.options.easing\n      });\n    }\n  }\n  /**\n   * @param {Point} [centerPoint]\n   */\n\n\n  toggleZoom(centerPoint) {\n    this.zoomTo(this.currZoomLevel === this.zoomLevels.initial ? this.zoomLevels.secondary : this.zoomLevels.initial, centerPoint, this.pswp.options.zoomAnimationDuration);\n  }\n  /**\n   * Updates zoom level property and recalculates new pan bounds,\n   * unlike zoomTo it does not apply transform (use applyCurrentZoomPan)\n   *\n   * @param {number} currZoomLevel\n   */\n\n\n  setZoomLevel(currZoomLevel) {\n    this.currZoomLevel = currZoomLevel;\n    this.bounds.update(this.currZoomLevel);\n  }\n  /**\n   * Get pan position after zoom at a given `point`.\n   *\n   * Always call setZoomLevel(newZoomLevel) beforehand to recalculate\n   * pan bounds according to the new zoom level.\n   *\n   * @param {'x' | 'y'} axis\n   * @param {Point} [point]\n   * point based on which zoom is performed, usually refers to the current mouse position,\n   * if false - viewport center will be used.\n   * @param {number} [prevZoomLevel] Zoom level before new zoom was applied.\n   * @returns {number}\n   */\n\n\n  calculateZoomToPanOffset(axis, point, prevZoomLevel) {\n    const totalPanDistance = this.bounds.max[axis] - this.bounds.min[axis];\n\n    if (totalPanDistance === 0) {\n      return this.bounds.center[axis];\n    }\n\n    if (!point) {\n      point = this.pswp.getViewportCenterPoint();\n    }\n\n    if (!prevZoomLevel) {\n      prevZoomLevel = this.zoomLevels.initial;\n    }\n\n    const zoomFactor = this.currZoomLevel / prevZoomLevel;\n    return this.bounds.correctPan(axis, (this.pan[axis] - point[axis]) * zoomFactor + point[axis]);\n  }\n  /**\n   * Apply pan and keep it within bounds.\n   *\n   * @param {number} panX\n   * @param {number} panY\n   */\n\n\n  panTo(panX, panY) {\n    this.pan.x = this.bounds.correctPan('x', panX);\n    this.pan.y = this.bounds.correctPan('y', panY);\n    this.applyCurrentZoomPan();\n  }\n  /**\n   * If the slide in the current state can be panned by the user\n   * @returns {boolean}\n   */\n\n\n  isPannable() {\n    return Boolean(this.width) && this.currZoomLevel > this.zoomLevels.fit;\n  }\n  /**\n   * If the slide can be zoomed\n   * @returns {boolean}\n   */\n\n\n  isZoomable() {\n    return Boolean(this.width) && this.content.isZoomable();\n  }\n  /**\n   * Apply transform and scale based on\n   * the current pan position (this.pan) and zoom level (this.currZoomLevel)\n   */\n\n\n  applyCurrentZoomPan() {\n    this._applyZoomTransform(this.pan.x, this.pan.y, this.currZoomLevel);\n\n    if (this === this.pswp.currSlide) {\n      this.pswp.dispatch('zoomPanUpdate', {\n        slide: this\n      });\n    }\n  }\n\n  zoomAndPanToInitial() {\n    this.currZoomLevel = this.zoomLevels.initial; // pan according to the zoom level\n\n    this.bounds.update(this.currZoomLevel);\n    equalizePoints(this.pan, this.bounds.center);\n    this.pswp.dispatch('initialZoomPan', {\n      slide: this\n    });\n  }\n  /**\n   * Set translate and scale based on current resolution\n   *\n   * @param {number} x\n   * @param {number} y\n   * @param {number} zoom\n   * @private\n   */\n\n\n  _applyZoomTransform(x, y, zoom) {\n    zoom /= this.currentResolution || this.zoomLevels.initial;\n    setTransform(this.container, x, y, zoom);\n  }\n\n  calculateSize() {\n    const {\n      pswp\n    } = this;\n    equalizePoints(this.panAreaSize, getPanAreaSize(pswp.options, pswp.viewportSize, this.data, this.index));\n    this.zoomLevels.update(this.width, this.height, this.panAreaSize);\n    pswp.dispatch('calcSlideSize', {\n      slide: this\n    });\n  }\n  /** @returns {string} */\n\n\n  getCurrentTransform() {\n    const scale = this.currZoomLevel / (this.currentResolution || this.zoomLevels.initial);\n    return toTransformString(this.pan.x, this.pan.y, scale);\n  }\n  /**\n   * Set resolution and re-render the image.\n   *\n   * For example, if the real image size is 2000x1500,\n   * and resolution is 0.5 - it will be rendered as 1000x750.\n   *\n   * Image with zoom level 2 and resolution 0.5 is\n   * the same as image with zoom level 1 and resolution 1.\n   *\n   * Used to optimize animations and make\n   * sure that browser renders image in the highest quality.\n   * Also used by responsive images to load the correct one.\n   *\n   * @param {number} newResolution\n   */\n\n\n  _setResolution(newResolution) {\n    if (newResolution === this.currentResolution) {\n      return;\n    }\n\n    this.currentResolution = newResolution;\n    this.updateContentSize();\n    this.pswp.dispatch('resolutionChanged');\n  }\n\n}\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {import('./gestures.js').default} Gestures */\n\nconst PAN_END_FRICTION = 0.35;\nconst VERTICAL_DRAG_FRICTION = 0.6; // 1 corresponds to the third of viewport height\n\nconst MIN_RATIO_TO_CLOSE = 0.4; // Minimum speed required to navigate\n// to next or previous slide\n\nconst MIN_NEXT_SLIDE_SPEED = 0.5;\n/**\n * @param {number} initialVelocity\n * @param {number} decelerationRate\n * @returns {number}\n */\n\nfunction project(initialVelocity, decelerationRate) {\n  return initialVelocity * decelerationRate / (1 - decelerationRate);\n}\n/**\n * Handles single pointer dragging\n */\n\n\nclass DragHandler {\n  /**\n   * @param {Gestures} gestures\n   */\n  constructor(gestures) {\n    this.gestures = gestures;\n    this.pswp = gestures.pswp;\n    /** @type {Point} */\n\n    this.startPan = {\n      x: 0,\n      y: 0\n    };\n  }\n\n  start() {\n    if (this.pswp.currSlide) {\n      equalizePoints(this.startPan, this.pswp.currSlide.pan);\n    }\n\n    this.pswp.animations.stopAll();\n  }\n\n  change() {\n    const {\n      p1,\n      prevP1,\n      dragAxis\n    } = this.gestures;\n    const {\n      currSlide\n    } = this.pswp;\n\n    if (dragAxis === 'y' && this.pswp.options.closeOnVerticalDrag && currSlide && currSlide.currZoomLevel <= currSlide.zoomLevels.fit && !this.gestures.isMultitouch) {\n      // Handle vertical drag to close\n      const panY = currSlide.pan.y + (p1.y - prevP1.y);\n\n      if (!this.pswp.dispatch('verticalDrag', {\n        panY\n      }).defaultPrevented) {\n        this._setPanWithFriction('y', panY, VERTICAL_DRAG_FRICTION);\n\n        const bgOpacity = 1 - Math.abs(this._getVerticalDragRatio(currSlide.pan.y));\n        this.pswp.applyBgOpacity(bgOpacity);\n        currSlide.applyCurrentZoomPan();\n      }\n    } else {\n      const mainScrollChanged = this._panOrMoveMainScroll('x');\n\n      if (!mainScrollChanged) {\n        this._panOrMoveMainScroll('y');\n\n        if (currSlide) {\n          roundPoint(currSlide.pan);\n          currSlide.applyCurrentZoomPan();\n        }\n      }\n    }\n  }\n\n  end() {\n    const {\n      velocity\n    } = this.gestures;\n    const {\n      mainScroll,\n      currSlide\n    } = this.pswp;\n    let indexDiff = 0;\n    this.pswp.animations.stopAll(); // Handle main scroll if it's shifted\n\n    if (mainScroll.isShifted()) {\n      // Position of the main scroll relative to the viewport\n      const mainScrollShiftDiff = mainScroll.x - mainScroll.getCurrSlideX(); // Ratio between 0 and 1:\n      // 0 - slide is not visible at all,\n      // 0.5 - half of the slide is visible\n      // 1 - slide is fully visible\n\n      const currentSlideVisibilityRatio = mainScrollShiftDiff / this.pswp.viewportSize.x; // Go next slide.\n      //\n      // - if velocity and its direction is matched,\n      //   and we see at least tiny part of the next slide\n      //\n      // - or if we see less than 50% of the current slide\n      //   and velocity is close to 0\n      //\n\n      if (velocity.x < -MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio < 0 || velocity.x < 0.1 && currentSlideVisibilityRatio < -0.5) {\n        // Go to next slide\n        indexDiff = 1;\n        velocity.x = Math.min(velocity.x, 0);\n      } else if (velocity.x > MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio > 0 || velocity.x > -0.1 && currentSlideVisibilityRatio > 0.5) {\n        // Go to prev slide\n        indexDiff = -1;\n        velocity.x = Math.max(velocity.x, 0);\n      }\n\n      mainScroll.moveIndexBy(indexDiff, true, velocity.x);\n    } // Restore zoom level\n\n\n    if (currSlide && currSlide.currZoomLevel > currSlide.zoomLevels.max || this.gestures.isMultitouch) {\n      this.gestures.zoomLevels.correctZoomPan(true);\n    } else {\n      // we run two animations instead of one,\n      // as each axis has own pan boundaries and thus different spring function\n      // (correctZoomPan does not have this functionality,\n      //  it animates all properties with single timing function)\n      this._finishPanGestureForAxis('x');\n\n      this._finishPanGestureForAxis('y');\n    }\n  }\n  /**\n   * @private\n   * @param {'x' | 'y'} axis\n   */\n\n\n  _finishPanGestureForAxis(axis) {\n    const {\n      velocity\n    } = this.gestures;\n    const {\n      currSlide\n    } = this.pswp;\n\n    if (!currSlide) {\n      return;\n    }\n\n    const {\n      pan,\n      bounds\n    } = currSlide;\n    const panPos = pan[axis];\n    const restoreBgOpacity = this.pswp.bgOpacity < 1 && axis === 'y'; // 0.995 means - scroll view loses 0.5% of its velocity per millisecond\n    // Increasing this number will reduce travel distance\n\n    const decelerationRate = 0.995; // 0.99\n    // Pan position if there is no bounds\n\n    const projectedPosition = panPos + project(velocity[axis], decelerationRate);\n\n    if (restoreBgOpacity) {\n      const vDragRatio = this._getVerticalDragRatio(panPos);\n\n      const projectedVDragRatio = this._getVerticalDragRatio(projectedPosition); // If we are above and moving upwards,\n      // or if we are below and moving downwards\n\n\n      if (vDragRatio < 0 && projectedVDragRatio < -MIN_RATIO_TO_CLOSE || vDragRatio > 0 && projectedVDragRatio > MIN_RATIO_TO_CLOSE) {\n        this.pswp.close();\n        return;\n      }\n    } // Pan position with corrected bounds\n\n\n    const correctedPanPosition = bounds.correctPan(axis, projectedPosition); // Exit if pan position should not be changed\n    // or if speed it too low\n\n    if (panPos === correctedPanPosition) {\n      return;\n    } // Overshoot if the final position is out of pan bounds\n\n\n    const dampingRatio = correctedPanPosition === projectedPosition ? 1 : 0.82;\n    const initialBgOpacity = this.pswp.bgOpacity;\n    const totalPanDist = correctedPanPosition - panPos;\n    this.pswp.animations.startSpring({\n      name: 'panGesture' + axis,\n      isPan: true,\n      start: panPos,\n      end: correctedPanPosition,\n      velocity: velocity[axis],\n      dampingRatio,\n      onUpdate: pos => {\n        // Animate opacity of background relative to Y pan position of an image\n        if (restoreBgOpacity && this.pswp.bgOpacity < 1) {\n          // 0 - start of animation, 1 - end of animation\n          const animationProgressRatio = 1 - (correctedPanPosition - pos) / totalPanDist; // We clamp opacity to keep it between 0 and 1.\n          // As progress ratio can be larger than 1 due to overshoot,\n          // and we do not want to bounce opacity.\n\n          this.pswp.applyBgOpacity(clamp(initialBgOpacity + (1 - initialBgOpacity) * animationProgressRatio, 0, 1));\n        }\n\n        pan[axis] = Math.floor(pos);\n        currSlide.applyCurrentZoomPan();\n      }\n    });\n  }\n  /**\n   * Update position of the main scroll,\n   * or/and update pan position of the current slide.\n   *\n   * Should return true if it changes (or can change) main scroll.\n   *\n   * @private\n   * @param {'x' | 'y'} axis\n   * @returns {boolean}\n   */\n\n\n  _panOrMoveMainScroll(axis) {\n    const {\n      p1,\n      dragAxis,\n      prevP1,\n      isMultitouch\n    } = this.gestures;\n    const {\n      currSlide,\n      mainScroll\n    } = this.pswp;\n    const delta = p1[axis] - prevP1[axis];\n    const newMainScrollX = mainScroll.x + delta;\n\n    if (!delta || !currSlide) {\n      return false;\n    } // Always move main scroll if image can not be panned\n\n\n    if (axis === 'x' && !currSlide.isPannable() && !isMultitouch) {\n      mainScroll.moveTo(newMainScrollX, true);\n      return true; // changed main scroll\n    }\n\n    const {\n      bounds\n    } = currSlide;\n    const newPan = currSlide.pan[axis] + delta;\n\n    if (this.pswp.options.allowPanToNext && dragAxis === 'x' && axis === 'x' && !isMultitouch) {\n      const currSlideMainScrollX = mainScroll.getCurrSlideX(); // Position of the main scroll relative to the viewport\n\n      const mainScrollShiftDiff = mainScroll.x - currSlideMainScrollX;\n      const isLeftToRight = delta > 0;\n      const isRightToLeft = !isLeftToRight;\n\n      if (newPan > bounds.min[axis] && isLeftToRight) {\n        // Panning from left to right, beyond the left edge\n        // Wether the image was at minimum pan position (or less)\n        // when this drag gesture started.\n        // Minimum pan position refers to the left edge of the image.\n        const wasAtMinPanPosition = bounds.min[axis] <= this.startPan[axis];\n\n        if (wasAtMinPanPosition) {\n          mainScroll.moveTo(newMainScrollX, true);\n          return true;\n        } else {\n          this._setPanWithFriction(axis, newPan); //currSlide.pan[axis] = newPan;\n\n        }\n      } else if (newPan < bounds.max[axis] && isRightToLeft) {\n        // Paning from right to left, beyond the right edge\n        // Maximum pan position refers to the right edge of the image.\n        const wasAtMaxPanPosition = this.startPan[axis] <= bounds.max[axis];\n\n        if (wasAtMaxPanPosition) {\n          mainScroll.moveTo(newMainScrollX, true);\n          return true;\n        } else {\n          this._setPanWithFriction(axis, newPan); //currSlide.pan[axis] = newPan;\n\n        }\n      } else {\n        // If main scroll is shifted\n        if (mainScrollShiftDiff !== 0) {\n          // If main scroll is shifted right\n          if (mainScrollShiftDiff > 0\n          /*&& isRightToLeft*/\n          ) {\n            mainScroll.moveTo(Math.max(newMainScrollX, currSlideMainScrollX), true);\n            return true;\n          } else if (mainScrollShiftDiff < 0\n          /*&& isLeftToRight*/\n          ) {\n            // Main scroll is shifted left (Position is less than 0 comparing to the viewport 0)\n            mainScroll.moveTo(Math.min(newMainScrollX, currSlideMainScrollX), true);\n            return true;\n          }\n        } else {\n          // We are within pan bounds, so just pan\n          this._setPanWithFriction(axis, newPan);\n        }\n      }\n    } else {\n      if (axis === 'y') {\n        // Do not pan vertically if main scroll is shifted o\n        if (!mainScroll.isShifted() && bounds.min.y !== bounds.max.y) {\n          this._setPanWithFriction(axis, newPan);\n        }\n      } else {\n        this._setPanWithFriction(axis, newPan);\n      }\n    }\n\n    return false;\n  } // If we move above - the ratio is negative\n  // If we move below the ratio is positive\n\n  /**\n   * Relation between pan Y position and third of viewport height.\n   *\n   * When we are at initial position (center bounds) - the ratio is 0,\n   * if position is shifted upwards - the ratio is negative,\n   * if position is shifted downwards - the ratio is positive.\n   *\n   * @private\n   * @param {number} panY The current pan Y position.\n   * @returns {number}\n   */\n\n\n  _getVerticalDragRatio(panY) {\n    var _this$pswp$currSlide$, _this$pswp$currSlide;\n\n    return (panY - ((_this$pswp$currSlide$ = (_this$pswp$currSlide = this.pswp.currSlide) === null || _this$pswp$currSlide === void 0 ? void 0 : _this$pswp$currSlide.bounds.center.y) !== null && _this$pswp$currSlide$ !== void 0 ? _this$pswp$currSlide$ : 0)) / (this.pswp.viewportSize.y / 3);\n  }\n  /**\n   * Set pan position of the current slide.\n   * Apply friction if the position is beyond the pan bounds,\n   * or if custom friction is defined.\n   *\n   * @private\n   * @param {'x' | 'y'} axis\n   * @param {number} potentialPan\n   * @param {number} [customFriction] (0.1 - 1)\n   */\n\n\n  _setPanWithFriction(axis, potentialPan, customFriction) {\n    const {\n      currSlide\n    } = this.pswp;\n\n    if (!currSlide) {\n      return;\n    }\n\n    const {\n      pan,\n      bounds\n    } = currSlide;\n    const correctedPan = bounds.correctPan(axis, potentialPan); // If we are out of pan bounds\n\n    if (correctedPan !== potentialPan || customFriction) {\n      const delta = Math.round(potentialPan - pan[axis]);\n      pan[axis] += delta * (customFriction || PAN_END_FRICTION);\n    } else {\n      pan[axis] = potentialPan;\n    }\n  }\n\n}\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {import('./gestures.js').default} Gestures */\n\nconst UPPER_ZOOM_FRICTION = 0.05;\nconst LOWER_ZOOM_FRICTION = 0.15;\n/**\n * Get center point between two points\n *\n * @param {Point} p\n * @param {Point} p1\n * @param {Point} p2\n * @returns {Point}\n */\n\nfunction getZoomPointsCenter(p, p1, p2) {\n  p.x = (p1.x + p2.x) / 2;\n  p.y = (p1.y + p2.y) / 2;\n  return p;\n}\n\nclass ZoomHandler {\n  /**\n   * @param {Gestures} gestures\n   */\n  constructor(gestures) {\n    this.gestures = gestures;\n    /**\n     * @private\n     * @type {Point}\n     */\n\n    this._startPan = {\n      x: 0,\n      y: 0\n    };\n    /**\n     * @private\n     * @type {Point}\n     */\n\n    this._startZoomPoint = {\n      x: 0,\n      y: 0\n    };\n    /**\n     * @private\n     * @type {Point}\n     */\n\n    this._zoomPoint = {\n      x: 0,\n      y: 0\n    };\n    /** @private */\n\n    this._wasOverFitZoomLevel = false;\n    /** @private */\n\n    this._startZoomLevel = 1;\n  }\n\n  start() {\n    const {\n      currSlide\n    } = this.gestures.pswp;\n\n    if (currSlide) {\n      this._startZoomLevel = currSlide.currZoomLevel;\n      equalizePoints(this._startPan, currSlide.pan);\n    }\n\n    this.gestures.pswp.animations.stopAllPan();\n    this._wasOverFitZoomLevel = false;\n  }\n\n  change() {\n    const {\n      p1,\n      startP1,\n      p2,\n      startP2,\n      pswp\n    } = this.gestures;\n    const {\n      currSlide\n    } = pswp;\n\n    if (!currSlide) {\n      return;\n    }\n\n    const minZoomLevel = currSlide.zoomLevels.min;\n    const maxZoomLevel = currSlide.zoomLevels.max;\n\n    if (!currSlide.isZoomable() || pswp.mainScroll.isShifted()) {\n      return;\n    }\n\n    getZoomPointsCenter(this._startZoomPoint, startP1, startP2);\n    getZoomPointsCenter(this._zoomPoint, p1, p2);\n\n    let currZoomLevel = 1 / getDistanceBetween(startP1, startP2) * getDistanceBetween(p1, p2) * this._startZoomLevel; // slightly over the zoom.fit\n\n\n    if (currZoomLevel > currSlide.zoomLevels.initial + currSlide.zoomLevels.initial / 15) {\n      this._wasOverFitZoomLevel = true;\n    }\n\n    if (currZoomLevel < minZoomLevel) {\n      if (pswp.options.pinchToClose && !this._wasOverFitZoomLevel && this._startZoomLevel <= currSlide.zoomLevels.initial) {\n        // fade out background if zooming out\n        const bgOpacity = 1 - (minZoomLevel - currZoomLevel) / (minZoomLevel / 1.2);\n\n        if (!pswp.dispatch('pinchClose', {\n          bgOpacity\n        }).defaultPrevented) {\n          pswp.applyBgOpacity(bgOpacity);\n        }\n      } else {\n        // Apply the friction if zoom level is below the min\n        currZoomLevel = minZoomLevel - (minZoomLevel - currZoomLevel) * LOWER_ZOOM_FRICTION;\n      }\n    } else if (currZoomLevel > maxZoomLevel) {\n      // Apply the friction if zoom level is above the max\n      currZoomLevel = maxZoomLevel + (currZoomLevel - maxZoomLevel) * UPPER_ZOOM_FRICTION;\n    }\n\n    currSlide.pan.x = this._calculatePanForZoomLevel('x', currZoomLevel);\n    currSlide.pan.y = this._calculatePanForZoomLevel('y', currZoomLevel);\n    currSlide.setZoomLevel(currZoomLevel);\n    currSlide.applyCurrentZoomPan();\n  }\n\n  end() {\n    const {\n      pswp\n    } = this.gestures;\n    const {\n      currSlide\n    } = pswp;\n\n    if ((!currSlide || currSlide.currZoomLevel < currSlide.zoomLevels.initial) && !this._wasOverFitZoomLevel && pswp.options.pinchToClose) {\n      pswp.close();\n    } else {\n      this.correctZoomPan();\n    }\n  }\n  /**\n   * @private\n   * @param {'x' | 'y'} axis\n   * @param {number} currZoomLevel\n   * @returns {number}\n   */\n\n\n  _calculatePanForZoomLevel(axis, currZoomLevel) {\n    const zoomFactor = currZoomLevel / this._startZoomLevel;\n    return this._zoomPoint[axis] - (this._startZoomPoint[axis] - this._startPan[axis]) * zoomFactor;\n  }\n  /**\n   * Correct currZoomLevel and pan if they are\n   * beyond minimum or maximum values.\n   * With animation.\n   *\n   * @param {boolean} [ignoreGesture]\n   * Wether gesture coordinates should be ignored when calculating destination pan position.\n   */\n\n\n  correctZoomPan(ignoreGesture) {\n    const {\n      pswp\n    } = this.gestures;\n    const {\n      currSlide\n    } = pswp;\n\n    if (!(currSlide !== null && currSlide !== void 0 && currSlide.isZoomable())) {\n      return;\n    }\n\n    if (this._zoomPoint.x === 0) {\n      ignoreGesture = true;\n    }\n\n    const prevZoomLevel = currSlide.currZoomLevel;\n    /** @type {number} */\n\n    let destinationZoomLevel;\n    let currZoomLevelNeedsChange = true;\n\n    if (prevZoomLevel < currSlide.zoomLevels.initial) {\n      destinationZoomLevel = currSlide.zoomLevels.initial; // zoom to min\n    } else if (prevZoomLevel > currSlide.zoomLevels.max) {\n      destinationZoomLevel = currSlide.zoomLevels.max; // zoom to max\n    } else {\n      currZoomLevelNeedsChange = false;\n      destinationZoomLevel = prevZoomLevel;\n    }\n\n    const initialBgOpacity = pswp.bgOpacity;\n    const restoreBgOpacity = pswp.bgOpacity < 1;\n    const initialPan = equalizePoints({\n      x: 0,\n      y: 0\n    }, currSlide.pan);\n    let destinationPan = equalizePoints({\n      x: 0,\n      y: 0\n    }, initialPan);\n\n    if (ignoreGesture) {\n      this._zoomPoint.x = 0;\n      this._zoomPoint.y = 0;\n      this._startZoomPoint.x = 0;\n      this._startZoomPoint.y = 0;\n      this._startZoomLevel = prevZoomLevel;\n      equalizePoints(this._startPan, initialPan);\n    }\n\n    if (currZoomLevelNeedsChange) {\n      destinationPan = {\n        x: this._calculatePanForZoomLevel('x', destinationZoomLevel),\n        y: this._calculatePanForZoomLevel('y', destinationZoomLevel)\n      };\n    } // set zoom level, so pan bounds are updated according to it\n\n\n    currSlide.setZoomLevel(destinationZoomLevel);\n    destinationPan = {\n      x: currSlide.bounds.correctPan('x', destinationPan.x),\n      y: currSlide.bounds.correctPan('y', destinationPan.y)\n    }; // return zoom level and its bounds to initial\n\n    currSlide.setZoomLevel(prevZoomLevel);\n    const panNeedsChange = !pointsEqual(destinationPan, initialPan);\n\n    if (!panNeedsChange && !currZoomLevelNeedsChange && !restoreBgOpacity) {\n      // update resolution after gesture\n      currSlide._setResolution(destinationZoomLevel);\n\n      currSlide.applyCurrentZoomPan(); // nothing to animate\n\n      return;\n    }\n\n    pswp.animations.stopAllPan();\n    pswp.animations.startSpring({\n      isPan: true,\n      start: 0,\n      end: 1000,\n      velocity: 0,\n      dampingRatio: 1,\n      naturalFrequency: 40,\n      onUpdate: now => {\n        now /= 1000; // 0 - start, 1 - end\n\n        if (panNeedsChange || currZoomLevelNeedsChange) {\n          if (panNeedsChange) {\n            currSlide.pan.x = initialPan.x + (destinationPan.x - initialPan.x) * now;\n            currSlide.pan.y = initialPan.y + (destinationPan.y - initialPan.y) * now;\n          }\n\n          if (currZoomLevelNeedsChange) {\n            const newZoomLevel = prevZoomLevel + (destinationZoomLevel - prevZoomLevel) * now;\n            currSlide.setZoomLevel(newZoomLevel);\n          }\n\n          currSlide.applyCurrentZoomPan();\n        } // Restore background opacity\n\n\n        if (restoreBgOpacity && pswp.bgOpacity < 1) {\n          // We clamp opacity to keep it between 0 and 1.\n          // As progress ratio can be larger than 1 due to overshoot,\n          // and we do not want to bounce opacity.\n          pswp.applyBgOpacity(clamp(initialBgOpacity + (1 - initialBgOpacity) * now, 0, 1));\n        }\n      },\n      onComplete: () => {\n        // update resolution after transition ends\n        currSlide._setResolution(destinationZoomLevel);\n\n        currSlide.applyCurrentZoomPan();\n      }\n    });\n  }\n\n}\n\n/**\n * @template {string} T\n * @template {string} P\n * @typedef {import('../types.js').AddPostfix<T, P>} AddPostfix<T, P>\n */\n\n/** @typedef {import('./gestures.js').default} Gestures */\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {'imageClick' | 'bgClick' | 'tap' | 'doubleTap'} Actions */\n\n/**\n * Whether the tap was performed on the main slide\n * (rather than controls or caption).\n *\n * @param {PointerEvent} event\n * @returns {boolean}\n */\nfunction didTapOnMainContent(event) {\n  return !!\n  /** @type {HTMLElement} */\n  event.target.closest('.pswp__container');\n}\n/**\n * Tap, double-tap handler.\n */\n\n\nclass TapHandler {\n  /**\n   * @param {Gestures} gestures\n   */\n  constructor(gestures) {\n    this.gestures = gestures;\n  }\n  /**\n   * @param {Point} point\n   * @param {PointerEvent} originalEvent\n   */\n\n\n  click(point, originalEvent) {\n    const targetClassList =\n    /** @type {HTMLElement} */\n    originalEvent.target.classList;\n    const isImageClick = targetClassList.contains('pswp__img');\n    const isBackgroundClick = targetClassList.contains('pswp__item') || targetClassList.contains('pswp__zoom-wrap');\n\n    if (isImageClick) {\n      this._doClickOrTapAction('imageClick', point, originalEvent);\n    } else if (isBackgroundClick) {\n      this._doClickOrTapAction('bgClick', point, originalEvent);\n    }\n  }\n  /**\n   * @param {Point} point\n   * @param {PointerEvent} originalEvent\n   */\n\n\n  tap(point, originalEvent) {\n    if (didTapOnMainContent(originalEvent)) {\n      this._doClickOrTapAction('tap', point, originalEvent);\n    }\n  }\n  /**\n   * @param {Point} point\n   * @param {PointerEvent} originalEvent\n   */\n\n\n  doubleTap(point, originalEvent) {\n    if (didTapOnMainContent(originalEvent)) {\n      this._doClickOrTapAction('doubleTap', point, originalEvent);\n    }\n  }\n  /**\n   * @private\n   * @param {Actions} actionName\n   * @param {Point} point\n   * @param {PointerEvent} originalEvent\n   */\n\n\n  _doClickOrTapAction(actionName, point, originalEvent) {\n    var _this$gestures$pswp$e;\n\n    const {\n      pswp\n    } = this.gestures;\n    const {\n      currSlide\n    } = pswp;\n    const actionFullName =\n    /** @type {AddPostfix<Actions, 'Action'>} */\n    actionName + 'Action';\n    const optionValue = pswp.options[actionFullName];\n\n    if (pswp.dispatch(actionFullName, {\n      point,\n      originalEvent\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (typeof optionValue === 'function') {\n      optionValue.call(pswp, point, originalEvent);\n      return;\n    }\n\n    switch (optionValue) {\n      case 'close':\n      case 'next':\n        pswp[optionValue]();\n        break;\n\n      case 'zoom':\n        currSlide === null || currSlide === void 0 || currSlide.toggleZoom(point);\n        break;\n\n      case 'zoom-or-close':\n        // by default click zooms current image,\n        // if it can not be zoomed - gallery will be closed\n        if (currSlide !== null && currSlide !== void 0 && currSlide.isZoomable() && currSlide.zoomLevels.secondary !== currSlide.zoomLevels.initial) {\n          currSlide.toggleZoom(point);\n        } else if (pswp.options.clickToCloseNonZoomable) {\n          pswp.close();\n        }\n\n        break;\n\n      case 'toggle-controls':\n        (_this$gestures$pswp$e = this.gestures.pswp.element) === null || _this$gestures$pswp$e === void 0 || _this$gestures$pswp$e.classList.toggle('pswp--ui-visible'); // if (_controlsVisible) {\n        //   _ui.hideControls();\n        // } else {\n        //   _ui.showControls();\n        // }\n\n        break;\n    }\n  }\n\n}\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n// How far should user should drag\n// until we can determine that the gesture is swipe and its direction\n\nconst AXIS_SWIPE_HYSTERISIS = 10; //const PAN_END_FRICTION = 0.35;\n\nconst DOUBLE_TAP_DELAY = 300; // ms\n\nconst MIN_TAP_DISTANCE = 25; // px\n\n/**\n * Gestures class bind touch, pointer or mouse events\n * and emits drag to drag-handler and zoom events zoom-handler.\n *\n * Drag and zoom events are emited in requestAnimationFrame,\n * and only when one of pointers was actually changed.\n */\n\nclass Gestures {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    /** @type {'x' | 'y' | null} */\n\n    this.dragAxis = null; // point objects are defined once and reused\n    // PhotoSwipe keeps track only of two pointers, others are ignored\n\n    /** @type {Point} */\n\n    this.p1 = {\n      x: 0,\n      y: 0\n    }; // the first pressed pointer\n\n    /** @type {Point} */\n\n    this.p2 = {\n      x: 0,\n      y: 0\n    }; // the second pressed pointer\n\n    /** @type {Point} */\n\n    this.prevP1 = {\n      x: 0,\n      y: 0\n    };\n    /** @type {Point} */\n\n    this.prevP2 = {\n      x: 0,\n      y: 0\n    };\n    /** @type {Point} */\n\n    this.startP1 = {\n      x: 0,\n      y: 0\n    };\n    /** @type {Point} */\n\n    this.startP2 = {\n      x: 0,\n      y: 0\n    };\n    /** @type {Point} */\n\n    this.velocity = {\n      x: 0,\n      y: 0\n    };\n    /** @type {Point}\n     * @private\n     */\n\n    this._lastStartP1 = {\n      x: 0,\n      y: 0\n    };\n    /** @type {Point}\n     * @private\n     */\n\n    this._intervalP1 = {\n      x: 0,\n      y: 0\n    };\n    /** @private */\n\n    this._numActivePoints = 0;\n    /** @type {Point[]}\n     * @private\n     */\n\n    this._ongoingPointers = [];\n    /** @private */\n\n    this._touchEventEnabled = 'ontouchstart' in window;\n    /** @private */\n\n    this._pointerEventEnabled = !!window.PointerEvent;\n    this.supportsTouch = this._touchEventEnabled || this._pointerEventEnabled && navigator.maxTouchPoints > 1;\n    /** @private */\n\n    this._numActivePoints = 0;\n    /** @private */\n\n    this._intervalTime = 0;\n    /** @private */\n\n    this._velocityCalculated = false;\n    this.isMultitouch = false;\n    this.isDragging = false;\n    this.isZooming = false;\n    /** @type {number | null} */\n\n    this.raf = null;\n    /** @type {NodeJS.Timeout | null}\n     * @private\n     */\n\n    this._tapTimer = null;\n\n    if (!this.supportsTouch) {\n      // disable pan to next slide for non-touch devices\n      pswp.options.allowPanToNext = false;\n    }\n\n    this.drag = new DragHandler(this);\n    this.zoomLevels = new ZoomHandler(this);\n    this.tapHandler = new TapHandler(this);\n    pswp.on('bindEvents', () => {\n      pswp.events.add(pswp.scrollWrap, 'click',\n      /** @type EventListener */\n      this._onClick.bind(this));\n\n      if (this._pointerEventEnabled) {\n        this._bindEvents('pointer', 'down', 'up', 'cancel');\n      } else if (this._touchEventEnabled) {\n        this._bindEvents('touch', 'start', 'end', 'cancel'); // In previous versions we also bound mouse event here,\n        // in case device supports both touch and mouse events,\n        // but newer versions of browsers now support PointerEvent.\n        // on iOS10 if you bind touchmove/end after touchstart,\n        // and you don't preventDefault touchstart (which PhotoSwipe does),\n        // preventDefault will have no effect on touchmove and touchend.\n        // Unless you bind it previously.\n\n\n        if (pswp.scrollWrap) {\n          pswp.scrollWrap.ontouchmove = () => {};\n\n          pswp.scrollWrap.ontouchend = () => {};\n        }\n      } else {\n        this._bindEvents('mouse', 'down', 'up');\n      }\n    });\n  }\n  /**\n   * @private\n   * @param {'mouse' | 'touch' | 'pointer'} pref\n   * @param {'down' | 'start'} down\n   * @param {'up' | 'end'} up\n   * @param {'cancel'} [cancel]\n   */\n\n\n  _bindEvents(pref, down, up, cancel) {\n    const {\n      pswp\n    } = this;\n    const {\n      events\n    } = pswp;\n    const cancelEvent = cancel ? pref + cancel : '';\n    events.add(pswp.scrollWrap, pref + down,\n    /** @type EventListener */\n    this.onPointerDown.bind(this));\n    events.add(window, pref + 'move',\n    /** @type EventListener */\n    this.onPointerMove.bind(this));\n    events.add(window, pref + up,\n    /** @type EventListener */\n    this.onPointerUp.bind(this));\n\n    if (cancelEvent) {\n      events.add(pswp.scrollWrap, cancelEvent,\n      /** @type EventListener */\n      this.onPointerUp.bind(this));\n    }\n  }\n  /**\n   * @param {PointerEvent} e\n   */\n\n\n  onPointerDown(e) {\n    // We do not call preventDefault for touch events\n    // to allow browser to show native dialog on longpress\n    // (the one that allows to save image or open it in new tab).\n    //\n    // Desktop Safari allows to drag images when preventDefault isn't called on mousedown,\n    // even though preventDefault IS called on mousemove. That's why we preventDefault mousedown.\n    const isMousePointer = e.type === 'mousedown' || e.pointerType === 'mouse'; // Allow dragging only via left mouse button.\n    // http://www.quirksmode.org/js/events_properties.html\n    // https://developer.mozilla.org/en-US/docs/Web/API/event.button\n\n    if (isMousePointer && e.button > 0) {\n      return;\n    }\n\n    const {\n      pswp\n    } = this; // if PhotoSwipe is opening or closing\n\n    if (!pswp.opener.isOpen) {\n      e.preventDefault();\n      return;\n    }\n\n    if (pswp.dispatch('pointerDown', {\n      originalEvent: e\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (isMousePointer) {\n      pswp.mouseDetected(); // preventDefault mouse event to prevent\n      // browser image drag feature\n\n      this._preventPointerEventBehaviour(e, 'down');\n    }\n\n    pswp.animations.stopAll();\n\n    this._updatePoints(e, 'down');\n\n    if (this._numActivePoints === 1) {\n      this.dragAxis = null; // we need to store initial point to determine the main axis,\n      // drag is activated only after the axis is determined\n\n      equalizePoints(this.startP1, this.p1);\n    }\n\n    if (this._numActivePoints > 1) {\n      // Tap or double tap should not trigger if more than one pointer\n      this._clearTapTimer();\n\n      this.isMultitouch = true;\n    } else {\n      this.isMultitouch = false;\n    }\n  }\n  /**\n   * @param {PointerEvent} e\n   */\n\n\n  onPointerMove(e) {\n    this._preventPointerEventBehaviour(e, 'move');\n\n    if (!this._numActivePoints) {\n      return;\n    }\n\n    this._updatePoints(e, 'move');\n\n    if (this.pswp.dispatch('pointerMove', {\n      originalEvent: e\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (this._numActivePoints === 1 && !this.isDragging) {\n      if (!this.dragAxis) {\n        this._calculateDragDirection();\n      } // Drag axis was detected, emit drag.start\n\n\n      if (this.dragAxis && !this.isDragging) {\n        if (this.isZooming) {\n          this.isZooming = false;\n          this.zoomLevels.end();\n        }\n\n        this.isDragging = true;\n\n        this._clearTapTimer(); // Tap can not trigger after drag\n        // Adjust starting point\n\n\n        this._updateStartPoints();\n\n        this._intervalTime = Date.now(); //this._startTime = this._intervalTime;\n\n        this._velocityCalculated = false;\n        equalizePoints(this._intervalP1, this.p1);\n        this.velocity.x = 0;\n        this.velocity.y = 0;\n        this.drag.start();\n\n        this._rafStopLoop();\n\n        this._rafRenderLoop();\n      }\n    } else if (this._numActivePoints > 1 && !this.isZooming) {\n      this._finishDrag();\n\n      this.isZooming = true; // Adjust starting points\n\n      this._updateStartPoints();\n\n      this.zoomLevels.start();\n\n      this._rafStopLoop();\n\n      this._rafRenderLoop();\n    }\n  }\n  /**\n   * @private\n   */\n\n\n  _finishDrag() {\n    if (this.isDragging) {\n      this.isDragging = false; // Try to calculate velocity,\n      // if it wasn't calculated yet in drag.change\n\n      if (!this._velocityCalculated) {\n        this._updateVelocity(true);\n      }\n\n      this.drag.end();\n      this.dragAxis = null;\n    }\n  }\n  /**\n   * @param {PointerEvent} e\n   */\n\n\n  onPointerUp(e) {\n    if (!this._numActivePoints) {\n      return;\n    }\n\n    this._updatePoints(e, 'up');\n\n    if (this.pswp.dispatch('pointerUp', {\n      originalEvent: e\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (this._numActivePoints === 0) {\n      this._rafStopLoop();\n\n      if (this.isDragging) {\n        this._finishDrag();\n      } else if (!this.isZooming && !this.isMultitouch) {\n        //this.zoomLevels.correctZoomPan();\n        this._finishTap(e);\n      }\n    }\n\n    if (this._numActivePoints < 2 && this.isZooming) {\n      this.isZooming = false;\n      this.zoomLevels.end();\n\n      if (this._numActivePoints === 1) {\n        // Since we have 1 point left, we need to reinitiate drag\n        this.dragAxis = null;\n\n        this._updateStartPoints();\n      }\n    }\n  }\n  /**\n   * @private\n   */\n\n\n  _rafRenderLoop() {\n    if (this.isDragging || this.isZooming) {\n      this._updateVelocity();\n\n      if (this.isDragging) {\n        // make sure that pointer moved since the last update\n        if (!pointsEqual(this.p1, this.prevP1)) {\n          this.drag.change();\n        }\n      } else\n        /* if (this.isZooming) */\n        {\n          if (!pointsEqual(this.p1, this.prevP1) || !pointsEqual(this.p2, this.prevP2)) {\n            this.zoomLevels.change();\n          }\n        }\n\n      this._updatePrevPoints();\n\n      this.raf = requestAnimationFrame(this._rafRenderLoop.bind(this));\n    }\n  }\n  /**\n   * Update velocity at 50ms interval\n   *\n   * @private\n   * @param {boolean} [force]\n   */\n\n\n  _updateVelocity(force) {\n    const time = Date.now();\n    const duration = time - this._intervalTime;\n\n    if (duration < 50 && !force) {\n      return;\n    }\n\n    this.velocity.x = this._getVelocity('x', duration);\n    this.velocity.y = this._getVelocity('y', duration);\n    this._intervalTime = time;\n    equalizePoints(this._intervalP1, this.p1);\n    this._velocityCalculated = true;\n  }\n  /**\n   * @private\n   * @param {PointerEvent} e\n   */\n\n\n  _finishTap(e) {\n    const {\n      mainScroll\n    } = this.pswp; // Do not trigger tap events if main scroll is shifted\n\n    if (mainScroll.isShifted()) {\n      // restore main scroll position\n      // (usually happens if stopped in the middle of animation)\n      mainScroll.moveIndexBy(0, true);\n      return;\n    } // Do not trigger tap for touchcancel or pointercancel\n\n\n    if (e.type.indexOf('cancel') > 0) {\n      return;\n    } // Trigger click instead of tap for mouse events\n\n\n    if (e.type === 'mouseup' || e.pointerType === 'mouse') {\n      this.tapHandler.click(this.startP1, e);\n      return;\n    } // Disable delay if there is no doubleTapAction\n\n\n    const tapDelay = this.pswp.options.doubleTapAction ? DOUBLE_TAP_DELAY : 0; // If tapTimer is defined - we tapped recently,\n    // check if the current tap is close to the previous one,\n    // if yes - trigger double tap\n\n    if (this._tapTimer) {\n      this._clearTapTimer(); // Check if two taps were more or less on the same place\n\n\n      if (getDistanceBetween(this._lastStartP1, this.startP1) < MIN_TAP_DISTANCE) {\n        this.tapHandler.doubleTap(this.startP1, e);\n      }\n    } else {\n      equalizePoints(this._lastStartP1, this.startP1);\n      this._tapTimer = setTimeout(() => {\n        this.tapHandler.tap(this.startP1, e);\n\n        this._clearTapTimer();\n      }, tapDelay);\n    }\n  }\n  /**\n   * @private\n   */\n\n\n  _clearTapTimer() {\n    if (this._tapTimer) {\n      clearTimeout(this._tapTimer);\n      this._tapTimer = null;\n    }\n  }\n  /**\n   * Get velocity for axis\n   *\n   * @private\n   * @param {'x' | 'y'} axis\n   * @param {number} duration\n   * @returns {number}\n   */\n\n\n  _getVelocity(axis, duration) {\n    // displacement is like distance, but can be negative.\n    const displacement = this.p1[axis] - this._intervalP1[axis];\n\n    if (Math.abs(displacement) > 1 && duration > 5) {\n      return displacement / duration;\n    }\n\n    return 0;\n  }\n  /**\n   * @private\n   */\n\n\n  _rafStopLoop() {\n    if (this.raf) {\n      cancelAnimationFrame(this.raf);\n      this.raf = null;\n    }\n  }\n  /**\n   * @private\n   * @param {PointerEvent} e\n   * @param {'up' | 'down' | 'move'} pointerType Normalized pointer type\n   */\n\n\n  _preventPointerEventBehaviour(e, pointerType) {\n    const preventPointerEvent = this.pswp.applyFilters('preventPointerEvent', true, e, pointerType);\n\n    if (preventPointerEvent) {\n      e.preventDefault();\n    }\n  }\n  /**\n   * Parses and normalizes points from the touch, mouse or pointer event.\n   * Updates p1 and p2.\n   *\n   * @private\n   * @param {PointerEvent | TouchEvent} e\n   * @param {'up' | 'down' | 'move'} pointerType Normalized pointer type\n   */\n\n\n  _updatePoints(e, pointerType) {\n    if (this._pointerEventEnabled) {\n      const pointerEvent =\n      /** @type {PointerEvent} */\n      e; // Try to find the current pointer in ongoing pointers by its ID\n\n      const pointerIndex = this._ongoingPointers.findIndex(ongoingPointer => {\n        return ongoingPointer.id === pointerEvent.pointerId;\n      });\n\n      if (pointerType === 'up' && pointerIndex > -1) {\n        // release the pointer - remove it from ongoing\n        this._ongoingPointers.splice(pointerIndex, 1);\n      } else if (pointerType === 'down' && pointerIndex === -1) {\n        // add new pointer\n        this._ongoingPointers.push(this._convertEventPosToPoint(pointerEvent, {\n          x: 0,\n          y: 0\n        }));\n      } else if (pointerIndex > -1) {\n        // update existing pointer\n        this._convertEventPosToPoint(pointerEvent, this._ongoingPointers[pointerIndex]);\n      }\n\n      this._numActivePoints = this._ongoingPointers.length; // update points that PhotoSwipe uses\n      // to calculate position and scale\n\n      if (this._numActivePoints > 0) {\n        equalizePoints(this.p1, this._ongoingPointers[0]);\n      }\n\n      if (this._numActivePoints > 1) {\n        equalizePoints(this.p2, this._ongoingPointers[1]);\n      }\n    } else {\n      const touchEvent =\n      /** @type {TouchEvent} */\n      e;\n      this._numActivePoints = 0;\n\n      if (touchEvent.type.indexOf('touch') > -1) {\n        // Touch Event\n        // https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent\n        if (touchEvent.touches && touchEvent.touches.length > 0) {\n          this._convertEventPosToPoint(touchEvent.touches[0], this.p1);\n\n          this._numActivePoints++;\n\n          if (touchEvent.touches.length > 1) {\n            this._convertEventPosToPoint(touchEvent.touches[1], this.p2);\n\n            this._numActivePoints++;\n          }\n        }\n      } else {\n        // Mouse Event\n        this._convertEventPosToPoint(\n        /** @type {PointerEvent} */\n        e, this.p1);\n\n        if (pointerType === 'up') {\n          // clear all points on mouseup\n          this._numActivePoints = 0;\n        } else {\n          this._numActivePoints++;\n        }\n      }\n    }\n  }\n  /** update points that were used during previous rAF tick\n   * @private\n   */\n\n\n  _updatePrevPoints() {\n    equalizePoints(this.prevP1, this.p1);\n    equalizePoints(this.prevP2, this.p2);\n  }\n  /** update points at the start of gesture\n   * @private\n   */\n\n\n  _updateStartPoints() {\n    equalizePoints(this.startP1, this.p1);\n    equalizePoints(this.startP2, this.p2);\n\n    this._updatePrevPoints();\n  }\n  /** @private */\n\n\n  _calculateDragDirection() {\n    if (this.pswp.mainScroll.isShifted()) {\n      // if main scroll position is shifted – direction is always horizontal\n      this.dragAxis = 'x';\n    } else {\n      // calculate delta of the last touchmove tick\n      const diff = Math.abs(this.p1.x - this.startP1.x) - Math.abs(this.p1.y - this.startP1.y);\n\n      if (diff !== 0) {\n        // check if pointer was shifted horizontally or vertically\n        const axisToCheck = diff > 0 ? 'x' : 'y';\n\n        if (Math.abs(this.p1[axisToCheck] - this.startP1[axisToCheck]) >= AXIS_SWIPE_HYSTERISIS) {\n          this.dragAxis = axisToCheck;\n        }\n      }\n    }\n  }\n  /**\n   * Converts touch, pointer or mouse event\n   * to PhotoSwipe point.\n   *\n   * @private\n   * @param {Touch | PointerEvent} e\n   * @param {Point} p\n   * @returns {Point}\n   */\n\n\n  _convertEventPosToPoint(e, p) {\n    p.x = e.pageX - this.pswp.offset.x;\n    p.y = e.pageY - this.pswp.offset.y;\n\n    if ('pointerId' in e) {\n      p.id = e.pointerId;\n    } else if (e.identifier !== undefined) {\n      p.id = e.identifier;\n    }\n\n    return p;\n  }\n  /**\n   * @private\n   * @param {PointerEvent} e\n   */\n\n\n  _onClick(e) {\n    // Do not allow click event to pass through after drag\n    if (this.pswp.mainScroll.isShifted()) {\n      e.preventDefault();\n      e.stopPropagation();\n    }\n  }\n\n}\n\n/** @typedef {import('./photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('./slide/slide.js').default} Slide */\n\n/** @typedef {{ el: HTMLDivElement; slide?: Slide }} ItemHolder */\n\nconst MAIN_SCROLL_END_FRICTION = 0.35; // const MIN_SWIPE_TRANSITION_DURATION = 250;\n// const MAX_SWIPE_TRABSITION_DURATION = 500;\n// const DEFAULT_SWIPE_TRANSITION_DURATION = 333;\n\n/**\n * Handles movement of the main scrolling container\n * (for example, it repositions when user swipes left or right).\n *\n * Also stores its state.\n */\n\nclass MainScroll {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    this.x = 0;\n    this.slideWidth = 0;\n    /** @private */\n\n    this._currPositionIndex = 0;\n    /** @private */\n\n    this._prevPositionIndex = 0;\n    /** @private */\n\n    this._containerShiftIndex = -1;\n    /** @type {ItemHolder[]} */\n\n    this.itemHolders = [];\n  }\n  /**\n   * Position the scroller and slide containers\n   * according to viewport size.\n   *\n   * @param {boolean} [resizeSlides] Whether slides content should resized\n   */\n\n\n  resize(resizeSlides) {\n    const {\n      pswp\n    } = this;\n    const newSlideWidth = Math.round(pswp.viewportSize.x + pswp.viewportSize.x * pswp.options.spacing); // Mobile browsers might trigger a resize event during a gesture.\n    // (due to toolbar appearing or hiding).\n    // Avoid re-adjusting main scroll position if width wasn't changed\n\n    const slideWidthChanged = newSlideWidth !== this.slideWidth;\n\n    if (slideWidthChanged) {\n      this.slideWidth = newSlideWidth;\n      this.moveTo(this.getCurrSlideX());\n    }\n\n    this.itemHolders.forEach((itemHolder, index) => {\n      if (slideWidthChanged) {\n        setTransform(itemHolder.el, (index + this._containerShiftIndex) * this.slideWidth);\n      }\n\n      if (resizeSlides && itemHolder.slide) {\n        itemHolder.slide.resize();\n      }\n    });\n  }\n  /**\n   * Reset X position of the main scroller to zero\n   */\n\n\n  resetPosition() {\n    // Position on the main scroller (offset)\n    // it is independent from slide index\n    this._currPositionIndex = 0;\n    this._prevPositionIndex = 0; // This will force recalculation of size on next resize()\n\n    this.slideWidth = 0; // _containerShiftIndex*viewportSize will give you amount of transform of the current slide\n\n    this._containerShiftIndex = -1;\n  }\n  /**\n   * Create and append array of three items\n   * that hold data about slides in DOM\n   */\n\n\n  appendHolders() {\n    this.itemHolders = []; // append our three slide holders -\n    // previous, current, and next\n\n    for (let i = 0; i < 3; i++) {\n      const el = createElement('pswp__item', 'div', this.pswp.container);\n      el.setAttribute('role', 'group');\n      el.setAttribute('aria-roledescription', 'slide');\n      el.setAttribute('aria-hidden', 'true'); // hide nearby item holders until initial zoom animation finishes (to avoid extra Paints)\n\n      el.style.display = i === 1 ? 'block' : 'none';\n      this.itemHolders.push({\n        el //index: -1\n\n      });\n    }\n  }\n  /**\n   * Whether the main scroll can be horizontally swiped to the next or previous slide.\n   * @returns {boolean}\n   */\n\n\n  canBeSwiped() {\n    return this.pswp.getNumItems() > 1;\n  }\n  /**\n   * Move main scroll by X amount of slides.\n   * For example:\n   *   `-1` will move to the previous slide,\n   *    `0` will reset the scroll position of the current slide,\n   *    `3` will move three slides forward\n   *\n   * If loop option is enabled - index will be automatically looped too,\n   * (for example `-1` will move to the last slide of the gallery).\n   *\n   * @param {number} diff\n   * @param {boolean} [animate]\n   * @param {number} [velocityX]\n   * @returns {boolean} whether index was changed or not\n   */\n\n\n  moveIndexBy(diff, animate, velocityX) {\n    const {\n      pswp\n    } = this;\n    let newIndex = pswp.potentialIndex + diff;\n    const numSlides = pswp.getNumItems();\n\n    if (pswp.canLoop()) {\n      newIndex = pswp.getLoopedIndex(newIndex);\n      const distance = (diff + numSlides) % numSlides;\n\n      if (distance <= numSlides / 2) {\n        // go forward\n        diff = distance;\n      } else {\n        // go backwards\n        diff = distance - numSlides;\n      }\n    } else {\n      if (newIndex < 0) {\n        newIndex = 0;\n      } else if (newIndex >= numSlides) {\n        newIndex = numSlides - 1;\n      }\n\n      diff = newIndex - pswp.potentialIndex;\n    }\n\n    pswp.potentialIndex = newIndex;\n    this._currPositionIndex -= diff;\n    pswp.animations.stopMainScroll();\n    const destinationX = this.getCurrSlideX();\n\n    if (!animate) {\n      this.moveTo(destinationX);\n      this.updateCurrItem();\n    } else {\n      pswp.animations.startSpring({\n        isMainScroll: true,\n        start: this.x,\n        end: destinationX,\n        velocity: velocityX || 0,\n        naturalFrequency: 30,\n        dampingRatio: 1,\n        //0.7,\n        onUpdate: x => {\n          this.moveTo(x);\n        },\n        onComplete: () => {\n          this.updateCurrItem();\n          pswp.appendHeavy();\n        }\n      });\n      let currDiff = pswp.potentialIndex - pswp.currIndex;\n\n      if (pswp.canLoop()) {\n        const currDistance = (currDiff + numSlides) % numSlides;\n\n        if (currDistance <= numSlides / 2) {\n          // go forward\n          currDiff = currDistance;\n        } else {\n          // go backwards\n          currDiff = currDistance - numSlides;\n        }\n      } // Force-append new slides during transition\n      // if difference between slides is more than 1\n\n\n      if (Math.abs(currDiff) > 1) {\n        this.updateCurrItem();\n      }\n    }\n\n    return Boolean(diff);\n  }\n  /**\n   * X position of the main scroll for the current slide\n   * (ignores position during dragging)\n   * @returns {number}\n   */\n\n\n  getCurrSlideX() {\n    return this.slideWidth * this._currPositionIndex;\n  }\n  /**\n   * Whether scroll position is shifted.\n   * For example, it will return true if the scroll is being dragged or animated.\n   * @returns {boolean}\n   */\n\n\n  isShifted() {\n    return this.x !== this.getCurrSlideX();\n  }\n  /**\n   * Update slides X positions and set their content\n   */\n\n\n  updateCurrItem() {\n    var _this$itemHolders$;\n\n    const {\n      pswp\n    } = this;\n    const positionDifference = this._prevPositionIndex - this._currPositionIndex;\n\n    if (!positionDifference) {\n      return;\n    }\n\n    this._prevPositionIndex = this._currPositionIndex;\n    pswp.currIndex = pswp.potentialIndex;\n    let diffAbs = Math.abs(positionDifference);\n    /** @type {ItemHolder | undefined} */\n\n    let tempHolder;\n\n    if (diffAbs >= 3) {\n      this._containerShiftIndex += positionDifference + (positionDifference > 0 ? -3 : 3);\n      diffAbs = 3; // If slides are changed by 3 screens or more - clean up previous slides\n\n      this.itemHolders.forEach(itemHolder => {\n        var _itemHolder$slide;\n\n        (_itemHolder$slide = itemHolder.slide) === null || _itemHolder$slide === void 0 || _itemHolder$slide.destroy();\n        itemHolder.slide = undefined;\n      });\n    }\n\n    for (let i = 0; i < diffAbs; i++) {\n      if (positionDifference > 0) {\n        tempHolder = this.itemHolders.shift();\n\n        if (tempHolder) {\n          this.itemHolders[2] = tempHolder; // move first to last\n\n          this._containerShiftIndex++;\n          setTransform(tempHolder.el, (this._containerShiftIndex + 2) * this.slideWidth);\n          pswp.setContent(tempHolder, pswp.currIndex - diffAbs + i + 2);\n        }\n      } else {\n        tempHolder = this.itemHolders.pop();\n\n        if (tempHolder) {\n          this.itemHolders.unshift(tempHolder); // move last to first\n\n          this._containerShiftIndex--;\n          setTransform(tempHolder.el, this._containerShiftIndex * this.slideWidth);\n          pswp.setContent(tempHolder, pswp.currIndex + diffAbs - i - 2);\n        }\n      }\n    } // Reset transfrom every 50ish navigations in one direction.\n    //\n    // Otherwise transform will keep growing indefinitely,\n    // which might cause issues as browsers have a maximum transform limit.\n    // I wasn't able to reach it, but just to be safe.\n    // This should not cause noticable lag.\n\n\n    if (Math.abs(this._containerShiftIndex) > 50 && !this.isShifted()) {\n      this.resetPosition();\n      this.resize();\n    } // Pan transition might be running (and consntantly updating pan position)\n\n\n    pswp.animations.stopAllPan();\n    this.itemHolders.forEach((itemHolder, i) => {\n      if (itemHolder.slide) {\n        // Slide in the 2nd holder is always active\n        itemHolder.slide.setIsActive(i === 1);\n      }\n    });\n    pswp.currSlide = (_this$itemHolders$ = this.itemHolders[1]) === null || _this$itemHolders$ === void 0 ? void 0 : _this$itemHolders$.slide;\n    pswp.contentLoader.updateLazy(positionDifference);\n\n    if (pswp.currSlide) {\n      pswp.currSlide.applyCurrentZoomPan();\n    }\n\n    pswp.dispatch('change');\n  }\n  /**\n   * Move the X position of the main scroll container\n   *\n   * @param {number} x\n   * @param {boolean} [dragging]\n   */\n\n\n  moveTo(x, dragging) {\n    if (!this.pswp.canLoop() && dragging) {\n      // Apply friction\n      let newSlideIndexOffset = (this.slideWidth * this._currPositionIndex - x) / this.slideWidth;\n      newSlideIndexOffset += this.pswp.currIndex;\n      const delta = Math.round(x - this.x);\n\n      if (newSlideIndexOffset < 0 && delta > 0 || newSlideIndexOffset >= this.pswp.getNumItems() - 1 && delta < 0) {\n        x = this.x + delta * MAIN_SCROLL_END_FRICTION;\n      }\n    }\n\n    this.x = x;\n\n    if (this.pswp.container) {\n      setTransform(this.pswp.container, x);\n    }\n\n    this.pswp.dispatch('moveMainScroll', {\n      x,\n      dragging: dragging !== null && dragging !== void 0 ? dragging : false\n    });\n  }\n\n}\n\n/** @typedef {import('./photoswipe.js').default} PhotoSwipe */\n\n/**\n * @template T\n * @typedef {import('./types.js').Methods<T>} Methods<T>\n */\n\nconst KeyboardKeyCodesMap = {\n  Escape: 27,\n  z: 90,\n  ArrowLeft: 37,\n  ArrowUp: 38,\n  ArrowRight: 39,\n  ArrowDown: 40,\n  Tab: 9\n};\n/**\n * @template {keyof KeyboardKeyCodesMap} T\n * @param {T} key\n * @param {boolean} isKeySupported\n * @returns {T | number | undefined}\n */\n\nconst getKeyboardEventKey = (key, isKeySupported) => {\n  return isKeySupported ? key : KeyboardKeyCodesMap[key];\n};\n/**\n * - Manages keyboard shortcuts.\n * - Helps trap focus within photoswipe.\n */\n\n\nclass Keyboard {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    /** @private */\n\n    this._wasFocused = false;\n    pswp.on('bindEvents', () => {\n      if (pswp.options.trapFocus) {\n        // Dialog was likely opened by keyboard if initial point is not defined\n        if (!pswp.options.initialPointerPos) {\n          // focus causes layout,\n          // which causes lag during the animation,\n          // that's why we delay it until the opener transition ends\n          this._focusRoot();\n        }\n\n        pswp.events.add(document, 'focusin',\n        /** @type EventListener */\n        this._onFocusIn.bind(this));\n      }\n\n      pswp.events.add(document, 'keydown',\n      /** @type EventListener */\n      this._onKeyDown.bind(this));\n    });\n    const lastActiveElement =\n    /** @type {HTMLElement} */\n    document.activeElement;\n    pswp.on('destroy', () => {\n      if (pswp.options.returnFocus && lastActiveElement && this._wasFocused) {\n        lastActiveElement.focus();\n      }\n    });\n  }\n  /** @private */\n\n\n  _focusRoot() {\n    if (!this._wasFocused && this.pswp.element) {\n      this.pswp.element.focus();\n      this._wasFocused = true;\n    }\n  }\n  /**\n   * @private\n   * @param {KeyboardEvent} e\n   */\n\n\n  _onKeyDown(e) {\n    const {\n      pswp\n    } = this;\n\n    if (pswp.dispatch('keydown', {\n      originalEvent: e\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (specialKeyUsed(e)) {\n      // don't do anything if special key pressed\n      // to prevent from overriding default browser actions\n      // for example, in Chrome on Mac cmd+arrow-left returns to previous page\n      return;\n    }\n    /** @type {Methods<PhotoSwipe> | undefined} */\n\n\n    let keydownAction;\n    /** @type {'x' | 'y' | undefined} */\n\n    let axis;\n    let isForward = false;\n    const isKeySupported = ('key' in e);\n\n    switch (isKeySupported ? e.key : e.keyCode) {\n      case getKeyboardEventKey('Escape', isKeySupported):\n        if (pswp.options.escKey) {\n          keydownAction = 'close';\n        }\n\n        break;\n\n      case getKeyboardEventKey('z', isKeySupported):\n        keydownAction = 'toggleZoom';\n        break;\n\n      case getKeyboardEventKey('ArrowLeft', isKeySupported):\n        axis = 'x';\n        break;\n\n      case getKeyboardEventKey('ArrowUp', isKeySupported):\n        axis = 'y';\n        break;\n\n      case getKeyboardEventKey('ArrowRight', isKeySupported):\n        axis = 'x';\n        isForward = true;\n        break;\n\n      case getKeyboardEventKey('ArrowDown', isKeySupported):\n        isForward = true;\n        axis = 'y';\n        break;\n\n      case getKeyboardEventKey('Tab', isKeySupported):\n        this._focusRoot();\n\n        break;\n    } // if left/right/top/bottom key\n\n\n    if (axis) {\n      // prevent page scroll\n      e.preventDefault();\n      const {\n        currSlide\n      } = pswp;\n\n      if (pswp.options.arrowKeys && axis === 'x' && pswp.getNumItems() > 1) {\n        keydownAction = isForward ? 'next' : 'prev';\n      } else if (currSlide && currSlide.currZoomLevel > currSlide.zoomLevels.fit) {\n        // up/down arrow keys pan the image vertically\n        // left/right arrow keys pan horizontally.\n        // Unless there is only one image,\n        // or arrowKeys option is disabled\n        currSlide.pan[axis] += isForward ? -80 : 80;\n        currSlide.panTo(currSlide.pan.x, currSlide.pan.y);\n      }\n    }\n\n    if (keydownAction) {\n      e.preventDefault(); // @ts-ignore\n\n      pswp[keydownAction]();\n    }\n  }\n  /**\n   * Trap focus inside photoswipe\n   *\n   * @private\n   * @param {FocusEvent} e\n   */\n\n\n  _onFocusIn(e) {\n    const {\n      template\n    } = this.pswp;\n\n    if (template && document !== e.target && template !== e.target && !template.contains(\n    /** @type {Node} */\n    e.target)) {\n      // focus root element\n      template.focus();\n    }\n  }\n\n}\n\nconst DEFAULT_EASING = 'cubic-bezier(.4,0,.22,1)';\n/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */\n\n/** @typedef {Object} DefaultCssAnimationProps\n *\n * @prop {HTMLElement} target\n * @prop {number} [duration]\n * @prop {string} [easing]\n * @prop {string} [transform]\n * @prop {string} [opacity]\n * */\n\n/** @typedef {SharedAnimationProps & DefaultCssAnimationProps} CssAnimationProps */\n\n/**\n * Runs CSS transition.\n */\n\nclass CSSAnimation {\n  /**\n   * onComplete can be unpredictable, be careful about current state\n   *\n   * @param {CssAnimationProps} props\n   */\n  constructor(props) {\n    var _props$prop;\n\n    this.props = props;\n    const {\n      target,\n      onComplete,\n      transform,\n      onFinish = () => {},\n      duration = 333,\n      easing = DEFAULT_EASING\n    } = props;\n    this.onFinish = onFinish; // support only transform and opacity\n\n    const prop = transform ? 'transform' : 'opacity';\n    const propValue = (_props$prop = props[prop]) !== null && _props$prop !== void 0 ? _props$prop : '';\n    /** @private */\n\n    this._target = target;\n    /** @private */\n\n    this._onComplete = onComplete;\n    /** @private */\n\n    this._finished = false;\n    /** @private */\n\n    this._onTransitionEnd = this._onTransitionEnd.bind(this); // Using timeout hack to make sure that animation\n    // starts even if the animated property was changed recently,\n    // otherwise transitionend might not fire or transition won't start.\n    // https://drafts.csswg.org/css-transitions/#starting\n    //\n    // ¯\\_(ツ)_/¯\n\n    /** @private */\n\n    this._helperTimeout = setTimeout(() => {\n      setTransitionStyle(target, prop, duration, easing);\n      this._helperTimeout = setTimeout(() => {\n        target.addEventListener('transitionend', this._onTransitionEnd, false);\n        target.addEventListener('transitioncancel', this._onTransitionEnd, false); // Safari occasionally does not emit transitionend event\n        // if element property was modified during the transition,\n        // which may be caused by resize or third party component,\n        // using timeout as a safety fallback\n\n        this._helperTimeout = setTimeout(() => {\n          this._finalizeAnimation();\n        }, duration + 500);\n        target.style[prop] = propValue;\n      }, 30); // Do not reduce this number\n    }, 0);\n  }\n  /**\n   * @private\n   * @param {TransitionEvent} e\n   */\n\n\n  _onTransitionEnd(e) {\n    if (e.target === this._target) {\n      this._finalizeAnimation();\n    }\n  }\n  /**\n   * @private\n   */\n\n\n  _finalizeAnimation() {\n    if (!this._finished) {\n      this._finished = true;\n      this.onFinish();\n\n      if (this._onComplete) {\n        this._onComplete();\n      }\n    }\n  } // Destroy is called automatically onFinish\n\n\n  destroy() {\n    if (this._helperTimeout) {\n      clearTimeout(this._helperTimeout);\n    }\n\n    removeTransitionStyle(this._target);\n\n    this._target.removeEventListener('transitionend', this._onTransitionEnd, false);\n\n    this._target.removeEventListener('transitioncancel', this._onTransitionEnd, false);\n\n    if (!this._finished) {\n      this._finalizeAnimation();\n    }\n  }\n\n}\n\nconst DEFAULT_NATURAL_FREQUENCY = 12;\nconst DEFAULT_DAMPING_RATIO = 0.75;\n/**\n * Spring easing helper\n */\n\nclass SpringEaser {\n  /**\n   * @param {number} initialVelocity Initial velocity, px per ms.\n   *\n   * @param {number} [dampingRatio]\n   * Determines how bouncy animation will be.\n   * From 0 to 1, 0 - always overshoot, 1 - do not overshoot.\n   * \"overshoot\" refers to part of animation that\n   * goes beyond the final value.\n   *\n   * @param {number} [naturalFrequency]\n   * Determines how fast animation will slow down.\n   * The higher value - the stiffer the transition will be,\n   * and the faster it will slow down.\n   * Recommended value from 10 to 50\n   */\n  constructor(initialVelocity, dampingRatio, naturalFrequency) {\n    this.velocity = initialVelocity * 1000; // convert to \"pixels per second\"\n    // https://en.wikipedia.org/wiki/Damping_ratio\n\n    this._dampingRatio = dampingRatio || DEFAULT_DAMPING_RATIO; // https://en.wikipedia.org/wiki/Natural_frequency\n\n    this._naturalFrequency = naturalFrequency || DEFAULT_NATURAL_FREQUENCY;\n    this._dampedFrequency = this._naturalFrequency;\n\n    if (this._dampingRatio < 1) {\n      this._dampedFrequency *= Math.sqrt(1 - this._dampingRatio * this._dampingRatio);\n    }\n  }\n  /**\n   * @param {number} deltaPosition Difference between current and end position of the animation\n   * @param {number} deltaTime Frame duration in milliseconds\n   *\n   * @returns {number} Displacement, relative to the end position.\n   */\n\n\n  easeFrame(deltaPosition, deltaTime) {\n    // Inspired by Apple Webkit and Android spring function implementation\n    // https://en.wikipedia.org/wiki/Oscillation\n    // https://en.wikipedia.org/wiki/Damping_ratio\n    // we ignore mass (assume that it's 1kg)\n    let displacement = 0;\n    let coeff;\n    deltaTime /= 1000;\n    const naturalDumpingPow = Math.E ** (-this._dampingRatio * this._naturalFrequency * deltaTime);\n\n    if (this._dampingRatio === 1) {\n      coeff = this.velocity + this._naturalFrequency * deltaPosition;\n      displacement = (deltaPosition + coeff * deltaTime) * naturalDumpingPow;\n      this.velocity = displacement * -this._naturalFrequency + coeff * naturalDumpingPow;\n    } else if (this._dampingRatio < 1) {\n      coeff = 1 / this._dampedFrequency * (this._dampingRatio * this._naturalFrequency * deltaPosition + this.velocity);\n      const dumpedFCos = Math.cos(this._dampedFrequency * deltaTime);\n      const dumpedFSin = Math.sin(this._dampedFrequency * deltaTime);\n      displacement = naturalDumpingPow * (deltaPosition * dumpedFCos + coeff * dumpedFSin);\n      this.velocity = displacement * -this._naturalFrequency * this._dampingRatio + naturalDumpingPow * (-this._dampedFrequency * deltaPosition * dumpedFSin + this._dampedFrequency * coeff * dumpedFCos);\n    } // Overdamped (>1) damping ratio is not supported\n\n\n    return displacement;\n  }\n\n}\n\n/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */\n\n/**\n * @typedef {Object} DefaultSpringAnimationProps\n *\n * @prop {number} start\n * @prop {number} end\n * @prop {number} velocity\n * @prop {number} [dampingRatio]\n * @prop {number} [naturalFrequency]\n * @prop {(end: number) => void} onUpdate\n */\n\n/** @typedef {SharedAnimationProps & DefaultSpringAnimationProps} SpringAnimationProps */\n\nclass SpringAnimation {\n  /**\n   * @param {SpringAnimationProps} props\n   */\n  constructor(props) {\n    this.props = props;\n    this._raf = 0;\n    const {\n      start,\n      end,\n      velocity,\n      onUpdate,\n      onComplete,\n      onFinish = () => {},\n      dampingRatio,\n      naturalFrequency\n    } = props;\n    this.onFinish = onFinish;\n    const easer = new SpringEaser(velocity, dampingRatio, naturalFrequency);\n    let prevTime = Date.now();\n    let deltaPosition = start - end;\n\n    const animationLoop = () => {\n      if (this._raf) {\n        deltaPosition = easer.easeFrame(deltaPosition, Date.now() - prevTime); // Stop the animation if velocity is low and position is close to end\n\n        if (Math.abs(deltaPosition) < 1 && Math.abs(easer.velocity) < 50) {\n          // Finalize the animation\n          onUpdate(end);\n\n          if (onComplete) {\n            onComplete();\n          }\n\n          this.onFinish();\n        } else {\n          prevTime = Date.now();\n          onUpdate(deltaPosition + end);\n          this._raf = requestAnimationFrame(animationLoop);\n        }\n      }\n    };\n\n    this._raf = requestAnimationFrame(animationLoop);\n  } // Destroy is called automatically onFinish\n\n\n  destroy() {\n    if (this._raf >= 0) {\n      cancelAnimationFrame(this._raf);\n    }\n\n    this._raf = 0;\n  }\n\n}\n\n/** @typedef {import('./css-animation.js').CssAnimationProps} CssAnimationProps */\n\n/** @typedef {import('./spring-animation.js').SpringAnimationProps} SpringAnimationProps */\n\n/** @typedef {Object} SharedAnimationProps\n * @prop {string} [name]\n * @prop {boolean} [isPan]\n * @prop {boolean} [isMainScroll]\n * @prop {VoidFunction} [onComplete]\n * @prop {VoidFunction} [onFinish]\n */\n\n/** @typedef {SpringAnimation | CSSAnimation} Animation */\n\n/** @typedef {SpringAnimationProps | CssAnimationProps} AnimationProps */\n\n/**\n * Manages animations\n */\n\nclass Animations {\n  constructor() {\n    /** @type {Animation[]} */\n    this.activeAnimations = [];\n  }\n  /**\n   * @param {SpringAnimationProps} props\n   */\n\n\n  startSpring(props) {\n    this._start(props, true);\n  }\n  /**\n   * @param {CssAnimationProps} props\n   */\n\n\n  startTransition(props) {\n    this._start(props);\n  }\n  /**\n   * @private\n   * @param {AnimationProps} props\n   * @param {boolean} [isSpring]\n   * @returns {Animation}\n   */\n\n\n  _start(props, isSpring) {\n    const animation = isSpring ? new SpringAnimation(\n    /** @type SpringAnimationProps */\n    props) : new CSSAnimation(\n    /** @type CssAnimationProps */\n    props);\n    this.activeAnimations.push(animation);\n\n    animation.onFinish = () => this.stop(animation);\n\n    return animation;\n  }\n  /**\n   * @param {Animation} animation\n   */\n\n\n  stop(animation) {\n    animation.destroy();\n    const index = this.activeAnimations.indexOf(animation);\n\n    if (index > -1) {\n      this.activeAnimations.splice(index, 1);\n    }\n  }\n\n  stopAll() {\n    // _stopAllAnimations\n    this.activeAnimations.forEach(animation => {\n      animation.destroy();\n    });\n    this.activeAnimations = [];\n  }\n  /**\n   * Stop all pan or zoom transitions\n   */\n\n\n  stopAllPan() {\n    this.activeAnimations = this.activeAnimations.filter(animation => {\n      if (animation.props.isPan) {\n        animation.destroy();\n        return false;\n      }\n\n      return true;\n    });\n  }\n\n  stopMainScroll() {\n    this.activeAnimations = this.activeAnimations.filter(animation => {\n      if (animation.props.isMainScroll) {\n        animation.destroy();\n        return false;\n      }\n\n      return true;\n    });\n  }\n  /**\n   * Returns true if main scroll transition is running\n   */\n  // isMainScrollRunning() {\n  //   return this.activeAnimations.some((animation) => {\n  //     return animation.props.isMainScroll;\n  //   });\n  // }\n\n  /**\n   * Returns true if any pan or zoom transition is running\n   */\n\n\n  isPanRunning() {\n    return this.activeAnimations.some(animation => {\n      return animation.props.isPan;\n    });\n  }\n\n}\n\n/** @typedef {import('./photoswipe.js').default} PhotoSwipe */\n\n/**\n * Handles scroll wheel.\n * Can pan and zoom current slide image.\n */\nclass ScrollWheel {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    pswp.events.add(pswp.element, 'wheel',\n    /** @type EventListener */\n    this._onWheel.bind(this));\n  }\n  /**\n   * @private\n   * @param {WheelEvent} e\n   */\n\n\n  _onWheel(e) {\n    e.preventDefault();\n    const {\n      currSlide\n    } = this.pswp;\n    let {\n      deltaX,\n      deltaY\n    } = e;\n\n    if (!currSlide) {\n      return;\n    }\n\n    if (this.pswp.dispatch('wheel', {\n      originalEvent: e\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (e.ctrlKey || this.pswp.options.wheelToZoom) {\n      // zoom\n      if (currSlide.isZoomable()) {\n        let zoomFactor = -deltaY;\n\n        if (e.deltaMode === 1\n        /* DOM_DELTA_LINE */\n        ) {\n          zoomFactor *= 0.05;\n        } else {\n          zoomFactor *= e.deltaMode ? 1 : 0.002;\n        }\n\n        zoomFactor = 2 ** zoomFactor;\n        const destZoomLevel = currSlide.currZoomLevel * zoomFactor;\n        currSlide.zoomTo(destZoomLevel, {\n          x: e.clientX,\n          y: e.clientY\n        });\n      }\n    } else {\n      // pan\n      if (currSlide.isPannable()) {\n        if (e.deltaMode === 1\n        /* DOM_DELTA_LINE */\n        ) {\n          // 18 - average line height\n          deltaX *= 18;\n          deltaY *= 18;\n        }\n\n        currSlide.panTo(currSlide.pan.x - deltaX, currSlide.pan.y - deltaY);\n      }\n    }\n  }\n\n}\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/**\n * @template T\n * @typedef {import('../types.js').Methods<T>} Methods<T>\n */\n\n/**\n * @typedef {Object} UIElementMarkupProps\n * @prop {boolean} [isCustomSVG]\n * @prop {string} inner\n * @prop {string} [outlineID]\n * @prop {number | string} [size]\n */\n\n/**\n * @typedef {Object} UIElementData\n * @prop {DefaultUIElements | string} [name]\n * @prop {string} [className]\n * @prop {UIElementMarkup} [html]\n * @prop {boolean} [isButton]\n * @prop {keyof HTMLElementTagNameMap} [tagName]\n * @prop {string} [title]\n * @prop {string} [ariaLabel]\n * @prop {(element: HTMLElement, pswp: PhotoSwipe) => void} [onInit]\n * @prop {Methods<PhotoSwipe> | ((e: MouseEvent, element: HTMLElement, pswp: PhotoSwipe) => void)} [onClick]\n * @prop {'bar' | 'wrapper' | 'root'} [appendTo]\n * @prop {number} [order]\n */\n\n/** @typedef {'arrowPrev' | 'arrowNext' | 'close' | 'zoom' | 'counter'} DefaultUIElements */\n\n/** @typedef {string | UIElementMarkupProps} UIElementMarkup */\n\n/**\n * @param {UIElementMarkup} [htmlData]\n * @returns {string}\n */\n\nfunction addElementHTML(htmlData) {\n  if (typeof htmlData === 'string') {\n    // Allow developers to provide full svg,\n    // For example:\n    // <svg viewBox=\"0 0 32 32\" width=\"32\" height=\"32\" aria-hidden=\"true\" class=\"pswp__icn\">\n    //   <path d=\"...\" />\n    //   <circle ... />\n    // </svg>\n    // Can also be any HTML string.\n    return htmlData;\n  }\n\n  if (!htmlData || !htmlData.isCustomSVG) {\n    return '';\n  }\n\n  const svgData = htmlData;\n  let out = '<svg aria-hidden=\"true\" class=\"pswp__icn\" viewBox=\"0 0 %d %d\" width=\"%d\" height=\"%d\">'; // replace all %d with size\n\n  out = out.split('%d').join(\n  /** @type {string} */\n  svgData.size || 32); // Icons may contain outline/shadow,\n  // to make it we \"clone\" base icon shape and add border to it.\n  // Icon itself and border are styled via CSS.\n  //\n  // Property shadowID defines ID of element that should be cloned.\n\n  if (svgData.outlineID) {\n    out += '<use class=\"pswp__icn-shadow\" xlink:href=\"#' + svgData.outlineID + '\"/>';\n  }\n\n  out += svgData.inner;\n  out += '</svg>';\n  return out;\n}\n\nclass UIElement {\n  /**\n   * @param {PhotoSwipe} pswp\n   * @param {UIElementData} data\n   */\n  constructor(pswp, data) {\n    var _container;\n\n    const name = data.name || data.className;\n    let elementHTML = data.html; // @ts-expect-error lookup only by `data.name` maybe?\n\n    if (pswp.options[name] === false) {\n      // exit if element is disabled from options\n      return;\n    } // Allow to override SVG icons from options\n    // @ts-expect-error lookup only by `data.name` maybe?\n\n\n    if (typeof pswp.options[name + 'SVG'] === 'string') {\n      // arrowPrevSVG\n      // arrowNextSVG\n      // closeSVG\n      // zoomSVG\n      // @ts-expect-error lookup only by `data.name` maybe?\n      elementHTML = pswp.options[name + 'SVG'];\n    }\n\n    pswp.dispatch('uiElementCreate', {\n      data\n    });\n    let className = '';\n\n    if (data.isButton) {\n      className += 'pswp__button ';\n      className += data.className || `pswp__button--${data.name}`;\n    } else {\n      className += data.className || `pswp__${data.name}`;\n    }\n\n    let tagName = data.isButton ? data.tagName || 'button' : data.tagName || 'div';\n    tagName =\n    /** @type {keyof HTMLElementTagNameMap} */\n    tagName.toLowerCase();\n    /** @type {HTMLElement} */\n\n    const element = createElement(className, tagName);\n\n    if (data.isButton) {\n      if (tagName === 'button') {\n        /** @type {HTMLButtonElement} */\n        element.type = 'button';\n      }\n\n      let {\n        title\n      } = data;\n      const {\n        ariaLabel\n      } = data; // @ts-expect-error lookup only by `data.name` maybe?\n\n      if (typeof pswp.options[name + 'Title'] === 'string') {\n        // @ts-expect-error lookup only by `data.name` maybe?\n        title = pswp.options[name + 'Title'];\n      }\n\n      if (title) {\n        element.title = title;\n      }\n\n      const ariaText = ariaLabel || title;\n\n      if (ariaText) {\n        element.setAttribute('aria-label', ariaText);\n      }\n    }\n\n    element.innerHTML = addElementHTML(elementHTML);\n\n    if (data.onInit) {\n      data.onInit(element, pswp);\n    }\n\n    if (data.onClick) {\n      element.onclick = e => {\n        if (typeof data.onClick === 'string') {\n          // @ts-ignore\n          pswp[data.onClick]();\n        } else if (typeof data.onClick === 'function') {\n          data.onClick(e, element, pswp);\n        }\n      };\n    } // Top bar is default position\n\n\n    const appendTo = data.appendTo || 'bar';\n    /** @type {HTMLElement | undefined} root element by default */\n\n    let container = pswp.element;\n\n    if (appendTo === 'bar') {\n      if (!pswp.topBar) {\n        pswp.topBar = createElement('pswp__top-bar pswp__hide-on-close', 'div', pswp.scrollWrap);\n      }\n\n      container = pswp.topBar;\n    } else {\n      // element outside of top bar gets a secondary class\n      // that makes element fade out on close\n      element.classList.add('pswp__hide-on-close');\n\n      if (appendTo === 'wrapper') {\n        container = pswp.scrollWrap;\n      }\n    }\n\n    (_container = container) === null || _container === void 0 || _container.appendChild(pswp.applyFilters('uiElement', element, data));\n  }\n\n}\n\n/*\n  Backward and forward arrow buttons\n */\n\n/** @typedef {import('./ui-element.js').UIElementData} UIElementData */\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/**\n *\n * @param {HTMLElement} element\n * @param {PhotoSwipe} pswp\n * @param {boolean} [isNextButton]\n */\nfunction initArrowButton(element, pswp, isNextButton) {\n  element.classList.add('pswp__button--arrow'); // TODO: this should point to a unique id for this instance\n\n  element.setAttribute('aria-controls', 'pswp__items');\n  pswp.on('change', () => {\n    if (!pswp.options.loop) {\n      if (isNextButton) {\n        /** @type {HTMLButtonElement} */\n        element.disabled = !(pswp.currIndex < pswp.getNumItems() - 1);\n      } else {\n        /** @type {HTMLButtonElement} */\n        element.disabled = !(pswp.currIndex > 0);\n      }\n    }\n  });\n}\n/** @type {UIElementData} */\n\n\nconst arrowPrev = {\n  name: 'arrowPrev',\n  className: 'pswp__button--arrow--prev',\n  title: 'Previous',\n  order: 10,\n  isButton: true,\n  appendTo: 'wrapper',\n  html: {\n    isCustomSVG: true,\n    size: 60,\n    inner: '<path d=\"M29 43l-3 3-16-16 16-16 3 3-13 13 13 13z\" id=\"pswp__icn-arrow\"/>',\n    outlineID: 'pswp__icn-arrow'\n  },\n  onClick: 'prev',\n  onInit: initArrowButton\n};\n/** @type {UIElementData} */\n\nconst arrowNext = {\n  name: 'arrowNext',\n  className: 'pswp__button--arrow--next',\n  title: 'Next',\n  order: 11,\n  isButton: true,\n  appendTo: 'wrapper',\n  html: {\n    isCustomSVG: true,\n    size: 60,\n    inner: '<use xlink:href=\"#pswp__icn-arrow\"/>',\n    outlineID: 'pswp__icn-arrow'\n  },\n  onClick: 'next',\n  onInit: (el, pswp) => {\n    initArrowButton(el, pswp, true);\n  }\n};\n\n/** @type {import('./ui-element.js').UIElementData} UIElementData */\nconst closeButton = {\n  name: 'close',\n  title: 'Close',\n  order: 20,\n  isButton: true,\n  html: {\n    isCustomSVG: true,\n    inner: '<path d=\"M24 10l-2-2-6 6-6-6-2 2 6 6-6 6 2 2 6-6 6 6 2-2-6-6z\" id=\"pswp__icn-close\"/>',\n    outlineID: 'pswp__icn-close'\n  },\n  onClick: 'close'\n};\n\n/** @type {import('./ui-element.js').UIElementData} UIElementData */\nconst zoomButton = {\n  name: 'zoom',\n  title: 'Zoom',\n  order: 10,\n  isButton: true,\n  html: {\n    isCustomSVG: true,\n    // eslint-disable-next-line max-len\n    inner: '<path d=\"M17.426 19.926a6 6 0 1 1 1.5-1.5L23 22.5 21.5 24l-4.074-4.074z\" id=\"pswp__icn-zoom\"/>' + '<path fill=\"currentColor\" class=\"pswp__zoom-icn-bar-h\" d=\"M11 16v-2h6v2z\"/>' + '<path fill=\"currentColor\" class=\"pswp__zoom-icn-bar-v\" d=\"M13 12h2v6h-2z\"/>',\n    outlineID: 'pswp__icn-zoom'\n  },\n  onClick: 'toggleZoom'\n};\n\n/** @type {import('./ui-element.js').UIElementData} UIElementData */\nconst loadingIndicator = {\n  name: 'preloader',\n  appendTo: 'bar',\n  order: 7,\n  html: {\n    isCustomSVG: true,\n    // eslint-disable-next-line max-len\n    inner: '<path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M21.2 16a5.2 5.2 0 1 1-5.2-5.2V8a8 8 0 1 0 8 8h-2.8Z\" id=\"pswp__icn-loading\"/>',\n    outlineID: 'pswp__icn-loading'\n  },\n  onInit: (indicatorElement, pswp) => {\n    /** @type {boolean | undefined} */\n    let isVisible;\n    /** @type {NodeJS.Timeout | null} */\n\n    let delayTimeout = null;\n    /**\n     * @param {string} className\n     * @param {boolean} add\n     */\n\n    const toggleIndicatorClass = (className, add) => {\n      indicatorElement.classList.toggle('pswp__preloader--' + className, add);\n    };\n    /**\n     * @param {boolean} visible\n     */\n\n\n    const setIndicatorVisibility = visible => {\n      if (isVisible !== visible) {\n        isVisible = visible;\n        toggleIndicatorClass('active', visible);\n      }\n    };\n\n    const updatePreloaderVisibility = () => {\n      var _pswp$currSlide;\n\n      if (!((_pswp$currSlide = pswp.currSlide) !== null && _pswp$currSlide !== void 0 && _pswp$currSlide.content.isLoading())) {\n        setIndicatorVisibility(false);\n\n        if (delayTimeout) {\n          clearTimeout(delayTimeout);\n          delayTimeout = null;\n        }\n\n        return;\n      }\n\n      if (!delayTimeout) {\n        // display loading indicator with delay\n        delayTimeout = setTimeout(() => {\n          var _pswp$currSlide2;\n\n          setIndicatorVisibility(Boolean((_pswp$currSlide2 = pswp.currSlide) === null || _pswp$currSlide2 === void 0 ? void 0 : _pswp$currSlide2.content.isLoading()));\n          delayTimeout = null;\n        }, pswp.options.preloaderDelay);\n      }\n    };\n\n    pswp.on('change', updatePreloaderVisibility);\n    pswp.on('loadComplete', e => {\n      if (pswp.currSlide === e.slide) {\n        updatePreloaderVisibility();\n      }\n    }); // expose the method\n\n    if (pswp.ui) {\n      pswp.ui.updatePreloaderVisibility = updatePreloaderVisibility;\n    }\n  }\n};\n\n/** @type {import('./ui-element.js').UIElementData} UIElementData */\nconst counterIndicator = {\n  name: 'counter',\n  order: 5,\n  onInit: (counterElement, pswp) => {\n    pswp.on('change', () => {\n      counterElement.innerText = pswp.currIndex + 1 + pswp.options.indexIndicatorSep + pswp.getNumItems();\n    });\n  }\n};\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('./ui-element.js').UIElementData} UIElementData */\n\n/**\n * Set special class on element when image is zoomed.\n *\n * By default, it is used to adjust\n * zoom icon and zoom cursor via CSS.\n *\n * @param {HTMLElement} el\n * @param {boolean} isZoomedIn\n */\n\nfunction setZoomedIn(el, isZoomedIn) {\n  el.classList.toggle('pswp--zoomed-in', isZoomedIn);\n}\n\nclass UI {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    this.isRegistered = false;\n    /** @type {UIElementData[]} */\n\n    this.uiElementsData = [];\n    /** @type {(UIElement | UIElementData)[]} */\n\n    this.items = [];\n    /** @type {() => void} */\n\n    this.updatePreloaderVisibility = () => {};\n    /**\n     * @private\n     * @type {number | undefined}\n     */\n\n\n    this._lastUpdatedZoomLevel = undefined;\n  }\n\n  init() {\n    const {\n      pswp\n    } = this;\n    this.isRegistered = false;\n    this.uiElementsData = [closeButton, arrowPrev, arrowNext, zoomButton, loadingIndicator, counterIndicator];\n    pswp.dispatch('uiRegister'); // sort by order\n\n    this.uiElementsData.sort((a, b) => {\n      // default order is 0\n      return (a.order || 0) - (b.order || 0);\n    });\n    this.items = [];\n    this.isRegistered = true;\n    this.uiElementsData.forEach(uiElementData => {\n      this.registerElement(uiElementData);\n    });\n    pswp.on('change', () => {\n      var _pswp$element;\n\n      (_pswp$element = pswp.element) === null || _pswp$element === void 0 || _pswp$element.classList.toggle('pswp--one-slide', pswp.getNumItems() === 1);\n    });\n    pswp.on('zoomPanUpdate', () => this._onZoomPanUpdate());\n  }\n  /**\n   * @param {UIElementData} elementData\n   */\n\n\n  registerElement(elementData) {\n    if (this.isRegistered) {\n      this.items.push(new UIElement(this.pswp, elementData));\n    } else {\n      this.uiElementsData.push(elementData);\n    }\n  }\n  /**\n   * Fired each time zoom or pan position is changed.\n   * Update classes that control visibility of zoom button and cursor icon.\n   *\n   * @private\n   */\n\n\n  _onZoomPanUpdate() {\n    const {\n      template,\n      currSlide,\n      options\n    } = this.pswp;\n\n    if (this.pswp.opener.isClosing || !template || !currSlide) {\n      return;\n    }\n\n    let {\n      currZoomLevel\n    } = currSlide; // if not open yet - check against initial zoom level\n\n    if (!this.pswp.opener.isOpen) {\n      currZoomLevel = currSlide.zoomLevels.initial;\n    }\n\n    if (currZoomLevel === this._lastUpdatedZoomLevel) {\n      return;\n    }\n\n    this._lastUpdatedZoomLevel = currZoomLevel;\n    const currZoomLevelDiff = currSlide.zoomLevels.initial - currSlide.zoomLevels.secondary; // Initial and secondary zoom levels are almost equal\n\n    if (Math.abs(currZoomLevelDiff) < 0.01 || !currSlide.isZoomable()) {\n      // disable zoom\n      setZoomedIn(template, false);\n      template.classList.remove('pswp--zoom-allowed');\n      return;\n    }\n\n    template.classList.add('pswp--zoom-allowed');\n    const potentialZoomLevel = currZoomLevel === currSlide.zoomLevels.initial ? currSlide.zoomLevels.secondary : currSlide.zoomLevels.initial;\n    setZoomedIn(template, potentialZoomLevel <= currZoomLevel);\n\n    if (options.imageClickAction === 'zoom' || options.imageClickAction === 'zoom-or-close') {\n      template.classList.add('pswp--click-to-zoom');\n    }\n  }\n\n}\n\n/** @typedef {import('./slide.js').SlideData} SlideData */\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {{ x: number; y: number; w: number; innerRect?: { w: number; h: number; x: number; y: number } }} Bounds */\n\n/**\n * @param {HTMLElement} el\n * @returns Bounds\n */\nfunction getBoundsByElement(el) {\n  const thumbAreaRect = el.getBoundingClientRect();\n  return {\n    x: thumbAreaRect.left,\n    y: thumbAreaRect.top,\n    w: thumbAreaRect.width\n  };\n}\n/**\n * @param {HTMLElement} el\n * @param {number} imageWidth\n * @param {number} imageHeight\n * @returns Bounds\n */\n\n\nfunction getCroppedBoundsByElement(el, imageWidth, imageHeight) {\n  const thumbAreaRect = el.getBoundingClientRect(); // fill image into the area\n  // (do they same as object-fit:cover does to retrieve coordinates)\n\n  const hRatio = thumbAreaRect.width / imageWidth;\n  const vRatio = thumbAreaRect.height / imageHeight;\n  const fillZoomLevel = hRatio > vRatio ? hRatio : vRatio;\n  const offsetX = (thumbAreaRect.width - imageWidth * fillZoomLevel) / 2;\n  const offsetY = (thumbAreaRect.height - imageHeight * fillZoomLevel) / 2;\n  /**\n   * Coordinates of the image,\n   * as if it was not cropped,\n   * height is calculated automatically\n   *\n   * @type {Bounds}\n   */\n\n  const bounds = {\n    x: thumbAreaRect.left + offsetX,\n    y: thumbAreaRect.top + offsetY,\n    w: imageWidth * fillZoomLevel\n  }; // Coordinates of inner crop area\n  // relative to the image\n\n  bounds.innerRect = {\n    w: thumbAreaRect.width,\n    h: thumbAreaRect.height,\n    x: offsetX,\n    y: offsetY\n  };\n  return bounds;\n}\n/**\n * Get dimensions of thumbnail image\n * (click on which opens photoswipe or closes photoswipe to)\n *\n * @param {number} index\n * @param {SlideData} itemData\n * @param {PhotoSwipe} instance PhotoSwipe instance\n * @returns {Bounds | undefined}\n */\n\n\nfunction getThumbBounds(index, itemData, instance) {\n  // legacy event, before filters were introduced\n  const event = instance.dispatch('thumbBounds', {\n    index,\n    itemData,\n    instance\n  }); // @ts-expect-error\n\n  if (event.thumbBounds) {\n    // @ts-expect-error\n    return event.thumbBounds;\n  }\n\n  const {\n    element\n  } = itemData;\n  /** @type {Bounds | undefined} */\n\n  let thumbBounds;\n  /** @type {HTMLElement | null | undefined} */\n\n  let thumbnail;\n\n  if (element && instance.options.thumbSelector !== false) {\n    const thumbSelector = instance.options.thumbSelector || 'img';\n    thumbnail = element.matches(thumbSelector) ? element :\n    /** @type {HTMLElement | null} */\n    element.querySelector(thumbSelector);\n  }\n\n  thumbnail = instance.applyFilters('thumbEl', thumbnail, itemData, index);\n\n  if (thumbnail) {\n    if (!itemData.thumbCropped) {\n      thumbBounds = getBoundsByElement(thumbnail);\n    } else {\n      thumbBounds = getCroppedBoundsByElement(thumbnail, itemData.width || itemData.w || 0, itemData.height || itemData.h || 0);\n    }\n  }\n\n  return instance.applyFilters('thumbBounds', thumbBounds, itemData, index);\n}\n\n/** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n\n/** @typedef {import('../photoswipe.js').DataSource} DataSource */\n\n/** @typedef {import('../ui/ui-element.js').UIElementData} UIElementData */\n\n/** @typedef {import('../slide/content.js').default} ContentDefault */\n\n/** @typedef {import('../slide/slide.js').default} Slide */\n\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n\n/** @typedef {import('../slide/zoom-level.js').default} ZoomLevel */\n\n/** @typedef {import('../slide/get-thumb-bounds.js').Bounds} Bounds */\n\n/**\n * Allow adding an arbitrary props to the Content\n * https://photoswipe.com/custom-content/#using-webp-image-format\n * @typedef {ContentDefault & Record<string, any>} Content\n */\n\n/** @typedef {{ x?: number; y?: number }} Point */\n\n/**\n * @typedef {Object} PhotoSwipeEventsMap https://photoswipe.com/events/\n *\n *\n * https://photoswipe.com/adding-ui-elements/\n *\n * @prop {undefined} uiRegister\n * @prop {{ data: UIElementData }} uiElementCreate\n *\n *\n * https://photoswipe.com/events/#initialization-events\n *\n * @prop {undefined} beforeOpen\n * @prop {undefined} firstUpdate\n * @prop {undefined} initialLayout\n * @prop {undefined} change\n * @prop {undefined} afterInit\n * @prop {undefined} bindEvents\n *\n *\n * https://photoswipe.com/events/#opening-or-closing-transition-events\n *\n * @prop {undefined} openingAnimationStart\n * @prop {undefined} openingAnimationEnd\n * @prop {undefined} closingAnimationStart\n * @prop {undefined} closingAnimationEnd\n *\n *\n * https://photoswipe.com/events/#closing-events\n *\n * @prop {undefined} close\n * @prop {undefined} destroy\n *\n *\n * https://photoswipe.com/events/#pointer-and-gesture-events\n *\n * @prop {{ originalEvent: PointerEvent }} pointerDown\n * @prop {{ originalEvent: PointerEvent }} pointerMove\n * @prop {{ originalEvent: PointerEvent }} pointerUp\n * @prop {{ bgOpacity: number }} pinchClose can be default prevented\n * @prop {{ panY: number }} verticalDrag can be default prevented\n *\n *\n * https://photoswipe.com/events/#slide-content-events\n *\n * @prop {{ content: Content }} contentInit\n * @prop {{ content: Content; isLazy: boolean }} contentLoad can be default prevented\n * @prop {{ content: Content; isLazy: boolean }} contentLoadImage can be default prevented\n * @prop {{ content: Content; slide: Slide; isError?: boolean }} loadComplete\n * @prop {{ content: Content; slide: Slide }} loadError\n * @prop {{ content: Content; width: number; height: number }} contentResize can be default prevented\n * @prop {{ content: Content; width: number; height: number; slide: Slide }} imageSizeChange\n * @prop {{ content: Content }} contentLazyLoad can be default prevented\n * @prop {{ content: Content }} contentAppend can be default prevented\n * @prop {{ content: Content }} contentActivate can be default prevented\n * @prop {{ content: Content }} contentDeactivate can be default prevented\n * @prop {{ content: Content }} contentRemove can be default prevented\n * @prop {{ content: Content }} contentDestroy can be default prevented\n *\n *\n * undocumented\n *\n * @prop {{ point: Point; originalEvent: PointerEvent }} imageClickAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} bgClickAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} tapAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} doubleTapAction can be default prevented\n *\n * @prop {{ originalEvent: KeyboardEvent }} keydown can be default prevented\n * @prop {{ x: number; dragging: boolean }} moveMainScroll\n * @prop {{ slide: Slide }} firstZoomPan\n * @prop {{ slide: Slide | undefined, data: SlideData, index: number }} gettingData\n * @prop {undefined} beforeResize\n * @prop {undefined} resize\n * @prop {undefined} viewportSize\n * @prop {undefined} updateScrollOffset\n * @prop {{ slide: Slide }} slideInit\n * @prop {{ slide: Slide }} afterSetContent\n * @prop {{ slide: Slide }} slideLoad\n * @prop {{ slide: Slide }} appendHeavy can be default prevented\n * @prop {{ slide: Slide }} appendHeavyContent\n * @prop {{ slide: Slide }} slideActivate\n * @prop {{ slide: Slide }} slideDeactivate\n * @prop {{ slide: Slide }} slideDestroy\n * @prop {{ destZoomLevel: number, centerPoint: Point | undefined, transitionDuration: number | false | undefined }} beforeZoomTo\n * @prop {{ slide: Slide }} zoomPanUpdate\n * @prop {{ slide: Slide }} initialZoomPan\n * @prop {{ slide: Slide }} calcSlideSize\n * @prop {undefined} resolutionChanged\n * @prop {{ originalEvent: WheelEvent }} wheel can be default prevented\n * @prop {{ content: Content }} contentAppendImage can be default prevented\n * @prop {{ index: number; itemData: SlideData }} lazyLoadSlide can be default prevented\n * @prop {undefined} lazyLoad\n * @prop {{ slide: Slide }} calcBounds\n * @prop {{ zoomLevels: ZoomLevel, slideData: SlideData }} zoomLevelsUpdate\n *\n *\n * legacy\n *\n * @prop {undefined} init\n * @prop {undefined} initialZoomIn\n * @prop {undefined} initialZoomOut\n * @prop {undefined} initialZoomInEnd\n * @prop {undefined} initialZoomOutEnd\n * @prop {{ dataSource: DataSource | undefined, numItems: number }} numItems\n * @prop {{ itemData: SlideData; index: number }} itemData\n * @prop {{ index: number, itemData: SlideData, instance: PhotoSwipe }} thumbBounds\n */\n\n/**\n * @typedef {Object} PhotoSwipeFiltersMap https://photoswipe.com/filters/\n *\n * @prop {(numItems: number, dataSource: DataSource | undefined) => number} numItems\n * Modify the total amount of slides. Example on Data sources page.\n * https://photoswipe.com/filters/#numitems\n *\n * @prop {(itemData: SlideData, index: number) => SlideData} itemData\n * Modify slide item data. Example on Data sources page.\n * https://photoswipe.com/filters/#itemdata\n *\n * @prop {(itemData: SlideData, element: HTMLElement, linkEl: HTMLAnchorElement) => SlideData} domItemData\n * Modify item data when it's parsed from DOM element. Example on Data sources page.\n * https://photoswipe.com/filters/#domitemdata\n *\n * @prop {(clickedIndex: number, e: MouseEvent, instance: PhotoSwipeLightbox) => number} clickedIndex\n * Modify clicked gallery item index.\n * https://photoswipe.com/filters/#clickedindex\n *\n * @prop {(placeholderSrc: string | false, content: Content) => string | false} placeholderSrc\n * Modify placeholder image source.\n * https://photoswipe.com/filters/#placeholdersrc\n *\n * @prop {(isContentLoading: boolean, content: Content) => boolean} isContentLoading\n * Modify if the content is currently loading.\n * https://photoswipe.com/filters/#iscontentloading\n *\n * @prop {(isContentZoomable: boolean, content: Content) => boolean} isContentZoomable\n * Modify if the content can be zoomed.\n * https://photoswipe.com/filters/#iscontentzoomable\n *\n * @prop {(useContentPlaceholder: boolean, content: Content) => boolean} useContentPlaceholder\n * Modify if the placeholder should be used for the content.\n * https://photoswipe.com/filters/#usecontentplaceholder\n *\n * @prop {(isKeepingPlaceholder: boolean, content: Content) => boolean} isKeepingPlaceholder\n * Modify if the placeholder should be kept after the content is loaded.\n * https://photoswipe.com/filters/#iskeepingplaceholder\n *\n *\n * @prop {(contentErrorElement: HTMLElement, content: Content) => HTMLElement} contentErrorElement\n * Modify an element when the content has error state (for example, if image cannot be loaded).\n * https://photoswipe.com/filters/#contenterrorelement\n *\n * @prop {(element: HTMLElement, data: UIElementData) => HTMLElement} uiElement\n * Modify a UI element that's being created.\n * https://photoswipe.com/filters/#uielement\n *\n * @prop {(thumbnail: HTMLElement | null | undefined, itemData: SlideData, index: number) => HTMLElement} thumbEl\n * Modify the thumbnail element from which opening zoom animation starts or ends.\n * https://photoswipe.com/filters/#thumbel\n *\n * @prop {(thumbBounds: Bounds | undefined, itemData: SlideData, index: number) => Bounds} thumbBounds\n * Modify the thumbnail bounds from which opening zoom animation starts or ends.\n * https://photoswipe.com/filters/#thumbbounds\n *\n * @prop {(srcsetSizesWidth: number, content: Content) => number} srcsetSizesWidth\n *\n * @prop {(preventPointerEvent: boolean, event: PointerEvent, pointerType: string) => boolean} preventPointerEvent\n *\n */\n\n/**\n * @template {keyof PhotoSwipeFiltersMap} T\n * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter\n */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent<T> : PhotoSwipeEvent<T> & PhotoSwipeEventsMap[T]} AugmentedEvent\n */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {(event: AugmentedEvent<T>) => void} EventCallback\n */\n\n/**\n * Base PhotoSwipe event object\n *\n * @template {keyof PhotoSwipeEventsMap} T\n */\nclass PhotoSwipeEvent {\n  /**\n   * @param {T} type\n   * @param {PhotoSwipeEventsMap[T]} [details]\n   */\n  constructor(type, details) {\n    this.type = type;\n    this.defaultPrevented = false;\n\n    if (details) {\n      Object.assign(this, details);\n    }\n  }\n\n  preventDefault() {\n    this.defaultPrevented = true;\n  }\n\n}\n/**\n * PhotoSwipe base class that can listen and dispatch for events.\n * Shared by PhotoSwipe Core and PhotoSwipe Lightbox, extended by base.js\n */\n\n\nclass Eventable {\n  constructor() {\n    /**\n     * @type {{ [T in keyof PhotoSwipeEventsMap]?: ((event: AugmentedEvent<T>) => void)[] }}\n     */\n    this._listeners = {};\n    /**\n     * @type {{ [T in keyof PhotoSwipeFiltersMap]?: Filter<T>[] }}\n     */\n\n    this._filters = {};\n    /** @type {PhotoSwipe | undefined} */\n\n    this.pswp = undefined;\n    /** @type {PhotoSwipeOptions | undefined} */\n\n    this.options = undefined;\n  }\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {PhotoSwipeFiltersMap[T]} fn\n   * @param {number} priority\n   */\n\n\n  addFilter(name, fn, priority = 100) {\n    var _this$_filters$name, _this$_filters$name2, _this$pswp;\n\n    if (!this._filters[name]) {\n      this._filters[name] = [];\n    }\n\n    (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.push({\n      fn,\n      priority\n    });\n    (_this$_filters$name2 = this._filters[name]) === null || _this$_filters$name2 === void 0 || _this$_filters$name2.sort((f1, f2) => f1.priority - f2.priority);\n    (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.addFilter(name, fn, priority);\n  }\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {PhotoSwipeFiltersMap[T]} fn\n   */\n\n\n  removeFilter(name, fn) {\n    if (this._filters[name]) {\n      // @ts-expect-error\n      this._filters[name] = this._filters[name].filter(filter => filter.fn !== fn);\n    }\n\n    if (this.pswp) {\n      this.pswp.removeFilter(name, fn);\n    }\n  }\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {Parameters<PhotoSwipeFiltersMap[T]>} args\n   * @returns {Parameters<PhotoSwipeFiltersMap[T]>[0]}\n   */\n\n\n  applyFilters(name, ...args) {\n    var _this$_filters$name3;\n\n    (_this$_filters$name3 = this._filters[name]) === null || _this$_filters$name3 === void 0 || _this$_filters$name3.forEach(filter => {\n      // @ts-expect-error\n      args[0] = filter.fn.apply(this, args);\n    });\n    return args[0];\n  }\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {EventCallback<T>} fn\n   */\n\n\n  on(name, fn) {\n    var _this$_listeners$name, _this$pswp2;\n\n    if (!this._listeners[name]) {\n      this._listeners[name] = [];\n    }\n\n    (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.push(fn); // When binding events to lightbox,\n    // also bind events to PhotoSwipe Core,\n    // if it's open.\n\n    (_this$pswp2 = this.pswp) === null || _this$pswp2 === void 0 || _this$pswp2.on(name, fn);\n  }\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {EventCallback<T>} fn\n   */\n\n\n  off(name, fn) {\n    var _this$pswp3;\n\n    if (this._listeners[name]) {\n      // @ts-expect-error\n      this._listeners[name] = this._listeners[name].filter(listener => fn !== listener);\n    }\n\n    (_this$pswp3 = this.pswp) === null || _this$pswp3 === void 0 || _this$pswp3.off(name, fn);\n  }\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {PhotoSwipeEventsMap[T]} [details]\n   * @returns {AugmentedEvent<T>}\n   */\n\n\n  dispatch(name, details) {\n    var _this$_listeners$name2;\n\n    if (this.pswp) {\n      return this.pswp.dispatch(name, details);\n    }\n\n    const event =\n    /** @type {AugmentedEvent<T>} */\n    new PhotoSwipeEvent(name, details);\n    (_this$_listeners$name2 = this._listeners[name]) === null || _this$_listeners$name2 === void 0 || _this$_listeners$name2.forEach(listener => {\n      listener.call(this, event);\n    });\n    return event;\n  }\n\n}\n\nclass Placeholder {\n  /**\n   * @param {string | false} imageSrc\n   * @param {HTMLElement} container\n   */\n  constructor(imageSrc, container) {\n    // Create placeholder\n    // (stretched thumbnail or simple div behind the main image)\n\n    /** @type {HTMLImageElement | HTMLDivElement | null} */\n    this.element = createElement('pswp__img pswp__img--placeholder', imageSrc ? 'img' : 'div', container);\n\n    if (imageSrc) {\n      const imgEl =\n      /** @type {HTMLImageElement} */\n      this.element;\n      imgEl.decoding = 'async';\n      imgEl.alt = '';\n      imgEl.src = imageSrc;\n      imgEl.setAttribute('role', 'presentation');\n    }\n\n    this.element.setAttribute('aria-hidden', 'true');\n  }\n  /**\n   * @param {number} width\n   * @param {number} height\n   */\n\n\n  setDisplayedSize(width, height) {\n    if (!this.element) {\n      return;\n    }\n\n    if (this.element.tagName === 'IMG') {\n      // Use transform scale() to modify img placeholder size\n      // (instead of changing width/height directly).\n      // This helps with performance, specifically in iOS15 Safari.\n      setWidthHeight(this.element, 250, 'auto');\n      this.element.style.transformOrigin = '0 0';\n      this.element.style.transform = toTransformString(0, 0, width / 250);\n    } else {\n      setWidthHeight(this.element, width, height);\n    }\n  }\n\n  destroy() {\n    var _this$element;\n\n    if ((_this$element = this.element) !== null && _this$element !== void 0 && _this$element.parentNode) {\n      this.element.remove();\n    }\n\n    this.element = null;\n  }\n\n}\n\n/** @typedef {import('./slide.js').default} Slide */\n\n/** @typedef {import('./slide.js').SlideData} SlideData */\n\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n\n/** @typedef {import('../util/util.js').LoadState} LoadState */\n\nclass Content {\n  /**\n   * @param {SlideData} itemData Slide data\n   * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance\n   * @param {number} index\n   */\n  constructor(itemData, instance, index) {\n    this.instance = instance;\n    this.data = itemData;\n    this.index = index;\n    /** @type {HTMLImageElement | HTMLDivElement | undefined} */\n\n    this.element = undefined;\n    /** @type {Placeholder | undefined} */\n\n    this.placeholder = undefined;\n    /** @type {Slide | undefined} */\n\n    this.slide = undefined;\n    this.displayedImageWidth = 0;\n    this.displayedImageHeight = 0;\n    this.width = Number(this.data.w) || Number(this.data.width) || 0;\n    this.height = Number(this.data.h) || Number(this.data.height) || 0;\n    this.isAttached = false;\n    this.hasSlide = false;\n    this.isDecoding = false;\n    /** @type {LoadState} */\n\n    this.state = LOAD_STATE.IDLE;\n\n    if (this.data.type) {\n      this.type = this.data.type;\n    } else if (this.data.src) {\n      this.type = 'image';\n    } else {\n      this.type = 'html';\n    }\n\n    this.instance.dispatch('contentInit', {\n      content: this\n    });\n  }\n\n  removePlaceholder() {\n    if (this.placeholder && !this.keepPlaceholder()) {\n      // With delay, as image might be loaded, but not rendered\n      setTimeout(() => {\n        if (this.placeholder) {\n          this.placeholder.destroy();\n          this.placeholder = undefined;\n        }\n      }, 1000);\n    }\n  }\n  /**\n   * Preload content\n   *\n   * @param {boolean} isLazy\n   * @param {boolean} [reload]\n   */\n\n\n  load(isLazy, reload) {\n    if (this.slide && this.usePlaceholder()) {\n      if (!this.placeholder) {\n        const placeholderSrc = this.instance.applyFilters('placeholderSrc', // use  image-based placeholder only for the first slide,\n        // as rendering (even small stretched thumbnail) is an expensive operation\n        this.data.msrc && this.slide.isFirstSlide ? this.data.msrc : false, this);\n        this.placeholder = new Placeholder(placeholderSrc, this.slide.container);\n      } else {\n        const placeholderEl = this.placeholder.element; // Add placeholder to DOM if it was already created\n\n        if (placeholderEl && !placeholderEl.parentElement) {\n          this.slide.container.prepend(placeholderEl);\n        }\n      }\n    }\n\n    if (this.element && !reload) {\n      return;\n    }\n\n    if (this.instance.dispatch('contentLoad', {\n      content: this,\n      isLazy\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (this.isImageContent()) {\n      this.element = createElement('pswp__img', 'img'); // Start loading only after width is defined, as sizes might depend on it.\n      // Due to Safari feature, we must define sizes before srcset.\n\n      if (this.displayedImageWidth) {\n        this.loadImage(isLazy);\n      }\n    } else {\n      this.element = createElement('pswp__content', 'div');\n      this.element.innerHTML = this.data.html || '';\n    }\n\n    if (reload && this.slide) {\n      this.slide.updateContentSize(true);\n    }\n  }\n  /**\n   * Preload image\n   *\n   * @param {boolean} isLazy\n   */\n\n\n  loadImage(isLazy) {\n    var _this$data$src, _this$data$alt;\n\n    if (!this.isImageContent() || !this.element || this.instance.dispatch('contentLoadImage', {\n      content: this,\n      isLazy\n    }).defaultPrevented) {\n      return;\n    }\n\n    const imageElement =\n    /** @type HTMLImageElement */\n    this.element;\n    this.updateSrcsetSizes();\n\n    if (this.data.srcset) {\n      imageElement.srcset = this.data.srcset;\n    }\n\n    imageElement.src = (_this$data$src = this.data.src) !== null && _this$data$src !== void 0 ? _this$data$src : '';\n    imageElement.alt = (_this$data$alt = this.data.alt) !== null && _this$data$alt !== void 0 ? _this$data$alt : '';\n    this.state = LOAD_STATE.LOADING;\n\n    if (imageElement.complete) {\n      this.onLoaded();\n    } else {\n      imageElement.onload = () => {\n        this.onLoaded();\n      };\n\n      imageElement.onerror = () => {\n        this.onError();\n      };\n    }\n  }\n  /**\n   * Assign slide to content\n   *\n   * @param {Slide} slide\n   */\n\n\n  setSlide(slide) {\n    this.slide = slide;\n    this.hasSlide = true;\n    this.instance = slide.pswp; // todo: do we need to unset slide?\n  }\n  /**\n   * Content load success handler\n   */\n\n\n  onLoaded() {\n    this.state = LOAD_STATE.LOADED;\n\n    if (this.slide && this.element) {\n      this.instance.dispatch('loadComplete', {\n        slide: this.slide,\n        content: this\n      }); // if content is reloaded\n\n      if (this.slide.isActive && this.slide.heavyAppended && !this.element.parentNode) {\n        this.append();\n        this.slide.updateContentSize(true);\n      }\n\n      if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {\n        this.removePlaceholder();\n      }\n    }\n  }\n  /**\n   * Content load error handler\n   */\n\n\n  onError() {\n    this.state = LOAD_STATE.ERROR;\n\n    if (this.slide) {\n      this.displayError();\n      this.instance.dispatch('loadComplete', {\n        slide: this.slide,\n        isError: true,\n        content: this\n      });\n      this.instance.dispatch('loadError', {\n        slide: this.slide,\n        content: this\n      });\n    }\n  }\n  /**\n   * @returns {Boolean} If the content is currently loading\n   */\n\n\n  isLoading() {\n    return this.instance.applyFilters('isContentLoading', this.state === LOAD_STATE.LOADING, this);\n  }\n  /**\n   * @returns {Boolean} If the content is in error state\n   */\n\n\n  isError() {\n    return this.state === LOAD_STATE.ERROR;\n  }\n  /**\n   * @returns {boolean} If the content is image\n   */\n\n\n  isImageContent() {\n    return this.type === 'image';\n  }\n  /**\n   * Update content size\n   *\n   * @param {Number} width\n   * @param {Number} height\n   */\n\n\n  setDisplayedSize(width, height) {\n    if (!this.element) {\n      return;\n    }\n\n    if (this.placeholder) {\n      this.placeholder.setDisplayedSize(width, height);\n    }\n\n    if (this.instance.dispatch('contentResize', {\n      content: this,\n      width,\n      height\n    }).defaultPrevented) {\n      return;\n    }\n\n    setWidthHeight(this.element, width, height);\n\n    if (this.isImageContent() && !this.isError()) {\n      const isInitialSizeUpdate = !this.displayedImageWidth && width;\n      this.displayedImageWidth = width;\n      this.displayedImageHeight = height;\n\n      if (isInitialSizeUpdate) {\n        this.loadImage(false);\n      } else {\n        this.updateSrcsetSizes();\n      }\n\n      if (this.slide) {\n        this.instance.dispatch('imageSizeChange', {\n          slide: this.slide,\n          width,\n          height,\n          content: this\n        });\n      }\n    }\n  }\n  /**\n   * @returns {boolean} If the content can be zoomed\n   */\n\n\n  isZoomable() {\n    return this.instance.applyFilters('isContentZoomable', this.isImageContent() && this.state !== LOAD_STATE.ERROR, this);\n  }\n  /**\n   * Update image srcset sizes attribute based on width and height\n   */\n\n\n  updateSrcsetSizes() {\n    // Handle srcset sizes attribute.\n    //\n    // Never lower quality, if it was increased previously.\n    // Chrome does this automatically, Firefox and Safari do not,\n    // so we store largest used size in dataset.\n    if (!this.isImageContent() || !this.element || !this.data.srcset) {\n      return;\n    }\n\n    const image =\n    /** @type HTMLImageElement */\n    this.element;\n    const sizesWidth = this.instance.applyFilters('srcsetSizesWidth', this.displayedImageWidth, this);\n\n    if (!image.dataset.largestUsedSize || sizesWidth > parseInt(image.dataset.largestUsedSize, 10)) {\n      image.sizes = sizesWidth + 'px';\n      image.dataset.largestUsedSize = String(sizesWidth);\n    }\n  }\n  /**\n   * @returns {boolean} If content should use a placeholder (from msrc by default)\n   */\n\n\n  usePlaceholder() {\n    return this.instance.applyFilters('useContentPlaceholder', this.isImageContent(), this);\n  }\n  /**\n   * Preload content with lazy-loading param\n   */\n\n\n  lazyLoad() {\n    if (this.instance.dispatch('contentLazyLoad', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    this.load(true);\n  }\n  /**\n   * @returns {boolean} If placeholder should be kept after content is loaded\n   */\n\n\n  keepPlaceholder() {\n    return this.instance.applyFilters('isKeepingPlaceholder', this.isLoading(), this);\n  }\n  /**\n   * Destroy the content\n   */\n\n\n  destroy() {\n    this.hasSlide = false;\n    this.slide = undefined;\n\n    if (this.instance.dispatch('contentDestroy', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    this.remove();\n\n    if (this.placeholder) {\n      this.placeholder.destroy();\n      this.placeholder = undefined;\n    }\n\n    if (this.isImageContent() && this.element) {\n      this.element.onload = null;\n      this.element.onerror = null;\n      this.element = undefined;\n    }\n  }\n  /**\n   * Display error message\n   */\n\n\n  displayError() {\n    if (this.slide) {\n      var _this$instance$option, _this$instance$option2;\n\n      let errorMsgEl = createElement('pswp__error-msg', 'div');\n      errorMsgEl.innerText = (_this$instance$option = (_this$instance$option2 = this.instance.options) === null || _this$instance$option2 === void 0 ? void 0 : _this$instance$option2.errorMsg) !== null && _this$instance$option !== void 0 ? _this$instance$option : '';\n      errorMsgEl =\n      /** @type {HTMLDivElement} */\n      this.instance.applyFilters('contentErrorElement', errorMsgEl, this);\n      this.element = createElement('pswp__content pswp__error-msg-container', 'div');\n      this.element.appendChild(errorMsgEl);\n      this.slide.container.innerText = '';\n      this.slide.container.appendChild(this.element);\n      this.slide.updateContentSize(true);\n      this.removePlaceholder();\n    }\n  }\n  /**\n   * Append the content\n   */\n\n\n  append() {\n    if (this.isAttached || !this.element) {\n      return;\n    }\n\n    this.isAttached = true;\n\n    if (this.state === LOAD_STATE.ERROR) {\n      this.displayError();\n      return;\n    }\n\n    if (this.instance.dispatch('contentAppend', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    const supportsDecode = ('decode' in this.element);\n\n    if (this.isImageContent()) {\n      // Use decode() on nearby slides\n      //\n      // Nearby slide images are in DOM and not hidden via display:none.\n      // However, they are placed offscreen (to the left and right side).\n      //\n      // Some browsers do not composite the image until it's actually visible,\n      // using decode() helps.\n      //\n      // You might ask \"why dont you just decode() and then append all images\",\n      // that's because I want to show image before it's fully loaded,\n      // as browser can render parts of image while it is loading.\n      // We do not do this in Safari due to partial loading bug.\n      if (supportsDecode && this.slide && (!this.slide.isActive || isSafari())) {\n        this.isDecoding = true; // purposefully using finally instead of then,\n        // as if srcset sizes changes dynamically - it may cause decode error\n\n        /** @type {HTMLImageElement} */\n\n        this.element.decode().catch(() => {}).finally(() => {\n          this.isDecoding = false;\n          this.appendImage();\n        });\n      } else {\n        this.appendImage();\n      }\n    } else if (this.slide && !this.element.parentNode) {\n      this.slide.container.appendChild(this.element);\n    }\n  }\n  /**\n   * Activate the slide,\n   * active slide is generally the current one,\n   * meaning the user can see it.\n   */\n\n\n  activate() {\n    if (this.instance.dispatch('contentActivate', {\n      content: this\n    }).defaultPrevented || !this.slide) {\n      return;\n    }\n\n    if (this.isImageContent() && this.isDecoding && !isSafari()) {\n      // add image to slide when it becomes active,\n      // even if it's not finished decoding\n      this.appendImage();\n    } else if (this.isError()) {\n      this.load(false, true); // try to reload\n    }\n\n    if (this.slide.holderElement) {\n      this.slide.holderElement.setAttribute('aria-hidden', 'false');\n    }\n  }\n  /**\n   * Deactivate the content\n   */\n\n\n  deactivate() {\n    this.instance.dispatch('contentDeactivate', {\n      content: this\n    });\n\n    if (this.slide && this.slide.holderElement) {\n      this.slide.holderElement.setAttribute('aria-hidden', 'true');\n    }\n  }\n  /**\n   * Remove the content from DOM\n   */\n\n\n  remove() {\n    this.isAttached = false;\n\n    if (this.instance.dispatch('contentRemove', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (this.element && this.element.parentNode) {\n      this.element.remove();\n    }\n\n    if (this.placeholder && this.placeholder.element) {\n      this.placeholder.element.remove();\n    }\n  }\n  /**\n   * Append the image content to slide container\n   */\n\n\n  appendImage() {\n    if (!this.isAttached) {\n      return;\n    }\n\n    if (this.instance.dispatch('contentAppendImage', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    } // ensure that element exists and is not already appended\n\n\n    if (this.slide && this.element && !this.element.parentNode) {\n      this.slide.container.appendChild(this.element);\n    }\n\n    if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {\n      this.removePlaceholder();\n    }\n  }\n\n}\n\n/** @typedef {import('./content.js').default} Content */\n\n/** @typedef {import('./slide.js').default} Slide */\n\n/** @typedef {import('./slide.js').SlideData} SlideData */\n\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\nconst MIN_SLIDES_TO_CACHE = 5;\n/**\n * Lazy-load an image\n * This function is used both by Lightbox and PhotoSwipe core,\n * thus it can be called before dialog is opened.\n *\n * @param {SlideData} itemData Data about the slide\n * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance\n * @param {number} index\n * @returns {Content} Image that is being decoded or false.\n */\n\nfunction lazyLoadData(itemData, instance, index) {\n  const content = instance.createContentFromData(itemData, index);\n  /** @type {ZoomLevel | undefined} */\n\n  let zoomLevel;\n  const {\n    options\n  } = instance; // We need to know dimensions of the image to preload it,\n  // as it might use srcset, and we need to define sizes\n\n  if (options) {\n    zoomLevel = new ZoomLevel(options, itemData, -1);\n    let viewportSize;\n\n    if (instance.pswp) {\n      viewportSize = instance.pswp.viewportSize;\n    } else {\n      viewportSize = getViewportSize(options, instance);\n    }\n\n    const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index);\n    zoomLevel.update(content.width, content.height, panAreaSize);\n  }\n\n  content.lazyLoad();\n\n  if (zoomLevel) {\n    content.setDisplayedSize(Math.ceil(content.width * zoomLevel.initial), Math.ceil(content.height * zoomLevel.initial));\n  }\n\n  return content;\n}\n/**\n * Lazy-loads specific slide.\n * This function is used both by Lightbox and PhotoSwipe core,\n * thus it can be called before dialog is opened.\n *\n * By default, it loads image based on viewport size and initial zoom level.\n *\n * @param {number} index Slide index\n * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox eventable instance\n * @returns {Content | undefined}\n */\n\nfunction lazyLoadSlide(index, instance) {\n  const itemData = instance.getItemData(index);\n\n  if (instance.dispatch('lazyLoadSlide', {\n    index,\n    itemData\n  }).defaultPrevented) {\n    return;\n  }\n\n  return lazyLoadData(itemData, instance, index);\n}\n\nclass ContentLoader {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp; // Total amount of cached images\n\n    this.limit = Math.max(pswp.options.preload[0] + pswp.options.preload[1] + 1, MIN_SLIDES_TO_CACHE);\n    /** @type {Content[]} */\n\n    this._cachedItems = [];\n  }\n  /**\n   * Lazy load nearby slides based on `preload` option.\n   *\n   * @param {number} [diff] Difference between slide indexes that was changed recently, or 0.\n   */\n\n\n  updateLazy(diff) {\n    const {\n      pswp\n    } = this;\n\n    if (pswp.dispatch('lazyLoad').defaultPrevented) {\n      return;\n    }\n\n    const {\n      preload\n    } = pswp.options;\n    const isForward = diff === undefined ? true : diff >= 0;\n    let i; // preload[1] - num items to preload in forward direction\n\n    for (i = 0; i <= preload[1]; i++) {\n      this.loadSlideByIndex(pswp.currIndex + (isForward ? i : -i));\n    } // preload[0] - num items to preload in backward direction\n\n\n    for (i = 1; i <= preload[0]; i++) {\n      this.loadSlideByIndex(pswp.currIndex + (isForward ? -i : i));\n    }\n  }\n  /**\n   * @param {number} initialIndex\n   */\n\n\n  loadSlideByIndex(initialIndex) {\n    const index = this.pswp.getLoopedIndex(initialIndex); // try to get cached content\n\n    let content = this.getContentByIndex(index);\n\n    if (!content) {\n      // no cached content, so try to load from scratch:\n      content = lazyLoadSlide(index, this.pswp); // if content can be loaded, add it to cache:\n\n      if (content) {\n        this.addToCache(content);\n      }\n    }\n  }\n  /**\n   * @param {Slide} slide\n   * @returns {Content}\n   */\n\n\n  getContentBySlide(slide) {\n    let content = this.getContentByIndex(slide.index);\n\n    if (!content) {\n      // create content if not found in cache\n      content = this.pswp.createContentFromData(slide.data, slide.index);\n      this.addToCache(content);\n    } // assign slide to content\n\n\n    content.setSlide(slide);\n    return content;\n  }\n  /**\n   * @param {Content} content\n   */\n\n\n  addToCache(content) {\n    // move to the end of array\n    this.removeByIndex(content.index);\n\n    this._cachedItems.push(content);\n\n    if (this._cachedItems.length > this.limit) {\n      // Destroy the first content that's not attached\n      const indexToRemove = this._cachedItems.findIndex(item => {\n        return !item.isAttached && !item.hasSlide;\n      });\n\n      if (indexToRemove !== -1) {\n        const removedItem = this._cachedItems.splice(indexToRemove, 1)[0];\n\n        removedItem.destroy();\n      }\n    }\n  }\n  /**\n   * Removes an image from cache, does not destroy() it, just removes.\n   *\n   * @param {number} index\n   */\n\n\n  removeByIndex(index) {\n    const indexToRemove = this._cachedItems.findIndex(item => item.index === index);\n\n    if (indexToRemove !== -1) {\n      this._cachedItems.splice(indexToRemove, 1);\n    }\n  }\n  /**\n   * @param {number} index\n   * @returns {Content | undefined}\n   */\n\n\n  getContentByIndex(index) {\n    return this._cachedItems.find(content => content.index === index);\n  }\n\n  destroy() {\n    this._cachedItems.forEach(content => content.destroy());\n\n    this._cachedItems = [];\n  }\n\n}\n\n/** @typedef {import(\"../photoswipe.js\").default} PhotoSwipe */\n\n/** @typedef {import(\"../slide/slide.js\").SlideData} SlideData */\n\n/**\n * PhotoSwipe base class that can retrieve data about every slide.\n * Shared by PhotoSwipe Core and PhotoSwipe Lightbox\n */\n\nclass PhotoSwipeBase extends Eventable {\n  /**\n   * Get total number of slides\n   *\n   * @returns {number}\n   */\n  getNumItems() {\n    var _this$options;\n\n    let numItems = 0;\n    const dataSource = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.dataSource;\n\n    if (dataSource && 'length' in dataSource) {\n      // may be an array or just object with length property\n      numItems = dataSource.length;\n    } else if (dataSource && 'gallery' in dataSource) {\n      // query DOM elements\n      if (!dataSource.items) {\n        dataSource.items = this._getGalleryDOMElements(dataSource.gallery);\n      }\n\n      if (dataSource.items) {\n        numItems = dataSource.items.length;\n      }\n    } // legacy event, before filters were introduced\n\n\n    const event = this.dispatch('numItems', {\n      dataSource,\n      numItems\n    });\n    return this.applyFilters('numItems', event.numItems, dataSource);\n  }\n  /**\n   * @param {SlideData} slideData\n   * @param {number} index\n   * @returns {Content}\n   */\n\n\n  createContentFromData(slideData, index) {\n    return new Content(slideData, this, index);\n  }\n  /**\n   * Get item data by index.\n   *\n   * \"item data\" should contain normalized information that PhotoSwipe needs to generate a slide.\n   * For example, it may contain properties like\n   * `src`, `srcset`, `w`, `h`, which will be used to generate a slide with image.\n   *\n   * @param {number} index\n   * @returns {SlideData}\n   */\n\n\n  getItemData(index) {\n    var _this$options2;\n\n    const dataSource = (_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : _this$options2.dataSource;\n    /** @type {SlideData | HTMLElement} */\n\n    let dataSourceItem = {};\n\n    if (Array.isArray(dataSource)) {\n      // Datasource is an array of elements\n      dataSourceItem = dataSource[index];\n    } else if (dataSource && 'gallery' in dataSource) {\n      // dataSource has gallery property,\n      // thus it was created by Lightbox, based on\n      // gallery and children options\n      // query DOM elements\n      if (!dataSource.items) {\n        dataSource.items = this._getGalleryDOMElements(dataSource.gallery);\n      }\n\n      dataSourceItem = dataSource.items[index];\n    }\n\n    let itemData = dataSourceItem;\n\n    if (itemData instanceof Element) {\n      itemData = this._domElementToItemData(itemData);\n    } // Dispatching the itemData event,\n    // it's a legacy verion before filters were introduced\n\n\n    const event = this.dispatch('itemData', {\n      itemData: itemData || {},\n      index\n    });\n    return this.applyFilters('itemData', event.itemData, index);\n  }\n  /**\n   * Get array of gallery DOM elements,\n   * based on childSelector and gallery element.\n   *\n   * @param {HTMLElement} galleryElement\n   * @returns {HTMLElement[]}\n   */\n\n\n  _getGalleryDOMElements(galleryElement) {\n    var _this$options3, _this$options4;\n\n    if ((_this$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.children || (_this$options4 = this.options) !== null && _this$options4 !== void 0 && _this$options4.childSelector) {\n      return getElementsFromOption(this.options.children, this.options.childSelector, galleryElement) || [];\n    }\n\n    return [galleryElement];\n  }\n  /**\n   * Converts DOM element to item data object.\n   *\n   * @param {HTMLElement} element DOM element\n   * @returns {SlideData}\n   */\n\n\n  _domElementToItemData(element) {\n    /** @type {SlideData} */\n    const itemData = {\n      element\n    };\n    const linkEl =\n    /** @type {HTMLAnchorElement} */\n    element.tagName === 'A' ? element : element.querySelector('a');\n\n    if (linkEl) {\n      // src comes from data-pswp-src attribute,\n      // if it's empty link href is used\n      itemData.src = linkEl.dataset.pswpSrc || linkEl.href;\n\n      if (linkEl.dataset.pswpSrcset) {\n        itemData.srcset = linkEl.dataset.pswpSrcset;\n      }\n\n      itemData.width = linkEl.dataset.pswpWidth ? parseInt(linkEl.dataset.pswpWidth, 10) : 0;\n      itemData.height = linkEl.dataset.pswpHeight ? parseInt(linkEl.dataset.pswpHeight, 10) : 0; // support legacy w & h properties\n\n      itemData.w = itemData.width;\n      itemData.h = itemData.height;\n\n      if (linkEl.dataset.pswpType) {\n        itemData.type = linkEl.dataset.pswpType;\n      }\n\n      const thumbnailEl = element.querySelector('img');\n\n      if (thumbnailEl) {\n        var _thumbnailEl$getAttri;\n\n        // msrc is URL to placeholder image that's displayed before large image is loaded\n        // by default it's displayed only for the first slide\n        itemData.msrc = thumbnailEl.currentSrc || thumbnailEl.src;\n        itemData.alt = (_thumbnailEl$getAttri = thumbnailEl.getAttribute('alt')) !== null && _thumbnailEl$getAttri !== void 0 ? _thumbnailEl$getAttri : '';\n      }\n\n      if (linkEl.dataset.pswpCropped || linkEl.dataset.cropped) {\n        itemData.thumbCropped = true;\n      }\n    }\n\n    return this.applyFilters('domItemData', itemData, element, linkEl);\n  }\n  /**\n   * Lazy-load by slide data\n   *\n   * @param {SlideData} itemData Data about the slide\n   * @param {number} index\n   * @returns {Content} Image that is being decoded or false.\n   */\n\n\n  lazyLoadData(itemData, index) {\n    return lazyLoadData(itemData, this, index);\n  }\n\n}\n\n/** @typedef {import('./photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('./slide/get-thumb-bounds.js').Bounds} Bounds */\n\n/** @typedef {import('./util/animations.js').AnimationProps} AnimationProps */\n// some browsers do not paint\n// elements which opacity is set to 0,\n// since we need to pre-render elements for the animation -\n// we set it to the minimum amount\n\nconst MIN_OPACITY = 0.003;\n/**\n * Manages opening and closing transitions of the PhotoSwipe.\n *\n * It can perform zoom, fade or no transition.\n */\n\nclass Opener {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    this.isClosed = true;\n    this.isOpen = false;\n    this.isClosing = false;\n    this.isOpening = false;\n    /**\n     * @private\n     * @type {number | false | undefined}\n     */\n\n    this._duration = undefined;\n    /** @private */\n\n    this._useAnimation = false;\n    /** @private */\n\n    this._croppedZoom = false;\n    /** @private */\n\n    this._animateRootOpacity = false;\n    /** @private */\n\n    this._animateBgOpacity = false;\n    /**\n     * @private\n     * @type { HTMLDivElement | HTMLImageElement | null | undefined }\n     */\n\n    this._placeholder = undefined;\n    /**\n     * @private\n     * @type { HTMLDivElement | undefined }\n     */\n\n    this._opacityElement = undefined;\n    /**\n     * @private\n     * @type { HTMLDivElement | undefined }\n     */\n\n    this._cropContainer1 = undefined;\n    /**\n     * @private\n     * @type { HTMLElement | null | undefined }\n     */\n\n    this._cropContainer2 = undefined;\n    /**\n     * @private\n     * @type {Bounds | undefined}\n     */\n\n    this._thumbBounds = undefined;\n    this._prepareOpen = this._prepareOpen.bind(this); // Override initial zoom and pan position\n\n    pswp.on('firstZoomPan', this._prepareOpen);\n  }\n\n  open() {\n    this._prepareOpen();\n\n    this._start();\n  }\n\n  close() {\n    if (this.isClosed || this.isClosing || this.isOpening) {\n      // if we close during opening animation\n      // for now do nothing,\n      // browsers aren't good at changing the direction of the CSS transition\n      return;\n    }\n\n    const slide = this.pswp.currSlide;\n    this.isOpen = false;\n    this.isOpening = false;\n    this.isClosing = true;\n    this._duration = this.pswp.options.hideAnimationDuration;\n\n    if (slide && slide.currZoomLevel * slide.width >= this.pswp.options.maxWidthToAnimate) {\n      this._duration = 0;\n    }\n\n    this._applyStartProps();\n\n    setTimeout(() => {\n      this._start();\n    }, this._croppedZoom ? 30 : 0);\n  }\n  /** @private */\n\n\n  _prepareOpen() {\n    this.pswp.off('firstZoomPan', this._prepareOpen);\n\n    if (!this.isOpening) {\n      const slide = this.pswp.currSlide;\n      this.isOpening = true;\n      this.isClosing = false;\n      this._duration = this.pswp.options.showAnimationDuration;\n\n      if (slide && slide.zoomLevels.initial * slide.width >= this.pswp.options.maxWidthToAnimate) {\n        this._duration = 0;\n      }\n\n      this._applyStartProps();\n    }\n  }\n  /** @private */\n\n\n  _applyStartProps() {\n    const {\n      pswp\n    } = this;\n    const slide = this.pswp.currSlide;\n    const {\n      options\n    } = pswp;\n\n    if (options.showHideAnimationType === 'fade') {\n      options.showHideOpacity = true;\n      this._thumbBounds = undefined;\n    } else if (options.showHideAnimationType === 'none') {\n      options.showHideOpacity = false;\n      this._duration = 0;\n      this._thumbBounds = undefined;\n    } else if (this.isOpening && pswp._initialThumbBounds) {\n      // Use initial bounds if defined\n      this._thumbBounds = pswp._initialThumbBounds;\n    } else {\n      this._thumbBounds = this.pswp.getThumbBounds();\n    }\n\n    this._placeholder = slide === null || slide === void 0 ? void 0 : slide.getPlaceholderElement();\n    pswp.animations.stopAll(); // Discard animations when duration is less than 50ms\n\n    this._useAnimation = Boolean(this._duration && this._duration > 50);\n    this._animateZoom = Boolean(this._thumbBounds) && (slide === null || slide === void 0 ? void 0 : slide.content.usePlaceholder()) && (!this.isClosing || !pswp.mainScroll.isShifted());\n\n    if (!this._animateZoom) {\n      this._animateRootOpacity = true;\n\n      if (this.isOpening && slide) {\n        slide.zoomAndPanToInitial();\n        slide.applyCurrentZoomPan();\n      }\n    } else {\n      var _options$showHideOpac;\n\n      this._animateRootOpacity = (_options$showHideOpac = options.showHideOpacity) !== null && _options$showHideOpac !== void 0 ? _options$showHideOpac : false;\n    }\n\n    this._animateBgOpacity = !this._animateRootOpacity && this.pswp.options.bgOpacity > MIN_OPACITY;\n    this._opacityElement = this._animateRootOpacity ? pswp.element : pswp.bg;\n\n    if (!this._useAnimation) {\n      this._duration = 0;\n      this._animateZoom = false;\n      this._animateBgOpacity = false;\n      this._animateRootOpacity = true;\n\n      if (this.isOpening) {\n        if (pswp.element) {\n          pswp.element.style.opacity = String(MIN_OPACITY);\n        }\n\n        pswp.applyBgOpacity(1);\n      }\n\n      return;\n    }\n\n    if (this._animateZoom && this._thumbBounds && this._thumbBounds.innerRect) {\n      var _this$pswp$currSlide;\n\n      // Properties are used when animation from cropped thumbnail\n      this._croppedZoom = true;\n      this._cropContainer1 = this.pswp.container;\n      this._cropContainer2 = (_this$pswp$currSlide = this.pswp.currSlide) === null || _this$pswp$currSlide === void 0 ? void 0 : _this$pswp$currSlide.holderElement;\n\n      if (pswp.container) {\n        pswp.container.style.overflow = 'hidden';\n        pswp.container.style.width = pswp.viewportSize.x + 'px';\n      }\n    } else {\n      this._croppedZoom = false;\n    }\n\n    if (this.isOpening) {\n      // Apply styles before opening transition\n      if (this._animateRootOpacity) {\n        if (pswp.element) {\n          pswp.element.style.opacity = String(MIN_OPACITY);\n        }\n\n        pswp.applyBgOpacity(1);\n      } else {\n        if (this._animateBgOpacity && pswp.bg) {\n          pswp.bg.style.opacity = String(MIN_OPACITY);\n        }\n\n        if (pswp.element) {\n          pswp.element.style.opacity = '1';\n        }\n      }\n\n      if (this._animateZoom) {\n        this._setClosedStateZoomPan();\n\n        if (this._placeholder) {\n          // tell browser that we plan to animate the placeholder\n          this._placeholder.style.willChange = 'transform'; // hide placeholder to allow hiding of\n          // elements that overlap it (such as icons over the thumbnail)\n\n          this._placeholder.style.opacity = String(MIN_OPACITY);\n        }\n      }\n    } else if (this.isClosing) {\n      // hide nearby slides to make sure that\n      // they are not painted during the transition\n      if (pswp.mainScroll.itemHolders[0]) {\n        pswp.mainScroll.itemHolders[0].el.style.display = 'none';\n      }\n\n      if (pswp.mainScroll.itemHolders[2]) {\n        pswp.mainScroll.itemHolders[2].el.style.display = 'none';\n      }\n\n      if (this._croppedZoom) {\n        if (pswp.mainScroll.x !== 0) {\n          // shift the main scroller to zero position\n          pswp.mainScroll.resetPosition();\n          pswp.mainScroll.resize();\n        }\n      }\n    }\n  }\n  /** @private */\n\n\n  _start() {\n    if (this.isOpening && this._useAnimation && this._placeholder && this._placeholder.tagName === 'IMG') {\n      // To ensure smooth animation\n      // we wait till the current slide image placeholder is decoded,\n      // but no longer than 250ms,\n      // and no shorter than 50ms\n      // (just using requestanimationframe is not enough in Firefox,\n      // for some reason)\n      new Promise(resolve => {\n        let decoded = false;\n        let isDelaying = true;\n        decodeImage(\n        /** @type {HTMLImageElement} */\n        this._placeholder).finally(() => {\n          decoded = true;\n\n          if (!isDelaying) {\n            resolve(true);\n          }\n        });\n        setTimeout(() => {\n          isDelaying = false;\n\n          if (decoded) {\n            resolve(true);\n          }\n        }, 50);\n        setTimeout(resolve, 250);\n      }).finally(() => this._initiate());\n    } else {\n      this._initiate();\n    }\n  }\n  /** @private */\n\n\n  _initiate() {\n    var _this$pswp$element, _this$pswp$element2;\n\n    (_this$pswp$element = this.pswp.element) === null || _this$pswp$element === void 0 || _this$pswp$element.style.setProperty('--pswp-transition-duration', this._duration + 'ms');\n    this.pswp.dispatch(this.isOpening ? 'openingAnimationStart' : 'closingAnimationStart'); // legacy event\n\n    this.pswp.dispatch(\n    /** @type {'initialZoomIn' | 'initialZoomOut'} */\n    'initialZoom' + (this.isOpening ? 'In' : 'Out'));\n    (_this$pswp$element2 = this.pswp.element) === null || _this$pswp$element2 === void 0 || _this$pswp$element2.classList.toggle('pswp--ui-visible', this.isOpening);\n\n    if (this.isOpening) {\n      if (this._placeholder) {\n        // unhide the placeholder\n        this._placeholder.style.opacity = '1';\n      }\n\n      this._animateToOpenState();\n    } else if (this.isClosing) {\n      this._animateToClosedState();\n    }\n\n    if (!this._useAnimation) {\n      this._onAnimationComplete();\n    }\n  }\n  /** @private */\n\n\n  _onAnimationComplete() {\n    const {\n      pswp\n    } = this;\n    this.isOpen = this.isOpening;\n    this.isClosed = this.isClosing;\n    this.isOpening = false;\n    this.isClosing = false;\n    pswp.dispatch(this.isOpen ? 'openingAnimationEnd' : 'closingAnimationEnd'); // legacy event\n\n    pswp.dispatch(\n    /** @type {'initialZoomInEnd' | 'initialZoomOutEnd'} */\n    'initialZoom' + (this.isOpen ? 'InEnd' : 'OutEnd'));\n\n    if (this.isClosed) {\n      pswp.destroy();\n    } else if (this.isOpen) {\n      var _pswp$currSlide;\n\n      if (this._animateZoom && pswp.container) {\n        pswp.container.style.overflow = 'visible';\n        pswp.container.style.width = '100%';\n      }\n\n      (_pswp$currSlide = pswp.currSlide) === null || _pswp$currSlide === void 0 || _pswp$currSlide.applyCurrentZoomPan();\n    }\n  }\n  /** @private */\n\n\n  _animateToOpenState() {\n    const {\n      pswp\n    } = this;\n\n    if (this._animateZoom) {\n      if (this._croppedZoom && this._cropContainer1 && this._cropContainer2) {\n        this._animateTo(this._cropContainer1, 'transform', 'translate3d(0,0,0)');\n\n        this._animateTo(this._cropContainer2, 'transform', 'none');\n      }\n\n      if (pswp.currSlide) {\n        pswp.currSlide.zoomAndPanToInitial();\n\n        this._animateTo(pswp.currSlide.container, 'transform', pswp.currSlide.getCurrentTransform());\n      }\n    }\n\n    if (this._animateBgOpacity && pswp.bg) {\n      this._animateTo(pswp.bg, 'opacity', String(pswp.options.bgOpacity));\n    }\n\n    if (this._animateRootOpacity && pswp.element) {\n      this._animateTo(pswp.element, 'opacity', '1');\n    }\n  }\n  /** @private */\n\n\n  _animateToClosedState() {\n    const {\n      pswp\n    } = this;\n\n    if (this._animateZoom) {\n      this._setClosedStateZoomPan(true);\n    } // do not animate opacity if it's already at 0\n\n\n    if (this._animateBgOpacity && pswp.bgOpacity > 0.01 && pswp.bg) {\n      this._animateTo(pswp.bg, 'opacity', '0');\n    }\n\n    if (this._animateRootOpacity && pswp.element) {\n      this._animateTo(pswp.element, 'opacity', '0');\n    }\n  }\n  /**\n   * @private\n   * @param {boolean} [animate]\n   */\n\n\n  _setClosedStateZoomPan(animate) {\n    if (!this._thumbBounds) return;\n    const {\n      pswp\n    } = this;\n    const {\n      innerRect\n    } = this._thumbBounds;\n    const {\n      currSlide,\n      viewportSize\n    } = pswp;\n\n    if (this._croppedZoom && innerRect && this._cropContainer1 && this._cropContainer2) {\n      const containerOnePanX = -viewportSize.x + (this._thumbBounds.x - innerRect.x) + innerRect.w;\n      const containerOnePanY = -viewportSize.y + (this._thumbBounds.y - innerRect.y) + innerRect.h;\n      const containerTwoPanX = viewportSize.x - innerRect.w;\n      const containerTwoPanY = viewportSize.y - innerRect.h;\n\n      if (animate) {\n        this._animateTo(this._cropContainer1, 'transform', toTransformString(containerOnePanX, containerOnePanY));\n\n        this._animateTo(this._cropContainer2, 'transform', toTransformString(containerTwoPanX, containerTwoPanY));\n      } else {\n        setTransform(this._cropContainer1, containerOnePanX, containerOnePanY);\n        setTransform(this._cropContainer2, containerTwoPanX, containerTwoPanY);\n      }\n    }\n\n    if (currSlide) {\n      equalizePoints(currSlide.pan, innerRect || this._thumbBounds);\n      currSlide.currZoomLevel = this._thumbBounds.w / currSlide.width;\n\n      if (animate) {\n        this._animateTo(currSlide.container, 'transform', currSlide.getCurrentTransform());\n      } else {\n        currSlide.applyCurrentZoomPan();\n      }\n    }\n  }\n  /**\n   * @private\n   * @param {HTMLElement} target\n   * @param {'transform' | 'opacity'} prop\n   * @param {string} propValue\n   */\n\n\n  _animateTo(target, prop, propValue) {\n    if (!this._duration) {\n      target.style[prop] = propValue;\n      return;\n    }\n\n    const {\n      animations\n    } = this.pswp;\n    /** @type {AnimationProps} */\n\n    const animProps = {\n      duration: this._duration,\n      easing: this.pswp.options.easing,\n      onComplete: () => {\n        if (!animations.activeAnimations.length) {\n          this._onAnimationComplete();\n        }\n      },\n      target\n    };\n    animProps[prop] = propValue;\n    animations.startTransition(animProps);\n  }\n\n}\n\n/**\n * @template T\n * @typedef {import('./types.js').Type<T>} Type<T>\n */\n\n/** @typedef {import('./slide/slide.js').SlideData} SlideData */\n\n/** @typedef {import('./slide/zoom-level.js').ZoomLevelOption} ZoomLevelOption */\n\n/** @typedef {import('./ui/ui-element.js').UIElementData} UIElementData */\n\n/** @typedef {import('./main-scroll.js').ItemHolder} ItemHolder */\n\n/** @typedef {import('./core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */\n\n/** @typedef {import('./core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */\n\n/** @typedef {import('./slide/get-thumb-bounds').Bounds} Bounds */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {import('./core/eventable.js').EventCallback<T>} EventCallback<T>\n */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {import('./core/eventable.js').AugmentedEvent<T>} AugmentedEvent<T>\n */\n\n/** @typedef {{ x: number; y: number; id?: string | number }} Point */\n\n/** @typedef {{ top: number; bottom: number; left: number; right: number }} Padding */\n\n/** @typedef {SlideData[]} DataSourceArray */\n\n/** @typedef {{ gallery: HTMLElement; items?: HTMLElement[] }} DataSourceObject */\n\n/** @typedef {DataSourceArray | DataSourceObject} DataSource */\n\n/** @typedef {(point: Point, originalEvent: PointerEvent) => void} ActionFn */\n\n/** @typedef {'close' | 'next' | 'zoom' | 'zoom-or-close' | 'toggle-controls'} ActionType */\n\n/** @typedef {Type<PhotoSwipe> | { default: Type<PhotoSwipe> }} PhotoSwipeModule */\n\n/** @typedef {PhotoSwipeModule | Promise<PhotoSwipeModule> | (() => Promise<PhotoSwipeModule>)} PhotoSwipeModuleOption */\n\n/**\n * @typedef {string | NodeListOf<HTMLElement> | HTMLElement[] | HTMLElement} ElementProvider\n */\n\n/** @typedef {Partial<PreparedPhotoSwipeOptions>} PhotoSwipeOptions https://photoswipe.com/options/ */\n\n/**\n * @typedef {Object} PreparedPhotoSwipeOptions\n *\n * @prop {DataSource} [dataSource]\n * Pass an array of any items via dataSource option. Its length will determine amount of slides\n * (which may be modified further from numItems event).\n *\n * Each item should contain data that you need to generate slide\n * (for image slide it would be src (image URL), width (image width), height, srcset, alt).\n *\n * If these properties are not present in your initial array, you may \"pre-parse\" each item from itemData filter.\n *\n * @prop {number} bgOpacity\n * Background backdrop opacity, always define it via this option and not via CSS rgba color.\n *\n * @prop {number} spacing\n * Spacing between slides. Defined as ratio relative to the viewport width (0.1 = 10% of viewport).\n *\n * @prop {boolean} allowPanToNext\n * Allow swipe navigation to the next slide when the current slide is zoomed. Does not apply to mouse events.\n *\n * @prop {boolean} loop\n * If set to true you'll be able to swipe from the last to the first image.\n * Option is always false when there are less than 3 slides.\n *\n * @prop {boolean} [wheelToZoom]\n * By default PhotoSwipe zooms image with ctrl-wheel, if you enable this option - image will zoom just via wheel.\n *\n * @prop {boolean} pinchToClose\n * Pinch touch gesture to close the gallery.\n *\n * @prop {boolean} closeOnVerticalDrag\n * Vertical drag gesture to close the PhotoSwipe.\n *\n * @prop {Padding} [padding]\n * Slide area padding (in pixels).\n *\n * @prop {(viewportSize: Point, itemData: SlideData, index: number) => Padding} [paddingFn]\n * The option is checked frequently, so make sure it's performant. Overrides padding option if defined. For example:\n *\n * @prop {number | false} hideAnimationDuration\n * Transition duration in milliseconds, can be 0.\n *\n * @prop {number | false} showAnimationDuration\n * Transition duration in milliseconds, can be 0.\n *\n * @prop {number | false} zoomAnimationDuration\n * Transition duration in milliseconds, can be 0.\n *\n * @prop {string} easing\n * String, 'cubic-bezier(.4,0,.22,1)'. CSS easing function for open/close/zoom transitions.\n *\n * @prop {boolean} escKey\n * Esc key to close.\n *\n * @prop {boolean} arrowKeys\n * Left/right arrow keys for navigation.\n *\n * @prop {boolean} trapFocus\n * Trap focus within PhotoSwipe element while it's open.\n *\n * @prop {boolean} returnFocus\n * Restore focus the last active element after PhotoSwipe is closed.\n *\n * @prop {boolean} clickToCloseNonZoomable\n * If image is not zoomable (for example, smaller than viewport) it can be closed by clicking on it.\n *\n * @prop {ActionType | ActionFn | false} imageClickAction\n * Refer to click and tap actions page.\n *\n * @prop {ActionType | ActionFn | false} bgClickAction\n * Refer to click and tap actions page.\n *\n * @prop {ActionType | ActionFn | false} tapAction\n * Refer to click and tap actions page.\n *\n * @prop {ActionType | ActionFn | false} doubleTapAction\n * Refer to click and tap actions page.\n *\n * @prop {number} preloaderDelay\n * Delay before the loading indicator will be displayed,\n * if image is loaded during it - the indicator will not be displayed at all. Can be zero.\n *\n * @prop {string} indexIndicatorSep\n * Used for slide count indicator (\"1 of 10 \").\n *\n * @prop {(options: PhotoSwipeOptions, pswp: PhotoSwipeBase) => Point} [getViewportSizeFn]\n * A function that should return slide viewport width and height, in format {x: 100, y: 100}.\n *\n * @prop {string} errorMsg\n * Message to display when the image wasn't able to load. If you need to display HTML - use contentErrorElement filter.\n *\n * @prop {[number, number]} preload\n * Lazy loading of nearby slides based on direction of movement. Should be an array with two integers,\n * first one - number of items to preload before the current image, second one - after the current image.\n * Two nearby images are always loaded.\n *\n * @prop {string} [mainClass]\n * Class that will be added to the root element of PhotoSwipe, may contain multiple separated by space.\n * Example on Styling page.\n *\n * @prop {HTMLElement} [appendToEl]\n * Element to which PhotoSwipe dialog will be appended when it opens.\n *\n * @prop {number} maxWidthToAnimate\n * Maximum width of image to animate, if initial rendered image width\n * is larger than this value - the opening/closing transition will be automatically disabled.\n *\n * @prop {string} [closeTitle]\n * Translating\n *\n * @prop {string} [zoomTitle]\n * Translating\n *\n * @prop {string} [arrowPrevTitle]\n * Translating\n *\n * @prop {string} [arrowNextTitle]\n * Translating\n *\n * @prop {'zoom' | 'fade' | 'none'} [showHideAnimationType]\n * To adjust opening or closing transition type use lightbox option `showHideAnimationType` (`String`).\n * It supports three values - `zoom` (default), `fade` (default if there is no thumbnail) and `none`.\n *\n * Animations are automatically disabled if user `(prefers-reduced-motion: reduce)`.\n *\n * @prop {number} index\n * Defines start slide index.\n *\n * @prop {(e: MouseEvent) => number} [getClickedIndexFn]\n *\n * @prop {boolean} [arrowPrev]\n * @prop {boolean} [arrowNext]\n * @prop {boolean} [zoom]\n * @prop {boolean} [close]\n * @prop {boolean} [counter]\n *\n * @prop {string} [arrowPrevSVG]\n * @prop {string} [arrowNextSVG]\n * @prop {string} [zoomSVG]\n * @prop {string} [closeSVG]\n * @prop {string} [counterSVG]\n *\n * @prop {string} [arrowPrevTitle]\n * @prop {string} [arrowNextTitle]\n * @prop {string} [zoomTitle]\n * @prop {string} [closeTitle]\n * @prop {string} [counterTitle]\n *\n * @prop {ZoomLevelOption} [initialZoomLevel]\n * @prop {ZoomLevelOption} [secondaryZoomLevel]\n * @prop {ZoomLevelOption} [maxZoomLevel]\n *\n * @prop {boolean} [mouseMovePan]\n * @prop {Point | null} [initialPointerPos]\n * @prop {boolean} [showHideOpacity]\n *\n * @prop {PhotoSwipeModuleOption} [pswpModule]\n * @prop {() => Promise<any>} [openPromise]\n * @prop {boolean} [preloadFirstSlide]\n * @prop {ElementProvider} [gallery]\n * @prop {string} [gallerySelector]\n * @prop {ElementProvider} [children]\n * @prop {string} [childSelector]\n * @prop {string | false} [thumbSelector]\n */\n\n/** @type {PreparedPhotoSwipeOptions} */\n\nconst defaultOptions = {\n  allowPanToNext: true,\n  spacing: 0.1,\n  loop: true,\n  pinchToClose: true,\n  closeOnVerticalDrag: true,\n  hideAnimationDuration: 333,\n  showAnimationDuration: 333,\n  zoomAnimationDuration: 333,\n  escKey: true,\n  arrowKeys: true,\n  trapFocus: true,\n  returnFocus: true,\n  maxWidthToAnimate: 4000,\n  clickToCloseNonZoomable: true,\n  imageClickAction: 'zoom-or-close',\n  bgClickAction: 'close',\n  tapAction: 'toggle-controls',\n  doubleTapAction: 'zoom',\n  indexIndicatorSep: ' / ',\n  preloaderDelay: 2000,\n  bgOpacity: 0.8,\n  index: 0,\n  errorMsg: 'The image cannot be loaded',\n  preload: [1, 2],\n  easing: 'cubic-bezier(.4,0,.22,1)'\n};\n/**\n * PhotoSwipe Core\n */\n\nclass PhotoSwipe extends PhotoSwipeBase {\n  /**\n   * @param {PhotoSwipeOptions} [options]\n   */\n  constructor(options) {\n    super();\n    this.options = this._prepareOptions(options || {});\n    /**\n     * offset of viewport relative to document\n     *\n     * @type {Point}\n     */\n\n    this.offset = {\n      x: 0,\n      y: 0\n    };\n    /**\n     * @type {Point}\n     * @private\n     */\n\n    this._prevViewportSize = {\n      x: 0,\n      y: 0\n    };\n    /**\n     * Size of scrollable PhotoSwipe viewport\n     *\n     * @type {Point}\n     */\n\n    this.viewportSize = {\n      x: 0,\n      y: 0\n    };\n    /**\n     * background (backdrop) opacity\n     */\n\n    this.bgOpacity = 1;\n    this.currIndex = 0;\n    this.potentialIndex = 0;\n    this.isOpen = false;\n    this.isDestroying = false;\n    this.hasMouse = false;\n    /**\n     * @private\n     * @type {SlideData}\n     */\n\n    this._initialItemData = {};\n    /** @type {Bounds | undefined} */\n\n    this._initialThumbBounds = undefined;\n    /** @type {HTMLDivElement | undefined} */\n\n    this.topBar = undefined;\n    /** @type {HTMLDivElement | undefined} */\n\n    this.element = undefined;\n    /** @type {HTMLDivElement | undefined} */\n\n    this.template = undefined;\n    /** @type {HTMLDivElement | undefined} */\n\n    this.container = undefined;\n    /** @type {HTMLElement | undefined} */\n\n    this.scrollWrap = undefined;\n    /** @type {Slide | undefined} */\n\n    this.currSlide = undefined;\n    this.events = new DOMEvents();\n    this.animations = new Animations();\n    this.mainScroll = new MainScroll(this);\n    this.gestures = new Gestures(this);\n    this.opener = new Opener(this);\n    this.keyboard = new Keyboard(this);\n    this.contentLoader = new ContentLoader(this);\n  }\n  /** @returns {boolean} */\n\n\n  init() {\n    if (this.isOpen || this.isDestroying) {\n      return false;\n    }\n\n    this.isOpen = true;\n    this.dispatch('init'); // legacy\n\n    this.dispatch('beforeOpen');\n\n    this._createMainStructure(); // add classes to the root element of PhotoSwipe\n\n\n    let rootClasses = 'pswp--open';\n\n    if (this.gestures.supportsTouch) {\n      rootClasses += ' pswp--touch';\n    }\n\n    if (this.options.mainClass) {\n      rootClasses += ' ' + this.options.mainClass;\n    }\n\n    if (this.element) {\n      this.element.className += ' ' + rootClasses;\n    }\n\n    this.currIndex = this.options.index || 0;\n    this.potentialIndex = this.currIndex;\n    this.dispatch('firstUpdate'); // starting index can be modified here\n    // initialize scroll wheel handler to block the scroll\n\n    this.scrollWheel = new ScrollWheel(this); // sanitize index\n\n    if (Number.isNaN(this.currIndex) || this.currIndex < 0 || this.currIndex >= this.getNumItems()) {\n      this.currIndex = 0;\n    }\n\n    if (!this.gestures.supportsTouch) {\n      // enable mouse features if no touch support detected\n      this.mouseDetected();\n    } // causes forced synchronous layout\n\n\n    this.updateSize();\n    this.offset.y = window.pageYOffset;\n    this._initialItemData = this.getItemData(this.currIndex);\n    this.dispatch('gettingData', {\n      index: this.currIndex,\n      data: this._initialItemData,\n      slide: undefined\n    }); // *Layout* - calculate size and position of elements here\n\n    this._initialThumbBounds = this.getThumbBounds();\n    this.dispatch('initialLayout');\n    this.on('openingAnimationEnd', () => {\n      const {\n        itemHolders\n      } = this.mainScroll; // Add content to the previous and next slide\n\n      if (itemHolders[0]) {\n        itemHolders[0].el.style.display = 'block';\n        this.setContent(itemHolders[0], this.currIndex - 1);\n      }\n\n      if (itemHolders[2]) {\n        itemHolders[2].el.style.display = 'block';\n        this.setContent(itemHolders[2], this.currIndex + 1);\n      }\n\n      this.appendHeavy();\n      this.contentLoader.updateLazy();\n      this.events.add(window, 'resize', this._handlePageResize.bind(this));\n      this.events.add(window, 'scroll', this._updatePageScrollOffset.bind(this));\n      this.dispatch('bindEvents');\n    }); // set content for center slide (first time)\n\n    if (this.mainScroll.itemHolders[1]) {\n      this.setContent(this.mainScroll.itemHolders[1], this.currIndex);\n    }\n\n    this.dispatch('change');\n    this.opener.open();\n    this.dispatch('afterInit');\n    return true;\n  }\n  /**\n   * Get looped slide index\n   * (for example, -1 will return the last slide)\n   *\n   * @param {number} index\n   * @returns {number}\n   */\n\n\n  getLoopedIndex(index) {\n    const numSlides = this.getNumItems();\n\n    if (this.options.loop) {\n      if (index > numSlides - 1) {\n        index -= numSlides;\n      }\n\n      if (index < 0) {\n        index += numSlides;\n      }\n    }\n\n    return clamp(index, 0, numSlides - 1);\n  }\n\n  appendHeavy() {\n    this.mainScroll.itemHolders.forEach(itemHolder => {\n      var _itemHolder$slide;\n\n      (_itemHolder$slide = itemHolder.slide) === null || _itemHolder$slide === void 0 || _itemHolder$slide.appendHeavy();\n    });\n  }\n  /**\n   * Change the slide\n   * @param {number} index New index\n   */\n\n\n  goTo(index) {\n    this.mainScroll.moveIndexBy(this.getLoopedIndex(index) - this.potentialIndex);\n  }\n  /**\n   * Go to the next slide.\n   */\n\n\n  next() {\n    this.goTo(this.potentialIndex + 1);\n  }\n  /**\n   * Go to the previous slide.\n   */\n\n\n  prev() {\n    this.goTo(this.potentialIndex - 1);\n  }\n  /**\n   * @see slide/slide.js zoomTo\n   *\n   * @param {Parameters<Slide['zoomTo']>} args\n   */\n\n\n  zoomTo(...args) {\n    var _this$currSlide;\n\n    (_this$currSlide = this.currSlide) === null || _this$currSlide === void 0 || _this$currSlide.zoomTo(...args);\n  }\n  /**\n   * @see slide/slide.js toggleZoom\n   */\n\n\n  toggleZoom() {\n    var _this$currSlide2;\n\n    (_this$currSlide2 = this.currSlide) === null || _this$currSlide2 === void 0 || _this$currSlide2.toggleZoom();\n  }\n  /**\n   * Close the gallery.\n   * After closing transition ends - destroy it\n   */\n\n\n  close() {\n    if (!this.opener.isOpen || this.isDestroying) {\n      return;\n    }\n\n    this.isDestroying = true;\n    this.dispatch('close');\n    this.events.removeAll();\n    this.opener.close();\n  }\n  /**\n   * Destroys the gallery:\n   * - instantly closes the gallery\n   * - unbinds events,\n   * - cleans intervals and timeouts\n   * - removes elements from DOM\n   */\n\n\n  destroy() {\n    var _this$element;\n\n    if (!this.isDestroying) {\n      this.options.showHideAnimationType = 'none';\n      this.close();\n      return;\n    }\n\n    this.dispatch('destroy');\n    this._listeners = {};\n\n    if (this.scrollWrap) {\n      this.scrollWrap.ontouchmove = null;\n      this.scrollWrap.ontouchend = null;\n    }\n\n    (_this$element = this.element) === null || _this$element === void 0 || _this$element.remove();\n    this.mainScroll.itemHolders.forEach(itemHolder => {\n      var _itemHolder$slide2;\n\n      (_itemHolder$slide2 = itemHolder.slide) === null || _itemHolder$slide2 === void 0 || _itemHolder$slide2.destroy();\n    });\n    this.contentLoader.destroy();\n    this.events.removeAll();\n  }\n  /**\n   * Refresh/reload content of a slide by its index\n   *\n   * @param {number} slideIndex\n   */\n\n\n  refreshSlideContent(slideIndex) {\n    this.contentLoader.removeByIndex(slideIndex);\n    this.mainScroll.itemHolders.forEach((itemHolder, i) => {\n      var _this$currSlide$index, _this$currSlide3;\n\n      let potentialHolderIndex = ((_this$currSlide$index = (_this$currSlide3 = this.currSlide) === null || _this$currSlide3 === void 0 ? void 0 : _this$currSlide3.index) !== null && _this$currSlide$index !== void 0 ? _this$currSlide$index : 0) - 1 + i;\n\n      if (this.canLoop()) {\n        potentialHolderIndex = this.getLoopedIndex(potentialHolderIndex);\n      }\n\n      if (potentialHolderIndex === slideIndex) {\n        // set the new slide content\n        this.setContent(itemHolder, slideIndex, true); // activate the new slide if it's current\n\n        if (i === 1) {\n          var _itemHolder$slide3;\n\n          this.currSlide = itemHolder.slide;\n          (_itemHolder$slide3 = itemHolder.slide) === null || _itemHolder$slide3 === void 0 || _itemHolder$slide3.setIsActive(true);\n        }\n      }\n    });\n    this.dispatch('change');\n  }\n  /**\n   * Set slide content\n   *\n   * @param {ItemHolder} holder mainScroll.itemHolders array item\n   * @param {number} index Slide index\n   * @param {boolean} [force] If content should be set even if index wasn't changed\n   */\n\n\n  setContent(holder, index, force) {\n    if (this.canLoop()) {\n      index = this.getLoopedIndex(index);\n    }\n\n    if (holder.slide) {\n      if (holder.slide.index === index && !force) {\n        // exit if holder already contains this slide\n        // this could be common when just three slides are used\n        return;\n      } // destroy previous slide\n\n\n      holder.slide.destroy();\n      holder.slide = undefined;\n    } // exit if no loop and index is out of bounds\n\n\n    if (!this.canLoop() && (index < 0 || index >= this.getNumItems())) {\n      return;\n    }\n\n    const itemData = this.getItemData(index);\n    holder.slide = new Slide(itemData, index, this); // set current slide\n\n    if (index === this.currIndex) {\n      this.currSlide = holder.slide;\n    }\n\n    holder.slide.append(holder.el);\n  }\n  /** @returns {Point} */\n\n\n  getViewportCenterPoint() {\n    return {\n      x: this.viewportSize.x / 2,\n      y: this.viewportSize.y / 2\n    };\n  }\n  /**\n   * Update size of all elements.\n   * Executed on init and on page resize.\n   *\n   * @param {boolean} [force] Update size even if size of viewport was not changed.\n   */\n\n\n  updateSize(force) {\n    // let item;\n    // let itemIndex;\n    if (this.isDestroying) {\n      // exit if PhotoSwipe is closed or closing\n      // (to avoid errors, as resize event might be delayed)\n      return;\n    } //const newWidth = this.scrollWrap.clientWidth;\n    //const newHeight = this.scrollWrap.clientHeight;\n\n\n    const newViewportSize = getViewportSize(this.options, this);\n\n    if (!force && pointsEqual(newViewportSize, this._prevViewportSize)) {\n      // Exit if dimensions were not changed\n      return;\n    } //this._prevViewportSize.x = newWidth;\n    //this._prevViewportSize.y = newHeight;\n\n\n    equalizePoints(this._prevViewportSize, newViewportSize);\n    this.dispatch('beforeResize');\n    equalizePoints(this.viewportSize, this._prevViewportSize);\n\n    this._updatePageScrollOffset();\n\n    this.dispatch('viewportSize'); // Resize slides only after opener animation is finished\n    // and don't re-calculate size on inital size update\n\n    this.mainScroll.resize(this.opener.isOpen);\n\n    if (!this.hasMouse && window.matchMedia('(any-hover: hover)').matches) {\n      this.mouseDetected();\n    }\n\n    this.dispatch('resize');\n  }\n  /**\n   * @param {number} opacity\n   */\n\n\n  applyBgOpacity(opacity) {\n    this.bgOpacity = Math.max(opacity, 0);\n\n    if (this.bg) {\n      this.bg.style.opacity = String(this.bgOpacity * this.options.bgOpacity);\n    }\n  }\n  /**\n   * Whether mouse is detected\n   */\n\n\n  mouseDetected() {\n    if (!this.hasMouse) {\n      var _this$element2;\n\n      this.hasMouse = true;\n      (_this$element2 = this.element) === null || _this$element2 === void 0 || _this$element2.classList.add('pswp--has_mouse');\n    }\n  }\n  /**\n   * Page resize event handler\n   *\n   * @private\n   */\n\n\n  _handlePageResize() {\n    this.updateSize(); // In iOS webview, if element size depends on document size,\n    // it'll be measured incorrectly in resize event\n    //\n    // https://bugs.webkit.org/show_bug.cgi?id=170595\n    // https://hackernoon.com/onresize-event-broken-in-mobile-safari-d8469027bf4d\n\n    if (/iPhone|iPad|iPod/i.test(window.navigator.userAgent)) {\n      setTimeout(() => {\n        this.updateSize();\n      }, 500);\n    }\n  }\n  /**\n   * Page scroll offset is used\n   * to get correct coordinates\n   * relative to PhotoSwipe viewport.\n   *\n   * @private\n   */\n\n\n  _updatePageScrollOffset() {\n    this.setScrollOffset(0, window.pageYOffset);\n  }\n  /**\n   * @param {number} x\n   * @param {number} y\n   */\n\n\n  setScrollOffset(x, y) {\n    this.offset.x = x;\n    this.offset.y = y;\n    this.dispatch('updateScrollOffset');\n  }\n  /**\n   * Create main HTML structure of PhotoSwipe,\n   * and add it to DOM\n   *\n   * @private\n   */\n\n\n  _createMainStructure() {\n    // root DOM element of PhotoSwipe (.pswp)\n    this.element = createElement('pswp', 'div');\n    this.element.setAttribute('tabindex', '-1');\n    this.element.setAttribute('role', 'dialog'); // template is legacy prop\n\n    this.template = this.element; // Background is added as a separate element,\n    // as animating opacity is faster than animating rgba()\n\n    this.bg = createElement('pswp__bg', 'div', this.element);\n    this.scrollWrap = createElement('pswp__scroll-wrap', 'section', this.element);\n    this.container = createElement('pswp__container', 'div', this.scrollWrap); // aria pattern: carousel\n\n    this.scrollWrap.setAttribute('aria-roledescription', 'carousel');\n    this.container.setAttribute('aria-live', 'off');\n    this.container.setAttribute('id', 'pswp__items');\n    this.mainScroll.appendHolders();\n    this.ui = new UI(this);\n    this.ui.init(); // append to DOM\n\n    (this.options.appendToEl || document.body).appendChild(this.element);\n  }\n  /**\n   * Get position and dimensions of small thumbnail\n   *   {x:,y:,w:}\n   *\n   * Height is optional (calculated based on the large image)\n   *\n   * @returns {Bounds | undefined}\n   */\n\n\n  getThumbBounds() {\n    return getThumbBounds(this.currIndex, this.currSlide ? this.currSlide.data : this._initialItemData, this);\n  }\n  /**\n   * If the PhotoSwipe can have continuous loop\n   * @returns Boolean\n   */\n\n\n  canLoop() {\n    return this.options.loop && this.getNumItems() > 2;\n  }\n  /**\n   * @private\n   * @param {PhotoSwipeOptions} options\n   * @returns {PreparedPhotoSwipeOptions}\n   */\n\n\n  _prepareOptions(options) {\n    if (window.matchMedia('(prefers-reduced-motion), (update: slow)').matches) {\n      options.showHideAnimationType = 'none';\n      options.zoomAnimationDuration = 0;\n    }\n    /** @type {PreparedPhotoSwipeOptions} */\n\n\n    return { ...defaultOptions,\n      ...options\n    };\n  }\n\n}\n\nexport { PhotoSwipe as default };\n//# sourceMappingURL=photoswipe.esm.js.map\n"
  },
  {
    "path": "dist/photoswipe-lightbox.esm.js",
    "content": "/*!\n  * PhotoSwipe Lightbox 5.4.4 - https://photoswipe.com\n  * (c) 2024 Dmytro Semenov\n  */\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/**\n * @template {keyof HTMLElementTagNameMap} T\n * @param {string} className\n * @param {T} tagName\n * @param {Node} [appendToEl]\n * @returns {HTMLElementTagNameMap[T]}\n */\nfunction createElement(className, tagName, appendToEl) {\n  const el = document.createElement(tagName);\n\n  if (className) {\n    el.className = className;\n  }\n\n  if (appendToEl) {\n    appendToEl.appendChild(el);\n  }\n\n  return el;\n}\n/**\n * Get transform string\n *\n * @param {number} x\n * @param {number} [y]\n * @param {number} [scale]\n * @returns {string}\n */\n\nfunction toTransformString(x, y, scale) {\n  let propValue = `translate3d(${x}px,${y || 0}px,0)`;\n\n  if (scale !== undefined) {\n    propValue += ` scale3d(${scale},${scale},1)`;\n  }\n\n  return propValue;\n}\n/**\n * Apply width and height CSS properties to element\n *\n * @param {HTMLElement} el\n * @param {string | number} w\n * @param {string | number} h\n */\n\nfunction setWidthHeight(el, w, h) {\n  el.style.width = typeof w === 'number' ? `${w}px` : w;\n  el.style.height = typeof h === 'number' ? `${h}px` : h;\n}\n/** @typedef {LOAD_STATE[keyof LOAD_STATE]} LoadState */\n\n/** @type {{ IDLE: 'idle'; LOADING: 'loading'; LOADED: 'loaded'; ERROR: 'error' }} */\n\nconst LOAD_STATE = {\n  IDLE: 'idle',\n  LOADING: 'loading',\n  LOADED: 'loaded',\n  ERROR: 'error'\n};\n/**\n * Check if click or keydown event was dispatched\n * with a special key or via mouse wheel.\n *\n * @param {MouseEvent | KeyboardEvent} e\n * @returns {boolean}\n */\n\nfunction specialKeyUsed(e) {\n  return 'button' in e && e.button === 1 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey;\n}\n/**\n * Parse `gallery` or `children` options.\n *\n * @param {import('../photoswipe.js').ElementProvider} [option]\n * @param {string} [legacySelector]\n * @param {HTMLElement | Document} [parent]\n * @returns HTMLElement[]\n */\n\nfunction getElementsFromOption(option, legacySelector, parent = document) {\n  /** @type {HTMLElement[]} */\n  let elements = [];\n\n  if (option instanceof Element) {\n    elements = [option];\n  } else if (option instanceof NodeList || Array.isArray(option)) {\n    elements = Array.from(option);\n  } else {\n    const selector = typeof option === 'string' ? option : legacySelector;\n\n    if (selector) {\n      elements = Array.from(parent.querySelectorAll(selector));\n    }\n  }\n\n  return elements;\n}\n/**\n * Check if variable is PhotoSwipe class\n *\n * @param {any} fn\n * @returns {boolean}\n */\n\nfunction isPswpClass(fn) {\n  return typeof fn === 'function' && fn.prototype && fn.prototype.goTo;\n}\n/**\n * Check if browser is Safari\n *\n * @returns {boolean}\n */\n\nfunction isSafari() {\n  return !!(navigator.vendor && navigator.vendor.match(/apple/i));\n}\n\n/** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n\n/** @typedef {import('../photoswipe.js').DataSource} DataSource */\n\n/** @typedef {import('../ui/ui-element.js').UIElementData} UIElementData */\n\n/** @typedef {import('../slide/content.js').default} ContentDefault */\n\n/** @typedef {import('../slide/slide.js').default} Slide */\n\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n\n/** @typedef {import('../slide/zoom-level.js').default} ZoomLevel */\n\n/** @typedef {import('../slide/get-thumb-bounds.js').Bounds} Bounds */\n\n/**\n * Allow adding an arbitrary props to the Content\n * https://photoswipe.com/custom-content/#using-webp-image-format\n * @typedef {ContentDefault & Record<string, any>} Content\n */\n\n/** @typedef {{ x?: number; y?: number }} Point */\n\n/**\n * @typedef {Object} PhotoSwipeEventsMap https://photoswipe.com/events/\n *\n *\n * https://photoswipe.com/adding-ui-elements/\n *\n * @prop {undefined} uiRegister\n * @prop {{ data: UIElementData }} uiElementCreate\n *\n *\n * https://photoswipe.com/events/#initialization-events\n *\n * @prop {undefined} beforeOpen\n * @prop {undefined} firstUpdate\n * @prop {undefined} initialLayout\n * @prop {undefined} change\n * @prop {undefined} afterInit\n * @prop {undefined} bindEvents\n *\n *\n * https://photoswipe.com/events/#opening-or-closing-transition-events\n *\n * @prop {undefined} openingAnimationStart\n * @prop {undefined} openingAnimationEnd\n * @prop {undefined} closingAnimationStart\n * @prop {undefined} closingAnimationEnd\n *\n *\n * https://photoswipe.com/events/#closing-events\n *\n * @prop {undefined} close\n * @prop {undefined} destroy\n *\n *\n * https://photoswipe.com/events/#pointer-and-gesture-events\n *\n * @prop {{ originalEvent: PointerEvent }} pointerDown\n * @prop {{ originalEvent: PointerEvent }} pointerMove\n * @prop {{ originalEvent: PointerEvent }} pointerUp\n * @prop {{ bgOpacity: number }} pinchClose can be default prevented\n * @prop {{ panY: number }} verticalDrag can be default prevented\n *\n *\n * https://photoswipe.com/events/#slide-content-events\n *\n * @prop {{ content: Content }} contentInit\n * @prop {{ content: Content; isLazy: boolean }} contentLoad can be default prevented\n * @prop {{ content: Content; isLazy: boolean }} contentLoadImage can be default prevented\n * @prop {{ content: Content; slide: Slide; isError?: boolean }} loadComplete\n * @prop {{ content: Content; slide: Slide }} loadError\n * @prop {{ content: Content; width: number; height: number }} contentResize can be default prevented\n * @prop {{ content: Content; width: number; height: number; slide: Slide }} imageSizeChange\n * @prop {{ content: Content }} contentLazyLoad can be default prevented\n * @prop {{ content: Content }} contentAppend can be default prevented\n * @prop {{ content: Content }} contentActivate can be default prevented\n * @prop {{ content: Content }} contentDeactivate can be default prevented\n * @prop {{ content: Content }} contentRemove can be default prevented\n * @prop {{ content: Content }} contentDestroy can be default prevented\n *\n *\n * undocumented\n *\n * @prop {{ point: Point; originalEvent: PointerEvent }} imageClickAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} bgClickAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} tapAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} doubleTapAction can be default prevented\n *\n * @prop {{ originalEvent: KeyboardEvent }} keydown can be default prevented\n * @prop {{ x: number; dragging: boolean }} moveMainScroll\n * @prop {{ slide: Slide }} firstZoomPan\n * @prop {{ slide: Slide | undefined, data: SlideData, index: number }} gettingData\n * @prop {undefined} beforeResize\n * @prop {undefined} resize\n * @prop {undefined} viewportSize\n * @prop {undefined} updateScrollOffset\n * @prop {{ slide: Slide }} slideInit\n * @prop {{ slide: Slide }} afterSetContent\n * @prop {{ slide: Slide }} slideLoad\n * @prop {{ slide: Slide }} appendHeavy can be default prevented\n * @prop {{ slide: Slide }} appendHeavyContent\n * @prop {{ slide: Slide }} slideActivate\n * @prop {{ slide: Slide }} slideDeactivate\n * @prop {{ slide: Slide }} slideDestroy\n * @prop {{ destZoomLevel: number, centerPoint: Point | undefined, transitionDuration: number | false | undefined }} beforeZoomTo\n * @prop {{ slide: Slide }} zoomPanUpdate\n * @prop {{ slide: Slide }} initialZoomPan\n * @prop {{ slide: Slide }} calcSlideSize\n * @prop {undefined} resolutionChanged\n * @prop {{ originalEvent: WheelEvent }} wheel can be default prevented\n * @prop {{ content: Content }} contentAppendImage can be default prevented\n * @prop {{ index: number; itemData: SlideData }} lazyLoadSlide can be default prevented\n * @prop {undefined} lazyLoad\n * @prop {{ slide: Slide }} calcBounds\n * @prop {{ zoomLevels: ZoomLevel, slideData: SlideData }} zoomLevelsUpdate\n *\n *\n * legacy\n *\n * @prop {undefined} init\n * @prop {undefined} initialZoomIn\n * @prop {undefined} initialZoomOut\n * @prop {undefined} initialZoomInEnd\n * @prop {undefined} initialZoomOutEnd\n * @prop {{ dataSource: DataSource | undefined, numItems: number }} numItems\n * @prop {{ itemData: SlideData; index: number }} itemData\n * @prop {{ index: number, itemData: SlideData, instance: PhotoSwipe }} thumbBounds\n */\n\n/**\n * @typedef {Object} PhotoSwipeFiltersMap https://photoswipe.com/filters/\n *\n * @prop {(numItems: number, dataSource: DataSource | undefined) => number} numItems\n * Modify the total amount of slides. Example on Data sources page.\n * https://photoswipe.com/filters/#numitems\n *\n * @prop {(itemData: SlideData, index: number) => SlideData} itemData\n * Modify slide item data. Example on Data sources page.\n * https://photoswipe.com/filters/#itemdata\n *\n * @prop {(itemData: SlideData, element: HTMLElement, linkEl: HTMLAnchorElement) => SlideData} domItemData\n * Modify item data when it's parsed from DOM element. Example on Data sources page.\n * https://photoswipe.com/filters/#domitemdata\n *\n * @prop {(clickedIndex: number, e: MouseEvent, instance: PhotoSwipeLightbox) => number} clickedIndex\n * Modify clicked gallery item index.\n * https://photoswipe.com/filters/#clickedindex\n *\n * @prop {(placeholderSrc: string | false, content: Content) => string | false} placeholderSrc\n * Modify placeholder image source.\n * https://photoswipe.com/filters/#placeholdersrc\n *\n * @prop {(isContentLoading: boolean, content: Content) => boolean} isContentLoading\n * Modify if the content is currently loading.\n * https://photoswipe.com/filters/#iscontentloading\n *\n * @prop {(isContentZoomable: boolean, content: Content) => boolean} isContentZoomable\n * Modify if the content can be zoomed.\n * https://photoswipe.com/filters/#iscontentzoomable\n *\n * @prop {(useContentPlaceholder: boolean, content: Content) => boolean} useContentPlaceholder\n * Modify if the placeholder should be used for the content.\n * https://photoswipe.com/filters/#usecontentplaceholder\n *\n * @prop {(isKeepingPlaceholder: boolean, content: Content) => boolean} isKeepingPlaceholder\n * Modify if the placeholder should be kept after the content is loaded.\n * https://photoswipe.com/filters/#iskeepingplaceholder\n *\n *\n * @prop {(contentErrorElement: HTMLElement, content: Content) => HTMLElement} contentErrorElement\n * Modify an element when the content has error state (for example, if image cannot be loaded).\n * https://photoswipe.com/filters/#contenterrorelement\n *\n * @prop {(element: HTMLElement, data: UIElementData) => HTMLElement} uiElement\n * Modify a UI element that's being created.\n * https://photoswipe.com/filters/#uielement\n *\n * @prop {(thumbnail: HTMLElement | null | undefined, itemData: SlideData, index: number) => HTMLElement} thumbEl\n * Modify the thumbnail element from which opening zoom animation starts or ends.\n * https://photoswipe.com/filters/#thumbel\n *\n * @prop {(thumbBounds: Bounds | undefined, itemData: SlideData, index: number) => Bounds} thumbBounds\n * Modify the thumbnail bounds from which opening zoom animation starts or ends.\n * https://photoswipe.com/filters/#thumbbounds\n *\n * @prop {(srcsetSizesWidth: number, content: Content) => number} srcsetSizesWidth\n *\n * @prop {(preventPointerEvent: boolean, event: PointerEvent, pointerType: string) => boolean} preventPointerEvent\n *\n */\n\n/**\n * @template {keyof PhotoSwipeFiltersMap} T\n * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter\n */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent<T> : PhotoSwipeEvent<T> & PhotoSwipeEventsMap[T]} AugmentedEvent\n */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {(event: AugmentedEvent<T>) => void} EventCallback\n */\n\n/**\n * Base PhotoSwipe event object\n *\n * @template {keyof PhotoSwipeEventsMap} T\n */\nclass PhotoSwipeEvent {\n  /**\n   * @param {T} type\n   * @param {PhotoSwipeEventsMap[T]} [details]\n   */\n  constructor(type, details) {\n    this.type = type;\n    this.defaultPrevented = false;\n\n    if (details) {\n      Object.assign(this, details);\n    }\n  }\n\n  preventDefault() {\n    this.defaultPrevented = true;\n  }\n\n}\n/**\n * PhotoSwipe base class that can listen and dispatch for events.\n * Shared by PhotoSwipe Core and PhotoSwipe Lightbox, extended by base.js\n */\n\n\nclass Eventable {\n  constructor() {\n    /**\n     * @type {{ [T in keyof PhotoSwipeEventsMap]?: ((event: AugmentedEvent<T>) => void)[] }}\n     */\n    this._listeners = {};\n    /**\n     * @type {{ [T in keyof PhotoSwipeFiltersMap]?: Filter<T>[] }}\n     */\n\n    this._filters = {};\n    /** @type {PhotoSwipe | undefined} */\n\n    this.pswp = undefined;\n    /** @type {PhotoSwipeOptions | undefined} */\n\n    this.options = undefined;\n  }\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {PhotoSwipeFiltersMap[T]} fn\n   * @param {number} priority\n   */\n\n\n  addFilter(name, fn, priority = 100) {\n    var _this$_filters$name, _this$_filters$name2, _this$pswp;\n\n    if (!this._filters[name]) {\n      this._filters[name] = [];\n    }\n\n    (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.push({\n      fn,\n      priority\n    });\n    (_this$_filters$name2 = this._filters[name]) === null || _this$_filters$name2 === void 0 || _this$_filters$name2.sort((f1, f2) => f1.priority - f2.priority);\n    (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.addFilter(name, fn, priority);\n  }\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {PhotoSwipeFiltersMap[T]} fn\n   */\n\n\n  removeFilter(name, fn) {\n    if (this._filters[name]) {\n      // @ts-expect-error\n      this._filters[name] = this._filters[name].filter(filter => filter.fn !== fn);\n    }\n\n    if (this.pswp) {\n      this.pswp.removeFilter(name, fn);\n    }\n  }\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {Parameters<PhotoSwipeFiltersMap[T]>} args\n   * @returns {Parameters<PhotoSwipeFiltersMap[T]>[0]}\n   */\n\n\n  applyFilters(name, ...args) {\n    var _this$_filters$name3;\n\n    (_this$_filters$name3 = this._filters[name]) === null || _this$_filters$name3 === void 0 || _this$_filters$name3.forEach(filter => {\n      // @ts-expect-error\n      args[0] = filter.fn.apply(this, args);\n    });\n    return args[0];\n  }\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {EventCallback<T>} fn\n   */\n\n\n  on(name, fn) {\n    var _this$_listeners$name, _this$pswp2;\n\n    if (!this._listeners[name]) {\n      this._listeners[name] = [];\n    }\n\n    (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.push(fn); // When binding events to lightbox,\n    // also bind events to PhotoSwipe Core,\n    // if it's open.\n\n    (_this$pswp2 = this.pswp) === null || _this$pswp2 === void 0 || _this$pswp2.on(name, fn);\n  }\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {EventCallback<T>} fn\n   */\n\n\n  off(name, fn) {\n    var _this$pswp3;\n\n    if (this._listeners[name]) {\n      // @ts-expect-error\n      this._listeners[name] = this._listeners[name].filter(listener => fn !== listener);\n    }\n\n    (_this$pswp3 = this.pswp) === null || _this$pswp3 === void 0 || _this$pswp3.off(name, fn);\n  }\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {PhotoSwipeEventsMap[T]} [details]\n   * @returns {AugmentedEvent<T>}\n   */\n\n\n  dispatch(name, details) {\n    var _this$_listeners$name2;\n\n    if (this.pswp) {\n      return this.pswp.dispatch(name, details);\n    }\n\n    const event =\n    /** @type {AugmentedEvent<T>} */\n    new PhotoSwipeEvent(name, details);\n    (_this$_listeners$name2 = this._listeners[name]) === null || _this$_listeners$name2 === void 0 || _this$_listeners$name2.forEach(listener => {\n      listener.call(this, event);\n    });\n    return event;\n  }\n\n}\n\nclass Placeholder {\n  /**\n   * @param {string | false} imageSrc\n   * @param {HTMLElement} container\n   */\n  constructor(imageSrc, container) {\n    // Create placeholder\n    // (stretched thumbnail or simple div behind the main image)\n\n    /** @type {HTMLImageElement | HTMLDivElement | null} */\n    this.element = createElement('pswp__img pswp__img--placeholder', imageSrc ? 'img' : 'div', container);\n\n    if (imageSrc) {\n      const imgEl =\n      /** @type {HTMLImageElement} */\n      this.element;\n      imgEl.decoding = 'async';\n      imgEl.alt = '';\n      imgEl.src = imageSrc;\n      imgEl.setAttribute('role', 'presentation');\n    }\n\n    this.element.setAttribute('aria-hidden', 'true');\n  }\n  /**\n   * @param {number} width\n   * @param {number} height\n   */\n\n\n  setDisplayedSize(width, height) {\n    if (!this.element) {\n      return;\n    }\n\n    if (this.element.tagName === 'IMG') {\n      // Use transform scale() to modify img placeholder size\n      // (instead of changing width/height directly).\n      // This helps with performance, specifically in iOS15 Safari.\n      setWidthHeight(this.element, 250, 'auto');\n      this.element.style.transformOrigin = '0 0';\n      this.element.style.transform = toTransformString(0, 0, width / 250);\n    } else {\n      setWidthHeight(this.element, width, height);\n    }\n  }\n\n  destroy() {\n    var _this$element;\n\n    if ((_this$element = this.element) !== null && _this$element !== void 0 && _this$element.parentNode) {\n      this.element.remove();\n    }\n\n    this.element = null;\n  }\n\n}\n\n/** @typedef {import('./slide.js').default} Slide */\n\n/** @typedef {import('./slide.js').SlideData} SlideData */\n\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n\n/** @typedef {import('../util/util.js').LoadState} LoadState */\n\nclass Content {\n  /**\n   * @param {SlideData} itemData Slide data\n   * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance\n   * @param {number} index\n   */\n  constructor(itemData, instance, index) {\n    this.instance = instance;\n    this.data = itemData;\n    this.index = index;\n    /** @type {HTMLImageElement | HTMLDivElement | undefined} */\n\n    this.element = undefined;\n    /** @type {Placeholder | undefined} */\n\n    this.placeholder = undefined;\n    /** @type {Slide | undefined} */\n\n    this.slide = undefined;\n    this.displayedImageWidth = 0;\n    this.displayedImageHeight = 0;\n    this.width = Number(this.data.w) || Number(this.data.width) || 0;\n    this.height = Number(this.data.h) || Number(this.data.height) || 0;\n    this.isAttached = false;\n    this.hasSlide = false;\n    this.isDecoding = false;\n    /** @type {LoadState} */\n\n    this.state = LOAD_STATE.IDLE;\n\n    if (this.data.type) {\n      this.type = this.data.type;\n    } else if (this.data.src) {\n      this.type = 'image';\n    } else {\n      this.type = 'html';\n    }\n\n    this.instance.dispatch('contentInit', {\n      content: this\n    });\n  }\n\n  removePlaceholder() {\n    if (this.placeholder && !this.keepPlaceholder()) {\n      // With delay, as image might be loaded, but not rendered\n      setTimeout(() => {\n        if (this.placeholder) {\n          this.placeholder.destroy();\n          this.placeholder = undefined;\n        }\n      }, 1000);\n    }\n  }\n  /**\n   * Preload content\n   *\n   * @param {boolean} isLazy\n   * @param {boolean} [reload]\n   */\n\n\n  load(isLazy, reload) {\n    if (this.slide && this.usePlaceholder()) {\n      if (!this.placeholder) {\n        const placeholderSrc = this.instance.applyFilters('placeholderSrc', // use  image-based placeholder only for the first slide,\n        // as rendering (even small stretched thumbnail) is an expensive operation\n        this.data.msrc && this.slide.isFirstSlide ? this.data.msrc : false, this);\n        this.placeholder = new Placeholder(placeholderSrc, this.slide.container);\n      } else {\n        const placeholderEl = this.placeholder.element; // Add placeholder to DOM if it was already created\n\n        if (placeholderEl && !placeholderEl.parentElement) {\n          this.slide.container.prepend(placeholderEl);\n        }\n      }\n    }\n\n    if (this.element && !reload) {\n      return;\n    }\n\n    if (this.instance.dispatch('contentLoad', {\n      content: this,\n      isLazy\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (this.isImageContent()) {\n      this.element = createElement('pswp__img', 'img'); // Start loading only after width is defined, as sizes might depend on it.\n      // Due to Safari feature, we must define sizes before srcset.\n\n      if (this.displayedImageWidth) {\n        this.loadImage(isLazy);\n      }\n    } else {\n      this.element = createElement('pswp__content', 'div');\n      this.element.innerHTML = this.data.html || '';\n    }\n\n    if (reload && this.slide) {\n      this.slide.updateContentSize(true);\n    }\n  }\n  /**\n   * Preload image\n   *\n   * @param {boolean} isLazy\n   */\n\n\n  loadImage(isLazy) {\n    var _this$data$src, _this$data$alt;\n\n    if (!this.isImageContent() || !this.element || this.instance.dispatch('contentLoadImage', {\n      content: this,\n      isLazy\n    }).defaultPrevented) {\n      return;\n    }\n\n    const imageElement =\n    /** @type HTMLImageElement */\n    this.element;\n    this.updateSrcsetSizes();\n\n    if (this.data.srcset) {\n      imageElement.srcset = this.data.srcset;\n    }\n\n    imageElement.src = (_this$data$src = this.data.src) !== null && _this$data$src !== void 0 ? _this$data$src : '';\n    imageElement.alt = (_this$data$alt = this.data.alt) !== null && _this$data$alt !== void 0 ? _this$data$alt : '';\n    this.state = LOAD_STATE.LOADING;\n\n    if (imageElement.complete) {\n      this.onLoaded();\n    } else {\n      imageElement.onload = () => {\n        this.onLoaded();\n      };\n\n      imageElement.onerror = () => {\n        this.onError();\n      };\n    }\n  }\n  /**\n   * Assign slide to content\n   *\n   * @param {Slide} slide\n   */\n\n\n  setSlide(slide) {\n    this.slide = slide;\n    this.hasSlide = true;\n    this.instance = slide.pswp; // todo: do we need to unset slide?\n  }\n  /**\n   * Content load success handler\n   */\n\n\n  onLoaded() {\n    this.state = LOAD_STATE.LOADED;\n\n    if (this.slide && this.element) {\n      this.instance.dispatch('loadComplete', {\n        slide: this.slide,\n        content: this\n      }); // if content is reloaded\n\n      if (this.slide.isActive && this.slide.heavyAppended && !this.element.parentNode) {\n        this.append();\n        this.slide.updateContentSize(true);\n      }\n\n      if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {\n        this.removePlaceholder();\n      }\n    }\n  }\n  /**\n   * Content load error handler\n   */\n\n\n  onError() {\n    this.state = LOAD_STATE.ERROR;\n\n    if (this.slide) {\n      this.displayError();\n      this.instance.dispatch('loadComplete', {\n        slide: this.slide,\n        isError: true,\n        content: this\n      });\n      this.instance.dispatch('loadError', {\n        slide: this.slide,\n        content: this\n      });\n    }\n  }\n  /**\n   * @returns {Boolean} If the content is currently loading\n   */\n\n\n  isLoading() {\n    return this.instance.applyFilters('isContentLoading', this.state === LOAD_STATE.LOADING, this);\n  }\n  /**\n   * @returns {Boolean} If the content is in error state\n   */\n\n\n  isError() {\n    return this.state === LOAD_STATE.ERROR;\n  }\n  /**\n   * @returns {boolean} If the content is image\n   */\n\n\n  isImageContent() {\n    return this.type === 'image';\n  }\n  /**\n   * Update content size\n   *\n   * @param {Number} width\n   * @param {Number} height\n   */\n\n\n  setDisplayedSize(width, height) {\n    if (!this.element) {\n      return;\n    }\n\n    if (this.placeholder) {\n      this.placeholder.setDisplayedSize(width, height);\n    }\n\n    if (this.instance.dispatch('contentResize', {\n      content: this,\n      width,\n      height\n    }).defaultPrevented) {\n      return;\n    }\n\n    setWidthHeight(this.element, width, height);\n\n    if (this.isImageContent() && !this.isError()) {\n      const isInitialSizeUpdate = !this.displayedImageWidth && width;\n      this.displayedImageWidth = width;\n      this.displayedImageHeight = height;\n\n      if (isInitialSizeUpdate) {\n        this.loadImage(false);\n      } else {\n        this.updateSrcsetSizes();\n      }\n\n      if (this.slide) {\n        this.instance.dispatch('imageSizeChange', {\n          slide: this.slide,\n          width,\n          height,\n          content: this\n        });\n      }\n    }\n  }\n  /**\n   * @returns {boolean} If the content can be zoomed\n   */\n\n\n  isZoomable() {\n    return this.instance.applyFilters('isContentZoomable', this.isImageContent() && this.state !== LOAD_STATE.ERROR, this);\n  }\n  /**\n   * Update image srcset sizes attribute based on width and height\n   */\n\n\n  updateSrcsetSizes() {\n    // Handle srcset sizes attribute.\n    //\n    // Never lower quality, if it was increased previously.\n    // Chrome does this automatically, Firefox and Safari do not,\n    // so we store largest used size in dataset.\n    if (!this.isImageContent() || !this.element || !this.data.srcset) {\n      return;\n    }\n\n    const image =\n    /** @type HTMLImageElement */\n    this.element;\n    const sizesWidth = this.instance.applyFilters('srcsetSizesWidth', this.displayedImageWidth, this);\n\n    if (!image.dataset.largestUsedSize || sizesWidth > parseInt(image.dataset.largestUsedSize, 10)) {\n      image.sizes = sizesWidth + 'px';\n      image.dataset.largestUsedSize = String(sizesWidth);\n    }\n  }\n  /**\n   * @returns {boolean} If content should use a placeholder (from msrc by default)\n   */\n\n\n  usePlaceholder() {\n    return this.instance.applyFilters('useContentPlaceholder', this.isImageContent(), this);\n  }\n  /**\n   * Preload content with lazy-loading param\n   */\n\n\n  lazyLoad() {\n    if (this.instance.dispatch('contentLazyLoad', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    this.load(true);\n  }\n  /**\n   * @returns {boolean} If placeholder should be kept after content is loaded\n   */\n\n\n  keepPlaceholder() {\n    return this.instance.applyFilters('isKeepingPlaceholder', this.isLoading(), this);\n  }\n  /**\n   * Destroy the content\n   */\n\n\n  destroy() {\n    this.hasSlide = false;\n    this.slide = undefined;\n\n    if (this.instance.dispatch('contentDestroy', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    this.remove();\n\n    if (this.placeholder) {\n      this.placeholder.destroy();\n      this.placeholder = undefined;\n    }\n\n    if (this.isImageContent() && this.element) {\n      this.element.onload = null;\n      this.element.onerror = null;\n      this.element = undefined;\n    }\n  }\n  /**\n   * Display error message\n   */\n\n\n  displayError() {\n    if (this.slide) {\n      var _this$instance$option, _this$instance$option2;\n\n      let errorMsgEl = createElement('pswp__error-msg', 'div');\n      errorMsgEl.innerText = (_this$instance$option = (_this$instance$option2 = this.instance.options) === null || _this$instance$option2 === void 0 ? void 0 : _this$instance$option2.errorMsg) !== null && _this$instance$option !== void 0 ? _this$instance$option : '';\n      errorMsgEl =\n      /** @type {HTMLDivElement} */\n      this.instance.applyFilters('contentErrorElement', errorMsgEl, this);\n      this.element = createElement('pswp__content pswp__error-msg-container', 'div');\n      this.element.appendChild(errorMsgEl);\n      this.slide.container.innerText = '';\n      this.slide.container.appendChild(this.element);\n      this.slide.updateContentSize(true);\n      this.removePlaceholder();\n    }\n  }\n  /**\n   * Append the content\n   */\n\n\n  append() {\n    if (this.isAttached || !this.element) {\n      return;\n    }\n\n    this.isAttached = true;\n\n    if (this.state === LOAD_STATE.ERROR) {\n      this.displayError();\n      return;\n    }\n\n    if (this.instance.dispatch('contentAppend', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    const supportsDecode = ('decode' in this.element);\n\n    if (this.isImageContent()) {\n      // Use decode() on nearby slides\n      //\n      // Nearby slide images are in DOM and not hidden via display:none.\n      // However, they are placed offscreen (to the left and right side).\n      //\n      // Some browsers do not composite the image until it's actually visible,\n      // using decode() helps.\n      //\n      // You might ask \"why dont you just decode() and then append all images\",\n      // that's because I want to show image before it's fully loaded,\n      // as browser can render parts of image while it is loading.\n      // We do not do this in Safari due to partial loading bug.\n      if (supportsDecode && this.slide && (!this.slide.isActive || isSafari())) {\n        this.isDecoding = true; // purposefully using finally instead of then,\n        // as if srcset sizes changes dynamically - it may cause decode error\n\n        /** @type {HTMLImageElement} */\n\n        this.element.decode().catch(() => {}).finally(() => {\n          this.isDecoding = false;\n          this.appendImage();\n        });\n      } else {\n        this.appendImage();\n      }\n    } else if (this.slide && !this.element.parentNode) {\n      this.slide.container.appendChild(this.element);\n    }\n  }\n  /**\n   * Activate the slide,\n   * active slide is generally the current one,\n   * meaning the user can see it.\n   */\n\n\n  activate() {\n    if (this.instance.dispatch('contentActivate', {\n      content: this\n    }).defaultPrevented || !this.slide) {\n      return;\n    }\n\n    if (this.isImageContent() && this.isDecoding && !isSafari()) {\n      // add image to slide when it becomes active,\n      // even if it's not finished decoding\n      this.appendImage();\n    } else if (this.isError()) {\n      this.load(false, true); // try to reload\n    }\n\n    if (this.slide.holderElement) {\n      this.slide.holderElement.setAttribute('aria-hidden', 'false');\n    }\n  }\n  /**\n   * Deactivate the content\n   */\n\n\n  deactivate() {\n    this.instance.dispatch('contentDeactivate', {\n      content: this\n    });\n\n    if (this.slide && this.slide.holderElement) {\n      this.slide.holderElement.setAttribute('aria-hidden', 'true');\n    }\n  }\n  /**\n   * Remove the content from DOM\n   */\n\n\n  remove() {\n    this.isAttached = false;\n\n    if (this.instance.dispatch('contentRemove', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (this.element && this.element.parentNode) {\n      this.element.remove();\n    }\n\n    if (this.placeholder && this.placeholder.element) {\n      this.placeholder.element.remove();\n    }\n  }\n  /**\n   * Append the image content to slide container\n   */\n\n\n  appendImage() {\n    if (!this.isAttached) {\n      return;\n    }\n\n    if (this.instance.dispatch('contentAppendImage', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    } // ensure that element exists and is not already appended\n\n\n    if (this.slide && this.element && !this.element.parentNode) {\n      this.slide.container.appendChild(this.element);\n    }\n\n    if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {\n      this.removePlaceholder();\n    }\n  }\n\n}\n\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n\n/**\n * @param {PhotoSwipeOptions} options\n * @param {PhotoSwipeBase} pswp\n * @returns {Point}\n */\nfunction getViewportSize(options, pswp) {\n  if (options.getViewportSizeFn) {\n    const newViewportSize = options.getViewportSizeFn(options, pswp);\n\n    if (newViewportSize) {\n      return newViewportSize;\n    }\n  }\n\n  return {\n    x: document.documentElement.clientWidth,\n    // TODO: height on mobile is very incosistent due to toolbar\n    // find a way to improve this\n    //\n    // document.documentElement.clientHeight - doesn't seem to work well\n    y: window.innerHeight\n  };\n}\n/**\n * Parses padding option.\n * Supported formats:\n *\n * // Object\n * padding: {\n *  top: 0,\n *  bottom: 0,\n *  left: 0,\n *  right: 0\n * }\n *\n * // A function that returns the object\n * paddingFn: (viewportSize, itemData, index) => {\n *  return {\n *    top: 0,\n *    bottom: 0,\n *    left: 0,\n *    right: 0\n *  };\n * }\n *\n * // Legacy variant\n * paddingLeft: 0,\n * paddingRight: 0,\n * paddingTop: 0,\n * paddingBottom: 0,\n *\n * @param {'left' | 'top' | 'bottom' | 'right'} prop\n * @param {PhotoSwipeOptions} options PhotoSwipe options\n * @param {Point} viewportSize PhotoSwipe viewport size, for example: { x:800, y:600 }\n * @param {SlideData} itemData Data about the slide\n * @param {number} index Slide index\n * @returns {number}\n */\n\nfunction parsePaddingOption(prop, options, viewportSize, itemData, index) {\n  let paddingValue = 0;\n\n  if (options.paddingFn) {\n    paddingValue = options.paddingFn(viewportSize, itemData, index)[prop];\n  } else if (options.padding) {\n    paddingValue = options.padding[prop];\n  } else {\n    const legacyPropName = 'padding' + prop[0].toUpperCase() + prop.slice(1); // @ts-expect-error\n\n    if (options[legacyPropName]) {\n      // @ts-expect-error\n      paddingValue = options[legacyPropName];\n    }\n  }\n\n  return Number(paddingValue) || 0;\n}\n/**\n * @param {PhotoSwipeOptions} options\n * @param {Point} viewportSize\n * @param {SlideData} itemData\n * @param {number} index\n * @returns {Point}\n */\n\nfunction getPanAreaSize(options, viewportSize, itemData, index) {\n  return {\n    x: viewportSize.x - parsePaddingOption('left', options, viewportSize, itemData, index) - parsePaddingOption('right', options, viewportSize, itemData, index),\n    y: viewportSize.y - parsePaddingOption('top', options, viewportSize, itemData, index) - parsePaddingOption('bottom', options, viewportSize, itemData, index)\n  };\n}\n\nconst MAX_IMAGE_WIDTH = 4000;\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n\n/** @typedef {'fit' | 'fill' | number | ((zoomLevelObject: ZoomLevel) => number)} ZoomLevelOption */\n\n/**\n * Calculates zoom levels for specific slide.\n * Depends on viewport size and image size.\n */\n\nclass ZoomLevel {\n  /**\n   * @param {PhotoSwipeOptions} options PhotoSwipe options\n   * @param {SlideData} itemData Slide data\n   * @param {number} index Slide index\n   * @param {PhotoSwipe} [pswp] PhotoSwipe instance, can be undefined if not initialized yet\n   */\n  constructor(options, itemData, index, pswp) {\n    this.pswp = pswp;\n    this.options = options;\n    this.itemData = itemData;\n    this.index = index;\n    /** @type { Point | null } */\n\n    this.panAreaSize = null;\n    /** @type { Point | null } */\n\n    this.elementSize = null;\n    this.fit = 1;\n    this.fill = 1;\n    this.vFill = 1;\n    this.initial = 1;\n    this.secondary = 1;\n    this.max = 1;\n    this.min = 1;\n  }\n  /**\n   * Calculate initial, secondary and maximum zoom level for the specified slide.\n   *\n   * It should be called when either image or viewport size changes.\n   *\n   * @param {number} maxWidth\n   * @param {number} maxHeight\n   * @param {Point} panAreaSize\n   */\n\n\n  update(maxWidth, maxHeight, panAreaSize) {\n    /** @type {Point} */\n    const elementSize = {\n      x: maxWidth,\n      y: maxHeight\n    };\n    this.elementSize = elementSize;\n    this.panAreaSize = panAreaSize;\n    const hRatio = panAreaSize.x / elementSize.x;\n    const vRatio = panAreaSize.y / elementSize.y;\n    this.fit = Math.min(1, hRatio < vRatio ? hRatio : vRatio);\n    this.fill = Math.min(1, hRatio > vRatio ? hRatio : vRatio); // zoom.vFill defines zoom level of the image\n    // when it has 100% of viewport vertical space (height)\n\n    this.vFill = Math.min(1, vRatio);\n    this.initial = this._getInitial();\n    this.secondary = this._getSecondary();\n    this.max = Math.max(this.initial, this.secondary, this._getMax());\n    this.min = Math.min(this.fit, this.initial, this.secondary);\n\n    if (this.pswp) {\n      this.pswp.dispatch('zoomLevelsUpdate', {\n        zoomLevels: this,\n        slideData: this.itemData\n      });\n    }\n  }\n  /**\n   * Parses user-defined zoom option.\n   *\n   * @private\n   * @param {'initial' | 'secondary' | 'max'} optionPrefix Zoom level option prefix (initial, secondary, max)\n   * @returns { number | undefined }\n   */\n\n\n  _parseZoomLevelOption(optionPrefix) {\n    const optionName =\n    /** @type {'initialZoomLevel' | 'secondaryZoomLevel' | 'maxZoomLevel'} */\n    optionPrefix + 'ZoomLevel';\n    const optionValue = this.options[optionName];\n\n    if (!optionValue) {\n      return;\n    }\n\n    if (typeof optionValue === 'function') {\n      return optionValue(this);\n    }\n\n    if (optionValue === 'fill') {\n      return this.fill;\n    }\n\n    if (optionValue === 'fit') {\n      return this.fit;\n    }\n\n    return Number(optionValue);\n  }\n  /**\n   * Get zoom level to which image will be zoomed after double-tap gesture,\n   * or when user clicks on zoom icon,\n   * or mouse-click on image itself.\n   * If you return 1 image will be zoomed to its original size.\n   *\n   * @private\n   * @return {number}\n   */\n\n\n  _getSecondary() {\n    let currZoomLevel = this._parseZoomLevelOption('secondary');\n\n    if (currZoomLevel) {\n      return currZoomLevel;\n    } // 3x of \"fit\" state, but not larger than original\n\n\n    currZoomLevel = Math.min(1, this.fit * 3);\n\n    if (this.elementSize && currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) {\n      currZoomLevel = MAX_IMAGE_WIDTH / this.elementSize.x;\n    }\n\n    return currZoomLevel;\n  }\n  /**\n   * Get initial image zoom level.\n   *\n   * @private\n   * @return {number}\n   */\n\n\n  _getInitial() {\n    return this._parseZoomLevelOption('initial') || this.fit;\n  }\n  /**\n   * Maximum zoom level when user zooms\n   * via zoom/pinch gesture,\n   * via cmd/ctrl-wheel or via trackpad.\n   *\n   * @private\n   * @return {number}\n   */\n\n\n  _getMax() {\n    // max zoom level is x4 from \"fit state\",\n    // used for zoom gesture and ctrl/trackpad zoom\n    return this._parseZoomLevelOption('max') || Math.max(1, this.fit * 4);\n  }\n\n}\n\n/**\n * Lazy-load an image\n * This function is used both by Lightbox and PhotoSwipe core,\n * thus it can be called before dialog is opened.\n *\n * @param {SlideData} itemData Data about the slide\n * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance\n * @param {number} index\n * @returns {Content} Image that is being decoded or false.\n */\n\nfunction lazyLoadData(itemData, instance, index) {\n  const content = instance.createContentFromData(itemData, index);\n  /** @type {ZoomLevel | undefined} */\n\n  let zoomLevel;\n  const {\n    options\n  } = instance; // We need to know dimensions of the image to preload it,\n  // as it might use srcset, and we need to define sizes\n\n  if (options) {\n    zoomLevel = new ZoomLevel(options, itemData, -1);\n    let viewportSize;\n\n    if (instance.pswp) {\n      viewportSize = instance.pswp.viewportSize;\n    } else {\n      viewportSize = getViewportSize(options, instance);\n    }\n\n    const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index);\n    zoomLevel.update(content.width, content.height, panAreaSize);\n  }\n\n  content.lazyLoad();\n\n  if (zoomLevel) {\n    content.setDisplayedSize(Math.ceil(content.width * zoomLevel.initial), Math.ceil(content.height * zoomLevel.initial));\n  }\n\n  return content;\n}\n/**\n * Lazy-loads specific slide.\n * This function is used both by Lightbox and PhotoSwipe core,\n * thus it can be called before dialog is opened.\n *\n * By default, it loads image based on viewport size and initial zoom level.\n *\n * @param {number} index Slide index\n * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox eventable instance\n * @returns {Content | undefined}\n */\n\nfunction lazyLoadSlide(index, instance) {\n  const itemData = instance.getItemData(index);\n\n  if (instance.dispatch('lazyLoadSlide', {\n    index,\n    itemData\n  }).defaultPrevented) {\n    return;\n  }\n\n  return lazyLoadData(itemData, instance, index);\n}\n\n/** @typedef {import(\"../photoswipe.js\").default} PhotoSwipe */\n\n/** @typedef {import(\"../slide/slide.js\").SlideData} SlideData */\n\n/**\n * PhotoSwipe base class that can retrieve data about every slide.\n * Shared by PhotoSwipe Core and PhotoSwipe Lightbox\n */\n\nclass PhotoSwipeBase extends Eventable {\n  /**\n   * Get total number of slides\n   *\n   * @returns {number}\n   */\n  getNumItems() {\n    var _this$options;\n\n    let numItems = 0;\n    const dataSource = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.dataSource;\n\n    if (dataSource && 'length' in dataSource) {\n      // may be an array or just object with length property\n      numItems = dataSource.length;\n    } else if (dataSource && 'gallery' in dataSource) {\n      // query DOM elements\n      if (!dataSource.items) {\n        dataSource.items = this._getGalleryDOMElements(dataSource.gallery);\n      }\n\n      if (dataSource.items) {\n        numItems = dataSource.items.length;\n      }\n    } // legacy event, before filters were introduced\n\n\n    const event = this.dispatch('numItems', {\n      dataSource,\n      numItems\n    });\n    return this.applyFilters('numItems', event.numItems, dataSource);\n  }\n  /**\n   * @param {SlideData} slideData\n   * @param {number} index\n   * @returns {Content}\n   */\n\n\n  createContentFromData(slideData, index) {\n    return new Content(slideData, this, index);\n  }\n  /**\n   * Get item data by index.\n   *\n   * \"item data\" should contain normalized information that PhotoSwipe needs to generate a slide.\n   * For example, it may contain properties like\n   * `src`, `srcset`, `w`, `h`, which will be used to generate a slide with image.\n   *\n   * @param {number} index\n   * @returns {SlideData}\n   */\n\n\n  getItemData(index) {\n    var _this$options2;\n\n    const dataSource = (_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : _this$options2.dataSource;\n    /** @type {SlideData | HTMLElement} */\n\n    let dataSourceItem = {};\n\n    if (Array.isArray(dataSource)) {\n      // Datasource is an array of elements\n      dataSourceItem = dataSource[index];\n    } else if (dataSource && 'gallery' in dataSource) {\n      // dataSource has gallery property,\n      // thus it was created by Lightbox, based on\n      // gallery and children options\n      // query DOM elements\n      if (!dataSource.items) {\n        dataSource.items = this._getGalleryDOMElements(dataSource.gallery);\n      }\n\n      dataSourceItem = dataSource.items[index];\n    }\n\n    let itemData = dataSourceItem;\n\n    if (itemData instanceof Element) {\n      itemData = this._domElementToItemData(itemData);\n    } // Dispatching the itemData event,\n    // it's a legacy verion before filters were introduced\n\n\n    const event = this.dispatch('itemData', {\n      itemData: itemData || {},\n      index\n    });\n    return this.applyFilters('itemData', event.itemData, index);\n  }\n  /**\n   * Get array of gallery DOM elements,\n   * based on childSelector and gallery element.\n   *\n   * @param {HTMLElement} galleryElement\n   * @returns {HTMLElement[]}\n   */\n\n\n  _getGalleryDOMElements(galleryElement) {\n    var _this$options3, _this$options4;\n\n    if ((_this$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.children || (_this$options4 = this.options) !== null && _this$options4 !== void 0 && _this$options4.childSelector) {\n      return getElementsFromOption(this.options.children, this.options.childSelector, galleryElement) || [];\n    }\n\n    return [galleryElement];\n  }\n  /**\n   * Converts DOM element to item data object.\n   *\n   * @param {HTMLElement} element DOM element\n   * @returns {SlideData}\n   */\n\n\n  _domElementToItemData(element) {\n    /** @type {SlideData} */\n    const itemData = {\n      element\n    };\n    const linkEl =\n    /** @type {HTMLAnchorElement} */\n    element.tagName === 'A' ? element : element.querySelector('a');\n\n    if (linkEl) {\n      // src comes from data-pswp-src attribute,\n      // if it's empty link href is used\n      itemData.src = linkEl.dataset.pswpSrc || linkEl.href;\n\n      if (linkEl.dataset.pswpSrcset) {\n        itemData.srcset = linkEl.dataset.pswpSrcset;\n      }\n\n      itemData.width = linkEl.dataset.pswpWidth ? parseInt(linkEl.dataset.pswpWidth, 10) : 0;\n      itemData.height = linkEl.dataset.pswpHeight ? parseInt(linkEl.dataset.pswpHeight, 10) : 0; // support legacy w & h properties\n\n      itemData.w = itemData.width;\n      itemData.h = itemData.height;\n\n      if (linkEl.dataset.pswpType) {\n        itemData.type = linkEl.dataset.pswpType;\n      }\n\n      const thumbnailEl = element.querySelector('img');\n\n      if (thumbnailEl) {\n        var _thumbnailEl$getAttri;\n\n        // msrc is URL to placeholder image that's displayed before large image is loaded\n        // by default it's displayed only for the first slide\n        itemData.msrc = thumbnailEl.currentSrc || thumbnailEl.src;\n        itemData.alt = (_thumbnailEl$getAttri = thumbnailEl.getAttribute('alt')) !== null && _thumbnailEl$getAttri !== void 0 ? _thumbnailEl$getAttri : '';\n      }\n\n      if (linkEl.dataset.pswpCropped || linkEl.dataset.cropped) {\n        itemData.thumbCropped = true;\n      }\n    }\n\n    return this.applyFilters('domItemData', itemData, element, linkEl);\n  }\n  /**\n   * Lazy-load by slide data\n   *\n   * @param {SlideData} itemData Data about the slide\n   * @param {number} index\n   * @returns {Content} Image that is being decoded or false.\n   */\n\n\n  lazyLoadData(itemData, index) {\n    return lazyLoadData(itemData, this, index);\n  }\n\n}\n\n/**\n * @template T\n * @typedef {import('../types.js').Type<T>} Type<T>\n */\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n\n/** @typedef {import('../photoswipe.js').DataSource} DataSource */\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {import('../slide/content.js').default} Content */\n\n/** @typedef {import('../core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */\n\n/** @typedef {import('../core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {import('../core/eventable.js').EventCallback<T>} EventCallback<T>\n */\n\n/**\n * PhotoSwipe Lightbox\n *\n * - If user has unsupported browser it falls back to default browser action (just opens URL)\n * - Binds click event to links that should open PhotoSwipe\n * - parses DOM strcture for PhotoSwipe (retrieves large image URLs and sizes)\n * - Initializes PhotoSwipe\n *\n *\n * Loader options use the same object as PhotoSwipe, and supports such options:\n *\n * gallery - Element | Element[] | NodeList | string selector for the gallery element\n * children - Element | Element[] | NodeList | string selector for the gallery children\n *\n */\n\nclass PhotoSwipeLightbox extends PhotoSwipeBase {\n  /**\n   * @param {PhotoSwipeOptions} [options]\n   */\n  constructor(options) {\n    super();\n    /** @type {PhotoSwipeOptions} */\n\n    this.options = options || {};\n    this._uid = 0;\n    this.shouldOpen = false;\n    /**\n     * @private\n     * @type {Content | undefined}\n     */\n\n    this._preloadedContent = undefined;\n    this.onThumbnailsClick = this.onThumbnailsClick.bind(this);\n  }\n  /**\n   * Initialize lightbox, should be called only once.\n   * It's not included in the main constructor, so you may bind events before it.\n   */\n\n\n  init() {\n    // Bind click events to each gallery\n    getElementsFromOption(this.options.gallery, this.options.gallerySelector).forEach(galleryElement => {\n      galleryElement.addEventListener('click', this.onThumbnailsClick, false);\n    });\n  }\n  /**\n   * @param {MouseEvent} e\n   */\n\n\n  onThumbnailsClick(e) {\n    // Exit and allow default browser action if:\n    if (specialKeyUsed(e) // ... if clicked with a special key (ctrl/cmd...)\n    || window.pswp) {\n      // ... if PhotoSwipe is already open\n      return;\n    } // If both clientX and clientY are 0 or not defined,\n    // the event is likely triggered by keyboard,\n    // so we do not pass the initialPoint\n    //\n    // Note that some screen readers emulate the mouse position,\n    // so it's not the ideal way to detect them.\n    //\n\n    /** @type {Point | null} */\n\n\n    let initialPoint = {\n      x: e.clientX,\n      y: e.clientY\n    };\n\n    if (!initialPoint.x && !initialPoint.y) {\n      initialPoint = null;\n    }\n\n    let clickedIndex = this.getClickedIndex(e);\n    clickedIndex = this.applyFilters('clickedIndex', clickedIndex, e, this);\n    /** @type {DataSource} */\n\n    const dataSource = {\n      gallery:\n      /** @type {HTMLElement} */\n      e.currentTarget\n    };\n\n    if (clickedIndex >= 0) {\n      e.preventDefault();\n      this.loadAndOpen(clickedIndex, dataSource, initialPoint);\n    }\n  }\n  /**\n   * Get index of gallery item that was clicked.\n   *\n   * @param {MouseEvent} e click event\n   * @returns {number}\n   */\n\n\n  getClickedIndex(e) {\n    // legacy option\n    if (this.options.getClickedIndexFn) {\n      return this.options.getClickedIndexFn.call(this, e);\n    }\n\n    const clickedTarget =\n    /** @type {HTMLElement} */\n    e.target;\n    const childElements = getElementsFromOption(this.options.children, this.options.childSelector,\n    /** @type {HTMLElement} */\n    e.currentTarget);\n    const clickedChildIndex = childElements.findIndex(child => child === clickedTarget || child.contains(clickedTarget));\n\n    if (clickedChildIndex !== -1) {\n      return clickedChildIndex;\n    } else if (this.options.children || this.options.childSelector) {\n      // click wasn't on a child element\n      return -1;\n    } // There is only one item (which is the gallery)\n\n\n    return 0;\n  }\n  /**\n   * Load and open PhotoSwipe\n   *\n   * @param {number} index\n   * @param {DataSource} [dataSource]\n   * @param {Point | null} [initialPoint]\n   * @returns {boolean}\n   */\n\n\n  loadAndOpen(index, dataSource, initialPoint) {\n    // Check if the gallery is already open\n    if (window.pswp || !this.options) {\n      return false;\n    } // Use the first gallery element if dataSource is not provided\n\n\n    if (!dataSource && this.options.gallery && this.options.children) {\n      const galleryElements = getElementsFromOption(this.options.gallery);\n\n      if (galleryElements[0]) {\n        dataSource = {\n          gallery: galleryElements[0]\n        };\n      }\n    } // set initial index\n\n\n    this.options.index = index; // define options for PhotoSwipe constructor\n\n    this.options.initialPointerPos = initialPoint;\n    this.shouldOpen = true;\n    this.preload(index, dataSource);\n    return true;\n  }\n  /**\n   * Load the main module and the slide content by index\n   *\n   * @param {number} index\n   * @param {DataSource} [dataSource]\n   */\n\n\n  preload(index, dataSource) {\n    const {\n      options\n    } = this;\n\n    if (dataSource) {\n      options.dataSource = dataSource;\n    } // Add the main module\n\n    /** @type {Promise<Type<PhotoSwipe>>[]} */\n\n\n    const promiseArray = [];\n    const pswpModuleType = typeof options.pswpModule;\n\n    if (isPswpClass(options.pswpModule)) {\n      promiseArray.push(Promise.resolve(\n      /** @type {Type<PhotoSwipe>} */\n      options.pswpModule));\n    } else if (pswpModuleType === 'string') {\n      throw new Error('pswpModule as string is no longer supported');\n    } else if (pswpModuleType === 'function') {\n      promiseArray.push(\n      /** @type {() => Promise<Type<PhotoSwipe>>} */\n      options.pswpModule());\n    } else {\n      throw new Error('pswpModule is not valid');\n    } // Add custom-defined promise, if any\n\n\n    if (typeof options.openPromise === 'function') {\n      // allow developers to perform some task before opening\n      promiseArray.push(options.openPromise());\n    }\n\n    if (options.preloadFirstSlide !== false && index >= 0) {\n      this._preloadedContent = lazyLoadSlide(index, this);\n    } // Wait till all promises resolve and open PhotoSwipe\n\n\n    const uid = ++this._uid;\n    Promise.all(promiseArray).then(iterableModules => {\n      if (this.shouldOpen) {\n        const mainModule = iterableModules[0];\n\n        this._openPhotoswipe(mainModule, uid);\n      }\n    });\n  }\n  /**\n   * @private\n   * @param {Type<PhotoSwipe> | { default: Type<PhotoSwipe> }} module\n   * @param {number} uid\n   */\n\n\n  _openPhotoswipe(module, uid) {\n    // Cancel opening if UID doesn't match the current one\n    // (if user clicked on another gallery item before current was loaded).\n    //\n    // Or if shouldOpen flag is set to false\n    // (developer may modify it via public API)\n    if (uid !== this._uid && this.shouldOpen) {\n      return;\n    }\n\n    this.shouldOpen = false; // PhotoSwipe is already open\n\n    if (window.pswp) {\n      return;\n    }\n    /**\n     * Pass data to PhotoSwipe and open init\n     *\n     * @type {PhotoSwipe}\n     */\n\n\n    const pswp = typeof module === 'object' ? new module.default(this.options) // eslint-disable-line\n    : new module(this.options); // eslint-disable-line\n\n    this.pswp = pswp;\n    window.pswp = pswp; // map listeners from Lightbox to PhotoSwipe Core\n\n    /** @type {(keyof PhotoSwipeEventsMap)[]} */\n\n    Object.keys(this._listeners).forEach(name => {\n      var _this$_listeners$name;\n\n      (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.forEach(fn => {\n        pswp.on(name,\n        /** @type {EventCallback<typeof name>} */\n        fn);\n      });\n    }); // same with filters\n\n    /** @type {(keyof PhotoSwipeFiltersMap)[]} */\n\n    Object.keys(this._filters).forEach(name => {\n      var _this$_filters$name;\n\n      (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.forEach(filter => {\n        pswp.addFilter(name, filter.fn, filter.priority);\n      });\n    });\n\n    if (this._preloadedContent) {\n      pswp.contentLoader.addToCache(this._preloadedContent);\n      this._preloadedContent = undefined;\n    }\n\n    pswp.on('destroy', () => {\n      // clean up public variables\n      this.pswp = undefined;\n      delete window.pswp;\n    });\n    pswp.init();\n  }\n  /**\n   * Unbinds all events, closes PhotoSwipe if it's open.\n   */\n\n\n  destroy() {\n    var _this$pswp;\n\n    (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.destroy();\n    this.shouldOpen = false;\n    this._listeners = {};\n    getElementsFromOption(this.options.gallery, this.options.gallerySelector).forEach(galleryElement => {\n      galleryElement.removeEventListener('click', this.onThumbnailsClick, false);\n    });\n  }\n\n}\n\nexport { PhotoSwipeLightbox as default };\n//# sourceMappingURL=photoswipe-lightbox.esm.js.map\n"
  },
  {
    "path": "dist/photoswipe.css",
    "content": "/*! PhotoSwipe main CSS by Dmytro Semenov | photoswipe.com */\n\n.pswp {\n  --pswp-bg: #000;\n  --pswp-placeholder-bg: #222;\n  \n\n  --pswp-root-z-index: 100000;\n  \n  --pswp-preloader-color: rgba(79, 79, 79, 0.4);\n  --pswp-preloader-color-secondary: rgba(255, 255, 255, 0.9);\n  \n  /* defined via js:\n  --pswp-transition-duration: 333ms; */\n  \n  --pswp-icon-color: #fff;\n  --pswp-icon-color-secondary: #4f4f4f;\n  --pswp-icon-stroke-color: #4f4f4f;\n  --pswp-icon-stroke-width: 2px;\n\n  --pswp-error-text-color: var(--pswp-icon-color);\n}\n\n\n/*\n\tStyles for basic PhotoSwipe (pswp) functionality (sliding area, open/close transitions)\n*/\n\n.pswp {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n\tz-index: var(--pswp-root-z-index);\n\tdisplay: none;\n\ttouch-action: none;\n\toutline: 0;\n\topacity: 0.003;\n\tcontain: layout style size;\n\t-webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\n/* Prevents focus outline on the root element,\n  (it may be focused initially) */\n.pswp:focus {\n  outline: 0;\n}\n\n.pswp * {\n  box-sizing: border-box;\n}\n\n.pswp img {\n  max-width: none;\n}\n\n.pswp--open {\n\tdisplay: block;\n}\n\n.pswp,\n.pswp__bg {\n\ttransform: translateZ(0);\n\twill-change: opacity;\n}\n\n.pswp__bg {\n  opacity: 0.005;\n\tbackground: var(--pswp-bg);\n}\n\n.pswp,\n.pswp__scroll-wrap {\n\toverflow: hidden;\n}\n\n.pswp__scroll-wrap,\n.pswp__bg,\n.pswp__container,\n.pswp__item,\n.pswp__content,\n.pswp__img,\n.pswp__zoom-wrap {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n}\n\n.pswp__img,\n.pswp__zoom-wrap {\n\twidth: auto;\n\theight: auto;\n}\n\n.pswp--click-to-zoom.pswp--zoom-allowed .pswp__img {\n\tcursor: -webkit-zoom-in;\n\tcursor: -moz-zoom-in;\n\tcursor: zoom-in;\n}\n\n.pswp--click-to-zoom.pswp--zoomed-in .pswp__img {\n\tcursor: move;\n\tcursor: -webkit-grab;\n\tcursor: -moz-grab;\n\tcursor: grab;\n}\n\n.pswp--click-to-zoom.pswp--zoomed-in .pswp__img:active {\n  cursor: -webkit-grabbing;\n  cursor: -moz-grabbing;\n  cursor: grabbing;\n}\n\n/* :active to override grabbing cursor */\n.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img,\n.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img:active,\n.pswp__img {\n\tcursor: -webkit-zoom-out;\n\tcursor: -moz-zoom-out;\n\tcursor: zoom-out;\n}\n\n\n/* Prevent selection and tap highlights */\n.pswp__container,\n.pswp__img,\n.pswp__button,\n.pswp__counter {\n\t-webkit-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n}\n\n.pswp__item {\n\t/* z-index for fade transition */\n\tz-index: 1;\n\toverflow: hidden;\n}\n\n.pswp__hidden {\n\tdisplay: none !important;\n}\n\n/* Allow to click through pswp__content element, but not its children */\n.pswp__content {\n  pointer-events: none;\n}\n.pswp__content > * {\n  pointer-events: auto;\n}\n\n\n/*\n\n  PhotoSwipe UI\n\n*/\n\n/*\n\tError message appears when image is not loaded\n\t(JS option errorMsg controls markup)\n*/\n.pswp__error-msg-container {\n  display: grid;\n}\n.pswp__error-msg {\n\tmargin: auto;\n\tfont-size: 1em;\n\tline-height: 1;\n\tcolor: var(--pswp-error-text-color);\n}\n\n/*\nclass pswp__hide-on-close is applied to elements that\nshould hide (for example fade out) when PhotoSwipe is closed\nand show (for example fade in) when PhotoSwipe is opened\n */\n.pswp .pswp__hide-on-close {\n\topacity: 0.005;\n\twill-change: opacity;\n\ttransition: opacity var(--pswp-transition-duration) cubic-bezier(0.4, 0, 0.22, 1);\n\tz-index: 10; /* always overlap slide content */\n\tpointer-events: none; /* hidden elements should not be clickable */\n}\n\n/* class pswp--ui-visible is added when opening or closing transition starts */\n.pswp--ui-visible .pswp__hide-on-close {\n\topacity: 1;\n\tpointer-events: auto;\n}\n\n/* <button> styles, including css reset */\n.pswp__button {\n\tposition: relative;\n\tdisplay: block;\n\twidth: 50px;\n\theight: 60px;\n\tpadding: 0;\n\tmargin: 0;\n\toverflow: hidden;\n\tcursor: pointer;\n\tbackground: none;\n\tborder: 0;\n\tbox-shadow: none;\n\topacity: 0.85;\n\t-webkit-appearance: none;\n\t-webkit-touch-callout: none;\n}\n\n.pswp__button:hover,\n.pswp__button:active,\n.pswp__button:focus {\n  transition: none;\n  padding: 0;\n  background: none;\n  border: 0;\n  box-shadow: none;\n  opacity: 1;\n}\n\n.pswp__button:disabled {\n  opacity: 0.3;\n  cursor: auto;\n}\n\n.pswp__icn {\n  fill: var(--pswp-icon-color);\n  color: var(--pswp-icon-color-secondary);\n}\n\n.pswp__icn {\n  position: absolute;\n  top: 14px;\n  left: 9px;\n  width: 32px;\n  height: 32px;\n  overflow: hidden;\n  pointer-events: none;\n}\n\n.pswp__icn-shadow {\n  stroke: var(--pswp-icon-stroke-color);\n  stroke-width: var(--pswp-icon-stroke-width);\n  fill: none;\n}\n\n.pswp__icn:focus {\n\toutline: 0;\n}\n\n/*\n\tdiv element that matches size of large image,\n\tlarge image loads on top of it,\n\tused when msrc is not provided\n*/\ndiv.pswp__img--placeholder,\n.pswp__img--with-bg {\n\tbackground: var(--pswp-placeholder-bg);\n}\n\n.pswp__top-bar {\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\twidth: 100%;\n\theight: 60px;\n\tdisplay: flex;\n  flex-direction: row;\n  justify-content: flex-end;\n\tz-index: 10;\n\n\t/* allow events to pass through top bar itself */\n\tpointer-events: none !important;\n}\n.pswp__top-bar > * {\n  pointer-events: auto;\n  /* this makes transition significantly more smooth,\n     even though inner elements are not animated */\n  will-change: opacity;\n}\n\n\n/*\n\n  Close button\n\n*/\n.pswp__button--close {\n  margin-right: 6px;\n}\n\n\n/*\n\n  Arrow buttons\n\n*/\n.pswp__button--arrow {\n  position: absolute;\n  top: 0;\n  width: 75px;\n  height: 100px;\n  top: 50%;\n  margin-top: -50px;\n}\n\n.pswp__button--arrow:disabled {\n  display: none;\n  cursor: default;\n}\n\n.pswp__button--arrow .pswp__icn {\n  top: 50%;\n  margin-top: -30px;\n  width: 60px;\n  height: 60px;\n  background: none;\n  border-radius: 0;\n}\n\n.pswp--one-slide .pswp__button--arrow {\n  display: none;\n}\n\n/* hide arrows on touch screens */\n.pswp--touch .pswp__button--arrow {\n  visibility: hidden;\n}\n\n/* show arrows only after mouse was used */\n.pswp--has_mouse .pswp__button--arrow {\n  visibility: visible;\n}\n\n.pswp__button--arrow--prev {\n  right: auto;\n  left: 0px;\n}\n\n.pswp__button--arrow--next {\n  right: 0px;\n}\n.pswp__button--arrow--next .pswp__icn {\n  left: auto;\n  right: 14px;\n  /* flip horizontally */\n  transform: scale(-1, 1);\n}\n\n/*\n\n  Zoom button\n\n*/\n.pswp__button--zoom {\n  display: none;\n}\n\n.pswp--zoom-allowed .pswp__button--zoom {\n  display: block;\n}\n\n/* \"+\" => \"-\" */\n.pswp--zoomed-in .pswp__zoom-icn-bar-v {\n  display: none;\n}\n\n\n/*\n\n  Loading indicator\n\n*/\n.pswp__preloader {\n  position: relative;\n  overflow: hidden;\n  width: 50px;\n  height: 60px;\n  margin-right: auto;\n}\n\n.pswp__preloader .pswp__icn {\n  opacity: 0;\n  transition: opacity 0.2s linear;\n  animation: pswp-clockwise 600ms linear infinite;\n}\n\n.pswp__preloader--active .pswp__icn {\n  opacity: 0.85;\n}\n\n@keyframes pswp-clockwise {\n  0% { transform: rotate(0deg); }\n  100% { transform: rotate(360deg); }\n}\n\n\n/*\n\n  \"1 of 10\" counter\n\n*/\n.pswp__counter {\n  height: 30px;\n  margin-top: 15px;\n  margin-inline-start: 20px;\n  font-size: 14px;\n  line-height: 30px;\n  color: var(--pswp-icon-color);\n  text-shadow: 1px 1px 3px var(--pswp-icon-color-secondary);\n  opacity: 0.85;\n}\n\n.pswp--one-slide .pswp__counter {\n  display: none;\n}\n"
  },
  {
    "path": "dist/photoswipe.esm.js",
    "content": "/*!\n  * PhotoSwipe 5.4.4 - https://photoswipe.com\n  * (c) 2024 Dmytro Semenov\n  */\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/**\n * @template {keyof HTMLElementTagNameMap} T\n * @param {string} className\n * @param {T} tagName\n * @param {Node} [appendToEl]\n * @returns {HTMLElementTagNameMap[T]}\n */\nfunction createElement(className, tagName, appendToEl) {\n  const el = document.createElement(tagName);\n\n  if (className) {\n    el.className = className;\n  }\n\n  if (appendToEl) {\n    appendToEl.appendChild(el);\n  }\n\n  return el;\n}\n/**\n * @param {Point} p1\n * @param {Point} p2\n * @returns {Point}\n */\n\nfunction equalizePoints(p1, p2) {\n  p1.x = p2.x;\n  p1.y = p2.y;\n\n  if (p2.id !== undefined) {\n    p1.id = p2.id;\n  }\n\n  return p1;\n}\n/**\n * @param {Point} p\n */\n\nfunction roundPoint(p) {\n  p.x = Math.round(p.x);\n  p.y = Math.round(p.y);\n}\n/**\n * Returns distance between two points.\n *\n * @param {Point} p1\n * @param {Point} p2\n * @returns {number}\n */\n\nfunction getDistanceBetween(p1, p2) {\n  const x = Math.abs(p1.x - p2.x);\n  const y = Math.abs(p1.y - p2.y);\n  return Math.sqrt(x * x + y * y);\n}\n/**\n * Whether X and Y positions of points are equal\n *\n * @param {Point} p1\n * @param {Point} p2\n * @returns {boolean}\n */\n\nfunction pointsEqual(p1, p2) {\n  return p1.x === p2.x && p1.y === p2.y;\n}\n/**\n * The float result between the min and max values.\n *\n * @param {number} val\n * @param {number} min\n * @param {number} max\n * @returns {number}\n */\n\nfunction clamp(val, min, max) {\n  return Math.min(Math.max(val, min), max);\n}\n/**\n * Get transform string\n *\n * @param {number} x\n * @param {number} [y]\n * @param {number} [scale]\n * @returns {string}\n */\n\nfunction toTransformString(x, y, scale) {\n  let propValue = `translate3d(${x}px,${y || 0}px,0)`;\n\n  if (scale !== undefined) {\n    propValue += ` scale3d(${scale},${scale},1)`;\n  }\n\n  return propValue;\n}\n/**\n * Apply transform:translate(x, y) scale(scale) to element\n *\n * @param {HTMLElement} el\n * @param {number} x\n * @param {number} [y]\n * @param {number} [scale]\n */\n\nfunction setTransform(el, x, y, scale) {\n  el.style.transform = toTransformString(x, y, scale);\n}\nconst defaultCSSEasing = 'cubic-bezier(.4,0,.22,1)';\n/**\n * Apply CSS transition to element\n *\n * @param {HTMLElement} el\n * @param {string} [prop] CSS property to animate\n * @param {number} [duration] in ms\n * @param {string} [ease] CSS easing function\n */\n\nfunction setTransitionStyle(el, prop, duration, ease) {\n  // inOut: 'cubic-bezier(.4, 0, .22, 1)', // for \"toggle state\" transitions\n  // out: 'cubic-bezier(0, 0, .22, 1)', // for \"show\" transitions\n  // in: 'cubic-bezier(.4, 0, 1, 1)'// for \"hide\" transitions\n  el.style.transition = prop ? `${prop} ${duration}ms ${ease || defaultCSSEasing}` : 'none';\n}\n/**\n * Apply width and height CSS properties to element\n *\n * @param {HTMLElement} el\n * @param {string | number} w\n * @param {string | number} h\n */\n\nfunction setWidthHeight(el, w, h) {\n  el.style.width = typeof w === 'number' ? `${w}px` : w;\n  el.style.height = typeof h === 'number' ? `${h}px` : h;\n}\n/**\n * @param {HTMLElement} el\n */\n\nfunction removeTransitionStyle(el) {\n  setTransitionStyle(el);\n}\n/**\n * @param {HTMLImageElement} img\n * @returns {Promise<HTMLImageElement | void>}\n */\n\nfunction decodeImage(img) {\n  if ('decode' in img) {\n    return img.decode().catch(() => {});\n  }\n\n  if (img.complete) {\n    return Promise.resolve(img);\n  }\n\n  return new Promise((resolve, reject) => {\n    img.onload = () => resolve(img);\n\n    img.onerror = reject;\n  });\n}\n/** @typedef {LOAD_STATE[keyof LOAD_STATE]} LoadState */\n\n/** @type {{ IDLE: 'idle'; LOADING: 'loading'; LOADED: 'loaded'; ERROR: 'error' }} */\n\nconst LOAD_STATE = {\n  IDLE: 'idle',\n  LOADING: 'loading',\n  LOADED: 'loaded',\n  ERROR: 'error'\n};\n/**\n * Check if click or keydown event was dispatched\n * with a special key or via mouse wheel.\n *\n * @param {MouseEvent | KeyboardEvent} e\n * @returns {boolean}\n */\n\nfunction specialKeyUsed(e) {\n  return 'button' in e && e.button === 1 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey;\n}\n/**\n * Parse `gallery` or `children` options.\n *\n * @param {import('../photoswipe.js').ElementProvider} [option]\n * @param {string} [legacySelector]\n * @param {HTMLElement | Document} [parent]\n * @returns HTMLElement[]\n */\n\nfunction getElementsFromOption(option, legacySelector, parent = document) {\n  /** @type {HTMLElement[]} */\n  let elements = [];\n\n  if (option instanceof Element) {\n    elements = [option];\n  } else if (option instanceof NodeList || Array.isArray(option)) {\n    elements = Array.from(option);\n  } else {\n    const selector = typeof option === 'string' ? option : legacySelector;\n\n    if (selector) {\n      elements = Array.from(parent.querySelectorAll(selector));\n    }\n  }\n\n  return elements;\n}\n/**\n * Check if browser is Safari\n *\n * @returns {boolean}\n */\n\nfunction isSafari() {\n  return !!(navigator.vendor && navigator.vendor.match(/apple/i));\n}\n\n// Detect passive event listener support\nlet supportsPassive = false;\n/* eslint-disable */\n\ntry {\n  /* @ts-ignore */\n  window.addEventListener('test', null, Object.defineProperty({}, 'passive', {\n    get: () => {\n      supportsPassive = true;\n    }\n  }));\n} catch (e) {}\n/* eslint-enable */\n\n/**\n * @typedef {Object} PoolItem\n * @prop {HTMLElement | Window | Document | undefined | null} target\n * @prop {string} type\n * @prop {EventListenerOrEventListenerObject} listener\n * @prop {boolean} [passive]\n */\n\n\nclass DOMEvents {\n  constructor() {\n    /**\n     * @type {PoolItem[]}\n     * @private\n     */\n    this._pool = [];\n  }\n  /**\n   * Adds event listeners\n   *\n   * @param {PoolItem['target']} target\n   * @param {PoolItem['type']} type Can be multiple, separated by space.\n   * @param {PoolItem['listener']} listener\n   * @param {PoolItem['passive']} [passive]\n   */\n\n\n  add(target, type, listener, passive) {\n    this._toggleListener(target, type, listener, passive);\n  }\n  /**\n   * Removes event listeners\n   *\n   * @param {PoolItem['target']} target\n   * @param {PoolItem['type']} type\n   * @param {PoolItem['listener']} listener\n   * @param {PoolItem['passive']} [passive]\n   */\n\n\n  remove(target, type, listener, passive) {\n    this._toggleListener(target, type, listener, passive, true);\n  }\n  /**\n   * Removes all bound events\n   */\n\n\n  removeAll() {\n    this._pool.forEach(poolItem => {\n      this._toggleListener(poolItem.target, poolItem.type, poolItem.listener, poolItem.passive, true, true);\n    });\n\n    this._pool = [];\n  }\n  /**\n   * Adds or removes event\n   *\n   * @private\n   * @param {PoolItem['target']} target\n   * @param {PoolItem['type']} type\n   * @param {PoolItem['listener']} listener\n   * @param {PoolItem['passive']} [passive]\n   * @param {boolean} [unbind] Whether the event should be added or removed\n   * @param {boolean} [skipPool] Whether events pool should be skipped\n   */\n\n\n  _toggleListener(target, type, listener, passive, unbind, skipPool) {\n    if (!target) {\n      return;\n    }\n\n    const methodName = unbind ? 'removeEventListener' : 'addEventListener';\n    const types = type.split(' ');\n    types.forEach(eType => {\n      if (eType) {\n        // Events pool is used to easily unbind all events when PhotoSwipe is closed,\n        // so developer doesn't need to do this manually\n        if (!skipPool) {\n          if (unbind) {\n            // Remove from the events pool\n            this._pool = this._pool.filter(poolItem => {\n              return poolItem.type !== eType || poolItem.listener !== listener || poolItem.target !== target;\n            });\n          } else {\n            // Add to the events pool\n            this._pool.push({\n              target,\n              type: eType,\n              listener,\n              passive\n            });\n          }\n        } // most PhotoSwipe events call preventDefault,\n        // and we do not need browser to scroll the page\n\n\n        const eventOptions = supportsPassive ? {\n          passive: passive || false\n        } : false;\n        target[methodName](eType, listener, eventOptions);\n      }\n    });\n  }\n\n}\n\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n\n/**\n * @param {PhotoSwipeOptions} options\n * @param {PhotoSwipeBase} pswp\n * @returns {Point}\n */\nfunction getViewportSize(options, pswp) {\n  if (options.getViewportSizeFn) {\n    const newViewportSize = options.getViewportSizeFn(options, pswp);\n\n    if (newViewportSize) {\n      return newViewportSize;\n    }\n  }\n\n  return {\n    x: document.documentElement.clientWidth,\n    // TODO: height on mobile is very incosistent due to toolbar\n    // find a way to improve this\n    //\n    // document.documentElement.clientHeight - doesn't seem to work well\n    y: window.innerHeight\n  };\n}\n/**\n * Parses padding option.\n * Supported formats:\n *\n * // Object\n * padding: {\n *  top: 0,\n *  bottom: 0,\n *  left: 0,\n *  right: 0\n * }\n *\n * // A function that returns the object\n * paddingFn: (viewportSize, itemData, index) => {\n *  return {\n *    top: 0,\n *    bottom: 0,\n *    left: 0,\n *    right: 0\n *  };\n * }\n *\n * // Legacy variant\n * paddingLeft: 0,\n * paddingRight: 0,\n * paddingTop: 0,\n * paddingBottom: 0,\n *\n * @param {'left' | 'top' | 'bottom' | 'right'} prop\n * @param {PhotoSwipeOptions} options PhotoSwipe options\n * @param {Point} viewportSize PhotoSwipe viewport size, for example: { x:800, y:600 }\n * @param {SlideData} itemData Data about the slide\n * @param {number} index Slide index\n * @returns {number}\n */\n\nfunction parsePaddingOption(prop, options, viewportSize, itemData, index) {\n  let paddingValue = 0;\n\n  if (options.paddingFn) {\n    paddingValue = options.paddingFn(viewportSize, itemData, index)[prop];\n  } else if (options.padding) {\n    paddingValue = options.padding[prop];\n  } else {\n    const legacyPropName = 'padding' + prop[0].toUpperCase() + prop.slice(1); // @ts-expect-error\n\n    if (options[legacyPropName]) {\n      // @ts-expect-error\n      paddingValue = options[legacyPropName];\n    }\n  }\n\n  return Number(paddingValue) || 0;\n}\n/**\n * @param {PhotoSwipeOptions} options\n * @param {Point} viewportSize\n * @param {SlideData} itemData\n * @param {number} index\n * @returns {Point}\n */\n\nfunction getPanAreaSize(options, viewportSize, itemData, index) {\n  return {\n    x: viewportSize.x - parsePaddingOption('left', options, viewportSize, itemData, index) - parsePaddingOption('right', options, viewportSize, itemData, index),\n    y: viewportSize.y - parsePaddingOption('top', options, viewportSize, itemData, index) - parsePaddingOption('bottom', options, viewportSize, itemData, index)\n  };\n}\n\n/** @typedef {import('./slide.js').default} Slide */\n\n/** @typedef {Record<Axis, number>} Point */\n\n/** @typedef {'x' | 'y'} Axis */\n\n/**\n * Calculates minimum, maximum and initial (center) bounds of a slide\n */\n\nclass PanBounds {\n  /**\n   * @param {Slide} slide\n   */\n  constructor(slide) {\n    this.slide = slide;\n    this.currZoomLevel = 1;\n    this.center =\n    /** @type {Point} */\n    {\n      x: 0,\n      y: 0\n    };\n    this.max =\n    /** @type {Point} */\n    {\n      x: 0,\n      y: 0\n    };\n    this.min =\n    /** @type {Point} */\n    {\n      x: 0,\n      y: 0\n    };\n  }\n  /**\n   * _getItemBounds\n   *\n   * @param {number} currZoomLevel\n   */\n\n\n  update(currZoomLevel) {\n    this.currZoomLevel = currZoomLevel;\n\n    if (!this.slide.width) {\n      this.reset();\n    } else {\n      this._updateAxis('x');\n\n      this._updateAxis('y');\n\n      this.slide.pswp.dispatch('calcBounds', {\n        slide: this.slide\n      });\n    }\n  }\n  /**\n   * _calculateItemBoundsForAxis\n   *\n   * @param {Axis} axis\n   */\n\n\n  _updateAxis(axis) {\n    const {\n      pswp\n    } = this.slide;\n    const elSize = this.slide[axis === 'x' ? 'width' : 'height'] * this.currZoomLevel;\n    const paddingProp = axis === 'x' ? 'left' : 'top';\n    const padding = parsePaddingOption(paddingProp, pswp.options, pswp.viewportSize, this.slide.data, this.slide.index);\n    const panAreaSize = this.slide.panAreaSize[axis]; // Default position of element.\n    // By default, it is center of viewport:\n\n    this.center[axis] = Math.round((panAreaSize - elSize) / 2) + padding; // maximum pan position\n\n    this.max[axis] = elSize > panAreaSize ? Math.round(panAreaSize - elSize) + padding : this.center[axis]; // minimum pan position\n\n    this.min[axis] = elSize > panAreaSize ? padding : this.center[axis];\n  } // _getZeroBounds\n\n\n  reset() {\n    this.center.x = 0;\n    this.center.y = 0;\n    this.max.x = 0;\n    this.max.y = 0;\n    this.min.x = 0;\n    this.min.y = 0;\n  }\n  /**\n   * Correct pan position if it's beyond the bounds\n   *\n   * @param {Axis} axis x or y\n   * @param {number} panOffset\n   * @returns {number}\n   */\n\n\n  correctPan(axis, panOffset) {\n    // checkPanBounds\n    return clamp(panOffset, this.max[axis], this.min[axis]);\n  }\n\n}\n\nconst MAX_IMAGE_WIDTH = 4000;\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n\n/** @typedef {'fit' | 'fill' | number | ((zoomLevelObject: ZoomLevel) => number)} ZoomLevelOption */\n\n/**\n * Calculates zoom levels for specific slide.\n * Depends on viewport size and image size.\n */\n\nclass ZoomLevel {\n  /**\n   * @param {PhotoSwipeOptions} options PhotoSwipe options\n   * @param {SlideData} itemData Slide data\n   * @param {number} index Slide index\n   * @param {PhotoSwipe} [pswp] PhotoSwipe instance, can be undefined if not initialized yet\n   */\n  constructor(options, itemData, index, pswp) {\n    this.pswp = pswp;\n    this.options = options;\n    this.itemData = itemData;\n    this.index = index;\n    /** @type { Point | null } */\n\n    this.panAreaSize = null;\n    /** @type { Point | null } */\n\n    this.elementSize = null;\n    this.fit = 1;\n    this.fill = 1;\n    this.vFill = 1;\n    this.initial = 1;\n    this.secondary = 1;\n    this.max = 1;\n    this.min = 1;\n  }\n  /**\n   * Calculate initial, secondary and maximum zoom level for the specified slide.\n   *\n   * It should be called when either image or viewport size changes.\n   *\n   * @param {number} maxWidth\n   * @param {number} maxHeight\n   * @param {Point} panAreaSize\n   */\n\n\n  update(maxWidth, maxHeight, panAreaSize) {\n    /** @type {Point} */\n    const elementSize = {\n      x: maxWidth,\n      y: maxHeight\n    };\n    this.elementSize = elementSize;\n    this.panAreaSize = panAreaSize;\n    const hRatio = panAreaSize.x / elementSize.x;\n    const vRatio = panAreaSize.y / elementSize.y;\n    this.fit = Math.min(1, hRatio < vRatio ? hRatio : vRatio);\n    this.fill = Math.min(1, hRatio > vRatio ? hRatio : vRatio); // zoom.vFill defines zoom level of the image\n    // when it has 100% of viewport vertical space (height)\n\n    this.vFill = Math.min(1, vRatio);\n    this.initial = this._getInitial();\n    this.secondary = this._getSecondary();\n    this.max = Math.max(this.initial, this.secondary, this._getMax());\n    this.min = Math.min(this.fit, this.initial, this.secondary);\n\n    if (this.pswp) {\n      this.pswp.dispatch('zoomLevelsUpdate', {\n        zoomLevels: this,\n        slideData: this.itemData\n      });\n    }\n  }\n  /**\n   * Parses user-defined zoom option.\n   *\n   * @private\n   * @param {'initial' | 'secondary' | 'max'} optionPrefix Zoom level option prefix (initial, secondary, max)\n   * @returns { number | undefined }\n   */\n\n\n  _parseZoomLevelOption(optionPrefix) {\n    const optionName =\n    /** @type {'initialZoomLevel' | 'secondaryZoomLevel' | 'maxZoomLevel'} */\n    optionPrefix + 'ZoomLevel';\n    const optionValue = this.options[optionName];\n\n    if (!optionValue) {\n      return;\n    }\n\n    if (typeof optionValue === 'function') {\n      return optionValue(this);\n    }\n\n    if (optionValue === 'fill') {\n      return this.fill;\n    }\n\n    if (optionValue === 'fit') {\n      return this.fit;\n    }\n\n    return Number(optionValue);\n  }\n  /**\n   * Get zoom level to which image will be zoomed after double-tap gesture,\n   * or when user clicks on zoom icon,\n   * or mouse-click on image itself.\n   * If you return 1 image will be zoomed to its original size.\n   *\n   * @private\n   * @return {number}\n   */\n\n\n  _getSecondary() {\n    let currZoomLevel = this._parseZoomLevelOption('secondary');\n\n    if (currZoomLevel) {\n      return currZoomLevel;\n    } // 3x of \"fit\" state, but not larger than original\n\n\n    currZoomLevel = Math.min(1, this.fit * 3);\n\n    if (this.elementSize && currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) {\n      currZoomLevel = MAX_IMAGE_WIDTH / this.elementSize.x;\n    }\n\n    return currZoomLevel;\n  }\n  /**\n   * Get initial image zoom level.\n   *\n   * @private\n   * @return {number}\n   */\n\n\n  _getInitial() {\n    return this._parseZoomLevelOption('initial') || this.fit;\n  }\n  /**\n   * Maximum zoom level when user zooms\n   * via zoom/pinch gesture,\n   * via cmd/ctrl-wheel or via trackpad.\n   *\n   * @private\n   * @return {number}\n   */\n\n\n  _getMax() {\n    // max zoom level is x4 from \"fit state\",\n    // used for zoom gesture and ctrl/trackpad zoom\n    return this._parseZoomLevelOption('max') || Math.max(1, this.fit * 4);\n  }\n\n}\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n/**\n * Renders and allows to control a single slide\n */\n\nclass Slide {\n  /**\n   * @param {SlideData} data\n   * @param {number} index\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(data, index, pswp) {\n    this.data = data;\n    this.index = index;\n    this.pswp = pswp;\n    this.isActive = index === pswp.currIndex;\n    this.currentResolution = 0;\n    /** @type {Point} */\n\n    this.panAreaSize = {\n      x: 0,\n      y: 0\n    };\n    /** @type {Point} */\n\n    this.pan = {\n      x: 0,\n      y: 0\n    };\n    this.isFirstSlide = this.isActive && !pswp.opener.isOpen;\n    this.zoomLevels = new ZoomLevel(pswp.options, data, index, pswp);\n    this.pswp.dispatch('gettingData', {\n      slide: this,\n      data: this.data,\n      index\n    });\n    this.content = this.pswp.contentLoader.getContentBySlide(this);\n    this.container = createElement('pswp__zoom-wrap', 'div');\n    /** @type {HTMLElement | null} */\n\n    this.holderElement = null;\n    this.currZoomLevel = 1;\n    /** @type {number} */\n\n    this.width = this.content.width;\n    /** @type {number} */\n\n    this.height = this.content.height;\n    this.heavyAppended = false;\n    this.bounds = new PanBounds(this);\n    this.prevDisplayedWidth = -1;\n    this.prevDisplayedHeight = -1;\n    this.pswp.dispatch('slideInit', {\n      slide: this\n    });\n  }\n  /**\n   * If this slide is active/current/visible\n   *\n   * @param {boolean} isActive\n   */\n\n\n  setIsActive(isActive) {\n    if (isActive && !this.isActive) {\n      // slide just became active\n      this.activate();\n    } else if (!isActive && this.isActive) {\n      // slide just became non-active\n      this.deactivate();\n    }\n  }\n  /**\n   * Appends slide content to DOM\n   *\n   * @param {HTMLElement} holderElement\n   */\n\n\n  append(holderElement) {\n    this.holderElement = holderElement;\n    this.container.style.transformOrigin = '0 0'; // Slide appended to DOM\n\n    if (!this.data) {\n      return;\n    }\n\n    this.calculateSize();\n    this.load();\n    this.updateContentSize();\n    this.appendHeavy();\n    this.holderElement.appendChild(this.container);\n    this.zoomAndPanToInitial();\n    this.pswp.dispatch('firstZoomPan', {\n      slide: this\n    });\n    this.applyCurrentZoomPan();\n    this.pswp.dispatch('afterSetContent', {\n      slide: this\n    });\n\n    if (this.isActive) {\n      this.activate();\n    }\n  }\n\n  load() {\n    this.content.load(false);\n    this.pswp.dispatch('slideLoad', {\n      slide: this\n    });\n  }\n  /**\n   * Append \"heavy\" DOM elements\n   *\n   * This may depend on a type of slide,\n   * but generally these are large images.\n   */\n\n\n  appendHeavy() {\n    const {\n      pswp\n    } = this;\n    const appendHeavyNearby = true; // todo\n    // Avoid appending heavy elements during animations\n\n    if (this.heavyAppended || !pswp.opener.isOpen || pswp.mainScroll.isShifted() || !this.isActive && !appendHeavyNearby) {\n      return;\n    }\n\n    if (this.pswp.dispatch('appendHeavy', {\n      slide: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    this.heavyAppended = true;\n    this.content.append();\n    this.pswp.dispatch('appendHeavyContent', {\n      slide: this\n    });\n  }\n  /**\n   * Triggered when this slide is active (selected).\n   *\n   * If it's part of opening/closing transition -\n   * activate() will trigger after the transition is ended.\n   */\n\n\n  activate() {\n    this.isActive = true;\n    this.appendHeavy();\n    this.content.activate();\n    this.pswp.dispatch('slideActivate', {\n      slide: this\n    });\n  }\n  /**\n   * Triggered when this slide becomes inactive.\n   *\n   * Slide can become inactive only after it was active.\n   */\n\n\n  deactivate() {\n    this.isActive = false;\n    this.content.deactivate();\n\n    if (this.currZoomLevel !== this.zoomLevels.initial) {\n      // allow filtering\n      this.calculateSize();\n    } // reset zoom level\n\n\n    this.currentResolution = 0;\n    this.zoomAndPanToInitial();\n    this.applyCurrentZoomPan();\n    this.updateContentSize();\n    this.pswp.dispatch('slideDeactivate', {\n      slide: this\n    });\n  }\n  /**\n   * The slide should destroy itself, it will never be used again.\n   * (unbind all events and destroy internal components)\n   */\n\n\n  destroy() {\n    this.content.hasSlide = false;\n    this.content.remove();\n    this.container.remove();\n    this.pswp.dispatch('slideDestroy', {\n      slide: this\n    });\n  }\n\n  resize() {\n    if (this.currZoomLevel === this.zoomLevels.initial || !this.isActive) {\n      // Keep initial zoom level if it was before the resize,\n      // as well as when this slide is not active\n      // Reset position and scale to original state\n      this.calculateSize();\n      this.currentResolution = 0;\n      this.zoomAndPanToInitial();\n      this.applyCurrentZoomPan();\n      this.updateContentSize();\n    } else {\n      // readjust pan position if it's beyond the bounds\n      this.calculateSize();\n      this.bounds.update(this.currZoomLevel);\n      this.panTo(this.pan.x, this.pan.y);\n    }\n  }\n  /**\n   * Apply size to current slide content,\n   * based on the current resolution and scale.\n   *\n   * @param {boolean} [force] if size should be updated even if dimensions weren't changed\n   */\n\n\n  updateContentSize(force) {\n    // Use initial zoom level\n    // if resolution is not defined (user didn't zoom yet)\n    const scaleMultiplier = this.currentResolution || this.zoomLevels.initial;\n\n    if (!scaleMultiplier) {\n      return;\n    }\n\n    const width = Math.round(this.width * scaleMultiplier) || this.pswp.viewportSize.x;\n    const height = Math.round(this.height * scaleMultiplier) || this.pswp.viewportSize.y;\n\n    if (!this.sizeChanged(width, height) && !force) {\n      return;\n    }\n\n    this.content.setDisplayedSize(width, height);\n  }\n  /**\n   * @param {number} width\n   * @param {number} height\n   */\n\n\n  sizeChanged(width, height) {\n    if (width !== this.prevDisplayedWidth || height !== this.prevDisplayedHeight) {\n      this.prevDisplayedWidth = width;\n      this.prevDisplayedHeight = height;\n      return true;\n    }\n\n    return false;\n  }\n  /** @returns {HTMLImageElement | HTMLDivElement | null | undefined} */\n\n\n  getPlaceholderElement() {\n    var _this$content$placeho;\n\n    return (_this$content$placeho = this.content.placeholder) === null || _this$content$placeho === void 0 ? void 0 : _this$content$placeho.element;\n  }\n  /**\n   * Zoom current slide image to...\n   *\n   * @param {number} destZoomLevel Destination zoom level.\n   * @param {Point} [centerPoint]\n   * Transform origin center point, or false if viewport center should be used.\n   * @param {number | false} [transitionDuration] Transition duration, may be set to 0.\n   * @param {boolean} [ignoreBounds] Minimum and maximum zoom levels will be ignored.\n   */\n\n\n  zoomTo(destZoomLevel, centerPoint, transitionDuration, ignoreBounds) {\n    const {\n      pswp\n    } = this;\n\n    if (!this.isZoomable() || pswp.mainScroll.isShifted()) {\n      return;\n    }\n\n    pswp.dispatch('beforeZoomTo', {\n      destZoomLevel,\n      centerPoint,\n      transitionDuration\n    }); // stop all pan and zoom transitions\n\n    pswp.animations.stopAllPan(); // if (!centerPoint) {\n    //   centerPoint = pswp.getViewportCenterPoint();\n    // }\n\n    const prevZoomLevel = this.currZoomLevel;\n\n    if (!ignoreBounds) {\n      destZoomLevel = clamp(destZoomLevel, this.zoomLevels.min, this.zoomLevels.max);\n    } // if (transitionDuration === undefined) {\n    //   transitionDuration = this.pswp.options.zoomAnimationDuration;\n    // }\n\n\n    this.setZoomLevel(destZoomLevel);\n    this.pan.x = this.calculateZoomToPanOffset('x', centerPoint, prevZoomLevel);\n    this.pan.y = this.calculateZoomToPanOffset('y', centerPoint, prevZoomLevel);\n    roundPoint(this.pan);\n\n    const finishTransition = () => {\n      this._setResolution(destZoomLevel);\n\n      this.applyCurrentZoomPan();\n    };\n\n    if (!transitionDuration) {\n      finishTransition();\n    } else {\n      pswp.animations.startTransition({\n        isPan: true,\n        name: 'zoomTo',\n        target: this.container,\n        transform: this.getCurrentTransform(),\n        onComplete: finishTransition,\n        duration: transitionDuration,\n        easing: pswp.options.easing\n      });\n    }\n  }\n  /**\n   * @param {Point} [centerPoint]\n   */\n\n\n  toggleZoom(centerPoint) {\n    this.zoomTo(this.currZoomLevel === this.zoomLevels.initial ? this.zoomLevels.secondary : this.zoomLevels.initial, centerPoint, this.pswp.options.zoomAnimationDuration);\n  }\n  /**\n   * Updates zoom level property and recalculates new pan bounds,\n   * unlike zoomTo it does not apply transform (use applyCurrentZoomPan)\n   *\n   * @param {number} currZoomLevel\n   */\n\n\n  setZoomLevel(currZoomLevel) {\n    this.currZoomLevel = currZoomLevel;\n    this.bounds.update(this.currZoomLevel);\n  }\n  /**\n   * Get pan position after zoom at a given `point`.\n   *\n   * Always call setZoomLevel(newZoomLevel) beforehand to recalculate\n   * pan bounds according to the new zoom level.\n   *\n   * @param {'x' | 'y'} axis\n   * @param {Point} [point]\n   * point based on which zoom is performed, usually refers to the current mouse position,\n   * if false - viewport center will be used.\n   * @param {number} [prevZoomLevel] Zoom level before new zoom was applied.\n   * @returns {number}\n   */\n\n\n  calculateZoomToPanOffset(axis, point, prevZoomLevel) {\n    const totalPanDistance = this.bounds.max[axis] - this.bounds.min[axis];\n\n    if (totalPanDistance === 0) {\n      return this.bounds.center[axis];\n    }\n\n    if (!point) {\n      point = this.pswp.getViewportCenterPoint();\n    }\n\n    if (!prevZoomLevel) {\n      prevZoomLevel = this.zoomLevels.initial;\n    }\n\n    const zoomFactor = this.currZoomLevel / prevZoomLevel;\n    return this.bounds.correctPan(axis, (this.pan[axis] - point[axis]) * zoomFactor + point[axis]);\n  }\n  /**\n   * Apply pan and keep it within bounds.\n   *\n   * @param {number} panX\n   * @param {number} panY\n   */\n\n\n  panTo(panX, panY) {\n    this.pan.x = this.bounds.correctPan('x', panX);\n    this.pan.y = this.bounds.correctPan('y', panY);\n    this.applyCurrentZoomPan();\n  }\n  /**\n   * If the slide in the current state can be panned by the user\n   * @returns {boolean}\n   */\n\n\n  isPannable() {\n    return Boolean(this.width) && this.currZoomLevel > this.zoomLevels.fit;\n  }\n  /**\n   * If the slide can be zoomed\n   * @returns {boolean}\n   */\n\n\n  isZoomable() {\n    return Boolean(this.width) && this.content.isZoomable();\n  }\n  /**\n   * Apply transform and scale based on\n   * the current pan position (this.pan) and zoom level (this.currZoomLevel)\n   */\n\n\n  applyCurrentZoomPan() {\n    this._applyZoomTransform(this.pan.x, this.pan.y, this.currZoomLevel);\n\n    if (this === this.pswp.currSlide) {\n      this.pswp.dispatch('zoomPanUpdate', {\n        slide: this\n      });\n    }\n  }\n\n  zoomAndPanToInitial() {\n    this.currZoomLevel = this.zoomLevels.initial; // pan according to the zoom level\n\n    this.bounds.update(this.currZoomLevel);\n    equalizePoints(this.pan, this.bounds.center);\n    this.pswp.dispatch('initialZoomPan', {\n      slide: this\n    });\n  }\n  /**\n   * Set translate and scale based on current resolution\n   *\n   * @param {number} x\n   * @param {number} y\n   * @param {number} zoom\n   * @private\n   */\n\n\n  _applyZoomTransform(x, y, zoom) {\n    zoom /= this.currentResolution || this.zoomLevels.initial;\n    setTransform(this.container, x, y, zoom);\n  }\n\n  calculateSize() {\n    const {\n      pswp\n    } = this;\n    equalizePoints(this.panAreaSize, getPanAreaSize(pswp.options, pswp.viewportSize, this.data, this.index));\n    this.zoomLevels.update(this.width, this.height, this.panAreaSize);\n    pswp.dispatch('calcSlideSize', {\n      slide: this\n    });\n  }\n  /** @returns {string} */\n\n\n  getCurrentTransform() {\n    const scale = this.currZoomLevel / (this.currentResolution || this.zoomLevels.initial);\n    return toTransformString(this.pan.x, this.pan.y, scale);\n  }\n  /**\n   * Set resolution and re-render the image.\n   *\n   * For example, if the real image size is 2000x1500,\n   * and resolution is 0.5 - it will be rendered as 1000x750.\n   *\n   * Image with zoom level 2 and resolution 0.5 is\n   * the same as image with zoom level 1 and resolution 1.\n   *\n   * Used to optimize animations and make\n   * sure that browser renders image in the highest quality.\n   * Also used by responsive images to load the correct one.\n   *\n   * @param {number} newResolution\n   */\n\n\n  _setResolution(newResolution) {\n    if (newResolution === this.currentResolution) {\n      return;\n    }\n\n    this.currentResolution = newResolution;\n    this.updateContentSize();\n    this.pswp.dispatch('resolutionChanged');\n  }\n\n}\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {import('./gestures.js').default} Gestures */\n\nconst PAN_END_FRICTION = 0.35;\nconst VERTICAL_DRAG_FRICTION = 0.6; // 1 corresponds to the third of viewport height\n\nconst MIN_RATIO_TO_CLOSE = 0.4; // Minimum speed required to navigate\n// to next or previous slide\n\nconst MIN_NEXT_SLIDE_SPEED = 0.5;\n/**\n * @param {number} initialVelocity\n * @param {number} decelerationRate\n * @returns {number}\n */\n\nfunction project(initialVelocity, decelerationRate) {\n  return initialVelocity * decelerationRate / (1 - decelerationRate);\n}\n/**\n * Handles single pointer dragging\n */\n\n\nclass DragHandler {\n  /**\n   * @param {Gestures} gestures\n   */\n  constructor(gestures) {\n    this.gestures = gestures;\n    this.pswp = gestures.pswp;\n    /** @type {Point} */\n\n    this.startPan = {\n      x: 0,\n      y: 0\n    };\n  }\n\n  start() {\n    if (this.pswp.currSlide) {\n      equalizePoints(this.startPan, this.pswp.currSlide.pan);\n    }\n\n    this.pswp.animations.stopAll();\n  }\n\n  change() {\n    const {\n      p1,\n      prevP1,\n      dragAxis\n    } = this.gestures;\n    const {\n      currSlide\n    } = this.pswp;\n\n    if (dragAxis === 'y' && this.pswp.options.closeOnVerticalDrag && currSlide && currSlide.currZoomLevel <= currSlide.zoomLevels.fit && !this.gestures.isMultitouch) {\n      // Handle vertical drag to close\n      const panY = currSlide.pan.y + (p1.y - prevP1.y);\n\n      if (!this.pswp.dispatch('verticalDrag', {\n        panY\n      }).defaultPrevented) {\n        this._setPanWithFriction('y', panY, VERTICAL_DRAG_FRICTION);\n\n        const bgOpacity = 1 - Math.abs(this._getVerticalDragRatio(currSlide.pan.y));\n        this.pswp.applyBgOpacity(bgOpacity);\n        currSlide.applyCurrentZoomPan();\n      }\n    } else {\n      const mainScrollChanged = this._panOrMoveMainScroll('x');\n\n      if (!mainScrollChanged) {\n        this._panOrMoveMainScroll('y');\n\n        if (currSlide) {\n          roundPoint(currSlide.pan);\n          currSlide.applyCurrentZoomPan();\n        }\n      }\n    }\n  }\n\n  end() {\n    const {\n      velocity\n    } = this.gestures;\n    const {\n      mainScroll,\n      currSlide\n    } = this.pswp;\n    let indexDiff = 0;\n    this.pswp.animations.stopAll(); // Handle main scroll if it's shifted\n\n    if (mainScroll.isShifted()) {\n      // Position of the main scroll relative to the viewport\n      const mainScrollShiftDiff = mainScroll.x - mainScroll.getCurrSlideX(); // Ratio between 0 and 1:\n      // 0 - slide is not visible at all,\n      // 0.5 - half of the slide is visible\n      // 1 - slide is fully visible\n\n      const currentSlideVisibilityRatio = mainScrollShiftDiff / this.pswp.viewportSize.x; // Go next slide.\n      //\n      // - if velocity and its direction is matched,\n      //   and we see at least tiny part of the next slide\n      //\n      // - or if we see less than 50% of the current slide\n      //   and velocity is close to 0\n      //\n\n      if (velocity.x < -MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio < 0 || velocity.x < 0.1 && currentSlideVisibilityRatio < -0.5) {\n        // Go to next slide\n        indexDiff = 1;\n        velocity.x = Math.min(velocity.x, 0);\n      } else if (velocity.x > MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio > 0 || velocity.x > -0.1 && currentSlideVisibilityRatio > 0.5) {\n        // Go to prev slide\n        indexDiff = -1;\n        velocity.x = Math.max(velocity.x, 0);\n      }\n\n      mainScroll.moveIndexBy(indexDiff, true, velocity.x);\n    } // Restore zoom level\n\n\n    if (currSlide && currSlide.currZoomLevel > currSlide.zoomLevels.max || this.gestures.isMultitouch) {\n      this.gestures.zoomLevels.correctZoomPan(true);\n    } else {\n      // we run two animations instead of one,\n      // as each axis has own pan boundaries and thus different spring function\n      // (correctZoomPan does not have this functionality,\n      //  it animates all properties with single timing function)\n      this._finishPanGestureForAxis('x');\n\n      this._finishPanGestureForAxis('y');\n    }\n  }\n  /**\n   * @private\n   * @param {'x' | 'y'} axis\n   */\n\n\n  _finishPanGestureForAxis(axis) {\n    const {\n      velocity\n    } = this.gestures;\n    const {\n      currSlide\n    } = this.pswp;\n\n    if (!currSlide) {\n      return;\n    }\n\n    const {\n      pan,\n      bounds\n    } = currSlide;\n    const panPos = pan[axis];\n    const restoreBgOpacity = this.pswp.bgOpacity < 1 && axis === 'y'; // 0.995 means - scroll view loses 0.5% of its velocity per millisecond\n    // Increasing this number will reduce travel distance\n\n    const decelerationRate = 0.995; // 0.99\n    // Pan position if there is no bounds\n\n    const projectedPosition = panPos + project(velocity[axis], decelerationRate);\n\n    if (restoreBgOpacity) {\n      const vDragRatio = this._getVerticalDragRatio(panPos);\n\n      const projectedVDragRatio = this._getVerticalDragRatio(projectedPosition); // If we are above and moving upwards,\n      // or if we are below and moving downwards\n\n\n      if (vDragRatio < 0 && projectedVDragRatio < -MIN_RATIO_TO_CLOSE || vDragRatio > 0 && projectedVDragRatio > MIN_RATIO_TO_CLOSE) {\n        this.pswp.close();\n        return;\n      }\n    } // Pan position with corrected bounds\n\n\n    const correctedPanPosition = bounds.correctPan(axis, projectedPosition); // Exit if pan position should not be changed\n    // or if speed it too low\n\n    if (panPos === correctedPanPosition) {\n      return;\n    } // Overshoot if the final position is out of pan bounds\n\n\n    const dampingRatio = correctedPanPosition === projectedPosition ? 1 : 0.82;\n    const initialBgOpacity = this.pswp.bgOpacity;\n    const totalPanDist = correctedPanPosition - panPos;\n    this.pswp.animations.startSpring({\n      name: 'panGesture' + axis,\n      isPan: true,\n      start: panPos,\n      end: correctedPanPosition,\n      velocity: velocity[axis],\n      dampingRatio,\n      onUpdate: pos => {\n        // Animate opacity of background relative to Y pan position of an image\n        if (restoreBgOpacity && this.pswp.bgOpacity < 1) {\n          // 0 - start of animation, 1 - end of animation\n          const animationProgressRatio = 1 - (correctedPanPosition - pos) / totalPanDist; // We clamp opacity to keep it between 0 and 1.\n          // As progress ratio can be larger than 1 due to overshoot,\n          // and we do not want to bounce opacity.\n\n          this.pswp.applyBgOpacity(clamp(initialBgOpacity + (1 - initialBgOpacity) * animationProgressRatio, 0, 1));\n        }\n\n        pan[axis] = Math.floor(pos);\n        currSlide.applyCurrentZoomPan();\n      }\n    });\n  }\n  /**\n   * Update position of the main scroll,\n   * or/and update pan position of the current slide.\n   *\n   * Should return true if it changes (or can change) main scroll.\n   *\n   * @private\n   * @param {'x' | 'y'} axis\n   * @returns {boolean}\n   */\n\n\n  _panOrMoveMainScroll(axis) {\n    const {\n      p1,\n      dragAxis,\n      prevP1,\n      isMultitouch\n    } = this.gestures;\n    const {\n      currSlide,\n      mainScroll\n    } = this.pswp;\n    const delta = p1[axis] - prevP1[axis];\n    const newMainScrollX = mainScroll.x + delta;\n\n    if (!delta || !currSlide) {\n      return false;\n    } // Always move main scroll if image can not be panned\n\n\n    if (axis === 'x' && !currSlide.isPannable() && !isMultitouch) {\n      mainScroll.moveTo(newMainScrollX, true);\n      return true; // changed main scroll\n    }\n\n    const {\n      bounds\n    } = currSlide;\n    const newPan = currSlide.pan[axis] + delta;\n\n    if (this.pswp.options.allowPanToNext && dragAxis === 'x' && axis === 'x' && !isMultitouch) {\n      const currSlideMainScrollX = mainScroll.getCurrSlideX(); // Position of the main scroll relative to the viewport\n\n      const mainScrollShiftDiff = mainScroll.x - currSlideMainScrollX;\n      const isLeftToRight = delta > 0;\n      const isRightToLeft = !isLeftToRight;\n\n      if (newPan > bounds.min[axis] && isLeftToRight) {\n        // Panning from left to right, beyond the left edge\n        // Wether the image was at minimum pan position (or less)\n        // when this drag gesture started.\n        // Minimum pan position refers to the left edge of the image.\n        const wasAtMinPanPosition = bounds.min[axis] <= this.startPan[axis];\n\n        if (wasAtMinPanPosition) {\n          mainScroll.moveTo(newMainScrollX, true);\n          return true;\n        } else {\n          this._setPanWithFriction(axis, newPan); //currSlide.pan[axis] = newPan;\n\n        }\n      } else if (newPan < bounds.max[axis] && isRightToLeft) {\n        // Paning from right to left, beyond the right edge\n        // Maximum pan position refers to the right edge of the image.\n        const wasAtMaxPanPosition = this.startPan[axis] <= bounds.max[axis];\n\n        if (wasAtMaxPanPosition) {\n          mainScroll.moveTo(newMainScrollX, true);\n          return true;\n        } else {\n          this._setPanWithFriction(axis, newPan); //currSlide.pan[axis] = newPan;\n\n        }\n      } else {\n        // If main scroll is shifted\n        if (mainScrollShiftDiff !== 0) {\n          // If main scroll is shifted right\n          if (mainScrollShiftDiff > 0\n          /*&& isRightToLeft*/\n          ) {\n            mainScroll.moveTo(Math.max(newMainScrollX, currSlideMainScrollX), true);\n            return true;\n          } else if (mainScrollShiftDiff < 0\n          /*&& isLeftToRight*/\n          ) {\n            // Main scroll is shifted left (Position is less than 0 comparing to the viewport 0)\n            mainScroll.moveTo(Math.min(newMainScrollX, currSlideMainScrollX), true);\n            return true;\n          }\n        } else {\n          // We are within pan bounds, so just pan\n          this._setPanWithFriction(axis, newPan);\n        }\n      }\n    } else {\n      if (axis === 'y') {\n        // Do not pan vertically if main scroll is shifted o\n        if (!mainScroll.isShifted() && bounds.min.y !== bounds.max.y) {\n          this._setPanWithFriction(axis, newPan);\n        }\n      } else {\n        this._setPanWithFriction(axis, newPan);\n      }\n    }\n\n    return false;\n  } // If we move above - the ratio is negative\n  // If we move below the ratio is positive\n\n  /**\n   * Relation between pan Y position and third of viewport height.\n   *\n   * When we are at initial position (center bounds) - the ratio is 0,\n   * if position is shifted upwards - the ratio is negative,\n   * if position is shifted downwards - the ratio is positive.\n   *\n   * @private\n   * @param {number} panY The current pan Y position.\n   * @returns {number}\n   */\n\n\n  _getVerticalDragRatio(panY) {\n    var _this$pswp$currSlide$, _this$pswp$currSlide;\n\n    return (panY - ((_this$pswp$currSlide$ = (_this$pswp$currSlide = this.pswp.currSlide) === null || _this$pswp$currSlide === void 0 ? void 0 : _this$pswp$currSlide.bounds.center.y) !== null && _this$pswp$currSlide$ !== void 0 ? _this$pswp$currSlide$ : 0)) / (this.pswp.viewportSize.y / 3);\n  }\n  /**\n   * Set pan position of the current slide.\n   * Apply friction if the position is beyond the pan bounds,\n   * or if custom friction is defined.\n   *\n   * @private\n   * @param {'x' | 'y'} axis\n   * @param {number} potentialPan\n   * @param {number} [customFriction] (0.1 - 1)\n   */\n\n\n  _setPanWithFriction(axis, potentialPan, customFriction) {\n    const {\n      currSlide\n    } = this.pswp;\n\n    if (!currSlide) {\n      return;\n    }\n\n    const {\n      pan,\n      bounds\n    } = currSlide;\n    const correctedPan = bounds.correctPan(axis, potentialPan); // If we are out of pan bounds\n\n    if (correctedPan !== potentialPan || customFriction) {\n      const delta = Math.round(potentialPan - pan[axis]);\n      pan[axis] += delta * (customFriction || PAN_END_FRICTION);\n    } else {\n      pan[axis] = potentialPan;\n    }\n  }\n\n}\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {import('./gestures.js').default} Gestures */\n\nconst UPPER_ZOOM_FRICTION = 0.05;\nconst LOWER_ZOOM_FRICTION = 0.15;\n/**\n * Get center point between two points\n *\n * @param {Point} p\n * @param {Point} p1\n * @param {Point} p2\n * @returns {Point}\n */\n\nfunction getZoomPointsCenter(p, p1, p2) {\n  p.x = (p1.x + p2.x) / 2;\n  p.y = (p1.y + p2.y) / 2;\n  return p;\n}\n\nclass ZoomHandler {\n  /**\n   * @param {Gestures} gestures\n   */\n  constructor(gestures) {\n    this.gestures = gestures;\n    /**\n     * @private\n     * @type {Point}\n     */\n\n    this._startPan = {\n      x: 0,\n      y: 0\n    };\n    /**\n     * @private\n     * @type {Point}\n     */\n\n    this._startZoomPoint = {\n      x: 0,\n      y: 0\n    };\n    /**\n     * @private\n     * @type {Point}\n     */\n\n    this._zoomPoint = {\n      x: 0,\n      y: 0\n    };\n    /** @private */\n\n    this._wasOverFitZoomLevel = false;\n    /** @private */\n\n    this._startZoomLevel = 1;\n  }\n\n  start() {\n    const {\n      currSlide\n    } = this.gestures.pswp;\n\n    if (currSlide) {\n      this._startZoomLevel = currSlide.currZoomLevel;\n      equalizePoints(this._startPan, currSlide.pan);\n    }\n\n    this.gestures.pswp.animations.stopAllPan();\n    this._wasOverFitZoomLevel = false;\n  }\n\n  change() {\n    const {\n      p1,\n      startP1,\n      p2,\n      startP2,\n      pswp\n    } = this.gestures;\n    const {\n      currSlide\n    } = pswp;\n\n    if (!currSlide) {\n      return;\n    }\n\n    const minZoomLevel = currSlide.zoomLevels.min;\n    const maxZoomLevel = currSlide.zoomLevels.max;\n\n    if (!currSlide.isZoomable() || pswp.mainScroll.isShifted()) {\n      return;\n    }\n\n    getZoomPointsCenter(this._startZoomPoint, startP1, startP2);\n    getZoomPointsCenter(this._zoomPoint, p1, p2);\n\n    let currZoomLevel = 1 / getDistanceBetween(startP1, startP2) * getDistanceBetween(p1, p2) * this._startZoomLevel; // slightly over the zoom.fit\n\n\n    if (currZoomLevel > currSlide.zoomLevels.initial + currSlide.zoomLevels.initial / 15) {\n      this._wasOverFitZoomLevel = true;\n    }\n\n    if (currZoomLevel < minZoomLevel) {\n      if (pswp.options.pinchToClose && !this._wasOverFitZoomLevel && this._startZoomLevel <= currSlide.zoomLevels.initial) {\n        // fade out background if zooming out\n        const bgOpacity = 1 - (minZoomLevel - currZoomLevel) / (minZoomLevel / 1.2);\n\n        if (!pswp.dispatch('pinchClose', {\n          bgOpacity\n        }).defaultPrevented) {\n          pswp.applyBgOpacity(bgOpacity);\n        }\n      } else {\n        // Apply the friction if zoom level is below the min\n        currZoomLevel = minZoomLevel - (minZoomLevel - currZoomLevel) * LOWER_ZOOM_FRICTION;\n      }\n    } else if (currZoomLevel > maxZoomLevel) {\n      // Apply the friction if zoom level is above the max\n      currZoomLevel = maxZoomLevel + (currZoomLevel - maxZoomLevel) * UPPER_ZOOM_FRICTION;\n    }\n\n    currSlide.pan.x = this._calculatePanForZoomLevel('x', currZoomLevel);\n    currSlide.pan.y = this._calculatePanForZoomLevel('y', currZoomLevel);\n    currSlide.setZoomLevel(currZoomLevel);\n    currSlide.applyCurrentZoomPan();\n  }\n\n  end() {\n    const {\n      pswp\n    } = this.gestures;\n    const {\n      currSlide\n    } = pswp;\n\n    if ((!currSlide || currSlide.currZoomLevel < currSlide.zoomLevels.initial) && !this._wasOverFitZoomLevel && pswp.options.pinchToClose) {\n      pswp.close();\n    } else {\n      this.correctZoomPan();\n    }\n  }\n  /**\n   * @private\n   * @param {'x' | 'y'} axis\n   * @param {number} currZoomLevel\n   * @returns {number}\n   */\n\n\n  _calculatePanForZoomLevel(axis, currZoomLevel) {\n    const zoomFactor = currZoomLevel / this._startZoomLevel;\n    return this._zoomPoint[axis] - (this._startZoomPoint[axis] - this._startPan[axis]) * zoomFactor;\n  }\n  /**\n   * Correct currZoomLevel and pan if they are\n   * beyond minimum or maximum values.\n   * With animation.\n   *\n   * @param {boolean} [ignoreGesture]\n   * Wether gesture coordinates should be ignored when calculating destination pan position.\n   */\n\n\n  correctZoomPan(ignoreGesture) {\n    const {\n      pswp\n    } = this.gestures;\n    const {\n      currSlide\n    } = pswp;\n\n    if (!(currSlide !== null && currSlide !== void 0 && currSlide.isZoomable())) {\n      return;\n    }\n\n    if (this._zoomPoint.x === 0) {\n      ignoreGesture = true;\n    }\n\n    const prevZoomLevel = currSlide.currZoomLevel;\n    /** @type {number} */\n\n    let destinationZoomLevel;\n    let currZoomLevelNeedsChange = true;\n\n    if (prevZoomLevel < currSlide.zoomLevels.initial) {\n      destinationZoomLevel = currSlide.zoomLevels.initial; // zoom to min\n    } else if (prevZoomLevel > currSlide.zoomLevels.max) {\n      destinationZoomLevel = currSlide.zoomLevels.max; // zoom to max\n    } else {\n      currZoomLevelNeedsChange = false;\n      destinationZoomLevel = prevZoomLevel;\n    }\n\n    const initialBgOpacity = pswp.bgOpacity;\n    const restoreBgOpacity = pswp.bgOpacity < 1;\n    const initialPan = equalizePoints({\n      x: 0,\n      y: 0\n    }, currSlide.pan);\n    let destinationPan = equalizePoints({\n      x: 0,\n      y: 0\n    }, initialPan);\n\n    if (ignoreGesture) {\n      this._zoomPoint.x = 0;\n      this._zoomPoint.y = 0;\n      this._startZoomPoint.x = 0;\n      this._startZoomPoint.y = 0;\n      this._startZoomLevel = prevZoomLevel;\n      equalizePoints(this._startPan, initialPan);\n    }\n\n    if (currZoomLevelNeedsChange) {\n      destinationPan = {\n        x: this._calculatePanForZoomLevel('x', destinationZoomLevel),\n        y: this._calculatePanForZoomLevel('y', destinationZoomLevel)\n      };\n    } // set zoom level, so pan bounds are updated according to it\n\n\n    currSlide.setZoomLevel(destinationZoomLevel);\n    destinationPan = {\n      x: currSlide.bounds.correctPan('x', destinationPan.x),\n      y: currSlide.bounds.correctPan('y', destinationPan.y)\n    }; // return zoom level and its bounds to initial\n\n    currSlide.setZoomLevel(prevZoomLevel);\n    const panNeedsChange = !pointsEqual(destinationPan, initialPan);\n\n    if (!panNeedsChange && !currZoomLevelNeedsChange && !restoreBgOpacity) {\n      // update resolution after gesture\n      currSlide._setResolution(destinationZoomLevel);\n\n      currSlide.applyCurrentZoomPan(); // nothing to animate\n\n      return;\n    }\n\n    pswp.animations.stopAllPan();\n    pswp.animations.startSpring({\n      isPan: true,\n      start: 0,\n      end: 1000,\n      velocity: 0,\n      dampingRatio: 1,\n      naturalFrequency: 40,\n      onUpdate: now => {\n        now /= 1000; // 0 - start, 1 - end\n\n        if (panNeedsChange || currZoomLevelNeedsChange) {\n          if (panNeedsChange) {\n            currSlide.pan.x = initialPan.x + (destinationPan.x - initialPan.x) * now;\n            currSlide.pan.y = initialPan.y + (destinationPan.y - initialPan.y) * now;\n          }\n\n          if (currZoomLevelNeedsChange) {\n            const newZoomLevel = prevZoomLevel + (destinationZoomLevel - prevZoomLevel) * now;\n            currSlide.setZoomLevel(newZoomLevel);\n          }\n\n          currSlide.applyCurrentZoomPan();\n        } // Restore background opacity\n\n\n        if (restoreBgOpacity && pswp.bgOpacity < 1) {\n          // We clamp opacity to keep it between 0 and 1.\n          // As progress ratio can be larger than 1 due to overshoot,\n          // and we do not want to bounce opacity.\n          pswp.applyBgOpacity(clamp(initialBgOpacity + (1 - initialBgOpacity) * now, 0, 1));\n        }\n      },\n      onComplete: () => {\n        // update resolution after transition ends\n        currSlide._setResolution(destinationZoomLevel);\n\n        currSlide.applyCurrentZoomPan();\n      }\n    });\n  }\n\n}\n\n/**\n * @template {string} T\n * @template {string} P\n * @typedef {import('../types.js').AddPostfix<T, P>} AddPostfix<T, P>\n */\n\n/** @typedef {import('./gestures.js').default} Gestures */\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {'imageClick' | 'bgClick' | 'tap' | 'doubleTap'} Actions */\n\n/**\n * Whether the tap was performed on the main slide\n * (rather than controls or caption).\n *\n * @param {PointerEvent} event\n * @returns {boolean}\n */\nfunction didTapOnMainContent(event) {\n  return !!\n  /** @type {HTMLElement} */\n  event.target.closest('.pswp__container');\n}\n/**\n * Tap, double-tap handler.\n */\n\n\nclass TapHandler {\n  /**\n   * @param {Gestures} gestures\n   */\n  constructor(gestures) {\n    this.gestures = gestures;\n  }\n  /**\n   * @param {Point} point\n   * @param {PointerEvent} originalEvent\n   */\n\n\n  click(point, originalEvent) {\n    const targetClassList =\n    /** @type {HTMLElement} */\n    originalEvent.target.classList;\n    const isImageClick = targetClassList.contains('pswp__img');\n    const isBackgroundClick = targetClassList.contains('pswp__item') || targetClassList.contains('pswp__zoom-wrap');\n\n    if (isImageClick) {\n      this._doClickOrTapAction('imageClick', point, originalEvent);\n    } else if (isBackgroundClick) {\n      this._doClickOrTapAction('bgClick', point, originalEvent);\n    }\n  }\n  /**\n   * @param {Point} point\n   * @param {PointerEvent} originalEvent\n   */\n\n\n  tap(point, originalEvent) {\n    if (didTapOnMainContent(originalEvent)) {\n      this._doClickOrTapAction('tap', point, originalEvent);\n    }\n  }\n  /**\n   * @param {Point} point\n   * @param {PointerEvent} originalEvent\n   */\n\n\n  doubleTap(point, originalEvent) {\n    if (didTapOnMainContent(originalEvent)) {\n      this._doClickOrTapAction('doubleTap', point, originalEvent);\n    }\n  }\n  /**\n   * @private\n   * @param {Actions} actionName\n   * @param {Point} point\n   * @param {PointerEvent} originalEvent\n   */\n\n\n  _doClickOrTapAction(actionName, point, originalEvent) {\n    var _this$gestures$pswp$e;\n\n    const {\n      pswp\n    } = this.gestures;\n    const {\n      currSlide\n    } = pswp;\n    const actionFullName =\n    /** @type {AddPostfix<Actions, 'Action'>} */\n    actionName + 'Action';\n    const optionValue = pswp.options[actionFullName];\n\n    if (pswp.dispatch(actionFullName, {\n      point,\n      originalEvent\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (typeof optionValue === 'function') {\n      optionValue.call(pswp, point, originalEvent);\n      return;\n    }\n\n    switch (optionValue) {\n      case 'close':\n      case 'next':\n        pswp[optionValue]();\n        break;\n\n      case 'zoom':\n        currSlide === null || currSlide === void 0 || currSlide.toggleZoom(point);\n        break;\n\n      case 'zoom-or-close':\n        // by default click zooms current image,\n        // if it can not be zoomed - gallery will be closed\n        if (currSlide !== null && currSlide !== void 0 && currSlide.isZoomable() && currSlide.zoomLevels.secondary !== currSlide.zoomLevels.initial) {\n          currSlide.toggleZoom(point);\n        } else if (pswp.options.clickToCloseNonZoomable) {\n          pswp.close();\n        }\n\n        break;\n\n      case 'toggle-controls':\n        (_this$gestures$pswp$e = this.gestures.pswp.element) === null || _this$gestures$pswp$e === void 0 || _this$gestures$pswp$e.classList.toggle('pswp--ui-visible'); // if (_controlsVisible) {\n        //   _ui.hideControls();\n        // } else {\n        //   _ui.showControls();\n        // }\n\n        break;\n    }\n  }\n\n}\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n// How far should user should drag\n// until we can determine that the gesture is swipe and its direction\n\nconst AXIS_SWIPE_HYSTERISIS = 10; //const PAN_END_FRICTION = 0.35;\n\nconst DOUBLE_TAP_DELAY = 300; // ms\n\nconst MIN_TAP_DISTANCE = 25; // px\n\n/**\n * Gestures class bind touch, pointer or mouse events\n * and emits drag to drag-handler and zoom events zoom-handler.\n *\n * Drag and zoom events are emited in requestAnimationFrame,\n * and only when one of pointers was actually changed.\n */\n\nclass Gestures {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    /** @type {'x' | 'y' | null} */\n\n    this.dragAxis = null; // point objects are defined once and reused\n    // PhotoSwipe keeps track only of two pointers, others are ignored\n\n    /** @type {Point} */\n\n    this.p1 = {\n      x: 0,\n      y: 0\n    }; // the first pressed pointer\n\n    /** @type {Point} */\n\n    this.p2 = {\n      x: 0,\n      y: 0\n    }; // the second pressed pointer\n\n    /** @type {Point} */\n\n    this.prevP1 = {\n      x: 0,\n      y: 0\n    };\n    /** @type {Point} */\n\n    this.prevP2 = {\n      x: 0,\n      y: 0\n    };\n    /** @type {Point} */\n\n    this.startP1 = {\n      x: 0,\n      y: 0\n    };\n    /** @type {Point} */\n\n    this.startP2 = {\n      x: 0,\n      y: 0\n    };\n    /** @type {Point} */\n\n    this.velocity = {\n      x: 0,\n      y: 0\n    };\n    /** @type {Point}\n     * @private\n     */\n\n    this._lastStartP1 = {\n      x: 0,\n      y: 0\n    };\n    /** @type {Point}\n     * @private\n     */\n\n    this._intervalP1 = {\n      x: 0,\n      y: 0\n    };\n    /** @private */\n\n    this._numActivePoints = 0;\n    /** @type {Point[]}\n     * @private\n     */\n\n    this._ongoingPointers = [];\n    /** @private */\n\n    this._touchEventEnabled = 'ontouchstart' in window;\n    /** @private */\n\n    this._pointerEventEnabled = !!window.PointerEvent;\n    this.supportsTouch = this._touchEventEnabled || this._pointerEventEnabled && navigator.maxTouchPoints > 1;\n    /** @private */\n\n    this._numActivePoints = 0;\n    /** @private */\n\n    this._intervalTime = 0;\n    /** @private */\n\n    this._velocityCalculated = false;\n    this.isMultitouch = false;\n    this.isDragging = false;\n    this.isZooming = false;\n    /** @type {number | null} */\n\n    this.raf = null;\n    /** @type {NodeJS.Timeout | null}\n     * @private\n     */\n\n    this._tapTimer = null;\n\n    if (!this.supportsTouch) {\n      // disable pan to next slide for non-touch devices\n      pswp.options.allowPanToNext = false;\n    }\n\n    this.drag = new DragHandler(this);\n    this.zoomLevels = new ZoomHandler(this);\n    this.tapHandler = new TapHandler(this);\n    pswp.on('bindEvents', () => {\n      pswp.events.add(pswp.scrollWrap, 'click',\n      /** @type EventListener */\n      this._onClick.bind(this));\n\n      if (this._pointerEventEnabled) {\n        this._bindEvents('pointer', 'down', 'up', 'cancel');\n      } else if (this._touchEventEnabled) {\n        this._bindEvents('touch', 'start', 'end', 'cancel'); // In previous versions we also bound mouse event here,\n        // in case device supports both touch and mouse events,\n        // but newer versions of browsers now support PointerEvent.\n        // on iOS10 if you bind touchmove/end after touchstart,\n        // and you don't preventDefault touchstart (which PhotoSwipe does),\n        // preventDefault will have no effect on touchmove and touchend.\n        // Unless you bind it previously.\n\n\n        if (pswp.scrollWrap) {\n          pswp.scrollWrap.ontouchmove = () => {};\n\n          pswp.scrollWrap.ontouchend = () => {};\n        }\n      } else {\n        this._bindEvents('mouse', 'down', 'up');\n      }\n    });\n  }\n  /**\n   * @private\n   * @param {'mouse' | 'touch' | 'pointer'} pref\n   * @param {'down' | 'start'} down\n   * @param {'up' | 'end'} up\n   * @param {'cancel'} [cancel]\n   */\n\n\n  _bindEvents(pref, down, up, cancel) {\n    const {\n      pswp\n    } = this;\n    const {\n      events\n    } = pswp;\n    const cancelEvent = cancel ? pref + cancel : '';\n    events.add(pswp.scrollWrap, pref + down,\n    /** @type EventListener */\n    this.onPointerDown.bind(this));\n    events.add(window, pref + 'move',\n    /** @type EventListener */\n    this.onPointerMove.bind(this));\n    events.add(window, pref + up,\n    /** @type EventListener */\n    this.onPointerUp.bind(this));\n\n    if (cancelEvent) {\n      events.add(pswp.scrollWrap, cancelEvent,\n      /** @type EventListener */\n      this.onPointerUp.bind(this));\n    }\n  }\n  /**\n   * @param {PointerEvent} e\n   */\n\n\n  onPointerDown(e) {\n    // We do not call preventDefault for touch events\n    // to allow browser to show native dialog on longpress\n    // (the one that allows to save image or open it in new tab).\n    //\n    // Desktop Safari allows to drag images when preventDefault isn't called on mousedown,\n    // even though preventDefault IS called on mousemove. That's why we preventDefault mousedown.\n    const isMousePointer = e.type === 'mousedown' || e.pointerType === 'mouse'; // Allow dragging only via left mouse button.\n    // http://www.quirksmode.org/js/events_properties.html\n    // https://developer.mozilla.org/en-US/docs/Web/API/event.button\n\n    if (isMousePointer && e.button > 0) {\n      return;\n    }\n\n    const {\n      pswp\n    } = this; // if PhotoSwipe is opening or closing\n\n    if (!pswp.opener.isOpen) {\n      e.preventDefault();\n      return;\n    }\n\n    if (pswp.dispatch('pointerDown', {\n      originalEvent: e\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (isMousePointer) {\n      pswp.mouseDetected(); // preventDefault mouse event to prevent\n      // browser image drag feature\n\n      this._preventPointerEventBehaviour(e, 'down');\n    }\n\n    pswp.animations.stopAll();\n\n    this._updatePoints(e, 'down');\n\n    if (this._numActivePoints === 1) {\n      this.dragAxis = null; // we need to store initial point to determine the main axis,\n      // drag is activated only after the axis is determined\n\n      equalizePoints(this.startP1, this.p1);\n    }\n\n    if (this._numActivePoints > 1) {\n      // Tap or double tap should not trigger if more than one pointer\n      this._clearTapTimer();\n\n      this.isMultitouch = true;\n    } else {\n      this.isMultitouch = false;\n    }\n  }\n  /**\n   * @param {PointerEvent} e\n   */\n\n\n  onPointerMove(e) {\n    this._preventPointerEventBehaviour(e, 'move');\n\n    if (!this._numActivePoints) {\n      return;\n    }\n\n    this._updatePoints(e, 'move');\n\n    if (this.pswp.dispatch('pointerMove', {\n      originalEvent: e\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (this._numActivePoints === 1 && !this.isDragging) {\n      if (!this.dragAxis) {\n        this._calculateDragDirection();\n      } // Drag axis was detected, emit drag.start\n\n\n      if (this.dragAxis && !this.isDragging) {\n        if (this.isZooming) {\n          this.isZooming = false;\n          this.zoomLevels.end();\n        }\n\n        this.isDragging = true;\n\n        this._clearTapTimer(); // Tap can not trigger after drag\n        // Adjust starting point\n\n\n        this._updateStartPoints();\n\n        this._intervalTime = Date.now(); //this._startTime = this._intervalTime;\n\n        this._velocityCalculated = false;\n        equalizePoints(this._intervalP1, this.p1);\n        this.velocity.x = 0;\n        this.velocity.y = 0;\n        this.drag.start();\n\n        this._rafStopLoop();\n\n        this._rafRenderLoop();\n      }\n    } else if (this._numActivePoints > 1 && !this.isZooming) {\n      this._finishDrag();\n\n      this.isZooming = true; // Adjust starting points\n\n      this._updateStartPoints();\n\n      this.zoomLevels.start();\n\n      this._rafStopLoop();\n\n      this._rafRenderLoop();\n    }\n  }\n  /**\n   * @private\n   */\n\n\n  _finishDrag() {\n    if (this.isDragging) {\n      this.isDragging = false; // Try to calculate velocity,\n      // if it wasn't calculated yet in drag.change\n\n      if (!this._velocityCalculated) {\n        this._updateVelocity(true);\n      }\n\n      this.drag.end();\n      this.dragAxis = null;\n    }\n  }\n  /**\n   * @param {PointerEvent} e\n   */\n\n\n  onPointerUp(e) {\n    if (!this._numActivePoints) {\n      return;\n    }\n\n    this._updatePoints(e, 'up');\n\n    if (this.pswp.dispatch('pointerUp', {\n      originalEvent: e\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (this._numActivePoints === 0) {\n      this._rafStopLoop();\n\n      if (this.isDragging) {\n        this._finishDrag();\n      } else if (!this.isZooming && !this.isMultitouch) {\n        //this.zoomLevels.correctZoomPan();\n        this._finishTap(e);\n      }\n    }\n\n    if (this._numActivePoints < 2 && this.isZooming) {\n      this.isZooming = false;\n      this.zoomLevels.end();\n\n      if (this._numActivePoints === 1) {\n        // Since we have 1 point left, we need to reinitiate drag\n        this.dragAxis = null;\n\n        this._updateStartPoints();\n      }\n    }\n  }\n  /**\n   * @private\n   */\n\n\n  _rafRenderLoop() {\n    if (this.isDragging || this.isZooming) {\n      this._updateVelocity();\n\n      if (this.isDragging) {\n        // make sure that pointer moved since the last update\n        if (!pointsEqual(this.p1, this.prevP1)) {\n          this.drag.change();\n        }\n      } else\n        /* if (this.isZooming) */\n        {\n          if (!pointsEqual(this.p1, this.prevP1) || !pointsEqual(this.p2, this.prevP2)) {\n            this.zoomLevels.change();\n          }\n        }\n\n      this._updatePrevPoints();\n\n      this.raf = requestAnimationFrame(this._rafRenderLoop.bind(this));\n    }\n  }\n  /**\n   * Update velocity at 50ms interval\n   *\n   * @private\n   * @param {boolean} [force]\n   */\n\n\n  _updateVelocity(force) {\n    const time = Date.now();\n    const duration = time - this._intervalTime;\n\n    if (duration < 50 && !force) {\n      return;\n    }\n\n    this.velocity.x = this._getVelocity('x', duration);\n    this.velocity.y = this._getVelocity('y', duration);\n    this._intervalTime = time;\n    equalizePoints(this._intervalP1, this.p1);\n    this._velocityCalculated = true;\n  }\n  /**\n   * @private\n   * @param {PointerEvent} e\n   */\n\n\n  _finishTap(e) {\n    const {\n      mainScroll\n    } = this.pswp; // Do not trigger tap events if main scroll is shifted\n\n    if (mainScroll.isShifted()) {\n      // restore main scroll position\n      // (usually happens if stopped in the middle of animation)\n      mainScroll.moveIndexBy(0, true);\n      return;\n    } // Do not trigger tap for touchcancel or pointercancel\n\n\n    if (e.type.indexOf('cancel') > 0) {\n      return;\n    } // Trigger click instead of tap for mouse events\n\n\n    if (e.type === 'mouseup' || e.pointerType === 'mouse') {\n      this.tapHandler.click(this.startP1, e);\n      return;\n    } // Disable delay if there is no doubleTapAction\n\n\n    const tapDelay = this.pswp.options.doubleTapAction ? DOUBLE_TAP_DELAY : 0; // If tapTimer is defined - we tapped recently,\n    // check if the current tap is close to the previous one,\n    // if yes - trigger double tap\n\n    if (this._tapTimer) {\n      this._clearTapTimer(); // Check if two taps were more or less on the same place\n\n\n      if (getDistanceBetween(this._lastStartP1, this.startP1) < MIN_TAP_DISTANCE) {\n        this.tapHandler.doubleTap(this.startP1, e);\n      }\n    } else {\n      equalizePoints(this._lastStartP1, this.startP1);\n      this._tapTimer = setTimeout(() => {\n        this.tapHandler.tap(this.startP1, e);\n\n        this._clearTapTimer();\n      }, tapDelay);\n    }\n  }\n  /**\n   * @private\n   */\n\n\n  _clearTapTimer() {\n    if (this._tapTimer) {\n      clearTimeout(this._tapTimer);\n      this._tapTimer = null;\n    }\n  }\n  /**\n   * Get velocity for axis\n   *\n   * @private\n   * @param {'x' | 'y'} axis\n   * @param {number} duration\n   * @returns {number}\n   */\n\n\n  _getVelocity(axis, duration) {\n    // displacement is like distance, but can be negative.\n    const displacement = this.p1[axis] - this._intervalP1[axis];\n\n    if (Math.abs(displacement) > 1 && duration > 5) {\n      return displacement / duration;\n    }\n\n    return 0;\n  }\n  /**\n   * @private\n   */\n\n\n  _rafStopLoop() {\n    if (this.raf) {\n      cancelAnimationFrame(this.raf);\n      this.raf = null;\n    }\n  }\n  /**\n   * @private\n   * @param {PointerEvent} e\n   * @param {'up' | 'down' | 'move'} pointerType Normalized pointer type\n   */\n\n\n  _preventPointerEventBehaviour(e, pointerType) {\n    const preventPointerEvent = this.pswp.applyFilters('preventPointerEvent', true, e, pointerType);\n\n    if (preventPointerEvent) {\n      e.preventDefault();\n    }\n  }\n  /**\n   * Parses and normalizes points from the touch, mouse or pointer event.\n   * Updates p1 and p2.\n   *\n   * @private\n   * @param {PointerEvent | TouchEvent} e\n   * @param {'up' | 'down' | 'move'} pointerType Normalized pointer type\n   */\n\n\n  _updatePoints(e, pointerType) {\n    if (this._pointerEventEnabled) {\n      const pointerEvent =\n      /** @type {PointerEvent} */\n      e; // Try to find the current pointer in ongoing pointers by its ID\n\n      const pointerIndex = this._ongoingPointers.findIndex(ongoingPointer => {\n        return ongoingPointer.id === pointerEvent.pointerId;\n      });\n\n      if (pointerType === 'up' && pointerIndex > -1) {\n        // release the pointer - remove it from ongoing\n        this._ongoingPointers.splice(pointerIndex, 1);\n      } else if (pointerType === 'down' && pointerIndex === -1) {\n        // add new pointer\n        this._ongoingPointers.push(this._convertEventPosToPoint(pointerEvent, {\n          x: 0,\n          y: 0\n        }));\n      } else if (pointerIndex > -1) {\n        // update existing pointer\n        this._convertEventPosToPoint(pointerEvent, this._ongoingPointers[pointerIndex]);\n      }\n\n      this._numActivePoints = this._ongoingPointers.length; // update points that PhotoSwipe uses\n      // to calculate position and scale\n\n      if (this._numActivePoints > 0) {\n        equalizePoints(this.p1, this._ongoingPointers[0]);\n      }\n\n      if (this._numActivePoints > 1) {\n        equalizePoints(this.p2, this._ongoingPointers[1]);\n      }\n    } else {\n      const touchEvent =\n      /** @type {TouchEvent} */\n      e;\n      this._numActivePoints = 0;\n\n      if (touchEvent.type.indexOf('touch') > -1) {\n        // Touch Event\n        // https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent\n        if (touchEvent.touches && touchEvent.touches.length > 0) {\n          this._convertEventPosToPoint(touchEvent.touches[0], this.p1);\n\n          this._numActivePoints++;\n\n          if (touchEvent.touches.length > 1) {\n            this._convertEventPosToPoint(touchEvent.touches[1], this.p2);\n\n            this._numActivePoints++;\n          }\n        }\n      } else {\n        // Mouse Event\n        this._convertEventPosToPoint(\n        /** @type {PointerEvent} */\n        e, this.p1);\n\n        if (pointerType === 'up') {\n          // clear all points on mouseup\n          this._numActivePoints = 0;\n        } else {\n          this._numActivePoints++;\n        }\n      }\n    }\n  }\n  /** update points that were used during previous rAF tick\n   * @private\n   */\n\n\n  _updatePrevPoints() {\n    equalizePoints(this.prevP1, this.p1);\n    equalizePoints(this.prevP2, this.p2);\n  }\n  /** update points at the start of gesture\n   * @private\n   */\n\n\n  _updateStartPoints() {\n    equalizePoints(this.startP1, this.p1);\n    equalizePoints(this.startP2, this.p2);\n\n    this._updatePrevPoints();\n  }\n  /** @private */\n\n\n  _calculateDragDirection() {\n    if (this.pswp.mainScroll.isShifted()) {\n      // if main scroll position is shifted – direction is always horizontal\n      this.dragAxis = 'x';\n    } else {\n      // calculate delta of the last touchmove tick\n      const diff = Math.abs(this.p1.x - this.startP1.x) - Math.abs(this.p1.y - this.startP1.y);\n\n      if (diff !== 0) {\n        // check if pointer was shifted horizontally or vertically\n        const axisToCheck = diff > 0 ? 'x' : 'y';\n\n        if (Math.abs(this.p1[axisToCheck] - this.startP1[axisToCheck]) >= AXIS_SWIPE_HYSTERISIS) {\n          this.dragAxis = axisToCheck;\n        }\n      }\n    }\n  }\n  /**\n   * Converts touch, pointer or mouse event\n   * to PhotoSwipe point.\n   *\n   * @private\n   * @param {Touch | PointerEvent} e\n   * @param {Point} p\n   * @returns {Point}\n   */\n\n\n  _convertEventPosToPoint(e, p) {\n    p.x = e.pageX - this.pswp.offset.x;\n    p.y = e.pageY - this.pswp.offset.y;\n\n    if ('pointerId' in e) {\n      p.id = e.pointerId;\n    } else if (e.identifier !== undefined) {\n      p.id = e.identifier;\n    }\n\n    return p;\n  }\n  /**\n   * @private\n   * @param {PointerEvent} e\n   */\n\n\n  _onClick(e) {\n    // Do not allow click event to pass through after drag\n    if (this.pswp.mainScroll.isShifted()) {\n      e.preventDefault();\n      e.stopPropagation();\n    }\n  }\n\n}\n\n/** @typedef {import('./photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('./slide/slide.js').default} Slide */\n\n/** @typedef {{ el: HTMLDivElement; slide?: Slide }} ItemHolder */\n\nconst MAIN_SCROLL_END_FRICTION = 0.35; // const MIN_SWIPE_TRANSITION_DURATION = 250;\n// const MAX_SWIPE_TRABSITION_DURATION = 500;\n// const DEFAULT_SWIPE_TRANSITION_DURATION = 333;\n\n/**\n * Handles movement of the main scrolling container\n * (for example, it repositions when user swipes left or right).\n *\n * Also stores its state.\n */\n\nclass MainScroll {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    this.x = 0;\n    this.slideWidth = 0;\n    /** @private */\n\n    this._currPositionIndex = 0;\n    /** @private */\n\n    this._prevPositionIndex = 0;\n    /** @private */\n\n    this._containerShiftIndex = -1;\n    /** @type {ItemHolder[]} */\n\n    this.itemHolders = [];\n  }\n  /**\n   * Position the scroller and slide containers\n   * according to viewport size.\n   *\n   * @param {boolean} [resizeSlides] Whether slides content should resized\n   */\n\n\n  resize(resizeSlides) {\n    const {\n      pswp\n    } = this;\n    const newSlideWidth = Math.round(pswp.viewportSize.x + pswp.viewportSize.x * pswp.options.spacing); // Mobile browsers might trigger a resize event during a gesture.\n    // (due to toolbar appearing or hiding).\n    // Avoid re-adjusting main scroll position if width wasn't changed\n\n    const slideWidthChanged = newSlideWidth !== this.slideWidth;\n\n    if (slideWidthChanged) {\n      this.slideWidth = newSlideWidth;\n      this.moveTo(this.getCurrSlideX());\n    }\n\n    this.itemHolders.forEach((itemHolder, index) => {\n      if (slideWidthChanged) {\n        setTransform(itemHolder.el, (index + this._containerShiftIndex) * this.slideWidth);\n      }\n\n      if (resizeSlides && itemHolder.slide) {\n        itemHolder.slide.resize();\n      }\n    });\n  }\n  /**\n   * Reset X position of the main scroller to zero\n   */\n\n\n  resetPosition() {\n    // Position on the main scroller (offset)\n    // it is independent from slide index\n    this._currPositionIndex = 0;\n    this._prevPositionIndex = 0; // This will force recalculation of size on next resize()\n\n    this.slideWidth = 0; // _containerShiftIndex*viewportSize will give you amount of transform of the current slide\n\n    this._containerShiftIndex = -1;\n  }\n  /**\n   * Create and append array of three items\n   * that hold data about slides in DOM\n   */\n\n\n  appendHolders() {\n    this.itemHolders = []; // append our three slide holders -\n    // previous, current, and next\n\n    for (let i = 0; i < 3; i++) {\n      const el = createElement('pswp__item', 'div', this.pswp.container);\n      el.setAttribute('role', 'group');\n      el.setAttribute('aria-roledescription', 'slide');\n      el.setAttribute('aria-hidden', 'true'); // hide nearby item holders until initial zoom animation finishes (to avoid extra Paints)\n\n      el.style.display = i === 1 ? 'block' : 'none';\n      this.itemHolders.push({\n        el //index: -1\n\n      });\n    }\n  }\n  /**\n   * Whether the main scroll can be horizontally swiped to the next or previous slide.\n   * @returns {boolean}\n   */\n\n\n  canBeSwiped() {\n    return this.pswp.getNumItems() > 1;\n  }\n  /**\n   * Move main scroll by X amount of slides.\n   * For example:\n   *   `-1` will move to the previous slide,\n   *    `0` will reset the scroll position of the current slide,\n   *    `3` will move three slides forward\n   *\n   * If loop option is enabled - index will be automatically looped too,\n   * (for example `-1` will move to the last slide of the gallery).\n   *\n   * @param {number} diff\n   * @param {boolean} [animate]\n   * @param {number} [velocityX]\n   * @returns {boolean} whether index was changed or not\n   */\n\n\n  moveIndexBy(diff, animate, velocityX) {\n    const {\n      pswp\n    } = this;\n    let newIndex = pswp.potentialIndex + diff;\n    const numSlides = pswp.getNumItems();\n\n    if (pswp.canLoop()) {\n      newIndex = pswp.getLoopedIndex(newIndex);\n      const distance = (diff + numSlides) % numSlides;\n\n      if (distance <= numSlides / 2) {\n        // go forward\n        diff = distance;\n      } else {\n        // go backwards\n        diff = distance - numSlides;\n      }\n    } else {\n      if (newIndex < 0) {\n        newIndex = 0;\n      } else if (newIndex >= numSlides) {\n        newIndex = numSlides - 1;\n      }\n\n      diff = newIndex - pswp.potentialIndex;\n    }\n\n    pswp.potentialIndex = newIndex;\n    this._currPositionIndex -= diff;\n    pswp.animations.stopMainScroll();\n    const destinationX = this.getCurrSlideX();\n\n    if (!animate) {\n      this.moveTo(destinationX);\n      this.updateCurrItem();\n    } else {\n      pswp.animations.startSpring({\n        isMainScroll: true,\n        start: this.x,\n        end: destinationX,\n        velocity: velocityX || 0,\n        naturalFrequency: 30,\n        dampingRatio: 1,\n        //0.7,\n        onUpdate: x => {\n          this.moveTo(x);\n        },\n        onComplete: () => {\n          this.updateCurrItem();\n          pswp.appendHeavy();\n        }\n      });\n      let currDiff = pswp.potentialIndex - pswp.currIndex;\n\n      if (pswp.canLoop()) {\n        const currDistance = (currDiff + numSlides) % numSlides;\n\n        if (currDistance <= numSlides / 2) {\n          // go forward\n          currDiff = currDistance;\n        } else {\n          // go backwards\n          currDiff = currDistance - numSlides;\n        }\n      } // Force-append new slides during transition\n      // if difference between slides is more than 1\n\n\n      if (Math.abs(currDiff) > 1) {\n        this.updateCurrItem();\n      }\n    }\n\n    return Boolean(diff);\n  }\n  /**\n   * X position of the main scroll for the current slide\n   * (ignores position during dragging)\n   * @returns {number}\n   */\n\n\n  getCurrSlideX() {\n    return this.slideWidth * this._currPositionIndex;\n  }\n  /**\n   * Whether scroll position is shifted.\n   * For example, it will return true if the scroll is being dragged or animated.\n   * @returns {boolean}\n   */\n\n\n  isShifted() {\n    return this.x !== this.getCurrSlideX();\n  }\n  /**\n   * Update slides X positions and set their content\n   */\n\n\n  updateCurrItem() {\n    var _this$itemHolders$;\n\n    const {\n      pswp\n    } = this;\n    const positionDifference = this._prevPositionIndex - this._currPositionIndex;\n\n    if (!positionDifference) {\n      return;\n    }\n\n    this._prevPositionIndex = this._currPositionIndex;\n    pswp.currIndex = pswp.potentialIndex;\n    let diffAbs = Math.abs(positionDifference);\n    /** @type {ItemHolder | undefined} */\n\n    let tempHolder;\n\n    if (diffAbs >= 3) {\n      this._containerShiftIndex += positionDifference + (positionDifference > 0 ? -3 : 3);\n      diffAbs = 3; // If slides are changed by 3 screens or more - clean up previous slides\n\n      this.itemHolders.forEach(itemHolder => {\n        var _itemHolder$slide;\n\n        (_itemHolder$slide = itemHolder.slide) === null || _itemHolder$slide === void 0 || _itemHolder$slide.destroy();\n        itemHolder.slide = undefined;\n      });\n    }\n\n    for (let i = 0; i < diffAbs; i++) {\n      if (positionDifference > 0) {\n        tempHolder = this.itemHolders.shift();\n\n        if (tempHolder) {\n          this.itemHolders[2] = tempHolder; // move first to last\n\n          this._containerShiftIndex++;\n          setTransform(tempHolder.el, (this._containerShiftIndex + 2) * this.slideWidth);\n          pswp.setContent(tempHolder, pswp.currIndex - diffAbs + i + 2);\n        }\n      } else {\n        tempHolder = this.itemHolders.pop();\n\n        if (tempHolder) {\n          this.itemHolders.unshift(tempHolder); // move last to first\n\n          this._containerShiftIndex--;\n          setTransform(tempHolder.el, this._containerShiftIndex * this.slideWidth);\n          pswp.setContent(tempHolder, pswp.currIndex + diffAbs - i - 2);\n        }\n      }\n    } // Reset transfrom every 50ish navigations in one direction.\n    //\n    // Otherwise transform will keep growing indefinitely,\n    // which might cause issues as browsers have a maximum transform limit.\n    // I wasn't able to reach it, but just to be safe.\n    // This should not cause noticable lag.\n\n\n    if (Math.abs(this._containerShiftIndex) > 50 && !this.isShifted()) {\n      this.resetPosition();\n      this.resize();\n    } // Pan transition might be running (and consntantly updating pan position)\n\n\n    pswp.animations.stopAllPan();\n    this.itemHolders.forEach((itemHolder, i) => {\n      if (itemHolder.slide) {\n        // Slide in the 2nd holder is always active\n        itemHolder.slide.setIsActive(i === 1);\n      }\n    });\n    pswp.currSlide = (_this$itemHolders$ = this.itemHolders[1]) === null || _this$itemHolders$ === void 0 ? void 0 : _this$itemHolders$.slide;\n    pswp.contentLoader.updateLazy(positionDifference);\n\n    if (pswp.currSlide) {\n      pswp.currSlide.applyCurrentZoomPan();\n    }\n\n    pswp.dispatch('change');\n  }\n  /**\n   * Move the X position of the main scroll container\n   *\n   * @param {number} x\n   * @param {boolean} [dragging]\n   */\n\n\n  moveTo(x, dragging) {\n    if (!this.pswp.canLoop() && dragging) {\n      // Apply friction\n      let newSlideIndexOffset = (this.slideWidth * this._currPositionIndex - x) / this.slideWidth;\n      newSlideIndexOffset += this.pswp.currIndex;\n      const delta = Math.round(x - this.x);\n\n      if (newSlideIndexOffset < 0 && delta > 0 || newSlideIndexOffset >= this.pswp.getNumItems() - 1 && delta < 0) {\n        x = this.x + delta * MAIN_SCROLL_END_FRICTION;\n      }\n    }\n\n    this.x = x;\n\n    if (this.pswp.container) {\n      setTransform(this.pswp.container, x);\n    }\n\n    this.pswp.dispatch('moveMainScroll', {\n      x,\n      dragging: dragging !== null && dragging !== void 0 ? dragging : false\n    });\n  }\n\n}\n\n/** @typedef {import('./photoswipe.js').default} PhotoSwipe */\n\n/**\n * @template T\n * @typedef {import('./types.js').Methods<T>} Methods<T>\n */\n\nconst KeyboardKeyCodesMap = {\n  Escape: 27,\n  z: 90,\n  ArrowLeft: 37,\n  ArrowUp: 38,\n  ArrowRight: 39,\n  ArrowDown: 40,\n  Tab: 9\n};\n/**\n * @template {keyof KeyboardKeyCodesMap} T\n * @param {T} key\n * @param {boolean} isKeySupported\n * @returns {T | number | undefined}\n */\n\nconst getKeyboardEventKey = (key, isKeySupported) => {\n  return isKeySupported ? key : KeyboardKeyCodesMap[key];\n};\n/**\n * - Manages keyboard shortcuts.\n * - Helps trap focus within photoswipe.\n */\n\n\nclass Keyboard {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    /** @private */\n\n    this._wasFocused = false;\n    pswp.on('bindEvents', () => {\n      if (pswp.options.trapFocus) {\n        // Dialog was likely opened by keyboard if initial point is not defined\n        if (!pswp.options.initialPointerPos) {\n          // focus causes layout,\n          // which causes lag during the animation,\n          // that's why we delay it until the opener transition ends\n          this._focusRoot();\n        }\n\n        pswp.events.add(document, 'focusin',\n        /** @type EventListener */\n        this._onFocusIn.bind(this));\n      }\n\n      pswp.events.add(document, 'keydown',\n      /** @type EventListener */\n      this._onKeyDown.bind(this));\n    });\n    const lastActiveElement =\n    /** @type {HTMLElement} */\n    document.activeElement;\n    pswp.on('destroy', () => {\n      if (pswp.options.returnFocus && lastActiveElement && this._wasFocused) {\n        lastActiveElement.focus();\n      }\n    });\n  }\n  /** @private */\n\n\n  _focusRoot() {\n    if (!this._wasFocused && this.pswp.element) {\n      this.pswp.element.focus();\n      this._wasFocused = true;\n    }\n  }\n  /**\n   * @private\n   * @param {KeyboardEvent} e\n   */\n\n\n  _onKeyDown(e) {\n    const {\n      pswp\n    } = this;\n\n    if (pswp.dispatch('keydown', {\n      originalEvent: e\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (specialKeyUsed(e)) {\n      // don't do anything if special key pressed\n      // to prevent from overriding default browser actions\n      // for example, in Chrome on Mac cmd+arrow-left returns to previous page\n      return;\n    }\n    /** @type {Methods<PhotoSwipe> | undefined} */\n\n\n    let keydownAction;\n    /** @type {'x' | 'y' | undefined} */\n\n    let axis;\n    let isForward = false;\n    const isKeySupported = ('key' in e);\n\n    switch (isKeySupported ? e.key : e.keyCode) {\n      case getKeyboardEventKey('Escape', isKeySupported):\n        if (pswp.options.escKey) {\n          keydownAction = 'close';\n        }\n\n        break;\n\n      case getKeyboardEventKey('z', isKeySupported):\n        keydownAction = 'toggleZoom';\n        break;\n\n      case getKeyboardEventKey('ArrowLeft', isKeySupported):\n        axis = 'x';\n        break;\n\n      case getKeyboardEventKey('ArrowUp', isKeySupported):\n        axis = 'y';\n        break;\n\n      case getKeyboardEventKey('ArrowRight', isKeySupported):\n        axis = 'x';\n        isForward = true;\n        break;\n\n      case getKeyboardEventKey('ArrowDown', isKeySupported):\n        isForward = true;\n        axis = 'y';\n        break;\n\n      case getKeyboardEventKey('Tab', isKeySupported):\n        this._focusRoot();\n\n        break;\n    } // if left/right/top/bottom key\n\n\n    if (axis) {\n      // prevent page scroll\n      e.preventDefault();\n      const {\n        currSlide\n      } = pswp;\n\n      if (pswp.options.arrowKeys && axis === 'x' && pswp.getNumItems() > 1) {\n        keydownAction = isForward ? 'next' : 'prev';\n      } else if (currSlide && currSlide.currZoomLevel > currSlide.zoomLevels.fit) {\n        // up/down arrow keys pan the image vertically\n        // left/right arrow keys pan horizontally.\n        // Unless there is only one image,\n        // or arrowKeys option is disabled\n        currSlide.pan[axis] += isForward ? -80 : 80;\n        currSlide.panTo(currSlide.pan.x, currSlide.pan.y);\n      }\n    }\n\n    if (keydownAction) {\n      e.preventDefault(); // @ts-ignore\n\n      pswp[keydownAction]();\n    }\n  }\n  /**\n   * Trap focus inside photoswipe\n   *\n   * @private\n   * @param {FocusEvent} e\n   */\n\n\n  _onFocusIn(e) {\n    const {\n      template\n    } = this.pswp;\n\n    if (template && document !== e.target && template !== e.target && !template.contains(\n    /** @type {Node} */\n    e.target)) {\n      // focus root element\n      template.focus();\n    }\n  }\n\n}\n\nconst DEFAULT_EASING = 'cubic-bezier(.4,0,.22,1)';\n/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */\n\n/** @typedef {Object} DefaultCssAnimationProps\n *\n * @prop {HTMLElement} target\n * @prop {number} [duration]\n * @prop {string} [easing]\n * @prop {string} [transform]\n * @prop {string} [opacity]\n * */\n\n/** @typedef {SharedAnimationProps & DefaultCssAnimationProps} CssAnimationProps */\n\n/**\n * Runs CSS transition.\n */\n\nclass CSSAnimation {\n  /**\n   * onComplete can be unpredictable, be careful about current state\n   *\n   * @param {CssAnimationProps} props\n   */\n  constructor(props) {\n    var _props$prop;\n\n    this.props = props;\n    const {\n      target,\n      onComplete,\n      transform,\n      onFinish = () => {},\n      duration = 333,\n      easing = DEFAULT_EASING\n    } = props;\n    this.onFinish = onFinish; // support only transform and opacity\n\n    const prop = transform ? 'transform' : 'opacity';\n    const propValue = (_props$prop = props[prop]) !== null && _props$prop !== void 0 ? _props$prop : '';\n    /** @private */\n\n    this._target = target;\n    /** @private */\n\n    this._onComplete = onComplete;\n    /** @private */\n\n    this._finished = false;\n    /** @private */\n\n    this._onTransitionEnd = this._onTransitionEnd.bind(this); // Using timeout hack to make sure that animation\n    // starts even if the animated property was changed recently,\n    // otherwise transitionend might not fire or transition won't start.\n    // https://drafts.csswg.org/css-transitions/#starting\n    //\n    // ¯\\_(ツ)_/¯\n\n    /** @private */\n\n    this._helperTimeout = setTimeout(() => {\n      setTransitionStyle(target, prop, duration, easing);\n      this._helperTimeout = setTimeout(() => {\n        target.addEventListener('transitionend', this._onTransitionEnd, false);\n        target.addEventListener('transitioncancel', this._onTransitionEnd, false); // Safari occasionally does not emit transitionend event\n        // if element property was modified during the transition,\n        // which may be caused by resize or third party component,\n        // using timeout as a safety fallback\n\n        this._helperTimeout = setTimeout(() => {\n          this._finalizeAnimation();\n        }, duration + 500);\n        target.style[prop] = propValue;\n      }, 30); // Do not reduce this number\n    }, 0);\n  }\n  /**\n   * @private\n   * @param {TransitionEvent} e\n   */\n\n\n  _onTransitionEnd(e) {\n    if (e.target === this._target) {\n      this._finalizeAnimation();\n    }\n  }\n  /**\n   * @private\n   */\n\n\n  _finalizeAnimation() {\n    if (!this._finished) {\n      this._finished = true;\n      this.onFinish();\n\n      if (this._onComplete) {\n        this._onComplete();\n      }\n    }\n  } // Destroy is called automatically onFinish\n\n\n  destroy() {\n    if (this._helperTimeout) {\n      clearTimeout(this._helperTimeout);\n    }\n\n    removeTransitionStyle(this._target);\n\n    this._target.removeEventListener('transitionend', this._onTransitionEnd, false);\n\n    this._target.removeEventListener('transitioncancel', this._onTransitionEnd, false);\n\n    if (!this._finished) {\n      this._finalizeAnimation();\n    }\n  }\n\n}\n\nconst DEFAULT_NATURAL_FREQUENCY = 12;\nconst DEFAULT_DAMPING_RATIO = 0.75;\n/**\n * Spring easing helper\n */\n\nclass SpringEaser {\n  /**\n   * @param {number} initialVelocity Initial velocity, px per ms.\n   *\n   * @param {number} [dampingRatio]\n   * Determines how bouncy animation will be.\n   * From 0 to 1, 0 - always overshoot, 1 - do not overshoot.\n   * \"overshoot\" refers to part of animation that\n   * goes beyond the final value.\n   *\n   * @param {number} [naturalFrequency]\n   * Determines how fast animation will slow down.\n   * The higher value - the stiffer the transition will be,\n   * and the faster it will slow down.\n   * Recommended value from 10 to 50\n   */\n  constructor(initialVelocity, dampingRatio, naturalFrequency) {\n    this.velocity = initialVelocity * 1000; // convert to \"pixels per second\"\n    // https://en.wikipedia.org/wiki/Damping_ratio\n\n    this._dampingRatio = dampingRatio || DEFAULT_DAMPING_RATIO; // https://en.wikipedia.org/wiki/Natural_frequency\n\n    this._naturalFrequency = naturalFrequency || DEFAULT_NATURAL_FREQUENCY;\n    this._dampedFrequency = this._naturalFrequency;\n\n    if (this._dampingRatio < 1) {\n      this._dampedFrequency *= Math.sqrt(1 - this._dampingRatio * this._dampingRatio);\n    }\n  }\n  /**\n   * @param {number} deltaPosition Difference between current and end position of the animation\n   * @param {number} deltaTime Frame duration in milliseconds\n   *\n   * @returns {number} Displacement, relative to the end position.\n   */\n\n\n  easeFrame(deltaPosition, deltaTime) {\n    // Inspired by Apple Webkit and Android spring function implementation\n    // https://en.wikipedia.org/wiki/Oscillation\n    // https://en.wikipedia.org/wiki/Damping_ratio\n    // we ignore mass (assume that it's 1kg)\n    let displacement = 0;\n    let coeff;\n    deltaTime /= 1000;\n    const naturalDumpingPow = Math.E ** (-this._dampingRatio * this._naturalFrequency * deltaTime);\n\n    if (this._dampingRatio === 1) {\n      coeff = this.velocity + this._naturalFrequency * deltaPosition;\n      displacement = (deltaPosition + coeff * deltaTime) * naturalDumpingPow;\n      this.velocity = displacement * -this._naturalFrequency + coeff * naturalDumpingPow;\n    } else if (this._dampingRatio < 1) {\n      coeff = 1 / this._dampedFrequency * (this._dampingRatio * this._naturalFrequency * deltaPosition + this.velocity);\n      const dumpedFCos = Math.cos(this._dampedFrequency * deltaTime);\n      const dumpedFSin = Math.sin(this._dampedFrequency * deltaTime);\n      displacement = naturalDumpingPow * (deltaPosition * dumpedFCos + coeff * dumpedFSin);\n      this.velocity = displacement * -this._naturalFrequency * this._dampingRatio + naturalDumpingPow * (-this._dampedFrequency * deltaPosition * dumpedFSin + this._dampedFrequency * coeff * dumpedFCos);\n    } // Overdamped (>1) damping ratio is not supported\n\n\n    return displacement;\n  }\n\n}\n\n/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */\n\n/**\n * @typedef {Object} DefaultSpringAnimationProps\n *\n * @prop {number} start\n * @prop {number} end\n * @prop {number} velocity\n * @prop {number} [dampingRatio]\n * @prop {number} [naturalFrequency]\n * @prop {(end: number) => void} onUpdate\n */\n\n/** @typedef {SharedAnimationProps & DefaultSpringAnimationProps} SpringAnimationProps */\n\nclass SpringAnimation {\n  /**\n   * @param {SpringAnimationProps} props\n   */\n  constructor(props) {\n    this.props = props;\n    this._raf = 0;\n    const {\n      start,\n      end,\n      velocity,\n      onUpdate,\n      onComplete,\n      onFinish = () => {},\n      dampingRatio,\n      naturalFrequency\n    } = props;\n    this.onFinish = onFinish;\n    const easer = new SpringEaser(velocity, dampingRatio, naturalFrequency);\n    let prevTime = Date.now();\n    let deltaPosition = start - end;\n\n    const animationLoop = () => {\n      if (this._raf) {\n        deltaPosition = easer.easeFrame(deltaPosition, Date.now() - prevTime); // Stop the animation if velocity is low and position is close to end\n\n        if (Math.abs(deltaPosition) < 1 && Math.abs(easer.velocity) < 50) {\n          // Finalize the animation\n          onUpdate(end);\n\n          if (onComplete) {\n            onComplete();\n          }\n\n          this.onFinish();\n        } else {\n          prevTime = Date.now();\n          onUpdate(deltaPosition + end);\n          this._raf = requestAnimationFrame(animationLoop);\n        }\n      }\n    };\n\n    this._raf = requestAnimationFrame(animationLoop);\n  } // Destroy is called automatically onFinish\n\n\n  destroy() {\n    if (this._raf >= 0) {\n      cancelAnimationFrame(this._raf);\n    }\n\n    this._raf = 0;\n  }\n\n}\n\n/** @typedef {import('./css-animation.js').CssAnimationProps} CssAnimationProps */\n\n/** @typedef {import('./spring-animation.js').SpringAnimationProps} SpringAnimationProps */\n\n/** @typedef {Object} SharedAnimationProps\n * @prop {string} [name]\n * @prop {boolean} [isPan]\n * @prop {boolean} [isMainScroll]\n * @prop {VoidFunction} [onComplete]\n * @prop {VoidFunction} [onFinish]\n */\n\n/** @typedef {SpringAnimation | CSSAnimation} Animation */\n\n/** @typedef {SpringAnimationProps | CssAnimationProps} AnimationProps */\n\n/**\n * Manages animations\n */\n\nclass Animations {\n  constructor() {\n    /** @type {Animation[]} */\n    this.activeAnimations = [];\n  }\n  /**\n   * @param {SpringAnimationProps} props\n   */\n\n\n  startSpring(props) {\n    this._start(props, true);\n  }\n  /**\n   * @param {CssAnimationProps} props\n   */\n\n\n  startTransition(props) {\n    this._start(props);\n  }\n  /**\n   * @private\n   * @param {AnimationProps} props\n   * @param {boolean} [isSpring]\n   * @returns {Animation}\n   */\n\n\n  _start(props, isSpring) {\n    const animation = isSpring ? new SpringAnimation(\n    /** @type SpringAnimationProps */\n    props) : new CSSAnimation(\n    /** @type CssAnimationProps */\n    props);\n    this.activeAnimations.push(animation);\n\n    animation.onFinish = () => this.stop(animation);\n\n    return animation;\n  }\n  /**\n   * @param {Animation} animation\n   */\n\n\n  stop(animation) {\n    animation.destroy();\n    const index = this.activeAnimations.indexOf(animation);\n\n    if (index > -1) {\n      this.activeAnimations.splice(index, 1);\n    }\n  }\n\n  stopAll() {\n    // _stopAllAnimations\n    this.activeAnimations.forEach(animation => {\n      animation.destroy();\n    });\n    this.activeAnimations = [];\n  }\n  /**\n   * Stop all pan or zoom transitions\n   */\n\n\n  stopAllPan() {\n    this.activeAnimations = this.activeAnimations.filter(animation => {\n      if (animation.props.isPan) {\n        animation.destroy();\n        return false;\n      }\n\n      return true;\n    });\n  }\n\n  stopMainScroll() {\n    this.activeAnimations = this.activeAnimations.filter(animation => {\n      if (animation.props.isMainScroll) {\n        animation.destroy();\n        return false;\n      }\n\n      return true;\n    });\n  }\n  /**\n   * Returns true if main scroll transition is running\n   */\n  // isMainScrollRunning() {\n  //   return this.activeAnimations.some((animation) => {\n  //     return animation.props.isMainScroll;\n  //   });\n  // }\n\n  /**\n   * Returns true if any pan or zoom transition is running\n   */\n\n\n  isPanRunning() {\n    return this.activeAnimations.some(animation => {\n      return animation.props.isPan;\n    });\n  }\n\n}\n\n/** @typedef {import('./photoswipe.js').default} PhotoSwipe */\n\n/**\n * Handles scroll wheel.\n * Can pan and zoom current slide image.\n */\nclass ScrollWheel {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    pswp.events.add(pswp.element, 'wheel',\n    /** @type EventListener */\n    this._onWheel.bind(this));\n  }\n  /**\n   * @private\n   * @param {WheelEvent} e\n   */\n\n\n  _onWheel(e) {\n    e.preventDefault();\n    const {\n      currSlide\n    } = this.pswp;\n    let {\n      deltaX,\n      deltaY\n    } = e;\n\n    if (!currSlide) {\n      return;\n    }\n\n    if (this.pswp.dispatch('wheel', {\n      originalEvent: e\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (e.ctrlKey || this.pswp.options.wheelToZoom) {\n      // zoom\n      if (currSlide.isZoomable()) {\n        let zoomFactor = -deltaY;\n\n        if (e.deltaMode === 1\n        /* DOM_DELTA_LINE */\n        ) {\n          zoomFactor *= 0.05;\n        } else {\n          zoomFactor *= e.deltaMode ? 1 : 0.002;\n        }\n\n        zoomFactor = 2 ** zoomFactor;\n        const destZoomLevel = currSlide.currZoomLevel * zoomFactor;\n        currSlide.zoomTo(destZoomLevel, {\n          x: e.clientX,\n          y: e.clientY\n        });\n      }\n    } else {\n      // pan\n      if (currSlide.isPannable()) {\n        if (e.deltaMode === 1\n        /* DOM_DELTA_LINE */\n        ) {\n          // 18 - average line height\n          deltaX *= 18;\n          deltaY *= 18;\n        }\n\n        currSlide.panTo(currSlide.pan.x - deltaX, currSlide.pan.y - deltaY);\n      }\n    }\n  }\n\n}\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/**\n * @template T\n * @typedef {import('../types.js').Methods<T>} Methods<T>\n */\n\n/**\n * @typedef {Object} UIElementMarkupProps\n * @prop {boolean} [isCustomSVG]\n * @prop {string} inner\n * @prop {string} [outlineID]\n * @prop {number | string} [size]\n */\n\n/**\n * @typedef {Object} UIElementData\n * @prop {DefaultUIElements | string} [name]\n * @prop {string} [className]\n * @prop {UIElementMarkup} [html]\n * @prop {boolean} [isButton]\n * @prop {keyof HTMLElementTagNameMap} [tagName]\n * @prop {string} [title]\n * @prop {string} [ariaLabel]\n * @prop {(element: HTMLElement, pswp: PhotoSwipe) => void} [onInit]\n * @prop {Methods<PhotoSwipe> | ((e: MouseEvent, element: HTMLElement, pswp: PhotoSwipe) => void)} [onClick]\n * @prop {'bar' | 'wrapper' | 'root'} [appendTo]\n * @prop {number} [order]\n */\n\n/** @typedef {'arrowPrev' | 'arrowNext' | 'close' | 'zoom' | 'counter'} DefaultUIElements */\n\n/** @typedef {string | UIElementMarkupProps} UIElementMarkup */\n\n/**\n * @param {UIElementMarkup} [htmlData]\n * @returns {string}\n */\n\nfunction addElementHTML(htmlData) {\n  if (typeof htmlData === 'string') {\n    // Allow developers to provide full svg,\n    // For example:\n    // <svg viewBox=\"0 0 32 32\" width=\"32\" height=\"32\" aria-hidden=\"true\" class=\"pswp__icn\">\n    //   <path d=\"...\" />\n    //   <circle ... />\n    // </svg>\n    // Can also be any HTML string.\n    return htmlData;\n  }\n\n  if (!htmlData || !htmlData.isCustomSVG) {\n    return '';\n  }\n\n  const svgData = htmlData;\n  let out = '<svg aria-hidden=\"true\" class=\"pswp__icn\" viewBox=\"0 0 %d %d\" width=\"%d\" height=\"%d\">'; // replace all %d with size\n\n  out = out.split('%d').join(\n  /** @type {string} */\n  svgData.size || 32); // Icons may contain outline/shadow,\n  // to make it we \"clone\" base icon shape and add border to it.\n  // Icon itself and border are styled via CSS.\n  //\n  // Property shadowID defines ID of element that should be cloned.\n\n  if (svgData.outlineID) {\n    out += '<use class=\"pswp__icn-shadow\" xlink:href=\"#' + svgData.outlineID + '\"/>';\n  }\n\n  out += svgData.inner;\n  out += '</svg>';\n  return out;\n}\n\nclass UIElement {\n  /**\n   * @param {PhotoSwipe} pswp\n   * @param {UIElementData} data\n   */\n  constructor(pswp, data) {\n    var _container;\n\n    const name = data.name || data.className;\n    let elementHTML = data.html; // @ts-expect-error lookup only by `data.name` maybe?\n\n    if (pswp.options[name] === false) {\n      // exit if element is disabled from options\n      return;\n    } // Allow to override SVG icons from options\n    // @ts-expect-error lookup only by `data.name` maybe?\n\n\n    if (typeof pswp.options[name + 'SVG'] === 'string') {\n      // arrowPrevSVG\n      // arrowNextSVG\n      // closeSVG\n      // zoomSVG\n      // @ts-expect-error lookup only by `data.name` maybe?\n      elementHTML = pswp.options[name + 'SVG'];\n    }\n\n    pswp.dispatch('uiElementCreate', {\n      data\n    });\n    let className = '';\n\n    if (data.isButton) {\n      className += 'pswp__button ';\n      className += data.className || `pswp__button--${data.name}`;\n    } else {\n      className += data.className || `pswp__${data.name}`;\n    }\n\n    let tagName = data.isButton ? data.tagName || 'button' : data.tagName || 'div';\n    tagName =\n    /** @type {keyof HTMLElementTagNameMap} */\n    tagName.toLowerCase();\n    /** @type {HTMLElement} */\n\n    const element = createElement(className, tagName);\n\n    if (data.isButton) {\n      if (tagName === 'button') {\n        /** @type {HTMLButtonElement} */\n        element.type = 'button';\n      }\n\n      let {\n        title\n      } = data;\n      const {\n        ariaLabel\n      } = data; // @ts-expect-error lookup only by `data.name` maybe?\n\n      if (typeof pswp.options[name + 'Title'] === 'string') {\n        // @ts-expect-error lookup only by `data.name` maybe?\n        title = pswp.options[name + 'Title'];\n      }\n\n      if (title) {\n        element.title = title;\n      }\n\n      const ariaText = ariaLabel || title;\n\n      if (ariaText) {\n        element.setAttribute('aria-label', ariaText);\n      }\n    }\n\n    element.innerHTML = addElementHTML(elementHTML);\n\n    if (data.onInit) {\n      data.onInit(element, pswp);\n    }\n\n    if (data.onClick) {\n      element.onclick = e => {\n        if (typeof data.onClick === 'string') {\n          // @ts-ignore\n          pswp[data.onClick]();\n        } else if (typeof data.onClick === 'function') {\n          data.onClick(e, element, pswp);\n        }\n      };\n    } // Top bar is default position\n\n\n    const appendTo = data.appendTo || 'bar';\n    /** @type {HTMLElement | undefined} root element by default */\n\n    let container = pswp.element;\n\n    if (appendTo === 'bar') {\n      if (!pswp.topBar) {\n        pswp.topBar = createElement('pswp__top-bar pswp__hide-on-close', 'div', pswp.scrollWrap);\n      }\n\n      container = pswp.topBar;\n    } else {\n      // element outside of top bar gets a secondary class\n      // that makes element fade out on close\n      element.classList.add('pswp__hide-on-close');\n\n      if (appendTo === 'wrapper') {\n        container = pswp.scrollWrap;\n      }\n    }\n\n    (_container = container) === null || _container === void 0 || _container.appendChild(pswp.applyFilters('uiElement', element, data));\n  }\n\n}\n\n/*\n  Backward and forward arrow buttons\n */\n\n/** @typedef {import('./ui-element.js').UIElementData} UIElementData */\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/**\n *\n * @param {HTMLElement} element\n * @param {PhotoSwipe} pswp\n * @param {boolean} [isNextButton]\n */\nfunction initArrowButton(element, pswp, isNextButton) {\n  element.classList.add('pswp__button--arrow'); // TODO: this should point to a unique id for this instance\n\n  element.setAttribute('aria-controls', 'pswp__items');\n  pswp.on('change', () => {\n    if (!pswp.options.loop) {\n      if (isNextButton) {\n        /** @type {HTMLButtonElement} */\n        element.disabled = !(pswp.currIndex < pswp.getNumItems() - 1);\n      } else {\n        /** @type {HTMLButtonElement} */\n        element.disabled = !(pswp.currIndex > 0);\n      }\n    }\n  });\n}\n/** @type {UIElementData} */\n\n\nconst arrowPrev = {\n  name: 'arrowPrev',\n  className: 'pswp__button--arrow--prev',\n  title: 'Previous',\n  order: 10,\n  isButton: true,\n  appendTo: 'wrapper',\n  html: {\n    isCustomSVG: true,\n    size: 60,\n    inner: '<path d=\"M29 43l-3 3-16-16 16-16 3 3-13 13 13 13z\" id=\"pswp__icn-arrow\"/>',\n    outlineID: 'pswp__icn-arrow'\n  },\n  onClick: 'prev',\n  onInit: initArrowButton\n};\n/** @type {UIElementData} */\n\nconst arrowNext = {\n  name: 'arrowNext',\n  className: 'pswp__button--arrow--next',\n  title: 'Next',\n  order: 11,\n  isButton: true,\n  appendTo: 'wrapper',\n  html: {\n    isCustomSVG: true,\n    size: 60,\n    inner: '<use xlink:href=\"#pswp__icn-arrow\"/>',\n    outlineID: 'pswp__icn-arrow'\n  },\n  onClick: 'next',\n  onInit: (el, pswp) => {\n    initArrowButton(el, pswp, true);\n  }\n};\n\n/** @type {import('./ui-element.js').UIElementData} UIElementData */\nconst closeButton = {\n  name: 'close',\n  title: 'Close',\n  order: 20,\n  isButton: true,\n  html: {\n    isCustomSVG: true,\n    inner: '<path d=\"M24 10l-2-2-6 6-6-6-2 2 6 6-6 6 2 2 6-6 6 6 2-2-6-6z\" id=\"pswp__icn-close\"/>',\n    outlineID: 'pswp__icn-close'\n  },\n  onClick: 'close'\n};\n\n/** @type {import('./ui-element.js').UIElementData} UIElementData */\nconst zoomButton = {\n  name: 'zoom',\n  title: 'Zoom',\n  order: 10,\n  isButton: true,\n  html: {\n    isCustomSVG: true,\n    // eslint-disable-next-line max-len\n    inner: '<path d=\"M17.426 19.926a6 6 0 1 1 1.5-1.5L23 22.5 21.5 24l-4.074-4.074z\" id=\"pswp__icn-zoom\"/>' + '<path fill=\"currentColor\" class=\"pswp__zoom-icn-bar-h\" d=\"M11 16v-2h6v2z\"/>' + '<path fill=\"currentColor\" class=\"pswp__zoom-icn-bar-v\" d=\"M13 12h2v6h-2z\"/>',\n    outlineID: 'pswp__icn-zoom'\n  },\n  onClick: 'toggleZoom'\n};\n\n/** @type {import('./ui-element.js').UIElementData} UIElementData */\nconst loadingIndicator = {\n  name: 'preloader',\n  appendTo: 'bar',\n  order: 7,\n  html: {\n    isCustomSVG: true,\n    // eslint-disable-next-line max-len\n    inner: '<path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M21.2 16a5.2 5.2 0 1 1-5.2-5.2V8a8 8 0 1 0 8 8h-2.8Z\" id=\"pswp__icn-loading\"/>',\n    outlineID: 'pswp__icn-loading'\n  },\n  onInit: (indicatorElement, pswp) => {\n    /** @type {boolean | undefined} */\n    let isVisible;\n    /** @type {NodeJS.Timeout | null} */\n\n    let delayTimeout = null;\n    /**\n     * @param {string} className\n     * @param {boolean} add\n     */\n\n    const toggleIndicatorClass = (className, add) => {\n      indicatorElement.classList.toggle('pswp__preloader--' + className, add);\n    };\n    /**\n     * @param {boolean} visible\n     */\n\n\n    const setIndicatorVisibility = visible => {\n      if (isVisible !== visible) {\n        isVisible = visible;\n        toggleIndicatorClass('active', visible);\n      }\n    };\n\n    const updatePreloaderVisibility = () => {\n      var _pswp$currSlide;\n\n      if (!((_pswp$currSlide = pswp.currSlide) !== null && _pswp$currSlide !== void 0 && _pswp$currSlide.content.isLoading())) {\n        setIndicatorVisibility(false);\n\n        if (delayTimeout) {\n          clearTimeout(delayTimeout);\n          delayTimeout = null;\n        }\n\n        return;\n      }\n\n      if (!delayTimeout) {\n        // display loading indicator with delay\n        delayTimeout = setTimeout(() => {\n          var _pswp$currSlide2;\n\n          setIndicatorVisibility(Boolean((_pswp$currSlide2 = pswp.currSlide) === null || _pswp$currSlide2 === void 0 ? void 0 : _pswp$currSlide2.content.isLoading()));\n          delayTimeout = null;\n        }, pswp.options.preloaderDelay);\n      }\n    };\n\n    pswp.on('change', updatePreloaderVisibility);\n    pswp.on('loadComplete', e => {\n      if (pswp.currSlide === e.slide) {\n        updatePreloaderVisibility();\n      }\n    }); // expose the method\n\n    if (pswp.ui) {\n      pswp.ui.updatePreloaderVisibility = updatePreloaderVisibility;\n    }\n  }\n};\n\n/** @type {import('./ui-element.js').UIElementData} UIElementData */\nconst counterIndicator = {\n  name: 'counter',\n  order: 5,\n  onInit: (counterElement, pswp) => {\n    pswp.on('change', () => {\n      counterElement.innerText = pswp.currIndex + 1 + pswp.options.indexIndicatorSep + pswp.getNumItems();\n    });\n  }\n};\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('./ui-element.js').UIElementData} UIElementData */\n\n/**\n * Set special class on element when image is zoomed.\n *\n * By default, it is used to adjust\n * zoom icon and zoom cursor via CSS.\n *\n * @param {HTMLElement} el\n * @param {boolean} isZoomedIn\n */\n\nfunction setZoomedIn(el, isZoomedIn) {\n  el.classList.toggle('pswp--zoomed-in', isZoomedIn);\n}\n\nclass UI {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    this.isRegistered = false;\n    /** @type {UIElementData[]} */\n\n    this.uiElementsData = [];\n    /** @type {(UIElement | UIElementData)[]} */\n\n    this.items = [];\n    /** @type {() => void} */\n\n    this.updatePreloaderVisibility = () => {};\n    /**\n     * @private\n     * @type {number | undefined}\n     */\n\n\n    this._lastUpdatedZoomLevel = undefined;\n  }\n\n  init() {\n    const {\n      pswp\n    } = this;\n    this.isRegistered = false;\n    this.uiElementsData = [closeButton, arrowPrev, arrowNext, zoomButton, loadingIndicator, counterIndicator];\n    pswp.dispatch('uiRegister'); // sort by order\n\n    this.uiElementsData.sort((a, b) => {\n      // default order is 0\n      return (a.order || 0) - (b.order || 0);\n    });\n    this.items = [];\n    this.isRegistered = true;\n    this.uiElementsData.forEach(uiElementData => {\n      this.registerElement(uiElementData);\n    });\n    pswp.on('change', () => {\n      var _pswp$element;\n\n      (_pswp$element = pswp.element) === null || _pswp$element === void 0 || _pswp$element.classList.toggle('pswp--one-slide', pswp.getNumItems() === 1);\n    });\n    pswp.on('zoomPanUpdate', () => this._onZoomPanUpdate());\n  }\n  /**\n   * @param {UIElementData} elementData\n   */\n\n\n  registerElement(elementData) {\n    if (this.isRegistered) {\n      this.items.push(new UIElement(this.pswp, elementData));\n    } else {\n      this.uiElementsData.push(elementData);\n    }\n  }\n  /**\n   * Fired each time zoom or pan position is changed.\n   * Update classes that control visibility of zoom button and cursor icon.\n   *\n   * @private\n   */\n\n\n  _onZoomPanUpdate() {\n    const {\n      template,\n      currSlide,\n      options\n    } = this.pswp;\n\n    if (this.pswp.opener.isClosing || !template || !currSlide) {\n      return;\n    }\n\n    let {\n      currZoomLevel\n    } = currSlide; // if not open yet - check against initial zoom level\n\n    if (!this.pswp.opener.isOpen) {\n      currZoomLevel = currSlide.zoomLevels.initial;\n    }\n\n    if (currZoomLevel === this._lastUpdatedZoomLevel) {\n      return;\n    }\n\n    this._lastUpdatedZoomLevel = currZoomLevel;\n    const currZoomLevelDiff = currSlide.zoomLevels.initial - currSlide.zoomLevels.secondary; // Initial and secondary zoom levels are almost equal\n\n    if (Math.abs(currZoomLevelDiff) < 0.01 || !currSlide.isZoomable()) {\n      // disable zoom\n      setZoomedIn(template, false);\n      template.classList.remove('pswp--zoom-allowed');\n      return;\n    }\n\n    template.classList.add('pswp--zoom-allowed');\n    const potentialZoomLevel = currZoomLevel === currSlide.zoomLevels.initial ? currSlide.zoomLevels.secondary : currSlide.zoomLevels.initial;\n    setZoomedIn(template, potentialZoomLevel <= currZoomLevel);\n\n    if (options.imageClickAction === 'zoom' || options.imageClickAction === 'zoom-or-close') {\n      template.classList.add('pswp--click-to-zoom');\n    }\n  }\n\n}\n\n/** @typedef {import('./slide.js').SlideData} SlideData */\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {{ x: number; y: number; w: number; innerRect?: { w: number; h: number; x: number; y: number } }} Bounds */\n\n/**\n * @param {HTMLElement} el\n * @returns Bounds\n */\nfunction getBoundsByElement(el) {\n  const thumbAreaRect = el.getBoundingClientRect();\n  return {\n    x: thumbAreaRect.left,\n    y: thumbAreaRect.top,\n    w: thumbAreaRect.width\n  };\n}\n/**\n * @param {HTMLElement} el\n * @param {number} imageWidth\n * @param {number} imageHeight\n * @returns Bounds\n */\n\n\nfunction getCroppedBoundsByElement(el, imageWidth, imageHeight) {\n  const thumbAreaRect = el.getBoundingClientRect(); // fill image into the area\n  // (do they same as object-fit:cover does to retrieve coordinates)\n\n  const hRatio = thumbAreaRect.width / imageWidth;\n  const vRatio = thumbAreaRect.height / imageHeight;\n  const fillZoomLevel = hRatio > vRatio ? hRatio : vRatio;\n  const offsetX = (thumbAreaRect.width - imageWidth * fillZoomLevel) / 2;\n  const offsetY = (thumbAreaRect.height - imageHeight * fillZoomLevel) / 2;\n  /**\n   * Coordinates of the image,\n   * as if it was not cropped,\n   * height is calculated automatically\n   *\n   * @type {Bounds}\n   */\n\n  const bounds = {\n    x: thumbAreaRect.left + offsetX,\n    y: thumbAreaRect.top + offsetY,\n    w: imageWidth * fillZoomLevel\n  }; // Coordinates of inner crop area\n  // relative to the image\n\n  bounds.innerRect = {\n    w: thumbAreaRect.width,\n    h: thumbAreaRect.height,\n    x: offsetX,\n    y: offsetY\n  };\n  return bounds;\n}\n/**\n * Get dimensions of thumbnail image\n * (click on which opens photoswipe or closes photoswipe to)\n *\n * @param {number} index\n * @param {SlideData} itemData\n * @param {PhotoSwipe} instance PhotoSwipe instance\n * @returns {Bounds | undefined}\n */\n\n\nfunction getThumbBounds(index, itemData, instance) {\n  // legacy event, before filters were introduced\n  const event = instance.dispatch('thumbBounds', {\n    index,\n    itemData,\n    instance\n  }); // @ts-expect-error\n\n  if (event.thumbBounds) {\n    // @ts-expect-error\n    return event.thumbBounds;\n  }\n\n  const {\n    element\n  } = itemData;\n  /** @type {Bounds | undefined} */\n\n  let thumbBounds;\n  /** @type {HTMLElement | null | undefined} */\n\n  let thumbnail;\n\n  if (element && instance.options.thumbSelector !== false) {\n    const thumbSelector = instance.options.thumbSelector || 'img';\n    thumbnail = element.matches(thumbSelector) ? element :\n    /** @type {HTMLElement | null} */\n    element.querySelector(thumbSelector);\n  }\n\n  thumbnail = instance.applyFilters('thumbEl', thumbnail, itemData, index);\n\n  if (thumbnail) {\n    if (!itemData.thumbCropped) {\n      thumbBounds = getBoundsByElement(thumbnail);\n    } else {\n      thumbBounds = getCroppedBoundsByElement(thumbnail, itemData.width || itemData.w || 0, itemData.height || itemData.h || 0);\n    }\n  }\n\n  return instance.applyFilters('thumbBounds', thumbBounds, itemData, index);\n}\n\n/** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n\n/** @typedef {import('../photoswipe.js').DataSource} DataSource */\n\n/** @typedef {import('../ui/ui-element.js').UIElementData} UIElementData */\n\n/** @typedef {import('../slide/content.js').default} ContentDefault */\n\n/** @typedef {import('../slide/slide.js').default} Slide */\n\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n\n/** @typedef {import('../slide/zoom-level.js').default} ZoomLevel */\n\n/** @typedef {import('../slide/get-thumb-bounds.js').Bounds} Bounds */\n\n/**\n * Allow adding an arbitrary props to the Content\n * https://photoswipe.com/custom-content/#using-webp-image-format\n * @typedef {ContentDefault & Record<string, any>} Content\n */\n\n/** @typedef {{ x?: number; y?: number }} Point */\n\n/**\n * @typedef {Object} PhotoSwipeEventsMap https://photoswipe.com/events/\n *\n *\n * https://photoswipe.com/adding-ui-elements/\n *\n * @prop {undefined} uiRegister\n * @prop {{ data: UIElementData }} uiElementCreate\n *\n *\n * https://photoswipe.com/events/#initialization-events\n *\n * @prop {undefined} beforeOpen\n * @prop {undefined} firstUpdate\n * @prop {undefined} initialLayout\n * @prop {undefined} change\n * @prop {undefined} afterInit\n * @prop {undefined} bindEvents\n *\n *\n * https://photoswipe.com/events/#opening-or-closing-transition-events\n *\n * @prop {undefined} openingAnimationStart\n * @prop {undefined} openingAnimationEnd\n * @prop {undefined} closingAnimationStart\n * @prop {undefined} closingAnimationEnd\n *\n *\n * https://photoswipe.com/events/#closing-events\n *\n * @prop {undefined} close\n * @prop {undefined} destroy\n *\n *\n * https://photoswipe.com/events/#pointer-and-gesture-events\n *\n * @prop {{ originalEvent: PointerEvent }} pointerDown\n * @prop {{ originalEvent: PointerEvent }} pointerMove\n * @prop {{ originalEvent: PointerEvent }} pointerUp\n * @prop {{ bgOpacity: number }} pinchClose can be default prevented\n * @prop {{ panY: number }} verticalDrag can be default prevented\n *\n *\n * https://photoswipe.com/events/#slide-content-events\n *\n * @prop {{ content: Content }} contentInit\n * @prop {{ content: Content; isLazy: boolean }} contentLoad can be default prevented\n * @prop {{ content: Content; isLazy: boolean }} contentLoadImage can be default prevented\n * @prop {{ content: Content; slide: Slide; isError?: boolean }} loadComplete\n * @prop {{ content: Content; slide: Slide }} loadError\n * @prop {{ content: Content; width: number; height: number }} contentResize can be default prevented\n * @prop {{ content: Content; width: number; height: number; slide: Slide }} imageSizeChange\n * @prop {{ content: Content }} contentLazyLoad can be default prevented\n * @prop {{ content: Content }} contentAppend can be default prevented\n * @prop {{ content: Content }} contentActivate can be default prevented\n * @prop {{ content: Content }} contentDeactivate can be default prevented\n * @prop {{ content: Content }} contentRemove can be default prevented\n * @prop {{ content: Content }} contentDestroy can be default prevented\n *\n *\n * undocumented\n *\n * @prop {{ point: Point; originalEvent: PointerEvent }} imageClickAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} bgClickAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} tapAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} doubleTapAction can be default prevented\n *\n * @prop {{ originalEvent: KeyboardEvent }} keydown can be default prevented\n * @prop {{ x: number; dragging: boolean }} moveMainScroll\n * @prop {{ slide: Slide }} firstZoomPan\n * @prop {{ slide: Slide | undefined, data: SlideData, index: number }} gettingData\n * @prop {undefined} beforeResize\n * @prop {undefined} resize\n * @prop {undefined} viewportSize\n * @prop {undefined} updateScrollOffset\n * @prop {{ slide: Slide }} slideInit\n * @prop {{ slide: Slide }} afterSetContent\n * @prop {{ slide: Slide }} slideLoad\n * @prop {{ slide: Slide }} appendHeavy can be default prevented\n * @prop {{ slide: Slide }} appendHeavyContent\n * @prop {{ slide: Slide }} slideActivate\n * @prop {{ slide: Slide }} slideDeactivate\n * @prop {{ slide: Slide }} slideDestroy\n * @prop {{ destZoomLevel: number, centerPoint: Point | undefined, transitionDuration: number | false | undefined }} beforeZoomTo\n * @prop {{ slide: Slide }} zoomPanUpdate\n * @prop {{ slide: Slide }} initialZoomPan\n * @prop {{ slide: Slide }} calcSlideSize\n * @prop {undefined} resolutionChanged\n * @prop {{ originalEvent: WheelEvent }} wheel can be default prevented\n * @prop {{ content: Content }} contentAppendImage can be default prevented\n * @prop {{ index: number; itemData: SlideData }} lazyLoadSlide can be default prevented\n * @prop {undefined} lazyLoad\n * @prop {{ slide: Slide }} calcBounds\n * @prop {{ zoomLevels: ZoomLevel, slideData: SlideData }} zoomLevelsUpdate\n *\n *\n * legacy\n *\n * @prop {undefined} init\n * @prop {undefined} initialZoomIn\n * @prop {undefined} initialZoomOut\n * @prop {undefined} initialZoomInEnd\n * @prop {undefined} initialZoomOutEnd\n * @prop {{ dataSource: DataSource | undefined, numItems: number }} numItems\n * @prop {{ itemData: SlideData; index: number }} itemData\n * @prop {{ index: number, itemData: SlideData, instance: PhotoSwipe }} thumbBounds\n */\n\n/**\n * @typedef {Object} PhotoSwipeFiltersMap https://photoswipe.com/filters/\n *\n * @prop {(numItems: number, dataSource: DataSource | undefined) => number} numItems\n * Modify the total amount of slides. Example on Data sources page.\n * https://photoswipe.com/filters/#numitems\n *\n * @prop {(itemData: SlideData, index: number) => SlideData} itemData\n * Modify slide item data. Example on Data sources page.\n * https://photoswipe.com/filters/#itemdata\n *\n * @prop {(itemData: SlideData, element: HTMLElement, linkEl: HTMLAnchorElement) => SlideData} domItemData\n * Modify item data when it's parsed from DOM element. Example on Data sources page.\n * https://photoswipe.com/filters/#domitemdata\n *\n * @prop {(clickedIndex: number, e: MouseEvent, instance: PhotoSwipeLightbox) => number} clickedIndex\n * Modify clicked gallery item index.\n * https://photoswipe.com/filters/#clickedindex\n *\n * @prop {(placeholderSrc: string | false, content: Content) => string | false} placeholderSrc\n * Modify placeholder image source.\n * https://photoswipe.com/filters/#placeholdersrc\n *\n * @prop {(isContentLoading: boolean, content: Content) => boolean} isContentLoading\n * Modify if the content is currently loading.\n * https://photoswipe.com/filters/#iscontentloading\n *\n * @prop {(isContentZoomable: boolean, content: Content) => boolean} isContentZoomable\n * Modify if the content can be zoomed.\n * https://photoswipe.com/filters/#iscontentzoomable\n *\n * @prop {(useContentPlaceholder: boolean, content: Content) => boolean} useContentPlaceholder\n * Modify if the placeholder should be used for the content.\n * https://photoswipe.com/filters/#usecontentplaceholder\n *\n * @prop {(isKeepingPlaceholder: boolean, content: Content) => boolean} isKeepingPlaceholder\n * Modify if the placeholder should be kept after the content is loaded.\n * https://photoswipe.com/filters/#iskeepingplaceholder\n *\n *\n * @prop {(contentErrorElement: HTMLElement, content: Content) => HTMLElement} contentErrorElement\n * Modify an element when the content has error state (for example, if image cannot be loaded).\n * https://photoswipe.com/filters/#contenterrorelement\n *\n * @prop {(element: HTMLElement, data: UIElementData) => HTMLElement} uiElement\n * Modify a UI element that's being created.\n * https://photoswipe.com/filters/#uielement\n *\n * @prop {(thumbnail: HTMLElement | null | undefined, itemData: SlideData, index: number) => HTMLElement} thumbEl\n * Modify the thumbnail element from which opening zoom animation starts or ends.\n * https://photoswipe.com/filters/#thumbel\n *\n * @prop {(thumbBounds: Bounds | undefined, itemData: SlideData, index: number) => Bounds} thumbBounds\n * Modify the thumbnail bounds from which opening zoom animation starts or ends.\n * https://photoswipe.com/filters/#thumbbounds\n *\n * @prop {(srcsetSizesWidth: number, content: Content) => number} srcsetSizesWidth\n *\n * @prop {(preventPointerEvent: boolean, event: PointerEvent, pointerType: string) => boolean} preventPointerEvent\n *\n */\n\n/**\n * @template {keyof PhotoSwipeFiltersMap} T\n * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter\n */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent<T> : PhotoSwipeEvent<T> & PhotoSwipeEventsMap[T]} AugmentedEvent\n */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {(event: AugmentedEvent<T>) => void} EventCallback\n */\n\n/**\n * Base PhotoSwipe event object\n *\n * @template {keyof PhotoSwipeEventsMap} T\n */\nclass PhotoSwipeEvent {\n  /**\n   * @param {T} type\n   * @param {PhotoSwipeEventsMap[T]} [details]\n   */\n  constructor(type, details) {\n    this.type = type;\n    this.defaultPrevented = false;\n\n    if (details) {\n      Object.assign(this, details);\n    }\n  }\n\n  preventDefault() {\n    this.defaultPrevented = true;\n  }\n\n}\n/**\n * PhotoSwipe base class that can listen and dispatch for events.\n * Shared by PhotoSwipe Core and PhotoSwipe Lightbox, extended by base.js\n */\n\n\nclass Eventable {\n  constructor() {\n    /**\n     * @type {{ [T in keyof PhotoSwipeEventsMap]?: ((event: AugmentedEvent<T>) => void)[] }}\n     */\n    this._listeners = {};\n    /**\n     * @type {{ [T in keyof PhotoSwipeFiltersMap]?: Filter<T>[] }}\n     */\n\n    this._filters = {};\n    /** @type {PhotoSwipe | undefined} */\n\n    this.pswp = undefined;\n    /** @type {PhotoSwipeOptions | undefined} */\n\n    this.options = undefined;\n  }\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {PhotoSwipeFiltersMap[T]} fn\n   * @param {number} priority\n   */\n\n\n  addFilter(name, fn, priority = 100) {\n    var _this$_filters$name, _this$_filters$name2, _this$pswp;\n\n    if (!this._filters[name]) {\n      this._filters[name] = [];\n    }\n\n    (_this$_filters$name = this._filters[name]) === null || _this$_filters$name === void 0 || _this$_filters$name.push({\n      fn,\n      priority\n    });\n    (_this$_filters$name2 = this._filters[name]) === null || _this$_filters$name2 === void 0 || _this$_filters$name2.sort((f1, f2) => f1.priority - f2.priority);\n    (_this$pswp = this.pswp) === null || _this$pswp === void 0 || _this$pswp.addFilter(name, fn, priority);\n  }\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {PhotoSwipeFiltersMap[T]} fn\n   */\n\n\n  removeFilter(name, fn) {\n    if (this._filters[name]) {\n      // @ts-expect-error\n      this._filters[name] = this._filters[name].filter(filter => filter.fn !== fn);\n    }\n\n    if (this.pswp) {\n      this.pswp.removeFilter(name, fn);\n    }\n  }\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {Parameters<PhotoSwipeFiltersMap[T]>} args\n   * @returns {Parameters<PhotoSwipeFiltersMap[T]>[0]}\n   */\n\n\n  applyFilters(name, ...args) {\n    var _this$_filters$name3;\n\n    (_this$_filters$name3 = this._filters[name]) === null || _this$_filters$name3 === void 0 || _this$_filters$name3.forEach(filter => {\n      // @ts-expect-error\n      args[0] = filter.fn.apply(this, args);\n    });\n    return args[0];\n  }\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {EventCallback<T>} fn\n   */\n\n\n  on(name, fn) {\n    var _this$_listeners$name, _this$pswp2;\n\n    if (!this._listeners[name]) {\n      this._listeners[name] = [];\n    }\n\n    (_this$_listeners$name = this._listeners[name]) === null || _this$_listeners$name === void 0 || _this$_listeners$name.push(fn); // When binding events to lightbox,\n    // also bind events to PhotoSwipe Core,\n    // if it's open.\n\n    (_this$pswp2 = this.pswp) === null || _this$pswp2 === void 0 || _this$pswp2.on(name, fn);\n  }\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {EventCallback<T>} fn\n   */\n\n\n  off(name, fn) {\n    var _this$pswp3;\n\n    if (this._listeners[name]) {\n      // @ts-expect-error\n      this._listeners[name] = this._listeners[name].filter(listener => fn !== listener);\n    }\n\n    (_this$pswp3 = this.pswp) === null || _this$pswp3 === void 0 || _this$pswp3.off(name, fn);\n  }\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {PhotoSwipeEventsMap[T]} [details]\n   * @returns {AugmentedEvent<T>}\n   */\n\n\n  dispatch(name, details) {\n    var _this$_listeners$name2;\n\n    if (this.pswp) {\n      return this.pswp.dispatch(name, details);\n    }\n\n    const event =\n    /** @type {AugmentedEvent<T>} */\n    new PhotoSwipeEvent(name, details);\n    (_this$_listeners$name2 = this._listeners[name]) === null || _this$_listeners$name2 === void 0 || _this$_listeners$name2.forEach(listener => {\n      listener.call(this, event);\n    });\n    return event;\n  }\n\n}\n\nclass Placeholder {\n  /**\n   * @param {string | false} imageSrc\n   * @param {HTMLElement} container\n   */\n  constructor(imageSrc, container) {\n    // Create placeholder\n    // (stretched thumbnail or simple div behind the main image)\n\n    /** @type {HTMLImageElement | HTMLDivElement | null} */\n    this.element = createElement('pswp__img pswp__img--placeholder', imageSrc ? 'img' : 'div', container);\n\n    if (imageSrc) {\n      const imgEl =\n      /** @type {HTMLImageElement} */\n      this.element;\n      imgEl.decoding = 'async';\n      imgEl.alt = '';\n      imgEl.src = imageSrc;\n      imgEl.setAttribute('role', 'presentation');\n    }\n\n    this.element.setAttribute('aria-hidden', 'true');\n  }\n  /**\n   * @param {number} width\n   * @param {number} height\n   */\n\n\n  setDisplayedSize(width, height) {\n    if (!this.element) {\n      return;\n    }\n\n    if (this.element.tagName === 'IMG') {\n      // Use transform scale() to modify img placeholder size\n      // (instead of changing width/height directly).\n      // This helps with performance, specifically in iOS15 Safari.\n      setWidthHeight(this.element, 250, 'auto');\n      this.element.style.transformOrigin = '0 0';\n      this.element.style.transform = toTransformString(0, 0, width / 250);\n    } else {\n      setWidthHeight(this.element, width, height);\n    }\n  }\n\n  destroy() {\n    var _this$element;\n\n    if ((_this$element = this.element) !== null && _this$element !== void 0 && _this$element.parentNode) {\n      this.element.remove();\n    }\n\n    this.element = null;\n  }\n\n}\n\n/** @typedef {import('./slide.js').default} Slide */\n\n/** @typedef {import('./slide.js').SlideData} SlideData */\n\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n\n/** @typedef {import('../util/util.js').LoadState} LoadState */\n\nclass Content {\n  /**\n   * @param {SlideData} itemData Slide data\n   * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance\n   * @param {number} index\n   */\n  constructor(itemData, instance, index) {\n    this.instance = instance;\n    this.data = itemData;\n    this.index = index;\n    /** @type {HTMLImageElement | HTMLDivElement | undefined} */\n\n    this.element = undefined;\n    /** @type {Placeholder | undefined} */\n\n    this.placeholder = undefined;\n    /** @type {Slide | undefined} */\n\n    this.slide = undefined;\n    this.displayedImageWidth = 0;\n    this.displayedImageHeight = 0;\n    this.width = Number(this.data.w) || Number(this.data.width) || 0;\n    this.height = Number(this.data.h) || Number(this.data.height) || 0;\n    this.isAttached = false;\n    this.hasSlide = false;\n    this.isDecoding = false;\n    /** @type {LoadState} */\n\n    this.state = LOAD_STATE.IDLE;\n\n    if (this.data.type) {\n      this.type = this.data.type;\n    } else if (this.data.src) {\n      this.type = 'image';\n    } else {\n      this.type = 'html';\n    }\n\n    this.instance.dispatch('contentInit', {\n      content: this\n    });\n  }\n\n  removePlaceholder() {\n    if (this.placeholder && !this.keepPlaceholder()) {\n      // With delay, as image might be loaded, but not rendered\n      setTimeout(() => {\n        if (this.placeholder) {\n          this.placeholder.destroy();\n          this.placeholder = undefined;\n        }\n      }, 1000);\n    }\n  }\n  /**\n   * Preload content\n   *\n   * @param {boolean} isLazy\n   * @param {boolean} [reload]\n   */\n\n\n  load(isLazy, reload) {\n    if (this.slide && this.usePlaceholder()) {\n      if (!this.placeholder) {\n        const placeholderSrc = this.instance.applyFilters('placeholderSrc', // use  image-based placeholder only for the first slide,\n        // as rendering (even small stretched thumbnail) is an expensive operation\n        this.data.msrc && this.slide.isFirstSlide ? this.data.msrc : false, this);\n        this.placeholder = new Placeholder(placeholderSrc, this.slide.container);\n      } else {\n        const placeholderEl = this.placeholder.element; // Add placeholder to DOM if it was already created\n\n        if (placeholderEl && !placeholderEl.parentElement) {\n          this.slide.container.prepend(placeholderEl);\n        }\n      }\n    }\n\n    if (this.element && !reload) {\n      return;\n    }\n\n    if (this.instance.dispatch('contentLoad', {\n      content: this,\n      isLazy\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (this.isImageContent()) {\n      this.element = createElement('pswp__img', 'img'); // Start loading only after width is defined, as sizes might depend on it.\n      // Due to Safari feature, we must define sizes before srcset.\n\n      if (this.displayedImageWidth) {\n        this.loadImage(isLazy);\n      }\n    } else {\n      this.element = createElement('pswp__content', 'div');\n      this.element.innerHTML = this.data.html || '';\n    }\n\n    if (reload && this.slide) {\n      this.slide.updateContentSize(true);\n    }\n  }\n  /**\n   * Preload image\n   *\n   * @param {boolean} isLazy\n   */\n\n\n  loadImage(isLazy) {\n    var _this$data$src, _this$data$alt;\n\n    if (!this.isImageContent() || !this.element || this.instance.dispatch('contentLoadImage', {\n      content: this,\n      isLazy\n    }).defaultPrevented) {\n      return;\n    }\n\n    const imageElement =\n    /** @type HTMLImageElement */\n    this.element;\n    this.updateSrcsetSizes();\n\n    if (this.data.srcset) {\n      imageElement.srcset = this.data.srcset;\n    }\n\n    imageElement.src = (_this$data$src = this.data.src) !== null && _this$data$src !== void 0 ? _this$data$src : '';\n    imageElement.alt = (_this$data$alt = this.data.alt) !== null && _this$data$alt !== void 0 ? _this$data$alt : '';\n    this.state = LOAD_STATE.LOADING;\n\n    if (imageElement.complete) {\n      this.onLoaded();\n    } else {\n      imageElement.onload = () => {\n        this.onLoaded();\n      };\n\n      imageElement.onerror = () => {\n        this.onError();\n      };\n    }\n  }\n  /**\n   * Assign slide to content\n   *\n   * @param {Slide} slide\n   */\n\n\n  setSlide(slide) {\n    this.slide = slide;\n    this.hasSlide = true;\n    this.instance = slide.pswp; // todo: do we need to unset slide?\n  }\n  /**\n   * Content load success handler\n   */\n\n\n  onLoaded() {\n    this.state = LOAD_STATE.LOADED;\n\n    if (this.slide && this.element) {\n      this.instance.dispatch('loadComplete', {\n        slide: this.slide,\n        content: this\n      }); // if content is reloaded\n\n      if (this.slide.isActive && this.slide.heavyAppended && !this.element.parentNode) {\n        this.append();\n        this.slide.updateContentSize(true);\n      }\n\n      if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {\n        this.removePlaceholder();\n      }\n    }\n  }\n  /**\n   * Content load error handler\n   */\n\n\n  onError() {\n    this.state = LOAD_STATE.ERROR;\n\n    if (this.slide) {\n      this.displayError();\n      this.instance.dispatch('loadComplete', {\n        slide: this.slide,\n        isError: true,\n        content: this\n      });\n      this.instance.dispatch('loadError', {\n        slide: this.slide,\n        content: this\n      });\n    }\n  }\n  /**\n   * @returns {Boolean} If the content is currently loading\n   */\n\n\n  isLoading() {\n    return this.instance.applyFilters('isContentLoading', this.state === LOAD_STATE.LOADING, this);\n  }\n  /**\n   * @returns {Boolean} If the content is in error state\n   */\n\n\n  isError() {\n    return this.state === LOAD_STATE.ERROR;\n  }\n  /**\n   * @returns {boolean} If the content is image\n   */\n\n\n  isImageContent() {\n    return this.type === 'image';\n  }\n  /**\n   * Update content size\n   *\n   * @param {Number} width\n   * @param {Number} height\n   */\n\n\n  setDisplayedSize(width, height) {\n    if (!this.element) {\n      return;\n    }\n\n    if (this.placeholder) {\n      this.placeholder.setDisplayedSize(width, height);\n    }\n\n    if (this.instance.dispatch('contentResize', {\n      content: this,\n      width,\n      height\n    }).defaultPrevented) {\n      return;\n    }\n\n    setWidthHeight(this.element, width, height);\n\n    if (this.isImageContent() && !this.isError()) {\n      const isInitialSizeUpdate = !this.displayedImageWidth && width;\n      this.displayedImageWidth = width;\n      this.displayedImageHeight = height;\n\n      if (isInitialSizeUpdate) {\n        this.loadImage(false);\n      } else {\n        this.updateSrcsetSizes();\n      }\n\n      if (this.slide) {\n        this.instance.dispatch('imageSizeChange', {\n          slide: this.slide,\n          width,\n          height,\n          content: this\n        });\n      }\n    }\n  }\n  /**\n   * @returns {boolean} If the content can be zoomed\n   */\n\n\n  isZoomable() {\n    return this.instance.applyFilters('isContentZoomable', this.isImageContent() && this.state !== LOAD_STATE.ERROR, this);\n  }\n  /**\n   * Update image srcset sizes attribute based on width and height\n   */\n\n\n  updateSrcsetSizes() {\n    // Handle srcset sizes attribute.\n    //\n    // Never lower quality, if it was increased previously.\n    // Chrome does this automatically, Firefox and Safari do not,\n    // so we store largest used size in dataset.\n    if (!this.isImageContent() || !this.element || !this.data.srcset) {\n      return;\n    }\n\n    const image =\n    /** @type HTMLImageElement */\n    this.element;\n    const sizesWidth = this.instance.applyFilters('srcsetSizesWidth', this.displayedImageWidth, this);\n\n    if (!image.dataset.largestUsedSize || sizesWidth > parseInt(image.dataset.largestUsedSize, 10)) {\n      image.sizes = sizesWidth + 'px';\n      image.dataset.largestUsedSize = String(sizesWidth);\n    }\n  }\n  /**\n   * @returns {boolean} If content should use a placeholder (from msrc by default)\n   */\n\n\n  usePlaceholder() {\n    return this.instance.applyFilters('useContentPlaceholder', this.isImageContent(), this);\n  }\n  /**\n   * Preload content with lazy-loading param\n   */\n\n\n  lazyLoad() {\n    if (this.instance.dispatch('contentLazyLoad', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    this.load(true);\n  }\n  /**\n   * @returns {boolean} If placeholder should be kept after content is loaded\n   */\n\n\n  keepPlaceholder() {\n    return this.instance.applyFilters('isKeepingPlaceholder', this.isLoading(), this);\n  }\n  /**\n   * Destroy the content\n   */\n\n\n  destroy() {\n    this.hasSlide = false;\n    this.slide = undefined;\n\n    if (this.instance.dispatch('contentDestroy', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    this.remove();\n\n    if (this.placeholder) {\n      this.placeholder.destroy();\n      this.placeholder = undefined;\n    }\n\n    if (this.isImageContent() && this.element) {\n      this.element.onload = null;\n      this.element.onerror = null;\n      this.element = undefined;\n    }\n  }\n  /**\n   * Display error message\n   */\n\n\n  displayError() {\n    if (this.slide) {\n      var _this$instance$option, _this$instance$option2;\n\n      let errorMsgEl = createElement('pswp__error-msg', 'div');\n      errorMsgEl.innerText = (_this$instance$option = (_this$instance$option2 = this.instance.options) === null || _this$instance$option2 === void 0 ? void 0 : _this$instance$option2.errorMsg) !== null && _this$instance$option !== void 0 ? _this$instance$option : '';\n      errorMsgEl =\n      /** @type {HTMLDivElement} */\n      this.instance.applyFilters('contentErrorElement', errorMsgEl, this);\n      this.element = createElement('pswp__content pswp__error-msg-container', 'div');\n      this.element.appendChild(errorMsgEl);\n      this.slide.container.innerText = '';\n      this.slide.container.appendChild(this.element);\n      this.slide.updateContentSize(true);\n      this.removePlaceholder();\n    }\n  }\n  /**\n   * Append the content\n   */\n\n\n  append() {\n    if (this.isAttached || !this.element) {\n      return;\n    }\n\n    this.isAttached = true;\n\n    if (this.state === LOAD_STATE.ERROR) {\n      this.displayError();\n      return;\n    }\n\n    if (this.instance.dispatch('contentAppend', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    const supportsDecode = ('decode' in this.element);\n\n    if (this.isImageContent()) {\n      // Use decode() on nearby slides\n      //\n      // Nearby slide images are in DOM and not hidden via display:none.\n      // However, they are placed offscreen (to the left and right side).\n      //\n      // Some browsers do not composite the image until it's actually visible,\n      // using decode() helps.\n      //\n      // You might ask \"why dont you just decode() and then append all images\",\n      // that's because I want to show image before it's fully loaded,\n      // as browser can render parts of image while it is loading.\n      // We do not do this in Safari due to partial loading bug.\n      if (supportsDecode && this.slide && (!this.slide.isActive || isSafari())) {\n        this.isDecoding = true; // purposefully using finally instead of then,\n        // as if srcset sizes changes dynamically - it may cause decode error\n\n        /** @type {HTMLImageElement} */\n\n        this.element.decode().catch(() => {}).finally(() => {\n          this.isDecoding = false;\n          this.appendImage();\n        });\n      } else {\n        this.appendImage();\n      }\n    } else if (this.slide && !this.element.parentNode) {\n      this.slide.container.appendChild(this.element);\n    }\n  }\n  /**\n   * Activate the slide,\n   * active slide is generally the current one,\n   * meaning the user can see it.\n   */\n\n\n  activate() {\n    if (this.instance.dispatch('contentActivate', {\n      content: this\n    }).defaultPrevented || !this.slide) {\n      return;\n    }\n\n    if (this.isImageContent() && this.isDecoding && !isSafari()) {\n      // add image to slide when it becomes active,\n      // even if it's not finished decoding\n      this.appendImage();\n    } else if (this.isError()) {\n      this.load(false, true); // try to reload\n    }\n\n    if (this.slide.holderElement) {\n      this.slide.holderElement.setAttribute('aria-hidden', 'false');\n    }\n  }\n  /**\n   * Deactivate the content\n   */\n\n\n  deactivate() {\n    this.instance.dispatch('contentDeactivate', {\n      content: this\n    });\n\n    if (this.slide && this.slide.holderElement) {\n      this.slide.holderElement.setAttribute('aria-hidden', 'true');\n    }\n  }\n  /**\n   * Remove the content from DOM\n   */\n\n\n  remove() {\n    this.isAttached = false;\n\n    if (this.instance.dispatch('contentRemove', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    }\n\n    if (this.element && this.element.parentNode) {\n      this.element.remove();\n    }\n\n    if (this.placeholder && this.placeholder.element) {\n      this.placeholder.element.remove();\n    }\n  }\n  /**\n   * Append the image content to slide container\n   */\n\n\n  appendImage() {\n    if (!this.isAttached) {\n      return;\n    }\n\n    if (this.instance.dispatch('contentAppendImage', {\n      content: this\n    }).defaultPrevented) {\n      return;\n    } // ensure that element exists and is not already appended\n\n\n    if (this.slide && this.element && !this.element.parentNode) {\n      this.slide.container.appendChild(this.element);\n    }\n\n    if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {\n      this.removePlaceholder();\n    }\n  }\n\n}\n\n/** @typedef {import('./content.js').default} Content */\n\n/** @typedef {import('./slide.js').default} Slide */\n\n/** @typedef {import('./slide.js').SlideData} SlideData */\n\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\nconst MIN_SLIDES_TO_CACHE = 5;\n/**\n * Lazy-load an image\n * This function is used both by Lightbox and PhotoSwipe core,\n * thus it can be called before dialog is opened.\n *\n * @param {SlideData} itemData Data about the slide\n * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance\n * @param {number} index\n * @returns {Content} Image that is being decoded or false.\n */\n\nfunction lazyLoadData(itemData, instance, index) {\n  const content = instance.createContentFromData(itemData, index);\n  /** @type {ZoomLevel | undefined} */\n\n  let zoomLevel;\n  const {\n    options\n  } = instance; // We need to know dimensions of the image to preload it,\n  // as it might use srcset, and we need to define sizes\n\n  if (options) {\n    zoomLevel = new ZoomLevel(options, itemData, -1);\n    let viewportSize;\n\n    if (instance.pswp) {\n      viewportSize = instance.pswp.viewportSize;\n    } else {\n      viewportSize = getViewportSize(options, instance);\n    }\n\n    const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index);\n    zoomLevel.update(content.width, content.height, panAreaSize);\n  }\n\n  content.lazyLoad();\n\n  if (zoomLevel) {\n    content.setDisplayedSize(Math.ceil(content.width * zoomLevel.initial), Math.ceil(content.height * zoomLevel.initial));\n  }\n\n  return content;\n}\n/**\n * Lazy-loads specific slide.\n * This function is used both by Lightbox and PhotoSwipe core,\n * thus it can be called before dialog is opened.\n *\n * By default, it loads image based on viewport size and initial zoom level.\n *\n * @param {number} index Slide index\n * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox eventable instance\n * @returns {Content | undefined}\n */\n\nfunction lazyLoadSlide(index, instance) {\n  const itemData = instance.getItemData(index);\n\n  if (instance.dispatch('lazyLoadSlide', {\n    index,\n    itemData\n  }).defaultPrevented) {\n    return;\n  }\n\n  return lazyLoadData(itemData, instance, index);\n}\n\nclass ContentLoader {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp; // Total amount of cached images\n\n    this.limit = Math.max(pswp.options.preload[0] + pswp.options.preload[1] + 1, MIN_SLIDES_TO_CACHE);\n    /** @type {Content[]} */\n\n    this._cachedItems = [];\n  }\n  /**\n   * Lazy load nearby slides based on `preload` option.\n   *\n   * @param {number} [diff] Difference between slide indexes that was changed recently, or 0.\n   */\n\n\n  updateLazy(diff) {\n    const {\n      pswp\n    } = this;\n\n    if (pswp.dispatch('lazyLoad').defaultPrevented) {\n      return;\n    }\n\n    const {\n      preload\n    } = pswp.options;\n    const isForward = diff === undefined ? true : diff >= 0;\n    let i; // preload[1] - num items to preload in forward direction\n\n    for (i = 0; i <= preload[1]; i++) {\n      this.loadSlideByIndex(pswp.currIndex + (isForward ? i : -i));\n    } // preload[0] - num items to preload in backward direction\n\n\n    for (i = 1; i <= preload[0]; i++) {\n      this.loadSlideByIndex(pswp.currIndex + (isForward ? -i : i));\n    }\n  }\n  /**\n   * @param {number} initialIndex\n   */\n\n\n  loadSlideByIndex(initialIndex) {\n    const index = this.pswp.getLoopedIndex(initialIndex); // try to get cached content\n\n    let content = this.getContentByIndex(index);\n\n    if (!content) {\n      // no cached content, so try to load from scratch:\n      content = lazyLoadSlide(index, this.pswp); // if content can be loaded, add it to cache:\n\n      if (content) {\n        this.addToCache(content);\n      }\n    }\n  }\n  /**\n   * @param {Slide} slide\n   * @returns {Content}\n   */\n\n\n  getContentBySlide(slide) {\n    let content = this.getContentByIndex(slide.index);\n\n    if (!content) {\n      // create content if not found in cache\n      content = this.pswp.createContentFromData(slide.data, slide.index);\n      this.addToCache(content);\n    } // assign slide to content\n\n\n    content.setSlide(slide);\n    return content;\n  }\n  /**\n   * @param {Content} content\n   */\n\n\n  addToCache(content) {\n    // move to the end of array\n    this.removeByIndex(content.index);\n\n    this._cachedItems.push(content);\n\n    if (this._cachedItems.length > this.limit) {\n      // Destroy the first content that's not attached\n      const indexToRemove = this._cachedItems.findIndex(item => {\n        return !item.isAttached && !item.hasSlide;\n      });\n\n      if (indexToRemove !== -1) {\n        const removedItem = this._cachedItems.splice(indexToRemove, 1)[0];\n\n        removedItem.destroy();\n      }\n    }\n  }\n  /**\n   * Removes an image from cache, does not destroy() it, just removes.\n   *\n   * @param {number} index\n   */\n\n\n  removeByIndex(index) {\n    const indexToRemove = this._cachedItems.findIndex(item => item.index === index);\n\n    if (indexToRemove !== -1) {\n      this._cachedItems.splice(indexToRemove, 1);\n    }\n  }\n  /**\n   * @param {number} index\n   * @returns {Content | undefined}\n   */\n\n\n  getContentByIndex(index) {\n    return this._cachedItems.find(content => content.index === index);\n  }\n\n  destroy() {\n    this._cachedItems.forEach(content => content.destroy());\n\n    this._cachedItems = [];\n  }\n\n}\n\n/** @typedef {import(\"../photoswipe.js\").default} PhotoSwipe */\n\n/** @typedef {import(\"../slide/slide.js\").SlideData} SlideData */\n\n/**\n * PhotoSwipe base class that can retrieve data about every slide.\n * Shared by PhotoSwipe Core and PhotoSwipe Lightbox\n */\n\nclass PhotoSwipeBase extends Eventable {\n  /**\n   * Get total number of slides\n   *\n   * @returns {number}\n   */\n  getNumItems() {\n    var _this$options;\n\n    let numItems = 0;\n    const dataSource = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.dataSource;\n\n    if (dataSource && 'length' in dataSource) {\n      // may be an array or just object with length property\n      numItems = dataSource.length;\n    } else if (dataSource && 'gallery' in dataSource) {\n      // query DOM elements\n      if (!dataSource.items) {\n        dataSource.items = this._getGalleryDOMElements(dataSource.gallery);\n      }\n\n      if (dataSource.items) {\n        numItems = dataSource.items.length;\n      }\n    } // legacy event, before filters were introduced\n\n\n    const event = this.dispatch('numItems', {\n      dataSource,\n      numItems\n    });\n    return this.applyFilters('numItems', event.numItems, dataSource);\n  }\n  /**\n   * @param {SlideData} slideData\n   * @param {number} index\n   * @returns {Content}\n   */\n\n\n  createContentFromData(slideData, index) {\n    return new Content(slideData, this, index);\n  }\n  /**\n   * Get item data by index.\n   *\n   * \"item data\" should contain normalized information that PhotoSwipe needs to generate a slide.\n   * For example, it may contain properties like\n   * `src`, `srcset`, `w`, `h`, which will be used to generate a slide with image.\n   *\n   * @param {number} index\n   * @returns {SlideData}\n   */\n\n\n  getItemData(index) {\n    var _this$options2;\n\n    const dataSource = (_this$options2 = this.options) === null || _this$options2 === void 0 ? void 0 : _this$options2.dataSource;\n    /** @type {SlideData | HTMLElement} */\n\n    let dataSourceItem = {};\n\n    if (Array.isArray(dataSource)) {\n      // Datasource is an array of elements\n      dataSourceItem = dataSource[index];\n    } else if (dataSource && 'gallery' in dataSource) {\n      // dataSource has gallery property,\n      // thus it was created by Lightbox, based on\n      // gallery and children options\n      // query DOM elements\n      if (!dataSource.items) {\n        dataSource.items = this._getGalleryDOMElements(dataSource.gallery);\n      }\n\n      dataSourceItem = dataSource.items[index];\n    }\n\n    let itemData = dataSourceItem;\n\n    if (itemData instanceof Element) {\n      itemData = this._domElementToItemData(itemData);\n    } // Dispatching the itemData event,\n    // it's a legacy verion before filters were introduced\n\n\n    const event = this.dispatch('itemData', {\n      itemData: itemData || {},\n      index\n    });\n    return this.applyFilters('itemData', event.itemData, index);\n  }\n  /**\n   * Get array of gallery DOM elements,\n   * based on childSelector and gallery element.\n   *\n   * @param {HTMLElement} galleryElement\n   * @returns {HTMLElement[]}\n   */\n\n\n  _getGalleryDOMElements(galleryElement) {\n    var _this$options3, _this$options4;\n\n    if ((_this$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.children || (_this$options4 = this.options) !== null && _this$options4 !== void 0 && _this$options4.childSelector) {\n      return getElementsFromOption(this.options.children, this.options.childSelector, galleryElement) || [];\n    }\n\n    return [galleryElement];\n  }\n  /**\n   * Converts DOM element to item data object.\n   *\n   * @param {HTMLElement} element DOM element\n   * @returns {SlideData}\n   */\n\n\n  _domElementToItemData(element) {\n    /** @type {SlideData} */\n    const itemData = {\n      element\n    };\n    const linkEl =\n    /** @type {HTMLAnchorElement} */\n    element.tagName === 'A' ? element : element.querySelector('a');\n\n    if (linkEl) {\n      // src comes from data-pswp-src attribute,\n      // if it's empty link href is used\n      itemData.src = linkEl.dataset.pswpSrc || linkEl.href;\n\n      if (linkEl.dataset.pswpSrcset) {\n        itemData.srcset = linkEl.dataset.pswpSrcset;\n      }\n\n      itemData.width = linkEl.dataset.pswpWidth ? parseInt(linkEl.dataset.pswpWidth, 10) : 0;\n      itemData.height = linkEl.dataset.pswpHeight ? parseInt(linkEl.dataset.pswpHeight, 10) : 0; // support legacy w & h properties\n\n      itemData.w = itemData.width;\n      itemData.h = itemData.height;\n\n      if (linkEl.dataset.pswpType) {\n        itemData.type = linkEl.dataset.pswpType;\n      }\n\n      const thumbnailEl = element.querySelector('img');\n\n      if (thumbnailEl) {\n        var _thumbnailEl$getAttri;\n\n        // msrc is URL to placeholder image that's displayed before large image is loaded\n        // by default it's displayed only for the first slide\n        itemData.msrc = thumbnailEl.currentSrc || thumbnailEl.src;\n        itemData.alt = (_thumbnailEl$getAttri = thumbnailEl.getAttribute('alt')) !== null && _thumbnailEl$getAttri !== void 0 ? _thumbnailEl$getAttri : '';\n      }\n\n      if (linkEl.dataset.pswpCropped || linkEl.dataset.cropped) {\n        itemData.thumbCropped = true;\n      }\n    }\n\n    return this.applyFilters('domItemData', itemData, element, linkEl);\n  }\n  /**\n   * Lazy-load by slide data\n   *\n   * @param {SlideData} itemData Data about the slide\n   * @param {number} index\n   * @returns {Content} Image that is being decoded or false.\n   */\n\n\n  lazyLoadData(itemData, index) {\n    return lazyLoadData(itemData, this, index);\n  }\n\n}\n\n/** @typedef {import('./photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {import('./slide/get-thumb-bounds.js').Bounds} Bounds */\n\n/** @typedef {import('./util/animations.js').AnimationProps} AnimationProps */\n// some browsers do not paint\n// elements which opacity is set to 0,\n// since we need to pre-render elements for the animation -\n// we set it to the minimum amount\n\nconst MIN_OPACITY = 0.003;\n/**\n * Manages opening and closing transitions of the PhotoSwipe.\n *\n * It can perform zoom, fade or no transition.\n */\n\nclass Opener {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    this.isClosed = true;\n    this.isOpen = false;\n    this.isClosing = false;\n    this.isOpening = false;\n    /**\n     * @private\n     * @type {number | false | undefined}\n     */\n\n    this._duration = undefined;\n    /** @private */\n\n    this._useAnimation = false;\n    /** @private */\n\n    this._croppedZoom = false;\n    /** @private */\n\n    this._animateRootOpacity = false;\n    /** @private */\n\n    this._animateBgOpacity = false;\n    /**\n     * @private\n     * @type { HTMLDivElement | HTMLImageElement | null | undefined }\n     */\n\n    this._placeholder = undefined;\n    /**\n     * @private\n     * @type { HTMLDivElement | undefined }\n     */\n\n    this._opacityElement = undefined;\n    /**\n     * @private\n     * @type { HTMLDivElement | undefined }\n     */\n\n    this._cropContainer1 = undefined;\n    /**\n     * @private\n     * @type { HTMLElement | null | undefined }\n     */\n\n    this._cropContainer2 = undefined;\n    /**\n     * @private\n     * @type {Bounds | undefined}\n     */\n\n    this._thumbBounds = undefined;\n    this._prepareOpen = this._prepareOpen.bind(this); // Override initial zoom and pan position\n\n    pswp.on('firstZoomPan', this._prepareOpen);\n  }\n\n  open() {\n    this._prepareOpen();\n\n    this._start();\n  }\n\n  close() {\n    if (this.isClosed || this.isClosing || this.isOpening) {\n      // if we close during opening animation\n      // for now do nothing,\n      // browsers aren't good at changing the direction of the CSS transition\n      return;\n    }\n\n    const slide = this.pswp.currSlide;\n    this.isOpen = false;\n    this.isOpening = false;\n    this.isClosing = true;\n    this._duration = this.pswp.options.hideAnimationDuration;\n\n    if (slide && slide.currZoomLevel * slide.width >= this.pswp.options.maxWidthToAnimate) {\n      this._duration = 0;\n    }\n\n    this._applyStartProps();\n\n    setTimeout(() => {\n      this._start();\n    }, this._croppedZoom ? 30 : 0);\n  }\n  /** @private */\n\n\n  _prepareOpen() {\n    this.pswp.off('firstZoomPan', this._prepareOpen);\n\n    if (!this.isOpening) {\n      const slide = this.pswp.currSlide;\n      this.isOpening = true;\n      this.isClosing = false;\n      this._duration = this.pswp.options.showAnimationDuration;\n\n      if (slide && slide.zoomLevels.initial * slide.width >= this.pswp.options.maxWidthToAnimate) {\n        this._duration = 0;\n      }\n\n      this._applyStartProps();\n    }\n  }\n  /** @private */\n\n\n  _applyStartProps() {\n    const {\n      pswp\n    } = this;\n    const slide = this.pswp.currSlide;\n    const {\n      options\n    } = pswp;\n\n    if (options.showHideAnimationType === 'fade') {\n      options.showHideOpacity = true;\n      this._thumbBounds = undefined;\n    } else if (options.showHideAnimationType === 'none') {\n      options.showHideOpacity = false;\n      this._duration = 0;\n      this._thumbBounds = undefined;\n    } else if (this.isOpening && pswp._initialThumbBounds) {\n      // Use initial bounds if defined\n      this._thumbBounds = pswp._initialThumbBounds;\n    } else {\n      this._thumbBounds = this.pswp.getThumbBounds();\n    }\n\n    this._placeholder = slide === null || slide === void 0 ? void 0 : slide.getPlaceholderElement();\n    pswp.animations.stopAll(); // Discard animations when duration is less than 50ms\n\n    this._useAnimation = Boolean(this._duration && this._duration > 50);\n    this._animateZoom = Boolean(this._thumbBounds) && (slide === null || slide === void 0 ? void 0 : slide.content.usePlaceholder()) && (!this.isClosing || !pswp.mainScroll.isShifted());\n\n    if (!this._animateZoom) {\n      this._animateRootOpacity = true;\n\n      if (this.isOpening && slide) {\n        slide.zoomAndPanToInitial();\n        slide.applyCurrentZoomPan();\n      }\n    } else {\n      var _options$showHideOpac;\n\n      this._animateRootOpacity = (_options$showHideOpac = options.showHideOpacity) !== null && _options$showHideOpac !== void 0 ? _options$showHideOpac : false;\n    }\n\n    this._animateBgOpacity = !this._animateRootOpacity && this.pswp.options.bgOpacity > MIN_OPACITY;\n    this._opacityElement = this._animateRootOpacity ? pswp.element : pswp.bg;\n\n    if (!this._useAnimation) {\n      this._duration = 0;\n      this._animateZoom = false;\n      this._animateBgOpacity = false;\n      this._animateRootOpacity = true;\n\n      if (this.isOpening) {\n        if (pswp.element) {\n          pswp.element.style.opacity = String(MIN_OPACITY);\n        }\n\n        pswp.applyBgOpacity(1);\n      }\n\n      return;\n    }\n\n    if (this._animateZoom && this._thumbBounds && this._thumbBounds.innerRect) {\n      var _this$pswp$currSlide;\n\n      // Properties are used when animation from cropped thumbnail\n      this._croppedZoom = true;\n      this._cropContainer1 = this.pswp.container;\n      this._cropContainer2 = (_this$pswp$currSlide = this.pswp.currSlide) === null || _this$pswp$currSlide === void 0 ? void 0 : _this$pswp$currSlide.holderElement;\n\n      if (pswp.container) {\n        pswp.container.style.overflow = 'hidden';\n        pswp.container.style.width = pswp.viewportSize.x + 'px';\n      }\n    } else {\n      this._croppedZoom = false;\n    }\n\n    if (this.isOpening) {\n      // Apply styles before opening transition\n      if (this._animateRootOpacity) {\n        if (pswp.element) {\n          pswp.element.style.opacity = String(MIN_OPACITY);\n        }\n\n        pswp.applyBgOpacity(1);\n      } else {\n        if (this._animateBgOpacity && pswp.bg) {\n          pswp.bg.style.opacity = String(MIN_OPACITY);\n        }\n\n        if (pswp.element) {\n          pswp.element.style.opacity = '1';\n        }\n      }\n\n      if (this._animateZoom) {\n        this._setClosedStateZoomPan();\n\n        if (this._placeholder) {\n          // tell browser that we plan to animate the placeholder\n          this._placeholder.style.willChange = 'transform'; // hide placeholder to allow hiding of\n          // elements that overlap it (such as icons over the thumbnail)\n\n          this._placeholder.style.opacity = String(MIN_OPACITY);\n        }\n      }\n    } else if (this.isClosing) {\n      // hide nearby slides to make sure that\n      // they are not painted during the transition\n      if (pswp.mainScroll.itemHolders[0]) {\n        pswp.mainScroll.itemHolders[0].el.style.display = 'none';\n      }\n\n      if (pswp.mainScroll.itemHolders[2]) {\n        pswp.mainScroll.itemHolders[2].el.style.display = 'none';\n      }\n\n      if (this._croppedZoom) {\n        if (pswp.mainScroll.x !== 0) {\n          // shift the main scroller to zero position\n          pswp.mainScroll.resetPosition();\n          pswp.mainScroll.resize();\n        }\n      }\n    }\n  }\n  /** @private */\n\n\n  _start() {\n    if (this.isOpening && this._useAnimation && this._placeholder && this._placeholder.tagName === 'IMG') {\n      // To ensure smooth animation\n      // we wait till the current slide image placeholder is decoded,\n      // but no longer than 250ms,\n      // and no shorter than 50ms\n      // (just using requestanimationframe is not enough in Firefox,\n      // for some reason)\n      new Promise(resolve => {\n        let decoded = false;\n        let isDelaying = true;\n        decodeImage(\n        /** @type {HTMLImageElement} */\n        this._placeholder).finally(() => {\n          decoded = true;\n\n          if (!isDelaying) {\n            resolve(true);\n          }\n        });\n        setTimeout(() => {\n          isDelaying = false;\n\n          if (decoded) {\n            resolve(true);\n          }\n        }, 50);\n        setTimeout(resolve, 250);\n      }).finally(() => this._initiate());\n    } else {\n      this._initiate();\n    }\n  }\n  /** @private */\n\n\n  _initiate() {\n    var _this$pswp$element, _this$pswp$element2;\n\n    (_this$pswp$element = this.pswp.element) === null || _this$pswp$element === void 0 || _this$pswp$element.style.setProperty('--pswp-transition-duration', this._duration + 'ms');\n    this.pswp.dispatch(this.isOpening ? 'openingAnimationStart' : 'closingAnimationStart'); // legacy event\n\n    this.pswp.dispatch(\n    /** @type {'initialZoomIn' | 'initialZoomOut'} */\n    'initialZoom' + (this.isOpening ? 'In' : 'Out'));\n    (_this$pswp$element2 = this.pswp.element) === null || _this$pswp$element2 === void 0 || _this$pswp$element2.classList.toggle('pswp--ui-visible', this.isOpening);\n\n    if (this.isOpening) {\n      if (this._placeholder) {\n        // unhide the placeholder\n        this._placeholder.style.opacity = '1';\n      }\n\n      this._animateToOpenState();\n    } else if (this.isClosing) {\n      this._animateToClosedState();\n    }\n\n    if (!this._useAnimation) {\n      this._onAnimationComplete();\n    }\n  }\n  /** @private */\n\n\n  _onAnimationComplete() {\n    const {\n      pswp\n    } = this;\n    this.isOpen = this.isOpening;\n    this.isClosed = this.isClosing;\n    this.isOpening = false;\n    this.isClosing = false;\n    pswp.dispatch(this.isOpen ? 'openingAnimationEnd' : 'closingAnimationEnd'); // legacy event\n\n    pswp.dispatch(\n    /** @type {'initialZoomInEnd' | 'initialZoomOutEnd'} */\n    'initialZoom' + (this.isOpen ? 'InEnd' : 'OutEnd'));\n\n    if (this.isClosed) {\n      pswp.destroy();\n    } else if (this.isOpen) {\n      var _pswp$currSlide;\n\n      if (this._animateZoom && pswp.container) {\n        pswp.container.style.overflow = 'visible';\n        pswp.container.style.width = '100%';\n      }\n\n      (_pswp$currSlide = pswp.currSlide) === null || _pswp$currSlide === void 0 || _pswp$currSlide.applyCurrentZoomPan();\n    }\n  }\n  /** @private */\n\n\n  _animateToOpenState() {\n    const {\n      pswp\n    } = this;\n\n    if (this._animateZoom) {\n      if (this._croppedZoom && this._cropContainer1 && this._cropContainer2) {\n        this._animateTo(this._cropContainer1, 'transform', 'translate3d(0,0,0)');\n\n        this._animateTo(this._cropContainer2, 'transform', 'none');\n      }\n\n      if (pswp.currSlide) {\n        pswp.currSlide.zoomAndPanToInitial();\n\n        this._animateTo(pswp.currSlide.container, 'transform', pswp.currSlide.getCurrentTransform());\n      }\n    }\n\n    if (this._animateBgOpacity && pswp.bg) {\n      this._animateTo(pswp.bg, 'opacity', String(pswp.options.bgOpacity));\n    }\n\n    if (this._animateRootOpacity && pswp.element) {\n      this._animateTo(pswp.element, 'opacity', '1');\n    }\n  }\n  /** @private */\n\n\n  _animateToClosedState() {\n    const {\n      pswp\n    } = this;\n\n    if (this._animateZoom) {\n      this._setClosedStateZoomPan(true);\n    } // do not animate opacity if it's already at 0\n\n\n    if (this._animateBgOpacity && pswp.bgOpacity > 0.01 && pswp.bg) {\n      this._animateTo(pswp.bg, 'opacity', '0');\n    }\n\n    if (this._animateRootOpacity && pswp.element) {\n      this._animateTo(pswp.element, 'opacity', '0');\n    }\n  }\n  /**\n   * @private\n   * @param {boolean} [animate]\n   */\n\n\n  _setClosedStateZoomPan(animate) {\n    if (!this._thumbBounds) return;\n    const {\n      pswp\n    } = this;\n    const {\n      innerRect\n    } = this._thumbBounds;\n    const {\n      currSlide,\n      viewportSize\n    } = pswp;\n\n    if (this._croppedZoom && innerRect && this._cropContainer1 && this._cropContainer2) {\n      const containerOnePanX = -viewportSize.x + (this._thumbBounds.x - innerRect.x) + innerRect.w;\n      const containerOnePanY = -viewportSize.y + (this._thumbBounds.y - innerRect.y) + innerRect.h;\n      const containerTwoPanX = viewportSize.x - innerRect.w;\n      const containerTwoPanY = viewportSize.y - innerRect.h;\n\n      if (animate) {\n        this._animateTo(this._cropContainer1, 'transform', toTransformString(containerOnePanX, containerOnePanY));\n\n        this._animateTo(this._cropContainer2, 'transform', toTransformString(containerTwoPanX, containerTwoPanY));\n      } else {\n        setTransform(this._cropContainer1, containerOnePanX, containerOnePanY);\n        setTransform(this._cropContainer2, containerTwoPanX, containerTwoPanY);\n      }\n    }\n\n    if (currSlide) {\n      equalizePoints(currSlide.pan, innerRect || this._thumbBounds);\n      currSlide.currZoomLevel = this._thumbBounds.w / currSlide.width;\n\n      if (animate) {\n        this._animateTo(currSlide.container, 'transform', currSlide.getCurrentTransform());\n      } else {\n        currSlide.applyCurrentZoomPan();\n      }\n    }\n  }\n  /**\n   * @private\n   * @param {HTMLElement} target\n   * @param {'transform' | 'opacity'} prop\n   * @param {string} propValue\n   */\n\n\n  _animateTo(target, prop, propValue) {\n    if (!this._duration) {\n      target.style[prop] = propValue;\n      return;\n    }\n\n    const {\n      animations\n    } = this.pswp;\n    /** @type {AnimationProps} */\n\n    const animProps = {\n      duration: this._duration,\n      easing: this.pswp.options.easing,\n      onComplete: () => {\n        if (!animations.activeAnimations.length) {\n          this._onAnimationComplete();\n        }\n      },\n      target\n    };\n    animProps[prop] = propValue;\n    animations.startTransition(animProps);\n  }\n\n}\n\n/**\n * @template T\n * @typedef {import('./types.js').Type<T>} Type<T>\n */\n\n/** @typedef {import('./slide/slide.js').SlideData} SlideData */\n\n/** @typedef {import('./slide/zoom-level.js').ZoomLevelOption} ZoomLevelOption */\n\n/** @typedef {import('./ui/ui-element.js').UIElementData} UIElementData */\n\n/** @typedef {import('./main-scroll.js').ItemHolder} ItemHolder */\n\n/** @typedef {import('./core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */\n\n/** @typedef {import('./core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */\n\n/** @typedef {import('./slide/get-thumb-bounds').Bounds} Bounds */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {import('./core/eventable.js').EventCallback<T>} EventCallback<T>\n */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {import('./core/eventable.js').AugmentedEvent<T>} AugmentedEvent<T>\n */\n\n/** @typedef {{ x: number; y: number; id?: string | number }} Point */\n\n/** @typedef {{ top: number; bottom: number; left: number; right: number }} Padding */\n\n/** @typedef {SlideData[]} DataSourceArray */\n\n/** @typedef {{ gallery: HTMLElement; items?: HTMLElement[] }} DataSourceObject */\n\n/** @typedef {DataSourceArray | DataSourceObject} DataSource */\n\n/** @typedef {(point: Point, originalEvent: PointerEvent) => void} ActionFn */\n\n/** @typedef {'close' | 'next' | 'zoom' | 'zoom-or-close' | 'toggle-controls'} ActionType */\n\n/** @typedef {Type<PhotoSwipe> | { default: Type<PhotoSwipe> }} PhotoSwipeModule */\n\n/** @typedef {PhotoSwipeModule | Promise<PhotoSwipeModule> | (() => Promise<PhotoSwipeModule>)} PhotoSwipeModuleOption */\n\n/**\n * @typedef {string | NodeListOf<HTMLElement> | HTMLElement[] | HTMLElement} ElementProvider\n */\n\n/** @typedef {Partial<PreparedPhotoSwipeOptions>} PhotoSwipeOptions https://photoswipe.com/options/ */\n\n/**\n * @typedef {Object} PreparedPhotoSwipeOptions\n *\n * @prop {DataSource} [dataSource]\n * Pass an array of any items via dataSource option. Its length will determine amount of slides\n * (which may be modified further from numItems event).\n *\n * Each item should contain data that you need to generate slide\n * (for image slide it would be src (image URL), width (image width), height, srcset, alt).\n *\n * If these properties are not present in your initial array, you may \"pre-parse\" each item from itemData filter.\n *\n * @prop {number} bgOpacity\n * Background backdrop opacity, always define it via this option and not via CSS rgba color.\n *\n * @prop {number} spacing\n * Spacing between slides. Defined as ratio relative to the viewport width (0.1 = 10% of viewport).\n *\n * @prop {boolean} allowPanToNext\n * Allow swipe navigation to the next slide when the current slide is zoomed. Does not apply to mouse events.\n *\n * @prop {boolean} loop\n * If set to true you'll be able to swipe from the last to the first image.\n * Option is always false when there are less than 3 slides.\n *\n * @prop {boolean} [wheelToZoom]\n * By default PhotoSwipe zooms image with ctrl-wheel, if you enable this option - image will zoom just via wheel.\n *\n * @prop {boolean} pinchToClose\n * Pinch touch gesture to close the gallery.\n *\n * @prop {boolean} closeOnVerticalDrag\n * Vertical drag gesture to close the PhotoSwipe.\n *\n * @prop {Padding} [padding]\n * Slide area padding (in pixels).\n *\n * @prop {(viewportSize: Point, itemData: SlideData, index: number) => Padding} [paddingFn]\n * The option is checked frequently, so make sure it's performant. Overrides padding option if defined. For example:\n *\n * @prop {number | false} hideAnimationDuration\n * Transition duration in milliseconds, can be 0.\n *\n * @prop {number | false} showAnimationDuration\n * Transition duration in milliseconds, can be 0.\n *\n * @prop {number | false} zoomAnimationDuration\n * Transition duration in milliseconds, can be 0.\n *\n * @prop {string} easing\n * String, 'cubic-bezier(.4,0,.22,1)'. CSS easing function for open/close/zoom transitions.\n *\n * @prop {boolean} escKey\n * Esc key to close.\n *\n * @prop {boolean} arrowKeys\n * Left/right arrow keys for navigation.\n *\n * @prop {boolean} trapFocus\n * Trap focus within PhotoSwipe element while it's open.\n *\n * @prop {boolean} returnFocus\n * Restore focus the last active element after PhotoSwipe is closed.\n *\n * @prop {boolean} clickToCloseNonZoomable\n * If image is not zoomable (for example, smaller than viewport) it can be closed by clicking on it.\n *\n * @prop {ActionType | ActionFn | false} imageClickAction\n * Refer to click and tap actions page.\n *\n * @prop {ActionType | ActionFn | false} bgClickAction\n * Refer to click and tap actions page.\n *\n * @prop {ActionType | ActionFn | false} tapAction\n * Refer to click and tap actions page.\n *\n * @prop {ActionType | ActionFn | false} doubleTapAction\n * Refer to click and tap actions page.\n *\n * @prop {number} preloaderDelay\n * Delay before the loading indicator will be displayed,\n * if image is loaded during it - the indicator will not be displayed at all. Can be zero.\n *\n * @prop {string} indexIndicatorSep\n * Used for slide count indicator (\"1 of 10 \").\n *\n * @prop {(options: PhotoSwipeOptions, pswp: PhotoSwipeBase) => Point} [getViewportSizeFn]\n * A function that should return slide viewport width and height, in format {x: 100, y: 100}.\n *\n * @prop {string} errorMsg\n * Message to display when the image wasn't able to load. If you need to display HTML - use contentErrorElement filter.\n *\n * @prop {[number, number]} preload\n * Lazy loading of nearby slides based on direction of movement. Should be an array with two integers,\n * first one - number of items to preload before the current image, second one - after the current image.\n * Two nearby images are always loaded.\n *\n * @prop {string} [mainClass]\n * Class that will be added to the root element of PhotoSwipe, may contain multiple separated by space.\n * Example on Styling page.\n *\n * @prop {HTMLElement} [appendToEl]\n * Element to which PhotoSwipe dialog will be appended when it opens.\n *\n * @prop {number} maxWidthToAnimate\n * Maximum width of image to animate, if initial rendered image width\n * is larger than this value - the opening/closing transition will be automatically disabled.\n *\n * @prop {string} [closeTitle]\n * Translating\n *\n * @prop {string} [zoomTitle]\n * Translating\n *\n * @prop {string} [arrowPrevTitle]\n * Translating\n *\n * @prop {string} [arrowNextTitle]\n * Translating\n *\n * @prop {'zoom' | 'fade' | 'none'} [showHideAnimationType]\n * To adjust opening or closing transition type use lightbox option `showHideAnimationType` (`String`).\n * It supports three values - `zoom` (default), `fade` (default if there is no thumbnail) and `none`.\n *\n * Animations are automatically disabled if user `(prefers-reduced-motion: reduce)`.\n *\n * @prop {number} index\n * Defines start slide index.\n *\n * @prop {(e: MouseEvent) => number} [getClickedIndexFn]\n *\n * @prop {boolean} [arrowPrev]\n * @prop {boolean} [arrowNext]\n * @prop {boolean} [zoom]\n * @prop {boolean} [close]\n * @prop {boolean} [counter]\n *\n * @prop {string} [arrowPrevSVG]\n * @prop {string} [arrowNextSVG]\n * @prop {string} [zoomSVG]\n * @prop {string} [closeSVG]\n * @prop {string} [counterSVG]\n *\n * @prop {string} [arrowPrevTitle]\n * @prop {string} [arrowNextTitle]\n * @prop {string} [zoomTitle]\n * @prop {string} [closeTitle]\n * @prop {string} [counterTitle]\n *\n * @prop {ZoomLevelOption} [initialZoomLevel]\n * @prop {ZoomLevelOption} [secondaryZoomLevel]\n * @prop {ZoomLevelOption} [maxZoomLevel]\n *\n * @prop {boolean} [mouseMovePan]\n * @prop {Point | null} [initialPointerPos]\n * @prop {boolean} [showHideOpacity]\n *\n * @prop {PhotoSwipeModuleOption} [pswpModule]\n * @prop {() => Promise<any>} [openPromise]\n * @prop {boolean} [preloadFirstSlide]\n * @prop {ElementProvider} [gallery]\n * @prop {string} [gallerySelector]\n * @prop {ElementProvider} [children]\n * @prop {string} [childSelector]\n * @prop {string | false} [thumbSelector]\n */\n\n/** @type {PreparedPhotoSwipeOptions} */\n\nconst defaultOptions = {\n  allowPanToNext: true,\n  spacing: 0.1,\n  loop: true,\n  pinchToClose: true,\n  closeOnVerticalDrag: true,\n  hideAnimationDuration: 333,\n  showAnimationDuration: 333,\n  zoomAnimationDuration: 333,\n  escKey: true,\n  arrowKeys: true,\n  trapFocus: true,\n  returnFocus: true,\n  maxWidthToAnimate: 4000,\n  clickToCloseNonZoomable: true,\n  imageClickAction: 'zoom-or-close',\n  bgClickAction: 'close',\n  tapAction: 'toggle-controls',\n  doubleTapAction: 'zoom',\n  indexIndicatorSep: ' / ',\n  preloaderDelay: 2000,\n  bgOpacity: 0.8,\n  index: 0,\n  errorMsg: 'The image cannot be loaded',\n  preload: [1, 2],\n  easing: 'cubic-bezier(.4,0,.22,1)'\n};\n/**\n * PhotoSwipe Core\n */\n\nclass PhotoSwipe extends PhotoSwipeBase {\n  /**\n   * @param {PhotoSwipeOptions} [options]\n   */\n  constructor(options) {\n    super();\n    this.options = this._prepareOptions(options || {});\n    /**\n     * offset of viewport relative to document\n     *\n     * @type {Point}\n     */\n\n    this.offset = {\n      x: 0,\n      y: 0\n    };\n    /**\n     * @type {Point}\n     * @private\n     */\n\n    this._prevViewportSize = {\n      x: 0,\n      y: 0\n    };\n    /**\n     * Size of scrollable PhotoSwipe viewport\n     *\n     * @type {Point}\n     */\n\n    this.viewportSize = {\n      x: 0,\n      y: 0\n    };\n    /**\n     * background (backdrop) opacity\n     */\n\n    this.bgOpacity = 1;\n    this.currIndex = 0;\n    this.potentialIndex = 0;\n    this.isOpen = false;\n    this.isDestroying = false;\n    this.hasMouse = false;\n    /**\n     * @private\n     * @type {SlideData}\n     */\n\n    this._initialItemData = {};\n    /** @type {Bounds | undefined} */\n\n    this._initialThumbBounds = undefined;\n    /** @type {HTMLDivElement | undefined} */\n\n    this.topBar = undefined;\n    /** @type {HTMLDivElement | undefined} */\n\n    this.element = undefined;\n    /** @type {HTMLDivElement | undefined} */\n\n    this.template = undefined;\n    /** @type {HTMLDivElement | undefined} */\n\n    this.container = undefined;\n    /** @type {HTMLElement | undefined} */\n\n    this.scrollWrap = undefined;\n    /** @type {Slide | undefined} */\n\n    this.currSlide = undefined;\n    this.events = new DOMEvents();\n    this.animations = new Animations();\n    this.mainScroll = new MainScroll(this);\n    this.gestures = new Gestures(this);\n    this.opener = new Opener(this);\n    this.keyboard = new Keyboard(this);\n    this.contentLoader = new ContentLoader(this);\n  }\n  /** @returns {boolean} */\n\n\n  init() {\n    if (this.isOpen || this.isDestroying) {\n      return false;\n    }\n\n    this.isOpen = true;\n    this.dispatch('init'); // legacy\n\n    this.dispatch('beforeOpen');\n\n    this._createMainStructure(); // add classes to the root element of PhotoSwipe\n\n\n    let rootClasses = 'pswp--open';\n\n    if (this.gestures.supportsTouch) {\n      rootClasses += ' pswp--touch';\n    }\n\n    if (this.options.mainClass) {\n      rootClasses += ' ' + this.options.mainClass;\n    }\n\n    if (this.element) {\n      this.element.className += ' ' + rootClasses;\n    }\n\n    this.currIndex = this.options.index || 0;\n    this.potentialIndex = this.currIndex;\n    this.dispatch('firstUpdate'); // starting index can be modified here\n    // initialize scroll wheel handler to block the scroll\n\n    this.scrollWheel = new ScrollWheel(this); // sanitize index\n\n    if (Number.isNaN(this.currIndex) || this.currIndex < 0 || this.currIndex >= this.getNumItems()) {\n      this.currIndex = 0;\n    }\n\n    if (!this.gestures.supportsTouch) {\n      // enable mouse features if no touch support detected\n      this.mouseDetected();\n    } // causes forced synchronous layout\n\n\n    this.updateSize();\n    this.offset.y = window.pageYOffset;\n    this._initialItemData = this.getItemData(this.currIndex);\n    this.dispatch('gettingData', {\n      index: this.currIndex,\n      data: this._initialItemData,\n      slide: undefined\n    }); // *Layout* - calculate size and position of elements here\n\n    this._initialThumbBounds = this.getThumbBounds();\n    this.dispatch('initialLayout');\n    this.on('openingAnimationEnd', () => {\n      const {\n        itemHolders\n      } = this.mainScroll; // Add content to the previous and next slide\n\n      if (itemHolders[0]) {\n        itemHolders[0].el.style.display = 'block';\n        this.setContent(itemHolders[0], this.currIndex - 1);\n      }\n\n      if (itemHolders[2]) {\n        itemHolders[2].el.style.display = 'block';\n        this.setContent(itemHolders[2], this.currIndex + 1);\n      }\n\n      this.appendHeavy();\n      this.contentLoader.updateLazy();\n      this.events.add(window, 'resize', this._handlePageResize.bind(this));\n      this.events.add(window, 'scroll', this._updatePageScrollOffset.bind(this));\n      this.dispatch('bindEvents');\n    }); // set content for center slide (first time)\n\n    if (this.mainScroll.itemHolders[1]) {\n      this.setContent(this.mainScroll.itemHolders[1], this.currIndex);\n    }\n\n    this.dispatch('change');\n    this.opener.open();\n    this.dispatch('afterInit');\n    return true;\n  }\n  /**\n   * Get looped slide index\n   * (for example, -1 will return the last slide)\n   *\n   * @param {number} index\n   * @returns {number}\n   */\n\n\n  getLoopedIndex(index) {\n    const numSlides = this.getNumItems();\n\n    if (this.options.loop) {\n      if (index > numSlides - 1) {\n        index -= numSlides;\n      }\n\n      if (index < 0) {\n        index += numSlides;\n      }\n    }\n\n    return clamp(index, 0, numSlides - 1);\n  }\n\n  appendHeavy() {\n    this.mainScroll.itemHolders.forEach(itemHolder => {\n      var _itemHolder$slide;\n\n      (_itemHolder$slide = itemHolder.slide) === null || _itemHolder$slide === void 0 || _itemHolder$slide.appendHeavy();\n    });\n  }\n  /**\n   * Change the slide\n   * @param {number} index New index\n   */\n\n\n  goTo(index) {\n    this.mainScroll.moveIndexBy(this.getLoopedIndex(index) - this.potentialIndex);\n  }\n  /**\n   * Go to the next slide.\n   */\n\n\n  next() {\n    this.goTo(this.potentialIndex + 1);\n  }\n  /**\n   * Go to the previous slide.\n   */\n\n\n  prev() {\n    this.goTo(this.potentialIndex - 1);\n  }\n  /**\n   * @see slide/slide.js zoomTo\n   *\n   * @param {Parameters<Slide['zoomTo']>} args\n   */\n\n\n  zoomTo(...args) {\n    var _this$currSlide;\n\n    (_this$currSlide = this.currSlide) === null || _this$currSlide === void 0 || _this$currSlide.zoomTo(...args);\n  }\n  /**\n   * @see slide/slide.js toggleZoom\n   */\n\n\n  toggleZoom() {\n    var _this$currSlide2;\n\n    (_this$currSlide2 = this.currSlide) === null || _this$currSlide2 === void 0 || _this$currSlide2.toggleZoom();\n  }\n  /**\n   * Close the gallery.\n   * After closing transition ends - destroy it\n   */\n\n\n  close() {\n    if (!this.opener.isOpen || this.isDestroying) {\n      return;\n    }\n\n    this.isDestroying = true;\n    this.dispatch('close');\n    this.events.removeAll();\n    this.opener.close();\n  }\n  /**\n   * Destroys the gallery:\n   * - instantly closes the gallery\n   * - unbinds events,\n   * - cleans intervals and timeouts\n   * - removes elements from DOM\n   */\n\n\n  destroy() {\n    var _this$element;\n\n    if (!this.isDestroying) {\n      this.options.showHideAnimationType = 'none';\n      this.close();\n      return;\n    }\n\n    this.dispatch('destroy');\n    this._listeners = {};\n\n    if (this.scrollWrap) {\n      this.scrollWrap.ontouchmove = null;\n      this.scrollWrap.ontouchend = null;\n    }\n\n    (_this$element = this.element) === null || _this$element === void 0 || _this$element.remove();\n    this.mainScroll.itemHolders.forEach(itemHolder => {\n      var _itemHolder$slide2;\n\n      (_itemHolder$slide2 = itemHolder.slide) === null || _itemHolder$slide2 === void 0 || _itemHolder$slide2.destroy();\n    });\n    this.contentLoader.destroy();\n    this.events.removeAll();\n  }\n  /**\n   * Refresh/reload content of a slide by its index\n   *\n   * @param {number} slideIndex\n   */\n\n\n  refreshSlideContent(slideIndex) {\n    this.contentLoader.removeByIndex(slideIndex);\n    this.mainScroll.itemHolders.forEach((itemHolder, i) => {\n      var _this$currSlide$index, _this$currSlide3;\n\n      let potentialHolderIndex = ((_this$currSlide$index = (_this$currSlide3 = this.currSlide) === null || _this$currSlide3 === void 0 ? void 0 : _this$currSlide3.index) !== null && _this$currSlide$index !== void 0 ? _this$currSlide$index : 0) - 1 + i;\n\n      if (this.canLoop()) {\n        potentialHolderIndex = this.getLoopedIndex(potentialHolderIndex);\n      }\n\n      if (potentialHolderIndex === slideIndex) {\n        // set the new slide content\n        this.setContent(itemHolder, slideIndex, true); // activate the new slide if it's current\n\n        if (i === 1) {\n          var _itemHolder$slide3;\n\n          this.currSlide = itemHolder.slide;\n          (_itemHolder$slide3 = itemHolder.slide) === null || _itemHolder$slide3 === void 0 || _itemHolder$slide3.setIsActive(true);\n        }\n      }\n    });\n    this.dispatch('change');\n  }\n  /**\n   * Set slide content\n   *\n   * @param {ItemHolder} holder mainScroll.itemHolders array item\n   * @param {number} index Slide index\n   * @param {boolean} [force] If content should be set even if index wasn't changed\n   */\n\n\n  setContent(holder, index, force) {\n    if (this.canLoop()) {\n      index = this.getLoopedIndex(index);\n    }\n\n    if (holder.slide) {\n      if (holder.slide.index === index && !force) {\n        // exit if holder already contains this slide\n        // this could be common when just three slides are used\n        return;\n      } // destroy previous slide\n\n\n      holder.slide.destroy();\n      holder.slide = undefined;\n    } // exit if no loop and index is out of bounds\n\n\n    if (!this.canLoop() && (index < 0 || index >= this.getNumItems())) {\n      return;\n    }\n\n    const itemData = this.getItemData(index);\n    holder.slide = new Slide(itemData, index, this); // set current slide\n\n    if (index === this.currIndex) {\n      this.currSlide = holder.slide;\n    }\n\n    holder.slide.append(holder.el);\n  }\n  /** @returns {Point} */\n\n\n  getViewportCenterPoint() {\n    return {\n      x: this.viewportSize.x / 2,\n      y: this.viewportSize.y / 2\n    };\n  }\n  /**\n   * Update size of all elements.\n   * Executed on init and on page resize.\n   *\n   * @param {boolean} [force] Update size even if size of viewport was not changed.\n   */\n\n\n  updateSize(force) {\n    // let item;\n    // let itemIndex;\n    if (this.isDestroying) {\n      // exit if PhotoSwipe is closed or closing\n      // (to avoid errors, as resize event might be delayed)\n      return;\n    } //const newWidth = this.scrollWrap.clientWidth;\n    //const newHeight = this.scrollWrap.clientHeight;\n\n\n    const newViewportSize = getViewportSize(this.options, this);\n\n    if (!force && pointsEqual(newViewportSize, this._prevViewportSize)) {\n      // Exit if dimensions were not changed\n      return;\n    } //this._prevViewportSize.x = newWidth;\n    //this._prevViewportSize.y = newHeight;\n\n\n    equalizePoints(this._prevViewportSize, newViewportSize);\n    this.dispatch('beforeResize');\n    equalizePoints(this.viewportSize, this._prevViewportSize);\n\n    this._updatePageScrollOffset();\n\n    this.dispatch('viewportSize'); // Resize slides only after opener animation is finished\n    // and don't re-calculate size on inital size update\n\n    this.mainScroll.resize(this.opener.isOpen);\n\n    if (!this.hasMouse && window.matchMedia('(any-hover: hover)').matches) {\n      this.mouseDetected();\n    }\n\n    this.dispatch('resize');\n  }\n  /**\n   * @param {number} opacity\n   */\n\n\n  applyBgOpacity(opacity) {\n    this.bgOpacity = Math.max(opacity, 0);\n\n    if (this.bg) {\n      this.bg.style.opacity = String(this.bgOpacity * this.options.bgOpacity);\n    }\n  }\n  /**\n   * Whether mouse is detected\n   */\n\n\n  mouseDetected() {\n    if (!this.hasMouse) {\n      var _this$element2;\n\n      this.hasMouse = true;\n      (_this$element2 = this.element) === null || _this$element2 === void 0 || _this$element2.classList.add('pswp--has_mouse');\n    }\n  }\n  /**\n   * Page resize event handler\n   *\n   * @private\n   */\n\n\n  _handlePageResize() {\n    this.updateSize(); // In iOS webview, if element size depends on document size,\n    // it'll be measured incorrectly in resize event\n    //\n    // https://bugs.webkit.org/show_bug.cgi?id=170595\n    // https://hackernoon.com/onresize-event-broken-in-mobile-safari-d8469027bf4d\n\n    if (/iPhone|iPad|iPod/i.test(window.navigator.userAgent)) {\n      setTimeout(() => {\n        this.updateSize();\n      }, 500);\n    }\n  }\n  /**\n   * Page scroll offset is used\n   * to get correct coordinates\n   * relative to PhotoSwipe viewport.\n   *\n   * @private\n   */\n\n\n  _updatePageScrollOffset() {\n    this.setScrollOffset(0, window.pageYOffset);\n  }\n  /**\n   * @param {number} x\n   * @param {number} y\n   */\n\n\n  setScrollOffset(x, y) {\n    this.offset.x = x;\n    this.offset.y = y;\n    this.dispatch('updateScrollOffset');\n  }\n  /**\n   * Create main HTML structure of PhotoSwipe,\n   * and add it to DOM\n   *\n   * @private\n   */\n\n\n  _createMainStructure() {\n    // root DOM element of PhotoSwipe (.pswp)\n    this.element = createElement('pswp', 'div');\n    this.element.setAttribute('tabindex', '-1');\n    this.element.setAttribute('role', 'dialog'); // template is legacy prop\n\n    this.template = this.element; // Background is added as a separate element,\n    // as animating opacity is faster than animating rgba()\n\n    this.bg = createElement('pswp__bg', 'div', this.element);\n    this.scrollWrap = createElement('pswp__scroll-wrap', 'section', this.element);\n    this.container = createElement('pswp__container', 'div', this.scrollWrap); // aria pattern: carousel\n\n    this.scrollWrap.setAttribute('aria-roledescription', 'carousel');\n    this.container.setAttribute('aria-live', 'off');\n    this.container.setAttribute('id', 'pswp__items');\n    this.mainScroll.appendHolders();\n    this.ui = new UI(this);\n    this.ui.init(); // append to DOM\n\n    (this.options.appendToEl || document.body).appendChild(this.element);\n  }\n  /**\n   * Get position and dimensions of small thumbnail\n   *   {x:,y:,w:}\n   *\n   * Height is optional (calculated based on the large image)\n   *\n   * @returns {Bounds | undefined}\n   */\n\n\n  getThumbBounds() {\n    return getThumbBounds(this.currIndex, this.currSlide ? this.currSlide.data : this._initialItemData, this);\n  }\n  /**\n   * If the PhotoSwipe can have continuous loop\n   * @returns Boolean\n   */\n\n\n  canLoop() {\n    return this.options.loop && this.getNumItems() > 2;\n  }\n  /**\n   * @private\n   * @param {PhotoSwipeOptions} options\n   * @returns {PreparedPhotoSwipeOptions}\n   */\n\n\n  _prepareOptions(options) {\n    if (window.matchMedia('(prefers-reduced-motion), (update: slow)').matches) {\n      options.showHideAnimationType = 'none';\n      options.zoomAnimationDuration = 0;\n    }\n    /** @type {PreparedPhotoSwipeOptions} */\n\n\n    return { ...defaultOptions,\n      ...options\n    };\n  }\n\n}\n\nexport { PhotoSwipe as default };\n//# sourceMappingURL=photoswipe.esm.js.map\n"
  },
  {
    "path": "dist/types/core/base.d.ts",
    "content": "export default PhotoSwipeBase;\nexport type PhotoSwipe = import(\"../photoswipe.js\").default;\nexport type SlideData = import(\"../slide/slide.js\").SlideData;\n/** @typedef {import(\"../photoswipe.js\").default} PhotoSwipe */\n/** @typedef {import(\"../slide/slide.js\").SlideData} SlideData */\n/**\n * PhotoSwipe base class that can retrieve data about every slide.\n * Shared by PhotoSwipe Core and PhotoSwipe Lightbox\n */\ndeclare class PhotoSwipeBase extends Eventable {\n    /**\n     * Get total number of slides\n     *\n     * @returns {number}\n     */\n    getNumItems(): number;\n    /**\n     * @param {SlideData} slideData\n     * @param {number} index\n     * @returns {Content}\n     */\n    createContentFromData(slideData: SlideData, index: number): Content;\n    /**\n     * Get item data by index.\n     *\n     * \"item data\" should contain normalized information that PhotoSwipe needs to generate a slide.\n     * For example, it may contain properties like\n     * `src`, `srcset`, `w`, `h`, which will be used to generate a slide with image.\n     *\n     * @param {number} index\n     * @returns {SlideData}\n     */\n    getItemData(index: number): SlideData;\n    /**\n     * Get array of gallery DOM elements,\n     * based on childSelector and gallery element.\n     *\n     * @param {HTMLElement} galleryElement\n     * @returns {HTMLElement[]}\n     */\n    _getGalleryDOMElements(galleryElement: HTMLElement): HTMLElement[];\n    /**\n     * Converts DOM element to item data object.\n     *\n     * @param {HTMLElement} element DOM element\n     * @returns {SlideData}\n     */\n    _domElementToItemData(element: HTMLElement): SlideData;\n    /**\n     * Lazy-load by slide data\n     *\n     * @param {SlideData} itemData Data about the slide\n     * @param {number} index\n     * @returns {Content} Image that is being decoded or false.\n     */\n    lazyLoadData(itemData: SlideData, index: number): Content;\n}\nimport Eventable from \"./eventable.js\";\nimport Content from \"../slide/content.js\";\n"
  },
  {
    "path": "dist/types/core/eventable.d.ts",
    "content": "export default Eventable;\nexport type PhotoSwipeLightbox = import('../lightbox/lightbox.js').default;\nexport type PhotoSwipe = import('../photoswipe.js').default;\nexport type PhotoSwipeOptions = import('../photoswipe.js').PhotoSwipeOptions;\nexport type DataSource = import('../photoswipe.js').DataSource;\nexport type UIElementData = import('../ui/ui-element.js').UIElementData;\nexport type ContentDefault = import('../slide/content.js').default;\nexport type Slide = import('../slide/slide.js').default;\nexport type SlideData = import('../slide/slide.js').SlideData;\nexport type ZoomLevel = import('../slide/zoom-level.js').default;\nexport type Bounds = import('../slide/get-thumb-bounds.js').Bounds;\n/**\n * Allow adding an arbitrary props to the Content\n * https://photoswipe.com/custom-content/#using-webp-image-format\n */\nexport type Content = ContentDefault & Record<string, any>;\nexport type Point = {\n    x?: number;\n    y?: number;\n};\n/**\n * https://photoswipe.com/events/\n *\n *\n * https://photoswipe.com/adding-ui-elements/\n */\nexport type PhotoSwipeEventsMap = {\n    uiRegister: undefined;\n    /**\n     * https://photoswipe.com/events/#initialization-events\n     */\n    uiElementCreate: {\n        data: UIElementData;\n    };\n    beforeOpen: undefined;\n    firstUpdate: undefined;\n    initialLayout: undefined;\n    change: undefined;\n    afterInit: undefined;\n    /**\n     * https://photoswipe.com/events/#opening-or-closing-transition-events\n     */\n    bindEvents: undefined;\n    openingAnimationStart: undefined;\n    openingAnimationEnd: undefined;\n    closingAnimationStart: undefined;\n    /**\n     * https://photoswipe.com/events/#closing-events\n     */\n    closingAnimationEnd: undefined;\n    close: undefined;\n    /**\n     * https://photoswipe.com/events/#pointer-and-gesture-events\n     */\n    destroy: undefined;\n    pointerDown: {\n        originalEvent: PointerEvent;\n    };\n    pointerMove: {\n        originalEvent: PointerEvent;\n    };\n    pointerUp: {\n        originalEvent: PointerEvent;\n    };\n    /**\n     * can be default prevented\n     */\n    pinchClose: {\n        bgOpacity: number;\n    };\n    /**\n     * can be default prevented\n     *\n     *\n     * https://photoswipe.com/events/#slide-content-events\n     */\n    verticalDrag: {\n        panY: number;\n    };\n    contentInit: {\n        content: Content;\n    };\n    /**\n     * can be default prevented\n     */\n    contentLoad: {\n        content: Content;\n        isLazy: boolean;\n    };\n    /**\n     * can be default prevented\n     */\n    contentLoadImage: {\n        content: Content;\n        isLazy: boolean;\n    };\n    loadComplete: {\n        content: Content;\n        slide: Slide;\n        isError?: boolean;\n    };\n    loadError: {\n        content: Content;\n        slide: Slide;\n    };\n    /**\n     * can be default prevented\n     */\n    contentResize: {\n        content: Content;\n        width: number;\n        height: number;\n    };\n    imageSizeChange: {\n        content: Content;\n        width: number;\n        height: number;\n        slide: Slide;\n    };\n    /**\n     * can be default prevented\n     */\n    contentLazyLoad: {\n        content: Content;\n    };\n    /**\n     * can be default prevented\n     */\n    contentAppend: {\n        content: Content;\n    };\n    /**\n     * can be default prevented\n     */\n    contentActivate: {\n        content: Content;\n    };\n    /**\n     * can be default prevented\n     */\n    contentDeactivate: {\n        content: Content;\n    };\n    /**\n     * can be default prevented\n     */\n    contentRemove: {\n        content: Content;\n    };\n    /**\n     * can be default prevented\n     *\n     *\n     * undocumented\n     */\n    contentDestroy: {\n        content: Content;\n    };\n    /**\n     * can be default prevented\n     */\n    imageClickAction: {\n        point: Point;\n        originalEvent: PointerEvent;\n    };\n    /**\n     * can be default prevented\n     */\n    bgClickAction: {\n        point: Point;\n        originalEvent: PointerEvent;\n    };\n    /**\n     * can be default prevented\n     */\n    tapAction: {\n        point: Point;\n        originalEvent: PointerEvent;\n    };\n    /**\n     * can be default prevented\n     */\n    doubleTapAction: {\n        point: Point;\n        originalEvent: PointerEvent;\n    };\n    /**\n     * can be default prevented\n     */\n    keydown: {\n        originalEvent: KeyboardEvent;\n    };\n    moveMainScroll: {\n        x: number;\n        dragging: boolean;\n    };\n    firstZoomPan: {\n        slide: Slide;\n    };\n    gettingData: {\n        slide: Slide | undefined;\n        data: SlideData;\n        index: number;\n    };\n    beforeResize: undefined;\n    resize: undefined;\n    viewportSize: undefined;\n    updateScrollOffset: undefined;\n    slideInit: {\n        slide: Slide;\n    };\n    afterSetContent: {\n        slide: Slide;\n    };\n    slideLoad: {\n        slide: Slide;\n    };\n    /**\n     * can be default prevented\n     */\n    appendHeavy: {\n        slide: Slide;\n    };\n    appendHeavyContent: {\n        slide: Slide;\n    };\n    slideActivate: {\n        slide: Slide;\n    };\n    slideDeactivate: {\n        slide: Slide;\n    };\n    slideDestroy: {\n        slide: Slide;\n    };\n    beforeZoomTo: {\n        destZoomLevel: number;\n        centerPoint: Point | undefined;\n        transitionDuration: number | false | undefined;\n    };\n    zoomPanUpdate: {\n        slide: Slide;\n    };\n    initialZoomPan: {\n        slide: Slide;\n    };\n    calcSlideSize: {\n        slide: Slide;\n    };\n    resolutionChanged: undefined;\n    /**\n     * can be default prevented\n     */\n    wheel: {\n        originalEvent: WheelEvent;\n    };\n    /**\n     * can be default prevented\n     */\n    contentAppendImage: {\n        content: Content;\n    };\n    /**\n     * can be default prevented\n     */\n    lazyLoadSlide: {\n        index: number;\n        itemData: SlideData;\n    };\n    lazyLoad: undefined;\n    calcBounds: {\n        slide: Slide;\n    };\n    /**\n     * legacy\n     */\n    zoomLevelsUpdate: {\n        zoomLevels: ZoomLevel;\n        slideData: SlideData;\n    };\n    init: undefined;\n    initialZoomIn: undefined;\n    initialZoomOut: undefined;\n    initialZoomInEnd: undefined;\n    initialZoomOutEnd: undefined;\n    numItems: {\n        dataSource: DataSource | undefined;\n        numItems: number;\n    };\n    itemData: {\n        itemData: SlideData;\n        index: number;\n    };\n    thumbBounds: {\n        index: number;\n        itemData: SlideData;\n        instance: PhotoSwipe;\n    };\n};\n/**\n * https://photoswipe.com/filters/\n */\nexport type PhotoSwipeFiltersMap = {\n    /**\n     * Modify the total amount of slides. Example on Data sources page.\n     * https://photoswipe.com/filters/#numitems\n     */\n    numItems: (numItems: number, dataSource: DataSource | undefined) => number;\n    /**\n     * Modify slide item data. Example on Data sources page.\n     * https://photoswipe.com/filters/#itemdata\n     */\n    itemData: (itemData: SlideData, index: number) => SlideData;\n    /**\n     * Modify item data when it's parsed from DOM element. Example on Data sources page.\n     * https://photoswipe.com/filters/#domitemdata\n     */\n    domItemData: (itemData: SlideData, element: HTMLElement, linkEl: HTMLAnchorElement) => SlideData;\n    /**\n     * Modify clicked gallery item index.\n     * https://photoswipe.com/filters/#clickedindex\n     */\n    clickedIndex: (clickedIndex: number, e: MouseEvent, instance: PhotoSwipeLightbox) => number;\n    /**\n     * Modify placeholder image source.\n     * https://photoswipe.com/filters/#placeholdersrc\n     */\n    placeholderSrc: (placeholderSrc: string | false, content: Content) => string | false;\n    /**\n     * Modify if the content is currently loading.\n     * https://photoswipe.com/filters/#iscontentloading\n     */\n    isContentLoading: (isContentLoading: boolean, content: Content) => boolean;\n    /**\n     * Modify if the content can be zoomed.\n     * https://photoswipe.com/filters/#iscontentzoomable\n     */\n    isContentZoomable: (isContentZoomable: boolean, content: Content) => boolean;\n    /**\n     * Modify if the placeholder should be used for the content.\n     * https://photoswipe.com/filters/#usecontentplaceholder\n     */\n    useContentPlaceholder: (useContentPlaceholder: boolean, content: Content) => boolean;\n    /**\n     * Modify if the placeholder should be kept after the content is loaded.\n     * https://photoswipe.com/filters/#iskeepingplaceholder\n     */\n    isKeepingPlaceholder: (isKeepingPlaceholder: boolean, content: Content) => boolean;\n    /**\n     * Modify an element when the content has error state (for example, if image cannot be loaded).\n     * https://photoswipe.com/filters/#contenterrorelement\n     */\n    contentErrorElement: (contentErrorElement: HTMLElement, content: Content) => HTMLElement;\n    /**\n     * Modify a UI element that's being created.\n     * https://photoswipe.com/filters/#uielement\n     */\n    uiElement: (element: HTMLElement, data: UIElementData) => HTMLElement;\n    /**\n     * Modify the thumbnail element from which opening zoom animation starts or ends.\n     * https://photoswipe.com/filters/#thumbel\n     */\n    thumbEl: (thumbnail: HTMLElement | null | undefined, itemData: SlideData, index: number) => HTMLElement;\n    /**\n     * Modify the thumbnail bounds from which opening zoom animation starts or ends.\n     * https://photoswipe.com/filters/#thumbbounds\n     */\n    thumbBounds: (thumbBounds: Bounds | undefined, itemData: SlideData, index: number) => Bounds;\n    srcsetSizesWidth: (srcsetSizesWidth: number, content: Content) => number;\n    preventPointerEvent: (preventPointerEvent: boolean, event: PointerEvent, pointerType: string) => boolean;\n};\nexport type Filter<T extends keyof PhotoSwipeFiltersMap> = {\n    fn: PhotoSwipeFiltersMap[T];\n    priority: number;\n};\nexport type AugmentedEvent<T extends keyof PhotoSwipeEventsMap> = PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent<T> : PhotoSwipeEvent<T> & PhotoSwipeEventsMap[T];\nexport type EventCallback<T extends keyof PhotoSwipeEventsMap> = (event: AugmentedEvent<T>) => void;\n/**\n * PhotoSwipe base class that can listen and dispatch for events.\n * Shared by PhotoSwipe Core and PhotoSwipe Lightbox, extended by base.js\n */\ndeclare class Eventable {\n    /**\n     * @type {{ [T in keyof PhotoSwipeEventsMap]?: ((event: AugmentedEvent<T>) => void)[] }}\n     */\n    _listeners: {\n        uiRegister?: ((event: PhotoSwipeEvent<\"uiRegister\">) => void)[] | undefined;\n        /**\n         * https://photoswipe.com/events/#initialization-events\n         */\n        uiElementCreate?: ((event: PhotoSwipeEvent<\"uiElementCreate\"> & {\n            data: import(\"../ui/ui-element.js\").UIElementData;\n        }) => void)[] | undefined;\n        beforeOpen?: ((event: PhotoSwipeEvent<\"beforeOpen\">) => void)[] | undefined;\n        firstUpdate?: ((event: PhotoSwipeEvent<\"firstUpdate\">) => void)[] | undefined;\n        initialLayout?: ((event: PhotoSwipeEvent<\"initialLayout\">) => void)[] | undefined;\n        change?: ((event: PhotoSwipeEvent<\"change\">) => void)[] | undefined;\n        afterInit?: ((event: PhotoSwipeEvent<\"afterInit\">) => void)[] | undefined;\n        /**\n         * https://photoswipe.com/events/#opening-or-closing-transition-events\n         */\n        bindEvents?: ((event: PhotoSwipeEvent<\"bindEvents\">) => void)[] | undefined;\n        openingAnimationStart?: ((event: PhotoSwipeEvent<\"openingAnimationStart\">) => void)[] | undefined;\n        openingAnimationEnd?: ((event: PhotoSwipeEvent<\"openingAnimationEnd\">) => void)[] | undefined;\n        closingAnimationStart?: ((event: PhotoSwipeEvent<\"closingAnimationStart\">) => void)[] | undefined;\n        /**\n         * https://photoswipe.com/events/#closing-events\n         */\n        closingAnimationEnd?: ((event: PhotoSwipeEvent<\"closingAnimationEnd\">) => void)[] | undefined;\n        close?: ((event: PhotoSwipeEvent<\"close\">) => void)[] | undefined;\n        /**\n         * https://photoswipe.com/events/#pointer-and-gesture-events\n         */\n        destroy?: ((event: PhotoSwipeEvent<\"destroy\">) => void)[] | undefined;\n        pointerDown?: ((event: PhotoSwipeEvent<\"pointerDown\"> & {\n            originalEvent: PointerEvent;\n        }) => void)[] | undefined;\n        pointerMove?: ((event: PhotoSwipeEvent<\"pointerMove\"> & {\n            originalEvent: PointerEvent;\n        }) => void)[] | undefined;\n        pointerUp?: ((event: PhotoSwipeEvent<\"pointerUp\"> & {\n            originalEvent: PointerEvent;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        pinchClose?: ((event: PhotoSwipeEvent<\"pinchClose\"> & {\n            bgOpacity: number;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         *\n         *\n         * https://photoswipe.com/events/#slide-content-events\n         */\n        verticalDrag?: ((event: PhotoSwipeEvent<\"verticalDrag\"> & {\n            panY: number;\n        }) => void)[] | undefined;\n        contentInit?: ((event: PhotoSwipeEvent<\"contentInit\"> & {\n            content: Content;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        contentLoad?: ((event: PhotoSwipeEvent<\"contentLoad\"> & {\n            content: Content;\n            isLazy: boolean;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        contentLoadImage?: ((event: PhotoSwipeEvent<\"contentLoadImage\"> & {\n            content: Content;\n            isLazy: boolean;\n        }) => void)[] | undefined;\n        loadComplete?: ((event: PhotoSwipeEvent<\"loadComplete\"> & {\n            content: Content;\n            slide: import(\"../slide/slide.js\").default;\n            isError?: boolean | undefined;\n        }) => void)[] | undefined;\n        loadError?: ((event: PhotoSwipeEvent<\"loadError\"> & {\n            content: Content;\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        contentResize?: ((event: PhotoSwipeEvent<\"contentResize\"> & {\n            content: Content;\n            width: number;\n            height: number;\n        }) => void)[] | undefined;\n        imageSizeChange?: ((event: PhotoSwipeEvent<\"imageSizeChange\"> & {\n            content: Content;\n            width: number;\n            height: number;\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        contentLazyLoad?: ((event: PhotoSwipeEvent<\"contentLazyLoad\"> & {\n            content: Content;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        contentAppend?: ((event: PhotoSwipeEvent<\"contentAppend\"> & {\n            content: Content;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        contentActivate?: ((event: PhotoSwipeEvent<\"contentActivate\"> & {\n            content: Content;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        contentDeactivate?: ((event: PhotoSwipeEvent<\"contentDeactivate\"> & {\n            content: Content;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        contentRemove?: ((event: PhotoSwipeEvent<\"contentRemove\"> & {\n            content: Content;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         *\n         *\n         * undocumented\n         */\n        contentDestroy?: ((event: PhotoSwipeEvent<\"contentDestroy\"> & {\n            content: Content;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        imageClickAction?: ((event: PhotoSwipeEvent<\"imageClickAction\"> & {\n            point: Point;\n            originalEvent: PointerEvent;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        bgClickAction?: ((event: PhotoSwipeEvent<\"bgClickAction\"> & {\n            point: Point;\n            originalEvent: PointerEvent;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        tapAction?: ((event: PhotoSwipeEvent<\"tapAction\"> & {\n            point: Point;\n            originalEvent: PointerEvent;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        doubleTapAction?: ((event: PhotoSwipeEvent<\"doubleTapAction\"> & {\n            point: Point;\n            originalEvent: PointerEvent;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        keydown?: ((event: PhotoSwipeEvent<\"keydown\"> & {\n            originalEvent: KeyboardEvent;\n        }) => void)[] | undefined;\n        moveMainScroll?: ((event: PhotoSwipeEvent<\"moveMainScroll\"> & {\n            x: number;\n            dragging: boolean;\n        }) => void)[] | undefined;\n        firstZoomPan?: ((event: PhotoSwipeEvent<\"firstZoomPan\"> & {\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        gettingData?: ((event: PhotoSwipeEvent<\"gettingData\"> & {\n            slide: import(\"../slide/slide.js\").default | undefined;\n            data: import(\"../slide/slide.js\").SlideData;\n            index: number;\n        }) => void)[] | undefined;\n        beforeResize?: ((event: PhotoSwipeEvent<\"beforeResize\">) => void)[] | undefined;\n        resize?: ((event: PhotoSwipeEvent<\"resize\">) => void)[] | undefined;\n        viewportSize?: ((event: PhotoSwipeEvent<\"viewportSize\">) => void)[] | undefined;\n        updateScrollOffset?: ((event: PhotoSwipeEvent<\"updateScrollOffset\">) => void)[] | undefined;\n        slideInit?: ((event: PhotoSwipeEvent<\"slideInit\"> & {\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        afterSetContent?: ((event: PhotoSwipeEvent<\"afterSetContent\"> & {\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        slideLoad?: ((event: PhotoSwipeEvent<\"slideLoad\"> & {\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        appendHeavy?: ((event: PhotoSwipeEvent<\"appendHeavy\"> & {\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        appendHeavyContent?: ((event: PhotoSwipeEvent<\"appendHeavyContent\"> & {\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        slideActivate?: ((event: PhotoSwipeEvent<\"slideActivate\"> & {\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        slideDeactivate?: ((event: PhotoSwipeEvent<\"slideDeactivate\"> & {\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        slideDestroy?: ((event: PhotoSwipeEvent<\"slideDestroy\"> & {\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        beforeZoomTo?: ((event: PhotoSwipeEvent<\"beforeZoomTo\"> & {\n            destZoomLevel: number;\n            centerPoint: Point | undefined;\n            transitionDuration: number | false | undefined;\n        }) => void)[] | undefined;\n        zoomPanUpdate?: ((event: PhotoSwipeEvent<\"zoomPanUpdate\"> & {\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        initialZoomPan?: ((event: PhotoSwipeEvent<\"initialZoomPan\"> & {\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        calcSlideSize?: ((event: PhotoSwipeEvent<\"calcSlideSize\"> & {\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        resolutionChanged?: ((event: PhotoSwipeEvent<\"resolutionChanged\">) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        wheel?: ((event: PhotoSwipeEvent<\"wheel\"> & {\n            originalEvent: WheelEvent;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        contentAppendImage?: ((event: PhotoSwipeEvent<\"contentAppendImage\"> & {\n            content: Content;\n        }) => void)[] | undefined;\n        /**\n         * can be default prevented\n         */\n        lazyLoadSlide?: ((event: PhotoSwipeEvent<\"lazyLoadSlide\"> & {\n            index: number;\n            itemData: import(\"../slide/slide.js\").SlideData;\n        }) => void)[] | undefined;\n        lazyLoad?: ((event: PhotoSwipeEvent<\"lazyLoad\">) => void)[] | undefined;\n        calcBounds?: ((event: PhotoSwipeEvent<\"calcBounds\"> & {\n            slide: import(\"../slide/slide.js\").default;\n        }) => void)[] | undefined;\n        /**\n         * legacy\n         */\n        zoomLevelsUpdate?: ((event: PhotoSwipeEvent<\"zoomLevelsUpdate\"> & {\n            zoomLevels: import(\"../slide/zoom-level.js\").default;\n            slideData: import(\"../slide/slide.js\").SlideData;\n        }) => void)[] | undefined;\n        init?: ((event: PhotoSwipeEvent<\"init\">) => void)[] | undefined;\n        initialZoomIn?: ((event: PhotoSwipeEvent<\"initialZoomIn\">) => void)[] | undefined;\n        initialZoomOut?: ((event: PhotoSwipeEvent<\"initialZoomOut\">) => void)[] | undefined;\n        initialZoomInEnd?: ((event: PhotoSwipeEvent<\"initialZoomInEnd\">) => void)[] | undefined;\n        initialZoomOutEnd?: ((event: PhotoSwipeEvent<\"initialZoomOutEnd\">) => void)[] | undefined;\n        numItems?: ((event: PhotoSwipeEvent<\"numItems\"> & {\n            dataSource: import(\"../photoswipe.js\").DataSource | undefined;\n            numItems: number;\n        }) => void)[] | undefined;\n        itemData?: ((event: PhotoSwipeEvent<\"itemData\"> & {\n            itemData: import(\"../slide/slide.js\").SlideData;\n            index: number;\n        }) => void)[] | undefined;\n        thumbBounds?: ((event: PhotoSwipeEvent<\"thumbBounds\"> & {\n            index: number;\n            itemData: import(\"../slide/slide.js\").SlideData;\n            instance: import(\"../photoswipe.js\").default;\n        }) => void)[] | undefined;\n    };\n    /**\n     * @type {{ [T in keyof PhotoSwipeFiltersMap]?: Filter<T>[] }}\n     */\n    _filters: {\n        /**\n         * Modify the total amount of slides. Example on Data sources page.\n         * https://photoswipe.com/filters/#numitems\n         */\n        numItems?: Filter<\"numItems\">[] | undefined;\n        /**\n         * Modify slide item data. Example on Data sources page.\n         * https://photoswipe.com/filters/#itemdata\n         */\n        itemData?: Filter<\"itemData\">[] | undefined;\n        /**\n         * Modify item data when it's parsed from DOM element. Example on Data sources page.\n         * https://photoswipe.com/filters/#domitemdata\n         */\n        domItemData?: Filter<\"domItemData\">[] | undefined;\n        /**\n         * Modify clicked gallery item index.\n         * https://photoswipe.com/filters/#clickedindex\n         */\n        clickedIndex?: Filter<\"clickedIndex\">[] | undefined;\n        /**\n         * Modify placeholder image source.\n         * https://photoswipe.com/filters/#placeholdersrc\n         */\n        placeholderSrc?: Filter<\"placeholderSrc\">[] | undefined;\n        /**\n         * Modify if the content is currently loading.\n         * https://photoswipe.com/filters/#iscontentloading\n         */\n        isContentLoading?: Filter<\"isContentLoading\">[] | undefined;\n        /**\n         * Modify if the content can be zoomed.\n         * https://photoswipe.com/filters/#iscontentzoomable\n         */\n        isContentZoomable?: Filter<\"isContentZoomable\">[] | undefined;\n        /**\n         * Modify if the placeholder should be used for the content.\n         * https://photoswipe.com/filters/#usecontentplaceholder\n         */\n        useContentPlaceholder?: Filter<\"useContentPlaceholder\">[] | undefined;\n        /**\n         * Modify if the placeholder should be kept after the content is loaded.\n         * https://photoswipe.com/filters/#iskeepingplaceholder\n         */\n        isKeepingPlaceholder?: Filter<\"isKeepingPlaceholder\">[] | undefined;\n        /**\n         * Modify an element when the content has error state (for example, if image cannot be loaded).\n         * https://photoswipe.com/filters/#contenterrorelement\n         */\n        contentErrorElement?: Filter<\"contentErrorElement\">[] | undefined;\n        /**\n         * Modify a UI element that's being created.\n         * https://photoswipe.com/filters/#uielement\n         */\n        uiElement?: Filter<\"uiElement\">[] | undefined;\n        /**\n         * Modify the thumbnail element from which opening zoom animation starts or ends.\n         * https://photoswipe.com/filters/#thumbel\n         */\n        thumbEl?: Filter<\"thumbEl\">[] | undefined;\n        /**\n         * Modify the thumbnail bounds from which opening zoom animation starts or ends.\n         * https://photoswipe.com/filters/#thumbbounds\n         */\n        thumbBounds?: Filter<\"thumbBounds\">[] | undefined;\n        srcsetSizesWidth?: Filter<\"srcsetSizesWidth\">[] | undefined;\n        preventPointerEvent?: Filter<\"preventPointerEvent\">[] | undefined;\n    };\n    /** @type {PhotoSwipe | undefined} */\n    pswp: PhotoSwipe | undefined;\n    /** @type {PhotoSwipeOptions | undefined} */\n    options: Partial<import(\"../photoswipe.js\").PreparedPhotoSwipeOptions> | undefined;\n    /**\n     * @template {keyof PhotoSwipeFiltersMap} T\n     * @param {T} name\n     * @param {PhotoSwipeFiltersMap[T]} fn\n     * @param {number} priority\n     */\n    addFilter<T extends keyof PhotoSwipeFiltersMap>(name: T, fn: PhotoSwipeFiltersMap[T], priority?: number): void;\n    /**\n     * @template {keyof PhotoSwipeFiltersMap} T\n     * @param {T} name\n     * @param {PhotoSwipeFiltersMap[T]} fn\n     */\n    removeFilter<T_1 extends keyof PhotoSwipeFiltersMap>(name: T_1, fn: PhotoSwipeFiltersMap[T_1]): void;\n    /**\n     * @template {keyof PhotoSwipeFiltersMap} T\n     * @param {T} name\n     * @param {Parameters<PhotoSwipeFiltersMap[T]>} args\n     * @returns {Parameters<PhotoSwipeFiltersMap[T]>[0]}\n     */\n    applyFilters<T_2 extends keyof PhotoSwipeFiltersMap>(name: T_2, ...args: Parameters<PhotoSwipeFiltersMap[T_2]>): Parameters<PhotoSwipeFiltersMap[T_2]>[0];\n    /**\n     * @template {keyof PhotoSwipeEventsMap} T\n     * @param {T} name\n     * @param {EventCallback<T>} fn\n     */\n    on<T_3 extends keyof PhotoSwipeEventsMap>(name: T_3, fn: EventCallback<T_3>): void;\n    /**\n     * @template {keyof PhotoSwipeEventsMap} T\n     * @param {T} name\n     * @param {EventCallback<T>} fn\n     */\n    off<T_4 extends keyof PhotoSwipeEventsMap>(name: T_4, fn: EventCallback<T_4>): void;\n    /**\n     * @template {keyof PhotoSwipeEventsMap} T\n     * @param {T} name\n     * @param {PhotoSwipeEventsMap[T]} [details]\n     * @returns {AugmentedEvent<T>}\n     */\n    dispatch<T_5 extends keyof PhotoSwipeEventsMap>(name: T_5, details?: PhotoSwipeEventsMap[T_5] | undefined): AugmentedEvent<T_5>;\n}\n/** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n/** @typedef {import('../photoswipe.js').DataSource} DataSource */\n/** @typedef {import('../ui/ui-element.js').UIElementData} UIElementData */\n/** @typedef {import('../slide/content.js').default} ContentDefault */\n/** @typedef {import('../slide/slide.js').default} Slide */\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n/** @typedef {import('../slide/zoom-level.js').default} ZoomLevel */\n/** @typedef {import('../slide/get-thumb-bounds.js').Bounds} Bounds */\n/**\n * Allow adding an arbitrary props to the Content\n * https://photoswipe.com/custom-content/#using-webp-image-format\n * @typedef {ContentDefault & Record<string, any>} Content\n */\n/** @typedef {{ x?: number; y?: number }} Point */\n/**\n * @typedef {Object} PhotoSwipeEventsMap https://photoswipe.com/events/\n *\n *\n * https://photoswipe.com/adding-ui-elements/\n *\n * @prop {undefined} uiRegister\n * @prop {{ data: UIElementData }} uiElementCreate\n *\n *\n * https://photoswipe.com/events/#initialization-events\n *\n * @prop {undefined} beforeOpen\n * @prop {undefined} firstUpdate\n * @prop {undefined} initialLayout\n * @prop {undefined} change\n * @prop {undefined} afterInit\n * @prop {undefined} bindEvents\n *\n *\n * https://photoswipe.com/events/#opening-or-closing-transition-events\n *\n * @prop {undefined} openingAnimationStart\n * @prop {undefined} openingAnimationEnd\n * @prop {undefined} closingAnimationStart\n * @prop {undefined} closingAnimationEnd\n *\n *\n * https://photoswipe.com/events/#closing-events\n *\n * @prop {undefined} close\n * @prop {undefined} destroy\n *\n *\n * https://photoswipe.com/events/#pointer-and-gesture-events\n *\n * @prop {{ originalEvent: PointerEvent }} pointerDown\n * @prop {{ originalEvent: PointerEvent }} pointerMove\n * @prop {{ originalEvent: PointerEvent }} pointerUp\n * @prop {{ bgOpacity: number }} pinchClose can be default prevented\n * @prop {{ panY: number }} verticalDrag can be default prevented\n *\n *\n * https://photoswipe.com/events/#slide-content-events\n *\n * @prop {{ content: Content }} contentInit\n * @prop {{ content: Content; isLazy: boolean }} contentLoad can be default prevented\n * @prop {{ content: Content; isLazy: boolean }} contentLoadImage can be default prevented\n * @prop {{ content: Content; slide: Slide; isError?: boolean }} loadComplete\n * @prop {{ content: Content; slide: Slide }} loadError\n * @prop {{ content: Content; width: number; height: number }} contentResize can be default prevented\n * @prop {{ content: Content; width: number; height: number; slide: Slide }} imageSizeChange\n * @prop {{ content: Content }} contentLazyLoad can be default prevented\n * @prop {{ content: Content }} contentAppend can be default prevented\n * @prop {{ content: Content }} contentActivate can be default prevented\n * @prop {{ content: Content }} contentDeactivate can be default prevented\n * @prop {{ content: Content }} contentRemove can be default prevented\n * @prop {{ content: Content }} contentDestroy can be default prevented\n *\n *\n * undocumented\n *\n * @prop {{ point: Point; originalEvent: PointerEvent }} imageClickAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} bgClickAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} tapAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} doubleTapAction can be default prevented\n *\n * @prop {{ originalEvent: KeyboardEvent }} keydown can be default prevented\n * @prop {{ x: number; dragging: boolean }} moveMainScroll\n * @prop {{ slide: Slide }} firstZoomPan\n * @prop {{ slide: Slide | undefined, data: SlideData, index: number }} gettingData\n * @prop {undefined} beforeResize\n * @prop {undefined} resize\n * @prop {undefined} viewportSize\n * @prop {undefined} updateScrollOffset\n * @prop {{ slide: Slide }} slideInit\n * @prop {{ slide: Slide }} afterSetContent\n * @prop {{ slide: Slide }} slideLoad\n * @prop {{ slide: Slide }} appendHeavy can be default prevented\n * @prop {{ slide: Slide }} appendHeavyContent\n * @prop {{ slide: Slide }} slideActivate\n * @prop {{ slide: Slide }} slideDeactivate\n * @prop {{ slide: Slide }} slideDestroy\n * @prop {{ destZoomLevel: number, centerPoint: Point | undefined, transitionDuration: number | false | undefined }} beforeZoomTo\n * @prop {{ slide: Slide }} zoomPanUpdate\n * @prop {{ slide: Slide }} initialZoomPan\n * @prop {{ slide: Slide }} calcSlideSize\n * @prop {undefined} resolutionChanged\n * @prop {{ originalEvent: WheelEvent }} wheel can be default prevented\n * @prop {{ content: Content }} contentAppendImage can be default prevented\n * @prop {{ index: number; itemData: SlideData }} lazyLoadSlide can be default prevented\n * @prop {undefined} lazyLoad\n * @prop {{ slide: Slide }} calcBounds\n * @prop {{ zoomLevels: ZoomLevel, slideData: SlideData }} zoomLevelsUpdate\n *\n *\n * legacy\n *\n * @prop {undefined} init\n * @prop {undefined} initialZoomIn\n * @prop {undefined} initialZoomOut\n * @prop {undefined} initialZoomInEnd\n * @prop {undefined} initialZoomOutEnd\n * @prop {{ dataSource: DataSource | undefined, numItems: number }} numItems\n * @prop {{ itemData: SlideData; index: number }} itemData\n * @prop {{ index: number, itemData: SlideData, instance: PhotoSwipe }} thumbBounds\n */\n/**\n * @typedef {Object} PhotoSwipeFiltersMap https://photoswipe.com/filters/\n *\n * @prop {(numItems: number, dataSource: DataSource | undefined) => number} numItems\n * Modify the total amount of slides. Example on Data sources page.\n * https://photoswipe.com/filters/#numitems\n *\n * @prop {(itemData: SlideData, index: number) => SlideData} itemData\n * Modify slide item data. Example on Data sources page.\n * https://photoswipe.com/filters/#itemdata\n *\n * @prop {(itemData: SlideData, element: HTMLElement, linkEl: HTMLAnchorElement) => SlideData} domItemData\n * Modify item data when it's parsed from DOM element. Example on Data sources page.\n * https://photoswipe.com/filters/#domitemdata\n *\n * @prop {(clickedIndex: number, e: MouseEvent, instance: PhotoSwipeLightbox) => number} clickedIndex\n * Modify clicked gallery item index.\n * https://photoswipe.com/filters/#clickedindex\n *\n * @prop {(placeholderSrc: string | false, content: Content) => string | false} placeholderSrc\n * Modify placeholder image source.\n * https://photoswipe.com/filters/#placeholdersrc\n *\n * @prop {(isContentLoading: boolean, content: Content) => boolean} isContentLoading\n * Modify if the content is currently loading.\n * https://photoswipe.com/filters/#iscontentloading\n *\n * @prop {(isContentZoomable: boolean, content: Content) => boolean} isContentZoomable\n * Modify if the content can be zoomed.\n * https://photoswipe.com/filters/#iscontentzoomable\n *\n * @prop {(useContentPlaceholder: boolean, content: Content) => boolean} useContentPlaceholder\n * Modify if the placeholder should be used for the content.\n * https://photoswipe.com/filters/#usecontentplaceholder\n *\n * @prop {(isKeepingPlaceholder: boolean, content: Content) => boolean} isKeepingPlaceholder\n * Modify if the placeholder should be kept after the content is loaded.\n * https://photoswipe.com/filters/#iskeepingplaceholder\n *\n *\n * @prop {(contentErrorElement: HTMLElement, content: Content) => HTMLElement} contentErrorElement\n * Modify an element when the content has error state (for example, if image cannot be loaded).\n * https://photoswipe.com/filters/#contenterrorelement\n *\n * @prop {(element: HTMLElement, data: UIElementData) => HTMLElement} uiElement\n * Modify a UI element that's being created.\n * https://photoswipe.com/filters/#uielement\n *\n * @prop {(thumbnail: HTMLElement | null | undefined, itemData: SlideData, index: number) => HTMLElement} thumbEl\n * Modify the thumbnail element from which opening zoom animation starts or ends.\n * https://photoswipe.com/filters/#thumbel\n *\n * @prop {(thumbBounds: Bounds | undefined, itemData: SlideData, index: number) => Bounds} thumbBounds\n * Modify the thumbnail bounds from which opening zoom animation starts or ends.\n * https://photoswipe.com/filters/#thumbbounds\n *\n * @prop {(srcsetSizesWidth: number, content: Content) => number} srcsetSizesWidth\n *\n * @prop {(preventPointerEvent: boolean, event: PointerEvent, pointerType: string) => boolean} preventPointerEvent\n *\n */\n/**\n * @template {keyof PhotoSwipeFiltersMap} T\n * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter\n */\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent<T> : PhotoSwipeEvent<T> & PhotoSwipeEventsMap[T]} AugmentedEvent\n */\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {(event: AugmentedEvent<T>) => void} EventCallback\n */\n/**\n * Base PhotoSwipe event object\n *\n * @template {keyof PhotoSwipeEventsMap} T\n */\ndeclare class PhotoSwipeEvent<T extends keyof PhotoSwipeEventsMap> {\n    /**\n     * @param {T} type\n     * @param {PhotoSwipeEventsMap[T]} [details]\n     */\n    constructor(type: T, details?: PhotoSwipeEventsMap[T] | undefined);\n    type: T;\n    defaultPrevented: boolean;\n    preventDefault(): void;\n}\n"
  },
  {
    "path": "dist/types/gestures/drag-handler.d.ts",
    "content": "export default DragHandler;\nexport type Point = import('../photoswipe.js').Point;\nexport type Gestures = import('./gestures.js').default;\n/**\n * Handles single pointer dragging\n */\ndeclare class DragHandler {\n    /**\n     * @param {Gestures} gestures\n     */\n    constructor(gestures: Gestures);\n    gestures: import(\"./gestures.js\").default;\n    pswp: import(\"../photoswipe.js\").default;\n    /** @type {Point} */\n    startPan: Point;\n    start(): void;\n    change(): void;\n    end(): void;\n    /**\n     * @private\n     * @param {'x' | 'y'} axis\n     */\n    private _finishPanGestureForAxis;\n    /**\n     * Update position of the main scroll,\n     * or/and update pan position of the current slide.\n     *\n     * Should return true if it changes (or can change) main scroll.\n     *\n     * @private\n     * @param {'x' | 'y'} axis\n     * @returns {boolean}\n     */\n    private _panOrMoveMainScroll;\n    /**\n     * Relation between pan Y position and third of viewport height.\n     *\n     * When we are at initial position (center bounds) - the ratio is 0,\n     * if position is shifted upwards - the ratio is negative,\n     * if position is shifted downwards - the ratio is positive.\n     *\n     * @private\n     * @param {number} panY The current pan Y position.\n     * @returns {number}\n     */\n    private _getVerticalDragRatio;\n    /**\n     * Set pan position of the current slide.\n     * Apply friction if the position is beyond the pan bounds,\n     * or if custom friction is defined.\n     *\n     * @private\n     * @param {'x' | 'y'} axis\n     * @param {number} potentialPan\n     * @param {number} [customFriction] (0.1 - 1)\n     */\n    private _setPanWithFriction;\n}\n"
  },
  {
    "path": "dist/types/gestures/gestures.d.ts",
    "content": "export default Gestures;\nexport type PhotoSwipe = import('../photoswipe.js').default;\nexport type Point = import('../photoswipe.js').Point;\n/**\n * Gestures class bind touch, pointer or mouse events\n * and emits drag to drag-handler and zoom events zoom-handler.\n *\n * Drag and zoom events are emited in requestAnimationFrame,\n * and only when one of pointers was actually changed.\n */\ndeclare class Gestures {\n    /**\n     * @param {PhotoSwipe} pswp\n     */\n    constructor(pswp: PhotoSwipe);\n    pswp: import(\"../photoswipe.js\").default;\n    /** @type {'x' | 'y' | null} */\n    dragAxis: 'x' | 'y' | null;\n    /** @type {Point} */\n    p1: Point;\n    /** @type {Point} */\n    p2: Point;\n    /** @type {Point} */\n    prevP1: Point;\n    /** @type {Point} */\n    prevP2: Point;\n    /** @type {Point} */\n    startP1: Point;\n    /** @type {Point} */\n    startP2: Point;\n    /** @type {Point} */\n    velocity: Point;\n    /** @type {Point}\n     * @private\n     */\n    private _lastStartP1;\n    /** @type {Point}\n     * @private\n     */\n    private _intervalP1;\n    /** @private */\n    private _numActivePoints;\n    /** @type {Point[]}\n     * @private\n     */\n    private _ongoingPointers;\n    /** @private */\n    private _touchEventEnabled;\n    /** @private */\n    private _pointerEventEnabled;\n    supportsTouch: boolean;\n    /** @private */\n    private _intervalTime;\n    /** @private */\n    private _velocityCalculated;\n    isMultitouch: boolean;\n    isDragging: boolean;\n    isZooming: boolean;\n    /** @type {number | null} */\n    raf: number | null;\n    /** @type {NodeJS.Timeout | null}\n     * @private\n     */\n    private _tapTimer;\n    drag: DragHandler;\n    zoomLevels: ZoomHandler;\n    tapHandler: TapHandler;\n    /**\n     * @private\n     * @param {'mouse' | 'touch' | 'pointer'} pref\n     * @param {'down' | 'start'} down\n     * @param {'up' | 'end'} up\n     * @param {'cancel'} [cancel]\n     */\n    private _bindEvents;\n    /**\n     * @param {PointerEvent} e\n     */\n    onPointerDown(e: PointerEvent): void;\n    /**\n     * @param {PointerEvent} e\n     */\n    onPointerMove(e: PointerEvent): void;\n    /**\n     * @private\n     */\n    private _finishDrag;\n    /**\n     * @param {PointerEvent} e\n     */\n    onPointerUp(e: PointerEvent): void;\n    /**\n     * @private\n     */\n    private _rafRenderLoop;\n    /**\n     * Update velocity at 50ms interval\n     *\n     * @private\n     * @param {boolean} [force]\n     */\n    private _updateVelocity;\n    /**\n     * @private\n     * @param {PointerEvent} e\n     */\n    private _finishTap;\n    /**\n     * @private\n     */\n    private _clearTapTimer;\n    /**\n     * Get velocity for axis\n     *\n     * @private\n     * @param {'x' | 'y'} axis\n     * @param {number} duration\n     * @returns {number}\n     */\n    private _getVelocity;\n    /**\n     * @private\n     */\n    private _rafStopLoop;\n    /**\n     * @private\n     * @param {PointerEvent} e\n     * @param {'up' | 'down' | 'move'} pointerType Normalized pointer type\n     */\n    private _preventPointerEventBehaviour;\n    /**\n     * Parses and normalizes points from the touch, mouse or pointer event.\n     * Updates p1 and p2.\n     *\n     * @private\n     * @param {PointerEvent | TouchEvent} e\n     * @param {'up' | 'down' | 'move'} pointerType Normalized pointer type\n     */\n    private _updatePoints;\n    /** update points that were used during previous rAF tick\n     * @private\n     */\n    private _updatePrevPoints;\n    /** update points at the start of gesture\n     * @private\n     */\n    private _updateStartPoints;\n    /** @private */\n    private _calculateDragDirection;\n    /**\n     * Converts touch, pointer or mouse event\n     * to PhotoSwipe point.\n     *\n     * @private\n     * @param {Touch | PointerEvent} e\n     * @param {Point} p\n     * @returns {Point}\n     */\n    private _convertEventPosToPoint;\n    /**\n     * @private\n     * @param {PointerEvent} e\n     */\n    private _onClick;\n}\nimport DragHandler from \"./drag-handler.js\";\nimport ZoomHandler from \"./zoom-handler.js\";\nimport TapHandler from \"./tap-handler.js\";\n"
  },
  {
    "path": "dist/types/gestures/tap-handler.d.ts",
    "content": "export default TapHandler;\n/**\n * <T, P>\n */\nexport type AddPostfix<T extends string, P extends string> = import('../types.js').AddPostfix<T, P>;\nexport type Gestures = import('./gestures.js').default;\nexport type Point = import('../photoswipe.js').Point;\nexport type Actions = 'imageClick' | 'bgClick' | 'tap' | 'doubleTap';\n/**\n * Tap, double-tap handler.\n */\ndeclare class TapHandler {\n    /**\n     * @param {Gestures} gestures\n     */\n    constructor(gestures: Gestures);\n    gestures: import(\"./gestures.js\").default;\n    /**\n     * @param {Point} point\n     * @param {PointerEvent} originalEvent\n     */\n    click(point: Point, originalEvent: PointerEvent): void;\n    /**\n     * @param {Point} point\n     * @param {PointerEvent} originalEvent\n     */\n    tap(point: Point, originalEvent: PointerEvent): void;\n    /**\n     * @param {Point} point\n     * @param {PointerEvent} originalEvent\n     */\n    doubleTap(point: Point, originalEvent: PointerEvent): void;\n    /**\n     * @private\n     * @param {Actions} actionName\n     * @param {Point} point\n     * @param {PointerEvent} originalEvent\n     */\n    private _doClickOrTapAction;\n}\n"
  },
  {
    "path": "dist/types/gestures/zoom-handler.d.ts",
    "content": "export default ZoomHandler;\nexport type Point = import('../photoswipe.js').Point;\nexport type Gestures = import('./gestures.js').default;\ndeclare class ZoomHandler {\n    /**\n     * @param {Gestures} gestures\n     */\n    constructor(gestures: Gestures);\n    gestures: import(\"./gestures.js\").default;\n    /**\n     * @private\n     * @type {Point}\n     */\n    private _startPan;\n    /**\n     * @private\n     * @type {Point}\n     */\n    private _startZoomPoint;\n    /**\n     * @private\n     * @type {Point}\n     */\n    private _zoomPoint;\n    /** @private */\n    private _wasOverFitZoomLevel;\n    /** @private */\n    private _startZoomLevel;\n    start(): void;\n    change(): void;\n    end(): void;\n    /**\n     * @private\n     * @param {'x' | 'y'} axis\n     * @param {number} currZoomLevel\n     * @returns {number}\n     */\n    private _calculatePanForZoomLevel;\n    /**\n     * Correct currZoomLevel and pan if they are\n     * beyond minimum or maximum values.\n     * With animation.\n     *\n     * @param {boolean} [ignoreGesture]\n     * Wether gesture coordinates should be ignored when calculating destination pan position.\n     */\n    correctZoomPan(ignoreGesture?: boolean | undefined): void;\n}\n"
  },
  {
    "path": "dist/types/keyboard.d.ts",
    "content": "export default Keyboard;\nexport type PhotoSwipe = import('./photoswipe.js').default;\n/**\n * <T>\n */\nexport type Methods<T> = import('./types.js').Methods<T>;\n/**\n * - Manages keyboard shortcuts.\n * - Helps trap focus within photoswipe.\n */\ndeclare class Keyboard {\n    /**\n     * @param {PhotoSwipe} pswp\n     */\n    constructor(pswp: PhotoSwipe);\n    pswp: import(\"./photoswipe.js\").default;\n    /** @private */\n    private _wasFocused;\n    /** @private */\n    private _focusRoot;\n    /**\n     * @private\n     * @param {KeyboardEvent} e\n     */\n    private _onKeyDown;\n    /**\n     * Trap focus inside photoswipe\n     *\n     * @private\n     * @param {FocusEvent} e\n     */\n    private _onFocusIn;\n}\n"
  },
  {
    "path": "dist/types/lightbox/lightbox.d.ts",
    "content": "export default PhotoSwipeLightbox;\n/**\n * <T>\n */\nexport type Type<T> = import('../types.js').Type<T>;\nexport type PhotoSwipe = import('../photoswipe.js').default;\nexport type PhotoSwipeOptions = import('../photoswipe.js').PhotoSwipeOptions;\nexport type DataSource = import('../photoswipe.js').DataSource;\nexport type Point = import('../photoswipe.js').Point;\nexport type Content = import('../slide/content.js').default;\nexport type PhotoSwipeEventsMap = import('../core/eventable.js').PhotoSwipeEventsMap;\nexport type PhotoSwipeFiltersMap = import('../core/eventable.js').PhotoSwipeFiltersMap;\n/**\n * <T>\n */\nexport type EventCallback<T extends keyof import(\"../core/eventable.js\").PhotoSwipeEventsMap> = import('../core/eventable.js').EventCallback<T>;\n/**\n * @template T\n * @typedef {import('../types.js').Type<T>} Type<T>\n */\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n/** @typedef {import('../photoswipe.js').DataSource} DataSource */\n/** @typedef {import('../photoswipe.js').Point} Point */\n/** @typedef {import('../slide/content.js').default} Content */\n/** @typedef {import('../core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */\n/** @typedef {import('../core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {import('../core/eventable.js').EventCallback<T>} EventCallback<T>\n */\n/**\n * PhotoSwipe Lightbox\n *\n * - If user has unsupported browser it falls back to default browser action (just opens URL)\n * - Binds click event to links that should open PhotoSwipe\n * - parses DOM strcture for PhotoSwipe (retrieves large image URLs and sizes)\n * - Initializes PhotoSwipe\n *\n *\n * Loader options use the same object as PhotoSwipe, and supports such options:\n *\n * gallery - Element | Element[] | NodeList | string selector for the gallery element\n * children - Element | Element[] | NodeList | string selector for the gallery children\n *\n */\ndeclare class PhotoSwipeLightbox extends PhotoSwipeBase {\n    /**\n     * @param {PhotoSwipeOptions} [options]\n     */\n    constructor(options?: Partial<import(\"../photoswipe.js\").PreparedPhotoSwipeOptions> | undefined);\n    /** @type {PhotoSwipeOptions} */\n    options: Partial<import(\"../photoswipe.js\").PreparedPhotoSwipeOptions>;\n    _uid: number;\n    shouldOpen: boolean;\n    /**\n     * @private\n     * @type {Content | undefined}\n     */\n    private _preloadedContent;\n    /**\n     * @param {MouseEvent} e\n     */\n    onThumbnailsClick(e: MouseEvent): void;\n    /**\n     * Initialize lightbox, should be called only once.\n     * It's not included in the main constructor, so you may bind events before it.\n     */\n    init(): void;\n    /**\n     * Get index of gallery item that was clicked.\n     *\n     * @param {MouseEvent} e click event\n     * @returns {number}\n     */\n    getClickedIndex(e: MouseEvent): number;\n    /**\n     * Load and open PhotoSwipe\n     *\n     * @param {number} index\n     * @param {DataSource} [dataSource]\n     * @param {Point | null} [initialPoint]\n     * @returns {boolean}\n     */\n    loadAndOpen(index: number, dataSource?: import(\"../photoswipe.js\").DataSource | undefined, initialPoint?: import(\"../photoswipe.js\").Point | null | undefined): boolean;\n    /**\n     * Load the main module and the slide content by index\n     *\n     * @param {number} index\n     * @param {DataSource} [dataSource]\n     */\n    preload(index: number, dataSource?: import(\"../photoswipe.js\").DataSource | undefined): void;\n    /**\n     * @private\n     * @param {Type<PhotoSwipe> | { default: Type<PhotoSwipe> }} module\n     * @param {number} uid\n     */\n    private _openPhotoswipe;\n    /**\n     * Unbinds all events, closes PhotoSwipe if it's open.\n     */\n    destroy(): void;\n}\nimport PhotoSwipeBase from \"../core/base.js\";\n"
  },
  {
    "path": "dist/types/main-scroll.d.ts",
    "content": "export default MainScroll;\nexport type PhotoSwipe = import('./photoswipe.js').default;\nexport type Slide = import('./slide/slide.js').default;\nexport type ItemHolder = {\n    el: HTMLDivElement;\n    slide?: Slide;\n};\n/**\n * Handles movement of the main scrolling container\n * (for example, it repositions when user swipes left or right).\n *\n * Also stores its state.\n */\ndeclare class MainScroll {\n    /**\n     * @param {PhotoSwipe} pswp\n     */\n    constructor(pswp: PhotoSwipe);\n    pswp: import(\"./photoswipe.js\").default;\n    x: number;\n    slideWidth: number;\n    /** @private */\n    private _currPositionIndex;\n    /** @private */\n    private _prevPositionIndex;\n    /** @private */\n    private _containerShiftIndex;\n    /** @type {ItemHolder[]} */\n    itemHolders: ItemHolder[];\n    /**\n     * Position the scroller and slide containers\n     * according to viewport size.\n     *\n     * @param {boolean} [resizeSlides] Whether slides content should resized\n     */\n    resize(resizeSlides?: boolean | undefined): void;\n    /**\n     * Reset X position of the main scroller to zero\n     */\n    resetPosition(): void;\n    /**\n     * Create and append array of three items\n     * that hold data about slides in DOM\n     */\n    appendHolders(): void;\n    /**\n     * Whether the main scroll can be horizontally swiped to the next or previous slide.\n     * @returns {boolean}\n     */\n    canBeSwiped(): boolean;\n    /**\n     * Move main scroll by X amount of slides.\n     * For example:\n     *   `-1` will move to the previous slide,\n     *    `0` will reset the scroll position of the current slide,\n     *    `3` will move three slides forward\n     *\n     * If loop option is enabled - index will be automatically looped too,\n     * (for example `-1` will move to the last slide of the gallery).\n     *\n     * @param {number} diff\n     * @param {boolean} [animate]\n     * @param {number} [velocityX]\n     * @returns {boolean} whether index was changed or not\n     */\n    moveIndexBy(diff: number, animate?: boolean | undefined, velocityX?: number | undefined): boolean;\n    /**\n     * X position of the main scroll for the current slide\n     * (ignores position during dragging)\n     * @returns {number}\n     */\n    getCurrSlideX(): number;\n    /**\n     * Whether scroll position is shifted.\n     * For example, it will return true if the scroll is being dragged or animated.\n     * @returns {boolean}\n     */\n    isShifted(): boolean;\n    /**\n     * Update slides X positions and set their content\n     */\n    updateCurrItem(): void;\n    /**\n     * Move the X position of the main scroll container\n     *\n     * @param {number} x\n     * @param {boolean} [dragging]\n     */\n    moveTo(x: number, dragging?: boolean | undefined): void;\n}\n"
  },
  {
    "path": "dist/types/opener.d.ts",
    "content": "export default Opener;\nexport type PhotoSwipe = import('./photoswipe.js').default;\nexport type Bounds = import('./slide/get-thumb-bounds.js').Bounds;\nexport type AnimationProps = import('./util/animations.js').AnimationProps;\n/**\n * Manages opening and closing transitions of the PhotoSwipe.\n *\n * It can perform zoom, fade or no transition.\n */\ndeclare class Opener {\n    /**\n     * @param {PhotoSwipe} pswp\n     */\n    constructor(pswp: PhotoSwipe);\n    pswp: import(\"./photoswipe.js\").default;\n    isClosed: boolean;\n    isOpen: boolean;\n    isClosing: boolean;\n    isOpening: boolean;\n    /**\n     * @private\n     * @type {number | false | undefined}\n     */\n    private _duration;\n    /** @private */\n    private _useAnimation;\n    /** @private */\n    private _croppedZoom;\n    /** @private */\n    private _animateRootOpacity;\n    /** @private */\n    private _animateBgOpacity;\n    /**\n     * @private\n     * @type { HTMLDivElement | HTMLImageElement | null | undefined }\n     */\n    private _placeholder;\n    /**\n     * @private\n     * @type { HTMLDivElement | undefined }\n     */\n    private _opacityElement;\n    /**\n     * @private\n     * @type { HTMLDivElement | undefined }\n     */\n    private _cropContainer1;\n    /**\n     * @private\n     * @type { HTMLElement | null | undefined }\n     */\n    private _cropContainer2;\n    /**\n     * @private\n     * @type {Bounds | undefined}\n     */\n    private _thumbBounds;\n    /** @private */\n    private _prepareOpen;\n    open(): void;\n    close(): void;\n    /** @private */\n    private _applyStartProps;\n    _animateZoom: boolean | undefined;\n    /** @private */\n    private _start;\n    /** @private */\n    private _initiate;\n    /** @private */\n    private _onAnimationComplete;\n    /** @private */\n    private _animateToOpenState;\n    /** @private */\n    private _animateToClosedState;\n    /**\n     * @private\n     * @param {boolean} [animate]\n     */\n    private _setClosedStateZoomPan;\n    /**\n     * @private\n     * @param {HTMLElement} target\n     * @param {'transform' | 'opacity'} prop\n     * @param {string} propValue\n     */\n    private _animateTo;\n}\n"
  },
  {
    "path": "dist/types/photoswipe.d.ts",
    "content": "export default PhotoSwipe;\n/**\n * <T>\n */\nexport type Type<T> = import('./types.js').Type<T>;\nexport type SlideData = import('./slide/slide.js').SlideData;\nexport type ZoomLevelOption = import('./slide/zoom-level.js').ZoomLevelOption;\nexport type UIElementData = import('./ui/ui-element.js').UIElementData;\nexport type ItemHolder = import('./main-scroll.js').ItemHolder;\nexport type PhotoSwipeEventsMap = import('./core/eventable.js').PhotoSwipeEventsMap;\nexport type PhotoSwipeFiltersMap = import('./core/eventable.js').PhotoSwipeFiltersMap;\nexport type Bounds = import('./slide/get-thumb-bounds').Bounds;\n/**\n * <T>\n */\nexport type EventCallback<T extends keyof import(\"./core/eventable.js\").PhotoSwipeEventsMap> = import('./core/eventable.js').EventCallback<T>;\n/**\n * <T>\n */\nexport type AugmentedEvent<T extends keyof import(\"./core/eventable.js\").PhotoSwipeEventsMap> = import('./core/eventable.js').AugmentedEvent<T>;\nexport type Point = {\n    x: number;\n    y: number;\n    id?: string | number;\n};\nexport type Padding = {\n    top: number;\n    bottom: number;\n    left: number;\n    right: number;\n};\nexport type DataSourceArray = SlideData[];\nexport type DataSourceObject = {\n    gallery: HTMLElement;\n    items?: HTMLElement[];\n};\nexport type DataSource = import(\"./slide/slide.js\").SlideData[] | DataSourceObject;\nexport type ActionFn = (point: Point, originalEvent: PointerEvent) => void;\nexport type ActionType = 'close' | 'next' | 'zoom' | 'zoom-or-close' | 'toggle-controls';\nexport type PhotoSwipeModule = Type<PhotoSwipe> | {\n    default: Type<PhotoSwipe>;\n};\nexport type PhotoSwipeModuleOption = PhotoSwipeModule | Promise<PhotoSwipeModule> | (() => Promise<PhotoSwipeModule>);\nexport type ElementProvider = string | NodeListOf<HTMLElement> | HTMLElement[] | HTMLElement;\n/**\n * https://photoswipe.com/options/\n */\nexport type PhotoSwipeOptions = Partial<PreparedPhotoSwipeOptions>;\nexport type PreparedPhotoSwipeOptions = {\n    /**\n     * Pass an array of any items via dataSource option. Its length will determine amount of slides\n     * (which may be modified further from numItems event).\n     *\n     * Each item should contain data that you need to generate slide\n     * (for image slide it would be src (image URL), width (image width), height, srcset, alt).\n     *\n     * If these properties are not present in your initial array, you may \"pre-parse\" each item from itemData filter.\n     */\n    dataSource?: DataSource | undefined;\n    /**\n     * Background backdrop opacity, always define it via this option and not via CSS rgba color.\n     */\n    bgOpacity: number;\n    /**\n     * Spacing between slides. Defined as ratio relative to the viewport width (0.1 = 10% of viewport).\n     */\n    spacing: number;\n    /**\n     * Allow swipe navigation to the next slide when the current slide is zoomed. Does not apply to mouse events.\n     */\n    allowPanToNext: boolean;\n    /**\n     * If set to true you'll be able to swipe from the last to the first image.\n     * Option is always false when there are less than 3 slides.\n     */\n    loop: boolean;\n    /**\n     * By default PhotoSwipe zooms image with ctrl-wheel, if you enable this option - image will zoom just via wheel.\n     */\n    wheelToZoom?: boolean | undefined;\n    /**\n     * Pinch touch gesture to close the gallery.\n     */\n    pinchToClose: boolean;\n    /**\n     * Vertical drag gesture to close the PhotoSwipe.\n     */\n    closeOnVerticalDrag: boolean;\n    /**\n     * Slide area padding (in pixels).\n     */\n    padding?: Padding | undefined;\n    /**\n     * The option is checked frequently, so make sure it's performant. Overrides padding option if defined. For example:\n     */\n    paddingFn?: ((viewportSize: Point, itemData: SlideData, index: number) => Padding) | undefined;\n    /**\n     * Transition duration in milliseconds, can be 0.\n     */\n    hideAnimationDuration: number | false;\n    /**\n     * Transition duration in milliseconds, can be 0.\n     */\n    showAnimationDuration: number | false;\n    /**\n     * Transition duration in milliseconds, can be 0.\n     */\n    zoomAnimationDuration: number | false;\n    /**\n     * String, 'cubic-bezier(.4,0,.22,1)'. CSS easing function for open/close/zoom transitions.\n     */\n    easing: string;\n    /**\n     * Esc key to close.\n     */\n    escKey: boolean;\n    /**\n     * Left/right arrow keys for navigation.\n     */\n    arrowKeys: boolean;\n    /**\n     * Trap focus within PhotoSwipe element while it's open.\n     */\n    trapFocus: boolean;\n    /**\n     * Restore focus the last active element after PhotoSwipe is closed.\n     */\n    returnFocus: boolean;\n    /**\n     * If image is not zoomable (for example, smaller than viewport) it can be closed by clicking on it.\n     */\n    clickToCloseNonZoomable: boolean;\n    /**\n     * Refer to click and tap actions page.\n     */\n    imageClickAction: ActionType | ActionFn | false;\n    /**\n     * Refer to click and tap actions page.\n     */\n    bgClickAction: ActionType | ActionFn | false;\n    /**\n     * Refer to click and tap actions page.\n     */\n    tapAction: ActionType | ActionFn | false;\n    /**\n     * Refer to click and tap actions page.\n     */\n    doubleTapAction: ActionType | ActionFn | false;\n    /**\n     * Delay before the loading indicator will be displayed,\n     * if image is loaded during it - the indicator will not be displayed at all. Can be zero.\n     */\n    preloaderDelay: number;\n    /**\n     * Used for slide count indicator (\"1 of 10 \").\n     */\n    indexIndicatorSep: string;\n    /**\n     * A function that should return slide viewport width and height, in format {x: 100, y: 100}.\n     */\n    getViewportSizeFn?: ((options: PhotoSwipeOptions, pswp: PhotoSwipeBase) => Point) | undefined;\n    /**\n     * Message to display when the image wasn't able to load. If you need to display HTML - use contentErrorElement filter.\n     */\n    errorMsg: string;\n    /**\n     * Lazy loading of nearby slides based on direction of movement. Should be an array with two integers,\n     * first one - number of items to preload before the current image, second one - after the current image.\n     * Two nearby images are always loaded.\n     */\n    preload: [number, number];\n    /**\n     * Class that will be added to the root element of PhotoSwipe, may contain multiple separated by space.\n     * Example on Styling page.\n     */\n    mainClass?: string | undefined;\n    /**\n     * Element to which PhotoSwipe dialog will be appended when it opens.\n     */\n    appendToEl?: HTMLElement | undefined;\n    /**\n     * Maximum width of image to animate, if initial rendered image width\n     * is larger than this value - the opening/closing transition will be automatically disabled.\n     */\n    maxWidthToAnimate: number;\n    /**\n     * Translating\n     */\n    closeTitle?: string | undefined;\n    /**\n     * Translating\n     */\n    zoomTitle?: string | undefined;\n    /**\n     * Translating\n     */\n    arrowPrevTitle?: string | undefined;\n    /**\n     * Translating\n     */\n    arrowNextTitle?: string | undefined;\n    /**\n     * To adjust opening or closing transition type use lightbox option `showHideAnimationType` (`String`).\n     * It supports three values - `zoom` (default), `fade` (default if there is no thumbnail) and `none`.\n     *\n     * Animations are automatically disabled if user `(prefers-reduced-motion: reduce)`.\n     */\n    showHideAnimationType?: \"none\" | \"zoom\" | \"fade\" | undefined;\n    /**\n     * Defines start slide index.\n     */\n    index: number;\n    getClickedIndexFn?: ((e: MouseEvent) => number) | undefined;\n    arrowPrev?: boolean | undefined;\n    arrowNext?: boolean | undefined;\n    zoom?: boolean | undefined;\n    close?: boolean | undefined;\n    counter?: boolean | undefined;\n    arrowPrevSVG?: string | undefined;\n    arrowNextSVG?: string | undefined;\n    zoomSVG?: string | undefined;\n    closeSVG?: string | undefined;\n    counterSVG?: string | undefined;\n    counterTitle?: string | undefined;\n    initialZoomLevel?: import(\"./slide/zoom-level.js\").ZoomLevelOption | undefined;\n    secondaryZoomLevel?: import(\"./slide/zoom-level.js\").ZoomLevelOption | undefined;\n    maxZoomLevel?: import(\"./slide/zoom-level.js\").ZoomLevelOption | undefined;\n    mouseMovePan?: boolean | undefined;\n    initialPointerPos?: Point | null | undefined;\n    showHideOpacity?: boolean | undefined;\n    pswpModule?: PhotoSwipeModuleOption | undefined;\n    openPromise?: (() => Promise<any>) | undefined;\n    preloadFirstSlide?: boolean | undefined;\n    gallery?: ElementProvider | undefined;\n    gallerySelector?: string | undefined;\n    children?: ElementProvider | undefined;\n    childSelector?: string | undefined;\n    thumbSelector?: string | false | undefined;\n};\n/**\n * PhotoSwipe Core\n */\ndeclare class PhotoSwipe extends PhotoSwipeBase {\n    /**\n     * @param {PhotoSwipeOptions} [options]\n     */\n    constructor(options?: Partial<PreparedPhotoSwipeOptions> | undefined);\n    options: PreparedPhotoSwipeOptions;\n    /**\n     * offset of viewport relative to document\n     *\n     * @type {Point}\n     */\n    offset: Point;\n    /**\n     * @type {Point}\n     * @private\n     */\n    private _prevViewportSize;\n    /**\n     * Size of scrollable PhotoSwipe viewport\n     *\n     * @type {Point}\n     */\n    viewportSize: Point;\n    /**\n     * background (backdrop) opacity\n     */\n    bgOpacity: number;\n    currIndex: number;\n    potentialIndex: number;\n    isOpen: boolean;\n    isDestroying: boolean;\n    hasMouse: boolean;\n    /**\n     * @private\n     * @type {SlideData}\n     */\n    private _initialItemData;\n    /** @type {Bounds | undefined} */\n    _initialThumbBounds: Bounds | undefined;\n    /** @type {HTMLDivElement | undefined} */\n    topBar: HTMLDivElement | undefined;\n    /** @type {HTMLDivElement | undefined} */\n    element: HTMLDivElement | undefined;\n    /** @type {HTMLDivElement | undefined} */\n    template: HTMLDivElement | undefined;\n    /** @type {HTMLDivElement | undefined} */\n    container: HTMLDivElement | undefined;\n    /** @type {HTMLElement | undefined} */\n    scrollWrap: HTMLElement | undefined;\n    /** @type {Slide | undefined} */\n    currSlide: Slide | undefined;\n    events: DOMEvents;\n    animations: Animations;\n    mainScroll: MainScroll;\n    gestures: Gestures;\n    opener: Opener;\n    keyboard: Keyboard;\n    contentLoader: ContentLoader;\n    /** @returns {boolean} */\n    init(): boolean;\n    scrollWheel: ScrollWheel | undefined;\n    /**\n     * Get looped slide index\n     * (for example, -1 will return the last slide)\n     *\n     * @param {number} index\n     * @returns {number}\n     */\n    getLoopedIndex(index: number): number;\n    appendHeavy(): void;\n    /**\n     * Change the slide\n     * @param {number} index New index\n     */\n    goTo(index: number): void;\n    /**\n     * Go to the next slide.\n     */\n    next(): void;\n    /**\n     * Go to the previous slide.\n     */\n    prev(): void;\n    /**\n     * @see slide/slide.js zoomTo\n     *\n     * @param {Parameters<Slide['zoomTo']>} args\n     */\n    zoomTo(destZoomLevel: number, centerPoint?: Point | undefined, transitionDuration?: number | false | undefined, ignoreBounds?: boolean | undefined): void;\n    /**\n     * @see slide/slide.js toggleZoom\n     */\n    toggleZoom(): void;\n    /**\n     * Close the gallery.\n     * After closing transition ends - destroy it\n     */\n    close(): void;\n    /**\n     * Destroys the gallery:\n     * - instantly closes the gallery\n     * - unbinds events,\n     * - cleans intervals and timeouts\n     * - removes elements from DOM\n     */\n    destroy(): void;\n    /**\n     * Refresh/reload content of a slide by its index\n     *\n     * @param {number} slideIndex\n     */\n    refreshSlideContent(slideIndex: number): void;\n    /**\n     * Set slide content\n     *\n     * @param {ItemHolder} holder mainScroll.itemHolders array item\n     * @param {number} index Slide index\n     * @param {boolean} [force] If content should be set even if index wasn't changed\n     */\n    setContent(holder: ItemHolder, index: number, force?: boolean | undefined): void;\n    /** @returns {Point} */\n    getViewportCenterPoint(): Point;\n    /**\n     * Update size of all elements.\n     * Executed on init and on page resize.\n     *\n     * @param {boolean} [force] Update size even if size of viewport was not changed.\n     */\n    updateSize(force?: boolean | undefined): void;\n    /**\n     * @param {number} opacity\n     */\n    applyBgOpacity(opacity: number): void;\n    /**\n     * Whether mouse is detected\n     */\n    mouseDetected(): void;\n    /**\n     * Page resize event handler\n     *\n     * @private\n     */\n    private _handlePageResize;\n    /**\n     * Page scroll offset is used\n     * to get correct coordinates\n     * relative to PhotoSwipe viewport.\n     *\n     * @private\n     */\n    private _updatePageScrollOffset;\n    /**\n     * @param {number} x\n     * @param {number} y\n     */\n    setScrollOffset(x: number, y: number): void;\n    /**\n     * Create main HTML structure of PhotoSwipe,\n     * and add it to DOM\n     *\n     * @private\n     */\n    private _createMainStructure;\n    bg: HTMLDivElement | undefined;\n    ui: UI | undefined;\n    /**\n     * Get position and dimensions of small thumbnail\n     *   {x:,y:,w:}\n     *\n     * Height is optional (calculated based on the large image)\n     *\n     * @returns {Bounds | undefined}\n     */\n    getThumbBounds(): Bounds | undefined;\n    /**\n     * If the PhotoSwipe can have continuous loop\n     * @returns Boolean\n     */\n    canLoop(): boolean;\n    /**\n     * @private\n     * @param {PhotoSwipeOptions} options\n     * @returns {PreparedPhotoSwipeOptions}\n     */\n    private _prepareOptions;\n}\nimport PhotoSwipeBase from \"./core/base.js\";\nimport Slide from \"./slide/slide.js\";\nimport DOMEvents from \"./util/dom-events.js\";\nimport Animations from \"./util/animations.js\";\nimport MainScroll from \"./main-scroll.js\";\nimport Gestures from \"./gestures/gestures.js\";\nimport Opener from \"./opener.js\";\nimport Keyboard from \"./keyboard.js\";\nimport ContentLoader from \"./slide/loader.js\";\nimport ScrollWheel from \"./scroll-wheel.js\";\nimport UI from \"./ui/ui.js\";\n"
  },
  {
    "path": "dist/types/scroll-wheel.d.ts",
    "content": "export default ScrollWheel;\nexport type PhotoSwipe = import('./photoswipe.js').default;\n/** @typedef {import('./photoswipe.js').default} PhotoSwipe */\n/**\n * Handles scroll wheel.\n * Can pan and zoom current slide image.\n */\ndeclare class ScrollWheel {\n    /**\n     * @param {PhotoSwipe} pswp\n     */\n    constructor(pswp: PhotoSwipe);\n    pswp: import(\"./photoswipe.js\").default;\n    /**\n     * @private\n     * @param {WheelEvent} e\n     */\n    private _onWheel;\n}\n"
  },
  {
    "path": "dist/types/slide/content.d.ts",
    "content": "export default Content;\nexport type Slide = import('./slide.js').default;\nexport type SlideData = import('./slide.js').SlideData;\nexport type PhotoSwipeBase = import('../core/base.js').default;\nexport type LoadState = import('../util/util.js').LoadState;\n/** @typedef {import('./slide.js').default} Slide */\n/** @typedef {import('./slide.js').SlideData} SlideData */\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n/** @typedef {import('../util/util.js').LoadState} LoadState */\ndeclare class Content {\n    /**\n     * @param {SlideData} itemData Slide data\n     * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance\n     * @param {number} index\n     */\n    constructor(itemData: SlideData, instance: PhotoSwipeBase, index: number);\n    instance: import(\"../core/base.js\").default;\n    data: import(\"./slide.js\").SlideData;\n    index: number;\n    /** @type {HTMLImageElement | HTMLDivElement | undefined} */\n    element: HTMLImageElement | HTMLDivElement | undefined;\n    /** @type {Placeholder | undefined} */\n    placeholder: Placeholder | undefined;\n    /** @type {Slide | undefined} */\n    slide: Slide | undefined;\n    displayedImageWidth: number;\n    displayedImageHeight: number;\n    width: number;\n    height: number;\n    isAttached: boolean;\n    hasSlide: boolean;\n    isDecoding: boolean;\n    /** @type {LoadState} */\n    state: LoadState;\n    type: string;\n    removePlaceholder(): void;\n    /**\n     * Preload content\n     *\n     * @param {boolean} isLazy\n     * @param {boolean} [reload]\n     */\n    load(isLazy: boolean, reload?: boolean | undefined): void;\n    /**\n     * Preload image\n     *\n     * @param {boolean} isLazy\n     */\n    loadImage(isLazy: boolean): void;\n    /**\n     * Assign slide to content\n     *\n     * @param {Slide} slide\n     */\n    setSlide(slide: Slide): void;\n    /**\n     * Content load success handler\n     */\n    onLoaded(): void;\n    /**\n     * Content load error handler\n     */\n    onError(): void;\n    /**\n     * @returns {Boolean} If the content is currently loading\n     */\n    isLoading(): boolean;\n    /**\n     * @returns {Boolean} If the content is in error state\n     */\n    isError(): boolean;\n    /**\n     * @returns {boolean} If the content is image\n     */\n    isImageContent(): boolean;\n    /**\n     * Update content size\n     *\n     * @param {Number} width\n     * @param {Number} height\n     */\n    setDisplayedSize(width: number, height: number): void;\n    /**\n     * @returns {boolean} If the content can be zoomed\n     */\n    isZoomable(): boolean;\n    /**\n     * Update image srcset sizes attribute based on width and height\n     */\n    updateSrcsetSizes(): void;\n    /**\n     * @returns {boolean} If content should use a placeholder (from msrc by default)\n     */\n    usePlaceholder(): boolean;\n    /**\n     * Preload content with lazy-loading param\n     */\n    lazyLoad(): void;\n    /**\n     * @returns {boolean} If placeholder should be kept after content is loaded\n     */\n    keepPlaceholder(): boolean;\n    /**\n     * Destroy the content\n     */\n    destroy(): void;\n    /**\n     * Display error message\n     */\n    displayError(): void;\n    /**\n     * Append the content\n     */\n    append(): void;\n    /**\n     * Activate the slide,\n     * active slide is generally the current one,\n     * meaning the user can see it.\n     */\n    activate(): void;\n    /**\n     * Deactivate the content\n     */\n    deactivate(): void;\n    /**\n     * Remove the content from DOM\n     */\n    remove(): void;\n    /**\n     * Append the image content to slide container\n     */\n    appendImage(): void;\n}\nimport Placeholder from \"./placeholder.js\";\n"
  },
  {
    "path": "dist/types/slide/get-thumb-bounds.d.ts",
    "content": "/**\n * Get dimensions of thumbnail image\n * (click on which opens photoswipe or closes photoswipe to)\n *\n * @param {number} index\n * @param {SlideData} itemData\n * @param {PhotoSwipe} instance PhotoSwipe instance\n * @returns {Bounds | undefined}\n */\nexport function getThumbBounds(index: number, itemData: SlideData, instance: PhotoSwipe): Bounds | undefined;\nexport type SlideData = import('./slide.js').SlideData;\nexport type PhotoSwipe = import('../photoswipe.js').default;\nexport type Bounds = {\n    x: number;\n    y: number;\n    w: number;\n    innerRect?: {\n        w: number;\n        h: number;\n        x: number;\n        y: number;\n    };\n};\n"
  },
  {
    "path": "dist/types/slide/loader.d.ts",
    "content": "/**\n * Lazy-load an image\n * This function is used both by Lightbox and PhotoSwipe core,\n * thus it can be called before dialog is opened.\n *\n * @param {SlideData} itemData Data about the slide\n * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance\n * @param {number} index\n * @returns {Content} Image that is being decoded or false.\n */\nexport function lazyLoadData(itemData: SlideData, instance: PhotoSwipeBase, index: number): Content;\n/**\n * Lazy-loads specific slide.\n * This function is used both by Lightbox and PhotoSwipe core,\n * thus it can be called before dialog is opened.\n *\n * By default, it loads image based on viewport size and initial zoom level.\n *\n * @param {number} index Slide index\n * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox eventable instance\n * @returns {Content | undefined}\n */\nexport function lazyLoadSlide(index: number, instance: PhotoSwipeBase): Content | undefined;\nexport default ContentLoader;\nexport type Content = import('./content.js').default;\nexport type Slide = import('./slide.js').default;\nexport type SlideData = import('./slide.js').SlideData;\nexport type PhotoSwipeBase = import('../core/base.js').default;\nexport type PhotoSwipe = import('../photoswipe.js').default;\ndeclare class ContentLoader {\n    /**\n     * @param {PhotoSwipe} pswp\n     */\n    constructor(pswp: PhotoSwipe);\n    pswp: import(\"../photoswipe.js\").default;\n    limit: number;\n    /** @type {Content[]} */\n    _cachedItems: Content[];\n    /**\n     * Lazy load nearby slides based on `preload` option.\n     *\n     * @param {number} [diff] Difference between slide indexes that was changed recently, or 0.\n     */\n    updateLazy(diff?: number | undefined): void;\n    /**\n     * @param {number} initialIndex\n     */\n    loadSlideByIndex(initialIndex: number): void;\n    /**\n     * @param {Slide} slide\n     * @returns {Content}\n     */\n    getContentBySlide(slide: Slide): Content;\n    /**\n     * @param {Content} content\n     */\n    addToCache(content: Content): void;\n    /**\n     * Removes an image from cache, does not destroy() it, just removes.\n     *\n     * @param {number} index\n     */\n    removeByIndex(index: number): void;\n    /**\n     * @param {number} index\n     * @returns {Content | undefined}\n     */\n    getContentByIndex(index: number): Content | undefined;\n    destroy(): void;\n}\n"
  },
  {
    "path": "dist/types/slide/pan-bounds.d.ts",
    "content": "export default PanBounds;\nexport type Slide = import('./slide.js').default;\nexport type Point = Record<Axis, number>;\nexport type Axis = 'x' | 'y';\n/** @typedef {import('./slide.js').default} Slide */\n/** @typedef {Record<Axis, number>} Point */\n/** @typedef {'x' | 'y'} Axis */\n/**\n * Calculates minimum, maximum and initial (center) bounds of a slide\n */\ndeclare class PanBounds {\n    /**\n     * @param {Slide} slide\n     */\n    constructor(slide: Slide);\n    slide: import(\"./slide.js\").default;\n    currZoomLevel: number;\n    center: {\n        x: number;\n        y: number;\n    };\n    max: {\n        x: number;\n        y: number;\n    };\n    min: {\n        x: number;\n        y: number;\n    };\n    /**\n     * _getItemBounds\n     *\n     * @param {number} currZoomLevel\n     */\n    update(currZoomLevel: number): void;\n    /**\n     * _calculateItemBoundsForAxis\n     *\n     * @param {Axis} axis\n     */\n    _updateAxis(axis: Axis): void;\n    reset(): void;\n    /**\n     * Correct pan position if it's beyond the bounds\n     *\n     * @param {Axis} axis x or y\n     * @param {number} panOffset\n     * @returns {number}\n     */\n    correctPan(axis: Axis, panOffset: number): number;\n}\n"
  },
  {
    "path": "dist/types/slide/placeholder.d.ts",
    "content": "export default Placeholder;\ndeclare class Placeholder {\n    /**\n     * @param {string | false} imageSrc\n     * @param {HTMLElement} container\n     */\n    constructor(imageSrc: string | false, container: HTMLElement);\n    /** @type {HTMLImageElement | HTMLDivElement | null} */\n    element: HTMLImageElement | HTMLDivElement | null;\n    /**\n     * @param {number} width\n     * @param {number} height\n     */\n    setDisplayedSize(width: number, height: number): void;\n    destroy(): void;\n}\n"
  },
  {
    "path": "dist/types/slide/slide.d.ts",
    "content": "export default Slide;\nexport type PhotoSwipe = import('../photoswipe.js').default;\nexport type Point = import('../photoswipe.js').Point;\nexport type SlideData = _SlideData & Record<string, any>;\nexport type _SlideData = {\n    /**\n     * thumbnail element\n     */\n    element?: HTMLElement | undefined;\n    /**\n     * image URL\n     */\n    src?: string | undefined;\n    /**\n     * image srcset\n     */\n    srcset?: string | undefined;\n    /**\n     * image width (deprecated)\n     */\n    w?: number | undefined;\n    /**\n     * image height (deprecated)\n     */\n    h?: number | undefined;\n    /**\n     * image width\n     */\n    width?: number | undefined;\n    /**\n     * image height\n     */\n    height?: number | undefined;\n    /**\n     * placeholder image URL that's displayed before large image is loaded\n     */\n    msrc?: string | undefined;\n    /**\n     * image alt text\n     */\n    alt?: string | undefined;\n    /**\n     * whether thumbnail is cropped client-side or not\n     */\n    thumbCropped?: boolean | undefined;\n    /**\n     * html content of a slide\n     */\n    html?: string | undefined;\n    /**\n     * slide type\n     */\n    type?: string | undefined;\n};\n/**\n * Renders and allows to control a single slide\n */\ndeclare class Slide {\n    /**\n     * @param {SlideData} data\n     * @param {number} index\n     * @param {PhotoSwipe} pswp\n     */\n    constructor(data: SlideData, index: number, pswp: PhotoSwipe);\n    data: SlideData;\n    index: number;\n    pswp: import(\"../photoswipe.js\").default;\n    isActive: boolean;\n    currentResolution: number;\n    /** @type {Point} */\n    panAreaSize: Point;\n    /** @type {Point} */\n    pan: Point;\n    isFirstSlide: boolean;\n    zoomLevels: ZoomLevel;\n    content: import(\"./content.js\").default;\n    container: HTMLDivElement;\n    /** @type {HTMLElement | null} */\n    holderElement: HTMLElement | null;\n    currZoomLevel: number;\n    /** @type {number} */\n    width: number;\n    /** @type {number} */\n    height: number;\n    heavyAppended: boolean;\n    bounds: PanBounds;\n    prevDisplayedWidth: number;\n    prevDisplayedHeight: number;\n    /**\n     * If this slide is active/current/visible\n     *\n     * @param {boolean} isActive\n     */\n    setIsActive(isActive: boolean): void;\n    /**\n     * Appends slide content to DOM\n     *\n     * @param {HTMLElement} holderElement\n     */\n    append(holderElement: HTMLElement): void;\n    load(): void;\n    /**\n     * Append \"heavy\" DOM elements\n     *\n     * This may depend on a type of slide,\n     * but generally these are large images.\n     */\n    appendHeavy(): void;\n    /**\n     * Triggered when this slide is active (selected).\n     *\n     * If it's part of opening/closing transition -\n     * activate() will trigger after the transition is ended.\n     */\n    activate(): void;\n    /**\n     * Triggered when this slide becomes inactive.\n     *\n     * Slide can become inactive only after it was active.\n     */\n    deactivate(): void;\n    /**\n     * The slide should destroy itself, it will never be used again.\n     * (unbind all events and destroy internal components)\n     */\n    destroy(): void;\n    resize(): void;\n    /**\n     * Apply size to current slide content,\n     * based on the current resolution and scale.\n     *\n     * @param {boolean} [force] if size should be updated even if dimensions weren't changed\n     */\n    updateContentSize(force?: boolean | undefined): void;\n    /**\n     * @param {number} width\n     * @param {number} height\n     */\n    sizeChanged(width: number, height: number): boolean;\n    /** @returns {HTMLImageElement | HTMLDivElement | null | undefined} */\n    getPlaceholderElement(): HTMLImageElement | HTMLDivElement | null | undefined;\n    /**\n     * Zoom current slide image to...\n     *\n     * @param {number} destZoomLevel Destination zoom level.\n     * @param {Point} [centerPoint]\n     * Transform origin center point, or false if viewport center should be used.\n     * @param {number | false} [transitionDuration] Transition duration, may be set to 0.\n     * @param {boolean} [ignoreBounds] Minimum and maximum zoom levels will be ignored.\n     */\n    zoomTo(destZoomLevel: number, centerPoint?: import(\"../photoswipe.js\").Point | undefined, transitionDuration?: number | false | undefined, ignoreBounds?: boolean | undefined): void;\n    /**\n     * @param {Point} [centerPoint]\n     */\n    toggleZoom(centerPoint?: import(\"../photoswipe.js\").Point | undefined): void;\n    /**\n     * Updates zoom level property and recalculates new pan bounds,\n     * unlike zoomTo it does not apply transform (use applyCurrentZoomPan)\n     *\n     * @param {number} currZoomLevel\n     */\n    setZoomLevel(currZoomLevel: number): void;\n    /**\n     * Get pan position after zoom at a given `point`.\n     *\n     * Always call setZoomLevel(newZoomLevel) beforehand to recalculate\n     * pan bounds according to the new zoom level.\n     *\n     * @param {'x' | 'y'} axis\n     * @param {Point} [point]\n     * point based on which zoom is performed, usually refers to the current mouse position,\n     * if false - viewport center will be used.\n     * @param {number} [prevZoomLevel] Zoom level before new zoom was applied.\n     * @returns {number}\n     */\n    calculateZoomToPanOffset(axis: 'x' | 'y', point?: import(\"../photoswipe.js\").Point | undefined, prevZoomLevel?: number | undefined): number;\n    /**\n     * Apply pan and keep it within bounds.\n     *\n     * @param {number} panX\n     * @param {number} panY\n     */\n    panTo(panX: number, panY: number): void;\n    /**\n     * If the slide in the current state can be panned by the user\n     * @returns {boolean}\n     */\n    isPannable(): boolean;\n    /**\n     * If the slide can be zoomed\n     * @returns {boolean}\n     */\n    isZoomable(): boolean;\n    /**\n     * Apply transform and scale based on\n     * the current pan position (this.pan) and zoom level (this.currZoomLevel)\n     */\n    applyCurrentZoomPan(): void;\n    zoomAndPanToInitial(): void;\n    /**\n     * Set translate and scale based on current resolution\n     *\n     * @param {number} x\n     * @param {number} y\n     * @param {number} zoom\n     * @private\n     */\n    private _applyZoomTransform;\n    calculateSize(): void;\n    /** @returns {string} */\n    getCurrentTransform(): string;\n    /**\n     * Set resolution and re-render the image.\n     *\n     * For example, if the real image size is 2000x1500,\n     * and resolution is 0.5 - it will be rendered as 1000x750.\n     *\n     * Image with zoom level 2 and resolution 0.5 is\n     * the same as image with zoom level 1 and resolution 1.\n     *\n     * Used to optimize animations and make\n     * sure that browser renders image in the highest quality.\n     * Also used by responsive images to load the correct one.\n     *\n     * @param {number} newResolution\n     */\n    _setResolution(newResolution: number): void;\n}\nimport ZoomLevel from \"./zoom-level.js\";\nimport PanBounds from \"./pan-bounds.js\";\n"
  },
  {
    "path": "dist/types/slide/zoom-level.d.ts",
    "content": "export default ZoomLevel;\nexport type PhotoSwipe = import('../photoswipe.js').default;\nexport type PhotoSwipeOptions = import('../photoswipe.js').PhotoSwipeOptions;\nexport type Point = import('../photoswipe.js').Point;\nexport type SlideData = import('../slide/slide.js').SlideData;\nexport type ZoomLevelOption = number | \"fit\" | \"fill\" | ((zoomLevelObject: ZoomLevel) => number);\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n/** @typedef {import('../photoswipe.js').Point} Point */\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n/** @typedef {'fit' | 'fill' | number | ((zoomLevelObject: ZoomLevel) => number)} ZoomLevelOption */\n/**\n * Calculates zoom levels for specific slide.\n * Depends on viewport size and image size.\n */\ndeclare class ZoomLevel {\n    /**\n     * @param {PhotoSwipeOptions} options PhotoSwipe options\n     * @param {SlideData} itemData Slide data\n     * @param {number} index Slide index\n     * @param {PhotoSwipe} [pswp] PhotoSwipe instance, can be undefined if not initialized yet\n     */\n    constructor(options: PhotoSwipeOptions, itemData: SlideData, index: number, pswp?: import(\"../photoswipe.js\").default | undefined);\n    pswp: import(\"../photoswipe.js\").default | undefined;\n    options: Partial<import(\"../photoswipe.js\").PreparedPhotoSwipeOptions>;\n    itemData: import(\"../slide/slide.js\").SlideData;\n    index: number;\n    /** @type { Point | null } */\n    panAreaSize: Point | null;\n    /** @type { Point | null } */\n    elementSize: Point | null;\n    fit: number;\n    fill: number;\n    vFill: number;\n    initial: number;\n    secondary: number;\n    max: number;\n    min: number;\n    /**\n     * Calculate initial, secondary and maximum zoom level for the specified slide.\n     *\n     * It should be called when either image or viewport size changes.\n     *\n     * @param {number} maxWidth\n     * @param {number} maxHeight\n     * @param {Point} panAreaSize\n     */\n    update(maxWidth: number, maxHeight: number, panAreaSize: Point): void;\n    /**\n     * Parses user-defined zoom option.\n     *\n     * @private\n     * @param {'initial' | 'secondary' | 'max'} optionPrefix Zoom level option prefix (initial, secondary, max)\n     * @returns { number | undefined }\n     */\n    private _parseZoomLevelOption;\n    /**\n     * Get zoom level to which image will be zoomed after double-tap gesture,\n     * or when user clicks on zoom icon,\n     * or mouse-click on image itself.\n     * If you return 1 image will be zoomed to its original size.\n     *\n     * @private\n     * @return {number}\n     */\n    private _getSecondary;\n    /**\n     * Get initial image zoom level.\n     *\n     * @private\n     * @return {number}\n     */\n    private _getInitial;\n    /**\n     * Maximum zoom level when user zooms\n     * via zoom/pinch gesture,\n     * via cmd/ctrl-wheel or via trackpad.\n     *\n     * @private\n     * @return {number}\n     */\n    private _getMax;\n}\n"
  },
  {
    "path": "dist/types/types.d.ts",
    "content": "export declare type Methods<T> = {\n    [M in keyof T]: T[M] extends (...a: any) => any ? M : never;\n}[keyof T];\nexport declare type AddPostfix<T extends string, P extends string> = `${T}${P}`;\nexport interface Type<T> extends Function {\n    new (...args: any[]): T;\n}\n"
  },
  {
    "path": "dist/types/ui/button-arrow.d.ts",
    "content": "/** @type {UIElementData} */\nexport const arrowPrev: UIElementData;\n/** @type {UIElementData} */\nexport const arrowNext: UIElementData;\nexport type UIElementData = import('./ui-element.js').UIElementData;\nexport type PhotoSwipe = import('../photoswipe.js').default;\n"
  },
  {
    "path": "dist/types/ui/button-close.d.ts",
    "content": "export default closeButton;\n/** @type {import('./ui-element.js').UIElementData} UIElementData */\ndeclare const closeButton: import('./ui-element.js').UIElementData;\n"
  },
  {
    "path": "dist/types/ui/button-zoom.d.ts",
    "content": "export default zoomButton;\n/** @type {import('./ui-element.js').UIElementData} UIElementData */\ndeclare const zoomButton: import('./ui-element.js').UIElementData;\n"
  },
  {
    "path": "dist/types/ui/counter-indicator.d.ts",
    "content": "/** @type {import('./ui-element.js').UIElementData} UIElementData */\nexport const counterIndicator: import('./ui-element.js').UIElementData;\n"
  },
  {
    "path": "dist/types/ui/loading-indicator.d.ts",
    "content": "/** @type {import('./ui-element.js').UIElementData} UIElementData */\nexport const loadingIndicator: import('./ui-element.js').UIElementData;\n"
  },
  {
    "path": "dist/types/ui/ui-element.d.ts",
    "content": "export default UIElement;\nexport type PhotoSwipe = import('../photoswipe.js').default;\n/**\n * <T>\n */\nexport type Methods<T> = import('../types.js').Methods<T>;\nexport type UIElementMarkupProps = {\n    isCustomSVG?: boolean | undefined;\n    inner: string;\n    outlineID?: string | undefined;\n    size?: string | number | undefined;\n};\nexport type UIElementData = {\n    name?: string | undefined;\n    className?: string | undefined;\n    html?: UIElementMarkup | undefined;\n    isButton?: boolean | undefined;\n    tagName?: keyof HTMLElementTagNameMap | undefined;\n    title?: string | undefined;\n    ariaLabel?: string | undefined;\n    onInit?: ((element: HTMLElement, pswp: PhotoSwipe) => void) | undefined;\n    onClick?: import(\"../types.js\").Methods<import(\"../photoswipe.js\").default> | ((e: MouseEvent, element: HTMLElement, pswp: PhotoSwipe) => void) | undefined;\n    appendTo?: \"bar\" | \"wrapper\" | \"root\" | undefined;\n    order?: number | undefined;\n};\nexport type DefaultUIElements = 'arrowPrev' | 'arrowNext' | 'close' | 'zoom' | 'counter';\nexport type UIElementMarkup = string | UIElementMarkupProps;\ndeclare class UIElement {\n    /**\n     * @param {PhotoSwipe} pswp\n     * @param {UIElementData} data\n     */\n    constructor(pswp: PhotoSwipe, data: UIElementData);\n}\n"
  },
  {
    "path": "dist/types/ui/ui.d.ts",
    "content": "export default UI;\nexport type PhotoSwipe = import('../photoswipe.js').default;\nexport type UIElementData = import('./ui-element.js').UIElementData;\ndeclare class UI {\n    /**\n     * @param {PhotoSwipe} pswp\n     */\n    constructor(pswp: PhotoSwipe);\n    pswp: import(\"../photoswipe.js\").default;\n    isRegistered: boolean;\n    /** @type {UIElementData[]} */\n    uiElementsData: UIElementData[];\n    /** @type {(UIElement | UIElementData)[]} */\n    items: (UIElement | UIElementData)[];\n    /** @type {() => void} */\n    updatePreloaderVisibility: () => void;\n    /**\n     * @private\n     * @type {number | undefined}\n     */\n    private _lastUpdatedZoomLevel;\n    init(): void;\n    /**\n     * @param {UIElementData} elementData\n     */\n    registerElement(elementData: UIElementData): void;\n    /**\n     * Fired each time zoom or pan position is changed.\n     * Update classes that control visibility of zoom button and cursor icon.\n     *\n     * @private\n     */\n    private _onZoomPanUpdate;\n}\nimport UIElement from \"./ui-element.js\";\n"
  },
  {
    "path": "dist/types/util/animations.d.ts",
    "content": "export default Animations;\nexport type CssAnimationProps = import('./css-animation.js').CssAnimationProps;\nexport type SpringAnimationProps = import('./spring-animation.js').SpringAnimationProps;\nexport type SharedAnimationProps = {\n    name?: string | undefined;\n    isPan?: boolean | undefined;\n    isMainScroll?: boolean | undefined;\n    onComplete?: VoidFunction | undefined;\n    onFinish?: VoidFunction | undefined;\n};\nexport type Animation = SpringAnimation | CSSAnimation;\nexport type AnimationProps = SpringAnimationProps | CssAnimationProps;\n/** @typedef {import('./css-animation.js').CssAnimationProps} CssAnimationProps */\n/** @typedef {import('./spring-animation.js').SpringAnimationProps} SpringAnimationProps */\n/** @typedef {Object} SharedAnimationProps\n * @prop {string} [name]\n * @prop {boolean} [isPan]\n * @prop {boolean} [isMainScroll]\n * @prop {VoidFunction} [onComplete]\n * @prop {VoidFunction} [onFinish]\n */\n/** @typedef {SpringAnimation | CSSAnimation} Animation */\n/** @typedef {SpringAnimationProps | CssAnimationProps} AnimationProps */\n/**\n * Manages animations\n */\ndeclare class Animations {\n    /** @type {Animation[]} */\n    activeAnimations: Animation[];\n    /**\n     * @param {SpringAnimationProps} props\n     */\n    startSpring(props: SpringAnimationProps): void;\n    /**\n     * @param {CssAnimationProps} props\n     */\n    startTransition(props: CssAnimationProps): void;\n    /**\n     * @private\n     * @param {AnimationProps} props\n     * @param {boolean} [isSpring]\n     * @returns {Animation}\n     */\n    private _start;\n    /**\n     * @param {Animation} animation\n     */\n    stop(animation: Animation): void;\n    stopAll(): void;\n    /**\n     * Stop all pan or zoom transitions\n     */\n    stopAllPan(): void;\n    stopMainScroll(): void;\n    /**\n     * Returns true if main scroll transition is running\n     */\n    /**\n     * Returns true if any pan or zoom transition is running\n     */\n    isPanRunning(): boolean;\n}\nimport SpringAnimation from \"./spring-animation.js\";\nimport CSSAnimation from \"./css-animation.js\";\n"
  },
  {
    "path": "dist/types/util/css-animation.d.ts",
    "content": "export default CSSAnimation;\nexport type SharedAnimationProps = import('./animations.js').SharedAnimationProps;\nexport type DefaultCssAnimationProps = {\n    target: HTMLElement;\n    duration?: number | undefined;\n    easing?: string | undefined;\n    transform?: string | undefined;\n    opacity?: string | undefined;\n};\nexport type CssAnimationProps = SharedAnimationProps & DefaultCssAnimationProps;\n/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */\n/** @typedef {Object} DefaultCssAnimationProps\n *\n * @prop {HTMLElement} target\n * @prop {number} [duration]\n * @prop {string} [easing]\n * @prop {string} [transform]\n * @prop {string} [opacity]\n * */\n/** @typedef {SharedAnimationProps & DefaultCssAnimationProps} CssAnimationProps */\n/**\n * Runs CSS transition.\n */\ndeclare class CSSAnimation {\n    /**\n     * onComplete can be unpredictable, be careful about current state\n     *\n     * @param {CssAnimationProps} props\n     */\n    constructor(props: CssAnimationProps);\n    props: CssAnimationProps;\n    onFinish: VoidFunction;\n    /** @private */\n    private _target;\n    /** @private */\n    private _onComplete;\n    /** @private */\n    private _finished;\n    /**\n     * @private\n     * @param {TransitionEvent} e\n     */\n    private _onTransitionEnd;\n    /** @private */\n    private _helperTimeout;\n    /**\n     * @private\n     */\n    private _finalizeAnimation;\n    destroy(): void;\n}\n"
  },
  {
    "path": "dist/types/util/dom-events.d.ts",
    "content": "export default DOMEvents;\nexport type PoolItem = {\n    target: HTMLElement | Window | Document | undefined | null;\n    type: string;\n    listener: EventListenerOrEventListenerObject;\n    passive?: boolean | undefined;\n};\n/**\n * @typedef {Object} PoolItem\n * @prop {HTMLElement | Window | Document | undefined | null} target\n * @prop {string} type\n * @prop {EventListenerOrEventListenerObject} listener\n * @prop {boolean} [passive]\n */\ndeclare class DOMEvents {\n    /**\n     * @type {PoolItem[]}\n     * @private\n     */\n    private _pool;\n    /**\n     * Adds event listeners\n     *\n     * @param {PoolItem['target']} target\n     * @param {PoolItem['type']} type Can be multiple, separated by space.\n     * @param {PoolItem['listener']} listener\n     * @param {PoolItem['passive']} [passive]\n     */\n    add(target: PoolItem['target'], type: PoolItem['type'], listener: PoolItem['listener'], passive?: PoolItem['passive']): void;\n    /**\n     * Removes event listeners\n     *\n     * @param {PoolItem['target']} target\n     * @param {PoolItem['type']} type\n     * @param {PoolItem['listener']} listener\n     * @param {PoolItem['passive']} [passive]\n     */\n    remove(target: PoolItem['target'], type: PoolItem['type'], listener: PoolItem['listener'], passive?: PoolItem['passive']): void;\n    /**\n     * Removes all bound events\n     */\n    removeAll(): void;\n    /**\n     * Adds or removes event\n     *\n     * @private\n     * @param {PoolItem['target']} target\n     * @param {PoolItem['type']} type\n     * @param {PoolItem['listener']} listener\n     * @param {PoolItem['passive']} [passive]\n     * @param {boolean} [unbind] Whether the event should be added or removed\n     * @param {boolean} [skipPool] Whether events pool should be skipped\n     */\n    private _toggleListener;\n}\n"
  },
  {
    "path": "dist/types/util/spring-animation.d.ts",
    "content": "export default SpringAnimation;\nexport type SharedAnimationProps = import('./animations.js').SharedAnimationProps;\nexport type DefaultSpringAnimationProps = {\n    start: number;\n    end: number;\n    velocity: number;\n    dampingRatio?: number | undefined;\n    naturalFrequency?: number | undefined;\n    onUpdate: (end: number) => void;\n};\nexport type SpringAnimationProps = SharedAnimationProps & DefaultSpringAnimationProps;\n/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */\n/**\n * @typedef {Object} DefaultSpringAnimationProps\n *\n * @prop {number} start\n * @prop {number} end\n * @prop {number} velocity\n * @prop {number} [dampingRatio]\n * @prop {number} [naturalFrequency]\n * @prop {(end: number) => void} onUpdate\n */\n/** @typedef {SharedAnimationProps & DefaultSpringAnimationProps} SpringAnimationProps */\ndeclare class SpringAnimation {\n    /**\n     * @param {SpringAnimationProps} props\n     */\n    constructor(props: SpringAnimationProps);\n    props: SpringAnimationProps;\n    _raf: number;\n    onFinish: VoidFunction;\n    destroy(): void;\n}\n"
  },
  {
    "path": "dist/types/util/spring-easer.d.ts",
    "content": "export default SpringEaser;\n/**\n * Spring easing helper\n */\ndeclare class SpringEaser {\n    /**\n     * @param {number} initialVelocity Initial velocity, px per ms.\n     *\n     * @param {number} [dampingRatio]\n     * Determines how bouncy animation will be.\n     * From 0 to 1, 0 - always overshoot, 1 - do not overshoot.\n     * \"overshoot\" refers to part of animation that\n     * goes beyond the final value.\n     *\n     * @param {number} [naturalFrequency]\n     * Determines how fast animation will slow down.\n     * The higher value - the stiffer the transition will be,\n     * and the faster it will slow down.\n     * Recommended value from 10 to 50\n     */\n    constructor(initialVelocity: number, dampingRatio?: number | undefined, naturalFrequency?: number | undefined);\n    velocity: number;\n    _dampingRatio: number;\n    _naturalFrequency: number;\n    _dampedFrequency: number;\n    /**\n     * @param {number} deltaPosition Difference between current and end position of the animation\n     * @param {number} deltaTime Frame duration in milliseconds\n     *\n     * @returns {number} Displacement, relative to the end position.\n     */\n    easeFrame(deltaPosition: number, deltaTime: number): number;\n}\n"
  },
  {
    "path": "dist/types/util/util.d.ts",
    "content": "/** @typedef {import('../photoswipe.js').Point} Point */\n/**\n * @template {keyof HTMLElementTagNameMap} T\n * @param {string} className\n * @param {T} tagName\n * @param {Node} [appendToEl]\n * @returns {HTMLElementTagNameMap[T]}\n */\nexport function createElement<T extends keyof HTMLElementTagNameMap>(className: string, tagName: T, appendToEl?: Node | undefined): HTMLElementTagNameMap[T];\n/**\n * @param {Point} p1\n * @param {Point} p2\n * @returns {Point}\n */\nexport function equalizePoints(p1: Point, p2: Point): Point;\n/**\n * @param {Point} p\n */\nexport function roundPoint(p: Point): void;\n/**\n * Returns distance between two points.\n *\n * @param {Point} p1\n * @param {Point} p2\n * @returns {number}\n */\nexport function getDistanceBetween(p1: Point, p2: Point): number;\n/**\n * Whether X and Y positions of points are equal\n *\n * @param {Point} p1\n * @param {Point} p2\n * @returns {boolean}\n */\nexport function pointsEqual(p1: Point, p2: Point): boolean;\n/**\n * The float result between the min and max values.\n *\n * @param {number} val\n * @param {number} min\n * @param {number} max\n * @returns {number}\n */\nexport function clamp(val: number, min: number, max: number): number;\n/**\n * Get transform string\n *\n * @param {number} x\n * @param {number} [y]\n * @param {number} [scale]\n * @returns {string}\n */\nexport function toTransformString(x: number, y?: number | undefined, scale?: number | undefined): string;\n/**\n * Apply transform:translate(x, y) scale(scale) to element\n *\n * @param {HTMLElement} el\n * @param {number} x\n * @param {number} [y]\n * @param {number} [scale]\n */\nexport function setTransform(el: HTMLElement, x: number, y?: number | undefined, scale?: number | undefined): void;\n/**\n * Apply CSS transition to element\n *\n * @param {HTMLElement} el\n * @param {string} [prop] CSS property to animate\n * @param {number} [duration] in ms\n * @param {string} [ease] CSS easing function\n */\nexport function setTransitionStyle(el: HTMLElement, prop?: string | undefined, duration?: number | undefined, ease?: string | undefined): void;\n/**\n * Apply width and height CSS properties to element\n *\n * @param {HTMLElement} el\n * @param {string | number} w\n * @param {string | number} h\n */\nexport function setWidthHeight(el: HTMLElement, w: string | number, h: string | number): void;\n/**\n * @param {HTMLElement} el\n */\nexport function removeTransitionStyle(el: HTMLElement): void;\n/**\n * @param {HTMLImageElement} img\n * @returns {Promise<HTMLImageElement | void>}\n */\nexport function decodeImage(img: HTMLImageElement): Promise<HTMLImageElement | void>;\n/**\n * Check if click or keydown event was dispatched\n * with a special key or via mouse wheel.\n *\n * @param {MouseEvent | KeyboardEvent} e\n * @returns {boolean}\n */\nexport function specialKeyUsed(e: MouseEvent | KeyboardEvent): boolean;\n/**\n * Parse `gallery` or `children` options.\n *\n * @param {import('../photoswipe.js').ElementProvider} [option]\n * @param {string} [legacySelector]\n * @param {HTMLElement | Document} [parent]\n * @returns HTMLElement[]\n */\nexport function getElementsFromOption(option?: import(\"../photoswipe.js\").ElementProvider | undefined, legacySelector?: string | undefined, parent?: Document | HTMLElement | undefined): HTMLElement[];\n/**\n * Check if variable is PhotoSwipe class\n *\n * @param {any} fn\n * @returns {boolean}\n */\nexport function isPswpClass(fn: any): boolean;\n/**\n * Check if browser is Safari\n *\n * @returns {boolean}\n */\nexport function isSafari(): boolean;\n/** @typedef {LOAD_STATE[keyof LOAD_STATE]} LoadState */\n/** @type {{ IDLE: 'idle'; LOADING: 'loading'; LOADED: 'loaded'; ERROR: 'error' }} */\nexport const LOAD_STATE: {\n    IDLE: 'idle';\n    LOADING: 'loading';\n    LOADED: 'loaded';\n    ERROR: 'error';\n};\nexport type Point = import('../photoswipe.js').Point;\nexport type LoadState = {\n    IDLE: \"idle\";\n    LOADING: \"loading\";\n    LOADED: \"loaded\";\n    ERROR: \"error\";\n}[keyof {\n    IDLE: \"idle\";\n    LOADING: \"loading\";\n    LOADED: \"loaded\";\n    ERROR: \"error\";\n}];\n"
  },
  {
    "path": "dist/types/util/viewport-size.d.ts",
    "content": "/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n/** @typedef {import('../photoswipe.js').Point} Point */\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n/**\n * @param {PhotoSwipeOptions} options\n * @param {PhotoSwipeBase} pswp\n * @returns {Point}\n */\nexport function getViewportSize(options: PhotoSwipeOptions, pswp: PhotoSwipeBase): Point;\n/**\n * Parses padding option.\n * Supported formats:\n *\n * // Object\n * padding: {\n *  top: 0,\n *  bottom: 0,\n *  left: 0,\n *  right: 0\n * }\n *\n * // A function that returns the object\n * paddingFn: (viewportSize, itemData, index) => {\n *  return {\n *    top: 0,\n *    bottom: 0,\n *    left: 0,\n *    right: 0\n *  };\n * }\n *\n * // Legacy variant\n * paddingLeft: 0,\n * paddingRight: 0,\n * paddingTop: 0,\n * paddingBottom: 0,\n *\n * @param {'left' | 'top' | 'bottom' | 'right'} prop\n * @param {PhotoSwipeOptions} options PhotoSwipe options\n * @param {Point} viewportSize PhotoSwipe viewport size, for example: { x:800, y:600 }\n * @param {SlideData} itemData Data about the slide\n * @param {number} index Slide index\n * @returns {number}\n */\nexport function parsePaddingOption(prop: 'left' | 'top' | 'bottom' | 'right', options: PhotoSwipeOptions, viewportSize: Point, itemData: SlideData, index: number): number;\n/**\n * @param {PhotoSwipeOptions} options\n * @param {Point} viewportSize\n * @param {SlideData} itemData\n * @param {number} index\n * @returns {Point}\n */\nexport function getPanAreaSize(options: PhotoSwipeOptions, viewportSize: Point, itemData: SlideData, index: number): Point;\nexport type PhotoSwipeOptions = import('../photoswipe.js').PhotoSwipeOptions;\nexport type PhotoSwipeBase = import('../core/base.js').default;\nexport type Point = import('../photoswipe.js').Point;\nexport type SlideData = import('../slide/slide.js').SlideData;\n"
  },
  {
    "path": "dist/umd/README.md",
    "content": "`umd/` folder contains transpiled version of PhotoSwipe in universal module definition format.\n\nUse it only if you are unable to use ESM version.\n\nBasic example:\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <title>Test Gallery</title>\n  </head>\n  <body>\n\n    <script src=\"./photoswipe.umd.min.js\"></script>\n    <script src=\"./photoswipe-lightbox.umd.min.js\"></script>\n    \n    <link rel=\"stylesheet\" href=\"../photoswipe.css\">\n\n    <div class=\"test-gallery\">\n      <a href=\"https://dummyimage.com/1200x600/000/fff\" data-pswp-width=\"1200\" data-pswp-height=\"600\">\n        <img src=\"https://dummyimage.com/120x60/000/fff\" alt=\"\" />\n      </a>\n      <a href=\"https://dummyimage.com/1200x1200/000/fff\" data-pswp-width=\"1200\" data-pswp-height=\"1200\">\n        <img src=\"https://dummyimage.com/60x60/000/fff\" alt=\"\" />\n      </a>\n      <a href=\"https://dummyimage.com/600x1200/000/fff\" data-pswp-width=\"600\" data-pswp-height=\"1200\">\n        <img src=\"https://dummyimage.com/30x60/000/fff\" alt=\"\" />\n      </a>\n    </div>\n    \n    <script type=\"text/javascript\">\n      var lightbox = new PhotoSwipeLightbox({\n        gallery: '.test-gallery',\n        children: 'a',\n        // dynamic import is not supported in UMD version\n        pswpModule: PhotoSwipe \n      });\n      lightbox.init();\n    </script>\n\n  </body>\n</html>\n\n```"
  },
  {
    "path": "docs/adding-ui-elements.md",
    "content": "---\nid: adding-ui-elements\ntitle: Adding UI elements\nsidebar_label: Adding UI elements\n---\n\nUse method `pswp.ui.registerElement` to add any interactive element inside PhotoSwipe. It must be called within or after `uiRegister` event. For example:\n\n## Adding a Button to the Toolbar\n\n<PswpCodePreview galleryID=\"with-custom-button\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst options = {\n  gallery:'#gallery--with-custom-button',\n  children:'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n};\nconst lightbox = new PhotoSwipeLightbox(options);\nlightbox.on('uiRegister', function() {\n  lightbox.pswp.ui.registerElement({\n    name: 'test-button',\n    ariaLabel: 'Toggle zoom',\n    order: 9,\n    isButton: true,\n    html: 'Test',\n    onClick: (event, el) => {\n      if ( confirm('Do you want to toggle zoom?') ) {\n        lightbox.pswp.toggleZoom();\n      }\n    }\n  });\n});\nlightbox.init();\n\n```\n\n```css pswpcode\nbutton.pswp__button--test-button {\n  background: #136912 !important;\n  font-size: 20px;\n  color: #fff;\n}\n```\n\n</PswpCodePreview>\n\n\n## Adding HTML Indicator to the Toolbar\n\nDisplay zoom level of the current image.\n\n<PswpCodePreview galleryID=\"with-custom-toolbar-indicator\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst options = {\n  gallery:'#gallery--with-custom-toolbar-indicator',\n  children:'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n};\nconst lightbox = new PhotoSwipeLightbox(options);\nlightbox.on('uiRegister', function() {\n  lightbox.pswp.ui.registerElement({\n    name: 'zoom-level-indicator',\n    order: 9,\n    onInit: (el, pswp) => {\n      pswp.on('zoomPanUpdate', (e) => {\n        if (e.slide === pswp.currSlide) {\n          el.innerText = 'Zoom level is ' + Math.round(pswp.currSlide.currZoomLevel * 100) + '%';\n        }\n      });\n    }\n  });\n});\nlightbox.init();\n```\n\n```css pswpcode\n.pswp__zoom-level-indicator {\n  background: #136912;\n  font-size: 16px;\n  line-height: 1;\n  font-weight: bold;\n  color: #fff;\n  height: auto;\n  align-self: center;\n  padding: 4px 6px 5px;\n  margin-right: 4px;\n}\n```\n\n</PswpCodePreview>\n\n\n## Adding Download Button\n\nWhen you provide an SVG, make sure that it has `aria-hidden=\"true\"` and `pswp__icn` class to preserve styling.\n\n<PswpCodePreview galleryID=\"with-download-button\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst options = {\n  gallery:'#gallery--with-download-button',\n  children:'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n};\nconst lightbox = new PhotoSwipeLightbox(options);\nlightbox.on('uiRegister', function() {\n  lightbox.pswp.ui.registerElement({\n    name: 'download-button',\n    order: 8,\n    isButton: true,\n    tagName: 'a',\n\n    // SVG with outline\n    html: {\n      isCustomSVG: true,\n      inner: '<path d=\"M20.5 14.3 17.1 18V10h-2.2v7.9l-3.4-3.6L10 16l6 6.1 6-6.1ZM23 23H9v2h14Z\" id=\"pswp__icn-download\"/>',\n      outlineID: 'pswp__icn-download'\n    },\n\n    // Or provide full svg:\n    // html: '<svg width=\"32\" height=\"32\" viewBox=\"0 0 32 32\" aria-hidden=\"true\" class=\"pswp__icn\"><path d=\"M20.5 14.3 17.1 18V10h-2.2v7.9l-3.4-3.6L10 16l6 6.1 6-6.1ZM23 23H9v2h14Z\" /></svg>',\n\n    // Or provide any other markup:\n    // html: '<i class=\"fa-solid fa-download\"></i>' \n\n    onInit: (el, pswp) => {\n      el.setAttribute('download', '');\n      el.setAttribute('target', '_blank');\n      el.setAttribute('rel', 'noopener');\n\n      pswp.on('change', () => {\n        console.log('change');\n        el.href = pswp.currSlide.data.src;\n      });\n    }\n  });\n});\nlightbox.init();\n\n```\n\n```css pswpcode\nbutton.pswp__button--test-button {\n  background: #136912 !important;\n  font-size: 20px;\n  color: #fff;\n}\n```\n\n</PswpCodePreview>\n\n\n## Adding Navigation Indicator (bullets)\n\n\n<PswpCodePreview galleryID=\"with-bullets\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst options = {\n  gallery:'#gallery--with-bullets',\n  children:'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n};\nconst lightbox = new PhotoSwipeLightbox(options);\nlightbox.on('uiRegister', function() {\n  lightbox.pswp.ui.registerElement({\n    name: 'bulletsIndicator',\n    className: 'pswp__bullets-indicator',\n    appendTo: 'wrapper',\n    onInit: (el, pswp) => {\n      const bullets = [];\n      let bullet;\n      let prevIndex = -1;\n\n      for (let i = 0; i < pswp.getNumItems(); i++) {\n        bullet = document.createElement('div');\n        bullet.className = 'pswp__bullet';\n        bullet.onclick = (e) => {\n          pswp.goTo(bullets.indexOf(e.target));\n        };\n        el.appendChild(bullet);\n        bullets.push(bullet);\n      }\n\n      pswp.on('change', (a,) => {\n        if (prevIndex >= 0) {\n          bullets[prevIndex].classList.remove('pswp__bullet--active');\n        }\n        bullets[pswp.currIndex].classList.add('pswp__bullet--active');\n        prevIndex = pswp.currIndex;\n      });\n    }\n  });\n});\nlightbox.init();\n\n```\n\n```css pswpcode\n.pswp__bullets-indicator {\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n\n  position: absolute;\n  bottom: 30px;\n  left: 50%;\n  transform: translate(-50%, 0);\n}\n.pswp__bullet {\n  width: 30px;\n  height: 30px;\n  border-radius: 50%;\n  background: #fff;\n  margin: 0 5px;\n}\n.pswp__bullet--active { \n  background: green;\n}\n```\n\n</PswpCodePreview>\n\n\n## ui.registerElement() API\n\n```js\n// registerElement method must be called within or after uiRegister event\npswp.ui.registerElement({\n  // Unique name of the UI element,\n  name: 'test123',\n\n  // Classname of the element. \n  // Optional, if not defined - name will be used \n  // in format pswp__button--name, or pswp__name\n  className: undefined,\n\n  // Order of element, default order elements:\n  // counter - 5, zoom button - 10, info - 15, close - 20.\n  order: 9,\n\n  // If element should be \n  // rendered as button\n  isButton: true,\n\n  // Element tag name,\n  // Optional, if not defined - button or div will be used\n  tagName: 'a',\n\n  // Button title, optional\n  title: 'Button title', \n\n  // Button aria-label attribute,\n  // if not defined - title will be used\n  ariaLabel: undefined,\n  \n  // html string, will be added inside button, optional\n  // can also be an object with svg data\n  html: 'Test', \n\n  // Element container, possible values:\n  // - 'bar'  (top toolbar, .pswp__top-bar, default value), \n  // - 'wrapper' (scroll viewport wrapper, .pswp__scroll-wrap),\n  // - 'root' (root element of the dialog, .pswp) \n  // If you add a text inside 'wrapper' - it won't be selectable,\n  // as PhotoSwipe intersects all touch events there.\n  appendTo: 'bar',\n\n  // callback is triggered right before \n  // corresponding element is added to DOM\n  // (while dialog is opening/creating)\n  onInit: function(el, pswp) {\n    // el - reference to your DOM element\n    // pswp - PhotoSwipe object\n    // You may modify element here, for example:\n    el.classList.add('my-test-class');\n  }, \n\n  // when user clicks or taps on element\n  onClick: function (event, el, pswp) {\n    console.log('clicked element:', el);\n  }\n});\n```\n\nAll default buttons and elements also use this syntax, so you can look up more examples in folder `/src/js/ui/` within the repository.\n\nIf you need to override or slightly adjust existing buttons - feel free to use [`uiElement` filter](/filters#uielement).\n\n`registerElement` is not the only method to add various UI elements, it's just an optional shortcut. Feel free to append elements manually."
  },
  {
    "path": "docs/adjusting-zoom-level.md",
    "content": "---\nid: adjusting-zoom-level\ntitle: Adjusting Zoom Level\nsidebar_label: Adjusting zoom level\n---\n\nPhotoswipe has three zoom level options:\n\n- `initialZoomLevel` - zoom level when photoswipe is opened.\n- `secondaryZoomLevel` - zoom level when user clicks \"zoom\" button, double-taps image, or clicks an image. If it equals to initial - secondary zoom functionality is disabled.\n- `maxZoomLevel` - maximum zoom level when user zooms via zoom/pinch gesture, via ctrl-wheel or via trackpad. Always highest among three.\n\n\n## Supported values\n\nEach zoom level option can be:\n\n- A positive `Number`, where `1` is original image size.\n- A `String`:\n  - `'fit'` - image fits into viewport. \n  - `'fill'` - image fills the viewport (similar to `background-size:cover`).\n  - In both cases image will not be larger than original.\n- A `Function` that should return number. Use it to define dynamic zoom level. Function is called separately for each image when it is rendered, or resized, or lazy-loaded. For example to set custom `secondaryZoomLevel`:\n\n```js\nsecondaryZoomLevel: (zoomLevelObject) => {\n  // zoomLevelObject is instance of ZoomLevel class\n  console.log('Element size:', zoomLevelObject.elementSize);\n  console.log('Pan area size (viewport minus padding):', zoomLevelObject.panAreaSize);\n  console.log('Item index:', zoomLevelObject.index);\n  console.log('Item data:', zoomLevelObject.itemData);\n\n  // return desired zoom level\n  return 1;\n}\n```\n\n## Default behaviour\n\n- Initial zoom level is `fit`.\n- Secondary zoom level is `2.5x` of `fit`, but not wider than `3000px`.\n- Maximum zoom level is `4x` of `fit`.\n\n<PswpCodePreview galleryID=\"open-in-original-size\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\nconst lightbox = new PhotoSwipeLightbox({\n  gallery:'#gallery--open-in-original-size',\n  children:'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n\n## Open images in `fill` state\n\n<PswpCodePreview galleryID=\"open-in-fill-state\" numItems=\"6\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\nconst lightbox = new PhotoSwipeLightbox({\n  gallery:'#gallery--open-in-fill-state',\n  children:'a',\n\n  initialZoomLevel: 'fill',\n  secondaryZoomLevel: 1,\n  maxZoomLevel: 2,\n\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n## Secondary zoom level is higher than initial\n\n<PswpCodePreview galleryID=\"secondary-zoom-higher\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\nconst lightbox = new PhotoSwipeLightbox({\n  gallery:'#gallery--secondary-zoom-higher',\n  children: 'a',\n  mouseMovePan: true,\n\n  initialZoomLevel: 'fit',\n  secondaryZoomLevel: 1.5,\n  maxZoomLevel: 1,\n\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n## Initial zoom level is higher than secondary\n\nInitial zoom level is set to `1` (original image size), you may want to disable opening closing transition (`showHideAnimationType:'none'`) - as the larger the image - the harder it is to animate it smoothly. In this example it's not disabled, just to show you how it behaves:\n\n<PswpCodePreview galleryID=\"initial-higher\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\nconst lightbox = new PhotoSwipeLightbox({\n  gallery:'#gallery--initial-higher',\n  children: 'a',\n  mouseMovePan: true,\n\n  initialZoomLevel: 1,\n  secondaryZoomLevel: 'fit',\n  maxZoomLevel: 4,\n\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n## Initial and secondary zoom level are equal\n\n<PswpCodePreview galleryID=\"zoom-levels-equal\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\nconst lightbox = new PhotoSwipeLightbox({\n  gallery:'#gallery--zoom-levels-equal',\n  children: 'a',\n  mouseMovePan: true,\n\n  initialZoomLevel: 'fill',\n  secondaryZoomLevel: 'fill',\n  maxZoomLevel: 3,\n\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n## Image is smaller than initial and secondary\n\n<PswpCodePreview>\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\nconst lightbox = new PhotoSwipeLightbox({\n  gallery:'#very-small-image',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.init();\n```\n\n```html pswpcode\n<a id=\"very-small-image\" href=\"https://dummyimage.com/200x200/555/fff/\" data-pswp-width=\"200\" data-pswp-height=\"200\" target=\"_blank\">\n  <img src=\"https://dummyimage.com/100x100/555/fff/?text=small%20image\" alt=\"\" />\n</a>\n```\n\n</PswpCodePreview>\n\n## Dynamic zoom level\n\nChange zoom levels based on viewport size and device orientation:\n\n- fill 100% height of viewport on phones with portrait orientation,\n- otherwise fit image into viewport\n\n<PswpCodePreview galleryID=\"dynamic-zoom-level\" orientation=\"landscape\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\n\nfunction isPhonePortrait() {\n  return window.matchMedia('(max-width: 600px) and (orientation: portrait)').matches;\n}\n\nconst lightbox = new PhotoSwipeLightbox({\n  gallery:'#gallery--dynamic-zoom-level',\n  children:'a',\n  \n\n  initialZoomLevel: (zoomLevelObject) => {\n    if (isPhonePortrait()) {\n      return zoomLevelObject.vFill;\n    } else {\n      return zoomLevelObject.fit;\n    }\n  },\n  secondaryZoomLevel: (zoomLevelObject) => {\n    if (isPhonePortrait()) {\n      return zoomLevelObject.fit;\n    } else {\n      return 1;\n    }\n  },\n\n  maxZoomLevel: 1,\n\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.init();\n```\n\n</PswpCodePreview>"
  },
  {
    "path": "docs/caption.md",
    "content": "---\nid: caption\ntitle: Caption\nsidebar_label: Caption\n---\n\nPhotoSwipe does not support caption out of box, but you may implement a basic caption via API, as you can see below. Or you may use a [dynamic caption plugin](https://github.com/dimsemenov/photoswipe-dynamic-caption-plugin). \n\n**Important!** Please make sure that caption is always accessible without PhotoSwipe for screen reader users — the lightbox is disabled in unsupported browsers. If you are unable to show the caption text on the page - make sure that image has a proper `alt` attribute, `aria-labelledby`, or `<figcaption>` inside `<figure>`.\n\nimport { captionTemplate } from '@site/src/components/PswpCodePreview/gallery-templates/caption.js';\n\n<PswpCodePreview galleryID=\"with-custom-caption\" templateFn={captionTemplate}>\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst options = {\n  gallery:'#gallery--with-custom-caption',\n  children:'.pswp-gallery__item',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n};\nconst lightbox = new PhotoSwipeLightbox(options);\nlightbox.on('uiRegister', function() {\n  lightbox.pswp.ui.registerElement({\n    name: 'custom-caption',\n    order: 9,\n    isButton: false,\n    appendTo: 'root',\n    html: 'Caption text',\n    onInit: (el, pswp) => {\n      lightbox.pswp.on('change', () => {\n        const currSlideElement = lightbox.pswp.currSlide.data.element;\n        let captionHTML = '';\n        if (currSlideElement) {\n          const hiddenCaption = currSlideElement.querySelector('.hidden-caption-content');\n          if (hiddenCaption) {\n            // get caption from element with class hidden-caption-content\n            captionHTML = hiddenCaption.innerHTML;\n          } else {\n            // get caption from alt attribute\n            captionHTML = currSlideElement.querySelector('img').getAttribute('alt');\n          }\n        }\n        el.innerHTML = captionHTML || '';\n      });\n    }\n  });\n});\nlightbox.init();\n```\n\n```css pswpcode\n.pswp__custom-caption {\n  background: rgba(75, 150, 75, 0.75);\n  font-size: 16px;\n  color: #fff;\n  width: calc(100% - 32px);\n  max-width: 400px;\n  padding: 2px 8px;\n  border-radius: 4px;\n  position: absolute;\n  left: 50%;\n  bottom: 16px;\n  transform: translateX(-50%);\n}\n.pswp__custom-caption a {\n  color: #fff;\n  text-decoration: underline;\n}\n.hidden-caption-content {\n  display: none;\n}\n```\n\n</PswpCodePreview>\n"
  },
  {
    "path": "docs/click-and-tap-actions.md",
    "content": "---\nid: click-and-tap-actions\ntitle: Click Actions\nsidebar_label: Click actions\n---\n\nList of options:\n\n- `imageClickAction` - click on image with mouse.\n- `tapAction` - tap on PhotoSwipe viewport content (excluding buttons).\n- `doubleTapAction` - double tap on anything. Tap delay is removed if this option is set to `false`.\n- `bgClickAction` - click on area around image (background), with mouse.\n\n## Supported action values\n\n- `'zoom'` - zooms current image ([depending on secondary zoom level](adjusting-zoom-level.md)) (default `doubleTapAction`).\n- `'zoom-or-close'` - image will be closed if it can not be zoomed (default `imageClickAction`).\n- `'toggle-controls'` - toggle visibility of controls (default `tapAction`).\n- `'next'` - move to the next slide\n- `'close'` - close the gallery\n- A Function that may perform any action, for example:\n\n```js\nimageClickAction: (releasePoint, e) => {}\n```\n\n\n## Click on image moves to the next slide\n\n<PswpCodePreview galleryID=\"click-to-next\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst options = {\n  gallery:'#gallery--click-to-next',\n  children:'a',\n\n  imageClickAction: 'next',\n  tapAction: 'next',\n  \n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n};\nconst lightbox = new PhotoSwipeLightbox(options);\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n## Disable tap delay, click/tap to close\n\n<PswpCodePreview galleryID=\"click-to-close\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst options = {\n  gallery:'#gallery--click-to-close',\n  children:'a',\n\n  initialZoomLevel: 'fill',\n  secondaryZoomLevel: 'fit',\n\n  imageClickAction: 'close',\n  tapAction: 'close',\n\n  // tap delay is removed if set to false\n  doubleTapAction: false,\n  \n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n};\nconst lightbox = new PhotoSwipeLightbox(options);\nlightbox.init();\n```\n\n```css pswpcode\n/* override zoom cursor */\n.pswp__img {\n    cursor: pointer !important;\n}\n```\n\n</PswpCodePreview>\n"
  },
  {
    "path": "docs/custom-content.md",
    "content": "---\nid: custom-content\ntitle: Custom Content in Slides\nsidebar_label: Custom content\n---\n\nBy default PhotoSwipe can only show images or raw HTML content, but you may use content events and filters to support new types.\n\nPlease note that PhotoSwipe is mainly designed to display photos. There are issues with displaying other types of content - for example, you can't swipe over iframes. Always have a fallback, for example if you embed Google Map - make sure that there is an outbound link to it.\n\n## Using WebP image format\n\nThe example below uses `<picture>` instead of `<img>` for slides that support webp. The webp image source is retrieved from `data-pswp-webp-src` attribute.\n\nimport { contentTypesTemplate } from '@site/src/components/PswpCodePreview/gallery-templates/content-types.js';\n\n<PswpCodePreview galleryID=\"webp-demo\" templateFn={contentTypesTemplate} displayHTML>\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--webp-demo',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\n\n// Parse data-pswp-webp-src attribute\nlightbox.addFilter('itemData', (itemData, index) => {\n  const webpSrc = itemData.element.dataset.pswpWebpSrc;\n  if (webpSrc) {\n    itemData.webpSrc = webpSrc;\n  }\n  return itemData;\n});\n\n// use <picture> instead of <img>\nlightbox.on('contentLoad', (e) => {\n    const { content, isLazy } = e;\n\n    if (content.data.webpSrc) {\n      // prevent to stop the default behavior\n      e.preventDefault();\n\n      content.pictureElement = document.createElement('picture');\n      \n      const sourceWebp = document.createElement('source');\n      sourceWebp.srcset = content.data.webpSrc;\n      sourceWebp.type = 'image/webp';\n\n      const sourceJpg = document.createElement('source');\n      sourceJpg.srcset = content.data.src;\n      sourceJpg.type = 'image/jpeg';\n\n      content.element = document.createElement('img');\n      content.element.src = content.data.src;\n      content.element.setAttribute('alt', '');\n      content.element.className = 'pswp__img';\n\n      content.pictureElement.appendChild(sourceWebp);\n      content.pictureElement.appendChild(sourceJpg);\n      content.pictureElement.appendChild(content.element);\n\n      content.state = 'loading';\n\n      if (content.element.complete) {\n        content.onLoaded();\n      } else {\n        content.element.onload = () => {\n          content.onLoaded();\n        };\n\n        content.element.onerror = () => {\n          content.onError();\n        };\n      }\n    }\n});\n\n\n// by default PhotoSwipe appends <img>,\n// but we want to append <picture>\nlightbox.on('contentAppend', (e) => {\n  const { content } = e;\n  if (content.pictureElement && !content.pictureElement.parentNode) {\n    e.preventDefault();\n    content.slide.container.appendChild(content.pictureElement);\n  }\n});\n\n// for next/prev navigation with <picture>\n// by default PhotoSwipe removes <img>,\n// but we want to remove <picture>\nlightbox.on('contentRemove', (e) => {\n  const { content } = e;\n  if (content.pictureElement && content.pictureElement.parentNode) {\n    e.preventDefault();\n    content.pictureElement.remove();\n  }\n});\n\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n## Google Maps demo\n\nAnother example that shows a map `<iframe>`. \n\nTo define the type of slide, you may use `data-pswp-type` attribute (or `type` property of the slide object). Built-in types are `image` and `html`. The example below uses a custom `google-map` type.\n\n```html\n<a data-pswp-type=\"google-map\" data-google-map-url=\"https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d325518.68780316407!2d30.252511957059642!3d50.4016990487754!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x40d4cf4ee15a4505%3A0x764931d2170146fe!2z0JrQuNC10LIsIDAyMDAw!5e0!3m2!1sru!2sua!4v1647422169265!5m2!1sru!2sua\" href=\"https://maps.google.com/maps?ll=50.402036,30.532691&z=10&t=m&mapclient=embed&q=%D0%9A%D0%B8%D0%B5%D0%B2%2002000\" target=\"_blank\"><img src=\"https://cdn.photoswipe.com/photoswipe-demo-images/photos/map-thumb.png\" alt=\"\"></a>\n```\n\n<PswpCodePreview>\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--google-map-demo',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\n\n// parse data-google-map-url attribute\nlightbox.addFilter('itemData', (itemData, index) => {\n  const googleMapUrl = itemData.element.dataset.googleMapUrl;\n  if (googleMapUrl) {\n    itemData.googleMapUrl = googleMapUrl;\n  }\n  return itemData;\n});\n\n// override slide content\nlightbox.on('contentLoad', (e) => {\n    const { content } = e;\n    if (content.type === 'google-map') {\n      // prevent the deafult behavior\n      e.preventDefault();\n\n      // Create a container for iframe\n      // and assign it to the `content.element` property\n      content.element = document.createElement('div');\n      content.element.className = 'pswp__google-map-container';\n\n      const iframe = document.createElement('iframe');\n      iframe.setAttribute('allowfullscreen', '');\n      iframe.src = content.data.googleMapUrl;\n      content.element.appendChild(iframe);\n    }\n});\n\nlightbox.init();\n```\n\n```css pswpcode \n.pswp__google-map-container {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  pointer-events: none;\n}\n.pswp__google-map-container iframe {\n  background: #444;\n  width: 100%;\n  height: 100%;\n  max-width: 800px;\n  max-height: 600px;\n  pointer-events: auto;\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n}\n```\n\n```html pswpcode\n<div class=\"pswp-gallery\" id=\"gallery--google-map-demo\">\n<a href=\"https://maps.google.com/maps?ll=50.402036,30.532691&z=10&t=m&mapclient=embed&q=%D0%9A%D0%B8%D0%B5%D0%B2%2002000\" data-google-map-url=\"https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d325518.68780316407!2d30.252511957059642!3d50.4016990487754!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x40d4cf4ee15a4505%3A0x764931d2170146fe!2z0JrQuNC10LIsIDAyMDAw!5e0!3m2!1sru!2sua!4v1647422169265!5m2!1sru!2sua\" data-pswp-type=\"google-map\" target=\"_blank\">\n    <img src=\"https://cdn.photoswipe.com/photoswipe-demo-images/photos/map-thumb.png\" alt=\"\">\n  </a>\n<a href=\"https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-2500.jpg\" data-pswp-width=\"1669\" data-pswp-height=\"2500\" target=\"_blank\">\n    <img src=\"https://cdn.photoswipe.com/photoswipe-demo-images/photos/2/img-200.jpg\" alt=\"\">\n  </a>\n<a href=\"https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-2500.jpg\" data-pswp-width=\"2500\" data-pswp-height=\"1666\" target=\"_blank\">\n    <img src=\"https://cdn.photoswipe.com/photoswipe-demo-images/photos/3/img-200.jpg\" alt=\"\">\n  </a>\n</div>\n```\n\n</PswpCodePreview>\n"
  },
  {
    "path": "docs/data-sources.md",
    "content": "---\nid: data-sources\ntitle: Data Sources\nsidebar_label: Data sources\n---\n\n## From an Array (or NodeList)\n\nPass an array of any items via `dataSource` option. Its length will determine amount of slides (which may be modified further from [numItems event](#dynamically-generated-data)).\n\nEach item should contain data that you need to generate slide (for image slide it would be `src` (image URL), `width` (image width), `height`, `srcset`, `alt`).\n\nIf these properties are not present in your initial array, you may \"pre-parse\" each item from  [itemData filter](#dynamically-generated-data).\n\n\n### Without Lightbox module\n\nThe Lightbox module connects gallery grid DOM to PhotoSwipe, thus when using a button, it's completely optional and you can just use PhotoSwipe core directly, for example:\n\n<PswpCodePreview>\n\n```js pswpcode\nimport PhotoSwipe from '/photoswipe/photoswipe.esm.js';\n\nconst options = {\n  dataSource: [\n\n    // simple image\n    {\n      src: 'https://dummyimage.com/1620x1080/555/fff/?text=1620x1080',\n      width: 1620,\n      height: 1080,\n      alt: 'test image 1'\n    },\n\n    {\n      src: 'https://dummyimage.com/1950x1300/555/fff/?text=1950x1300',\n      width: 1950,\n      height: 1300,\n      alt: 'test image 2'\n    },\n\n    // responsive image\n    {\n      srcset: 'https://dummyimage.com/1500x1000/555/fff/?text=1500x1000 1500w, https://dummyimage.com/1200x800/555/fff/?text=1200x800 1200w, https://dummyimage.com/600x400/555/fff/?text=600x400 600w',\n      src: 'https://dummyimage.com/1500x1000/555/fff/?text=1500x1000',\n      width: 1500,\n      height: 1000,\n      alt: 'test image 3',\n    },\n\n    // HTML slide\n    {\n      html: '<div class=\"custom-html-slide\">This is custom HTML slide. <a href=\"http://example.com\" target=\"_blank\" rel=\"nofollow\">Test Link</a>.</div>'\n    }\n\n  ],\n  showHideAnimationType: 'none'\n};\n\ndocument.querySelector('#btn-open-pswp-from-arr').onclick = () => {\n  options.index = 0; // defines start slide index\n  const pswp = new PhotoSwipe(options);\n  pswp.init(); // initializing PhotoSwipe core adds it to DOM\n};\n```\n\n```css pswpcode\n.custom-html-slide {\n  font-size: 40px;\n  line-height: 45px;\n  max-width: 400px;\n  width: 100%;\n  padding: 0 20px;\n  margin: 50px auto 0;\n  color: #fff;\n}\n.custom-html-slide a {\n  color: #fff;\n  text-decoration: underline;\n}\n```\n\n```html pswpcode\n<button id=\"btn-open-pswp-from-arr\" type=\"button\">Open PhotoSwipe</button>\n```\n\n</PswpCodePreview>\n\n\n### With Lightbox module\n\nThis example is identical to the previous one, but using Lightbox and dynamically loading the core.\n\n<PswpCodePreview>\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\nconst options = {\n  dataSource: [\n\n    // simple image\n    {\n      src: 'https://dummyimage.com/1620x1080/555/fff/?text=1620x1080',\n      width: 1620,\n      height: 1080,\n      alt: 'test image 1'\n    },\n\n    {\n      src: 'https://dummyimage.com/1950x1300/555/fff/?text=1950x1300',\n      width: 1950,\n      height: 1300,\n      alt: 'test image 2'\n    },\n\n    // responsive image\n    {\n      srcset: 'https://dummyimage.com/1500x1000/555/fff/?text=1500x1000 1500w, https://dummyimage.com/1200x800/555/fff/?text=1200x800 1200w, https://dummyimage.com/600x400/555/fff/?text=600x400 600w',\n      src: 'https://dummyimage.com/1500x1000/555/fff/?text=1500x1000',\n      width: 1500,\n      height: 1000,\n      alt: 'test image 3',\n    },\n\n    // HTML slide\n    {\n      html: '<div class=\"custom-html-slide\">This is custom HTML slide. <a href=\"http://example.com\" target=\"_blank\" rel=\"nofollow\">Test Link</a>.</div>'\n    }\n\n  ],\n  showHideAnimationType: 'none',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n};\nconst lightbox = new PhotoSwipeLightbox(options);\nlightbox.init();\n\ndocument.querySelector('#btn-open-pswp-from-arr-lightbox').onclick = () => {\n  lightbox.loadAndOpen(0); // defines start slide index\n};\n```\n\n```html pswpcode\n<button id=\"btn-open-pswp-from-arr-lightbox\" type=\"button\">Open PhotoSwipe</button>\n```\n\n</PswpCodePreview>\n\n\n## Custom last slide\n\nTo add a custom last slide increase the total number of slides by one using `numItems` filter and make sure that correct `itemData` is returned for the last slide.\n\n<PswpCodePreview galleryID=\"custom-last-slide\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--custom-last-slide',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n\n});\nlightbox.addFilter('numItems', (numItems) => {\n  return ++numItems;\n});\nlightbox.addFilter('itemData', (itemData, index) => {\n  if (index === lightbox.getNumItems() - 1) {\n    return {\n      html: '<div class=\"custom-html-slide\">This is custom HTML slide. <a href=\"http://example.com\" target=\"_blank\" rel=\"nofollow\">Link</a>.</div>',\n    };\n  }\n  return itemData;\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n## Dynamically generated data\n\nThe filter `numItems` allows you to override the total number of slides. And `itemData` will trigger every time PhotoSwipe requests data about the slide, which usually happens before slide is displayed or lazy-loaded.\n\nThe example below creates a gallery with 1000 images.\n\n<PswpCodePreview>\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  showHideAnimationType: 'none',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  preload: [1,2],\n});\nlightbox.addFilter('numItems', (numItems) => {\n  return 1000;\n});\nlightbox.addFilter('itemData', (itemData, index) => {\n  return {\n    src: 'https://dummyimage.com/100x100/555/fff/?text=' + (index + 1),\n    width: 100,\n    height: 100\n  };;\n});\nlightbox.init();\n\ndocument.querySelector('#btn-open-pswp-dyn-gen').onclick = () => {\n  lightbox.loadAndOpen(0);\n};\n```\n\n```html pswpcode\n<button id=\"btn-open-pswp-dyn-gen\" type=\"button\">Open PhotoSwipe</button>\n```\n\n</PswpCodePreview>\n\n\n## Custom HTML markup\n\nYou may completely override default requirements for HTML markup. In the example below we add thumbnail as `background-image`, a custom attribute that stores image size, and make sure that zoom transition runs from `<a>` rather than from `<img>` within.\n\nWe also use `domItemData` filter, instead of `itemData`. It fires only once per each slide.\n\nimport { customHTMLDataSourceTemplate } from '@site/src/components/PswpCodePreview/gallery-templates/custom-html-markup-data-source.js';\n\n<PswpCodePreview galleryID=\"custom-html-markup\" numItems=\"6\" displayHTML templateFn={customHTMLDataSourceTemplate}>\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--custom-html-markup',\n  children: 'a',\n\n  // Adjust thumbnail selector,\n  // (for opening/closing zoom transition)\n  thumbSelector: 'a',\n\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\n\nlightbox.addFilter('domItemData', (itemData, element, linkEl) => {\n  if (linkEl) {\n    const sizeAttr = linkEl.dataset.mySize;\n\n    itemData.src = linkEl.href;\n    itemData.w = Number(sizeAttr.split('x')[0]);\n    itemData.h = Number(sizeAttr.split('x')[1]);\n    itemData.msrc = linkEl.dataset.thumbSrc;\n    itemData.thumbCropped = true;\n  }\n\n  return itemData;\n});\n\nlightbox.init();\n```\n\n```css pswpcode\n#gallery--custom-html-markup a {\n  width: 100px;\n  height: 100px;\n\n  background-size: cover;\n  background-position: 50% 50%;\n\n  text-indent: -300px;\n  overflowidth: hidden;\n}\n```\n\n</PswpCodePreview>\n\n\n## Separate DOM and data\n\nIf data is provided as array, but you still want to keep zoom animation. This is often the case when using some kind of dynamic image grid with paging or infinite scroll.\n\nHow-to:\n\n1. Pass array of images to `dataSource` option.\n2. Use `thumbEl` filter to provide source of thumbnail element. PhotoSwipe will use its coordinates for animation.\n3. Use `placeholderSrc` filter to provide the source of placeholder image. Alternatively, you may define `msrc` property in your data array.\n4. Bind `click` event to gallery elements and call method `lightbox.loadAndOpen(3)` (where `3` is image index in your array).\n\nIn the example below there are ten images in array, but only three are in DOM.\n\n<PswpCodePreview displayHTML numItems=\"6\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\nconst images = [\n  { id: 1, src: 'https://dummyimage.com/1500x1000/555/fff/?text=Image+1', width: 1500, height: 1000 },\n  { id: 2, src: 'https://dummyimage.com/1500x1000/555/fff/?text=Image+2', width: 1500, height: 1000 },\n  { id: 3, src: 'https://dummyimage.com/1500x1000/555/fff/?text=Image+3', width: 1500, height: 1000 },\n  { id: 4, src: 'https://dummyimage.com/1500x1000/555/fff/?text=Image+4', width: 1500, height: 1000 },\n  { id: 5, src: 'https://dummyimage.com/1500x1000/555/fff/?text=Image+5', width: 1500, height: 1000 },\n  { id: 6, src: 'https://dummyimage.com/1500x1000/555/fff/?text=Image+6', width: 1500, height: 1000 },\n  { id: 7, src: 'https://dummyimage.com/1500x1000/555/fff/?text=Image+7', width: 1500, height: 1000 },\n  { id: 8, src: 'https://dummyimage.com/1500x1000/555/fff/?text=Image+8', width: 1500, height: 1000 },\n  { id: 9, src: 'https://dummyimage.com/1500x1000/555/fff/?text=Image+9', width: 1500, height: 1000 },\n  { id: 10, src: 'https://dummyimage.com/1500x1000/555/fff/?text=Image+10', width: 1500, height: 1000 },\n];\nconst galleryEl = document.querySelector('#gallery--mixed-source');\n\nconst lightbox = new PhotoSwipeLightbox({\n  dataSource: images,\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n});\nlightbox.addFilter('thumbEl', (thumbEl, data, index) => {\n  const el = galleryEl.querySelector('[data-id=\"' + data.id + '\"] img');\n  if (el) {\n    return el;\n  }\n  return thumbEl;\n});\nlightbox.addFilter('placeholderSrc', (placeholderSrc, slide) => {\n  const el = galleryEl.querySelector('[data-id=\"' + slide.data.id + '\"] img');\n  if (el) {\n    return el.src;\n  }\n  return placeholderSrc;\n});\nlightbox.init();\n\n// expose to use within onclick attribute\nwindow.pswpLightbox = lightbox;\n```\n\n```html pswpcode pswpdisplayhtml\n<div class=\"pswp-gallery\" id=\"gallery--mixed-source\">\n  <a onclick=\"pswpLightbox.loadAndOpen(2);return false;\"\n    data-id=\"3\" \n    href=\"https://dummyimage.com/1500x1000/555/fff/?text=Image+3\" \n    target=\"_blank\">\n    <img src=\"https://dummyimage.com/150x100/555/fff/?text=Thumb+3\" alt=\"\">\n  </a>\n  <a onclick=\"pswpLightbox.loadAndOpen(3);return false;\" \n    data-id=\"4\" \n    href=\"https://dummyimage.com/1500x1000/555/fff/?text=Image+4\" \n    target=\"_blank\">\n    <img src=\"https://dummyimage.com/150x100/555/fff/?text=Thumb+4\" alt=\"\">\n  </a>\n  <a onclick=\"pswpLightbox.loadAndOpen(4);return false;\" \n    data-id=\"5\" \n    href=\"https://dummyimage.com/1500x1000/555/fff/?text=Image+5\" \n    target=\"_blank\">\n    <img src=\"https://dummyimage.com/150x100/555/fff/?text=Thumb+5\" alt=\"\">\n  </a>\n</div>\n```\n\n</PswpCodePreview>\n"
  },
  {
    "path": "docs/events.md",
    "content": "---\nid: events\ntitle: Events\nsidebar_label: Events\n---\n\n```js\nconst lightbox = new PhotoSwipeLightbox({\n  // options...\n});\nlightbox.init();\n```  \n\nAll events can be bound directly to lightbox, they'll automatically map to PhotoSwipe core when it's open.\n\n## Initialization events\n\n<PswpCodePreview galleryID=\"test-init-events\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-init-events',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.on('beforeOpen', () => {\n  console.log('beforeOpen');\n  // photoswipe starts to open\n});\n\nlightbox.on('firstUpdate', () => {\n  console.log('firstUpdate');\n  // photoswipe keeps opening\n  // you may modify initial index or basic DOM structure\n});\n\nlightbox.on('initialLayout', () => {\n  console.log('initialLayout');\n  // photoswipe measures size of various elements\n  // if you need to read getBoundingClientRect of something - do it here\n});\n\nlightbox.on('change', () => {\n  // triggers when slide is switched, and at initialization\n  console.log('change');\n});\n\nlightbox.on('afterInit', () => {\n  console.log('afterInit');\n  // photoswipe fully initialized and opening transition is running (if available)\n});\n\nlightbox.on('bindEvents', () => {\n  console.log('bindEvents');\n  // photoswipe binds DOM events (such as pointer events, wheel, etc)\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n## Opening or closing transition events\n\nThe events will trigger even if transition is disabled.\n\n<PswpCodePreview galleryID=\"test-opening-closing-events\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-opening-closing-events',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.on('openingAnimationStart', () => {\n  console.log('openingAnimationStart');\n});\nlightbox.on('openingAnimationEnd', () => {\n  console.log('openingAnimationEnd');\n});\nlightbox.on('closingAnimationStart', () => {\n  console.log('closingAnimationStart');\n});\nlightbox.on('closingAnimationEnd', () => {\n  console.log('closingAnimationEnd');\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n## Closing events\n\n<PswpCodePreview galleryID=\"test-closing-events\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-closing-events',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.on('close', () => {\n  // PhotoSwipe starts to close, unbind most events here\n  console.log('close');\n});\nlightbox.on('destroy', () => {\n  // PhotoSwipe is fully closed, destroy everything\n  console.log('destroy');\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n## Pointer and gesture events\n\n<PswpCodePreview galleryID=\"test-pointer-events\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-pointer-events',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.on('pointerDown', (e) => {\n  console.log('pointerDown', e.originalEvent);\n});\nlightbox.on('pointerMove', (e) => {\n  console.log('pointerMove', e.originalEvent);\n});\nlightbox.on('pointerUp', (e) => {\n  console.log('pointerUp', e.originalEvent);\n});\nlightbox.on('pinchClose', (e) => {\n  // triggered when using pinch to close gesture\n  // can be default prevented\n  console.log('pinchClose', e.bgOpacity);\n});\nlightbox.on('verticalDrag', (e) => {\n  // triggered when using vertical drag to close gesture\n  // can be default prevented\n  console.log('verticalDrag', e.panY);\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n## Slide content events\n\nRefer to [Custom Content](/custom-content) section of docs for examples.\n\n<PswpCodePreview galleryID=\"test-content-events\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nimport PhotoSwipe from '/photoswipe/photoswipe.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-content-events',\n  children: 'a',\n  pswpModule: PhotoSwipe\n});\nlightbox.on('contentInit', ({ content }) => {\n  console.log('contentInit', content);\n});\nlightbox.on('contentLoad', ({ content, isLazy }) => {\n  // content starts to load \n  // can be default prevented\n  // assign elements to `content.element`\n  console.log('contentLoad', content, isLazy);\n});\nlightbox.on('contentLoadImage', ({ content, isLazy }) => {\n  // similar to the previous one, but triggers only for image content\n  // can be default prevented\n  console.log('contentLoadImage', content, isLazy);\n});\nlightbox.on('loadComplete', ({ content, slide }) => {\n  console.log('loadComplete', content);\n});\nlightbox.on('contentResize', ({ content, width, height }) => {\n  // content will be resized\n  // can be default prevented\n  console.log('contentResize', content, width, height);\n});\nlightbox.on('imageSizeChange', ({ content, width, height, slide }) => {\n  // content.element is image\n  console.log('imageSizeChange', content, width, height, slide, slide.index);\n});\nlightbox.on('contentLazyLoad', ({ content }) => {\n  // content start to lazy-load\n  // can be default prevented\n  console.log('contentLazyLoad', content);\n});\nlightbox.on('contentAppend', ({ content }) => {\n  // content is added to dom\n  // can be default prevented\n  // content.slide.container.appendChild(content.element);\n  console.log('contentAppend', content);\n});\nlightbox.on('contentActivate', ({ content }) => {\n  // content becomes active (the current slide)\n  // can be default prevented\n  console.log('contentActivate', content);\n});\nlightbox.on('contentDeactivate', ({ content }) => {\n  // content becomes inactive\n  // can be default prevented\n  console.log('contentDeactivate', content);\n});\nlightbox.on('contentRemove', ({ content }) => {\n  // content is removed from DOM\n  // can be default prevented\n  console.log('contentRemove', content);\n});\nlightbox.on('contentDestroy', ({ content }) => {\n  // content will be destroyed\n  // can be default prevented\n  console.log('contentDestroy', content);\n});\nlightbox.init();\n```\n\n</PswpCodePreview>"
  },
  {
    "path": "docs/filters.md",
    "content": "---\nid: filters\ntitle: Filters\nsidebar_label: Filters\n---\n\nFilters allow to modify data, they expect to have something returned back to them.\n\n### numItems\n\nModify the total amount of slides. Example on [Data sources](/data-sources#custom-last-slide) page.\n\n```js\nlightbox.addFilter('numItems', (numItems, dataSource) => {\n  return numItems;\n});\n```\n\n### itemData\n\nModify slide item data. Example on [Data sources](/data-sources#custom-last-slide) page.\n\n```js\nlightbox.addFilter('itemData', (itemData, index) => {\n  return itemData;\n});\n```\n\n### domItemData\n\nModify item data when it's parsed from DOM element.  Example on [Data sources](/data-sources#custom-html-markup) page.\n\n```js\nlightbox.addFilter('domItemData', (itemData, element, linkEl) => {\n  return itemData;\n});\n```\n\n### clickedIndex\n\nModify clicked gallery item index.\n\n```js\nlightbox.addFilter('clickedIndex', (clickedIndex, e) => {\n  return clickedIndex;\n});\n```\n\n### placeholderSrc\n\nModify placeholder image source.\n\n```js\nlightbox.addFilter('placeholderSrc', (placeholderSrc, content) => {\n  return placeholderSrc;\n});\n```\n\n### isContentLoading\n\nModify if the content is currently loading.\n\n```js\nlightbox.addFilter('isContentLoading', (isContentLoading, content) => {\n  return isContentLoading;\n});\n```\n\n### isContentZoomable\n\nModify if the content can be zoomed.\n\n```js\nlightbox.addFilter('isContentZoomable', (isContentZoomable, content) => {\n  return isContentZoomable;\n});\n```\n\n### useContentPlaceholder\n\nModify if the placeholder should be used for the content.\n\n```js\nlightbox.addFilter('useContentPlaceholder', (useContentPlaceholder, content) => {\n  return useContentPlaceholder;\n});\n```\n\n### isKeepingPlaceholder\n\nModify if the placeholder should be kept after the content is loaded.\n\n```js\nlightbox.addFilter('isKeepingPlaceholder', (isKeepingPlaceholder, content) => {\n  return isKeepingPlaceholder;\n});\n```\n\n### contentErrorElement\n\nModify an element when the content has error state (for example, if image cannot be loaded).\n\n<PswpCodePreview>\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--content-error-element',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.addFilter('contentErrorElement', (contentErrorElement, content) => {\n  const el = document.createElement('div');\n  el.className = 'pswp__error-msg';\n  el.innerHTML = `<a href=\"${content.data.src}\" target=\"_blank\">The image #${content.slide.index + 1}</a> cannot be loaded</a>`;\n  return el;\n});\nlightbox.init();\n```\n\n```html pswpcode \n<div class=\"pswp-gallery pswp-gallery--single-column\" id=\"gallery--content-error-element\">\n  <a data-pswp-width=\"900\" data-pswp-height=\"600\" href=\"https://example.com/broken-image-link\" target=\"_blank\">\n    <img src=\"https://dummyimage.com/300x200/555/fff/?text=404+1st\" alt=\"\">\n  </a>\n  <a data-pswp-width=\"1000\" data-pswp-height=\"1000\" href=\"https://dummyimage.com/1000x1000/555/fff/?text=1000x1000-2nd\" target=\"_blank\">\n    <img src=\"https://dummyimage.com/100x100/555/fff/?text=100x100+2nd\" alt=\"\">\n  </a>\n  <a data-pswp-width=\"1200\" data-pswp-height=\"800\" href=\"https://example.com/another-broken-image-link\" target=\"_blank\">\n    <img src=\"https://dummyimage.com/300x200/555/fff/?text=404+3rd\" alt=\"\">\n  </a>\n</div>\n```\n\n</PswpCodePreview>\n\n\n\n### uiElement\n\nModify a UI element that's being created.\n\n<PswpCodePreview galleryID=\"test-ui-element-filter\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-ui-element-filter',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.addFilter('uiElement', (element, data) => {\n  if (data.name === 'arrowNext') {\n    element.style.background = 'red';\n  }\n  return element;\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n### thumbEl\n\nModify the thubmnail element from which opening zoom animation starts or ends.\n\n```js\nlightbox.addFilter('thumbEl', (thumbnail, itemData, index) => {\n  return thumbnail;\n});\n```\n\n### thumbBounds\n\nModify the thubmnail bounds from which opening zoom animation starts or ends.\n\n```js\nlightbox.addFilter('thumbBounds', (thumbBounds, itemData, index) => {\n  return thumbBounds;\n});\n```\n\n### preventPointerEvent\n\nBy default, PhotoSwipe calls `preventDefault` on `pointermove` event to disable default browser gestures and prevent page from scrolling. This filter gives you control on when it's called. For example, you may adjust it based on `originalEvent.target`.\n\n```js\nlightbox.addFilter('preventPointerEvent', (preventPointerEvent, originalEvent, pointerType) => {\n  // return true to preventDefault pointermove/pointerdown events\n  // (also applies to touchmove/mousemove)\n  return true;\n});\n```"
  },
  {
    "path": "docs/getting-started.md",
    "content": "---\nid: getting-started\ntitle: Getting Started\nsidebar_label: Getting Started\n---\n\n**PhotoSwipe v6 is under development** Please leave feedback in [GitHub Discussion](https://github.com/dimsemenov/PhotoSwipe/discussions/2170).\n\nBefore you start:\n\n- PhotoSwipe requires predefined image dimensions, you must define the width and height of each image.\n- PhotoSwipe is not designed to display very large images. Serve responsive images. Maximum recommended size is 3000x3000px. However, there is an [experimental tiling plugin](https://github.com/dimsemenov/photoswipe-deep-zoom-plugin) that allows showing extremely large images.\n- PhotoSwipe is designed with progressive enhancement in mind and works only in modern browsers. Always provide an alternative way to view the content (for example, link to the image).\n\n\n## Initialization\n\n\n\nThe PhotoSwipe consists of three parts:\n\n1. **Core** (`photoswipe.esm.js`).\n2. **Lightbox** (`photoswipe-lightbox.esm.js`) - loads **Core** and chooses when PhotoSwipe should be opened. Its file size is significantly smaller. It also loads the first image (in parallel with **Core**).\n3. **CSS** (`photoswipe.css`) - a single file that controls all the styling. There are no external assets for icons - all of them are dynamically generated via JS and very tiny. Refer to [styling](/styling) for more info.\n\nJS files are separated, so you can dynamically load **Core** only when the user needs it, thus reducing the size of your main bundle.\n\nThe recommended way to use PhotoSwipe is using a single `<script type=\"module\"></script>`, for example:\n\n```html\n<script type=\"module\">\nimport PhotoSwipeLightbox from 'photoswipe/dist/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#my-gallery',\n  children: 'a',\n  pswpModule: () => import('photoswipe/dist/photoswipe.esm.js')\n});\nlightbox.init();\n</script>\n```\n\nDon't forget to include the CSS too:\n\n```html\n<link rel=\"stylesheet\" href=\"photoswipe/dist/photoswipe.css\">\n```\n\nAlternatively, you may install PhotoSwipe via NPM or Yarn:\n\n```\nnpm i photoswipe --save\n```\n\n```js\nimport PhotoSwipeLightbox from 'photoswipe/lightbox';\nimport 'photoswipe/style.css';\n\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#my-gallery',\n  children: 'a',\n  pswpModule: () => import('photoswipe')\n});\nlightbox.init();\n```\n\nPlaygrounds: [StackBlitz](https://stackblitz.com/edit/js-o1vrod?file=index.js), [CodeSandbox](https://codesandbox.io/s/vigorous-matan-zxok9x?file=/src/index.js), [CodePen (with unpkg)](https://codepen.io/dimsemenov/pen/ZEvypBw).\n\nIf you are unable to use ES modules, the transpiled version can be found in [dist/umd/](https://github.com/dimsemenov/PhotoSwipe/tree/master/dist/umd) folder of the repository.\n\n### Basic vanilla JS example\n\nimport { gettingStartedTemplate } from '@site/src/components/PswpCodePreview/gallery-templates/getting-started.js';\n\n<PswpCodePreview galleryID=\"getting-started\" numItems=\"6\" displayHTML templateFn={gettingStartedTemplate}>\n\n```js pswpcode\n// Include Lightbox\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\nconst lightbox = new PhotoSwipeLightbox({\n  // may select multiple \"galleries\"\n  gallery: '#gallery--getting-started',\n\n  // Elements within gallery (slides)\n  children: 'a',\n\n  // setup PhotoSwipe Core dynamic import\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n### Without dynamic import\n\nYou don't have to dynamically import pswpModule, especially if PhotoSwipe is one of the primary features of your page.\n\n<PswpCodePreview galleryID=\"no-dynamic-import\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n// highlight-next-line\nimport PhotoSwipe from '/photoswipe/photoswipe.esm.js';\n\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--no-dynamic-import',\n  children: 'a',\n  // highlight-next-line\n  pswpModule: PhotoSwipe\n});\n\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n\n## Required HTML markup\n\nEach element that matches the selector should be or should contain link `<a>` element. The link must have such attributes:\n\n  - Image URL in `href` or `data-pswp-src` attribute (latter has higher priority).\n  - Image width in `data-pswp-width`.\n  - Image height in `data-pswp-height`.\n\nAnd optionally:\n\n- `<img>` thumbnail within the link element that will be displayed before the large image is loaded (applied only for the first image, can be adjusted via `thumbSelector`).\n- Optional `data-cropped=\"true\"` attribute if thumbnail is cropped. See also [Animating from Cropped Thumbnail](/opening-or-closing-transition#animating-from-cropped-thumbnail).\n\nPhotoSwipe API supports almost any markup and any data source, [read more about it here](/data-sources#custom-html-markup).\n\n## Open each image individually\n\n<PswpCodePreview  galleryID=\"individual\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst options = {\n  // highlight-next-line\n  gallery: '#gallery--individual a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n};\nconst lightbox = new PhotoSwipeLightbox(options);\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n## Responsive images with srcset\n\n- Add `data-pswp-srcset` attribute. It supports the same markup as [native srcset](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-srcset).\n- Attributes `data-pswp-width` and `data-pswp-height` should define the largest image size.\n- `sizes` attribute will be generated automatically based on actual width of the image. For example, if user zooms-in - `sizes` will be adjusted to the new zoom level.\n\n\nimport { srcsetTemplate } from '@site/src/components/PswpCodePreview/gallery-templates/srcset-test.js';\n\n<PswpCodePreview galleryID=\"responsive-images\" templateFn={srcsetTemplate}>\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst options = {\n  gallery: '#gallery--responsive-images',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n};\nconst lightbox = new PhotoSwipeLightbox(options);\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n## Supported browsers and fallback\n\n- The PhotoSwipe supports all browsers that [support ES6 modules](https://caniuse.com/#search=module).\n  - Thus, it might not work in IE11, Opera Mini, UC browser, and old versions of Chrome, Safari, and Firefox. Check your website/region statistics before deciding whether you should use PhotoSwipe or not.\n- Users in unsupported browsers will still be able to view the large image if you use recommended HTML markup - link to image, or link to page that contains image.\n- You may add any fallback via `script type=\"nomodule\"`.\n"
  },
  {
    "path": "docs/methods.md",
    "content": "---\nid: methods\ntitle: Methods\nsidebar_label: Methods\n---\n\n## PhotoSwipeLightbox methods\n\n### `init()`\n\nInitialize  the lightbox, must be called once for each instance. It does not open the dialog, only binds events that would open it (by default just `click`). It also allows preloading images before the dialog is opened.\n\n```js\nconst lightbox = new PhotoSwipeLightbox({\n  // options\n});\n\n/* you may bind events here */\n\n// highlight-next-line\nlightbox.init();\n```\n\n\n### `destroy()`\n\nUnbinds all events, and closes PhotoSwipe if it is currently open. Once the instance is destroyed, it cannot be initialized again.\n\n\n<PswpCodePreview galleryID=\"test-destroy\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nlet lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-destroy',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  returnFocus: false\n});\nlightbox.init();\n\nconst button = document.createElement('button');\nbutton.type = 'button';\nbutton.innerText = 'destroy the photoswipe';\ndocument.querySelector('#gallery--test-destroy').after(button);\nbutton.onclick = () => {\n  // highlight-start\n  if (lightbox) {\n    lightbox.destroy();\n    lightbox = null;\n  }\n  // highlight-end\n}\n```\n\n</PswpCodePreview>\n\n\n## `loadAndOpen(index, dataSource, point)`\n\nOpen PhotoSwipe at a given index. Arguments:\n\n1. Index of a slide to open (`number`)\n2. Optional data source (`DataSource`).\n3. Optional `Point` (`x: number, y: number`) that determines where exactly the user clicked on the thumbnail (by default it is `{ x: e.clientX, y: e.clientY }` of the corresponding `click` event).\n\nIf you use `gallery` & `children` options and you omit the second argument (do not provide data source) - PhotoSwipe will use the first `gallery` element. To select another `gallery` element you must define data source as `{ gallery: HTMLElement }`, for example:\n\n\n<PswpCodePreview galleryID=\"with-button\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--with-button',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.init();\n\nconst btn = document.createElement('button');\nbtn.type = 'button';\nbtn.innerText = 'open second image';\ndocument.querySelector('#gallery--with-button').after(btn);\n\nbtn.onclick = () => {\n  // highlight-start\n  lightbox.loadAndOpen(1, {\n    gallery: document.querySelector('#gallery--with-button')\n  });\n\n  // You also can just trigger the native click\n  // document.querySelector('#gallery--with-button a:nth-of-type(2)').click();\n  // highlight-end\n};\n\n\n```\n\n</PswpCodePreview>\n\n## PhotoSwipe core methods\n\nYou can access the PhotoSwipe core instance only after the lightbox is opened (for example, within the `beforeOpen` event).\n\nAlternatively, you may use PhotoSwipe core without Lightbox, see example on [Data Sources page](/data-sources#without-lightbox-module).\n\n```js\nconst lightbox = new PhotoSwipeLightbox({\n  // options...\n});\nlightbox.init();\n\nlightbox.on('beforeOpen', () => {\n  const pswp = lightbox.pswp;\n\n  // go to slide by index\n  pswp.goTo(3);\n\n  // go to next slide\n  pswp.next();\n\n  // go to previous slide\n  pswp.prev();\n\n  // close the PhotoSwipe (with animation, if enabled)\n  // PhotoSwipe will automatically destroy after it's closed\n  pswp.close();\n\n  // instantly close and destroy the PhotoSwipe\n  pswp.destroy();\n\n  // zoom slide to\n  pswp.currSlide.zoomTo(\n    1, // slide zoom level, 1 - original image size\n    { x: 0, y: 0 }, // zoom center point\n    2000, // transition duration, can be 0\n    false // wether pan/zoom bounds should be ignored\n  );\n\n  // pan slide to\n  pswp.currSlide.panTo(\n    100, // x position\n    100, // y position\n  );\n});\n```\n\n\n\n## Dynamically adding or removing slides\n\nPhotoSwipe parses and renders only nearby slides based on `preload` option (but not less than 2 nearby). \n\nThe data about slides is stored within `pswp.options.dataSource` and its structure depends on the source. If you initialize PhotoSwipe from DOM elements (via `gallery` and `children` options) the `dataSource` will be:\n\n```\npswp.options.dataSource = {\n  gallery: <Gallery element>\n  items: [\n    <Child element>,\n    <Child element>,\n    <Child element>,\n    // etc.\n  ]\n}\n```\n\nAnd if you pass array as a `dataSource`, it'll be kept the same way:\n\n```js\npswp.options.dataSource = [\n  { src: 'image1.jpg', width: 100, height: 50 },\n  { src: 'image2.jpg', width: 100, height: 50 },\n  { src: 'image3.jpg', width: 100, height: 50 }\n]\n```\n\nYou may modify `pswp.options.dataSource` array however you wish - push new items, replace, sort, pop, shift, etc. \n\nIf you modified slides that are currently active (current, next or previous) - call method `refreshSlideContent(slideIndex)` to reload a slide by index. For example:\n\n<PswpCodePreview>\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  dataSource: [\n    { src: 'https://dummyimage.com/800x600/555/fff/?text=1', width: 800, height: 600 },\n    { src: 'https://dummyimage.com/800x600/555/fff/?text=2', width: 800, height: 600 },\n    { src: 'https://dummyimage.com/800x600/555/fff/?text=3', width: 800, height: 600 },\n    { src: 'https://dummyimage.com/800x600/555/fff/?text=4', width: 800, height: 600 },\n    { src: 'https://dummyimage.com/800x600/555/fff/?text=5', width: 800, height: 600 },\n  ],\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.on('uiRegister', () => {\n  const { pswp }  = lightbox;\n\n  let replacedCount = 0;\n  pswp.ui.registerElement({\n    name: 'replaceCurrentSlide',\n    className: 'pswp__button--test-button',\n    order: 9,\n    isButton: true,\n    html: 'Replace Current Slide',\n    onClick: (event, el) => {\n      replacedCount++;\n      pswp.options.dataSource[pswp.currSlide.index] = {\n        src: 'https://dummyimage.com/800x600/555/fff/?text=New%20Slide%20' + replacedCount, width: 800, \n        height: 600\n      };\n      pswp.refreshSlideContent(pswp.currSlide.index);\n    }\n  });\n\n  let addedCount = 0;\n  pswp.ui.registerElement({\n    name: 'addSlide',\n    className: 'pswp__button--test-button',\n    order: 9,\n    isButton: true,\n    html: 'Add Slide',\n    onClick: (event, el) => {\n      addedCount++;\n      pswp.options.dataSource.push({\n        src: 'https://dummyimage.com/800x600/555/fff/?text=Added%20slide%20' + addedCount, width: 800, \n        height: 600\n      });\n      pswp.refreshSlideContent(pswp.getNumItems() - 1);\n    }\n  });\n});\nlightbox.init();\n\ndocument.querySelector('#btn-add-remove-test').onclick = () => {\n  lightbox.loadAndOpen(0);\n};\n```\n\n```html pswpcode\n<button id=\"btn-add-remove-test\" type=\"button\">Open PhotoSwipe</button>\n```\n\n```css pswpcode\nbutton.pswp__button--test-button {\n  background: #136912 !important;\n  font-size: 20px;\n  color: #fff;\n}\n```\n\n</PswpCodePreview>\n\nAlternatively, you may omit `dataSource` option entirely and use filters to supply data and number of items, example on the [Data Sources page](/data-sources#dynamically-generated-data).\n\n## UI\n\nRefer to [Styling](/styling) page on how to adjust the UI (add buttons, modify icons, etc).\n"
  },
  {
    "path": "docs/native-fullscreen-on-open.md",
    "content": "---\nid: native-fullscreen-on-open\ntitle: Trigger native fullscreen when lightbox opens\nsidebar_label: Native fullscreen on open\n---\n\nThere is [an unoficial fullscreen plugin by @arnowelzel](https://github.com/arnowelzel/photoswipe-fullscreen) that might be useful.\n\nYou may use asynchronous initialization to open gallery in fullscreen mode right away. Fullscreen API is [supported in all major browsers](https://caniuse.com/#feat=fullscreen), except mobile Safari. Fullscreen in mobile Chrome on Android works exceptionally well in landscape orientation as it removes toolbars.\n\nTo implement it, create a promise that is resolved after fullscreen mode is entered and pass it to lightbox as an `openPromise` option.\n\n<PswpCodePreview>\n\n```js pswpcode  \nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\n// Simple fullscreen API\nconst fullscreenAPI = getFullscreenAPI();\n\n// Create custom container\n// which will be stretched to fullscreen.\n//\n// (we can not use PhotoSwipe root element (.pswp),\n//  as it is created only after openPromise is resolved)\n//\nconst pswpContainer = getContainer();\n\nfunction getFullscreenPromise() {\n  // Always resolve promise,\n  // as wa want to open lightbox \n  // (no matter if fullscreen is supported or not)\n  return new Promise((resolve) => {\n    if (!fullscreenAPI || fullscreenAPI.isFullscreen()) {\n      // fullscreen API not supported, or already fullscreen\n      resolve();\n      return;\n    }\n\n    document.addEventListener(fullscreenAPI.change, (event) => {\n      pswpContainer.style.display = 'block';\n      // delay to make sure that browser fullscreen animation is finished\n      setTimeout(function() {\n        resolve();\n      }, 300);\n    }, { once: true });\n\n    fullscreenAPI.request(pswpContainer);\n  });\n}\n\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--native-fs',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n\n  // Add function that returns promise\n  openPromise: getFullscreenPromise,\n  \n  // Append PhotoSwipe to our container\n  appendToEl: fullscreenAPI ? pswpContainer : document.body,\n\n  // disable opening/closing animations\n  showAnimationDuration: 0,\n  hideAnimationDuration: 0,\n\n  // Add if you're using responsive images\n  // since viewport size is unpredictable\n  // at initialization\n  preloadFirstSlide: false\n});\nlightbox.on('close', () => {\n  pswpContainer.style.display = 'none';\n  if (fullscreenAPI && fullscreenAPI.isFullscreen()) {\n    fullscreenAPI.exit();\n  }\n});\nlightbox.init();\n\n// Simple fullscreen API helper,\n// supports unprefixed and webkit-prefixed versions\nfunction getFullscreenAPI() {\n  let api;\n  let enterFS;\n  let exitFS;\n  let elementFS;\n  let changeEvent;\n  let errorEvent;\n\n  if (document.documentElement.requestFullscreen) {\n    enterFS = 'requestFullscreen';\n    exitFS = 'exitFullscreen';\n    elementFS = 'fullscreenElement';\n    changeEvent = 'fullscreenchange';\n    errorEvent = 'fullscreenerror';\n  } else if (document.documentElement.webkitRequestFullscreen) {\n    enterFS = 'webkitRequestFullscreen';\n    exitFS = 'webkitExitFullscreen';\n    elementFS = 'webkitFullscreenElement';\n    changeEvent = 'webkitfullscreenchange';\n    errorEvent = 'webkitfullscreenerror';\n  }\n\n  if (enterFS) {\n    api = {\n      request: function (el) {\n        if (enterFS === 'webkitRequestFullscreen') {\n          el[enterFS](Element.ALLOW_KEYBOARD_INPUT);\n        } else {\n          el[enterFS]();\n        }\n      },\n\n      exit: function () {\n        return document[exitFS]();\n      },\n\n      isFullscreen: function () {\n        return document[elementFS];\n      },\n\n      change: changeEvent,\n      error: errorEvent\n    };\n  }\n\n  return api;\n};\n\nfunction getContainer() {\n  const pswpContainer = document.createElement('div');\n  pswpContainer.style.background = '#000';\n  pswpContainer.style.width = '100%';\n  pswpContainer.style.height = '100%';\n  pswpContainer.style.display = 'none';\n  document.body.appendChild(pswpContainer);\n  return pswpContainer;\n}\n```\n\n```html pswpcode\n<div class=\"pswp-gallery\" id=\"gallery--native-fs\">\n<a href=\"https://dummyimage.com/1500x1000/555/fff/?text=1-1500x1000\" \n    data-pswp-srcset=\"\n      https://dummyimage.com/1500x1000/555/fff/?text=1-1500x1000 1500w,\n      https://dummyimage.com/1200x800/555/fff/?text=1-1200x800 1200w,\n      https://dummyimage.com/600x400/555/fff/?text=1-600x400 600w,\n      https://dummyimage.com/300x200/555/fff/?text=1-300x200 300w\" \n    data-pswp-width=\"1500\" \n    data-pswp-height=\"1000\"\n    target=\"_blank\">\n    <img src=\"https://dummyimage.com/300x200/555/fff/?text=1\" alt=\"\">\n  </a>\n\n  <a href=\"https://dummyimage.com/1620x1080/555/fff/?text=2-1620x1080\" data-pswp-width=\"1620\" data-pswp-height=\"1080\" target=\"_blank\">\n    <img src=\"https://dummyimage.com/162x108/555/fff/?text=2\" alt=\"\" />\n  </a>\n  <a href=\"https://dummyimage.com/1600x900/555/fff/?text=3-1600x900\" data-pswp-width=\"1600\" data-pswp-height=\"900\"  target=\"_blank\">\n    <img src=\"https://dummyimage.com/160x90/555/fff/?text=3\" alt=\"\" />\n  </a>\n  <a href=\"https://dummyimage.com/1950x1300/555/fff/?text=4-1950x1300\" data-pswp-width=\"1950\" data-pswp-height=\"1300\"target=\"_blank\">\n    <img src=\"https://dummyimage.com/195x130/555/fff/?text=4\" alt=\"\" />\n  </a>\n</div>\n```\n\n</PswpCodePreview>\n\n"
  },
  {
    "path": "docs/opening-or-closing-transition.md",
    "content": "---\nid: opening-or-closing-transition\ntitle: Opening or closing transition\nsidebar_label: Opening or closing transition\n---\n\nTo adjust opening or closing transition type use lightbox option `showHideAnimationType` (`String`). It supports three values - `zoom` (default), `fade` (default if there is no thumbnail) and `none`.\n\nAnimations are automatically disabled if user `(prefers-reduced-motion: reduce)`.\n\n\n## zoom\n\n<PswpCodePreview galleryID=\"zoom-transition\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--zoom-transition',\n  children: 'a',\n  showHideAnimationType: 'zoom',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\nIf you're using a different datasource, for example Array of images and you still want to use zoom transition, please refer to [Separate DOM and data](/data-sources#separate-dom-and-data) guide.\n\n## fade\n\n<PswpCodePreview galleryID=\"fade-transition\" numItems=\"5\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery:'#gallery--fade-transition',\n  children:'a',\n  showHideAnimationType: 'fade',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n## none\n\nAutomatically selected if user agent `(prefers-reduced-motion: reduce)`.\n\n<PswpCodePreview galleryID=\"none-transition\" numItems=\"8\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--none-transition',\n  children: 'a',\n  showHideAnimationType: 'none',\n\n  // optionally disable zoom transition,\n  // to create more consistent experience\n  zoomAnimationDuration: false,\n\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n## Transition duration and easing\n\nUse options `showAnimationDuration` and `hideAnimationDuration` (`Integer`, default `333`).\n\nOption `easing` (`String`, default `cubic-bezier(.4,0,.22,1)`) accepts any [CSS timing-function](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function). It is applied to any zoom transition (including double-tap).\n\nBoth options can be modified dynamically while PhotoSwipe is opened.\n\nIn the example below transition duration is set to 1000ms (1s). Easing is defined dynamically (opening transition gets ease-out-back, zoom transitions gets ease-in-out-back, and closing transition gets ease-in-back):\n\n<PswpCodePreview galleryID=\"customized-transition\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\nconst backEasing = {\n  in: 'cubic-bezier(0.6, -0.28, 0.7, 1)',\n  out: 'cubic-bezier(0.3, 0, 0.32, 1.275)',\n  inOut: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)'\n};\n\nconst lightbox = new PhotoSwipeLightbox({\n  gallery:'#gallery--customized-transition',\n  children:'a',\n\n  showHideAnimationType: 'zoom',\n  showAnimationDuration: 1000,\n  hideAnimationDuration: 1000,\n\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.on('firstUpdate', () => {\n  lightbox.pswp.options.easing = backEasing.out;\n});\nlightbox.on('initialZoomInEnd', () => {\n  lightbox.pswp.options.easing = backEasing.inOut;\n});\nlightbox.on('close', () => {\n  lightbox.pswp.options.easing = backEasing.in;\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n## Animating from cropped thumbnail\n\n1. Use thumbnail image that matches aspect ratio of the large image. Animation from thumbnails that are cropped on server side is not supported.\n2. Crop thumbnail via CSS using `object-fit:cover`. If you're using a different `object-position` - use [Object Position plugin](https://github.com/vovayatsyuk/photoswipe-object-position) by Vova Yatsyuk, or adjust it manually via `thumbBounds` filter.\n3. Add `data-cropped=\"true\"` attribute to links that open PhotoSwipe.\n\nimport { basicCroppedTemplate } from '@site/src/components/PswpCodePreview/gallery-templates/basic--cropped.js';\n\n<PswpCodePreview galleryID=\"cropped-thumbs\" numItems=\"6\" templateFn={basicCroppedTemplate}>\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery:'#gallery--cropped-thumbs',\n  children:'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n## Hiding elements that overlap thumbnails\n\nIf you have some element that overlays thumbnail, you may want to fade it out when PhotoSwipe is opening, and fade it back in when PhotoSwipe is closed.\n\n\nimport { basicBadgesTemplate } from '@site/src/components/PswpCodePreview/gallery-templates/basic--badges.js';\n\n<PswpCodePreview galleryID=\"badges\" numItems=\"6\" templateFn={basicBadgesTemplate}>\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery:'#gallery--badges',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\n\nlet firstElWithBadge;\nlet lastElWithBadge;\n\n// Gallery is starting to open\nlightbox.on('afterInit', () => {\n  firstElWithBadge = lightbox.pswp.currSlide.data.element;\n  hideBadge(firstElWithBadge);\n});\n\n// Gallery is starting to close\nlightbox.on('close', () => {\n  lastElWithBadge = lightbox.pswp.currSlide.data.element;\n  if(lastElWithBadge !== firstElWithBadge) {\n    showBadge(firstElWithBadge);\n    hideBadge(lastElWithBadge);\n  }\n});\n\n// Gallery is closed\nlightbox.on('destroy', () => {\n    showBadge(lastElWithBadge);\n});\n\nlightbox.init();\n\nfunction hideBadge(el) {\n  el.querySelector('.badge')\n    .classList\n    .add('badge--hidden');\n};\nfunction showBadge(el) {\n  el.querySelector('.badge')\n    .classList\n    .remove('badge--hidden');\n}\n```\n\n```css pswpcode\n.badge {\n  position: absolute;\n  bottom: 5px;\n  left: 5px;\n  padding: 5px;\n  color: #FFF;\n  background: rgba(0,0,0,0.5);\n  line-height: 1;\n  transition: opacity 100ms linear;\n  font-size: 16px;\n  font-weight: normal;\n}\n.badge--hidden {\n  opacity: 0;\n}\n```\n\n</PswpCodePreview>\n\nYou may use this technique to hide any element that might interfere with the transition. For example, fixed header, sharing icons or carousel arrows.\n"
  },
  {
    "path": "docs/options.md",
    "content": "---\nid: options\ntitle: Options\nsidebar_label: Options\n---\n\n### bgOpacity\n\nNumber, `0.8`. \n\nBackground backdrop opacity, always define it via this option and not via CSS rgba color.\n\n<PswpCodePreview galleryID=\"test-bgopacity\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-bgopacity',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  bgOpacity: 0.2,\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n### spacing\n\nNumber, `0.1`. Spacing between slides. Defined as ratio relative to the viewport width (0.1 = 10% of viewport).\n\n<PswpCodePreview galleryID=\"test-spacing\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-spacing',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  spacing: 0.5, // 50% of viewport width\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n### allowPanToNext\n\nBoolean, `true`. Allow swipe navigation to the next slide when the current slide is zoomed. Does not apply to mouse events.\n\n<PswpCodePreview galleryID=\"test-spacing\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-spacing',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  allowPanToNext: false\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n### loop\n\nBoolean, `true`. If set to true you'll be able to swipe from the last to the first image. Option is always `false` when there are less than 3 slides.\n\n<PswpCodePreview galleryID=\"test-loop\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-loop',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  loop: false\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n\n### wheelToZoom\n\nBoolean, `undefined`. By default PhotoSwipe zooms image with ctrl-wheel, if you enable this option - image will zoom just via wheel.\n\n<PswpCodePreview galleryID=\"test-wheelToZoom\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-wheelToZoom',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  wheelToZoom: true\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n### pinchToClose\n\nBoolean, `true`. Pinch touch gesture to close the gallery.\n\n<PswpCodePreview galleryID=\"test-pinch-to-close\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-pinch-to-close',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  pinchToClose: false\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n### closeOnVerticalDrag\n\nBoolean, `true`. Vertical drag gesture to close the PhotoSwipe.\n\n<PswpCodePreview galleryID=\"close-on-vertical-drag\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--close-on-vertical-drag',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  closeOnVerticalDrag: false\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n### padding\n  \nObject, `{ top: 0, bottom: 0, left: 0, right: 0 }`. Slide area padding (in pixels).\n\n<PswpCodePreview galleryID=\"test-padding\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-padding',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  padding: { top: 20, bottom: 40, left: 100, right: 100 }\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n### paddingFn\n\nFunction, should return padding object. The option is checked frequently, so make sure it's performant. Overrides `padding` option if defined. For example:\n\n\n<PswpCodePreview galleryID=\"test-paddingfn\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-paddingfn',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n\n  paddingFn: (viewportSize, itemData, index) => {\n    return {\n      // check based on slide index\n      top: index === 0 ? 100 : 0,\n\n      // check based on viewport size\n      bottom: viewportSize.x < 600 ? 0 : 200,\n\n      // check based on image size\n      left: itemData.w < 2000 ? 50 : 0,\n\n      right: 0\n    };\n  }\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n### hideAnimationDuration, showAnimationDuration, zoomAnimationDuration\n\nNumber, `333`. Transition duration in milliseconds, can be `0`. [Example here](opening-or-closing-transition.md#transition-duration-and-easing).\n\n### easing\n\nString, `'cubic-bezier(.4,0,.22,1)'`. CSS easing function for open/close/zoom transitions. [Example here](opening-or-closing-transition.md#transition-duration-and-easing).\n\n\n### escKey\n\nBoolean, `true`. Esc key to close.\n\n<PswpCodePreview galleryID=\"test-esc-key\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-esc-key',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  escKey: false\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n### arrowKeys\n\nBoolean, `true`. Left/right arrow keys for navigation.\n\n<PswpCodePreview galleryID=\"test-arrow-keys\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-arrow-keys',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  arrowKeys: false\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n### trapFocus\n\nBoolean, `true`. Trap focus within PhotoSwipe element while it's open. \n\n\n### returnFocus\n\nBoolean, `true`. Restore focus the last active element after PhotoSwipe is closed. \n\n<PswpCodePreview galleryID=\"test-restore-focus\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-restore-focus',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  returnFocus: false\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n### clickToCloseNonZoomable\n\nBoolean, `true`. If image is not zoomable (for example, smaller than viewport) it can be closed by clicking on it.\n\n<PswpCodePreview>\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--clickToCloseNonZoomable',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  clickToCloseNonZoomable: false\n});\nlightbox.init();\n```\n\n```html pswpcode \n<div class=\"pswp-gallery\" id=\"gallery--clickToCloseNonZoomable\">\n  <a data-pswp-width=\"300\" data-pswp-height=\"200\" href=\"https://dummyimage.com/300x200/555/fff/?text=1st-300x200\" target=\"_blank\">\n    <img src=\"https://dummyimage.com/150x100/555/fff/?text=1st-150x100\" alt=\"\">\n  </a>\n  <a data-pswp-width=\"1000\" data-pswp-height=\"1000\" href=\"https://dummyimage.com/1000x1000/555/fff/?text=2nd-1000x1000\" target=\"_blank\">\n    <img src=\"https://dummyimage.com/100x100/555/fff/?text=2nd-100x100\" alt=\"\">\n  </a>\n  <a data-pswp-width=\"100\" data-pswp-height=\"700\" href=\"https://dummyimage.com/100x700/555/fff/?text=3rd-100x700\" target=\"_blank\">\n    <img src=\"https://dummyimage.com/50x350/555/fff/?text=3rd-50x350\" alt=\"\">\n  </a>\n</div>\n```\n\n</PswpCodePreview>\n\n### imageClickAction, bgClickAction, tapAction, doubleTapAction\n\nRefer to [click and tap actions](click-and-tap-actions.md) page.\n\n### preloaderDelay\n\nNumber (ms), `2000`. Delay before the loading indicator will be displayed, if image is loaded during it - the indicator will not be displayed at all. Can be zero.\n\n<PswpCodePreview galleryID=\"test-preloader-delay\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-preloader-delay',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  preloaderDelay: 0\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n### indexIndicatorSep\n\nString, ` / `. Used for slide count indicator (\"1 of 10 \").\n\n<PswpCodePreview galleryID=\"test-indexIndicatorSep\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-indexIndicatorSep',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  indexIndicatorSep: ' of '\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n### getViewportSizeFn\n\nFunction `{x: width, y: height}`, `undefined`. A function that should return slide viewport width and height, in format `{x: 100, y: 100}`.\n\n<PswpCodePreview galleryID=\"test-getViewportSizeFn\" numItems=\"4\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-getViewportSizeFn',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  getViewportSizeFn: function(options, pswp) {\n    return {\n      x: document.documentElement.clientWidth - 200,\n      y: window.innerHeight\n    };\n  }\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n### initialZoomLevel, secondaryZoomLevel, maxZoomLevel\n\nRefer to [Adjusting zoom level](adjusting-zoom-level.md) page for more info. The default values are described [there too](adjusting-zoom-level.md#default-behaviour).\n\n\n### errorMsg\n\nString, `'The image cannot be loaded'`.\n\nMessage to display when the image wasn't able to load. If you need to display HTML - use [contentErrorElement filter](/filters#contenterrorelement).\n\n<PswpCodePreview>\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--errorMsg',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  errorMsg: 'The photo cannot be loaded'\n});\nlightbox.init();\n```\n\n```html pswpcode \n<div class=\"pswp-gallery pswp-gallery--single-column\" id=\"gallery--errorMsg\">\n  <a data-pswp-width=\"1200\" data-pswp-height=\"800\" href=\"https://picsum.photos/1200/800\" target=\"_blank\">\n    <img src=\"https://dummyimage.com/300x200/555/fff/?text=0-random-image\" alt=\"\">\n  </a>\n\n  <a data-pswp-width=\"800\" data-pswp-height=\"600\" href=\"https://example.com/broken-image-link\" target=\"_blank\">\n    <img src=\"https://dummyimage.com/300x200/555/fff/?text=1st-broken-large-image\" alt=\"\">\n  </a>\n  <a data-pswp-width=\"1000\" data-pswp-height=\"1000\" href=\"https://dummyimage.com/1000x1000/555/fff/?text=2nd-1000x1000\" target=\"_blank\">\n    <img src=\"https://dummyimage.com/100x100/555/fff/?text=2nd-100x100\" alt=\"\">\n  </a>\n  <a data-pswp-width=\"800\" data-pswp-height=\"600\" href=\"https://example.com/another-broken-image-link\" target=\"_blank\">\n    <img src=\"https://dummyimage.com/300x200/555/fff/?text=3rd-broken-image-link\" alt=\"\">\n  </a>\n</div>\n```\n\n</PswpCodePreview>\n\n### preload\n\nArray, `[1, 2]`. Lazy loading of nearby slides based on direction of movement. Should be an array with two integers, first one - number of items to preload before the current image, second one - after the current image. Two nearby images are always loaded.\n\n<PswpCodePreview galleryID=\"test-preload\" numItems=\"10\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-preload',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  preload: [1, 4]\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n### mainClass\n\nString, `undefined`. Class that will be added to the root element of PhotoSwipe, may contain multiple separated by space. Example on [Styling](/styling#modifying-icons) page.\n\n### appendToEl\n\nDOMElement, `document.body`. Element to which PhotoSwipe dialog will be appended when it opens.\n\n<PswpCodePreview galleryID=\"test-appendToEl\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-appendToEl',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  appendToEl: document.querySelector('#__docusaurus')\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n### maxWidthToAnimate\n\nInteger, `4000`. Maximum width of image to animate, if initial rendered image width is larger than this value - the opening/closing transition will be automatically disabled.\n\n<PswpCodePreview galleryID=\"test-maxWidthToAnimate\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-maxWidthToAnimate',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  maxWidthToAnimate: 800,\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n## Lightbox module options\n\nSee [getting started](getting-started.md) and [data sources](data-sources.md) for examples.\n\n### gallery\n\nElement, NodeList, or CSS selector (string) for the gallery element.\n\n### children\n\nElement, NodeList, or CSS selector (string) for elements within `gallery`. For example, link elements. If not defined or set to false - root `gallery` node will be used.\n\n### pswpModule\n\nFunction or Module, `undefined`. A function that should return import() promise (if you need dynamic import), for example:\n\n```\npswpModule: () => import('photoswipe/dist/photoswipe.esm.js');\n```\n\nOr the PhotoSwipe Core module itself, for example:\n\n```js\nimport PhotoSwipeLightbox from 'photoswipe/dist/photoswipe-lightbox.esm.js';\nimport PhotoSwipe from 'photoswipe/dist/photoswipe.esm.js';\n\nconst lightbox = new PhotoSwipeLightbox({\n  pswpModule: PhotoSwipe\n  // ...\n});\n```\n\n### preloadFirstSlide\n\nBoolean, `true`. Loads the first slide image in parallel with PhotoSwipe Core (while PhotoSwipe is opening).\n\n<PswpCodePreview galleryID=\"test-appendToEl\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-appendToEl',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  preloadFirstSlide: false\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n\n\n## Translating\n\nA list of options that might need a translation:\n\n```\ncloseTitle: 'Close',\nzoomTitle: 'Zoom',\narrowPrevTitle: 'Previous',\narrowNextTitle: 'Next',\n\nerrorMsg: 'The image cannot be loaded',\nindexIndicatorSep: ' / ',\n```\n\n<!-- internationalization, i18n, localization, language -->\n\nExample:\n\n<PswpCodePreview galleryID=\"test-i18n\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--test-i18n',\n  children: 'a',\n\n  closeTitle: 'Close the dialog',\n  zoomTitle: 'Zoom the photo',\n  arrowPrevTitle: 'Go to the previous photo',\n  arrowNextTitle: 'Go to the next photo',\n\n  errorMsg: 'The photo cannot be loaded',\n  indexIndicatorSep: ' of ',\n\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  preloadFirstSlide: false\n});\nlightbox.init();\n```\n\n</PswpCodePreview>\n"
  },
  {
    "path": "docs/properties.md",
    "content": "---\nid: properties\ntitle: Properties\nsidebar_label: Properties\n---\n\n\n*page is under construction*\n\nWhen PhotoSwipe is open, you can access its instance globally via `window.pswp`.\n\n<PswpCodePreview numItems=\"4\" galleryID=\"custom-bg\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--custom-bg',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n});\nlightbox.init();\n\nlightbox.on('beforeOpen', () => {\n  console.log('PhotoSwipe Core instance:', lightbox.pswp);\n\n  const { pswp } = lightbox;\n  console.log('Options (read-only):', pswp.options);\n});\nlightbox.on('afterInit', () => {\n  const { pswp } = lightbox;\n  // Elements:\n  console.log('Root element', pswp.element);\n  console.log('Background element', pswp.bg);\n  console.log('Scroll wrapper element', pswp.scrollWrap);\n});\nlightbox.on('change', () => {\n  const { pswp } = lightbox;\n  console.log('Slide index', pswp.currIndex);\n  console.log('Slide object', pswp.currSlide);\n  console.log('Slide object data', pswp.currSlide.data);\n});\n```\n\n</PswpCodePreview>\n"
  },
  {
    "path": "docs/react-image-gallery.md",
    "content": "---\nid: react-image-gallery\ntitle: Image Gallery with Lightbox for React\nsidebar_label: for React\n---\n\nHere's a basic example of how to attach PhotoSwipe Lightbox to a simple image grid in React. ([edit Stackblitz](https://stackblitz.com/edit/react-ts-gvpqsb?file=SimpleGallery.js)). The example creates a simple component from an array of image URLs and sizes.\n\nThe example uses dynamic import - PhotoSwipe JS starts loading only after the user clicks on a thumbnail. If you'd like to load PhotoSwipe initially - set `pswpModule: PhotoSwipe` as [shown here](/getting-started/#without-dynamic-import).\n\n<iframe src=\"https://stackblitz.com/edit/react-ts-gvpqsb?embed=1&file=SimpleGallery.js&hideNavigation=1\"></iframe>\n\nIf you want to provide images data directly to PhotoSwipe, refer to [Data sources page](/data-sources).\n"
  },
  {
    "path": "docs/styling.md",
    "content": "---\nid: styling\ntitle: Styling PhotoSwipe\nsidebar_label: Styling\n---\n\n\n## Modifying icons\n\nBy default PhotoSwipe includes Close, Zoom and Arrow icon (right arrow is flipped left arrow). Each is an SVG element that is generated by JavaScript. \n\nDefault icons contain an outline/shadow so the icon is visible on both dark and light background (to disable it add style `.pswp__icn-shadow { display: none }`).\n\nTo modify the color of the icons use CSS variables. To modify SVG shapes use JS options: `arrowPrevSVG`, `arrowNextSVG`, `closeSVG`, `zoomSVG` (set value to empty string `''` if you want to remove the icon), these options support any HTML string.\n\nFor example:\n\n<PswpCodePreview numItems=\"4\" galleryID=\"custom-icon-colors\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\nconst leftArrowSVGString = '<svg aria-hidden=\"true\" class=\"pswp__icn\" viewBox=\"0 0 100 125\" width=\"100\" height=\"125\"><path d=\"M5,50L50,5l3,3L11,50l42,42l-3,3L5,50z M92,95l3-3L53,50L95,8l-3-3L47,50L92,95z\"/></svg>';\n\nconst options = {\n  arrowPrevSVG: leftArrowSVGString,\n  arrowNextSVG: leftArrowSVGString,\n\n  // to apply styles just to this instance of PhotoSwipe\n  mainClass: 'pswp--custom-icon-colors',\n\n  gallery: '#gallery--custom-icon-colors',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n};\nconst lightbox = new PhotoSwipeLightbox(options);\nlightbox.init();\n```\n\n```css pswpcode\n.pswp--custom-icon-colors {\n  --pswp-icon-color: #00fffc;\n  --pswp-icon-color-secondary: #333;\n}\n```\n\n</PswpCodePreview>\n\n- Use SVGO to optimize icons. ([SVGOMG online tool](https://jakearchibald.github.io/svgomg/)).\n- Add `pswp__icn` class, define `viewBox`, `width`, `height` and `aria-hidden=\"true\"` attributes.\n\n```html\n<svg aria-hidden=\"true\" class=\"pswp__icn\" viewBox=\"0 0 50 30\" width=\"50\" height=\"30\">\n  your svg content\n</svg>\n```\n\n\n## Hiding a specific UI element\n\nElements can be disabled by their name, for example:\n\n```\narrowPrev: false,\narrowNext: false,\nzoom: false,\nclose: false,\ncounter: false\n```\n\nYou may also hide them via CSS, for example:\n\n```\n.pswp__counter {\n  display: none;\n}\n```\n\n## Background color and opacity\n\nBackground color can be controlled via CSS variable `.pswp { --pswp-bg: red; }` or directly `.pswp__bg { background: red; }`\n\nTo adjust opacity - use JS option `bgOpacity`, for example `bgOpacity: 0.6`.\n\n- If you need transparency - do not use rgba color to define background of the PhotoSwipe, it affects the performance.\n- Do no try to apply blur to the background of PhotoSwipe, as it also heavily affects the performance.\n\n<PswpCodePreview numItems=\"4\" galleryID=\"custom-bg\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\n\nconst options = {\n  // set background opacity\n  bgOpacity: 0.6,\n\n  // to apply styles just to this instance of PhotoSwipe\n  mainClass: 'pswp--custom-bg',\n\n  gallery: '#gallery--custom-bg',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js')\n};\nconst lightbox = new PhotoSwipeLightbox(options);\nlightbox.init();\n```\n\n```css pswpcode\n.pswp--custom-bg {\n  --pswp-bg: #7079bf;\n}\n```\n\n</PswpCodePreview>\n\n## Loading indicator (preloader)\n\nIf you have a good connection, you probably haven't even seen the loading indicator. That's because PhotoSwipe displays loading indicator only if image is not loaded within 2 seconds (can be adjusted via [preloaderDelay](options#preloaderDelay) option). Use dev tools network throttling to test it.\n\nThe default loading indicator is displayed in the top left corner and is just a spinning 3/4 circle. You may adjust it just via CSS, the default styles are in `photoswipe.css`.\n\nThe example below permanently displays it for debugging:\n\n<PswpCodePreview numItems=\"4\" galleryID=\"perma-preloader\">\n\n```js pswpcode\nimport PhotoSwipeLightbox from '/photoswipe/photoswipe-lightbox.esm.js';\nconst lightbox = new PhotoSwipeLightbox({\n  gallery: '#gallery--perma-preloader',\n  children: 'a',\n  pswpModule: () => import('/photoswipe/photoswipe.esm.js'),\n  mainClass: 'pswp-with-perma-preloader',\n});\nlightbox.init();\n```\n\n```css pswpcode \n/* debug: permanently display preloader */\n.pswp-with-perma-preloader .pswp__icn {\n  opacity: 0.85 !important;\n}\n```\n\n</PswpCodePreview>"
  },
  {
    "path": "docs/svelte-image-gallery.md",
    "content": "---\nid: svelte-image-gallery\ntitle: Image Gallery for Svelte\nsidebar_label: for Svelte\n---\n\nHere's a basic example on how to create a simple image gallery with Svelte. ([edit Stackblitz](https://stackblitz.com/edit/vitejs-vite-3ib82v?file=src/lib/SimpleGallery.svelte)).\n\n<iframe src=\"https://stackblitz.com/edit/vitejs-vite-3ib82v?embed=1&file=src/lib/SimpleGallery.svelte&hideNavigation=1\"></iframe>\n\nIf you want to provide images data directly to PhotoSwipe, refer to [Data sources page](/data-sources).\n\nThere is also an unofficial [Svelte gallery component by @mvolfik](https://github.com/mvolfik/svelte-photoswipe) that could be very useful.\n"
  },
  {
    "path": "docs/vue-image-gallery.md",
    "content": "---\nid: vue-image-gallery\ntitle: Image Gallery for Vue.js\nsidebar_label: for Vue\n---\n\nHere's an example on how to use PhotoSwipe with Vue to create a simple image gallery. [Browse and edit code on Stackblitz](https://stackblitz.com/edit/vue-hatnqg).\n\n<iframe src=\"https://stackblitz.com/edit/vue-hatnqg?embed=1&file=src/SimpleGallery.vue&hideNavigation=1\"></iframe>\n\nThere is also an unofficial [Vue gallery component by @hzpeng57](https://github.com/hzpeng57/vue-preview-imgs) that could be very useful.\n\n   \n   \n"
  },
  {
    "path": "global.d.ts",
    "content": "export {}\n\ndeclare global {\n  interface Window {\n      pswp?: import('./src/js/photoswipe').default;\n  }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"photoswipe\",\n  \"version\": \"5.4.4\",\n  \"main\": \"dist/photoswipe.esm.js\",\n  \"style\": \"dist/photoswipe.css\",\n  \"type\": \"module\",\n  \"types\": \"./dist/types/photoswipe.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/types/photoswipe.d.ts\",\n      \"default\": \"./dist/photoswipe.esm.js\"\n    },\n    \"./lightbox\": {\n      \"types\": \"./dist/types/lightbox/lightbox.d.ts\",\n      \"default\": \"./dist/photoswipe-lightbox.esm.js\"\n    },\n    \"./dist/photoswipe.css\": \"./dist/photoswipe.css\",\n    \"./photoswipe.css\": \"./dist/photoswipe.css\",\n    \"./style.css\": \"./dist/photoswipe.css\"\n  },\n  \"typesVersions\": {\n    \"*\": {\n      \"lightbox\": [\n        \"dist/types/lightbox/lightbox.d.ts\"\n      ]\n    }\n  },\n  \"files\": [\n    \"dist\",\n    \"src\",\n    \"README.md\"\n  ],\n  \"engines\": {\n    \"node\": \">= 0.12.0\"\n  },\n  \"scripts\": {\n    \"build\": \"npm run build-and-minify-js && npm run build-css && npm run copy-to-dist\",\n    \"watch\": \"concurrently \\\"npm:watch-js\\\" \\\"npm:watch-css\\\" \\\"npm:watch-docs\\\"\",\n    \"watch-local\": \"concurrently \\\"npm:watch-js\\\" \\\"npm:watch-css\\\" \\\"npm:watch-docs-local\\\"\",\n    \"build-and-minify-js\": \"rollup --config build/rollup.config.js && npm run tsc\",\n    \"watch-js\": \"rollup --config build/rollup.config.watch.js --watch\",\n    \"build-css\": \"copyfiles -f src/*.css dist/ && copyfiles -f src/*.css demo-docs-website/static/photoswipe/\",\n    \"watch-css\": \"chokidar \\\"src/*.css\\\" --command \\\"npm run build-css\\\"\",\n    \"watch-docs\": \"cd demo-docs-website/ && npm start\",\n    \"watch-docs-local\": \"cd demo-docs-website/ && npm start -- --host 192.168.1.120\",\n    \"copy-to-dist\": \"copyfiles -f demo-docs-website/static/photoswipe/* dist/ && copyfiles -f demo-docs-website/static/photoswipe/umd/* dist/umd/\",\n    \"lint\": \"eslint src/js/**/*.js\",\n    \"lint-auto-fix\": \"eslint src/js/**/*.js --fix\",\n    \"test\": \"npm run lint\",\n    \"tsc\": \"tsc\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.18.0\",\n    \"@babel/eslint-parser\": \"^7.17.0\",\n    \"@babel/plugin-syntax-class-properties\": \"^7.12.13\",\n    \"@babel/plugin-transform-nullish-coalescing-operator\": \"^7.22.11\",\n    \"@babel/plugin-transform-optional-chaining\": \"^7.22.10\",\n    \"@rollup/plugin-babel\": \"^6.0.3\",\n    \"chokidar-cli\": \"^3.0.0\",\n    \"concurrently\": \"^6.0.2\",\n    \"copyfiles\": \"^2.4.1\",\n    \"eslint\": \"^8.16.0\",\n    \"eslint-config-airbnb-base\": \"^15.0.0\",\n    \"eslint-plugin-import\": \"^2.26.0\",\n    \"rollup\": \"^2.46.0\",\n    \"rollup-plugin-copy\": \"^3.4.0\",\n    \"rollup-plugin-terser\": \"^7.0.2\",\n    \"typescript\": \"^4.7.3\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/dimsemenov/Photoswipe.git\"\n  },\n  \"description\": \"JavaScript gallery\",\n  \"bugs\": {\n    \"url\": \"https://github.com/dimsemenov/Photoswipe/issues\"\n  },\n  \"homepage\": \"https://photoswipe.com\",\n  \"keywords\": [\n    \"gallery\",\n    \"lightbox\",\n    \"photo\",\n    \"image\",\n    \"touch\",\n    \"swipe\",\n    \"zoom\"\n  ],\n  \"author\": \"Dmytro Semenov (https://dimsemenov.com)\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "src/js/core/base.js",
    "content": "import Eventable from './eventable.js';\nimport { getElementsFromOption } from '../util/util.js';\nimport Content from '../slide/content.js';\nimport { lazyLoadData } from '../slide/loader.js';\n\n/** @typedef {import(\"../photoswipe.js\").default} PhotoSwipe */\n/** @typedef {import(\"../slide/slide.js\").SlideData} SlideData */\n\n/**\n * PhotoSwipe base class that can retrieve data about every slide.\n * Shared by PhotoSwipe Core and PhotoSwipe Lightbox\n */\nclass PhotoSwipeBase extends Eventable {\n  /**\n   * Get total number of slides\n   *\n   * @returns {number}\n   */\n  getNumItems() {\n    let numItems = 0;\n    const dataSource = this.options?.dataSource;\n\n    if (dataSource && 'length' in dataSource) {\n      // may be an array or just object with length property\n      numItems = dataSource.length;\n    } else if (dataSource && 'gallery' in dataSource) {\n      // query DOM elements\n      if (!dataSource.items) {\n        dataSource.items = this._getGalleryDOMElements(dataSource.gallery);\n      }\n\n      if (dataSource.items) {\n        numItems = dataSource.items.length;\n      }\n    }\n\n    // legacy event, before filters were introduced\n    const event = this.dispatch('numItems', {\n      dataSource,\n      numItems\n    });\n    return this.applyFilters('numItems', event.numItems, dataSource);\n  }\n\n  /**\n   * @param {SlideData} slideData\n   * @param {number} index\n   * @returns {Content}\n   */\n  createContentFromData(slideData, index) {\n    return new Content(slideData, this, index);\n  }\n\n  /**\n   * Get item data by index.\n   *\n   * \"item data\" should contain normalized information that PhotoSwipe needs to generate a slide.\n   * For example, it may contain properties like\n   * `src`, `srcset`, `w`, `h`, which will be used to generate a slide with image.\n   *\n   * @param {number} index\n   * @returns {SlideData}\n   */\n  getItemData(index) {\n    const dataSource = this.options?.dataSource;\n    /** @type {SlideData | HTMLElement} */\n    let dataSourceItem = {};\n    if (Array.isArray(dataSource)) {\n      // Datasource is an array of elements\n      dataSourceItem = dataSource[index];\n    } else if (dataSource && 'gallery' in dataSource) {\n      // dataSource has gallery property,\n      // thus it was created by Lightbox, based on\n      // gallery and children options\n\n      // query DOM elements\n      if (!dataSource.items) {\n        dataSource.items = this._getGalleryDOMElements(dataSource.gallery);\n      }\n\n      dataSourceItem = dataSource.items[index];\n    }\n\n    let itemData = dataSourceItem;\n\n    if (itemData instanceof Element) {\n      itemData = this._domElementToItemData(itemData);\n    }\n\n    // Dispatching the itemData event,\n    // it's a legacy verion before filters were introduced\n    const event = this.dispatch('itemData', {\n      itemData: itemData || {},\n      index\n    });\n\n    return this.applyFilters('itemData', event.itemData, index);\n  }\n\n  /**\n   * Get array of gallery DOM elements,\n   * based on childSelector and gallery element.\n   *\n   * @param {HTMLElement} galleryElement\n   * @returns {HTMLElement[]}\n   */\n  _getGalleryDOMElements(galleryElement) {\n    if (this.options?.children || this.options?.childSelector) {\n      return getElementsFromOption(\n        this.options.children,\n        this.options.childSelector,\n        galleryElement\n      ) || [];\n    }\n\n    return [galleryElement];\n  }\n\n  /**\n   * Converts DOM element to item data object.\n   *\n   * @param {HTMLElement} element DOM element\n   * @returns {SlideData}\n   */\n  _domElementToItemData(element) {\n    /** @type {SlideData} */\n    const itemData = {\n      element\n    };\n\n    const linkEl = /** @type {HTMLAnchorElement} */ (\n      element.tagName === 'A'\n        ? element\n        : element.querySelector('a')\n    );\n\n    if (linkEl) {\n      // src comes from data-pswp-src attribute,\n      // if it's empty link href is used\n      itemData.src = linkEl.dataset.pswpSrc || linkEl.href;\n\n      if (linkEl.dataset.pswpSrcset) {\n        itemData.srcset = linkEl.dataset.pswpSrcset;\n      }\n\n      itemData.width = linkEl.dataset.pswpWidth ? parseInt(linkEl.dataset.pswpWidth, 10) : 0;\n      itemData.height = linkEl.dataset.pswpHeight ? parseInt(linkEl.dataset.pswpHeight, 10) : 0;\n\n      // support legacy w & h properties\n      itemData.w = itemData.width;\n      itemData.h = itemData.height;\n\n      if (linkEl.dataset.pswpType) {\n        itemData.type = linkEl.dataset.pswpType;\n      }\n\n      const thumbnailEl = element.querySelector('img');\n\n      if (thumbnailEl) {\n        // msrc is URL to placeholder image that's displayed before large image is loaded\n        // by default it's displayed only for the first slide\n        itemData.msrc = thumbnailEl.currentSrc || thumbnailEl.src;\n        itemData.alt = thumbnailEl.getAttribute('alt') ?? '';\n      }\n\n      if (linkEl.dataset.pswpCropped || linkEl.dataset.cropped) {\n        itemData.thumbCropped = true;\n      }\n    }\n\n    return this.applyFilters('domItemData', itemData, element, linkEl);\n  }\n\n  /**\n   * Lazy-load by slide data\n   *\n   * @param {SlideData} itemData Data about the slide\n   * @param {number} index\n   * @returns {Content} Image that is being decoded or false.\n   */\n  lazyLoadData(itemData, index) {\n    return lazyLoadData(itemData, this, index);\n  }\n}\n\nexport default PhotoSwipeBase;\n"
  },
  {
    "path": "src/js/core/eventable.js",
    "content": "/** @typedef {import('../lightbox/lightbox.js').default} PhotoSwipeLightbox */\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n/** @typedef {import('../photoswipe.js').DataSource} DataSource */\n/** @typedef {import('../ui/ui-element.js').UIElementData} UIElementData */\n/** @typedef {import('../slide/content.js').default} ContentDefault */\n/** @typedef {import('../slide/slide.js').default} Slide */\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n/** @typedef {import('../slide/zoom-level.js').default} ZoomLevel */\n/** @typedef {import('../slide/get-thumb-bounds.js').Bounds} Bounds */\n\n/**\n * Allow adding an arbitrary props to the Content\n * https://photoswipe.com/custom-content/#using-webp-image-format\n * @typedef {ContentDefault & Record<string, any>} Content\n */\n/** @typedef {{ x?: number; y?: number }} Point */\n\n/**\n * @typedef {Object} PhotoSwipeEventsMap https://photoswipe.com/events/\n *\n *\n * https://photoswipe.com/adding-ui-elements/\n *\n * @prop {undefined} uiRegister\n * @prop {{ data: UIElementData }} uiElementCreate\n *\n *\n * https://photoswipe.com/events/#initialization-events\n *\n * @prop {undefined} beforeOpen\n * @prop {undefined} firstUpdate\n * @prop {undefined} initialLayout\n * @prop {undefined} change\n * @prop {undefined} afterInit\n * @prop {undefined} bindEvents\n *\n *\n * https://photoswipe.com/events/#opening-or-closing-transition-events\n *\n * @prop {undefined} openingAnimationStart\n * @prop {undefined} openingAnimationEnd\n * @prop {undefined} closingAnimationStart\n * @prop {undefined} closingAnimationEnd\n *\n *\n * https://photoswipe.com/events/#closing-events\n *\n * @prop {undefined} close\n * @prop {undefined} destroy\n *\n *\n * https://photoswipe.com/events/#pointer-and-gesture-events\n *\n * @prop {{ originalEvent: PointerEvent }} pointerDown\n * @prop {{ originalEvent: PointerEvent }} pointerMove\n * @prop {{ originalEvent: PointerEvent }} pointerUp\n * @prop {{ bgOpacity: number }} pinchClose can be default prevented\n * @prop {{ panY: number }} verticalDrag can be default prevented\n *\n *\n * https://photoswipe.com/events/#slide-content-events\n *\n * @prop {{ content: Content }} contentInit\n * @prop {{ content: Content; isLazy: boolean }} contentLoad can be default prevented\n * @prop {{ content: Content; isLazy: boolean }} contentLoadImage can be default prevented\n * @prop {{ content: Content; slide: Slide; isError?: boolean }} loadComplete\n * @prop {{ content: Content; slide: Slide }} loadError\n * @prop {{ content: Content; width: number; height: number }} contentResize can be default prevented\n * @prop {{ content: Content; width: number; height: number; slide: Slide }} imageSizeChange\n * @prop {{ content: Content }} contentLazyLoad can be default prevented\n * @prop {{ content: Content }} contentAppend can be default prevented\n * @prop {{ content: Content }} contentActivate can be default prevented\n * @prop {{ content: Content }} contentDeactivate can be default prevented\n * @prop {{ content: Content }} contentRemove can be default prevented\n * @prop {{ content: Content }} contentDestroy can be default prevented\n *\n *\n * undocumented\n *\n * @prop {{ point: Point; originalEvent: PointerEvent }} imageClickAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} bgClickAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} tapAction can be default prevented\n * @prop {{ point: Point; originalEvent: PointerEvent }} doubleTapAction can be default prevented\n *\n * @prop {{ originalEvent: KeyboardEvent }} keydown can be default prevented\n * @prop {{ x: number; dragging: boolean }} moveMainScroll\n * @prop {{ slide: Slide }} firstZoomPan\n * @prop {{ slide: Slide | undefined, data: SlideData, index: number }} gettingData\n * @prop {undefined} beforeResize\n * @prop {undefined} resize\n * @prop {undefined} viewportSize\n * @prop {undefined} updateScrollOffset\n * @prop {{ slide: Slide }} slideInit\n * @prop {{ slide: Slide }} afterSetContent\n * @prop {{ slide: Slide }} slideLoad\n * @prop {{ slide: Slide }} appendHeavy can be default prevented\n * @prop {{ slide: Slide }} appendHeavyContent\n * @prop {{ slide: Slide }} slideActivate\n * @prop {{ slide: Slide }} slideDeactivate\n * @prop {{ slide: Slide }} slideDestroy\n * @prop {{ destZoomLevel: number, centerPoint: Point | undefined, transitionDuration: number | false | undefined }} beforeZoomTo\n * @prop {{ slide: Slide }} zoomPanUpdate\n * @prop {{ slide: Slide }} initialZoomPan\n * @prop {{ slide: Slide }} calcSlideSize\n * @prop {undefined} resolutionChanged\n * @prop {{ originalEvent: WheelEvent }} wheel can be default prevented\n * @prop {{ content: Content }} contentAppendImage can be default prevented\n * @prop {{ index: number; itemData: SlideData }} lazyLoadSlide can be default prevented\n * @prop {undefined} lazyLoad\n * @prop {{ slide: Slide }} calcBounds\n * @prop {{ zoomLevels: ZoomLevel, slideData: SlideData }} zoomLevelsUpdate\n *\n *\n * legacy\n *\n * @prop {undefined} init\n * @prop {undefined} initialZoomIn\n * @prop {undefined} initialZoomOut\n * @prop {undefined} initialZoomInEnd\n * @prop {undefined} initialZoomOutEnd\n * @prop {{ dataSource: DataSource | undefined, numItems: number }} numItems\n * @prop {{ itemData: SlideData; index: number }} itemData\n * @prop {{ index: number, itemData: SlideData, instance: PhotoSwipe }} thumbBounds\n */\n\n/**\n * @typedef {Object} PhotoSwipeFiltersMap https://photoswipe.com/filters/\n *\n * @prop {(numItems: number, dataSource: DataSource | undefined) => number} numItems\n * Modify the total amount of slides. Example on Data sources page.\n * https://photoswipe.com/filters/#numitems\n *\n * @prop {(itemData: SlideData, index: number) => SlideData} itemData\n * Modify slide item data. Example on Data sources page.\n * https://photoswipe.com/filters/#itemdata\n *\n * @prop {(itemData: SlideData, element: HTMLElement, linkEl: HTMLAnchorElement) => SlideData} domItemData\n * Modify item data when it's parsed from DOM element. Example on Data sources page.\n * https://photoswipe.com/filters/#domitemdata\n *\n * @prop {(clickedIndex: number, e: MouseEvent, instance: PhotoSwipeLightbox) => number} clickedIndex\n * Modify clicked gallery item index.\n * https://photoswipe.com/filters/#clickedindex\n *\n * @prop {(placeholderSrc: string | false, content: Content) => string | false} placeholderSrc\n * Modify placeholder image source.\n * https://photoswipe.com/filters/#placeholdersrc\n *\n * @prop {(isContentLoading: boolean, content: Content) => boolean} isContentLoading\n * Modify if the content is currently loading.\n * https://photoswipe.com/filters/#iscontentloading\n *\n * @prop {(isContentZoomable: boolean, content: Content) => boolean} isContentZoomable\n * Modify if the content can be zoomed.\n * https://photoswipe.com/filters/#iscontentzoomable\n *\n * @prop {(useContentPlaceholder: boolean, content: Content) => boolean} useContentPlaceholder\n * Modify if the placeholder should be used for the content.\n * https://photoswipe.com/filters/#usecontentplaceholder\n *\n * @prop {(isKeepingPlaceholder: boolean, content: Content) => boolean} isKeepingPlaceholder\n * Modify if the placeholder should be kept after the content is loaded.\n * https://photoswipe.com/filters/#iskeepingplaceholder\n *\n *\n * @prop {(contentErrorElement: HTMLElement, content: Content) => HTMLElement} contentErrorElement\n * Modify an element when the content has error state (for example, if image cannot be loaded).\n * https://photoswipe.com/filters/#contenterrorelement\n *\n * @prop {(element: HTMLElement, data: UIElementData) => HTMLElement} uiElement\n * Modify a UI element that's being created.\n * https://photoswipe.com/filters/#uielement\n *\n * @prop {(thumbnail: HTMLElement | null | undefined, itemData: SlideData, index: number) => HTMLElement} thumbEl\n * Modify the thumbnail element from which opening zoom animation starts or ends.\n * https://photoswipe.com/filters/#thumbel\n *\n * @prop {(thumbBounds: Bounds | undefined, itemData: SlideData, index: number) => Bounds} thumbBounds\n * Modify the thumbnail bounds from which opening zoom animation starts or ends.\n * https://photoswipe.com/filters/#thumbbounds\n *\n * @prop {(srcsetSizesWidth: number, content: Content) => number} srcsetSizesWidth\n *\n * @prop {(preventPointerEvent: boolean, event: PointerEvent, pointerType: string) => boolean} preventPointerEvent\n *\n */\n\n/**\n * @template {keyof PhotoSwipeFiltersMap} T\n * @typedef {{ fn: PhotoSwipeFiltersMap[T], priority: number }} Filter\n */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {PhotoSwipeEventsMap[T] extends undefined ? PhotoSwipeEvent<T> : PhotoSwipeEvent<T> & PhotoSwipeEventsMap[T]} AugmentedEvent\n */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {(event: AugmentedEvent<T>) => void} EventCallback\n */\n\n/**\n * Base PhotoSwipe event object\n *\n * @template {keyof PhotoSwipeEventsMap} T\n */\nclass PhotoSwipeEvent {\n  /**\n   * @param {T} type\n   * @param {PhotoSwipeEventsMap[T]} [details]\n   */\n  constructor(type, details) {\n    this.type = type;\n    this.defaultPrevented = false;\n    if (details) {\n      Object.assign(this, details);\n    }\n  }\n\n  preventDefault() {\n    this.defaultPrevented = true;\n  }\n}\n\n/**\n * PhotoSwipe base class that can listen and dispatch for events.\n * Shared by PhotoSwipe Core and PhotoSwipe Lightbox, extended by base.js\n */\nclass Eventable {\n  constructor() {\n    /**\n     * @type {{ [T in keyof PhotoSwipeEventsMap]?: ((event: AugmentedEvent<T>) => void)[] }}\n     */\n    this._listeners = {};\n\n    /**\n     * @type {{ [T in keyof PhotoSwipeFiltersMap]?: Filter<T>[] }}\n     */\n    this._filters = {};\n\n    /** @type {PhotoSwipe | undefined} */\n    this.pswp = undefined;\n\n    /** @type {PhotoSwipeOptions | undefined} */\n    this.options = undefined;\n  }\n\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {PhotoSwipeFiltersMap[T]} fn\n   * @param {number} priority\n   */\n  addFilter(name, fn, priority = 100) {\n    if (!this._filters[name]) {\n      this._filters[name] = [];\n    }\n\n    this._filters[name]?.push({ fn, priority });\n    this._filters[name]?.sort((f1, f2) => f1.priority - f2.priority);\n\n    this.pswp?.addFilter(name, fn, priority);\n  }\n\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {PhotoSwipeFiltersMap[T]} fn\n   */\n  removeFilter(name, fn) {\n    if (this._filters[name]) {\n      // @ts-expect-error\n      this._filters[name] = this._filters[name].filter(filter => (filter.fn !== fn));\n    }\n\n    if (this.pswp) {\n      this.pswp.removeFilter(name, fn);\n    }\n  }\n\n  /**\n   * @template {keyof PhotoSwipeFiltersMap} T\n   * @param {T} name\n   * @param {Parameters<PhotoSwipeFiltersMap[T]>} args\n   * @returns {Parameters<PhotoSwipeFiltersMap[T]>[0]}\n   */\n  applyFilters(name, ...args) {\n    this._filters[name]?.forEach((filter) => {\n      // @ts-expect-error\n      args[0] = filter.fn.apply(this, args);\n    });\n    return args[0];\n  }\n\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {EventCallback<T>} fn\n   */\n  on(name, fn) {\n    if (!this._listeners[name]) {\n      this._listeners[name] = [];\n    }\n    this._listeners[name]?.push(fn);\n\n    // When binding events to lightbox,\n    // also bind events to PhotoSwipe Core,\n    // if it's open.\n    this.pswp?.on(name, fn);\n  }\n\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {EventCallback<T>} fn\n   */\n  off(name, fn) {\n    if (this._listeners[name]) {\n      // @ts-expect-error\n      this._listeners[name] = this._listeners[name].filter(listener => (fn !== listener));\n    }\n\n    this.pswp?.off(name, fn);\n  }\n\n  /**\n   * @template {keyof PhotoSwipeEventsMap} T\n   * @param {T} name\n   * @param {PhotoSwipeEventsMap[T]} [details]\n   * @returns {AugmentedEvent<T>}\n   */\n  dispatch(name, details) {\n    if (this.pswp) {\n      return this.pswp.dispatch(name, details);\n    }\n\n    const event = /** @type {AugmentedEvent<T>} */ (new PhotoSwipeEvent(name, details));\n\n    this._listeners[name]?.forEach((listener) => {\n      listener.call(this, event);\n    });\n\n    return event;\n  }\n}\n\nexport default Eventable;\n"
  },
  {
    "path": "src/js/gestures/drag-handler.js",
    "content": "import {\n  equalizePoints, roundPoint, clamp\n} from '../util/util.js';\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n/** @typedef {import('./gestures.js').default} Gestures */\n\nconst PAN_END_FRICTION = 0.35;\nconst VERTICAL_DRAG_FRICTION = 0.6;\n\n// 1 corresponds to the third of viewport height\nconst MIN_RATIO_TO_CLOSE = 0.4;\n\n// Minimum speed required to navigate\n// to next or previous slide\nconst MIN_NEXT_SLIDE_SPEED = 0.5;\n\n/**\n * @param {number} initialVelocity\n * @param {number} decelerationRate\n * @returns {number}\n */\nfunction project(initialVelocity, decelerationRate) {\n  return initialVelocity * decelerationRate / (1 - decelerationRate);\n}\n\n/**\n * Handles single pointer dragging\n */\nclass DragHandler {\n  /**\n   * @param {Gestures} gestures\n   */\n  constructor(gestures) {\n    this.gestures = gestures;\n    this.pswp = gestures.pswp;\n    /** @type {Point} */\n    this.startPan = { x: 0, y: 0 };\n  }\n\n  start() {\n    if (this.pswp.currSlide) {\n      equalizePoints(this.startPan, this.pswp.currSlide.pan);\n    }\n    this.pswp.animations.stopAll();\n  }\n\n  change() {\n    const { p1, prevP1, dragAxis } = this.gestures;\n    const { currSlide } = this.pswp;\n\n    if (dragAxis === 'y'\n        && this.pswp.options.closeOnVerticalDrag\n        && (currSlide && currSlide.currZoomLevel <= currSlide.zoomLevels.fit)\n        && !this.gestures.isMultitouch) {\n      // Handle vertical drag to close\n      const panY = currSlide.pan.y + (p1.y - prevP1.y);\n      if (!this.pswp.dispatch('verticalDrag', { panY }).defaultPrevented) {\n        this._setPanWithFriction('y', panY, VERTICAL_DRAG_FRICTION);\n        const bgOpacity = 1 - Math.abs(this._getVerticalDragRatio(currSlide.pan.y));\n        this.pswp.applyBgOpacity(bgOpacity);\n        currSlide.applyCurrentZoomPan();\n      }\n    } else {\n      const mainScrollChanged = this._panOrMoveMainScroll('x');\n      if (!mainScrollChanged) {\n        this._panOrMoveMainScroll('y');\n\n        if (currSlide) {\n          roundPoint(currSlide.pan);\n          currSlide.applyCurrentZoomPan();\n        }\n      }\n    }\n  }\n\n  end() {\n    const { velocity } = this.gestures;\n    const { mainScroll, currSlide } = this.pswp;\n    let indexDiff = 0;\n\n    this.pswp.animations.stopAll();\n\n    // Handle main scroll if it's shifted\n    if (mainScroll.isShifted()) {\n      // Position of the main scroll relative to the viewport\n      const mainScrollShiftDiff = mainScroll.x - mainScroll.getCurrSlideX();\n\n      // Ratio between 0 and 1:\n      // 0 - slide is not visible at all,\n      // 0.5 - half of the slide is visible\n      // 1 - slide is fully visible\n      const currentSlideVisibilityRatio = (mainScrollShiftDiff / this.pswp.viewportSize.x);\n\n      // Go next slide.\n      //\n      // - if velocity and its direction is matched,\n      //   and we see at least tiny part of the next slide\n      //\n      // - or if we see less than 50% of the current slide\n      //   and velocity is close to 0\n      //\n      if ((velocity.x < -MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio < 0)\n          || (velocity.x < 0.1 && currentSlideVisibilityRatio < -0.5)) {\n        // Go to next slide\n        indexDiff = 1;\n        velocity.x = Math.min(velocity.x, 0);\n      } else if ((velocity.x > MIN_NEXT_SLIDE_SPEED && currentSlideVisibilityRatio > 0)\n          || (velocity.x > -0.1 && currentSlideVisibilityRatio > 0.5)) {\n        // Go to prev slide\n        indexDiff = -1;\n        velocity.x = Math.max(velocity.x, 0);\n      }\n\n      mainScroll.moveIndexBy(indexDiff, true, velocity.x);\n    }\n\n    // Restore zoom level\n    if ((currSlide && currSlide.currZoomLevel > currSlide.zoomLevels.max)\n        || this.gestures.isMultitouch) {\n      this.gestures.zoomLevels.correctZoomPan(true);\n    } else {\n      // we run two animations instead of one,\n      // as each axis has own pan boundaries and thus different spring function\n      // (correctZoomPan does not have this functionality,\n      //  it animates all properties with single timing function)\n      this._finishPanGestureForAxis('x');\n      this._finishPanGestureForAxis('y');\n    }\n  }\n\n  /**\n   * @private\n   * @param {'x' | 'y'} axis\n   */\n  _finishPanGestureForAxis(axis) {\n    const { velocity } = this.gestures;\n    const { currSlide } = this.pswp;\n\n    if (!currSlide) {\n      return;\n    }\n\n    const { pan, bounds } = currSlide;\n    const panPos = pan[axis];\n    const restoreBgOpacity = (this.pswp.bgOpacity < 1 && axis === 'y');\n\n    // 0.995 means - scroll view loses 0.5% of its velocity per millisecond\n    // Increasing this number will reduce travel distance\n    const decelerationRate = 0.995; // 0.99\n\n    // Pan position if there is no bounds\n    const projectedPosition = panPos + project(velocity[axis], decelerationRate);\n\n    if (restoreBgOpacity) {\n      const vDragRatio = this._getVerticalDragRatio(panPos);\n      const projectedVDragRatio = this._getVerticalDragRatio(projectedPosition);\n\n      // If we are above and moving upwards,\n      // or if we are below and moving downwards\n      if ((vDragRatio < 0 && projectedVDragRatio < -MIN_RATIO_TO_CLOSE)\n          || (vDragRatio > 0 && projectedVDragRatio > MIN_RATIO_TO_CLOSE)) {\n        this.pswp.close();\n        return;\n      }\n    }\n\n    // Pan position with corrected bounds\n    const correctedPanPosition = bounds.correctPan(axis, projectedPosition);\n\n    // Exit if pan position should not be changed\n    // or if speed it too low\n    if (panPos === correctedPanPosition) {\n      return;\n    }\n\n    // Overshoot if the final position is out of pan bounds\n    const dampingRatio = (correctedPanPosition === projectedPosition) ? 1 : 0.82;\n\n    const initialBgOpacity = this.pswp.bgOpacity;\n    const totalPanDist = correctedPanPosition - panPos;\n\n    this.pswp.animations.startSpring({\n      name: 'panGesture' + axis,\n      isPan: true,\n      start: panPos,\n      end: correctedPanPosition,\n      velocity: velocity[axis],\n      dampingRatio,\n      onUpdate: (pos) => {\n        // Animate opacity of background relative to Y pan position of an image\n        if (restoreBgOpacity && this.pswp.bgOpacity < 1) {\n          // 0 - start of animation, 1 - end of animation\n          const animationProgressRatio = 1 - (correctedPanPosition - pos) / totalPanDist;\n\n          // We clamp opacity to keep it between 0 and 1.\n          // As progress ratio can be larger than 1 due to overshoot,\n          // and we do not want to bounce opacity.\n          this.pswp.applyBgOpacity(clamp(\n            initialBgOpacity + (1 - initialBgOpacity) * animationProgressRatio,\n            0,\n            1\n          ));\n        }\n\n        pan[axis] = Math.floor(pos);\n        currSlide.applyCurrentZoomPan();\n      },\n    });\n  }\n\n  /**\n   * Update position of the main scroll,\n   * or/and update pan position of the current slide.\n   *\n   * Should return true if it changes (or can change) main scroll.\n   *\n   * @private\n   * @param {'x' | 'y'} axis\n   * @returns {boolean}\n   */\n  _panOrMoveMainScroll(axis) {\n    const { p1, dragAxis, prevP1, isMultitouch } = this.gestures;\n    const { currSlide, mainScroll } = this.pswp;\n    const delta = (p1[axis] - prevP1[axis]);\n    const newMainScrollX = mainScroll.x + delta;\n\n    if (!delta || !currSlide) {\n      return false;\n    }\n\n    // Always move main scroll if image can not be panned\n    if (axis === 'x' && !currSlide.isPannable() && !isMultitouch) {\n      mainScroll.moveTo(newMainScrollX, true);\n      return true; // changed main scroll\n    }\n\n    const { bounds } = currSlide;\n    const newPan = currSlide.pan[axis] + delta;\n\n    if (this.pswp.options.allowPanToNext\n        && dragAxis === 'x'\n        && axis === 'x'\n        && !isMultitouch) {\n      const currSlideMainScrollX = mainScroll.getCurrSlideX();\n\n      // Position of the main scroll relative to the viewport\n      const mainScrollShiftDiff = mainScroll.x - currSlideMainScrollX;\n\n      const isLeftToRight = delta > 0;\n      const isRightToLeft = !isLeftToRight;\n\n      if (newPan > bounds.min[axis] && isLeftToRight) {\n        // Panning from left to right, beyond the left edge\n\n        // Wether the image was at minimum pan position (or less)\n        // when this drag gesture started.\n        // Minimum pan position refers to the left edge of the image.\n        const wasAtMinPanPosition = (bounds.min[axis] <= this.startPan[axis]);\n\n        if (wasAtMinPanPosition) {\n          mainScroll.moveTo(newMainScrollX, true);\n          return true;\n        } else {\n          this._setPanWithFriction(axis, newPan);\n          //currSlide.pan[axis] = newPan;\n        }\n      } else if (newPan < bounds.max[axis] && isRightToLeft) {\n        // Paning from right to left, beyond the right edge\n\n        // Maximum pan position refers to the right edge of the image.\n        const wasAtMaxPanPosition = (this.startPan[axis] <= bounds.max[axis]);\n\n        if (wasAtMaxPanPosition) {\n          mainScroll.moveTo(newMainScrollX, true);\n          return true;\n        } else {\n          this._setPanWithFriction(axis, newPan);\n          //currSlide.pan[axis] = newPan;\n        }\n      } else {\n        // If main scroll is shifted\n        if (mainScrollShiftDiff !== 0) {\n          // If main scroll is shifted right\n          if (mainScrollShiftDiff > 0 /*&& isRightToLeft*/) {\n            mainScroll.moveTo(Math.max(newMainScrollX, currSlideMainScrollX), true);\n            return true;\n          } else if (mainScrollShiftDiff < 0 /*&& isLeftToRight*/) {\n            // Main scroll is shifted left (Position is less than 0 comparing to the viewport 0)\n            mainScroll.moveTo(Math.min(newMainScrollX, currSlideMainScrollX), true);\n            return true;\n          }\n        } else {\n          // We are within pan bounds, so just pan\n          this._setPanWithFriction(axis, newPan);\n        }\n      }\n    } else {\n      if (axis === 'y') {\n        // Do not pan vertically if main scroll is shifted o\n        if (!mainScroll.isShifted() && bounds.min.y !== bounds.max.y) {\n          this._setPanWithFriction(axis, newPan);\n        }\n      } else {\n        this._setPanWithFriction(axis, newPan);\n      }\n    }\n\n    return false;\n  }\n\n  // If we move above - the ratio is negative\n  // If we move below the ratio is positive\n\n  /**\n   * Relation between pan Y position and third of viewport height.\n   *\n   * When we are at initial position (center bounds) - the ratio is 0,\n   * if position is shifted upwards - the ratio is negative,\n   * if position is shifted downwards - the ratio is positive.\n   *\n   * @private\n   * @param {number} panY The current pan Y position.\n   * @returns {number}\n   */\n  _getVerticalDragRatio(panY) {\n    return (panY - (this.pswp.currSlide?.bounds.center.y ?? 0)) / (this.pswp.viewportSize.y / 3);\n  }\n\n  /**\n   * Set pan position of the current slide.\n   * Apply friction if the position is beyond the pan bounds,\n   * or if custom friction is defined.\n   *\n   * @private\n   * @param {'x' | 'y'} axis\n   * @param {number} potentialPan\n   * @param {number} [customFriction] (0.1 - 1)\n   */\n  _setPanWithFriction(axis, potentialPan, customFriction) {\n    const { currSlide } = this.pswp;\n\n    if (!currSlide) {\n      return;\n    }\n\n    const { pan, bounds } = currSlide;\n    const correctedPan = bounds.correctPan(axis, potentialPan);\n    // If we are out of pan bounds\n    if (correctedPan !== potentialPan || customFriction) {\n      const delta = Math.round(potentialPan - pan[axis]);\n      pan[axis] += delta * (customFriction || PAN_END_FRICTION);\n    } else {\n      pan[axis] = potentialPan;\n    }\n  }\n}\n\nexport default DragHandler;\n"
  },
  {
    "path": "src/js/gestures/gestures.js",
    "content": "import {\n  equalizePoints, pointsEqual, getDistanceBetween\n} from '../util/util.js';\n\nimport DragHandler from './drag-handler.js';\nimport ZoomHandler from './zoom-handler.js';\nimport TapHandler from './tap-handler.js';\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n// How far should user should drag\n// until we can determine that the gesture is swipe and its direction\nconst AXIS_SWIPE_HYSTERISIS = 10;\n//const PAN_END_FRICTION = 0.35;\n\nconst DOUBLE_TAP_DELAY = 300; // ms\nconst MIN_TAP_DISTANCE = 25; // px\n\n/**\n * Gestures class bind touch, pointer or mouse events\n * and emits drag to drag-handler and zoom events zoom-handler.\n *\n * Drag and zoom events are emited in requestAnimationFrame,\n * and only when one of pointers was actually changed.\n */\nclass Gestures {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n\n    /** @type {'x' | 'y' | null} */\n    this.dragAxis = null;\n\n    // point objects are defined once and reused\n    // PhotoSwipe keeps track only of two pointers, others are ignored\n    /** @type {Point} */\n    this.p1 = { x: 0, y: 0 }; // the first pressed pointer\n    /** @type {Point} */\n    this.p2 = { x: 0, y: 0 }; // the second pressed pointer\n    /** @type {Point} */\n    this.prevP1 = { x: 0, y: 0 };\n    /** @type {Point} */\n    this.prevP2 = { x: 0, y: 0 };\n    /** @type {Point} */\n    this.startP1 = { x: 0, y: 0 };\n    /** @type {Point} */\n    this.startP2 = { x: 0, y: 0 };\n    /** @type {Point} */\n    this.velocity = { x: 0, y: 0 };\n\n    /** @type {Point}\n     * @private\n     */\n    this._lastStartP1 = { x: 0, y: 0 };\n    /** @type {Point}\n     * @private\n     */\n    this._intervalP1 = { x: 0, y: 0 };\n    /** @private */\n    this._numActivePoints = 0;\n    /** @type {Point[]}\n     * @private\n     */\n    this._ongoingPointers = [];\n    /** @private */\n    this._touchEventEnabled = 'ontouchstart' in window;\n    /** @private */\n    this._pointerEventEnabled = !!(window.PointerEvent);\n    this.supportsTouch = this._touchEventEnabled\n                          || (this._pointerEventEnabled && navigator.maxTouchPoints > 1);\n    /** @private */\n    this._numActivePoints = 0;\n    /** @private */\n    this._intervalTime = 0;\n    /** @private */\n    this._velocityCalculated = false;\n    this.isMultitouch = false;\n    this.isDragging = false;\n    this.isZooming = false;\n    /** @type {number | null} */\n    this.raf = null;\n    /** @type {NodeJS.Timeout | null}\n     * @private\n     */\n    this._tapTimer = null;\n\n    if (!this.supportsTouch) {\n      // disable pan to next slide for non-touch devices\n      pswp.options.allowPanToNext = false;\n    }\n\n    this.drag = new DragHandler(this);\n    this.zoomLevels = new ZoomHandler(this);\n    this.tapHandler = new TapHandler(this);\n\n    pswp.on('bindEvents', () => {\n      pswp.events.add(\n        pswp.scrollWrap,\n        'click',\n        /** @type EventListener */(this._onClick.bind(this))\n      );\n\n      if (this._pointerEventEnabled) {\n        this._bindEvents('pointer', 'down', 'up', 'cancel');\n      } else if (this._touchEventEnabled) {\n        this._bindEvents('touch', 'start', 'end', 'cancel');\n\n        // In previous versions we also bound mouse event here,\n        // in case device supports both touch and mouse events,\n        // but newer versions of browsers now support PointerEvent.\n\n        // on iOS10 if you bind touchmove/end after touchstart,\n        // and you don't preventDefault touchstart (which PhotoSwipe does),\n        // preventDefault will have no effect on touchmove and touchend.\n        // Unless you bind it previously.\n        if (pswp.scrollWrap) {\n          pswp.scrollWrap.ontouchmove = () => {};\n          pswp.scrollWrap.ontouchend = () => {};\n        }\n      } else {\n        this._bindEvents('mouse', 'down', 'up');\n      }\n    });\n  }\n\n  /**\n   * @private\n   * @param {'mouse' | 'touch' | 'pointer'} pref\n   * @param {'down' | 'start'} down\n   * @param {'up' | 'end'} up\n   * @param {'cancel'} [cancel]\n   */\n  _bindEvents(pref, down, up, cancel) {\n    const { pswp } = this;\n    const { events } = pswp;\n\n    const cancelEvent = cancel ? pref + cancel : '';\n\n    events.add(\n      pswp.scrollWrap,\n      pref + down,\n      /** @type EventListener */(this.onPointerDown.bind(this))\n    );\n    events.add(window, pref + 'move', /** @type EventListener */(this.onPointerMove.bind(this)));\n    events.add(window, pref + up, /** @type EventListener */(this.onPointerUp.bind(this)));\n    if (cancelEvent) {\n      events.add(\n        pswp.scrollWrap,\n        cancelEvent,\n        /** @type EventListener */(this.onPointerUp.bind(this))\n      );\n    }\n  }\n\n  /**\n   * @param {PointerEvent} e\n   */\n  onPointerDown(e) {\n    // We do not call preventDefault for touch events\n    // to allow browser to show native dialog on longpress\n    // (the one that allows to save image or open it in new tab).\n    //\n    // Desktop Safari allows to drag images when preventDefault isn't called on mousedown,\n    // even though preventDefault IS called on mousemove. That's why we preventDefault mousedown.\n    const isMousePointer = e.type === 'mousedown' || e.pointerType === 'mouse';\n\n    // Allow dragging only via left mouse button.\n    // http://www.quirksmode.org/js/events_properties.html\n    // https://developer.mozilla.org/en-US/docs/Web/API/event.button\n    if (isMousePointer && e.button > 0) {\n      return;\n    }\n\n    const { pswp } = this;\n\n    // if PhotoSwipe is opening or closing\n    if (!pswp.opener.isOpen) {\n      e.preventDefault();\n      return;\n    }\n\n    if (pswp.dispatch('pointerDown', { originalEvent: e }).defaultPrevented) {\n      return;\n    }\n\n    if (isMousePointer) {\n      pswp.mouseDetected();\n\n      // preventDefault mouse event to prevent\n      // browser image drag feature\n      this._preventPointerEventBehaviour(e, 'down');\n    }\n\n    pswp.animations.stopAll();\n\n    this._updatePoints(e, 'down');\n\n    if (this._numActivePoints === 1) {\n      this.dragAxis = null;\n      // we need to store initial point to determine the main axis,\n      // drag is activated only after the axis is determined\n      equalizePoints(this.startP1, this.p1);\n    }\n\n    if (this._numActivePoints > 1) {\n      // Tap or double tap should not trigger if more than one pointer\n      this._clearTapTimer();\n      this.isMultitouch = true;\n    } else {\n      this.isMultitouch = false;\n    }\n  }\n\n  /**\n   * @param {PointerEvent} e\n   */\n  onPointerMove(e) {\n    this._preventPointerEventBehaviour(e, 'move');\n\n    if (!this._numActivePoints) {\n      return;\n    }\n\n    this._updatePoints(e, 'move');\n\n    if (this.pswp.dispatch('pointerMove', { originalEvent: e }).defaultPrevented) {\n      return;\n    }\n\n    if (this._numActivePoints === 1 && !this.isDragging) {\n      if (!this.dragAxis) {\n        this._calculateDragDirection();\n      }\n\n      // Drag axis was detected, emit drag.start\n      if (this.dragAxis && !this.isDragging) {\n        if (this.isZooming) {\n          this.isZooming = false;\n          this.zoomLevels.end();\n        }\n\n        this.isDragging = true;\n        this._clearTapTimer(); // Tap can not trigger after drag\n\n        // Adjust starting point\n        this._updateStartPoints();\n        this._intervalTime = Date.now();\n        //this._startTime = this._intervalTime;\n        this._velocityCalculated = false;\n        equalizePoints(this._intervalP1, this.p1);\n        this.velocity.x = 0;\n        this.velocity.y = 0;\n        this.drag.start();\n\n        this._rafStopLoop();\n        this._rafRenderLoop();\n      }\n    } else if (this._numActivePoints > 1 && !this.isZooming) {\n      this._finishDrag();\n\n      this.isZooming = true;\n\n      // Adjust starting points\n      this._updateStartPoints();\n\n      this.zoomLevels.start();\n\n      this._rafStopLoop();\n      this._rafRenderLoop();\n    }\n  }\n\n  /**\n   * @private\n   */\n  _finishDrag() {\n    if (this.isDragging) {\n      this.isDragging = false;\n\n      // Try to calculate velocity,\n      // if it wasn't calculated yet in drag.change\n      if (!this._velocityCalculated) {\n        this._updateVelocity(true);\n      }\n\n      this.drag.end();\n      this.dragAxis = null;\n    }\n  }\n\n  /**\n   * @param {PointerEvent} e\n   */\n  onPointerUp(e) {\n    if (!this._numActivePoints) {\n      return;\n    }\n\n    this._updatePoints(e, 'up');\n\n    if (this.pswp.dispatch('pointerUp', { originalEvent: e }).defaultPrevented) {\n      return;\n    }\n\n    if (this._numActivePoints === 0) {\n      this._rafStopLoop();\n\n      if (this.isDragging) {\n        this._finishDrag();\n      } else if (!this.isZooming && !this.isMultitouch) {\n        //this.zoomLevels.correctZoomPan();\n        this._finishTap(e);\n      }\n    }\n\n    if (this._numActivePoints < 2 && this.isZooming) {\n      this.isZooming = false;\n      this.zoomLevels.end();\n\n      if (this._numActivePoints === 1) {\n        // Since we have 1 point left, we need to reinitiate drag\n        this.dragAxis = null;\n        this._updateStartPoints();\n      }\n    }\n  }\n\n  /**\n   * @private\n   */\n  _rafRenderLoop() {\n    if (this.isDragging || this.isZooming) {\n      this._updateVelocity();\n\n      if (this.isDragging) {\n        // make sure that pointer moved since the last update\n        if (!pointsEqual(this.p1, this.prevP1)) {\n          this.drag.change();\n        }\n      } else /* if (this.isZooming) */ {\n        if (!pointsEqual(this.p1, this.prevP1)\n            || !pointsEqual(this.p2, this.prevP2)) {\n          this.zoomLevels.change();\n        }\n      }\n\n      this._updatePrevPoints();\n      this.raf = requestAnimationFrame(this._rafRenderLoop.bind(this));\n    }\n  }\n\n  /**\n   * Update velocity at 50ms interval\n   *\n   * @private\n   * @param {boolean} [force]\n   */\n  _updateVelocity(force) {\n    const time = Date.now();\n    const duration = time - this._intervalTime;\n\n    if (duration < 50 && !force) {\n      return;\n    }\n\n\n    this.velocity.x = this._getVelocity('x', duration);\n    this.velocity.y = this._getVelocity('y', duration);\n\n    this._intervalTime = time;\n    equalizePoints(this._intervalP1, this.p1);\n    this._velocityCalculated = true;\n  }\n\n  /**\n   * @private\n   * @param {PointerEvent} e\n   */\n  _finishTap(e) {\n    const { mainScroll } = this.pswp;\n\n    // Do not trigger tap events if main scroll is shifted\n    if (mainScroll.isShifted()) {\n      // restore main scroll position\n      // (usually happens if stopped in the middle of animation)\n      mainScroll.moveIndexBy(0, true);\n      return;\n    }\n\n    // Do not trigger tap for touchcancel or pointercancel\n    if (e.type.indexOf('cancel') > 0) {\n      return;\n    }\n\n    // Trigger click instead of tap for mouse events\n    if (e.type === 'mouseup' || e.pointerType === 'mouse') {\n      this.tapHandler.click(this.startP1, e);\n      return;\n    }\n\n    // Disable delay if there is no doubleTapAction\n    const tapDelay = this.pswp.options.doubleTapAction ? DOUBLE_TAP_DELAY : 0;\n\n    // If tapTimer is defined - we tapped recently,\n    // check if the current tap is close to the previous one,\n    // if yes - trigger double tap\n    if (this._tapTimer) {\n      this._clearTapTimer();\n      // Check if two taps were more or less on the same place\n      if (getDistanceBetween(this._lastStartP1, this.startP1) < MIN_TAP_DISTANCE) {\n        this.tapHandler.doubleTap(this.startP1, e);\n      }\n    } else {\n      equalizePoints(this._lastStartP1, this.startP1);\n      this._tapTimer = setTimeout(() => {\n        this.tapHandler.tap(this.startP1, e);\n        this._clearTapTimer();\n      }, tapDelay);\n    }\n  }\n\n  /**\n   * @private\n   */\n  _clearTapTimer() {\n    if (this._tapTimer) {\n      clearTimeout(this._tapTimer);\n      this._tapTimer = null;\n    }\n  }\n\n  /**\n   * Get velocity for axis\n   *\n   * @private\n   * @param {'x' | 'y'} axis\n   * @param {number} duration\n   * @returns {number}\n   */\n  _getVelocity(axis, duration) {\n    // displacement is like distance, but can be negative.\n    const displacement = this.p1[axis] - this._intervalP1[axis];\n\n    if (Math.abs(displacement) > 1 && duration > 5) {\n      return displacement / duration;\n    }\n\n    return 0;\n  }\n\n  /**\n   * @private\n   */\n  _rafStopLoop() {\n    if (this.raf) {\n      cancelAnimationFrame(this.raf);\n      this.raf = null;\n    }\n  }\n\n  /**\n   * @private\n   * @param {PointerEvent} e\n   * @param {'up' | 'down' | 'move'} pointerType Normalized pointer type\n   */\n  _preventPointerEventBehaviour(e, pointerType) {\n    const preventPointerEvent = this.pswp.applyFilters('preventPointerEvent', true, e, pointerType);\n    if (preventPointerEvent) {\n      e.preventDefault();\n    }\n  }\n\n  /**\n   * Parses and normalizes points from the touch, mouse or pointer event.\n   * Updates p1 and p2.\n   *\n   * @private\n   * @param {PointerEvent | TouchEvent} e\n   * @param {'up' | 'down' | 'move'} pointerType Normalized pointer type\n   */\n  _updatePoints(e, pointerType) {\n    if (this._pointerEventEnabled) {\n      const pointerEvent = /** @type {PointerEvent} */ (e);\n      // Try to find the current pointer in ongoing pointers by its ID\n      const pointerIndex = this._ongoingPointers.findIndex((ongoingPointer) => {\n        return ongoingPointer.id === pointerEvent.pointerId;\n      });\n\n      if (pointerType === 'up' && pointerIndex > -1) {\n        // release the pointer - remove it from ongoing\n        this._ongoingPointers.splice(pointerIndex, 1);\n      } else if (pointerType === 'down' && pointerIndex === -1) {\n        // add new pointer\n        this._ongoingPointers.push(this._convertEventPosToPoint(pointerEvent, { x: 0, y: 0 }));\n      } else if (pointerIndex > -1) {\n        // update existing pointer\n        this._convertEventPosToPoint(pointerEvent, this._ongoingPointers[pointerIndex]);\n      }\n\n      this._numActivePoints = this._ongoingPointers.length;\n\n      // update points that PhotoSwipe uses\n      // to calculate position and scale\n      if (this._numActivePoints > 0) {\n        equalizePoints(this.p1, this._ongoingPointers[0]);\n      }\n\n      if (this._numActivePoints > 1) {\n        equalizePoints(this.p2, this._ongoingPointers[1]);\n      }\n    } else {\n      const touchEvent = /** @type {TouchEvent} */ (e);\n\n      this._numActivePoints = 0;\n      if (touchEvent.type.indexOf('touch') > -1) {\n        // Touch Event\n        // https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent\n        if (touchEvent.touches && touchEvent.touches.length > 0) {\n          this._convertEventPosToPoint(touchEvent.touches[0], this.p1);\n          this._numActivePoints++;\n          if (touchEvent.touches.length > 1) {\n            this._convertEventPosToPoint(touchEvent.touches[1], this.p2);\n            this._numActivePoints++;\n          }\n        }\n      } else {\n        // Mouse Event\n        this._convertEventPosToPoint(/** @type {PointerEvent} */ (e), this.p1);\n        if (pointerType === 'up') {\n          // clear all points on mouseup\n          this._numActivePoints = 0;\n        } else {\n          this._numActivePoints++;\n        }\n      }\n    }\n  }\n\n  /** update points that were used during previous rAF tick\n   * @private\n   */\n  _updatePrevPoints() {\n    equalizePoints(this.prevP1, this.p1);\n    equalizePoints(this.prevP2, this.p2);\n  }\n\n  /** update points at the start of gesture\n   * @private\n   */\n  _updateStartPoints() {\n    equalizePoints(this.startP1, this.p1);\n    equalizePoints(this.startP2, this.p2);\n    this._updatePrevPoints();\n  }\n\n  /** @private */\n  _calculateDragDirection() {\n    if (this.pswp.mainScroll.isShifted()) {\n      // if main scroll position is shifted – direction is always horizontal\n      this.dragAxis = 'x';\n    } else {\n      // calculate delta of the last touchmove tick\n      const diff = Math.abs(this.p1.x - this.startP1.x) - Math.abs(this.p1.y - this.startP1.y);\n\n      if (diff !== 0) {\n        // check if pointer was shifted horizontally or vertically\n        const axisToCheck = diff > 0 ? 'x' : 'y';\n\n        if (Math.abs(this.p1[axisToCheck] - this.startP1[axisToCheck]) >= AXIS_SWIPE_HYSTERISIS) {\n          this.dragAxis = axisToCheck;\n        }\n      }\n    }\n  }\n\n  /**\n   * Converts touch, pointer or mouse event\n   * to PhotoSwipe point.\n   *\n   * @private\n   * @param {Touch | PointerEvent} e\n   * @param {Point} p\n   * @returns {Point}\n   */\n  _convertEventPosToPoint(e, p) {\n    p.x = e.pageX - this.pswp.offset.x;\n    p.y = e.pageY - this.pswp.offset.y;\n\n    if ('pointerId' in e) {\n      p.id = e.pointerId;\n    } else if (e.identifier !== undefined) {\n      p.id = e.identifier;\n    }\n\n    return p;\n  }\n\n  /**\n   * @private\n   * @param {PointerEvent} e\n   */\n  _onClick(e) {\n    // Do not allow click event to pass through after drag\n    if (this.pswp.mainScroll.isShifted()) {\n      e.preventDefault();\n      e.stopPropagation();\n    }\n  }\n}\n\nexport default Gestures;\n"
  },
  {
    "path": "src/js/gestures/tap-handler.js",
    "content": "/**\n * @template {string} T\n * @template {string} P\n * @typedef {import('../types.js').AddPostfix<T, P>} AddPostfix<T, P>\n */\n\n/** @typedef {import('./gestures.js').default} Gestures */\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/** @typedef {'imageClick' | 'bgClick' | 'tap' | 'doubleTap'} Actions */\n\n/**\n * Whether the tap was performed on the main slide\n * (rather than controls or caption).\n *\n * @param {PointerEvent} event\n * @returns {boolean}\n */\nfunction didTapOnMainContent(event) {\n  return !!(/** @type {HTMLElement} */ (event.target).closest('.pswp__container'));\n}\n\n/**\n * Tap, double-tap handler.\n */\nclass TapHandler {\n  /**\n   * @param {Gestures} gestures\n   */\n  constructor(gestures) {\n    this.gestures = gestures;\n  }\n\n  /**\n   * @param {Point} point\n   * @param {PointerEvent} originalEvent\n   */\n  click(point, originalEvent) {\n    const targetClassList = /** @type {HTMLElement} */ (originalEvent.target).classList;\n    const isImageClick = targetClassList.contains('pswp__img');\n    const isBackgroundClick = targetClassList.contains('pswp__item')\n                              || targetClassList.contains('pswp__zoom-wrap');\n\n    if (isImageClick) {\n      this._doClickOrTapAction('imageClick', point, originalEvent);\n    } else if (isBackgroundClick) {\n      this._doClickOrTapAction('bgClick', point, originalEvent);\n    }\n  }\n\n  /**\n   * @param {Point} point\n   * @param {PointerEvent} originalEvent\n   */\n  tap(point, originalEvent) {\n    if (didTapOnMainContent(originalEvent)) {\n      this._doClickOrTapAction('tap', point, originalEvent);\n    }\n  }\n\n  /**\n   * @param {Point} point\n   * @param {PointerEvent} originalEvent\n   */\n  doubleTap(point, originalEvent) {\n    if (didTapOnMainContent(originalEvent)) {\n      this._doClickOrTapAction('doubleTap', point, originalEvent);\n    }\n  }\n\n  /**\n   * @private\n   * @param {Actions} actionName\n   * @param {Point} point\n   * @param {PointerEvent} originalEvent\n   */\n  _doClickOrTapAction(actionName, point, originalEvent) {\n    const { pswp } = this.gestures;\n    const { currSlide } = pswp;\n    const actionFullName = /** @type {AddPostfix<Actions, 'Action'>} */ (actionName + 'Action');\n    const optionValue = pswp.options[actionFullName];\n\n    if (pswp.dispatch(actionFullName, { point, originalEvent }).defaultPrevented) {\n      return;\n    }\n\n    if (typeof optionValue === 'function') {\n      optionValue.call(pswp, point, originalEvent);\n      return;\n    }\n\n    switch (optionValue) {\n      case 'close':\n      case 'next':\n        pswp[optionValue]();\n        break;\n      case 'zoom':\n        currSlide?.toggleZoom(point);\n        break;\n      case 'zoom-or-close':\n        // by default click zooms current image,\n        // if it can not be zoomed - gallery will be closed\n        if (currSlide?.isZoomable()\n            && currSlide.zoomLevels.secondary !== currSlide.zoomLevels.initial) {\n          currSlide.toggleZoom(point);\n        } else if (pswp.options.clickToCloseNonZoomable) {\n          pswp.close();\n        }\n        break;\n      case 'toggle-controls':\n        this.gestures.pswp.element?.classList.toggle('pswp--ui-visible');\n        // if (_controlsVisible) {\n        //   _ui.hideControls();\n        // } else {\n        //   _ui.showControls();\n        // }\n        break;\n    }\n  }\n}\n\nexport default TapHandler;\n"
  },
  {
    "path": "src/js/gestures/zoom-handler.js",
    "content": "import {\n  equalizePoints, getDistanceBetween, clamp, pointsEqual\n} from '../util/util.js';\n\n/** @typedef {import('../photoswipe.js').Point} Point */\n/** @typedef {import('./gestures.js').default} Gestures */\n\nconst UPPER_ZOOM_FRICTION = 0.05;\nconst LOWER_ZOOM_FRICTION = 0.15;\n\n\n/**\n * Get center point between two points\n *\n * @param {Point} p\n * @param {Point} p1\n * @param {Point} p2\n * @returns {Point}\n */\nfunction getZoomPointsCenter(p, p1, p2) {\n  p.x = (p1.x + p2.x) / 2;\n  p.y = (p1.y + p2.y) / 2;\n  return p;\n}\n\nclass ZoomHandler {\n  /**\n   * @param {Gestures} gestures\n   */\n  constructor(gestures) {\n    this.gestures = gestures;\n    /**\n     * @private\n     * @type {Point}\n     */\n    this._startPan = { x: 0, y: 0 };\n    /**\n     * @private\n     * @type {Point}\n     */\n    this._startZoomPoint = { x: 0, y: 0 };\n    /**\n     * @private\n     * @type {Point}\n     */\n    this._zoomPoint = { x: 0, y: 0 };\n    /** @private */\n    this._wasOverFitZoomLevel = false;\n    /** @private */\n    this._startZoomLevel = 1;\n  }\n\n  start() {\n    const { currSlide } = this.gestures.pswp;\n    if (currSlide) {\n      this._startZoomLevel = currSlide.currZoomLevel;\n      equalizePoints(this._startPan, currSlide.pan);\n    }\n\n    this.gestures.pswp.animations.stopAllPan();\n    this._wasOverFitZoomLevel = false;\n  }\n\n  change() {\n    const { p1, startP1, p2, startP2, pswp } = this.gestures;\n    const { currSlide } = pswp;\n\n    if (!currSlide) {\n      return;\n    }\n\n    const minZoomLevel = currSlide.zoomLevels.min;\n    const maxZoomLevel = currSlide.zoomLevels.max;\n\n    if (!currSlide.isZoomable() || pswp.mainScroll.isShifted()) {\n      return;\n    }\n\n    getZoomPointsCenter(this._startZoomPoint, startP1, startP2);\n    getZoomPointsCenter(this._zoomPoint, p1, p2);\n\n    let currZoomLevel = (1 / getDistanceBetween(startP1, startP2))\n                      * getDistanceBetween(p1, p2)\n                      * this._startZoomLevel;\n\n    // slightly over the zoom.fit\n    if (currZoomLevel > currSlide.zoomLevels.initial + (currSlide.zoomLevels.initial / 15)) {\n      this._wasOverFitZoomLevel = true;\n    }\n\n    if (currZoomLevel < minZoomLevel) {\n      if (pswp.options.pinchToClose\n          && !this._wasOverFitZoomLevel\n          && this._startZoomLevel <= currSlide.zoomLevels.initial) {\n        // fade out background if zooming out\n        const bgOpacity = 1 - ((minZoomLevel - currZoomLevel) / (minZoomLevel / 1.2));\n        if (!pswp.dispatch('pinchClose', { bgOpacity }).defaultPrevented) {\n          pswp.applyBgOpacity(bgOpacity);\n        }\n      } else {\n        // Apply the friction if zoom level is below the min\n        currZoomLevel = minZoomLevel - (minZoomLevel - currZoomLevel) * LOWER_ZOOM_FRICTION;\n      }\n    } else if (currZoomLevel > maxZoomLevel) {\n      // Apply the friction if zoom level is above the max\n      currZoomLevel = maxZoomLevel + (currZoomLevel - maxZoomLevel) * UPPER_ZOOM_FRICTION;\n    }\n\n    currSlide.pan.x = this._calculatePanForZoomLevel('x', currZoomLevel);\n    currSlide.pan.y = this._calculatePanForZoomLevel('y', currZoomLevel);\n\n    currSlide.setZoomLevel(currZoomLevel);\n    currSlide.applyCurrentZoomPan();\n  }\n\n  end() {\n    const { pswp } = this.gestures;\n    const { currSlide } = pswp;\n    if ((!currSlide || currSlide.currZoomLevel < currSlide.zoomLevels.initial)\n        && !this._wasOverFitZoomLevel\n        && pswp.options.pinchToClose) {\n      pswp.close();\n    } else {\n      this.correctZoomPan();\n    }\n  }\n\n  /**\n   * @private\n   * @param {'x' | 'y'} axis\n   * @param {number} currZoomLevel\n   * @returns {number}\n   */\n  _calculatePanForZoomLevel(axis, currZoomLevel) {\n    const zoomFactor = currZoomLevel / this._startZoomLevel;\n    return this._zoomPoint[axis]\n            - ((this._startZoomPoint[axis] - this._startPan[axis]) * zoomFactor);\n  }\n\n  /**\n   * Correct currZoomLevel and pan if they are\n   * beyond minimum or maximum values.\n   * With animation.\n   *\n   * @param {boolean} [ignoreGesture]\n   * Wether gesture coordinates should be ignored when calculating destination pan position.\n   */\n  correctZoomPan(ignoreGesture) {\n    const { pswp } = this.gestures;\n    const { currSlide } = pswp;\n\n    if (!currSlide?.isZoomable()) {\n      return;\n    }\n\n    if (this._zoomPoint.x === 0) {\n      ignoreGesture = true;\n    }\n\n    const prevZoomLevel = currSlide.currZoomLevel;\n\n    /** @type {number} */\n    let destinationZoomLevel;\n    let currZoomLevelNeedsChange = true;\n\n    if (prevZoomLevel < currSlide.zoomLevels.initial) {\n      destinationZoomLevel = currSlide.zoomLevels.initial;\n      // zoom to min\n    } else if (prevZoomLevel > currSlide.zoomLevels.max) {\n      destinationZoomLevel = currSlide.zoomLevels.max;\n      // zoom to max\n    } else {\n      currZoomLevelNeedsChange = false;\n      destinationZoomLevel = prevZoomLevel;\n    }\n\n    const initialBgOpacity = pswp.bgOpacity;\n    const restoreBgOpacity = pswp.bgOpacity < 1;\n\n    const initialPan = equalizePoints({ x: 0, y: 0 }, currSlide.pan);\n    let destinationPan = equalizePoints({ x: 0, y: 0 }, initialPan);\n\n    if (ignoreGesture) {\n      this._zoomPoint.x = 0;\n      this._zoomPoint.y = 0;\n      this._startZoomPoint.x = 0;\n      this._startZoomPoint.y = 0;\n      this._startZoomLevel = prevZoomLevel;\n      equalizePoints(this._startPan, initialPan);\n    }\n\n    if (currZoomLevelNeedsChange) {\n      destinationPan = {\n        x: this._calculatePanForZoomLevel('x', destinationZoomLevel),\n        y: this._calculatePanForZoomLevel('y', destinationZoomLevel)\n      };\n    }\n\n    // set zoom level, so pan bounds are updated according to it\n    currSlide.setZoomLevel(destinationZoomLevel);\n\n    destinationPan = {\n      x: currSlide.bounds.correctPan('x', destinationPan.x),\n      y: currSlide.bounds.correctPan('y', destinationPan.y)\n    };\n\n    // return zoom level and its bounds to initial\n    currSlide.setZoomLevel(prevZoomLevel);\n\n    const panNeedsChange = !pointsEqual(destinationPan, initialPan);\n\n    if (!panNeedsChange && !currZoomLevelNeedsChange && !restoreBgOpacity) {\n      // update resolution after gesture\n      currSlide._setResolution(destinationZoomLevel);\n      currSlide.applyCurrentZoomPan();\n\n      // nothing to animate\n      return;\n    }\n\n    pswp.animations.stopAllPan();\n\n    pswp.animations.startSpring({\n      isPan: true,\n      start: 0,\n      end: 1000,\n      velocity: 0,\n      dampingRatio: 1,\n      naturalFrequency: 40,\n      onUpdate: (now) => {\n        now /= 1000; // 0 - start, 1 - end\n\n        if (panNeedsChange || currZoomLevelNeedsChange) {\n          if (panNeedsChange) {\n            currSlide.pan.x = initialPan.x + (destinationPan.x - initialPan.x) * now;\n            currSlide.pan.y = initialPan.y + (destinationPan.y - initialPan.y) * now;\n          }\n\n          if (currZoomLevelNeedsChange) {\n            const newZoomLevel = prevZoomLevel\n                        + (destinationZoomLevel - prevZoomLevel) * now;\n            currSlide.setZoomLevel(newZoomLevel);\n          }\n\n          currSlide.applyCurrentZoomPan();\n        }\n\n        // Restore background opacity\n        if (restoreBgOpacity && pswp.bgOpacity < 1) {\n          // We clamp opacity to keep it between 0 and 1.\n          // As progress ratio can be larger than 1 due to overshoot,\n          // and we do not want to bounce opacity.\n          pswp.applyBgOpacity(clamp(\n            initialBgOpacity + (1 - initialBgOpacity) * now, 0, 1\n          ));\n        }\n      },\n      onComplete: () => {\n        // update resolution after transition ends\n        currSlide._setResolution(destinationZoomLevel);\n        currSlide.applyCurrentZoomPan();\n      }\n    });\n  }\n}\n\nexport default ZoomHandler;\n"
  },
  {
    "path": "src/js/keyboard.js",
    "content": "import { specialKeyUsed } from './util/util.js';\n\n/** @typedef {import('./photoswipe.js').default} PhotoSwipe */\n\n/**\n * @template T\n * @typedef {import('./types.js').Methods<T>} Methods<T>\n */\n\nconst KeyboardKeyCodesMap = {\n  Escape: 27,\n  z: 90,\n  ArrowLeft: 37,\n  ArrowUp: 38,\n  ArrowRight: 39,\n  ArrowDown: 40,\n  Tab: 9,\n};\n\n/**\n * @template {keyof KeyboardKeyCodesMap} T\n * @param {T} key\n * @param {boolean} isKeySupported\n * @returns {T | number | undefined}\n */\nconst getKeyboardEventKey = (key, isKeySupported) => {\n  return isKeySupported ? key : KeyboardKeyCodesMap[key];\n};\n\n/**\n * - Manages keyboard shortcuts.\n * - Helps trap focus within photoswipe.\n */\nclass Keyboard {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    /** @private */\n    this._wasFocused = false;\n\n    pswp.on('bindEvents', () => {\n      if (pswp.options.trapFocus) {\n        // Dialog was likely opened by keyboard if initial point is not defined\n        if (!pswp.options.initialPointerPos) {\n          // focus causes layout,\n          // which causes lag during the animation,\n          // that's why we delay it until the opener transition ends\n          this._focusRoot();\n        }\n\n        pswp.events.add(\n          document,\n          'focusin',\n          /** @type EventListener */(this._onFocusIn.bind(this))\n        );\n      }\n\n      pswp.events.add(document, 'keydown', /** @type EventListener */(this._onKeyDown.bind(this)));\n    });\n\n    const lastActiveElement = /** @type {HTMLElement} */ (document.activeElement);\n    pswp.on('destroy', () => {\n      if (pswp.options.returnFocus\n          && lastActiveElement\n          && this._wasFocused) {\n        lastActiveElement.focus();\n      }\n    });\n  }\n\n  /** @private */\n  _focusRoot() {\n    if (!this._wasFocused && this.pswp.element) {\n      this.pswp.element.focus();\n      this._wasFocused = true;\n    }\n  }\n\n  /**\n   * @private\n   * @param {KeyboardEvent} e\n   */\n  _onKeyDown(e) {\n    const { pswp } = this;\n\n    if (pswp.dispatch('keydown', { originalEvent: e }).defaultPrevented) {\n      return;\n    }\n\n    if (specialKeyUsed(e)) {\n      // don't do anything if special key pressed\n      // to prevent from overriding default browser actions\n      // for example, in Chrome on Mac cmd+arrow-left returns to previous page\n      return;\n    }\n\n    /** @type {Methods<PhotoSwipe> | undefined} */\n    let keydownAction;\n    /** @type {'x' | 'y' | undefined} */\n    let axis;\n    let isForward = false;\n    const isKeySupported = 'key' in e;\n\n    switch (isKeySupported ? e.key : e.keyCode) {\n      case getKeyboardEventKey('Escape', isKeySupported):\n        if (pswp.options.escKey) {\n          keydownAction = 'close';\n        }\n        break;\n      case getKeyboardEventKey('z', isKeySupported):\n        keydownAction = 'toggleZoom';\n        break;\n      case getKeyboardEventKey('ArrowLeft', isKeySupported):\n        axis = 'x';\n        break;\n      case getKeyboardEventKey('ArrowUp', isKeySupported):\n        axis = 'y';\n        break;\n      case getKeyboardEventKey('ArrowRight', isKeySupported):\n        axis = 'x';\n        isForward = true;\n        break;\n      case getKeyboardEventKey('ArrowDown', isKeySupported):\n        isForward = true;\n        axis = 'y';\n        break;\n      case getKeyboardEventKey('Tab', isKeySupported):\n        this._focusRoot();\n        break;\n      default:\n    }\n\n    // if left/right/top/bottom key\n    if (axis) {\n      // prevent page scroll\n      e.preventDefault();\n\n      const { currSlide } = pswp;\n\n      if (pswp.options.arrowKeys\n          && axis === 'x'\n          && pswp.getNumItems() > 1) {\n        keydownAction = isForward ? 'next' : 'prev';\n      } else if (currSlide && currSlide.currZoomLevel > currSlide.zoomLevels.fit) {\n        // up/down arrow keys pan the image vertically\n        // left/right arrow keys pan horizontally.\n        // Unless there is only one image,\n        // or arrowKeys option is disabled\n        currSlide.pan[axis] += isForward ? -80 : 80;\n        currSlide.panTo(currSlide.pan.x, currSlide.pan.y);\n      }\n    }\n\n    if (keydownAction) {\n      e.preventDefault();\n      // @ts-ignore\n      pswp[keydownAction]();\n    }\n  }\n\n  /**\n   * Trap focus inside photoswipe\n   *\n   * @private\n   * @param {FocusEvent} e\n   */\n  _onFocusIn(e) {\n    const { template } = this.pswp;\n    if (template\n        && document !== e.target\n        && template !== e.target\n        && !template.contains(/** @type {Node} */ (e.target))) {\n      // focus root element\n      template.focus();\n    }\n  }\n}\n\nexport default Keyboard;\n"
  },
  {
    "path": "src/js/lightbox/lightbox.js",
    "content": "import {\n  specialKeyUsed,\n  getElementsFromOption,\n  isPswpClass\n} from '../util/util.js';\n\nimport PhotoSwipeBase from '../core/base.js';\nimport { lazyLoadSlide } from '../slide/loader.js';\n\n/**\n * @template T\n * @typedef {import('../types.js').Type<T>} Type<T>\n */\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n/** @typedef {import('../photoswipe.js').DataSource} DataSource */\n/** @typedef {import('../photoswipe.js').Point} Point */\n/** @typedef {import('../slide/content.js').default} Content */\n/** @typedef {import('../core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */\n/** @typedef {import('../core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */\n\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {import('../core/eventable.js').EventCallback<T>} EventCallback<T>\n */\n\n/**\n * PhotoSwipe Lightbox\n *\n * - If user has unsupported browser it falls back to default browser action (just opens URL)\n * - Binds click event to links that should open PhotoSwipe\n * - parses DOM strcture for PhotoSwipe (retrieves large image URLs and sizes)\n * - Initializes PhotoSwipe\n *\n *\n * Loader options use the same object as PhotoSwipe, and supports such options:\n *\n * gallery - Element | Element[] | NodeList | string selector for the gallery element\n * children - Element | Element[] | NodeList | string selector for the gallery children\n *\n */\nclass PhotoSwipeLightbox extends PhotoSwipeBase {\n  /**\n   * @param {PhotoSwipeOptions} [options]\n   */\n  constructor(options) {\n    super();\n    /** @type {PhotoSwipeOptions} */\n    this.options = options || {};\n    this._uid = 0;\n    this.shouldOpen = false;\n    /**\n     * @private\n     * @type {Content | undefined}\n     */\n    this._preloadedContent = undefined;\n\n    this.onThumbnailsClick = this.onThumbnailsClick.bind(this);\n  }\n\n  /**\n   * Initialize lightbox, should be called only once.\n   * It's not included in the main constructor, so you may bind events before it.\n   */\n  init() {\n    // Bind click events to each gallery\n    getElementsFromOption(this.options.gallery, this.options.gallerySelector)\n      .forEach((galleryElement) => {\n        galleryElement.addEventListener('click', this.onThumbnailsClick, false);\n      });\n  }\n\n  /**\n   * @param {MouseEvent} e\n   */\n  onThumbnailsClick(e) {\n    // Exit and allow default browser action if:\n    if (specialKeyUsed(e) // ... if clicked with a special key (ctrl/cmd...)\n        || window.pswp) { // ... if PhotoSwipe is already open\n      return;\n    }\n\n    // If both clientX and clientY are 0 or not defined,\n    // the event is likely triggered by keyboard,\n    // so we do not pass the initialPoint\n    //\n    // Note that some screen readers emulate the mouse position,\n    // so it's not the ideal way to detect them.\n    //\n    /** @type {Point | null} */\n    let initialPoint = { x: e.clientX, y: e.clientY };\n\n    if (!initialPoint.x && !initialPoint.y) {\n      initialPoint = null;\n    }\n\n    let clickedIndex = this.getClickedIndex(e);\n    clickedIndex = this.applyFilters('clickedIndex', clickedIndex, e, this);\n    /** @type {DataSource} */\n    const dataSource = {\n      gallery: /** @type {HTMLElement} */ (e.currentTarget)\n    };\n\n    if (clickedIndex >= 0) {\n      e.preventDefault();\n      this.loadAndOpen(clickedIndex, dataSource, initialPoint);\n    }\n  }\n\n  /**\n   * Get index of gallery item that was clicked.\n   *\n   * @param {MouseEvent} e click event\n   * @returns {number}\n   */\n  getClickedIndex(e) {\n    // legacy option\n    if (this.options.getClickedIndexFn) {\n      return this.options.getClickedIndexFn.call(this, e);\n    }\n\n    const clickedTarget = /** @type {HTMLElement} */ (e.target);\n    const childElements = getElementsFromOption(\n      this.options.children,\n      this.options.childSelector,\n      /** @type {HTMLElement} */ (e.currentTarget)\n    );\n    const clickedChildIndex = childElements.findIndex(\n      child => child === clickedTarget || child.contains(clickedTarget)\n    );\n\n    if (clickedChildIndex !== -1) {\n      return clickedChildIndex;\n    } else if (this.options.children || this.options.childSelector) {\n      // click wasn't on a child element\n      return -1;\n    }\n\n    // There is only one item (which is the gallery)\n    return 0;\n  }\n\n  /**\n   * Load and open PhotoSwipe\n   *\n   * @param {number} index\n   * @param {DataSource} [dataSource]\n   * @param {Point | null} [initialPoint]\n   * @returns {boolean}\n   */\n  loadAndOpen(index, dataSource, initialPoint) {\n    // Check if the gallery is already open\n    if (window.pswp || !this.options) {\n      return false;\n    }\n\n    // Use the first gallery element if dataSource is not provided\n    if (!dataSource && this.options.gallery && this.options.children) {\n      const galleryElements = getElementsFromOption(this.options.gallery);\n      if (galleryElements[0]) {\n        dataSource = {\n          gallery: galleryElements[0]\n        };\n      }\n    }\n\n    // set initial index\n    this.options.index = index;\n\n    // define options for PhotoSwipe constructor\n    this.options.initialPointerPos = initialPoint;\n\n    this.shouldOpen = true;\n    this.preload(index, dataSource);\n    return true;\n  }\n\n  /**\n   * Load the main module and the slide content by index\n   *\n   * @param {number} index\n   * @param {DataSource} [dataSource]\n   */\n  preload(index, dataSource) {\n    const { options } = this;\n\n    if (dataSource) {\n      options.dataSource = dataSource;\n    }\n\n    // Add the main module\n    /** @type {Promise<Type<PhotoSwipe>>[]} */\n    const promiseArray = [];\n\n    const pswpModuleType = typeof options.pswpModule;\n    if (isPswpClass(options.pswpModule)) {\n      promiseArray.push(Promise.resolve(/** @type {Type<PhotoSwipe>} */ (options.pswpModule)));\n    } else if (pswpModuleType === 'string') {\n      throw new Error('pswpModule as string is no longer supported');\n    } else if (pswpModuleType === 'function') {\n      promiseArray.push(/** @type {() => Promise<Type<PhotoSwipe>>} */ (options.pswpModule)());\n    } else {\n      throw new Error('pswpModule is not valid');\n    }\n\n    // Add custom-defined promise, if any\n    if (typeof options.openPromise === 'function') {\n      // allow developers to perform some task before opening\n      promiseArray.push(options.openPromise());\n    }\n\n    if (options.preloadFirstSlide !== false && index >= 0) {\n      this._preloadedContent = lazyLoadSlide(index, this);\n    }\n\n    // Wait till all promises resolve and open PhotoSwipe\n    const uid = ++this._uid;\n    Promise.all(promiseArray).then((iterableModules) => {\n      if (this.shouldOpen) {\n        const mainModule = iterableModules[0];\n        this._openPhotoswipe(mainModule, uid);\n      }\n    });\n  }\n\n  /**\n   * @private\n   * @param {Type<PhotoSwipe> | { default: Type<PhotoSwipe> }} module\n   * @param {number} uid\n   */\n  _openPhotoswipe(module, uid) {\n    // Cancel opening if UID doesn't match the current one\n    // (if user clicked on another gallery item before current was loaded).\n    //\n    // Or if shouldOpen flag is set to false\n    // (developer may modify it via public API)\n    if (uid !== this._uid && this.shouldOpen) {\n      return;\n    }\n\n    this.shouldOpen = false;\n\n    // PhotoSwipe is already open\n    if (window.pswp) {\n      return;\n    }\n\n    /**\n     * Pass data to PhotoSwipe and open init\n     *\n     * @type {PhotoSwipe}\n     */\n    const pswp = typeof module === 'object'\n        ? new module.default(this.options) // eslint-disable-line\n        : new module(this.options); // eslint-disable-line\n\n    this.pswp = pswp;\n    window.pswp = pswp;\n\n    // map listeners from Lightbox to PhotoSwipe Core\n    /** @type {(keyof PhotoSwipeEventsMap)[]} */\n    (Object.keys(this._listeners)).forEach((name) => {\n      this._listeners[name]?.forEach((fn) => {\n        pswp.on(name, /** @type {EventCallback<typeof name>} */(fn));\n      });\n    });\n\n    // same with filters\n    /** @type {(keyof PhotoSwipeFiltersMap)[]} */\n    (Object.keys(this._filters)).forEach((name) => {\n      this._filters[name]?.forEach((filter) => {\n        pswp.addFilter(name, filter.fn, filter.priority);\n      });\n    });\n\n    if (this._preloadedContent) {\n      pswp.contentLoader.addToCache(this._preloadedContent);\n      this._preloadedContent = undefined;\n    }\n\n    pswp.on('destroy', () => {\n      // clean up public variables\n      this.pswp = undefined;\n      delete window.pswp;\n    });\n\n    pswp.init();\n  }\n\n  /**\n   * Unbinds all events, closes PhotoSwipe if it's open.\n   */\n  destroy() {\n    this.pswp?.destroy();\n\n    this.shouldOpen = false;\n    this._listeners = {};\n\n    getElementsFromOption(this.options.gallery, this.options.gallerySelector)\n      .forEach((galleryElement) => {\n        galleryElement.removeEventListener('click', this.onThumbnailsClick, false);\n      });\n  }\n}\n\nexport default PhotoSwipeLightbox;\n"
  },
  {
    "path": "src/js/main-scroll.js",
    "content": "import {\n  setTransform,\n  createElement,\n} from './util/util.js';\n\n/** @typedef {import('./photoswipe.js').default} PhotoSwipe */\n/** @typedef {import('./slide/slide.js').default} Slide */\n\n/** @typedef {{ el: HTMLDivElement; slide?: Slide }} ItemHolder */\n\nconst MAIN_SCROLL_END_FRICTION = 0.35;\n\n\n// const MIN_SWIPE_TRANSITION_DURATION = 250;\n// const MAX_SWIPE_TRABSITION_DURATION = 500;\n// const DEFAULT_SWIPE_TRANSITION_DURATION = 333;\n\n/**\n * Handles movement of the main scrolling container\n * (for example, it repositions when user swipes left or right).\n *\n * Also stores its state.\n */\nclass MainScroll {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    this.x = 0;\n    this.slideWidth = 0;\n    /** @private */\n    this._currPositionIndex = 0;\n    /** @private */\n    this._prevPositionIndex = 0;\n    /** @private */\n    this._containerShiftIndex = -1;\n\n    /** @type {ItemHolder[]} */\n    this.itemHolders = [];\n  }\n\n  /**\n   * Position the scroller and slide containers\n   * according to viewport size.\n   *\n   * @param {boolean} [resizeSlides] Whether slides content should resized\n   */\n  resize(resizeSlides) {\n    const { pswp } = this;\n    const newSlideWidth = Math.round(\n      pswp.viewportSize.x + pswp.viewportSize.x * pswp.options.spacing\n    );\n    // Mobile browsers might trigger a resize event during a gesture.\n    // (due to toolbar appearing or hiding).\n    // Avoid re-adjusting main scroll position if width wasn't changed\n    const slideWidthChanged = (newSlideWidth !== this.slideWidth);\n\n    if (slideWidthChanged) {\n      this.slideWidth = newSlideWidth;\n      this.moveTo(this.getCurrSlideX());\n    }\n\n    this.itemHolders.forEach((itemHolder, index) => {\n      if (slideWidthChanged) {\n        setTransform(itemHolder.el, (index + this._containerShiftIndex)\n                                    * this.slideWidth);\n      }\n\n      if (resizeSlides && itemHolder.slide) {\n        itemHolder.slide.resize();\n      }\n    });\n  }\n\n  /**\n   * Reset X position of the main scroller to zero\n   */\n  resetPosition() {\n    // Position on the main scroller (offset)\n    // it is independent from slide index\n    this._currPositionIndex = 0;\n    this._prevPositionIndex = 0;\n\n    // This will force recalculation of size on next resize()\n    this.slideWidth = 0;\n\n    // _containerShiftIndex*viewportSize will give you amount of transform of the current slide\n    this._containerShiftIndex = -1;\n  }\n\n  /**\n   * Create and append array of three items\n   * that hold data about slides in DOM\n   */\n  appendHolders() {\n    this.itemHolders = [];\n\n    // append our three slide holders -\n    // previous, current, and next\n    for (let i = 0; i < 3; i++) {\n      const el = createElement('pswp__item', 'div', this.pswp.container);\n      el.setAttribute('role', 'group');\n      el.setAttribute('aria-roledescription', 'slide');\n      el.setAttribute('aria-hidden', 'true');\n\n      // hide nearby item holders until initial zoom animation finishes (to avoid extra Paints)\n      el.style.display = (i === 1) ? 'block' : 'none';\n\n      this.itemHolders.push({\n        el,\n        //index: -1\n      });\n    }\n  }\n\n  /**\n   * Whether the main scroll can be horizontally swiped to the next or previous slide.\n   * @returns {boolean}\n   */\n  canBeSwiped() {\n    return this.pswp.getNumItems() > 1;\n  }\n\n  /**\n   * Move main scroll by X amount of slides.\n   * For example:\n   *   `-1` will move to the previous slide,\n   *    `0` will reset the scroll position of the current slide,\n   *    `3` will move three slides forward\n   *\n   * If loop option is enabled - index will be automatically looped too,\n   * (for example `-1` will move to the last slide of the gallery).\n   *\n   * @param {number} diff\n   * @param {boolean} [animate]\n   * @param {number} [velocityX]\n   * @returns {boolean} whether index was changed or not\n   */\n  moveIndexBy(diff, animate, velocityX) {\n    const { pswp } = this;\n    let newIndex = pswp.potentialIndex + diff;\n    const numSlides = pswp.getNumItems();\n\n    if (pswp.canLoop()) {\n      newIndex = pswp.getLoopedIndex(newIndex);\n      const distance = (diff + numSlides) % numSlides;\n      if (distance <= numSlides / 2) {\n        // go forward\n        diff = distance;\n      } else {\n        // go backwards\n        diff = distance - numSlides;\n      }\n    } else {\n      if (newIndex < 0) {\n        newIndex = 0;\n      } else if (newIndex >= numSlides) {\n        newIndex = numSlides - 1;\n      }\n      diff = newIndex - pswp.potentialIndex;\n    }\n\n    pswp.potentialIndex = newIndex;\n    this._currPositionIndex -= diff;\n\n    pswp.animations.stopMainScroll();\n\n    const destinationX = this.getCurrSlideX();\n    if (!animate) {\n      this.moveTo(destinationX);\n      this.updateCurrItem();\n    } else {\n      pswp.animations.startSpring({\n        isMainScroll: true,\n        start: this.x,\n        end: destinationX,\n        velocity: velocityX || 0,\n        naturalFrequency: 30,\n        dampingRatio: 1, //0.7,\n        onUpdate: (x) => {\n          this.moveTo(x);\n        },\n        onComplete: () => {\n          this.updateCurrItem();\n          pswp.appendHeavy();\n        }\n      });\n\n      let currDiff = pswp.potentialIndex - pswp.currIndex;\n      if (pswp.canLoop()) {\n        const currDistance = (currDiff + numSlides) % numSlides;\n        if (currDistance <= numSlides / 2) {\n          // go forward\n          currDiff = currDistance;\n        } else {\n          // go backwards\n          currDiff = currDistance - numSlides;\n        }\n      }\n\n      // Force-append new slides during transition\n      // if difference between slides is more than 1\n      if (Math.abs(currDiff) > 1) {\n        this.updateCurrItem();\n      }\n    }\n\n    return Boolean(diff);\n  }\n\n  /**\n   * X position of the main scroll for the current slide\n   * (ignores position during dragging)\n   * @returns {number}\n   */\n  getCurrSlideX() {\n    return this.slideWidth * this._currPositionIndex;\n  }\n\n  /**\n   * Whether scroll position is shifted.\n   * For example, it will return true if the scroll is being dragged or animated.\n   * @returns {boolean}\n   */\n  isShifted() {\n    return this.x !== this.getCurrSlideX();\n  }\n\n  /**\n   * Update slides X positions and set their content\n   */\n  updateCurrItem() {\n    const { pswp } = this;\n    const positionDifference = this._prevPositionIndex - this._currPositionIndex;\n\n    if (!positionDifference) {\n      return;\n    }\n\n    this._prevPositionIndex = this._currPositionIndex;\n\n    pswp.currIndex = pswp.potentialIndex;\n\n    let diffAbs = Math.abs(positionDifference);\n    /** @type {ItemHolder | undefined} */\n    let tempHolder;\n\n    if (diffAbs >= 3) {\n      this._containerShiftIndex += positionDifference + (positionDifference > 0 ? -3 : 3);\n      diffAbs = 3;\n\n      // If slides are changed by 3 screens or more - clean up previous slides\n      this.itemHolders.forEach((itemHolder) => {\n        itemHolder.slide?.destroy();\n        itemHolder.slide = undefined;\n      });\n    }\n\n    for (let i = 0; i < diffAbs; i++) {\n      if (positionDifference > 0) {\n        tempHolder = this.itemHolders.shift();\n        if (tempHolder) {\n          this.itemHolders[2] = tempHolder; // move first to last\n\n          this._containerShiftIndex++;\n\n          setTransform(tempHolder.el, (this._containerShiftIndex + 2) * this.slideWidth);\n\n          pswp.setContent(tempHolder, (pswp.currIndex - diffAbs) + i + 2);\n        }\n      } else {\n        tempHolder = this.itemHolders.pop();\n        if (tempHolder) {\n          this.itemHolders.unshift(tempHolder); // move last to first\n\n          this._containerShiftIndex--;\n\n          setTransform(tempHolder.el, this._containerShiftIndex * this.slideWidth);\n\n          pswp.setContent(tempHolder, (pswp.currIndex + diffAbs) - i - 2);\n        }\n      }\n    }\n\n    // Reset transfrom every 50ish navigations in one direction.\n    //\n    // Otherwise transform will keep growing indefinitely,\n    // which might cause issues as browsers have a maximum transform limit.\n    // I wasn't able to reach it, but just to be safe.\n    // This should not cause noticable lag.\n    if (Math.abs(this._containerShiftIndex) > 50 && !this.isShifted()) {\n      this.resetPosition();\n      this.resize();\n    }\n\n    // Pan transition might be running (and consntantly updating pan position)\n    pswp.animations.stopAllPan();\n\n    this.itemHolders.forEach((itemHolder, i) => {\n      if (itemHolder.slide) {\n        // Slide in the 2nd holder is always active\n        itemHolder.slide.setIsActive(i === 1);\n      }\n    });\n\n    pswp.currSlide = this.itemHolders[1]?.slide;\n    pswp.contentLoader.updateLazy(positionDifference);\n\n    if (pswp.currSlide) {\n      pswp.currSlide.applyCurrentZoomPan();\n    }\n\n    pswp.dispatch('change');\n  }\n\n  /**\n   * Move the X position of the main scroll container\n   *\n   * @param {number} x\n   * @param {boolean} [dragging]\n   */\n  moveTo(x, dragging) {\n    if (!this.pswp.canLoop() && dragging) {\n      // Apply friction\n      let newSlideIndexOffset = ((this.slideWidth * this._currPositionIndex) - x) / this.slideWidth;\n      newSlideIndexOffset += this.pswp.currIndex;\n      const delta = Math.round(x - this.x);\n\n      if ((newSlideIndexOffset < 0 && delta > 0)\n          || (newSlideIndexOffset >= this.pswp.getNumItems() - 1 && delta < 0)) {\n        x = this.x + (delta * MAIN_SCROLL_END_FRICTION);\n      }\n    }\n\n    this.x = x;\n\n    if (this.pswp.container) {\n      setTransform(this.pswp.container, x);\n    }\n\n    this.pswp.dispatch('moveMainScroll', { x, dragging: dragging ?? false });\n  }\n}\n\nexport default MainScroll;\n"
  },
  {
    "path": "src/js/opener.js",
    "content": "import {\n  setTransform,\n  equalizePoints,\n  decodeImage,\n  toTransformString\n} from './util/util.js';\n\n/** @typedef {import('./photoswipe.js').default} PhotoSwipe */\n/** @typedef {import('./slide/get-thumb-bounds.js').Bounds} Bounds */\n/** @typedef {import('./util/animations.js').AnimationProps} AnimationProps */\n\n// some browsers do not paint\n// elements which opacity is set to 0,\n// since we need to pre-render elements for the animation -\n// we set it to the minimum amount\nconst MIN_OPACITY = 0.003;\n\n/**\n * Manages opening and closing transitions of the PhotoSwipe.\n *\n * It can perform zoom, fade or no transition.\n */\nclass Opener {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    this.isClosed = true;\n    this.isOpen = false;\n    this.isClosing = false;\n    this.isOpening = false;\n    /**\n     * @private\n     * @type {number | false | undefined}\n     */\n    this._duration = undefined;\n    /** @private */\n    this._useAnimation = false;\n    /** @private */\n    this._croppedZoom = false;\n    /** @private */\n    this._animateRootOpacity = false;\n    /** @private */\n    this._animateBgOpacity = false;\n    /**\n     * @private\n     * @type { HTMLDivElement | HTMLImageElement | null | undefined }\n     */\n    this._placeholder = undefined;\n    /**\n     * @private\n     * @type { HTMLDivElement | undefined }\n     */\n    this._opacityElement = undefined;\n    /**\n     * @private\n     * @type { HTMLDivElement | undefined }\n     */\n    this._cropContainer1 = undefined;\n    /**\n     * @private\n     * @type { HTMLElement | null | undefined }\n     */\n    this._cropContainer2 = undefined;\n\n    /**\n     * @private\n     * @type {Bounds | undefined}\n     */\n    this._thumbBounds = undefined;\n\n\n    this._prepareOpen = this._prepareOpen.bind(this);\n\n    // Override initial zoom and pan position\n    pswp.on('firstZoomPan', this._prepareOpen);\n  }\n\n  open() {\n    this._prepareOpen();\n    this._start();\n  }\n\n  close() {\n    if (this.isClosed || this.isClosing || this.isOpening) {\n      // if we close during opening animation\n      // for now do nothing,\n      // browsers aren't good at changing the direction of the CSS transition\n      return;\n    }\n\n    const slide = this.pswp.currSlide;\n\n    this.isOpen = false;\n    this.isOpening = false;\n    this.isClosing = true;\n    this._duration = this.pswp.options.hideAnimationDuration;\n\n    if (slide && slide.currZoomLevel * slide.width >= this.pswp.options.maxWidthToAnimate) {\n      this._duration = 0;\n    }\n\n    this._applyStartProps();\n    setTimeout(() => {\n      this._start();\n    }, this._croppedZoom ? 30 : 0);\n  }\n\n  /** @private */\n  _prepareOpen() {\n    this.pswp.off('firstZoomPan', this._prepareOpen);\n    if (!this.isOpening) {\n      const slide = this.pswp.currSlide;\n      this.isOpening = true;\n      this.isClosing = false;\n      this._duration = this.pswp.options.showAnimationDuration;\n      if (slide && slide.zoomLevels.initial * slide.width >= this.pswp.options.maxWidthToAnimate) {\n        this._duration = 0;\n      }\n      this._applyStartProps();\n    }\n  }\n\n  /** @private */\n  _applyStartProps() {\n    const { pswp } = this;\n    const slide = this.pswp.currSlide;\n    const { options } = pswp;\n\n    if (options.showHideAnimationType === 'fade') {\n      options.showHideOpacity = true;\n      this._thumbBounds = undefined;\n    } else if (options.showHideAnimationType === 'none') {\n      options.showHideOpacity = false;\n      this._duration = 0;\n      this._thumbBounds = undefined;\n    } else if (this.isOpening && pswp._initialThumbBounds) {\n      // Use initial bounds if defined\n      this._thumbBounds = pswp._initialThumbBounds;\n    } else {\n      this._thumbBounds = this.pswp.getThumbBounds();\n    }\n\n    this._placeholder = slide?.getPlaceholderElement();\n\n    pswp.animations.stopAll();\n\n    // Discard animations when duration is less than 50ms\n    this._useAnimation = Boolean(this._duration && this._duration > 50);\n    this._animateZoom = Boolean(this._thumbBounds)\n                        && slide?.content.usePlaceholder()\n                        && (!this.isClosing || !pswp.mainScroll.isShifted());\n    if (!this._animateZoom) {\n      this._animateRootOpacity = true;\n\n      if (this.isOpening && slide) {\n        slide.zoomAndPanToInitial();\n        slide.applyCurrentZoomPan();\n      }\n    } else {\n      this._animateRootOpacity = options.showHideOpacity ?? false;\n    }\n    this._animateBgOpacity = !this._animateRootOpacity && this.pswp.options.bgOpacity > MIN_OPACITY;\n    this._opacityElement = this._animateRootOpacity ? pswp.element : pswp.bg;\n\n    if (!this._useAnimation) {\n      this._duration = 0;\n      this._animateZoom = false;\n      this._animateBgOpacity = false;\n      this._animateRootOpacity = true;\n      if (this.isOpening) {\n        if (pswp.element) {\n          pswp.element.style.opacity = String(MIN_OPACITY);\n        }\n        pswp.applyBgOpacity(1);\n      }\n      return;\n    }\n\n    if (this._animateZoom && this._thumbBounds && this._thumbBounds.innerRect) {\n      // Properties are used when animation from cropped thumbnail\n      this._croppedZoom = true;\n      this._cropContainer1 = this.pswp.container;\n      this._cropContainer2 = this.pswp.currSlide?.holderElement;\n\n      if (pswp.container) {\n        pswp.container.style.overflow = 'hidden';\n        pswp.container.style.width = pswp.viewportSize.x + 'px';\n      }\n    } else {\n      this._croppedZoom = false;\n    }\n\n    if (this.isOpening) {\n      // Apply styles before opening transition\n      if (this._animateRootOpacity) {\n        if (pswp.element) {\n          pswp.element.style.opacity = String(MIN_OPACITY);\n        }\n        pswp.applyBgOpacity(1);\n      } else {\n        if (this._animateBgOpacity && pswp.bg) {\n          pswp.bg.style.opacity = String(MIN_OPACITY);\n        }\n        if (pswp.element) {\n          pswp.element.style.opacity = '1';\n        }\n      }\n\n      if (this._animateZoom) {\n        this._setClosedStateZoomPan();\n        if (this._placeholder) {\n          // tell browser that we plan to animate the placeholder\n          this._placeholder.style.willChange = 'transform';\n\n          // hide placeholder to allow hiding of\n          // elements that overlap it (such as icons over the thumbnail)\n          this._placeholder.style.opacity = String(MIN_OPACITY);\n        }\n      }\n    } else if (this.isClosing) {\n      // hide nearby slides to make sure that\n      // they are not painted during the transition\n      if (pswp.mainScroll.itemHolders[0]) {\n        pswp.mainScroll.itemHolders[0].el.style.display = 'none';\n      }\n      if (pswp.mainScroll.itemHolders[2]) {\n        pswp.mainScroll.itemHolders[2].el.style.display = 'none';\n      }\n\n      if (this._croppedZoom) {\n        if (pswp.mainScroll.x !== 0) {\n          // shift the main scroller to zero position\n          pswp.mainScroll.resetPosition();\n          pswp.mainScroll.resize();\n        }\n      }\n    }\n  }\n\n  /** @private */\n  _start() {\n    if (this.isOpening\n        && this._useAnimation\n        && this._placeholder\n        && this._placeholder.tagName === 'IMG') {\n      // To ensure smooth animation\n      // we wait till the current slide image placeholder is decoded,\n      // but no longer than 250ms,\n      // and no shorter than 50ms\n      // (just using requestanimationframe is not enough in Firefox,\n      // for some reason)\n      new Promise((resolve) => {\n        let decoded = false;\n        let isDelaying = true;\n        decodeImage(/** @type {HTMLImageElement} */ (this._placeholder)).finally(() => {\n          decoded = true;\n          if (!isDelaying) {\n            resolve(true);\n          }\n        });\n        setTimeout(() => {\n          isDelaying = false;\n          if (decoded) {\n            resolve(true);\n          }\n        }, 50);\n        setTimeout(resolve, 250);\n      }).finally(() => this._initiate());\n    } else {\n      this._initiate();\n    }\n  }\n\n  /** @private */\n  _initiate() {\n    this.pswp.element?.style.setProperty('--pswp-transition-duration', this._duration + 'ms');\n\n    this.pswp.dispatch(\n      this.isOpening ? 'openingAnimationStart' : 'closingAnimationStart'\n    );\n\n    // legacy event\n    this.pswp.dispatch(\n      /** @type {'initialZoomIn' | 'initialZoomOut'} */\n      ('initialZoom' + (this.isOpening ? 'In' : 'Out'))\n    );\n\n    this.pswp.element?.classList.toggle('pswp--ui-visible', this.isOpening);\n\n    if (this.isOpening) {\n      if (this._placeholder) {\n        // unhide the placeholder\n        this._placeholder.style.opacity = '1';\n      }\n      this._animateToOpenState();\n    } else if (this.isClosing) {\n      this._animateToClosedState();\n    }\n\n    if (!this._useAnimation) {\n      this._onAnimationComplete();\n    }\n  }\n\n  /** @private */\n  _onAnimationComplete() {\n    const { pswp } = this;\n    this.isOpen = this.isOpening;\n    this.isClosed = this.isClosing;\n    this.isOpening = false;\n    this.isClosing = false;\n\n    pswp.dispatch(\n      this.isOpen ? 'openingAnimationEnd' : 'closingAnimationEnd'\n    );\n\n    // legacy event\n    pswp.dispatch(\n      /** @type {'initialZoomInEnd' | 'initialZoomOutEnd'} */\n      ('initialZoom' + (this.isOpen ? 'InEnd' : 'OutEnd'))\n    );\n\n    if (this.isClosed) {\n      pswp.destroy();\n    } else if (this.isOpen) {\n      if (this._animateZoom && pswp.container) {\n        pswp.container.style.overflow = 'visible';\n        pswp.container.style.width = '100%';\n      }\n      pswp.currSlide?.applyCurrentZoomPan();\n    }\n  }\n\n  /** @private */\n  _animateToOpenState() {\n    const { pswp } = this;\n    if (this._animateZoom) {\n      if (this._croppedZoom && this._cropContainer1 && this._cropContainer2) {\n        this._animateTo(this._cropContainer1, 'transform', 'translate3d(0,0,0)');\n        this._animateTo(this._cropContainer2, 'transform', 'none');\n      }\n\n      if (pswp.currSlide) {\n        pswp.currSlide.zoomAndPanToInitial();\n        this._animateTo(\n          pswp.currSlide.container,\n          'transform',\n          pswp.currSlide.getCurrentTransform()\n        );\n      }\n    }\n\n    if (this._animateBgOpacity && pswp.bg) {\n      this._animateTo(pswp.bg, 'opacity', String(pswp.options.bgOpacity));\n    }\n\n    if (this._animateRootOpacity && pswp.element) {\n      this._animateTo(pswp.element, 'opacity', '1');\n    }\n  }\n\n  /** @private */\n  _animateToClosedState() {\n    const { pswp } = this;\n\n    if (this._animateZoom) {\n      this._setClosedStateZoomPan(true);\n    }\n\n    // do not animate opacity if it's already at 0\n    if (this._animateBgOpacity && pswp.bgOpacity > 0.01 && pswp.bg) {\n      this._animateTo(pswp.bg, 'opacity', '0');\n    }\n\n    if (this._animateRootOpacity && pswp.element) {\n      this._animateTo(pswp.element, 'opacity', '0');\n    }\n  }\n\n  /**\n   * @private\n   * @param {boolean} [animate]\n   */\n  _setClosedStateZoomPan(animate) {\n    if (!this._thumbBounds) return;\n\n    const { pswp } = this;\n    const { innerRect } = this._thumbBounds;\n    const { currSlide, viewportSize } = pswp;\n\n    if (this._croppedZoom && innerRect && this._cropContainer1 && this._cropContainer2) {\n      const containerOnePanX = -viewportSize.x + (this._thumbBounds.x - innerRect.x) + innerRect.w;\n      const containerOnePanY = -viewportSize.y + (this._thumbBounds.y - innerRect.y) + innerRect.h;\n      const containerTwoPanX = viewportSize.x - innerRect.w;\n      const containerTwoPanY = viewportSize.y - innerRect.h;\n\n\n      if (animate) {\n        this._animateTo(\n          this._cropContainer1,\n          'transform',\n          toTransformString(containerOnePanX, containerOnePanY)\n        );\n\n        this._animateTo(\n          this._cropContainer2,\n          'transform',\n          toTransformString(containerTwoPanX, containerTwoPanY)\n        );\n      } else {\n        setTransform(this._cropContainer1, containerOnePanX, containerOnePanY);\n        setTransform(this._cropContainer2, containerTwoPanX, containerTwoPanY);\n      }\n    }\n\n    if (currSlide) {\n      equalizePoints(currSlide.pan, innerRect || this._thumbBounds);\n      currSlide.currZoomLevel = this._thumbBounds.w / currSlide.width;\n      if (animate) {\n        this._animateTo(currSlide.container, 'transform', currSlide.getCurrentTransform());\n      } else {\n        currSlide.applyCurrentZoomPan();\n      }\n    }\n  }\n\n  /**\n   * @private\n   * @param {HTMLElement} target\n   * @param {'transform' | 'opacity'} prop\n   * @param {string} propValue\n   */\n  _animateTo(target, prop, propValue) {\n    if (!this._duration) {\n      target.style[prop] = propValue;\n      return;\n    }\n\n    const { animations } = this.pswp;\n    /** @type {AnimationProps} */\n    const animProps = {\n      duration: this._duration,\n      easing: this.pswp.options.easing,\n      onComplete: () => {\n        if (!animations.activeAnimations.length) {\n          this._onAnimationComplete();\n        }\n      },\n      target,\n    };\n    animProps[prop] = propValue;\n    animations.startTransition(animProps);\n  }\n}\n\nexport default Opener;\n"
  },
  {
    "path": "src/js/photoswipe.js",
    "content": "import {\n  createElement,\n  equalizePoints,\n  pointsEqual,\n  clamp,\n} from './util/util.js';\n\nimport DOMEvents from './util/dom-events.js';\nimport Slide from './slide/slide.js';\nimport Gestures from './gestures/gestures.js';\nimport MainScroll from './main-scroll.js';\n\nimport Keyboard from './keyboard.js';\nimport Animations from './util/animations.js';\nimport ScrollWheel from './scroll-wheel.js';\nimport UI from './ui/ui.js';\nimport { getViewportSize } from './util/viewport-size.js';\nimport { getThumbBounds } from './slide/get-thumb-bounds.js';\nimport PhotoSwipeBase from './core/base.js';\nimport Opener from './opener.js';\nimport ContentLoader from './slide/loader.js';\n\n/**\n * @template T\n * @typedef {import('./types.js').Type<T>} Type<T>\n */\n\n/** @typedef {import('./slide/slide.js').SlideData} SlideData */\n/** @typedef {import('./slide/zoom-level.js').ZoomLevelOption} ZoomLevelOption */\n/** @typedef {import('./ui/ui-element.js').UIElementData} UIElementData */\n/** @typedef {import('./main-scroll.js').ItemHolder} ItemHolder */\n/** @typedef {import('./core/eventable.js').PhotoSwipeEventsMap} PhotoSwipeEventsMap */\n/** @typedef {import('./core/eventable.js').PhotoSwipeFiltersMap} PhotoSwipeFiltersMap */\n/** @typedef {import('./slide/get-thumb-bounds').Bounds} Bounds */\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {import('./core/eventable.js').EventCallback<T>} EventCallback<T>\n */\n/**\n * @template {keyof PhotoSwipeEventsMap} T\n * @typedef {import('./core/eventable.js').AugmentedEvent<T>} AugmentedEvent<T>\n */\n\n/** @typedef {{ x: number; y: number; id?: string | number }} Point */\n/** @typedef {{ top: number; bottom: number; left: number; right: number }} Padding */\n/** @typedef {SlideData[]} DataSourceArray */\n/** @typedef {{ gallery: HTMLElement; items?: HTMLElement[] }} DataSourceObject */\n/** @typedef {DataSourceArray | DataSourceObject} DataSource */\n/** @typedef {(point: Point, originalEvent: PointerEvent) => void} ActionFn */\n/** @typedef {'close' | 'next' | 'zoom' | 'zoom-or-close' | 'toggle-controls'} ActionType */\n/** @typedef {Type<PhotoSwipe> | { default: Type<PhotoSwipe> }} PhotoSwipeModule */\n/** @typedef {PhotoSwipeModule | Promise<PhotoSwipeModule> | (() => Promise<PhotoSwipeModule>)} PhotoSwipeModuleOption */\n\n/**\n * @typedef {string | NodeListOf<HTMLElement> | HTMLElement[] | HTMLElement} ElementProvider\n */\n\n/** @typedef {Partial<PreparedPhotoSwipeOptions>} PhotoSwipeOptions https://photoswipe.com/options/ */\n/**\n * @typedef {Object} PreparedPhotoSwipeOptions\n *\n * @prop {DataSource} [dataSource]\n * Pass an array of any items via dataSource option. Its length will determine amount of slides\n * (which may be modified further from numItems event).\n *\n * Each item should contain data that you need to generate slide\n * (for image slide it would be src (image URL), width (image width), height, srcset, alt).\n *\n * If these properties are not present in your initial array, you may \"pre-parse\" each item from itemData filter.\n *\n * @prop {number} bgOpacity\n * Background backdrop opacity, always define it via this option and not via CSS rgba color.\n *\n * @prop {number} spacing\n * Spacing between slides. Defined as ratio relative to the viewport width (0.1 = 10% of viewport).\n *\n * @prop {boolean} allowPanToNext\n * Allow swipe navigation to the next slide when the current slide is zoomed. Does not apply to mouse events.\n *\n * @prop {boolean} loop\n * If set to true you'll be able to swipe from the last to the first image.\n * Option is always false when there are less than 3 slides.\n *\n * @prop {boolean} [wheelToZoom]\n * By default PhotoSwipe zooms image with ctrl-wheel, if you enable this option - image will zoom just via wheel.\n *\n * @prop {boolean} pinchToClose\n * Pinch touch gesture to close the gallery.\n *\n * @prop {boolean} closeOnVerticalDrag\n * Vertical drag gesture to close the PhotoSwipe.\n *\n * @prop {Padding} [padding]\n * Slide area padding (in pixels).\n *\n * @prop {(viewportSize: Point, itemData: SlideData, index: number) => Padding} [paddingFn]\n * The option is checked frequently, so make sure it's performant. Overrides padding option if defined. For example:\n *\n * @prop {number | false} hideAnimationDuration\n * Transition duration in milliseconds, can be 0.\n *\n * @prop {number | false} showAnimationDuration\n * Transition duration in milliseconds, can be 0.\n *\n * @prop {number | false} zoomAnimationDuration\n * Transition duration in milliseconds, can be 0.\n *\n * @prop {string} easing\n * String, 'cubic-bezier(.4,0,.22,1)'. CSS easing function for open/close/zoom transitions.\n *\n * @prop {boolean} escKey\n * Esc key to close.\n *\n * @prop {boolean} arrowKeys\n * Left/right arrow keys for navigation.\n *\n * @prop {boolean} trapFocus\n * Trap focus within PhotoSwipe element while it's open.\n *\n * @prop {boolean} returnFocus\n * Restore focus the last active element after PhotoSwipe is closed.\n *\n * @prop {boolean} clickToCloseNonZoomable\n * If image is not zoomable (for example, smaller than viewport) it can be closed by clicking on it.\n *\n * @prop {ActionType | ActionFn | false} imageClickAction\n * Refer to click and tap actions page.\n *\n * @prop {ActionType | ActionFn | false} bgClickAction\n * Refer to click and tap actions page.\n *\n * @prop {ActionType | ActionFn | false} tapAction\n * Refer to click and tap actions page.\n *\n * @prop {ActionType | ActionFn | false} doubleTapAction\n * Refer to click and tap actions page.\n *\n * @prop {number} preloaderDelay\n * Delay before the loading indicator will be displayed,\n * if image is loaded during it - the indicator will not be displayed at all. Can be zero.\n *\n * @prop {string} indexIndicatorSep\n * Used for slide count indicator (\"1 of 10 \").\n *\n * @prop {(options: PhotoSwipeOptions, pswp: PhotoSwipeBase) => Point} [getViewportSizeFn]\n * A function that should return slide viewport width and height, in format {x: 100, y: 100}.\n *\n * @prop {string} errorMsg\n * Message to display when the image wasn't able to load. If you need to display HTML - use contentErrorElement filter.\n *\n * @prop {[number, number]} preload\n * Lazy loading of nearby slides based on direction of movement. Should be an array with two integers,\n * first one - number of items to preload before the current image, second one - after the current image.\n * Two nearby images are always loaded.\n *\n * @prop {string} [mainClass]\n * Class that will be added to the root element of PhotoSwipe, may contain multiple separated by space.\n * Example on Styling page.\n *\n * @prop {HTMLElement} [appendToEl]\n * Element to which PhotoSwipe dialog will be appended when it opens.\n *\n * @prop {number} maxWidthToAnimate\n * Maximum width of image to animate, if initial rendered image width\n * is larger than this value - the opening/closing transition will be automatically disabled.\n *\n * @prop {string} [closeTitle]\n * Translating\n *\n * @prop {string} [zoomTitle]\n * Translating\n *\n * @prop {string} [arrowPrevTitle]\n * Translating\n *\n * @prop {string} [arrowNextTitle]\n * Translating\n *\n * @prop {'zoom' | 'fade' | 'none'} [showHideAnimationType]\n * To adjust opening or closing transition type use lightbox option `showHideAnimationType` (`String`).\n * It supports three values - `zoom` (default), `fade` (default if there is no thumbnail) and `none`.\n *\n * Animations are automatically disabled if user `(prefers-reduced-motion: reduce)`.\n *\n * @prop {number} index\n * Defines start slide index.\n *\n * @prop {(e: MouseEvent) => number} [getClickedIndexFn]\n *\n * @prop {boolean} [arrowPrev]\n * @prop {boolean} [arrowNext]\n * @prop {boolean} [zoom]\n * @prop {boolean} [close]\n * @prop {boolean} [counter]\n *\n * @prop {string} [arrowPrevSVG]\n * @prop {string} [arrowNextSVG]\n * @prop {string} [zoomSVG]\n * @prop {string} [closeSVG]\n * @prop {string} [counterSVG]\n *\n * @prop {string} [arrowPrevTitle]\n * @prop {string} [arrowNextTitle]\n * @prop {string} [zoomTitle]\n * @prop {string} [closeTitle]\n * @prop {string} [counterTitle]\n *\n * @prop {ZoomLevelOption} [initialZoomLevel]\n * @prop {ZoomLevelOption} [secondaryZoomLevel]\n * @prop {ZoomLevelOption} [maxZoomLevel]\n *\n * @prop {boolean} [mouseMovePan]\n * @prop {Point | null} [initialPointerPos]\n * @prop {boolean} [showHideOpacity]\n *\n * @prop {PhotoSwipeModuleOption} [pswpModule]\n * @prop {() => Promise<any>} [openPromise]\n * @prop {boolean} [preloadFirstSlide]\n * @prop {ElementProvider} [gallery]\n * @prop {string} [gallerySelector]\n * @prop {ElementProvider} [children]\n * @prop {string} [childSelector]\n * @prop {string | false} [thumbSelector]\n */\n\n/** @type {PreparedPhotoSwipeOptions} */\nconst defaultOptions = {\n  allowPanToNext: true,\n  spacing: 0.1,\n  loop: true,\n  pinchToClose: true,\n  closeOnVerticalDrag: true,\n  hideAnimationDuration: 333,\n  showAnimationDuration: 333,\n  zoomAnimationDuration: 333,\n  escKey: true,\n  arrowKeys: true,\n  trapFocus: true,\n  returnFocus: true,\n  maxWidthToAnimate: 4000,\n  clickToCloseNonZoomable: true,\n  imageClickAction: 'zoom-or-close',\n  bgClickAction: 'close',\n  tapAction: 'toggle-controls',\n  doubleTapAction: 'zoom',\n  indexIndicatorSep: ' / ',\n  preloaderDelay: 2000,\n  bgOpacity: 0.8,\n\n  index: 0,\n  errorMsg: 'The image cannot be loaded',\n  preload: [1, 2],\n  easing: 'cubic-bezier(.4,0,.22,1)'\n};\n\n/**\n * PhotoSwipe Core\n */\nclass PhotoSwipe extends PhotoSwipeBase {\n  /**\n   * @param {PhotoSwipeOptions} [options]\n   */\n  constructor(options) {\n    super();\n\n    this.options = this._prepareOptions(options || {});\n\n    /**\n     * offset of viewport relative to document\n     *\n     * @type {Point}\n     */\n    this.offset = { x: 0, y: 0 };\n\n    /**\n     * @type {Point}\n     * @private\n     */\n    this._prevViewportSize = { x: 0, y: 0 };\n\n    /**\n     * Size of scrollable PhotoSwipe viewport\n     *\n     * @type {Point}\n     */\n    this.viewportSize = { x: 0, y: 0 };\n\n    /**\n     * background (backdrop) opacity\n     */\n    this.bgOpacity = 1;\n    this.currIndex = 0;\n    this.potentialIndex = 0;\n    this.isOpen = false;\n    this.isDestroying = false;\n    this.hasMouse = false;\n\n    /**\n     * @private\n     * @type {SlideData}\n     */\n    this._initialItemData = {};\n    /** @type {Bounds | undefined} */\n    this._initialThumbBounds = undefined;\n\n    /** @type {HTMLDivElement | undefined} */\n    this.topBar = undefined;\n    /** @type {HTMLDivElement | undefined} */\n    this.element = undefined;\n    /** @type {HTMLDivElement | undefined} */\n    this.template = undefined;\n    /** @type {HTMLDivElement | undefined} */\n    this.container = undefined;\n    /** @type {HTMLElement | undefined} */\n    this.scrollWrap = undefined;\n    /** @type {Slide | undefined} */\n    this.currSlide = undefined;\n\n    this.events = new DOMEvents();\n    this.animations = new Animations();\n    this.mainScroll = new MainScroll(this);\n    this.gestures = new Gestures(this);\n    this.opener = new Opener(this);\n    this.keyboard = new Keyboard(this);\n    this.contentLoader = new ContentLoader(this);\n  }\n\n  /** @returns {boolean} */\n  init() {\n    if (this.isOpen || this.isDestroying) {\n      return false;\n    }\n\n    this.isOpen = true;\n    this.dispatch('init'); // legacy\n    this.dispatch('beforeOpen');\n\n    this._createMainStructure();\n\n    // add classes to the root element of PhotoSwipe\n    let rootClasses = 'pswp--open';\n    if (this.gestures.supportsTouch) {\n      rootClasses += ' pswp--touch';\n    }\n    if (this.options.mainClass) {\n      rootClasses += ' ' + this.options.mainClass;\n    }\n    if (this.element) {\n      this.element.className += ' ' + rootClasses;\n    }\n\n    this.currIndex = this.options.index || 0;\n    this.potentialIndex = this.currIndex;\n    this.dispatch('firstUpdate'); // starting index can be modified here\n\n    // initialize scroll wheel handler to block the scroll\n    this.scrollWheel = new ScrollWheel(this);\n\n    // sanitize index\n    if (Number.isNaN(this.currIndex)\n        || this.currIndex < 0\n        || this.currIndex >= this.getNumItems()) {\n      this.currIndex = 0;\n    }\n\n    if (!this.gestures.supportsTouch) {\n      // enable mouse features if no touch support detected\n      this.mouseDetected();\n    }\n\n    // causes forced synchronous layout\n    this.updateSize();\n\n    this.offset.y = window.pageYOffset;\n\n    this._initialItemData = this.getItemData(this.currIndex);\n    this.dispatch('gettingData', {\n      index: this.currIndex,\n      data: this._initialItemData,\n      slide: undefined\n    });\n\n    // *Layout* - calculate size and position of elements here\n    this._initialThumbBounds = this.getThumbBounds();\n    this.dispatch('initialLayout');\n\n    this.on('openingAnimationEnd', () => {\n      const { itemHolders } = this.mainScroll;\n\n      // Add content to the previous and next slide\n      if (itemHolders[0]) {\n        itemHolders[0].el.style.display = 'block';\n        this.setContent(itemHolders[0], this.currIndex - 1);\n      }\n      if (itemHolders[2]) {\n        itemHolders[2].el.style.display = 'block';\n        this.setContent(itemHolders[2], this.currIndex + 1);\n      }\n\n      this.appendHeavy();\n\n      this.contentLoader.updateLazy();\n\n      this.events.add(window, 'resize', this._handlePageResize.bind(this));\n      this.events.add(window, 'scroll', this._updatePageScrollOffset.bind(this));\n      this.dispatch('bindEvents');\n    });\n\n    // set content for center slide (first time)\n    if (this.mainScroll.itemHolders[1]) {\n      this.setContent(this.mainScroll.itemHolders[1], this.currIndex);\n    }\n    this.dispatch('change');\n\n    this.opener.open();\n\n    this.dispatch('afterInit');\n\n    return true;\n  }\n\n  /**\n   * Get looped slide index\n   * (for example, -1 will return the last slide)\n   *\n   * @param {number} index\n   * @returns {number}\n   */\n  getLoopedIndex(index) {\n    const numSlides = this.getNumItems();\n\n    if (this.options.loop) {\n      if (index > numSlides - 1) {\n        index -= numSlides;\n      }\n\n      if (index < 0) {\n        index += numSlides;\n      }\n    }\n\n    return clamp(index, 0, numSlides - 1);\n  }\n\n  appendHeavy() {\n    this.mainScroll.itemHolders.forEach((itemHolder) => {\n      itemHolder.slide?.appendHeavy();\n    });\n  }\n\n  /**\n   * Change the slide\n   * @param {number} index New index\n   */\n  goTo(index) {\n    this.mainScroll.moveIndexBy(\n      this.getLoopedIndex(index) - this.potentialIndex\n    );\n  }\n\n  /**\n   * Go to the next slide.\n   */\n  next() {\n    this.goTo(this.potentialIndex + 1);\n  }\n\n  /**\n   * Go to the previous slide.\n   */\n  prev() {\n    this.goTo(this.potentialIndex - 1);\n  }\n\n  /**\n   * @see slide/slide.js zoomTo\n   *\n   * @param {Parameters<Slide['zoomTo']>} args\n   */\n  zoomTo(...args) {\n    this.currSlide?.zoomTo(...args);\n  }\n\n  /**\n   * @see slide/slide.js toggleZoom\n   */\n  toggleZoom() {\n    this.currSlide?.toggleZoom();\n  }\n\n  /**\n   * Close the gallery.\n   * After closing transition ends - destroy it\n   */\n  close() {\n    if (!this.opener.isOpen || this.isDestroying) {\n      return;\n    }\n\n    this.isDestroying = true;\n\n    this.dispatch('close');\n\n    this.events.removeAll();\n    this.opener.close();\n  }\n\n  /**\n   * Destroys the gallery:\n   * - instantly closes the gallery\n   * - unbinds events,\n   * - cleans intervals and timeouts\n   * - removes elements from DOM\n   */\n  destroy() {\n    if (!this.isDestroying) {\n      this.options.showHideAnimationType = 'none';\n      this.close();\n      return;\n    }\n\n    this.dispatch('destroy');\n\n    this._listeners = {};\n\n    if (this.scrollWrap) {\n      this.scrollWrap.ontouchmove = null;\n      this.scrollWrap.ontouchend = null;\n    }\n\n    this.element?.remove();\n\n    this.mainScroll.itemHolders.forEach((itemHolder) => {\n      itemHolder.slide?.destroy();\n    });\n\n    this.contentLoader.destroy();\n    this.events.removeAll();\n  }\n\n  /**\n   * Refresh/reload content of a slide by its index\n   *\n   * @param {number} slideIndex\n   */\n  refreshSlideContent(slideIndex) {\n    this.contentLoader.removeByIndex(slideIndex);\n    this.mainScroll.itemHolders.forEach((itemHolder, i) => {\n      let potentialHolderIndex = (this.currSlide?.index ?? 0) - 1 + i;\n      if (this.canLoop()) {\n        potentialHolderIndex = this.getLoopedIndex(potentialHolderIndex);\n      }\n      if (potentialHolderIndex === slideIndex) {\n        // set the new slide content\n        this.setContent(itemHolder, slideIndex, true);\n\n        // activate the new slide if it's current\n        if (i === 1) {\n          this.currSlide = itemHolder.slide;\n          itemHolder.slide?.setIsActive(true);\n        }\n      }\n    });\n\n    this.dispatch('change');\n  }\n\n\n  /**\n   * Set slide content\n   *\n   * @param {ItemHolder} holder mainScroll.itemHolders array item\n   * @param {number} index Slide index\n   * @param {boolean} [force] If content should be set even if index wasn't changed\n   */\n  setContent(holder, index, force) {\n    if (this.canLoop()) {\n      index = this.getLoopedIndex(index);\n    }\n\n    if (holder.slide) {\n      if (holder.slide.index === index && !force) {\n        // exit if holder already contains this slide\n        // this could be common when just three slides are used\n        return;\n      }\n\n      // destroy previous slide\n      holder.slide.destroy();\n      holder.slide = undefined;\n    }\n\n    // exit if no loop and index is out of bounds\n    if (!this.canLoop() && (index < 0 || index >= this.getNumItems())) {\n      return;\n    }\n\n    const itemData = this.getItemData(index);\n    holder.slide = new Slide(itemData, index, this);\n\n    // set current slide\n    if (index === this.currIndex) {\n      this.currSlide = holder.slide;\n    }\n\n    holder.slide.append(holder.el);\n  }\n\n  /** @returns {Point} */\n  getViewportCenterPoint() {\n    return {\n      x: this.viewportSize.x / 2,\n      y: this.viewportSize.y / 2\n    };\n  }\n\n  /**\n   * Update size of all elements.\n   * Executed on init and on page resize.\n   *\n   * @param {boolean} [force] Update size even if size of viewport was not changed.\n   */\n  updateSize(force) {\n    // let item;\n    // let itemIndex;\n\n    if (this.isDestroying) {\n      // exit if PhotoSwipe is closed or closing\n      // (to avoid errors, as resize event might be delayed)\n      return;\n    }\n\n    //const newWidth = this.scrollWrap.clientWidth;\n    //const newHeight = this.scrollWrap.clientHeight;\n\n    const newViewportSize = getViewportSize(this.options, this);\n\n    if (!force && pointsEqual(newViewportSize, this._prevViewportSize)) {\n      // Exit if dimensions were not changed\n      return;\n    }\n\n    //this._prevViewportSize.x = newWidth;\n    //this._prevViewportSize.y = newHeight;\n    equalizePoints(this._prevViewportSize, newViewportSize);\n\n    this.dispatch('beforeResize');\n\n    equalizePoints(this.viewportSize, this._prevViewportSize);\n\n    this._updatePageScrollOffset();\n\n    this.dispatch('viewportSize');\n\n    // Resize slides only after opener animation is finished\n    // and don't re-calculate size on inital size update\n    this.mainScroll.resize(this.opener.isOpen);\n\n    if (!this.hasMouse && window.matchMedia('(any-hover: hover)').matches) {\n      this.mouseDetected();\n    }\n\n    this.dispatch('resize');\n  }\n\n  /**\n   * @param {number} opacity\n   */\n  applyBgOpacity(opacity) {\n    this.bgOpacity = Math.max(opacity, 0);\n    if (this.bg) {\n      this.bg.style.opacity = String(this.bgOpacity * this.options.bgOpacity);\n    }\n  }\n\n  /**\n   * Whether mouse is detected\n   */\n  mouseDetected() {\n    if (!this.hasMouse) {\n      this.hasMouse = true;\n      this.element?.classList.add('pswp--has_mouse');\n    }\n  }\n\n  /**\n   * Page resize event handler\n   *\n   * @private\n   */\n  _handlePageResize() {\n    this.updateSize();\n\n    // In iOS webview, if element size depends on document size,\n    // it'll be measured incorrectly in resize event\n    //\n    // https://bugs.webkit.org/show_bug.cgi?id=170595\n    // https://hackernoon.com/onresize-event-broken-in-mobile-safari-d8469027bf4d\n    if (/iPhone|iPad|iPod/i.test(window.navigator.userAgent)) {\n      setTimeout(() => {\n        this.updateSize();\n      }, 500);\n    }\n  }\n\n  /**\n   * Page scroll offset is used\n   * to get correct coordinates\n   * relative to PhotoSwipe viewport.\n   *\n   * @private\n   */\n  _updatePageScrollOffset() {\n    this.setScrollOffset(0, window.pageYOffset);\n  }\n\n  /**\n   * @param {number} x\n   * @param {number} y\n   */\n  setScrollOffset(x, y) {\n    this.offset.x = x;\n    this.offset.y = y;\n    this.dispatch('updateScrollOffset');\n  }\n\n  /**\n   * Create main HTML structure of PhotoSwipe,\n   * and add it to DOM\n   *\n   * @private\n   */\n  _createMainStructure() {\n    // root DOM element of PhotoSwipe (.pswp)\n    this.element = createElement('pswp', 'div');\n    this.element.setAttribute('tabindex', '-1');\n    this.element.setAttribute('role', 'dialog');\n\n    // template is legacy prop\n    this.template = this.element;\n\n    // Background is added as a separate element,\n    // as animating opacity is faster than animating rgba()\n    this.bg = createElement('pswp__bg', 'div', this.element);\n    this.scrollWrap = createElement('pswp__scroll-wrap', 'section', this.element);\n    this.container = createElement('pswp__container', 'div', this.scrollWrap);\n\n    // aria pattern: carousel\n    this.scrollWrap.setAttribute('aria-roledescription', 'carousel');\n    this.container.setAttribute('aria-live', 'off');\n    this.container.setAttribute('id', 'pswp__items');\n\n    this.mainScroll.appendHolders();\n\n    this.ui = new UI(this);\n    this.ui.init();\n\n    // append to DOM\n    (this.options.appendToEl || document.body).appendChild(this.element);\n  }\n\n\n  /**\n   * Get position and dimensions of small thumbnail\n   *   {x:,y:,w:}\n   *\n   * Height is optional (calculated based on the large image)\n   *\n   * @returns {Bounds | undefined}\n   */\n  getThumbBounds() {\n    return getThumbBounds(\n      this.currIndex,\n      this.currSlide ? this.currSlide.data : this._initialItemData,\n      this\n    );\n  }\n\n  /**\n   * If the PhotoSwipe can have continuous loop\n   * @returns Boolean\n   */\n  canLoop() {\n    return (this.options.loop && this.getNumItems() > 2);\n  }\n\n  /**\n   * @private\n   * @param {PhotoSwipeOptions} options\n   * @returns {PreparedPhotoSwipeOptions}\n   */\n  _prepareOptions(options) {\n    if (window.matchMedia('(prefers-reduced-motion), (update: slow)').matches) {\n      options.showHideAnimationType = 'none';\n      options.zoomAnimationDuration = 0;\n    }\n\n    /** @type {PreparedPhotoSwipeOptions} */\n    return {\n      ...defaultOptions,\n      ...options\n    };\n  }\n}\n\nexport default PhotoSwipe;\n"
  },
  {
    "path": "src/js/scroll-wheel.js",
    "content": "/** @typedef {import('./photoswipe.js').default} PhotoSwipe */\n\n/**\n * Handles scroll wheel.\n * Can pan and zoom current slide image.\n */\nclass ScrollWheel {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    pswp.events.add(pswp.element, 'wheel', /** @type EventListener */(this._onWheel.bind(this)));\n  }\n\n  /**\n   * @private\n   * @param {WheelEvent} e\n   */\n  _onWheel(e) {\n    e.preventDefault();\n    const { currSlide } = this.pswp;\n    let { deltaX, deltaY } = e;\n\n    if (!currSlide) {\n      return;\n    }\n\n    if (this.pswp.dispatch('wheel', { originalEvent: e }).defaultPrevented) {\n      return;\n    }\n\n    if (e.ctrlKey || this.pswp.options.wheelToZoom) {\n      // zoom\n      if (currSlide.isZoomable()) {\n        let zoomFactor = -deltaY;\n        if (e.deltaMode === 1 /* DOM_DELTA_LINE */) {\n          zoomFactor *= 0.05;\n        } else {\n          zoomFactor *= e.deltaMode ? 1 : 0.002;\n        }\n        zoomFactor = 2 ** zoomFactor;\n\n        const destZoomLevel = currSlide.currZoomLevel * zoomFactor;\n        currSlide.zoomTo(destZoomLevel, {\n          x: e.clientX,\n          y: e.clientY\n        });\n      }\n    } else {\n      // pan\n      if (currSlide.isPannable()) {\n        if (e.deltaMode === 1 /* DOM_DELTA_LINE */) {\n          // 18 - average line height\n          deltaX *= 18;\n          deltaY *= 18;\n        }\n\n        currSlide.panTo(\n          currSlide.pan.x - deltaX,\n          currSlide.pan.y - deltaY\n        );\n      }\n    }\n  }\n}\n\nexport default ScrollWheel;\n"
  },
  {
    "path": "src/js/slide/content.js",
    "content": "import { createElement, isSafari, LOAD_STATE, setWidthHeight } from '../util/util.js';\nimport Placeholder from './placeholder.js';\n\n/** @typedef {import('./slide.js').default} Slide */\n/** @typedef {import('./slide.js').SlideData} SlideData */\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n/** @typedef {import('../util/util.js').LoadState} LoadState */\n\nclass Content {\n  /**\n   * @param {SlideData} itemData Slide data\n   * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance\n   * @param {number} index\n   */\n  constructor(itemData, instance, index) {\n    this.instance = instance;\n    this.data = itemData;\n    this.index = index;\n\n    /** @type {HTMLImageElement | HTMLDivElement | undefined} */\n    this.element = undefined;\n    /** @type {Placeholder | undefined} */\n    this.placeholder = undefined;\n    /** @type {Slide | undefined} */\n    this.slide = undefined;\n\n    this.displayedImageWidth = 0;\n    this.displayedImageHeight = 0;\n\n    this.width = Number(this.data.w) || Number(this.data.width) || 0;\n    this.height = Number(this.data.h) || Number(this.data.height) || 0;\n\n    this.isAttached = false;\n    this.hasSlide = false;\n    this.isDecoding = false;\n    /** @type {LoadState} */\n    this.state = LOAD_STATE.IDLE;\n\n    if (this.data.type) {\n      this.type = this.data.type;\n    } else if (this.data.src) {\n      this.type = 'image';\n    } else {\n      this.type = 'html';\n    }\n\n    this.instance.dispatch('contentInit', { content: this });\n  }\n\n  removePlaceholder() {\n    if (this.placeholder && !this.keepPlaceholder()) {\n      // Use animation duration + buffer time to ensure placeholder \n      // does not disappear during opening animation\n      const animationDuration = this.instance?.options?.showAnimationDuration || 0;\n      const safeDelay = animationDuration + 500;\n      \n      setTimeout(() => {\n        if (this.placeholder) {\n          this.placeholder.destroy();\n          this.placeholder = undefined;\n        }\n      }, safeDelay);\n    }\n  }\n\n  /**\n   * Preload content\n   *\n   * @param {boolean} isLazy\n   * @param {boolean} [reload]\n   */\n  load(isLazy, reload) {\n    if (this.slide && this.usePlaceholder()) {\n      if (!this.placeholder) {\n        const placeholderSrc = this.instance.applyFilters(\n          'placeholderSrc',\n          // use  image-based placeholder only for the first slide,\n          // as rendering (even small stretched thumbnail) is an expensive operation\n          (this.data.msrc && this.slide.isFirstSlide) ? this.data.msrc : false,\n          this\n        );\n        this.placeholder = new Placeholder(\n          placeholderSrc,\n          this.slide.container\n        );\n      } else {\n        const placeholderEl = this.placeholder.element;\n        // Add placeholder to DOM if it was already created\n        if (placeholderEl && !placeholderEl.parentElement) {\n          this.slide.container.prepend(placeholderEl);\n        }\n      }\n    }\n\n    if (this.element && !reload) {\n      return;\n    }\n\n    if (this.instance.dispatch('contentLoad', { content: this, isLazy }).defaultPrevented) {\n      return;\n    }\n\n    if (this.isImageContent()) {\n      this.element = createElement('pswp__img', 'img');\n      // Start loading only after width is defined, as sizes might depend on it.\n      // Due to Safari feature, we must define sizes before srcset.\n      if (this.displayedImageWidth) {\n        this.loadImage(isLazy);\n      }\n    } else {\n      this.element = createElement('pswp__content', 'div');\n      this.element.innerHTML = this.data.html || '';\n    }\n\n    if (reload && this.slide) {\n      this.slide.updateContentSize(true);\n    }\n  }\n\n  /**\n   * Preload image\n   *\n   * @param {boolean} isLazy\n   */\n  loadImage(isLazy) {\n    if (!this.isImageContent()\n      || !this.element\n      || this.instance.dispatch('contentLoadImage', { content: this, isLazy }).defaultPrevented) {\n      return;\n    }\n\n    const imageElement = /** @type HTMLImageElement */ (this.element);\n\n    this.updateSrcsetSizes();\n\n    if (this.data.srcset) {\n      imageElement.srcset = this.data.srcset;\n    }\n\n    imageElement.src = this.data.src ?? '';\n    imageElement.alt = this.data.alt ?? '';\n\n    this.state = LOAD_STATE.LOADING;\n\n    if (imageElement.complete) {\n      this.onLoaded();\n    } else {\n      imageElement.onload = () => {\n        this.onLoaded();\n      };\n\n      imageElement.onerror = () => {\n        this.onError();\n      };\n    }\n  }\n\n  /**\n   * Assign slide to content\n   *\n   * @param {Slide} slide\n   */\n  setSlide(slide) {\n    this.slide = slide;\n    this.hasSlide = true;\n    this.instance = slide.pswp;\n\n    // todo: do we need to unset slide?\n  }\n\n  /**\n   * Content load success handler\n   */\n  onLoaded() {\n    this.state = LOAD_STATE.LOADED;\n\n    if (this.slide && this.element) {\n      this.instance.dispatch('loadComplete', { slide: this.slide, content: this });\n\n      // if content is reloaded\n      if (this.slide.isActive\n          && this.slide.heavyAppended\n          && !this.element.parentNode) {\n        this.append();\n        this.slide.updateContentSize(true);\n      }\n\n      if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {\n        this.removePlaceholder();\n      }\n    }\n  }\n\n  /**\n   * Content load error handler\n   */\n  onError() {\n    this.state = LOAD_STATE.ERROR;\n\n    if (this.slide) {\n      this.displayError();\n      this.instance.dispatch('loadComplete', { slide: this.slide, isError: true, content: this });\n      this.instance.dispatch('loadError', { slide: this.slide, content: this });\n    }\n  }\n\n  /**\n   * @returns {Boolean} If the content is currently loading\n   */\n  isLoading() {\n    return this.instance.applyFilters(\n      'isContentLoading',\n      this.state === LOAD_STATE.LOADING,\n      this\n    );\n  }\n\n  /**\n   * @returns {Boolean} If the content is in error state\n   */\n  isError() {\n    return this.state === LOAD_STATE.ERROR;\n  }\n\n  /**\n   * @returns {boolean} If the content is image\n   */\n  isImageContent() {\n    return this.type === 'image';\n  }\n\n  /**\n   * Update content size\n   *\n   * @param {Number} width\n   * @param {Number} height\n   */\n  setDisplayedSize(width, height) {\n    if (!this.element) {\n      return;\n    }\n\n    if (this.placeholder) {\n      this.placeholder.setDisplayedSize(width, height);\n    }\n\n    if (this.instance.dispatch(\n      'contentResize',\n      { content: this, width, height }).defaultPrevented\n    ) {\n      return;\n    }\n\n    setWidthHeight(this.element, width, height);\n\n    if (this.isImageContent() && !this.isError()) {\n      const isInitialSizeUpdate = (!this.displayedImageWidth && width);\n\n      this.displayedImageWidth = width;\n      this.displayedImageHeight = height;\n\n      if (isInitialSizeUpdate) {\n        this.loadImage(false);\n      } else {\n        this.updateSrcsetSizes();\n      }\n\n      if (this.slide) {\n        this.instance.dispatch(\n          'imageSizeChange',\n          { slide: this.slide, width, height, content: this }\n        );\n      }\n    }\n  }\n\n  /**\n   * @returns {boolean} If the content can be zoomed\n   */\n  isZoomable() {\n    return this.instance.applyFilters(\n      'isContentZoomable',\n      this.isImageContent() && (this.state !== LOAD_STATE.ERROR),\n      this\n    );\n  }\n\n  /**\n   * Update image srcset sizes attribute based on width and height\n   */\n  updateSrcsetSizes() {\n    // Handle srcset sizes attribute.\n    //\n    // Never lower quality, if it was increased previously.\n    // Chrome does this automatically, Firefox and Safari do not,\n    // so we store largest used size in dataset.\n    if (!this.isImageContent() || !this.element || !this.data.srcset) {\n      return;\n    }\n\n    const image = /** @type HTMLImageElement */ (this.element);\n    const sizesWidth = this.instance.applyFilters(\n      'srcsetSizesWidth',\n      this.displayedImageWidth,\n      this\n    );\n\n    if (\n      !image.dataset.largestUsedSize\n      || sizesWidth > parseInt(image.dataset.largestUsedSize, 10)\n    ) {\n      image.sizes = sizesWidth + 'px';\n      image.dataset.largestUsedSize = String(sizesWidth);\n    }\n  }\n\n  /**\n   * @returns {boolean} If content should use a placeholder (from msrc by default)\n   */\n  usePlaceholder() {\n    return this.instance.applyFilters(\n      'useContentPlaceholder',\n      this.isImageContent(),\n      this\n    );\n  }\n\n  /**\n   * Preload content with lazy-loading param\n   */\n  lazyLoad() {\n    if (this.instance.dispatch('contentLazyLoad', { content: this }).defaultPrevented) {\n      return;\n    }\n\n    this.load(true);\n  }\n\n  /**\n   * @returns {boolean} If placeholder should be kept after content is loaded\n   */\n  keepPlaceholder() {\n    return this.instance.applyFilters(\n      'isKeepingPlaceholder',\n      this.isLoading(),\n      this\n    );\n  }\n\n  /**\n   * Destroy the content\n   */\n  destroy() {\n    this.hasSlide = false;\n    this.slide = undefined;\n\n    if (this.instance.dispatch('contentDestroy', { content: this }).defaultPrevented) {\n      return;\n    }\n\n    this.remove();\n\n    if (this.placeholder) {\n      this.placeholder.destroy();\n      this.placeholder = undefined;\n    }\n\n    if (this.isImageContent() && this.element) {\n      this.element.onload = null;\n      this.element.onerror = null;\n      this.element = undefined;\n    }\n  }\n\n  /**\n   * Display error message\n   */\n  displayError() {\n    if (this.slide) {\n      let errorMsgEl = createElement('pswp__error-msg', 'div');\n      errorMsgEl.innerText = this.instance.options?.errorMsg ?? '';\n      errorMsgEl = /** @type {HTMLDivElement} */ (this.instance.applyFilters(\n        'contentErrorElement',\n        errorMsgEl,\n        this\n      ));\n      this.element = createElement('pswp__content pswp__error-msg-container', 'div');\n      this.element.appendChild(errorMsgEl);\n      this.slide.container.innerText = '';\n      this.slide.container.appendChild(this.element);\n      this.slide.updateContentSize(true);\n      this.removePlaceholder();\n    }\n  }\n\n  /**\n   * Append the content\n   */\n  append() {\n    if (this.isAttached || !this.element) {\n      return;\n    }\n\n    this.isAttached = true;\n\n    if (this.state === LOAD_STATE.ERROR) {\n      this.displayError();\n      return;\n    }\n\n    if (this.instance.dispatch('contentAppend', { content: this }).defaultPrevented) {\n      return;\n    }\n\n    const supportsDecode = ('decode' in this.element);\n\n    if (this.isImageContent()) {\n      // Use decode() on nearby slides\n      //\n      // Nearby slide images are in DOM and not hidden via display:none.\n      // However, they are placed offscreen (to the left and right side).\n      //\n      // Some browsers do not composite the image until it's actually visible,\n      // using decode() helps.\n      //\n      // You might ask \"why dont you just decode() and then append all images\",\n      // that's because I want to show image before it's fully loaded,\n      // as browser can render parts of image while it is loading.\n      // We do not do this in Safari due to partial loading bug.\n      if (supportsDecode && this.slide && (!this.slide.isActive || isSafari())) {\n        this.isDecoding = true;\n        // purposefully using finally instead of then,\n        // as if srcset sizes changes dynamically - it may cause decode error\n        /** @type {HTMLImageElement} */\n        (this.element).decode().catch(() => {}).finally(() => {\n          this.isDecoding = false;\n          this.appendImage();\n        });\n      } else {\n        this.appendImage();\n      }\n    } else if (this.slide && !this.element.parentNode) {\n      this.slide.container.appendChild(this.element);\n    }\n  }\n\n  /**\n   * Activate the slide,\n   * active slide is generally the current one,\n   * meaning the user can see it.\n   */\n  activate() {\n    if (this.instance.dispatch('contentActivate', { content: this }).defaultPrevented\n      || !this.slide) {\n      return;\n    }\n\n    if (this.isImageContent() && this.isDecoding && !isSafari()) {\n      // add image to slide when it becomes active,\n      // even if it's not finished decoding\n      this.appendImage();\n    } else if (this.isError()) {\n      this.load(false, true); // try to reload\n    }\n\n    if (this.slide.holderElement) {\n      this.slide.holderElement.setAttribute('aria-hidden', 'false');\n    }\n  }\n\n  /**\n   * Deactivate the content\n   */\n  deactivate() {\n    this.instance.dispatch('contentDeactivate', { content: this });\n    if (this.slide && this.slide.holderElement) {\n      this.slide.holderElement.setAttribute('aria-hidden', 'true');\n    }\n  }\n\n\n  /**\n   * Remove the content from DOM\n   */\n  remove() {\n    this.isAttached = false;\n\n    if (this.instance.dispatch('contentRemove', { content: this }).defaultPrevented) {\n      return;\n    }\n\n    if (this.element && this.element.parentNode) {\n      this.element.remove();\n    }\n\n    if (this.placeholder && this.placeholder.element) {\n      this.placeholder.element.remove();\n    }\n  }\n\n  /**\n   * Append the image content to slide container\n   */\n  appendImage() {\n    if (!this.isAttached) {\n      return;\n    }\n\n    if (this.instance.dispatch('contentAppendImage', { content: this }).defaultPrevented) {\n      return;\n    }\n\n    // ensure that element exists and is not already appended\n    if (this.slide && this.element && !this.element.parentNode) {\n      this.slide.container.appendChild(this.element);\n    }\n\n    if (this.state === LOAD_STATE.LOADED || this.state === LOAD_STATE.ERROR) {\n      this.removePlaceholder();\n    }\n  }\n}\n\nexport default Content;\n"
  },
  {
    "path": "src/js/slide/get-thumb-bounds.js",
    "content": "/** @typedef {import('./slide.js').SlideData} SlideData */\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/** @typedef {{ x: number; y: number; w: number; innerRect?: { w: number; h: number; x: number; y: number } }} Bounds */\n\n/**\n * @param {HTMLElement} el\n * @returns Bounds\n */\nfunction getBoundsByElement(el) {\n  const thumbAreaRect = el.getBoundingClientRect();\n  return {\n    x: thumbAreaRect.left,\n    y: thumbAreaRect.top,\n    w: thumbAreaRect.width\n  };\n}\n\n/**\n * @param {HTMLElement} el\n * @param {number} imageWidth\n * @param {number} imageHeight\n * @returns Bounds\n */\nfunction getCroppedBoundsByElement(el, imageWidth, imageHeight) {\n  const thumbAreaRect = el.getBoundingClientRect();\n\n  // fill image into the area\n  // (do they same as object-fit:cover does to retrieve coordinates)\n  const hRatio = thumbAreaRect.width / imageWidth;\n  const vRatio = thumbAreaRect.height / imageHeight;\n  const fillZoomLevel = hRatio > vRatio ? hRatio : vRatio;\n\n  const offsetX = (thumbAreaRect.width - imageWidth * fillZoomLevel) / 2;\n  const offsetY = (thumbAreaRect.height - imageHeight * fillZoomLevel) / 2;\n\n  /**\n   * Coordinates of the image,\n   * as if it was not cropped,\n   * height is calculated automatically\n   *\n   * @type {Bounds}\n   */\n  const bounds = {\n    x: thumbAreaRect.left + offsetX,\n    y: thumbAreaRect.top + offsetY,\n    w: imageWidth * fillZoomLevel\n  };\n\n  // Coordinates of inner crop area\n  // relative to the image\n  bounds.innerRect = {\n    w: thumbAreaRect.width,\n    h: thumbAreaRect.height,\n    x: offsetX,\n    y: offsetY\n  };\n\n  return bounds;\n}\n\n/**\n * Get dimensions of thumbnail image\n * (click on which opens photoswipe or closes photoswipe to)\n *\n * @param {number} index\n * @param {SlideData} itemData\n * @param {PhotoSwipe} instance PhotoSwipe instance\n * @returns {Bounds | undefined}\n */\nexport function getThumbBounds(index, itemData, instance) {\n  // legacy event, before filters were introduced\n  const event = instance.dispatch('thumbBounds', {\n    index,\n    itemData,\n    instance\n  });\n  // @ts-expect-error\n  if (event.thumbBounds) {\n    // @ts-expect-error\n    return event.thumbBounds;\n  }\n\n  const { element } = itemData;\n  /** @type {Bounds | undefined} */\n  let thumbBounds;\n  /** @type {HTMLElement | null | undefined} */\n  let thumbnail;\n\n  if (element && instance.options.thumbSelector !== false) {\n    const thumbSelector = instance.options.thumbSelector || 'img';\n    thumbnail = element.matches(thumbSelector)\n      ? element : /** @type {HTMLElement | null} */ (element.querySelector(thumbSelector));\n  }\n\n  thumbnail = instance.applyFilters('thumbEl', thumbnail, itemData, index);\n\n  if (thumbnail) {\n    if (!itemData.thumbCropped) {\n      thumbBounds = getBoundsByElement(thumbnail);\n    } else {\n      thumbBounds = getCroppedBoundsByElement(\n        thumbnail,\n        itemData.width || itemData.w || 0,\n        itemData.height || itemData.h || 0\n      );\n    }\n  }\n\n  return instance.applyFilters('thumbBounds', thumbBounds, itemData, index);\n}\n"
  },
  {
    "path": "src/js/slide/loader.js",
    "content": "import { getViewportSize, getPanAreaSize } from '../util/viewport-size.js';\nimport ZoomLevel from './zoom-level.js';\n\n/** @typedef {import('./content.js').default} Content */\n/** @typedef {import('./slide.js').default} Slide */\n/** @typedef {import('./slide.js').SlideData} SlideData */\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\nconst MIN_SLIDES_TO_CACHE = 5;\n\n/**\n * Lazy-load an image\n * This function is used both by Lightbox and PhotoSwipe core,\n * thus it can be called before dialog is opened.\n *\n * @param {SlideData} itemData Data about the slide\n * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox instance\n * @param {number} index\n * @returns {Content} Image that is being decoded or false.\n */\nexport function lazyLoadData(itemData, instance, index) {\n  const content = instance.createContentFromData(itemData, index);\n  /** @type {ZoomLevel | undefined} */\n  let zoomLevel;\n\n  const { options } = instance;\n\n  // We need to know dimensions of the image to preload it,\n  // as it might use srcset, and we need to define sizes\n  if (options) {\n    zoomLevel = new ZoomLevel(options, itemData, -1);\n\n    let viewportSize;\n    if (instance.pswp) {\n      viewportSize = instance.pswp.viewportSize;\n    } else {\n      viewportSize = getViewportSize(options, instance);\n    }\n\n    const panAreaSize = getPanAreaSize(options, viewportSize, itemData, index);\n    zoomLevel.update(content.width, content.height, panAreaSize);\n  }\n\n  content.lazyLoad();\n\n  if (zoomLevel) {\n    content.setDisplayedSize(\n      Math.ceil(content.width * zoomLevel.initial),\n      Math.ceil(content.height * zoomLevel.initial)\n    );\n  }\n\n  return content;\n}\n\n\n/**\n * Lazy-loads specific slide.\n * This function is used both by Lightbox and PhotoSwipe core,\n * thus it can be called before dialog is opened.\n *\n * By default, it loads image based on viewport size and initial zoom level.\n *\n * @param {number} index Slide index\n * @param {PhotoSwipeBase} instance PhotoSwipe or PhotoSwipeLightbox eventable instance\n * @returns {Content | undefined}\n */\nexport function lazyLoadSlide(index, instance) {\n  const itemData = instance.getItemData(index);\n\n  if (instance.dispatch('lazyLoadSlide', { index, itemData }).defaultPrevented) {\n    return;\n  }\n\n  return lazyLoadData(itemData, instance, index);\n}\n\nclass ContentLoader {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    // Total amount of cached images\n    this.limit = Math.max(\n      pswp.options.preload[0] + pswp.options.preload[1] + 1,\n      MIN_SLIDES_TO_CACHE\n    );\n    /** @type {Content[]} */\n    this._cachedItems = [];\n  }\n\n  /**\n   * Lazy load nearby slides based on `preload` option.\n   *\n   * @param {number} [diff] Difference between slide indexes that was changed recently, or 0.\n   */\n  updateLazy(diff) {\n    const { pswp } = this;\n\n    if (pswp.dispatch('lazyLoad').defaultPrevented) {\n      return;\n    }\n\n    const { preload } = pswp.options;\n    const isForward = diff === undefined ? true : (diff >= 0);\n    let i;\n\n    // preload[1] - num items to preload in forward direction\n    for (i = 0; i <= preload[1]; i++) {\n      this.loadSlideByIndex(pswp.currIndex + (isForward ? i : (-i)));\n    }\n\n    // preload[0] - num items to preload in backward direction\n    for (i = 1; i <= preload[0]; i++) {\n      this.loadSlideByIndex(pswp.currIndex + (isForward ? (-i) : i));\n    }\n  }\n\n  /**\n   * @param {number} initialIndex\n   */\n  loadSlideByIndex(initialIndex) {\n    const index = this.pswp.getLoopedIndex(initialIndex);\n    // try to get cached content\n    let content = this.getContentByIndex(index);\n    if (!content) {\n      // no cached content, so try to load from scratch:\n      content = lazyLoadSlide(index, this.pswp);\n      // if content can be loaded, add it to cache:\n      if (content) {\n        this.addToCache(content);\n      }\n    }\n  }\n\n  /**\n   * @param {Slide} slide\n   * @returns {Content}\n   */\n  getContentBySlide(slide) {\n    let content = this.getContentByIndex(slide.index);\n    if (!content) {\n      // create content if not found in cache\n      content = this.pswp.createContentFromData(slide.data, slide.index);\n      this.addToCache(content);\n    }\n\n    // assign slide to content\n    content.setSlide(slide);\n\n    return content;\n  }\n\n  /**\n   * @param {Content} content\n   */\n  addToCache(content) {\n    // move to the end of array\n    this.removeByIndex(content.index);\n    this._cachedItems.push(content);\n\n    if (this._cachedItems.length > this.limit) {\n      // Destroy the first content that's not attached\n      const indexToRemove = this._cachedItems.findIndex((item) => {\n        return !item.isAttached && !item.hasSlide;\n      });\n      if (indexToRemove !== -1) {\n        const removedItem = this._cachedItems.splice(indexToRemove, 1)[0];\n        removedItem.destroy();\n      }\n    }\n  }\n\n  /**\n   * Removes an image from cache, does not destroy() it, just removes.\n   *\n   * @param {number} index\n   */\n  removeByIndex(index) {\n    const indexToRemove = this._cachedItems.findIndex(item => item.index === index);\n    if (indexToRemove !== -1) {\n      this._cachedItems.splice(indexToRemove, 1);\n    }\n  }\n\n  /**\n   * @param {number} index\n   * @returns {Content | undefined}\n   */\n  getContentByIndex(index) {\n    return this._cachedItems.find(content => content.index === index);\n  }\n\n  destroy() {\n    this._cachedItems.forEach(content => content.destroy());\n    this._cachedItems = [];\n  }\n}\n\nexport default ContentLoader;\n"
  },
  {
    "path": "src/js/slide/pan-bounds.js",
    "content": "import { clamp } from '../util/util.js';\nimport { parsePaddingOption } from '../util/viewport-size.js';\n\n/** @typedef {import('./slide.js').default} Slide */\n/** @typedef {Record<Axis, number>} Point */\n/** @typedef {'x' | 'y'} Axis */\n\n/**\n * Calculates minimum, maximum and initial (center) bounds of a slide\n */\nclass PanBounds {\n  /**\n   * @param {Slide} slide\n   */\n  constructor(slide) {\n    this.slide = slide;\n    this.currZoomLevel = 1;\n    this.center = /** @type {Point} */ { x: 0, y: 0 };\n    this.max = /** @type {Point} */ { x: 0, y: 0 };\n    this.min = /** @type {Point} */ { x: 0, y: 0 };\n  }\n\n  /**\n   * _getItemBounds\n   *\n   * @param {number} currZoomLevel\n   */\n  update(currZoomLevel) {\n    this.currZoomLevel = currZoomLevel;\n\n    if (!this.slide.width) {\n      this.reset();\n    } else {\n      this._updateAxis('x');\n      this._updateAxis('y');\n      this.slide.pswp.dispatch('calcBounds', { slide: this.slide });\n    }\n  }\n\n  /**\n   * _calculateItemBoundsForAxis\n   *\n   * @param {Axis} axis\n   */\n  _updateAxis(axis) {\n    const { pswp } = this.slide;\n    const elSize = this.slide[axis === 'x' ? 'width' : 'height'] * this.currZoomLevel;\n    const paddingProp = axis === 'x' ? 'left' : 'top';\n    const padding = parsePaddingOption(\n      paddingProp,\n      pswp.options,\n      pswp.viewportSize,\n      this.slide.data,\n      this.slide.index\n    );\n\n    const panAreaSize = this.slide.panAreaSize[axis];\n\n    // Default position of element.\n    // By default, it is center of viewport:\n    this.center[axis] = Math.round((panAreaSize - elSize) / 2) + padding;\n\n    // maximum pan position\n    this.max[axis] = (elSize > panAreaSize)\n      ? Math.round(panAreaSize - elSize) + padding\n      : this.center[axis];\n\n    // minimum pan position\n    this.min[axis] = (elSize > panAreaSize)\n      ? padding\n      : this.center[axis];\n  }\n\n  // _getZeroBounds\n  reset() {\n    this.center.x = 0;\n    this.center.y = 0;\n    this.max.x = 0;\n    this.max.y = 0;\n    this.min.x = 0;\n    this.min.y = 0;\n  }\n\n  /**\n   * Correct pan position if it's beyond the bounds\n   *\n   * @param {Axis} axis x or y\n   * @param {number} panOffset\n   * @returns {number}\n   */\n  correctPan(axis, panOffset) { // checkPanBounds\n    return clamp(panOffset, this.max[axis], this.min[axis]);\n  }\n}\n\nexport default PanBounds;\n"
  },
  {
    "path": "src/js/slide/placeholder.js",
    "content": "import { createElement, setWidthHeight, toTransformString } from '../util/util.js';\n\nclass Placeholder {\n  /**\n   * @param {string | false} imageSrc\n   * @param {HTMLElement} container\n   */\n  constructor(imageSrc, container) {\n    // Create placeholder\n    // (stretched thumbnail or simple div behind the main image)\n    /** @type {HTMLImageElement | HTMLDivElement | null} */\n    this.element = createElement(\n      'pswp__img pswp__img--placeholder',\n      imageSrc ? 'img' : 'div',\n      container\n    );\n\n    if (imageSrc) {\n      const imgEl = /** @type {HTMLImageElement} */ (this.element);\n      imgEl.decoding = 'async';\n      imgEl.alt = '';\n      imgEl.src = imageSrc;\n      imgEl.setAttribute('role', 'presentation');\n    }\n\n    this.element.setAttribute('aria-hidden', 'true');\n  }\n\n  /**\n   * @param {number} width\n   * @param {number} height\n   */\n  setDisplayedSize(width, height) {\n    if (!this.element) {\n      return;\n    }\n\n    if (this.element.tagName === 'IMG') {\n      // Use transform scale() to modify img placeholder size\n      // (instead of changing width/height directly).\n      // This helps with performance, specifically in iOS15 Safari.\n      setWidthHeight(this.element, 250, 'auto');\n      this.element.style.transformOrigin = '0 0';\n      this.element.style.transform = toTransformString(0, 0, width / 250);\n    } else {\n      setWidthHeight(this.element, width, height);\n    }\n  }\n\n  destroy() {\n    if (this.element?.parentNode) {\n      this.element.remove();\n    }\n    this.element = null;\n  }\n}\n\nexport default Placeholder;\n"
  },
  {
    "path": "src/js/slide/slide.js",
    "content": "/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n/** @typedef {import('../photoswipe.js').Point} Point */\n\n/**\n * @typedef {_SlideData & Record<string, any>} SlideData\n * @typedef {Object} _SlideData\n * @prop {HTMLElement} [element] thumbnail element\n * @prop {string} [src] image URL\n * @prop {string} [srcset] image srcset\n * @prop {number} [w] image width (deprecated)\n * @prop {number} [h] image height (deprecated)\n * @prop {number} [width] image width\n * @prop {number} [height] image height\n * @prop {string} [msrc] placeholder image URL that's displayed before large image is loaded\n * @prop {string} [alt] image alt text\n * @prop {boolean} [thumbCropped] whether thumbnail is cropped client-side or not\n * @prop {string} [html] html content of a slide\n * @prop {'image' | 'html' | string} [type] slide type\n */\n\nimport {\n  createElement,\n  setTransform,\n  equalizePoints,\n  roundPoint,\n  toTransformString,\n  clamp,\n} from '../util/util.js';\n\nimport PanBounds from './pan-bounds.js';\nimport ZoomLevel from './zoom-level.js';\nimport { getPanAreaSize } from '../util/viewport-size.js';\n\n/**\n * Renders and allows to control a single slide\n */\nclass Slide {\n  /**\n   * @param {SlideData} data\n   * @param {number} index\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(data, index, pswp) {\n    this.data = data;\n    this.index = index;\n    this.pswp = pswp;\n    this.isActive = (index === pswp.currIndex);\n    this.currentResolution = 0;\n    /** @type {Point} */\n    this.panAreaSize = { x: 0, y: 0 };\n    /** @type {Point} */\n    this.pan = { x: 0, y: 0 };\n\n    this.isFirstSlide = (this.isActive && !pswp.opener.isOpen);\n\n    this.zoomLevels = new ZoomLevel(pswp.options, data, index, pswp);\n\n    this.pswp.dispatch('gettingData', {\n      slide: this,\n      data: this.data,\n      index\n    });\n\n    this.content = this.pswp.contentLoader.getContentBySlide(this);\n    this.container = createElement('pswp__zoom-wrap', 'div');\n    /** @type {HTMLElement | null} */\n    this.holderElement = null;\n\n    this.currZoomLevel = 1;\n    /** @type {number} */\n    this.width = this.content.width;\n    /** @type {number} */\n    this.height = this.content.height;\n    this.heavyAppended = false;\n    this.bounds = new PanBounds(this);\n\n    this.prevDisplayedWidth = -1;\n    this.prevDisplayedHeight = -1;\n\n    this.pswp.dispatch('slideInit', { slide: this });\n  }\n\n  /**\n   * If this slide is active/current/visible\n   *\n   * @param {boolean} isActive\n   */\n  setIsActive(isActive) {\n    if (isActive && !this.isActive) {\n      // slide just became active\n      this.activate();\n    } else if (!isActive && this.isActive) {\n      // slide just became non-active\n      this.deactivate();\n    }\n  }\n\n  /**\n   * Appends slide content to DOM\n   *\n   * @param {HTMLElement} holderElement\n   */\n  append(holderElement) {\n    this.holderElement = holderElement;\n\n    this.container.style.transformOrigin = '0 0';\n\n    // Slide appended to DOM\n    if (!this.data) {\n      return;\n    }\n\n    this.calculateSize();\n\n    this.load();\n    this.updateContentSize();\n    this.appendHeavy();\n\n    this.holderElement.appendChild(this.container);\n\n    this.zoomAndPanToInitial();\n\n    this.pswp.dispatch('firstZoomPan', { slide: this });\n\n    this.applyCurrentZoomPan();\n\n    this.pswp.dispatch('afterSetContent', { slide: this });\n\n    if (this.isActive) {\n      this.activate();\n    }\n  }\n\n  load() {\n    this.content.load(false);\n    this.pswp.dispatch('slideLoad', { slide: this });\n  }\n\n  /**\n   * Append \"heavy\" DOM elements\n   *\n   * This may depend on a type of slide,\n   * but generally these are large images.\n   */\n  appendHeavy() {\n    const { pswp } = this;\n    const appendHeavyNearby = true; // todo\n\n    // Avoid appending heavy elements during animations\n    if (this.heavyAppended\n        || !pswp.opener.isOpen\n        || pswp.mainScroll.isShifted()\n        || (!this.isActive && !appendHeavyNearby)) {\n      return;\n    }\n\n    if (this.pswp.dispatch('appendHeavy', { slide: this }).defaultPrevented) {\n      return;\n    }\n\n    this.heavyAppended = true;\n\n    this.content.append();\n\n    this.pswp.dispatch('appendHeavyContent', { slide: this });\n  }\n\n  /**\n   * Triggered when this slide is active (selected).\n   *\n   * If it's part of opening/closing transition -\n   * activate() will trigger after the transition is ended.\n   */\n  activate() {\n    this.isActive = true;\n    this.appendHeavy();\n    this.content.activate();\n    this.pswp.dispatch('slideActivate', { slide: this });\n  }\n\n  /**\n   * Triggered when this slide becomes inactive.\n   *\n   * Slide can become inactive only after it was active.\n   */\n  deactivate() {\n    this.isActive = false;\n    this.content.deactivate();\n\n    if (this.currZoomLevel !== this.zoomLevels.initial) {\n      // allow filtering\n      this.calculateSize();\n    }\n\n    // reset zoom level\n    this.currentResolution = 0;\n    this.zoomAndPanToInitial();\n    this.applyCurrentZoomPan();\n    this.updateContentSize();\n\n    this.pswp.dispatch('slideDeactivate', { slide: this });\n  }\n\n  /**\n   * The slide should destroy itself, it will never be used again.\n   * (unbind all events and destroy internal components)\n   */\n  destroy() {\n    this.content.hasSlide = false;\n    this.content.remove();\n    this.container.remove();\n    this.pswp.dispatch('slideDestroy', { slide: this });\n  }\n\n  resize() {\n    if (this.currZoomLevel === this.zoomLevels.initial || !this.isActive) {\n      // Keep initial zoom level if it was before the resize,\n      // as well as when this slide is not active\n\n      // Reset position and scale to original state\n      this.calculateSize();\n      this.currentResolution = 0;\n      this.zoomAndPanToInitial();\n      this.applyCurrentZoomPan();\n      this.updateContentSize();\n    } else {\n      // readjust pan position if it's beyond the bounds\n      this.calculateSize();\n      this.bounds.update(this.currZoomLevel);\n      this.panTo(this.pan.x, this.pan.y);\n    }\n  }\n\n\n  /**\n   * Apply size to current slide content,\n   * based on the current resolution and scale.\n   *\n   * @param {boolean} [force] if size should be updated even if dimensions weren't changed\n   */\n  updateContentSize(force) {\n    // Use initial zoom level\n    // if resolution is not defined (user didn't zoom yet)\n    const scaleMultiplier = this.currentResolution || this.zoomLevels.initial;\n\n    if (!scaleMultiplier) {\n      return;\n    }\n\n    const width = Math.round(this.width * scaleMultiplier) || this.pswp.viewportSize.x;\n    const height = Math.round(this.height * scaleMultiplier) || this.pswp.viewportSize.y;\n\n    if (!this.sizeChanged(width, height) && !force) {\n      return;\n    }\n    this.content.setDisplayedSize(width, height);\n  }\n\n  /**\n   * @param {number} width\n   * @param {number} height\n   */\n  sizeChanged(width, height) {\n    if (width !== this.prevDisplayedWidth\n        || height !== this.prevDisplayedHeight) {\n      this.prevDisplayedWidth = width;\n      this.prevDisplayedHeight = height;\n      return true;\n    }\n\n    return false;\n  }\n\n  /** @returns {HTMLImageElement | HTMLDivElement | null | undefined} */\n  getPlaceholderElement() {\n    return this.content.placeholder?.element;\n  }\n\n  /**\n   * Zoom current slide image to...\n   *\n   * @param {number} destZoomLevel Destination zoom level.\n   * @param {Point} [centerPoint]\n   * Transform origin center point, or false if viewport center should be used.\n   * @param {number | false} [transitionDuration] Transition duration, may be set to 0.\n   * @param {boolean} [ignoreBounds] Minimum and maximum zoom levels will be ignored.\n   */\n  zoomTo(destZoomLevel, centerPoint, transitionDuration, ignoreBounds) {\n    const { pswp } = this;\n    if (!this.isZoomable()\n        || pswp.mainScroll.isShifted()) {\n      return;\n    }\n\n    pswp.dispatch('beforeZoomTo', {\n      destZoomLevel, centerPoint, transitionDuration\n    });\n\n    // stop all pan and zoom transitions\n    pswp.animations.stopAllPan();\n\n    // if (!centerPoint) {\n    //   centerPoint = pswp.getViewportCenterPoint();\n    // }\n\n    const prevZoomLevel = this.currZoomLevel;\n\n    if (!ignoreBounds) {\n      destZoomLevel = clamp(destZoomLevel, this.zoomLevels.min, this.zoomLevels.max);\n    }\n\n    // if (transitionDuration === undefined) {\n    //   transitionDuration = this.pswp.options.zoomAnimationDuration;\n    // }\n\n    this.setZoomLevel(destZoomLevel);\n    this.pan.x = this.calculateZoomToPanOffset('x', centerPoint, prevZoomLevel);\n    this.pan.y = this.calculateZoomToPanOffset('y', centerPoint, prevZoomLevel);\n    roundPoint(this.pan);\n\n    const finishTransition = () => {\n      this._setResolution(destZoomLevel);\n      this.applyCurrentZoomPan();\n    };\n\n    if (!transitionDuration) {\n      finishTransition();\n    } else {\n      pswp.animations.startTransition({\n        isPan: true,\n        name: 'zoomTo',\n        target: this.container,\n        transform: this.getCurrentTransform(),\n        onComplete: finishTransition,\n        duration: transitionDuration,\n        easing: pswp.options.easing\n      });\n    }\n  }\n\n  /**\n   * @param {Point} [centerPoint]\n   */\n  toggleZoom(centerPoint) {\n    this.zoomTo(\n      this.currZoomLevel === this.zoomLevels.initial\n        ? this.zoomLevels.secondary : this.zoomLevels.initial,\n      centerPoint,\n      this.pswp.options.zoomAnimationDuration\n    );\n  }\n\n  /**\n   * Updates zoom level property and recalculates new pan bounds,\n   * unlike zoomTo it does not apply transform (use applyCurrentZoomPan)\n   *\n   * @param {number} currZoomLevel\n   */\n  setZoomLevel(currZoomLevel) {\n    this.currZoomLevel = currZoomLevel;\n    this.bounds.update(this.currZoomLevel);\n  }\n\n  /**\n   * Get pan position after zoom at a given `point`.\n   *\n   * Always call setZoomLevel(newZoomLevel) beforehand to recalculate\n   * pan bounds according to the new zoom level.\n   *\n   * @param {'x' | 'y'} axis\n   * @param {Point} [point]\n   * point based on which zoom is performed, usually refers to the current mouse position,\n   * if false - viewport center will be used.\n   * @param {number} [prevZoomLevel] Zoom level before new zoom was applied.\n   * @returns {number}\n   */\n  calculateZoomToPanOffset(axis, point, prevZoomLevel) {\n    const totalPanDistance = this.bounds.max[axis] - this.bounds.min[axis];\n    if (totalPanDistance === 0) {\n      return this.bounds.center[axis];\n    }\n\n    if (!point) {\n      point = this.pswp.getViewportCenterPoint();\n    }\n\n    if (!prevZoomLevel) {\n      prevZoomLevel = this.zoomLevels.initial;\n    }\n\n    const zoomFactor = this.currZoomLevel / prevZoomLevel;\n    return this.bounds.correctPan(\n      axis,\n      (this.pan[axis] - point[axis]) * zoomFactor + point[axis]\n    );\n  }\n\n  /**\n   * Apply pan and keep it within bounds.\n   *\n   * @param {number} panX\n   * @param {number} panY\n   */\n  panTo(panX, panY) {\n    this.pan.x = this.bounds.correctPan('x', panX);\n    this.pan.y = this.bounds.correctPan('y', panY);\n    this.applyCurrentZoomPan();\n  }\n\n  /**\n   * If the slide in the current state can be panned by the user\n   * @returns {boolean}\n   */\n  isPannable() {\n    return Boolean(this.width) && (this.currZoomLevel > this.zoomLevels.fit);\n  }\n\n  /**\n   * If the slide can be zoomed\n   * @returns {boolean}\n   */\n  isZoomable() {\n    return Boolean(this.width) && this.content.isZoomable();\n  }\n\n  /**\n   * Apply transform and scale based on\n   * the current pan position (this.pan) and zoom level (this.currZoomLevel)\n   */\n  applyCurrentZoomPan() {\n    this._applyZoomTransform(this.pan.x, this.pan.y, this.currZoomLevel);\n    if (this === this.pswp.currSlide) {\n      this.pswp.dispatch('zoomPanUpdate', { slide: this });\n    }\n  }\n\n  zoomAndPanToInitial() {\n    this.currZoomLevel = this.zoomLevels.initial;\n\n    // pan according to the zoom level\n    this.bounds.update(this.currZoomLevel);\n    equalizePoints(this.pan, this.bounds.center);\n    this.pswp.dispatch('initialZoomPan', { slide: this });\n  }\n\n  /**\n   * Set translate and scale based on current resolution\n   *\n   * @param {number} x\n   * @param {number} y\n   * @param {number} zoom\n   * @private\n   */\n  _applyZoomTransform(x, y, zoom) {\n    zoom /= this.currentResolution || this.zoomLevels.initial;\n    setTransform(this.container, x, y, zoom);\n  }\n\n  calculateSize() {\n    const { pswp } = this;\n\n    equalizePoints(\n      this.panAreaSize,\n      getPanAreaSize(pswp.options, pswp.viewportSize, this.data, this.index)\n    );\n\n    this.zoomLevels.update(this.width, this.height, this.panAreaSize);\n\n    pswp.dispatch('calcSlideSize', {\n      slide: this\n    });\n  }\n\n  /** @returns {string} */\n  getCurrentTransform() {\n    const scale = this.currZoomLevel / (this.currentResolution || this.zoomLevels.initial);\n    return toTransformString(this.pan.x, this.pan.y, scale);\n  }\n\n  /**\n   * Set resolution and re-render the image.\n   *\n   * For example, if the real image size is 2000x1500,\n   * and resolution is 0.5 - it will be rendered as 1000x750.\n   *\n   * Image with zoom level 2 and resolution 0.5 is\n   * the same as image with zoom level 1 and resolution 1.\n   *\n   * Used to optimize animations and make\n   * sure that browser renders image in the highest quality.\n   * Also used by responsive images to load the correct one.\n   *\n   * @param {number} newResolution\n   */\n  _setResolution(newResolution) {\n    if (newResolution === this.currentResolution) {\n      return;\n    }\n\n    this.currentResolution = newResolution;\n    this.updateContentSize();\n\n    this.pswp.dispatch('resolutionChanged');\n  }\n}\n\nexport default Slide;\n"
  },
  {
    "path": "src/js/slide/zoom-level.js",
    "content": "const MAX_IMAGE_WIDTH = 4000;\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n/** @typedef {import('../photoswipe.js').Point} Point */\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n\n/** @typedef {'fit' | 'fill' | number | ((zoomLevelObject: ZoomLevel) => number)} ZoomLevelOption */\n\n/**\n * Calculates zoom levels for specific slide.\n * Depends on viewport size and image size.\n */\nclass ZoomLevel {\n  /**\n   * @param {PhotoSwipeOptions} options PhotoSwipe options\n   * @param {SlideData} itemData Slide data\n   * @param {number} index Slide index\n   * @param {PhotoSwipe} [pswp] PhotoSwipe instance, can be undefined if not initialized yet\n   */\n  constructor(options, itemData, index, pswp) {\n    this.pswp = pswp;\n    this.options = options;\n    this.itemData = itemData;\n    this.index = index;\n    /** @type { Point | null } */\n    this.panAreaSize = null;\n    /** @type { Point | null } */\n    this.elementSize = null;\n    this.fit = 1;\n    this.fill = 1;\n    this.vFill = 1;\n    this.initial = 1;\n    this.secondary = 1;\n    this.max = 1;\n    this.min = 1;\n  }\n\n  /**\n   * Calculate initial, secondary and maximum zoom level for the specified slide.\n   *\n   * It should be called when either image or viewport size changes.\n   *\n   * @param {number} maxWidth\n   * @param {number} maxHeight\n   * @param {Point} panAreaSize\n   */\n  update(maxWidth, maxHeight, panAreaSize) {\n    /** @type {Point} */\n    const elementSize = { x: maxWidth, y: maxHeight };\n    this.elementSize = elementSize;\n    this.panAreaSize = panAreaSize;\n\n    const hRatio = panAreaSize.x / elementSize.x;\n    const vRatio = panAreaSize.y / elementSize.y;\n\n    this.fit = Math.min(1, hRatio < vRatio ? hRatio : vRatio);\n    this.fill = Math.min(1, hRatio > vRatio ? hRatio : vRatio);\n\n    // zoom.vFill defines zoom level of the image\n    // when it has 100% of viewport vertical space (height)\n    this.vFill = Math.min(1, vRatio);\n\n    this.initial = this._getInitial();\n    this.secondary = this._getSecondary();\n    this.max = Math.max(\n      this.initial,\n      this.secondary,\n      this._getMax()\n    );\n\n    this.min = Math.min(\n      this.fit,\n      this.initial,\n      this.secondary\n    );\n\n    if (this.pswp) {\n      this.pswp.dispatch('zoomLevelsUpdate', { zoomLevels: this, slideData: this.itemData });\n    }\n  }\n\n  /**\n   * Parses user-defined zoom option.\n   *\n   * @private\n   * @param {'initial' | 'secondary' | 'max'} optionPrefix Zoom level option prefix (initial, secondary, max)\n   * @returns { number | undefined }\n   */\n  _parseZoomLevelOption(optionPrefix) {\n    const optionName = /** @type {'initialZoomLevel' | 'secondaryZoomLevel' | 'maxZoomLevel'} */ (\n      optionPrefix + 'ZoomLevel'\n    );\n    const optionValue = this.options[optionName];\n\n    if (!optionValue) {\n      return;\n    }\n\n    if (typeof optionValue === 'function') {\n      return optionValue(this);\n    }\n\n    if (optionValue === 'fill') {\n      return this.fill;\n    }\n\n    if (optionValue === 'fit') {\n      return this.fit;\n    }\n\n    return Number(optionValue);\n  }\n\n  /**\n   * Get zoom level to which image will be zoomed after double-tap gesture,\n   * or when user clicks on zoom icon,\n   * or mouse-click on image itself.\n   * If you return 1 image will be zoomed to its original size.\n   *\n   * @private\n   * @return {number}\n   */\n  _getSecondary() {\n    let currZoomLevel = this._parseZoomLevelOption('secondary');\n\n    if (currZoomLevel) {\n      return currZoomLevel;\n    }\n\n    // 3x of \"fit\" state, but not larger than original\n    currZoomLevel = Math.min(1, this.fit * 3);\n\n    if (this.elementSize && currZoomLevel * this.elementSize.x > MAX_IMAGE_WIDTH) {\n      currZoomLevel = MAX_IMAGE_WIDTH / this.elementSize.x;\n    }\n\n    return currZoomLevel;\n  }\n\n  /**\n   * Get initial image zoom level.\n   *\n   * @private\n   * @return {number}\n   */\n  _getInitial() {\n    return this._parseZoomLevelOption('initial') || this.fit;\n  }\n\n  /**\n   * Maximum zoom level when user zooms\n   * via zoom/pinch gesture,\n   * via cmd/ctrl-wheel or via trackpad.\n   *\n   * @private\n   * @return {number}\n   */\n  _getMax() {\n    // max zoom level is x4 from \"fit state\",\n    // used for zoom gesture and ctrl/trackpad zoom\n    return this._parseZoomLevelOption('max') || Math.max(1, this.fit * 4);\n  }\n}\n\nexport default ZoomLevel;\n"
  },
  {
    "path": "src/js/types.ts",
    "content": "export type Methods<T> = {[M in keyof T]: T[M] extends (...a: any) => any ? M : never}[keyof T]\n\nexport type AddPostfix<T extends string, P extends string> = `${T}${P}`\n\nexport interface Type<T> extends Function {\n  new(...args: any[]): T;\n}\n"
  },
  {
    "path": "src/js/ui/button-arrow.js",
    "content": "/*\n  Backward and forward arrow buttons\n */\n\n/** @typedef {import('./ui-element.js').UIElementData} UIElementData */\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/**\n *\n * @param {HTMLElement} element\n * @param {PhotoSwipe} pswp\n * @param {boolean} [isNextButton]\n */\nfunction initArrowButton(element, pswp, isNextButton) {\n  element.classList.add('pswp__button--arrow');\n  // TODO: this should point to a unique id for this instance\n  element.setAttribute('aria-controls', 'pswp__items');\n  pswp.on('change', () => {\n    if (!pswp.options.loop) {\n      if (isNextButton) {\n        /** @type {HTMLButtonElement} */\n        (element).disabled = !(pswp.currIndex < pswp.getNumItems() - 1);\n      } else {\n        /** @type {HTMLButtonElement} */\n        (element).disabled = !(pswp.currIndex > 0);\n      }\n    }\n  });\n}\n\n/** @type {UIElementData} */\nexport const arrowPrev = {\n  name: 'arrowPrev',\n  className: 'pswp__button--arrow--prev',\n  title: 'Previous',\n  order: 10,\n  isButton: true,\n  appendTo: 'wrapper',\n  html: {\n    isCustomSVG: true,\n    size: 60,\n    inner: '<path d=\"M29 43l-3 3-16-16 16-16 3 3-13 13 13 13z\" id=\"pswp__icn-arrow\"/>',\n    outlineID: 'pswp__icn-arrow'\n  },\n  onClick: 'prev',\n  onInit: initArrowButton\n};\n\n/** @type {UIElementData} */\nexport const arrowNext = {\n  name: 'arrowNext',\n  className: 'pswp__button--arrow--next',\n  title: 'Next',\n  order: 11,\n  isButton: true,\n  appendTo: 'wrapper',\n  html: {\n    isCustomSVG: true,\n    size: 60,\n    inner: '<use xlink:href=\"#pswp__icn-arrow\"/>',\n    outlineID: 'pswp__icn-arrow'\n  },\n  onClick: 'next',\n  onInit: (el, pswp) => {\n    initArrowButton(el, pswp, true);\n  }\n};\n"
  },
  {
    "path": "src/js/ui/button-close.js",
    "content": "/** @type {import('./ui-element.js').UIElementData} UIElementData */\nconst closeButton = {\n  name: 'close',\n  title: 'Close',\n  order: 20,\n  isButton: true,\n  html: {\n    isCustomSVG: true,\n    inner: '<path d=\"M24 10l-2-2-6 6-6-6-2 2 6 6-6 6 2 2 6-6 6 6 2-2-6-6z\" id=\"pswp__icn-close\"/>',\n    outlineID: 'pswp__icn-close'\n  },\n  onClick: 'close'\n};\n\nexport default closeButton;\n"
  },
  {
    "path": "src/js/ui/button-zoom.js",
    "content": "/** @type {import('./ui-element.js').UIElementData} UIElementData */\nconst zoomButton = {\n  name: 'zoom',\n  title: 'Zoom',\n  order: 10,\n  isButton: true,\n  html: {\n    isCustomSVG: true,\n    // eslint-disable-next-line max-len\n    inner: '<path d=\"M17.426 19.926a6 6 0 1 1 1.5-1.5L23 22.5 21.5 24l-4.074-4.074z\" id=\"pswp__icn-zoom\"/>'\n          + '<path fill=\"currentColor\" class=\"pswp__zoom-icn-bar-h\" d=\"M11 16v-2h6v2z\"/>'\n          + '<path fill=\"currentColor\" class=\"pswp__zoom-icn-bar-v\" d=\"M13 12h2v6h-2z\"/>',\n    outlineID: 'pswp__icn-zoom'\n  },\n  onClick: 'toggleZoom'\n};\n\nexport default zoomButton;\n"
  },
  {
    "path": "src/js/ui/counter-indicator.js",
    "content": "/** @type {import('./ui-element.js').UIElementData} UIElementData */\nexport const counterIndicator = {\n  name: 'counter',\n  order: 5,\n  onInit: (counterElement, pswp) => {\n    pswp.on('change', () => {\n      counterElement.innerText = (pswp.currIndex + 1)\n                                  + pswp.options.indexIndicatorSep\n                                  + pswp.getNumItems();\n    });\n  }\n};\n"
  },
  {
    "path": "src/js/ui/loading-indicator.js",
    "content": "/** @type {import('./ui-element.js').UIElementData} UIElementData */\nexport const loadingIndicator = {\n  name: 'preloader',\n  appendTo: 'bar',\n  order: 7,\n  html: {\n    isCustomSVG: true,\n    // eslint-disable-next-line max-len\n    inner: '<path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M21.2 16a5.2 5.2 0 1 1-5.2-5.2V8a8 8 0 1 0 8 8h-2.8Z\" id=\"pswp__icn-loading\"/>',\n    outlineID: 'pswp__icn-loading'\n  },\n  onInit: (indicatorElement, pswp) => {\n    /** @type {boolean | undefined} */\n    let isVisible;\n    /** @type {NodeJS.Timeout | null} */\n    let delayTimeout = null;\n\n    /**\n     * @param {string} className\n     * @param {boolean} add\n     */\n    const toggleIndicatorClass = (className, add) => {\n      indicatorElement.classList.toggle('pswp__preloader--' + className, add);\n    };\n\n    /**\n     * @param {boolean} visible\n     */\n    const setIndicatorVisibility = (visible) => {\n      if (isVisible !== visible) {\n        isVisible = visible;\n        toggleIndicatorClass('active', visible);\n      }\n    };\n\n    const updatePreloaderVisibility = () => {\n      if (!pswp.currSlide?.content.isLoading()) {\n        setIndicatorVisibility(false);\n        if (delayTimeout) {\n          clearTimeout(delayTimeout);\n          delayTimeout = null;\n        }\n        return;\n      }\n\n      if (!delayTimeout) {\n        // display loading indicator with delay\n        delayTimeout = setTimeout(() => {\n          setIndicatorVisibility(Boolean(pswp.currSlide?.content.isLoading()));\n          delayTimeout = null;\n        }, pswp.options.preloaderDelay);\n      }\n    };\n\n    pswp.on('change', updatePreloaderVisibility);\n\n    pswp.on('loadComplete', (e) => {\n      if (pswp.currSlide === e.slide) {\n        updatePreloaderVisibility();\n      }\n    });\n\n    // expose the method\n    if (pswp.ui) {\n      pswp.ui.updatePreloaderVisibility = updatePreloaderVisibility;\n    }\n  }\n};\n"
  },
  {
    "path": "src/js/ui/ui-element.js",
    "content": "import { createElement } from '../util/util.js';\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n\n/**\n * @template T\n * @typedef {import('../types.js').Methods<T>} Methods<T>\n */\n\n/**\n * @typedef {Object} UIElementMarkupProps\n * @prop {boolean} [isCustomSVG]\n * @prop {string} inner\n * @prop {string} [outlineID]\n * @prop {number | string} [size]\n */\n\n/**\n * @typedef {Object} UIElementData\n * @prop {DefaultUIElements | string} [name]\n * @prop {string} [className]\n * @prop {UIElementMarkup} [html]\n * @prop {boolean} [isButton]\n * @prop {keyof HTMLElementTagNameMap} [tagName]\n * @prop {string} [title]\n * @prop {string} [ariaLabel]\n * @prop {(element: HTMLElement, pswp: PhotoSwipe) => void} [onInit]\n * @prop {Methods<PhotoSwipe> | ((e: MouseEvent, element: HTMLElement, pswp: PhotoSwipe) => void)} [onClick]\n * @prop {'bar' | 'wrapper' | 'root'} [appendTo]\n * @prop {number} [order]\n */\n\n/** @typedef {'arrowPrev' | 'arrowNext' | 'close' | 'zoom' | 'counter'} DefaultUIElements */\n\n/** @typedef {string | UIElementMarkupProps} UIElementMarkup */\n\n/**\n * @param {UIElementMarkup} [htmlData]\n * @returns {string}\n */\nfunction addElementHTML(htmlData) {\n  if (typeof htmlData === 'string') {\n    // Allow developers to provide full svg,\n    // For example:\n    // <svg viewBox=\"0 0 32 32\" width=\"32\" height=\"32\" aria-hidden=\"true\" class=\"pswp__icn\">\n    //   <path d=\"...\" />\n    //   <circle ... />\n    // </svg>\n    // Can also be any HTML string.\n    return htmlData;\n  }\n\n  if (!htmlData || !htmlData.isCustomSVG) {\n    return '';\n  }\n\n  const svgData = htmlData;\n  let out = '<svg aria-hidden=\"true\" class=\"pswp__icn\" viewBox=\"0 0 %d %d\" width=\"%d\" height=\"%d\">';\n  // replace all %d with size\n  out = out.split('%d').join(/** @type {string} */ (svgData.size || 32));\n\n  // Icons may contain outline/shadow,\n  // to make it we \"clone\" base icon shape and add border to it.\n  // Icon itself and border are styled via CSS.\n  //\n  // Property shadowID defines ID of element that should be cloned.\n  if (svgData.outlineID) {\n    out += '<use class=\"pswp__icn-shadow\" xlink:href=\"#' + svgData.outlineID + '\"/>';\n  }\n\n  out += svgData.inner;\n\n  out += '</svg>';\n\n  return out;\n}\n\nclass UIElement {\n  /**\n   * @param {PhotoSwipe} pswp\n   * @param {UIElementData} data\n   */\n  constructor(pswp, data) {\n    const name = data.name || data.className;\n    let elementHTML = data.html;\n\n    // @ts-expect-error lookup only by `data.name` maybe?\n    if (pswp.options[name] === false) {\n      // exit if element is disabled from options\n      return;\n    }\n\n    // Allow to override SVG icons from options\n    // @ts-expect-error lookup only by `data.name` maybe?\n    if (typeof pswp.options[name + 'SVG'] === 'string') {\n      // arrowPrevSVG\n      // arrowNextSVG\n      // closeSVG\n      // zoomSVG\n      // @ts-expect-error lookup only by `data.name` maybe?\n      elementHTML = pswp.options[name + 'SVG'];\n    }\n\n    pswp.dispatch('uiElementCreate', { data });\n\n    let className = '';\n    if (data.isButton) {\n      className += 'pswp__button ';\n      className += (data.className || `pswp__button--${data.name}`);\n    } else {\n      className += (data.className || `pswp__${data.name}`);\n    }\n\n    let tagName = data.isButton ? (data.tagName || 'button') : (data.tagName || 'div');\n    tagName = /** @type {keyof HTMLElementTagNameMap} */ (tagName.toLowerCase());\n    /** @type {HTMLElement} */\n    const element = createElement(className, tagName);\n\n    if (data.isButton) {\n      if (tagName === 'button') {\n        /** @type {HTMLButtonElement} */ (element).type = 'button';\n      }\n\n      let { title } = data;\n      const { ariaLabel } = data;\n\n      // @ts-expect-error lookup only by `data.name` maybe?\n      if (typeof pswp.options[name + 'Title'] === 'string') {\n        // @ts-expect-error lookup only by `data.name` maybe?\n        title = pswp.options[name + 'Title'];\n      }\n\n      if (title) {\n        element.title = title;\n      }\n\n      const ariaText = ariaLabel || title;\n      if (ariaText) {\n        element.setAttribute('aria-label', ariaText);\n      }\n    }\n\n    element.innerHTML = addElementHTML(elementHTML);\n\n    if (data.onInit) {\n      data.onInit(element, pswp);\n    }\n\n    if (data.onClick) {\n      element.onclick = (e) => {\n        if (typeof data.onClick === 'string') {\n          // @ts-ignore\n          pswp[data.onClick]();\n        } else if (typeof data.onClick === 'function') {\n          data.onClick(e, element, pswp);\n        }\n      };\n    }\n\n    // Top bar is default position\n    const appendTo = data.appendTo || 'bar';\n    /** @type {HTMLElement | undefined} root element by default */\n    let container = pswp.element;\n    if (appendTo === 'bar') {\n      if (!pswp.topBar) {\n        pswp.topBar = createElement('pswp__top-bar pswp__hide-on-close', 'div', pswp.scrollWrap);\n      }\n      container = pswp.topBar;\n    } else {\n      // element outside of top bar gets a secondary class\n      // that makes element fade out on close\n      element.classList.add('pswp__hide-on-close');\n\n      if (appendTo === 'wrapper') {\n        container = pswp.scrollWrap;\n      }\n    }\n\n    container?.appendChild(pswp.applyFilters('uiElement', element, data));\n  }\n}\n\nexport default UIElement;\n"
  },
  {
    "path": "src/js/ui/ui.js",
    "content": "import UIElement from './ui-element.js';\nimport { arrowPrev, arrowNext } from './button-arrow.js';\nimport closeButton from './button-close.js';\nimport zoomButton from './button-zoom.js';\nimport { loadingIndicator } from './loading-indicator.js';\nimport { counterIndicator } from './counter-indicator.js';\n\n/** @typedef {import('../photoswipe.js').default} PhotoSwipe */\n/** @typedef {import('./ui-element.js').UIElementData} UIElementData */\n\n/**\n * Set special class on element when image is zoomed.\n *\n * By default, it is used to adjust\n * zoom icon and zoom cursor via CSS.\n *\n * @param {HTMLElement} el\n * @param {boolean} isZoomedIn\n */\nfunction setZoomedIn(el, isZoomedIn) {\n  el.classList.toggle('pswp--zoomed-in', isZoomedIn);\n}\n\nclass UI {\n  /**\n   * @param {PhotoSwipe} pswp\n   */\n  constructor(pswp) {\n    this.pswp = pswp;\n    this.isRegistered = false;\n    /** @type {UIElementData[]} */\n    this.uiElementsData = [];\n    /** @type {(UIElement | UIElementData)[]} */\n    this.items = [];\n    /** @type {() => void} */\n    this.updatePreloaderVisibility = () => {};\n\n    /**\n     * @private\n     * @type {number | undefined}\n     */\n    this._lastUpdatedZoomLevel = undefined;\n  }\n\n  init() {\n    const { pswp } = this;\n    this.isRegistered = false;\n    this.uiElementsData = [\n      closeButton,\n      arrowPrev,\n      arrowNext,\n      zoomButton,\n      loadingIndicator,\n      counterIndicator\n    ];\n\n    pswp.dispatch('uiRegister');\n\n    // sort by order\n    this.uiElementsData.sort((a, b) => {\n      // default order is 0\n      return (a.order || 0) - (b.order || 0);\n    });\n\n    this.items = [];\n\n    this.isRegistered = true;\n    this.uiElementsData.forEach((uiElementData) => {\n      this.registerElement(uiElementData);\n    });\n\n    pswp.on('change', () => {\n      pswp.element?.classList.toggle('pswp--one-slide', pswp.getNumItems() === 1);\n    });\n\n    pswp.on('zoomPanUpdate', () => this._onZoomPanUpdate());\n  }\n\n  /**\n   * @param {UIElementData} elementData\n   */\n  registerElement(elementData) {\n    if (this.isRegistered) {\n      this.items.push(\n        new UIElement(this.pswp, elementData)\n      );\n    } else {\n      this.uiElementsData.push(elementData);\n    }\n  }\n\n  /**\n   * Fired each time zoom or pan position is changed.\n   * Update classes that control visibility of zoom button and cursor icon.\n   *\n   * @private\n   */\n  _onZoomPanUpdate() {\n    const { template, currSlide, options } = this.pswp;\n\n    if (this.pswp.opener.isClosing || !template || !currSlide) {\n      return;\n    }\n\n    let { currZoomLevel } = currSlide;\n\n    // if not open yet - check against initial zoom level\n    if (!this.pswp.opener.isOpen) {\n      currZoomLevel = currSlide.zoomLevels.initial;\n    }\n\n    if (currZoomLevel === this._lastUpdatedZoomLevel) {\n      return;\n    }\n    this._lastUpdatedZoomLevel = currZoomLevel;\n\n    const currZoomLevelDiff = currSlide.zoomLevels.initial - currSlide.zoomLevels.secondary;\n\n    // Initial and secondary zoom levels are almost equal\n    if (Math.abs(currZoomLevelDiff) < 0.01 || !currSlide.isZoomable()) {\n      // disable zoom\n      setZoomedIn(template, false);\n      template.classList.remove('pswp--zoom-allowed');\n      return;\n    }\n\n    template.classList.add('pswp--zoom-allowed');\n\n    const potentialZoomLevel = currZoomLevel === currSlide.zoomLevels.initial\n      ? currSlide.zoomLevels.secondary : currSlide.zoomLevels.initial;\n\n    setZoomedIn(template, potentialZoomLevel <= currZoomLevel);\n\n    if (options.imageClickAction === 'zoom'\n        || options.imageClickAction === 'zoom-or-close') {\n      template.classList.add('pswp--click-to-zoom');\n    }\n  }\n}\n\nexport default UI;\n"
  },
  {
    "path": "src/js/util/animations.js",
    "content": "import CSSAnimation from './css-animation.js';\nimport SpringAnimation from './spring-animation.js';\n\n/** @typedef {import('./css-animation.js').CssAnimationProps} CssAnimationProps */\n/** @typedef {import('./spring-animation.js').SpringAnimationProps} SpringAnimationProps */\n\n/** @typedef {Object} SharedAnimationProps\n * @prop {string} [name]\n * @prop {boolean} [isPan]\n * @prop {boolean} [isMainScroll]\n * @prop {VoidFunction} [onComplete]\n * @prop {VoidFunction} [onFinish]\n */\n\n/** @typedef {SpringAnimation | CSSAnimation} Animation */\n/** @typedef {SpringAnimationProps | CssAnimationProps} AnimationProps */\n\n/**\n * Manages animations\n */\nclass Animations {\n  constructor() {\n    /** @type {Animation[]} */\n    this.activeAnimations = [];\n  }\n\n  /**\n   * @param {SpringAnimationProps} props\n   */\n  startSpring(props) {\n    this._start(props, true);\n  }\n\n  /**\n   * @param {CssAnimationProps} props\n   */\n  startTransition(props) {\n    this._start(props);\n  }\n\n  /**\n   * @private\n   * @param {AnimationProps} props\n   * @param {boolean} [isSpring]\n   * @returns {Animation}\n   */\n  _start(props, isSpring) {\n    const animation = isSpring\n      ? new SpringAnimation(/** @type SpringAnimationProps */ (props))\n      : new CSSAnimation(/** @type CssAnimationProps */ (props));\n\n    this.activeAnimations.push(animation);\n    animation.onFinish = () => this.stop(animation);\n\n    return animation;\n  }\n\n  /**\n   * @param {Animation} animation\n   */\n  stop(animation) {\n    animation.destroy();\n    const index = this.activeAnimations.indexOf(animation);\n    if (index > -1) {\n      this.activeAnimations.splice(index, 1);\n    }\n  }\n\n  stopAll() { // _stopAllAnimations\n    this.activeAnimations.forEach((animation) => {\n      animation.destroy();\n    });\n    this.activeAnimations = [];\n  }\n\n  /**\n   * Stop all pan or zoom transitions\n   */\n  stopAllPan() {\n    this.activeAnimations = this.activeAnimations.filter((animation) => {\n      if (animation.props.isPan) {\n        animation.destroy();\n        return false;\n      }\n\n      return true;\n    });\n  }\n\n  stopMainScroll() {\n    this.activeAnimations = this.activeAnimations.filter((animation) => {\n      if (animation.props.isMainScroll) {\n        animation.destroy();\n        return false;\n      }\n\n      return true;\n    });\n  }\n\n  /**\n   * Returns true if main scroll transition is running\n   */\n  // isMainScrollRunning() {\n  //   return this.activeAnimations.some((animation) => {\n  //     return animation.props.isMainScroll;\n  //   });\n  // }\n\n  /**\n   * Returns true if any pan or zoom transition is running\n   */\n  isPanRunning() {\n    return this.activeAnimations.some((animation) => {\n      return animation.props.isPan;\n    });\n  }\n}\n\nexport default Animations;\n"
  },
  {
    "path": "src/js/util/css-animation.js",
    "content": "import { setTransitionStyle, removeTransitionStyle } from './util.js';\n\nconst DEFAULT_EASING = 'cubic-bezier(.4,0,.22,1)';\n\n/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */\n\n/** @typedef {Object} DefaultCssAnimationProps\n *\n * @prop {HTMLElement} target\n * @prop {number} [duration]\n * @prop {string} [easing]\n * @prop {string} [transform]\n * @prop {string} [opacity]\n * */\n\n/** @typedef {SharedAnimationProps & DefaultCssAnimationProps} CssAnimationProps */\n\n/**\n * Runs CSS transition.\n */\nclass CSSAnimation {\n  /**\n   * onComplete can be unpredictable, be careful about current state\n   *\n   * @param {CssAnimationProps} props\n   */\n  constructor(props) {\n    this.props = props;\n    const {\n      target,\n      onComplete,\n      transform,\n      onFinish = () => {},\n      duration = 333,\n      easing = DEFAULT_EASING,\n    } = props;\n\n    this.onFinish = onFinish;\n\n    // support only transform and opacity\n    const prop = transform ? 'transform' : 'opacity';\n    const propValue = props[prop] ?? '';\n\n    /** @private */\n    this._target = target;\n    /** @private */\n    this._onComplete = onComplete;\n    /** @private */\n    this._finished = false;\n\n    /** @private */\n    this._onTransitionEnd = this._onTransitionEnd.bind(this);\n\n    // Using timeout hack to make sure that animation\n    // starts even if the animated property was changed recently,\n    // otherwise transitionend might not fire or transition won't start.\n    // https://drafts.csswg.org/css-transitions/#starting\n    //\n    // ¯\\_(ツ)_/¯\n    /** @private */\n    this._helperTimeout = setTimeout(() => {\n      setTransitionStyle(target, prop, duration, easing);\n      this._helperTimeout = setTimeout(() => {\n        target.addEventListener('transitionend', this._onTransitionEnd, false);\n        target.addEventListener('transitioncancel', this._onTransitionEnd, false);\n\n        // Safari occasionally does not emit transitionend event\n        // if element property was modified during the transition,\n        // which may be caused by resize or third party component,\n        // using timeout as a safety fallback\n        this._helperTimeout = setTimeout(() => {\n          this._finalizeAnimation();\n        }, duration + 500);\n        target.style[prop] = propValue;\n      }, 30); // Do not reduce this number\n    }, 0);\n  }\n\n  /**\n   * @private\n   * @param {TransitionEvent} e\n   */\n  _onTransitionEnd(e) {\n    if (e.target === this._target) {\n      this._finalizeAnimation();\n    }\n  }\n\n  /**\n   * @private\n   */\n  _finalizeAnimation() {\n    if (!this._finished) {\n      this._finished = true;\n      this.onFinish();\n      if (this._onComplete) {\n        this._onComplete();\n      }\n    }\n  }\n\n  // Destroy is called automatically onFinish\n  destroy() {\n    if (this._helperTimeout) {\n      clearTimeout(this._helperTimeout);\n    }\n    removeTransitionStyle(this._target);\n    this._target.removeEventListener('transitionend', this._onTransitionEnd, false);\n    this._target.removeEventListener('transitioncancel', this._onTransitionEnd, false);\n    if (!this._finished) {\n      this._finalizeAnimation();\n    }\n  }\n}\n\nexport default CSSAnimation;\n"
  },
  {
    "path": "src/js/util/dom-events.js",
    "content": "// Detect passive event listener support\nlet supportsPassive = false;\n/* eslint-disable */\ntry {\n  /* @ts-ignore */\n  window.addEventListener('test', null, Object.defineProperty({}, 'passive', {\n    get: () => {\n      supportsPassive = true;\n    }\n  }));\n} catch (e) {}\n/* eslint-enable */\n\n/**\n * @typedef {Object} PoolItem\n * @prop {HTMLElement | Window | Document | undefined | null} target\n * @prop {string} type\n * @prop {EventListenerOrEventListenerObject} listener\n * @prop {boolean} [passive]\n */\n\nclass DOMEvents {\n  constructor() {\n    /**\n     * @type {PoolItem[]}\n     * @private\n     */\n    this._pool = [];\n  }\n\n  /**\n   * Adds event listeners\n   *\n   * @param {PoolItem['target']} target\n   * @param {PoolItem['type']} type Can be multiple, separated by space.\n   * @param {PoolItem['listener']} listener\n   * @param {PoolItem['passive']} [passive]\n   */\n  add(target, type, listener, passive) {\n    this._toggleListener(target, type, listener, passive);\n  }\n\n  /**\n   * Removes event listeners\n   *\n   * @param {PoolItem['target']} target\n   * @param {PoolItem['type']} type\n   * @param {PoolItem['listener']} listener\n   * @param {PoolItem['passive']} [passive]\n   */\n  remove(target, type, listener, passive) {\n    this._toggleListener(target, type, listener, passive, true);\n  }\n\n  /**\n   * Removes all bound events\n   */\n  removeAll() {\n    this._pool.forEach((poolItem) => {\n      this._toggleListener(\n        poolItem.target,\n        poolItem.type,\n        poolItem.listener,\n        poolItem.passive,\n        true,\n        true\n      );\n    });\n    this._pool = [];\n  }\n\n  /**\n   * Adds or removes event\n   *\n   * @private\n   * @param {PoolItem['target']} target\n   * @param {PoolItem['type']} type\n   * @param {PoolItem['listener']} listener\n   * @param {PoolItem['passive']} [passive]\n   * @param {boolean} [unbind] Whether the event should be added or removed\n   * @param {boolean} [skipPool] Whether events pool should be skipped\n   */\n  _toggleListener(target, type, listener, passive, unbind, skipPool) {\n    if (!target) {\n      return;\n    }\n\n    const methodName = unbind ? 'removeEventListener' : 'addEventListener';\n    const types = type.split(' ');\n    types.forEach((eType) => {\n      if (eType) {\n        // Events pool is used to easily unbind all events when PhotoSwipe is closed,\n        // so developer doesn't need to do this manually\n        if (!skipPool) {\n          if (unbind) {\n            // Remove from the events pool\n            this._pool = this._pool.filter((poolItem) => {\n              return poolItem.type !== eType\n                || poolItem.listener !== listener\n                || poolItem.target !== target;\n            });\n          } else {\n            // Add to the events pool\n            this._pool.push({\n              target,\n              type: eType,\n              listener,\n              passive\n            });\n          }\n        }\n\n        // most PhotoSwipe events call preventDefault,\n        // and we do not need browser to scroll the page\n        const eventOptions = supportsPassive ? { passive: (passive || false) } : false;\n\n        target[methodName](\n          eType,\n          listener,\n          eventOptions\n        );\n      }\n    });\n  }\n}\n\nexport default DOMEvents;\n"
  },
  {
    "path": "src/js/util/spring-animation.js",
    "content": "import SpringEaser from './spring-easer.js';\n\n/** @typedef {import('./animations.js').SharedAnimationProps} SharedAnimationProps */\n\n/**\n * @typedef {Object} DefaultSpringAnimationProps\n *\n * @prop {number} start\n * @prop {number} end\n * @prop {number} velocity\n * @prop {number} [dampingRatio]\n * @prop {number} [naturalFrequency]\n * @prop {(end: number) => void} onUpdate\n */\n\n/** @typedef {SharedAnimationProps & DefaultSpringAnimationProps} SpringAnimationProps */\n\nclass SpringAnimation {\n  /**\n   * @param {SpringAnimationProps} props\n   */\n  constructor(props) {\n    this.props = props;\n    this._raf = 0;\n\n    const {\n      start,\n      end,\n      velocity,\n      onUpdate,\n      onComplete,\n      onFinish = () => {},\n      dampingRatio,\n      naturalFrequency\n    } = props;\n\n    this.onFinish = onFinish;\n\n    const easer = new SpringEaser(velocity, dampingRatio, naturalFrequency);\n    let prevTime = Date.now();\n    let deltaPosition = start - end;\n\n    const animationLoop = () => {\n      if (this._raf) {\n        deltaPosition = easer.easeFrame(deltaPosition, Date.now() - prevTime);\n\n        // Stop the animation if velocity is low and position is close to end\n        if (Math.abs(deltaPosition) < 1 && Math.abs(easer.velocity) < 50) {\n          // Finalize the animation\n          onUpdate(end);\n          if (onComplete) {\n            onComplete();\n          }\n          this.onFinish();\n        } else {\n          prevTime = Date.now();\n          onUpdate(deltaPosition + end);\n          this._raf = requestAnimationFrame(animationLoop);\n        }\n      }\n    };\n\n    this._raf = requestAnimationFrame(animationLoop);\n  }\n\n  // Destroy is called automatically onFinish\n  destroy() {\n    if (this._raf >= 0) {\n      cancelAnimationFrame(this._raf);\n    }\n    this._raf = 0;\n  }\n}\n\nexport default SpringAnimation;\n"
  },
  {
    "path": "src/js/util/spring-easer.js",
    "content": "const DEFAULT_NATURAL_FREQUENCY = 12;\nconst DEFAULT_DAMPING_RATIO = 0.75;\n\n/**\n * Spring easing helper\n */\nclass SpringEaser {\n  /**\n   * @param {number} initialVelocity Initial velocity, px per ms.\n   *\n   * @param {number} [dampingRatio]\n   * Determines how bouncy animation will be.\n   * From 0 to 1, 0 - always overshoot, 1 - do not overshoot.\n   * \"overshoot\" refers to part of animation that\n   * goes beyond the final value.\n   *\n   * @param {number} [naturalFrequency]\n   * Determines how fast animation will slow down.\n   * The higher value - the stiffer the transition will be,\n   * and the faster it will slow down.\n   * Recommended value from 10 to 50\n   */\n  constructor(initialVelocity, dampingRatio, naturalFrequency) {\n    this.velocity = initialVelocity * 1000; // convert to \"pixels per second\"\n\n    // https://en.wikipedia.org/wiki/Damping_ratio\n    this._dampingRatio = dampingRatio || DEFAULT_DAMPING_RATIO;\n\n    // https://en.wikipedia.org/wiki/Natural_frequency\n    this._naturalFrequency = naturalFrequency || DEFAULT_NATURAL_FREQUENCY;\n\n    this._dampedFrequency = this._naturalFrequency;\n\n    if (this._dampingRatio < 1) {\n      this._dampedFrequency *= Math.sqrt(1 - this._dampingRatio * this._dampingRatio);\n    }\n  }\n\n  /**\n   * @param {number} deltaPosition Difference between current and end position of the animation\n   * @param {number} deltaTime Frame duration in milliseconds\n   *\n   * @returns {number} Displacement, relative to the end position.\n   */\n  easeFrame(deltaPosition, deltaTime) {\n    // Inspired by Apple Webkit and Android spring function implementation\n    // https://en.wikipedia.org/wiki/Oscillation\n    // https://en.wikipedia.org/wiki/Damping_ratio\n    // we ignore mass (assume that it's 1kg)\n\n    let displacement = 0;\n    let coeff;\n\n    deltaTime /= 1000;\n\n    const naturalDumpingPow = Math.E ** (-this._dampingRatio * this._naturalFrequency * deltaTime);\n\n    if (this._dampingRatio === 1) {\n      coeff = this.velocity + this._naturalFrequency * deltaPosition;\n\n      displacement = (deltaPosition + coeff * deltaTime) * naturalDumpingPow;\n\n      this.velocity = displacement\n                        * (-this._naturalFrequency) + coeff\n                        * naturalDumpingPow;\n    } else if (this._dampingRatio < 1) {\n      coeff = (1 / this._dampedFrequency)\n                * (this._dampingRatio * this._naturalFrequency * deltaPosition + this.velocity);\n\n      const dumpedFCos = Math.cos(this._dampedFrequency * deltaTime);\n      const dumpedFSin = Math.sin(this._dampedFrequency * deltaTime);\n\n      displacement = naturalDumpingPow\n                       * (deltaPosition * dumpedFCos + coeff * dumpedFSin);\n\n      this.velocity = displacement\n                        * (-this._naturalFrequency)\n                        * this._dampingRatio\n                        + naturalDumpingPow\n                        * (-this._dampedFrequency * deltaPosition * dumpedFSin\n                        + this._dampedFrequency * coeff * dumpedFCos);\n    }\n\n    // Overdamped (>1) damping ratio is not supported\n\n    return displacement;\n  }\n}\n\nexport default SpringEaser;\n"
  },
  {
    "path": "src/js/util/util.js",
    "content": "/** @typedef {import('../photoswipe.js').Point} Point */\n\n/**\n * @template {keyof HTMLElementTagNameMap} T\n * @param {string} className\n * @param {T} tagName\n * @param {Node} [appendToEl]\n * @returns {HTMLElementTagNameMap[T]}\n */\nexport function createElement(className, tagName, appendToEl) {\n  const el = document.createElement(tagName);\n  if (className) {\n    el.className = className;\n  }\n  if (appendToEl) {\n    appendToEl.appendChild(el);\n  }\n  return el;\n}\n\n/**\n * @param {Point} p1\n * @param {Point} p2\n * @returns {Point}\n */\nexport function equalizePoints(p1, p2) {\n  p1.x = p2.x;\n  p1.y = p2.y;\n  if (p2.id !== undefined) {\n    p1.id = p2.id;\n  }\n  return p1;\n}\n\n/**\n * @param {Point} p\n */\nexport function roundPoint(p) {\n  p.x = Math.round(p.x);\n  p.y = Math.round(p.y);\n}\n\n/**\n * Returns distance between two points.\n *\n * @param {Point} p1\n * @param {Point} p2\n * @returns {number}\n */\nexport function getDistanceBetween(p1, p2) {\n  const x = Math.abs(p1.x - p2.x);\n  const y = Math.abs(p1.y - p2.y);\n  return Math.sqrt((x * x) + (y * y));\n}\n\n/**\n * Whether X and Y positions of points are equal\n *\n * @param {Point} p1\n * @param {Point} p2\n * @returns {boolean}\n */\nexport function pointsEqual(p1, p2) {\n  return p1.x === p2.x && p1.y === p2.y;\n}\n\n/**\n * The float result between the min and max values.\n *\n * @param {number} val\n * @param {number} min\n * @param {number} max\n * @returns {number}\n */\nexport function clamp(val, min, max) {\n  return Math.min(Math.max(val, min), max);\n}\n\n/**\n * Get transform string\n *\n * @param {number} x\n * @param {number} [y]\n * @param {number} [scale]\n * @returns {string}\n */\nexport function toTransformString(x, y, scale) {\n  let propValue = `translate3d(${x}px,${y || 0}px,0)`;\n\n  if (scale !== undefined) {\n    propValue += ` scale3d(${scale},${scale},1)`;\n  }\n\n  return propValue;\n}\n\n/**\n * Apply transform:translate(x, y) scale(scale) to element\n *\n * @param {HTMLElement} el\n * @param {number} x\n * @param {number} [y]\n * @param {number} [scale]\n */\nexport function setTransform(el, x, y, scale) {\n  el.style.transform = toTransformString(x, y, scale);\n}\n\nconst defaultCSSEasing = 'cubic-bezier(.4,0,.22,1)';\n\n/**\n * Apply CSS transition to element\n *\n * @param {HTMLElement} el\n * @param {string} [prop] CSS property to animate\n * @param {number} [duration] in ms\n * @param {string} [ease] CSS easing function\n */\nexport function setTransitionStyle(el, prop, duration, ease) {\n  // inOut: 'cubic-bezier(.4, 0, .22, 1)', // for \"toggle state\" transitions\n  // out: 'cubic-bezier(0, 0, .22, 1)', // for \"show\" transitions\n  // in: 'cubic-bezier(.4, 0, 1, 1)'// for \"hide\" transitions\n  el.style.transition = prop\n    ? `${prop} ${duration}ms ${ease || defaultCSSEasing}`\n    : 'none';\n}\n\n/**\n * Apply width and height CSS properties to element\n *\n * @param {HTMLElement} el\n * @param {string | number} w\n * @param {string | number} h\n */\nexport function setWidthHeight(el, w, h) {\n  el.style.width = (typeof w === 'number') ? `${w}px` : w;\n  el.style.height = (typeof h === 'number') ? `${h}px` : h;\n}\n\n/**\n * @param {HTMLElement} el\n */\nexport function removeTransitionStyle(el) {\n  setTransitionStyle(el);\n}\n\n/**\n * @param {HTMLImageElement} img\n * @returns {Promise<HTMLImageElement | void>}\n */\nexport function decodeImage(img) {\n  if ('decode' in img) {\n    return img.decode().catch(() => {});\n  }\n\n  if (img.complete) {\n    return Promise.resolve(img);\n  }\n\n  return new Promise((resolve, reject) => {\n    img.onload = () => resolve(img);\n    img.onerror = reject;\n  });\n}\n\n/** @typedef {LOAD_STATE[keyof LOAD_STATE]} LoadState */\n/** @type {{ IDLE: 'idle'; LOADING: 'loading'; LOADED: 'loaded'; ERROR: 'error' }} */\nexport const LOAD_STATE = {\n  IDLE: 'idle',\n  LOADING: 'loading',\n  LOADED: 'loaded',\n  ERROR: 'error',\n};\n\n\n/**\n * Check if click or keydown event was dispatched\n * with a special key or via mouse wheel.\n *\n * @param {MouseEvent | KeyboardEvent} e\n * @returns {boolean}\n */\nexport function specialKeyUsed(e) {\n  return ('button' in e && e.button === 1) || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey;\n}\n\n/**\n * Parse `gallery` or `children` options.\n *\n * @param {import('../photoswipe.js').ElementProvider} [option]\n * @param {string} [legacySelector]\n * @param {HTMLElement | Document} [parent]\n * @returns HTMLElement[]\n */\nexport function getElementsFromOption(option, legacySelector, parent = document) {\n  /** @type {HTMLElement[]} */\n  let elements = [];\n\n  if (option instanceof Element) {\n    elements = [option];\n  } else if (option instanceof NodeList || Array.isArray(option)) {\n    elements = Array.from(option);\n  } else {\n    const selector = typeof option === 'string' ? option : legacySelector;\n    if (selector) {\n      elements = Array.from(parent.querySelectorAll(selector));\n    }\n  }\n\n  return elements;\n}\n\n/**\n * Check if variable is PhotoSwipe class\n *\n * @param {any} fn\n * @returns {boolean}\n */\nexport function isPswpClass(fn) {\n  return typeof fn === 'function'\n    && fn.prototype\n    && fn.prototype.goTo;\n}\n\n/**\n * Check if browser is Safari\n *\n * @returns {boolean}\n */\nexport function isSafari() {\n  return !!(navigator.vendor && navigator.vendor.match(/apple/i));\n}\n\n"
  },
  {
    "path": "src/js/util/viewport-size.js",
    "content": "/** @typedef {import('../photoswipe.js').PhotoSwipeOptions} PhotoSwipeOptions */\n/** @typedef {import('../core/base.js').default} PhotoSwipeBase */\n/** @typedef {import('../photoswipe.js').Point} Point */\n/** @typedef {import('../slide/slide.js').SlideData} SlideData */\n\n/**\n * @param {PhotoSwipeOptions} options\n * @param {PhotoSwipeBase} pswp\n * @returns {Point}\n */\nexport function getViewportSize(options, pswp) {\n  if (options.getViewportSizeFn) {\n    const newViewportSize = options.getViewportSizeFn(options, pswp);\n    if (newViewportSize) {\n      return newViewportSize;\n    }\n  }\n\n  return {\n    x: document.documentElement.clientWidth,\n\n    // TODO: height on mobile is very incosistent due to toolbar\n    // find a way to improve this\n    //\n    // document.documentElement.clientHeight - doesn't seem to work well\n    y: window.innerHeight\n  };\n}\n\n/**\n * Parses padding option.\n * Supported formats:\n *\n * // Object\n * padding: {\n *  top: 0,\n *  bottom: 0,\n *  left: 0,\n *  right: 0\n * }\n *\n * // A function that returns the object\n * paddingFn: (viewportSize, itemData, index) => {\n *  return {\n *    top: 0,\n *    bottom: 0,\n *    left: 0,\n *    right: 0\n *  };\n * }\n *\n * // Legacy variant\n * paddingLeft: 0,\n * paddingRight: 0,\n * paddingTop: 0,\n * paddingBottom: 0,\n *\n * @param {'left' | 'top' | 'bottom' | 'right'} prop\n * @param {PhotoSwipeOptions} options PhotoSwipe options\n * @param {Point} viewportSize PhotoSwipe viewport size, for example: { x:800, y:600 }\n * @param {SlideData} itemData Data about the slide\n * @param {number} index Slide index\n * @returns {number}\n */\nexport function parsePaddingOption(prop, options, viewportSize, itemData, index) {\n  let paddingValue = 0;\n\n  if (options.paddingFn) {\n    paddingValue = options.paddingFn(viewportSize, itemData, index)[prop];\n  } else if (options.padding) {\n    paddingValue = options.padding[prop];\n  } else {\n    const legacyPropName = 'padding' + prop[0].toUpperCase() + prop.slice(1);\n    // @ts-expect-error\n    if (options[legacyPropName]) {\n      // @ts-expect-error\n      paddingValue = options[legacyPropName];\n    }\n  }\n\n  return Number(paddingValue) || 0;\n}\n\n/**\n * @param {PhotoSwipeOptions} options\n * @param {Point} viewportSize\n * @param {SlideData} itemData\n * @param {number} index\n * @returns {Point}\n */\nexport function getPanAreaSize(options, viewportSize, itemData, index) {\n  return {\n    x: viewportSize.x\n      - parsePaddingOption('left', options, viewportSize, itemData, index)\n      - parsePaddingOption('right', options, viewportSize, itemData, index),\n    y: viewportSize.y\n      - parsePaddingOption('top', options, viewportSize, itemData, index)\n      - parsePaddingOption('bottom', options, viewportSize, itemData, index)\n  };\n}\n"
  },
  {
    "path": "src/photoswipe.css",
    "content": "/*! PhotoSwipe main CSS by Dmytro Semenov | photoswipe.com */\n\n.pswp {\n  --pswp-bg: #000;\n  --pswp-placeholder-bg: #222;\n  \n\n  --pswp-root-z-index: 100000;\n  \n  --pswp-preloader-color: rgba(79, 79, 79, 0.4);\n  --pswp-preloader-color-secondary: rgba(255, 255, 255, 0.9);\n  \n  /* defined via js:\n  --pswp-transition-duration: 333ms; */\n  \n  --pswp-icon-color: #fff;\n  --pswp-icon-color-secondary: #4f4f4f;\n  --pswp-icon-stroke-color: #4f4f4f;\n  --pswp-icon-stroke-width: 2px;\n\n  --pswp-error-text-color: var(--pswp-icon-color);\n}\n\n\n/*\n\tStyles for basic PhotoSwipe (pswp) functionality (sliding area, open/close transitions)\n*/\n\n.pswp {\n\tposition: fixed;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n\tz-index: var(--pswp-root-z-index);\n\tdisplay: none;\n\ttouch-action: none;\n\toutline: 0;\n\topacity: 0.003;\n\tcontain: layout style size;\n\t-webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\n/* Prevents focus outline on the root element,\n  (it may be focused initially) */\n.pswp:focus {\n  outline: 0;\n}\n\n.pswp * {\n  box-sizing: border-box;\n}\n\n.pswp img {\n  max-width: none;\n}\n\n.pswp--open {\n\tdisplay: block;\n}\n\n.pswp,\n.pswp__bg {\n\ttransform: translateZ(0);\n\twill-change: opacity;\n}\n\n.pswp__bg {\n  opacity: 0.005;\n\tbackground: var(--pswp-bg);\n}\n\n.pswp,\n.pswp__scroll-wrap {\n\toverflow: hidden;\n}\n\n.pswp__scroll-wrap,\n.pswp__bg,\n.pswp__container,\n.pswp__item,\n.pswp__content,\n.pswp__img,\n.pswp__zoom-wrap {\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\twidth: 100%;\n\theight: 100%;\n}\n\n.pswp__img,\n.pswp__zoom-wrap {\n\twidth: auto;\n\theight: auto;\n}\n\n.pswp--click-to-zoom.pswp--zoom-allowed .pswp__img {\n\tcursor: -webkit-zoom-in;\n\tcursor: -moz-zoom-in;\n\tcursor: zoom-in;\n}\n\n.pswp--click-to-zoom.pswp--zoomed-in .pswp__img {\n\tcursor: move;\n\tcursor: -webkit-grab;\n\tcursor: -moz-grab;\n\tcursor: grab;\n}\n\n.pswp--click-to-zoom.pswp--zoomed-in .pswp__img:active {\n  cursor: -webkit-grabbing;\n  cursor: -moz-grabbing;\n  cursor: grabbing;\n}\n\n/* :active to override grabbing cursor */\n.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img,\n.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img:active,\n.pswp__img {\n\tcursor: -webkit-zoom-out;\n\tcursor: -moz-zoom-out;\n\tcursor: zoom-out;\n}\n\n\n/* Prevent selection and tap highlights */\n.pswp__container,\n.pswp__img,\n.pswp__button,\n.pswp__counter {\n\t-webkit-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n}\n\n.pswp__item {\n\t/* z-index for fade transition */\n\tz-index: 1;\n\toverflow: hidden;\n}\n\n.pswp__hidden {\n\tdisplay: none !important;\n}\n\n/* Allow to click through pswp__content element, but not its children */\n.pswp__content {\n  pointer-events: none;\n}\n.pswp__content > * {\n  pointer-events: auto;\n}\n\n\n/*\n\n  PhotoSwipe UI\n\n*/\n\n/*\n\tError message appears when image is not loaded\n\t(JS option errorMsg controls markup)\n*/\n.pswp__error-msg-container {\n  display: grid;\n}\n.pswp__error-msg {\n\tmargin: auto;\n\tfont-size: 1em;\n\tline-height: 1;\n\tcolor: var(--pswp-error-text-color);\n}\n\n/*\nclass pswp__hide-on-close is applied to elements that\nshould hide (for example fade out) when PhotoSwipe is closed\nand show (for example fade in) when PhotoSwipe is opened\n */\n.pswp .pswp__hide-on-close {\n\topacity: 0.005;\n\twill-change: opacity;\n\ttransition: opacity var(--pswp-transition-duration) cubic-bezier(0.4, 0, 0.22, 1);\n\tz-index: 10; /* always overlap slide content */\n\tpointer-events: none; /* hidden elements should not be clickable */\n}\n\n/* class pswp--ui-visible is added when opening or closing transition starts */\n.pswp--ui-visible .pswp__hide-on-close {\n\topacity: 1;\n\tpointer-events: auto;\n}\n\n/* <button> styles, including css reset */\n.pswp__button {\n\tposition: relative;\n\tdisplay: block;\n\twidth: 50px;\n\theight: 60px;\n\tpadding: 0;\n\tmargin: 0;\n\toverflow: hidden;\n\tcursor: pointer;\n\tbackground: none;\n\tborder: 0;\n\tbox-shadow: none;\n\topacity: 0.85;\n\t-webkit-appearance: none;\n\t-webkit-touch-callout: none;\n}\n\n.pswp__button:hover,\n.pswp__button:active,\n.pswp__button:focus {\n  transition: none;\n  padding: 0;\n  background: none;\n  border: 0;\n  box-shadow: none;\n  opacity: 1;\n}\n\n.pswp__button:disabled {\n  opacity: 0.3;\n  cursor: auto;\n}\n\n.pswp__icn {\n  fill: var(--pswp-icon-color);\n  color: var(--pswp-icon-color-secondary);\n}\n\n.pswp__icn {\n  position: absolute;\n  top: 14px;\n  left: 9px;\n  width: 32px;\n  height: 32px;\n  overflow: hidden;\n  pointer-events: none;\n}\n\n.pswp__icn-shadow {\n  stroke: var(--pswp-icon-stroke-color);\n  stroke-width: var(--pswp-icon-stroke-width);\n  fill: none;\n}\n\n.pswp__icn:focus {\n\toutline: 0;\n}\n\n/*\n\tdiv element that matches size of large image,\n\tlarge image loads on top of it,\n\tused when msrc is not provided\n*/\ndiv.pswp__img--placeholder,\n.pswp__img--with-bg {\n\tbackground: var(--pswp-placeholder-bg);\n}\n\n.pswp__top-bar {\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\twidth: 100%;\n\theight: 60px;\n\tdisplay: flex;\n  flex-direction: row;\n  justify-content: flex-end;\n\tz-index: 10;\n\n\t/* allow events to pass through top bar itself */\n\tpointer-events: none !important;\n}\n.pswp__top-bar > * {\n  pointer-events: auto;\n  /* this makes transition significantly more smooth,\n     even though inner elements are not animated */\n  will-change: opacity;\n}\n\n\n/*\n\n  Close button\n\n*/\n.pswp__button--close {\n  margin-right: 6px;\n}\n\n\n/*\n\n  Arrow buttons\n\n*/\n.pswp__button--arrow {\n  position: absolute;\n  top: 0;\n  width: 75px;\n  height: 100px;\n  top: 50%;\n  margin-top: -50px;\n}\n\n.pswp__button--arrow:disabled {\n  display: none;\n  cursor: default;\n}\n\n.pswp__button--arrow .pswp__icn {\n  top: 50%;\n  margin-top: -30px;\n  width: 60px;\n  height: 60px;\n  background: none;\n  border-radius: 0;\n}\n\n.pswp--one-slide .pswp__button--arrow {\n  display: none;\n}\n\n/* hide arrows on touch screens */\n.pswp--touch .pswp__button--arrow {\n  visibility: hidden;\n}\n\n/* show arrows only after mouse was used */\n.pswp--has_mouse .pswp__button--arrow {\n  visibility: visible;\n}\n\n.pswp__button--arrow--prev {\n  right: auto;\n  left: 0px;\n}\n\n.pswp__button--arrow--next {\n  right: 0px;\n}\n.pswp__button--arrow--next .pswp__icn {\n  left: auto;\n  right: 14px;\n  /* flip horizontally */\n  transform: scale(-1, 1);\n}\n\n/*\n\n  Zoom button\n\n*/\n.pswp__button--zoom {\n  display: none;\n}\n\n.pswp--zoom-allowed .pswp__button--zoom {\n  display: block;\n}\n\n/* \"+\" => \"-\" */\n.pswp--zoomed-in .pswp__zoom-icn-bar-v {\n  display: none;\n}\n\n\n/*\n\n  Loading indicator\n\n*/\n.pswp__preloader {\n  position: relative;\n  overflow: hidden;\n  width: 50px;\n  height: 60px;\n  margin-right: auto;\n}\n\n.pswp__preloader .pswp__icn {\n  opacity: 0;\n  transition: opacity 0.2s linear;\n  animation: pswp-clockwise 600ms linear infinite;\n}\n\n.pswp__preloader--active .pswp__icn {\n  opacity: 0.85;\n}\n\n@keyframes pswp-clockwise {\n  0% { transform: rotate(0deg); }\n  100% { transform: rotate(360deg); }\n}\n\n\n/*\n\n  \"1 of 10\" counter\n\n*/\n.pswp__counter {\n  height: 30px;\n  margin-top: 15px;\n  margin-inline-start: 20px;\n  font-size: 14px;\n  line-height: 30px;\n  color: var(--pswp-icon-color);\n  text-shadow: 1px 1px 3px var(--pswp-icon-color-secondary);\n  opacity: 0.85;\n}\n\n.pswp--one-slide .pswp__counter {\n  display: none;\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"dist/types\",\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"strict\": true,\n    \"strictNullChecks\": true,\n    \"skipLibCheck\": true,\n    \"sourceMap\": true,\n    \"allowJs\": true,\n    \"checkJs\": true,\n  },\n  \"include\": [\n    \"global.d.ts\",\n    \"src/js/**/*\"\n  ],\n}\n"
  }
]